HarmonyOS 校园应用系列之ArkTS 表单工程:水电缴费页的金额选择器与代扣开关联动
HarmonyOS 校园应用系列之ArkTS 表单工程:水电缴费页的金额选择器与代扣开关联动
应用背景:10 水电缴费的缴费创建页(Func1Tab.ets,278 行)是用户发起缴费的核心表单。本文聚焦 RoomCard 房间卡、TypeSelector 类型三选一、AmountSelector 金额选择、AutoPaySwitch 代扣开关、PreviewCard 缴费预览 等模块。

一、页面整体结构
// Func1Tab.ets — 页面根结构
Column() {
this.Header()
Scroll() {
Column({ space: 16 }) {
this.RoomCard()
this.TypeSelector()
this.AmountSelector()
this.CustomInput()
this.AutoPaySwitch()
this.PreviewCard()
this.TemplateSection()
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)
根布局为 Column 两段式:Header 固定在 Scroll 外部不随内容滚动,滚动区内部 Column({ space: 16 }) 纵向排布 8 个模块,底部 padding 动态叠加 safeBottom 安全区。

二、Header 固定标题栏
// Header — 固定标题栏
@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)
}
标题栏白底,高度 safeTop + 56 动态适配状态栏,alignItems(VerticalAlign.Bottom) 让"水电缴费" 20vp 粗体标题贴底显示。固定效果由"放在 Scroll 外部"这一结构实现,无需 sticky 属性。
三、RoomCard 宿舍信息卡片
// RoomCard — 宿舍信息
@Builder
RoomCard() {
Row({ space: 14 }) {
Row() {
Text('🏠').fontSize(30)
}
.width(56).height(56)
.backgroundColor(C.primarySoft).borderRadius(D.rMd)
.justifyContent(FlexAlign.Center)
Column({ space: 4 }) {
Text('梅园 6 栋 302 室').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text)
Text('绑定人: 张同学 · 校园卡 6228***4821').fontSize(12).fontColor(C.textDim)
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Text('切换 >').fontSize(12).fontColor(C.primary)
.onClick(() => { promptAction.showToast({ message: '切换房间' }); })
}
.width('100%')
.padding(14)
.backgroundColor(C.card)
.borderRadius(D.rLg)
.border({ width: 1, color: C.stroke })
}
卡片左侧 🏠 Emoji(56vp 圆角方块浅青底),中间纵排两行信息(地址 / 绑定人+脱敏卡号),右侧"切换 >"主色链接点击 toast,预留多房间切换入口。

四、TypeSelector 缴费类型选择器
4.1 类型定义与状态
// 类型选项定义
private types: string[] = ['电费', '水费', '燃气费'];
@State typeIdx: number = 0;
类型用纯字符串数组承载,三种缴费类型覆盖校园生活主要场景。typeIdx 记录当前选中索引,默认 0(电费)。
4.2 三选一渲染
// TypeSelector — 类型选择器
@Builder
TypeSelector() {
Column({ space: 12 }) {
Text('缴费类型').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
Row({ space: 10 }) {
ForEach(this.types, (t: string, idx: number) => {
Column({ space: 4 }) {
Text(idx === 0 ? '⚡' : idx === 1 ? '💧' : '🔥').fontSize(24)
Text(t).fontSize(13).fontColor(this.typeIdx === idx ? '#FFFFFF' : C.textSub)
}
.layoutWeight(1)
.padding({ top: 14, bottom: 14 })
.backgroundColor(this.typeIdx === idx ? C.primary : C.card)
.borderRadius(D.rMd)
.border({ width: 1, color: this.typeIdx === idx ? C.primary : C.stroke })
.onClick(() => { this.typeIdx = idx; })
}, (t: string) => t)
}
.width('100%')
}
.width('100%')
.padding(14)
.backgroundColor(C.card)
.borderRadius(D.rLg)
.border({ width: 1, color: C.stroke })
}
图标由索引三元表达式推导(0→⚡ / 1→💧 / 其余→🔥),无需扩展数据结构。选中态:实底主色背景 + 白字 + 主色描边;未选中:白底 + 次要色字 + 灰描边。三项 layoutWeight(1) 等分宽度,非固定尺寸。

