移动端导航步骤条

步骤条Steps

横向展示多步流程的当前进度(如「提交 → 审核 → 完成」)

导航 规格 15 · 步骤条 Steps 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-steps.css">

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

演示

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

01 组件类型

横向步骤direction=horizontal

三种状态并列展示:已完成带勾选、进行中加粗并用品牌色实心点。

查看代码(演示页原文 · 17 行)
frameworks-mobile/Steps.html · basic
<section class="demo-block" data-demo="basic">
  <p class="demo-label">横向步骤(status:wait / process / finish —— 进行中加粗,已完成带勾选)</p>
  <ol class="kole-m-steps" data-assert="steps-basic">
    <li class="kole-m-steps__item kole-m-steps__item--finish" role="listitem">
      <span class="kole-m-steps__dot" aria-hidden="true">✓</span>
      <span class="kole-m-steps__label">提交订单</span>
    </li>
    <li class="kole-m-steps__item kole-m-steps__item--process" role="listitem" aria-current="step">
      <span class="kole-m-steps__dot" aria-hidden="true">2</span>
      <span class="kole-m-steps__label">等待审核</span>
    </li>
    <li class="kole-m-steps__item" role="listitem">
      <span class="kole-m-steps__dot" aria-hidden="true">3</span>
      <span class="kole-m-steps__label">完成</span>
    </li>
  </ol>
</section>
含失败步骤status=error

失败步骤用错误色 + 感叹号,状态同时写在文字里 —— 不只靠颜色传达。

查看代码(演示页原文 · 17 行)
frameworks-mobile/Steps.html · error
<section class="demo-block" data-demo="error">
  <p class="demo-label">含失败步骤(status=error:错误色 + 状态写在文字里,不只靠颜色)</p>
  <ol class="kole-m-steps" data-assert="steps-error">
    <li class="kole-m-steps__item kole-m-steps__item--finish" role="listitem">
      <span class="kole-m-steps__dot" aria-hidden="true">✓</span>
      <span class="kole-m-steps__label">实名认证</span>
    </li>
    <li class="kole-m-steps__item kole-m-steps__item--error" role="listitem" aria-current="step">
      <span class="kole-m-steps__dot" aria-hidden="true">!</span>
      <span class="kole-m-steps__label">银行卡校验失败</span>
    </li>
    <li class="kole-m-steps__item" role="listitem">
      <span class="kole-m-steps__dot" aria-hidden="true">3</span>
      <span class="kole-m-steps__label">开通服务</span>
    </li>
  </ol>
</section>
四步流程direction=horizontal · 4 步

步骤较多时横向平铺,标题短、点不被挤压。

查看代码(演示页原文 · 21 行)
frameworks-mobile/Steps.html · four-steps
<section class="demo-block" data-demo="four-steps">
  <p class="demo-label">四步流程(步骤较多时横向排布,标题换行不挤点)</p>
  <ol class="kole-m-steps" data-assert="steps-four">
    <li class="kole-m-steps__item kole-m-steps__item--finish" role="listitem">
      <span class="kole-m-steps__dot" aria-hidden="true">✓</span>
      <span class="kole-m-steps__label">下单</span>
    </li>
    <li class="kole-m-steps__item kole-m-steps__item--finish" role="listitem">
      <span class="kole-m-steps__dot" aria-hidden="true">✓</span>
      <span class="kole-m-steps__label">支付</span>
    </li>
    <li class="kole-m-steps__item kole-m-steps__item--process" role="listitem" aria-current="step">
      <span class="kole-m-steps__dot" aria-hidden="true">3</span>
      <span class="kole-m-steps__label">发货</span>
    </li>
    <li class="kole-m-steps__item" role="listitem">
      <span class="kole-m-steps__dot" aria-hidden="true">4</span>
      <span class="kole-m-steps__label">收货</span>
    </li>
  </ol>
</section>

02 组件状态

纵向步骤direction=vertical

