HarmonyOS 校园应用系列之ArkTS 表单工程:会议室预约页的六卡纵向拆分与快捷模板

一、页面定位与功能概述

预约页(Func1Tab)是会议室预约 App 的 "录入中心"——用户创建记录,填写标题与描述,选择类型和优先级,设置提醒通知,还可套用快捷模板。标题 "创建",副标题 "填写信息快速创建"

页面由六个卡片纵向组成:FormCard(基本信息)→ TypeCard(选择类型)→ PriorityCard(优先级)→ SettingCard(提醒/隐私)→ TemplateCard(快捷模板)→ SubmitBtn(提交)。

@State inputTitle: string = ''       // 标题
@State inputDesc: string = ''        // 描述
@State selectedType: number = 0      // 类型(图标四选一)
@State selectedPriority: number = 1  // 优先级(默认"中")
@State remindOn: boolean = true      // 提醒通知开关

selectedPriority 默认值 1——初始即选中"中"档;索引单选的默认态完全由初始值决定。


应用效果图

预约创建页首屏

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

配图

二、FormCard —— 标题与描述录入

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%')
// 描述用 TextArea,height(90)

TextInput({ placeholder, text: this.inputTitle }).onChange(...) 标准受控组件,输入框高 44vp。表单分区用 14vp 内边距的边框卡片包裹,卡片标题行右侧有 关闭符,底部还有 📷 图片 / 🔗 链接 快捷标签与 ${this.inputTitle.length}/50 字数统计。


三、TypeCard —— 图标四选一(单选)

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

3.1 选中态的三重反馈

选中项同时变化三处:图标底色 C.cardSoft → C.primarySoft、描边 0 → 2vp C.primary、文字变主题色加粗。"底色+描边+字色"三重反馈 比单一反色更立体。

3.2 ForEach + layoutWeight 等分布局

四个选项用 ForEach(this.types, ...) 渲染,每项 .layoutWeight(1) 等分整行宽度——无需计算宽度,自动适配屏幕。键值生成器用 item.name 保证唯一。


四、PriorityCard —— 三档单选胶囊

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

4.1 语义化条件配色

选中色随档位变化:idx === 2 ? C.danger : idx === 1 ? C.warn : C.ok——高=红、中=橙、低=绿。颜色即语义,用户未读文字也能感知轻重。未选中项统一 C.cardSoft 底、C.textSub 字。

4.2 标题行右侧的当前值回显

Text(this.priorities[this.selectedPriority]) 在标题行右侧用主题色加粗回显当前档位——选中状态不止一处可见,扫一眼标题行即可确认。


预约页 - 类型与时段选择

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

5.1 一卡两行:开关行 + 跳转行

SettingCard 用 Column({ space: 0 }) 装两行设置项,中间 Divider().color(C.stroke) 分隔——"设置组"卡片 的标准结构。第一行是 Toggle 开关(提醒通知),第二行是 箭头跳转项(隐私设置)。

5.2 Toggle 受控绑定

Toggle({ type: ToggleType.Switch, isOn: this.remindOn })
  .selectedColor(C.primary)
  .onChange((on: boolean) => { this.remindOn = on; })

Toggle 的 isOn 直接绑定 this.remindOnonChange 回写。表单场景最简单的受控组件——仅一个布尔状态。注意卡片外层只设左右 14vp 内边距,行内各自用 padding({ top: 12, bottom: 12 }) 控制高度,Divider 才能通栏。


六、TemplateCard —— 快捷模板列表

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

三个模板(快速创建/高级模式/批量导入)渲染为图标+标题+描述+箭头的行列表,点击 Toast 提示。模板图标底色用 C.primarySoft(区别于设置项的 C.cardSoft),暗示"可点选的预设"。


七、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%')
}

7.1 动态文案 + 点击时校验

按钮文案随标题输入实时切换:未填时显示 "请填写标题"(灰底 C.cardSoft),填好后变 "✓ 提交创建"(主题色底)。注意源码 没有用 .enabled() 禁用按钮——按钮始终可点,校验放在 onClick 内:标题为空时 Toast 报"请输入标题"并 return,成功则 Toast"创建成功!"并清空 inputTitle/inputDesc 两个状态。这是 "动态文案引导 + 点击时校验" 模式,与 .enabled() 防错禁用是两种不同取舍。


预约页 - 设置与提交

八、表单设计模式总结

组件模式状态类型
TextInput/TextArea受控输入string
TypeCard图标四选一(单选)number
PriorityCard三档胶囊(单选)number
Toggle布尔开关boolean
SubmitBtn动态文案+点击校验

关键经验:①单选索引默认值即初始选中态(如 selectedPriority=1 默认“中”)②语义化配色让档位轻重一目了然 ③提交按钮动态文案引导、onClick 内校验兜底 ④成功后清空表单状态复位。


预约页 - 完整创建表单

九、与 07 表单对比

有趣的事实:07 校园打印与 08 会议室预约的 Func1Tab 结构完全相同——同样的 FormCard/TypeCard/PriorityCard/SettingCard/TemplateCard/SubmitBtn 六卡片,同样的状态变量与交互逻辑,是同一套模板在不同业务域的复用。

特性07 校园打印08 会议室预约
页面结构六卡片表单完全相同
状态变量inputTitle/inputDesc/selectedType/selectedPriority/remindOn完全相同
主题色绿色系 #10B981靛蓝系 #6366F1
提交逻辑动态文案+onClick 校验完全相同

差异仅在 Theme.ets 的主题色定义——换肤不改结构 正是 C/D 双类主题体系的价值:业务模板沉淀后,新项目只需替换色板即可快速产出。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