news 2026/10/3 6:06:55

鸿蒙原生应用 ArkTS 表单工程:明信片发送页的接收人输入与最近联系人

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
鸿蒙原生应用 ArkTS 表单工程:明信片发送页的接收人输入与最近联系人

鸿蒙原生应用 ArkTS 表单工程:明信片发送页的接收人输入与最近联系人

App 43「校园电子明信片」发送页(Func2Tab),主题色#00B894绿色(green),4 个 Tab 分别为首页(📮)、制作(🎨)、发送(✉️)、我的(👤)。发送页采用"Header + 成果卡 + 接收人 + 最近联系人 + 发送方式 + 按钮"六区布局——白色单行 Header("发送明信片"20 号加粗)+成果预览卡(80×80 绿色渐变 🎓 缩略 + "毕业季明信片"加粗 + 绿色"已完成制作"状态)+接收人卡(44 高浅底 TextInput"输入接收人姓名或账号")+最近联系人卡(👩 妈妈/👨🏫 李老师/🧑🤝🧑 室友小张/👧 同学小美 4 个头像,点击填入接收人 + Toast"选择: XX")+发送方式卡(立即发送/定时发送 2 等分胶囊)+全宽绿色"发送明信片 💌"按钮(接收人为空 Toast"请填写接收人"、非空"发送成功")。本篇基于43-e-postcard/entry/src/main/ets/pages/Func2Tab.ets(共 113 行)逐段拆解,附 4 张实机截图。

一、整体结构:六区"Header + 成果 + 接收人 + 最近 + 方式 + 按钮"布局

发送页是"品牌头部 + 成果确认 + 收件人填写 + 发送设置 + 提交"的六区布局:

build() { Column() { this.Header() Scroll() { Column({ space: 16 }) { this.CardPreview() this.ReceiverCard() this.RecentCard() this.ModeCard() Button('发送明信片 💌').fontSize(15).fontColor('#FFFFFF').backgroundColor(C.primary) .width('100%').height(48).borderRadius(D.rMd) .onClick(() => { promptAction.showToast({ message: this.receiver.length > 0 ? '发送成功' : '请填写接收人' }); }) } .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) }

6 块结构:

  1. Header— 单行品牌头("发送明信片")
  2. CardPreview— 制作成果确认卡("毕业季明信片 · 已完成制作")
  3. ReceiverCard— 接收人 TextInput(@State receiver)
  4. RecentCard— 最近联系人(点击回填接收人)
  5. ModeCard— 立即/定时发送(@State sendMode)
  6. 发送按钮— 全宽绿色主按钮,按接收人是否为空分支反馈

两个@State(receiver/sendMode)贯穿全页——接收人是校验核心(空则拦截),发送方式是扩展设置。

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

二、CardPreview:制作成果确认卡

CardPreview() { Row({ space: 14 }) { Column() { Text('🎓').fontSize(40) } .width(80).height(80).borderRadius(D.rMd).justifyContent(FlexAlign.Center) .linearGradient({ angle: 135, colors: [[C.primary, 0.0], [C.accent, 1.0]] }) Column({ space: 4 }) { Text('毕业季明信片').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text) Text('已完成制作').fontSize(12).fontColor(C.ok) }.alignItems(HorizontalAlign.Start).layoutWeight(1) } .width('100%').padding(14).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke }) }

技术拆解:

  1. 成果缩略:80×80 绿色渐变方块 + 40 号 🎓——承接制作页的成果(默认背景 🎓),是"要寄的东西"的视觉确认。
  2. 状态文字:"毕业季明信片"15 号加粗 + "已完成制作"12 号绿色(C.ok)——"已完成制作"用绿色传达"一切就绪,可以寄出"的正向信号,与发送按钮的绿色呼应。
  3. 信息架构:左缩略右描述(layoutWeight(1)撑开),让用户在发送前最后确认"寄的是什么"——发送前确认是工具类产品的必要环节。

三、ReceiverCard:接收人输入

