HarmonyOS 应用 · 寝室投票投票页深度解析:Radio 单选与 enabled 按钮联动
HarmonyOS 应用 · 寝室投票投票页深度解析:Radio 单选与 enabled 按钮联动
项目编号:49-dorm-vote 技术栈:HarmonyOS ArkTS · ArkUI 声明式 UI · Stage 模型 本篇页面:投票页
Func2Tab.ets(81 行) 系列导航:(一)首页 · (二)创建 · (三)投票 · (四)结果

一、Option interface 与 selected 状态
interface Option {
id: number;
emoji: string;
name: string;
}
三个字段:id 是 key 锚点,emoji 是选项图标,name 是选项名。
@StorageProp('safeTop') safeTop: number = 0;
@StorageProp('safeBottom') safeBottom: number = 0;
@State selected: number = -1;
private options: Option[] = [
{ id: 1, emoji: '🍜', name: '黄焖鸡米饭' },
{ id: 2, emoji: '🍔', name: '汉堡炸鸡' },
{ id: 3, emoji: '🍕', name: '披萨意面' },
{ id: 4, emoji: '🍛', name: '麻辣香锅' }
];
@State selected: number = -1——初始 -1 表示未选中任何选项。-1 是"哨兵值":selected >= 0 即有选中项,驱动按钮的启用/禁用。四个外卖选项:黄焖鸡/汉堡炸鸡/披萨意面/麻辣香锅。
二、页面骨架:QuestionCard + OptionList + Button
build() {
Column() {
this.Header()
Scroll() {
Column({ space: 16 }) {
this.QuestionCard()
this.OptionList()
Button(this.selected >= 0 ? '确认投票' : '请选择一个选项').fontSize(15)
.fontColor('#FFFFFF').backgroundColor(this.selected >= 0 ? C.primary : C.textDim)
.width('100%').height(48).borderRadius(D.rMd)
.enabled(this.selected >= 0)
.onClick(() => { promptAction.showToast({ message: '投票成功' }); })
}
.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)
}
selected 驱动按钮三态——文案、底色、可点性三路分支:
// 文案:确认投票 vs 请选择一个选项
Button(this.selected >= 0 ? '确认投票' : '请选择一个选项')
// 底色:粉色 vs 灰色
.backgroundColor(this.selected >= 0 ? C.primary : C.textDim)
// 可点性:启用 vs 禁用
.enabled(this.selected >= 0)
enabled() 是本页特色——系列首次使用组件禁用属性。未选中时按钮灰色 + 禁用(点击无效),选中后按钮变粉 + 启用——必须选择才能投票的强约束,比 Toast 校验更前置。
三、Header 与 QuestionCard:问题上下文
@Builder
Header() {
Row() { Text('参与投票').fontSize(20).fontWeight(FontWeight.Bold).fontColor(C.text) }
.width('100%').height(this.safeTop + 56).padding({ top: this.safeTop, left: D.pad, right: D.pad })
.backgroundColor(C.card).alignItems(VerticalAlign.Bottom)
}
@Builder
QuestionCard() {
Column({ space: 8 }) {
Text('🍜 今晚点什么外卖?').fontSize(18).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
Text('单选 · 3/4 人已投票 · 剩余 2 小时').fontSize(12).fontColor(C.textDim).width('100%')
}
.width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
}
18 号加粗问题 + 12 号灰色上下文"单选 · 3/4 人已投票 · 剩余 2 小时"——一句话交代投票类型、进度、剩余时间。问题卡让用户先理解"投什么"再选择。
四、OptionList:Radio 单选 + 卡片边框联动
@Builder
OptionList() {
Column({ space: 12 }) {
ForEach(this.options, (o: Option, idx: number) => {
Row({ space: 12 }) {
Text(o.emoji).fontSize(26)
Text(o.name).fontSize(15).fontColor(C.text).layoutWeight(1)
Radio({ value: o.name, group: 'vote' })
.checked(this.selected === idx).radioStyle({ checkedBackgroundColor: C.primary })
.onClick(() => { this.selected = idx; })
}
.width('100%').padding(16)
.backgroundColor(this.selected === idx ? C.primarySoft : C.card).borderRadius(D.rMd)
.border({ width: this.selected === idx ? 2 : 1, color: this.selected === idx ? C.primary : C.stroke })
.onClick(() => { this.selected = idx; })
}, (o: Option) => o.id.toString())
}.width('100%')
}
Radio 组件——系列首次出现的单选组件:
Radio({ value: o.name, group: 'vote' })
.checked(this.selected === idx)
.radioStyle({ checkedBackgroundColor: C.primary })
.onClick(() => { this.selected = idx; })
group: 'vote' 同组 Radio 互斥(单选语义的系统级保证),checked(this.selected === idx) 绑定选中态,radioStyle({ checkedBackgroundColor: C.primary }) 选中圆点粉色。
卡片双态联动——selected === idx 驱动底色与边框:
// 底色:浅粉 vs 白底
.backgroundColor(this.selected === idx ? C.primarySoft : C.card)
// 边框:2px 粉色 vs 1px 浅灰
.border({ width: this.selected === idx ? 2 : 1, color: this.selected === idx ? C.primary : C.stroke })
选中项卡片浅粉底 + 2px 粉色边框(加粗突出),未选白底 + 1px 浅灰边框。整卡 onClick 设 this.selected = idx——点击卡片任意位置(图标/名称/Radio)都能选中。



五、单选状态链路
selected 形成三处联动:
点击卡片/Radio → selected = idx
→ Radio checked 转移(单选互斥)
→ 卡片底色/边框高亮转移
→ 按钮变"确认投票"粉色 + enabled 启用
一次点击触发三处刷新——Radio 组互斥 + 卡片高亮 + 按钮启用的完整链路。enabled 的存在让"未选择"状态在按钮层面就被拦截,无需 Toast 提示。
六、可演进方向
| 方向 | 现状 | 改法 | 收益 |
|---|---|---|---|
| 投票写入 | Toast 模拟 | 服务器提交 + 票数+1 | 真实投票 |
| 多选支持 | 仅单选 | multiChoice 时 Checkbox + selected 数组 | 单/多选切换 |
| 倒计时 | 静态"剩余 2 小时" | 动态倒计时 + 到时禁用 | 投票限时 |
| 投票后反馈 | Toast | 跳转结果页查看实时票数 | 闭环反馈 |
| 已投状态 | 可重复投 | 记录已投选项 + 按钮变已投 | 一人一票 |
投票页是投票的核心交互——selected 驱动 Radio 互斥、卡片高亮、按钮启用的三处联动。从哨兵值 -1 的状态设计到 enabled 的禁用约束,每一层都有明确的职责边界。

更多推荐



所有评论(0)