小V健身助手开发手记(四):打造专属健康空间——以 PersonContent构建统一风格的个人中心

小V健身助手开发手记(四)

  • [打造专属健康空间------以 `PersonContent` 构建统一风格的个人中心](#打造专属健康空间——以 PersonContent 构建统一风格的个人中心)
    • [🧩 一、为什么是 `PersonContent` 而非 `PersonalPage`?](#🧩 一、为什么是 PersonContent 而非 PersonalPage?)
    • [🖼️ 二、UI 结构与视觉语言](#🖼️ 二、UI 结构与视觉语言)
    • [💻 三、核心代码解析](#💻 三、核心代码解析)
      • [1. 组件定义与状态管理](#1. 组件定义与状态管理)
      • [2. 用户信息区(`buildUserInfo`)](#2. 用户信息区(buildUserInfo))
      • [3. 功能列表(基于 `List` + `PersonalItem`)](#3. 功能列表(基于 List + PersonalItem))
    • [🔗 四、路由与集成](#🔗 四、路由与集成)
    • [🛠️ 五、扩展性与未来演进](#🛠️ 五、扩展性与未来演进)
    • [✅ 六、结语](#✅ 六、结语)
    • 代码总结

打造专属健康空间------以 PersonContent 构建统一风格的个人中心

在移动应用的用户体验地图中,个人中心往往被低估。它不像首页那样高频曝光,也不如成就页那样充满激励感,但它却是用户建立"归属感"与"掌控感"的关键节点。一个设计良好的个人中心,能让用户感受到:"这是我的空间,一切尽在掌握。"

在「小V健身助手」的开发进程中,我们始终坚持组件化、一致性与轻量化 三大原则。继首页、成就页之后,我们以同样的架构思想,构建了名为 PersonContent 的个人中心视图组件------它不是独立页面,而是可嵌入、可复用、风格统一的 UI 模块。

本文将详解 PersonContent 的设计思路、代码实现与工程价值。


🧩 一、为什么是 PersonContent 而非 PersonalPage

在 HarmonyOS 的 Stage 模型中,页面通常由 @Entry 装饰的组件作为入口。但在 Tab 导航或多场景复用的场景下,将内容逻辑与路由入口解耦是更优的选择。

参考我们已有的 AchievementContent

ts 复制代码
@Component
export default struct AchievementContent { ... }

它不带 @Entry,仅负责渲染成就区域的内容,可被主页面按需嵌入。

同理,我们将个人中心也抽象为 PersonContent

  • 职责单一:只管 UI 渲染,不处理路由跳转逻辑(除内部子跳转);
  • 高度复用:未来可用于侧边栏、设置弹窗等场景;
  • 风格统一 :与 AchievementContent 共享布局规范、间距系统与交互反馈。

这是一种"页面即组件"的现代前端思维。


🖼️ 二、UI 结构与视觉语言

PersonContent 采用经典的"头像区 + 功能列表"布局:

复制代码
[ 头像 + 昵称 + 日期 ]
──────────────────────
[ 目标设置     → ]
[ 历史记录     → ]
[ 我的成就     → ]
[ 隐私与数据   → ]
[ 关于小V   v1.0.0 ]
[ 退出应用(红色)]

设计细节:

  • 背景色 :使用浅灰 #f5f5f5,区别于首页的深色运动氛围,营造"后台管理"的冷静感;
  • 列表项:白底圆角卡片,内含图标、标题、描述与箭头,信息层级清晰;
  • 退出按钮:单独高亮为红色文字,避免误触,同时传递"重要操作"信号;
  • 日期同步 :顶部展示当前选中日期(来自全局 AppStorage),与首页、成就页保持上下文一致。

💻 三、核心代码解析

1. 组件定义与状态管理

ts 复制代码
@Component
export struct PersonContent {
  // 双向绑定全局日期,确保与其他页面同步
  @StorageLink('date') date: number = DateUtil.beginTimeOfDay(new Date())

  // 本地状态:今日目标(后续可接入持久化)
  @State dailyTarget: number = 2000

  build() { /* ... */ }
}

使用 @StorageLink 而非 @StorageProp,是因为我们希望个人中心也能响应日期变更(例如从日历选择新日期后,顶部自动刷新)。

2. 用户信息区(buildUserInfo

ts 复制代码
@Builder
buildUserInfo() {
  Row() {
    Image($r('app.media.ic_default_avatar'))
      .width(60).height(60).borderRadius(30)
    Column() {
      Text('小V用户').fontSize(18).fontWeight(600)
      Text(DateUtil.formatDate(this.date)).fontSize(12).fontColor('#888')
    }.margin({ left: 15 })
    Blank() // 推动右侧对齐
  }
  .padding({ bottom: 25 })
}

简洁、克制,突出身份认同而非社交属性(当前无账号体系)。

3. 功能列表(基于 List + PersonalItem

我们封装了可复用的 PersonalItem 组件:

ts 复制代码
// component/PersonalItem.ets
@Component
export struct PersonalItem {
  icon: ResourceStr
  title: string
  desc: string
  showArrow: boolean
  onClick: () => void

  build() {
    Row() {
      Image(this.icon).width(24).height(24).margin({ right: 15 })
      Column() {
        Text(this.title).fontSize(16).fontWeight(500)
        if (this.desc) Text(this.desc).fontSize(12).opacity(0.6)
      }
      Blank()
      if (this.showArrow) Image($r('app.media.arrow_right')).width(16)
    }
    .backgroundColor(Color.White)
    .borderRadius(12)
    .padding({ horizontal: 16, vertical: 12 })
    .onClick(this.onClick)
  }
}

通过组合 PersonalItemPersonContent 的列表代码变得极其简洁且语义清晰。


🔗 四、路由与集成

在主页面中,只需一行即可嵌入:

ts 复制代码
// MainIndexPage.ets
if (tabIndex === 2) {
  PersonContent()
}

内部跳转使用标准 ArkTS 路由:

ts 复制代码
router.pushUrl({ url: 'pages/AchievementPage' })

注意:AchievementPage 应是一个带 @Entry 的完整页面,而 AchievementContent 是其内部的内容组件------这种"页面壳 + 内容体"模式,是我们推荐的分层方式。


🛠️ 五、扩展性与未来演进

当前 PersonContent 是 MVP 版本,但已预留扩展路径:

功能 实现方式
动态昵称/头像 preferences 读取,支持编辑弹窗
目标持久化 dailyTarget 存入 AppStoragedata_preferences
缓存清理 在"隐私与数据"页调用 preferences.clear()
深色模式适配 使用 @Watch 监听系统主题,动态切换颜色

所有这些,都不会破坏现有组件结构。


✅ 六、结语

PersonContent 的诞生,标志着「小V健身助手」完成了从"功能驱动"到"体验驱动"的一次跃迁。它不再只是一个功能列表,而是一个有温度、有秩序、有控制感的个人健康空间

更重要的是,它延续了我们对代码可维护性的坚持:

好的 UI,不仅是看起来舒服,更是写起来清晰、改起来安全。

代码总结

此次更新只有这两部分代码,这里路由部分没有实现

PersonalItem

ts 复制代码
// component/PersonalItem.ets
interface PersonalItemFace{
  icon: ResourceStr
  title: string
  desc?: string
  showArrow?: boolean
}
@Component
export default struct PersonalItem {
  @Prop icon: ResourceStr;
  @Prop title: string;
  @Prop desc: string = '';
  @Prop showArrow: boolean = true;
  // @Prop onClick: (event?: ClickEvent) => void;


  build() {
    Row() {
      Image(this.icon)
        .width(24)
        .height(24)
        .margin({ right: 15 })
      Column() {
        Text(this.title)
          .fontSize(16)
          .fontWeight(500)
          .alignSelf(ItemAlign.Start)
        if (this.desc) {
          Text(this.desc)
            .fontSize(12)
            .opacity(0.6)
            .alignSelf(ItemAlign.Start)
            .margin({ top: 3 })
        }
      }
      Blank()
      if (this.showArrow) {
        Image($r('app.media.arrow_right'))
          .width(16)
          .height(16)
          .opacity(0.5)
      }
    }
    .width('100%')
    .height(60)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .padding({ left: 16, right: 16 })
    // .onClick(this.onClick)
  }
}

PersonContent

ts 复制代码
// view/PersonContent.ets
import { router } from '@kit.ArkUI'
import DateUtil from '../../util/DateUtil'
import PersonalItem from '../../component/PersonalItem'

@Component
export struct PersonContent {
  // 从全局 Storage 获取当前日期(用于展示)
  @StorageLink('date') date: number = DateUtil.beginTimeOfDay(new Date())

  // 当前用户的每日目标(实际项目中应从持久化存储读取)
  @State dailyTarget: number = 2000

  build() {
    Column() {
      // 用户信息区域
      this.buildUserInfo()

      // 功能列表
      List({ space: 12 }) {
        // 目标设置
        ListItem() {
          PersonalItem({
            icon: $r('app.media.ic_target'),
            title: '今日目标',
            desc: `${this.dailyTarget} 千卡`,
            showArrow: true,
            // onClick: () => {
            //   // TODO: 弹出目标设置弹窗(后续可扩展)
            //   console.log('打开目标设置')
            // }
          })
        }

        // 历史记录
        ListItem() {
          PersonalItem({
            icon: $r('app.media.ic_history'),
            title: '历史记录',
            desc: '',
            showArrow: true,
            // onClick: () => {
            //   router.pushUrl({ url: 'pages/HistoryPage' })
            // }
          })
        }

        // 成就系统
        ListItem() {
          PersonalItem({
            icon: $r('app.media.ic_achieve'),
            title: '我的成就',
            desc: '',
            showArrow: true,
            // onClick: () => {
            //   router.pushUrl({ url: 'pages/AchievementPage' })
            // }
          })
        }

        // 隐私与数据
        ListItem() {
          PersonalItem({
            icon: $r('app.media.ic_privacy'),
            title: '隐私与数据',
            desc: '',
            showArrow: true,
            // onClick: () => {
            //   router.pushUrl({ url: 'pages/PrivacyDataPage' })
            // }
          })
        }

        // 关于小V
        ListItem() {
          PersonalItem({
            icon: $r('app.media.ic_about'),
            title: '关于小V',
            desc: 'v1.0.0',
            showArrow: false,
            // onClick: () => {}
          })
        }

        // 退出应用
        ListItem() {
          Button('退出应用')
            .width('100%')
            .height(50)
            .fontSize(16)
            .fontColor('#ff3b30')
            .backgroundColor(Color.Transparent)
            .onClick(() => {
              router.clear()
            })
        }
      }
      .width('100%')
      .alignListItem(ListItemAlign.Start)
    }
    .width('100%')
    .height('100%')
    .padding({ top: 20, left: 20, right: 20 })
    .backgroundColor('#f5f5f5')
  }

  @Builder
  buildUserInfo() {
    Row() {
      Image($r('app.media.ic_default_avatar'))
        .width(60)
        .height(60)
        .borderRadius(30)
      Column() {
        Text('小V用户')
          .fontSize(18)
          .fontWeight(600)
          .fontColor(Color.Black)
        Text(DateUtil.formatDate(this.date))
          .fontSize(12)
          .fontColor('#888888')
      }
      .alignItems(HorizontalAlign.Start)
      .margin({ left: 15 })
      Blank()
    }
    .width('100%')
    .padding({ bottom: 25 })
  }
}
相关推荐
●VON6 小时前
小V健身助手开发手记(三):用成就点燃坚持——构建可视化激励系统
学习·openharmony·总结·开源鸿蒙·von
Nan_Shu_6146 小时前
学习:Vue (2)
javascript·vue.js·学习
古城小栈6 小时前
支付宝MCP:AI支付的智能体
人工智能
分布式存储与RustFS6 小时前
MinIO替代方案与团队适配性分析:RustFS如何匹配不同规模团队?
人工智能·rust·开源项目·对象存储·minio·企业存储·rustfs
爱笑的眼睛116 小时前
强化学习组件:超越Hello World的架构级思考与实践
java·人工智能·python·ai
yiersansiwu123d6 小时前
AI伦理风险与治理体系构建 守护技术向善之路
人工智能
Thomas_Cai6 小时前
MCP服务创建指南
人工智能·大模型·agent·智能体·mcp
硅谷秋水6 小时前
LLM的测试-时规模化:基于子问题结构视角的综述
人工智能·深度学习·机器学习·语言模型
Boxsc_midnight6 小时前
【规范驱动的开发方式】之【spec-kit】 的安装入门指南
人工智能·python·深度学习·软件工程·设计规范