鸿蒙原生应用 ArkUI 实战:双卡分离表单 + 三档提醒胶囊 —— 续费添加页的录入架构

App 55「自动续费提醒」添加页(Func1Tab),主题色 #FF6B6B 珊瑚红,4 个 Tab 分别为首页(⏰)、添加(➕)、日历(📅)、我的(👤)。本篇基于 55-renewal-reminder/entry/src/main/ets/pages/Func1Tab.ets(共 97 行)逐段拆解,附 4 张实机截图。

添加页整体布局

一、整体结构:三区"Header + 表单卡 + 提醒卡"布局

添加页是"服务信息录入 + 提醒规则设置"的组合布局——2 个 @Builder 块 + 1 个按钮:

build() {
  Column() {
    this.Header()
    Scroll() {
      Column({ space: 16 }) {
        this.FormCard()
        this.RemindCard()
        Button('添加提醒').fontSize(15).fontColor('#FFFFFF').backgroundColor(C.primary)
          .width('100%').height(48).borderRadius(D.rMd)
          .onClick(() => { promptAction.showToast({ message: this.name.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)
}

3 块结构:FormCard(四件套服务表单)→ RemindCard(提前提醒三档胶囊)→ 添加提醒按钮。与 App 52/53 的"单卡表单"不同,本页把表单拆成两张卡——FormCard 管"扣什么"(服务信息),RemindCard 管"何时提醒"(提醒规则)。信息录入与规则设置分离成卡,职责边界清晰。

二、四状态声明 + 数据模型

@State name: string = '';
@State amount: string = '';
@State activeCycle: number = 0;
@State remindDays: number = 3;
private cycles: string[] = ['每月', '每季', '每年'];
private remindOptions: number[] = [1, 3, 7];

四个 @State:name(服务名称)、amount(金额)、activeCycle(周期索引,默认 0 = 每月)、remindDays(提醒天数,默认 3)。注意 remindDays 存的是天数数值而非索引——remindOptions 是 [1, 3, 7],选中值直接用天数比较(this.remindDays === d),比索引更语义化。

三、Header:单行标题

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)
}

20 号加粗、safeTop + 56 固定高、文字贴底——系列功能页 Header 统一范式。"添加提醒"是动词短语(动作型标题),暗示"这是一个待完成的操作"。

四、FormCard:四件套服务表单

FormCard() {
  Column({ space: 14 }) {
    Column({ space: 6 }) {
      Text('服务名称').fontSize(13).fontColor(C.textSub).width('100%')
      TextInput({ placeholder: '例如:视频VIP会员' })
        .backgroundColor(C.cardSoft).borderRadius(D.rSm).height(44)
        .onChange((v: string) => { this.name = v; })
    }
    Column({ space: 6 }) {
      Text('扣款金额(元)').fontSize(13).fontColor(C.textSub).width('100%')
      TextInput({ placeholder: '0.00' }).type(InputType.Number)
        .backgroundColor(C.cardSoft).borderRadius(D.rSm).height(44)
        .onChange((v: string) => { this.amount = v; })
    }

前两件套:

  1. 服务名称:TextInput 44 高淡灰底圆角 10,onChange 绑定 this.name——保存按钮校验的锚点字段(与 App 52 金额、App 53 名称同逻辑:校验最核心的标识字段)。
  2. 扣款金额:InputType.Number 数字键盘 + 占位符"0.00"——金额字段调起数字键盘减少输入错误,与 App 52 金额输入同款处理。
    Column({ space: 6 }) {
      Text('扣款周期').fontSize(13).fontColor(C.textSub).width('100%')
      Row({ space: 10 }) {
        ForEach(this.cycles, (c: string, idx: number) => {
          Text(c).fontSize(13)
            .fontColor(this.activeCycle === idx ? '#FFFFFF' : C.textSub)
            .textAlign(TextAlign.Center).layoutWeight(1).padding({ top: 9, bottom: 9 })
            .backgroundColor(this.activeCycle === idx ? C.primary : C.cardSoft).borderRadius(D.rSm)
            .onClick(() => { this.activeCycle = idx; })
        }, (c: string) => c)
      }.width('100%')
    }
    Column({ space: 6 }) {
      Text('扣款日期').fontSize(13).fontColor(C.textSub).width('100%')
      TextInput({ placeholder: '每月 1 日' })
        .backgroundColor(C.cardSoft).borderRadius(D.rSm).height(44)
    }

后两件套:

  1. 扣款周期 3 等分胶囊:每月/每季/每年——layoutWeight(1) 三等分 + textAlign(TextAlign.Center) 居中,选中红底白字(C.primary 珊瑚红)。与 App 53 的"保修期限 4 等分"同款布局,选项少(3 项)用等分比换行更高效。
  2. 扣款日期:TextInput 占位符"每月 1 日"——注意此项无 onChange 绑定(76 行无状态写入),是四件套中唯一的"展示型输入框",产品化需补绑定或换 DatePicker。

选择扣款周期胶囊

五、RemindCard:提前提醒三档胶囊

RemindCard() {
  Column({ space: 12 }) {
    Text('提前提醒').fontSize(14).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
    Row({ space: 10 }) {
      ForEach(this.remindOptions, (d: number) => {
        Text('提前 ' + d + ' 天').fontSize(12)
          .fontColor(this.remindDays === d ? '#FFFFFF' : C.textSub)
          .textAlign(TextAlign.Center).layoutWeight(1).padding({ top: 9, bottom: 9 })
          .backgroundColor(this.remindDays === d ? C.primary : C.cardSoft).borderRadius(D.rSm)
          .onClick(() => { this.remindDays = d; })
      }, (d: number) => d.toString())
    }.width('100%')
  }
  .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
}

逐点拆解:

  1. 独立成卡:提醒设置不混在服务表单里,单独一张卡——"扣什么"与"何时提醒"是两类信息,分卡降低认知负荷。
  2. 天数直存:ForEach(this.remindOptions, (d: number)——遍历的是 [1, 3, 7] 数值数组,onClick 直接 this.remindDays = d 存天数(非索引),语义直观。
  3. 默认 3 天:remindDays = 3——中位数默认值,与首页临期阈值(daysLeft <= 3)一致,用户添加的提醒与首页告警天然对齐。
  4. 胶囊文案带前缀:"提前 1 天/提前 3 天/提前 7 天"——文案自带"提前"语境,比光秃秃的数字更易懂。

选择提前提醒天数

六、保存按钮:名称校验

Button('添加提醒').fontSize(15).fontColor('#FFFFFF').backgroundColor(C.primary)
  .width('100%').height(48).borderRadius(D.rMd)
  .onClick(() => { promptAction.showToast({ message: this.name.length > 0 ? '提醒已添加' : '请填写服务名称' }); })

逐点拆解:

  1. 全宽红色 CTA:C.primary 珊瑚红全宽 48 高——表单页标准 CTA 形态。
  2. 三元校验:this.name.length > 0 ? '提醒已添加' : '请填写服务名称'——以名称为校验锚点(服务名称是提醒的标识,没有名字的提醒无法识别)。
  3. 系列校验锚点对比:App 52 校验金额(票据核心是钱)、App 53 校验名称(商品核心是名)、App 55 校验名称(提醒核心是名)——校验锚点永远指向"没有它记录就没意义"的字段。

表单填写与添加按钮

七、跨页数据自洽:提醒是续费体系的"录入口"

  • 添加页"扣款周期 每月/每季/每年" ↔ 日历页各项的月份分布——周期决定扣款频率,日历时间轴按周期展开(月付项每月出现、年费项一年一次)。
  • 添加页"提前提醒 3 天"(默认) ↔ 首页"临期 ≤3 天"——提醒天数 = 告警阈值,用户设 3 天提醒,首页就把 3 天内的扣款标红,录入即生效。
  • 添加页"服务名称 + 金额" ↔ 首页列表"名称 + 金额"——保存的新提醒进入首页列表(产品化后 items.push()),录入即展示。
  • 添加页"扣款日期" ↔ 日历页"9月1日/9月5日"——录入的日期决定日历时间轴上的位置。

八、系列横向对比:录入页的三种分卡策略

录入页代表 App分卡策略胶囊布局主题色
双卡分离App 55 续费服务卡 + 提醒卡周期 3 等分 + 提醒 3 等分#FF6B6B 珊瑚红
单卡五件套App 53 商品一卡全包分类 5 换行 + 期限 4 等分#00A862 生态绿
上传卡 + 表单App 52 票据来源卡 + 表单卡类型/分类双换行#0066CC 科技蓝

差异点:App 55 的"双卡分离"——把"提醒规则"从服务表单中独立成卡,因为提醒天数是本应用的差异化能力(续费提醒的核心是"何时提醒"而非"记录什么")。App 53 把分类/期限混在一卡(都是商品属性),55 把提醒单列(规则 ≠ 属性),分卡的依据是信息的心智归属。

九、开发者视角:调试与验证技巧

  1. 天数存值 vs 存索引:remindDays = 3 存天数(this.remindDays === d),activeCycle = 0 存索引(this.activeCycle === idx)——同页两种选中态策略,天数选项语义化(值即含义)所以直存数值,周期选项是字符串所以存索引;混用不冲突但要在团队内统一约定。
  2. 扣款日期无绑定:76 行的 TextInput 无 onChange——演示漏绑,产品化必须补 onChange 存状态,否则保存时取不到日期;更优方案是周期选"每月"时用数字选择器选日、选"每年"时用 DatePicker 选月日。
  3. 金额格式校验:InputType.Number 只限键盘类型——产品化应 amount.replace(/[^\d.]/g, '') 过滤非法字符并 toFixed(2) 统一两位小数。
  4. 默认值联动:周期默认"每月"时日期占位符"每月 1 日"——产品化应让占位符随周期切换变化(选"每年"变"每年 1 月 1 日"),占位符是"活的提示语"。

十、演进方向

方向当前状态产品化演进
日期输入TextInput 无绑定按周期切换选择器(每月→选日/每年→DatePicker)
金额校验Number 键盘非法字符过滤 + 两位小数格式化
提醒持久化Toast 占位Preferences 存储 + 通知权限申请
周期扩展3 档固定支持自定义周期(每 N 天)
保存反馈Toast"提醒已添加"返回首页刷新 + 新提醒置顶

十一、一句话总结

添加页以"双卡分离表单 + 三档提醒胶囊"完成了续费提醒的录入闭环。FormCard 四件套(名称/金额 Number 键盘/周期 3 等分胶囊/日期)+ RemindCard 独立成卡(remindDays 天数直存、默认 3 天与首页临期阈值同源)、名称三元校验——97 行代码,一个"录服务 → 设周期 → 定提醒 → 保存"的提醒建档页,也是"属性与规则分卡"型表单的标准范式。

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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