HarmonyOS 应用 · 寝室投票创建页深度解析:选项动态增删与不可变更新

项目编号:49-dorm-vote 技术栈:HarmonyOS ArkTS · ArkUI 声明式 UI · Stage 模型 本篇页面:创建页 Func1Tab.ets(114 行) 系列导航:(一)首页 · (二)创建 · (三)投票 · (四)结果

49 创建页首屏


一、三个操作方法:增删改的三重边界

  @State question: string = '';
  @State options: string[] = ['', ''];
  @State multiChoice: boolean = false;

  private addOption(): void {
    if (this.options.length < 6) {
      this.options.push('');
      this.options = this.options.slice();
    }
  }

  private removeOption(idx: number): void {
    if (this.options.length > 2) {
      this.options.splice(idx, 1);
      this.options = this.options.slice();
    }
  }

  private setOption(idx: number, v: string): void {
    this.options[idx] = v;
  }

三个 @State:question(问题)、options(选项数组,初始 2 个空串)、multiChoice(多选开关)。三个方法构成增删改三重边界:

  • addOption():length < 6 上限 6 个选项,push('') 追加空选项 + slice() 生成新引用触发刷新
  • removeOption(idx):length > 2 下限 2 个选项,splice(idx, 1) 删除 + slice() 触发刷新
  • setOption(idx, v):直接赋值 options[idx] = v——修改无需新引用(ForEach 的 key 是索引,输入不影响列表结构)

投票至少 2 个选项、最多 6 个——边界的存在保证投票的合法性(1 个选项无意义,太多选项难以决策)。


二、页面骨架:QuestionCard + OptionCard + SettingCard + Button

  build() {
    Column() {
      this.Header()
      Scroll() {
        Column({ space: 16 }) {
          this.QuestionCard()
          this.OptionCard()
          this.SettingCard()
          Button('发起投票').fontSize(15).fontColor('#FFFFFF').backgroundColor(C.primary)
            .width('100%').height(48).borderRadius(D.rMd)
            .onClick(() => { promptAction.showToast({ message: this.question.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)
  }

Header 在 Scroll 外固定,Scroll 内四块:QuestionCard(问题)+ OptionCard(选项)+ SettingCard(多选设置)+ Button(发起投票)。按钮 onClick 空值校验:question.length > 0 才提示"投票已发起"。


三、QuestionCard:问题输入

  @Builder
  QuestionCard() {
    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.question = v; })
    }
    .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
  }

14 号加粗"投票问题" + TextInput,placeholder 带示例"例如:今晚点什么外卖?"——示例占位符降低用户输入门槛。onChange 写入 this.question。


四、OptionCard:动态选项列表

  @Builder
  OptionCard() {
    Column({ space: 12 }) {
      Text('候选选项').fontSize(14).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
      ForEach(this.options, (opt: string, idx: number) => {
        Row({ space: 10 }) {
          Text((idx + 1).toString()).fontSize(13).fontColor(C.primary)
            .width(26).height(26).textAlign(TextAlign.Center).backgroundColor(C.primarySoft).borderRadius(13)
          TextInput({ placeholder: '选项 ' + (idx + 1), text: this.options[idx] })
            .backgroundColor(C.cardSoft).borderRadius(D.rSm).height(42).layoutWeight(1)
            .onChange((v: string) => { this.setOption(idx, v); })
          if (this.options.length > 2) {
            Text('✕').fontSize(16).fontColor(C.textDim)
              .onClick(() => { this.removeOption(idx); })
          }
        }.width('100%')
      }, (opt: string, idx: number) => idx.toString())
      if (this.options.length < 6) {
        Row({ space: 6 }) {
          Text('+').fontSize(16).fontColor(C.primary)
          Text('添加选项').fontSize(13).fontColor(C.primary)
        }
        .width('100%').justifyContent(FlexAlign.Center).padding({ top: 10, bottom: 10 })
        .backgroundColor(C.cardSoft).borderRadius(D.rSm)
        .onClick(() => { this.addOption(); })
      }
    }
    .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
  }

三个条件渲染——if 分支控制增删按钮:

// 序号圆:26×26 浅粉底 + 紫色序号
Text((idx + 1).toString()).width(26).height(26).backgroundColor(C.primarySoft).borderRadius(13)

// ✕ 删除按钮:选项数 > 2 时渲染
if (this.options.length > 2) { Text('✕').onClick(() => { this.removeOption(idx); }) }

// + 添加按钮:选项数 < 6 时渲染
if (this.options.length < 6) { Row({ space: 6 }) { ... }.onClick(() => { this.addOption(); }) }

恰好 2 个选项时无 ✕(下限保护),恰好 6 个选项时无 + (上限保护)——条件渲染与增删边界严格对应。TextInput 的 text: this.options[idx] 绑定当前值,onChange 调 setOption(idx, v) 写回数组。

ForEach key 用 idx.toString()——选项是动态增删的列表,索引用作 key 保证增删时列表正确重建。

49 创建页添加选项


五、SettingCard:Toggle 多选开关

  @Builder
  SettingCard() {
    Row() {
      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.multiChoice })
        .selectedColor(C.primary)
        .onChange((v: boolean) => { this.multiChoice = v; })
    }
    .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
  }

Toggle({ type: ToggleType.Switch, isOn: this.multiChoice }) + selectedColor(C.primary) 开启时粉色圆点。onChange 写回 this.multiChoice。左侧双行文案:14 号"允许多选" + 11 号灰色说明"开启后可选择多个选项"。

49 创建页校验 Toast

49 创建页完整视图


六、可演进方向

方向现状改法收益
发起写入Toast 模拟追加到首页 votes 数组投票真实创建
选项校验仅问题校验校验选项非空 + 去重选项合法
截止时间无DatePicker 设置截止投票限时结束
匿名投票无匿名开关 + 结果隐藏投票人隐私保护
表单重置无发起后清空 question/options连续创建

创建页是投票的录入入口——三个操作方法(增删改)+ 三重边界(2 下限/6 上限/条件渲染)。从 push + slice 的不可变更新到 Toggle 的多选开关,每一层都有明确的职责边界。

系列导航:(一)首页 · (二)创建 · (三)投票 · (四)结果

Logo

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

更多推荐