news 2026/9/9 14:20:35

鸿蒙原生应用 ArkTS 严格模式:投票页的单选、提交与 voted 锁定

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
鸿蒙原生应用 ArkTS 严格模式:投票页的单选、提交与 voted 锁定

鸿蒙原生应用 ArkTS 严格模式:投票页的单选、提交与 voted 锁定

App 22「校园投票管理」投票 Tab(Func1Tab),是本系列最完整的"单选 → 提交 → 锁定"投票交互。整页用 Header + TitleCard(标题/票数/剩余/规则)+ 候选项列表(4 个候选人 + ⬜/✅ 复选框 + 进度条 Progress 跟随 picked 变色)+ 提交按钮(picked<0 拦截、已投票变绿"已投票 ✓")。本篇基于22-campus-vote/entry/src/main/ets/pages/Func1Tab.ets(约 128 行)逐段拆解,附 4 张实机截图(未选/选张/选王/已投票 4 种状态演示)。

一、整体结构:4 个 @Builder + 2 状态字段

Func1Tab 是**"投票交互"页面**——用户勾选候选人 → 进度条实时变色 → 提交后整页"锁定"(不能再选):

build() { Column() { this.Header() Scroll() { Column({ space: 16 }) { this.TitleCard() this.SectionTitle('候选项') this.CandidateList() this.SubmitBtn() } .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 个块按"信息 → 选项 → 提交"漏斗展开

  1. Header— "参与投票"标题
  2. TitleCard— 投票标题 🏆 + 1,820 票 + 剩 3 天 + 规则
  3. SectionTitle('候选项') + CandidateList— 4 个候选人
  4. SubmitBtn— 提交按钮(已投票变"已投票 ✓"绿)

2 个 @Statepicked/voted)——**"2 个状态控制整页"**是本页最大亮点。

项目源码开源:https://gitee.com/codenestFlow/HarmonyOSHub

二、Header + TitleCard:投票信息

Header 是"参与投票"单行(与系列同款)。

TitleCard是投票信息卡:

@Builder TitleCard() { Column({ space: 8 }) { Text('🏆 最佳校园歌手评选').fontSize(18).fontWeight(FontWeight.Bold).fontColor(C.text) Row({ space: 8 }) { Text('1,820 票').fontSize(13).fontColor(C.textDim) Text('剩 3 天').fontSize(13).fontColor(C.warn) } .width('100%') Text('每人限投 1 票,投票后不可修改').fontSize(12).fontColor(C.textDim) } .width('100%') .padding(16) .backgroundColor(C.card) .borderRadius(D.rLg) .border({ width: 1, color: C.stroke }) }

3 行信息

  • 第 1 行:投票标题 🏆("最佳校园歌手评选")——"emoji + 文字"是 Header 风格延续
  • 第 2 行:1,820 票(灰)+ 剩 3 天(橙C.warn)——"票数 + 剩余"双指标
  • 第 3 行:规则"每人限投 1 票,投票后不可修改"(灰)——"投票规则"是投票场景的合规提示(对比问卷"每人限填 1 次")

"1,820 票"是当前总票数(与首页首页"1,856 票"略有差异——首页是 App 启动时初始值、投票页是 Func1Tab 实例化时的值)——"跨页数据不完全一致"是 demo 的小瑕疵

"剩 3 天"用C.warn——"剩余时间 = 警示色"的延续(首页同款)——"截止前 3 天"已是中等警示(< 1 天才是真紧迫)。

"投票后不可修改"是合规规则——真实项目应服务端验证(防止前端绕过)——"投票一次性"是大多数选举/评比的规则(一次投票不重复)。

三、CandidateList:4 候选人 + ⬜ 复选框 + 进度条(本页核心)

CandidateList 是4 个候选人的列表,核心交互在 onClick 的 picked 状态联动:

@Builder CandidateList() { Column({ space: 12 }) { ForEach(this.candidates, (c: Candidate) => { Column({ space: 8 }) { Row({ space: 10 }) { Text(this.picked === c.id ? '✅' : '⬜').fontSize(20) .onClick(() => { if (!this.voted) { this.picked = c.id; } }) Text(c.name).fontSize(14).fontColor(C.text).layoutWeight(1) Text(c.votes + '票').fontSize(12).fontColor(C.primary) } .width('100%') Progress({ value: c.pct, total: 100 }) .color(this.picked === c.id ? C.primary : C.stroke).width('100%') } .width('100%') .padding(14) .backgroundColor(this.picked === c.id ? C.primarySoft : C.card) .borderRadius(D.rMd) .border({ width: 1, color: this.picked === c.id ? C.primary : C.stroke }) }, (c: Candidate) => c.id.toString()) } .width('100%') }

3.1 4 个候选人的数据

private candidates: Candidate[] = [ { id: 1, name: '张同学 · 计算机学院', votes: 680, pct: 37 }, { id: 2, name: '李同学 · 艺术学院', votes: 520, pct: 28 }, { id: 3, name: '王同学 · 文学院', votes: 380, pct: 21 }, { id: 4, name: '赵同学 · 外语学院', votes: 240, pct: 14 } ];

4 个候选人覆盖 4 个学院——**"候选人 = 学院代表"**让投票变成"学院之战"(增加投票参与度)——真实项目应给候选人加头像/简介/竞选口号

4 个票数 37%+28%+21%+14% = 100%——百分比严格自洽(4 个候选人之和 100%)。

4 学院票数差异:计算机(680)> 艺术(520)> 文(380)> 外语(240)——"计算机学院人多 + 技术宅活跃"是合理的数据故事(真实校园里计算机学院常是最大院系)。

3.2 ⬜/✅ 复选框的状态联动

Text(this.picked === c.id ? '✅' : '⬜').fontSize(20).onClick(() => { if (!this.voted) { this.picked = c.id; } })——这是整个投票页的核心交互

Text(this.picked === c.id ? '✅' : '⬜').fontSize(20) .onClick(() => { if (!this.voted) { this.picked = c.id; } })

3 个关键设计

  1. picked === c.id决定 ⬜/✅——picked是"被选中的候选人 id",id 匹配的项显示 ✅、其他显示 ⬜——"单选语义"

  2. if (!this.voted)拦截——已投票(voted = true)后不能修改——**"投票后锁定"**的真实业务约束

  3. onClick只绑在 ⬜ 文本——整行其他位置(候选人名/票数)不响应点击——只点 ⬜/✅ 区域才能选——**"点击区域限制"**防止误操作

注意:这里没绑定整行 onClick——与 App 21 问卷"整行可点"不同——投票要求"精确点击 ⬜"(避免用户误选),问卷"整行可点"(降低操作门槛)。

3.3 进度条跟随 picked 变色

Progress({ value: c.pct, total: 100 }) .color(this.picked === c.id ? C.primary : C.stroke).width('100%')

进度条颜色随 picked 联动

  • 未选中的候选人C.stroke浅灰(视觉弱化)
  • 被选中的候选人C.primary紫(视觉强调)

"未选中 = 灰、选中 = 紫"——**"进度条颜色 = 选中状态"**的视觉映射。

3.4 整卡背景的"选中态"

.backgroundColor(this.picked === c.id ? C.primarySoft : C.card) .borderRadius(D.rMd) .border({ width: 1, color: this.picked === c.id ? C.primary : C.stroke })

**整张候选人卡的"三件套"**全部跟随 picked 联动:

  • 背景C.primarySoft浅紫 vsC.card
  • 边框C.primary紫 vsC.stroke
  • 进度条颜色C.primary紫 vsC.stroke

"3 处联动 = 强烈的选中视觉"——被选中的候选人在 4 张卡里"脱颖而出"。

对比 App 18 同款"选中三件套"(背景/边框/文字)——App 22 是"背景/边框/进度条"——"三件套 = 视觉强调的通用模式"

四、SubmitBtn:双态按钮 + 校验 + 锁定

SubmitBtn 是双态按钮(未投票/已投票)+ 校验 + 锁定:

@Builder SubmitBtn() { Button(this.voted ? '已投票 ✓' : '提交投票') .width('100%').height(48) .fontSize(16).fontColor('#FFFFFF') .backgroundColor(this.voted ? C.ok : C.primary) .borderRadius(D.rMd) .onClick(() => { if (this.picked < 0) { promptAction.showToast({ message: '请先选择候选项' }); return; } this.voted = true; promptAction.showToast({ message: '投票成功!' }); }) }

按钮双态

  • 未投票→ "提交投票" 紫底
  • 已投票→ "已投票 ✓" 绿底(C.ok)——绿色 = 成功(与 App 19 作品完成、App 18 互关成功同款语义)

校验 + 早返回

if (this.picked < 0) { promptAction.showToast({ message: '请先选择候选项' }); return; } this.voted = true; promptAction.showToast({ message: '投票成功!' });
  • 未选(picked < 0→ 拦截 + Toast "请先选择候选项"
  • 已选voted = true锁定 + Toast "投票成功!"

picked初始值-1("无选中"用 -1 标记)——onClick设置picked = c.id(正整数 1-4)——"-1 是合法值(无选中)"——比用null更省心(数字比较 vs 对象比较)。

4.1 提交后的"3 重锁定"

提交成功后,voted = true触发 3 重连锁反应:

1. 候选人不可改if (!this.voted) { this.picked = c.id; }——voted=true后 onClick 拦截,再点 ⬜/✅ 无效

2. 按钮变绿:按钮文字/背景变 "已投票 ✓" + 绿底

3. 进度条不响应:进度条颜色仍由picked决定(不是 voted)——但因为候选人锁定,整个页面进入"展示态"——"投票完成"是只读态

"1 状态变 → 3 联动"是"提交后锁定"的完整范式——"状态驱动 + 多处联动"是 ArkUI 的核心红利

五、@State 的 2 字段设计

Func1Tab 有2 个 @State

@State类型用途联动
pickednumber选中的候选人 id(-1=未选、1-4=已选)4 张卡的 ⬜/✅ + 进度条 + 背景 + 边框
votedboolean是否已投票按钮文案/颜色 + 候选人锁定

picked影响 4 张卡的 3 个属性(⬜/✅ + 进度条 + 背景)——picked一变,4 张卡整体重渲染。voted影响按钮 + 候选人可交互性——voted=true后整页进入"只读态"。

2 个状态 = 整页交互——**"少状态、强联动"的问卷设计器(App 21)和"投票页 2 状态控制"**的设计哲学一致——业务复杂度低时,少状态 + 强联动比多状态 + 弱联动更优雅

六、4 状态实机演示(本文 4 张图)

本篇 4 张图是App 22 投票页的 4 种状态——真实展示 picked/voted 联动的视觉变化

  1. top(初始态):4 个 ⬜ 复选框 + 灰色进度条 + "提交投票"紫按钮
  2. mid(选张同学):第 1 张变 ✅ + 紫进度条 + 浅紫背景 + 紫边框
  3. lower(选王同学):第 3 张变 ✅ + 紫进度条 + 浅紫背景 + 紫边框(张同学回 ⬜)
  4. bottom(已投票):王同学保持 ✅ + "已投票 ✓" 绿按钮 + "投票成功!" Toast

4 张图是同一页面的 4 帧——读者可对比看"未选→选中→换选→已投票"的状态变化——"交互演示"比静态截图更有信息量

七、投票页的"投票后只读"与"问卷后跳转"的对比

App 22 投票 vs App 21 问卷——两种"任务型"应用的提交后行为

维度App 22 投票App 21 问卷
提交后页面锁定只读跳转下一页
提交后用户看结果(结果 Tab)继续填/看统计
提交后状态voted=true 持久不持久
重复提交禁止(拦截)通常允许

"投票后只读"是投票的合规要求(一人一票、不可改)——"问卷后跳转"是问卷的常规流程(填完看结果)——"业务规则决定提交后行为"

八、Progress 进度条的双重作用

Progress({ value: c.pct, total: 100 }).color(this.picked === c.id ? C.primary : C.stroke)

  • 作用 1:显示当前票数占比(37% / 28% / 21% / 14%)
  • 作用 2:跟随 picked 变色(未选灰/选中紫)

"一个组件承担两个职责"——Progress既是"数据可视化"(显示比例)也是"交互反馈"(选中态颜色)——真实项目应拆分:单独的"票数进度"和单独的"选中态高亮"(避免混淆)。

App 22 的简化:demo 用同一个 Progress 兼任两职——读者做产品时可拆成"背景进度条(始终显示)+ 选中态前景高亮"两层。

九、投票交互的 5 个最佳实践

App 22 投票页的设计遵循 5 个最佳实践:

  1. 限制点击区域(只 ⬜ 可点)——避免误选
  2. "选中三件套"(背景/边框/进度条)——视觉强反馈
  3. 早返回校验(picked<0 拦截)——避免空提交
  4. 提交后锁定(voted=true 整体只读)——投票合规
  5. 按钮双态(紫"提交"/绿"已投票")——状态可视化

**"5 步投票交互设计"**值得读者在所有"单选 → 提交 → 锁定"场景复用(投票/投票/确认/支付/同意协议……)。

十、picked/voted 2 状态 vs 系列其他页面的状态对比

回顾系列各 Tab 的 @State 数量与"状态联动深度":

App/Tab@State状态联动
App 11 快递1物流状态切换
App 12 ACG1标签筛选
App 13 音乐1搜索
App 14 运动1Tab 视觉
App 15 环保1联动预览卡
App 16 商城6联动积分计算链
App 17 志愿1Tab 视觉
App 18 社交6联动预览卡 + 实时计算
App 19 摄影1联动封面图
App 20 阅读1搜索
App 21 问卷1联动条件渲染
App 22 首页1Tab 视觉
App 22 投票2联动 ⬜/✅ + 进度条 + 背景 + 边框 + 按钮
App 22 结果0纯展示
App 22 我的0纯展示

App 22 投票页的 2 @State 是"精确控制"型——picked 决定 4 张卡的视觉(⬜/✅/进度条/背景/边框),voted 决定按钮 + 锁定——"2 状态覆盖整页交互"

对比 App 16 商品兑换页(6 @State 控制 5 处联动)vs App 22 投票页(2 @State 控制 5 处联动)

  • App 16:productId联动预览卡 +quantity联动积分 +addrId/name/phone/note联动表单 + 校验——6 状态各管一处
  • App 22:picked联动 4 张卡 +voted联动按钮——2 状态各管多处

**"2 状态控制 5 处"比"6 状态控制 5 处"更优雅——状态数量 ≠ 业务复杂度——"少状态 + 强联动"**是设计水平的体现。

十一、picked 用 -1 而非 null 的工程考量

@State picked: number = -1;

用 -1 而非 null/undefined是 ArkTS 状态管理的常见做法:

  • 数字比较简单picked < 0判断"未选"、picked >= 0判断"已选"
  • 数字有默认值:-1 是合法数字,初始化不报错
  • 避免空值陷阱:null/undefined 在模板里要加?判空,数字不用

对比

  • @State picked: number | null = null——picked ? ... : ...判空更繁琐
  • @State picked: number = -1——picked < 0更简洁

"哨兵值(sentinel value)模式"——用 -1 作为"无值"的标志——比 null 简单——但要小心 -1 与合法值冲突(如果 id 可能是负数就要用别的哨兵)。

十二、投票页与系列其他表单的对比

App 22 投票页与系列其他"创建/填写"页对比:

App表单类型@State特殊交互
13 音乐创建多字段5类型/优先级/模板
14 场地预约多字段6数量/地址/动态积分
16 商品兑换多字段6数量+积分实时计算
17 活动创建多字段5模板
19 作品发布多字段6场景+滤镜预览
20 书库多字段5模板
21 问卷创建1 字段14 种题型条件渲染
22 投票2 字段2单选 + 提交锁定

"投票页"是系列最简的填写页——2 状态就够了——"投票的填写 = 选一个人"——比"音乐创建(5 字段)"或"商品兑换(6 字段)"简单得多。

"投票 = 极简填写"的业务意义

  • 用户来投票,不愿意填 10 个字段
  • 投票的核心是"选谁",不是"填什么"
  • 1-2 步完成 → 投票转化率高

真实投票平台的"1 步完成"原则"尽量让用户用最少的点击完成任务"——App 22 的"1 次 ⬜ + 1 次提交 = 2 步"是投票的最佳实践。

十三、UI 组件的"语义复用"vs"角色复用"

App 22 投票页的 ⬜/✅ 文本是**"语义复用"**——同一个 Text 元素在不同状态显示不同内容:

  • 未选:(语义"未选")
  • 已选:(语义"已选")

"语义复用" vs "角色复用"

  • 语义复用:同一个组件承担"相似但不同"的语义(⬜/✅ 都是"复选框语义")
  • 角色复用:同一个组件承担"完全不同"的角色(按钮既做"提交"又做"显示已投票")

App 22 投票页的 ⬜/✅ 是语义复用(表达"选/未选")——按钮文字是角色复用("提交投票" + "已投票 ✓"两个角色)——两种复用模式都用得上

"语义复用"在排版上更紧凑(同一位置不重排),"角色复用"在功能上更高效(同一按钮覆盖多个状态)——读者设计时根据场景选择。

十四、总结

App 22 投票交互页解析完毕。2 状态(picked/voted)驱动 4 张卡的 ⬜/✅ + 进度条 + 背景 + 边框 + 按钮是核心交互。"提交后锁定"是投票合规的必然要求,"早返回校验 + 双态按钮"是 2 状态控制的标准模式"投票 = 极简填写(1 步完成)"是投票平台的转化率关键

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 14:19:23

Android Build Tools离线安装:解决“组件未安装”报错的完整指南

简介&#xff1a;面向Android应用开发者的SDK Build-Tools 29.0.2离线补全包&#xff0c;针对Android 10&#xff08;API级别29&#xff09;的构建链&#xff0c;解决本地SDK缺少build-tools组件时无法正常编译、打包和签名的问题。压缩包共173个文件&#xff0c;主要类型包括C…

作者头像 李华
网站建设 2026/9/9 14:18:47

Java接口自动化测试从入门到实战:框架搭建与断言规范

1. 先理清头绪&#xff1a;Java接口自动化到底在学什么做Java接口自动化测试这件事&#xff0c;我从带过的新人身上总结出一个规律&#xff1a;大多数人来问的时候&#xff0c;手里可能已经会一点Python、懂一点Postman&#xff0c;或者干脆是纯手工测试出身&#xff0c;被公司…

作者头像 李华
网站建设 2026/9/9 14:18:43

TDOA/FDOA联合定位仿真:TSWLS算法改进与CRLB性能对比

先说点背景。这个项目是我最近用MATLAB完整跑下来的一套TDOA/FDOA联合定位仿真分析&#xff0c;核心对象是经典的TSWLS两步加权最小二乘算法&#xff0c;以及我用约束修正加最大似然牛顿精化组合出来的改进方法。场景是无源定位里很典型的一种&#xff1a;目标辐射源运动&#…

作者头像 李华
网站建设 2026/9/9 14:17:44

IoT上行信道协议全解析:从LoRaWAN到MQTT的链路与实战

IOT上行信道的协议刨析做物联网开发这几年&#xff0c;我花在“上行”上的调试时间远比“下行”多。原因很简单——设备端的上行信道要面对的是极其复杂的环境&#xff1a;弱网、高延迟、丢包、断电重连、数据乱序&#xff0c;任何一个环节出问题&#xff0c;数据都到不了云端。…

作者头像 李华
网站建设 2026/9/9 14:17:37

基于朴素贝叶斯的垃圾邮件过滤系统设计与实现

1. 项目定位与整体思路&#xff1a;这个毕设到底做了什么如果你正在准备计算机毕业设计答辩&#xff0c;或者刚开始接触机器学习相关的课程设计&#xff0c;这个题目应该不陌生&#xff1a;基于贝叶斯的垃圾邮件过滤的设计与实现。标题里挂了“大数据”和“深度学习”两个热门标…

作者头像 李华
网站建设 2026/9/9 14:17:36

PAJ7620手势识别传感器开源项目实战:从工程导入到避坑指南

简介&#xff1a;压缩包内含一套面向初中级Arduino开发者的PAJ7620手势识别传感器库与示例工程&#xff0c;PAJ7620U2单芯片即可识别9种基本手势&#xff0c;适用于智能车、机器人、手势交互装置等非接触式控制场景。整个包共8个文件&#xff0c;包括2个示例ino程序&#xff08…

作者头像 李华