标题较长时改纵向:连接线变竖线,标题可以换行。

查看代码(演示页原文 · 17 行)
frameworks-mobile/Steps.html · vertical
<section class="demo-block" data-demo="vertical">
  <p class="demo-label">纵向步骤(direction=vertical:步骤标题较长时用,连接线改为竖线)</p>
  <ol class="kole-m-steps kole-m-steps--vertical" data-assert="steps-vertical">
    <li class="kole-m-steps__item kole-m-steps__item--finish" role="listitem">
      <span class="kole-m-steps__dot" aria-hidden="true">✓</span>
      <span class="kole-m-steps__content"><span class="kole-m-steps__label">已提交资料</span></span>
    </li>
    <li class="kole-m-steps__item kole-m-steps__item--process" role="listitem" aria-current="step">
      <span class="kole-m-steps__dot" aria-hidden="true">2</span>
      <span class="kole-m-steps__content"><span class="kole-m-steps__label">资质审核中(预计 1 个工作日)</span></span>
    </li>
    <li class="kole-m-steps__item" role="listitem">
      <span class="kole-m-steps__dot" aria-hidden="true">3</span>
      <span class="kole-m-steps__content"><span class="kole-m-steps__label">开通账号</span></span>
    </li>
  </ol>
</section>

API

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

Props

名称类型默认值说明必传
direction'horizontal' | 'vertical''horizontal'变体 direction:横向或纵向排列(规格 §15.3)N
stepsArray<{ label, status }>[]步骤数据;status ∈ wait / process / finish / error(规格 §15.3)N

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

事件

名称参数说明

插槽

名称说明
default额外的自定义步骤(追加在 steps 之后)

CSS 变量

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

名称默认值说明
--kole-m-steps-dot-size24px组件内部默认值,可在业务侧覆盖

何时使用

  • 横向展示多步流程的当前进度(如「提交 → 审核 → 完成」)
  • 纯展示组件,步骤本身不可点(这是设计如此,不是缺失)
  • 步骤过多时横向可滚动(容器 overflow-x: auto)
  • 容器 role="list",每步 role="listitem"
  • aria-current="step" 标出当前步
  • 状态不只靠颜色(进行中加粗 + 已完成用勾选字符)

交互与触控

  • 纯展示组件,步骤本身不可点(这是设计如此,不是缺失)
  • 步骤过多时横向可滚动(容器 overflow-x: auto)

无障碍

  • 容器 role="list",每步 role="listitem"
  • aria-current="step" 标出当前步
  • 状态不只靠颜色(进行中加粗 + 已完成用勾选字符)

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

组件何时用它而不是本组件
单元格Cell步骤条展示流程进度;具体每一步的详情入口用单元格列出来
标签Tag单条记录的状态用标签;只有多步流程才用步骤条
分割线Divider步骤之间靠连接线表达先后关系,不要用分割线替代

规格未定 / 禁止发明

类别条目
禁止发明步骤之间的动画过渡
禁止发明点击步骤跳转的规则
规格未定纵向步骤条的推荐间距
规格未定标题最多几行

结构(anatomy)

字段说明
steps根容器,横向排列
steps__item单个步骤
steps__dot序号点 / 勾选标记
steps__label步骤标题
steps__line连接线

变体维度与类名映射

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

维度取值对应类名 / 变量
directionhorizontal / vertical
horizontal (由数据驱动,无专属类)
vertical .kole-m-steps--vertical
statuswait / process / finish / error
wait (由数据驱动,无专属类)
process .kole-m-steps__item--process
finish .kole-m-steps__item--finish
error .kole-m-steps__item--error

代表变体

变体标签
direction=horizontal · status=process横向(进行中)
direction=horizontal · status=finish横向(已完成)
direction=horizontal · status=error横向(含失败)
direction=vertical · status=process纵向(标题较长)

用到的令牌

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

