移动端导航进度条

进度条Progress

把一个过程的完成度可视化(上传、审核、额度耗尽)

反馈 规格 26 · 进度条 Progress 6 端实现 触摸优先

引入(H5 原生;其余 5 端见「快速开始」)
<!-- ① 令牌:PC 令牌 + 移动端 --kole-m-* 合成单文件,引一次 -->
<link rel="stylesheet" href="kole-ui/mobile/tokens.css">

<!-- ② 本组件样式(全量则用 kole-ui/mobile/components/index.css) -->
<link rel="stylesheet" href="kole-ui/mobile/components/mobile-progress.css">

<!-- ③ 结构照抄下方任一演示块(类名与 6 端实现一致) -->

演示

每个演示都是真实渲染:预览帧加载 frameworks-mobile/Progress.html?demo=<id>(只显示该演示块),代码是该演示块在演示页里的原文,可复制。全部演示同屏可看 演示页 ↗。

01 组件类型

线形type=line / labelPlacement=right

type=line + labelPlacement=right:文案固定在条右侧,数字用等宽数字避免跳动。

查看代码(演示页原文 · 19 行)
frameworks-mobile/Progress.html · line
<section class="demo-block" data-demo="line">
  <p class="demo-label">线形(type=line,labelPlacement=right:文案在条右侧)</p>
  <div class="demo-box">
    <div class="kole-m-progress kole-m-progress--line kole-m-progress--right" style="--kole-m-progress-percent: 65"
         role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="65" data-assert="progress-line-right">
      <span class="kole-m-progress__track">
        <span class="kole-m-progress__bar" style="width: 65%"></span>
      </span>
      <span class="kole-m-progress__label" aria-hidden="true">65%</span>
    </div>
    <div class="kole-m-progress kole-m-progress--line kole-m-progress--right" style="--kole-m-progress-percent: 30"
         role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="30">
      <span class="kole-m-progress__track">
        <span class="kole-m-progress__bar" style="width: 30%"></span>
      </span>
      <span class="kole-m-progress__label" aria-hidden="true">30%</span>
    </div>
  </div>
</section>
文案在条内labelPlacement=inside

labelPlacement=inside 时条体加高到 20px 以容纳文字,适合窄屏与卡片内。

查看代码(演示页原文 · 11 行)
frameworks-mobile/Progress.html · inside
<section class="demo-block" data-demo="inside">
  <p class="demo-label">文案在条内(labelPlacement=inside:条体加高到 20px,文案落在条体内部而非压在灰轨上)</p>
  <div class="demo-box">
    <div class="kole-m-progress kole-m-progress--line kole-m-progress--inside" style="--kole-m-progress-percent: 48"
         role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="48" data-assert="progress-inside">
      <span class="kole-m-progress__track">
        <span class="kole-m-progress__bar" style="width: 48%"><span class="kole-m-progress__label" aria-hidden="true">48%</span></span>
      </span>
    </div>
  </div>
</section>
环形type=circle / labelPlacement=center

type=circle + labelPlacement=center:环心挖空与所在容器同底,文案在环心。

查看代码(演示页原文 · 19 行)
frameworks-mobile/Progress.html · circle
<section class="demo-block" data-demo="circle">
  <p class="demo-label">环形(type=circle + labelPlacement=center:文案在环心)</p>
  <div class="demo-box demo-box--center">
    <div class="demo-row">
      <div class="kole-m-progress kole-m-progress--circle" style="--kole-m-progress-percent: 72"
           role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="72" data-assert="progress-circle">
        <span class="kole-m-progress__ring">
          <span class="kole-m-progress__hole" aria-hidden="true">72%</span>
        </span>
      </div>
      <div class="kole-m-progress kole-m-progress--circle" style="--kole-m-progress-percent: 24"
           role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="24" aria-label="下载进度">
        <span class="kole-m-progress__ring">
          <span class="kole-m-progress__hole" aria-hidden="true">24%</span>
        </span>
      </div>
    </div>
  </div>
</section>

02 组件状态

三种状态status=normal|success|error

进行中 / 成功 / 失败:色相不同,且都带文案与状态图标,不靠颜色单独表意。

查看代码(演示页原文 · 31 行)
frameworks-mobile/Progress.html · status
<section class="demo-block" data-demo="status">
  <p class="demo-label">三种状态(正在上传 / 上传成功 / 上传失败:色相不同,且都带文案)</p>
  <div class="demo-box">
    <div class="kole-m-progress kole-m-progress--line kole-m-progress--right" style="--kole-m-progress-percent: 42"
         role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="42" aria-valuetext="上传中 42%"
         data-assert="progress-status-normal">
      <span class="kole-m-progress__track">
        <span class="kole-m-progress__bar" style="width: 42%"></span>
      </span>
      <span class="kole-m-progress__label" aria-hidden="true">上传中 42%</span>
    </div>
    <div class="kole-m-progress kole-m-progress--line kole-m-progress--right kole-m-progress--success" style="--kole-m-progress-percent: 100"
         role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="100" aria-valuetext="已完成 100%"
         data-assert="progress-status-success">
      <span class="kole-m-progress__track">
        <span class="kole-m-progress__bar" style="width: 100%"></span>
      </span>
      <span class="kole-m-progress__label" aria-hidden="true">已完成</span>
      <span class="kole-m-progress__status" aria-hidden="true">✓</span>
    </div>
    <div class="kole-m-progress kole-m-progress--line kole-m-progress--right kole-m-progress--error" style="--kole-m-progress-percent: 58"
         role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="58" aria-valuetext="上传失败 58%"
         data-assert="progress-status-error">
      <span class="kole-m-progress__track">
        <span class="kole-m-progress__bar" style="width: 58%"></span>
      </span>
      <span class="kole-m-progress__label" aria-hidden="true">上传失败</span>
      <span class="kole-m-progress__status" aria-hidden="true">!</span>
    </div>
  </div>
</section>
暂停paused=true

状态 paused:条体降透明度、进度保留,用户能看出「停住了」而不是「没开始」。

