HarmonyOS 校园应用系列之状态机驱动:ArkTS 严格模式下,校园打印订单页双态卡片与进度条条件渲染
HarmonyOS 校园应用系列之状态机驱动:ArkTS 严格模式下,校园打印订单页双态卡片与进度条条件渲染
一、页面定位与功能概述
订单页(Func2Tab,标题显示为 "记录")是校园打印 App 的 "任务管理中心"——展示所有打印订单,支持按状态筛选(全部/进行中/已完成)。每个订单卡片包含项目名称、描述、评分、时间戳、完成百分比和操作按钮。
核心设计思想是 "状态机驱动渲染" —— 两种视图状态(进行中/已完成)下 RecordCard 展示不同样式:进行中显示橙色标签+进度条,已完成显示绿色标签+无进度条。
1.1 页面状态定义
@State activeTab: number = 0 // 0=全部, 1=进行中, 2=已完成
private tabs: string[] = ['全部', '进行中', '已完成'];
private stats: StatItem[] = [
{ value: '28', label: '总数', color: C.primary },
{ value: '3', label: '进行中', color: C.warn },
{ value: '25', label: '已完成', color: C.ok },
];
private records: RecordItem[] = [
{ id: 1, emoji: '📦', title: '项目一', status: '进行中', time: '今天 14:30',
desc: '描述信息 · 已完成75%', statusColor: C.warn },
// ... 共 5 条记录
]
页面只有 1 个 @State 变量(activeTab),统计数据与订单数据均为不可变的 private 数组。RecordItem 接口包含 7 个字段:id、emoji、title、status、time、desc、statusColor——其中 statusColor 将状态颜色直接存入数据,渲染时无需再写条件判断。



二、页面头部 —— 标题 + 统计 + 搜索
@Builder
Header() {
Row({ space: 12 }) {
Column({ space: 2 }) {
Text('记录').fontSize(20).fontWeight(FontWeight.Bold).fontColor(C.text)
Text(`${this.records.length} 条记录`).fontSize(10).fontColor(C.textDim)
}.alignItems(HorizontalAlign.Start)
Blank()
Row() { Text('🔍').fontSize(18) }
.width(36).height(36).backgroundColor(C.cardSoft).borderRadius(D.rSm).justifyContent(FlexAlign.Center)
.onClick(() => { promptAction.showToast({ message: '搜索' }); })
}
.width('100%').height(this.safeTop + 60)
.padding({ top: this.safeTop, left: D.pad, right: D.pad })
.backgroundColor(C.card).alignItems(VerticalAlign.Bottom)
.border({ width: { bottom: 1 }, color: C.stroke })
}
头部是 "主标题+副标题"双行结构:左侧 20vp 加粗"记录"与 10vp 灰色副标题纵向排列。副标题 ${this.records.length} 条记录 用模板字符串动态统计记录数(当前为 5 条),让用户对数据规模有即时认知。右侧是 🔍 emoji 搜索按钮(C.cardSoft 底 36×36 方块),点击弹出"搜索" toast。头部高度 safeTop + 60 配合 alignItems(VerticalAlign.Bottom) 让内容沉底对齐,底部 1vp C.stroke 分隔线构成通栏吸顶效果。
三、StatsCard —— 三维统计数据卡片
@Builder
StatsCard() {
Row() {
ForEach(this.stats, (s: StatItem, idx: number) => {
Column({ space: 3 }) {
Text(s.value).fontSize(20).fontWeight(FontWeight.Bold).fontColor(s.color)
Text(s.label).fontSize(10).fontColor(C.textDim)
}.layoutWeight(1)
if (idx < this.stats.length - 1) { Divider().vertical(true).height(28).color(C.stroke) }
}, (s: StatItem) => s.label)
}
.width('100%').height(64).backgroundColor(C.card).borderRadius(D.rMd)
.border({ width: 1, color: C.stroke }).justifyContent(FlexAlign.SpaceAround)
.margin({ top: 10, left: D.pad, right: D.pad })
}
| 维度 | 颜色 | 语义 |
|---|---|---|
| 总数 28 | 绿色 C.primary | 品牌主色,整体规模 |
| 进行中 3 | 橙色 C.warn | 警示,有待处理 |
| 已完成 25 | 绿色 C.ok | 正向反馈 |
三个统计项由 ForEach 数据驱动渲染,项与项之间用 竖向 Divider().vertical(true).height(28) 分隔(最后一项后不画),layoutWeight(1) 保证三等分。语义化着色 让用户在 0.5 秒内把握整体状态分布。
四、TabBar —— 筛选标签栏
@Builder
TabBar() {
Row({ space: 0 }) {
ForEach(this.tabs, (t: string, idx: number) => {
Column({ space: 6 }) {
Text(t).fontSize(13)
.fontColor(this.activeTab === idx ? C.primary : C.textDim)
.fontWeight(this.activeTab === idx ? FontWeight.Bold : FontWeight.Normal)
Column().width(24).height(2).borderRadius(1)
.backgroundColor(this.activeTab === idx ? C.primary : 'transparent')
}
.layoutWeight(1).padding({ top: 8, bottom: 8 })
.onClick(() => { this.activeTab = idx; })
}, (t: string) => t)
}
.width('100%').backgroundColor(C.card).borderRadius(D.rSm)
.border({ width: 1, color: C.stroke })
.margin({ top: 10, left: D.pad, right: D.pad })
}
4.1 Indicator 设计规范
底部选中指示器遵循 Material Design Tabs 规范:
- 固定宽度 24vp、高度 2vp、圆角 1vp:形成"浮动指示器"效果
- 未选中时透明而非消失:
backgroundColor('transparent')保持布局稳定,避免切换时跳动 - 文字同步变化:选中项绿色加粗(
C.primary+FontWeight.Bold),未选中灰色常规
整个 TabBar 是白底圆角卡片(C.card + D.rSm + C.stroke 边框),三个标签 layoutWeight(1) 三等分。
4.2 数据过滤逻辑
注意:当前源码中切换 Tab 只更新 activeTab 状态,列表并未实际过滤——5 条记录始终全部显示。若要实现真正的筛选,可配合计算:
// activeTab: 0→全部, 1→进行中, 2→已完成
let filtered = this.activeTab === 0 ? this.records
: this.records.filter(r => r.status === (this.activeTab === 1 ? '进行中' : '已完成'));

