HarmonyOS 校园应用系列之ArkTS 表单工程:自习室选座页的三色优先级与开关配置

应用背景:09 自习室预约的选座创建页(Func1Tab.ets,215 行)是用户发起座位申请的核心表单。本文聚焦 FormCard 文本输入TypeCard 类型四选一PriorityCard 三色优先级SettingCard 开关配置TemplateCard 快捷模板 五大模块。

应用效果图

一、整体架构

选座页采用 "固定头部 + 可滚动表单区" 布局——Header 在 Scroll 之外,天然不随内容滚动:

Column
├── Header(标题"创建"+副标题+📋 图标按钮,白底底部描边)
└── Scroll (.scrollBar Off)
    ├── FormCard(基本信息:标题输入+描述文本域+附件行)
    ├── TypeCard(选择类型:四选一单选)
    ├── PriorityCard(优先级:低/中/高 三档)
    ├── SettingCard(提醒通知 Toggle + 隐私设置)
    ├── TemplateCard(快捷模板列表)
    └── SubmitBtn(提交按钮+条款说明)

Header 通过 .height(this.safeTop + 60) 适配状态栏,.alignItems(VerticalAlign.Bottom) 让内容沉底。

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

配图

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

标题行包含三部分:左侧"创建"大标题+副标题说明(纵向 Column)、中间弹性空白、右侧 📋 图标按钮(36vp 圆角方块)。头部白底 + 底部 1vp 描边,与滚动区形成分层。

三、FormCard —— 受控文本输入

3.1 表单卡片容器

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('标题')... TextInput(...) }

  // 描述文本域(带标签)
  Column({ space: 6 }) { Text('描述')... TextArea(...) }

  // 底部操作行(附件按钮 + 字数统计)
  Row() { ... }
}
.width('100%').padding(14).backgroundColor(C.card).borderRadius(D.rMd)
.border({ width: 1, color: C.stroke })

FormCard 用 Column 纵向排列所有表单元素,统一 14vp 内边距、card 背景色、rMd 圆角、stroke 描边。每个输入控件上方都有独立的小标签 Text。

3.2 标题输入(TextInput)

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%')

受控模式text: this.inputTitle 绑定状态值,.onChange() 回写。双向绑定确保 UI 与数据同步。输入框用 cardSoft 底色 + rSm 圆角,占位符 textDim 灰。

3.3 描述文本域(TextArea)

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%')

TextArea 比 TextInput 多行,高度 90vp 适合短描述场景。同样采用受控模式。

3.4 附件按钮 + 字数统计

表单底部一行包含两部分:

左侧:附件按钮组(图片📷 + 链接🔗)

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%')

附件按钮就是带 cardSoft 底色和内边距的 Text 胶囊,无点击事件,预留后续扩展点。

右侧:实时字数统计

Text(`${this.inputTitle.length}/50`).fontSize(10).fontColor(C.textDim)

统计的是 标题(inputTitle)长度而非描述,上限 50 字,固定 textDim 灰色显示。

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

选中态双标记:图标底 primarySoft 浅青底 + 2vp 主色描边,文字主色加粗。用 Row + ForEach + layoutWeight(1) 四等分(非 Grid)。

四种类型:类型一📝 / 类型二🎯 / 类型三⭐ / 类型四📊。

五、PriorityCard —— 三色优先级选择

优先级卡片用 水平 Row 布局三个选项,priorities 是纯字符串数组 ['低', '中', '高'],颜色由索引三元表达式决定:

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%')

卡片头部右侧还有当前选中档位的回显:Text(this.priorities[this.selectedPriority]) 主色加粗。

三级优先级的颜色语义:

级别颜色色值场景
绿色#2BB673普通需求
橙色#FF9F1C常规需求(默认选中,selectedPriority 初始 1)
红色#FF5A6E紧急需求

关键技巧idx === 2 ? C.danger : idx === 1 ? C.warn : C.ok——选中时白字实底(红/橙/绿),未选中时 textSub 灰字 + cardSoft 浅底。颜色语义遵循国际惯例:绿色安全、橙色注意、红色警告。

选座页 - 底部提交区

六、SettingCard —— Toggle 配置开关

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 开关,.selectedColor(C.primary) 定制选中色,默认开启)与 隐私设置(› 箭头入口),中间用 Divider().color(C.stroke) 分隔。layoutWeight(1) 将开关/箭头推到最右端。

选座页 - 表单完整视图

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

三个模板:快速创建(使用默认模板)/ 高级模式(自定义所有字段)/ 批量导入(从文件导入)。图标底用 primarySoft 浅青色区分于普通设置项,点击 Toast 提示。

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

防错机制:按钮文案与背景色由 this.inputTitle.length > 0 驱动——未填标题时显示"请填写标题"(cardSoft 灰底),填写后变"✓ 提交创建"(primary 青底)。点击时二次校验,空标题直接 Toast 拦截;成功后清空两个输入框。下方还有 9vp 的条款说明小字。

选座页 - 创建表单顶部

九、与 08 对比

特性08 会议室09 自习室
表单字段标题+描述+类型四选一+优先级三档标题+描述+类型四选一+优先级三档
选择控件Row + ForEach + layoutWeightRow + ForEach + layoutWeight(同构)
配置项提醒通知 Toggle + 隐私设置入口提醒通知 Toggle + 隐私设置入口(同构)
主题色靛蓝 #6366F1青绿 #14B8A6

08 与 09 的创建页 完全同构,仅主题色不同(靛蓝 vs 青绿),属于"同模板换肤"关系。

选座页 - 表单中部

十、总结

选座页的核心工程实践:

  1. 受控输入模式:TextInput/TextArea 双向绑定 + onChange 回写
  2. 字数统计${this.inputTitle.length}/50 实时显示标题长度
  3. 优先级语义配色:绿/橙/红三级索引三元 + 选中白字实底/未选中灰字浅底
  4. 提交防错:按钮文案/背景随 inputTitle 切换 + 点击二次校验 + 成功后清空表单

这套模式可直接复用于任何表单场景。

Logo

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

更多推荐