ReceiverCard() { Column({ space: 10 }) { Text('接收人').fontSize(14).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%') TextInput({ placeholder: '输入接收人姓名或账号' }) .backgroundColor(C.cardSoft).borderRadius(D.rSm).height(44) .onChange((v: string) => { this.receiver = v; }) } .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke }) }
  • 单行 TextInput:44 高浅底圆角单行输入——接收人是短文本(姓名/账号),用单行输入框(区别于制作页的多行 TextArea)。
  • 占位文案:"输入接收人姓名或账号"——两种收件标识都支持,提示明确。
  • onChange 写入:@State receiver实时更新,供校验与最近联系人回填使用。
  • 校验核心:receiver.length是发送按钮分支判断的输入源——空则"请填写接收人"。

四、RecentCard:最近联系人(点击回填)

RecentCard() { Column({ space: 10 }) { Text('最近联系人').fontSize(14).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%') Row({ space: 12 }) { ForEach(this.recent, (c: Contact) => { Column({ space: 6 }) { Row() { Text(c.emoji).fontSize(24) } .width(48).height(48).backgroundColor(C.cardSoft).borderRadius(24).justifyContent(FlexAlign.Center) Text(c.name).fontSize(11).fontColor(C.textSub).maxLines(1) }.onClick(() => { this.receiver = c.name; promptAction.showToast({ message: '选择: ' + c.name }); }) }, (c: Contact) => c.id.toString()) }.width('100%').justifyContent(FlexAlign.SpaceBetween) } .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke }) }

4 位最近联系人(22-27 行):👩 妈妈(家人)/ 👨🏫 李老师(老师)/ 🧑🤝🧑 室友小张(朋友)/ 👧 同学小美(同学)——覆盖"家人/老师/朋友/同学"四类收件场景。

技术拆解:

  1. 头像 + 名称:48×48 浅灰圆底 + 24 号 emoji 头像,下方 11 号名称(maxLines(1)防溢出),Column({ space: 6 })上下组合。
  2. 点击回填:onClick(() => { this.receiver = c.name; ... })——点击联系人直接把名字写入接收人输入框(@State receiver驱动 TextInput 显示),同时 Toast"选择: 妈妈"确认——这是"快捷回填"的标准交互,省去手打姓名。
  3. SpaceBetween 均布:justifyContent(FlexAlign.SpaceBetween)让 4 位联系人等距分布整行——Row({ space: 12 })间距 + 两端对齐。
  4. key:c.id.toString()稳定唯一。

五、ModeCard:立即/定时发送

ModeCard() { Row({ space: 10 }) { ForEach(this.modes, (m: string, idx: number) => { Text(m).fontSize(13) .fontColor(this.sendMode === idx ? '#FFFFFF' : C.textSub) .textAlign(TextAlign.Center).layoutWeight(1).padding({ top: 11, bottom: 11 }) .backgroundColor(this.sendMode === idx ? C.primary : C.card).borderRadius(D.rSm) .border({ width: 1, color: this.sendMode === idx ? C.primary : C.stroke }) .onClick(() => { this.sendMode = idx; }) }, (m: string) => m) }.width('100%') }

2 种发送方式(21 行):['立即发送', '定时发送']。

技术要点:

  1. 等分胶囊:两个 TextlayoutWeight(1)平分,选中绿底白字 + 绿色描边、未选白底灰字 + 浅灰描边——选中态"底色 + 描边"双表达(与背景选择同款手法)。
  2. 定时发送的语义:选中"定时发送"产品化后应弹出时间选择器(DatePicker/TimePicker),设定未来时间投递——本页保留选择结构,定时 UI 是扩展点。
  3. 发送方式的联动:发送按钮文案可随方式变化("立即发送"vs"预约发送")——当前统一"发送明信片 💌",联动是产品化小事。

六、发送按钮与校验

Button('发送明信片 💌').fontSize(15).fontColor('#FFFFFF').backgroundColor(C.primary) .width('100%').height(48).borderRadius(D.rMd) .onClick(() => { promptAction.showToast({ message: this.receiver.length > 0 ? '发送成功' : '请填写接收人' }); })
  • 按钮文案:"发送明信片 💌"——动作 + 品牌 emoji,💌 强化"寄出心意"的情感。
  • 分支校验:receiver.length > 0 ? '发送成功' : '请填写接收人'——与 App 41/42 同款三元校验,接收人为空拦截(明信片必须有人收)。
  • 产品化链路:发送成功应把明信片(模板 + 背景 + 祝福语 + 字体 + 接收人)提交服务端 → 我的页"已发送" +1 → 若为定时发送则进入定时队列——当前分支反馈已预留。

七、跨页数据自洽:发送页是链路的"出口"

发送页完成"选模板 → 制作 → 发送"的最后一环:

  • 成果卡"毕业季明信片"↔ 首页毕业季模板 ↔ 制作页默认背景 🎓——三页同一模板,发送页是最终出口。
  • "已完成制作"状态↔ 制作页"下一步 · 发送"——制作完成才进入发送页,状态衔接自然。
  • 发送成功 → 我的页"26 已发送"——发送动作的累计统计。
  • 最近联系人↔ 我的页/通讯录——联系人是长期数据,我的页可管理(增删联系人)。
  • 定时发送↔ 发送成功后的延迟投递——数据链路扩展点。

八、实机截图与交互演示

本节结合 4 张实机截图,逐张还原发送页的交互链路。

1. 发送页首屏:成果卡 + 接收人 + 最近联系人

第一张截图是发送页默认首屏:顶部"发送明信片"标题;下方成果卡——80×80 绿色渐变 🎓 缩略 + "毕业季明信片"加粗 + 绿色"已完成制作";"接收人"卡(44 高浅底输入框,占位"输入接收人姓名或账号");"最近联系人"卡——👩 妈妈 / 👨🏫 李老师 / 🧑🤝🧑 室友小张 / 👧 同学小美 四个头像等距排布;再往下是"立即发送 / 定时发送"(第一个绿色选中)与底部绿色"发送明信片 💌"按钮。

2. 点击"妈妈":接收人回填 + Toast

第二张截图是点击最近联系人第一位"👩 妈妈"后的反馈:弹出"选择: 妈妈"Toast——@State receiver被写入"妈妈",接收人输入框随之回填(截图可见输入框显示"妈妈")。快捷回填免去手打姓名,是发送类页面的关键效率交互。

3. 点击"定时发送":方式切换

第三张截图是点击"定时发送"后的状态:绿色选中态从"立即发送"切换到"定时发送"(绿底白字 + 绿描边)——@State sendMode由 0 变 1。发送方式的选择结构完整,为"设定未来时间投递"的定时功能预留了位置。

4. 点击"发送明信片":校验 Toast

第四张截图是接收人为空(未选联系人)时点击"发送明信片 💌"按钮的反馈:弹出"请填写接收人"Toast——receiver.length > 0为 false 走校验分支。无效发送被拦截,用户得到明确引导(手输或点联系人),明信片不会"无处可寄"。

九、扩展思考:定时发送与投递设计

"定时发送"是本页的特色扩展,其产品设计值得展开:

  1. 定时场景:生日祝福零点送达、考试日鼓励早晨到达、纪念日准点寄出——定时让明信片"在该到的时刻到",情感价值倍增。
  2. 时间选择 UI:定时发送选中后弹出DatePicker(日期)+TimePicker(时间),选完显示"将于 X月X日 20:00 发送"确认文案——把抽象设定转化为明确承诺。
  3. 投递状态机:明信片的生命周期草稿 → 待发送 → 已发送/已投递 → 已读——定时发送处于"待发送"队列,我的页"已发送"展示投递状态(含定时任务管理:改期/取消)。
  4. 时区处理:定时必须按接收人时区投递(异地亲友)——@ohos.i18n时区转换是定时功能的隐藏难点。
  5. 推送提醒:定时投递成功可推送通知("你的明信片已送达 💌")——闭环的最后一环,与鸿蒙推送服务对接。

十、开发者视角:发送页的调试与验证技巧

  1. 回填的联动验证:点联系人后确认接收人输入框确实显示名字(@State receiver驱动 TextInput)——若 TextInput 显示不更新,检查是否用value: this.receiver绑定而非仅 onChange 监听(本页是受控/非受控混合的简化,产品化建议受控绑定)。
  2. 校验分支测试:分别测试"空接收人 + 点击"(请填写接收人)与"回填后 + 点击"(发送成功)两条路径——确认分支判断读取的是同一 receiver 状态源。
  3. SpaceBetween 的均布:justifyContent(FlexAlign.SpaceBetween)在 4 个联系人时两端对齐、中间均布;若增删联系人(3 个/5 个)均布依然成立,但名称过长会挤压——maxLines(1)已兜底。
  4. 定时发送的边界:若实现定时,需校验"时间必须晚于当前"(选过去时间应提示)——定时逻辑的基础校验。
  5. 键盘与按钮:接收人 TextInput 弹键盘压缩可视区,发送按钮可能被顶出——expandSafeArea或滚动适配(系列高频坑)。

十一、系列横向对比:发送/交付类页面的三种形态

发送页是"交付类页面",与系列其他"出口页面"对比可看清设计差异:

交付类型代表 App收件对象核心动作校验字段
明信片投递App 43 发送页接收人姓名发送接收人必填
照片上传App 40 上传页相册/活动上传照片数
技能发布App 38 发布页平台广场发布技能名称
表白发布App 41 发布页匿名墙发布内容非空

发送页的差异点:

  1. 对象是"特定的人":明信片寄给"妈妈/李老师"(指定收件人),而发布类内容寄给"广场"(平台所有人)——所以发送页有"接收人"与"最近联系人",是系列中唯一需要"收件人地址"的页面,这是传递型产品(点对点)与发布型产品(点对面)的本质区别。
  2. 联系人快捷回填:最近联系人点击填入接收人是"点对点"场景的效率刚需(反复寄给同一批人)——发布型产品没有此需求(不需要指定收件人),这是本页区别于所有发布页的独有交互。
  3. 定时发送:明信片可定时投递(零点祝福/纪念日)——"定时"是传递型产品的情感能力(让心意在该到的时刻到),发布型产品很少需要。
  4. 双向数据:发送后进入"已发送",对方收到进入"已收到"——双向档案(我的页 26/18)是传递型产品的数据特征。

十二、开发者视角:发送页的调试与验证技巧

  1. 回填的受控性:点击联系人后@State receiver更新,TextInput 显示应同步——若 TextInput 不显示回填值,改用value: this.receiver受控绑定(本页简化版依赖状态驱动,产品化建议显式受控)。
  2. 校验分支测试:空接收人(请填写接收人)与回填后(发送成功)两分支——重点验证"先点联系人再清空"的边界(清空后 receiver 为空应回拦截分支)。
  3. SpaceBetween 的均布:4 联系人两端对齐均布;3 或 5 个时均布仍成立但名称可能挤压——maxLines(1)已兜底,长名称("室友小张")在窄卡上注意省略。
  4. 定时时间校验:若实现定时,必须校验所选时间晚于当前(过去时间应提示重选)——定时功能的隐藏边界。
  5. 键盘适配:接收人 TextInput 弹键盘压缩可视区,发送按钮被顶出——expandSafeArea或滚动适配(系列高频坑),真机验证键盘弹出场景。

十三、扩展思考:明信片的送达与情感闭环

发送页是投递动作,而明信片的完整情感体验在"送达":

  1. 送达通知:明信片送达接收人后,发送方可收到"TA 已收到你的明信片 💌"通知——投递闭环让发送方知道心意已到达(我的页"已发送"可显示"已送达"状态角标)。
  2. 接收体验:接收方打开明信片有"拆信封"仪式(封面 → 开启动画 → 祝福语)——接收的仪式感决定明信片的情感价值,是产品体验的高光时刻。
  3. 回信机制:接收方可"回寄一张"(一键跳转制作页,复用发送方模板)——回信让明信片从单向投递变为双向对话,是"传递型产品"的互动升级。
  4. 明信片的收藏价值:收到的好看明信片可收藏(我的页"已收到"支持收藏)——明信片成为"收到的温暖"收藏品,长期留存。
  5. 隐私与可见性:明信片仅收件人可见(点对点隐私)——与发布型产品的公开内容不同,明信片的"私密投递"是产品信任的基础,隐私设置(我的页"设置")应明确管理。

十四、FAQ 与一句话总结

Q1:为什么发送页有"成果卡"确认?发送前确认"寄的是什么"是工具类产品的必要环节——成果卡让用户最后看一眼"毕业季明信片 · 已完成制作",避免"寄错东西"的尴尬,也承接制作页的成果形成流程连续性。

Q2:最近联系人点击后输入框真的回填了吗?是的——onClick里this.receiver = c.name写入@State,TextInput 显示随之更新(截图 2 可见"妈妈"回填)。这是"快捷回填"的标准交互,免去手打。

Q3:定时发送现在能定时间吗?Demo 只做了方式切换(高亮),未实现时间选择 UI。产品化后选中"定时发送"弹出日期时间选择器即可,选择结构已就绪。

Q4:发送成功后的明信片去哪了?Demo 只弹 Toast。产品化后进入我的页"已发送"列表(26 已发送 +1),接收人收到后在"已收到"出现——发送与接收的数据闭环是明信片产品的核心。

Q5:为什么接收人必须填?明信片必须有收件对象——空接收人发送是无效操作,所以校验拦截("请填写接收人")。这与 App 41/42 的"内容为空拦截"同理:关键字段必填校验是表单类页面的底线。

一句话总结:发送页以"成果确认 + 接收人输入 + 联系人回填 + 发送方式 + 校验提交"完成了明信片投递的最小闭环。最近联系人的"点击回填"是高效的效率交互、"已完成制作"的绿色状态与"发送明信片 💌"按钮形成"一切就绪"的正向闭环、接收人必填校验守住投递底线——113 行代码,一个完整可验证的明信片发送流程。

最后从验收视角确认:四张实机截图覆盖了首屏(成果卡+接收人+最近联系人)、联系人选择(妈妈回填 + Toast)、定时发送切换、空接收人校验(请填写接收人 Toast)四个状态,成果确认卡、接收人输入、最近联系人点击回填、立即/定时发送切换、接收人必填校验均验证正常。最近联系人(妈妈/李老师/室友小张/同学小美)覆盖家人/老师/朋友/同学四类收件场景。整页 113 行代码完成"成果确认 → 填写收件 → 选择方式 → 校验提交"的投递闭环,验收通过,可作为点对点投递类页面的参考范本。

补充开发问答:Q1 接收人输入与最近联系人点击会冲突吗?——不会,联系人点击写入@State receiver回填输入框,手输则覆盖,两条路径统一收敛到同一状态,互斥且无冲突。Q2 为什么最近联系人不带"更多"入口?——4 个联系人恰好一行(SpaceBetween 均布),更多联系人在产品化后由"通讯录"菜单(我的页)管理,发送页只展示高频 4 人。Q3 定时发送的 UI 藏在哪?——选中"定时发送"后弹出日期时间选择器(DatePicker/TimePicker),当前仅切换高亮,选择结构已就绪,定时 UI 是明显扩展点。Q4 发送按钮的 💌 emoji 有什么作用?——强化"寄出心意"的情感动作,让"发送"从冷冰冰的操作变成有温度的交付;与头像 💌、Tab 📮 的品牌符号体系呼应。Q5 发送成功后页面状态如何处理?——应清空接收人、Toast"发送成功"、跳转我的页"已发送"(26 +1),发送即归档,避免重复发送。发送页的每条交互都指向"心意准确送达"的目标。

最后以一句收尾:每一次投递,都是心意跨越距离的抵达。 回望整页,从成果确认到接收人填写,从联系人回填到定时发送,从校验拦截到发送成功,每个设计都在确保"心意准确、及时、体面地送达"——这就是发送页的全部价值,也是点对点投递类页面的标准参考。 按下发送的那一刻,心意便开始了它的旅程。 投递的终点是感动,而旅程的起点,正是这小小的发送页。

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

从零开始AI工程:数据、训练、部署到监控的完整实践指南

如果你最近也准备啃 AI 工程这块硬骨头,那这个标题里的 from scratch 我太有感触了。所谓 AI 工程,不是跑通一个 notebook 就算完事,而是从数据、模型、训练、评估、部署到监控,一条链路都能稳定落地。这个项目就是典型的最小化落…

作者头像 李华
网站建设 2026/10/3 6:04:48

带隙基准高阶温度补偿与启动电路设计详解

前面的铺垫如果你已经走完了,那么你手里现在应该有一个能跑出近似1.2V输出、温漂在30~60ppm/C量级的一阶带隙基准。这时候你大概率会盯着仿真曲线发呆:室温附近还行,可一到低温或者高温端,输出电压就开始往下弯,整条曲…

作者头像 李华
网站建设 2026/10/3 6:04:13

GitHub热榜日榜深度拆解:趋势洞察、项目评估与源码精读指南

刚开始接触开源项目的时候,我几乎每天都会打开 GitHub 的热榜页面刷一圈,看看今天又有什么新东西冒出来。时间长了发现,热榜这个东西,不只是“看热闹”的地方,它其实是一个极度浓缩的技术风向标。你只要持续盯一段时间…

作者头像 李华
网站建设 2026/10/3 6:03:45

Transformer原理与PyTorch实现:从自注意力到编码器-解码器架构详解

1. Attention Is All You Need——从“一句话”到一场架构革命2017年,一篇题为《Attention Is All You Need》的论文被放到arXiv上,彼时自然语言处理领域还在RNN、LSTM、GRU的统治之下。序列建模的常规打法是“一步步走”:当前时刻的输出依赖…

作者头像 李华
网站建设 2026/10/3 6:03:18

SolidWorks安装错误UNKNOWN\Components修复方法

办公桌上这台工作站前两天还在帮同事出图,今天重装 SolidWorks 就给我来了个下马威:安装管理程序跑到一半,弹窗提示“软件安装管理程序在创建该注册表项时遇到错误: UNKNOWN\Components”。点重试毫无反应,点忽略又怕装出个半残环…

作者头像 李华
网站建设 2026/10/3 6:01:47

LLM行为回溯系统:Hindsight设计与生产实践

1. 项目概述:Hindsight 不是“事后诸葛亮”,而是一套可落地的 LLM 行为回溯与决策归因系统最近在多个技术社区和内部工程组里,频繁看到hindsight这个词被单独拎出来讨论——不是作为形容词“事后之明”,而是作为一个具象化、可集成…

作者头像 李华