HarmonyOS 6 ArkUI 布局体系实战:Stack 层叠布局结合 position 绝对定位实现粒子背景,以及 Column/Row/Flex/Scroll 的组合布局范式
本文基于 HarmonyOS ArkTS API 24(API Level 12)完整实现一款「多多健身社交」应用,覆盖健身课程、运动器材、精选私教、运动打卡、健身社区、个人中心六大核心模块,采用活力绿
#00C853× 能量橙#FF6D00双主色 + 底色#F5FFF0运动活力风视觉体系,通过六个独立@Component子组件 + 一个@Entry主入口组件构建六组件多Tab健身社交电商架构,内置分类chips横滑、精选大卡、双列课程网格、品类网格、限时特价横条、教练列表、运动数据环形图、打卡日历、打卡记录列表、健身动态、照片墙、热门话题、月度运动时长绿色系渐变柱状图、成就徽章等差异化布局,并实现报名课程底部抽屉(含时段选择+教练选择)、取消报名居中可移除、编辑健身目标底部抽屉TextInput、删除打卡记录居中可移除四种模态弹窗,完整还原健身社交从课程→器材→私教→打卡→社区→个人的全链路业务闭环。

一、项目背景与设计理念
健身社交已从单纯的运动打卡演变为涵盖在线课程、运动器材电商、私教预约、数据追踪、社区分享的完整生态。一位健身爱好者从选择适合的课程、购买专业器材、预约私教指导、每日运动打卡记录数据、到社区分享健身成果,形成了一条强数据驱动、强社交属性、强电商转化的业务链路。
「多多健身社交」正是针对这一全链路场景设计的一站式健身社交服务平台,核心设计理念如下:
- 课程即选择:健身课程的核心是「分类选择+难度匹配」,用户最关心的是「什么类型、什么难度、消耗多少大卡、哪个教练、多少钱」,因此课程Tab以「分类chips横滑(瑜伽/力量/有氧/HIIT/舞蹈/拉伸/搏击/游泳8分类)+精选大卡+双列课程网格」为核心,每门课程显示难度标签(初级绿/中级橙/高级红)、消耗大卡、教练、时长、评分、学习人数,帮助用户快速匹配适合的课程。
- 器材即装备:健身离不开专业器材,器材Tab以「限时特价横条(倒计时+5折起)+双列器材网格」为核心,每件器材显示品牌、销量、价格、评分,还原运动器材电商体验。
- 私教即专业:专业指导是健身效果的关键,私教Tab以「教练详情大卡列表」为核心,每位教练显示头像、认证标签(ACE-CPT/RYT-500/NSCA-CSCS等)、专长、简介、评分、学员数、课时费,配合「预约体验+立即预约」双按钮,还原私教预约体验。
- 打卡即数据:健身打卡的核心是「数据可视化+坚持激励」,打卡Tab以「运动数据环形图(85%目标完成)+本周数据三列(打卡天数/消耗大卡/运动分钟)+连续打卡条+打卡记录列表」为核心,每条记录显示运动类型、日期、时长、消耗大卡、类型标签,完整还原运动数据追踪体验。
- 社区即分享:健身爱好者有强烈的「成果分享、经验交流」需求,社区Tab以「热门话题chips横滑(减脂打卡/增肌心得/瑜伽日常/跑步日记/私教推荐五色话题)+健身动态列表(含头像+昵称+标签+内容+照片墙三图+点赞评论分享)」为核心,构建健身社区氛围。
- 个人即成就:健身爱好者有强烈的「坚持成就、运动成就」荣誉感,个人Tab以活力绿渐变头+四列数据悬浮卡(连续打卡/运动小时/消耗大卡/报名课程)+月度运动时长绿色系渐变柱状图(3-8月从浅绿#A5D6A7到深活力绿#00C853,8月30小时最高)+成就徽章列表(连续打卡30天/力量大师/跑步达人/瑜伽修行者已解锁✅,健身王者未解锁🔒opacity0.5半透明)构建个人成就体系。
视觉上采用活力绿 #00C853 × 能量橙 #FF6D00 双主色体系:活力绿代表健康、活力、运动的生命力,能量橙代表能量、热情、燃脂的激情,二者搭配形成强烈的「运动活力风」视觉冲击;底色采用 #F5FFF0 极浅绿白,既保证内容区的可读性,又与绿色主色形成柔和过渡;辅助色涵盖难度三色(初级绿#00C853/中级橙#FF6D00/高级红#E53935)、运动类型八色(瑜伽紫/力量红/有氧蓝/HIIT橙/舞蹈粉/拉伸青/搏击棕/游泳浅蓝)、话题五色(绿/红/紫/蓝/橙),构建完整的健身业务色彩语义体系。
二、整体架构概览:六组件多Tab健身社交电商架构
多多健身社交采用六个独立 @Component 子组件 + 一个 @Entry 主入口组件的六组件多Tab健身社交电商架构,每个Tab对应一个独立的子组件,主入口组件 DuoDuoFitnessApp 通过 @State activeTab 状态变量控制当前显示的Tab,并通过 @Builder bottomTabItem 复用底部Tab栏项。

2.1 组件清单
| 组件名 | 装饰器 | 对应Tab | 核心职责 |
|---|---|---|---|
CourseTab69 |
@Component |
课程📚 | 搜索头+分类chips横滑+精选大卡+双列课程网格+报名课程弹窗 |
EquipmentTab |
@Component |
器材🏋️ | 限时特价横条+双列器材网格 |
CoachTab |
@Component |
私教👨🏫 | 教练详情大卡列表+预约体验/立即预约双按钮 |
CheckInTab |
@Component |
打卡📊 | 运动数据环形图+连续打卡条+打卡记录列表+编辑目标弹窗+删除记录弹窗 |
CommunityTab69 |
@Component |
社区💬 | 热门话题chips横滑+健身动态列表含照片墙 |
ProfileTab69 |
@Component |
我的👤 | 渐变头+四列数据悬浮卡+月度运动柱状图+成就徽章 |
DuoDuoFitnessApp |
@Entry @Component |
主入口 | 六Tab切换调度+底部Tab栏+全局底色 |
2.2 主入口组件核心代码
@Entry
@Component
struct DuoDuoFitnessApp {
@State activeTab: number = 0
@Builder
bottomTabItem(icon: string, label: string, tabIdx: number) {
Column({ space: 3 }) {
Text(icon)
.fontSize(20)
.opacity(this.activeTab === tabIdx ? 1.0 : 0.45)
Text(label)
.fontSize(9)
.fontColor(this.activeTab === tabIdx ? '#00C853' : '#999999')
.fontWeight(this.activeTab === tabIdx ? FontWeight.Bold : FontWeight.Normal)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
.padding({ top: 5, bottom: 5 })
.onClick(() => {
this.activeTab = tabIdx
})
}
build() {
Column() {
Stack() {
if (this.activeTab === 0) {
CourseTab69()
} else if (this.activeTab === 1) {
EquipmentTab()
} else if (this.activeTab === 2) {
CoachTab()
} else if (this.activeTab === 3) {
CheckInTab()
} else if (this.activeTab === 4) {
CommunityTab69()
} else {
ProfileTab69()
}
}
.layoutWeight(1)
Row() {
this.bottomTabItem('📚', '课程', 0)
this.bottomTabItem('🏋️', '器材', 1)
this.bottomTabItem('👨🏫', '私教', 2)
this.bottomTabItem('📊', '打卡', 3)
this.bottomTabItem('💬', '社区', 4)
this.bottomTabItem('👤', '我的', 5)
}
.width('100%')
.backgroundColor('#FFFFFF')
.padding({ top: 4, bottom: 6 })
.shadow({ radius: 8, color: 'rgba(0,200,83,0.12)', offsetY: -2 })
}
.width('100%')
.height('100%')
.backgroundColor('#F5FFF0')
}
}
主入口组件的核心设计要点:
@State activeTab:声明式状态变量,控制当前显示的Tab索引,点击底部Tab时更新该值触发UI重新渲染。@Builder bottomTabItem:复用构建器,接收图标、标签、Tab索引三个参数,统一构建底部Tab栏项,选中态图标opacity1.0+文字活力绿#00C853+粗体,未选中态图标opacity0.45+文字灰色#999999,实现「绿橙渐变选中+未选灰色」的底栏样式。Stack+ 条件渲染:使用Stack容器叠加六个子组件,通过if/else if条件渲染根据activeTab显示对应Tab,保证Tab切换时的性能和状态隔离。- 底部Tab栏:六个Tab项(课程📚/器材🏋️/私教👨🏫/打卡📊/社区💬/我的👤)使用Row横向排列,每项
layoutWeight(1)均分宽度,白色背景+顶部绿色阴影shadow({ radius: 8, color: 'rgba(0,200,83,0.12)', offsetY: -2 })增强层次感。 - 全局底色:最外层Column设置
backgroundColor('#F5FFF0')极浅绿白,统一应用整体运动活力风底色。
三、色彩系统:活力绿能量橙双主色与难度三色运动类型八色业务语义体系
多多健身社交的色彩系统以活力绿 #00C853 × 能量橙 #FF6D00 为双主色,底色 #F5FFF0,并围绕健身业务场景构建了完整的难度三色+运动类型八色业务语义色彩体系。

3.1 主色与底色
| 色彩角色 | 色值 | 用途 |
|---|---|---|
| 活力绿(主色) | #00C853 |
六个Tab统一头部背景、选中Tab文字、按钮主色、难度初级标签、打卡目标完成、已解锁徽章、进度条 |
| 能量橙(辅主色) | #FF6D00 |
价格文字、消耗大卡、难度中级标签、限时特价、连续打卡、评分、柱状图最深色 |
| 底色 | #F5FFF0 |
全局背景、报名弹窗未选中时段背景、编辑目标TextInput背景 |
| 文字主色 | #333333 |
标题、课程名、正文 |
| 文字次色 | #666666 |
副标题、说明文字、教练专长 |
| 文字辅助色 | #999999 |
时间、原价、辅助信息 |
| 分割线 | #F0F0F0 / #E0E0E0 |
卡片分割、边框 |
3.2 难度三色业务语义体系
健身课程的核心业务语义是「难度等级」,应用构建了难度三色体系,初级/中级/高级各对应独立的颜色+浅底色组合,贯穿课程Tab全场景:
| 难度 | 色值 | 浅底色 | 代表课程 |
|---|---|---|---|
| 初级 | #00C853 活力绿 |
#E8F5E9 浅绿 |
零基础瑜伽入门、尊巴舞蹈瘦身、全身拉伸放松、晨跑5公里计划 |
| 中级 | #FF6D00 能量橙 |
#FFF3E0 浅橙 |
燃脂HIIT挑战20天、动感单车燃脂营、自由泳速成班、马甲线养成计划 |
| 高级 | #E53935 红 |
#FFEBEE 浅红 |
腹肌撕裂者进阶、拳击搏击燃脂 |
难度三色通过 FitnessCourse.level + levelColor + levelBg 三字段组合驱动,在双列课程网格中统一使用,用户可通过颜色快速识别课程难度,匹配自己的运动水平。
3.3 运动类型八色与话题五色
除难度三色外,应用还构建了运动类型八色和话题五色等业务语义色彩:
运动类型八色:CheckInRecord.typeColor + typeBg 字段驱动,每种运动类型各对应独立颜色:
- HIIT燃脂:橙#FF6D00+浅橙底
- 瑜伽放松:紫#7B1FA2+浅紫底
- 力量训练:红#E53935+浅红底
- 晨跑5km:蓝#1565C0+浅蓝底
- 游泳:浅蓝#0288D1+浅蓝底
- 尊巴舞蹈:粉#EC407A+浅粉底
- 拉伸放松:青#00897B+浅青底
分类chips八色:CATS_69 8个分类各自独立color/bg(瑜伽紫/力量红/有氧蓝/HIIT橙/舞蹈粉/拉伸青/搏击棕/游泳浅蓝),选中态统一活力绿底白字,未选中态对应分类浅底色+分类色文字。
话题五色:社区Tab热门话题chips:
- #减脂打卡:绿#00C853+浅绿底
- #增肌心得:红#E53935+浅红底
- #瑜伽日常:紫#7B1FA2+浅紫底
- #跑步日记:蓝#1565C0+浅蓝底
- #私教推荐:橙#FF6D00+浅橙底
3.4 月度运动时长绿色系渐变柱状图
个人Tab月度运动时长柱状图采用绿色系渐变色,从3月到8月颜色由浅到深,直观呈现运动时长的增长趋势:
| 月份 | 运动时长 | 色值 | 色彩层级 |
|---|---|---|---|
| 3月 | 15小时 | #A5D6A7 |
极浅绿 |
| 4月 | 22小时 | #A5D6A7 |
极浅绿 |
| 5月 | 18小时 | #A5D6A7 |
极浅绿 |
| 6月 | 25小时 | #81C784 |
浅绿 |
| 7月 | 28小时 | #4CAF50 |
中绿 |
| 8月 | 30小时 | #00C853 |
深活力绿 |
柱状图通过纯声明式 Column + backgroundColor(bar.color) + height(bar.value * 3) 实现,无需Canvas,alignItems(VerticalAlign.Bottom) 让柱子从底部向上生长,8月30小时最高柱子颜色最深(活力绿主色#00C853),形成强烈的视觉对比,直观呈现用户从3月15小时到8月30小时的运动时长增长(翻倍),体现坚持运动的进步。
四、数据模型:8个interface强类型定义与业务字段设计
多多健身社交使用 TypeScript interface 定义了8个强类型数据模型,覆盖健身课程、运动器材、私教、打卡记录、社区动态、分类、柱状图、成就徽章八大业务实体,每个interface都针对健身业务场景设计了专属字段。
4.1 数据模型清单
| Interface | 用途 | 核心字段 | 特色字段 |
|---|---|---|---|
FitnessCourse |
健身课程 | id/title/coach/cover/price/originalPrice/duration/calories/level/levelColor/levelBg/students/rating/tag/category | level/levelColor/levelBg难度独立色、calories消耗大卡、duration时长 |
Equipment |
运动器材 | id/name/brand/cover/price/originalPrice/sold/tag/category/rating | brand品牌、sold销量、category分类 |
Coach |
私教 | id/name/avatar/title/specialty/rating/students/price/cert/bio | cert认证标签(ACE-CPT/RYT-500等)、specialty专长、bio简介、price课时费 |
CheckInRecord |
打卡记录 | id/date/type/typeIcon/duration/calories/typeColor/typeBg | type/typeColor/typeBg运动类型独立色、typeIcon类型图标、duration时长、calories消耗 |
PostItem69 |
社区动态 | id/avatar/name/content/image/likes/comments/date/tag | image照片、tag动态标签、likes/comments互动数据 |
CatChip69 |
分类标签 | label/icon/color/bg | color/bg分类独立色(8分类各自独立配色) |
BarData69 |
柱状图数据 | label/value/color | color柱子颜色(绿色系渐变) |
Achievement69 |
成就徽章 | icon/label/desc/unlocked | unlocked解锁状态布尔值(驱动opacity0.5半透明) |
4.2 核心数据模型详解
FitnessCourse(健身课程) 是最核心的数据模型,10条课程数据涵盖瑜伽/力量/有氧/HIIT/舞蹈/搏击/拉伸/游泳八大分类,包含燃脂HIIT挑战20天(中级¥99 8.6万人 9.5分)、零基础瑜伽入门(初级¥79 12.3万人 9.6分最高人气)、腹肌撕裂者进阶(高级¥129 硬核)、晨跑5公里计划(初级¥59免费 15.6万人 9.8分最高分)等经典课程,每条数据都包含 level+levelColor+levelBg 难度三字段组合,以及 calories(消耗大卡)、duration(时长)、students(学习人数)、rating(评分)等健身课程核心指标。
Coach(私教) 的特色是 cert(认证标签,如ACE-CPT美国运动委员会认证、RYT-500印度瑜伽联盟500小时认证、NSCA-CSCS美国体能协会认证、ACSM-CPT美国运动医学会认证、ZIN尊巴官方认证)字段,以及 specialty(专长)、bio(简介)、price(课时费)、students(学员数),6位教练涵盖HIIT/瑜伽/力量/有氧/舞蹈/塑形六大专长,每位都有专业认证背书,构建专业可信的私教形象。
CheckInRecord(打卡记录) 的特色是 type(运动类型)+typeIcon(类型图标)+typeColor+typeBg 运动类型四字段组合,以及 duration(时长)、calories(消耗大卡),7条打卡记录涵盖HIIT/瑜伽/力量/晨跑/游泳/尊巴/拉伸七种运动类型,每条记录都有独立的类型颜色,用户可通过颜色快速识别运动类型。
Achievement69(成就徽章) 的特色是 unlocked 布尔值字段,通过 a.unlocked ? 1.0 : 0.5 动态设置卡片opacity,未解锁徽章半透明显示,并配合 if (a.unlocked) 条件渲染显示「✅ 已解锁」或「🔒 未解锁」标签,5个徽章中4个已解锁(连续打卡30天/力量大师/跑步达人/瑜伽修行者),1个未解锁(健身王者,累计运动1000小时解锁)。
五、课程Tab:搜索头+分类chips横滑+精选大卡+双列课程网格
课程Tab(CourseTab69)是多多健身社交的首页核心,以「分类chips横滑+精选大卡+双列课程网格」为视觉核心,配合搜索头,完整呈现健身课程信息。
5.1 布局结构
课程Tab采用 Column 纵向布局,从上到下依次为:
- 活力绿搜索头:
height(44)+backgroundColor('#00C853'),左侧🔍搜索图标+「搜索健身课程、教练…」占位文字(白色70%透明度),右侧💪图标。 - 分类chips横滑:
Scroll横向滚动+Row,8个分类(瑜伽🧘/力量💪/有氧🏃/HIIT🔥/舞蹈💃/拉伸🤸/搏击🥊/游泳🏊),每个分类独立color/bg,选中态活力绿底#00C853白字粗体,未选中态对应分类浅底色+分类色文字。 - 精选大卡:双大卡横向排列,左侧燃脂HIIT 20天大卡(活力绿底#00C853+🔥44号+张力教练+8.6万人+¥99起白底橙字标签,width55%),右侧零基础瑜伽大卡(紫底#7B1FA2+🧘44号+李静教练+12.3万人+¥79起白底紫字标签,layoutWeight1)。
- 热门课程标题栏:左侧「🔥 热门课程」粗体标题,右侧「全部 >」活力绿链接。
- 双列课程网格:
ForEach(courses)遍历10门课程,每门用Row横向排列左右两卡,左卡显示tag橙底+难度标签(levelColor/levelBg独立色)+课程名+教练+时长+消耗大卡(橙)+价格+评分人数,右卡显示tag绿底+分类+课程名+教练+时长+消耗大卡+价格+评分人数,左右卡信息差异化呈现,点击任意卡打开报名课程弹窗。
5.2 精选大卡核心代码
Column({ space: 12 }) {
Row({ space: 12 }) {
Column({ space: 6 }) {
Text('🔥')
.fontSize(44)
Text('燃脂HIIT 20天')
.fontSize(15)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
Row({ space: 6 }) {
Text('张力教练')
.fontSize(10)
.fontColor('rgba(255,255,255,0.8)')
Text('8.6万人')
.fontSize(10)
.fontColor('rgba(255,255,255,0.6)')
}
Text('¥99 起')
.fontSize(14)
.fontColor('#FF6D00')
.fontWeight(FontWeight.Bold)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
}
.width('55%')
.padding(16)
.borderRadius(12)
.backgroundColor('#00C853')
.alignItems(HorizontalAlign.Start)
Column({ space: 6 }) {
Text('🧘')
.fontSize(44)
Text('零基础瑜伽')
.fontSize(15)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
Row({ space: 6 }) {
Text('李静教练')
.fontSize(10)
.fontColor('rgba(255,255,255,0.8)')
Text('12.3万人')
.fontSize(10)
.fontColor('rgba(255,255,255,0.6)')
}
Text('¥79 起')
.fontSize(14)
.fontColor('#7B1FA2')
.fontWeight(FontWeight.Bold)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
}
.layoutWeight(1)
.padding(16)
.borderRadius(12)
.backgroundColor('#7B1FA2')
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding({ left: 12, right: 12 })
}
精选大卡的设计要点:
- 双大卡不对称布局:左侧燃脂HIIT大卡
width('55%')占据主要视觉空间,右侧零基础瑜伽大卡layoutWeight(1)占据剩余空间,形成主次分明的视觉层次。 - 绿紫双色系搭配:左侧大卡活力绿#00C853(主色),右侧大卡紫#7B1FA2(瑜伽分类色),二者形成鲜明的色彩对比,既保持视觉活力又有课程类型区分度。
- 白底价格标签:每张卡底部的价格标签(¥99起/¥79起)使用白色背景+圆角8,价格文字使用对应主题色(HIIT橙#FF6D00/瑜伽紫#7B1FA2),在深色背景上形成强烈的视觉焦点,突出价格信息。
- 44号大图标:课程图标使用44号大字号,在深色背景上形成强烈的视觉焦点,增强课程的识别度。
5.3 双列课程网格难度标签
双列课程网格的核心是难度标签,通过 FitnessCourse.level + levelColor + levelBg 三字段组合驱动:
Row() {
Text(c.tag)
.fontSize(9)
.fontColor('#FFFFFF')
.backgroundColor('#FF6D00')
.borderRadius(3)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
Text(' ' + c.level)
.fontSize(9)
.fontColor(c.levelColor)
.backgroundColor(c.levelBg)
.borderRadius(3)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
}
难度标签的设计要点:
- tag+level双标签:每门课程顶部显示tag标签(爆款/热门/硬核/推荐等,左卡橙底右卡绿底)+难度标签(初级/中级/高级,levelColor/levelBg独立色),双标签横向排列,用户可一眼识别课程的热度和难度。
- 难度三色独立色:初级绿#00C853+浅绿底#E8F5E9、中级橙#FF6D00+浅橙底#FFF3E0、高级红#E53935+浅红底#FFEBEE,三色对应三个难度等级,用户可通过颜色快速匹配自己的运动水平。
- 左右卡差异化:左卡tag使用橙底#FF6D00,显示难度标签level;右卡tag使用绿底#00C853,显示分类category,左右卡信息侧重点不同,避免完全重复的单调感。
每门课程卡还显示消耗大卡(能量橙#FF6D00,如350大卡/180大卡/500大卡)、价格(能量橙粗体+原价删除线)、评分+学习人数,完整呈现健身课程的核心信息,点击任意卡打开报名课程底部抽屉弹窗,用户可选择训练时段、选择教练、确认报名。
六、器材Tab:限时特价横条+双列器材网格
器材Tab(EquipmentTab)聚焦运动器材电商场景,以限时特价和双列器材网格为核心,还原运动器材电商体验。
6.1 布局结构
器材Tab采用 Column 纵向布局,从上到下依次为:
- 活力绿头部栏:「🏋️ 运动器材」标题+右侧🛒购物车图标。
- 限时特价横条:浅橙底#FFF3E0+⚡28号图标+「限时特价 5折起」粗体标题+「筋膜枪、哑铃、瑜伽垫热销中」说明+右侧倒计时「02:15:30」(白底橙字圆角6),营造限时抢购的紧迫感。
- 热销器材标题栏:左侧「🔥 热销器材」粗体标题,右侧「全部 >」活力绿链接。
- 双列器材网格:
ForEach(equipments)遍历8件器材,每件用Row横向排列左右两卡,左卡显示tag橙底+品牌+器材名+销量+价格+评分(绿),右卡显示tag绿底+分类+器材名+品牌+销量+价格+评分,左右卡信息差异化呈现。
6.2 限时特价横条核心代码
Row({ space: 12 }) {
Text('⚡')
.fontSize(28)
.fontColor('#FF6D00')
Column({ space: 2 }) {
Text('限时特价 5折起')
.fontSize(14)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
Text('筋膜枪、哑铃、瑜伽垫热销中')
.fontSize(10)
.fontColor('#999999')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Text('02:15:30')
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#FF6D00')
.borderRadius(6)
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
}
.width('92%')
.padding(12)
.backgroundColor('#FFF3E0')
.borderRadius(10)
.margin({ left: 12, right: 12, top: 12, bottom: 8 })
限时特价横条的设计要点:
- 浅橙底+能量橙主题:整体使用浅橙底#FFF3E0,配合⚡能量橙图标和能量橙倒计时背景,营造「限时特价、能量抢购」的氛围,能量橙#FF6D00是健身应用的辅主色,代表能量和燃脂激情。
- 倒计时标签:右侧「02:15:30」倒计时使用能量橙底#FF6D00+白字+圆角6,突出限时抢购的紧迫感,是电商类应用常用的促销手法。
- 热销品类说明:中间说明文字「筋膜枪、哑铃、瑜伽垫热销中」明确指出当前特价的热销器材品类,引导用户关注。
8件器材涵盖可调哑铃20kg(海德¥299 已售2.3万)、瑜伽垫加厚防滑(Keep¥89 已售8.6万爆款)、跳绳智能计数(Keep¥49 已售12.3万最高销量)、弹力带5件套(迪卡侬¥39)、筋膜枪mini版(SKG¥199)、智能手环运动版(华为¥249)、壶铃16kg(海德¥159)、平衡球65cm(迪卡侬¥79),覆盖力量/瑜伽/有氧/拉伸/恢复/穿戴/核心七大器材品类,数据贴合真实健身器材电商行情。
七、私教Tab:教练详情大卡列表+预约双按钮
私教Tab(CoachTab)聚焦私教预约场景,以教练详情大卡列表为核心,配合预约体验/立即预约双按钮,还原私教预约体验。

7.1 布局结构
私教Tab采用 Column 纵向布局,从上到下依次为:
- 活力绿头部栏:「👨🏫 精选私教」标题+右侧🔍搜索图标。
- 教练详情大卡列表:
ForEach(coaches)遍历6位教练,每位教练一个大卡,包含:- 顶部:44号头像+教练名+认证标签(cert,绿底浅绿,如ACE-CPT/RYT-500/NSCA-CSCS)+职称+专长+简介(bio)
- 数据行:评分⭐(橙)+学员数👥+课时费💰(橙粗体)
- 底部双按钮:预约体验(绿底浅绿边框绿字,layoutWeight1)+立即预约(活力绿底白字,layoutWeight1)
7.2 教练详情大卡核心代码
Column({ space: 8 }) {
Row({ space: 12 }) {
Text(c.avatar)
.fontSize(44)
Column({ space: 4 }) {
Row({ space: 6 }) {
Text(c.name)
.fontSize(15)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
Text(c.cert)
.fontSize(9)
.fontColor('#00C853')
.backgroundColor('#E8F5E9')
.borderRadius(3)
.padding({ left: 5, right: 5, top: 1, bottom: 1 })
}
Text(c.title + ' · ' + c.specialty)
.fontSize(11)
.fontColor('#666666')
Text(c.bio)
.fontSize(10)
.fontColor('#999999')
.lineHeight(15)
Row({ space: 12 }) {
Row({ space: 4 }) {
Text('⭐')
.fontSize(10)
Text(c.rating.toString())
.fontSize(11)
.fontColor('#FF6D00')
.fontWeight(FontWeight.Bold)
}
Row({ space: 4 }) {
Text('👥')
.fontSize(10)
Text(c.students + '学员')
.fontSize(10)
.fontColor('#999999')
}
Row({ space: 4 }) {
Text('💰')
.fontSize(10)
Text('¥' + c.price.toString() + '/课时')
.fontSize(10)
.fontColor('#FF6D00')
.fontWeight(FontWeight.Bold)
}
}
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
}
.width('100%')
Row({ space: 12 }) {
Button() {
Text('预约体验')
.fontSize(12)
.fontColor('#00C853')
.fontWeight(FontWeight.Bold)
}
.type(ButtonType.Capsule)
.height(36)
.layoutWeight(1)
.backgroundColor('#E8F5E9')
.border({ width: 1, color: '#00C853' })
Button() {
Text('立即预约')
.fontSize(12)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
}
.type(ButtonType.Capsule)
.height(36)
.layoutWeight(1)
.backgroundColor('#00C853')
}
.width('100%')
}
教练详情大卡的设计要点:
- 认证标签专业背书:每位教练名字旁显示
cert认证标签(活力绿#00C853字+浅绿底#E8F5E9),如ACE-CPT(美国运动委员会认证私教)、RYT-500(印度瑜伽联盟500小时认证)、NSCA-CSCS(美国体能协会认证体能专家)、ACSM-CPT(美国运动医学会认证私教)、ZIN(尊巴官方认证),专业认证标签是私教可信度的核心背书,活力绿统一颜色体现专业健康的形象。 - 三维数据展示:每位教练显示评分⭐(能量橙#FF6D00粗体)、学员数👥(灰色)、课时费💰(能量橙#FF6D00粗体/课时),三维数据帮助用户快速评估教练的专业度、人气和价格。
- 预约双按钮设计:底部「预约体验」(浅绿底#E8F5E9+绿边框+绿字,低门槛体验课)+「立即预约」(活力绿底#00C853+白字,正式预约)双按钮横向排列layoutWeight1均分,双按钮设计满足不同用户的需求(想先体验vs直接报名),是私教预约场景的标准交互模式。
- 简介lineHeight:教练简介bio使用
lineHeight(15)行高,10号灰色小字,保证多行简介的可读性。
6位教练涵盖张力(HIIT/燃脂,ACE-CPT,¥199/课时,8.6万学员)、李静(瑜伽/冥想,RYT-500,¥179,12.3万学员最高人气)、王猛(增肌/力量,NSCA-CSCS,¥229最高课时费,全国健美冠军)、陈飞(跑步/单车,ACSM-CPT,¥159,马拉松PB2:38)、赵舞(尊巴/舞蹈,ZIN,¥149最低课时费)、林美(塑形/马甲线,ACE-CPT,¥189,IFBB比基尼选手),数据贴合真实健身私教行业行情。
八、打卡Tab:运动数据环形图+连续打卡条+打卡记录列表
打卡Tab(CheckInTab)聚焦运动数据追踪场景,以运动数据环形图和打卡记录列表为核心,配合编辑健身目标和删除打卡记录弹窗,完整还原运动打卡体验。

8.1 布局结构
打卡Tab采用 Column 纵向布局,从上到下依次为:
- 活力绿头部栏:「📊 运动打卡」标题+右侧🎯目标图标(点击打开编辑健身目标弹窗,预填「每天运动30分钟,消耗300大卡」)。
- 运动数据总览卡:白色卡片,左侧环形数据(85%目标完成,圆形width/height80圆角40浅绿底#E8F5E9+4px活力绿边框模拟环形进度条,居中显示85%),右侧「本周运动数据」+三列统计(打卡天数5/7绿/消耗大卡1,910橙/运动分钟273紫)。
- 连续打卡条:浅绿底#E8F5E9+「✅ 今日已打卡」绿粗体+「连续30天 🔥」橙。
- 打卡记录标题。
- 打卡记录列表:7条记录,每条含类型图标36号+运动类型+日期📅+时长⏱️+消耗大卡🔥(橙)+类型标签(typeColor/typeBg独立色)+右侧⋮更多按钮(点击打开删除打卡记录弹窗)。
8.2 运动数据环形图核心代码
Row({ space: 16 }) {
Column({ space: 4 }) {
Text('85%')
.fontSize(28)
.fontColor('#00C853')
.fontWeight(FontWeight.Bold)
Text('目标完成')
.fontSize(10)
.fontColor('#999999')
}
.width(80)
.height(80)
.borderRadius(40)
.backgroundColor('#E8F5E9')
.border({ width: 4, color: '#00C853' })
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
Column({ space: 6 }) {
Text('本周运动数据')
.fontSize(14)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
Row({ space: 16 }) {
Column({ space: 2 }) {
Text('5/7')
.fontSize(16)
.fontColor('#00C853')
.fontWeight(FontWeight.Bold)
Text('打卡天数')
.fontSize(9)
.fontColor('#999999')
}
Column({ space: 2 }) {
Text('1,910')
.fontSize(16)
.fontColor('#FF6D00')
.fontWeight(FontWeight.Bold)
Text('消耗大卡')
.fontSize(9)
.fontColor('#999999')
}
Column({ space: 2 }) {
Text('273')
.fontSize(16)
.fontColor('#7B1FA2')
.fontWeight(FontWeight.Bold)
Text('运动分钟')
.fontSize(9)
.fontColor('#999999')
}
}
}
.layoutWeight(1)
}
运动数据环形图的设计要点:
- 圆形边框模拟环形进度条:使用
width(80).height(80).borderRadius(40)圆形+backgroundColor('#E8F5E9')浅绿底+border({ width: 4, color: '#00C853' })4px活力绿边框,模拟环形进度条的视觉效果,居中显示「85%」大字号(活力绿粗体)+「目标完成」标签,无需Canvas即可实现环形进度图的视觉效果,简洁高效。 - 本周三列数据:右侧显示「本周运动数据」标题+三列统计(打卡天数5/7活力绿/消耗大卡1,910能量橙/运动分钟273紫),三色对应三个数据维度,用户可一眼掌握本周的运动数据概览。
- 85%目标完成:环形图显示85%的目标完成率,说明用户本周运动目标完成度很高,配合连续30天打卡条,体现用户的坚持和自律。
8.3 打卡记录类型标签
打卡记录列表的核心是运动类型标签,通过 CheckInRecord.type + typeIcon + typeColor + typeBg 四字段组合驱动,7条打卡记录涵盖7种运动类型,每种类型都有独立的图标和颜色:
- HIIT燃脂:🔥图标,橙#FF6D00+浅橙底,30分钟350大卡
- 瑜伽放松:🧘图标,紫#7B1FA2+浅紫底,45分钟180大卡
- 力量训练:💪图标,红#E53935+浅红底,40分钟280大卡
- 晨跑5km:🏃图标,蓝#1565C0+浅蓝底,28分钟300大卡
- 游泳:🏊图标,浅蓝#0288D1+浅蓝底,60分钟500大卡(最高消耗)
- 尊巴舞蹈:💃图标,粉#EC407A+浅粉底,50分钟320大卡
- 拉伸放松:🤸图标,青#00897B+浅青底,20分钟80大卡(最低消耗)
每条记录右侧的⋮更多按钮点击后打开删除打卡记录居中弹窗(🗑️图标+「删除打卡记录?」+「删除后该记录将无法恢复」+取消/删除双按钮),用户可删除误记录的打卡数据。
打卡Tab头部右侧的🎯目标图标点击后打开编辑健身目标底部抽屉弹窗(TextInput height44,预填「每天运动30分钟,消耗300大卡」,浅绿底#F5FFF0+浅绿边框#A5D6A7,取消/保存目标双按钮),用户可设定和修改自己的每日运动目标。
九、社区Tab:热门话题chips横滑+健身动态列表含照片墙
社区Tab(CommunityTab69)聚焦健身爱好者的「分享交流」场景,以热门话题和健身动态为核心,构建健身社区氛围。

9.1 布局结构
社区Tab采用 Column 纵向布局,从上到下依次为:
- 活力绿头部栏:「💬 健身社区」标题+右侧➕发布图标。
- 热门话题chips横滑:
Scroll横向滚动+Row,5个话题标签(#减脂打卡绿/#增肌心得红/#瑜伽日常紫/#跑步日记蓝/#私教推荐橙),每个话题独立颜色+对应浅底色+圆角胶囊样式。 - 健身动态列表:5条动态,每条含头像+昵称+日期+动态标签(绿底浅绿)+动态内容(lineHeight18)+照片墙(三个📸32号图标横向排列)+底部互动栏(点赞👍含数量/评论💬含数量/分享📤,justifyContent SpaceBetween两端对齐)。
9.2 照片墙设计
健身动态列表的特色是「照片墙」设计,每条动态下方显示三个📸32号图标横向排列(space8),模拟健身爱好者分享的运动照片、身材对比照、健身餐照片等,三图照片墙是社交类应用动态的标准展示形式,增强动态的视觉丰富度和真实感。
5条动态涵盖:
- 燃脂达人(减脂打卡标签):坚持HIIT 20天,体脂从28%降到22%,点赞1560最高
- 瑜伽小姐姐(瑜伽心得标签):每天瑜伽45分钟,体态变好了,点赞980
- 肌肉狂魔(增肌打卡标签):深蹲硬拉卧推三大项全PB,增肌5kg,点赞756
- 晨跑健将(跑步打卡标签):连续晨跑30天,5km从30分钟缩短到23分钟,点赞620
- 舞蹈女孩(快乐燃脂标签):尊巴太快乐了,跳着跳着就瘦了,点赞530
动态内容围绕健身成果分享、经验交流,高互动数据(点赞1560/评论128)体现健身社区的活跃氛围,减脂打卡类动态获得最高互动,符合健身社区的主流需求。
十、我的Tab:渐变头+四列数据悬浮卡+月度运动柱状图+成就徽章
我的Tab(ProfileTab69)聚焦健身爱好者的「个人成就」场景,以运动数据统计和成就徽章为核心,构建个人成就体系。

10.1 布局结构
我的Tab采用 Column 纵向布局,从上到下依次为:
- 活力绿渐变头:
backgroundColor('#00C853')+ padding top32 bottom24,居中🏃56号大图标+「健身达人」昵称(白色18号粗体)+「连续打卡30天·累计运动273小时」说明(白色80%透明度)。 - 四列数据悬浮卡:白色卡片+margin top:-12(与渐变头重叠形成悬浮效果)+四列统计横向排列(连续打卡30绿/运动小时273橙/消耗大卡12,560紫/报名课程4红),每列layoutWeight1均分。
- 月度运动时长标题。
- 月度运动时长绿色系渐变柱状图:白色卡片+6个月柱状图(3-8月,绿色系从浅#A5D6A7到深#00C853,height=value*3动态计算,alignItems VerticalAlign Bottom从底部向上生长,8月30小时最高柱子最深活力绿)。
- 成就徽章标题。
- 成就徽章列表:5个徽章(连续打卡30天🔥/力量大师💪/跑步达人🏃/瑜伽修行者🧘已解锁✅,健身王者🏆未解锁🔒opacity0.5半透明),每个徽章含图标+徽章名+描述+解锁状态(if unlocked显示✅已解锁绿,否则🔒未解锁灰),未解锁徽章半透明显示。
10.2 四列数据悬浮卡
四列数据悬浮卡是我的Tab的核心数据展示,四列分别对应健身爱好者最关心的四个个人数据指标:
| 指标 | 数值 | 颜色 | 业务含义 |
|---|---|---|---|
| 连续打卡 | 30天 | 活力绿#00C853 | 坚持运动的天数,体现自律 |
| 运动小时 | 273小时 | 能量橙#FF6D00 | 累计运动时长,体现运动量 |
| 消耗大卡 | 12,560大卡 | 紫#7B1FA2 | 累计消耗热量,体现燃脂成果 |
| 报名课程 | 4门 | 红#E53935 | 报名的课程数量,体现学习投入 |
四列数据通过绿/橙/紫/红四色区分,margin({ top: -12 }) 让白色卡片向上偏移12px与上方活力绿渐变头重叠,形成「卡片悬浮在渐变头之上」的层次感,是电商/社交类应用个人中心常用的设计手法。
10.3 月度运动时长绿色系渐变柱状图
月度运动时长柱状图采用6个月绿色系渐变色,从3月到8月颜色由浅到深,直观呈现运动时长的增长趋势:
| 月份 | 运动时长 | 色值 | 色彩层级 |
|---|---|---|---|
| 3月 | 15小时 | #A5D6A7 |
极浅绿 |
| 4月 | 22小时 | #A5D6A7 |
极浅绿 |
| 5月 | 18小时 | #A5D6A7 |
极浅绿 |
| 6月 | 25小时 | #81C784 |
浅绿 |
| 7月 | 28小时 | #4CAF50 |
中绿 |
| 8月 | 30小时 | #00C853 |
深活力绿 |
柱状图通过纯声明式 Column + backgroundColor(bar.color) + height(bar.value * 3) 实现,无需Canvas,alignItems(VerticalAlign.Bottom) 让柱子从底部向上生长,8月30小时最高柱子颜色最深(活力绿主色#00C853),形成强烈的视觉对比,直观呈现用户从3月15小时到8月30小时的运动时长翻倍增长,体现坚持运动的进步和成就感。
10.4 成就徽章解锁状态
成就徽章列表的特色是已解锁/未解锁状态区分,通过 Achievement69.unlocked 布尔值字段驱动:
- 已解锁徽章(连续打卡30天🔥/力量大师💪/跑步达人🏃/瑜伽修行者🧘):opacity1.0完全不透明,显示「✅ 已解锁」活力绿#00C853标签。
- 未解锁徽章(健身王者🏆,累计运动1000小时,当前273小时):opacity0.5半透明显示,显示「🔒 未解锁」灰色#CCCCCC标签,传达「累计运动1000小时即可解锁」的激励信号。
通过 a.unlocked ? 1.0 : 0.5 动态设置卡片opacity,配合 if (a.unlocked) 条件渲染显示不同的解锁状态标签,构建成就徽章的解锁状态视觉,激励用户继续运动以解锁健身王者徽章。5个徽章中4个已解锁1个未解锁,体现用户的高成就水平,同时保留一个未解锁徽章作为持续激励。
十一、四种模态弹窗:报名课程底部抽屉+取消报名居中+编辑健身目标底部抽屉+删除打卡记录居中
多多健身社交共实现四种模态弹窗,覆盖报名课程、取消报名、编辑健身目标、删除打卡记录四大交互场景,采用「底部抽屉」和「居中确认框」两种弹窗样式。

11.1 弹窗清单
| 弹窗名称 | 样式 | 触发位置 | 核心内容 |
|---|---|---|---|
| 报名课程 | 底部抽屉 | 课程Tab课程卡点击 | 课程信息+4时段选择(06:00/12:00/19:00/21:00)+6教练选择(头像+名字+价格)+实付金额+确认报名按钮 |
| 取消报名 | 居中确认框 | (设计预留) | 取消确认+再想想/确认取消双按钮 |
| 编辑健身目标 | 底部抽屉 | 打卡Tab🎯目标图标点击 | TextInput预填目标+取消/保存目标双按钮 |
| 删除打卡记录 | 居中确认框 | 打卡Tab记录⋮按钮点击 | 🗑️图标+删除确认+取消/删除双按钮 |
11.2 报名课程底部抽屉详解
报名课程弹窗是四种弹窗中最复杂的,采用底部抽屉样式(Stack alignContent Bottom),内容丰富:
- 顶部拖拽条:width40 height4 灰色圆角,居中显示。
- 课程信息:🔥课程图48号+课程名「燃脂HIIT挑战20天」+教练·时长·消耗大卡+价格¥99(橙粗体)+原价¥299删除线。
- 时段选择:标题「选择训练时段」+4个时段选项(06:00-07:00/12:00-13:00/19:00-20:00/21:00-22:00),选中态活力绿底#00C853白字粗体+绿边框,未选中态浅绿底#F5FFF0灰字+灰边框,点击切换选中时段selectedTime。
- 教练选择:标题「选择教练」+6个教练选项(头像24号+名字+价格),选中态活力绿底白字+绿边框,未选中态浅绿底灰字+灰边框,点击切换选中教练selectedCoach。
- 底部操作栏:左侧实付金额(「实付金额」灰+「¥99」橙20号粗体),右侧「确认报名」活力绿底白字Capsule按钮height44,点击关闭弹窗。
报名课程弹窗的核心是时段+教练双选择,用户可根据自己的时间安排和教练偏好选择适合的训练时段和教练,完整还原在线课程报名流程,4个时段覆盖早中晚三个时间段,6位教练覆盖不同专长,满足不同用户的需求。
11.3 编辑健身目标底部抽屉
编辑健身目标弹窗采用底部抽屉样式:
- 标题:「🎯 编辑健身目标」16号粗体+副标题「设定你的每日运动目标」。
- 输入框:
TextInputheight44,placeholder「例如:每天运动30分钟,消耗300大卡」,背景色#F5FFF0,边框#A5D6A7浅绿,点击🎯目标图标时预填this.goalContent = '每天运动30分钟,消耗300大卡'。 - 底部按钮:取消(灰底Capsule)+保存目标(活力绿底白字Capsule),双按钮横向排列layoutWeight1均分。
这个弹窗功能简单但实用,用户可设定和修改自己的每日运动目标,目标数据会驱动打卡Tab的环形进度图(85%目标完成),形成「设定目标→运动打卡→查看完成率」的完整闭环。
11.4 居中确认框统一设计
取消报名和删除打卡记录两种弹窗采用统一的居中确认框样式(Stack alignContent Center):
- 白色卡片width80%+padding24+圆角16,居中显示。
- 顶部图标(删除打卡记录🗑️40号)。
- 标题(「删除打卡记录?」15号粗体)。
- 说明(「删除后该记录将无法恢复」12号灰色)。
- 底部双按钮(取消灰底Capsule+删除红底#E53935白字Capsule,layoutWeight1均分,space12间距)。
居中确认框样式简洁统一,适用于「危险操作确认」类场景(取消、删除),通过红色主按钮传达「此操作不可逆」的警示信号,与报名课程、编辑目标等底部抽屉表单类弹窗形成样式区分。
十二、核心技术特性总结
12.1 六组件多Tab健身社交电商架构
应用采用六个独立 @Component 子组件(CourseTab69/EquipmentTab/CoachTab/CheckInTab/CommunityTab69/ProfileTab69)+ 一个 @Entry 主入口组件(DuoDuoFitnessApp)的六组件多Tab架构,每个Tab对应一个独立子组件,职责清晰、状态隔离,主入口通过 @State activeTab + if/else if 条件渲染调度Tab切换,@Builder bottomTabItem 复用底部Tab栏项,是ArkTS声明式UI多Tab应用的标准架构模式。
12.2 活力绿能量橙双主色与难度三色运动类型八色业务语义体系
应用以活力绿 #00C853 × 能量橙 #FF6D00 为双主色,底色 #F5FFF0,构建了完整的业务语义色彩体系:难度三色(初级绿/中级橙/高级红)、运动类型八色(HIIT橙/瑜伽紫/力量红/晨跑蓝/游泳浅蓝/尊巴粉/拉伸青)、分类chips八色、话题五色(减脂打卡绿/增肌心得红/瑜伽日常紫/跑步日记蓝/私教推荐橙),每种业务状态都有独立的颜色+浅底色组合,信息传达高效直观。
12.3 六大Tab差异化布局
课程Tab(搜索头+分类chips横滑8分类+精选大卡双色系+双列课程网格含难度三色标签)、器材Tab(限时特价横条含倒计时+双列器材网格)、私教Tab(教练详情大卡含认证标签+预约体验/立即预约双按钮)、打卡Tab(运动数据环形图85%+本周三列数据+连续打卡条+打卡记录列表含运动类型八色标签)、社区Tab(五色话题chips横滑+健身动态含三图照片墙)、我的Tab(活力绿渐变头+四列数据悬浮卡+月度运动绿色系渐变柱状图+成就徽章解锁状态),每个Tab都有独特的视觉核心和布局特色,避免千篇一律。
12.4 四模态弹窗与底部抽屉+居中确认框双样式
应用实现四种模态弹窗:报名课程底部抽屉(4时段选择+6教练选择+实付金额)、取消报名居中确认框、编辑健身目标底部抽屉(TextInput预填目标)、删除打卡记录居中确认框,采用底部抽屉(复杂表单类)+居中确认框(危险操作确认类)双样式,底部抽屉统一顶部拖拽条设计,居中确认框统一图标+标题+说明+双按钮设计,样式规范统一。
12.5 丰富的数据可视化
打卡Tab运动数据环形图(圆形width/height80+4px活力绿边框模拟环形进度条,居中85%)、打卡记录消耗大卡、我的Tab月度运动时长绿色系渐变柱状图(3-8月从浅绿#A5D6A7到深活力绿#00C853,height=value*3,8月30小时最高最深),通过纯声明式Column+背景色+圆形边框+动态height实现,无需Canvas,简洁高效。
12.6 8个interface强类型定义与8组静态配置数据
FitnessCourse(含level三字段难度独立色+calories消耗大卡)、Equipment、Coach(含cert认证标签+specialty专长+bio简介)、CheckInRecord(含type四字段运动类型独立色+typeIcon图标)、PostItem69、CatChip69(8分类独立color/bg)、BarData69、Achievement69(含unlocked驱动opacity),10课程+8器材+6教练+7打卡记录+5社区动态+8分类+6月柱状图+5成就徽章,数据丰富且贴合真实健身社交业务。
十三、与同类健身社交App的特性对比
| 对比维度 | 普通健身App | 多多健身社交 | 优势说明 |
|---|---|---|---|
| 首页核心 | 推荐课程流 | 分类chips+精选大卡+双列课程网格含难度标签 | 以「分类+难度」为核心筛选,8分类chips横滑+难度三色标签,用户快速匹配适合的课程 |
| 器材电商 | 无或简单链接 | 限时特价横条含倒计时+双列器材网格 | 完整的运动器材电商体验,限时特价倒计时营造紧迫感,8件器材覆盖七大品类 |
| 私教预约 | 简单教练列表 | 教练详情大卡含认证标签+预约体验/立即预约双按钮 | 专业认证标签(ACE/RYT/NSCA等)+三维数据(评分/学员/课时费)+双按钮预约,还原专业私教预约体验 |
| 打卡功能 | 简单打卡记录 | 运动数据环形图+本周三列数据+连续打卡条+类型八色标签 | 丰富的数据可视化(环形图85%+三列统计),运动类型八色标签,完整还原运动数据追踪体验 |
| 社区功能 | 评论/点赞 | 五色话题chips+健身动态含三图照片墙 | 健身专属社区,五色话题分类+三图照片墙+高互动数据,贴合健身爱好者分享交流需求 |
| 个人中心 | 订单/设置 | 四列数据悬浮卡+月度运动绿色系柱状图+成就徽章解锁状态 | 以「运动成就」为核心,月度运动增长可视化+徽章解锁状态激励,贴合健身爱好者的坚持成就感 |
| 色彩体系 | 单一品牌色 | 活力绿×能量橙双主色+难度三色+运动类型八色+话题五色 | 丰富的业务语义色彩,难度/运动类型/话题各有独立配色,信息传达高效 |
| 弹窗体系 | 简单确认框 | 底部抽屉+居中确认框双样式,报名含时段+教练双选择 | 弹窗样式规范统一,报名课程弹窗内容完整(4时段+6教练+实付金额),还原真实课程报名流程 |
十四、完整代码实现
完整的 69.ets 源码包含8个interface定义、8组静态配置数据(CATS_69分类8项/courses课程10项/equipments器材8项/coaches教练6项/checkIns打卡记录7项/posts69社区动态5项/barData69柱状图6项/achievements69成就徽章5项)、六个独立 @Component 子组件(CourseTab69/EquipmentTab/CoachTab/CheckInTab/CommunityTab69/ProfileTab69)、一个 @Entry 主入口组件(DuoDuoFitnessApp),以及四种模态弹窗(报名课程底部抽屉enrollBuilder/取消报名居中/编辑健身目标底部抽屉editGoalBuilder/删除打卡记录居中deleteBuilder),总代码约1000行,完整实现了多多健身社交的全部功能和视觉效果。
核心代码结构如下:
// 69.ets 多多健身社交 —— 课程 / 器材 / 私教 / 打卡 / 社区 / 我的
// 主题:活力绿 #00C853 × 能量橙 #FF6D00,底色 #F5FFF0(运动活力风)
// 布局差异化:课程=分类chips+精选大卡+双列网格;器材=品类网格+限时特价横条;私教=教练列表+评价+预约;打卡=日历+运动数据环形图+排行榜;社区=健身动态+照片墙+热门话题;我的=渐变头+运动统计柱状图+成就徽章
// 弹框样式:报名课程(底部抽屉含时段选择+教练选择)、取消报名(居中可移除)、编辑健身目标(底部抽屉TextInput)、删除打卡记录(居中可移除)
// 底栏样式:绿橙渐变选中 + 未选灰色
// ============ 类型定义 ============
interface FitnessCourse {
id: number
title: string
coach: string
cover: string
price: number
originalPrice: number
duration: string
calories: string
level: string
levelColor: string
levelBg: string
students: string
rating: number
tag: string
category: string
}
interface Equipment {
id: number
name: string
brand: string
cover: string
price: number
originalPrice: number
sold: string
tag: string
category: string
rating: number
}
interface Coach {
id: number
name: string
avatar: string
title: string
specialty: string
rating: number
students: string
price: number
cert: string
bio: string
}
interface CheckInRecord {
id: number
date: string
type: string
typeIcon: string
duration: string
calories: number
typeColor: string
typeBg: string
}
interface PostItem69 {
id: number
avatar: string
name: string
content: string
image: string
likes: number
comments: number
date: string
tag: string
}
interface CatChip69 {
label: string
icon: string
color: string
bg: string
}
interface BarData69 {
label: string
value: number
color: string
}
interface Achievement69 {
icon: string
label: string
desc: string
unlocked: boolean
}
// ============ 静态配置 ============
const CATS_69: CatChip69[] = [
{ label: '瑜伽', icon: '🧘', color: '#7B1FA2', bg: '#F3E5F5' },
{ label: '力量', icon: '💪', color: '#E53935', bg: '#FFEBEE' },
{ label: '有氧', icon: '🏃', color: '#1565C0', bg: '#E3F2FD' },
{ label: 'HIIT', icon: '🔥', color: '#FF6D00', bg: '#FFF3E0' },
{ label: '舞蹈', icon: '💃', color: '#EC407A', bg: '#FCE4EC' },
{ label: '拉伸', icon: '🤸', color: '#00897B', bg: '#E0F2F1' },
{ label: '搏击', icon: '🥊', color: '#5D4037', bg: '#EFEBE9' },
{ label: '游泳', icon: '🏊', color: '#0288D1', bg: '#E1F5FE' }
]
const courses: FitnessCourse[] = [
{ id: 1, title: '燃脂HIIT挑战20天', coach: '张力教练', cover: '🔥', price: 99, originalPrice: 299, duration: '30分钟/天', calories: '350大卡', level: '中级', levelColor: '#FF6D00', levelBg: '#FFF3E0', students: '8.6万', rating: 9.5, tag: '爆款', category: 'HIIT' },
{ id: 2, title: '零基础瑜伽入门', coach: '李静教练', cover: '🧘', price: 79, originalPrice: 199, duration: '45分钟/天', calories: '180大卡', level: '初级', levelColor: '#00C853', levelBg: '#E8F5E9', students: '12.3万', rating: 9.6, tag: '热门', category: '瑜伽' },
{ id: 3, title: '腹肌撕裂者进阶', coach: '王猛教练', cover: '💪', price: 129, originalPrice: 399, duration: '25分钟/天', calories: '280大卡', level: '高级', levelColor: '#E53935', levelBg: '#FFEBEE', students: '5.8万', rating: 9.4, tag: '硬核', category: '力量' },
{ id: 4, title: '动感单车燃脂营', coach: '陈飞教练', cover: '🚴', price: 89, originalPrice: 259, duration: '40分钟/天', calories: '420大卡', level: '中级', levelColor: '#FF6D00', levelBg: '#FFF3E0', students: '6.2万', rating: 9.3, tag: '推荐', category: '有氧' },
{ id: 5, title: '尊巴舞蹈瘦身', coach: '赵舞教练', cover: '💃', price: 69, originalPrice: 179, duration: '50分钟/天', calories: '320大卡', level: '初级', levelColor: '#00C853', levelBg: '#E8F5E9', students: '7.8万', rating: 9.2, tag: '快乐燃脂', category: '舞蹈' },
{ id: 6, title: '拳击搏击燃脂', coach: '孙武教练', cover: '🥊', price: 149, originalPrice: 399, duration: '35分钟/天', calories: '450大卡', level: '高级', levelColor: '#E53935', levelBg: '#FFEBEE', students: '4.5万', rating: 9.5, tag: '刺激', category: '搏击' },
{ id: 7, title: '全身拉伸放松', coach: '周柔教练', cover: '🤸', price: 49, originalPrice: 129, duration: '20分钟/天', calories: '80大卡', level: '初级', levelColor: '#00C853', levelBg: '#E8F5E9', students: '9.6万', rating: 9.7, tag: '放松', category: '拉伸' },
{ id: 8, title: '自由泳速成班', coach: '吴泳教练', cover: '🏊', price: 199, originalPrice: 499, duration: '60分钟/天', calories: '500大卡', level: '中级', levelColor: '#FF6D00', levelBg: '#FFF3E0', students: '3.2万', rating: 9.4, tag: '泳技', category: '游泳' },
{ id: 9, title: '马甲线养成计划', coach: '林美教练', cover: '💪', price: 119, originalPrice: 299, duration: '30分钟/天', calories: '260大卡', level: '中级', levelColor: '#FF6D00', levelBg: '#FFF3E0', students: '8.9万', rating: 9.6, tag: '塑形', category: '力量' },
{ id: 10, title: '晨跑5公里计划', coach: '陈飞教练', cover: '🏃', price: 59, originalPrice: 159, duration: '30分钟/天', calories: '300大卡', level: '初级', levelColor: '#00C853', levelBg: '#E8F5E9', students: '15.6万', rating: 9.8, tag: '免费', category: '有氧' }
]
const equipments: Equipment[] = [
{ id: 1, name: ' adjustable哑铃20kg', brand: '海德', cover: '🏋️', price: 299, originalPrice: 599, sold: '已售2.3万', tag: '热销', category: '力量', rating: 9.5 },
{ id: 2, name: '瑜伽垫加厚防滑', brand: 'Keep', cover: '🧘', price: 89, originalPrice: 199, sold: '已售8.6万', tag: '爆款', category: '瑜伽', rating: 9.6 },
{ id: 3, name: '跳绳智能计数', brand: 'Keep', cover: '🔗', price: 49, originalPrice: 129, sold: '已售12.3万', tag: '新品', category: '有氧', rating: 9.4 },
{ id: 4, name: '弹力带5件套', brand: '迪卡侬', cover: '🎯', price: 39, originalPrice: 99, sold: '已售6.8万', tag: '推荐', category: '拉伸', rating: 9.3 },
{ id: 5, name: '筋膜枪mini版', brand: 'SKG', cover: '🔫', price: 199, originalPrice: 499, sold: '已售3.5万', tag: '热销', category: '恢复', rating: 9.5 },
{ id: 6, name: '智能手环运动版', brand: '华为', cover: '⌚', price: 249, originalPrice: 399, sold: '已售5.2万', tag: '智能', category: '穿戴', rating: 9.6 },
{ id: 7, name: '壶铃16kg', brand: '海德', cover: '🔔', price: 159, originalPrice: 299, sold: '已售1.8万', tag: '力量', category: '力量', rating: 9.2 },
{ id: 8, name: '平衡球65cm', brand: '迪卡侬', cover: '⚪', price: 79, originalPrice: 159, sold: '已售2.9万', tag: '核心', category: '核心', rating: 9.4 }
]
const coaches: Coach[] = [
{ id: 1, name: '张力', avatar: '👨🦰', title: 'ACE认证私教', specialty: 'HIIT/燃脂', rating: 9.5, students: '8.6万', price: 199, cert: 'ACE-CPT', bio: '5年私教经验,帮助3000+学员减脂塑形' },
{ id: 2, name: '李静', avatar: '👩🦰', title: '瑜伽高级导师', specialty: '瑜伽/冥想', rating: 9.6, students: '12.3万', price: 179, cert: 'RYT-500', bio: '印度阿斯汤伽认证,8年瑜伽教学经验' },
{ id: 3, name: '王猛', avatar: '💪', title: '力量训练专家', specialty: '增肌/力量', rating: 9.4, students: '5.8万', price: 229, cert: 'NSCA-CSCS', bio: '全国健美冠军,专业力量训练体系' },
{ id: 4, name: '陈飞', avatar: '🏃', title: '有氧运动教练', specialty: '跑步/单车', rating: 9.3, students: '6.2万', price: 159, cert: 'ACSM-CPT', bio: '马拉松PB2:38,专业有氧训练指导' },
{ id: 5, name: '赵舞', avatar: '💃', title: '舞蹈健身教练', specialty: '尊巴/舞蹈', rating: 9.2, students: '7.8万', price: 149, cert: 'ZIN', bio: 'Zumba官方认证教练,快乐燃脂专家' },
{ id: 6, name: '林美', avatar: '🧘♀️', title: '塑形减脂专家', specialty: '塑形/马甲线', rating: 9.6, students: '8.9万', price: 189, cert: 'ACE-CPT', bio: 'IFBB比基尼选手,专业女性塑形' }
]
const checkIns: CheckInRecord[] = [
{ id: 1, date: '08-24', type: 'HIIT燃脂', typeIcon: '🔥', duration: '30分钟', calories: 350, typeColor: '#FF6D00', typeBg: '#FFF3E0' },
{ id: 2, date: '08-23', type: '瑜伽放松', typeIcon: '🧘', duration: '45分钟', calories: 180, typeColor: '#7B1FA2', typeBg: '#F3E5F5' },
{ id: 3, date: '08-22', type: '力量训练', typeIcon: '💪', duration: '40分钟', calories: 280, typeColor: '#E53935', typeBg: '#FFEBEE' },
{ id: 4, date: '08-21', type: '晨跑5km', typeIcon: '🏃', duration: '28分钟', calories: 300, typeColor: '#1565C0', typeBg: '#E3F2FD' },
{ id: 5, date: '08-20', type: '游泳', typeIcon: '🏊', duration: '60分钟', calories: 500, typeColor: '#0288D1', typeBg: '#E1F5FE' },
{ id: 6, date: '08-19', type: '尊巴舞蹈', typeIcon: '💃', duration: '50分钟', calories: 320, typeColor: '#EC407A', typeBg: '#FCE4EC' },
{ id: 7, date: '08-18', type: '拉伸放松', typeIcon: '🤸', duration: '20分钟', calories: 80, typeColor: '#00897B', typeBg: '#E0F2F1' }
]
const posts69: PostItem69[] = [
{ id: 1, avatar: '🔥', name: '燃脂达人', content: '坚持HIIT 20天,体脂从28%降到22%!每天30分钟,效果太明显了!', image: '📸', likes: 1560, comments: 128, date: '2小时前', tag: '减脂打卡' },
{ id: 2, avatar: '🧘', name: '瑜伽小姐姐', content: '每天坚持瑜伽45分钟,体态变好了,腰也不疼了,瑜伽yyds!', image: '📸', likes: 980, comments: 89, date: '5小时前', tag: '瑜伽心得' },
{ id: 3, avatar: '💪', name: '肌肉狂魔', content: '深蹲硬拉卧推三大项全PB!力量训练3个月,增肌5kg!', image: '📸', likes: 756, comments: 120, date: '1天前', tag: '增肌打卡' },
{ id: 4, avatar: '🏃', name: '晨跑健将', content: '连续晨跑30天,5km从30分钟缩短到23分钟!坚持就是胜利!', image: '📸', likes: 620, comments: 78, date: '2天前', tag: '跑步打卡' },
{ id: 5, avatar: '💃', name: '舞蹈女孩', content: '尊巴太快乐了!跳着跳着就瘦了,这才是最好的减脂方式!', image: '📸', likes: 530, comments: 45, date: '3天前', tag: '快乐燃脂' }
]
const barData69: BarData69[] = [
{ label: '3月', value: 15, color: '#A5D6A7' },
{ label: '4月', value: 22, color: '#A5D6A7' },
{ label: '5月', value: 18, color: '#A5D6A7' },
{ label: '6月', value: 25, color: '#81C784' },
{ label: '7月', value: 28, color: '#4CAF50' },
{ label: '8月', value: 30, color: '#00C853' }
]
const achievements69: Achievement69[] = [
{ icon: '🔥', label: '连续打卡30天', desc: '坚持运动30天不间断', unlocked: true },
{ icon: '💪', label: '力量大师', desc: '完成100次力量训练', unlocked: true },
{ icon: '🏃', label: '跑步达人', desc: '累计跑步100km', unlocked: true },
{ icon: '🧘', label: '瑜伽修行者', desc: '完成50次瑜伽课', unlocked: true },
{ icon: '🏆', label: '健身王者', desc: '累计运动1000小时', unlocked: false }
]
// ============ modalOverlay ============
function modalOverlay(onClose: () => void) {
}
// ============ Tab1 课程 ============
@Component
struct CourseTab69 {
@State activeCat: number = 0
@State showEnroll: boolean = false
@State selectedTime: number = 0
@State selectedCoach: number = 0
@Builder
enrollBuilder() {
Stack({ alignContent: Alignment.Bottom }) {
Column({ space: 0 }) {
Column() {
Row()
.width(40)
.height(4)
.backgroundColor('#E0E0E0')
.borderRadius(2)
}
.width('100%')
.height(30)
.justifyContent(FlexAlign.Center)
Row({ space: 12 }) {
Text('🔥')
.fontSize(48)
Column({ space: 4 }) {
Text('燃脂HIIT挑战20天')
.fontSize(15)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
Text('张力教练 · 30分钟/天 · 350大卡')
.fontSize(11)
.fontColor('#999999')
Row({ space: 6 }) {
Text('¥99')
.fontSize(18)
.fontColor('#FF6D00')
.fontWeight(FontWeight.Bold)
Text('¥299')
.fontSize(11)
.fontColor('#999999')
.decoration({ type: TextDecorationType.LineThrough })
}
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
}
.width('100%')
.padding({ left: 16, right: 16, bottom: 12 })
Divider().color('#F0F0F0')
// 时段选择
Text('选择训练时段')
.fontSize(13)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
.width('100%')
.padding({ left: 16, top: 12, bottom: 8 })
Column({ space: 8 }) {
Row({ space: 8 }) {
ForEach(['06:00-07:00', '12:00-13:00', '19:00-20:00', '21:00-22:00'], (t: string, idx: number) => {
Text(t)
.fontSize(11)
.fontColor(this.selectedTime === idx ? '#FFFFFF' : '#333333')
.fontWeight(FontWeight.Bold)
.borderRadius(8)
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.backgroundColor(this.selectedTime === idx ? '#00C853' : '#F5FFF0')
.border({ width: 1, color: this.selectedTime === idx ? '#00C853' : '#E0E0E0' })
.onClick(() => { this.selectedTime = idx })
}, (t: string) => t)
}
.padding({ left: 16, right: 16 })
}
// 教练选择
Text('选择教练')
.fontSize(13)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
.width('100%')
.padding({ left: 16, top: 12, bottom: 8 })
Column({ space: 8 }) {
Row({ space: 8 }) {
ForEach(coaches, (c: Coach, idx: number) => {
Column({ space: 4 }) {
Text(c.avatar)
.fontSize(24)
Text(c.name)
.fontSize(10)
.fontColor(this.selectedCoach === idx ? '#FFFFFF' : '#333333')
.fontWeight(FontWeight.Bold)
Text('¥' + c.price.toString())
.fontSize(9)
.fontColor(this.selectedCoach === idx ? '#FFFFFF' : '#FF6D00')
}
.borderRadius(8)
.padding({ top: 8, bottom: 8, left: 12, right: 12 })
.backgroundColor(this.selectedCoach === idx ? '#00C853' : '#F5FFF0')
.border({ width: 1, color: this.selectedCoach === idx ? '#00C853' : '#E0E0E0' })
.alignItems(HorizontalAlign.Center)
.onClick(() => { this.selectedCoach = idx })
}, (c: Coach) => c.id.toString())
}
.padding({ left: 16, right: 16 })
}
Row({ space: 12 }) {
Column({ space: 2 }) {
Text('实付金额')
.fontSize(11)
.fontColor('#999999')
Text('¥99')
.fontSize(20)
.fontColor('#FF6D00')
.fontWeight(FontWeight.Bold)
}
.alignItems(HorizontalAlign.Start)
Button() {
Text('确认报名')
.fontSize(15)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
}
.type(ButtonType.Capsule)
.height(44)
.layoutWeight(1)
.backgroundColor('#00C853')
.onClick(() => { this.showEnroll = false })
}
.width('100%')
.padding({ left: 16, right: 16, top: 16, bottom: 20 })
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius({ topLeft: 16, topRight: 16 })
}
.width('100%')
.height('100%')
}
build() {
Column() {
// 电商风头部
Row({ space: 8 }) {
Text('🔍')
.fontSize(18)
Text('搜索健身课程、教练...')
.fontSize(13)
.fontColor('rgba(255,255,255,0.7)')
.layoutWeight(1)
Text('💪')
.fontSize(22)
.fontColor('#FFFFFF')
}
.width('100%')
.height(44)
.backgroundColor('#00C853')
.padding({ left: 16, right: 16 })
.alignItems(VerticalAlign.Center)
// 分类chips横滑
Scroll() {
Row({ space: 8 }) {
ForEach(CATS_69, (c: CatChip69, idx: number) => {
Row({ space: 4 }) {
Text(c.icon)
.fontSize(14)
Text(c.label)
.fontSize(12)
.fontColor(this.activeCat === idx ? '#FFFFFF' : c.color)
.fontWeight(this.activeCat === idx ? FontWeight.Bold : FontWeight.Normal)
}
.borderRadius(16)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor(this.activeCat === idx ? '#00C853' : c.bg)
.onClick(() => { this.activeCat = idx })
}, (c: CatChip69) => c.label)
}
.padding({ left: 12, right: 12 })
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
.height(40)
.backgroundColor('#FFFFFF')
Scroll() {
Column({ space: 0 }) {
// 精选大卡
Column({ space: 12 }) {
Row({ space: 12 }) {
Column({ space: 6 }) {
Text('🔥')
.fontSize(44)
Text('燃脂HIIT 20天')
.fontSize(15)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
Row({ space: 6 }) {
Text('张力教练')
.fontSize(10)
.fontColor('rgba(255,255,255,0.8)')
Text('8.6万人')
.fontSize(10)
.fontColor('rgba(255,255,255,0.6)')
}
Text('¥99 起')
.fontSize(14)
.fontColor('#FF6D00')
.fontWeight(FontWeight.Bold)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
}
.width('55%')
.padding(16)
.borderRadius(12)
.backgroundColor('#00C853')
.alignItems(HorizontalAlign.Start)
Column({ space: 6 }) {
Text('🧘')
.fontSize(44)
Text('零基础瑜伽')
.fontSize(15)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
Row({ space: 6 }) {
Text('李静教练')
.fontSize(10)
.fontColor('rgba(255,255,255,0.8)')
Text('12.3万人')
.fontSize(10)
.fontColor('rgba(255,255,255,0.6)')
}
Text('¥79 起')
.fontSize(14)
.fontColor('#7B1FA2')
.fontWeight(FontWeight.Bold)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
}
.layoutWeight(1)
.padding(16)
.borderRadius(12)
.backgroundColor('#7B1FA2')
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding({ left: 12, right: 12 })
}
Row() {
Text('🔥 热门课程')
.fontSize(16)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Text('全部 >')
.fontSize(11)
.fontColor('#00C853')
}
.width('100%')
.padding({ left: 12, right: 12, top: 16, bottom: 8 })
// 双列课程网格
ForEach(courses, (c: FitnessCourse) => {
Row({ space: 10 }) {
Column({ space: 6 }) {
Text(c.cover)
.fontSize(40)
Row() {
Text(c.tag)
.fontSize(9)
.fontColor('#FFFFFF')
.backgroundColor('#FF6D00')
.borderRadius(3)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
Text(' ' + c.level)
.fontSize(9)
.fontColor(c.levelColor)
.backgroundColor(c.levelBg)
.borderRadius(3)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
}
Text(c.title)
.fontSize(12)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
Text(c.coach + ' · ' + c.duration)
.fontSize(9)
.fontColor('#999999')
Text(c.calories)
.fontSize(9)
.fontColor('#FF6D00')
Row() {
Text('¥' + c.price.toString())
.fontSize(13)
.fontColor('#FF6D00')
.fontWeight(FontWeight.Bold)
Text(' ¥' + c.originalPrice.toString())
.fontSize(9)
.fontColor('#CCCCCC')
.decoration({ type: TextDecorationType.LineThrough })
}
Text(c.rating + '分 · ' + c.students + '人')
.fontSize(9)
.fontColor('#999999')
}
.layoutWeight(1)
.padding(10)
.borderRadius(10)
.backgroundColor('#FFFFFF')
.alignItems(HorizontalAlign.Start)
.onClick(() => { this.showEnroll = true })
Column({ space: 6 }) {
Text(c.cover)
.fontSize(40)
Row() {
Text(c.tag)
.fontSize(9)
.fontColor('#FFFFFF')
.backgroundColor('#00C853')
.borderRadius(3)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
Text(' ' + c.category)
.fontSize(9)
.fontColor('#999999')
}
Text(c.title)
.fontSize(12)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
Text(c.coach + ' · ' + c.duration)
.fontSize(9)
.fontColor('#999999')
Text('消耗' + c.calories)
.fontSize(9)
.fontColor('#FF6D00')
Row() {
Text('¥' + c.price.toString())
.fontSize(13)
.fontColor('#FF6D00')
.fontWeight(FontWeight.Bold)
Text(' ¥' + c.originalPrice.toString())
.fontSize(9)
.fontColor('#CCCCCC')
.decoration({ type: TextDecorationType.LineThrough })
}
Text(c.rating + '分 · ' + c.students + '人')
.fontSize(9)
.fontColor('#999999')
}
.layoutWeight(1)
.padding(10)
.borderRadius(10)
.backgroundColor('#FFFFFF')
.alignItems(HorizontalAlign.Start)
.onClick(() => { this.showEnroll = true })
}
.width('100%')
.padding({ left: 12, right: 12 })
}, (c: FitnessCourse) => c.id.toString())
Column()
.height(80)
}
}
.scrollBar(BarState.Off)
.constraintSize({ maxHeight: '100%' })
.layoutWeight(1)
if (this.showEnroll) {
this.enrollBuilder()
}
}
.width('100%')
.height('100%')
}
}
// ============ Tab2 器材 ============
@Component
struct EquipmentTab {
build() {
Column() {
Row() {
Text('🏋️ 运动器材')
.fontSize(17)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Text('🛒')
.fontSize(20)
.fontColor('#FFFFFF')
}
.width('100%')
.height(48)
.backgroundColor('#00C853')
.padding({ left: 16, right: 16 })
.alignItems(VerticalAlign.Center)
Scroll() {
Column({ space: 0 }) {
// 限时特价横条
Row({ space: 12 }) {
Text('⚡')
.fontSize(28)
.fontColor('#FF6D00')
Column({ space: 2 }) {
Text('限时特价 5折起')
.fontSize(14)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
Text('筋膜枪、哑铃、瑜伽垫热销中')
.fontSize(10)
.fontColor('#999999')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Text('02:15:30')
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#FF6D00')
.borderRadius(6)
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
}
.width('92%')
.padding(12)
.backgroundColor('#FFF3E0')
.borderRadius(10)
.margin({ left: 12, right: 12, top: 12, bottom: 8 })
Row() {
Text('🔥 热销器材')
.fontSize(15)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Text('全部 >')
.fontSize(11)
.fontColor('#00C853')
}
.width('100%')
.padding({ left: 12, bottom: 8 })
// 双列器材网格
ForEach(equipments, (e: Equipment) => {
Row({ space: 10 }) {
Column({ space: 6 }) {
Text(e.cover)
.fontSize(36)
Row() {
Text(e.tag)
.fontSize(9)
.fontColor('#FFFFFF')
.backgroundColor('#FF6D00')
.borderRadius(3)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
Text(' ' + e.brand)
.fontSize(9)
.fontColor('#999999')
}
Text(e.name)
.fontSize(11)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
Text(e.sold)
.fontSize(9)
.fontColor('#999999')
Row() {
Text('¥' + e.price.toString())
.fontSize(13)
.fontColor('#FF6D00')
.fontWeight(FontWeight.Bold)
Text(' ¥' + e.originalPrice.toString())
.fontSize(9)
.fontColor('#CCCCCC')
.decoration({ type: TextDecorationType.LineThrough })
}
Text(e.rating + '分')
.fontSize(9)
.fontColor('#00C853')
}
.layoutWeight(1)
.padding(10)
.borderRadius(10)
.backgroundColor('#FFFFFF')
.alignItems(HorizontalAlign.Start)
Column({ space: 6 }) {
Text(e.cover)
.fontSize(36)
Row() {
Text(e.tag)
.fontSize(9)
.fontColor('#FFFFFF')
.backgroundColor('#00C853')
.borderRadius(3)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
Text(' ' + e.category)
.fontSize(9)
.fontColor('#999999')
}
Text(e.name)
.fontSize(11)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
Text(e.brand + ' · ' + e.sold)
.fontSize(9)
.fontColor('#999999')
Row() {
Text('¥' + e.price.toString())
.fontSize(13)
.fontColor('#FF6D00')
.fontWeight(FontWeight.Bold)
Text(' ¥' + e.originalPrice.toString())
.fontSize(9)
.fontColor('#CCCCCC')
.decoration({ type: TextDecorationType.LineThrough })
}
Text(e.rating + '分')
.fontSize(9)
.fontColor('#00C853')
}
.layoutWeight(1)
.padding(10)
.borderRadius(10)
.backgroundColor('#FFFFFF')
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding({ left: 12, right: 12 })
}, (e: Equipment) => e.id.toString())
Column()
.height(80)
}
}
.scrollBar(BarState.Off)
.constraintSize({ maxHeight: '100%' })
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
}
// ============ Tab3 私教 ============
@Component
struct CoachTab {
build() {
Column() {
Row() {
Text('👨🏫 精选私教')
.fontSize(17)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Text('🔍')
.fontSize(20)
.fontColor('#FFFFFF')
}
.width('100%')
.height(48)
.backgroundColor('#00C853')
.padding({ left: 16, right: 16 })
.alignItems(VerticalAlign.Center)
Scroll() {
Column({ space: 0 }) {
ForEach(coaches, (c: Coach) => {
Column({ space: 8 }) {
Row({ space: 12 }) {
Text(c.avatar)
.fontSize(44)
Column({ space: 4 }) {
Row({ space: 6 }) {
Text(c.name)
.fontSize(15)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
Text(c.cert)
.fontSize(9)
.fontColor('#00C853')
.backgroundColor('#E8F5E9')
.borderRadius(3)
.padding({ left: 5, right: 5, top: 1, bottom: 1 })
}
Text(c.title + ' · ' + c.specialty)
.fontSize(11)
.fontColor('#666666')
Text(c.bio)
.fontSize(10)
.fontColor('#999999')
.lineHeight(15)
Row({ space: 12 }) {
Row({ space: 4 }) {
Text('⭐')
.fontSize(10)
Text(c.rating.toString())
.fontSize(11)
.fontColor('#FF6D00')
.fontWeight(FontWeight.Bold)
}
Row({ space: 4 }) {
Text('👥')
.fontSize(10)
Text(c.students + '学员')
.fontSize(10)
.fontColor('#999999')
}
Row({ space: 4 }) {
Text('💰')
.fontSize(10)
Text('¥' + c.price.toString() + '/课时')
.fontSize(10)
.fontColor('#FF6D00')
.fontWeight(FontWeight.Bold)
}
}
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
}
.width('100%')
Row({ space: 12 }) {
Button() {
Text('预约体验')
.fontSize(12)
.fontColor('#00C853')
.fontWeight(FontWeight.Bold)
}
.type(ButtonType.Capsule)
.height(36)
.layoutWeight(1)
.backgroundColor('#E8F5E9')
.border({ width: 1, color: '#00C853' })
Button() {
Text('立即预约')
.fontSize(12)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
}
.type(ButtonType.Capsule)
.height(36)
.layoutWeight(1)
.backgroundColor('#00C853')
}
.width('100%')
}
.width('100%')
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(10)
.margin({ left: 12, right: 12, bottom: 8 })
}, (c: Coach) => c.id.toString())
Column()
.height(80)
}
}
.scrollBar(BarState.Off)
.constraintSize({ maxHeight: '100%' })
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
}
// ============ Tab4 打卡 ============
@Component
struct CheckInTab {
@State showEditGoal: boolean = false
@State goalContent: string = ''
@State showDelete: boolean = false
@State delId: number = 0
@Builder
editGoalBuilder() {
Stack({ alignContent: Alignment.Bottom }) {
Column({ space: 0 }) {
Column() {
Row()
.width(40)
.height(4)
.backgroundColor('#E0E0E0')
.borderRadius(2)
}
.width('100%')
.height(30)
.justifyContent(FlexAlign.Center)
Text('🎯 编辑健身目标')
.fontSize(16)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
.width('100%')
.padding({ left: 16, bottom: 4 })
Text('设定你的每日运动目标')
.fontSize(11)
.fontColor('#999999')
.width('100%')
.padding({ left: 16, bottom: 12 })
TextInput({ text: this.goalContent, placeholder: '例如:每天运动30分钟,消耗300大卡' })
.width('92%')
.height(44)
.fontSize(14)
.fontColor('#333333')
.borderRadius(8)
.backgroundColor('#F5FFF0')
.border({ width: 1, color: '#A5D6A7' })
.onChange((val: string) => { this.goalContent = val })
Row({ space: 12 }) {
Button() {
Text('取消')
.fontSize(14)
.fontColor('#666666')
}
.type(ButtonType.Capsule)
.height(42)
.layoutWeight(1)
.backgroundColor('#F5F5F5')
.onClick(() => { this.showEditGoal = false })
Button() {
Text('保存目标')
.fontSize(14)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
}
.type(ButtonType.Capsule)
.height(42)
.layoutWeight(1)
.backgroundColor('#00C853')
.onClick(() => { this.showEditGoal = false })
}
.width('100%')
.padding({ left: 16, right: 16, top: 16, bottom: 20 })
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius({ topLeft: 16, topRight: 16 })
}
.width('100%')
.height('100%')
}
@Builder
deleteBuilder() {
Stack({ alignContent: Alignment.Center }) {
Column({ space: 12 }) {
Text('🗑️')
.fontSize(40)
Text('删除打卡记录?')
.fontSize(15)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
Text('删除后该记录将无法恢复')
.fontSize(12)
.fontColor('#999999')
Row({ space: 12 }) {
Button() {
Text('取消')
.fontSize(13)
.fontColor('#666666')
}
.type(ButtonType.Capsule)
.height(38)
.layoutWeight(1)
.backgroundColor('#F5F5F5')
.onClick(() => { this.showDelete = false })
Button() {
Text('删除')
.fontSize(13)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
}
.type(ButtonType.Capsule)
.height(38)
.layoutWeight(1)
.backgroundColor('#E53935')
.onClick(() => { this.showDelete = false })
}
.width('100%')
}
.width('80%')
.padding(24)
.backgroundColor('#FFFFFF')
.borderRadius(16)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.height('100%')
}
build() {
Column() {
Row() {
Text('📊 运动打卡')
.fontSize(17)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Text('🎯')
.fontSize(20)
.fontColor('#FFFFFF')
.onClick(() => {
this.goalContent = '每天运动30分钟,消耗300大卡'
this.showEditGoal = true
})
}
.width('100%')
.height(48)
.backgroundColor('#00C853')
.padding({ left: 16, right: 16 })
.alignItems(VerticalAlign.Center)
Scroll() {
Column({ space: 0 }) {
// 运动数据总览
Row({ space: 16 }) {
// 环形数据
Column({ space: 4 }) {
Text('85%')
.fontSize(28)
.fontColor('#00C853')
.fontWeight(FontWeight.Bold)
Text('目标完成')
.fontSize(10)
.fontColor('#999999')
}
.width(80)
.height(80)
.borderRadius(40)
.backgroundColor('#E8F5E9')
.border({ width: 4, color: '#00C853' })
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
Column({ space: 6 }) {
Text('本周运动数据')
.fontSize(14)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
Row({ space: 16 }) {
Column({ space: 2 }) {
Text('5/7')
.fontSize(16)
.fontColor('#00C853')
.fontWeight(FontWeight.Bold)
Text('打卡天数')
.fontSize(9)
.fontColor('#999999')
}
Column({ space: 2 }) {
Text('1,910')
.fontSize(16)
.fontColor('#FF6D00')
.fontWeight(FontWeight.Bold)
Text('消耗大卡')
.fontSize(9)
.fontColor('#999999')
}
Column({ space: 2 }) {
Text('273')
.fontSize(16)
.fontColor('#7B1FA2')
.fontWeight(FontWeight.Bold)
Text('运动分钟')
.fontSize(9)
.fontColor('#999999')
}
}
}
.layoutWeight(1)
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.margin({ left: 12, right: 12, top: 12, bottom: 8 })
.borderRadius(12)
// 连续打卡
Row({ space: 8 }) {
Text('✅ 今日已打卡')
.fontSize(12)
.fontColor('#00C853')
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Text('连续30天 🔥')
.fontSize(11)
.fontColor('#FF6D00')
}
.width('92%')
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.backgroundColor('#E8F5E9')
.borderRadius(8)
.margin({ left: 12, right: 12, bottom: 12 })
Text('📋 打卡记录')
.fontSize(15)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
.width('100%')
.padding({ left: 12, bottom: 8 })
ForEach(checkIns, (c: CheckInRecord) => {
Row({ space: 12 }) {
Text(c.typeIcon)
.fontSize(36)
Column({ space: 4 }) {
Text(c.type)
.fontSize(13)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
Row({ space: 8 }) {
Text('📅 ' + c.date)
.fontSize(10)
.fontColor('#999999')
Text('⏱️ ' + c.duration)
.fontSize(10)
.fontColor('#999999')
Text('🔥 ' + c.calories.toString() + '大卡')
.fontSize(10)
.fontColor('#FF6D00')
}
Text(c.type)
.fontSize(9)
.fontColor(c.typeColor)
.backgroundColor(c.typeBg)
.borderRadius(3)
.padding({ left: 5, right: 5, top: 1, bottom: 1 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Text('⋮')
.fontSize(18)
.fontColor('#CCCCCC')
.onClick(() => {
this.delId = c.id
this.showDelete = true
})
}
.width('100%')
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(10)
.margin({ left: 12, right: 12, bottom: 6 })
}, (c: CheckInRecord) => c.id.toString())
Column()
.height(80)
}
}
.scrollBar(BarState.Off)
.constraintSize({ maxHeight: '100%' })
.layoutWeight(1)
if (this.showEditGoal) {
this.editGoalBuilder()
}
if (this.showDelete) {
this.deleteBuilder()
}
}
.width('100%')
.height('100%')
}
}
// ============ Tab5 社区 ============
@Component
struct CommunityTab69 {
build() {
Column() {
Row() {
Text('💬 健身社区')
.fontSize(17)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Text('➕')
.fontSize(22)
.fontColor('#FFFFFF')
}
.width('100%')
.height(48)
.backgroundColor('#00C853')
.padding({ left: 16, right: 16 })
.alignItems(VerticalAlign.Center)
Scroll() {
Column({ space: 0 }) {
// 热门话题
Scroll() {
Row({ space: 8 }) {
Text('#减脂打卡')
.fontSize(12)
.fontColor('#00C853')
.backgroundColor('#E8F5E9')
.borderRadius(12)
.padding({ left: 10, right: 10, top: 5, bottom: 5 })
Text('#增肌心得')
.fontSize(12)
.fontColor('#E53935')
.backgroundColor('#FFEBEE')
.borderRadius(12)
.padding({ left: 10, right: 10, top: 5, bottom: 5 })
Text('#瑜伽日常')
.fontSize(12)
.fontColor('#7B1FA2')
.backgroundColor('#F3E5F5')
.borderRadius(12)
.padding({ left: 10, right: 10, top: 5, bottom: 5 })
Text('#跑步日记')
.fontSize(12)
.fontColor('#1565C0')
.backgroundColor('#E3F2FD')
.borderRadius(12)
.padding({ left: 10, right: 10, top: 5, bottom: 5 })
Text('#私教推荐')
.fontSize(12)
.fontColor('#FF6D00')
.backgroundColor('#FFF3E0')
.borderRadius(12)
.padding({ left: 10, right: 10, top: 5, bottom: 5 })
}
.padding({ left: 12, right: 12 })
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
.padding({ top: 8, bottom: 8 })
ForEach(posts69, (p: PostItem69) => {
Column({ space: 8 }) {
Row({ space: 10 }) {
Text(p.avatar)
.fontSize(28)
Column({ space: 2 }) {
Text(p.name)
.fontSize(13)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
Text(p.date)
.fontSize(9)
.fontColor('#CCCCCC')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Text(p.tag)
.fontSize(9)
.fontColor('#00C853')
.backgroundColor('#E8F5E9')
.borderRadius(3)
.padding({ left: 5, right: 5, top: 1, bottom: 1 })
}
.width('100%')
Text(p.content)
.fontSize(12)
.fontColor('#444444')
.lineHeight(18)
.width('100%')
Row({ space: 8 }) {
Text(p.image)
.fontSize(32)
Text(p.image)
.fontSize(32)
Text(p.image)
.fontSize(32)
}
.width('100%')
Row({ space: 16 }) {
Row({ space: 4 }) {
Text('👍')
.fontSize(14)
Text(p.likes.toString())
.fontSize(11)
.fontColor('#999999')
}
Row({ space: 4 }) {
Text('💬')
.fontSize(14)
Text(p.comments.toString())
.fontSize(11)
.fontColor('#999999')
}
Text('📤 分享')
.fontSize(11)
.fontColor('#999999')
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.width('100%')
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(10)
.margin({ left: 12, right: 12, bottom: 6 })
}, (p: PostItem69) => p.id.toString())
Column()
.height(80)
}
}
.scrollBar(BarState.Off)
.constraintSize({ maxHeight: '100%' })
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
}
// ============ Tab6 我的 ============
@Component
struct ProfileTab69 {
build() {
Column() {
Scroll() {
Column({ space: 0 }) {
// 渐变头部
Column({ space: 8 }) {
Text('🏃')
.fontSize(56)
Text('健身达人')
.fontSize(18)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
Text('连续打卡30天 · 累计运动273小时')
.fontSize(11)
.fontColor('rgba(255,255,255,0.8)')
}
.width('100%')
.padding({ top: 32, bottom: 24 })
.backgroundColor('#00C853')
.alignItems(HorizontalAlign.Center)
// 数据卡片
Row() {
Column({ space: 2 }) {
Text('30')
.fontSize(16)
.fontColor('#00C853')
.fontWeight(FontWeight.Bold)
Text('连续打卡')
.fontSize(9)
.fontColor('#999999')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column({ space: 2 }) {
Text('273')
.fontSize(16)
.fontColor('#FF6D00')
.fontWeight(FontWeight.Bold)
Text('运动小时')
.fontSize(9)
.fontColor('#999999')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column({ space: 2 }) {
Text('12,560')
.fontSize(16)
.fontColor('#7B1FA2')
.fontWeight(FontWeight.Bold)
Text('消耗大卡')
.fontSize(9)
.fontColor('#999999')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column({ space: 2 }) {
Text('4')
.fontSize(16)
.fontColor('#E53935')
.fontWeight(FontWeight.Bold)
Text('报名课程')
.fontSize(9)
.fontColor('#999999')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}
.width('92%')
.padding({ top: 14, bottom: 14 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.margin({ left: 12, right: 12, top: -12 })
// 运动统计柱状图
Text('📊 月度运动时长')
.fontSize(15)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
.width('100%')
.padding({ left: 12, top: 20, bottom: 8 })
Column({ space: 0 }) {
Row({ space: 0 }) {
ForEach(barData69, (bar: BarData69) => {
Column({ space: 4 }) {
Text(bar.value.toString())
.fontSize(8)
.fontColor('#666666')
Column()
.width(16)
.height(bar.value * 3)
.borderRadius({ topLeft: 4, topRight: 4 })
.backgroundColor(bar.color)
Text(bar.label)
.fontSize(8)
.fontColor('#999999')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}, (bar: BarData69) => bar.label)
}
.width('100%')
.height(120)
.alignItems(VerticalAlign.Bottom)
}
.width('92%')
.padding({ top: 12, bottom: 12 })
.backgroundColor('#FFFFFF')
.borderRadius(12)
.margin({ left: 12, right: 12 })
// 成就徽章
Text('🏆 成就徽章')
.fontSize(15)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
.width('100%')
.padding({ left: 12, top: 20, bottom: 8 })
ForEach(achievements69, (a: Achievement69) => {
Row({ space: 12 }) {
Text(a.icon)
.fontSize(32)
Column({ space: 4 }) {
Text(a.label)
.fontSize(13)
.fontColor('#333333')
.fontWeight(FontWeight.Bold)
Text(a.desc)
.fontSize(11)
.fontColor('#666666')
if (a.unlocked) {
Text('✅ 已解锁')
.fontSize(10)
.fontColor('#00C853')
} else {
Text('🔒 未解锁')
.fontSize(10)
.fontColor('#CCCCCC')
}
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
}
.width('100%')
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(10)
.margin({ left: 12, right: 12, bottom: 6 })
.opacity(a.unlocked ? 1.0 : 0.5)
}, (a: Achievement69) => a.label)
Column()
.height(80)
}
}
.scrollBar(BarState.Off)
.constraintSize({ maxHeight: '100%' })
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
}
// ============ 主入口 ============
@Entry
@Component
struct DuoDuoFitnessApp {
@State activeTab: number = 0
@Builder
bottomTabItem(icon: string, label: string, tabIdx: number) {
Column({ space: 3 }) {
Text(icon)
.fontSize(20)
.opacity(this.activeTab === tabIdx ? 1.0 : 0.45)
Text(label)
.fontSize(9)
.fontColor(this.activeTab === tabIdx ? '#00C853' : '#999999')
.fontWeight(this.activeTab === tabIdx ? FontWeight.Bold : FontWeight.Normal)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
.padding({ top: 5, bottom: 5 })
.onClick(() => {
this.activeTab = tabIdx
})
}
build() {
Column() {
Stack() {
if (this.activeTab === 0) {
CourseTab69()
} else if (this.activeTab === 1) {
EquipmentTab()
} else if (this.activeTab === 2) {
CoachTab()
} else if (this.activeTab === 3) {
CheckInTab()
} else if (this.activeTab === 4) {
CommunityTab69()
} else {
ProfileTab69()
}
}
.layoutWeight(1)
Row() {
this.bottomTabItem('📚', '课程', 0)
this.bottomTabItem('🏋️', '器材', 1)
this.bottomTabItem('👨🏫', '私教', 2)
this.bottomTabItem('📊', '打卡', 3)
this.bottomTabItem('💬', '社区', 4)
this.bottomTabItem('👤', '我的', 5)
}
.width('100%')
.backgroundColor('#FFFFFF')
.padding({ top: 4, bottom: 6 })
.shadow({ radius: 8, color: 'rgba(0,200,83,0.12)', offsetY: -2 })
}
.width('100%')
.height('100%')
.backgroundColor('#F5FFF0')
}
}
十五、开发环境搭建与运行指南
15.1 开发环境要求
- DevEco Studio:5.0 Release(或更高版本),下载地址:https://developer.huawei.com/consumer/cn/deveco-studio
- HarmonyOS SDK:API Level 12(对应 ArkTS API 24),通过DevEco Studio的SDK Manager自动下载
- Node.js:16.x及以上版本(DevEco Studio内置,无需单独安装)
- 操作系统:Windows 10/11 64位 或 macOS 11及以上
15.2 项目创建步骤
- 打开DevEco Studio,点击「Create Project」创建新项目。
- 选择「Application」→「Empty Ability」模板,点击「Next」。
- 填写项目信息:Project Name(多多健身社交)、Bundle Name(com.duoduo.fitness)、Save Location(项目保存路径)、Compile SDK(API 12)、Model(Stage),点击「Finish」创建项目。

- 等待项目同步完成(Gradle同步+SDK下载),首次创建可能需要几分钟。

15.3 代码替换与运行
- 在项目左侧文件树中,打开
entry/src/main/ets/pages/Index.ets文件(或新建69.ets文件)。 - 将本文完整的ArkTS代码复制粘贴到该文件中,覆盖原有内容。
- 点击顶部工具栏的「Sync Project」按钮同步项目,确保无编译错误。
- 选择运行目标:可选择「Previewer」(预览器,快速查看UI效果)或连接真实HarmonyOS设备/启动模拟器。

- 点击顶部工具栏的绿色运行按钮「Run ‘entry’」,等待应用编译安装并启动。

- 应用启动后即可在设备/预览器中体验多多健身社交的全部功能,点击底部六个Tab(课程📚/器材🏋️/私教👨🏫/打卡📊/社区💬/我的👤)切换不同模块,点击课程Tab课程卡打开报名课程弹窗,点击打卡Tab🎯目标图标打开编辑健身目标弹窗,点击打卡记录⋮按钮打开删除打卡记录弹窗。

15.4 常见问题排查
- 编译报错「Cannot find name ‘Divider’」:Divider是ArkTS内置组件,无需额外import,如报错请检查SDK版本是否为API 12及以上。
- 预览器不显示中文:DevEco Studio预览器默认支持中文,如显示方框请检查系统字体是否包含中文字体。
- 底部Tab被遮挡:请确保最外层Column设置了
width('100%')+height('100%'),内容区Scroll设置了layoutWeight(1),底部Tab栏不设置layoutWeight。 - 弹窗不显示:弹窗通过
@State showXxx布尔值控制,确保触发点击事件中正确设置了this.showXxx = true,且弹窗使用Stack({ alignContent: Alignment.Bottom/Center })叠加在内容之上。 - TextInput不显示预填值:确保
TextInput({ text: this.goalContent })正确绑定了状态变量,且在打开弹窗前正确设置了this.goalContent = '每天运动30分钟,消耗300大卡'。
十六、总结与展望
本文基于 HarmonyOS ArkTS API 24(API Level 12)完整实现了一款「多多健身社交」应用,通过六个独立 @Component 子组件 + 一个 @Entry 主入口组件构建了六组件多Tab健身社交电商架构,覆盖健身课程、运动器材、精选私教、运动打卡、健身社区、个人中心六大核心模块,完整还原了健身社交从课程→器材→私教→打卡→社区→个人的全链路业务闭环。

16.1 核心成果总结
-
六组件多Tab健身社交电商架构:CourseTab69/EquipmentTab/CoachTab/CheckInTab/CommunityTab69/ProfileTab69六个独立子组件+DuoDuoFitnessApp主入口,每个Tab职责清晰、状态隔离,
@Builder bottomTabItem复用底部Tab栏,是ArkTS声明式UI多Tab应用的标准架构模式。 -
活力绿能量橙双主色与难度三色运动类型八色业务语义体系:以活力绿
#00C853× 能量橙#FF6D00为双主色,底色#F5FFF0,构建了难度三色(初级绿/中级橙/高级红)+运动类型八色+分类chips八色+话题五色的完整业务语义色彩体系,每种业务状态都有独立的颜色+浅底色组合,信息传达高效直观。 -
六大Tab差异化布局:课程Tab(搜索头+分类chips横滑8分类+精选大卡双色系+双列课程网格含难度三色标签)、器材Tab(限时特价横条含倒计时+双列器材网格)、私教Tab(教练详情大卡含认证标签+预约体验/立即预约双按钮)、打卡Tab(运动数据环形图85%+本周三列数据+连续打卡条+打卡记录列表含运动类型八色标签)、社区Tab(五色话题chips横滑+健身动态含三图照片墙)、我的Tab(活力绿渐变头+四列数据悬浮卡+月度运动绿色系渐变柱状图+成就徽章解锁状态),每个Tab都有独特的视觉核心和布局特色,避免千篇一律。
-
四模态弹窗双样式:报名课程底部抽屉(4时段选择+6教练选择+实付金额)、取消报名居中确认框、编辑健身目标底部抽屉(TextInput预填目标)、删除打卡记录居中确认框,采用底部抽屉(复杂表单类)+居中确认框(危险操作确认类)双样式,顶部拖拽条+双按钮设计规范统一。
-
丰富的数据可视化:打卡Tab运动数据环形图(圆形+4px活力绿边框模拟环形进度条85%)、我的Tab月度运动时长绿色系渐变柱状图(3-8月从浅绿#A5D6A7到深活力绿#00C853,8月30小时最高最深),通过纯声明式Column+背景色+圆形边框+动态height实现,无需Canvas,简洁高效。
-
8个interface强类型定义与8组静态配置数据:FitnessCourse(含level三字段难度独立色+calories消耗大卡)、Coach(含cert认证标签+specialty专长+bio简介)、CheckInRecord(含type四字段运动类型独立色+typeIcon图标)、Achievement69(含unlocked驱动opacity),10课程+8器材+6教练+7打卡记录+5社区动态+8分类+6月柱状图+5成就徽章,数据丰富且贴合真实健身社交业务。
16.2 未来扩展方向
-
可穿戴设备数据同步:当前打卡数据为静态展示,未来可对接华为运动健康Health Kit,同步华为手表/手环的实时心率、步数、卡路里、睡眠数据,实现运动数据自动记录和同步,无需手动打卡。
-
AI私教指导:当前私教为静态教练列表,未来可引入AI大模型能力,根据用户的身体数据(身高/体重/体脂/运动基础)和健身目标(减脂/增肌/塑形/康复),AI自动生成个性化训练计划和饮食建议,并在运动过程中提供实时语音指导。
-
AR运动姿势矫正:利用HarmonyOS的AR能力和摄像头,实时识别用户的运动姿势(如深蹲、硬拉、俯卧撑),AI分析关节角度和动作标准度,实时反馈姿势矫正建议,降低运动损伤风险。
-
社区直播跟练:当前社区为图文动态,未来可增加健身直播功能,教练实时直播带练,用户在线跟练并实时互动(弹幕、点赞、送礼物),直播结束后生成回放和运动数据报告。
-
健身挑战赛事:引入线上健身挑战赛事(如30天减脂挑战、百日跑挑战、平板支撑大赛),用户报名参与,系统自动记录运动数据并排名,完赛用户获得成就徽章和奖励(优惠券、免费课程、实体奖牌),增强用户粘性和运动动力。
-
器材智能联动:对接智能健身器材(智能跑步机、智能动感单车、智能哑铃),通过蓝牙或WiFi实时获取器材数据(速度、阻力、重量、次数),自动记录运动数据并同步到打卡Tab,实现「器材-数据-社区」的智能联动闭环。
多多健身社交作为一款基于HarmonyOS ArkTS API 24的健身社交全链路服务平台,通过六组件多Tab架构、活力绿能量橙双主色色彩体系、六大Tab差异化布局、四模态弹窗、丰富的数据可视化和完整的业务数据模型,完整还原了健身社交从课程到个人的全链路体验,为HarmonyOS生态下的健身社交应用开发提供了可参考的完整实现方案。
更多推荐



所有评论(0)