鸿蒙原生应用实战:校园投票我的页 —— 四块最简结构的个人中心设计

App 22「校园投票管理」我的 Tab(ProfileTab),是个人中心页——结构最简洁(4 块):渐变用户卡(🗳️ 投票达人 + 参与 28 次投票)+ 4 栏统计(创建投票 6/参与投票 28/历史投票 12/进行中 3)+ 5 菜单(参与记录/创建投票/历史投票/投票统计/设置)+ About(校园投票管理 v1.0.0)。与 App 21 问卷我的页同构(4 块最简 + 92% 宽 + 负边距悬浮)——是"任务型应用"的典型个人中心。本篇基于 22-campus-vote/entry/src/main/ets/pages/ProfileTab.ets(约 128 行)逐段拆解,附 4 张实机截图。

一、整体结构:4 个 @Builder 的最简个人中心

ProfileTab 是系列最简洁的个人中心——4 个 @Builder(UserCard / StatRow / MenuList / About):

build() {
  Column() {
    Scroll() {
      Column() {
        this.UserCard()
        this.StatRow()
        this.MenuList()
        this.About()
      }
      .width('100%')
      .padding({ bottom: D.pad + this.safeBottom + 20 })
    }
    .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top)
  }
  .width('100%').height('100%').backgroundColor(C.bg)
}

4 块顺序:UserCard(身份)→ StatRow(数据)→ MenuList(资产)→ About(关于)。

与 App 21 问卷我的页 100% 同构(结构/StatRow 92% 宽 + 负边距/MenuList emoji+标题+>/About 双行版本)——"任务型个人中心模板"再次复用

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

二、UserCard:紫色渐变 + 称号型身份

UserCard 是简洁的横向用户卡(与 App 21 同款"最简 UserCard"):

@Builder UserCard() {
  Row({ space: 14 }) {
    Row() {
      Text('🗳️').fontSize(32)
    }
    .width(64).height(64)
    .backgroundColor('#33FFFFFF').borderRadius(32)
    .justifyContent(FlexAlign.Center)

    Column({ space: 5 }) {
      Text('投票达人').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
      Text('参与 28 次投票').fontSize(12).fontColor('#FFFFFF').opacity(0.85)
    }
    .alignItems(HorizontalAlign.Start).layoutWeight(1)
  }
  .width('100%')
  .padding({ top: this.safeTop + 30, left: D.pad, right: D.pad, bottom: 44 })
  .linearGradient({ angle: 135, colors: [[C.primary, 0.0], [C.accent, 1.0]] })
}

关键设计

  1. 64×64 正圆头像borderRadius(32))—— 系列标准(App 18/19/20/21/22 同款 64×64)
  2. 🗳️ 投票箱 emoji —— 投票 App 的视觉符号(与 App 18"张同学"/App 19"📷"/App 21"📊"/App 20"📚"同款"业务 emoji 头像")
  3. "投票达人"称号 —— 称号型身份(App 19"摄影爱好者"/App 21"问卷达人"同款)——"业务 + 身份"组合
  4. "参与 28 次投票" Slogan —— 业务型 Slogan(不用"已使用 X 天")——"我的投票身份"叙事
  5. 紫色 135° 渐变 —— linearGradient 主题色

bottom: 44 给 StatRow margin: -24 留 20vp 实际重叠——**"卡片叠加悬浮"**效果——比 App 14/15/16 的 bottom: 16 + margin: -28 略多

校园投票我的页首屏 · 紫色渐变用户卡+🗳️投票达人+参与28次投票+4栏统计(创建投票6/参与28/历史12/进行中3)+5菜单(参与记录/创建投票/历史投票/投票统计/设置)+底部Tab栏

三、StatRow:4 栏统计(投票双重视角)

StatRow 是 4 栏统计(与 App 21 我的页统计项结构一致):

private stats: StatItem[] = [
  { value: '6', label: '创建投票' },
  { value: '28', label: '参与投票' },
  { value: '12', label: '历史投票' },
  { value: '3', label: '进行中' }
];

4 栏的"投票双重视角"

