ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

鸿蒙原生应用 ArkUI 声明式实战:不引图表库手写兑换记录页积分消耗趋势图

鸿蒙原生应用 ArkUI 声明式实战:不引图表库手写兑换记录页积分消耗趋势图 鸿蒙原生应用 ArkUI 声明式实战不引图表库手写兑换记录页积分消耗趋势图App 16「校园积分商城」兑换 TabFunc2Tab是兑换订单的历史列表页。整页用 Header 4 个胶囊筛选 Tab全部/待发货/已发货/已完成 3 栏统计概览 积分消耗趋势条形图总计 3,700 6 条兑换记录时间线每条记录按状态显示不同操作待发货→取消兑换/修改地址、已发货→查看物流/运输中、已完成→再次兑换/✓已完成每条含数量/消耗积分/收货地址。本篇基于16-points-mall/entry/src/main/ets/pages/Func2Tab.ets约 260 行逐段拆解附 4 张实机截图。一、整体结构筛选 统计 趋势 时间线Func2Tab 与 App 14/15 记录页同构Header TabBar 固定、Scroll 内容流趋势图替代占比图build() { Column() { this.Header() this.TabBar() Scroll() { Column({ space: 16 }) { this.StatsOverview() this.PointsTrend() this.SectionTitle(兑换记录) this.RecordTimeline() } .width(100%) .padding({ left: D.pad, right: D.pad, top: 16, bottom: D.pad this.safeBottom 20 }) } .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top) } .width(100%).height(100%).backgroundColor(C.bg) }4 个模块StatsOverview3 栏统计→PointsTrend积分消耗趋势——本页特色→ SectionTitle(兑换记录) → RecordTimeline6 条时间线。与 App 14 预约页的差异App 14 是统计 → 时间线两段App 15 是统计 → 占比 → 时间线三段App 16 是统计 →趋势→ 时间线三段——占比构成分析换成趋势时间变化——App 15 回答分类的都是什么App 16 回答积分怎么花掉的。项目源码开源https://gitee.com/codenestFlow/HarmonyOSHub二、Header TabBar4 个胶囊筛选Header 单行标题兑换记录TabBar 是 4 个胶囊全部/待发货/已发货/已完成Builder TabBar() { Row() { ForEach(this.tabs, (t: FilterTab) { Text(t.label) .fontSize(13) .fontColor(this.activeTab t.id ? C.primary : C.textSub) .fontWeight(this.activeTab t.id ? FontWeight.Bold : FontWeight.Normal) .padding({ left: 14, right: 14, top: 8, bottom: 8 }) .backgroundColor(this.activeTab t.id ? C.primarySoft : transparent) .borderRadius(16) .onClick(() { this.activeTab t.id; }) }, (t: FilterTab) t.id.toString()) } .width(100%) .padding({ left: D.pad, right: D.pad, top: 12, bottom: 12 }) .backgroundColor(C.card) }4 个 Tab全部/待发货/已发货/已完成——物流状态视角区别于 App 14 的待确认/已完成/已取消业务状态、App 15 的已分类/待处理/错误分类处理状态。状态分类跟随业务App 14 关注预约确认、App 15 关注分类处理、App 16 关注物流发货——每个 App 的状态机不同。id status 值0 全部/1 待发货/2 已发货/3 已完成filtered()一行过滤r.status this.activeTab——Tab id 状态值模板第 3 次复用。三、StatsOverview3 栏统计StatsOverview 与 App 14/15 同构3 栏数字 标签private stats: StatItem[] [ { value: 12, label: 总兑换 }, { value: 8, label: 已发货 }, { value: 2, label: 待发货 } ];总兑换 12 / 已发货 8 / 待发货 2——与首页已兑换 12一致 ✓。已发货 8 待发货 2 10 ≠ 12——其余 2 条是已完成82212 才对demo 数据已完成未计入统计——小瑕疵统计只列了总/发货中/待发货三项没把已完成单独列出已完成 4 条在时间线里有但统计口径里已发货 8可能包含已完成口径不清晰——不过 demo 场景无伤大雅。3 栏同色C.text颜色留给状态。四、PointsTrend积分消耗趋势本页特色PointsTrend 是 7 天积分消耗的条形图Builder PointsTrend() { Column({ space: 12 }) { Row() { Text(积分消耗趋势).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text) Blank() Text(总计 3,700).fontSize(12).fontColor(C.primary) } .width(100%) Row({ space: 4 }) { ForEach([30, 55, 40, 70, 85, 60, 45], (v: number, idx: number) { Column({ space: 6 }) { Stack({ alignContent: Alignment.Bottom }) { Column() .width(18).height(v) .linearGradient({ angle: 180, colors: [[C.primary, 0.0], [C.accent, 1.0]] }) .borderRadius(4) } .width(24).height(60) Text([一, 二, 三, 四, 五, 六, 日][idx]).fontSize(10).fontColor(C.textDim) } .layoutWeight(1) .alignItems(HorizontalAlign.Center) }, (v: number, idx: number) idx.toString()) } .width(100%) } .width(100%) .padding(14) .backgroundColor(C.card).borderRadius(D.rMd) .border({ width: 1, color: C.stroke }) }与前几代 WeeklyChart 的 3 个差异1. 内联数组ForEach([30, 55, 40, 70, 85, 60, 45], ...)直接写数组字面量没有private barData——**一次性数据内联**的取舍数据只在 Chart 用、不需要复用内联更短但可读性略差。2. 内联星期[一, 二, 三, 四, 五, 六, 日][idx]数组索引取星期——7 个字符串直接写在 ForEach 里。索引取数代替数据字段BarItem 没有 day 字段了靠 idx 取星期。3. 高度 60 而非 90.width(24).height(60)柱子容器更矮——趋势图更紧凑记录页空间紧张图表让位。总计 3,700的数学30554070856045 385 ≠ 3700——总计 3,700与柱子数据不匹配柱子是消耗强度30-85总计却写 3,700 积分这是 demo 的数据瑕疵——柱子是强度百分比30-85总计是真实积分3,700两个口径。App 15 也有同类问题趋势 68% vs 总计 47 次。图表数据与汇总口径不一致是 demo 高频坑值得反复提醒要么柱子真实值30 积分/55 积分…总计 385要么总计柱子之和。图表本身Stack 底部对齐 渐变玫红与前几代同构。五、RecordTimeline三态物流记录RecordTimeline 是 6 条兑换记录的时间线每条含数量、消耗积分、收货地址、状态操作Builder RecordTimeline() { Column({ space: 0 }) { ForEach(this.filtered(), (r: Record, idx: number) { Column({ space: 0 }) { if (idx 0) { Row().width(2).height(12).backgroundColor(C.stroke).margin({ left: 18 }) } Row({ space: 12 }) { Column() { Row() { Text(r.emoji).fontSize(18) } .width(36).height(36) .backgroundColor(C.primarySoft).borderRadius(18) .justifyContent(FlexAlign.Center) } .width(36) Column({ space: 8 }) { Row({ space: 8 }) { Text(r.product).fontSize(14).fontWeight(FontWeight.Medium).fontColor(C.text) Text(x r.qty).fontSize(12).fontColor(C.textDim) Blank() Text(r.statusText) .fontSize(11).fontColor(r.statusColor) .padding({ left: 8, right: 8, top: 3, bottom: 3 }) .backgroundColor(C.primarySoft).borderRadius(8) } .width(100%) Row({ space: 6 }) { Text(消耗).fontSize(11).fontColor(C.textDim) Text(- r.points 积分).fontSize(12).fontColor(C.primary).fontWeight(FontWeight.Medium) Text(· r.date r.time).fontSize(12).fontColor(C.textDim) } Text(收货: r.addr).fontSize(11).fontColor(C.textDim) if (r.status 1) { Row({ space: 8 }) { Button(取消兑换) .fontSize(12).fontColor(C.textSub).backgroundColor(C.cardSoft) .borderRadius(D.rSm).height(30) .onClick(() { promptAction.showToast({ message: 取消兑换 }); }) Button(修改地址) .fontSize(12).fontColor(#FFFFFF).backgroundColor(C.primary) .borderRadius(D.rSm).height(30) .onClick(() { promptAction.showToast({ message: 修改地址 }); }) } .width(100%) } if (r.status 2) { Row({ space: 8 }) { Button(查看物流) .fontSize(12).fontColor(#FFFFFF).backgroundColor(C.primary) .borderRadius(D.rSm).height(30) .onClick(() { promptAction.showToast({ message: 查看物流 }); }) Text(运输中).fontSize(11).fontColor(C.primary) } .width(100%) } if (r.status 3) { Row({ space: 8 }) { Button(再次兑换) .fontSize(12).fontColor(#FFFFFF).backgroundColor(C.primary) .borderRadius(D.rSm).height(30) .onClick(() { promptAction.showToast({ message: 再次兑换 r.product }); }) Text(✓ 已完成).fontSize(11).fontColor(C.ok) } .width(100%) } } .alignItems(HorizontalAlign.Start).layoutWeight(1) } .width(100%) .padding({ left: 14, right: 14, top: 14, bottom: 14 }) .backgroundColor(C.card).borderRadius(D.rMd) .border({ width: 1, color: C.stroke }) } .width(100%) }, (r: Record) r.id.toString()) } .width(100%) }5.1 记录头商品 数量 状态标签Row({ space: 8 }) { Text(r.product).fontSize(14).fontWeight(FontWeight.Medium).fontColor(C.text) Text(x r.qty).fontSize(12).fontColor(C.textDim) Blank() Text(r.statusText) .fontSize(11).fontColor(r.statusColor) .padding({ left: 8, right: 8, top: 3, bottom: 3 }) .backgroundColor(C.primarySoft).borderRadius(8) }商品名 x数量x2表示 2 件——电商订单的标准数量表示。状态标签待发货橙/已发货主色/已完成绿——注意已发货用C.primary玫红进行中用主色与 App 15 的已发货用主色一致——**进行中状态用主色**是本系列记录页的通用约定待处理用橙警示、进行中用主色品牌、完成用绿成功。5.2 消耗积分 收货地址Row({ space: 6 }) { Text(消耗).fontSize(11).fontColor(C.textDim) Text(- r.points 积分).fontSize(12).fontColor(C.primary).fontWeight(FontWeight.Medium) Text(· r.date r.time).fontSize(12).fontColor(C.textDim) } Text(收货: r.addr).fontSize(11).fontColor(C.textDim)消耗 -800 积分用负号 主色——支出用负号是财务显示的标准收入 、支出 −。收货地址单独一行收货: 7 号楼 305——电商订单必须显示收货地址App 14/15 记录没有地址维度App 16 有——记录字段反映业务必须。5.3 三态操作区if (r.status 1) { Row({ space: 8 }) { Button(取消兑换) .fontSize(12).fontColor(C.textSub).backgroundColor(C.cardSoft) .borderRadius(D.rSm).height(30) .onClick(() { promptAction.showToast({ message: 取消兑换 }); }) Button(修改地址) .fontSize(12).fontColor(#FFFFFF).backgroundColor(C.primary) .borderRadius(D.rSm).height(30) .onClick(() { promptAction.showToast({ message: 修改地址 }); }) } .width(100%) } if (r.status 2) { Row({ space: 8 }) { Button(查看物流) .fontSize(12).fontColor(#FFFFFF).backgroundColor(C.primary) .borderRadius(D.rSm).height(30) .onClick(() { promptAction.showToast({ message: 查看物流 }); }) Text(运输中).fontSize(11).fontColor(C.primary) } .width(100%) } if (r.status 3) { Row({ space: 8 }) { Button(再次兑换) .fontSize(12).fontColor(#FFFFFF).backgroundColor(C.primary) .borderRadius(D.rSm).height(30) .onClick(() { promptAction.showToast({ message: 再次兑换 r.product }); }) Text(✓ 已完成).fontSize(11).fontColor(C.ok) } .width(100%) }3 个状态分支3 组操作状态主按钮次操作语义待发货1修改地址主色取消兑换灰底未发货可改可退已发货2查看物流主色运输中文字路上可跟踪已完成3再次兑换主色✓ 已完成绿字可回购关键设计——待发货双按钮取消兑换灰底次级 修改地址主色主按钮——发货前可反悔可修改物流状态机未发货 可操作窗口。已发货单按钮查看物流主色 运输中状态文字。已完成再次兑换 ✓ 已完成。对比 App 14/15 的三态App状态 1状态 2状态 314 预约待确认→取消/详情已完成→再次预约已取消→无操作15 分类已分类→✓正确待处理→立即分类错误→重新分类红16 兑换待发货→取消/改址已发货→查看物流已完成→再次兑换状态 → 操作映射每个 App 不同但模式相同状态机驱动 UI 操作。App 16 的待发货改地址是电商特有的操作物流未出可改收货信息。5.4 数据与筛选6 条记录蓝牙耳机 800 分待发货、保温杯 500 分已发货、学习笔记本 200 分 x2已完成、游戏手柄 600 分已完成、台灯 400 分已发货、校园背包 1200 分已完成——三态全覆盖 消耗积分梯度200-1200。filtered()真筛选App 14 同款。记录里的地址7 号楼 305宿舍/A 栋 302教学楼/校门口快递站自取——与商品页 AddressSelector 的 3 个地址一致✓跨页数据一致。六、物流状态机App 16 兑换页背后的物流状态机下单 → 待发货 → 已发货 → 已完成 ↘ 取消兑换退款待发货status1订单已生成、未发货——可取消退积分、可改地址已发货status2物流在途——可查看物流、不可取消已完成status3签收完成——可再次兑换发货是分水岭发货前可反悔取消/改址发货后只能跟踪/签收——**操作权限随状态收紧**是订单状态机的核心规则淘宝下单后可退、发货后只能退货运费险同理。再次兑换是复购逻辑用户用过觉得好 → 一键再换App 14再次预约同款——已完成 → 再次购买是电商留存的经典设计。七、时间线 vs 列表的取舍App 16 用时间线圆点 竖线连接而非独立卡片——因为兑换记录有时间先后08-14 → 08-02 按日期倒序时间线强化消费历史的顺序感与 App 11 快递取件、App 14 预约记录同理。记录间竖线if (idx 0) Row().width(2).height(12)高度固定 12vp——记录卡片高度不固定待发货双按钮 vs 已完成单按钮高度不同竖线可能对不齐——真实项目应让竖线layoutWeight(1)跟随内容前几篇已反复提醒的断线问题。八、与 App 14/15 记录页的对比总结维度App 14 预约App 15 分类App 16 兑换Tab待确认/已完成/已取消已分类/待处理/错误待发货/已发货/已完成特色模块无占比进度条消耗趋势图记录字段场地时间分类数量数量积分地址状态1操作取消/详情✓正确取消/改址状态2操作再次预约立即分类查看物流状态3操作无重新分类红再次兑换记录列表模板第 3 次复用App 14/15/16每次的差异化App 15 加占比、App 16 加趋势 物流状态——模板稳定 能力递进是本系列记录页的进化路径。九、记录页的财务视图设计App 16 兑换页是本系列第一个财务视图记录页——每条记录都带消耗积分消耗 -800 积分的设计细节Row({ space: 6 }) { Text(消耗).fontSize(11).fontColor(C.textDim) Text(- r.points 积分).fontSize(12).fontColor(C.primary).fontWeight(FontWeight.Medium) Text(· r.date r.time).fontSize(12).fontColor(C.textDim) }负号前缀-800——支出的通用符号收入 、支出 −用户一眼识别这是花掉的主色加粗——金额最重原则财务记录里数字是核心信息灰字时间——次要信息弱化收入视图 vs 支出视图我的页/首页看385 积分收入绿色主色兑换页看-800 积分支出玫红主色——收支双视图同一种积分两种语义。真实项目的财务记录进阶收支汇总页头显示本月支出 -3,700 / 收入 385 / 结余三行筛选维度按商品/按时间/按金额排序图表联动点击趋势图某天 → 下方列表过滤当天记录财务视图模板可复用到记账 App、钱包 App、积分系统、消费记录——任何钱/积分流向的记录场景。十、记录列表模板的系列进化回顾系列 3 个记录列表页App 14/15/16可以清晰看到**记录模板的能力递进**能力App 14App 15App 16真筛选filtered✓✓✓时间线连接✓✓✓状态差异化操作✓✓✓状态色编码单维双维分类状态单维数据图表无占比趋势业务字段场地/时长分类/数量数量/积分/地址特殊操作再次预约红色纠错取消/改址/物流/复购基础能力筛选/时间线/状态操作三连复用 业务能力占比/趋势/财务字段逐步增加——模板稳定能力递进这是系列化 demo 记录页的进化路径。读者如果要把这套模板用到自己的项目基础能力直接抄业务字段按需加。十一、小结App 16 兑换记录页解析完毕。物流三态操作取消/改址 → 物流 → 复购 积分消耗趋势 财务负号显示是三大亮点。总兑换 12 首页已兑换 12跨页一致。
返回列表