查看代码(演示页原文 · 13 行)
frameworks-mobile/Progress.html · paused
<section class="demo-block" data-demo="paused">
  <p class="demo-label">暂停(状态 paused:条体降透明度,进度保留)</p>
  <div class="demo-box">
    <div class="kole-m-progress kole-m-progress--line kole-m-progress--right is-paused" style="--kole-m-progress-percent: 55"
         role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="55" aria-valuetext="已暂停 55%"
         data-assert="progress-paused">
      <span class="kole-m-progress__track">
        <span class="kole-m-progress__bar" style="width: 55%"></span>
      </span>
      <span class="kole-m-progress__label" aria-hidden="true">已暂停 55%</span>
    </div>
  </div>
</section>
进度不可交互无事件

进度条本身不接收点击;需要打断时由宿主在旁边放按钮(演示里用按钮把进度推到 100%)。

查看代码(演示页原文 · 17 行)
frameworks-mobile/Progress.html · clickable
<section class="demo-block" data-demo="clickable">
  <p class="demo-label">进度不动(进度条本身不可交互:需要取消时旁边放按钮,点按钮推进演示进度)</p>
  <div class="demo-box">
    <div class="kole-m-progress kole-m-progress--line kole-m-progress--right" id="progress-live" style="--kole-m-progress-percent: 20"
         role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="20" data-assert="progress-live">
      <span class="kole-m-progress__track">
        <span class="kole-m-progress__bar" id="progress-bar" style="width: 20%"></span>
      </span>
      <span class="kole-m-progress__label" aria-hidden="true">20%</span>
    </div>
    <div class="demo-row">
      <button class="demo-btn" type="button" id="progress-btn"
              data-behavior="click-sets-attr:#progress-live|aria-valuenow|100">推进到 100%</button>
      <span class="demo-hint">真实业务里由上传进度驱动</span>
    </div>
  </div>
</section>

API

props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs

Props

名称类型默认值说明必传
percentagenumber0完成度 0~100,超出范围按边界收敛(规格 §26.2 bar)N
type'line' | 'circle''line'变体 type:线形 / 环形(规格 §26.3)N
status'normal' | 'success' | 'error''normal'变体 status:进行中 / 成功 / 失败,决定条体色相(规格 §26.3)N
labelPlacement'inside' | 'right' | 'center''right'变体 labelPlacement:文案位置;center 仅对环形有效(规格 §26.3)N
labelstring''自定义文案,缺省显示「percentage%」(规格 §26.2 label)N
pausedbooleanfalse状态 paused:暂停,条体降透明度且进度保留(规格 §26.4)N

「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。

事件

名称参数说明
——无事件;进度条不可交互(规格 §26.5)

插槽

名称说明
default追加在进度条之后的自定义内容(如「取消」按钮)(规格 §26.2 status)

CSS 变量

组件级变量(在组件样式表里定义)。业务侧可在自己的作用域内覆盖,不必改组件源码。

名称默认值说明
--kole-m-progress-height6px线形条体高度
--kole-m-progress-size88px环形直径
--kole-m-progress-stroke6px环形线宽
--kole-m-progress-duration240ms数值过渡时长
--kole-m-progress-percent0当前进度(0~100),由宿主按 percentage 写入
--kole-m-progress-hole-bgvar(--kole-color-card-bg)环心挖空底色,需与所在容器同底

何时使用

  • 把一个过程的完成度可视化(上传、审核、额度耗尽)
  • 移动端的进度多数伴随文字出现,因此百分比文案与状态色是标配而非可选
  • 进度变化要能被读屏播报而不只是画出来
  • 进度条本身不可交互、不接收点击;需要取消时由宿主在旁边放按钮
  • 数值变化用 CSS 宽度过渡(240ms),不做无限循环动画(省电且不干扰读屏)

交互与触控

  • 进度条本身不可交互、不接收点击;需要取消时由宿主在旁边放按钮
  • 数值变化用 CSS 宽度过渡(240ms),不做无限循环动画(省电且不干扰读屏)
  • 无动画偏好(prefers-reduced-motion)下直接跳到目标宽度

无障碍

  • 根节点 role="progressbar" + aria-valuemin="0" / aria-valuemax="100" / aria-valuenow
  • 文案节点 aria-hidden="true",避免与 aria-valuenow 重复播报
  • 不确定进度(无法给出百分比)用 aria-valuetext="进行中" 表达

从「该用哪一个」的角度区分;PC 端的对应实现见 PC 文档站。

组件何时用它而不是本组件
步骤条Steps过程有明确的阶段(提交 → 审核 → 完成)用步骤条;只有完成度没有阶段时用进度条
下拉刷新PullRefresh等待短任务完成用下拉刷新的加载态;长过程需要量化完成度时用进度条
轻提示Toast过程瞬间完成只需一句轻提示;过程持续可见才需要进度条

规格未定 / 禁止发明

类别条目
禁止发明环形进度的线宽与半径的自适应规则(由 size 决定,不做响应式推导)
禁止发明进度到达 100% 后的自动隐藏时机
规格未定百分比是否四舍五入到整数
规格未定环形进度是否支持渐变描边

结构(anatomy)

字段说明
progress根元素,承载轨道与文案
track轨道,未完成部分的底色
bar已完成部分,宽度由 percentage 决定
ring环形进度的圆环轨道(type=circle 时替代 track/bar)
label百分比文案,可置于条内、条右侧或环心
status状态图标位(成功 / 失败),非进行中时显示

变体维度与类名映射

类名映射由构建脚本从契约 variantClasses 生成,并被 verify:mobile-docs 逐条对照组件 CSS 校验(类/变量必须真实存在)。

维度取值对应类名 / 变量
typeline / circle
line .kole-m-progress--line
circle .kole-m-progress--circle
statusnormal / success / error
normal (由数据驱动,无专属类)
success .kole-m-progress--success
error .kole-m-progress--error
labelPlacementinside / right / center
inside .kole-m-progress--inside
right .kole-m-progress--right
center .kole-m-progress__hole --kole-m-progress-percent

代表变体

变体标签
type=line · status=normal · labelPlacement=right线形进行中(文案在条右侧)
type=line · status=success · labelPlacement=inside线形成功(文案压在条内)
type=circle · status=normal · labelPlacement=center环形进行中(文案在环心)
type=line · status=error · labelPlacement=right线形失败(进度停在断点转错误色)

用到的令牌