五、AmountSelector 金额选择器
5.1 预设金额
// 金额预设值
private amounts: string[] = ['¥20', '¥50', '¥100', '¥200'];
@State amountIdx: number = 1;
四种预设金额满足常见场景,amountIdx 默认 1(¥50)。
5.2 Flex 换行渲染
// AmountSelector — 金额选择器
@Builder
AmountSelector() {
Column({ space: 12 }) {
Text('选择金额').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(this.amounts, (a: string, idx: number) => {
Text(a)
.fontSize(15).fontWeight(FontWeight.Medium)
.fontColor(this.amountIdx === idx ? '#FFFFFF' : C.text)
.padding({ left: 20, right: 20, top: 12, bottom: 12 })
.backgroundColor(this.amountIdx === idx ? C.primary : C.cardSoft)
.borderRadius(D.rSm)
.border({ width: 1, color: this.amountIdx === idx ? C.primary : C.stroke })
.margin({ right: 10, bottom: 10 })
.onClick(() => { this.amountIdx = idx; })
}, (a: string) => a)
}
.width('100%')
}
.width('100%')
.padding(14)
.backgroundColor(C.card)
.borderRadius(D.rLg)
.border({ width: 1, color: C.stroke })
}
Flex 布局自动换行,比固定 Grid 更灵活。选中态与 TypeSelector 同一套视觉语言:主色实底+白字+主色描边;未选中浅灰底(C.cardSoft)+灰描边。
5.3 CustomInput 自定义金额
// CustomInput — 自定义金额输入
@Builder
CustomInput() {
Column({ space: 8 }) {
Text('自定义金额').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
Row({ space: 10 }) {
Text('¥').fontSize(18).fontColor(C.textDim)
TextInput({ placeholder: '请输入金额', text: this.customAmount })
.backgroundColor('transparent').borderRadius(D.rSm).height(44)
.layoutWeight(1).type(InputType.Number)
.placeholderColor(C.textDim).placeholderFont({ size: 14 })
.onChange((val: string) => { this.customAmount = val; })
}
.width('100%')
.padding({ left: 14, right: 14 })
.backgroundColor(C.cardSoft).borderRadius(D.rSm)
}
.width('100%')
.padding(14)
.backgroundColor(C.card)
.borderRadius(D.rLg)
.border({ width: 1, color: C.stroke })
}
自定义输入是独立卡片:¥ 前缀 + 数字键盘(type(InputType.Number))+ 浅灰底输入槽。注意预设与自定义互不干扰——金额 onClick 只更新 amountIdx,输入框 onChange 只更新 customAmount,两者各自独立;最终取值在 PreviewCard 中按"自定义优先"裁决。

