本文基于 HarmonyOS NEXT(API 24)ArkTS 声明式 UI 框架,完整拆解一个亲子母婴电商社区应用「多多亲子母婴」的源码实现。应用以亲子粉 #EC407A 为主色、婴幼蓝 #42A5F5 为强调色、底色 #FFF5F8 温馨亲子风,采用六组件多 Tab 电商架构(MilkTab 奶粉 / ToyTab 玩具 / CourseTab 课程 / CommunityTab63 社区 / OrderTab63 订单 / ProfileTab63 我的 六个独立 @Component 子组件 + DuoDuoQinZiApp 主入口),每个 Tab 拥有独立的渐变头与差异化布局(奶粉=分类横滑+单列段位大卡+双列网格+我的收藏;玩具=年龄段 chips+三列网格;课程=早教列表+免费试听;社区=宝妈动态+育儿图片+点赞评论;订单=状态分段器+列表+取消订单/编辑地址;我的=渐变头+三列统计+宝宝成长档案+月度消费柱状图+优惠券),配套 4 种模态弹窗(加入购物车底部抽屉规格+数量 / 取消订单居中 / 编辑收货地址底部抽屉 / 删除收藏居中可移除),覆盖从奶粉玩具选购、早教课程报名、宝妈社区交流、订单管理到个人中心的完整亲子母婴电商闭环。

多多亲子母婴整体运行效果

一、引言:妈咪放心选 · 宝宝健康成长

母婴电商是移动端最温情的赛道之一——一罐奶粉的配方决定了宝宝的营养吸收,一款玩具的材质关系到宝宝的安全健康,一门早教课程影响着宝宝的成长发育,一条宝妈动态承载着新手妈妈的焦虑与喜悦。「多多亲子母婴」正是为这群新手爸妈打造的一站式亲子母婴电商社区应用:用户可以在奶粉 Tab 按分类浏览 8 款奶粉(飞鹤星飞帆/爱他美卓萃/君乐宝乐铂/美素佳儿皇家/a2 至初/金领冠珍护/惠氏启赋/贝因美爱加)、在玩具 Tab 按年龄段筛选 9 款益智玩具(乐高得宝/费雪脚踏钢琴/遥控挖掘机/磁力片/木质拼图/儿童电子琴/变形金刚/芭比娃娃/水彩画板)、在课程 Tab 报名 6 门早教课程(蒙特梭利感统/金宝贝音乐/东方爱婴全脑/美吉姆艺术/小小运动馆/乐高启蒙机械)、在社区 Tab 浏览 5 条宝妈动态(育儿分享/好物推荐/早教心得/奶粉选择/辅食交流)、在订单 Tab 管理 6 笔订单(待发货/已发货/已完成/已退款)、在我的 Tab 查看宝宝成长档案(身高 88cm/体重 12.5kg/乳牙 20 颗/发育 A+)与月度消费统计。

应用的视觉语言高度统一:亲子粉 #EC407A 代表妈妈的温柔与关爱,婴幼蓝 #42A5F5 代表宝宝的纯净与健康,底色 #FFF5F8 营造温馨柔和的亲子氛围。六个 Tab 各自拥有独立的渐变头——奶粉 Tab 粉蓝渐变 #EC407A→#42A5F5(angle:135)、玩具 Tab 蓝粉渐变 #42A5F5→#EC407A(angle:180)、课程 Tab 紫粉渐变 #AB47BC→#EC407A(angle:135)、社区 Tab 粉紫渐变 #EC407A→#AB47BC(angle:135)、订单 Tab 深蓝渐变 #42A5F5→#1565C0(angle:180)、我的 Tab 粉蓝渐变 #EC407A→#42A5F5(angle:180)——每个 Tab 通过渐变头的色彩组合快速区分模块,同时保持粉蓝主色调的统一。

与之前的单文件单组件应用不同,本应用采用六组件多 Tab 电商架构——六个独立的 @Component 子组件(MilkTab/ToyTab/CourseTab/CommunityTab63/OrderTab63/ProfileTab63)各自管理自己的状态与弹窗,主入口 DuoDuoQinZiApp 通过 @State activeTab 切换显示哪个子组件。这种多组件架构让每个 Tab 的代码独立、职责清晰、可复用性强,是中大型 ArkTS 应用的标准架构模式。我的 Tab 内嵌月度消费柱状图(6 个月 SPEND_BARS_63,粉系渐变色从浅 #F8BBD0 到深 #C2185B,8 月消费 ¥2380 最高形成「育儿支出逐月攀升」可视化),是本应用最具特色的数据可视化。

本文将从架构概览、色彩系统、数据模型、六组件多 Tab 电商架构、6 大 Tab 差异化布局、4 种模态弹窗、特性对比、完整代码、开发环境搭建等维度,对源码 63.ets 进行逐模块深度解析。

二、架构概览:六组件多 Tab 电商架构 + 主入口分发

整个应用采用 ArkTS 声明式 UI 范式,多组件架构——六个独立的 @Component 子组件 + 一个 @Entry 主入口组件,通过主入口的 @State activeTab 状态切换显示哪个子组件。架构上可分为以下几层:

  • 类型定义层MilkPowder(奶粉 12 字段)、ToyItem(玩具 11 字段含独立 color/bg)、CourseItem(课程 9 字段含 trialAvail 免费试听)、MomPost(宝妈动态 11 字段含 images 图片数组)、OrderItem63(订单 9 字段含 statusColor 状态色)、CatMeta63(分类元数据 4 字段)、BarData63(柱状图数据 3 字段)、AgeChip(年龄段标签 2 字段),共 8 个 interface。
  • 静态配置层CAT_63(8 个分类:奶粉/洗护/服饰/出行/辅食/寝具/营养/绘本,每个含独立 icon/color/bg)、milkPowders(8 款奶粉)、toys(9 款玩具每款独立 color/bg)、courses(6 门课程)、momPosts(5 条宝妈动态)、orders63(6 笔订单)、AGE_CHIPS(5 个年龄段标签)、SPEND_BARS_63(6 个月消费柱状图数据粉系渐变色)、ORDER_SEG_63(5 个订单状态分段器)。
  • 子组件层MilkTab(奶粉 Tab,含加入购物车底部抽屉+删除收藏居中弹窗)、ToyTab(玩具 Tab,含年龄段 chips 筛选)、CourseTab(课程 Tab,含免费试听/立即报名)、CommunityTab63(社区 Tab,含宝妈动态+图片+点赞评论)、OrderTab63(订单 Tab,含状态分段器+取消订单居中弹窗+编辑地址底部抽屉)、ProfileTab63(我的 Tab,含渐变头+宝宝档案+月度消费柱状图+优惠券),共 6 个独立 @Component 子组件。
  • 主入口层DuoDuoQinZiApp@Entry 主组件,@State activeTab 控制当前 Tab,@Builder bottomTabItem 构建底部 Tab 项,build() 中通过 if/else if 分发显示对应子组件,底部 6 Tab 导航栏带粉系阴影)。

多多亲子母婴架构与奶粉Tab运行效果

主入口的 build() 方法以 Column 为根容器,上层是 Stack 包裹的子组件内容区(layoutWeight(1) 占满剩余空间),下层是 Row 布局的底部 6 Tab 导航栏。子组件内容区通过 if (this.activeTab === 0) { MilkTab() } 等条件渲染切换,每个子组件独立管理自己的 @State 与弹窗,互不干扰。底部 Tab 导航栏使用纯白底 + 粉系阴影 shadow({ radius: 8, color: 'rgba(236,64,122,0.12)', offsetY: -2 }),激活态亲子粉 #EC407A 字粗体 + 图标 opacity 1.0,非激活态灰字 + 图标 opacity 0.45,通过 @Builder bottomTabItem(icon, label, tabIdx) 复用构建。

这种六组件多 Tab 电商架构是中大型 ArkTS 应用的标准模式——每个子组件独立管理状态与弹窗,代码职责清晰、可复用性强、便于团队协作开发,主入口只负责 Tab 切换与分发,不涉及具体业务逻辑。

三、色彩系统:亲子粉主色 + 婴幼蓝强调 + 温馨亲子风

应用的配色通过 8 个 interface 中的 CatMeta63(分类元数据含 color/bg)和各数据模型中的 color 字段约束,由静态配置统一提供,核心色值如下:

色值名 HEX 用途
亲子粉 #EC407A 主色,按钮、标签、激活文字、价格、柱状图深色
婴幼蓝 #42A5F5 强调色,玩具 Tab 渐变头、订单 Tab 渐变头、统计数字
温馨底色 #FFF5F8 页面底色,温馨亲子风核心
纯白 #FFFFFF 卡片背景、底部导航栏
深紫 #AB47BC 课程 Tab 渐变头、社区 Tab 渐变头、课程标签
深蓝 #1565C0 订单 Tab 渐变头终点
价格红 #E53935 商品价格、原价删除线对比
金色 #FFB300 评分星号、发育等级
#999999/#666666/#333333 次级文字/辅助文字/主文字

除了这 10 个基础色,应用还在多个场景中引入了分类独立色与粉系渐变色以丰富视觉层次:

  • 分类独立色CAT_63 8 个分类各自拥有独立的 color/bg——奶粉 #EC407A/#FCE4EC、洗护 #42A5F5/#E3F2FD、服饰 #66BB6A/#E8F5E9、出行 #FFA726/#FFF3E0、辅食 #AB47BC/#F3E5F5、寝具 #26A69A/#E0F2F1、营养 #EF5350/#FFEBEE、绘本 #5C6BC0/#E8EAF6。分类横滑中每个分类卡片使用独立的 bg 底色 + color 文字色,用颜色直观区分母婴品类。
  • 玩具独立色toys 9 款玩具各自拥有独立的 color/bg——乐高得宝 #F44336/#FFEBEE、费雪脚踏钢琴 #42A5F5/#E3F2FD、遥控挖掘机 #FFA726/#FFF3E0、磁力片 #66BB6A/#E8F5E9、木质拼图 #AB47BC/#F3E5F5、儿童电子琴 #26A69A/#E0F2F1、变形金刚 #5C6BC0/#E8EAF6、芭比娃娃 #EC407A/#FCE4EC、水彩画板 #FF7043/#FBE9E7。三列网格中每款玩具的图标区使用独立 bg 底色 + 年龄段文字使用独立 color + 标签使用独立 color 底白字,9 款玩具 9 种色彩,营造缤纷童趣的玩具商城氛围。
  • 粉系渐变柱状图SPEND_BARS_63 6 个月消费数据各自拥有独立的粉系渐变色——3 月 #F8BBD0 最浅、4 月 #F48FB1、5 月 #EC407A、6 月 #E91E63、7 月 #D81B60、8 月 #C2185B 最深。月度消费柱状图中每根柱子使用对应月份的粉系渐变色,从浅到深形成「育儿支出逐月攀升」的可视化效果,8 月消费 ¥2380 最高柱子最深。
  • 订单状态色orders63 6 笔订单各自拥有独立的 statusColor——待发货 #FF8F00 橙、已发货 #42A5F5 蓝、已完成 #66BB6A 绿、已退款 #999999 灰。订单列表中状态标签使用 statusColor 文字 + 浅灰底,用颜色直观区分订单状态。

多多亲子母婴色彩系统与玩具Tab运行效果

渐变的运用是本应用色彩表达的重点——六个 Tab 各自拥有独立的渐变头

  • 奶粉 Tab:angle:135 粉蓝渐变 #EC407A→#42A5F5(从亲子粉到婴幼蓝,温馨母婴主色调)
  • 玩具 Tab:angle:180 蓝粉渐变 #42A5F5→#EC407A(从婴幼蓝到亲子粉,垂直渐变童趣风)
  • 课程 Tab:angle:135 紫粉渐变 #AB47BC→#EC407A(从深紫到亲子粉,早教专业感)
  • 社区 Tab:angle:135 粉紫渐变 #EC407A→#AB47BC(从亲子粉到深紫,宝妈交流温馨感)
  • 订单 Tab:angle:180 深蓝渐变 #42A5F5→#1565C0(从婴幼蓝到深蓝,订单管理专业感)
  • 我的 Tab:angle:180 粉蓝渐变 #EC407A→#42A5F5(从亲子粉到婴幼蓝,个人中心温馨感)

每个 Tab 通过渐变头的色彩组合快速区分模块,同时保持粉蓝主色调的统一,是本应用最具特色的色彩设计。

四、数据模型设计:8 个 interface 强类型约束

应用定义了 8 个 interface,覆盖奶粉、玩具、课程、宝妈动态、订单、分类元数据、柱状图数据、年龄段标签八大领域:

4.1 MilkPowder 奶粉模型(12 字段)

interface MilkPowder {
  id: number
  name: string
  brand: string
  icon: string
  stage: string
  origin: string
  price: number
  originalPrice: number
  formula: string
  tag: string
  sold: string
  rating: number
}

奶粉模型 12 字段覆盖了母婴电商奶粉商品的完整信息:id 唯一标识、name 商品名(含段位如飞鹤星飞帆卓睿3段)、brand 品牌、icon 图标、stage 段位(如3段(12-36月))、origin 产地(如中国黑龙江/德国进口)、price 现价、originalPrice 原价(删除线显示)、formula 配方(如OPO+乳铁蛋白/A2β-酪蛋白)、tag 标签(热销/进口/国产/推荐/高端)、sold 已售数量(如已售5.6万)、rating 评分。

stage 段位、origin 产地、formula 配方是奶粉模型的特色字段——段位直接关系到宝宝的年龄段适配,产地影响妈妈对奶源的信任度,配方是奶粉选购的核心决策因素(OPO结构脂/乳铁蛋白/A2β-酪蛋白/天然乳脂/益生元等)。milkPowders 常量预置了 8 款奶粉,从国产飞鹤星飞帆(¥268)到进口 a2 至初白金(¥388),覆盖不同价位与配方,每款的 stage/origin/formula/tag/sold/rating 都精心设计以匹配真实母婴电商奶粉商品。

4.2 ToyItem 玩具模型(11 字段含独立 color/bg)

interface ToyItem {
  id: number
  name: string
  icon: string
  age: string
  category: string
  price: number
  originalPrice: number
  tag: string
  sold: string
  color: string
  bg: string
}

