为一篇讲解 HarmonyOS 7 中 Stepper 式流程页的文章绘制一张手绘笔记风格的流程图信

前言

实名认证、开户、报销提交这类页面,如果把所有输入一次性摆出来,用户压力会很大。姓名、证件号、照片、协议确认、提交按钮全在一屏里,出错时也不好定位。

Stepper 式流程更适合这类任务。一步只处理一件事,完成后再进入下一步,用户知道自己现在在哪里,也知道下一步要做什么。

流程页稳定的关键,是每一步都有进入条件和完成条件。

为什么这个问题经常被写乱

为一篇讲解 Stepper 式流程页设计原则的技术文章绘制一张手绘笔记风格的教程信息图,重点解释“先

Stepper 式流程页更稳 这类内容很容易被写成“代码能跑就算讲完了”,但对初学者来说,这恰恰是最不够的地方。真正让人卡住的,往往不是某个组件名记不住,而是不知道这段代码为什么要这样拆、状态为什么要这样放、以后需求变化时应该从哪里改。

所以这篇文章不只想给你一个能跑的例子,更想把背后的判断过程讲清楚。你只要把这个判断过程吃透,后面自己改页面、补需求、查问题时,心里会稳很多。

先拆步骤,再写页面

流程页不要直接从 UI 开始写。先把步骤、内容和完成条件列清楚。

步骤 内容 完成条件
1 填写身份信息 姓名和证件号不为空
2 上传证件 已选择文件

为一篇关于 HarmonyOS 7 ArkTS Stepper 流程页的文章绘制一张手绘笔记风格的框

| 3 | 确认提交 | 勾选确认 |

这张表决定了后面的状态设计。页面只是在不同步骤展示不同内容,核心逻辑是 currentStepcanNext()

先把页面目标想清楚

在真正写代码之前,先别急着盯着 API。更有用的做法是先想清楚:这个页面到底想解决什么问题,用户最在意的反馈是什么,哪些状态必须一直保持一致。

当你先把这条主线想明白,再回头看组件和状态设计,很多选择都会顺理成章。对小白来说,这一步尤其重要,因为它能帮你从“照着抄”慢慢过渡到“看得懂、改得动”。

完整 ArkTS 示例

interface VerifyStep39 {
  id: number
  title: string
}

@Entry
@Component
struct StepperVerifyPage39 {
  @State currentStep: number = 0
  @State realName: string = ''
  @State cardNo: string = ''
  @State hasFile: boolean = false
  @State confirmed: boolean = false
  private steps: VerifyStep39[] = [
    { id: 0, title: '身份信息' },
    { id: 1, title: '证件上传' },
    { id: 2, title: '确认提交' }
  ]

  canNext(): boolean {
    if (this.currentStep === 0) {
      return this.realName.length > 0 && this.cardNo.length > 0
    }
    if (this.currentStep === 1) {
      return this.hasFile
    }
    return this.confirmed
  }

  next(): void {
    if (!this.canNext()) {
      return
    }
    if (this.currentStep < 2) {
      this.currentStep += 1
    }
  }

  build() {
    Column({ space: 16 }) {
      Text('实名认证')
        .fontSize(26)
        .fontWeight(FontWeight.Bold)
        .width('100%')
      Row({ space: 8 }) {
        ForEach(this.steps, (item: VerifyStep39) => {
          Text(item.title)
            .fontSize(13)
            .fontColor(this.currentStep === item.id ? '#FFFFFF' : '#666666')
            .padding({ left: 10, right: 10, top: 6, bottom: 6 })
            .backgroundColor(this.currentStep === item.id ? '#2F6FED' : '#FFFFFF')
            .borderRadius(14)
        }, (item: VerifyStep39) => item.id.toString())
      }
      .width('100%')

      Column({ space: 12 }) {
        if (this.currentStep === 0) {
          TextInput({ placeholder: '请输入姓名', text: this.realName })
            .onChange((value: string) => { this.realName = value })
          TextInput({ placeholder: '请输入证件号', text: this.cardNo })
            .onChange((value: string) => { this.cardNo = value })
        } else if (this.currentStep === 1) {
          Text(this.hasFile ? '已选择身份证照片' : '请上传身份证照片')
            .fontSize(16)
          Button(this.hasFile ? '重新选择' : '选择文件')
            .onClick(() => { this.hasFile = true })
        } else {
          Text('请确认信息真实有效,提交后进入审核。')
            .fontSize(15)
            .fontColor('#666666')
          Row() {
            Checkbox()
              .select(this.confirmed)
              .onChange((value: boolean) => { this.confirmed = value })
            Text('我确认以上信息无误')
              .fontSize(14)
              .margin({ left: 8 })
          }
        }
      }
      .padding(16)
      .width('100%')
      .backgroundColor('#FFFFFF')
      .borderRadius(12)

      Row() {
        Button('上一步')
          .enabled(this.currentStep > 0)
          .onClick(() => { this.currentStep -= 1 })
        Blank()
        Button(this.currentStep === 2 ? '提交' : '下一步')
          .enabled(this.canNext())
          .onClick(() => this.next())
      }
      .width('100%')
    }
    .padding(16)
    .width('100%')
    .height('100%')
    .backgroundColor('#F6F7F9')
  }
}

