萌爪星球宠物O2O实战:HarmonyOS API 24下奶黄暖棕主题、五Tab全差异化布局与宠物社交闭环的完整拆解
萌爪星球宠物O2O实战:HarmonyOS API 24下奶黄暖棕主题、五Tab全差异化布局与宠物社交闭环的完整拆解
每一只毛孩子,都值得被温柔以待。本文完整拆解一个基于鸿蒙 HarmonyOS API 24 与 ArkUI 声明式框架构建的「萌爪星球」宠物服务与社交应用,覆盖奶黄暖棕主题配色系统、宠物档案头部、疫苗到期提醒、三种焦糖橙渐变视觉语言、五Tab全差异化布局、服务网格跨页跳转、洗护项目热门标签呼吸动画、社区宠友横滑与瀑布流动态、点赞交互闭环、月度服务消费渐变柱状图、宠物档案增删改、三大弹窗复用设计等核心技术点,附完整源码与运行效果截图。
一、引言:宠物经济的数字化浪潮
在宠物经济高速发展的今天,养宠已经从传统的"喂粮、遛弯、看病"三件套,演变为一种精细化、社交化、智能化的生活方式。宠物O2O,以其"线上预约、线下服务、全程追踪、社交分享"的特点,正在成为宠物经济的关键基础设施。从洗护美容到医疗寄养,从训导零食到写真接送,每一个服务品类都需要精准的门店匹配与透明的价格呈现。然而,服务价格不透明、门店选择困难、宠物档案分散、社交分享缺失,正成为铲屎官们获取可靠宠物服务的现实痛点。
正是在这样的背景下,萌爪星球类宠物O2O应用应运而生,并迅速成为宠物经济数字化的核心载体。一个优秀的宠物服务应用,不仅需要覆盖从"选服务"到"约门店"再到"晒萌宠"的完整业务闭环,还要在视觉设计、交互体验、数据呈现上做到温暖而专业,让铲屎官在指尖滑动间便能完成查服务、约洗护、看订单、晒萌宠、管档案的全流程。
本文将深入剖析一个基于鸿蒙 HarmonyOS API 24 与 ArkUI 声明式开发框架构建的萌爪星球宠物服务与社交应用。该应用以"首页"为核心,横向扩展出服务、社区、订单、我的五大功能模块,每个模块采用完全不同的布局风格——首页Banner+服务网格+门店列表、服务分类横滑+项目卡列表、社区宠友横滑+瀑布流动态、订单柱状图+订单列表、我的用户卡+宠物档案,形成了一个完整闭环的宠物O2O生态。它不仅是一个功能完备的演示项目,更是一份典型的 ArkUI 工程实践范本,涵盖了状态管理、组件化设计、Stack弹窗叠加、单动画状态联动、奶黄暖棕视觉语言、宠物档案增删改、社区点赞交互闭环、三大弹窗复用设计等关键技术点。