--kole-m-font-size-caption --kole-m-font-size-label --kole-m-gutter --kole-color-border --kole-color-brand --kole-color-card-bg --kole-color-error --kole-color-text-body --kole-color-text-inverse --kole-color-text-secondary --kole-font-family --kole-space-16 --kole-space-4 --kole-space-8 --kole-m-steps-dot-size

6 端源码

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

frameworks-mobile/Steps.css · 纯样式(CSS) · 115 行
frameworks-mobile/Steps.css
/* Kole UI Mobile · Steps 样式 — 对齐移动端规格 §15
   步骤条:纯展示;状态不只靠颜色(进行中加粗 + 已完成用勾选字符);
   步骤过多时横向可滚动。 */

.kole-m-steps {
  --kole-m-steps-dot-size: 24px;
  display: flex;
  align-items: flex-start;
  gap: 0;
  margin: 0;
  padding: var(--kole-space-16) var(--kole-m-gutter);
  background: var(--kole-color-card-bg);
  font-family: var(--kole-font-family);
  list-style: none;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.kole-m-steps__item {
  position: relative;
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--kole-space-8);
  min-width: 72px;
  color: var(--kole-color-text-secondary);
  font-size: var(--kole-m-font-size-label);
  line-height: 1.3;
  text-align: center;
}

.kole-m-steps__dot {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--kole-m-steps-dot-size);
  height: var(--kole-m-steps-dot-size);
  border: 1px solid var(--kole-color-border);
  border-radius: 50%;
  background: var(--kole-color-card-bg);
  color: var(--kole-color-text-secondary);
  font-size: var(--kole-m-font-size-caption);
  line-height: 1;
}

.kole-m-steps__label {
  max-width: 100%;
  overflow-wrap: anywhere;
}

/* 连接线:由每项的 ::after 画到下一项(最后一项不画) */
.kole-m-steps__item:not(:last-child)::after {
  content: '';
  position: absolute;
  top: calc(var(--kole-m-steps-dot-size) / 2);
  inset-inline-start: calc(50% + var(--kole-m-steps-dot-size) / 2 + var(--kole-space-4));
  width: calc(100% - var(--kole-m-steps-dot-size) - var(--kole-space-8));
  height: 1px;
  background: var(--kole-color-border);
}

/* 变体 status=process:进行中(品牌色 + 加粗) */
.kole-m-steps__item--process { color: var(--kole-color-brand); font-weight: 500; }

.kole-m-steps__item--process .kole-m-steps__dot {
  border-color: var(--kole-color-brand);
  background: var(--kole-color-brand);
  color: var(--kole-color-text-inverse);
}

/* 变体 status=finish:已完成(品牌色 + 勾选) */
.kole-m-steps__item--finish { color: var(--kole-color-text-body); }

.kole-m-steps__item--finish .kole-m-steps__dot {
  border-color: var(--kole-color-brand);
  color: var(--kole-color-brand);
}

.kole-m-steps__item--finish:not(:last-child)::after { background: var(--kole-color-brand); }

/* 变体 status=error:错误(错误色 + 感叹号) */
.kole-m-steps__item--error { color: var(--kole-color-error); }

.kole-m-steps__item--error .kole-m-steps__dot {
  border-color: var(--kole-color-error);
  color: var(--kole-color-error);
}

/* 变体 direction=vertical:纵向排列,连接线改为竖线 */
.kole-m-steps--vertical {
  flex-direction: column;
  gap: var(--kole-space-16);
  overflow-x: visible;
}

.kole-m-steps--vertical .kole-m-steps__item {
  flex-direction: row;
  align-items: flex-start;
  justify-content: flex-start;
  width: 100%;
  min-width: 0;
  text-align: start;
}

.kole-m-steps--vertical .kole-m-steps__item:not(:last-child)::after {
  top: calc(var(--kole-m-steps-dot-size) + var(--kole-space-4));
  inset-inline-start: calc(var(--kole-m-steps-dot-size) / 2);
  width: 1px;
  height: calc(100% - var(--kole-m-steps-dot-size));
}