currentStep 是唯一位置来源

流程页最怕多个布尔值控制显示,比如 showUserInfoshowUploadshowConfirm。一旦状态没同步,就可能两个步骤同时显示,或者都不显示。

示例只用 currentStep 表示当前位置。当前是 0 就展示身份信息,是 1 就展示证件上传,是 2 就展示确认提交。

位置来源只有一个,页面就不会互相打架。

canNext() 要同时服务按钮和逻辑

按钮的 enabled(this.canNext()) 是体验层,能让用户知道当前步骤还没完成。

next() 里仍然要再调用一次 canNext()。这不是重复,而是底线。以后如果某个快捷入口直接调用 next(),或者异步状态在点击前后发生变化,方法内部校验能兜住问题。

按钮状态负责提示,方法内部校验负责保证流程不被绕过。

上一步也要考虑边界

示例里“上一步”按钮通过 .enabled(this.currentStep > 0) 禁用,避免从第一步继续往前退。

如果要写得更严谨,可以把上一步也封装成方法:

previous(): void {
  if (this.currentStep > 0) {
    this.currentStep -= 1
  }
}

流程页越复杂,越建议把前进、后退、提交都收成方法。UI 只绑定动作,不直接改很多状态。

Stepper 页面要考虑可恢复

实名认证、开户、报销这类流程不一定一次完成。用户可能切到后台,也可能上传失败后回来重试。

中断点 建议处理 用户体验
填完第一步退出 保存草稿 不用重填
上传文件失败 停留在第二步 知道哪里失败
最后提交失败 保留确认页 可以重试
审核中返回 展示结果状态 不重复提交

示例把字段都放在页面状态里,页面切步骤时不会丢。真实项目可以在每一步完成后保存草稿。

不要把提交状态塞进步骤里

currentStep 表示流程位置,submitting 表示是否正在提交,它们是两件事。

如果把提交中写成一个新的步骤,比如 currentStep = 3,后面失败、重试、返回确认页都会变麻烦。更好的做法是保留 currentStep === 2,再加 submittingsubmitError

状态越清楚,流程越好维护。

新手最容易踩的坑

这一类示例最容易让人产生错觉:界面出来了,就以为已经掌握了。其实真正容易出问题的地方,通常都在效果之外,比如状态有没有收拢、失败后怎么兜底、以后要扩展时会不会牵一发动全身。

所以你练这篇内容时,别只看“现在能不能跑”,还要继续看“以后好不好改”。能把这个习惯养起来,你写出来的页面会比单纯照着示例拼出来的页面稳很多。

放进真实项目还要补什么

示例代码的重点是把核心思路讲明白,所以很多工程化细节会故意省掉。真正落到项目里时,你通常还要继续补接口联动、异常处理、边界保护、资源抽离,以及和其他页面状态之间的配合。

比较稳的做法是分三步走:先把结构和职责立住,再把真实业务接进去,最后再优化视觉和交互体验。这样改出来的页面不只是“能演示”,而是真的更接近可以长期维护的业务代码。

写在最后

HarmonyOS7 写这类流程页,先把步骤、完成条件和状态边界定好,再写 UI。这样校验、回退、失败重试都会稳很多。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