五、Scroll 列表与 ForEach keyGenerator
Scroll() {
Column({ space: 12 }) {
ForEach(this.records, (item: RecordItem) => {
this.RecordCard(item)
}, (item: RecordItem) => item.id.toString()) // keyGenerator
Blank().height(this.safeBottom + 20)
}
.width('100%').padding({ left: D.pad, right: D.pad, top: 6 })
}
.layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top)
列表直接内嵌在 build() 的 Scroll 中(没有单独的 RecordList Builder),.layoutWeight(1) 让滚动区占满头部/统计/TabBar 以下的剩余空间,scrollBar(BarState.Off) 隐藏滚动条。keyGenerator 使用 item.id.toString() 作为唯一标识,确保数据变化时 ArkUI 能正确执行增量 DOM diff 而非重建整个列表。列表末尾的 Blank().height(this.safeBottom + 20) 为底部安全区留白。

六、RecordCard —— 订单卡片的条件渲染
@Builder
RecordCard(item: RecordItem) {
Column({ space: 10 }) {
// 第一行:emoji 图标 + 标题/描述/时间 + 状态标签 + 箭头
Row({ space: 12 }) {
Row() { Text(item.emoji).fontSize(24) }
.width(44).height(44).backgroundColor(C.cardSoft).borderRadius(D.rSm).justifyContent(FlexAlign.Center)
Column({ space: 4 }) {
Row() {
Text(item.title).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text).layoutWeight(1)
Text(item.status).fontSize(10).fontColor('#FFFFFF')
.backgroundColor(item.statusColor)
.padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(D.rSm)
}.width('100%')
Text(item.desc).fontSize(12).fontColor(C.textSub)
Text(`⏱ ${item.time}`).fontSize(10).fontColor(C.textDim)
}.alignItems(HorizontalAlign.Start).layoutWeight(1)
Text('›').fontSize(20).fontColor(C.textDim)
}.width('100%')
// 第二行:进度条(仅进行中)
if (item.status === '进行中') {
Row() {
Text('进度').fontSize(10).fontColor(C.textDim)
Stack({ alignContent: Alignment.Start }) {
Column().width('100%').height(4).backgroundColor(C.cardSoft).borderRadius(2)
Column().width('75%').height(4).borderRadius(2).backgroundColor(C.warn)
}.layoutWeight(1).margin({ left: 8, right: 8 })
Text('75%').fontSize(10).fontColor(C.warn).fontWeight(FontWeight.Bold)
}.width('100%')
}
// 第三行:操作按钮
Row({ space: 10 }) {
Button('查看详情')
.fontSize(12).fontColor('#FFFFFF').backgroundColor(C.primary)
.borderRadius(D.rSm).height(34).layoutWeight(1)
.onClick(() => { promptAction.showToast({ message: item.title }); })
Button('更多')
.fontSize(12).fontColor(C.textSub).backgroundColor(C.cardSoft)
.borderRadius(D.rSm).height(34).layoutWeight(1)
.onClick(() => { promptAction.showToast({ message: '更多操作' }); })
}.width('100%')
}
.width('100%').padding(12).backgroundColor(C.card).borderRadius(D.rMd)
.border({ width: 1, color: C.stroke })
}
6.1 三种条件渲染方式对比
| 方式 | 适用场景 | 性能特点 |
|---|---|---|
| if/else | 完全不同的 UI 结构 | 销毁旧节点,创建新节点 |
| visibility | 相同结构仅显隐 | 保留 DOM,切换 display |
| opacity | 需要过渡动画 | 保留 DOM,改透明度 |
本例使用 if/else 是正确的——"进行中"有进度条而"已完成"没有,UI 结构确实不同。
6.2 状态标签色彩系统
状态标签是 白字 + 语义色背景 的胶囊样式,颜色直接取自数据字段 item.statusColor:
| 状态 | 文字色 | 背景色 |
|---|---|---|
| 进行中 | 白色 | C.warn(琥珀) |
| 已完成 | 白色 | C.ok(绿色) |
把颜色存进数据而非在模板里写三元表达式,是 "数据即样式" 的极简实践——新增状态(如"已取消"红色)只需扩展数据,无需改动渲染代码。
6.3 进度条:Stack 双层实现
Text('进度') → Stack[ 底层轨道 100% × 4vp 灰色 | 上层填充 75% × 4vp 橙色 ] → Text('75%')
进度条用 Stack 叠放两层 Column 实现:底层是 width('100%') 的灰色轨道(C.cardSoft),上层是 width('75%') 的橙色填充(C.warn),alignContent: Alignment.Start 保证填充从左端开始。轨道高度 4vp 极为纤细,清晰可见又不厚重。填充色用琥珀色 C.warn 与"进行中"标签呼应。
七、按钮组交互模式
每个 RecordCard 底部双按钮遵循 Material Design "Filled + Tonal Button" 规范:
| 属性 | 主按钮(查看详情) | 次按钮(更多) |
|---|---|---|
| 背景色 | 绿色 C.primary | 浅灰 C.cardSoft |
| 文字色 | 白色 #FFFFFF | 灰色 C.textSub |
| 视觉权重 | 强(行动召唤) | 弱(辅助入口) |
两者共享相同尺寸规格(fontSize(12), height(34), borderRadius(D.rSm), layoutWeight(1)),看起来像一个整体。两个按钮均已绑定点击事件:主按钮弹出订单标题 toast,次按钮弹出"更多操作" toast。