数字标签视角
6创建投票创作者(我创建了 6 份投票)
28参与投票填写者(我参与了 28 次投票)
12历史投票已结束/已投票(12 条历史)
3进行中待处理/未结束(3 条进行中)

"创作者 + 填写者"双重视角(与 App 21 问卷的"我的问卷 + 填写记录"一致)——投票平台也是双向 UGC

  • 创作者:我发起的(6 份创建 + 3 进行中 + 部分历史)
  • 填写者:我参与的(28 次参与 + 12 条历史)

"28 次参与"是 UserCard Slogan 的数据来源 —— "参与 28 次投票" = StatRow 第 2 栏——"用户卡 Slogan = 统计指标" 跨组件数据呼应。

4 栏同色 C.text 深色——"颜色留给状态"——投票统计没有"好/坏"语义(不像满意度 98% 那种),统一深色更稳重。

width('92%') 92% 宽 + margin({ top: -24 }) —— 与 App 21 同款——"卡片叠加"(与 UserCard 底部 20vp 重叠)。

StatRow @Builder 完整源码

StatRow 的 @Builder 实现(76-93 行)是系列"4 栏统计"的标准写法:

@Builder
StatRow() {
  Row() {
    ForEach(this.stats, (s: StatItem) => {
      Column({ space: 4 }) {
        Text(s.value).fontSize(18).fontWeight(FontWeight.Bold).fontColor(C.text)
        Text(s.label).fontSize(11).fontColor(C.textDim)
      }
      .layoutWeight(1)
    }, (s: StatItem) => s.label)
  }
  .width('92%')
  .padding({ top: 14, bottom: 14 })
  .backgroundColor(C.card)
  .borderRadius(D.rMd)
  .border({ width: 1, color: C.stroke })
  .margin({ top: -24 })
}

StatRow @Builder 关键设计

  1. ForEach(this.stats, ...) 遍历 4 栏——每栏一个 Column({ space: 4 }),数字在上(18sp 加粗)、标签在下(11sp 灰色)——"数字 + 标签"垂直组合

  2. .layoutWeight(1) 等分 4 栏——4 个 Column 各占 1/4 宽——**"4 栏等宽"**是统计行的标准布局。

  3. width('92%') + margin({ top: -24 })——92% 宽 + 负 24vp 上边距——"悬浮在 UserCard 底部"——与 UserCard 的 bottom: 44 配合(44 - 24 = 20vp 实际重叠)。

  4. .border({ width: 1, color: C.stroke })——1vp 描边——"卡片边框"——让 StatRow 在视觉上与 UserCard 区分开(不使用阴影,用边框更轻量)。

  5. padding({ top: 14, bottom: 14 })——上下 14vp——"上下宽松"——因为 4 栏已经等分,左右无需额外 padding。

StatRow 与 UserCard 的"悬浮叠加"是本系列个人中心的标志性设计——负边距让两张卡片"咬合"——视觉上"用户卡 + 统计卡 = 一个整体"——比"两张独立卡片"更有层次感