玩具模型 11 字段,colorbg 是特色字段——每款玩具拥有独立的主题色和背景色,三列网格中图标区使用 bg 底色、年龄段文字使用 color、标签使用 color 底白字,9 款玩具 9 种色彩营造缤纷童趣。age 年龄段(如1.5-5岁/0-1岁/3-8岁)配合 AGE_CHIPS 年龄段 chips 筛选,category 分类(积木/早教/遥控/益智/认知/音乐/模型/娃娃/美术)。toys 常量预置了 9 款玩具,从乐高得宝大颗粒(¥168)到水彩画板套装(¥68),覆盖不同年龄段与品类,每款的 color/bg 独立设计。

4.3 CourseItem 课程模型(9 字段含 trialAvail 免费试听)

interface CourseItem {
  id: number
  name: string
  icon: string
  ageRange: string
  lessons: number
  method: string
  price: number
  teacher: string
  rating: number
  trialAvail: boolean
}

课程模型 9 字段,trialAvail 是特色字段——布尔值标记是否支持免费试听,课程列表中 if (c.trialAvail) 条件渲染「免费试听」按钮(紫底白字),不支持的课程只显示「立即报名」按钮(粉底白字)。ageRange 适龄范围(如6个月-3岁)、lessons 课时数、method 授课方式(一对一/小班6人/小班8人/小班10人)、teacher 授课老师、rating 评分。courses 常量预置了 6 门课程,从蒙特梭利感统训练(¥2880/24课时/一对一)到乐高启蒙机械(¥2680/24课时/小班6人),其中 5 门支持免费试听、1 门(美吉姆艺术创意)不支持。

4.4 MomPost 宝妈动态模型(11 字段含 images 图片数组)

interface MomPost {
  id: number
  avatar: string
  name: string
  babyAge: string
  content: string
  images: string[]
  likes: number
  comments: number
  date: string
  topic: string
}

宝妈动态模型 11 字段,images 是特色字段——字符串数组存储动态配图(Emoji 图标),社区 Tab 中通过 ForEach(p.images) 渲染图片行(每张 70x70 圆角 8 灰底),支持 1-3 张配图。babyAge 宝宝年龄(如宝宝2岁3月/宝宝8个月)、topic 话题标签(育儿分享/好物推荐/早教心得/奶粉选择/辅食交流,紫底白字)、likes 点赞数、comments 评论数、date 日期。momPosts 常量预置了 5 条宝妈动态,从果果妈「宝宝第一次自己用勺子吃饭」到米米妈「开始加辅食了」,每条都有真实的育儿场景与配图,营造温馨的宝妈社区氛围。

4.5 OrderItem63 订单模型(9 字段含 statusColor 状态色)

interface OrderItem63 {
  id: number
  icon: string
  title: string
  brand: string
  amount: number
  status: string
  statusColor: string
  date: string
}

订单模型 9 字段,statusColor 是特色字段——每笔订单拥有独立的状态色,订单列表中状态标签使用 statusColor 文字 + 浅灰底,用颜色直观区分订单状态(待发货橙/已发货蓝/已完成绿/已退款灰)。status 状态文字配合 ORDER_SEG_63 分段器筛选,amount 订单金额、date 下单日期。orders63 常量预置了 6 笔订单,从待发货的飞鹤星飞帆(¥268)到已退款的爱他美卓萃(¥358),覆盖全部 5 种订单状态。

4.6 CatMeta63、BarData63、AgeChip 辅助模型

interface CatMeta63 {
  icon: string
  label: string
  color: string
  bg: string
}

interface BarData63 {
  label: string
  value: number
  color: string
}

interface AgeChip {
  label: string
  range: string
}

CatMeta63 分类元数据 4 字段,CAT_63 预置 8 个母婴分类(奶粉/洗护/服饰/出行/辅食/寝具/营养/绘本),每个含独立 icon/color/bg,用于奶粉 Tab 顶部分类横滑。BarData63 柱状图数据 3 字段,SPEND_BARS_63 预置 6 个月消费数据(3-8月),每个含 label 月份/value 金额/color 粉系渐变色,用于我的 Tab 月度消费柱状图。AgeChip 年龄段标签 2 字段,AGE_CHIPS 预置 5 个年龄段(全部/0-1岁/1-3岁/3-6岁/6-12岁),用于玩具 Tab 年龄段 chips 筛选。

五、六组件多 Tab 电商架构:6 个独立子组件 + 主入口分发

本应用采用六组件多 Tab 电商架构,是与之前单文件单组件应用最大的架构差异。六个独立的 @Component 子组件各自管理自己的 @State 与弹窗,主入口 DuoDuoQinZiApp 通过 @State activeTab 切换显示哪个子组件。

5.1 主入口 DuoDuoQinZiApp:Tab 分发 + 底部导航

@Entry
@Component
struct DuoDuoQinZiApp {
  @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 ? '#EC407A' : '#999999')
        .fontWeight(this.activeTab === tabIdx ? FontWeight.Bold : FontWeight.Normal)
    }
    .layoutWeight(1).alignItems(HorizontalAlign.Center)
    .padding({ top: 5, bottom: 6 })
    .onClick(() => { this.activeTab = tabIdx })
  }

  build() {
    Column() {
      Stack() {
        if (this.activeTab === 0) { MilkTab() }
        else if (this.activeTab === 1) { ToyTab() }
        else if (this.activeTab === 2) { CourseTab() }
        else if (this.activeTab === 3) { CommunityTab63() }
        else if (this.activeTab === 4) { OrderTab63() }
        else { ProfileTab63() }
      }.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(236,64,122,0.12)', offsetY: -2 })
    }
    .width('100%').height('100%').backgroundColor('#FFF5F8')
  }
}

主入口是整个应用的调度中心:

  • @State activeTab: number = 0:当前激活的 Tab 索引,默认 0(奶粉)。
  • @Builder bottomTabItem(icon, label, tabIdx):可复用的底部 Tab 项构建器,接收图标、文字、Tab 索引三个参数,激活态亲子粉字粗体 + 图标 opacity 1.0,非激活态灰字 + 图标 opacity 0.45,点击切换 activeTab
  • build()Column 根容器,上层 Stack 包裹子组件内容区(通过 if/else if 条件渲染分发显示对应子组件,layoutWeight(1) 占满剩余空间),下层 Row 布局的底部 6 Tab 导航栏(纯白底 + 粉系阴影 shadow({ radius: 8, color: 'rgba(236,64,122,0.12)', offsetY: -2 }),通过 this.bottomTabItem() 复用构建 6 个 Tab 项)。
  • 根容器底色 #FFF5F8 温馨亲子风。

主入口只负责 Tab 切换与分发,不涉及具体业务逻辑,所有业务逻辑(数据渲染、弹窗、状态管理)都封装在各自的子组件中,这是多组件架构的核心优势——职责分离、代码独立、可复用性强、便于团队协作

5.2 六个子组件的状态与弹窗分布

子组件 @State 变量 弹窗 特色功能
MilkTab showCart/showDelete/delId/cartNum/selectedSpec/favList 加入购物车底部抽屉 + 删除收藏居中 分类横滑+段位大卡+双列网格+我的收藏
ToyTab selectedAge 年龄段 chips 筛选+三列网格(9款独立色)
CourseTab 早教列表+免费试听/立即报名
CommunityTab63 宝妈动态+图片数组+点赞评论分享
OrderTab63 segIdx/showCancel/cancelId/showEditAddr/addrVal/orderList 取消订单居中 + 编辑地址底部抽屉 状态分段器+订单列表+取消/编辑地址
ProfileTab63 渐变头+三列统计+宝宝档案+月度消费柱状图+优惠券

六个子组件中,MilkTab 和 OrderTab63 拥有弹窗(各 2 个),ToyTab 拥有筛选状态,CourseTab/CommunityTab63/ProfileTab63 为纯展示组件。每个子组件独立管理自己的状态,互不干扰,切换 Tab 时子组件的状态会保留(因为 ArkTS 的 if/else if 条件渲染在切换时不会销毁已创建的组件实例,除非使用 if 的显式销毁)。

六、奶粉 Tab:分类横滑 + 单列段位大卡 + 双列网格 + 我的收藏

奶粉 Tab MilkTab 是应用的默认首页,由粉蓝渐变头 + 分类横滑 + 热销奶粉段位大卡 + 全部奶粉双列网格 + 我的收藏列表五部分组成,配套加入购物车底部抽屉弹窗和删除收藏居中弹窗。