.kole-m-steps__content { min-width: 0; }
frameworks-mobile/Steps.html · H5 原生(无框架) · 117 行
frameworks-mobile/Steps.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 · Steps(H5)</title>
<link rel="stylesheet" href="../.design_library/kole-ui-mobile/colors_and_type.css">
<link rel="stylesheet" href="Steps.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-block[hidden] { display: none; }
</style>
</head>
<body>
<div class="demo">
  <section class="demo-block" data-demo="basic">
    <p class="demo-label">横向步骤(status:wait / process / finish —— 进行中加粗,已完成带勾选)</p>
    <ol class="kole-m-steps" data-assert="steps-basic">
      <li class="kole-m-steps__item kole-m-steps__item--finish" role="listitem">
        <span class="kole-m-steps__dot" aria-hidden="true">✓</span>
        <span class="kole-m-steps__label">提交订单</span>
      </li>
      <li class="kole-m-steps__item kole-m-steps__item--process" role="listitem" aria-current="step">
        <span class="kole-m-steps__dot" aria-hidden="true">2</span>
        <span class="kole-m-steps__label">等待审核</span>
      </li>
      <li class="kole-m-steps__item" role="listitem">
        <span class="kole-m-steps__dot" aria-hidden="true">3</span>
        <span class="kole-m-steps__label">完成</span>
      </li>
    </ol>
  </section>

  <section class="demo-block" data-demo="error">
    <p class="demo-label">含失败步骤(status=error:错误色 + 状态写在文字里,不只靠颜色)</p>
    <ol class="kole-m-steps" data-assert="steps-error">
      <li class="kole-m-steps__item kole-m-steps__item--finish" role="listitem">
        <span class="kole-m-steps__dot" aria-hidden="true">✓</span>
        <span class="kole-m-steps__label">实名认证</span>
      </li>
      <li class="kole-m-steps__item kole-m-steps__item--error" role="listitem" aria-current="step">
        <span class="kole-m-steps__dot" aria-hidden="true">!</span>
        <span class="kole-m-steps__label">银行卡校验失败</span>
      </li>
      <li class="kole-m-steps__item" role="listitem">
        <span class="kole-m-steps__dot" aria-hidden="true">3</span>
        <span class="kole-m-steps__label">开通服务</span>
      </li>
    </ol>
  </section>

  <section class="demo-block" data-demo="four-steps">
    <p class="demo-label">四步流程(步骤较多时横向排布,标题换行不挤点)</p>
    <ol class="kole-m-steps" data-assert="steps-four">
      <li class="kole-m-steps__item kole-m-steps__item--finish" role="listitem">
        <span class="kole-m-steps__dot" aria-hidden="true">✓</span>
        <span class="kole-m-steps__label">下单</span>
      </li>
      <li class="kole-m-steps__item kole-m-steps__item--finish" role="listitem">
        <span class="kole-m-steps__dot" aria-hidden="true">✓</span>
        <span class="kole-m-steps__label">支付</span>
      </li>
      <li class="kole-m-steps__item kole-m-steps__item--process" role="listitem" aria-current="step">
        <span class="kole-m-steps__dot" aria-hidden="true">3</span>
        <span class="kole-m-steps__label">发货</span>
      </li>
      <li class="kole-m-steps__item" role="listitem">
        <span class="kole-m-steps__dot" aria-hidden="true">4</span>
        <span class="kole-m-steps__label">收货</span>
      </li>
    </ol>
  </section>

  <section class="demo-block" data-demo="vertical">
    <p class="demo-label">纵向步骤(direction=vertical:步骤标题较长时用,连接线改为竖线)</p>
    <ol class="kole-m-steps kole-m-steps--vertical" data-assert="steps-vertical">
      <li class="kole-m-steps__item kole-m-steps__item--finish" role="listitem">
        <span class="kole-m-steps__dot" aria-hidden="true">✓</span>
        <span class="kole-m-steps__content"><span class="kole-m-steps__label">已提交资料</span></span>
      </li>
      <li class="kole-m-steps__item kole-m-steps__item--process" role="listitem" aria-current="step">
        <span class="kole-m-steps__dot" aria-hidden="true">2</span>
        <span class="kole-m-steps__content"><span class="kole-m-steps__label">资质审核中(预计 1 个工作日)</span></span>
      </li>
      <li class="kole-m-steps__item" role="listitem">
        <span class="kole-m-steps__dot" aria-hidden="true">3</span>
        <span class="kole-m-steps__content"><span class="kole-m-steps__label">开通账号</span></span>
      </li>
    </ol>
  </section>