构建时从本组件样式表扫描得出。蓝色为移动端自有令牌,绿色为继承的 PC 令牌(改一处两端生效)。

--kole-m-font-size-caption --kole-m-font-size-label --kole-color-brand --kole-color-card-bg --kole-color-disabled-bg --kole-color-error --kole-color-success --kole-color-text-body --kole-color-text-inverse --kole-ease-out --kole-font-family --kole-space-12 --kole-space-4 --kole-m-progress-duration --kole-m-progress-height --kole-m-progress-hole-bg --kole-m-progress-percent --kole-m-progress-size --kole-m-progress-stroke

6 端源码

同一组件的六份实现(生产环境的类名与结构一致,差异只在技术栈写法与单位)。点开查看,右侧可复制。

frameworks-mobile/Progress.css · 纯样式(CSS) · 134 行
frameworks-mobile/Progress.css
/* Kole UI Mobile · Progress 样式 — 对齐移动端规格 §26
   进度条:线形 / 环形两形态,状态色三档(normal / success / error);
   条体只用宽度过渡(240ms),不做无限循环动画;减少动态偏好下直接跳变。 */

.kole-m-progress {
  --kole-m-progress-height: 6px;      /* 线形条体高度 */
  --kole-m-progress-size: 88px;       /* 环形直径 */
  --kole-m-progress-stroke: 6px;      /* 环形线宽 */
  --kole-m-progress-duration: 240ms;  /* 数值过渡时长 */
  --kole-m-progress-percent: 0;       /* 当前进度(0~100),由宿主按 percentage 写入 */
  --kole-m-progress-hole-bg: var(--kole-color-card-bg); /* 环心挖空底色,需与所在容器同底 */
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: var(--kole-space-12);
  width: 100%;
  font-family: var(--kole-font-family);
  color: var(--kole-color-text-body);
  font-size: var(--kole-m-font-size-label);
}

/* 变体 type=line:轨道 + 条体 */
.kole-m-progress--line { flex-direction: row; }

.kole-m-progress__track {
  position: relative;
  flex: 1 1 auto;
  box-sizing: border-box;
  overflow: hidden;
  height: var(--kole-m-progress-height);
  border-radius: calc(var(--kole-m-progress-height) / 2);
  background: var(--kole-color-disabled-bg);
}

.kole-m-progress__bar {
  display: block;
  box-sizing: border-box;
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: var(--kole-color-brand);
  transition: width var(--kole-m-progress-duration) var(--kole-ease-out);
}

/* 变体 labelPlacement=inside:文案压在条体内(条体加高到 20px 以容纳文字)。
   文案节点放在**条体内部**:条体是不透明品牌底,反色字对比度 5.85:1 达标;
   若把反色字直接压在灰色轨道上就是真实的对比度缺陷(1.1:1)。
   代价是条体过窄时文案会被裁掉 —— 所以 inside 只适合进度已经不小、或文案较短的场合。 */
.kole-m-progress--inside { --kole-m-progress-height: 20px; }

.kole-m-progress--inside .kole-m-progress__bar {
  display: flex;
  align-items: center;
  justify-content: center;
}

.kole-m-progress--inside .kole-m-progress__label {
  margin: 0 var(--kole-space-4);
  overflow: hidden;
  white-space: nowrap;
  color: var(--kole-color-text-inverse);
  font-size: var(--kole-m-font-size-caption);
  line-height: 1;
}

/* 变体 labelPlacement=right:文案固定在条的右侧 */
.kole-m-progress--right .kole-m-progress__label {
  flex: 0 0 auto;
  min-width: 36px;
  text-align: end;
  font-variant-numeric: tabular-nums;
}

/* 变体 type=circle:环形用 conic-gradient 画(零依赖,不用 SVG) */
.kole-m-progress--circle {
  display: inline-flex;
  justify-content: center;
  width: auto;
}

.kole-m-progress__ring {
  position: relative;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--kole-m-progress-size);
  height: var(--kole-m-progress-size);
  border-radius: 50%;
  background: conic-gradient(var(--kole-color-brand) calc(var(--kole-m-progress-percent) * 1%), var(--kole-color-disabled-bg) 0);
  transition: background var(--kole-m-progress-duration) var(--kole-ease-out);
}

/* 环心挖空:与所在容器同底,形成圆环(不引 SVG,也不用 mask 兼容性赌注) */
.kole-m-progress__hole {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: calc(var(--kole-m-progress-size) - var(--kole-m-progress-stroke) * 2);
  height: calc(var(--kole-m-progress-size) - var(--kole-m-progress-stroke) * 2);
  border-radius: 50%;
  background: var(--kole-m-progress-hole-bg);
  color: var(--kole-color-text-body);
  font-size: var(--kole-m-font-size-label);
  font-variant-numeric: tabular-nums;
}

/* 状态 normal:进行中(品牌色,默认)
   状态 paused:暂停,条体降透明度(进度保留) */
.kole-m-progress.is-paused .kole-m-progress__bar,
.kole-m-progress.is-paused .kole-m-progress__ring { opacity: 0.5; }

/* 状态 success:完成 / 成功色 */
.kole-m-progress.kole-m-progress--success .kole-m-progress__bar { background: var(--kole-color-success); }
.kole-m-progress.kole-m-progress--success .kole-m-progress__ring {
  background: conic-gradient(var(--kole-color-success) calc(var(--kole-m-progress-percent) * 1%), var(--kole-color-disabled-bg) 0);
}

/* 状态 error:失败,进度停在断点并转错误色 */
.kole-m-progress.kole-m-progress--error .kole-m-progress__bar { background: var(--kole-color-error); }
.kole-m-progress.kole-m-progress--error .kole-m-progress__ring {
  background: conic-gradient(var(--kole-color-error) calc(var(--kole-m-progress-percent) * 1%), var(--kole-color-disabled-bg) 0);
}

/* 状态图标位:非进行中时显示(成功 / 失败) */
.kole-m-progress__status { flex: 0 0 auto; display: inline-flex; }