6.1 粉蓝渐变头 + 分类横滑

  • 粉蓝渐变头angle:135 粉蓝渐变 #EC407A→#42A5F5(从亲子粉到婴幼蓝),「🍼 多多亲子」18sp 粗体白字 + 「妈咪放心选 · 宝宝健康成长」11sp 半透明白字,居中对齐 padding top/bottom 20。
  • 分类横滑Scroll horizontal 横向滚动,CAT_63 8 个分类(奶粉/洗护/服饰/出行/辅食/寝具/营养/绘本),每个分类卡片 width:64 padding top/bottom 10,使用独立的 bg 底色 + color 文字色(如奶粉 #FCE4EC#EC407A 字、洗护 #E3F2FD#42A5F5 字),icon 22sp + label 9sp,圆角 10 居中对齐。分类横滑是母婴电商的标准导航模式,8 个分类覆盖母婴全品类。

6.2 热销奶粉段位大卡(第 1 款 milkPowders[0])

「🔥 热销奶粉」标题 + 「正品保障」亲子粉标签(SpaceBetween 两端对齐),下方是 milkPowders[0](飞鹤星飞帆卓睿3段)的单列段位大卡:

  • 左侧:🍼 图标 48sp,width/height 90,#FCE4EC 浅粉底圆角 12 居中。
  • 右侧 Column:
    • 商品名 14sp 粗体深字(layoutWeight 1)+ 「热销」标签(亲子粉底白字圆角 3)。
    • 段位·产地 11sp 灰字(3段(12-36月) · 中国黑龙江)。
    • 配方 11sp 深灰字(配方:OPO+乳铁蛋白)。
    • 评分 11sp 金色粗体(★4.9)+ 已售 10sp 灰字(已售5.6万)。
    • 价格行:现价 ¥268 18sp 价格红粗体 + 原价 ¥328 11sp 灰字删除线 + 「加入」按钮(亲子粉底白字 Capsule height 28,点击打开加入购物车弹窗)。

段位大卡纯白底圆角 12 padding 12 margin 左右 12 下 8,是奶粉 Tab 的核心商品展示区,突出热销奶粉的完整信息(段位/产地/配方/评分/已售/价格/加入购物车)。

6.3 全部奶粉双列网格(7 款 milkPowders.slice(1,8))

「🥛 全部奶粉」标题,下方是 Row 布局的双列网格(实际通过 ForEach(milkPowders.slice(1,8)) 在一个 Row 中渲染 7 款,每款 layoutWeight(1) 等宽),每款卡片:

  • 🍼 图标 36sp,width 100% height 80,#FCE4EC 浅粉底圆角 8 居中。
  • 商品名 10sp 深字(maxLines 1 省略号)。
  • 段位 8sp 灰字。
  • 价格行:现价 ¥N 11sp 价格红粗体 + 原价 ¥N 8sp 灰字删除线。

双列网格纯白底圆角 8 padding 6 margin 左右 3 下 6,居中对齐,紧凑展示 7 款奶粉的核心信息(图标/名称/段位/价格),适合快速浏览对比。

6.4 我的收藏列表(favList,可删除)

「❤️ 我的收藏」标题,下方是 ForEach(this.favList) 渲染收藏列表(初始 milkPowders.slice(0,4) 前 4 款),每行:

  • 🍼 图标 32sp,width/height 56,#FCE4EC 浅粉底圆角 8 居中。
  • 商品名 12sp 粗体深字(maxLines 1)+ 段位·¥价格 10sp 灰字。
  • ❤️ 图标 16sp 亲子粉(点击设置 delId + 打开删除收藏弹窗)。

收藏列表纯白底圆角 8 padding 8 margin 左右 12 下 4,垂直居中对齐。点击 ❤️ 图标打开删除收藏居中弹窗,确认后通过 this.favList = this.favList.filter((m) => m.id !== this.delId) 从收藏列表中移除,实现可移除的收藏管理

奶粉 Tab 通过分类横滑 + 段位大卡 + 双列网格 + 我的收藏四段式布局,完整展示了母婴电商奶粉频道的商品浏览与收藏管理功能,是亲子母婴电商的核心频道。

七、玩具 Tab:年龄段 chips 筛选 + 三列网格(9 款独立色)

玩具 Tab ToyTab 由蓝粉渐变头 + 年龄段 chips 筛选 + 精选玩具三列网格三部分组成,9 款玩具各自拥有独立的 color/bg 营造缤纷童趣。

7.1 蓝粉渐变头 + 年龄段 chips 筛选

  • 蓝粉渐变头angle:180 蓝粉渐变 #42A5F5→#EC407A(从婴幼蓝到亲子粉,垂直渐变),「🧸 玩具商城」16sp 粗体白字 + 「益智成长 · 安全材质」11sp 半透明白字,居中对齐 padding top/bottom 14。
  • 年龄段 chips 筛选Scroll horizontal 横向滚动,AGE_CHIPS 5 个年龄段标签(全部/0-1岁/1-3岁/3-6岁/6-12岁),每个 chip 文字 12sp,激活态婴幼蓝 #42A5F5 底白字粗体圆角 16,非激活态 #F5F5F5 底灰字圆角 16,padding 左右 14 上下 6,点击切换 selectedAge。年龄段 chips 是玩具电商的标准筛选模式,按宝宝年龄段快速筛选适合的玩具。

7.2 精选玩具三列网格(9 款 toys,每款独立 color/bg)

「🎯 精选玩具」标题,下方是 Row 布局的三列网格(ForEach(toys) 渲染 9 款,每款 layoutWeight(1) 等宽,自动换行形成三列),每款卡片:

  • 图标区:icon 32sp,width 100% height 70,使用独立的 bg 底色(如乐高得宝 #FFEBEE 浅红底、费雪脚踏钢琴 #E3F2FD 浅蓝底、遥控挖掘机 #FFF3E0 浅橙底等),圆角 8 居中。
  • 商品名 10sp 深字(maxLines 1 省略号)。
  • 年龄段 8sp,使用独立的 color 文字色(如乐高得宝 #F44336 红字、费雪脚踏钢琴 #42A5F5 蓝字等)。
  • 价格行:现价 ¥N 11sp 价格红粗体 + 原价 ¥N 8sp 灰字删除线。
  • 标签:tag 8sp 白字,使用独立的 color 底色(如乐高得宝 #F44336 红底、费雪脚踏钢琴 #42A5F5 蓝底等),圆角 3 padding 左右 4。

三列网格纯白底圆角 8 padding 6 margin 左右 3 下 6,居中对齐。9 款玩具 9 种色彩——每款的图标区 bg、年龄段 color、标签 color 都独立设计,从红/蓝/橙/绿/紫/青/靛/粉/橙红,营造缤纷童趣的玩具商城氛围,是本应用最具视觉特色的布局。

玩具 Tab 通过年龄段 chips 筛选 + 三列网格(9 款独立色)的布局,完整展示了母婴电商玩具频道的商品筛选与浏览功能,缤纷童趣的色彩设计符合玩具商城的定位。

八、课程 Tab:早教列表 + 免费试听/立即报名

课程 Tab CourseTab 由紫粉渐变头 + 课程列表两部分组成,6 门早教课程每门含免费试听(条件渲染)/立即报名按钮。

8.1 紫粉渐变头 + 课程列表

  • 紫粉渐变头angle:135 紫粉渐变 #AB47BC→#EC407A(从深紫到亲子粉),「🎓 早教课程」16sp 粗体白字 + 「科学育儿 · 快乐成长」11sp 半透明白字,居中对齐 padding top/bottom 14。
  • 课程列表:「📋 课程列表」标题,下方 ForEach(courses) 渲染 6 门课程,每门课程卡片:
    • 顶部行:icon 36sp,width/height 56,#F3E5F5 浅紫底圆角 12 居中;右侧 Column:课程名 13sp 粗体深字(maxLines 1)+ 适龄范围标签(#AB47BC 紫字浅紫底圆角 3)+ 课时数 + 授课方式(9sp 灰字)+ 评分(金色粗体)+ 授课老师(10sp 深灰字)。
    • 底部行:价格 ¥N 18sp 价格红粗体;if (c.trialAvail) 条件渲染「免费试听」按钮(#F3E5F5 浅紫底紫字 Capsule height 30);「立即报名」按钮(亲子粉底白字 Capsule height 30)。

课程卡片纯白底圆角 10 padding 10 margin 左右 12 下 6。免费试听按钮是条件渲染的——trialAvail: true 的课程(5 门)显示「免费试听」+「立即报名」双按钮,trialAvail: false 的课程(美吉姆艺术创意 1 门)只显示「立即报名」单按钮,通过 if (c.trialAvail) 实现差异化渲染。

课程 Tab 通过紫粉渐变头 + 早教列表(免费试听条件渲染)的布局,完整展示了母婴电商早教课程频道的课程浏览与报名功能,紫粉渐变头营造早教专业感,免费试听按钮降低了家长的试课门槛。

多多亲子母婴课程Tab与社区Tab运行效果

九、社区 Tab:宝妈动态 + 育儿图片 + 点赞评论分享

社区 Tab CommunityTab63 由粉紫渐变头 + 宝妈动态列表两部分组成,5 条宝妈动态每条含头像/宝宝年龄/内容/图片数组/点赞评论分享。

9.1 粉紫渐变头 + 宝妈动态列表

  • 粉紫渐变头angle:135 粉紫渐变 #EC407A→#AB47BC(从亲子粉到深紫),「💬 宝妈社区」16sp 粗体白字 + 「分享育儿日常 · 交流经验」11sp 半透明白字,居中对齐 padding top/bottom 14。
  • 宝妈动态列表:「👩 宝妈动态」标题,下方 ForEach(momPosts) 渲染 5 条动态,每条动态卡片:
    • 顶部行:avatar 28sp,width/height 40,#FCE4EC 浅粉底圆角 20(圆形头像)居中;右侧 Column:用户名 13sp 粗体深字 + 宝宝年龄(亲子粉 10sp)+ 日期(灰字 10sp);话题标签(#AB47BC 紫字浅紫底圆角 3 padding 左右 6 上下 2,如育儿分享/好物推荐/早教心得/奶粉选择/辅食交流)。
    • 内容:content 12sp 深灰字 lineHeight 18,width 100%。
    • 图片行:ForEach(p.images) 渲染图片数组(1-3 张 Emoji 配图),每张 32sp,width/height 70,#F5F5F5 灰底圆角 8 居中,space 6。
    • 底部行(SpaceBetween 两端对齐):👍 点赞(icon 14sp + 点赞数 11sp 灰字)+ 💬 评论(icon 14sp + 评论数 11sp 灰字)+ 📤 分享(11sp 灰字)。

动态卡片纯白底圆角 10 padding 12 margin 左右 12 下 6。图片数组是特色字段——images: string[] 存储 1-3 张 Emoji 配图,通过 ForEach(p.images) 渲染图片行,支持多条动态配图,营造真实的宝妈社区动态氛围。5 条动态覆盖育儿分享/好物推荐/早教心得/奶粉选择/辅食交流 5 个话题,每条都有真实的育儿场景(宝宝第一次自己用勺子吃饭/出牙期牙胶推荐/感统训练课效果/换奶粉选择/开始加辅食),营造温馨的宝妈社区交流氛围。

社区 Tab 通过粉紫渐变头 + 宝妈动态列表(图片数组+点赞评论分享)的布局,完整展示了母婴电商社区频道的宝妈动态浏览与互动功能,粉紫渐变头营造宝妈交流温馨感,是亲子母婴电商的社区核心频道。

十、订单 Tab:状态分段器 + 订单列表 + 取消订单/编辑地址

订单 Tab OrderTab63 由深蓝渐变头 + 状态分段器 + 订单列表三部分组成,配套取消订单居中弹窗和编辑收货地址底部抽屉弹窗,6 笔订单覆盖全部 5 种状态。

10.1 深蓝渐变头 + 状态分段器

  • 深蓝渐变头angle:180 深蓝渐变 #42A5F5→#1565C0(从婴幼蓝到深蓝),「📦 我的订单」16sp 粗体白字 + 「共 N 笔订单」11sp 半透明白字(动态显示 this.orderList.length),居中对齐 padding top/bottom 14。
  • 状态分段器Row 布局,ORDER_SEG_63 5 个状态(全部/待发货/已发货/已完成/已退款),每个状态文字 12sp,激活态亲子粉 #EC407A 字粗体,非激活态灰字,layoutWeight(1) 等宽居中,padding 上下 10,点击切换 segIdx。分段器纯白底,是订单管理的标准筛选模式。

10.2 订单列表(filtered 计算属性筛选)

get filtered(): OrderItem63[] {
  if (this.segIdx === 0) {
    return this.orderList
  }
  return this.orderList.filter((o: OrderItem63) => o.status === ORDER_SEG_63[this.segIdx])
}

filtered计算属性 getter——根据当前 segIdx 筛选订单列表,segIdx=0 返回全部,其他返回对应状态的订单。通过 ForEach(this.filtered) 渲染筛选后的订单列表,每笔订单卡片:

  • 顶部行:icon 32sp,width/height 56,#F5F5F5 灰底圆角 8 居中;右侧 Column:商品名 13sp 粗体深字(maxLines 1)+ 品牌·日期 10sp 灰字;价格 ¥N 14sp 价格红粗体。
  • 底部行:状态标签(使用独立的 statusColor 文字 + 浅灰底圆角 3,如待发货橙/已发货蓝/已完成绿/已退款灰);if (o.status === '待发货') 条件渲染「取消订单」按钮(浅灰底灰字 Capsule height 28,点击设置 cancelId + 打开取消订单弹窗);「编辑地址」按钮(#FCE4EC 浅粉底亲子粉字 Capsule height 28,点击打开编辑地址弹窗)。

订单卡片纯白底圆角 10 padding 10 margin 左右 12 下 6。取消订单按钮是条件渲染的——只有「待发货」状态的订单显示取消订单按钮,其他状态不显示,通过 if (o.status === '待发货') 实现差异化渲染。编辑地址按钮所有订单都显示,方便用户随时修改收货地址。

10.3 取消订单居中弹窗 + 编辑地址底部抽屉弹窗

  • 取消订单居中弹窗 cancelOrderBuilderStack alignContent: Center 居中,纯白底圆角 16 width 80% padding 20,包含:📦 图标 40sp + 「取消此订单?」15sp 粗体深字 + 「取消后将全额退款,3-5个工作日到账」12sp 灰字 + 操作按钮行(「再想想」浅灰底灰字 Capsule + 「确认取消」#F44336 红底白字 Capsule,layoutWeight 各 1)。确认取消后通过 this.orderList = this.orderList.filter((o) => o.id !== this.cancelId) 从订单列表中移除,实现取消订单功能
  • 编辑收货地址底部抽屉弹窗 editAddrBuilderStack alignContent: Bottom 底部抽屉,纯白底圆角 topLeft/topRight 16 maxHeight 55%,包含:顶部行(📍 图标 + 「编辑收货地址」15sp 粗体深字 + ✕ 关闭按钮)+ TextArea 文本输入框(placeholder「请输入详细收货地址」,height 80,边框 1px 灰,onChange 更新 addrVal)+ 操作按钮行(「取消」浅灰底灰字 Capsule + 「保存」亲子粉底白字 Capsule,layoutWeight 各 1)。编辑地址底部抽屉是电商 App 的标准地址编辑交互,从底部滑出不遮挡全部页面。

订单 Tab 通过深蓝渐变头 + 状态分段器 + 订单列表(取消订单条件渲染/编辑地址)+ 取消订单居中弹窗 + 编辑地址底部抽屉弹窗的完整布局,实现了母婴电商订单管理的核心功能(状态筛选/取消订单/编辑地址),是亲子母婴电商的订单管理核心频道。

多多亲子母婴订单Tab运行效果

十一、我的 Tab:渐变头 + 三列统计 + 宝宝成长档案 + 月度消费柱状图 + 优惠券

我的 Tab ProfileTab63 是应用信息最丰富的 Tab,由粉蓝渐变头 + 三列统计卡 + 宝宝成长档案 + 月度消费柱状图 + 优惠券五部分组成,月度消费柱状图是本应用最具特色的数据可视化。

11.1 粉蓝渐变头 + 三列统计卡

  • 粉蓝渐变头angle:180 粉蓝渐变 #EC407A→#42A5F5(从亲子粉到婴幼蓝),包含:👩 头像 50sp,width/height 70,半透明白底圆角 35(圆形头像)居中;「果果妈妈」16sp 粗体白字;宝宝年龄标签(👶 果果 2岁3月,半透明白底白字圆角 10)+ 💎 钻石会员(半透明白字)。渐变头 padding top 30 bottom 20 居中对齐,是个人中心的标准头部设计。
  • 三列统计卡:纯白底圆角 10 padding 上下 12,margin 左右 12 top -12(向上偏移与渐变头重叠形成悬浮效果)+ shadow 阴影,等宽三列:
    • 「¥6,820 累计消费」亲子粉粗体 16sp + 灰字 10sp
    • 「86 购买商品」婴幼蓝粗体 16sp + 灰字 10sp
    • 「12 优惠券」深紫粗体 16sp + 灰字 10sp

三列统计卡通过 margin top: -12 向上偏移与渐变头重叠,形成悬浮卡片效果,是电商 App 个人中心的标准统计展示。

11.2 宝宝成长档案

「👶 宝宝成长档案」标题,下方是纯白底圆角 10 padding 12 margin 左右 12 下 8 的档案卡片:

  • 顶部行:👶 宝宝头像 28sp,width/height 50,#FCE4EC 浅粉底圆角 25(圆形)居中;右侧 Column:「果果」13sp 粗体深字 + 「女宝宝 · 2岁3个月」11sp 灰字 + 「生日:2024-05-20」10sp 灰字。
  • 四列发育数据(等宽):
    • 「88cm 身高」亲子粉粗体 14sp + 灰字 9sp
    • 「12.5kg 体重」婴幼蓝粗体 14sp + 灰字 9sp
    • 「20颗 乳牙」绿色粗体 14sp + 灰字 9sp
    • 「A+ 发育」金色粗体 14sp + 灰字 9sp

宝宝成长档案是母婴电商个人中心的特色功能——记录宝宝的身高/体重/乳牙/发育等级,让妈妈随时追踪宝宝的成长数据,四列发育数据使用粉/蓝/绿/金四色区分,直观展示宝宝的成长指标。

11.3 月度消费柱状图(SPEND_BARS_63,粉系渐变色)

「📊 月度消费统计」标题,下方是纯白底圆角 10 padding 12 margin 左右 12 下 8 的柱状图卡片:

  • Row space 4 布局,ForEach(SPEND_BARS_63) 渲染 6 根柱子(3-8月),每根柱子 Column:
    • 顶部金额:「¥N」8sp 灰字(value/100 取整,如 ¥8/¥12/¥6/¥15/¥11/¥23,单位百元)。
    • 柱子主体:Column layoutWeight(1) width 20,使用独立的粉系渐变色(3月 #F8BBD0 最浅→4月 #F48FB1→5月 #EC407A→6月 #E91E63→7月 #D81B60→8月 #C2185B 最深),圆角 3,justifyContent: FlexAlign.End(从底部向上生长)。
    • 底部月份标签:「3月/4月/…/8月」9sp 灰字。
  • 整行 height 110,每根柱子 height 100,等宽 layoutWeight 1 居中对齐。

月度消费柱状图是本应用最具特色的数据可视化——6 个月消费数据使用粉系渐变色从浅到深,8 月消费 ¥2380 最高柱子最深(#C2185B),3 月消费 ¥880 最低柱子最浅(#F8BBD0),形成「育儿支出逐月攀升」的可视化效果。柱子通过 layoutWeight(1) + justifyContent: FlexAlign.End 实现从底部向上生长的柱状图效果,顶部显示金额、底部显示月份,是 ArkTS 中纯声明式实现柱状图的标准模式(无需 Canvas,纯 Column + 背景色 + layoutWeight 实现)。

11.4 优惠券

「🎫 我的优惠券」标题,下方是 Row 布局的三列优惠券(等宽 layoutWeight 1):

  • 「¥20 满100可用 奶粉券」:#FCE4EC 浅粉底圆角 8,亲子粉字,padding 上下 10 margin 左右 2 居中。
  • 「¥10 满50可用 玩具券」:#E3F2FD 浅蓝底圆角 8,婴幼蓝字。
  • 「¥30 满200可用 课程券」:#F3E5F5 浅紫底圆角 8,深紫字。

三列优惠券使用粉/蓝/紫三色对应奶粉/玩具/课程三个品类,直观展示可用优惠券,是电商 App 个人中心的标准优惠券展示。

我的 Tab 通过粉蓝渐变头 + 三列统计卡(悬浮重叠)+ 宝宝成长档案(身高体重乳牙发育)+ 月度消费柱状图(粉系渐变色从浅到深)+ 优惠券(三色对应品类)的丰富布局,完整展示了母婴电商个人中心的核心功能(消费统计/宝宝档案/优惠券),月度消费柱状图是本应用最具特色的数据可视化,是亲子母婴电商的个人中心核心频道。

多多亲子母婴我的Tab运行效果

十二、4 种模态弹窗:底部抽屉 + 居中两种样式

应用实现了 4 种模态弹窗,分布在 MilkTab(加入购物车底部抽屉 + 删除收藏居中)和 OrderTab63(取消订单居中 + 编辑地址底部抽屉),覆盖底部抽屉和居中两种弹窗样式。

12.1 加入购物车底部抽屉(MilkTab,规格+数量)

cartBuilderStack alignContent: Bottom 底部抽屉,纯白底圆角 topLeft/topRight 16 maxHeight 70%,包含:

  • 顶部行:🛒 图标 20sp + 「加入购物车」15sp 粗体深字(layoutWeight 1)+ ✕ 关闭按钮(灰字 18sp,点击关闭)。
  • 商品信息行:🍼 图标 40sp,width/height 64,#FCE4EC 浅粉底圆角 8 居中;右侧 Column:商品名 13sp 粗体深字 + 段位·产地 11sp 灰字 + 价格 ¥268 16sp 价格红粗体。
  • 规格选择:「规格」12sp 灰字 + ForEach(['单罐装','三罐装','六罐装']) 渲染 3 个规格选项,激活态亲子粉底白字圆角 6,非激活态浅灰底灰字圆角 6,点击切换 selectedSpec。
  • 数量步进器:「购买数量」12sp 灰字(layoutWeight 1)+ 步进器(− 圆形按钮浅灰底灰字 width/height 32,点击 cartNum-1 最小 1 / 数量显示 16sp 粗体深字 / + 圆形按钮浅灰底灰字,点击 cartNum+1)。
  • 确认按钮:「确认加入」15sp 粗体白字,width 100% height 44,亲子粉底居中,点击关闭弹窗。

加入购物车底部抽屉是电商 App 的标准加购交互——从底部滑出展示商品信息/规格选择/数量步进器/确认按钮,不遮挡全部页面,规格选择(单罐/三罐/六罐)和数量步进器是加购的核心交互。

12.2 删除收藏居中弹窗(MilkTab,可移除)

deleteFavBuilderStack alignContent: Center 居中,纯白底圆角 16 width 80% padding 20,包含:

  • 💔 图标 40sp
  • 「取消收藏?」15sp 粗体深字
  • 「取消后将移出收藏列表」12sp 灰字
  • 操作按钮行:「取消」浅灰底灰字 Capsule height 38(layoutWeight 1,点击关闭)+ 「确认」亲子粉底白字粗体 Capsule height 38(layoutWeight 1,点击通过 filter 从 favList 移除对应 id 的收藏 + 关闭弹窗)。

删除收藏居中弹窗是轻量确认弹窗——💔 图标 + 简洁文案 + 双按钮,确认后通过 this.favList = this.favList.filter((m) => m.id !== this.delId) 实现可移除的收藏管理,是电商 App 的标准收藏删除交互。

12.3 取消订单居中弹窗(OrderTab63,全额退款提示)

cancelOrderBuilderStack alignContent: Center 居中,纯白底圆角 16 width 80% padding 20,包含:

  • 📦 图标 40sp
  • 「取消此订单?」15sp 粗体深字
  • 「取消后将全额退款,3-5个工作日到账」12sp 灰字(退款到账时间提示是取消订单的特色文案)
  • 操作按钮行:「再想想」浅灰底灰字 Capsule height 38(layoutWeight 1,点击关闭)+ 「确认取消」#F44336 红底白字粗体 Capsule height 38(layoutWeight 1,点击通过 filter 从 orderList 移除对应 id 的订单 + 关闭弹窗)。

取消订单居中弹窗与删除收藏弹窗结构类似,但文案更详细(全额退款+3-5个工作日到账),确认按钮使用红色 #F44336(比亲子粉更红,强调取消订单的风险),是电商 App 的标准订单取消交互。

12.4 编辑收货地址底部抽屉(OrderTab63,TextArea 输入)

editAddrBuilderStack alignContent: Bottom 底部抽屉,纯白底圆角 topLeft/topRight 16 maxHeight 55%,包含:

  • 顶部行:📍 图标 20sp + 「编辑收货地址」15sp 粗体深字(layoutWeight 1)+ ✕ 关闭按钮(灰字 18sp,点击关闭)。
  • TextArea 文本输入框:placeholder「请输入详细收货地址」,width 100% height 80,fontSize 12,圆角 8,边框 1px 灰,margin 左右 12,onChange 更新 addrVal。
  • 操作按钮行:「取消」浅灰底灰字 Capsule height 38(layoutWeight 1,点击关闭)+ 「保存」亲子粉底白字粗体 Capsule height 38(layoutWeight 1,点击关闭弹窗)。

编辑收货地址底部抽屉是电商 App 的标准地址编辑交互——从底部滑出展示 TextArea 输入框 + 保存按钮,不遮挡全部页面,TextArea 支持多行输入详细地址。

4 种模态弹窗覆盖了底部抽屉(加入购物车/编辑地址)和居中(删除收藏/取消订单)两种样式,底部抽屉用于需要输入或选择的复杂交互(规格选择/数量步进/地址输入),居中用于轻量确认交互(删除收藏/取消订单),是电商 App 弹窗设计的标准模式。

多多亲子母婴加入购物车弹窗运行效果

多多亲子母婴取消订单与编辑地址弹窗运行效果

十三、特性对比表

特性维度 多多亲子母婴实现
主题风格 亲子粉 #EC407A + 婴幼蓝 #42A5F5 + 底色 #FFF5F8 温馨亲子风
架构模式 六组件多 Tab 电商架构(6 个独立 @Component 子组件 + 1 个 @Entry 主入口),主入口通过 @State activeTab + if/else if 分发显示子组件,每个子组件独立管理状态与弹窗
Tab 数量 底部 6 Tab(奶粉🍼/玩具🧸/课程🎓/社区💬/订单📦/我的👤),每个 Tab 独立渐变头
渐变头 6 个 Tab 各自独立渐变头:奶粉粉蓝 angle:135 / 玩具蓝粉 angle:180 / 课程紫粉 angle:135 / 社区粉紫 angle:135 / 订单深蓝 angle:180 / 我的粉蓝 angle:180
数据模型 8 个 interface(MilkPowder 12字段含stage段位/origin产地/formula配方 / ToyItem 11字段含独立color/bg / CourseItem 9字段含trialAvail免费试听 / MomPost 11字段含images图片数组 / OrderItem63 9字段含statusColor状态色 / CatMeta63 / BarData63 / AgeChip)
业务数据量 8 奶粉 + 9 玩具(每款独立色) + 6 课程 + 5 宝妈动态 + 6 订单 + 8 分类 + 5 年龄段 + 6 月消费柱状图 + 5 订单状态
分类独立色 CAT_63 8 个分类各自独立 color/bg(奶粉粉/洗护蓝/服饰绿/出行橙/辅食紫/寝具青/营养红/绘本靛),分类横滑中使用
玩具独立色 toys 9 款玩具各自独立 color/bg(红/蓝/橙/绿/紫/青/靛/粉/橙红),三列网格中图标区bg+年龄段color+标签color独立
粉系渐变柱状图 SPEND_BARS_63 6 个月消费数据各自独立粉系渐变色(3月#F8BBD0最浅→8月#C2185B最深),月度消费柱状图从浅到深形成「育儿支出逐月攀升」可视化,8月¥2380最高
订单状态色 orders63 6 笔订单各自独立 statusColor(待发货橙/已发货蓝/已完成绿/已退款灰),状态标签使用 statusColor
条件渲染 课程免费试听按钮 if(c.trialAvail)、订单取消按钮 if(o.status===‘待发货’),通过条件渲染实现差异化交互
计算属性 OrderTab63 的 filtered getter 根据 segIdx 筛选订单列表,是 ArkTS 计算属性的标准用法
模态弹窗 4 种:加入购物车底部抽屉(规格3档+数量步进器) / 删除收藏居中(可移除filter) / 取消订单居中(全额退款3-5工作日提示) / 编辑收货地址底部抽屉(TextArea多行输入),覆盖底部抽屉+居中两种样式
悬浮卡片 我的 Tab 三列统计卡通过 margin top:-12 向上偏移与渐变头重叠形成悬浮效果 + shadow 阴影
宝宝成长档案 我的 Tab 特色功能,四列发育数据(身高88cm粉/体重12.5kg蓝/乳牙20颗绿/发育A+金),记录宝宝成长指标
优惠券 我的 Tab 三列优惠券(¥20奶粉券粉/¥10玩具券蓝/¥30课程券紫),三色对应品类
底部导航 6 Tab 纯白底 + 粉系阴影 shadow(radius:8,color:rgba(236,64,122,0.12),offsetY:-2),激活态亲子粉字粗体+图标opacity1.0,非激活灰字+图标opacity0.45,@Builder bottomTabItem 复用构建
状态管理 6 个子组件各自独立 @State,主入口只管理 activeTab,多组件架构职责分离
特色字段 MilkPowder.stage段位/origin产地/formula配方、ToyItem.color/bg独立色、CourseItem.trialAvail免费试听、MomPost.images图片数组、OrderItem63.statusColor状态色、BarData63.color粉系渐变色
布局差异化 奶粉=分类横滑+段位大卡+双列网格+我的收藏 / 玩具=年龄段chips+三列网格(独立色) / 课程=早教列表+免费试听 / 社区=宝妈动态+图片+点赞评论 / 订单=状态分段器+列表+取消/编辑地址 / 我的=渐变头+统计卡+宝宝档案+月度消费柱状图+优惠券
无粒子动画 本应用没有粒子动画系统,是纯电商静态布局应用
电商功能 加入购物车(规格+数量)、收藏管理(可删除)、订单管理(状态筛选+取消)、地址编辑(TextArea)、优惠券、消费统计,完整母婴电商闭环

十四、完整代码

应用完整源码位于 63.ets,总计约 1200 行,包含 8 个 interface、9 个常量数组/对象、6 个 @Component 子组件(MilkTab/ToyTab/CourseTab/CommunityTab63/OrderTab63/ProfileTab63)+ 1 个 @Entry 主入口组件(DuoDuoQinZiApp)。

核心代码结构如下:

// 63.ets 多多亲子母婴 —— 奶粉 / 玩具 / 课程 / 社区 / 订单 / 我的
// 主题:亲子粉 #EC407A × 婴幼蓝 #42A5F5,底色 #FFF5F8(温馨亲子风)
// 布局差异化:奶粉=分类横滑+单列段位大卡+双列网格;玩具=年龄段chips+三列网格;课程=早教列表+试听预约;社区=宝妈动态+育儿问答;订单=分段器+列表;我的=渐变头+宝宝档案+消费柱状图+优惠券
// 弹框样式:加入购物车(底部抽屉规格+数量)、取消订单(居中)、编辑收货地址(底部抽屉)、删除收藏(居中可移除)
// 底栏样式:粉蓝渐变选中 + 未选灰色

// ============ 类型定义 ============
interface MilkPowder {
  id: number
  name: string
  brand: string
  icon: string
  stage: string
  origin: string
  price: number
  originalPrice: number
  formula: string
  tag: string
  sold: string
  rating: number
}

interface ToyItem {
  id: number
  name: string
  icon: string
  age: string
  category: string
  price: number
  originalPrice: number
  tag: string
  sold: string
  color: string
  bg: string
}

interface CourseItem {
  id: number
  name: string
  icon: string
  ageRange: string
  lessons: number
  method: string
  price: number
  teacher: string
  rating: number
  trialAvail: boolean
}

interface MomPost {
  id: number
  avatar: string
  name: string
  babyAge: string
  content: string
  images: string[]
  likes: number
  comments: number
  date: string
  topic: string
}

interface OrderItem63 {
  id: number
  icon: string
  title: string
  brand: string
  amount: number
  status: string
  statusColor: string
  date: string
}

interface CatMeta63 {
  icon: string
  label: string
  color: string
  bg: string
}

interface BarData63 {
  label: string
  value: number
  color: string
}

interface AgeChip {
  label: string
  range: string
}

// ============ 静态配置 ============
const CAT_63: CatMeta63[] = [
  { icon: '🍼', label: '奶粉', color: '#EC407A', bg: '#FCE4EC' },
  { icon: '🧴', label: '洗护', color: '#42A5F5', bg: '#E3F2FD' },
  { icon: '👖', label: '服饰', color: '#66BB6A', bg: '#E8F5E9' },
  { icon: '🚗', label: '出行', color: '#FFA726', bg: '#FFF3E0' },
  { icon: '🍽️', label: '辅食', color: '#AB47BC', bg: '#F3E5F5' },
  { icon: '🧸', label: '寝具', color: '#26A69A', bg: '#E0F2F1' },
  { icon: '💊', label: '营养', color: '#EF5350', bg: '#FFEBEE' },
  { icon: '📚', label: '绘本', color: '#5C6BC0', bg: '#E8EAF6' }
]

const milkPowders: MilkPowder[] = [
  { id: 1, name: '飞鹤星飞帆卓睿3段', brand: '飞鹤', icon: '🍼', stage: '3段(12-36月)', origin: '中国黑龙江', price: 268, originalPrice: 328, formula: 'OPO+乳铁蛋白', tag: '热销', sold: '已售5.6万', rating: 4.9 },
  { id: 2, name: '爱他美卓萃白金版3段', brand: '爱他美', icon: '🍼', stage: '3段(12-36月)', origin: '德国进口', price: 358, originalPrice: 428, formula: '天然乳脂+益生元', tag: '进口', sold: '已售3.8万', rating: 4.8 },
  { id: 3, name: '君乐宝乐铂K2儿童奶粉', brand: '君乐宝', icon: '🍼', stage: '4段(3-6岁)', origin: '中国河北', price: 128, originalPrice: 168, formula: '钙铁锌+DHA', tag: '国产', sold: '已售8.2万', rating: 4.7 },
  { id: 4, name: '美素佳儿皇家3段', brand: '美素佳儿', icon: '🍼', stage: '3段(12-36月)', origin: '荷兰进口', price: 328, originalPrice: 398, formula: '天然乳蛋白+核苷酸', tag: '推荐', sold: '已售4.5万', rating: 4.9 },
  { id: 5, name: 'a2至初白金3段', brand: 'a2', icon: '🍼', stage: '3段(12-36月)', origin: '新西兰进口', price: 388, originalPrice: 468, formula: 'A2β-酪蛋白', tag: '高端', sold: '已售2.9万', rating: 4.9 },
  { id: 6, name: '金领冠珍护3段', brand: '伊利', icon: '🍼', stage: '3段(12-36月)', origin: '中国内蒙古', price: 218, originalPrice: 278, formula: 'α+β蛋白+益生菌', tag: '热销', sold: '已售6.1万', rating: 4.8 },
  { id: 7, name: '惠氏启赋蓝钻3段', brand: '惠氏', icon: '🍼', stage: '3段(12-36月)', origin: '爱尔兰进口', price: 358, originalPrice: 428, formula: 'OPO结构脂+叶黄素', tag: '进口', sold: '已售3.3万', rating: 4.8 },
  { id: 8, name: '贝因美爱加3段', brand: '贝因美', icon: '🍼', stage: '3段(12-36月)', origin: '中国浙江', price: 188, originalPrice: 238, formula: 'INFAT+核苷酸', tag: '国产', sold: '已售4.7万', rating: 4.6 }
]

const toys: ToyItem[] = [
  { id: 1, name: '乐高得宝大颗粒', icon: '🧱', age: '1.5-5岁', category: '积木', price: 168, originalPrice: 258, tag: '益智', sold: '已售3.2万', color: '#F44336', bg: '#FFEBEE' },
  { id: 2, name: '费雪脚踏钢琴健身架', icon: '🎹', age: '0-1岁', category: '早教', price: 268, originalPrice: 399, tag: '热销', sold: '已售5.6万', color: '#42A5F5', bg: '#E3F2FD' },
  { id: 3, name: '遥控挖掘机工程车', icon: '🚜', age: '3-8岁', category: '遥控', price: 128, originalPrice: 198, tag: '男孩', sold: '已售4.1万', color: '#FFA726', bg: '#FFF3E0' },
  { id: 4, name: '磁力片拼装积木100件', icon: '🧲', age: '3-12岁', category: '益智', price: 88, originalPrice: 158, tag: '益智', sold: '已售6.8万', color: '#66BB6A', bg: '#E8F5E9' },
  { id: 5, name: '木质拼图认知板', icon: '🧩', age: '1-3岁', category: '益智', price: 48, originalPrice: 88, tag: '认知', sold: '已售3.9万', color: '#AB47BC', bg: '#F3E5F5' },
  { id: 6, name: '儿童电子琴61键', icon: '🎹', age: '3-10岁', category: '音乐', price: 188, originalPrice: 298, tag: '音乐', sold: '已售2.8万', color: '#26A69A', bg: '#E0F2F1' },
  { id: 7, name: '变形金刚手办模型', icon: '🤖', age: '6-12岁', category: '模型', price: 158, originalPrice: 268, tag: '收藏', sold: '已售3.5万', color: '#5C6BC0', bg: '#E8EAF6' },
  { id: 8, name: '芭比换装娃娃套装', icon: '👗', age: '3-8岁', category: '娃娃', price: 98, originalPrice: 168, tag: '女孩', sold: '已售5.2万', color: '#EC407A', bg: '#FCE4EC' },
  { id: 9, name: '水彩画板套装', icon: '🎨', age: '3-10岁', category: '美术', price: 68, originalPrice: 128, tag: '美术', sold: '已售4.6万', color: '#FF7043', bg: '#FBE9E7' }
]

const courses: CourseItem[] = [
  { id: 1, name: '蒙特梭利感统训练课', icon: '🧠', ageRange: '6个月-3岁', lessons: 24, method: '一对一', price: 2880, teacher: '王老师', rating: 4.9, trialAvail: true },
  { id: 2, name: '金宝贝音乐律动课', icon: '🎵', ageRange: '6个月-2岁', lessons: 16, method: '小班6人', price: 1980, teacher: '李老师', rating: 4.8, trialAvail: true },
  { id: 3, name: '东方爱婴全脑开发课', icon: '🧩', ageRange: '1-3岁', lessons: 32, method: '小班8人', price: 3280, teacher: '张老师', rating: 4.7, trialAvail: true },
  { id: 4, name: '美吉姆艺术创意课', icon: '🎨', ageRange: '2-5岁', lessons: 20, method: '小班6人', price: 2480, teacher: '陈老师', rating: 4.9, trialAvail: false },
  { id: 5, name: '小小运动馆体能课', icon: '⚽', ageRange: '3-6岁', lessons: 18, method: '小班10人', price: 1880, teacher: '刘教练', rating: 4.6, trialAvail: true },
  { id: 6, name: '乐高启蒙机械课', icon: '🤖', ageRange: '4-8岁', lessons: 24, method: '小班6人', price: 2680, teacher: '赵老师', rating: 4.8, trialAvail: true }
]

const momPosts: MomPost[] = [
  { id: 1, avatar: '👩', name: '果果妈', babyAge: '宝宝2岁3月', content: '今天果果第一次自己用勺子吃饭了!虽然洒了一桌子但好开心,宝贝长大了~分享一款特别好用的训练碗,碗底有吸盘不容易打翻。', images: ['🥣', '🍽️', '📸'], likes: 256, comments: 89, date: '08-24', topic: '育儿分享' },
  { id: 2, avatar: '👩', name: '豆豆妈', babyAge: '宝宝8个月', content: '豆豆最近出牙期闹腾,买了这款牙胶玩具,冰冰凉凉的宝宝很喜欢咬,比之前好多了。推荐给同样在出牙期的妈妈们。', images: ['🦷', '🧸'], likes: 198, comments: 67, date: '08-23', topic: '好物推荐' },
  { id: 3, avatar: '👩', name: '糖糖妈', babyAge: '宝宝3岁', content: '糖糖上了三个月感统训练课,专注力明显提升了!以前坐不住5分钟,现在能安静拼图20分钟。老师也夸进步很大。', images: ['🧩', '📸'], likes: 345, comments: 102, date: '08-22', topic: '早教心得' },
  { id: 4, avatar: '👩', name: '团团妈', babyAge: '宝宝1岁半', content: '换奶粉选了好久,最后选了飞鹤星飞帆,宝宝消化吸收很好,便便也正常。国产奶粉现在真的不错,性价比很高。', images: ['🍼'], likes: 412, comments: 156, date: '08-21', topic: '奶粉选择' },
  { id: 5, avatar: '👩', name: '米米妈', babyAge: '宝宝6个月', content: '开始加辅食了!第一次做了胡萝卜泥,米米吃了好几口好开心。新手妈妈做辅食好紧张,大家有什么食谱推荐吗?', images: ['🥕', '📸', '🍼'], likes: 289, comments: 178, date: '08-20', topic: '辅食交流' }
]

const orders63: OrderItem63[] = [
  { id: 1, icon: '🍼', title: '飞鹤星飞帆卓睿3段', brand: '飞鹤', amount: 268, status: '待发货', statusColor: '#FF8F00', date: '08-24' },
  { id: 2, icon: '🧸', title: '费雪脚踏钢琴健身架', brand: '费雪', amount: 268, status: '已发货', statusColor: '#42A5F5', date: '08-22' },
  { id: 3, icon: '🧩', title: '磁力片拼装积木100件', brand: '磁力片', amount: 88, status: '已完成', statusColor: '#66BB6A', date: '08-18' },
  { id: 4, icon: '🧱', title: '乐高得宝大颗粒', brand: '乐高', amount: 168, status: '已完成', statusColor: '#66BB6A', date: '08-12' },
  { id: 5, icon: '🍼', title: '爱他美卓萃白金版3段', brand: '爱他美', amount: 358, status: '已退款', statusColor: '#999999', date: '08-08' },
  { id: 6, icon: '🎨', title: '水彩画板套装', brand: '画板', amount: 68, status: '已完成', statusColor: '#66BB6A', date: '08-03' }
]

const AGE_CHIPS: AgeChip[] = [
  { label: '全部', range: '全部' },
  { label: '0-1岁', range: '0-1岁' },
  { label: '1-3岁', range: '1-3岁' },
  { label: '3-6岁', range: '3-6岁' },
  { label: '6-12岁', range: '6-12岁' }
]

const SPEND_BARS_63: BarData63[] = [
  { label: '3月', value: 880, color: '#F8BBD0' },
  { label: '4月', value: 1280, color: '#F48FB1' },
  { label: '5月', value: 680, color: '#EC407A' },
  { label: '6月', value: 1580, color: '#E91E63' },
  { label: '7月', value: 1120, color: '#D81B60' },
  { label: '8月', value: 2380, color: '#C2185B' }
]

const ORDER_SEG_63: string[] = ['全部', '待发货', '已发货', '已完成', '已退款']

// ============ 全局弹窗遮罩 ============
function modalOverlay(onClose: () => void) {
}

// ============ Tab1 奶粉 ============
@Component
struct MilkTab {
  @State showCart: boolean = false
  @State showDelete: boolean = false
  @State delId: number = 0
  @State cartNum: number = 1
  @State selectedSpec: number = 0
  @State favList: MilkPowder[] = milkPowders.slice(0, 4)

  @Builder
  cartBuilder() {
    Stack({ alignContent: Alignment.Bottom }) {
      Column() {
        Column({ space: 12 }) {
          Row({ space: 8 }) {
            Text('🛒')
              .fontSize(20)
            Text('加入购物车')
              .fontSize(15)
              .fontColor('#333333')
              .fontWeight(FontWeight.Bold)
              .layoutWeight(1)
            Text('✕')
              .fontSize(18)
              .fontColor('#999999')
              .onClick(() => {
                this.showCart = false
              })
          }
          .width('100%')
          .padding(12)

          Row({ space: 10 }) {
            Text('🍼')
              .fontSize(40)
              .width(64)
              .height(64)
              .backgroundColor('#FCE4EC')
              .borderRadius(8)
              .textAlign(TextAlign.Center)
            Column({ space: 4 }) {
              Text('飞鹤星飞帆卓睿3段')
                .fontSize(13)
                .fontColor('#333333')
                .fontWeight(FontWeight.Bold)
              Text('3段(12-36月) · 中国黑龙江')
                .fontSize(11)
                .fontColor('#999999')
              Text('¥268')
                .fontSize(16)
                .fontColor('#E53935')
                .fontWeight(FontWeight.Bold)
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Start)
          }
          .width('100%')
          .padding({ left: 12, right: 12 })

          Column({ space: 6 }) {
            Text('规格')
              .fontSize(12)
              .fontColor('#666666')
            Row({ space: 8 }) {
              ForEach(['单罐装', '三罐装', '六罐装'], (s: string, idx: number) => {
                Text(s)
                  .fontSize(11)
                  .fontColor(this.selectedSpec === idx ? '#FFFFFF' : '#666666')
                  .backgroundColor(this.selectedSpec === idx ? '#EC407A' : '#F5F5F5')
                  .borderRadius(6)
                  .padding({ left: 10, right: 10, top: 6, bottom: 6 })
                  .onClick(() => {
                    this.selectedSpec = idx
                  })
              }, (s: string) => s)
            }
          }
          .width('100%')
          .padding({ left: 12, right: 12, top: 8 })

          Row({ space: 12 }) {
            Text('购买数量')
              .fontSize(12)
              .fontColor('#666666')
              .layoutWeight(1)
            Row({ space: 12 }) {
              Button() {
                Text('−')
                  .fontSize(16)
                  .fontColor('#666666')
              }
              .type(ButtonType.Circle)
              .width(32)
              .height(32)
              .backgroundColor('#F5F5F5')
              .onClick(() => {
                if (this.cartNum > 1) {
                  this.cartNum = this.cartNum - 1
                }
              })
              Text(this.cartNum.toString())
                .fontSize(16)
                .fontColor('#333333')
                .fontWeight(FontWeight.Bold)
              Button() {
                Text('+')
                  .fontSize(16)
                  .fontColor('#666666')
              }
              .type(ButtonType.Circle)
              .width(32)
              .height(32)
              .backgroundColor('#F5F5F5')
              .onClick(() => {
                this.cartNum = this.cartNum + 1
              })
            }
          }
          .width('100%')
          .padding({ left: 12, right: 12, top: 8, bottom: 12 })
          .alignItems(VerticalAlign.Center)

          Row() {
            Text('确认加入')
              .fontSize(15)
              .fontColor('#FFFFFF')
              .fontWeight(FontWeight.Bold)
          }
          .width('100%')
          .height(44)
          .backgroundColor('#EC407A')
          .justifyContent(FlexAlign.Center)
          .onClick(() => {
            this.showCart = false
          })
        }
        .width('100%')
        .backgroundColor('#FFFFFF')
        .borderRadius({ topLeft: 16, topRight: 16 })
      }
      .width('100%')
      .constraintSize({ maxHeight: '70%' })
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  deleteFavBuilder() {
    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)
          .backgroundColor('#F5F5F5')
          .layoutWeight(1)
          .onClick(() => {
            this.showDelete = false
          })
          Button() {
            Text('确认')
              .fontSize(13)
              .fontColor('#FFFFFF')
              .fontWeight(FontWeight.Bold)
          }
          .type(ButtonType.Capsule)
          .height(38)
          .backgroundColor('#EC407A')
          .layoutWeight(1)
          .onClick(() => {
            this.favList = this.favList.filter((m: MilkPowder) => m.id !== this.delId)
            this.showDelete = false
          })
        }
        .width('100%')
      }
      .width('80%')
      .padding(20)
      .backgroundColor('#FFFFFF')
      .borderRadius(16)
      .alignItems(HorizontalAlign.Center)
    }
    .width('100%')
    .height('100%')
  }

  build() {
    Stack({ alignContent: Alignment.Bottom }) {
      Scroll() {
        Column() {
          Column({ space: 4 }) {
            Text('🍼 多多亲子')
              .fontSize(18)
              .fontColor('#FFFFFF')
              .fontWeight(FontWeight.Bold)
            Text('妈咪放心选 · 宝宝健康成长')
              .fontSize(11)
              .fontColor('rgba(255,255,255,0.8)')
          }
          .width('100%')
          .padding({ top: 20, bottom: 20 })
          .linearGradient({
            colors: [['#EC407A', 0.0], ['#42A5F5', 1.0]],
            angle: 135
          })
          .alignItems(HorizontalAlign.Center)

          Scroll() {
            Row({ space: 6 }) {
              ForEach(CAT_63, (c: CatMeta63) => {
                Column({ space: 3 }) {
                  Text(c.icon)
                    .fontSize(22)
                  Text(c.label)
                    .fontSize(9)
                    .fontColor(c.color)
                }
                .width(64)
                .padding({ top: 10, bottom: 10 })
                .backgroundColor(c.bg)
                .borderRadius(10)
                .alignItems(HorizontalAlign.Center)
              }, (c: CatMeta63) => c.label)
            }
            .padding({ left: 12, right: 12 })
          }
          .scrollable(ScrollDirection.Horizontal)
          .scrollBar(BarState.Off)
          .width('100%')
          .padding({ top: 10, bottom: 10 })

          Row({ space: 6 }) {
            Text('🔥 热销奶粉')
              .fontSize(15)
              .fontColor('#333333')
              .fontWeight(FontWeight.Bold)
            Text('正品保障')
              .fontSize(11)
              .fontColor('#EC407A')
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .padding({ left: 12, right: 12, top: 6, bottom: 6 })

          Column({ space: 10 }) {
            Row({ space: 12 }) {
              Text(milkPowders[0].icon)
                .fontSize(48)
                .width(90)
                .height(90)
                .backgroundColor('#FCE4EC')
                .borderRadius(12)
                .textAlign(TextAlign.Center)
              Column({ space: 6 }) {
                Row({ space: 6 }) {
                  Text(milkPowders[0].name)
                    .fontSize(14)
                    .fontColor('#333333')
                    .fontWeight(FontWeight.Bold)
                    .layoutWeight(1)
                  Text(milkPowders[0].tag)
                    .fontSize(9)
                    .fontColor('#FFFFFF')
                    .backgroundColor('#EC407A')
                    .borderRadius(3)
                    .padding({ left: 4, right: 4 })
                }
                .width('100%')
                Text(milkPowders[0].stage + ' · ' + milkPowders[0].origin)
                  .fontSize(11)
                  .fontColor('#999999')
                Text('配方:' + milkPowders[0].formula)
                  .fontSize(11)
                  .fontColor('#666666')
                Row({ space: 8 }) {
                  Text('★' + milkPowders[0].rating)
                    .fontSize(11)
                    .fontColor('#FFB300')
                    .fontWeight(FontWeight.Bold)
                  Text(milkPowders[0].sold)
                    .fontSize(10)
                    .fontColor('#999999')
                }
                Row({ space: 8 }) {
                  Text('¥' + milkPowders[0].price)
                    .fontSize(18)
                    .fontColor('#E53935')
                    .fontWeight(FontWeight.Bold)
                  Text('¥' + milkPowders[0].originalPrice)
                    .fontSize(11)
                    .fontColor('#999999')
                    .decoration({ type: TextDecorationType.LineThrough })
                  Button() {
                    Text('加入')
                      .fontSize(11)
                      .fontColor('#FFFFFF')
                      .fontWeight(FontWeight.Bold)
                  }
                  .type(ButtonType.Capsule)
                  .height(28)
                  .backgroundColor('#EC407A')
                  .padding({ left: 12, right: 12 })
                  .onClick(() => {
                    this.showCart = true
                  })
                }
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)
            }
            .width('100%')
            .alignItems(VerticalAlign.Top)
          }
          .width('100%')
          .padding(12)
          .backgroundColor('#FFFFFF')
          .borderRadius(12)
          .margin({ left: 12, right: 12, bottom: 8 })

          Row({ space: 6 }) {
            Text('🥛 全部奶粉')
              .fontSize(15)
              .fontColor('#333333')
              .fontWeight(FontWeight.Bold)
          }
          .width('100%')
          .padding({ left: 12, right: 12, top: 8, bottom: 6 })

          Row() {
            ForEach(milkPowders.slice(1, 8), (m: MilkPowder) => {
              Column({ space: 5 }) {
                Text(m.icon)
                  .fontSize(36)
                  .width('100%')
                  .height(80)
                  .backgroundColor('#FCE4EC')
                  .borderRadius(8)
                  .textAlign(TextAlign.Center)
                Text(m.name)
                  .fontSize(10)
                  .fontColor('#333333')
                  .maxLines(1)
                  .textOverflow({ overflow: TextOverflow.Ellipsis })
                Text(m.stage)
                  .fontSize(8)
                  .fontColor('#999999')
                Row({ space: 4 }) {
                  Text('¥' + m.price)
                    .fontSize(11)
                    .fontColor('#E53935')
                    .fontWeight(FontWeight.Bold)
                  Text('¥' + m.originalPrice)
                    .fontSize(8)
                    .fontColor('#999999')
                    .decoration({ type: TextDecorationType.LineThrough })
                }
              }
              .layoutWeight(1)
              .padding(6)
              .backgroundColor('#FFFFFF')
              .borderRadius(8)
              .margin({ left: 3, right: 3 })
              .alignItems(HorizontalAlign.Center)
            }, (m: MilkPowder) => m.id.toString())
          }
          .width('100%')
          .padding({ left: 4, right: 4 })

          Text('❤️ 我的收藏')
            .fontSize(14)
            .fontColor('#333333')
            .fontWeight(FontWeight.Bold)
            .margin({ left: 12, top: 12, bottom: 6 })

          ForEach(this.favList, (m: MilkPowder) => {
            Row({ space: 10 }) {
              Text(m.icon)
                .fontSize(32)
                .width(56)
                .height(56)
                .backgroundColor('#FCE4EC')
                .borderRadius(8)
                .textAlign(TextAlign.Center)
              Column({ space: 3 }) {
                Text(m.name)
                  .fontSize(12)
                  .fontColor('#333333')
                  .fontWeight(FontWeight.Bold)
                  .maxLines(1)
                  .textOverflow({ overflow: TextOverflow.Ellipsis })
                Text(m.stage + ' · ¥' + m.price)
                  .fontSize(10)
                  .fontColor('#999999')
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)
              Text('❤️')
                .fontSize(16)
                .fontColor('#EC407A')
                .onClick(() => {
                  this.delId = m.id
                  this.showDelete = true
                })
            }
            .width('100%')
            .padding(8)
            .backgroundColor('#FFFFFF')
            .borderRadius(8)
            .margin({ left: 12, right: 12, bottom: 4 })
            .alignItems(VerticalAlign.Center)
          }, (m: MilkPowder) => m.id.toString())

          Column()
            .height(80)
        }
      }
      .scrollBar(BarState.Off)
      .constraintSize({ maxHeight: '100%' })

      if (this.showCart) {
        this.cartBuilder()
      }
      if (this.showDelete) {
        this.deleteFavBuilder()
      }
    }
    .width('100%')
    .height('100%')
  }
}

// ============ Tab2 玩具 ============
@Component
struct ToyTab {
  @State selectedAge: number = 0

  build() {
    Scroll() {
      Column() {
        Column() {
          Text('🧸 玩具商城')
            .fontSize(16)
            .fontColor('#FFFFFF')
            .fontWeight(FontWeight.Bold)
          Text('益智成长 · 安全材质')
            .fontSize(11)
            .fontColor('rgba(255,255,255,0.8)')
        }
        .width('100%')
        .padding({ top: 14, bottom: 14 })
        .linearGradient({
          colors: [['#42A5F5', 0.0], ['#EC407A', 1.0]],
          angle: 180
        })
        .alignItems(HorizontalAlign.Center)

        Scroll() {
          Row({ space: 6 }) {
            ForEach(AGE_CHIPS, (a: AgeChip, idx: number) => {
              Text(a.label)
                .fontSize(12)
                .fontColor(this.selectedAge === idx ? '#FFFFFF' : '#666666')
                .fontWeight(this.selectedAge === idx ? FontWeight.Bold : FontWeight.Normal)
                .padding({ left: 14, right: 14, top: 6, bottom: 6 })
                .backgroundColor(this.selectedAge === idx ? '#42A5F5' : '#F5F5F5')
                .borderRadius(16)
                .onClick(() => {
                  this.selectedAge = idx
                })
            }, (a: AgeChip) => a.label)
          }
          .padding({ left: 12, right: 12 })
        }
        .scrollable(ScrollDirection.Horizontal)
        .scrollBar(BarState.Off)
        .width('100%')
        .padding({ top: 10, bottom: 10 })

        Text('🎯 精选玩具')
          .fontSize(14)
          .fontColor('#333333')
          .fontWeight(FontWeight.Bold)
          .margin({ left: 12, top: 4, bottom: 6 })

        Row() {
          ForEach(toys, (t: ToyItem) => {
            Column({ space: 5 }) {
              Text(t.icon)
                .fontSize(32)
                .width('100%')
                .height(70)
                .backgroundColor(t.bg)
                .borderRadius(8)
                .textAlign(TextAlign.Center)
              Text(t.name)
                .fontSize(10)
                .fontColor('#333333')
                .maxLines(1)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
              Text(t.age)
                .fontSize(8)
                .fontColor(t.color)
              Row({ space: 4 }) {
                Text('¥' + t.price)
                  .fontSize(11)
                  .fontColor('#E53935')
                  .fontWeight(FontWeight.Bold)
                Text('¥' + t.originalPrice)
                  .fontSize(8)
                  .fontColor('#999999')
                  .decoration({ type: TextDecorationType.LineThrough })
              }
              Text(t.tag)
                .fontSize(8)
                .fontColor('#FFFFFF')
                .backgroundColor(t.color)
                .borderRadius(3)
                .padding({ left: 4, right: 4 })
            }
            .layoutWeight(1)
            .padding(6)
            .backgroundColor('#FFFFFF')
            .borderRadius(8)
            .margin({ left: 3, right: 3, bottom: 6 })
            .alignItems(HorizontalAlign.Center)
          }, (t: ToyItem) => t.id.toString())
        }
        .width('100%')
        .padding({ left: 4, right: 4 })

        Column()
          .height(80)
      }
    }
    .scrollBar(BarState.Off)
    .constraintSize({ maxHeight: '100%' })
  }
}

// ============ Tab3 课程 ============
@Component
struct CourseTab {
  build() {
    Scroll() {
      Column() {
        Column() {
          Text('🎓 早教课程')
            .fontSize(16)
            .fontColor('#FFFFFF')
            .fontWeight(FontWeight.Bold)
          Text('科学育儿 · 快乐成长')
            .fontSize(11)
            .fontColor('rgba(255,255,255,0.8)')
        }
        .width('100%')
        .padding({ top: 14, bottom: 14 })
        .linearGradient({
          colors: [['#AB47BC', 0.0], ['#EC407A', 1.0]],
          angle: 135
        })
        .alignItems(HorizontalAlign.Center)

        Text('📋 课程列表')
          .fontSize(14)
          .fontColor('#333333')
          .fontWeight(FontWeight.Bold)
          .margin({ left: 12, top: 10, bottom: 6 })

        ForEach(courses, (c: CourseItem) => {
          Column({ space: 8 }) {
            Row({ space: 10 }) {
              Text(c.icon)
                .fontSize(36)
                .width(56)
                .height(56)
                .backgroundColor('#F3E5F5')
                .borderRadius(12)
                .textAlign(TextAlign.Center)
              Column({ space: 4 }) {
                Text(c.name)
                  .fontSize(13)
                  .fontColor('#333333')
                  .fontWeight(FontWeight.Bold)
                  .maxLines(1)
                  .textOverflow({ overflow: TextOverflow.Ellipsis })
                Row({ space: 6 }) {
                  Text(c.ageRange)
                    .fontSize(9)
                    .fontColor('#AB47BC')
                    .backgroundColor('#F3E5F5')
                    .borderRadius(3)
                    .padding({ left: 4, right: 4 })
                  Text(c.lessons + '课时')
                    .fontSize(9)
                    .fontColor('#999999')
                  Text(c.method)
                    .fontSize(9)
                    .fontColor('#999999')
                }
                Row({ space: 6 }) {
                  Text('★' + c.rating)
                    .fontSize(10)
                    .fontColor('#FFB300')
                    .fontWeight(FontWeight.Bold)
                  Text(c.teacher)
                    .fontSize(10)
                    .fontColor('#666666')
                }
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)
            }
            .width('100%')
            .alignItems(VerticalAlign.Top)

            Row({ space: 12 }) {
              Text('¥' + c.price)
                .fontSize(18)
                .fontColor('#E53935')
                .fontWeight(FontWeight.Bold)
              if (c.trialAvail) {
                Button() {
                  Text('免费试听')
                    .fontSize(11)
                    .fontColor('#AB47BC')
                    .fontWeight(FontWeight.Bold)
                }
                .type(ButtonType.Capsule)
                .height(30)
                .backgroundColor('#F3E5F5')
                .padding({ left: 12, right: 12 })
              }
              Button() {
                Text('立即报名')
                  .fontSize(11)
                  .fontColor('#FFFFFF')
                  .fontWeight(FontWeight.Bold)
              }
              .type(ButtonType.Capsule)
              .height(30)
              .backgroundColor('#EC407A')
              .padding({ left: 12, right: 12 })
            }
            .width('100%')
            .alignItems(VerticalAlign.Center)
          }
          .width('100%')
          .padding(10)
          .backgroundColor('#FFFFFF')
          .borderRadius(10)
          .margin({ left: 12, right: 12, bottom: 6 })
        }, (c: CourseItem) => c.id.toString())

        Column()
          .height(80)
      }
    }
    .scrollBar(BarState.Off)
    .constraintSize({ maxHeight: '100%' })
  }
}

// ============ Tab4 社区 ============
@Component
struct CommunityTab63 {
  build() {
    Scroll() {
      Column() {
        Column() {
          Text('💬 宝妈社区')
            .fontSize(16)
            .fontColor('#FFFFFF')
            .fontWeight(FontWeight.Bold)
          Text('分享育儿日常 · 交流经验')
            .fontSize(11)
            .fontColor('rgba(255,255,255,0.8)')
        }
        .width('100%')
        .padding({ top: 14, bottom: 14 })
        .linearGradient({
          colors: [['#EC407A', 0.0], ['#AB47BC', 1.0]],
          angle: 135
        })
        .alignItems(HorizontalAlign.Center)

        Text('👩 宝妈动态')
          .fontSize(14)
          .fontColor('#333333')
          .fontWeight(FontWeight.Bold)
          .margin({ left: 12, top: 8, bottom: 6 })

        ForEach(momPosts, (p: MomPost) => {
          Column({ space: 8 }) {
            Row({ space: 8 }) {
              Text(p.avatar)
                .fontSize(28)
                .width(40)
                .height(40)
                .backgroundColor('#FCE4EC')
                .borderRadius(20)
                .textAlign(TextAlign.Center)
              Column({ space: 3 }) {
                Text(p.name)
                  .fontSize(13)
                  .fontColor('#333333')
                  .fontWeight(FontWeight.Bold)
                Row({ space: 6 }) {
                  Text(p.babyAge)
                    .fontSize(10)
                    .fontColor('#EC407A')
                  Text('· ' + p.date)
                    .fontSize(10)
                    .fontColor('#CCCCCC')
                }
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)
              Text(p.topic)
                .fontSize(9)
                .fontColor('#AB47BC')
                .backgroundColor('#F3E5F5')
                .borderRadius(3)
                .padding({ left: 6, right: 6, top: 2, bottom: 2 })
            }
            .width('100%')
            .alignItems(VerticalAlign.Top)

            Text(p.content)
              .fontSize(12)
              .fontColor('#444444')
              .lineHeight(18)
              .width('100%')

            Row({ space: 6 }) {
              ForEach(p.images, (img: string) => {
                Text(img)
                  .fontSize(32)
                  .width(70)
                  .height(70)
                  .backgroundColor('#F5F5F5')
                  .borderRadius(8)
                  .textAlign(TextAlign.Center)
              }, (img: string, idx: number) => idx.toString())
            }

            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: MomPost) => p.id.toString())

        Column()
          .height(80)
      }
    }
    .scrollBar(BarState.Off)
    .constraintSize({ maxHeight: '100%' })
  }
}

// ============ Tab5 订单 ============
@Component
struct OrderTab63 {
  @State segIdx: number = 0
  @State showCancel: boolean = false
  @State cancelId: number = 0
  @State showEditAddr: boolean = false
  @State addrVal: string = ''
  @State orderList: OrderItem63[] = orders63

  @Builder
  cancelOrderBuilder() {
    Stack({ alignContent: Alignment.Center }) {
      Column({ space: 12 }) {
        Text('📦')
          .fontSize(40)
        Text('取消此订单?')
          .fontSize(15)
          .fontColor('#333333')
          .fontWeight(FontWeight.Bold)
        Text('取消后将全额退款,3-5个工作日到账')
          .fontSize(12)
          .fontColor('#999999')
        Row({ space: 12 }) {
          Button() {
            Text('再想想')
              .fontSize(13)
              .fontColor('#666666')
          }
          .type(ButtonType.Capsule)
          .height(38)
          .backgroundColor('#F5F5F5')
          .layoutWeight(1)
          .onClick(() => {
            this.showCancel = false
          })
          Button() {
            Text('确认取消')
              .fontSize(13)
              .fontColor('#FFFFFF')
              .fontWeight(FontWeight.Bold)
          }
          .type(ButtonType.Capsule)
          .height(38)
          .backgroundColor('#F44336')
          .layoutWeight(1)
          .onClick(() => {
            this.orderList = this.orderList.filter((o: OrderItem63) => o.id !== this.cancelId)
            this.showCancel = false
          })
        }
        .width('100%')
      }
      .width('80%')
      .padding(20)
      .backgroundColor('#FFFFFF')
      .borderRadius(16)
      .alignItems(HorizontalAlign.Center)
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  editAddrBuilder() {
    Stack({ alignContent: Alignment.Bottom }) {
      Column() {
        Column({ space: 10 }) {
          Row({ space: 8 }) {
            Text('📍')
              .fontSize(20)
            Text('编辑收货地址')
              .fontSize(15)
              .fontColor('#333333')
              .fontWeight(FontWeight.Bold)
              .layoutWeight(1)
            Text('✕')
              .fontSize(18)
              .fontColor('#999999')
              .onClick(() => {
                this.showEditAddr = false
              })
          }
          .width('100%')
          .padding(12)

          TextArea({ placeholder: '请输入详细收货地址', text: this.addrVal })
            .width('100%')
            .height(80)
            .fontSize(12)
            .borderRadius(8)
            .border({ width: 1, color: '#E0E0E0' })
            .margin({ left: 12, right: 12 })
            .onChange((val: string) => {
              this.addrVal = val
            })

          Row({ space: 12 }) {
            Button() {
              Text('取消')
                .fontSize(13)
                .fontColor('#666666')
            }
            .type(ButtonType.Capsule)
            .height(38)
            .backgroundColor('#F5F5F5')
            .layoutWeight(1)
            .onClick(() => {
              this.showEditAddr = false
            })
            Button() {
              Text('保存')
                .fontSize(13)
                .fontColor('#FFFFFF')
                .fontWeight(FontWeight.Bold)
            }
            .type(ButtonType.Capsule)
            .height(38)
            .backgroundColor('#EC407A')
            .layoutWeight(1)
            .onClick(() => {
              this.showEditAddr = false
            })
          }
          .width('100%')
          .padding(12)
        }
        .width('100%')
        .backgroundColor('#FFFFFF')
        .borderRadius({ topLeft: 16, topRight: 16 })
      }
      .width('100%')
      .constraintSize({ maxHeight: '55%' })
    }
    .width('100%')
    .height('100%')
  }

  get filtered(): OrderItem63[] {
    if (this.segIdx === 0) {
      return this.orderList
    }
    return this.orderList.filter((o: OrderItem63) => o.status === ORDER_SEG_63[this.segIdx])
  }

  build() {
    Stack({ alignContent: Alignment.Bottom }) {
      Scroll() {
        Column() {
          Column() {
            Text('📦 我的订单')
              .fontSize(16)
              .fontColor('#FFFFFF')
              .fontWeight(FontWeight.Bold)
            Text('共 ' + this.orderList.length + ' 笔订单')
              .fontSize(11)
              .fontColor('rgba(255,255,255,0.8)')
          }
          .width('100%')
          .padding({ top: 14, bottom: 14 })
          .linearGradient({
            colors: [['#42A5F5', 0.0], ['#1565C0', 1.0]],
            angle: 180
          })
          .alignItems(HorizontalAlign.Center)

          Row({ space: 0 }) {
            ForEach(ORDER_SEG_63, (s: string, idx: number) => {
              Text(s)
                .fontSize(12)
                .fontColor(this.segIdx === idx ? '#EC407A' : '#999999')
                .fontWeight(this.segIdx === idx ? FontWeight.Bold : FontWeight.Normal)
                .layoutWeight(1)
                .textAlign(TextAlign.Center)
                .padding({ top: 10, bottom: 10 })
                .borderColor('#EC407A')
                .onClick(() => {
                  this.segIdx = idx
                })
            }, (s: string) => s)
          }
          .width('100%')
          .backgroundColor('#FFFFFF')

          ForEach(this.filtered, (o: OrderItem63) => {
            Column({ space: 8 }) {
              Row({ space: 10 }) {
                Text(o.icon)
                  .fontSize(32)
                  .width(56)
                  .height(56)
                  .backgroundColor('#F5F5F5')
                  .borderRadius(8)
                  .textAlign(TextAlign.Center)
                Column({ space: 3 }) {
                  Text(o.title)
                    .fontSize(13)
                    .fontColor('#333333')
                    .fontWeight(FontWeight.Bold)
                    .maxLines(1)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                  Text(o.brand + ' · ' + o.date)
                    .fontSize(10)
                    .fontColor('#999999')
                }
                .layoutWeight(1)
                .alignItems(HorizontalAlign.Start)
                Text('¥' + o.amount)
                  .fontSize(14)
                  .fontColor('#E53935')
                  .fontWeight(FontWeight.Bold)
              }
              .width('100%')
              .alignItems(VerticalAlign.Top)

              Row({ space: 8 }) {
                Text(o.status)
                  .fontSize(10)
                  .fontColor(o.statusColor)
                  .backgroundColor('#F5F5F5')
                  .borderRadius(3)
                  .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                Column()
                  .layoutWeight(1)
                if (o.status === '待发货') {
                  Button() {
                    Text('取消订单')
                      .fontSize(11)
                      .fontColor('#666666')
                  }
                  .type(ButtonType.Capsule)
                  .height(28)
                  .backgroundColor('#F5F5F5')
                  .padding({ left: 10, right: 10 })
                  .onClick(() => {
                    this.cancelId = o.id
                    this.showCancel = true
                  })
                }
                Button() {
                  Text('编辑地址')
                    .fontSize(11)
                    .fontColor('#EC407A')
                }
                .type(ButtonType.Capsule)
                .height(28)
                .backgroundColor('#FCE4EC')
                .padding({ left: 10, right: 10 })
                .onClick(() => {
                  this.showEditAddr = true
                })
              }
              .width('100%')
              .alignItems(VerticalAlign.Center)
            }
            .width('100%')
            .padding(10)
            .backgroundColor('#FFFFFF')
            .borderRadius(10)
            .margin({ left: 12, right: 12, bottom: 6 })
          }, (o: OrderItem63) => o.id.toString())

          Column()
            .height(80)
        }
      }
      .scrollBar(BarState.Off)
      .constraintSize({ maxHeight: '100%' })

      if (this.showCancel) {
        this.cancelOrderBuilder()
      }
      if (this.showEditAddr) {
        this.editAddrBuilder()
      }
    }
    .width('100%')
    .height('100%')
  }
}

// ============ Tab6 我的 ============
@Component
struct ProfileTab63 {
  build() {
    Scroll() {
      Column() {
        Column({ space: 6 }) {
          Text('👩')
            .fontSize(50)
            .width(70)
            .height(70)
            .backgroundColor('rgba(255,255,255,0.3)')
            .borderRadius(35)
            .textAlign(TextAlign.Center)
          Text('果果妈妈')
            .fontSize(16)
            .fontColor('#FFFFFF')
            .fontWeight(FontWeight.Bold)
          Row({ space: 8 }) {
            Text('👶 果果 2岁3月')
              .fontSize(10)
              .fontColor('#FFFFFF')
              .backgroundColor('rgba(255,255,255,0.25)')
              .borderRadius(10)
              .padding({ left: 8, right: 8, top: 2, bottom: 2 })
            Text('💎 钻石会员')
              .fontSize(10)
              .fontColor('rgba(255,255,255,0.9)')
          }
        }
        .width('100%')
        .padding({ top: 30, bottom: 20 })
        .linearGradient({
          colors: [['#EC407A', 0.0], ['#42A5F5', 1.0]],
          angle: 180
        })
        .alignItems(HorizontalAlign.Center)

        Row() {
          Column({ space: 2 }) {
            Text('¥6,820')
              .fontSize(16)
              .fontColor('#EC407A')
              .fontWeight(FontWeight.Bold)
            Text('累计消费')
              .fontSize(10)
              .fontColor('#999999')
          }
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Center)
          Column({ space: 2 }) {
            Text('86')
              .fontSize(16)
              .fontColor('#42A5F5')
              .fontWeight(FontWeight.Bold)
            Text('购买商品')
              .fontSize(10)
              .fontColor('#999999')
          }
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Center)
          Column({ space: 2 }) {
            Text('12')
              .fontSize(16)
              .fontColor('#AB47BC')
              .fontWeight(FontWeight.Bold)
            Text('优惠券')
              .fontSize(10)
              .fontColor('#999999')
          }
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Center)
        }
        .width('100%')
        .padding({ top: 12, bottom: 12 })
        .backgroundColor('#FFFFFF')
        .margin({ left: 12, right: 12, top: -12 })
        .borderRadius(10)
        .shadow({ radius: 4, color: 'rgba(0,0,0,0.08)', offsetY: 2 })

        Text('👶 宝宝成长档案')
          .fontSize(14)
          .fontColor('#333333')
          .fontWeight(FontWeight.Bold)
          .margin({ left: 12, top: 14, bottom: 6 })

        Column({ space: 8 }) {
          Row({ space: 10 }) {
            Text('👶')
              .fontSize(28)
              .width(50)
              .height(50)
              .backgroundColor('#FCE4EC')
              .borderRadius(25)
              .textAlign(TextAlign.Center)
            Column({ space: 3 }) {
              Text('果果')
                .fontSize(13)
                .fontColor('#333333')
                .fontWeight(FontWeight.Bold)
              Text('女宝宝 · 2岁3个月')
                .fontSize(11)
                .fontColor('#999999')
              Text('生日:2024-05-20')
                .fontSize(10)
                .fontColor('#999999')
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Start)
          }
          .width('100%')
          .alignItems(VerticalAlign.Center)

          Row() {
            Column({ space: 2 }) {
              Text('88cm')
                .fontSize(14)
                .fontColor('#EC407A')
                .fontWeight(FontWeight.Bold)
              Text('身高')
                .fontSize(9)
                .fontColor('#999999')
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Center)
            Column({ space: 2 }) {
              Text('12.5kg')
                .fontSize(14)
                .fontColor('#42A5F5')
                .fontWeight(FontWeight.Bold)
              Text('体重')
                .fontSize(9)
                .fontColor('#999999')
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Center)
            Column({ space: 2 }) {
              Text('20颗')
                .fontSize(14)
                .fontColor('#66BB6A')
                .fontWeight(FontWeight.Bold)
              Text('乳牙')
                .fontSize(9)
                .fontColor('#999999')
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Center)
            Column({ space: 2 }) {
              Text('A+')
                .fontSize(14)
                .fontColor('#FFB300')
                .fontWeight(FontWeight.Bold)
              Text('发育')
                .fontSize(9)
                .fontColor('#999999')
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Center)
          }
          .width('100%')
          .padding({ top: 8 })
        }
        .width('100%')
        .padding(12)
        .backgroundColor('#FFFFFF')
        .borderRadius(10)
        .margin({ left: 12, right: 12, bottom: 8 })

        Text('📊 月度消费统计')
          .fontSize(14)
          .fontColor('#333333')
          .fontWeight(FontWeight.Bold)
          .margin({ left: 12, top: 8, bottom: 6 })

        Column() {
          Row({ space: 4 }) {
            ForEach(SPEND_BARS_63, (b: BarData63) => {
              Column({ space: 4 }) {
                Text('¥' + (b.value / 100).toFixed(0))
                  .fontSize(8)
                  .fontColor('#999999')
                Column()
                  .layoutWeight(1)
                  .width(20)
                  .backgroundColor(b.color)
                  .borderRadius(3)
                Text(b.label)
                  .fontSize(9)
                  .fontColor('#999999')
              }
              .height(100)
              .layoutWeight(1)
              .justifyContent(FlexAlign.End)
              .alignItems(HorizontalAlign.Center)
            }, (b: BarData63) => b.label)
          }
          .width('100%')
          .height(110)
        }
        .width('100%')
        .padding(12)
        .backgroundColor('#FFFFFF')
        .borderRadius(10)
        .margin({ left: 12, right: 12, bottom: 8 })

        Text('🎫 我的优惠券')
          .fontSize(14)
          .fontColor('#333333')
          .fontWeight(FontWeight.Bold)
          .margin({ left: 12, top: 8, bottom: 6 })

        Row() {
          Column({ space: 2 }) {
            Text('¥20')
              .fontSize(16)
              .fontColor('#EC407A')
              .fontWeight(FontWeight.Bold)
            Text('满100可用')
              .fontSize(9)
              .fontColor('#999999')
            Text('奶粉券')
              .fontSize(9)
              .fontColor('#EC407A')
          }
          .layoutWeight(1)
          .padding({ top: 10, bottom: 10 })
          .backgroundColor('#FCE4EC')
          .borderRadius(8)
          .margin({ left: 2, right: 2 })
          .alignItems(HorizontalAlign.Center)
          Column({ space: 2 }) {
            Text('¥10')
              .fontSize(16)
              .fontColor('#42A5F5')
              .fontWeight(FontWeight.Bold)
            Text('满50可用')
              .fontSize(9)
              .fontColor('#999999')
            Text('玩具券')
              .fontSize(9)
              .fontColor('#42A5F5')
          }
          .layoutWeight(1)
          .padding({ top: 10, bottom: 10 })
          .backgroundColor('#E3F2FD')
          .borderRadius(8)
          .margin({ left: 2, right: 2 })
          .alignItems(HorizontalAlign.Center)
          Column({ space: 2 }) {
            Text('¥30')
              .fontSize(16)
              .fontColor('#AB47BC')
              .fontWeight(FontWeight.Bold)
            Text('满200可用')
              .fontSize(9)
              .fontColor('#999999')
            Text('课程券')
              .fontSize(9)
              .fontColor('#AB47BC')
          }
          .layoutWeight(1)
          .padding({ top: 10, bottom: 10 })
          .backgroundColor('#F3E5F5')
          .borderRadius(8)
          .margin({ left: 2, right: 2 })
          .alignItems(HorizontalAlign.Center)
        }
        .width('100%')
        .padding({ left: 4, right: 4 })

        Column()
          .height(80)
      }
    }
    .scrollBar(BarState.Off)
    .constraintSize({ maxHeight: '100%' })
  }
}

// ============ 主入口 ============
@Entry
@Component
struct DuoDuoQinZiApp {
  @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 ? '#EC407A' : '#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) {
          MilkTab()
        } else if (this.activeTab === 1) {
          ToyTab()
        } else if (this.activeTab === 2) {
          CourseTab()
        } else if (this.activeTab === 3) {
          CommunityTab63()
        } else if (this.activeTab === 4) {
          OrderTab63()
        } else {
          ProfileTab63()
        }
      }
      .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(236,64,122,0.12)', offsetY: -2 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFF5F8')
  }
}

63.ets 放入 HarmonyOS 项目的 entry/src/main/ets/pages/ 目录下,在 main_pages.json 中注册路由,即可在 DevEco Studio 中预览运行。

十五、开发环境搭建:DevEco Studio + API 24

本应用基于 HarmonyOS NEXT(API 24)开发,使用 ArkTS 声明式 UI 框架。以下是完整的开发环境搭建步骤。

15.1 安装 DevEco Studio

从华为开发者联盟官网下载 DevEco Studio 安装包,双击运行安装程序。安装欢迎界面如下,点击「Next」继续。

DevEco Studio安装欢迎界面

15.2 选择安装位置

在安装路径选择界面,建议选择空间充足的磁盘(建议剩余空间 20GB 以上),避免使用中文路径或含空格的路径。点击「Next」继续。

选择DevEco Studio安装路径

15.3 配置安装选项

在安装选项配置界面,勾选「Create Desktop Shortcut」(创建桌面快捷方式)、「Add to PATH」(添加到环境变量)等选项,方便后续命令行调用。点击「Next」继续。

DevEco Studio安装选项配置

15.4 新建项目:选择 Empty Ability 模板

安装完成后启动 DevEco Studio,点击「Create Project」新建项目。在模板选择界面,选择「Empty Ability」(空 Ability)模板,这是最基础的 ArkTS 应用模板,适合从零构建自定义 UI。点击「Next」继续。

选择Empty Ability模板

15.5 配置项目信息:API 24

在项目配置界面,填写项目名称(如 DuoDuoQinZi)、包名(如 com.example.duoduoqinzi),Compatible SDK 选择 6.1.1(24)(即 API 24),Save location 选择项目保存路径。确认信息无误后点击「Finish」创建项目。

配置项目信息与API 24

15.6 导入源码与运行

项目创建完成后,将 63.ets 文件复制到 entry/src/main/ets/pages/ 目录下,打开 entry/src/main/resources/base/profile/main_pages.json,在 src 数组中添加 "pages/63" 路由注册。

在 DevEco Studio 顶部工具栏选择 Previewer(预览器)或连接真机/模拟器,点击运行按钮即可看到「多多亲子母婴」应用的完整运行效果。可以切换底部 6 Tab(奶粉/玩具/课程/社区/订单/我的),在奶粉 Tab 点击加入购物车查看底部抽屉、点击收藏 ❤️ 删除收藏,在订单 Tab 取消订单/编辑地址,在我的 Tab 查看宝宝成长档案与月度消费柱状图,体验完整的亲子母婴电商闭环。

十六、总结

「多多亲子母婴」是一个完整的亲子母婴电商社区应用,基于 HarmonyOS NEXT(API 24)ArkTS 声明式 UI 框架构建,单文件 63.ets 约 1200 行代码实现了全部功能。

应用的核心亮点包括:

  1. 六组件多 Tab 电商架构:与之前的单文件单组件应用不同,本应用采用六个独立 @Component 子组件(MilkTab 奶粉/ToyTab 玩具/CourseTab 课程/CommunityTab63 社区/OrderTab63 订单/ProfileTab63 我的)+ 一个 @Entry 主入口 DuoDuoQinZiApp 的多组件架构。主入口通过 @State activeTab + if/else if 条件渲染分发显示对应子组件,每个子组件独立管理自己的 @State 与弹窗,互不干扰。@Builder bottomTabItem(icon, label, tabIdx) 可复用构建底部 6 Tab 项,激活态亲子粉字粗体+图标 opacity 1.0,非激活灰字+图标 opacity 0.45,底部导航栏纯白底+粉系阴影 shadow({ radius: 8, color: 'rgba(236,64,122,0.12)', offsetY: -2 })。这种多组件架构让每个 Tab 的代码独立、职责清晰、可复用性强,是中大型 ArkTS 应用的标准架构模式。
  2. 亲子粉 + 婴幼蓝 + 温馨亲子风视觉体系:亲子粉 #EC407A 主色 + 婴幼蓝 #42A5F5 强调色 + 底色 #FFF5F8 温馨亲子风,价格红 #E53935、金色 #FFB300 评分、深紫 #AB47BC 课程、深蓝 #1565C0 订单。六个 Tab 各自拥有独立的渐变头——奶粉粉蓝 angle:135、玩具蓝粉 angle:180、课程紫粉 angle:135、社区粉紫 angle:135、订单深蓝 angle:180、我的粉蓝 angle:180——每个 Tab 通过渐变头的色彩组合快速区分模块,同时保持粉蓝主色调的统一。
  3. 分类独立色 + 玩具独立色 + 粉系渐变柱状图三色独立色体系CAT_63 8 个分类各自独立 color/bg(奶粉粉/洗护蓝/服饰绿/出行橙/辅食紫/寝具青/营养红/绘本靛),分类横滑中使用;toys 9 款玩具各自独立 color/bg(红/蓝/橙/绿/紫/青/靛/粉/橙红),三列网格中图标区 bg+年龄段 color+标签 color 独立,9 款玩具 9 种色彩营造缤纷童趣;SPEND_BARS_63 6 个月消费数据各自独立粉系渐变色(3月 #F8BBD0 最浅→8月 #C2185B 最深),月度消费柱状图从浅到深形成「育儿支出逐月攀升」可视化;orders63 6 笔订单各自独立 statusColor(待发货橙/已发货蓝/已完成绿/已退款灰)。三色独立色体系是本应用最具视觉特色的设计。
  4. 6 大 Tab 完全差异化布局:奶粉=分类横滑(CAT_63 8分类独立色)+单列段位大卡(第1款 milkPowders[0] 完整信息配方/评分/已售)+双列网格(7款)+我的收藏列表(可删除);玩具=年龄段 chips(AGE_CHIPS 5个)+三列网格(9款独立色);课程=早教列表(6门,免费试听 if(trialAvail) 条件渲染+立即报名);社区=宝妈动态(5条,images 图片数组 1-3张+点赞评论分享+话题标签);订单=状态分段器(ORDER_SEG_63 5状态)+订单列表(取消订单 if(status===‘待发货’) 条件渲染+编辑地址,filtered 计算属性筛选);我的=粉蓝渐变头+三列统计卡(悬浮重叠 margin top:-12+shadow)+宝宝成长档案(身高88cm粉/体重12.5kg蓝/乳牙20颗绿/发育A+金)+月度消费柱状图(粉系渐变色)+优惠券(三色对应品类)。6 大 Tab 布局完全不同,是亲子母婴电商差异化设计的典范。
  5. 月度消费柱状图(粉系渐变色,纯声明式实现):我的 Tab 内嵌月度消费柱状图(6 个月 SPEND_BARS_63,粉系渐变色从浅 #F8BBD0 到深 #C2185B,8 月消费 ¥2380 最高柱子最深),通过纯声明式实现——Row 布局 + ForEach 渲染 6 根柱子 + 每根柱子 Column layoutWeight(1) width 20 backgroundColor(b.color) borderRadius(3) justifyContent(FlexAlign.End) 实现从底部向上生长 + 顶部金额显示 + 底部月份标签,无需 Canvas,纯 Column + 背景色 + layoutWeight 实现柱状图,是 ArkTS 声明式 UI 数据可视化的标准模式。
  6. 4 种模态弹窗覆盖底部抽屉+居中两种样式:加入购物车底部抽屉(规格 3 档单罐/三罐/六罐+数量步进器+确认按钮,maxHeight 70%)、删除收藏居中弹窗(💔 图标+可移除 filter)、取消订单居中弹窗(📦 图标+全额退款 3-5 工作日到账提示+红色确认按钮)、编辑收货地址底部抽屉(TextArea 多行输入+保存按钮,maxHeight 55%)。底部抽屉用于需要输入或选择的复杂交互,居中用于轻量确认交互,是电商 App 弹窗设计的标准模式。
  7. 强类型数据模型 + 特色字段 + 条件渲染 + 计算属性:8 个 interface 约束所有数据结构,MilkPowder 含 stage 段位/origin 产地/formula 配方特色字段,ToyItem 含独立 color/bg,CourseItem 含 trialAvail 免费试听,MomPost 含 images 图片数组,OrderItem63 含 statusColor 状态色,BarData63 含粉系渐变色。条件渲染 if (c.trialAvail)(免费试听按钮)、if (o.status === '待发货')(取消订单按钮)实现差异化交互。计算属性 get filtered() 根据 segIdx 筛选订单列表,是 ArkTS 计算属性的标准用法。
  8. 完整母婴电商闭环:从奶粉玩具选购(分类浏览+加入购物车+收藏)、早教课程报名(免费试听+立即报名)、宝妈社区交流(动态+图片+点赞评论)、订单管理(状态筛选+取消订单+编辑地址)到个人中心(消费统计+宝宝档案+优惠券),覆盖亲子母婴电商的完整业务闭环。8 款奶粉+9 款玩具+6 门课程+5 条宝妈动态+6 笔订单的丰富预置数据,让应用开箱即可体验完整的母婴电商流程。

多多亲子母婴整体运行效果

对于希望学习 HarmonyOS ArkTS 声明式 UI 开发的读者,本应用提供了一个完整的多组件电商架构参考范本:如何通过多个独立 @Component 子组件拆分复杂应用、如何通过主入口 @State activeTab + if/else if 分发显示子组件、如何用 @Builder 复用底部 Tab 项构建、如何用条件渲染 if() 实现差异化交互(免费试听/取消订单)、如何用计算属性 get filtered() 实现列表筛选、如何用纯声明式(Column+背景色+layoutWeight)实现柱状图数据可视化、如何用底部抽屉(Stack alignContent:Bottom+maxHeight)和居中弹窗(Stack alignContent:Center)两种弹窗样式、如何用 margin top:-12 实现悬浮卡片重叠效果、如何用分类独立色/玩具独立色/粉系渐变柱状图三色独立色体系营造缤纷童趣、如何用六个 Tab 各自独立渐变头快速区分模块同时保持主色调统一。将源码导入 DevEco Studio(API 24)即可直接运行预览,在此基础上可以扩展更多母婴商品数据、接入真实电商 API 与支付物流、增加宝宝成长曲线图表、增加宝妈私信与关注功能、增加母婴知识库与育儿问答,打造一个真正可用的亲子母婴电商社区平台。

妈咪放心选,宝宝健康成长。愿每一位新手妈妈都能在多多亲子母婴里找到适合宝宝的奶粉与玩具,在宝妈社区里分享育儿的喜悦与焦虑,在宝宝成长档案里记录每一个珍贵的瞬间,也能在代码与粉蓝渐变之间找到属于自己的那份温馨与从容。🍼🧸🎓——多多亲子母婴,妈咪放心选,宝宝健康成长。

标签: HarmonyOS · ArkTS · API 24

Logo

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

更多推荐