ARTICLE DETAIL

资讯详情

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

鸿蒙原生应用 ArkTS 表单工程:书库创建页的类型选择与动态提交按钮

鸿蒙原生应用 ArkTS 表单工程:书库创建页的类型选择与动态提交按钮 鸿蒙原生应用 ArkTS 表单工程书库创建页的类型选择与动态提交按钮App 20「阅读管理」书库 TabFunc1Tab是添加书籍的表单页。整页由 6 个 Builder 块拼出 Header 基本信息 类型选择 优先级选择 设置 快捷模板 提交按钮动态文案。与 App 13/17 创建表单几乎完全同源——是模板换肤适配新业务的又一例证。本篇基于20-reading-manager/entry/src/main/ets/pages/Func1Tab.ets约 215 行逐段拆解附 4 张实机截图。一、整体结构6 个 Builder 的表单漏斗Func1Tab 的骨架与 App 13/17 创建页完全同源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 FormCard标题/描述/字数统计Header 是创建/填写信息快速创建单行 右侧 图标与 App 13 同构。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 字数统计三件套与 App 13/17 完全同源TextInput44vp 单行 TextArea90vp 多行3-4 行onChange双向绑定text: this.inputTitle受控 回调写回${this.inputTitle.length}/50字数统计——输入实时更新0/50→1/50→2/5050 字上限的语义在阅读 App 里——书名标题要精简三体两个字50 字足够任何书名的长度——上限 业务约束App 13 音乐 50 字、App 20 书名 50 字同款。 图片 / 链接伪按钮——添加书的封面图/豆瓣链接阅读 App 添加书常要填封面和链接——Blank()撑开把字数统计推到右侧。三、TypeCard4 种类型图标网格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; })更新。类型一/二/三/四的通用命名App 13/17 同款——类型名没换是模板换肤的遗留真实阅读 App 应换成文学/科幻/历史/心理等真实分类——通用命名 vs 业务命名的取舍demo 用通用名省事读者做产品应换业务名。四、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低 → 绿C.ok、中 → 橙C.warn、高 → 红C.danger绿-黄-红国际通用风险色阶信号灯语义。优先级在阅读 App 里的语义——这本书多急着读高优先级 马上要读如课程指定书。右上角实时显示priorities[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.SwitchiOS 风格开关isOn: this.remindOn默认 true默认开启提醒selectedColor(C.primary)紫色开启色onChange双向绑定提醒通知在阅读 App 里的语义——阅读打卡提醒每天 20:00 提醒读书——提醒 习惯养成阅读 App 的核心价值是帮用户养成阅读习惯。Divider()分割线Column({ space: 0 })分割线本身提供间距。›箭头表示二级页面隐私设置入口。六、TemplateCard SubmitBtnTemplateCard 是 3 个快捷模板快速创建/高级模式/批量导入列表与 App 13/17 同源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 }) }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%) }两态按钮标题为空 → 请填写标题 浅灰底 Toast 拦截标题填写 → ✓ 提交创建 紫底白字 Toast 创建成功 清空两个输入早返回校验if (!title) return——比 if-else 嵌套更扁平。成功清空inputTitle ; inputDesc ——提交后可立即再添加。注意提交即表示同意相关条款在添加书场景有点违和看书 App 加书不需要同意条款——模板复刻的文案遗留App 13 的音乐创建表单文案没换——**业务文案要随业务换**是模板复刻必须注意的点真实阅读 App 应改成添加后将同步到书架之类。七、State 的 5 状态映射Func1Tab 有 5 个 StateState类型用途联动inputTitlestring标题输入按钮文案/颜色/字数统计inputDescstring描述输入—selectedTypenumber类型选中0-3TypeCard 高亮selectedPrioritynumber优先级选中0-2PriorityCard 高亮 文字remindOnboolean提醒开关ToggleinputTitle是最忙的状态同时驱动按钮文案/颜色/字数统计 3 处——一处状态、三处渲染。5 个 State 5 个交互点——State 数量 交互点数规律App 13/17/20 表单页都是 5 个高度稳定。八、创建表单模板的系列复用史App 20 书库页与 App 13 歌曲创建、App 17 活动创建几乎 100% 同源——回顾系列 3 个创建表单App主题业务State提交校验13 音乐紫 #8B5CF6添加歌曲5标题非空17 志愿红 #EF4444创建活动5标题非空20 阅读紫 #A855F7添加书籍5标题非空3 个创建表单的差异只有主题色 图标 文案——创建表单模板已稳定复用 3 次——FormCard/TypeCard/PriorityCard/SettingCard/TemplateCard/SubmitBtn 六件套是 ArkUI 创建类页面的标准骨架。创建表单六件套可复用到添加歌曲、创建活动、添加书籍、新增订单、发布帖子、新建任务——任何填信息 选类型 设优先级 开关设置 模板 提交的场景。九、表单校验的边界防护进阶App 20 SubmitBtn 只做了标题非空单校验if (this.inputTitle.length 0)真实项目应加边界防护1. 空串校验当前有inputTitle.length 0→ 拦截2. 纯空格校验应补if (this.inputTitle.trim().length 0) { promptAction.showToast({ message: 标题不能为空白 }); return; }trim()去掉首尾空格——用户输入 纯空格时.length是 3 但.trim().length是 0——**trim 校验防纯空格**是表单必备。3. 长度上限应补if (this.inputTitle.length 50) { promptAction.showToast({ message: 标题最多 50 字 }); return; }4. 字数统计联动${this.inputTitle.length}/50达到 50 时输入框变红C.danger提示——**视觉预警**比事后拦截更友好。边界防护 空串 纯空格 长度 格式四件套——demo 只做了空串真实项目要补全尤其是 trim 防空格——空格绕过非空校验是表单最常见的漏洞。十、六件套表单模板的组件化建议App 20 书库页的表单六件套FormCard/TypeCard/PriorityCard/SettingCard/TemplateCard/SubmitBtn是高度复用的模板建议拆成独立组件方案一Builder 参数化当前做法——适合单页使用6 个 Builder 在 Func1Tab 内。方案二独立 Component推荐真实项目——把 FormCard/TypeCard 抽成components/下的独立组件Component export struct TypeSelector { Prop selectedType: number; Prop options: OptionItem[]; onSelect: (idx: number) void; // 回调 ... }抽组件的 3 个好处跨页复用——书籍创建页、笔记创建页、书签创建页都能用 TypeSelector独立测试——每个表单组件可单独 Preview 调试协作分工——A 写 FormCard、B 写 TypeCard互不干扰六件套抽组件后任何创建类页面只需拼装build() { Column() { FormCard(...) TypeSelector(...) PrioritySelector(...) SettingToggle(...) TemplatePicker(...) SubmitButton(...) } }表单组件化是 ArkUI 工程化的重要一步——App 20 的六件套是现成的拆分清单。十一、模板复刻的正确姿势App 20 书库页与 App 13/17 创建页的同源展示了一个关键方法论——模板复刻的正确姿势该复刻的代码结构6 个 Builder 的布局骨架State 状态管理5 个TextInput/TextArea/Toggle/ForEach 交互主题色变量引用C.primary/C.cardSoft该换的业务内容占位文案请输入标题 → 请输入书名类型名类型一/二 → 文学/科幻/历史提交成功文案创建成功 → 已添加到书架条款文案同意相关条款 → 添加后将同步书架**复刻骨架、换业务**是模板复刻的正确打开方式——App 13/17/20 的书库页复刻了骨架但没换业务文案类型一仍是通用名——读者应把该换的清单记下来占位/类型/成功文案/条款/数据全换。十二、阅读场景下的表单语义再设计App 20 书库页虽是模板复刻但**添加书场景下的表单语义**值得重新设计读者可对比 App 13 音乐场景理解同一表单不同语义FormCard基本信息音乐场景标题 歌曲名、描述 歌曲介绍阅读场景标题 书名、描述 书籍简介——输入框语义随业务占位符应改请输入书名TypeCard类型音乐场景类型一/二/三/四流派占位阅读场景应改为文学/科幻/历史/心理学真实分类——类型选项 业务分类体系PriorityCard优先级音乐场景歌曲重要程度阅读场景这本书多急着读如高 课程指定必读——优先级语义可复用急迫程度通用SettingCard设置音乐场景提醒通知新歌提醒阅读场景阅读提醒每天 20:00 提醒读书——提醒 习惯养成——阅读 App 的提醒比音乐更有价值读书需要自律提醒帮用户坚持TemplateCard模板音乐场景快速创建/高级模式/批量导入阅读场景从豆瓣导入ISBN 扫码添加书单模板——模板 业务快捷方式SubmitBtn提交音乐场景创建成功阅读场景已添加到书架——成功反馈 业务动作确认同一表单六件套业务语义全换——这是表单模板复用的最高级姿势骨架不变、语义全换就是完全不同的产品。十三、总结App 20 书库创建页解析完毕。与 App 13/17 的模板复刻展示了创建表单模板的稳定复用——表单六件套是 ArkUI 创建类页面的标准骨架。同时指出类型名/条款文案等模板复刻的遗留问题读者应随业务替换。
返回列表