/* 减少动态偏好:不做过渡,直接跳到目标宽度 */
@media (prefers-reduced-motion: reduce) {
  .kole-m-progress__bar,
  .kole-m-progress__ring { transition: none; }
}
frameworks-mobile/Progress.html · H5 原生(无框架) · 185 行
frameworks-mobile/Progress.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>Kole UI Mobile · Progress(H5)</title>
<link rel="stylesheet" href="../.design_library/kole-ui-mobile/colors_and_type.css">
<link rel="stylesheet" href="Progress.css">
<style>
  body { margin: 0; background: var(--kole-color-page-bg); font-family: var(--kole-font-family); color: var(--kole-color-text-body); }
  .demo { max-width: 375px; margin: 0 auto; padding: var(--kole-m-gutter) 0; }
  .demo-label { margin: 0; padding: var(--kole-space-12) var(--kole-m-gutter) var(--kole-space-8);
    font-size: var(--kole-m-font-size-label); color: var(--kole-color-text-secondary); }
  .demo-box { display: flex; flex-direction: column; gap: var(--kole-space-24); padding: var(--kole-space-24) var(--kole-m-gutter);
    background: var(--kole-color-card-bg); border-block: 1px solid var(--kole-color-border); }
  .demo-box--center { align-items: center; }
  .demo-row { display: flex; align-items: center; gap: var(--kole-space-12); }
  /* 演示页专用按钮(不属于 Progress 组件;组件本身不可交互) */
  .demo-btn { min-height: var(--kole-m-touch-target); padding: 0 var(--kole-m-gutter);
    border: 1px solid var(--kole-color-border); border-radius: var(--kole-radius-base);
    background: var(--kole-color-card-bg); color: var(--kole-color-text-body);
    font-family: inherit; font-size: var(--kole-m-font-size-label); cursor: pointer; touch-action: manipulation; }
  .demo-btn:focus-visible { outline: 2px solid var(--kole-color-focus-ring); outline-offset: 2px; }
  .demo-hint { font-size: var(--kole-m-font-size-caption); color: var(--kole-color-text-secondary); }
  .demo-block[hidden] { display: none; }
</style>
</head>
<body>
<div class="demo">
  <section class="demo-block" data-demo="line">
    <p class="demo-label">线形(type=line,labelPlacement=right:文案在条右侧)</p>
    <div class="demo-box">
      <div class="kole-m-progress kole-m-progress--line kole-m-progress--right" style="--kole-m-progress-percent: 65"
           role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="65" data-assert="progress-line-right">
        <span class="kole-m-progress__track">
          <span class="kole-m-progress__bar" style="width: 65%"></span>
        </span>
        <span class="kole-m-progress__label" aria-hidden="true">65%</span>
      </div>
      <div class="kole-m-progress kole-m-progress--line kole-m-progress--right" style="--kole-m-progress-percent: 30"
           role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="30">
        <span class="kole-m-progress__track">
          <span class="kole-m-progress__bar" style="width: 30%"></span>
        </span>
        <span class="kole-m-progress__label" aria-hidden="true">30%</span>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="inside">
    <p class="demo-label">文案在条内(labelPlacement=inside:条体加高到 20px,文案落在条体内部而非压在灰轨上)</p>
    <div class="demo-box">
      <div class="kole-m-progress kole-m-progress--line kole-m-progress--inside" style="--kole-m-progress-percent: 48"
           role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="48" data-assert="progress-inside">
        <span class="kole-m-progress__track">
          <span class="kole-m-progress__bar" style="width: 48%"><span class="kole-m-progress__label" aria-hidden="true">48%</span></span>
        </span>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="status">
    <p class="demo-label">三种状态(正在上传 / 上传成功 / 上传失败:色相不同,且都带文案)</p>
    <div class="demo-box">
      <div class="kole-m-progress kole-m-progress--line kole-m-progress--right" style="--kole-m-progress-percent: 42"
           role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="42" aria-valuetext="上传中 42%"
           data-assert="progress-status-normal">
        <span class="kole-m-progress__track">
          <span class="kole-m-progress__bar" style="width: 42%"></span>
        </span>
        <span class="kole-m-progress__label" aria-hidden="true">上传中 42%</span>
      </div>
      <div class="kole-m-progress kole-m-progress--line kole-m-progress--right kole-m-progress--success" style="--kole-m-progress-percent: 100"
           role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="100" aria-valuetext="已完成 100%"
           data-assert="progress-status-success">
        <span class="kole-m-progress__track">
          <span class="kole-m-progress__bar" style="width: 100%"></span>
        </span>
        <span class="kole-m-progress__label" aria-hidden="true">已完成</span>
        <span class="kole-m-progress__status" aria-hidden="true">✓</span>
      </div>
      <div class="kole-m-progress kole-m-progress--line kole-m-progress--right kole-m-progress--error" style="--kole-m-progress-percent: 58"
           role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="58" aria-valuetext="上传失败 58%"
           data-assert="progress-status-error">
        <span class="kole-m-progress__track">
          <span class="kole-m-progress__bar" style="width: 58%"></span>
        </span>
        <span class="kole-m-progress__label" aria-hidden="true">上传失败</span>
        <span class="kole-m-progress__status" aria-hidden="true">!</span>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="circle">
    <p class="demo-label">环形(type=circle + labelPlacement=center:文案在环心)</p>
    <div class="demo-box demo-box--center">
      <div class="demo-row">
        <div class="kole-m-progress kole-m-progress--circle" style="--kole-m-progress-percent: 72"
             role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="72" data-assert="progress-circle">
          <span class="kole-m-progress__ring">
            <span class="kole-m-progress__hole" aria-hidden="true">72%</span>
          </span>
        </div>
        <div class="kole-m-progress kole-m-progress--circle" style="--kole-m-progress-percent: 24"
             role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="24" aria-label="下载进度">
          <span class="kole-m-progress__ring">
            <span class="kole-m-progress__hole" aria-hidden="true">24%</span>
          </span>
        </div>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="paused">
    <p class="demo-label">暂停(状态 paused:条体降透明度,进度保留)</p>
    <div class="demo-box">
      <div class="kole-m-progress kole-m-progress--line kole-m-progress--right is-paused" style="--kole-m-progress-percent: 55"
           role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="55" aria-valuetext="已暂停 55%"
           data-assert="progress-paused">
        <span class="kole-m-progress__track">
          <span class="kole-m-progress__bar" style="width: 55%"></span>
        </span>
        <span class="kole-m-progress__label" aria-hidden="true">已暂停 55%</span>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="clickable">
    <p class="demo-label">进度不动(进度条本身不可交互:需要取消时旁边放按钮,点按钮推进演示进度)</p>
    <div class="demo-box">
      <div class="kole-m-progress kole-m-progress--line kole-m-progress--right" id="progress-live" style="--kole-m-progress-percent: 20"
           role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="20" data-assert="progress-live">
        <span class="kole-m-progress__track">
          <span class="kole-m-progress__bar" id="progress-bar" style="width: 20%"></span>
        </span>
        <span class="kole-m-progress__label" aria-hidden="true">20%</span>
      </div>
      <div class="demo-row">
        <button class="demo-btn" type="button" id="progress-btn"
                data-behavior="click-sets-attr:#progress-live|aria-valuenow|100">推进到 100%</button>
        <span class="demo-hint">真实业务里由上传进度驱动</span>
      </div>
    </div>
  </section>