</div>
<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/Steps.jsx · React · 53 行
frameworks-mobile/Steps.jsx
import React from 'react';
import './Steps.css';

/* 步骤条(移动端)— 规格 §15
   纯展示:步骤本身不可点(规格 §15.5 明确如此,不是缺失);
   状态不只靠颜色 —— 进行中加粗、已完成用勾选字符、失败用感叹号。 */
export default function Steps({
  direction = 'horizontal',
  steps = [],
  children = null,
}) {
  const cls =
    'kole-m-steps' + (direction === 'vertical' ? ' kole-m-steps--vertical' : '');

  const statusOf = (s) => (s && s.status) || 'wait';
  /* aria-current="step" 标出当前步(进行中与失败都算「停在这一步」) */
  const isCurrent = (s) => {
    const st = statusOf(s);
    return st === 'process' || st === 'error';
  };
  const dotOf = (s, i) => {
    const st = statusOf(s);
    if (st === 'finish') return '✓';
    if (st === 'error') return '!';
    return i + 1;
  };

  return (
    <ol className={cls}>
      {steps.map((s, i) => (
        <li
          key={i}
          className={`kole-m-steps__item kole-m-steps__item--${statusOf(s)}`}
          role="listitem"
          aria-current={isCurrent(s) ? 'step' : undefined}
        >
          <span className="kole-m-steps__dot" aria-hidden="true">
            {dotOf(s, i)}
          </span>
          {direction === 'vertical' ? (
            <span className="kole-m-steps__content">
              <span className="kole-m-steps__label">{s && s.label}</span>
            </span>
          ) : (
            <span className="kole-m-steps__label">{s && s.label}</span>
          )}
        </li>
      ))}
      {children}
    </ol>
  );
}
frameworks-mobile/Steps.vue2.vue · Vue 2 · 51 行
frameworks-mobile/Steps.vue2.vue
<template>
  <ol class="kole-m-steps" :class="stepsClass">
    <li
      v-for="(s, i) in steps"
      :key="i"
      class="kole-m-steps__item"
      :class="itemClass(s)"
      role="listitem"
      :aria-current="s && s.status === 'process' ? 'step' : null"
    >
      <span class="kole-m-steps__dot" aria-hidden="true">{{ dotOf(s) }}</span>
      <span class="kole-m-steps__content"><span class="kole-m-steps__label">{{ s && s.label }}</span></span>
    </li>
    <slot></slot>
  </ol>
</template>

<script>
export default {
  name: 'KoleMSteps',
  props: {
    direction: { type: String, default: 'horizontal' },
    steps: { type: Array, default: function () { return []; } }
  },
  computed: {
    stepsClass: function () {
      return [this.direction === 'vertical' ? 'kole-m-steps--vertical' : ''].filter(Boolean);
    }
  },
  methods: {
    itemClass: function (s) {
      if (!s) return [];
      var out = [];
      if (s.status === 'process') out.push('kole-m-steps__item--process');
      if (s.status === 'finish') out.push('kole-m-steps__item--finish');
      if (s.status === 'error') out.push('kole-m-steps__item--error');
      return out;
    },
    /* 状态不只靠颜色:已完成用勾选字符,失败用感叹号(规格 §15.6) */
    dotOf: function (s) {
      if (!s) return '';
      if (s.status === 'finish') return '✓';
      if (s.status === 'error') return '!';
      return String(s.index || 0);
    }
  }
};
</script>