二、应用整体架构概览
整个应用采用了"单入口 + 五Tab全差异化布局 + Stack弹窗叠加"的宠物O2O应用架构。入口组件 Page817 作为整个应用的根容器,负责管理当前激活的 Tab 状态、动画状态以及三个弹窗的显隐状态。
应用一共划分为五个一级页面:首页(HOME)、服务(SERVICE)、社区(COMMUNITY)、订单(ORDER)以及我的(PROFILE)。与之前项目"页面布局风格统一或半差异化"不同,本应用的五个 Tab 每个布局完全不同——首页采用宠物健康季Banner+全部服务网格+附近好店列表、服务采用洗护美容分类横滑+项目卡列表+温馨提示、社区采用宠友横滑头像卡+瀑布流动态双列、订单采用月度服务消费柱状图+订单列表、我的采用用户卡+宠物档案+优惠券+常用门店,这种"一页一型"的差异化设计最大程度还原了真实宠物O2O App的交互体验。底部导航采用单排五Tab布局,激活Tab文字焦糖色加粗,简洁统一。
在数据层面,应用定义了一套完整的类型系统,包括色彩主题、宠物档案元数据、洗护项目元数据、门店元数据、社区动态元数据、宠物订单元数据,并通过常量数组将数据与 UI 表现层解耦。所有业务数据以 @Observed 装饰的可观察类为载体(PetProfile/GroomItem/ShopItem/FeedPost/PetOrder),配合 2 个宠物、6 个洗护项目、5 个门店、8 条社区动态、6 个订单的模拟数据,模拟了一个真实的宠物O2O数据环境。所有数据类都带有 constructor 构造函数,使用 new ClassName(...) 方式实例化。应用还定义了 10 个工具函数(serviceIconColor/orderTagColor/feedLikeColor/petAgeOf/spendBarH/likeFeed/removePetById/removeOrderById/updatePetById/updateOrderTitle),将颜色映射、数据计算、增删改逻辑从 UI 层抽离,实现数据与逻辑分离。
在交互层面,应用通过 Stack 布局叠加实现了三类弹窗的显示:添加宠物档案、修改信息、确认删除。与之前项目"每个弹窗独立 Stack 叠加"一致,本应用三个弹窗各自独立 Stack 叠加,共享 modalOverlay 统一遮罩封装。特别的是,本应用独创了"弹窗复用设计"——修改信息弹窗(editModal)通过 editTarget 状态区分修改对象('pet’宠物档案或’order’订单标题),确认后根据 editTarget 调用不同的更新函数(updatePetById 或 updateOrderTitle),一个弹窗服务两类数据修改,大幅减少了重复代码。确认删除弹窗(delModal)确认后同时调用 removePetById 和 removeOrderById,因为 delIdx 可能是宠物或订单 ID,两个列表都按 ID 删除,实现一个弹窗服务两类数据删除。添加宠物档案弹窗(addModal)确认后 petList.push 新宠物档案,种类含"猫"时自动选🐱头像否则🐱头像,体现了智能默认值设计。
在视觉层面,应用独创了"奶黄暖棕主题"设计——bg #FBF6E9 奶黄暖棕底,caramel #D98324 焦糖色为主色,orange #F08A24 活力橘为辅助色,title #4A3418 焦糖棕黑而非纯黑,与奶黄底形成高对比度的同时带有温暖质感。应用的渐变设计是色彩系统的核心亮点——首页宠物健康季Banner使用 linearGradient 135 度从 caramel 焦糖到 orange 活力橘的两色渐变,订单月度服务消费柱状图使用 linearGradient 180 度从 orange 活力橘到 caramelD 深焦糖的垂直渐变,我的用户卡使用 linearGradient 135 度从 caramelD 深焦糖到 orange 活力橘的两色渐变,三种不同角度和配色的焦糖橙渐变贯穿整个应用,形成了统一而丰富的焦糖橙渐变视觉语言,是本应用色彩设计的核心特色。宠物档案头部采用圆形头像+名称品种年龄+体重疫苗+会员等级的卡片布局,疫苗到期提醒(奶昔疫苗还有45天到期绿字)体现宠物健康管理,是本应用头部的特色设计。应用还独创了"单动画状态联动"设计——aboutToAppear 中 setInterval 750ms 切换 breath 布尔动画状态,breath 同时用于服务页热门标签透明度动画(opacity 0.6-1,duration 600ms)和首页Banner🐾图标旋转动画(rotate 8/-8度,duration 700ms EaseInOut),一个动画状态驱动两种不同的动效,形成整体协调的联动动效体系。

下面逐页展示应用的五大功能模块运行效果。
三、服务页:洗护美容明码标价与热门标签呼吸动画
服务页以"洗护美容"为核心,采用"分类横滑+项目卡列表+温馨提示"的三层布局。

页面顶部第一部分是标题"🛁 洗护美容 · 明码标价"加粗 13 号 margin 下 12。
第二部分是分类横滑,Scroll horizontal 遍历 6 个分类(全部/狗狗专区/猫咪专区/深层清洁/造型修剪/皮肤护理),每个分类 Text 10 号,选中(c === ‘全部’)时白字焦糖底,未选中时 sub 灰字白底边框 1px line 色,padding 左右 14 上下 7,圆角 16。分类横滑让用户快速筛选服务品类,选中态焦糖底高亮。
第三部分是项目卡列表,遍历 6 个洗护项目,每个项目横向布局,padding 12,白底圆角 14,border 1px line 色,alignItems Center,包含:
左侧:图标 58×58 圆角 16 dark 底,图标 28 号,textAlign Center。
中部:项目信息列(layoutWeight 1,左对齐,space 5):
- 名称行:项目名(加粗 12 号)+ 热门标签(hot 为 true 时显示"🔥 热门"red 红字 8 号,chip 底圆角 6 padding 左右 5 上下 2,带 breath 透明度动画
this.breath ? 0.6 : 1,animation duration 600ms) - 描述:项目描述(sub 灰 9 号 maxLines 1)
- 时长:“⏱️ " + 时长 + " · 专业宠物美容师”(text3 灰 8 号)
右侧:价格按钮列(右对齐,space 6):
- 价格:caramel 焦糖色 14 号加粗
- 下单按钮:Column 包裹"下单"白字 9 号加粗,padding 左右 12 上下 5,圆角 10,caramel 焦糖底
6 个洗护项目覆盖六大品类:精致洗护套餐(¥128,60分钟,热门,深层清洁+护毛素+吹干造型)、萌系造型修剪(¥188,90分钟,热门,泰迪装/比熊装/猫咪修面)、药浴除蚤(¥158,70分钟,皮肤检测+药浴+体外驱虫)、SPA香氛护理(¥218,80分钟,精油按摩+香氛+美毛)、猫咪专属洗护(¥168,75分钟,热门,低应激猫舍环境+专业保定)、洁牙护理(¥298,60分钟,超声波洁牙+口腔检查)。价格从 ¥128 到 ¥298,时长从 30 到 90 分钟,3 个热门项目,完整还原了宠物洗护美容的价格体系。热门标签的 breath 呼吸动画让热门项目有了"吸引眼球"的动态感,明码标价让价格透明可信,是本应用服务页交互设计的亮点。
第四部分是温馨提示,Column 包裹 space 8,padding 14,dark 底圆角 14,alignItems Start:
- 标题:"💡 温馨提示"加粗 11 号
- 提示1:“· 8月龄以下幼宠、孕期宠物请提前告知门店”(sub 灰 9 号)
- 提示2:“· 高温天气接送服务免费,全程空调舱运输”(sub 灰 9 号)
- 提示3:“· 服务过程可全程摄像头直播查看”(sub 灰 9 号)
温馨提示 dark 底与项目卡白底形成视觉层次,三条提示覆盖幼宠/高温/摄像头三大关注点,体现专业服务意识。
四、社区页:宠友横滑与瀑布流动态双列及点赞交互闭环
社区页以"萌宠社区"为核心,独创"宠友横滑+瀑布流动态双列"的社交布局。

页面顶部第一部分是标题"🌍 萌宠社区 · 今日热门"加粗 13 号 margin 下 12。
第二部分是宠友横滑,Scroll horizontal 遍历 8 条动态的作者,每个宠友卡片 width 74,padding 8,白底圆角 14,border 1px line 色,alignItems Center,包含:圆形头像 54×54 圆角 27 dark 底,头像 26 号 + 作者名(title 焦糖棕黑 9 号加粗 maxLines 1)+ 宠物种类(caramel 焦糖色 8 号)。宠友横滑让用户快速浏览活跃铲屎官,点击可查看其动态。
第三部分是瀑布流动态双列,Flex wrap 遍历 8 条动态,每个动态卡片 width 47.5%,padding 10,白底圆角 14,margin 5,alignItems Start,包含:
- 作者行:Row space 8,圆形头像 32×32 圆角 16 dark 底,头像 18 号 + 作者信息列(layoutWeight 1,左对齐):作者名(title 焦糖棕黑 10 号加粗)+ 时间 + " · " + 宠物种类 + “家长”(text3 灰 8 号)。
- 内容:动态文案(title 焦糖棕黑 10 号 maxLines 2,minFontSize 9 maxFontSize 10)。
- 图片区:动态图片图标 46 号,width 100% height 100,圆角 12,dark 底,textAlign Center。
- 互动行:Row space 12,"❤️ " + 点赞数(feedLikeColor(liked) 动态着色:已赞 red 红字/未赞 text3 灰字,加粗 10 号,带 animation duration 500ms,点击触发 likeFeed(this.feedList, f.id) 函数)+ "💬 " + 评论数(text3 灰 10 号)+ Column layoutWeight 1 + "🔗"分享图标 12 号。
8 条社区动态覆盖八大萌宠场景:布丁妈妈(柯基,剪完造型回头率200%,328赞46评论,已赞)、铲屎官老王(橘猫,大橘为重减肥第7天失败,892赞132评论)、雪球爸爸(萨摩耶,夏日降温神器,456赞67评论)、咕噜姐姐(英短,体检全过牙齿好,233赞28评论,已赞)、旺财家族(柴犬,学会握手训导课没白上,612赞95评论)、奶昔阿姨(布偶,写真出炉仙女猫,1204赞210评论,最高赞)、土豆叔叔(法斗,药浴初体验全程乖,345赞52评论)、团子奶奶(银渐层,绝育后伊丽莎白圈日常,567赞88评论,已赞)。点赞数从 233 到 1204,评论数从 28 到 210,2 条已赞,完整还原了宠物社区的内容生态。
点赞交互闭环是本应用社区页的核心亮点——likeFeed 函数遍历 feedList 找到对应 ID 的动态,切换 liked 状态(!o.liked),点赞数相应 ±1,然后用 list.splice(i, 1, new FeedPost(…)) 替换原对象。因为 @Observed 装饰的类在替换整个对象时会触发 UI 刷新,而直接修改对象属性可能不触发响应式更新,所以采用 splice 替换对象的方式实现点赞状态的响应式更新。feedLikeColor 函数根据 liked 状态动态返回红色(已赞)或灰色(未赞),❤️文字带 animation duration 500ms 让点赞有了"心动"的动效,形成了完整的点赞交互闭环。瀑布流双列布局(Flex wrap 47.5%宽)让动态卡片自动排列成双列,模拟真实社交App的瀑布流效果,是本应用社区页布局设计的特色。
五、订单页:月度服务消费渐变柱状图与订单增删改
订单页以"我的订单"为核心,独创"月度服务消费柱状图+订单列表增删改"的复合布局。

页面顶部第一部分是标题"📦 我的订单 · 服务记录"加粗 13 号 margin 下 12。
第二部分是月度服务消费柱状图,Column 包裹 space 10:
- 标题:"💰 月度服务消费(元)"加粗 11 号。
- 柱状图 Row space 8:遍历 MONTH_LABELS 6 个月(3月-8月),每栏 alignItems Center,包含:顶部数值(SPEND_BARS[i],sub 灰 8 号)+ 柱子(width 16,height = spendBarH(v) = Math.round(v / 700 * 90) 动态计算,圆角 8,linearGradient 180 度从 orange #F08A24 活力橘到 caramelD #B0650F 深焦糖的垂直渐变)+ 底部月份(text3 灰 8 号)。柱状图卡片 padding 上下 10 下 12,白底圆角 14,justifyContent Center。
6 个月消费数据:3月180/4月320/5月260/6月540/7月410/8月690(最高),8月 690 元最高(暑期宠物消费高峰),3月 180 最低。柱子高度 = v/700*90 动态计算,以 700 元为基准映射到 90px 高度,180 度活力橘→深焦糖垂直渐变让柱子有了"从上到下由浅入深"的视觉感,是本应用数据可视化的核心亮点。
第三部分是订单列表,遍历 6 个订单,每个订单卡片 Column 包裹 space 10,padding 12,白底圆角 14,border 1px line 色,包含:
- 门店状态行:Row space 8,订单图标 16 号 + 门店名(sub 灰 9 号 layoutWeight 1 maxLines 1)+ 状态(orderTagColor(status) 动态着色:已完成 green 绿/服务中 orange 橘/待到店 blue 蓝/其他 text3 灰,加粗 9 号)。
- 订单信息行:Row space 12,订单图标 52×52 圆角 14 dark 底,图标 26 号 + 订单信息列(layoutWeight 1,左对齐):订单标题(title 焦糖棕黑 12 号加粗)+ "📅 " + 日期(text3 灰 9 号)+ 金额(caramel 焦糖色 14 号加粗,右对齐)。
- 操作按钮行:Row space 10,修改预约按钮(Column 包裹"修改预约"caramel 焦糖字 10 号加粗,padding 左右 14 上下 7,圆角 10,chip 底,点击触发 editModal 修改信息弹窗,editTarget=‘order’,editTargetId=订单ID,editName=订单标题)+ 删除订单按钮(Column 包裹"删除订单"red 红字 10 号加粗,padding 左右 14 上下 7,圆角 10,dark 底,点击触发 delModal 确认删除弹窗,delIdx=订单ID)+ Column layoutWeight 1 + “📞 联系门店”(blue 蓝字 10 号)。
6 个订单覆盖六大服务品类:精致洗护·布丁(¥128,服务中,08-21 10:30,萌爪旗舰店·滨江店)、萌系造型·布丁(¥188,已完成,08-14,汪汪美容工坊)、年度体检·奶昔(¥358,已完成,08-02,宠乐星球·中央店)、药浴除蚤·布丁(¥158,已完成,07-22,萌爪旗舰店·滨江店)、寄养三天·奶昔(¥306,已完成,07-15,猫语生活馆)、洁牙护理·布丁(¥298,待到店,08-25,宠乐星球·中央店)。金额从 ¥128 到 ¥358,状态覆盖服务中/已完成/待到店三档,完整还原了宠物订单的服务历史。订单状态三档着色(已完成绿/服务中橘/待到店蓝)让用户一眼判断订单状态,修改预约和删除订单按钮实现订单增删改闭环,联系门店蓝字提供客服入口,是本应用订单页交互设计的核心亮点。
六、我的页:用户卡焦糖橙渐变与宠物档案增删改
我的页以"个人中心"为核心,采用"用户卡+宠物档案+优惠券+常用门店"的四层布局。

页面顶部第一部分是用户卡,Column 包裹 space 10,padding 16,圆角 18,linearGradient 135 度从 caramelD #B0650F 深焦糖到 orange #F08A24 活力橘的两色渐变:
- 用户信息行:Row space 12,👩头像 58×58 圆角 29,半透明白底 rgba(255,255,255,0.25),32 号 + 用户信息列(layoutWeight 1,左对齐):"布丁妈妈 · 金牌铲屎官"白字 14 号加粗 + "萌力值 2860 · 打卡 96 天"白字 9 号 0.85 透明度 + "🐱 2只"金 #FFE082 字 11 号加粗(右对齐)。
用户卡使用 135 度深焦糖→活力橘渐变,与首页Banner的 135 度焦糖→活力橘渐变、订单柱状图的 180 度活力橘→深焦糖渐变形成统一的焦糖橙渐变视觉语言,但用户卡用深焦糖→活力橘的深色渐变营造"高端会员"的质感,金色"🐱2只"文字突出宠物数量,半透明白底头像在深色渐变上清晰可读,是本应用我的页视觉设计的核心亮点。
第二部分是我的宠物档案,Row 标题行:"🐾 我的宠物档案"加粗 13 号 layoutWeight 1 + "➕ 添加"caramel 焦糖色 10 号加粗(点击触发 addModal 添加宠物档案弹窗)。
下方遍历 2 个宠物档案,每个宠物卡片 Column 包裹 space 10,padding 12,白底圆角 14,border 1px line 色,包含:
- 宠物信息行:Row space 12,圆形头像 60×60 圆角 30 dark 底,头像 32 号 + 宠物信息列(layoutWeight 1,左对齐):名称 + " · " + 品种(title 焦糖棕黑 12 号加粗)+ 种类 + " · " + petAgeOf(ageMonths) + " · " + 体重(sub 灰 9 号)+ "💉 疫苗至 " + 疫苗日期(green 绿字 9 号)。
- 操作按钮行:Row space 10,编辑按钮(Column 包裹"✏️ 编辑"caramel 焦糖字 10 号加粗,padding 左右 14 上下 7,圆角 10,chip 底,点击触发 editModal 修改信息弹窗,editTarget=‘pet’,editTargetId=宠物ID,editName=宠物名,editWeight=体重)+ 移除按钮(Column 包裹"🗑️ 移除"red 红字 10 号加粗,padding 左右 14 上下 7,圆角 10,dark 底,点击触发 delModal 确认删除弹窗,delIdx=宠物ID)+ Column layoutWeight 1 + “📋 体检报告”(blue 蓝字 10 号)。
2 个宠物档案:布丁(🐕狗狗,柯基,2岁2个月,12.4kg,疫苗至2026-11-20)、奶昔(🐱猫咪,布偶,1岁6个月,4.2kg,疫苗至2026-10-05)。petAgeOf 函数将月龄转换为岁/月显示(<12月显示X个月,>=12月显示X岁或X岁X个月),2岁2个月和1岁6个月的显示让宠物年龄更直观。疫苗至绿字体现宠物健康管理,编辑和移除按钮实现宠物档案增删改闭环,体检报告蓝字提供健康档案入口,是本应用我的页交互设计的核心亮点。
第三部分是优惠券卡,Row space 10:"🎁"图标 20 号 + “优惠券 4 张 · 最高抵 ¥50”(sub 灰 10 号 layoutWeight 1)+ "使用 >"caramel 焦糖色 10 号加粗。padding 12,白底圆角 14。
第四部分是常用门店卡,Row space 10:"📍"图标 20 号 + “常用门店 · 萌爪旗舰店·滨江店”(sub 灰 10 号 layoutWeight 1)+ "切换 >"caramel 焦糖色 10 号加粗。padding 12,白底圆角 14。
优惠券和常用门店两张卡片白底圆角,与宠物档案卡片视觉统一,提供快捷入口。
七、色彩主题系统:奶黄暖棕主题与三种焦糖橙渐变视觉语言
7.1 色彩接口定义
任何一款设计精良的应用,背后都有一套严谨的色彩规范。该应用首先通过一个 ColorPalette 接口,将整个应用所需的色彩抽象为一个结构化的契约,共 21 个字段:
interface ColorPalette {
bg: string; card: string; dark: string; darkL: string;
title: string; sub: string; text3: string;
caramel: string; caramelD: string; orange: string;
red: string; green: string; blue: string; purple: string; pink: string;
line: string; tabOn: string; mask: string; chip: string;
}
- bg:页面整体背景色 #FBF6E9,奶黄暖棕底,营造宠物O2O的温暖治愈氛围。
- caramel:主色调 #D98324,焦糖色,用于按钮、强调文字、激活Tab、价格、渐变起始色等高优先级视觉元素,是宠物温暖的标志性色彩(甜蜜、治愈、温暖)。
- caramelD:深主色 #B0650F,深焦糖,用于渐变结束色、柱状图底部色,与 caramel 形成同色系深浅对比。
- orange:辅助色 #F08A24,活力橘,用于渐变结束色、柱状图顶部色、服务中状态,与焦糖色形成"焦糖橘"的渐变组合,象征宠物的活力与热情。
- title:文字主色 #4A3418,焦糖棕黑,用于主标题文字,与奶黄底形成高对比度的同时带有温暖质感(非纯黑,更柔和)。
- mask:遮罩色 rgba(74,52,24,0.55),焦糖棕黑半透明,用于弹窗遮罩,与 title 同色系保证遮罩质感统一。
- chip:标签底色 rgba(217,131,36,0.12),焦糖色 12% 透明度,用于标签、数据卡底色、按钮底色等,与 caramel 同色系保证标签质感统一。

7.2 色彩实例化
const COLORS: ColorPalette = {
bg: '#FBF6E9', card: '#FFFFFF', dark: '#F5EAD3', darkL: '#EDDFC0',
title: '#4A3418', sub: '#8C744D', text3: '#BCA87F',
caramel: '#D98324', caramelD: '#B0650F', orange: '#F08A24',
red: '#E05B4B', green: '#3E9E6C', blue: '#3D7DC8', purple: '#8A5FC0', pink: '#E0719B',
line: '#F0E4CB', tabOn: '#D98324',
mask: 'rgba(74,52,24,0.55)', chip: 'rgba(217,131,36,0.12)'
};
主色 #D98324 是一种温暖的焦糖色,既契合"宠物O2O"主题(甜蜜、治愈、温暖的标志性色彩),又具有亲切、柔和的气质。辅助色 #F08A24 活力橘与焦糖色形成"焦糖橘"的渐变组合,是本应用最具辨识度的色彩组合。bg #FBF6E9 奶黄暖棕底与主色同暖色系,营造出宠物O2O的温暖治愈氛围。title #4A3418 焦糖棕黑而非纯黑,与奶黄底形成高对比度的同时带有温暖质感,是本应用色彩设计的细致之处。
值得注意的是,本应用的渐变设计是色彩系统的核心亮点——首页宠物健康季Banner使用 linearGradient 135 度从 caramel #D98324 焦糖到 orange #F08A24 活力橘的两色渐变,订单月度服务消费柱状图使用 linearGradient 180 度从 orange #F08A24 活力橘到 caramelD #B0650F 深焦糖的垂直渐变,我的用户卡使用 linearGradient 135 度从 caramelD #B0650F 深焦糖到 orange #F08A24 活力橘的两色渐变。三种不同角度和配色的焦糖橙渐变贯穿整个应用(135度亮色渐变用于Banner、180度垂直渐变用于柱状图、135度深色渐变用于用户卡),形成了统一而丰富的焦糖橙渐变视觉语言,是本应用色彩设计的核心特色。
本应用的动态颜色逻辑也十分丰富——服务图标按 serviceIconColor 分五色着色(洗澡 blue 蓝/美容 pink 粉/寄养 purple 紫/医疗 green 绿/其他 orange 橘),订单状态按 orderTagColor 分四档着色(已完成 green 绿/服务中 orange 橘/待到店 blue 蓝/其他 text3 灰),社区点赞按 feedLikeColor 分两档着色(已赞 red 红/未赞 text3 灰),门店标签统一 purple 紫字 dark 底,形成了"以色定服务品类、以色定订单状态、以色定点赞状态"的多层语义着色模式。宠物档案头部三卡用不同底色对应不同数据维度,本周训练报告三卡用三种颜色对应三种数据维度,形成了"以色定数据维度"的统计着色模式。这种将所有色彩集中管理、配合多维度渐变与动态着色的做法保证了全局一致性,降低了维护成本。

八、类型定义:构建严谨的数据契约
应用通过一组带 constructor 的 @Observed 类,为每一类业务对象建立了明确的数据契约。
8.1 宠物档案、洗护项目、门店、社区动态、订单元数据
@Observed export class PetProfile {
id: number; name: string; kind: string; avatar: string;
breed: string; ageMonths: number; weight: string; vaccine: string;
constructor(...) { ... }
}
@Observed export class GroomItem {
id: number; name: string; desc: string; price: string;
duration: string; icon: string; hot: boolean;
constructor(...) { ... }
}
@Observed export class ShopItem {
id: number; name: string; distance: string; score: string;
orders: number; icon: string; tags: string[];
constructor(...) { ... }
}
@Observed export class FeedPost {
id: number; author: string; avatar: string; petKind: string;
content: string; image: string; likes: number; comments: number; liked: boolean; time: string;
constructor(...) { ... }
}
@Observed export class PetOrder {
id: number; title: string; shop: string; amount: string;
status: string; date: string; icon: string;
constructor(...) { ... }
}
PetProfile 描述宠物档案的 ID、名称、种类、头像、品种、月龄、体重、疫苗日期,用 ageMonths 数字字段配合 petAgeOf 函数转换为岁/月显示,vaccine 字段体现疫苗健康管理。GroomItem 描述洗护项目的 ID、名称、描述、价格、时长、图标、是否热门,用 hot 布尔字段体现热门标签,price/duration 字段体现价格透明度。ShopItem 描述门店的 ID、名称、距离、评分、已服务单数、图标、标签数组,用 tags 数组字段体现门店特色标签,score/orders 字段体现门店口碑。FeedPost 描述社区动态的 ID、作者、头像、宠物种类、内容、图片、点赞数、评论数、是否已赞、时间,用 liked 布尔字段体现点赞状态,likes/comments 字段体现互动数据,是本应用最复杂的数据类。PetOrder 描述宠物订单的 ID、标题、门店、金额、状态、日期、图标,用 status 字段体现订单状态(已完成/服务中/待到店)。
8.2 工具函数:颜色映射、数据计算、增删改逻辑
function serviceIconColor(name: string): string { ... }
function orderTagColor(s: string): string { ... }
function feedLikeColor(liked: boolean): string { ... }
function petAgeOf(months: number): string { ... }
function spendBarH(v: number): number { return Math.round(v / 700 * 90); }
function likeFeed(list: FeedPost[], id: number): void { ... }
function removePetById(list: PetProfile[], id: number): void { ... }
function removeOrderById(list: PetOrder[], id: number): void { ... }
function updatePetById(list: PetProfile[], id: number, name: string, weight: string): void { ... }
function updateOrderTitle(list: PetOrder[], id: number, title: string): void { ... }
10 个工具函数将颜色映射、数据计算、增删改逻辑从 UI 层抽离,实现数据与逻辑分离:
- serviceIconColor:服务图标着色,洗澡 blue/美容 pink/寄养 purple/医疗 green/其他 orange。
- orderTagColor:订单状态着色,已完成 green/服务中 orange/待到店 blue/其他 text3。
- feedLikeColor:点赞状态着色,已赞 red/未赞 text3。
- petAgeOf:月龄转岁/月显示,<12月显示X个月,>=12月显示X岁或X岁X个月。
- spendBarH:柱状图高度计算,Math.round(v / 700 * 90),以 700 元为基准映射到 90px 高度。
- likeFeed:点赞/取消点赞,遍历找到对应ID,切换liked状态,likes±1,splice替换对象触发响应式更新。
- removePetById/removeOrderById:按ID删除,倒序遍历找到对应ID,splice删除。
- updatePetById/updateOrderTitle:按ID更新,遍历找到对应ID,splice替换对象(保留其他字段,更新指定字段)。
所有数据类都带有 constructor 构造函数,使用 new ClassName(...) 方式实例化,配合 @Observed 装饰器实现响应式更新。10 个工具函数将逻辑从 UI 层抽离,实现数据与逻辑分离,特别是 likeFeed/updatePetById/updateOrderTitle 采用 splice 替换对象的方式触发 @Observed 响应式更新,是本应用工程化设计的核心亮点。
九、配置驱动:常量数据与模拟数据规模
9.1 Tab 导航与常量配置
const TAB_LIST: TabMeta[] = [
{ icon: '🐾', label: '首页' },
{ icon: '🛁', label: '服务' },
{ icon: '🌍', label: '社区' },
{ icon: '📦', label: '订单' },
{ icon: '👤', label: '我的' }
];
const MONTH_LABELS: string[] = ['3月', '4月', '5月', '6月', '7月', '8月'];
const SPEND_BARS: number[] = [180, 320, 260, 540, 410, 690];
const SERVICE_GRIDS: TabMeta[] = [
{ icon: '🛁', label: '洗澡' }, { icon: '✂️', label: '美容' },
{ icon: '🏥', label: '医疗' }, { icon: '🏨', label: '寄养' },
{ icon: '🎓', label: '训导' }, { icon: '🍖', label: '零食' },
{ icon: '📸', label: '写真' }, { icon: '🚗', label: '接送' }
];
Tab 导航采用 TabMeta 对象数组配置,包含图标和名称,共 5 个 Tab。底部 tabBar 通过 ForEach 遍历 TAB_LIST,每个 Tab 用 Column 包裹图标(19 号)+ 文字(9 号,激活态 tabOn 焦糖色加粗,非激活 text3 灰),padding 上 7 下 6,layoutWeight 1 均分,点击切换 tabIdx。底部白底圆角 16。MONTH_LABELS 常量定义6个月标签,SPEND_BARS 常量定义月度消费数据(180/320/260/540/410/690,8月最高),SERVICE_GRIDS 常量定义8个服务品类(洗澡/美容/医疗/寄养/训导/零食/写真/接送)。
9.2 模拟数据规模
const GROOM_LIST: GroomItem[] = [ new GroomItem(1, '精致洗护套餐', '深层清洁+护毛素+吹干造型', '¥128', '60分钟', '🛁', true), ... ];
const SHOP_LIST: ShopItem[] = [ new ShopItem(1, '萌爪旗舰店·滨江店', '850m', '4.9', 12680, '🐾', ['24h营业', '可摄像头']), ... ];
const FEED_INIT: FeedPost[] = [ new FeedPost(1, '布丁妈妈', '👩', '柯基', '布丁今天剪完造型,回头率200%!', '🐕', 328, 46, true, '2小时前'), ... ];
const PET_INIT: PetProfile[] = [ new PetProfile(1, '布丁', '狗狗', '🐕', '柯基', 26, '12.4kg', '2026-11-20'), ... ];
const ORDER_INIT: PetOrder[] = [ new PetOrder(1, '精致洗护 · 布丁', '萌爪旗舰店·滨江店', '¥128', '服务中', '08-21 10:30', '🛁'), ... ];
七组常量数据承载了萌爪星球的全部业务信息:SERVICE_GRIDS 8 个服务品类、GROOM_LIST 6 个洗护项目(价格 ¥128-¥298,时长 30-90分钟,3 个热门)、SHOP_LIST 5 个门店(距离 850m-2.8km,评分 4.6-4.9,已服务 4510-15320 单,每店 2 个标签)、FEED_INIT 8 条社区动态(点赞 233-1204,评论 28-210,2 条已赞,覆盖柯基/橘猫/萨摩耶/英短/柴犬/布偶/法斗/银渐层八大品种)、PET_INIT 2 个宠物档案(布丁柯基2岁2个月12.4kg/奶昔布偶1岁6个月4.2kg)、ORDER_INIT 6 个订单(金额 ¥128-¥358,1 服务中 1 待到店 4 已完成)、MONTH_LABELS+SPEND_BARS 6 个月消费数据。所有 @Observed 类数据使用 new ClassName(...) 构造函数实例化,TabMeta 使用对象字面量。10 个工具函数处理颜色映射、数据计算、增删改逻辑,是本应用唯一的逻辑层。
十、动态颜色与单动画状态:数据驱动的视觉映射与联动动效
应用通过动态颜色逻辑和单动画状态联动,将业务数据转化为视觉色彩与动态效果,实现"数据即配色、状态即动效"。
10.1 动态颜色逻辑
本应用的动态颜色逻辑分布在多个维度:
- 服务图标着色:
serviceIconColor(name),洗澡 blue 蓝/美容 pink 粉/寄养 purple 紫/医疗 green 绿/其他 orange 橘,五色服务品类着色让用户一眼判断服务类型。 - 订单状态着色:
orderTagColor(s),已完成 green 绿/服务中 orange 橘/待到店 blue 蓝/其他 text3 灰,四档订单状态着色让用户一眼判断订单流转状态。 - 社区点赞着色:
feedLikeColor(liked),已赞 red 红/未赞 text3 灰,两档点赞状态着色让用户一眼判断点赞状态。 - 门店标签着色:统一 purple 紫字 dark 底,门店特色标签用紫色突出。
- 疫苗状态着色:疫苗至 green 绿字,体现宠物健康管理。
- 距离着色:门店距离 blue 蓝字,突出距离信息。
- 服务网格跨页跳转:8 个服务图标点击 tabIdx=1 跳服务 tab,实现跨页面导航。
10.2 单动画状态联动
aboutToAppear(): void {
this.timer = setInterval(() => { this.breath = !this.breath; }, 750);
}
aboutToDisappear(): void {
if (this.timer > 0) { clearInterval(this.timer); }
}
aboutToAppear 中启动 setInterval 750ms 切换 breath 布尔动画状态,aboutToDisappear 中判断 timer > 0 时 clearInterval 清理定时器避免内存泄漏。一个动画状态同时驱动两种不同的动效:
- breath 用于服务页热门标签透明度动画:
this.breath ? 0.6 : 1,animation duration 600ms,仅热门项目(hot === true)的"🔥 热门"标签有呼吸动画,营造出"热门吸引眼球"的动态感。非热门项目无动画,视觉上区分热门/非热门。 - breath 用于首页Banner🐾图标旋转动画:
.rotate({ angle: this.breath ? 8 : -8 }),animation duration 700ms EaseInOut,Banner 右侧的🐾图标随 breath 状态在 8 度和 -8 度之间旋转,营造出"萌爪摇摆"的可爱动效,与宠物主题高度契合。
一个 breath 动画状态同时驱动热门标签透明度和Banner图标旋转两种动效,750ms 周期同步切换,形成整体协调的联动动效体系。热门标签的呼吸动画让热门项目有了"吸引眼球"的动态感,Banner🐾图标旋转动画让首页有了"萌爪摇摆"的可爱感,两种动效各司其职,让静态的页面有了"鲜活"的动态感,是本应用动效设计的核心亮点。社区点赞❤️文字带 animation duration 500ms,点击点赞时有"心动"的动效反馈,形成了完整的点赞交互动效闭环。
十一、入口组件:应用的骨架与五Tab全差异化布局
11.1 组件声明与状态
@Entry
@Component
struct Page817 {
@State tabIdx: number = 0;
@State breath: boolean = false;
@State addModal: boolean = false;
@State editModal: boolean = false;
@State delModal: boolean = false;
@State delIdx: number = 0;
@State addName: string = '';
@State addKind: string = '';
@State editName: string = '';
@State editWeight: string = '';
@State editTarget: string = 'pet';
@State editTargetId: number = 0;
@State feedList: FeedPost[] = FEED_INIT;
@State orderList: PetOrder[] = ORDER_INIT;
@State petList: PetProfile[] = PET_INIT;
private timer: number = -1;
}
16 个 @State 状态分六类:导航状态(tabIdx)、动画状态(breath)、弹窗控制状态(addModal/editModal/delModal)、弹窗数据状态(delIdx/addName/addKind/editName/editWeight/editTarget/editTargetId,预填数据避免空输入,editTarget 区分修改对象)、业务数据状态(feedList/orderList/petList,初始化为常量数组,支持增删改)、定时器引用(private timer)。private 字段(GROOM_LIST/SHOP_LIST/MONTH_LABELS/SPEND_BARS/SERVICE_GRIDS/TAB_LIST/timer)存储静态模拟数据和定时器引用,不触发响应式更新。
11.2 生命周期与单动画联动
aboutToAppear(): void {
this.timer = setInterval(() => { this.breath = !this.breath; }, 750);
}
aboutToDisappear(): void {
if (this.timer > 0) { clearInterval(this.timer); }
}
aboutToAppear 中启动 setInterval 750ms 切换 breath 动画状态,实现单动画联动。aboutToDisappear 中判断 timer > 0 时 clearInterval 清理定时器,避免内存泄漏和重复启动。这种"生命周期管理定时器"的模式是 ArkUI 动效的标准实践。
11.3 宠物档案头部(@Builder header)
顶部头部通过 @Builder header() 抽取,Column 包裹,space 12,padding 左右 14 上下 12:
- 标题行:Row space 10,"🐾"图标 16 号 padding 6 圆角 12 chip 底 + "萌爪星球 · 宠物生活"title 焦糖棕黑 13 号加粗 + Column layoutWeight 1 + "📍 滨江"sub 灰 10 号 + "💬"消息 18 号。
- 宠物档案卡:Row space 12,padding 12,白底圆角 14,border 1px line 色,包含:🐕圆形头像 62×62 圆角 31 dark 底,34 号 + 宠物信息列(layoutWeight 1,左对齐):"布丁 · 柯基 · " + petAgeOf(26)(title 焦糖棕黑 13 号加粗)+ “体重 12.4kg · 下次疫苗 2026-11-20”(sub 灰 9 号)+ 会员等级列(右对齐):"⭐ 4.9"caramel 焦糖色 13 号加粗 + "会员等级"text3 灰 8 号。
- 疫苗到期横条:Row space 8,"💉 奶昔疫苗还有 45 天到期"green 绿字 9 号,dark 底圆角 8 padding 左右 8 上下 4 + "🎁 新店立减 ¥30"caramel 焦糖字 9 号,chip 底圆角 8 padding 左右 8 上下 4。
宠物档案头部是本应用最具辨识度的视觉元素,圆形头像+名称品种年龄(petAgeOf 函数转换)+体重疫苗+会员等级的卡片布局,让用户一眼看到当前宠物的核心信息。疫苗到期提醒(奶昔疫苗还有45天到期绿字)体现宠物健康管理,是本应用头部的特色设计。优惠横条用绿/焦糖两色对应疫苗提醒和新店优惠,与整体奶黄暖棕主题呼应。
11.4 底部 Tab 导航与 Stack 弹窗
底部导航通过 @Builder tabBar() 构建,Row 内 ForEach 遍历 TAB_LIST 5 个 Tab,每个 Tab Column 包裹:图标(19 号)+ 文字(9 号,激活 tabOn 焦糖色加粗,非激活 text3 灰),padding 上 7 下 6,layoutWeight 1 均分,点击切换 tabIdx。底部白底圆角 16。
build() 使用 Stack 布局,主 Column 包含 header + Scroll(if-else 切换 5 个 @Builder 页面:tabIdx 0-4 对应 homeTab/serviceTab/communityTab/orderTab/profileTab)+ tabBar,width/height 100%,bg 奶黄暖棕底。三个弹窗条件渲染:每个弹窗独立 Stack 叠加,共享 modalOverlay 统一遮罩封装(全屏 mask 遮罩 + 点击关闭)。
十二、关键技术特性对比
| 特性维度 | 实现方式 | 技术要点 | 设计价值 |
|---|---|---|---|
| 色彩管理 | ColorPalette 接口 + COLORS 常量 | 集中定义 21 种语义色,含焦糖橘主题色 | 保证视觉一致性,支持主题切换 |
| 奶黄暖棕主题 | bg #FBF6E9 + caramel #D98324 + orange #F08A24 | 奶黄底+焦糖主色+活力橘辅助,title焦糖棕黑非纯黑 | 温暖治愈氛围,柔和不刺眼 |
| 三种焦糖橙渐变 | 三种不同角度 linearGradient | 首页Banner 135度焦糖→橘/柱状图 180度橘→深焦糖/用户卡 135度深焦糖→橘 | 统一而丰富的渐变视觉语言 |
| 宠物档案头部 | 圆形头像+名称品种年龄+体重疫苗+会员等级 | petAgeOf月龄转岁/月,疫苗到期提醒绿字 | 宠物核心信息一目了然,健康管理 |
| 数据建模 | @Observed 类 + constructor + 工具函数 | 5 个 @Observed 类(PetProfile/GroomItem/ShopItem/FeedPost/PetOrder)+ 10 个工具函数 | 数据与逻辑分离,工程化设计 |
| 五Tab差异化 | 每页独立 @Builder 布局 | 首页Banner+服务网格+门店/服务分类横滑+项目卡+提示/社区宠友横滑+瀑布流/订单柱状图+列表/我的用户卡+宠物档案 | 一页一型,还原真实宠物O2O App |
| 服务网格跨页跳转 | onClick设置tabIdx=1 | 8个服务图标点击跳服务tab | 跨页面导航,用户路径顺畅 |
| 热门标签呼吸动画 | breath opacity 0.6-1 | 仅热门项目有呼吸动画,duration 600ms | 热门吸引眼球,动态感 |
| Banner图标旋转动画 | breath rotate 8/-8度 | 🐾图标随breath旋转,duration 700ms EaseInOut | 萌爪摇摆可爱动效,宠物主题契合 |
| 社区点赞交互闭环 | likeFeed函数splice替换对象 | 切换liked状态,likes±1,splice替换触发@Observed响应式更新 | 点赞状态实时更新,交互闭环 |
| 瀑布流双列布局 | Flex wrap 47.5%宽 | 动态卡片自动排列双列 | 模拟真实社交App瀑布流 |
| 月度消费柱状图 | 180度橘→深焦糖渐变 + 动态高度 | height=spendBarH(v)=v/700*90,6个月数据 | 燃烧视觉感,消费趋势直观 |
| 订单增删改 | 修改预约+删除订单按钮 | editModal修改/delModal删除,联系门店入口 | 订单管理完整闭环 |
| 宠物档案增删改 | 编辑+移除按钮 + 添加按钮 | addModal添加/editModal编辑/delModal删除,体检报告入口 | 宠物档案管理完整闭环 |
| 弹窗复用设计 | editTarget状态区分修改对象 | editModal一个弹窗服务pet和order两类修改,delModal同时删两个列表 | 减少重复代码,工程化设计 |
| 添加宠物智能默认 | 种类含"猫"选🐱否则🐕 | addModal确认后自动判断头像,名称默认新毛球 | 智能默认值,用户体验好 |
| splice替换对象更新 | likeFeed/updatePetById/updateOrderTitle | 遍历找到ID,splice(i,1,new Obj)替换整个对象 | 触发@Observed响应式更新,关键技巧 |
| 三大弹窗 | 独立 Stack 叠加 + modalOverlay | 添加宠物/修改信息/确认删除,共享遮罩样式 | 完整增删改交互闭环 |
| 状态管理 | @State + @Observed + private | 16个@State分六类,private存静态数据不触发更新 | 细粒度状态控制,性能优化 |
| 组件化 | @Component + @Builder | 1个入口组件 + 5个页面@Builder + 3个弹窗 + header/tabBar/modalOverlay | 高内聚低耦合,代码复用率高 |
| 列表渲染 | ForEach + 网格/横滑/列表/瀑布流 | 服务网格/分类横滑/项目卡列表/宠友横滑/瀑布流/门店列表/订单列表/宠物档案列表 | 数据驱动渲染,布局灵活 |
| 数据可视化 | 柱状图 + 宠物档案头部 | 月度消费渐变柱状图/宠物档案头部三信息 | 多维度呈现,消费趋势直观 |
| 文本省略 | maxLines + textOverflow + minFontSize | 项目描述maxLines1/动态内容maxLines2 minFontSize9 maxFontSize10 | 长文本优雅截断,布局整齐 |
| 生命周期 | aboutToAppear/aboutToDisappear | setInterval启动/clearInterval清理,timer判>0 | 健壮性强,无资源泄漏 |
| 布局系统 | Column/Row + Stack + Scroll + Flex | 弹性布局、Stack叠加、Scroll纵向滚动、Flex wrap换行 | 自适应布局,层次分明 |
完整代码
// ============================================================
// 817 萌爪星球 · 宠物服务与社交(现代行业:宠物经济 / 宠物O2O)
// 头部样式:宠物档案头部(宠物头像卡 + 疫苗到期横条)无动画
// 布局风格:5 个 tab 每个布局完全不同
// 首页=Banner+服务网格+门店列表 / 服务=洗护价目横滑+项目卡列表
// 社区=横滑宠友+瀑布流动态 / 订单=订单列表+服务柱状图 / 我的=宠物档案+预约记录
// 底部 5 tab 单排
// 主题:奶黄暖棕底 + 焦糖 + 活力橘
// ============================================================
interface ColorPalette {
bg: string;
card: string;
dark: string;
darkL: string;
title: string;
sub: string;
text3: string;
caramel: string;
caramelD: string;
orange: string;
red: string;
green: string;
blue: string;
purple: string;
pink: string;
line: string;
tabOn: string;
mask: string;
chip: string;
}
const COLORS: ColorPalette = {
bg: '#FBF6E9',
card: '#FFFFFF',
dark: '#F5EAD3',
darkL: '#EDDFC0',
title: '#4A3418',
sub: '#8C744D',
text3: '#BCA87F',
caramel: '#D98324',
caramelD: '#B0650F',
orange: '#F08A24',
red: '#E05B4B',
green: '#3E9E6C',
blue: '#3D7DC8',
purple: '#8A5FC0',
pink: '#E0719B',
line: '#F0E4CB',
tabOn: '#D98324',
mask: 'rgba(74,52,24,0.55)',
chip: 'rgba(217,131,36,0.12)'
};
interface TabMeta {
icon: string;
label: string;
}
const TAB_LIST: TabMeta[] = [
{ icon: '🐾', label: '首页' },
{ icon: '🛁', label: '服务' },
{ icon: '🌍', label: '社区' },
{ icon: '📦', label: '订单' },
{ icon: '👤', label: '我的' }
];
const MONTH_LABELS: string[] = ['3月', '4月', '5月', '6月', '7月', '8月'];
const SPEND_BARS: number[] = [180, 320, 260, 540, 410, 690];
function serviceIconColor(name: string): string {
if (name === '洗澡') { return COLORS.blue; }
if (name === '美容') { return COLORS.pink; }
if (name === '寄养') { return COLORS.purple; }
if (name === '医疗') { return COLORS.green; }
return COLORS.orange;
}
function orderTagColor(s: string): string {
if (s === '已完成') { return COLORS.green; }
if (s === '服务中') { return COLORS.orange; }
if (s === '待到店') { return COLORS.blue; }
return COLORS.text3;
}
function feedLikeColor(liked: boolean): string {
return liked ? COLORS.red : COLORS.text3;
}
function petAgeOf(months: number): string {
if (months < 12) { return months.toString() + '个月'; }
const y: number = Math.floor(months / 12);
const m: number = months % 12;
if (m === 0) { return y.toString() + '岁'; }
return y.toString() + '岁' + m.toString() + '个月';
}
@Observed export class PetProfile {
id: number;
name: string;
kind: string;
avatar: string;
breed: string;
ageMonths: number;
weight: string;
vaccine: string;
constructor(id: number, name: string, kind: string, avatar: string, breed: string, ageMonths: number, weight: string, vaccine: string) {
this.id = id; this.name = name; this.kind = kind; this.avatar = avatar;
this.breed = breed; this.ageMonths = ageMonths; this.weight = weight; this.vaccine = vaccine;
}
}
@Observed export class GroomItem {
id: number;
name: string;
desc: string;
price: string;
duration: string;
icon: string;
hot: boolean;
constructor(id: number, name: string, desc: string, price: string, duration: string, icon: string, hot: boolean) {
this.id = id; this.name = name; this.desc = desc; this.price = price; this.duration = duration; this.icon = icon; this.hot = hot;
}
}
@Observed export class ShopItem {
id: number;
name: string;
distance: string;
score: string;
orders: number;
icon: string;
tags: string[];
constructor(id: number, name: string, distance: string, score: string, orders: number, icon: string, tags: string[]) {
this.id = id; this.name = name; this.distance = distance; this.score = score; this.orders = orders; this.icon = icon; this.tags = tags;
}
}
@Observed export class FeedPost {
id: number;
author: string;
avatar: string;
petKind: string;
content: string;
image: string;
likes: number;
comments: number;
liked: boolean;
time: string;
constructor(id: number, author: string, avatar: string, petKind: string, content: string, image: string, likes: number, comments: number, liked: boolean, time: string) {
this.id = id; this.author = author; this.avatar = avatar; this.petKind = petKind;
this.content = content; this.image = image; this.likes = likes; this.comments = comments; this.liked = liked; this.time = time;
}
}
@Observed export class PetOrder {
id: number;
title: string;
shop: string;
amount: string;
status: string;
date: string;
icon: string;
constructor(id: number, title: string, shop: string, amount: string, status: string, date: string, icon: string) {
this.id = id; this.title = title; this.shop = shop; this.amount = amount; this.status = status; this.date = date; this.icon = icon;
}
}
const SERVICE_GRIDS: TabMeta[] = [
{ icon: '🛁', label: '洗澡' },
{ icon: '✂️', label: '美容' },
{ icon: '🏥', label: '医疗' },
{ icon: '🏨', label: '寄养' },
{ icon: '🎓', label: '训导' },
{ icon: '🍖', label: '零食' },
{ icon: '📸', label: '写真' },
{ icon: '🚗', label: '接送' }
];
const GROOM_LIST: GroomItem[] = [
new GroomItem(1, '精致洗护套餐', '深层清洁+护毛素+吹干造型', '¥128', '60分钟', '🛁', true),
new GroomItem(2, '萌系造型修剪', '泰迪装/比熊装/猫咪修面', '¥188', '90分钟', '✂️', true),
new GroomItem(3, '药浴除蚤', '皮肤检测+药浴+体外驱虫', '¥158', '70分钟', '💊', false),
new GroomItem(4, 'SPA香氛护理', '精油按摩+香氛+美毛', '¥218', '80分钟', '🕯️', false),
new GroomItem(5, '猫咪专属洗护', '低应激猫舍环境+专业保定', '¥168', '75分钟', '🐱', true),
new GroomItem(6, '洁牙护理', '超声波洁牙+口腔检查', '¥298', '60分钟', '🦷', false)
];
const SHOP_LIST: ShopItem[] = [
new ShopItem(1, '萌爪旗舰店·滨江店', '850m', '4.9', 12680, '🐾', ['24h营业', '可摄像头']),
new ShopItem(2, '猫语生活馆', '1.2km', '4.8', 8932, '🐱', ['猫咪友好', '独立包间']),
new ShopItem(3, '汪汪美容工坊', '1.6km', '4.7', 6721, '🐶', ['老店10年', '免费接送']),
new ShopItem(4, '宠乐星球·中央店', '2.3km', '4.9', 15320, '🪐', ['旗舰店', '医生坐诊']),
new ShopItem(5, '尾巴乐园宠物SPA', '2.8km', '4.6', 4510, '🐾', ['新客立减', '环境好'])
];
const FEED_INIT: FeedPost[] = [
new FeedPost(1, '布丁妈妈', '👩', '柯基', '布丁今天剪完造型,回头率200%!', '🐕', 328, 46, true, '2小时前'),
new FeedPost(2, '铲屎官老王', '🧑', '橘猫', '大橘为重,减肥计划第7天宣告失败', '🐈', 892, 132, false, '3小时前'),
new FeedPost(3, '雪球爸爸', '👨', '萨摩耶', '雪球的夏日降温神器,谁懂啊', '❄️', 456, 67, false, '5小时前'),
new FeedPost(4, '咕噜姐姐', '👧', '英短', '咕噜体检全过,医生夸牙齿刷得好', '🦷', 233, 28, true, '8小时前'),
new FeedPost(5, '旺财家族', '👨👩👧', '柴犬', '旺财学会握手了!训导课没白上', '🐕', 612, 95, false, '昨天'),
new FeedPost(6, '奶昔阿姨', '👩', '布偶', '奶昔的写真出炉,这就是仙女猫吧', '📸', 1204, 210, false, '昨天'),
new FeedPost(7, '土豆叔叔', '🧔', '法斗', '土豆的药浴初体验,全程乖到离谱', '🛁', 345, 52, false, '2天前'),
new FeedPost(8, '团子奶奶', '👵', '银渐层', '团子绝育后戴着伊丽莎白圈的日常', '🐱', 567, 88, true, '2天前')
];
const PET_INIT: PetProfile[] = [
new PetProfile(1, '布丁', '狗狗', '🐕', '柯基', 26, '12.4kg', '2026-11-20'),
new PetProfile(2, '奶昔', '猫咪', '🐱', '布偶', 18, '4.2kg', '2026-10-05')
];
const ORDER_INIT: PetOrder[] = [
new PetOrder(1, '精致洗护 · 布丁', '萌爪旗舰店·滨江店', '¥128', '服务中', '08-21 10:30', '🛁'),
new PetOrder(2, '萌系造型 · 布丁', '汪汪美容工坊', '¥188', '已完成', '08-14', '✂️'),
new PetOrder(3, '年度体检 · 奶昔', '宠乐星球·中央店', '¥358', '已完成', '08-02', '🏥'),
new PetOrder(4, '药浴除蚤 · 布丁', '萌爪旗舰店·滨江店', '¥158', '已完成', '07-22', '💊'),
new PetOrder(5, '寄养三天 · 奶昔', '猫语生活馆', '¥306', '已完成', '07-15', '🏨'),
new PetOrder(6, '洁牙护理 · 布丁', '宠乐星球·中央店', '¥298', '待到店', '08-25', '🦷')
];
@Entry
@Component
struct Page817 {
@State tabIdx: number = 0;
@State breath: boolean = false;
@State addModal: boolean = false;
@State editModal: boolean = false;
@State delModal: boolean = false;
@State delIdx: number = 0;
@State addName: string = '';
@State addKind: string = '';
@State editName: string = '';
@State editWeight: string = '';
@State editTarget: string = 'pet';
@State editTargetId: number = 0;
@State feedList: FeedPost[] = FEED_INIT;
@State orderList: PetOrder[] = ORDER_INIT;
@State petList: PetProfile[] = PET_INIT;
private timer: number = -1;
aboutToAppear(): void {
this.timer = setInterval(() => { this.breath = !this.breath; }, 750);
}
aboutToDisappear(): void {
if (this.timer > 0) { clearInterval(this.timer); }
}
@Builder
header() {
Column({ space: 12 }) {
Row({ space: 10 }) {
Text('🐾').fontSize(16).padding(6).borderRadius(12).backgroundColor(COLORS.chip)
Text('萌爪星球 · 宠物生活').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Column().layoutWeight(1)
Text('📍 滨江').fontSize(10).fontColor(COLORS.sub)
Text('💬').fontSize(18)
}
.width('100%')
Row({ space: 12 }) {
Text('🐕').fontSize(34)
.width(62).height(62).borderRadius(31).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
Column({ space: 4 }) {
Text('布丁 · 柯基 · ' + petAgeOf(26)).fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Text('体重 12.4kg · 下次疫苗 2026-11-20').fontSize(9).fontColor(COLORS.sub)
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Column({ space: 3 }) {
Text('⭐ 4.9').fontSize(13).fontColor(COLORS.caramel).fontWeight(FontWeight.Bold)
Text('会员等级').fontSize(8).fontColor(COLORS.text3)
}
.alignItems(HorizontalAlign.End)
}
.width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card)
.borderWidth(1).borderColor(COLORS.line)
Row({ space: 8 }) {
Text('💉 奶昔疫苗还有 45 天到期').fontSize(9).fontColor(COLORS.green)
.padding({ left: 8, right: 8, top: 4, bottom: 4 }).borderRadius(8).backgroundColor(COLORS.dark)
Text('🎁 新店立减 ¥30').fontSize(9).fontColor(COLORS.caramel)
.padding({ left: 8, right: 8, top: 4, bottom: 4 }).borderRadius(8).backgroundColor(COLORS.chip)
}
.width('100%')
}
.width('100%').padding({ left: 14, right: 14, top: 12, bottom: 12 })
}
@Builder
homeTab() {
Column({ space: 12 }) {
Column({ space: 8 }) {
Row({ space: 10 }) {
Column({ space: 4 }) {
Text('宠物健康季 · 年度体检节').fontSize(15).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
Text('疫苗驱虫 8 折 · 体检套餐买一送一').fontSize(9).fontColor('rgba(255,255,255,0.85)')
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Text('🐾').fontSize(34)
.rotate({ angle: this.breath ? 8 : -8 })
.animation({ duration: 700, iterations: 1, curve: Curve.EaseInOut })
}
.width('100%')
Row({ space: 8 }) {
Text('已有 28,536 只毛孩子报名').fontSize(9).fontColor('rgba(255,255,255,0.85)')
Column().layoutWeight(1)
Text('查看详情 >').fontSize(10).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
}
.width('100%')
}
.width('100%').padding(16).borderRadius(18)
.linearGradient({ angle: 135, colors: [[COLORS.caramel, 0], [COLORS.orange, 1]] })
Text('🧰 全部服务').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(SERVICE_GRIDS, (s: TabMeta) => {
Column({ space: 6 }) {
Text(s.icon).fontSize(24)
.width(48).height(48).borderRadius(24).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
Text(s.label).fontSize(10).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
}
.width('23%').padding(10).borderRadius(14).backgroundColor(COLORS.card).margin(4)
.alignItems(HorizontalAlign.Center)
.onClick(() => { this.tabIdx = 1; })
}, (s: TabMeta) => 's' + s.label)
}
.width('100%')
Row({ space: 8 }) {
Text('🏠 附近好店').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Column().layoutWeight(1)
Text('按距离 >').fontSize(9).fontColor(COLORS.caramel)
}
.width('100%')
ForEach(SHOP_LIST, (s: ShopItem) => {
Row({ space: 12 }) {
Text(s.icon).fontSize(28)
.width(56).height(56).borderRadius(16).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
Column({ space: 5 }) {
Text(s.name).fontSize(12).fontColor(COLORS.title).fontWeight(FontWeight.Bold).maxLines(1)
Row({ space: 6 }) {
Text('⭐ ' + s.score).fontSize(9).fontColor(COLORS.caramel).fontWeight(FontWeight.Bold)
Text('已服务 ' + s.orders + '单').fontSize(8).fontColor(COLORS.text3)
Text(s.distance).fontSize(8).fontColor(COLORS.blue)
}
Row({ space: 6 }) {
ForEach(s.tags, (t: string) => {
Text(t).fontSize(8).fontColor(COLORS.purple)
.padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(6).backgroundColor(COLORS.dark)
}, (t: string) => s.id.toString() + t)
}
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Column() { Text('预约').fontSize(10).fontColor('#FFFFFF').fontWeight(FontWeight.Bold) }
.padding({ left: 14, right: 14, top: 8, bottom: 8 }).borderRadius(12).backgroundColor(COLORS.caramel)
}
.width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card)
.alignItems(VerticalAlign.Center).borderWidth(1).borderColor(COLORS.line)
}, (s: ShopItem) => 'sh' + s.id.toString())
}
.width('100%')
}
@Builder
serviceTab() {
Column({ space: 12 }) {
Text('🛁 洗护美容 · 明码标价').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Scroll() {
Row({ space: 10 }) {
ForEach(['全部', '狗狗专区', '猫咪专区', '深层清洁', '造型修剪', '皮肤护理'], (c: string) => {
Text(c).fontSize(10).fontColor(c === '全部' ? '#FFFFFF' : COLORS.sub)
.padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(16)
.backgroundColor(c === '全部' ? COLORS.caramel : COLORS.card)
.borderWidth(c === '全部' ? 0 : 1).borderColor(COLORS.line)
}, (c: string) => c)
}
.padding({ left: 4, right: 4 })
}
.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width('100%')
ForEach(GROOM_LIST, (g: GroomItem) => {
Row({ space: 12 }) {
Text(g.icon).fontSize(28)
.width(58).height(58).borderRadius(16).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
Column({ space: 5 }) {
Row({ space: 6 }) {
Text(g.name).fontSize(12).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
if (g.hot) {
Text('🔥 热门').fontSize(8).fontColor(COLORS.red)
.padding({ left: 5, right: 5, top: 2, bottom: 2 }).borderRadius(6).backgroundColor(COLORS.chip)
.opacity(this.breath ? 0.6 : 1).animation({ duration: 600, iterations: 1 })
}
}
Text(g.desc).fontSize(9).fontColor(COLORS.sub).maxLines(1)
Text('⏱️ ' + g.duration + ' · 专业宠物美容师').fontSize(8).fontColor(COLORS.text3)
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Column({ space: 6 }) {
Text(g.price).fontSize(14).fontColor(COLORS.caramel).fontWeight(FontWeight.Bold)
Column() { Text('下单').fontSize(9).fontColor('#FFFFFF').fontWeight(FontWeight.Bold) }
.padding({ left: 12, right: 12, top: 5, bottom: 5 }).borderRadius(10).backgroundColor(COLORS.caramel)
}
.alignItems(HorizontalAlign.End)
}
.width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card)
.alignItems(VerticalAlign.Center).borderWidth(1).borderColor(COLORS.line)
}, (g: GroomItem) => 'g' + g.id.toString())
Column({ space: 8 }) {
Text('💡 温馨提示').fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Text('· 8月龄以下幼宠、孕期宠物请提前告知门店').fontSize(9).fontColor(COLORS.sub)
Text('· 高温天气接送服务免费,全程空调舱运输').fontSize(9).fontColor(COLORS.sub)
Text('· 服务过程可全程摄像头直播查看').fontSize(9).fontColor(COLORS.sub)
}
.width('100%').padding(14).borderRadius(14).backgroundColor(COLORS.dark)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
}
@Builder
communityTab() {
Column({ space: 12 }) {
Text('🌍 萌宠社区 · 今日热门').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Scroll() {
Row({ space: 12 }) {
ForEach(this.feedList, (f: FeedPost) => {
Column({ space: 5 }) {
Text(f.avatar).fontSize(26)
.width(54).height(54).borderRadius(27).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
Text(f.author).fontSize(9).fontColor(COLORS.title).fontWeight(FontWeight.Bold).maxLines(1)
Text(f.petKind).fontSize(8).fontColor(COLORS.caramel)
}
.width(74).padding(8).borderRadius(14).backgroundColor(COLORS.card)
.alignItems(HorizontalAlign.Center).borderWidth(1).borderColor(COLORS.line)
}, (f: FeedPost) => 'fa' + f.id.toString())
}
.padding({ left: 4, right: 4 })
}
.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width('100%')
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.feedList, (f: FeedPost) => {
Column({ space: 8 }) {
Row({ space: 8 }) {
Text(f.avatar).fontSize(18)
.width(32).height(32).borderRadius(16).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
Column({ space: 2 }) {
Text(f.author).fontSize(10).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Text(f.time + ' · ' + f.petKind + '家长').fontSize(8).fontColor(COLORS.text3)
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
}
.width('100%')
Text(f.content).fontSize(10).fontColor(COLORS.title).maxLines(2)
.minFontSize(9).maxFontSize(10)
Text(f.image).fontSize(46)
.width('100%').height(100).borderRadius(12).backgroundColor(COLORS.dark)
.textAlign(TextAlign.Center)
Row({ space: 12 }) {
Text('❤️ ' + f.likes).fontSize(10).fontColor(feedLikeColor(f.liked)).fontWeight(FontWeight.Bold)
.animation({ duration: 500, iterations: 1 })
.onClick(() => { likeFeed(this.feedList, f.id); })
Text('💬 ' + f.comments).fontSize(10).fontColor(COLORS.text3)
Column().layoutWeight(1)
Text('🔗').fontSize(12)
}
.width('100%')
}
.width('47.5%').padding(10).borderRadius(14).backgroundColor(COLORS.card).margin(5)
.alignItems(HorizontalAlign.Start)
}, (f: FeedPost) => 'fp' + f.id.toString())
}
.width('100%')
}
.width('100%')
}
@Builder
orderTab() {
Column({ space: 12 }) {
Text('📦 我的订单 · 服务记录').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Column({ space: 10 }) {
Text('💰 月度服务消费(元)').fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Row({ space: 8 }) {
ForEach(MONTH_LABELS, (m: string, i: number) => {
Column({ space: 6 }) {
Text(SPEND_BARS[i].toString()).fontSize(8).fontColor(COLORS.sub)
Column().width(16).height(spendBarH(SPEND_BARS[i])).borderRadius(8)
.linearGradient({ angle: 180, colors: [[COLORS.orange, 0], [COLORS.caramelD, 1]] })
Text(m).fontSize(8).fontColor(COLORS.text3)
}
.alignItems(HorizontalAlign.Center)
}, (m: string) => 'm' + m)
}
.width('100%').padding({ top: 10, bottom: 12 }).borderRadius(14).backgroundColor(COLORS.card)
.justifyContent(FlexAlign.Center)
}
.width('100%')
ForEach(this.orderList, (o: PetOrder) => {
Column({ space: 10 }) {
Row({ space: 8 }) {
Text(o.icon).fontSize(16)
Text(o.shop).fontSize(9).fontColor(COLORS.sub).layoutWeight(1).maxLines(1)
Text(o.status).fontSize(9).fontColor(orderTagColor(o.status)).fontWeight(FontWeight.Bold)
}
.width('100%')
Row({ space: 12 }) {
Text(o.icon).fontSize(26)
.width(52).height(52).borderRadius(14).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
Column({ space: 4 }) {
Text(o.title).fontSize(12).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Text('📅 ' + o.date).fontSize(9).fontColor(COLORS.text3)
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Text(o.amount).fontSize(14).fontColor(COLORS.caramel).fontWeight(FontWeight.Bold)
}
.width('100%')
Row({ space: 10 }) {
Column() { Text('修改预约').fontSize(10).fontColor(COLORS.caramel).fontWeight(FontWeight.Bold) }
.padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(10).backgroundColor(COLORS.chip)
.onClick(() => {
this.editTarget = 'order';
this.editTargetId = o.id;
this.editName = o.title;
this.editModal = true;
})
Column() { Text('删除订单').fontSize(10).fontColor(COLORS.red).fontWeight(FontWeight.Bold) }
.padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(10).backgroundColor(COLORS.dark)
.onClick(() => { this.delModal = true; this.delIdx = o.id; })
Column().layoutWeight(1)
Text('📞 联系门店').fontSize(10).fontColor(COLORS.blue)
}
.width('100%')
}
.width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card).borderWidth(1).borderColor(COLORS.line)
}, (o: PetOrder) => 'o' + o.id.toString())
}
.width('100%')
}
@Builder
profileTab() {
Column({ space: 12 }) {
Column({ space: 10 }) {
Row({ space: 12 }) {
Text('👩').fontSize(32)
.width(58).height(58).borderRadius(29).backgroundColor('rgba(255,255,255,0.25)').textAlign(TextAlign.Center)
Column({ space: 4 }) {
Text('布丁妈妈 · 金牌铲屎官').fontSize(14).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
Text('萌力值 2860 · 打卡 96 天').fontSize(9).fontColor('rgba(255,255,255,0.85)')
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Text('🐱 2只').fontSize(11).fontColor('#FFE082').fontWeight(FontWeight.Bold)
}
.width('100%')
}
.width('100%').padding(16).borderRadius(18)
.linearGradient({ angle: 135, colors: [[COLORS.caramelD, 0], [COLORS.orange, 1]] })
Row({ space: 8 }) {
Text('🐾 我的宠物档案').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Column().layoutWeight(1)
Text('➕ 添加').fontSize(10).fontColor(COLORS.caramel).fontWeight(FontWeight.Bold)
.onClick(() => { this.addModal = true; })
}
.width('100%')
ForEach(this.petList, (p: PetProfile) => {
Column({ space: 10 }) {
Row({ space: 12 }) {
Text(p.avatar).fontSize(32)
.width(60).height(60).borderRadius(30).backgroundColor(COLORS.dark).textAlign(TextAlign.Center)
Column({ space: 4 }) {
Text(p.name + ' · ' + p.breed).fontSize(12).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Text(p.kind + ' · ' + petAgeOf(p.ageMonths) + ' · ' + p.weight).fontSize(9).fontColor(COLORS.sub)
Text('💉 疫苗至 ' + p.vaccine).fontSize(9).fontColor(COLORS.green)
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
}
.width('100%')
Row({ space: 10 }) {
Column() { Text('✏️ 编辑').fontSize(10).fontColor(COLORS.caramel).fontWeight(FontWeight.Bold) }
.padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(10).backgroundColor(COLORS.chip)
.onClick(() => {
this.editTarget = 'pet';
this.editTargetId = p.id;
this.editName = p.name;
this.editWeight = p.weight;
this.editModal = true;
})
Column() { Text('🗑️ 移除').fontSize(10).fontColor(COLORS.red).fontWeight(FontWeight.Bold) }
.padding({ left: 14, right: 14, top: 7, bottom: 7 }).borderRadius(10).backgroundColor(COLORS.dark)
.onClick(() => { this.delModal = true; this.delIdx = p.id; })
Column().layoutWeight(1)
Text('📋 体检报告').fontSize(10).fontColor(COLORS.blue)
}
.width('100%')
}
.width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card).borderWidth(1).borderColor(COLORS.line)
}, (p: PetProfile) => 'pp' + p.id.toString())
Row({ space: 10 }) {
Text('🎁').fontSize(20)
Text('优惠券 4 张 · 最高抵 ¥50').fontSize(10).fontColor(COLORS.sub).layoutWeight(1)
Text('使用 >').fontSize(10).fontColor(COLORS.caramel).fontWeight(FontWeight.Bold)
}
.width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card)
Row({ space: 10 }) {
Text('📍').fontSize(20)
Text('常用门店 · 萌爪旗舰店·滨江店').fontSize(10).fontColor(COLORS.sub).layoutWeight(1)
Text('切换 >').fontSize(10).fontColor(COLORS.caramel).fontWeight(FontWeight.Bold)
}
.width('100%').padding(12).borderRadius(14).backgroundColor(COLORS.card)
}
.width('100%')
}
@Builder
tabBar() {
Row({ space: 0 }) {
ForEach(TAB_LIST, (t: TabMeta, i: number) => {
Column({ space: 3 }) {
Text(t.icon).fontSize(19)
Text(t.label).fontSize(9)
.fontColor(this.tabIdx === i ? COLORS.tabOn : COLORS.text3)
.fontWeight(this.tabIdx === i ? FontWeight.Bold : FontWeight.Normal)
}
.layoutWeight(1).padding({ top: 7, bottom: 6 })
.onClick(() => { this.tabIdx = i; })
}, (t: TabMeta, i: number) => t.label + i.toString())
}
.width('100%').backgroundColor(COLORS.card).borderRadius(16)
}
@Builder
modalOverlay(onClose: () => void) {
Column().width('100%').height('100%')
.backgroundColor(COLORS.mask)
.onClick(() => { onClose(); })
}
build() {
Stack({ alignContent: Alignment.Center }) {
Column() {
this.header()
Scroll() {
Column({ space: 10 }) {
if (this.tabIdx === 0) { this.homeTab() }
else if (this.tabIdx === 1) { this.serviceTab() }
else if (this.tabIdx === 2) { this.communityTab() }
else if (this.tabIdx === 3) { this.orderTab() }
else { this.profileTab() }
}
.width('100%').padding({ left: 14, right: 14, top: 12, bottom: 16 })
}
.layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top)
this.tabBar()
}
.width('100%').height('100%')
if (this.addModal) {
Stack({ alignContent: Alignment.Center }) {
this.modalOverlay(() => { this.addModal = false; })
Column({ space: 12 }) {
Text('➕ 添加宠物档案').fontSize(15).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Text('为新的家庭成员建立健康档案').fontSize(10).fontColor(COLORS.sub)
TextInput({ placeholder: '宠物昵称', text: this.addName })
.onChange((v: string) => { this.addName = v; })
.fontSize(12).padding(10).borderRadius(10).backgroundColor(COLORS.dark)
TextInput({ placeholder: '种类(如 狗狗 / 猫咪 / 兔兔)', text: this.addKind })
.onChange((v: string) => { this.addKind = v; })
.fontSize(12).padding(10).borderRadius(10).backgroundColor(COLORS.dark)
Row({ space: 10 }) {
Column() { Text('取消').fontSize(12).fontColor(COLORS.sub) }
.layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.darkL)
.onClick(() => { this.addModal = false; })
Column() { Text('确认添加').fontSize(12).fontColor('#FFFFFF').fontWeight(FontWeight.Bold) }
.layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.caramel)
.onClick(() => {
this.petList.push(new PetProfile(
Date.now(),
this.addName.length > 0 ? this.addName : '新毛球',
this.addKind.length > 0 ? this.addKind : '狗狗',
this.addKind.indexOf('猫') >= 0 ? '🐱' : '🐕',
'待登记', 3, '待称重', '待接种'));
this.addModal = false;
})
}
.width('100%')
}
.width('82%').constraintSize({ maxHeight: '80%' }).padding(16).borderRadius(16).backgroundColor(COLORS.card)
}
.width('100%').height('100%').zIndex(999)
}
if (this.editModal) {
Stack({ alignContent: Alignment.Center }) {
this.modalOverlay(() => { this.editModal = false; })
Column({ space: 12 }) {
Text('✏️ 修改信息').fontSize(15).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
TextInput({ placeholder: '名称', text: this.editName })
.onChange((v: string) => { this.editName = v; })
.fontSize(12).padding(10).borderRadius(10).backgroundColor(COLORS.dark)
TextInput({ placeholder: '体重(如 12.6kg)', text: this.editWeight })
.onChange((v: string) => { this.editWeight = v; })
.fontSize(12).padding(10).borderRadius(10).backgroundColor(COLORS.dark)
Row({ space: 10 }) {
Column() { Text('取消').fontSize(12).fontColor(COLORS.sub) }
.layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.darkL)
.onClick(() => { this.editModal = false; })
Column() { Text('保存').fontSize(12).fontColor('#FFFFFF').fontWeight(FontWeight.Bold) }
.layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.caramel)
.onClick(() => {
if (this.editTarget === 'pet') {
updatePetById(this.petList, this.editTargetId,
this.editName, this.editWeight);
} else {
updateOrderTitle(this.orderList, this.editTargetId, this.editName);
}
this.editModal = false;
})
}
.width('100%')
}
.width('82%').constraintSize({ maxHeight: '80%' }).padding(16).borderRadius(16).backgroundColor(COLORS.card)
}
.width('100%').height('100%').zIndex(999)
}
if (this.delModal) {
Stack({ alignContent: Alignment.Center }) {
this.modalOverlay(() => { this.delModal = false; })
Column({ space: 14 }) {
Text('🗑️ 确认删除?').fontSize(15).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
Text('选中的档案/订单将被移除,删除后不可恢复。').fontSize(10).fontColor(COLORS.sub)
Row({ space: 10 }) {
Column() { Text('再想想').fontSize(12).fontColor(COLORS.sub) }
.layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.darkL)
.onClick(() => { this.delModal = false; })
Column() { Text('确认删除').fontSize(12).fontColor('#FFFFFF') }
.layoutWeight(1).padding({ top: 10, bottom: 10 }).borderRadius(10).backgroundColor(COLORS.red)
.onClick(() => {
removePetById(this.petList, this.delIdx);
removeOrderById(this.orderList, this.delIdx);
this.delModal = false;
})
}
.width('100%')
}
.width('78%').constraintSize({ maxHeight: '80%' }).padding(16).borderRadius(16).backgroundColor(COLORS.card)
}
.width('100%').height('100%').zIndex(999)
}
}
.width('100%').height('100%').backgroundColor(COLORS.bg)
}
}
function spendBarH(v: number): number {
return Math.round(v / 700 * 90);
}
function likeFeed(list: FeedPost[], id: number): void {
for (let i = 0; i < list.length; i++) {
if (list[i].id === id) {
const o: FeedPost = list[i];
const liked: boolean = !o.liked;
const likes: number = liked ? o.likes + 1 : o.likes - 1;
list.splice(i, 1, new FeedPost(o.id, o.author, o.avatar, o.petKind, o.content, o.image, likes, o.comments, liked, o.time));
}
}
}
function removePetById(list: PetProfile[], id: number): void {
for (let i = list.length - 1; i >= 0; i--) {
if (list[i].id === id) { list.splice(i, 1); }
}
}
function removeOrderById(list: PetOrder[], id: number): void {
for (let i = list.length - 1; i >= 0; i--) {
if (list[i].id === id) { list.splice(i, 1); }
}
}
function updatePetById(list: PetProfile[], id: number, name: string, weight: string): void {
for (let i = 0; i < list.length; i++) {
if (list[i].id === id) {
const o: PetProfile = list[i];
list.splice(i, 1, new PetProfile(o.id, name, o.kind, o.avatar, o.breed, o.ageMonths, weight, o.vaccine));
}
}
}
function updateOrderTitle(list: PetOrder[], id: number, title: string): void {
for (let i = 0; i < list.length; i++) {
if (list[i].id === id) {
const o: PetOrder = list[i];
list.splice(i, 1, new PetOrder(o.id, title, o.shop, o.amount, o.status, o.date, o.icon));
}
}
}
十三、开发环境搭建
13.1 安装 DevEco Studio
DevEco Studio 是 HarmonyOS 应用开发的官方 IDE。下载安装包后双击运行,进入安装欢迎界面,点击「下一步」继续。