</div>
<script>
  /* 演示页脚本:把进度推到 100%(真实业务里由上传进度驱动)。
     同时改三处:条体宽、根节点 aria-valuenow、文案。 */
  (function () {
    var btn = document.getElementById('progress-btn');
    var root = document.getElementById('progress-live');
    var bar = document.getElementById('progress-bar');
    if (!btn || !root || !bar) return;
    btn.addEventListener('click', function () {
      root.style.setProperty('--kole-m-progress-percent', '100');
      root.setAttribute('aria-valuenow', '100');
      bar.style.width = '100%';
      var label = root.querySelector('.kole-m-progress__label');
      if (label) label.textContent = '100%';
    });
  })();
</script>
<script>
  /* ?demo=<id> → 只显示该演示块(文档站按块预览用;无参数时全部显示,测试与回归走无参数路径) */
  (function () {
    var id = new URLSearchParams(location.search).get('demo');
    if (!id) return;
    var blocks = Array.prototype.slice.call(document.querySelectorAll('.demo-block'));
    var hit = false;
    blocks.forEach(function (b) {
      var on = b.getAttribute('data-demo') === id;
      if (on) hit = true;
      b.hidden = !on;
    });
    if (!hit) { blocks.forEach(function (b) { b.hidden = false; }); return; }
    document.body.classList.add('demo-single');
    blocks.forEach(function (b) {
      var label = b.querySelector('.demo-label');
      if (label && !b.hidden) label.hidden = true;
    });
  })();
</script>
</body>
</html>
frameworks-mobile/Progress.jsx · React · 86 行
frameworks-mobile/Progress.jsx
import React from 'react';
import './Progress.css';

/* 进度条(移动端)— 规格 §26
   根节点 role="progressbar" + aria-valuemin/max/now;文案节点 aria-hidden(避免与 aria-valuenow 重复播报)。
   线形与环形共用 --kole-m-progress-percent:环形用 conic-gradient 画,环心挖空与所在容器同底
   (--kole-m-progress-hole-bg 可覆盖)。进度条本身不可交互(规格 §26.5)。 */
export default function Progress({
  percentage = 0,
  type = 'line',
  status = 'normal',
  labelPlacement = 'right',
  label = '',
  paused = false,
  children = null,
}) {
  const value = Math.max(0, Math.min(100, Number(percentage) || 0));
  const percentStyle = { '--kole-m-progress-percent': String(value) };

  const cls =
    'kole-m-progress' +
    ` kole-m-progress--${type}` +
    (type === 'line' ? ` kole-m-progress--${labelPlacement}` : '') +
    (status === 'success' ? ' kole-m-progress--success' : '') +
    (status === 'error' ? ' kole-m-progress--error' : '') +
    (paused ? ' is-paused' : '');

  const text = label || value + '%';

  /* 文案在条内的线形:标签落在**条体内部**(品牌底 + 反色字才达标),
     与 H5 演示页同一结构(规格 §26.3 labelPlacement=inside) */
  const inlineLabel = (
    <span className="kole-m-progress__label" aria-hidden="true">
      {text}
    </span>
  );

  const ring = (
    <span className="kole-m-progress__ring">
      <span className="kole-m-progress__hole" aria-hidden="true">
        {text}
      </span>
    </span>
  );

  return (
    <div
      className={cls}
      style={percentStyle}
      role="progressbar"
      aria-valuemin="0"
      aria-valuemax="100"
      aria-valuenow={value}
      aria-valuetext={status === 'error' ? '失败 ' + value + '%' : undefined}
    >
      {type === 'circle' ? (
        ring
      ) : (
        <>
          <span className="kole-m-progress__track">
            <span className="kole-m-progress__bar" style={{ width: value + '%' }}>
              {labelPlacement === 'inside' ? inlineLabel : null}
            </span>
          </span>
          {labelPlacement !== 'inside' ? (
            <span className="kole-m-progress__label" aria-hidden="true">
              {text}
            </span>
          ) : null}
        </>
      )}
      {status === 'success' ? (
        <span className="kole-m-progress__status" aria-hidden="true">
          ✓
        </span>
      ) : null}
      {status === 'error' ? (
        <span className="kole-m-progress__status" aria-hidden="true">
          !
        </span>
      ) : null}
      {children}
    </div>
  );
}
frameworks-mobile/Progress.vue2.vue · Vue 2 · 68 行
frameworks-mobile/Progress.vue2.vue
<template>
  <div
    :class="progressClass"
    :style="percentStyle"
    role="progressbar"
    aria-valuemin="0"
    aria-valuemax="100"
    :aria-valuenow="value"
    :aria-valuetext="status === 'error' ? '失败 ' + value + '%' : null"
  >
    <template v-if="type === 'circle'">
      <span class="kole-m-progress__ring">
        <span class="kole-m-progress__hole" aria-hidden="true">{{ text }}</span>
      </span>
    </template>

    <template v-else>
      <span class="kole-m-progress__track">
        <span class="kole-m-progress__bar" :style="{ width: value + '%' }">
          <span v-if="labelPlacement === 'inside'" class="kole-m-progress__label" aria-hidden="true">{{ text }}</span>
        </span>
      </span>
      <span v-if="labelPlacement !== 'inside'" class="kole-m-progress__label" aria-hidden="true">{{ text }}</span>
    </template>

    <span v-if="status === 'success'" class="kole-m-progress__status" aria-hidden="true">✓</span>
    <span v-if="status === 'error'" class="kole-m-progress__status" aria-hidden="true">!</span>
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: 'KoleMProgress',
  props: {
    percentage: { type: Number, default: 0 },
    type: { type: String, default: 'line' },
    status: { type: String, default: 'normal' },
    labelPlacement: { type: String, default: 'right' },
    label: { type: String, default: '' },
    paused: { type: Boolean, default: false }
  },
  computed: {
    value: function () {
      var n = Number(this.percentage) || 0;
      return Math.max(0, Math.min(100, n));
    },
    text: function () {
      return this.label || this.value + '%';
    },
    percentStyle: function () {
      return { '--kole-m-progress-percent': String(this.value) };
    },
    progressClass: function () {
      return [
        'kole-m-progress--' + this.type,
        this.type === 'line' ? 'kole-m-progress--' + this.labelPlacement : '',
        this.status === 'success' ? 'kole-m-progress--success' : '',
        this.status === 'error' ? 'kole-m-progress--error' : '',
        this.paused ? 'is-paused' : ''
      ].filter(Boolean);
    }
  }
};
</script>