<style src="./Steps.css"></style>
frameworks-mobile/Steps.vue3.vue · Vue 3 · 47 行
frameworks-mobile/Steps.vue3.vue
<template>
  <ol class="kole-m-steps" :class="stepsClass">
    <li
      v-for="(s, i) in steps"
      :key="i"
      class="kole-m-steps__item"
      :class="itemClass(s)"
      role="listitem"
      :aria-current="s && s.status === 'process' ? 'step' : null"
    >
      <span class="kole-m-steps__dot" aria-hidden="true">{{ dotOf(s) }}</span>
      <span class="kole-m-steps__content"><span class="kole-m-steps__label">{{ s && s.label }}</span></span>
    </li>
    <slot></slot>
  </ol>
</template>

<script setup>
import { computed } from 'vue';

const props = defineProps({
  direction: { type: String, default: 'horizontal' },
  steps: { type: Array, default: () => [] }
});

const stepsClass = computed(() => [props.direction === 'vertical' ? 'kole-m-steps--vertical' : ''].filter(Boolean));

function itemClass(s) {
  if (!s) return [];
  const out = [];
  if (s.status === 'process') out.push('kole-m-steps__item--process');
  if (s.status === 'finish') out.push('kole-m-steps__item--finish');
  if (s.status === 'error') out.push('kole-m-steps__item--error');
  return out;
}

/* 状态不只靠颜色:已完成用勾选字符,失败用感叹号(规格 §15.6) */
function dotOf(s) {
  if (!s) return '';
  if (s.status === 'finish') return '✓';
  if (s.status === 'error') return '!';
  return String(s.index || 0);
}
</script>

<style src="./Steps.css"></style>
frameworks-mobile/Steps.uniapp.vue · uni-app(跨端:小程序 / App / H5) · 150 行
frameworks-mobile/Steps.uniapp.vue
<template>
  <view class="kole-m-steps" :class="stepsClass" role="list">
    <view
      v-for="(s, i) in steps"
      :key="i"
      class="kole-m-steps__item"
      :class="itemClass(s)"
      role="listitem"
      :aria-current="s && s.status === 'process' ? 'step' : ''"
    >
      <text class="kole-m-steps__dot">{{ dotOf(s) }}</text>
      <view class="kole-m-steps__content"><text class="kole-m-steps__label">{{ s && s.label }}</text></view>
    </view>
    <slot></slot>
  </view>
</template>

<script setup>
/* uni-app 端 · 步骤条(移动端)— 规格 §15
   跨端差异:用 view / text(ol/li 不是 uni 基础组件),列表语义靠 role="list"/"listitem";
   尺寸用 rpx(2rpx ≈ 1px);纯展示组件,无手势与点击。 */
import { computed } from 'vue';

const props = defineProps({
  direction: { type: String, default: 'horizontal' },
  steps: { type: Array, default: () => [] }
});

const stepsClass = computed(() => [props.direction === 'vertical' ? 'kole-m-steps--vertical' : ''].filter(Boolean));

function itemClass(s) {
  if (!s) return [];
  const out = [];
  if (s.status === 'process') out.push('kole-m-steps__item--process');
  if (s.status === 'finish') out.push('kole-m-steps__item--finish');
  if (s.status === 'error') out.push('kole-m-steps__item--error');
  return out;
}

/* 状态不只靠颜色:已完成用勾选字符,失败用感叹号(规格 §15.6) */
function dotOf(s) {
  if (!s) return '';
  if (s.status === 'finish') return '✓';
  if (s.status === 'error') return '!';
  return String(s.index || 0);
}
</script>