六、AutoPaySwitch 自动代扣开关
// AutoPaySwitch — 代扣开关
@Builder
AutoPaySwitch() {
Row({ space: 12 }) {
Column({ space: 4 }) {
Text('自动代扣').fontSize(14).fontWeight(FontWeight.Medium).fontColor(C.text)
Text('余额低于 ¥20 时自动充值').fontSize(11).fontColor(C.textDim)
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Toggle({ type: ToggleType.Switch, isOn: this.autoPay })
.selectedColor(C.primary)
.onChange((on: boolean) => {
this.autoPay = on;
promptAction.showToast({ message: on ? '已开启自动代扣' : '已关闭自动代扣' });
})
}
.width('100%')
.padding(14)
.backgroundColor(C.card)
.borderRadius(D.rLg)
.border({ width: 1, color: C.stroke })
}
Toggle 用系统 Switch 控件,selectedColor(C.primary) 定制开启色,.onChange() 回调更新 @State autoPay 并 toast 反馈开关状态。左侧文案 layoutWeight(1) 占满剩余宽度,把 Toggle 推到最右端。
七、PreviewCard 缴费预览
// PreviewCard — 缴费预览
@Builder
PreviewCard() {
Column({ space: 12 }) {
Text('缴费预览').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
ForEach(this.previewItems, (p: PreviewItem) => {
Row() {
Text(p.label).fontSize(13).fontColor(C.textDim)
Blank()
Text(p.value).fontSize(13).fontColor(C.text).fontWeight(FontWeight.Medium)
}
.width('100%')
}, (p: PreviewItem) => p.label)
Divider().color(C.stroke).strokeWidth(0.5)
Row() {
Text('实付金额').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text)
Blank()
Text(this.customAmount.length > 0 ? '¥' + this.customAmount : this.amounts[this.amountIdx])
.fontSize(22).fontColor(C.danger).fontWeight(FontWeight.Bold)
}
.width('100%')
}
.width('100%')
.padding(14)
.backgroundColor(C.card)
.borderRadius(D.rLg)
.border({ width: 1, color: C.stroke })
}
五行预览(缴费类型/缴费金额/房屋信息/支付方式/到账时间)用 ForEach + Blank 两端对齐;Divider 分隔后是实付金额裁决逻辑:customAmount 非空优先,否则回落 amounts[amountIdx]——这就是预设与自定义"互不干扰、显示时择一"的实现方式。
八、TemplateSection 快捷模板
// TemplateSection — 快捷模板
@Builder
TemplateSection() {
Column({ space: 12 }) {
Text('快捷模板').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
ForEach(this.templates, (t: TemplateItem) => {
Row({ space: 12 }) {
Row() {
Text(t.emoji).fontSize(22)
}
.width(42).height(42)
.backgroundColor(C.primarySoft).borderRadius(D.rSm)
.justifyContent(FlexAlign.Center)
Column({ space: 3 }) {
Text(t.name).fontSize(14).fontColor(C.text).fontWeight(FontWeight.Medium)
Text(t.desc).fontSize(11).fontColor(C.textDim)
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Text(t.amount).fontSize(15).fontColor(C.primary).fontWeight(FontWeight.Bold)
}
.width('100%')
.padding({ left: 12, right: 12, top: 10, bottom: 10 })
.backgroundColor(C.cardSoft).borderRadius(D.rSm)
.onClick(() => {
promptAction.showToast({ message: '使用模板: ' + t.name });
})
}, (t: TemplateItem) => t.id.toString())
}
.width('100%')
.padding(14)
.backgroundColor(C.card)
.borderRadius(D.rLg)
.border({ width: 1, color: C.stroke })
}
四个模板(电费快捷充值 ¥50 / 水费月度缴纳 ¥30 / 电费大额充值 ¥200 / 水费季度预缴 ¥100)浅灰底行卡,点击 toast"使用模板: xxx"。
九、SubmitBtn 提交按钮
// SubmitBtn — 提交按钮
@Builder
SubmitBtn() {
Button('确认缴费')
.width('100%').height(50)
.fontSize(16).fontColor('#FFFFFF').backgroundColor(C.primary)
.borderRadius(D.rMd)
.onClick(() => { promptAction.showToast({ message: '缴费成功!' }); })
}
提交按钮走极简路线:主色纯色实底 + 白字,宽 100%、高 50、D.rMd 圆角,点击 toast"缴费成功!"。不做禁用态判断——表单始终有默认值(类型默认电费、金额默认 ¥50),天然可提交。

十、本章小结
| 组件 | 核心技术 | 设计亮点 |
|---|---|---|
| RoomCard | Row 三段式布局 | 脱敏卡号 + 切换链接 |
| TypeSelector | 字符串数组 + 索引三元图标 | 选中态实底主色+白字 |
| AmountSelector | Flex Wrap 换行 | 预设/自定义互不干扰 |
| AutoPaySwitch | Toggle Switch | selectedColor + toast 反馈 |
| PreviewCard | Blank 两端对齐 + 条件取值 | 自定义金额优先裁决 |
| SubmitBtn | 纯色实底按钮 | 默认值兜底免校验 |
这套表单模式可直接复用于任何支付/充值场景——话费充值、会员购买、打赏等。
更多推荐




所有评论(0)