<style src="./Progress.css"></style>
frameworks-mobile/Progress.vue3.vue · Vue 3 · 59 行
frameworks-mobile/Progress.vue3.vue
<template>
  <div
    :class="progressClass"
    :style="percentStyle"
    role="progressbar"
    aria-valuemin="0"
    aria-valuemax="100"
    :aria-valuenow="value"
    :aria-valuetext="status === 'error' ? '失败 ' + value + '%' : null"
  >
    <template v-if="type === 'circle'">
      <span class="kole-m-progress__ring">
        <span class="kole-m-progress__hole" aria-hidden="true">{{ text }}</span>
      </span>
    </template>

    <template v-else>
      <span class="kole-m-progress__track">
        <span class="kole-m-progress__bar" :style="{ width: value + '%' }">
          <span v-if="labelPlacement === 'inside'" class="kole-m-progress__label" aria-hidden="true">{{ text }}</span>
        </span>
      </span>
      <span v-if="labelPlacement !== 'inside'" class="kole-m-progress__label" aria-hidden="true">{{ text }}</span>
    </template>

    <span v-if="status === 'success'" class="kole-m-progress__status" aria-hidden="true">✓</span>
    <span v-if="status === 'error'" class="kole-m-progress__status" aria-hidden="true">!</span>
    <slot></slot>
  </div>
</template>

<script setup>
/* 进度条(移动端)— 规格 §26:文案节点 aria-hidden(不重复播报),进度条本身不可交互 */
import { computed } from 'vue';

const props = defineProps({
  percentage: { type: Number, default: 0 },
  type: { type: String, default: 'line' },
  status: { type: String, default: 'normal' },
  labelPlacement: { type: String, default: 'right' },
  label: { type: String, default: '' },
  paused: { type: Boolean, default: false }
});

const value = computed(() => Math.max(0, Math.min(100, Number(props.percentage) || 0)));
const text = computed(() => props.label || value.value + '%');
const percentStyle = computed(() => ({ '--kole-m-progress-percent': String(value.value) }));

const progressClass = computed(() => [
  `kole-m-progress--${props.type}`,
  props.type === 'line' ? `kole-m-progress--${props.labelPlacement}` : '',
  props.status === 'success' ? 'kole-m-progress--success' : '',
  props.status === 'error' ? 'kole-m-progress--error' : '',
  props.paused ? 'is-paused' : ''
].filter(Boolean));
</script>

<style src="./Progress.css"></style>
frameworks-mobile/Progress.uniapp.vue · uni-app(跨端:小程序 / App / H5) · 179 行
frameworks-mobile/Progress.uniapp.vue
<template>
  <view
    class="kole-m-progress"
    :class="progressClass"
    :style="percentStyle"
    role="progressbar"
    aria-valuemin="0"
    aria-valuemax="100"
    :aria-valuenow="value"
    :aria-valuetext="status === 'error' ? '失败 ' + value + '%' : ''"
  >
    <template v-if="type === 'circle'">
      <view class="kole-m-progress__ring">
        <view class="kole-m-progress__hole" aria-hidden="true">
          <text class="kole-m-progress__hole-text">{{ text }}</text>
        </view>
      </view>
    </template>

    <template v-else>
      <view class="kole-m-progress__track">
        <view class="kole-m-progress__bar" :style="{ width: value + '%' }">
          <text v-if="labelPlacement === 'inside'" class="kole-m-progress__label" aria-hidden="true">{{ text }}</text>
        </view>
      </view>
      <text v-if="labelPlacement !== 'inside'" class="kole-m-progress__label" aria-hidden="true">{{ text }}</text>
    </template>

    <text v-if="status === 'success'" class="kole-m-progress__status" aria-hidden="true">✓</text>
    <text v-if="status === 'error'" class="kole-m-progress__status" aria-hidden="true">!</text>
    <slot></slot>
  </view>
</template>

<script setup>
/* uni-app 端 · 进度条(移动端)— 规格 §26
   跨端差异:全部用 view / text;条体宽度用百分比内联样式(与 H5 端同一取值口径);
   环形用 conic-gradient 画(H5 / App 目标直接支持;微信小程序基础库 2.9.0+ 亦支持),
   环心挖空的底色用 --kole-m-progress-hole-bg 覆盖(与所在容器同底)。
   尺寸用 rpx(88rpx = 375pt 下的 44px)。 */
import { computed } from 'vue';

const props = defineProps({
  percentage: { type: Number, default: 0 },
  type: { type: String, default: 'line' },
  status: { type: String, default: 'normal' },
  labelPlacement: { type: String, default: 'right' },
  label: { type: String, default: '' },
  paused: { type: Boolean, default: false }
});

const value = computed(() => Math.max(0, Math.min(100, Number(props.percentage) || 0)));
const text = computed(() => props.label || value.value + '%');
const percentStyle = computed(() => ({ '--kole-m-progress-percent': String(value.value) }));