<style>
.kole-m-steps {
  --kole-m-steps-dot-size: 48rpx;
  --kole-m-font-size-label: 28rpx;
  --kole-m-font-size-caption: 22rpx;
  --kole-m-gutter: 32rpx;
  display: flex;
  align-items: flex-start;
  padding: 32rpx var(--kole-m-gutter);
  background-color: var(--kole-color-card-bg);
  font-size: var(--kole-m-font-size-label);
}

.kole-m-steps__item {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 144rpx;
  color: var(--kole-color-text-secondary);
  text-align: center;
}

.kole-m-steps__dot {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--kole-m-steps-dot-size);
  height: var(--kole-m-steps-dot-size);
  border: 2rpx solid var(--kole-color-border);
  border-radius: 50%;
  background-color: var(--kole-color-card-bg);
  color: var(--kole-color-text-secondary);
  font-size: var(--kole-m-font-size-caption);
  line-height: 1;
}

.kole-m-steps__label {
  margin-top: 16rpx;
  font-size: var(--kole-m-font-size-label);
}

/* 连接线:由每项的 ::after 画到下一项(最后一项不画) */
.kole-m-steps__item:not(:last-child)::after {
  content: '';
  position: absolute;
  top: 24rpx;
  left: calc(50% + 28rpx);
  width: calc(100% - 48rpx);
  height: 2rpx;
  background-color: var(--kole-color-border);
}

.kole-m-steps__item--process { color: var(--kole-color-brand); }

.kole-m-steps__item--process .kole-m-steps__dot {
  border-color: var(--kole-color-brand);
  background-color: var(--kole-color-brand);
  color: var(--kole-color-text-inverse);
}

.kole-m-steps__item--finish { color: var(--kole-color-text-body); }

.kole-m-steps__item--finish .kole-m-steps__dot {
  border-color: var(--kole-color-brand);
  color: var(--kole-color-brand);
}

.kole-m-steps__item--finish:not(:last-child)::after { background-color: var(--kole-color-brand); }

.kole-m-steps__item--error { color: var(--kole-color-error); }

.kole-m-steps__item--error .kole-m-steps__dot {
  border-color: var(--kole-color-error);
  color: var(--kole-color-error);
}

.kole-m-steps--vertical {
  flex-direction: column;
}

.kole-m-steps--vertical .kole-m-steps__item {
  flex-direction: row;
  align-items: flex-start;
  width: 100%;
  min-width: 0;
  margin-bottom: 32rpx;
  text-align: left;
}

.kole-m-steps--vertical .kole-m-steps__item:not(:last-child)::after {
  top: 56rpx;
  left: 24rpx;
  width: 2rpx;
  height: calc(100% - 48rpx);
}

.kole-m-steps__content { flex: 1; }
</style>

测试与回归

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