13.2 选择安装路径
选择目标安装目录,建议安装到非系统盘,确认所需空间后点击「下一步」。

13.3 配置安装选项
设置环境变量,勾选创建桌面快捷方式、将 bin 文件夹添加到 PATH、添加上下文菜单,但是需要重启一下电脑使环境变量生效。

13.4 选择项目模板
新建一个空白模板,在模板市场中选择 Empty Ability。

13.5 配置项目信息
设置 API 为 24 的模板项目,项目名称 kfschool,Compatible SDK 6.1.1(24),设备类型勾选 Phone,点击完成后初始化项目,自动下载相关依赖。

十四、总结
通过对这款萌爪星球宠物服务与社交应用的深度拆解,我们可以看到,一个优秀的移动端应用远不止是功能的堆砌,它更是架构设计、数据建模、视觉系统、交互逻辑、动效设计、数据计算等多维度能力的综合体现。
在架构层面,该应用采用了"配置驱动 + 组件化 + 状态管理"的三层设计模式。配置层将所有可变的数据与视觉属性抽离为常量数组和元数据接口,使得业务逻辑与表现层彻底解耦;组件层将五个功能页面拆分为独立的 @Builder 构建器,每个页面采用完全不同的布局风格,实现"一页一型"的差异化设计;状态层利用 ArkUI 的 @State、@Observed 机制,实现了数据变化到 UI 刷新的自动响应,特别是弹窗数据状态的预填(addName/addKind/editName/editWeight 预填数据避免空输入)和 editTarget 状态区分修改对象('pet’宠物档案或’order’订单标题),体现了对状态管理的精准理解和弹窗复用设计的工程化思维。
在数据层面,五个 @Observed 数据类(PetProfile/GroomItem/ShopItem/FeedPost/PetOrder)以 constructor 构造函数方式实例化,完整描述了萌爪星球的全部业务信息。特别的是,应用定义了 10 个工具函数(serviceIconColor/orderTagColor/feedLikeColor/petAgeOf/spendBarH/likeFeed/removePetById/removeOrderById/updatePetById/updateOrderTitle),将颜色映射、数据计算、增删改逻辑从 UI 层抽离,实现数据与逻辑分离,是本应用工程化设计的核心亮点。spendBarH 函数将消费金额动态映射为柱状图像素高度(v/700*90),petAgeOf 函数将月龄转换为岁/月显示(<12月显示X个月,>=12月显示X岁或X岁X个月),likeFeed/updatePetById/updateOrderTitle 三个函数采用 splice 替换对象的方式触发 @Observed 响应式更新(因为直接修改 @Observed 对象属性可能不触发响应式刷新,而替换整个对象会触发),这是 ArkUI 响应式更新的关键技巧,体现了对框架机制的深入理解。社区点赞交互闭环——likeFeed 函数遍历找到对应ID,切换 liked 状态,点赞数相应 ±1,然后 splice 替换对象,feedLikeColor 函数动态着色,❤️文字带动画反馈,形成了完整的点赞交互闭环。
在视觉层面,本应用独创了"奶黄暖棕主题"设计——主色 #D98324 焦糖色搭配辅助色 #F08A24 活力橘,bg #FBF6E9 奶黄暖棕底,title #4A3418 焦糖棕黑而非纯黑,与奶黄底形成高对比度的同时带有温暖质感。应用的渐变设计是色彩系统的核心亮点——首页宠物健康季Banner使用 135 度焦糖→活力橘渐变,订单月度消费柱状图使用 180 度活力橘→深焦糖垂直渐变,我的用户卡使用 135 度深焦糖→活力橘渐变,三种不同角度和配色的焦糖橙渐变贯穿整个应用,形成了统一而丰富的焦糖橙渐变视觉语言。宠物档案头部采用圆形头像+名称品种年龄+体重疫苗+会员等级的卡片布局,疫苗到期提醒(奶昔疫苗还有45天到期绿字)体现宠物健康管理,是本应用头部的特色设计。应用的动态颜色逻辑也十分丰富——服务图标五色着色(洗澡蓝/美容粉/寄养紫/医疗绿/其他橘)、订单状态四档着色(已完成绿/服务中橘/待到店蓝/其他灰)、社区点赞两档着色(已赞红/未赞灰),形成了"以色定服务品类、以色定订单状态、以色定点赞状态"的多层语义着色模式。统一的色彩系统是整个应用的视觉基石,焦糖主色搭配活力橘辅助色,配合奶黄暖棕底和三种焦糖橙渐变,营造出既温暖治愈又活力热情的宠物O2O氛围。
在交互层面,应用独创了"单动画状态联动"设计——aboutToAppear 中 setInterval 750ms 切换 breath 布尔动画状态,breath 同时用于服务页热门标签透明度动画(opacity 0.6-1,duration 600ms,仅热门项目)和首页Banner🐾图标旋转动画(rotate 8/-8度,duration 700ms EaseInOut),一个动画状态驱动两种不同的动效,形成整体协调的联动动效体系。热门标签的呼吸动画让热门项目有了"吸引眼球"的动态感,Banner🐾图标旋转动画让首页有了"萌爪摇摆"的可爱感,两种动效各司其职,让静态的页面有了"鲜活"的动态感。社区点赞❤️文字带 animation duration 500ms,点击点赞时有"心动"的动效反馈。三个弹窗(添加宠物档案/修改信息/确认删除)各自独立 Stack 叠加,共享 modalOverlay 统一遮罩封装。特别的是,本应用独创了"弹窗复用设计"——修改信息弹窗通过 editTarget 状态区分修改对象(pet或order),确认后根据 editTarget 调用 updatePetById 或 updateOrderTitle,一个弹窗服务两类数据修改;确认删除弹窗确认后同时调用 removePetById 和 removeOrderById,因为 delIdx 可能是宠物或订单ID,两个列表都按ID删除,一个弹窗服务两类数据删除。添加宠物档案弹窗确认后种类含"猫"自动选🐱头像否则🐕头像,名称默认新毛球,体现智能默认值设计。服务网格8个图标点击 tabIdx=1 跳服务tab,实现跨页面导航。订单页每个订单有修改预约和删除订单按钮,我的页每个宠物有编辑和移除按钮,形成了完整的宠物档案和订单增删改闭环。

标签: HarmonyOS · API 24 · ArkTS · 华为 · 宠物O2O · 萌宠社交 · 宠物经济 · 移动端开发
更多推荐


所有评论(0)