const progressClass = computed(() => [
  `kole-m-progress--${props.type}`,
  props.type === 'line' ? `kole-m-progress--${props.labelPlacement}` : '',
  props.status === 'success' ? 'kole-m-progress--success' : '',
  props.status === 'error' ? 'kole-m-progress--error' : '',
  props.paused ? 'is-paused' : ''
].filter(Boolean));
</script>

<style>
.kole-m-progress {
  --kole-m-progress-height: 12rpx;
  --kole-m-progress-size: 176rpx;
  --kole-m-progress-stroke: 12rpx;
  --kole-m-progress-duration: 240ms;
  --kole-m-progress-percent: 0;
  --kole-m-progress-hole-bg: var(--kole-color-card-bg);
  --kole-m-font-size-label: 28rpx;
  --kole-m-font-size-caption: 22rpx;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  width: 100%;
  color: var(--kole-color-text-body);
  font-size: var(--kole-m-font-size-label);
}

.kole-m-progress__track {
  position: relative;
  flex: 1;
  box-sizing: border-box;
  overflow: hidden;
  height: var(--kole-m-progress-height);
  border-radius: 999rpx;
  background-color: var(--kole-color-disabled-bg);
}

.kole-m-progress__bar {
  display: block;
  height: 100%;
  border-radius: 999rpx;
  background-color: var(--kole-color-brand);
  transition: width var(--kole-m-progress-duration) ease-out;
}

/* 变体 labelPlacement=inside:条体加高到 40rpx,文案落在**条体内部**
   (品牌底 + 反色字 5.85:1;压在灰色轨道上是真实缺陷)。 */
.kole-m-progress--inside { --kole-m-progress-height: 40rpx; }

.kole-m-progress--inside .kole-m-progress__bar {
  display: flex;
  align-items: center;
  justify-content: center;
}

.kole-m-progress--inside .kole-m-progress__label {
  overflow: hidden;
  white-space: nowrap;
  color: var(--kole-color-text-inverse);
  font-size: var(--kole-m-font-size-caption);
  line-height: 1;
}

.kole-m-progress--right .kole-m-progress__label {
  flex-shrink: 0;
  min-width: 72rpx;
  padding-left: 16rpx;
  text-align: right;
}

/* 变体 type=circle:环形(外圈 conic-gradient + 环心挖空) */
.kole-m-progress--circle {
  display: flex;
  justify-content: center;
  width: auto;
}

.kole-m-progress__ring {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--kole-m-progress-size);
  height: var(--kole-m-progress-size);
  border-radius: 50%;
  background: conic-gradient(var(--kole-color-brand) calc(var(--kole-m-progress-percent) * 1%), var(--kole-color-disabled-bg) 0);
  transition: background var(--kole-m-progress-duration) ease-out;
}

.kole-m-progress__hole {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: calc(var(--kole-m-progress-size) - var(--kole-m-progress-stroke) * 2);
  height: calc(var(--kole-m-progress-size) - var(--kole-m-progress-stroke) * 2);
  border-radius: 50%;
  background-color: var(--kole-m-progress-hole-bg);
}

.kole-m-progress__hole-text { color: var(--kole-color-text-body); font-size: var(--kole-m-font-size-label); }

.kole-m-progress.is-paused .kole-m-progress__bar,
.kole-m-progress.is-paused .kole-m-progress__ring { opacity: 0.5; }

.kole-m-progress.kole-m-progress--success .kole-m-progress__bar { background-color: var(--kole-color-success); }

.kole-m-progress.kole-m-progress--success .kole-m-progress__ring {
  background: conic-gradient(var(--kole-color-success) calc(var(--kole-m-progress-percent) * 1%), var(--kole-color-disabled-bg) 0);
}

.kole-m-progress.kole-m-progress--error .kole-m-progress__bar { background-color: var(--kole-color-error); }

.kole-m-progress.kole-m-progress--error .kole-m-progress__ring {
  background: conic-gradient(var(--kole-color-error) calc(var(--kole-m-progress-percent) * 1%), var(--kole-color-disabled-bg) 0);
}

.kole-m-progress__status {
  flex-shrink: 0;
  padding-left: 12rpx;
  color: var(--kole-color-text-secondary);
}
</style>

测试与回归

断言在真实的 375×640 设备帧里跑(引擎与 PC 侧共用 tests/_runtime.js,触控行为动词来自移动端 tests/mobile/_behaviors.js)。

断言 19 条 · 全部通过 报告 2026-09-20 16:50:33

复现命令
node site/dev-server.js &
REG_BASE=http://127.0.0.1:3311 node tools/run-mobile-regression.mjs   # 全量 5 个组件
npm run verify:mobile-docs                                           # 本页内容完整性 + API 与源码一致性

设计契约

