HarmonyOS 应用 · 室友评价评价页深度解析:四维星级打分与不可变更新

项目编号:50-roommate-rate 技术栈:HarmonyOS ArkTS · ArkUI 声明式 UI · Stage 模型 本篇页面:评价页 Func1Tab.ets(104 行) 系列导航:(一)首页 · (二)评价 · (三)数据 · (四)我的

50 评价页首屏


一、RateDim interface 与 scores 数组状态

interface RateDim {
  id: number;
  emoji: string;
  name: string;
  score: number;
}

四个字段:id 是 key 锚点,emoji 是维度图标,name 是维度名,score 是默认分值。

  @State activeMate: number = 0;
  @State scores: number[] = [5, 4, 5, 4];

  private mates: string[] = ['阿强', '小明', '大伟'];
  private dims: RateDim[] = [
    { id: 1, emoji: '🧹', name: '卫生习惯', score: 5 },
    { id: 2, emoji: '🌙', name: '作息规律', score: 4 },
    { id: 3, emoji: '🤝', name: '相处融洽', score: 5 },
    { id: 4, emoji: '🔊', name: '安静程度', score: 4 }
  ];

两个 @State:activeMate(选中室友索引)+ scores(四维评分数组 [5,4,5,4])。mates 是被评价室友(不含自己),dims 是四个评价维度:卫生习惯/作息规律/相处融洽/安静程度。scores 数组与 dims 数组索引对应——scores[0] 是 dims[0] 的评分。


二、setScore:不可变更新触发星级刷新

  private setScore(idx: number, s: number): void {
    this.scores[idx] = s;
    this.scores = this.scores.slice();
  }

setScore(idx, s) 两步不可变更新——先赋值 scores[idx] = s,再 slice() 生成新引用触发 @State 刷新。与班级任务首页的 toggle 同款模式:直接修改数组元素不触发刷新,必须 slice() 换引用。


三、页面骨架:MateSelect + RateCard + CommentCard + Button

  build() {
    Column() {
      this.Header()
      Scroll() {
        Column({ space: 16 }) {
          this.MateSelect()
          this.RateCard()
          this.CommentCard()
          Button('提交评价').fontSize(15).fontColor('#FFFFFF').backgroundColor(C.primary)
            .width('100%').height(48).borderRadius(D.rMd)
            .onClick(() => { promptAction.showToast({ message: '评价已提交' }); })
        }
        .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 内四块:MateSelect(室友选择)+ RateCard(评分卡)+ CommentCard(留言卡)+ Button(提交)。


四、MateSelect:activeMate 驱动的等分胶囊

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

activeMate === idx 驱动选中态:靛蓝底白字 + 靛蓝描边 vs 白底灰字 + 浅灰描边。三个室友等分,onClick 设 this.activeMate = idx 切换被评价对象。

50 评价页切换室友


五、RateCard:嵌套 ForEach 星级打分

  @Builder
  RateCard() {
    Column({ space: 16 }) {
      Text('评分维度').fontSize(14).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
      ForEach(this.dims, (d: RateDim, dimIdx: number) => {
        Row({ space: 10 }) {
          Text(d.emoji).fontSize(20)
          Text(d.name).fontSize(14).fontColor(C.text).layoutWeight(1)
          Row({ space: 4 }) {
            ForEach([1, 2, 3, 4, 5], (star: number) => {
              Text(this.scores[dimIdx] >= star ? '⭐' : '☆').fontSize(16)
                .onClick(() => { this.setScore(dimIdx, star); })
            }, (star: number) => star.toString())
          }
        }.width('100%')
      }, (d: RateDim) => d.id.toString())
    }
    .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
  }

嵌套 ForEach——外层遍历维度,内层遍历 [1,2,3,4,5] 星级:

// 外层:4 个评价维度
ForEach(this.dims, (d: RateDim, dimIdx: number) => {
  // 内层:5 颗星
  ForEach([1, 2, 3, 4, 5], (star: number) => {
    // scores[dimIdx] >= star → 实心星,否则空心星
    Text(this.scores[dimIdx] >= star ? '⭐' : '☆')
      .onClick(() => { this.setScore(dimIdx, star); })
  })
})

scores[dimIdx] >= star 驱动实心/空心星——scores[0] = 5 时 5 颗全实心,scores[1] = 4 时前 4 颗实心 + 第 5 颗空心。点击第 3 颗星调 setScore(dimIdx, 3)——scores[dimIdx] 变 3,前 3 颗实心 + 后 2 颗空心。这是星级打分的经典实现:比较驱动渲染,点击驱动赋值。

50 评价页星级调整


六、CommentCard:TextArea 留言

  @Builder
  CommentCard() {
    Column({ space: 10 }) {
      Text('评价留言').fontSize(14).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
      TextArea({ placeholder: '写下对室友的评价...' })
        .backgroundColor(C.cardSoft).borderRadius(D.rSm).height(90)
    }
    .width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
  }

TextArea 多行输入,height(90) 容纳多行评价文字。cardSoft 浅灰底 + rSm 小圆角。

50 评价页提交 Toast


七、可演进方向

方向现状改法收益
评价提交Toast 模拟写入评价记录 + 评分更新评价真实保存
室友切换scores 不重置切换时加载该室友已有评分切换不丢失数据
评分校验无至少一个维度打分才能提交评价完整性
留言绑定TextArea 无 onChange绑定 remark 状态留言随提交保存
多轮评价单次记录评价时间 + 历史追溯评价可回溯

评价页是室友评价的核心交互——activeMate 驱动室友切换、嵌套 ForEach 实现星级打分、setScore 的不可变更新触发星级刷新。从比较驱动的实心/空心星到 slice() 的数组刷新,每一层都有明确的职责边界。

系列导航:(一)首页 · (二)评价 · (三)数据 · (四)我的

Logo

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

更多推荐