断言 14 条 · 全部通过 报告 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-steps.json(点击展开原始 JSON)
{
  "schemaVersion": 1,
  "sourceKind": "authored-spec",
  "provenance": "authored-in-repo",
  "specFile": "spec/移动端规格.md",
  "confidence": "high",
  "specSection": "15 · 步骤条 Steps",
  "slug": "mobile-steps",
  "name": "步骤条 Steps",
  "semanticTypeCandidates": [
    "steps",
    "wizard",
    "progress"
  ],
  "variantDimensions": [
    {
      "name": "direction",
      "values": [
        "horizontal",
        "vertical"
      ]
    },
    {
      "name": "status",
      "values": [
        "wait",
        "process",
        "finish",
        "error"
      ]
    }
  ],
  "representativeVariants": [
    {
      "direction": "horizontal",
      "status": "process",
      "label": "横向(进行中)"
    },
    {
      "direction": "horizontal",
      "status": "finish",
      "label": "横向(已完成)"
    },
    {
      "direction": "horizontal",
      "status": "error",
      "label": "横向(含失败)"
    },
    {
      "direction": "vertical",
      "status": "process",
      "label": "纵向(标题较长)"
    }
  ],
  "anatomy": {
    "steps": "根容器,横向排列",
    "steps__item": "单个步骤",
    "steps__dot": "序号点 / 勾选标记",
    "steps__label": "步骤标题",
    "steps__line": "连接线"
  },
  "structurePatterns": {
    "direction": "horizontal / vertical",
    "status": "wait(未开始)/ process(进行中)/ finish(已完成)/ error"
  },
  "usageHints": [
    "横向展示多步流程的当前进度(如「提交 → 审核 → 完成」)",
    "纯展示组件,步骤本身不可点(这是设计如此,不是缺失)",
    "步骤过多时横向可滚动(容器 overflow-x: auto)",
    "容器 role=\"list\",每步 role=\"listitem\"",
    "aria-current=\"step\" 标出当前步",
    "状态不只靠颜色(进行中加粗 + 已完成用勾选字符)"
  ],
  "doNotInvent": [
    "步骤之间的动画过渡",
    "点击步骤跳转的规则"
  ],
  "unknowns": [
    "纵向步骤条的推荐间距",
    "标题最多几行"
  ],
  "interaction": [
    "纯展示组件,步骤本身不可点(这是设计如此,不是缺失)",
    "步骤过多时横向可滚动(容器 overflow-x: auto)"
  ],
  "accessibility": [
    "容器 role=\"list\",每步 role=\"listitem\"",
    "aria-current=\"step\" 标出当前步",
    "状态不只靠颜色(进行中加粗 + 已完成用勾选字符)"
  ],
  "api": {
    "source": "implementation",
    "note": "props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs",
    "requiredNote": "「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。",
    "props": [
      {
        "name": "direction",
        "type": "'horizontal' | 'vertical'",
        "default": "'horizontal'",
        "desc": "变体 direction:横向或纵向排列(规格 §15.3)",
        "required": false
      },
      {
        "name": "steps",
        "type": "Array<{ label, status }>",
        "default": "[]",
        "desc": "步骤数据;status ∈ wait / process / finish / error(规格 §15.3)",
        "required": false
      }
    ],
    "events": [],
    "slots": [
      {
        "name": "default",
        "desc": "额外的自定义步骤(追加在 steps 之后)"
      }
    ]
  },
  "variantClasses": {
    "direction": {
      "horizontal": [],
      "vertical": [
        ".kole-m-steps--vertical"
      ]
    },
    "status": {
      "wait": [],
      "process": [
        ".kole-m-steps__item--process"
      ],
      "finish": [
        ".kole-m-steps__item--finish"
      ],
      "error": [
        ".kole-m-steps__item--error"
      ]
    }
  },
  "demos": [
    {
      "id": "basic",
      "group": "01 组件类型",
      "title": "横向步骤",
      "desc": "三种状态并列展示:已完成带勾选、进行中加粗并用品牌色实心点。",
      "variant": "direction=horizontal"
    },
    {
      "id": "error",
      "group": "01 组件类型",
      "title": "含失败步骤",
      "desc": "失败步骤用错误色 + 感叹号,状态同时写在文字里 —— 不只靠颜色传达。",
      "variant": "status=error"
    },
    {
      "id": "four-steps",
      "group": "01 组件类型",
      "title": "四步流程",
      "desc": "步骤较多时横向平铺,标题短、点不被挤压。",
      "variant": "direction=horizontal · 4 步"
    },
    {
      "id": "vertical",
      "group": "02 组件状态",
      "title": "纵向步骤",
      "desc": "标题较长时改纵向:连接线变竖线,标题可以换行。",
      "variant": "direction=vertical"
    }
  ],
  "related": [
    {
      "slug": "cell",
      "why": "步骤条展示流程进度;具体每一步的详情入口用单元格列出来"
    },
    {
      "slug": "mobile-tag",
      "why": "单条记录的状态用标签;只有多步流程才用步骤条"
    },
    {
      "slug": "mobile-divider",
      "why": "步骤之间靠连接线表达先后关系,不要用分割线替代"
    }
  ]
}