components/mobile-progress.json(点击展开原始 JSON)
{
  "schemaVersion": 1,
  "sourceKind": "authored-spec",
  "provenance": "authored-in-repo",
  "specFile": "spec/移动端规格.md",
  "specSection": "26 · 进度条 Progress",
  "confidence": "high",
  "slug": "mobile-progress",
  "name": "进度条 Progress",
  "semanticTypeCandidates": [
    "progress",
    "progress-bar",
    "progress-ring"
  ],
  "variantDimensions": [
    {
      "name": "type",
      "values": [
        "line",
        "circle"
      ]
    },
    {
      "name": "status",
      "values": [
        "normal",
        "success",
        "error"
      ]
    },
    {
      "name": "labelPlacement",
      "values": [
        "inside",
        "right",
        "center"
      ]
    }
  ],
  "representativeVariants": [
    {
      "type": "line",
      "status": "normal",
      "labelPlacement": "right",
      "label": "线形进行中(文案在条右侧)"
    },
    {
      "type": "line",
      "status": "success",
      "labelPlacement": "inside",
      "label": "线形成功(文案压在条内)"
    },
    {
      "type": "circle",
      "status": "normal",
      "labelPlacement": "center",
      "label": "环形进行中(文案在环心)"
    },
    {
      "type": "line",
      "status": "error",
      "labelPlacement": "right",
      "label": "线形失败(进度停在断点转错误色)"
    }
  ],
  "anatomy": {
    "progress": "根元素,承载轨道与文案",
    "track": "轨道,未完成部分的底色",
    "bar": "已完成部分,宽度由 percentage 决定",
    "ring": "环形进度的圆环轨道(type=circle 时替代 track/bar)",
    "label": "百分比文案,可置于条内、条右侧或环心",
    "status": "状态图标位(成功 / 失败),非进行中时显示"
  },
  "structurePatterns": {
    "type": "line(线形)/ circle(环形)",
    "status": "normal(进行中,品牌色)/ success(成功)/ error(失败)",
    "labelPlacement": "inside(文案在条内)/ right(条右侧)/ center(环心,仅 circle)"
  },
  "usageHints": [
    "把一个过程的完成度可视化(上传、审核、额度耗尽)",
    "移动端的进度多数伴随文字出现,因此百分比文案与状态色是标配而非可选",
    "进度变化要能被读屏播报而不只是画出来",
    "进度条本身不可交互、不接收点击;需要取消时由宿主在旁边放按钮",
    "数值变化用 CSS 宽度过渡(240ms),不做无限循环动画(省电且不干扰读屏)"
  ],
  "doNotInvent": [
    "环形进度的线宽与半径的自适应规则(由 size 决定,不做响应式推导)",
    "进度到达 100% 后的自动隐藏时机"
  ],
  "unknowns": [
    "百分比是否四舍五入到整数",
    "环形进度是否支持渐变描边"
  ],
  "interaction": [
    "进度条本身不可交互、不接收点击;需要取消时由宿主在旁边放按钮",
    "数值变化用 CSS 宽度过渡(240ms),不做无限循环动画(省电且不干扰读屏)",
    "无动画偏好(prefers-reduced-motion)下直接跳到目标宽度"
  ],
  "accessibility": [
    "根节点 role=\"progressbar\" + aria-valuemin=\"0\" / aria-valuemax=\"100\" / aria-valuenow",
    "文案节点 aria-hidden=\"true\",避免与 aria-valuenow 重复播报",
    "不确定进度(无法给出百分比)用 aria-valuetext=\"进行中\" 表达"
  ],
  "api": {
    "source": "implementation",
    "note": "props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs",
    "requiredNote": "「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。",
    "props": [
      {
        "name": "percentage",
        "type": "number",
        "default": "0",
        "desc": "完成度 0~100,超出范围按边界收敛(规格 §26.2 bar)",
        "required": false
      },
      {
        "name": "type",
        "type": "'line' | 'circle'",
        "default": "'line'",
        "desc": "变体 type:线形 / 环形(规格 §26.3)",
        "required": false
      },
      {
        "name": "status",
        "type": "'normal' | 'success' | 'error'",
        "default": "'normal'",
        "desc": "变体 status:进行中 / 成功 / 失败,决定条体色相(规格 §26.3)",
        "required": false
      },
      {
        "name": "labelPlacement",
        "type": "'inside' | 'right' | 'center'",
        "default": "'right'",
        "desc": "变体 labelPlacement:文案位置;center 仅对环形有效(规格 §26.3)",
        "required": false
      },
      {
        "name": "label",
        "type": "string",
        "default": "''",
        "desc": "自定义文案,缺省显示「percentage%」(规格 §26.2 label)",
        "required": false
      },
      {
        "name": "paused",
        "type": "boolean",
        "default": "false",
        "desc": "状态 paused:暂停,条体降透明度且进度保留(规格 §26.4)",
        "required": false
      }
    ],
    "events": [
      {
        "name": "—",
        "params": "—",
        "desc": "无事件;进度条不可交互(规格 §26.5)"
      }
    ],
    "slots": [
      {
        "name": "default",
        "desc": "追加在进度条之后的自定义内容(如「取消」按钮)(规格 §26.2 status)"
      }
    ]
  },
  "variantClasses": {
    "type": {
      "line": [
        ".kole-m-progress--line"
      ],
      "circle": [
        ".kole-m-progress--circle"
      ]
    },
    "status": {
      "normal": [],
      "success": [
        ".kole-m-progress--success"
      ],
      "error": [
        ".kole-m-progress--error"
      ]
    },
    "labelPlacement": {
      "inside": [
        ".kole-m-progress--inside"
      ],
      "right": [
        ".kole-m-progress--right"
      ],
      "center": [
        ".kole-m-progress__hole",
        "--kole-m-progress-percent"
      ]
    }
  },
  "demos": [
    {
      "id": "line",
      "group": "01 组件类型",
      "title": "线形",
      "desc": "type=line + labelPlacement=right:文案固定在条右侧,数字用等宽数字避免跳动。",
      "variant": "type=line / labelPlacement=right"
    },
    {
      "id": "inside",
      "group": "01 组件类型",
      "title": "文案在条内",
      "desc": "labelPlacement=inside 时条体加高到 20px 以容纳文字,适合窄屏与卡片内。",
      "variant": "labelPlacement=inside"
    },
    {
      "id": "status",
      "group": "02 组件状态",
      "title": "三种状态",
      "desc": "进行中 / 成功 / 失败:色相不同,且都带文案与状态图标,不靠颜色单独表意。",
      "variant": "status=normal|success|error"
    },
    {
      "id": "circle",
      "group": "01 组件类型",
      "title": "环形",
      "desc": "type=circle + labelPlacement=center:环心挖空与所在容器同底,文案在环心。",
      "variant": "type=circle / labelPlacement=center"
    },
    {
      "id": "paused",
      "group": "02 组件状态",
      "title": "暂停",
      "desc": "状态 paused:条体降透明度、进度保留,用户能看出「停住了」而不是「没开始」。",
      "variant": "paused=true"
    },
    {
      "id": "clickable",
      "group": "02 组件状态",
      "title": "进度不可交互",
      "desc": "进度条本身不接收点击;需要打断时由宿主在旁边放按钮(演示里用按钮把进度推到 100%)。",
      "variant": "无事件"
    }
  ],
  "related": [
    {
      "slug": "mobile-steps",
      "why": "过程有明确的阶段(提交 → 审核 → 完成)用步骤条;只有完成度没有阶段时用进度条"
    },
    {
      "slug": "pullrefresh",
      "why": "等待短任务完成用下拉刷新的加载态;长过程需要量化完成度时用进度条"
    },
    {
      "slug": "mobile-toast",
      "why": "过程瞬间完成只需一句轻提示;过程持续可见才需要进度条"
    }
  ]
}