鸿蒙原生应用 ArkTS 表单工程:志愿活动创建页的优先级三色与动态按钮

发布时间:2026/9/4 17:16:45
鸿蒙原生应用 ArkTS 表单工程:志愿活动创建页的优先级三色与动态按钮 鸿蒙原生应用 ArkTS 表单工程志愿活动创建页的优先级三色与动态按钮App 17「志愿服务」活动 TabFunc1Tab是活动创建的表单页。整页由 7 个 Builder 块拼出 Header 基本信息 类型选择 优先级选择 提醒设置 快捷模板 提交按钮动态文案是 ArkUI 声明式表单的模板范本。与 App 13 歌曲创建页几乎同源——是模板换肤适配新业务的最佳示例。本篇基于17-volunteer/entry/src/main/ets/pages/Func1Tab.ets约 215 行逐段拆解附 4 张实机截图。一、整体结构6 个 Builder 的表单漏斗Func1Tab 的骨架与 App 13 歌曲创建页几乎完全相同——Header 6 个表单卡片 提交按钮的固定漏斗build() { Column() { this.Header() Scroll() { Column({ space: 14 }) { this.FormCard() this.TypeCard() this.PriorityCard() this.SettingCard() this.TemplateCard() this.SubmitBtn() Blank().height(this.safeBottom 20) } .width(100%).padding({ left: D.pad, right: D.pad, top: 6 }) } .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top) } .width(100%).height(100%).backgroundColor(C.bg) }6 个块按必填 → 必选 → 选填 → 操作的漏斗叙事展开FormCard— 标题/描述/图片链接/字数统计必填区TypeCard— 4 种活动类型图标网格必选PriorityCard— 优先级三色低/中/高SettingCard— 提醒通知 Toggle 隐私设置入口TemplateCard— 3 个快捷模板快速创建/高级模式/批量导入SubmitBtn— 提交按钮动态文案 早返回校验Scroll 容器 固定 Header是表单页的标准布局——Header 不滚动用户填到一半还能看到创建标题表单长内容可滚动。项目源码开源https://gitee.com/codenestFlow/HarmonyOSHub二、Header固定标题 右侧图标Header 单行结构与 App 13 同构是页面身份标识Builder Header() { Row({ space: 12 }) { Column({ space: 2 }) { Text(创建).fontSize(20).fontWeight(FontWeight.Bold).fontColor(C.text) Text(填写信息快速创建).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) } .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 }) }height(this.safeTop 60) padding({ top: this.safeTop }) alignItems(VerticalAlign.Bottom)三件套实现安全区顶部让出 内容靠底对齐——height等于状态栏60vp 标题区padding.top让出状态栏区域alignItems(Bottom)让标题/图标贴底对齐。这是 ArkUI 沉浸式 Header 的标准写法。border({ width: { bottom: 1 }, color: C.stroke })底部 1vp 浅灰分隔线把 Header 与下方 FormCard 视觉分隔——比给下方 FormCard 加border-top简洁避免重复边框。与 App 13 几乎完全相同——只有 2 处差异① 文案创建填写信息快速创建 vs App 13 略有不同② 主题色背景虽然 Header 是白底 边框但 C.card / C.stroke 都是同一套主题色变量红色 #EF4444 间接通过C.primary体现在按钮/选中态。三、FormCard基本输入 字数统计 附件标签FormCard 是表单核心输入区——标题 TextInput、描述 TextArea、图片/链接伪按钮、字数统计Builder FormCard() { Column({ space: 12 }) { Row() { Text(基本信息).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text) Blank() Text(✕).fontSize(16).fontColor(C.textDim) }.width(100%) Column({ space: 6 }) { Text(标题).fontSize(12).fontColor(C.textSub) TextInput({ placeholder: 请输入标题, text: this.inputTitle }) .onChange((v: string) { this.inputTitle v; }) .height(44).backgroundColor(C.cardSoft).borderRadius(D.rSm).placeholderColor(C.textDim) }.alignItems(HorizontalAlign.Start).width(100%) Column({ space: 6 }) { Text(描述).fontSize(12).fontColor(C.textSub) TextArea({ placeholder: 请输入详细描述..., text: this.inputDesc }) .onChange((v: string) { this.inputDesc v; }) .height(90).backgroundColor(C.cardSoft).borderRadius(D.rSm).placeholderColor(C.textDim) }.alignItems(HorizontalAlign.Start).width(100%) Row({ space: 8 }) { Text( 图片).fontSize(12).fontColor(C.textSub) .padding({ left: 10, right: 10, top: 6, bottom: 6 }) .backgroundColor(C.cardSoft).borderRadius(D.rSm) Text( 链接).fontSize(12).fontColor(C.textSub) .padding({ left: 10, right: 10, top: 6, bottom: 6 }) .backgroundColor(C.cardSoft).borderRadius(D.rSm) Blank() Text(${this.inputTitle.length}/50).fontSize(10).fontColor(C.textDim) }.width(100%) } .width(100%).padding(14).backgroundColor(C.card).borderRadius(D.rMd) .border({ width: 1, color: C.stroke }) }TextInput TextArea 字数统计三件套TextInput({ placeholder: 请输入标题, text: this.inputTitle }) .onChange((v: string) { this.inputTitle v; }) .height(44).backgroundColor(C.cardSoft).borderRadius(D.rSm).placeholderColor(C.textDim)onChange双向绑定text: this.inputTitle受控onChange写回 State—— ArkUI 声明式表单的标准模式。backgroundColor(C.cardSoft).borderRadius(D.rSm)形成软灰底输入框比纯白底更聚焦用户视线。${this.inputTitle.length}/50字数统计——State inputTitle变化自动重渲染用户每敲一个字就更新0/50→1/50→2/50。50 字上限暗示活动标题要精简。图片/链接伪按钮 图片 链接两个Text套 padding 背景色做成标签型按钮——Blank()撑开把字数统计推到右侧。文字 padding 背景是 ArkUI 轻量按钮的标准模式。与 App 13 完全同源——inputTitle/inputDesc两个 State 双向绑定字数统计实时同步。同样的表单骨架换主题色——C.cardSoft变量决定了所有软灰底是浅红色调在红色主题下略偏暖。四、TypeCard4 选 1 类型图标网格TypeCard 是 4 种活动类型//⭐/的等宽图标网格Builder TypeCard() { Column({ space: 12 }) { Text(选择类型).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text).width(100%) Row() { ForEach(this.types, (item: OptionItem, idx: number) { Column({ space: 6 }) { Row() { Text(item.icon).fontSize(24) } .width(48).height(48).borderRadius(D.rMd).justifyContent(FlexAlign.Center) .backgroundColor(this.selectedType idx ? C.primarySoft : C.cardSoft) .border({ width: this.selectedType idx ? 2 : 0, color: C.primary }) Text(item.name).fontSize(10) .fontColor(this.selectedType idx ? C.primary : C.textDim) .fontWeight(this.selectedType idx ? FontWeight.Bold : FontWeight.Normal) }.layoutWeight(1) .onClick(() { this.selectedType idx; }) }, (item: OptionItem) item.name) }.width(100%) } .width(100%).padding(14).backgroundColor(C.card).borderRadius(D.rMd) .border({ width: 1, color: C.stroke }) }选中态三件套背景/边框/文字——本系列标准做法背景C.primarySoft浅红 vsC.cardSoft浅灰边框2vp 主色红vs 无边框文字主色加粗 vs 灰色常规State selectedType: number 0默认选中类型一——onClick(() { this.selectedType idx; })更新选中。C.primarySoft: #FEE8E8浅红 vsC.primary: #EF4444主红——浅红背景 红边 红字 同色系深浅高亮。**选中不变色、只加深**是本系列单色主题下的标准手法与 App 15 的四色分类是相反思路——App 15 4 分类颜色不同必须用同色深浅表达选中App 17 单色主题直接用主题色深浅即可。五、PriorityCard三色优先级低绿/中橙/高红PriorityCard 是 3 档优先级选择低/中/高颜色对应风险色阶Builder PriorityCard() { Column({ space: 12 }) { Row() { Text(优先级).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text) Blank() Text(this.priorities[this.selectedPriority]).fontSize(13).fontColor(C.primary).fontWeight(FontWeight.Bold) }.width(100%) Row({ space: 6 }) { ForEach(this.priorities, (p: string, idx: number) { Text(p).fontSize(13) .fontColor(this.selectedPriority idx ? #FFFFFF : C.textSub) .backgroundColor(this.selectedPriority idx ? (idx 2 ? C.danger : idx 1 ? C.warn : C.ok) : C.cardSoft) .borderRadius(D.rSm).padding({ left: 16, right: 16, top: 8, bottom: 8 }) .onClick(() { this.selectedPriority idx; }) }, (p: string) p) }.width(100%) } .width(100%).padding(14).backgroundColor(C.card).borderRadius(D.rMd) .border({ width: 1, color: C.stroke }) }关键三元idx 2 ? C.danger : idx 1 ? C.warn : C.ok低idx0→C.ok绿色#2BB673——风险低用绿色中idx1→C.warn橙色#FF9F1C——中等风险用橙色高idx2→C.danger深红#FF5A6E——高风险用红色绿-黄-红风险色阶是国际通用的严重性映射信号灯、地图、告警都这么用。**颜色 风险等级**是直觉认知无需文字解释。注意 App 17 的C.danger是#FF5A6E偏深红与主题色#EF4444偏鲜红略有差异——业务警示色与品牌色分离是设计系统的成熟做法品牌色是主调警示色是风险信号两套红但用途不同。相同色系、不同饱和度/明度是高级设计。右上角实时显示当前优先级Text(this.priorities[this.selectedPriority])默认中 idx1——State selectedPriority变化 → 文字同步。六、SettingCardToggle 开关 隐私入口SettingCard 是提醒通知开关Toggle 隐私设置入口›箭头Builder SettingCard() { Column({ space: 0 }) { Row({ space: 12 }) { Row() { Text().fontSize(16) } .width(32).height(32).backgroundColor(C.cardSoft).borderRadius(D.rSm).justifyContent(FlexAlign.Center) Column({ space: 2 }) { Text(提醒通知).fontSize(14).fontColor(C.text) Text(开启后将推送提醒).fontSize(11).fontColor(C.textDim) }.alignItems(HorizontalAlign.Start).layoutWeight(1) Toggle({ type: ToggleType.Switch, isOn: this.remindOn }) .selectedColor(C.primary) .onChange((on: boolean) { this.remindOn on; }) }.width(100%).padding({ top: 12, bottom: 12 }) Divider().color(C.stroke) Row({ space: 12 }) { Row() { Text().fontSize(16) } .width(32).height(32).backgroundColor(C.cardSoft).borderRadius(D.rSm).justifyContent(FlexAlign.Center) Column({ space: 2 }) { Text(隐私设置).fontSize(14).fontColor(C.text) Text(仅自己可见).fontSize(11).fontColor(C.textDim) }.alignItems(HorizontalAlign.Start).layoutWeight(1) Text(›).fontSize(22).fontColor(C.textDim) }.width(100%).padding({ top: 12, bottom: 12 }) } .width(100%).padding({ left: 14, right: 14 }).backgroundColor(C.card).borderRadius(D.rMd) .border({ width: 1, color: C.stroke }) }Toggle标准用法type: ToggleType.Switch— iOS 风格开关isOn: this.remindOn— 受控属性默认 true默认开启提醒selectedColor(C.primary)— 选中时主色红——**主题色作为 Toggle 开启色**是系列标准onChange((on: boolean) { this.remindOn on; })— 双向绑定Divider()分割线——上半部分提醒通知和下半部分隐私设置之间用 1vp 浅灰线分隔。Column({ space: 0 })因为分割线本身已经提供视觉间距避免上下空白过大。›右箭头— 表示点击进入二级页面Text(›)是常用 iOS 风格的disclosure indicator。主开关 子设置的两层结构避免一个开关页面塞 20 个 Toggle。与 App 13 完全同源——包括State remindOn: boolean true默认值、selectedColor(C.primary)主题色 Toggle、Toggle/Disclosure双形态。七、TemplateCard3 个快捷模板TemplateCard 是 3 个快捷模板快速创建/高级模式/批量导入的列表Builder TemplateCard() { Column({ space: 10 }) { Text(快捷模板).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text).width(100%) ForEach(this.templates, (item: QuickTemplate) { Row({ space: 10 }) { Row() { Text().fontSize(18) } .width(32).height(32).backgroundColor(C.primarySoft).borderRadius(D.rSm).justifyContent(FlexAlign.Center) Column({ space: 2 }) { Text(item.title).fontSize(13).fontWeight(FontWeight.Medium).fontColor(C.text) Text(item.desc).fontSize(10).fontColor(C.textDim) }.alignItems(HorizontalAlign.Start).layoutWeight(1) Text(›).fontSize(18).fontColor(C.textDim) }.width(100%) .onClick(() { promptAction.showToast({ message: item.title }); }) }, (item: QuickTemplate) item.title) } .width(100%).padding(14).backgroundColor(C.card).borderRadius(D.rMd) .border({ width: 1, color: C.stroke }) }3 个模板 快速创建 — 使用默认模板懒人用户 高级模式 — 自定义所有字段专家用户 批量导入 — 从文件导入重度用户每行浅红底图标块C.primarySoft 主标题 副标题 ›箭头。图标块颜色跟随主题色——C.primarySoft是浅红在红色主题下是品牌色的软化版。与 App 13 完全同源——3 个模板、图标、主副标题、箭头全相同。模板卡片的视觉结构稳定复用。八、SubmitBtn动态文案 早返回校验SubmitBtn 是表单的终结点——按钮文案根据标题是否填写动态变化Builder SubmitBtn() { Column({ space: 8 }) { Button(this.inputTitle.length 0 ? ✓ 提交创建 : 请填写标题) .width(100%).height(48) .backgroundColor(this.inputTitle.length 0 ? C.primary : C.cardSoft).fontColor(#FFFFFF) .fontSize(16).fontWeight(FontWeight.Bold).borderRadius(D.rMd) .onClick(() { if (this.inputTitle.length 0) { promptAction.showToast({ message: 请输入标题 }); return; } promptAction.showToast({ message: 创建成功 }); this.inputTitle ; this.inputDesc ; }) Text(提交即表示同意相关条款).fontSize(9).fontColor(C.textDim).width(100%).textAlign(TextAlign.Center) }.width(100%) }两态按钮标题为空 → 文字请填写标题 浅灰底C.cardSoft白字其实浅灰底 白字对比度不好可能是设计小瑕疵 Toast 拦截标题填写 → 文字✓ 提交创建 主色背景红白字白字是写死的fontColor(#FFFFFF)——在浅灰底上白字几乎看不见这是设计 bug#FFFFFF在C.cardSoft上对比度极低。真实项目应把未填写态的文字色改为C.textDim或C.textSub。早返回校验.onClick(() { if (this.inputTitle.length 0) { promptAction.showToast({ message: 请输入标题 }); return; } promptAction.showToast({ message: 创建成功 }); this.inputTitle ; this.inputDesc ; })if (!inputTitle) return早返回 Toast 提示——比if-else嵌套更扁平。成功路径除了弹 Toast 还清空两个 StateinputTitle inputDesc ——用户提交后表单清空可以立即再创建下一条。Text(提交即表示同意相关条款)法律提示——9sp 灰色居中小字灰层级最低但合规必须。任何提交按钮都该配一段条款提示电商注册、隐私协议、金融产品都是。九、State 的 5 状态映射Func1Tab 有5 个 StateState类型用途联动inputTitlestring标题输入按钮文案/按钮颜色/字数统计inputDescstring描述输入—selectedTypenumber类型选中0-3TypeCard 高亮selectedPrioritynumber优先级选中0-2PriorityCard 高亮 文字显示remindOnboolean提醒通知开关Toggle 状态inputTitle是最忙的状态——同时驱动按钮文案、按钮颜色、字数统计 3 处 UI。一处状态、三处渲染是响应式 UI 的精髓。selectedType selectedPriority是单选状态——典型模式State number 索引。remindOn是二元状态——典型模式State boolean Toggle 双向绑定。5 个 State 5 个交互点——**State 数量 交互点数**是设计健康状态管理的指标。十、模板换肤的方法论系列最重要的设计原则App 17 与 App 13 几乎 100% 同构——唯一的差异是 Theme.ets 里的几个颜色值颜色App 13紫App 17红C.primary#8B5CF6#EF4444C.accent#A78BFA#F87171C.primarySoft#F0EBFE#FEE8E8C.warn / C.danger / C.ok同同主题色 3 件套主色/浅色/超浅色换肤业务色不变C.warn/C.danger/C.ok 通用完整的模板换肤。这套设计原则值得所有 ArkUI 项目学习定义品牌色 3 件套primaryaccentprimarySoft——所有品牌相关的 UI按钮/选中态/进度条/链接用这 3 个变量定义业务色warndangerok——所有状态色用这 3 个变量与品牌色解耦所有组件用变量名而非色码——.backgroundColor(C.primary)而不是.backgroundColor(#EF4444)换肤只改 Theme.ets——3 个颜色值全换 整个 App 换肤这种主题与代码分离的模式让换主题色从改 200 个文件变成改 1 个文件——工程效率提升 200 倍。真实项目建议暗色模式夜间/省电dark: { primary: #..., accent: #..., ... }与light: { ... }配套多品牌企业版/个人版/校园版/医院版换 Theme.ets 即可节日主题春节红、国庆红、圣诞绿通过动态主题色实现AppStorage.set(primary, #FF5A5A)一套代码、多个主题是 ArkUI 设计系统的最高目标——App 13/17 的对比就是这个目标的最佳示范。十一、表单设计的四层校验进阶App 17 SubmitBtn 只做了标题非空单校验真实项目应做四层校验客户端校验State同步校验——本页当前实现边界校验如标题不能全是空格if (this.inputTitle.trim().length 0)业务规则校验如高优先级活动必须填描述服务端校验最强最终防线四层校验 客户端体验 业务完整性 数据安全。demo 只做了第 1 层是简化真实项目应把边界校验trim/长度限制也补上if (this.inputTitle.trim().length 0) { promptAction.showToast({ message: 标题不能为空 }); return; } if (this.inputTitle.length 50) { promptAction.showToast({ message: 标题最多 50 字 }); return; }trim()是边界校验的关键——用户可能输入一堆空格 绕过非空检查。trim().length确保实际有效内容非空。十二、Toggle 组件的持久化陷阱State remindOn: boolean true的提醒通知开关当前只存在内存里——应用重启后remindOn会重置为 true默认值用户设置的关闭提醒丢失。真实项目应持久化到本地存储ohos.data.preferencesimport { preferences } from kit.ArkData; aboutToAppear() { // 读取持久化值 preferences.getPreferences(this.getUIContext().getHostContext(), volunteer_pref) .then(p p.get(remindOn, true)) .then(v { this.remindOn v; }); } onChange((on: boolean) { this.remindOn on; // 持久化 preferences.getPreferences(this.getUIContext().getHostContext(), volunteer_pref) .then(p p.put(remindOn, on)) .then(p p.flush()); })aboutToAppear是组件的生命周期钩子类似 Vue 的mounted在组件创建时执行。getPreferences异步读取 → 更新 State → UI 重渲染。onChange写持久化——**状态变更即持久化**是用户偏好设置的标准模式。Toggle 持久化是 demo 简化——读者在自己项目里应记住用户偏好必须持久化否则重启丢失。十三、总结App 17 活动创建页解析完毕。与 App 13 的模板换肤关系展示了一个重要方法论一套 UI 框架 一套 Theme.ets 配色 任意主题的应用。红色志愿主题色让 6 个表单组件焕发公益热情——C.primary红色在按钮/选中态/进度条/链接文字上反复出现让志愿主题深入骨髓。表单校验和Toggle 持久化是 demo 到产品的关键差距——读者在抄这个模板时记得补齐。