八、"UI = f(state)" 数据驱动模式
整个订单页大量使用 "数据 → 样式" 动态映射:
// 状态颜色 → 标签背景(颜色存于数据字段)
.backgroundColor(item.statusColor)
// 状态 → 是否渲染
if (item.status === '进行中') { /* 进度条 */ }
// 记录数 → 副标题文案
Text(`${this.records.length} 条记录`)
所有视觉表现由数据状态唯一确定,不存在游离于状态之外的 UI 变量。这正是声明式 UI 的核心优势。
九、边界状态扩展思考
空状态:当过滤后的记录为空时展示引导内容(当前源码未实现,属扩展方向):
Column() {
Text('📭').fontSize(48)
Text('暂无订单记录').fontSize(16).fontColor(C.textSub)
Button('立即创建').onClick(() => { /* 切换 tab */ })
}.justifyContent(FlexAlign.Center)
加载状态:请求期间显示 LoadingProgress()。
精心设计的空状态能将"无数据"的负面体验转化为引导操作的正面动力。
十、订单状态机扩展
实际产品的完整状态机:
| 状态 | 标签色 | 操作 |
|---|---|---|
| pending 待支付 | 灰色 | 支付/取消 |
| queued 排队中 | 橙色 | 查看位置 |
| printing 打印中 | 蓝色 | 实时进度 |
| completed 已完成 | 绿色 | 评价/再打印 |
| failed 失败 | 红色 | 重打/退款 |
关键原则:每种状态都有明确的视觉标识和可用的操作路径。
总结
订单页展示了 ArkUI 列表开发的完整模式:StatsCard 提供数据概览、TabBar 实现状态筛选、RecordCard 通过条件渲染适配不同状态、Stack 双层进度条可视化完成进度。其中 if/else 条件渲染、statusColor 数据驱动配色 和 Stack 双层进度条 是最具复用价值的技术模式。
更多推荐




所有评论(0)