四、MenuList:5 项菜单(emoji + 标题 + >

MenuList 是 5 项菜单(与 App 21 问卷的 5 项菜单同款"最简结构"):

private menus: MenuItem[] = [
  { id: 1, emoji: '📋', title: '参与记录' },
  { id: 2, emoji: '➕', title: '创建投票' },
  { id: 3, emoji: '📜', title: '历史投票' },
  { id: 4, emoji: '📊', title: '投票统计' },
  { id: 5, emoji: '⚙️', title: '设置' }
];

5 项菜单的"投票业务"覆盖

菜单业务
📋 参与记录我投过哪些(填写者历史)
➕ 创建投票发起新投票(创作者入口)
📜 历史投票已结束的投票
📊 投票统计个人数据看板
⚙️ 设置通用

5 项按"使用频次"排序

  • 高频:参与记录、创建投票(日常操作)
  • 中频:历史投票、投票统计(偶尔查)
  • 低频:设置(少用)

"➕ 创建投票"和"📊 投票统计"是投票平台特色菜单——前者是创作者入口,后者是数据看板——"特色菜单 = 业务核心"

"📜 历史投票"是 App 22 与 App 21 的差异——投票需要"历史记录"(投过什么可追溯),问卷是"匿名参与"(不需要历史)——"业务差异决定菜单"

> 箭头 + emoji + 标题 三件套——"最简菜单结构"(无副标题/无角标/无图标块)——与 App 21 问卷完全一致。

五、MenuList 的"5 项"结构

@Builder MenuList() {
  Column() {
    ForEach(this.menus, (m: MenuItem, idx: number) => {
      Row({ space: 12 }) {
        Text(m.emoji).fontSize(18)
        Text(m.title).fontSize(14).fontColor(C.text).layoutWeight(1)
        Text('>').fontSize(14).fontColor(C.textDim)
      }
      .width('100%')
      .padding({ left: 14, right: 14, top: 14, bottom: 14 })
      .onClick(() => { promptAction.showToast({ message: m.title }); })
      if (idx < this.menus.length - 1) {
        Divider().color(C.stroke).strokeWidth(0.5)
      }
    }, (m: MenuItem) => m.title)
  }
  .width('92%')
  .margin({ top: 16 })
  .backgroundColor(C.card)
  .borderRadius(D.rMd)
  .border({ width: 1, color: C.stroke })
}

width('92%') 92% 宽 + margin({ top: 16 }) —— 与 StatRow 的 92% 宽形成"双 92%""两条 92% 卡片叠加" —— 视觉上"StatRow + MenuList 都是 92% 宽"——"等宽 = 一组"。

if (idx < this.menus.length - 1) 最后一条不加分隔线 —— 用 idx 判断(更可靠)—— Divider().strokeWidth(0.5) 0.5vp 超细分隔线。

六、About:双行版本信息

About 是简洁的双行版本信息(与 App 21 问卷同款):

@Builder About() {
  Column({ space: 4 }) {
    Text('校园投票管理').fontSize(12).fontColor(C.textDim)
    Text('Version 1.0.0').fontSize(11).fontColor(C.textDim).opacity(0.7)
  }
  .width('100%').margin({ top: 24 })
}

"校园投票管理 / Version 1.0.0" —— App 名 + 版本号 —— 系列标准 About(App 13-21 同款)——opacity(0.7) 让版本号行更淡。

App 22 没有"© 2024 校园"版权行(App 14/15/16 有)—— "最简 About"(App 21/22 同款)。

七、@State 数量 = 0(系列第 6 个"0 @State 个人中心")

ProfileTab 0 个 @State——所有数据 private 不可变。这是系列第六个"0 @State"个人中心(App 13/17/20/21/22 + Func2Tab)——"展示型页面 = 0 @State"规律继续成立。

4 个 Tab 的 @State 分布(App 22):

页面@State类型
首页1(tab)Tab 视觉切换
投票2(picked/voted)真交互
结果0纯展示
我的0纯展示

App 22 是"任务型 2 状态控制"的代表——picked/voted 2 个状态精确控制整个投票交互。

八、数据一致性

UserCard "参与 28 次投票" = StatRow 第 2 栏 "参与投票 28" ✓——Slogan 和统计的精确对应——读者一看就知道"这 28 次是从 StatRow 来的"。

没有"3 进行中"vs 首页/结果页的直接数据——"进行中"是用户维度(我投的还在进行中的 3 个)——不是平台维度(首页 4 张投票卡是"平台所有进行中")——两个不同维度的"进行中"

九、与 App 21 问卷我的页的 100% 同构

App 22 我的页与 App 21 问卷我的页几乎 100% 同构——模板复刻第 4 次:

组件App 21(问卷)App 22(投票)
UserCard📊 问卷达人 + 8 份问卷🗳️ 投票达人 + 28 次投票
StatRow 4 栏我的问卷/填写记录/收集份数/进行中创建投票/参与投票/历史投票/进行中
MenuList 5 项我的问卷/填写记录/数据导出/模板收藏/设置参与记录/创建投票/历史投票/投票统计/设置
About在线问卷调查 v1.0.0校园投票管理 v1.0.0

"4 个组件 + 同结构"是任务型个人中心模板的完整复用——读者做自己的"任务型"产品(记账/打卡/工具)可直接套用此模板。

十、任务型个人中心的"4 块"模板

**App 21 问卷 + App 22 投票 的"4 块个人中心"**可抽成通用模板:

UserCard(业务 emoji + 称号 + 业务 Slogan)
+ StatRow(4 栏业务统计,92% 宽 + 负边距悬浮)
+ MenuList(5 项业务菜单,92% 宽)
+ About(App 名 + 版本号)

"4 块 = 4 业务信息层"

  • UserCard —— 身份("我是谁、我做多少")
  • StatRow —— 数据("4 个业务指标")
  • MenuList —— 资产/操作("我能做什么")
  • About —— 版本("当前版本")

这个模板适合"轻量级任务型 App"——任何"用户来 App 是做任务"的产品可套用——对比"内容型/社交型"的复杂个人中心(Lv/经验/签到/成就墙),任务型只需要"我做了多少"。

校园投票我的页下部 · 5菜单完整+About版本信息+底部Tab栏

十一、"参与 28 次投票"的 Slogan 设计

App 22 UserCard Slogan "参与 28 次投票" 值得从文案角度分析:

Slogan 4 要素

  1. 数字(28)—— 精确、可信
  2. 动词(参与)—— 行为描述
  3. 对象(投票)—— 业务核心
  4. 隐含时间(隐含"历史累计")—— 不写"已使用 28 天"避免"老气"

**"数字 + 动词 + 对象"**是业务型 Slogan 的标准结构:

  • App 18:"匹配 X 位同好"
  • App 19:"累计 X 作品"
  • App 20:"已读书籍 X 本"
  • App 21:"已创建 X 份问卷"
  • App 22:"参与 X 次投票"

"X 次投票"是"活跃度指标"——比"已使用 X 天"(时长)更有激励性——**"X 次 = 我做了多少"**比"X 天 = 我注册了多久"更"有成就感"。

真实项目的 Slogan 设计原则

  • 数字 + 动词 + 对象——三件套不能少
  • 避免"已使用 X 天"——"天数"暗示"用户老了"
  • 聚焦业务而非时间——"我做了什么"比"我注册了多久"更正面

App 22 Slogan 是系列"业务型 Slogan"的代表(与 App 13"已使用 28 天"对比鲜明)。

校园投票我的页底部 · 菜单完整+About+底部Tab栏

十二、"4 块个人中心"与"系列其他个人中心"的数据维度对比

对比系列 5 个"个人中心"的数据维度:

App4 栏统计数据维度
13 音乐订单 28/进行中 3/满意度 98%/连续 15消费 + 满意度(电商)
17 志愿同上(订单没换)同上(遗留)
20 阅读同上同上(遗留)
21 问卷我的问卷 8/填写记录 36/收集份数 2,860/进行中 3创作者 + 填写者(双向)
22 投票创建投票 6/参与投票 28/历史投票 12/进行中 3创作者 + 参与者(双向)

App 21 和 App 22 都是"双向 UGC"——用户既是创作者(创建问卷/投票)又是参与者(填写/投票)——4 栏统计覆盖"双向"

  • 创作者维度:创建 X(创建数)+ 进行中 X(待处理)
  • 参与者维度:参与 X(参与数)+ 历史 X(已完成)

"双向 UGC"4 栏公式创建 + 参与 + 进行中 + 历史——4 个角度完整刻画"创作者 + 参与者"双身份——读者做自己"双向 UGC"产品时可参考此公式

十三、"任务型 App" 与 "内容型 App" 个人中心 5 大差异

总结系列 2 大类 App 的个人中心差异:

维度任务型(App 21/22)内容型(App 13/17/18/19/20)
@State 数量0-11-3
@Builder 数量46
个人卡简洁(emoji+称号+Slogan)复杂(Stack+等级+进度条+签到)
统计4 栏业务数据4 栏+周图+成就墙
菜单5 项业务菜单6 项 + 数字/NEW 角标

5 大差异都指向同一个核心

"任务型 App = 用户来做事"——用户来"创建投票/填写问卷"——做完就走——"简洁个人中心"足够"内容型 App = 用户来消费"——用户来"看作品/看书/刷社交"——需要长期留存——"游戏化个人中心"留住用户

真实项目决定个人中心复杂度

  • 任务型(记账/打卡/工具)—— 4 块简洁
  • 内容型(小红书/抖音/B站)—— 6 块 + 游戏化
  • 混合型(淘宝/美团)—— 5 块(任务 + 留存兼顾)

"用户来做什么"决定个人中心骨架——本系列 2 大类对比就是设计参考。

十四、App 22 "任务闭环" vs App 18 "关系闭环"

对比 App 22 投票的"任务闭环"和 App 18 社交的"关系闭环":

App 22 投票的"任务闭环"

  • 首页(找投票)→ 投票(完成任务)→ 结果(看结果)→ 我的(看历史)
  • "任务 = 投票"——完成就结束
  • 用户不需要"回来"(除非有新投票)

App 18 社交的"关系闭环"

  • 首页(找同好)→ 匹配(建立关系)→ 消息(维护关系)→ 我的(身份维护)
  • "关系 = 朋友"——持续维护
  • 用户需要"持续回来"(维护关系)

"任务闭环" vs "关系闭环"

  • 任务 = 一次性,闭环短——完成就走——"简洁个人中心"足够
  • 关系 = 持续性,闭环长——要维护——"游戏化个人中心"留住

"用户来 App 完成什么"决定"用户会不会回来"——任务型 App 解决"我做完了"、关系型 App 解决"我要常来"——两类 App 的留存策略不同

十五、MenuList @Builder 源码与交互细节

MenuList 的 @Builder 实现(95-117 行)是系列"菜单列表"的标准写法:

@Builder MenuList() {
  Column() {
    ForEach(this.menus, (m: MenuItem, idx: number) => {
      Row({ space: 12 }) {
        Text(m.emoji).fontSize(18)
        Text(m.title).fontSize(14).fontColor(C.text).layoutWeight(1)
        Text('>').fontSize(14).fontColor(C.textDim)
      }
      .width('100%')
      .padding({ left: 14, right: 14, top: 14, bottom: 14 })
      .onClick(() => { promptAction.showToast({ message: m.title }); })
      if (idx < this.menus.length - 1) {
        Divider().color(C.stroke).strokeWidth(0.5)
      }
    }, (m: MenuItem) => m.title)
  }
  .width('92%')
  .margin({ top: 16 })
  .backgroundColor(C.card)
  .borderRadius(D.rMd)
  .border({ width: 1, color: C.stroke })
}

MenuList @Builder 关键设计

  1. Column() —— 菜单项之间无间距——因为整个 Column 是一张卡片(白底 + 圆角 + 边框),菜单项之间用分隔线区分——if (idx < this.menus.length - 1) { Divider()... } 最后一条不加分隔线。

  2. Row({ space: 12 }) 三列 —— emoji 图标(18sp)+ 标题(14sp layoutWeight(1))+ 箭头 >(14sp 灰色)——**"图标 + 标题 + 箭头"**是系列菜单行的标准三列。

  3. padding({ left: 14, right: 14, top: 14, bottom: 14 }) —— 四向 14vp 等距 padding——**"等距 padding"**控制行高。

  4. .onClick(() => { promptAction.showToast(...) }) —— 点击弹出 toast——系列标准"占位交互"——真实项目中应跳转到对应子页面。

  5. 整张卡片样式 —— width('92%') + backgroundColor(C.card) + borderRadius(D.rMd) + border({ width: 1, color: C.stroke }) ——与 StatRow 同款卡片样式——"个人中心 = 统一卡片风格"

十六、总结

App 22 我的页解析完毕。UserCard 紫色渐变 + StatRow 4 栏统计 + MenuList 5 项菜单 + About 双行版本是 4 大核心组件。"0 @State + 4 @Builder + 静态数据"是最简个人中心的标志——适合"任务型 App"(投票/问卷/打卡)。StatRow 的"悬浮叠加"设计(负边距 + 边框)是系列个人中心的视觉亮点——让"用户卡 + 统计卡"在视觉上合为一体。"4 块模板"(用户卡/统计行/菜单列表/关于)是任务型个人中心的通用骨架——读者做自己的"任务型 App"时可以直接复用。

配图

Logo

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

更多推荐