HarmonyOS 6效果实现:底部安全区适配思路,底部 Tab 导航栏预留空间,避免页面内容被系统导航条遮挡
引言
当穿戴甲从美甲店的附属品进化为Z世代的日常美妆快消品,一场关于"拼多多风格×穿戴甲美妆电商"的视觉革命正在美妆圈蔓延。多多穿戴美甲正是这样一款概念应用——它将美甲款式浏览、系列成套购买、个性定制工作台、同城美甲师预约、社区晒甲分享、个人订单管理,全部浓缩进一个 ArkTS 单文件之中。
不同于传统美甲 App 的工具路线,多多穿戴美甲选择了"拼多多风格·穿戴甲美妆电商"的定位:裸粉白 #FFF7F9 铺底,蔷薇粉 #E91E63 作为主强调色贯穿按钮、Tab激活、价格、角标,香槟金 #D4A857 作为辅助强调色用于评分、系列折扣、积分等级,如同拼多多式的高饱和电商视觉。按压缩放、选中缩放、爱心点赞缩放、星星脉冲——4 组交互动画让整个界面如同真实的电商购物体验。
6 个 Tab 覆盖完整业务链路:美甲浏览 18 款款式并立即购买、系列浏览 9 个系列并查看详情、定制工作台选择甲型长度底色装饰并提交需求、美甲师浏览 12 位同城美甲师并预约、社区浏览 12 条晒甲帖子并点赞收藏、我的管理 10 笔订单与积分等级与月度消费。6 套弹框各具特色:立即购买(甲围+穿戴时长+数量stepper+合计)、系列详情(横幅信息+进入系列)、定制需求(名称+风格+预算+工期+描述)、预约美甲师(时段+服务+备注)、编辑收藏夹(名称+封面+可见性+排序)、删除订单(红色警告+确认删除)。
本文将从项目概述、色彩系统、架构设计、数据模型、六大 Tab、六大弹框、交互动画、美甲与系列系统、定制与美甲师系统、社区与我的系统、性能优化、穿戴甲文化、美妆电商生活方式、开发环境搭建共十五个维度,对多多穿戴美甲进行深度解析,文末附完整 ArkTS 源码。

第一章 项目概述与设计理念
1.1 项目定位
多多穿戴美甲是一款面向拼多多风格穿戴甲美妆电商的概念应用,核心定位是"美甲款式+系列成套+个性定制+美甲师预约+社区晒甲"五位一体的美妆电商 App 风格。目标用户是 18-35 岁的爱美女性、穿戴甲爱好者、美甲新手、Z世代美妆消费者。应用场景包括:浏览美甲款式、立即购买、查看系列详情、个性定制穿戴甲、预约同城美甲师、社区晒甲分享、管理个人订单。
1.2 设计理念
拼多多风格电商美学:以拼多多式高饱和电商视觉为灵感来源,蔷薇粉 #E91E63 作为主强调色贯穿按钮、Tab激活、价格、角标、立即购买,香槟金 #D4A857 作为辅助强调色用于评分、系列折扣、积分等级、订单迷你条,裸粉白 #FFF7F9 铺底构建柔和美妆氛围,纯白 #FFFFFF 卡片背景,深粉褐 #4A2C3A 主文字,如同拼多多式的高转化电商视觉。
电商快消风:顶部Logo+搜索栏+快捷入口+购物车角标、美甲Tab滤镜chips+双列网格+按压缩放、系列Tab横滑Banner+系列列表折扣进度条、定制Tab预览卡+选项区+计价卡,完全复现拼多多式电商购物体验。立即购买弹框的甲围+穿戴时长+数量stepper+合计如同电商下单流程。
高转化与紧迫感:大量使用原价删除线、销量"已售X+"、热卖标签、限时折扣、系列折扣进度条、新人1元、免费试戴等电商营销元素,营造高转化购物氛围。按压缩放动画让点击有真实反馈,爱心点赞缩放让社交互动有满足感。
1.3 核心功能矩阵
| 模块 | 核心功能 | 数据量 |
|---|---|---|
| 美甲 | 款式浏览、风格筛选、立即购买、按压缩放 | 18款/6风格 |
| 系列 | 横滑Banner、系列列表、折扣进度条、系列详情 | 6横幅/9系列 |
| 定制 | 甲型/长度/底色/装饰选择、预览卡、计价、需求提交 | 3甲型/3长度/5底色/4装饰 |
| 美甲师 | 同城美甲师浏览、星星脉冲、订单迷你条、预约 | 12位美甲师 |
| 社区 | 晒甲信息流、爱心点赞缩放、评论收藏、发帖 | 12条帖子 |
| 我的 | 资料卡、积分等级、月度消费柱状图、订单管理、收藏夹 | 10笔订单/6个月 |
1.4 技术亮点
- 单文件 ArkTS 零依赖:约 1500 行代码在一个 .ets 文件中,复制即运行
- 4 组交互动画:按压缩放(scale 0.96)、选中缩放(scale 1.1)、爱心点赞缩放(scale 1.35)、星星脉冲(opacity 1↔0.35)
- 23 个全局纯函数:styleColor、salesText、seriesPct、seriesDiscountLabel、orderStatusColor、orderStatusBg、ratingStars、orderBarWidth、spendBarH、buyTotal、pointsProgress、removeOrder、withPostLiked、filterNails、customSummary、customPrice、customPriceText、commentCount、decorIcon、decorColor、bannerCardH
- 6 差异化弹框:立即购买 / 系列详情 / 定制需求 / 预约美甲师 / 编辑收藏夹 / 删除订单
- 定制工作台:甲型/长度/底色/装饰四维选择,实时预览卡,动态计价
- 系列折扣进度条:9个系列折扣力度可视化,折扣标签+百分比进度条
- 月度消费柱状图:6个月消费柱状,高消费蔷薇粉/普通香槟金
- 积分等级进度:2680/5000积分进度条,升级提示
- 订单状态系统:制作中/已发货/已完成三态,不同颜色和背景
第二章 色彩系统与视觉风格
2.1 色彩体系
多多穿戴美甲定义了 11 色完整调色板(NailPalette 接口),分为四个层级:
基础层(裸粉白):
- bg #FFF7F9:应用背景,裸粉白
- card #FFFFFF:卡片背景,纯白
- cardDeep #FDEEF3:深层卡片,浅粉
- chip #FBE3EC:标签/搜索栏背景,粉白
强调层(蔷薇粉 × 香槟金):
- primary #E91E63:蔷薇粉,主按钮、Tab激活、价格、角标、立即购买、折扣
- secondary #D4A857:香槟金,评分、系列折扣、积分等级、订单迷你条、装饰色
- warn #D32F2F:警告红,删除订单、库存警告
- white #FFFFFF:纯白
文字层:
- text #4A2C3A:主文字,深粉褐
- textSub #A67F8E:次要文字,中粉褐
2.2 色彩应用策略
蔷薇粉 #E91E63 是应用的灵魂色,承担"行动召唤与价格强调"角色:底部 Tab 激活文字、主操作按钮(立即购买/提交需求/确认预约/保存)、价格显示(¥19.9)、购物车角标(2)、热卖标签、系列折扣、定制计价、积分进度条、订单价格。蔷薇粉在裸粉白背景上具有强烈的视觉穿透力,如同拼多多式的高转化主色。
香槟金 #D4A857 承担"品质与价值"角色:美甲师评分星星、系列折扣标签、积分等级(Lv.8剁手党)、订单迷你条进度、定制装饰色、月度消费普通柱。香槟金与蔷薇粉形成冷暖对撞,在数据可视化中区分"品质/等级"与"行动/价格"。
裸粉白 #FFF7F9 承担"柔和美妆氛围"角色:应用背景、卡片间留白、弹框遮罩外区域。裸粉白色调柔和,符合美妆电商的女性化定位,与蔷薇粉形成高对比,让强调色更加突出。
2.3 电商渐变与营销质感
应用大量使用纯色块和渐变构建视觉层次:
- 系列横幅:6种主题色(蔷薇粉/深粉褐/紫/金/红/绿),纯色背景+白色文字
- 美甲卡片:纯色背景(#FFF1F5 纯色款 / #FBE3EC 其他款),emoji居中
- 定制预览卡:#FDEEF3 浅粉背景,装饰emoji+美甲emoji叠加
- 社区图片占位:#FDEEF3 浅粉背景,美甲emoji居中
- 积分进度条:#FBE3EC 背景 + #E91E63 前景
- 月度消费柱:#E91E63 高消费 / #D4A857 普通
拼多多式营销元素是多多穿戴美甲的标志性设计:原价删除线(¥29.9→¥19.9)、销量"已售1.2w+"、热卖标签(🔥热卖)、限时折扣、系列折扣进度条(58%OFF)、新人1元、免费试戴、满减提示(加购满3件立减¥5)、7天无理由退换。这些元素共同营造拼多多式的高转化电商购物氛围。
不对称圆角贯穿全应用:卡片使用 12-14vp 统一圆角,弹框使用顶部 20vp 圆角(底部抽屉式),搜索栏 17vp 圆角,按钮 18-20vp 圆角。
2.4 按压缩放与爱心点赞
项目通过交互动画模拟真实电商购物体验:
- 按压缩放:美甲卡片点击时 scale 从 1.0 到 0.96,180ms EaseOut,如同真实按钮按压反馈
- 选中缩放:定制选项(甲型/长度/装饰)选中时 scale 从 1.0 到 1.1,200ms EaseOut,选中项放大突出
- 爱心点赞缩放:社区帖子点赞时爱心 scale 从 1.0 到 1.35,250ms EaseOut,点赞满足感
- 星星脉冲:美甲师评分星星点击时 opacity 从 1.0 到 0.35,500ms EaseInOut,星星闪烁反馈
这些动画共同营造真实电商购物的交互反馈,让静态界面"活"起来。按压缩放是本应用的特色动画,每张美甲卡片都有按压反馈,如同真实电商App的点击体验。爱心点赞缩放让社区互动有满足感,点赞时爱心放大变红。

第三章 项目架构与文件组织
3.1 单文件组件化架构
多多穿戴美甲采用"单文件组件化"架构,所有代码在一个 2023.ets 文件中,通过 @Component 装饰器划分为约 9 个独立组件(6 Tab + 头部 + 内联弹框)。代码量约 1500 行,处于单文件架构的合理范围。
3.2 组件层次结构
NailApp (@Entry 根组件)
├── 顶部标题栏(NailHeader:Logo+搜索+快捷入口+购物车角标)
├── 内容区 Stack
│ ├── 6 个 Tab 内容组件(6 选 1)
│ │ ├── NailsContent(美甲)
│ │ ├── SeriesContent(系列)
│ │ ├── CustomContent(定制)
│ │ ├── ArtistContent(美甲师)
│ │ ├── CommunityContent(社区)
│ │ └── MineContent(我的)
│ └── 弹框(各Tab内联管理,zIndex 999)
│ ├── 立即购买弹框(NailsContent内)
│ ├── 系列详情弹框(SeriesContent内)
│ ├── 定制需求弹框(CustomContent内)
│ ├── 预约美甲师弹框(ArtistContent内)
│ ├── 编辑收藏夹弹框(MineContent内)
│ └── 删除订单弹框(MineContent内)
└── 底部 Tab 导航栏(6 项)
根组件管理当前 Tab(activeTab)。每个 Tab 组件内部管理自己的弹框显隐状态和交互状态,这种内联管理方式减少了根组件的状态复杂度。
3.3 @Builder 自定义构建函数
项目使用多个 @Builder 方法封装可复用 UI 片段:tabItem(底部Tab项)、quickLink(快捷入口)、modalOverlay(弹框遮罩层)、buyModal(购买弹框)、bannerModal(系列详情弹框)、reqModal(定制需求弹框)、bookModal(预约美甲师弹框)、editColModal(编辑收藏夹弹框)、deleteModal(删除订单弹框)。
3.4 弹框架构设计
弹框采用"遮罩层 + 内容层"Stack 叠加模式,每个 Tab 组件内部管理自己的弹框。6 套弹框各具特色:
- 立即购买:款式回显 + 甲围chips(XS/S/M/L)+ 穿戴时长chips(7/14/30天)+ 数量stepper + 合计 + 立即购买
- 系列详情:横幅信息回显 + 系列描述 + 进入系列
- 定制需求:需求名称输入 + 风格参考chips + 预算chips + 工期chips + 需求描述TextArea + 提交需求
- 预约美甲师:美甲师回显 + 预约日期 + 时段chips + 服务项目chips + 备注输入 + 确认预约
- 编辑收藏夹:收藏夹名称输入 + 封面风格chips + 可见性chips + 排序方式chips + 保存
- 删除订单:⚠️警告图标 + 订单名称回显 + 红色警告说明 + 取消/确认删除
第四章 数据模型与全局状态管理
4.1 接口定义体系
项目定义 9 个 TypeScript 接口描述数据模型:NailItem(美甲款式)、SeriesBanner(系列横幅)、SeriesItem(系列列表)、ArtistItem(美甲师)、FeedPost(社区帖子)、MyOrder(我的订单)、MonthSpend(月度消费)、NailPalette(调色板)。
4.2 全局数据常量
约 20 组全局 const 常量:核心业务数据(NAILS 18款 / BANNERS 6横幅 / SERIES 9系列 / ARTISTS 12位 / POSTS 12条 / ORDERS 10笔 / MONTH_SPEND 6个月)、配置数据(NAIL_STYLES 6风格 / SIZE_OPTIONS 4尺码 / WEAR_DURATIONS 3时长 / CUSTOM_SHAPES 3甲型 / CUSTOM_LENGTHS 3长度 / CUSTOM_COLOR_NAMES 5底色名 / CUSTOM_COLORS 5底色 / CUSTOM_DECORS 4装饰 / TIME_SLOTS 4时段 / SERVICE_TYPES 3服务 / REF_STYLES 4风格 / BUDGET_OPTIONS 4预算 / DEADLINE_OPTIONS 4工期 / COVER_STYLES 4封面 / VISIBILITY_OPTIONS 2可见性 / SORT_OPTIONS 2排序)。
4.3 三级状态管理策略
- 第一级 全局常量(const):业务数据和配置,任何组件可直接读取
- 第二级 组件局部状态(@State):组件内交互状态,如 curStyle(当前风格筛选)、pressedIndex(按压索引)、showBuy(购买弹框)、buySize/buyDuration/buyCount(购买选项)、shapeIdx/lenIdx/colorIdx/decorIdx(定制选项)、showBook(预约弹框)、starPulse(星星脉冲)、posts(社区帖子,可点赞更新)、orders(订单,可删除)
- 第三级 根组件协调状态(@State):跨组件状态,如 activeTab(当前 Tab)
4.4 全局纯函数
23 个全局纯函数处理数据转换和业务逻辑:
- styleColor(style):根据美甲风格返回对应颜色(法式金/猫眼紫/晕染粉/镶钻蓝/纯色褐)
- salesText(n):销量格式化(≥10000显示1.2w+,否则显示n+)
- seriesPct(s):系列折扣百分比字符串
- seriesDiscountLabel(s):系列折扣标签(如5.8折)
- orderStatusColor(status):订单状态文字颜色
- orderStatusBg(status):订单状态背景色
- ratingStars®:评分星星字符串(★☆组合)
- orderBarWidth(n):订单迷你条宽度百分比
- spendBarH(v):月度消费柱高度
- buyTotal(price, count):购买合计金额
- pointsProgress():积分进度百分比
- removeOrder(list, id):删除订单
- withPostLiked(list, id):切换帖子点赞状态
- filterNails(list, style):按风格筛选美甲
- customSummary(…):定制摘要字符串
- customPrice(lenIdx, decorIdx):定制价格
- customPriceText(lenIdx, decorIdx):定制价格文本
- commentCount(n):评论数文本
- decorIcon(idx):装饰图标
- decorColor(idx):装饰颜色
- bannerCardH():横幅卡片高度
纯函数相同输入永远产生相同输出,无副作用,易于测试和复用。salesText 纯函数是本应用的特色,将销量格式化为"1.2w+"的电商常见格式。withPostLiked 纯函数实现点赞状态切换,返回新数组避免直接修改状态。removeOrder 纯函数实现订单删除,返回过滤后的新数组。
第五章 六大 Tab 页面深度解析
5.1 美甲 Tab(NailsContent)
美甲是入口,承担"风格筛选→浏览款式→按压缩放→立即购买"路径。页面结构:
- 今日美甲标题:💅今日美甲 + 新款式每日上新·全场包邮 + X款
- 风格筛选 chips(6 风格):全部/法式/猫眼/晕染/镶钻/纯色,选中蔷薇粉背景白字
- 双列网格(18 款,按风格筛选 filterNails):每卡含封面纯色+emoji+🔥热卖标签+款式名+风格标签+穿戴时长+价格(蔷薇粉加粗)+原价删除线+销量,按压缩放动画(scale 0.96,180ms EaseOut)
- 到底提示:— 已经到底啦,去逛逛更多美甲吧 —
按压缩放动画是美甲Tab的核心交互:每张美甲卡片 onTouch 事件监听 Down/Up/Cancel,Down 时 pressedIndex=n.id,Up/Cancel 时 pressedIndex=0,scale 根据 pressedIndex===n.id 在 0.96 和 1.0 之间切换,180ms EaseOut 动画。这种按压反馈如同真实电商App的按钮点击,增加交互真实感。
立即购买弹框:点击美甲卡片触发,含款式回显(emoji+名称+风格+穿戴时长+销量)+ 甲围chips(XS/S/M/L 4选1)+ 穿戴时长chips(7天/14天/30天 3选1)+ 数量stepper(−/+,1-10件)+ 合计(buyTotal 纯函数计算)+ 7天无理由退换·加购满3件立减¥5 + 立即购买按钮。

5.2 系列 Tab(SeriesContent)
系列是成套购买中心,承担"横滑Banner→浏览系列→折扣进度条→查看详情"路径。页面结构:
- 精选系列标题:🎀精选系列 + 系列成套购买·折扣叠加 + 9个系列
- 横滑 Banner(6 横幅):每卡含图标+系列名+描述+标签+圆点指示器,选中金色边框,点击触发系列详情弹框
- 全部系列标题:📚全部系列 + 按折扣力度排序
- 系列列表(9 系列):每卡含图标+系列名+折扣标签(seriesDiscountLabel 纯函数,如5.8折)+款数+价格区间+销量+折扣进度条(seriesPct 纯函数,蔷薇粉填充)
- 成套提示:成套入手更划算 · 系列内任意混搭
系列折扣进度条是系列Tab的核心设计:每个系列卡片底部有进度条,背景 #FBE3EC,前景 #E91E63,宽度 seriesPct(s) 即折扣百分比(如58%),直观展示折扣力度。折扣标签(如5.8折)香槟金加粗,系列名右侧显示。9个系列折扣从35%到71%不等,元气多巴胺系列71%OFF折扣最大。
系列详情弹框:点击横幅触发,含横幅信息回显(图标+系列名+描述+标签)+ 系列描述 + 进入系列按钮。

5.3 定制 Tab(CustomContent)
定制是个性定制工作台,承担"选择甲型/长度/底色/装饰→实时预览→动态计价→提交需求"路径。页面结构:
- 个性定制标题:✂️个性定制 + 专属你的独一无二·定制工期3-15天 + 提交需求按钮
- 当前设计预览卡:浅粉背景 + 装饰emoji(decorIcon 纯函数)+ 美甲emoji叠加 + 当前设计 + 装饰色标签(decorColor 纯函数)+ 定制摘要(customSummary 纯函数)
- 甲型选择(3 选 1):圆形/方形/杏仁形,选中缩放动画(scale 1.1,200ms EaseOut)
- 长度选择(3 选 1):短/中/长,选中缩放动画
- 底色选择(5 选 1):甜莓粉/蔷薇粉/奶油杏/薰衣紫/珠光白,色点圆形,选中蔷薇粉3vp边框
- 装饰选择(4 选 1):猫眼/碎钻/金属线/贴片,每格含图标+名称+价格,选中缩放动画+蔷薇粉边框
- 计价卡:定制费用(customPriceText 纯函数,如¥49)+ 包含说明 + 立即定制按钮
实时预览与动态计价是定制Tab的核心设计:用户选择甲型/长度/底色/装饰时,预览卡实时更新(装饰emoji+装饰色标签+定制摘要),计价卡实时更新价格(customPrice = 长度价格 + 装饰价格)。选中缩放动画让选项交互有反馈,色点圆形选择让底色选择直观。
定制需求弹框:点击提交需求/立即定制触发,含需求名称输入 + 风格参考chips(法式/猫眼/晕染/镶钻)+ 预算chips(¥50内/¥50-100/¥100-200/¥200+)+ 工期chips(3天/7天/10天/15天)+ 需求描述TextArea + 提交需求按钮。

5.4 美甲师 Tab(ArtistContent)
美甲师是同城预约中心,承担"浏览美甲师→星星脉冲→订单迷你条→预约"路径。页面结构:
- 美甲师标题:💆美甲师 + 同城到店·好评认证美甲师 + 12位在约
- 美甲师列表(12 位):每卡含头像+昵称+工作室名+位置+评分星星(ratingStars 纯函数,星星脉冲动画 opacity 1↔0.35,500ms EaseInOut)+评分数字+订单数+订单迷你条(orderBarWidth 纯函数,香槟金填充)+标签+价格起+预约按钮
- 到底提示:— 点击星星点亮评分 · 更多美甲师等你发现 —
星星脉冲动画是美甲师Tab的核心交互:每位美甲师的评分星星(⭐+ratingStars)点击时 starPulse 状态切换,星星 opacity 在 1.0 和 0.35 之间脉冲,500ms EaseInOut 动画。这种星星闪烁反馈如同真实电商的评分交互,增加趣味性。
订单迷你条是美甲师Tab的特色设计:每位美甲师卡片底部有迷你进度条,背景 #FBE3EC,前景 #D4A857 香槟金,宽度 orderBarWidth(a.orders) 即订单数/3200*100%,直观展示美甲师的订单量。阿花美甲师3200单订单迷你条最满,价格199起最高端。
预约美甲师弹框:点击预约按钮触发,含美甲师回显(头像+工作室+位置+评分)+ 预约日期(08-28周五)+ 时段chips(10:00/14:00/16:00/19:00)+ 服务项目chips(贴甲/卸甲/手部护理)+ 备注输入 + 取消/确认预约按钮。

5.5 社区 Tab(CommunityContent)
社区是晒甲分享中心,承担"浏览信息流→爱心点赞缩放→评论收藏→发帖"路径。页面结构:
- 穿戴甲社区标题:💬穿戴甲社区 + 晒出你的美甲·一起变美 + 发帖按钮
- 热门美甲分享标题:🔥热门美甲分享 + 按点赞排序
- 信息流(12 条):每条含头像+昵称+日期+关注按钮+文字内容(3行省略)+图片占位区(浅粉背景+美甲emoji+作品图标签)+分割线+互动栏(爱心点赞缩放+点赞数+评论数+收藏)
爱心点赞缩放动画是社区Tab的核心交互:每条帖子的爱心(🤍/❤️)点击时 withPostLiked 纯函数切换点赞状态,爱心 scale 在 1.0 和 1.35 之间缩放,250ms EaseOut 动画。点赞时爱心从白色🤍变为红色❤️并放大,点赞数+1,取消点赞时爱心缩小变回白色,点赞数-1。这种爱心缩放反馈如同真实社交App的点赞交互,增加满足感。
图片占位区是社区Tab的特色设计:每条帖子有96vp高的图片占位区,浅粉 #FDEEF3 背景,美甲emoji居中,"作品图"标签底部,模拟真实晒甲图片。文字内容3行省略,保持信息流整洁。

5.6 我的 Tab(MineContent)
我的是个人中心,承担"资料卡→积分等级→月度消费→订单管理→收藏夹"路径。页面结构:
- 资料卡:头像+昵称(美甲小仙女)+等级标签(Lv.8剁手党)+ID+加入时间+编辑资料 + 4项统计(订单12/收藏8/积分2680/已省¥856)
- 积分等级进度:🎯积分等级 + 2680/5000积分 + 进度条(pointsProgress 纯函数,53.6%)+ 升级提示(再攒2320积分升级「闪耀名媛」)
- 月度消费柱状图(6 个月):📊月度消费 + 近6个月 + 柱状图(spendBarH 纯函数,高消费≥180蔷薇粉/普通香槟金)+月份+金额
- 我的订单标题:📦我的订单 + 10笔订单 + 编辑收藏夹按钮
- 订单列表(10 笔):每单含图标+名称+类型+日期+价格+状态标签(orderStatusColor/orderStatusBg 纯函数,制作中金/已发货粉/已完成绿)+删除按钮
- 消费提示:— 消费满¥99立返100积分 —
月度消费柱状图是我的Tab的核心数据可视化:6个月消费柱状,1月¥128/2月¥96/3月¥152/4月¥88/5月¥210/6月¥175,柱子高度 spendBarH(v)=v*0.3,高消费≥180用蔷薇粉 #E91E63(5月),普通用香槟金 #D4A857。5月消费最高¥210,4月最低¥88。柱状图直观展示消费趋势。
积分等级进度是我的Tab的特色设计:2680/5000积分,进度条53.6%,升级提示"再攒2320积分升级「闪耀名媛」,享专属客服+新品优先购"。等级标签Lv.8剁手党蔷薇粉背景白字,增加游戏化成就感。
订单状态系统是我的Tab的实用设计:3种状态(制作中/已发货/已完成),不同颜色和背景(制作中金#B8860B背景#FBF0D9/已发货粉#E91E63背景#FBE3EC/已完成绿#7CB342背景#EDF5DC),直观展示订单进度。删除按钮触发红色警告删除弹框。
编辑收藏夹弹框:点击编辑收藏夹触发,含收藏夹名称输入 + 封面风格chips(裸粉/闪金/樱花/星河)+ 可见性chips(公开/私密)+ 排序方式chips(最新/价格)+ 取消/保存按钮。
删除订单弹框:点击删除按钮触发,含⚠️警告图标 + 删除订单标题 + 订单名称回显 + 红色警告说明(删除后订单记录将被永久移除,无法恢复!若订单仍在制作或发货中,建议先联系客服处理售后。)+ 取消/确认删除按钮(确认删除时 removeOrder 纯函数删除订单)。

第六章 六大弹框组件设计
弹框是多多穿戴美甲交互体系的核心组成部分。项目共设计了 6 套弹框组件,覆盖立即购买、系列详情、定制需求、预约美甲师、编辑收藏夹、删除订单六大业务场景。
6.1 立即购买弹框(buyModal)
定位:美甲款式购买入口,甲围+穿戴时长+数量stepper+合计设计。
核心功能:
- 款式回显:emoji+款式名+风格+穿戴时长+已售销量
- 选择甲围(4 选 1):XS/S/M/L,选中蔷薇粉背景白字
- 穿戴时长(3 选 1):7天/14天/30天,选中蔷薇粉背景白字
- 购买数量:−/+ stepper(1-10件),−粉白背景/+蔷薇粉背景
- 合计:buyTotal 纯函数计算(price*count,保留1位小数),蔷薇粉20vp加粗
- 服务说明:支持7天无理由退换·加购满3件立减¥5
- 立即购买按钮:蔷薇粉背景白字,点击关闭弹框
数量 stepper 边界 1-10 件,合计金额 buyTotal 纯函数动态计算(Math.round(pricecount10)/10)。甲围和穿戴时长 chips 选择增加购买定制感。
6.2 系列详情弹框(bannerModal)
定位:系列横幅详情入口,横幅信息回显+进入系列设计。
核心功能:
- 横幅信息回显:图标+系列名+描述+标签
- 系列描述:系列包含多款同主题穿戴甲,支持成套混搭与专属礼盒包装。
- 进入系列按钮:蔷薇粉背景白字,点击关闭弹框
系列详情弹框简洁明了,横幅信息回显让用户了解系列主题,进入系列按钮引导用户浏览系列内款式。
6.3 定制需求弹框(reqModal)
定位:个性定制需求提交入口,名称+风格+预算+工期+描述设计。
核心功能:
- 需求名称:TextInput 输入(如:闺蜜婚礼伴娘甲)
- 风格参考(4 选 1):法式/猫眼/晕染/镶钻,选中蔷薇粉背景白字
- 预算范围(4 选 1):¥50内/¥50-100/¥100-200/¥200+,选中蔷薇粉背景白字
- 期望工期(4 选 1):3天/7天/10天/15天,选中蔷薇粉背景白字
- 需求描述:TextArea 多行输入(补充描述你的想法,如场合、配色偏好…)
- 提交需求按钮:蔷薇粉背景白字,点击关闭弹框
定制需求弹框覆盖完整的定制需求要素,风格参考+预算+工期chips选择增加便捷性,需求描述TextArea允许自由补充。
6.4 预约美甲师弹框(bookModal)
定位:同城美甲师预约入口,时段+服务+备注设计。
核心功能:
- 美甲师回显:头像+工作室名+位置+评分
- 预约日期:08-28 周五(固定显示)
- 选择时段(4 选 1):10:00/14:00/16:00/19:00,选中蔷薇粉背景白字
- 服务项目(3 选 1):贴甲/卸甲/手部护理,选中蔷薇粉背景白字
- 备注信息:TextInput 输入(如:需要贴甲,请准备猫眼款式…)
- 取消/确认预约按钮:取消粉边/确认蔷薇粉,点击关闭弹框
预约美甲师弹框采用居中弹窗设计(非底部抽屉),88%宽度,18vp圆角,与其他弹框的底部抽屉式形成差异化。
6.5 编辑收藏夹弹框(editColModal)
定位:收藏夹编辑入口,名称+封面+可见性+排序设计。
核心功能:
- 收藏夹名称:TextInput 输入(如:我的宝藏美甲)
- 封面风格(4 选 1):裸粉/闪金/樱花/星河,选中蔷薇粉背景白字
- 可见性(2 选 1):公开/私密,选中蔷薇粉背景白字
- 排序方式(2 选 1):最新/价格,选中蔷薇粉背景白字
- 取消/保存按钮:取消粉边/保存蔷薇粉,点击关闭弹框
编辑收藏夹弹框覆盖收藏夹的完整配置要素,封面风格+可见性+排序方式chips选择增加便捷性。
6.6 删除订单弹框(deleteModal)
定位:订单删除的二次确认,红色警告式设计。
核心设计——红色警告:⚠️图标44vp + 删除订单标题 + 订单名称回显(「猫眼星辰」)+ 红色警告说明区(#FDEBEE 浅粉背景,10vp圆角):删除后订单记录将被永久移除,无法恢复!若订单仍在制作或发货中,建议先联系客服处理售后。
- 取消/确认删除按钮(取消粉白背景/确认警告红#D32F2F背景),确认删除时 removeOrder 纯函数删除订单并关闭弹框。
删除订单弹框采用居中弹窗设计,86%宽度,18vp圆角,⚠️警告图标增加警示感,红色警告说明区增加不可恢复的透明度。
第七章 交互动画与用户体验
7.1 ArkTS 交互动画机制
ArkTS 通过属性动画(.animation())和显式动画(animateTo())实现交互动画。多多穿戴美甲主要使用属性动画(.animation()),在状态变化时自动应用过渡动画:
.scale({ x: this.pressedIndex === n.id ? 0.96 : 1, y: this.pressedIndex === n.id ? 0.96 : 1 })
.animation({ duration: 180, curve: Curve.EaseOut })
属性动画绑定在组件属性上,当依赖的状态变化时自动触发动画过渡,无需手动管理动画帧。本应用 4 组交互动画全部使用属性动画方式。
7.2 四组交互动画详解
| 动画 | 组件 | 属性 | 时长 | 曲线 | 效果 |
|---|---|---|---|---|---|
| 按压缩放 | 美甲卡片 | scale 1.0↔0.96 | 180ms | EaseOut | 按压时缩小0.96 |
| 选中缩放 | 定制选项 | scale 1.0↔1.1 | 200ms | EaseOut | 选中时放大1.1 |
| 爱心点赞缩放 | 社区帖子 | scale 1.0↔1.35 | 250ms | EaseOut | 点赞时放大1.35 |
| 星星脉冲 | 美甲师评分 | opacity 1.0↔0.35 | 500ms | EaseInOut | 星星闪烁脉冲 |
按压缩放动画:美甲卡片 onTouch 事件监听 Down/Up/Cancel,Down 时 pressedIndex=n.id,Up/Cancel 时 pressedIndex=0,scale 根据 pressedIndex===n.id 在 0.96 和 1.0 之间切换,180ms EaseOut 动画。EaseOut 曲线模拟按压时快速缩小、释放时缓慢恢复的真实按钮反馈。每张美甲卡片都有此动画,如同真实电商App的卡片点击。
选中缩放动画:定制选项(甲型/长度/装饰)选中时 scale 从 1.0 到 1.1,200ms EaseOut 动画。选中项放大突出,未选中项保持原大小,视觉上明确区分选中状态。甲型3选项、长度3选项、装饰4选项均使用此动画。
爱心点赞缩放动画:社区帖子爱心点击时 withPostLiked 纯函数切换点赞状态,爱心 scale 从 1.0 到 1.35,250ms EaseOut 动画。点赞时爱心从白色🤍变为红色❤️并放大1.35倍,点赞数+1;取消点赞时爱心缩小变回白色,点赞数-1。1.35倍放大比普通缩放更大,增加点赞的满足感和仪式感。
星星脉冲动画:美甲师评分星星点击时 starPulse 状态切换,星星 opacity 从 1.0 到 0.35,500ms EaseInOut 动画。EaseInOut 曲线模拟星星缓慢闪烁的效果,500ms较长时长增加梦幻感。点击星星区域触发脉冲,增加评分交互的趣味性。
7.3 属性动画的优势
传统逐帧动画需要手动管理动画帧和定时器,属性动画通过框架自动调度:状态变化时自动触发过渡、动画参数(时长/曲线)声明式配置、多个属性动画可同时运行互不干扰、组件销毁时动画自动停止。多多穿戴美甲 4 组交互动画全部采用属性动画方式,代码简洁且性能优良。
7.4 交互反馈设计
项目通过多种方式提供即时交互反馈:
- 按钮点击:按压缩放动画提供真实按压反馈
- 选中态:选中缩放动画+颜色变化(蔷薇粉背景白字)双重反馈
- Tab 切换:文字颜色(蔷薇粉/粉褐)+ 字重(加粗/常规)双重反馈
- stepper:−/+ 点击后数字立即变化,边界值自动限制(数量1-10)
- chips 选择:点击后立即切换选中态,颜色变化+选中缩放
- 点赞:爱心颜色变化(🤍→❤️)+ 缩放动画 + 点赞数变化三重反馈
- 星星脉冲:点击后星星 opacity 脉冲,增加趣味性
- 色点选择:选中蔷薇粉3vp边框,未选中浅粉1vp边框
- 删除警告:⚠️图标+红色警告说明+确认按钮警告红,增加操作谨慎性
这些即时反馈确保用户操作的"确定性",提升交互流畅度和购物体验。爱心点赞缩放是本应用的特色交互,点赞时爱心放大变红的三重反馈(颜色+缩放+数字)如同真实社交App的点赞体验。
第八章 美甲与系列系统深度解析
8.1 美甲款式数据体系
项目定义 18 款美甲款式(NAILS 常量),每款包含:id、name、icon(emoji)、style(法式/猫眼/晕染/镶钻/纯色)、duration(可穿戴7/14/30天)、price(现价)、originPrice(原价)、sales(销量)。
18 款款式覆盖 6 种风格:
- 法式(4款):法式白边¥19.9、法式波点¥22.9、法式小香风¥39、珍珠法式¥55
- 猫眼(4款):猫眼星辰¥45、琥珀猫眼¥49、绿野猫眼¥42、幻彩猫眼¥59
- 晕染(4款):奶油晕染¥25.9、渐变晕染¥29.9、玫瑰晕染¥32、冰透晕染¥36
- 镶钻(3款):钻石女王¥68、星钻贴片¥89、蕾丝镶钻¥75
- 纯色(3款):裸粉纯色¥9.9、经典纯白¥12.9、樱花纯色¥15.9
价格区间 ¥9.9-¥89,覆盖平价到中高端。销量从 310 到 6100 不等,经典纯白 6100 销量最高,裸粉纯色 5200 次之,符合拼多多式平价爆款特征。原价删除线(originPrice)营造折扣感,如法式白边 ¥29.9→¥19.9。
8.2 风格筛选与双列网格
美甲Tab通过 NAIL_STYLES 常量定义 6 种风格筛选 chips(全部/法式/猫眼/晕染/镶钻/纯色),点击切换 curStyle 状态,filterNails 纯函数按风格筛选款式。选中态蔷薇粉背景白字,未选中粉白背景深粉褐文字。
双列网格使用 Flex({wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween}) 实现,每卡宽度 47%,自动换行。每卡包含:封面纯色区(96vp高,纯色款#FFF1F5/其他款#FBE3EC)+emoji居中+🔥热卖标签(左上角)+款式名(12vp加粗,1行省略)+风格标签(styleColor 纯函数,法式金/猫眼紫/晕染粉/镶钻蓝/纯色褐)+穿戴时长+价格(蔷薇粉15vp加粗)+原价删除线+销量(salesText 纯函数,如1.2w+)。
8.3 系列横幅与系列列表
项目定义 6 个系列横幅(BANNERS 常量)和 9 个系列(SERIES 常量)。
6 个系列横幅各有主题色:
- 樱花限定系列(蔷薇粉#D81B60,首发标签)
- 法式经典系列(深粉褐#4A2C3A,人气TOP1标签)
- 猫眼幻境系列(紫#5E35B1,高光推荐标签)
- 婚礼定制系列(金#B8860B,高端标签)
- 国风鎏金系列(红#C0392B,新中式标签)
- 元气多巴胺系列(绿#00897B,年轻力标签)
横滑 Banner 使用 Scroll(Horizontal) 实现,每卡 230vp 宽 128vp 高,选中金色2vp边框,圆点指示器(●查看/○)。点击触发系列详情弹框。
9 个系列列表每卡包含:图标+系列名+折扣标签(seriesDiscountLabel 纯函数,如5.8折,香槟金加粗)+款数+价格区间+销量+折扣进度条(seriesPct 纯函数,蔷薇粉填充,宽度=折扣百分比)。折扣从 35%(婚礼定制)到 71%(元气多巴胺)不等,元气多巴胺系列 71%OFF 折扣最大。
8.4 系列折扣进度条设计
系列折扣进度条是系列Tab的核心数据可视化:每个系列卡片底部有进度条,背景 #FBE3EC,前景 #E91E63 蔷薇粉,宽度 seriesPct(s) 即折扣百分比(如58%),圆角4vp。进度条右侧显示"58%OFF"标签,蔷薇粉9vp文字。
折扣进度条直观展示系列折扣力度,配合折扣标签(5.8折)形成双重折扣展示。9个系列按折扣力度排序(元气多巴胺71%>猫眼幻境62%>冰透水感66%>樱花限定58%>国风鎏金52%>法式经典46%>轻熟职场44%>复古巴洛克39%>婚礼定制35%),高折扣系列排在前面,符合拼多多式高折扣优先展示策略。
第九章 定制与美甲师系统深度解析
9.1 定制工作台四维选择
定制Tab提供四维选择系统,覆盖穿戴甲定制的核心要素:
甲型(3 选 1):圆形/方形/杏仁形,选中缩放动画(scale 1.1,200ms EaseOut),选中蔷薇粉背景白字,未选中粉白背景深粉褐文字。
长度(3 选 1):短/中/长,对应价格 CUSTOM_LENGTH_PRICES=[29,39,55],短甲¥29/中甲¥39/长甲¥55。选中缩放动画。
底色(5 选 1):甜莓粉#F06292/蔷薇粉#E91E63/奶油杏#FFD9A0/薰衣紫#C9B6E4/珠光白#FDF2F4,色点圆形30vp,选中蔷薇粉3vp边框,未选中浅粉1vp边框。色点下方显示底色名称。
装饰(4 选 1):猫眼/碎钻/金属线/贴片,对应价格 CUSTOM_DECOR_PRICES=[10,25,15,5],猫眼¥10/碎钻¥25/金属线¥15/贴片¥5。每格含装饰图标(decorIcon 纯函数:✨猫眼/💎碎钻/➿金属线/🫧贴片)+名称+价格,选中缩放动画+蔷薇粉边框+浅粉背景。
9.2 实时预览与动态计价
定制Tab的核心设计是"选择即预览,选择即计价":
实时预览卡:用户选择甲型/长度/底色/装饰时,预览卡实时更新。预览卡含浅粉背景区(132vp高)+装饰emoji(decorIcon 纯函数,56vp)+美甲emoji(44vp,叠加在装饰下方)+当前设计标题+装饰色标签(decorColor 纯函数:猫眼紫#7E57C2/碎钻蓝#29B6F6/金属线灰#B0BEC5/贴片橙#FFB74D)+定制摘要(customSummary 纯函数,如"甲型·圆形 / 长度·中 / 底色·蔷薇粉 / 装饰·猫眼")。
动态计价卡:用户选择长度/装饰时,计价卡实时更新价格。customPrice 纯函数计算价格=长度价格+装饰价格(如中甲¥39+猫眼¥10=¥49),customPriceText 纯函数格式化为"¥49"。计价卡含定制费用标题+价格(蔷薇粉20vp加粗)+分割线+包含说明(基础甲片+长度升级+装饰·附赠收纳盒与果冻胶)+立即定制按钮。
9.3 定制需求提交
点击提交需求/立即定制触发定制需求弹框,含需求名称输入+风格参考chips+预算chips+工期chips+需求描述TextArea+提交需求按钮。定制需求弹框覆盖完整的定制需求要素,风格参考+预算+工期chips选择增加便捷性,需求描述TextArea允许自由补充。提交后关闭弹框,模拟需求提交成功。
9.4 美甲师数据体系
项目定义 12 位美甲师(ARTISTS 常量),每位包含:id、name、avatar(emoji)、studio(工作室名)、location(城市·区域)、tags(擅长标签,|分隔)、rating(评分4.5-4.9)、orders(订单数430-3200)、price(价格起¥35-¥199)。
12 位美甲师覆盖 12 个城市:杭州/上海/成都/广州/北京/深圳/南京/武汉/重庆/西安/长沙/天津。价格从 ¥35(妮妮,短甲简约)到 ¥199(阿花,贴片钻饰奢华),覆盖平价到高端。评分 4.5-4.9,小鹿/蓝蓝/阿花/阿茶 4.9 最高。订单数从 430 到 3200,阿花 3200 单最高。
每位美甲师有3个擅长标签(|分隔),如小鹿"法式|猫眼|手绘"、阿花"贴片|钻饰|奢华"、阿茶"国风|鎏金|晕染"。标签显示为蔷薇粉背景粉白文字的小标签。
9.5 星星脉冲与订单迷你条
星星脉冲动画:每位美甲师卡片的评分区域(⭐+ratingStars 纯函数星星+评分数字)点击时 starPulse 状态切换,星星 opacity 从 1.0 到 0.35,500ms EaseInOut 动画。EaseInOut 曲线模拟星星缓慢闪烁的效果,500ms较长时长增加梦幻感。点击星星区域触发脉冲,增加评分交互的趣味性。
订单迷你条:每位美甲师卡片底部有迷你进度条,背景 #FBE3EC,前景 #D4A857 香槟金,宽度 orderBarWidth(a.orders) 即订单数/3200*100%,圆角3vp。进度条左侧显示"📦 X"订单数。阿花 3200 单订单迷你条最满(100%),妮妮 430 单最窄(13%)。订单迷你条直观展示美甲师的订单量和受欢迎程度。
9.6 预约美甲师
点击预约按钮触发预约美甲师弹框,含美甲师回显(头像+工作室名+位置+评分)+预约日期(08-28周五固定显示)+时段chips(10:00/14:00/16:00/19:00)+服务项目chips(贴甲/卸甲/手部护理)+备注输入+取消/确认预约按钮。预约美甲师弹框采用居中弹窗设计(88%宽度,18vp圆角),与其他弹框的底部抽屉式形成差异化。确认预约后关闭弹框,模拟预约成功。
第十章 社区与我的系统深度解析
10.1 社区信息流数据体系
项目定义 12 条社区帖子(POSTS 常量),每条包含:id、user(用户名)、avatar(emoji)、text(晒甲文字)、likes(点赞数)、comments(评论数)、liked(是否已点赞)、date(发布日期)。
12 条帖子覆盖多种晒甲主题:订婚甲(阿茶,1280赞)、冰透晕染(桃桃,890赞)、暗黑金属线(小野,520赞)、婚礼法式蕾丝(茉莉,2100赞最高)、极光猫眼(蓝蓝,1560赞)、贴满钻(阿花,990赞)、果冻色渐变(西西,760赞)、手绘小猫咪(七仔,830赞)、晕染贴钻教程(阿凝,1450赞)、短方圆甲(妮妮,470赞)、手绘敦煌配色(小鹿,1800赞)、职场法式白边(阿妍,690赞)。
点赞数从 470 到 2100,茉莉婚礼法式蕾丝 2100 赞最高。已点赞状态(liked=true)的帖子有3条(桃桃/茉莉/西西/小鹿),爱心显示红色❤️,未点赞显示白色🤍。
10.2 爱心点赞缩放动画
社区Tab的核心交互是爱心点赞缩放动画:每条帖子的爱心(🤍/❤️)点击时 withPostLiked 纯函数切换点赞状态,返回新数组(不直接修改原状态)。爱心 scale 从 1.0 到 1.35,250ms EaseOut 动画。
点赞时:爱心从白色🤍变为红色❤️,scale 放大到 1.35,点赞数+1。取消点赞时:爱心从红色❤️变为白色🤍,scale 缩小回 1.0,点赞数-1。1.35倍放大比普通缩放更大,增加点赞的满足感和仪式感。250ms EaseOut 曲线模拟点赞时快速放大、释放时缓慢恢复的弹性效果。
withPostLiked 纯函数实现:遍历帖子数组,找到id匹配的帖子,创建新帖子对象(liked取反,likes根据liked状态+1或-1),其他帖子保持不变,返回新数组。这种不可变更新方式符合 ArkTS 状态管理最佳实践。
10.3 图片占位区与信息流布局
每条帖子有96vp高的图片占位区,浅粉 #FDEEF3 背景,美甲emoji居中(34vp),"作品图"标签底部(9vp粉褐文字),模拟真实晒甲图片。文字内容3行省略(maxLines(3)+textOverflow Ellipsis),保持信息流整洁。
信息流布局:头像(44vp圆形)+昵称+日期+关注按钮(蔷薇粉边框)在上排,文字内容在中排,图片占位区在下排,分割线+互动栏(爱心点赞+点赞数+评论数+收藏)在底部。每卡 94% 宽度,纯白背景,12vp圆角,浅粉边框,12vp内边距,10vp顶部间距。
10.4 我的资料卡与统计
我的Tab顶部是资料卡,含:头像(62vp圆形,粉白背景)+昵称(美甲小仙女,16vp加粗)+等级标签(Lv.8剁手党,蔷薇粉背景白字)+ID+加入时间+编辑资料按钮 + 4项统计(订单12/收藏8/积分2680/已省¥856,每项蔷薇粉17vp加粗+粉褐9vp标签)。
4项统计直观展示用户的消费和积累情况:订单12笔、收藏8个、积分2680、已省¥856。已省金额营造拼多多式省钱感,积分营造游戏化成就感。
10.5 积分等级进度
积分等级进度卡含:🎯积分等级标题+2680/5000积分(香槟金10vp)+进度条(pointsProgress 纯函数=2680/5000*100=53.6%,背景粉白/前景蔷薇粉)+升级提示(再攒2320积分升级「闪耀名媛」,享专属客服+新品优先购,9vp粉褐文字)。
积分等级进度营造游戏化成长感,53.6%进度显示用户已过半,升级提示引导用户继续消费攒积分。等级标签Lv.8剁手党蔷薇粉背景白字,增加身份认同感。
10.6 月度消费柱状图
月度消费柱状图是我的Tab的核心数据可视化:6个月消费柱状(1月¥128/2月¥96/3月¥152/4月¥88/5月¥210/6月¥175),柱子高度 spendBarH(v)=v*0.3(如5月¥210→63vp高),高消费≥180用蔷薇粉 #E91E63(5月),普通用香槟金 #D4A857(其他5个月)。每柱下方显示月份(9vp粉褐)和金额(8vp蔷薇粉)。
柱状图容器96vp高,柱子底部对齐(justifyContent FlexAlign.End),直观展示消费趋势。5月消费最高¥210(蔷薇粉柱),4月最低¥88。月度消费柱状图帮助用户了解自己的消费习惯和趋势,高消费月份用蔷薇粉突出显示。
10.7 订单管理与状态系统
我的Tab订单列表含10笔订单(ORDERS 常量),每笔包含:id、name、icon、price、status(制作中/已发货/已完成)、date、type(穿戴甲/系列装/定制/服务)。
订单状态系统通过 orderStatusColor 和 orderStatusBg 纯函数实现3种状态的差异化显示:
- 制作中:文字金#B8860B,背景#FBF0D9(浅金)
- 已发货:文字蔷薇粉#E91E63,背景#FBE3EC(浅粉)
- 已完成:文字绿#7CB342,背景#EDF5DC(浅绿)
每笔订单卡片含:图标(50vp圆角,粉白背景)+名称(12vp,1行省略)+类型·日期(9vp粉褐)+价格(13vp蔷薇粉加粗)+状态标签(orderStatusColor/orderStatusBg 纯函数)+删除按钮(10vp警告红)。
删除按钮触发删除订单弹框(⚠️警告+红色警告说明+取消/确认删除),确认删除时 removeOrder 纯函数删除订单(返回过滤后的新数组)并关闭弹框。
10.8 编辑收藏夹
点击编辑收藏夹按钮触发编辑收藏夹弹框,含收藏夹名称输入(默认"我的收藏")+封面风格chips(裸粉/闪金/樱花/星河)+可见性chips(公开/私密)+排序方式chips(最新/价格)+取消/保存按钮。编辑收藏夹弹框覆盖收藏夹的完整配置要素,封面风格+可见性+排序方式chips选择增加便捷性。保存后关闭弹框,模拟收藏夹编辑成功。
第十一章 性能优化与工程实践
11.1 单文件架构的性能考量
多多穿戴美甲采用单文件架构(约 1500 行),所有组件、数据、函数在一个 .ets 文件中。单文件架构的性能优势:无跨文件导入开销、编译时类型检查更高效、运行时模块加载更快。对于中等规模应用(1500行以内),单文件架构是合理选择。
11.2 @State 状态最小化原则
项目遵循"状态最小化"原则:
- 全局数据用 const 常量(NAILS/BANNERS/SERIES/ARTISTS/POSTS/ORDERS 等),不进入响应式系统
- 组件内交互状态用 @State(curStyle/pressedIndex/showBuy/buySize 等),仅在需要时声明
- 根组件仅管理 activeTab 一个跨组件状态
状态最小化减少响应式系统的依赖追踪开销,提升渲染性能。每个 @State 变化时仅触发依赖该状态的组件重新渲染,不影响其他组件。
11.3 ForEach 键值优化
项目所有 ForEach 均提供键值生成函数(第二个参数),如 (n: NailItem) => n.id.toString()、(s: SeriesItem) => s.id.toString()、(p: FeedPost) => p.id.toString()。键值生成函数确保列表项在数据变化时被正确复用,而非全量重建,大幅提升列表渲染性能。
ForEach 键值优化在列表数据变化时(如点赞切换、订单删除)尤为重要:框架通过键值识别哪些项变化、哪些项不变,仅重新渲染变化的项,不变的项直接复用。
11.4 纯函数数据转换
项目 23 个全局纯函数处理数据转换(styleColor/salesText/seriesPct/ratingStars/buyTotal/pointsProgress/withPostLiked/filterNails/customSummary 等),纯函数相同输入永远产生相同输出,无副作用。纯函数的性能优势:可缓存结果、易于测试、无状态依赖、可并行执行。
withPostLiked 和 removeOrder 纯函数采用不可变更新方式(返回新数组而非修改原数组),符合 ArkTS 状态管理最佳实践,避免直接修改 @State 导致的状态不一致。
11.5 属性动画性能
项目 4 组交互动画全部使用属性动画(.animation()),属性动画由框架自动调度,性能优于手动逐帧动画。属性动画仅在依赖的状态变化时触发,不持续占用 CPU。动画时长控制在 180-500ms,符合移动端交互动画的最佳时长范围(200-500ms)。
按压缩放 180ms(快速按压反馈)、选中缩放 200ms(适中选中反馈)、爱心点赞缩放 250ms(弹性点赞反馈)、星星脉冲 500ms(缓慢闪烁反馈),不同动画时长匹配不同交互场景。
11.6 Flex 布局性能
项目大量使用 Flex 布局(双列网格 FlexWrap.Wrap、Tab栏 FlexAlign.Center、统计栏 FlexAlign.SpaceBetween),Flex 布局是 ArkTS 推荐的高性能布局方式,框架对 Flex 布局有专门优化。避免使用复杂的 position 绝对定位(仅在弹框和角标等必要场景使用),减少布局计算开销。
11.7 图片占位与懒加载
社区帖子图片使用纯色占位区(浅粉背景+emoji),而非真实图片加载,避免网络图片加载的性能开销。对于概念应用,纯色占位区是合理选择,既展示布局又保证性能。实际生产环境可替换为 LazyForEach + 网络图片懒加载。
11.8 代码组织规范
项目代码组织遵循以下规范:
- 接口定义在前(9 个 interface)
- 调色板常量次之(NP)
- 静态选项数组再次之(20 组 const)
- 纯函数再次之(23 个 function)
- 业务数据常量再次之(7 组 const)
- Tab 枚举次之(NailTab)
- 根入口组件最后(NailApp @Entry)
- 子组件按 Tab 顺序排列(NailsContent→SeriesContent→CustomContent→ArtistContent→CommunityContent→MineContent)
这种"定义→配置→函数→数据→组件"的组织方式符合阅读习惯,便于维护和扩展。
第十二章 穿戴甲文化与美妆电商趋势
12.1 穿戴甲的兴起
穿戴甲(Press-on Nails)是近年来美妆界的新兴品类,它将美甲从"到店服务"转变为"居家快消品"。穿戴甲的核心优势:价格亲民(¥9.9-¥89 vs 到店美甲¥100-¥500)、使用便捷(果冻胶粘贴,5分钟上手)、款式丰富(随时更换搭配服装)、不伤甲(无需打磨本甲)。
Z世代是穿戴甲的核心消费群体,她们追求"每日美甲不重样"的穿搭体验,穿戴甲的快消属性完美契合这一需求。多多穿戴美甲正是瞄准这一市场,以拼多多式高性价比电商模式提供丰富的穿戴甲款式。
12.2 穿戴甲款式分类
项目定义 6 种穿戴甲款式风格,覆盖主流需求:
- 法式:优雅白边,通勤气质首选,适合职场和日常
- 猫眼:极光折射,流光溢彩,适合约会和派对
- 晕染:渐变过渡,清透自然,适合春夏和休闲
- 镶钻:奢华闪耀,吸睛夺目,适合婚礼和重要场合
- 纯色:简约百搭,基础必备,适合新手和日常
每种风格有不同的价格区间和目标用户,法式和纯色偏平价(¥12.9-¥55),猫眼和晕染偏中端(¥25.9-¥59),镶钻偏高端(¥68-¥89)。
12.3 系列成套购买趋势
系列成套购买是穿戴甲电商的新兴趋势:将同主题多款穿戴甲打包成系列,提供折扣优惠,鼓励用户成套购买。项目定义 9 个系列(樱花限定/法式经典/猫眼幻境/婚礼定制/国风鎏金/元气多巴胺/冰透水感/轻熟职场/复古巴洛克),每个系列含 7-18 款,折扣 35%-71%。
系列成套购买的优势:用户一次获得多款同主题穿戴甲(搭配不同服装和场合)、系列折扣比单买更划算、系列主题增加收藏价值和仪式感。折扣进度条直观展示系列折扣力度,高折扣系列优先展示,符合拼多多式高折扣营销策略。
12.4 个性定制需求增长
个性定制是穿戴甲市场的高端增长点:用户根据自己的喜好选择甲型、长度、底色、装饰,定制独一无二的穿戴甲。项目定制Tab提供四维选择系统(甲型3种/长度3种/底色5种/装饰4种),实时预览和动态计价,定制价格 ¥34-¥80(长度¥29-¥55+装饰¥5-¥25)。
个性定制的目标用户是追求独特性的中高端消费者,她们愿意为"独一无二"支付溢价。定制需求弹框允许用户提交更复杂的定制需求(名称/风格/预算/工期/描述),满足高级定制需求。
12.5 同城美甲师预约服务
穿戴甲的普及并未完全替代到店美甲服务,而是形成"居家穿戴甲+到店精致美甲"的双轨消费模式。项目美甲师Tab提供12位同城美甲师预约服务,覆盖12个城市,价格 ¥35-¥199,评分 4.5-4.9。
美甲师预约服务的目标用户是追求精致体验的消费者,她们在重要场合(婚礼/派对/约会)选择到店美甲师提供专业服务。星星脉冲动画和订单迷你条增加美甲师页面的交互性和信息密度。
12.6 社区晒甲与UGC趋势
社区晒甲是穿戴甲电商的重要增长引擎:用户晒出自己的穿戴甲作品,形成 UGC(用户生成内容)内容生态,激发其他用户的购买欲望。项目社区Tab提供12条晒甲帖子,爱心点赞缩放动画增加互动趣味性。
社区晒甲的价值:真实用户作品比官方商品图更有说服力("买家秀"效应)、点赞评论互动增加用户粘性和停留时长、热门帖子形成种草效应带动销售。发帖按钮鼓励用户创作内容,形成内容闭环。
12.7 拼多多式美妆电商模式
多多穿戴美甲采用拼多多式美妆电商模式,核心特征:
- 高性价比:价格 ¥9.9-¥89,远低于到店美甲
- 高折扣营销:原价删除线、系列折扣35%-71%、新人1元、限时折扣、满减
- 高转化UI:蔷薇粉主按钮、价格加粗、销量展示、热卖标签、按压缩放反馈
- 社交裂变:社区晒甲、点赞收藏、发帖分享
- 游戏化运营:积分等级、Lv.8剁手党、已省金额、消费满返积分
拼多多式美妆电商模式通过极致性价比和高转化UI,在下沉市场和年轻消费者中获得成功。多多穿戴美甲将这一模式应用于穿戴甲品类,具有市场潜力。
第十三章 美妆电商生活方式与用户体验
13.1 美甲作为生活方式
美甲早已超越"装饰"的范畴,成为一种生活方式表达。对于Z世代女性而言,美甲是"今日穿搭"的重要组成部分,不同款式的美甲搭配不同的服装、场合、心情。多多穿戴美甲的6Tab设计正是围绕这一生活方式展开:美甲浏览满足"今日穿什么甲"的需求、系列成套满足"主题搭配"的需求、定制满足"独一无二"的需求、美甲师满足"精致场合"的需求、社区满足"晒甲分享"的需求、我的满足"消费管理"的需求。
13.2 裸粉白蔷薇粉香槟金的色彩心理学
多多穿戴美甲的三色配色体系具有深层的色彩心理学考量:
- 裸粉白 #FFF7F9:柔和、温暖、女性化,营造美妆电商的舒适购物氛围,降低用户的心理防御,增加停留时长
- 蔷薇粉 #E91E63:热情、活力、行动召唤,作为主按钮和价格强调色,激发购买冲动,蔷薇粉在裸粉白背景上具有强烈的视觉穿透力
- 香槟金 #D4A857:品质、奢华、价值感,作为评分和等级强调色,传递"高品质"和"尊贵感",香槟金与蔷薇粉形成冷暖对撞,增加视觉层次
三色配色体系既符合美妆电商的女性化定位,又通过高对比的蔷薇粉实现高转化,通过香槟金传递品质感,是经过精心设计的配色方案。
13.3 按压缩放与购物体验
按压缩放动画是多多穿戴美甲的特色交互设计,每张美甲卡片点击时 scale 缩小到 0.96,180ms EaseOut 动画,模拟真实按钮按压反馈。这种微交互虽然微小,但对购物体验有显著提升:
- 真实感:按压反馈让用户感觉在操作真实物体,增加界面的"质感"
- 确定性:按压反馈确认用户的点击已被接收,减少误操作焦虑
- 满足感:按压-释放的弹性反馈带来轻微的愉悦感,增加购物的趣味性
按压缩放动画在电商App中广泛应用(如淘宝/拼多多/京东的商品卡片),多多穿戴美甲将这一交互模式应用于 ArkTS 声明式UI,还原真实电商购物体验。
13.4 爱心点赞与社交满足感
爱心点赞缩放动画是社区Tab的核心交互,点赞时爱心放大到 1.35 倍并变红,250ms EaseOut 动画。爱心点赞的心理学机制:
- 即时反馈:点赞后爱心立即变色放大,确认操作成功
- 放大效应:1.35倍放大比普通缩放更大,增加点赞的"仪式感"和"满足感"
- 颜色变化:白色🤍→红色❤️的颜色变化传递"喜欢"的情感表达
- 数字变化:点赞数+1提供量化反馈,增加成就感
爱心点赞是社交App的核心交互(如微信/小红书/抖音),多多穿戴美甲将这一交互模式应用于社区晒甲,还原真实社交体验。
13.5 积分等级与游戏化运营
积分等级系统是我的Tab的游戏化设计:用户消费获得积分,积分累积升级等级(Lv.8剁手党),等级提升解锁特权(专属客服+新品优先购)。积分等级进度条(53.6%)直观展示升级进度,升级提示引导用户继续消费。
游戏化运营的心理学机制:
- 成就感:等级提升带来成就感和身份认同(Lv.8剁手党)
- 进度感:进度条展示升级进度,"再攒2320积分"提供明确目标
- 特权感:等级特权(专属客服+新品优先购)传递尊贵感
- 沉没成本:已累积的积分和等级增加用户粘性,减少流失
积分等级系统在电商App中广泛应用(如淘宝88VIP/京东Plus/拼多多多多果园),多多穿戴美甲将这一运营模式应用于穿戴甲电商,增加用户粘性和复购率。
13.6 月度消费与自我认知
月度消费柱状图是我的Tab的数据可视化设计,展示用户近6个月的消费趋势(1月¥128/2月¥96/3月¥152/4月¥88/5月¥210/6月¥175),高消费月份用蔷薇粉突出显示。月度消费柱状图的价值:
- 自我认知:帮助用户了解自己的消费习惯和趋势(“我5月花了¥210,是不是太多了?”)
- 消费控制:可视化的消费数据帮助用户控制消费,避免过度消费
- 趋势洞察:消费趋势反映用户的生活变化(如5月消费高峰可能对应节日/聚会季)
- 数据驱动:数据可视化让消费管理从"感觉"变为"数据",增加理性消费
月度消费柱状图在理财App中广泛应用,多多穿戴美甲将这一数据可视化模式应用于美妆电商,增加应用的实用性和用户粘性。
13.7 拼多多式高转化UI设计
多多穿戴美甲的UI设计遵循拼多多式高转化原则:
- 主按钮蔷薇粉:所有主操作按钮(立即购买/提交需求/确认预约/保存/确认删除)使用蔷薇粉背景白字,高对比度高可见性
- 价格蔷薇粉加粗:所有价格使用蔷薇粉15-20vp加粗,突出价格信息
- 原价删除线:原价使用粉褐文字+删除线,营造折扣感
- 销量展示:销量使用"已售1.2w+"格式,营造热销感和社会认同
- 热卖标签:🔥热卖标签左上角定位,营造紧迫感
- 角标设计:购物车角标(2)使用警告红背景白字,右上角定位
- 按压缩放:卡片点击按压缩放,增加交互真实感
- 高折扣展示:系列折扣进度条+折扣标签,高折扣优先展示
拼多多式高转化UI设计通过视觉层次和交互反馈,最大化用户的购买转化率,是电商App经过大量A/B测试验证的设计模式。多多穿戴美甲将这一设计模式应用于 ArkTS 声明式UI,还原真实电商购物体验。
第十四章 开发环境搭建与运行指南
本章介绍多多穿戴美甲的开发环境搭建流程,包括 DevEco Studio 安装、HarmonyOS SDK 配置、项目创建、应用运行、预览调试五个步骤,帮助开发者快速上手 ArkTS 声明式UI开发。
14.1 安装 DevEco Studio
DevEco Studio 是华为官方推出的 HarmonyOS 应用开发 IDE,基于 IntelliJ IDEA 社区版构建,提供 ArkTS/ArkUI 代码编辑、预览器、模拟器、调试器等完整开发工具链。
安装步骤:
- 访问华为开发者联盟官网(developer.huawei.com),进入"开发→DevEco Studio"下载页面
- 选择对应操作系统版本(Windows/macOS),下载最新稳定版安装包
- Windows 版运行 .exe 安装程序,按照向导完成安装(建议安装路径不含中文和空格)
- macOS 版打开 .dmg 镜像,将 DevEco Studio 拖拽到 Applications 文件夹
- 首次启动 DevEco Studio,选择"不导入设置",进入欢迎界面
- 按照引导安装 Node.js、ohpm(HarmonyOS 包管理器)等必要依赖(IDE 会自动检测并提示安装)
系统要求:Windows 10/11 64位 或 macOS 11+,内存建议 8GB 以上(推荐 16GB),硬盘空间 10GB 以上。

14.2 配置 HarmonyOS SDK
安装完成后需要配置 HarmonyOS SDK,SDK 包含 ArkTS 编译器、ArkUI 组件库、系统 API、模拟器镜像等开发必需组件。
配置步骤:
- 打开 DevEco Studio,进入欢迎界面,点击"Configure→SDK Manager"(或在项目中通过"File→Settings→SDK"打开)
- 在 SDK Manager 中,设置 SDK 安装路径(建议路径不含中文和空格)
- 勾选需要安装的 API 版本(如 API 9/10/11/12),每个 API 版本包含:ArkTS SDK、System SDK、Previewer(预览器)、Emulator(模拟器镜像)等组件
- 点击"Apply"开始下载安装,等待下载完成(下载时间取决于网络速度,建议使用稳定网络)
- 安装完成后,SDK 路径会自动配置到项目中,无需手动设置环境变量
SDK 版本选择:多多穿戴美甲使用 ArkTS 声明式UI(ArkUI),建议使用 API 9 及以上版本。API 9 是首个正式支持 ArkTS 声明式UI的版本,API 10/11/12 增加了更多组件和能力。本项目使用的组件(Column/Row/Text/Stack/Scroll/Flex/ForEach/TextInput/TextArea/Progress/Divider/Button)和装饰器(@Entry/@Component/@State/@Builder)在 API 9 及以上版本均可用。

14.3 创建 HarmonyOS 项目
SDK 配置完成后,创建一个新的 HarmonyOS 项目来运行多多穿戴美甲代码。
创建步骤:
- 打开 DevEco Studio 欢迎界面,点击"Create Project"创建新项目
- 选择项目模板:选择"Application→Empty Ability"(空能力模板),这是最基础的 ArkTS 项目模板
- 配置项目信息:
- Project name:项目名称(如 NailApp)
- Bundle name:包名(如 com.example.nailapp,需唯一标识应用)
- Save location:项目保存路径(建议不含中文和空格)
- Compile SDK:编译 SDK 版本(选择 API 9 或以上)
- Model:模型选择"Stage"(Stage 模型是 HarmonyOS 推荐的应用模型)
- Language:语言选择"ArkTS"
- 点击"Finish"创建项目,等待项目初始化和依赖下载完成
- 项目创建完成后,在左侧项目树中找到
entry/src/main/ets/pages/Index.ets文件,这是应用的主页面 - 将多多穿戴美甲的完整 ArkTS 代码复制到 Index.ets 文件中(覆盖原有内容)
- 检查
entry/src/main/module.json5配置文件,确认主页面路径指向 Index.ets
项目结构说明:
entry/src/main/ets/pages/Index.ets:主页面(多多穿戴美甲代码所在文件)entry/src/main/module.json5:模块配置文件(应用名称、图标、权限等)entry/src/main/resources/:资源文件(字符串、颜色、图片等)build-profile.json5:构建配置文件oh-package.json5:ohpm 包管理配置文件

14.4 运行应用
代码复制完成后,可以通过模拟器或真机运行多多穿戴美甲应用。
通过模拟器运行:
- 点击 DevEco Studio 顶部工具栏的设备选择器,选择"Tools→Device Manager"打开设备管理器
- 在设备管理器中,点击"Create Device"创建新模拟器
- 选择设备型号(如 Phone→Mate 60 Pro),选择系统镜像(需与 SDK API 版本匹配)
- 点击"Next→Finish"完成模拟器创建,等待模拟器镜像下载完成
- 启动模拟器(点击设备管理器中的播放按钮),等待模拟器启动完成
- 模拟器启动后,在顶部工具栏的设备选择器中选择该模拟器
- 点击运行按钮(绿色三角形▶️)或按快捷键 Shift+F10,开始构建并安装应用
- 等待构建完成(首次构建可能需要 1-2 分钟),应用会自动安装到模拟器并启动
- 在模拟器中查看多多穿戴美甲应用,测试各 Tab 和弹框功能
通过真机运行:
- 将 HarmonyOS 手机通过 USB 连接到电脑
- 在手机上开启"开发者模式"(设置→关于手机→连续点击版本号7次)
- 开启"USB 调试"(设置→系统和更新→开发人员选项→USB 调试)
- 在 DevEco Studio 顶部工具栏的设备选择器中选择连接的真机
- 点击运行按钮,应用会自动安装到手机并启动
- 首次运行可能需要在手机上确认"允许安装"
常见问题:
- 构建失败:检查 SDK 版本是否匹配、代码是否有语法错误、依赖是否完整
- 模拟器启动慢:首次启动需要初始化系统,后续启动会加快
- 应用安装失败:检查设备是否开启 USB 调试、签名配置是否正确

14.5 预览与调试
DevEco Studio 提供强大的预览器和调试器,帮助开发者在开发过程中实时查看 UI 效果和调试代码。
预览器(Previewer):
- 打开 Index.ets 文件,点击右侧边栏的"Previewer"标签(或按快捷键 Alt+2)
- 预览器会实时渲染当前文件的 UI 效果,代码修改后自动刷新
- 预览器支持多种设备尺寸(手机/平板/折叠屏),可在顶部切换
- 预览器支持实时交互(点击按钮、切换 Tab、滚动列表),可直接在预览器中测试功能
- 多多穿戴美甲的按压缩放、选中缩放、爱心点赞缩放、星星脉冲等交互动画均可在预览器中实时查看
- 预览器支持"多预览模式",可同时查看多个设备的渲染效果
调试器(Debugger):
- 点击调试按钮(绿色虫子🐛)或按快捷键 Shift+F9,以调试模式运行应用
- 在代码行号左侧点击设置断点(红色圆点),程序运行到断点时会暂停
- 调试器面板显示:调用栈(Call Stack)、变量(Variables)、监视(Watches)、控制台(Console)
- 可在变量面板查看 @State 状态变量的当前值(如 activeTab/curStyle/pressedIndex/posts/orders)
- 可在控制台输出日志(使用 console.log()),查看运行时信息
- 调试控制:继续(F9)、单步跳过(F8)、单步进入(F7)、单步退出(Shift+F8)
日志查看:
- 使用
console.log()输出调试日志 - 在 DevEco Studio 底部的"Log"面板查看应用日志
- 可通过日志级别(info/warn/error)和关键词过滤日志
- 多多穿戴美甲可在关键交互点添加日志(如点赞时输出帖子id、删除订单时输出订单id),方便调试
性能分析:
- DevEco Studio 提供"Profiler"性能分析工具,可分析 CPU、内存、帧率
- 可在运行应用时启动 Profiler,录制性能数据
- 多多穿戴美甲的交互动画(按压缩放/爱心点赞缩放等)可通过 Profiler 分析帧率是否流畅
- 建议目标帧率 60fps,动画期间帧率不低于 30fps

总结与展望
项目总结
多多穿戴美甲是一款基于 HarmonyOS ArkTS 声明式UI的拼多多风格穿戴甲美妆电商概念应用,通过单文件架构实现了完整的电商购物体验。项目核心亮点:
视觉设计:裸粉白 #FFF7F9 × 蔷薇粉 #E91E63 × 香槟金 #D4A857 三色配色体系,11 色完整调色板,拼多多式高饱和电商视觉,原价删除线、销量展示、热卖标签、系列折扣进度条等营销元素。
架构设计:单文件组件化架构,约 1500 行代码,9 个 TypeScript 接口,20 组全局数据常量,23 个全局纯函数,6 个 Tab 内容组件,6 套差异化弹框,4 组交互动画。
功能完整:美甲浏览(18款/6风格/按压缩放/立即购买)、系列浏览(6横幅/9系列/折扣进度条/系列详情)、个性定制(甲型/长度/底色/装饰四维选择/实时预览/动态计价/需求提交)、美甲师预约(12位同城美甲师/星星脉冲/订单迷你条/预约)、社区晒甲(12条帖子/爱心点赞缩放/评论收藏/发帖)、我的(资料卡/积分等级/月度消费柱状图/10笔订单/收藏夹/删除订单)。
交互体验:按压缩放(scale 0.96,180ms)、选中缩放(scale 1.1,200ms)、爱心点赞缩放(scale 1.35,250ms)、星星脉冲(opacity 1↔0.35,500ms),4 组属性动画还原真实电商购物体验。
数据可视化:系列折扣进度条(9系列折扣力度可视化)、月度消费柱状图(6个月消费趋势,高消费蔷薇粉/普通香槟金)、积分等级进度条(53.6%进度)、订单迷你条(12位美甲师订单量可视化)、评分星星(★☆组合)。
技术价值
多多穿戴美甲展示了 ArkTS 声明式UI在电商类应用中的完整实践:
- 声明式UI的表达力:通过 Column/Row/Stack/Scroll/Flex/ForEach 等基础组件,配合 @Builder 自定义构建函数,可高效构建复杂电商界面
- 属性动画的便捷性:.animation() 属性动画一行代码实现交互动画,无需手动管理动画帧
- 状态管理的清晰性:@State 局部状态 + const 全局常量 + 纯函数数据转换,三级状态管理策略清晰高效
- 单文件架构的可行性:1500 行单文件架构对于中等规模应用是合理选择,复制即运行,便于学习和分享
- 电商模式的可复用性:拼多多式高转化UI设计(主按钮蔷薇粉/价格加粗/原价删除线/销量展示/按压缩放)可复用于其他电商类应用
未来展望
多多穿戴美甲作为概念应用,未来可在以下方向扩展:
功能扩展:
- 增加搜索功能(顶部搜索栏可输入关键词搜索美甲款式/系列/美甲师)
- 增加购物车功能(当前仅有购物车角标,可扩展为完整购物车页面)
- 增加支付流程(立即购买后可扩展为完整的下单-支付-物流跟踪流程)
- 增加用户评价系统(美甲款式和美甲师可增加用户评价和评分)
- 增加消息通知系统(拼车成功/订单发货/促销活动等消息通知)
- 增加深色模式(当前仅浅色模式,可扩展为跟随系统深色模式)
技术升级:
- 引入 LazyForEach 实现大数据量列表懒加载(当前 ForEach 适合中小数据量)
- 引入网络请求框架(当前数据为硬编码常量,可扩展为从后端API获取)
- 引入本地持久化(当前状态为内存状态,可扩展为 Preferences 或关系型数据库持久化)
- 引入组件化拆分(当前单文件架构,可扩展为多文件多模块架构,便于团队协作)
- 引入单元测试和UI测试(当前无测试,可扩展为完整测试体系)
- 引入性能监控和埋点分析(当前无监控,可扩展为用户行为分析和性能监控)
设计优化:
- 增加更多交互动画(如页面切换动画、列表加载动画、弹框入场动画)
- 增加手势交互(如左滑删除订单、下拉刷新、上拉加载更多)
- 增加自定义组件封装(如美甲卡片组件、系列卡片组件、订单卡片组件,提高复用性)
- 增加主题切换(如裸粉白主题/暗黑主题/节日主题)
- 增加无障碍支持(如屏幕阅读器、字体大小适配、颜色对比度优化)
商业化方向:
- 接入真实电商后端(商品管理/订单管理/支付/物流)
- 接入推荐算法(基于用户浏览历史和购买记录的个性化推荐)
- 接入社交裂变(拼团/砍价/分享有奖等拼多多式社交玩法)
- 接入直播带货(美甲师直播展示穿戴甲款式,实时购买)
- 接入AR试戴(通过摄像头AR技术预览穿戴甲效果)
多多穿戴美甲展示了 ArkTS 声明式UI在电商类应用中的强大表达力和开发效率,拼多多式高转化UI设计与穿戴甲美妆电商品类的结合具有市场潜力。希望本文的深度解析能帮助开发者更好地理解 ArkTS 声明式UI开发和电商应用设计,欢迎基于本项目进行二次开发和创新。

附录:完整 ArkTS 源码
以下是多多穿戴美甲的完整 ArkTS 源码,包含接口定义、调色板、静态选项数组、纯函数、业务数据常量、Tab枚举、根入口组件、6个Tab内容组件、6套弹框组件,约 1500 行。复制到 DevEco Studio 项目的 Index.ets 文件中即可运行。
// ============================================================
// 2023.ets —— 多多穿戴美甲(拼多多风格 · 穿戴甲美妆电商)
// 主题:裸粉白 #FFF7F9 × 蔷薇粉 #E91E63 × 香槟金 #D4A857
// 6 Tab 单排:美甲 / 系列 / 定制 / 美甲师 / 社区 / 我的
// ============================================================
// ---------- 接口定义 ----------
interface NailItem {
id: number
name: string
icon: string
style: string
duration: string
price: number
originPrice: number
sales: number
}
interface SeriesBanner {
id: number
name: string
icon: string
desc: string
tag: string
color: string
}
interface SeriesItem {
id: number
name: string
icon: string
count: number
priceRange: string
discount: number
sold: number
}
interface ArtistItem {
id: number
name: string
avatar: string
studio: string
location: string
tags: string
rating: number
orders: number
price: number
}
interface FeedPost {
id: number
user: string
avatar: string
text: string
likes: number
comments: number
liked: boolean
date: string
}
interface MyOrder {
id: number
name: string
icon: string
price: number
status: string
date: string
type: string
}
interface MonthSpend {
month: string
amount: number
}
interface NailPalette {
bg: string
primary: string
secondary: string
card: string
cardDeep: string
text: string
textSub: string
chip: string
warn: string
white: string
}
// ---------- 调色板(裸粉白 × 蔷薇粉 × 香槟金) ----------
const NP: NailPalette = {
bg: '#FFF7F9',
primary: '#E91E63',
secondary: '#D4A857',
card: '#FFFFFF',
cardDeep: '#FDEEF3',
text: '#4A2C3A',
textSub: '#A67F8E',
chip: '#FBE3EC',
warn: '#D32F2F',
white: '#FFFFFF'
}
// ---------- 静态选项数组 ----------
const NAIL_STYLES: string[] = ['全部', '法式', '猫眼', '晕染', '镶钻', '纯色']
const SIZE_OPTIONS: string[] = ['XS', 'S', 'M', 'L']
const WEAR_DURATIONS: string[] = ['7天', '14天', '30天']
const CUSTOM_SHAPES: string[] = ['圆形', '方形', '杏仁形']
const CUSTOM_LENGTHS: string[] = ['短', '中', '长']
const CUSTOM_LENGTH_PRICES: number[] = [29, 39, 55]
const CUSTOM_COLOR_NAMES: string[] = ['甜莓粉', '蔷薇粉', '奶油杏', '薰衣紫', '珠光白']
const CUSTOM_COLORS: string[] = ['#F06292', '#E91E63', '#FFD9A0', '#C9B6E4', '#FDF2F4']
const CUSTOM_DECORS: string[] = ['猫眼', '碎钻', '金属线', '贴片']
const CUSTOM_DECOR_PRICES: number[] = [10, 25, 15, 5]
const TIME_SLOTS: string[] = ['10:00', '14:00', '16:00', '19:00']
const SERVICE_TYPES: string[] = ['贴甲', '卸甲', '手部护理']
const REF_STYLES: string[] = ['法式', '猫眼', '晕染', '镶钻']
const BUDGET_OPTIONS: string[] = ['¥50内', '¥50-100', '¥100-200', '¥200+']
const DEADLINE_OPTIONS: string[] = ['3天', '7天', '10天', '15天']
const COVER_STYLES: string[] = ['裸粉', '闪金', '樱花', '星河']
const VISIBILITY_OPTIONS: string[] = ['公开', '私密']
const SORT_OPTIONS: string[] = ['最新', '价格']
// ---------- 纯函数 ----------
function styleColor(style: string): string {
if (style === '法式') {
return '#C29A45'
}
if (style === '猫眼') {
return '#7E57C2'
}
if (style === '晕染') {
return '#E91E63'
}
if (style === '镶钻') {
return '#29B6F6'
}
return '#8D6E63'
}
function salesText(n: number): string {
if (n >= 10000) {
return (n / 10000).toFixed(1) + 'w+'
}
return n + '+'
}
function seriesPct(s: SeriesItem): string {
return s.discount + '%'
}
function seriesDiscountLabel(s: SeriesItem): string {
return (10 - s.discount / 10).toFixed(1) + '折'
}
function orderStatusColor(status: string): string {
if (status === '制作中') {
return '#B8860B'
}
if (status === '已发货') {
return NP.primary
}
return '#7CB342'
}
function orderStatusBg(status: string): string {
if (status === '制作中') {
return '#FBF0D9'
}
if (status === '已发货') {
return NP.chip
}
return '#EDF5DC'
}
function ratingStars(r: number): string {
let s: string = ''
for (let i: number = 0; i < 5; i++) {
s += (i < Math.round(r)) ? '★' : '☆'
}
return s
}
function orderBarWidth(n: number): string {
return Math.round(n / 3200 * 100) + '%'
}
function spendBarH(v: number): number {
return v * 0.3
}
function buyTotal(price: number, count: number): number {
return Math.round(price * count * 10) / 10
}
function pointsProgress(): number {
return Math.round(2680 / 5000 * 100)
}
function removeOrder(list: MyOrder[], id: number): MyOrder[] {
return list.filter((o: MyOrder) => o.id !== id)
}
function withPostLiked(list: FeedPost[], id: number): FeedPost[] {
return list.map((p: FeedPost) => {
if (p.id !== id) {
return p
}
const np: FeedPost = {
id: p.id, user: p.user, avatar: p.avatar, text: p.text,
likes: p.likes + (p.liked ? -1 : 1), comments: p.comments, liked: !p.liked, date: p.date
}
return np
})
}
function filterNails(list: NailItem[], style: string): NailItem[] {
if (style === '全部') {
return list
}
return list.filter((n: NailItem) => n.style === style)
}
function customSummary(shapeIdx: number, lenIdx: number, colorIdx: number, decorIdx: number): string {
return '甲型·' + CUSTOM_SHAPES[shapeIdx] + ' / 长度·' + CUSTOM_LENGTHS[lenIdx] +
' / 底色·' + CUSTOM_COLOR_NAMES[colorIdx] + ' / 装饰·' + CUSTOM_DECORS[decorIdx]
}
function customPrice(lenIdx: number, decorIdx: number): number {
return CUSTOM_LENGTH_PRICES[lenIdx] + CUSTOM_DECOR_PRICES[decorIdx]
}
function customPriceText(lenIdx: number, decorIdx: number): string {
return '¥' + (CUSTOM_LENGTH_PRICES[lenIdx] + CUSTOM_DECOR_PRICES[decorIdx])
}
function commentCount(n: number): string {
return n + ''
}
function decorIcon(idx: number): string {
if (idx === 0) {
return '✨'
}
if (idx === 1) {
return '💎'
}
if (idx === 2) {
return '➿'
}
return '🫧'
}
function decorColor(idx: number): string {
if (idx === 0) {
return '#7E57C2'
}
if (idx === 1) {
return '#29B6F6'
}
if (idx === 2) {
return '#B0BEC5'
}
return '#FFB74D'
}
function bannerCardH(): number {
return 128
}
// ---------- 数据:美甲款式 ----------
const NAILS: NailItem[] = [
{ id: 1, name: '法式白边', icon: '💅', style: '法式', duration: '可穿戴7天', price: 19.9, originPrice: 29.9, sales: 1280 },
{ id: 2, name: '猫眼星辰', icon: '✨', style: '猫眼', duration: '可穿戴14天', price: 45, originPrice: 69, sales: 3800 },
{ id: 3, name: '奶油晕染', icon: '🍰', style: '晕染', duration: '可穿戴7天', price: 25.9, originPrice: 39.9, sales: 2100 },
{ id: 4, name: '钻石女王', icon: '💎', style: '镶钻', duration: '可穿戴14天', price: 68, originPrice: 99, sales: 860 },
{ id: 5, name: '裸粉纯色', icon: '🌸', style: '纯色', duration: '可穿戴7天', price: 9.9, originPrice: 15.9, sales: 5200 },
{ id: 6, name: '法式波点', icon: '🟤', style: '法式', duration: '可穿戴7天', price: 22.9, originPrice: 35, sales: 1600 },
{ id: 7, name: '琥珀猫眼', icon: '🐈', style: '猫眼', duration: '可穿戴14天', price: 49, originPrice: 75, sales: 1500 },
{ id: 8, name: '渐变晕染', icon: '🌈', style: '晕染', duration: '可穿戴7天', price: 29.9, originPrice: 45, sales: 980 },
{ id: 9, name: '星钻贴片', icon: '✦', style: '镶钻', duration: '可穿戴30天', price: 89, originPrice: 129, sales: 420 },
{ id: 10, name: '经典纯白', icon: '⚪', style: '纯色', duration: '可穿戴7天', price: 12.9, originPrice: 19.9, sales: 6100 },
{ id: 11, name: '法式小香风', icon: '👜', style: '法式', duration: '可穿戴14天', price: 39, originPrice: 58, sales: 720 },
{ id: 12, name: '绿野猫眼', icon: '🌿', style: '猫眼', duration: '可穿戴14天', price: 42, originPrice: 65, sales: 1100 },
{ id: 13, name: '玫瑰晕染', icon: '🌹', style: '晕染', duration: '可穿戴7天', price: 32, originPrice: 48, sales: 640 },
{ id: 14, name: '蕾丝镶钻', icon: '🎀', style: '镶钻', duration: '可穿戴30天', price: 75, originPrice: 108, sales: 310 },
{ id: 15, name: '樱花纯色', icon: '🍒', style: '纯色', duration: '可穿戴7天', price: 15.9, originPrice: 24.9, sales: 4300 },
{ id: 16, name: '珍珠法式', icon: '🦪', style: '法式', duration: '可穿戴14天', price: 55, originPrice: 79, sales: 560 },
{ id: 17, name: '幻彩猫眼', icon: '🌌', style: '猫眼', duration: '可穿戴30天', price: 59, originPrice: 89, sales: 890 },
{ id: 18, name: '冰透晕染', icon: '🧊', style: '晕染', duration: '可穿戴14天', price: 36, originPrice: 52, sales: 470 }
]
// ---------- 数据:系列横幅 ----------
const BANNERS: SeriesBanner[] = [
{ id: 1, name: '樱花限定系列', icon: '🌸', desc: '春日樱花薄涂 · 元气少女感', tag: '首发', color: '#D81B60' },
{ id: 2, name: '法式经典系列', icon: '🤍', desc: '优雅白边 · 通勤气质首选', tag: '人气TOP1', color: '#4A2C3A' },
{ id: 3, name: '猫眼幻境系列', icon: '🌌', desc: '极光折射 · 流光溢彩', tag: '高光推荐', color: '#5E35B1' },
{ id: 4, name: '婚礼定制系列', icon: '💍', desc: '一生一次的闪耀 · 高端定制', tag: '高端', color: '#B8860B' },
{ id: 5, name: '国风鎏金系列', icon: '🏮', desc: '新中式美学 · 鎏金缠枝', tag: '新中式', color: '#C0392B' },
{ id: 6, name: '元气多巴胺系列', icon: '🍬', desc: '高饱和撞色 · 夏天就要缤纷', tag: '年轻力', color: '#00897B' }
]
// ---------- 数据:系列列表 ----------
const SERIES: SeriesItem[] = [
{ id: 1, name: '樱花限定', icon: '🌸', count: 12, priceRange: '¥19-89', discount: 58, sold: 3200 },
{ id: 2, name: '法式经典', icon: '🤍', count: 18, priceRange: '¥12-79', discount: 46, sold: 5200 },
{ id: 3, name: '猫眼幻境', icon: '🌌', count: 15, priceRange: '¥29-99', discount: 62, sold: 2800 },
{ id: 4, name: '婚礼定制', icon: '💍', count: 8, priceRange: '¥89-399', discount: 35, sold: 760 },
{ id: 5, name: '国风鎏金', icon: '🏮', count: 10, priceRange: '¥45-168', discount: 52, sold: 1500 },
{ id: 6, name: '元气多巴胺', icon: '🍬', count: 14, priceRange: '¥9-59', discount: 71, sold: 4100 },
{ id: 7, name: '冰透水感', icon: '🧊', count: 11, priceRange: '¥25-69', discount: 66, sold: 2300 },
{ id: 8, name: '轻熟职场', icon: '👜', count: 9, priceRange: '¥22-88', discount: 44, sold: 980 },
{ id: 9, name: '复古巴洛克', icon: '🎻', count: 7, priceRange: '¥68-199', discount: 39, sold: 540 }
]
// ---------- 数据:美甲师 ----------
const ARTISTS: ArtistItem[] = [
{ id: 1, name: '小鹿', avatar: '🦌', studio: '鹿角工作室', location: '杭州·西湖区', tags: '法式|猫眼|手绘', rating: 4.9, orders: 1280, price: 68 },
{ id: 2, name: '阿凝', avatar: '🦢', studio: '凝光美甲屋', location: '上海·静安区', tags: '晕染|渐变|贴钻', rating: 4.8, orders: 960, price: 88 },
{ id: 3, name: '桃桃', avatar: '🍑', studio: '桃气手作', location: '成都·锦江区', tags: '纯色|短甲|学生党', rating: 4.7, orders: 1580, price: 39 },
{ id: 4, name: '蓝蓝', avatar: '💙', studio: '蓝调美甲', location: '广州·天河区', tags: '猫眼|镜面|极光', rating: 4.9, orders: 2050, price: 99 },
{ id: 5, name: '小野', avatar: '🐱', studio: '野路子工作室', location: '北京·朝阳区', tags: '暗黑|撞色|金属线', rating: 4.6, orders: 780, price: 120 },
{ id: 6, name: '茉莉', avatar: '🌼', studio: '茉莉花开', location: '深圳·南山区', tags: '法式|蕾丝|复古', rating: 4.8, orders: 890, price: 75 },
{ id: 7, name: '阿花', avatar: '🌸', studio: '花间美甲', location: '南京·鼓楼区', tags: '贴片|钻饰|奢华', rating: 4.9, orders: 3200, price: 199 },
{ id: 8, name: '七仔', avatar: '🐻', studio: '七仔手绘', location: '武汉·洪山区', tags: '手绘|卡通|定制', rating: 4.7, orders: 650, price: 58 },
{ id: 9, name: '西西', avatar: '🍓', studio: '草莓美甲', location: '重庆·渝北区', tags: '纯色|渐变|果冻', rating: 4.6, orders: 540, price: 45 },
{ id: 10, name: '阿茶', avatar: '🍵', studio: '茶里茶气', location: '西安·雁塔区', tags: '国风|鎏金|晕染', rating: 4.9, orders: 1120, price: 88 },
{ id: 11, name: '妮妮', avatar: '🧸', studio: '妮妮工作室', location: '长沙·岳麓区', tags: '短甲|方圆|简约', rating: 4.5, orders: 430, price: 35 },
{ id: 12, name: '阿妍', avatar: '💅', studio: '妍美甲', location: '天津·和平区', tags: '法式|白边|职场', rating: 4.8, orders: 760, price: 69 }
]
// ---------- 数据:社区帖子 ----------
const POSTS: FeedPost[] = [
{ id: 1, user: '阿茶', avatar: '🍵', text: '今天用猫眼幻境系列做了订婚甲,灯光下低调又闪耀,姐妹们冲!', likes: 1280, comments: 156, liked: false, date: '08-26' },
{ id: 2, user: '桃桃', avatar: '🍑', text: '夏天就要冰透晕染,清透感一绝,配小裙子绝绝子~', likes: 890, comments: 88, liked: true, date: '08-25' },
{ id: 3, user: '小野', avatar: '🐱', text: '暗黑风金属线撞色,酷女孩出街必备,回头率爆表!', likes: 520, comments: 64, liked: false, date: '08-24' },
{ id: 4, user: '茉莉', avatar: '🌼', text: '法式蕾丝配婚纱,婚礼当天被夸爆,提前一个月定制的值了。', likes: 2100, comments: 230, liked: true, date: '08-23' },
{ id: 5, user: '蓝蓝', avatar: '💙', text: '极光猫眼在不同光线下颜色完全不一样,梦幻到犯规。', likes: 1560, comments: 120, liked: false, date: '08-22' },
{ id: 6, user: '阿花', avatar: '🌸', text: '贴满钻的一天,走路都带风,今天我就是全场最亮的崽。', likes: 990, comments: 77, liked: false, date: '08-21' },
{ id: 7, user: '西西', avatar: '🍓', text: '果冻色渐变,吃土少女平价首选,9.9 的快乐谁懂啊。', likes: 760, comments: 52, liked: true, date: '08-20' },
{ id: 8, user: '七仔', avatar: '🐻', text: '手绘小猫咪太治愈了,每天打字心情都变好,教程看主页。', likes: 830, comments: 95, liked: false, date: '08-19' },
{ id: 9, user: '阿凝', avatar: '🦢', text: '晕染+贴钻的进阶玩法教程来了,保姆级手把手教学。', likes: 1450, comments: 180, liked: false, date: '08-18' },
{ id: 10, user: '妮妮', avatar: '🧸', text: '短方圆甲选手的福音,上手超快,妈妈再也不怕我留长甲。', likes: 470, comments: 40, liked: false, date: '08-17' },
{ id: 11, user: '小鹿', avatar: '🦌', text: '手绘敦煌配色,国风永远的神,客人看了直接订同款。', likes: 1800, comments: 210, liked: true, date: '08-16' },
{ id: 12, user: '阿妍', avatar: '💅', text: '职场通勤法式白边,气质拿捏得死死的,低调又精致。', likes: 690, comments: 48, liked: false, date: '08-15' }
]
// ---------- 数据:我的订单 ----------
const ORDERS: MyOrder[] = [
{ id: 1, name: '猫眼星辰', icon: '✨', price: 45, status: '制作中', date: '08-20', type: '穿戴甲' },
{ id: 2, name: '樱花限定系列', icon: '🌸', price: 89, status: '已发货', date: '08-18', type: '系列装' },
{ id: 3, name: '法式白边', icon: '💅', price: 19.9, status: '已完成', date: '08-10', type: '穿戴甲' },
{ id: 4, name: '定制·蓝宝石婚礼甲', icon: '💍', price: 399, status: '制作中', date: '08-22', type: '定制' },
{ id: 5, name: '裸粉纯色', icon: '🌸', price: 9.9, status: '已完成', date: '08-05', type: '穿戴甲' },
{ id: 6, name: '国风鎏金系列', icon: '🏮', price: 168, status: '已发货', date: '08-19', type: '系列装' },
{ id: 7, name: '手部护理预约', icon: '🧴', price: 58, status: '已完成', date: '08-12', type: '服务' },
{ id: 8, name: '珍珠法式', icon: '🦪', price: 55, status: '制作中', date: '08-24', type: '穿戴甲' },
{ id: 9, name: '钻石女王', icon: '💎', price: 68, status: '已完成', date: '07-28', type: '穿戴甲' },
{ id: 10, name: '猫眼幻境系列', icon: '🌌', price: 99, status: '已发货', date: '08-15', type: '系列装' }
]
// ---------- 数据:月度消费柱状图 ----------
const MONTH_SPEND: MonthSpend[] = [
{ month: '1月', amount: 128 },
{ month: '2月', amount: 96 },
{ month: '3月', amount: 152 },
{ month: '4月', amount: 88 },
{ month: '5月', amount: 210 },
{ month: '6月', amount: 175 }
]
// ---------- Tab 枚举 ----------
enum NailTab {
NAILS = 0,
SERIES = 1,
CUSTOM = 2,
ARTIST = 3,
COMMUNITY = 4,
MINE = 5
}
// ---------- 入口 ----------
@Entry @Component struct NailApp {
@State activeTab: number = 0
build() {
Column() {
NailHeader()
Stack() {
if (this.activeTab === NailTab.NAILS) {
NailsContent()
}
if (this.activeTab === NailTab.SERIES) {
SeriesContent()
}
if (this.activeTab === NailTab.CUSTOM) {
CustomContent()
}
if (this.activeTab === NailTab.ARTIST) {
ArtistContent()
}
if (this.activeTab === NailTab.COMMUNITY) {
CommunityContent()
}
if (this.activeTab === NailTab.MINE) {
MineContent()
}
}
.layoutWeight(1)
.width('100%')
Row() {
this.tabItem('💅', '美甲', NailTab.NAILS)
this.tabItem('🎀', '系列', NailTab.SERIES)
this.tabItem('✂️', '定制', NailTab.CUSTOM)
this.tabItem('💆', '美甲师', NailTab.ARTIST)
this.tabItem('💬', '社区', NailTab.COMMUNITY)
this.tabItem('👤', '我的', NailTab.MINE)
}
.width('100%')
.height(56)
.backgroundColor(NP.white)
.border({ width: 1, color: '#F5E1E8' })
.padding({ top: 3 })
}
.width('100%')
.height('100%')
.backgroundColor(NP.bg)
}
@Builder
tabItem(icon: string, label: string, tab: number) {
Column() {
Text(icon).fontSize(17)
Text(label).fontSize(10).fontColor(this.activeTab === tab ? NP.primary : NP.textSub)
.fontWeight(this.activeTab === tab ? FontWeight.Bold : FontWeight.Normal).margin({ top: 1 })
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.activeTab = tab
})
}
}
// ---------- 头部:电商风(静态无动画) ----------
@Component struct NailHeader {
@State linkIdx: number = 0
build() {
Column() {
Row() {
Text('💅').fontSize(22).backgroundColor(NP.primary).borderRadius(8).padding(5)
Text('多多穿戴美甲').fontSize(16).fontWeight(FontWeight.Bold).fontColor(NP.text).margin({ left: 7 })
Column().layoutWeight(1)
Text('💬').fontSize(17).fontColor(NP.textSub).margin({ right: 12 })
Stack() {
Text('🛒').fontSize(17).fontColor(NP.textSub)
Text('2').fontSize(8).fontColor(NP.white).backgroundColor(NP.warn).borderRadius(6)
.width(12).height(12).textAlign(TextAlign.Center).margin({ top: -14, left: 12 })
}
.width(24)
.height(22)
}
.width('100%')
.padding({ left: 14, right: 14, top: 8, bottom: 6 })
Row() {
Text('🔍').fontSize(13).fontColor(NP.textSub).margin({ left: 12 })
Text('搜美甲:猫眼 / 法式 / 穿戴甲').fontSize(12).fontColor(NP.textSub).margin({ left: 6 })
Column().layoutWeight(1)
Text('⌕').fontSize(15).fontColor(NP.primary).margin({ right: 12 })
}
.width('92%')
.height(34)
.backgroundColor(NP.chip)
.borderRadius(17)
Row() {
this.quickLink('🎁', '新人1元', 0)
this.quickLink('⚡', '限时折扣', 1)
this.quickLink('🌸', '美甲节', 2)
this.quickLink('🆓', '免费试戴', 3)
}
.width('100%')
.margin({ top: 8 })
.padding({ left: 8, right: 8 })
}
.width('100%')
.backgroundColor(NP.white)
.borderRadius({ bottomLeft: 16, bottomRight: 16 })
.padding({ bottom: 10 })
}
@Builder
quickLink(icon: string, label: string, idx: number) {
Row() {
Text(icon).fontSize(11)
Text(label).fontSize(10).fontColor(this.linkIdx === idx ? NP.primary : NP.textSub)
.fontWeight(this.linkIdx === idx ? FontWeight.Bold : FontWeight.Normal).margin({ left: 3 })
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
.height(24)
.backgroundColor(this.linkIdx === idx ? NP.chip : '#FFFFFF')
.borderRadius(12)
.onClick(() => {
this.linkIdx = idx
})
}
}
// ---------- Tab1 美甲:滤镜 Chips + 双列网格(按压缩放) ----------
@Component struct NailsContent {
@State nails: NailItem[] = NAILS
@State curStyle: number = 0
@State pressedIndex: number = 0
@State showBuy: boolean = false
@State curNail: NailItem = NAILS[0]
@State buySize: number = 1
@State buyDuration: number = 0
@State buyCount: number = 1
build() {
Stack() {
Column() {
Scroll() {
Column() {
Row() {
Column() {
Text('💅 今日美甲').fontSize(16).fontWeight(FontWeight.Bold).fontColor(NP.text)
Text('新款式每日上新 · 全场包邮').fontSize(10).fontColor(NP.textSub).margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
Column().layoutWeight(1)
Text(filterNails(this.nails, NAIL_STYLES[this.curStyle]).length + ' 款').fontSize(10).fontColor(NP.primary)
}
.width('94%')
.margin({ top: 12 })
Row({ space: 6 }) {
ForEach(NAIL_STYLES, (s: string, idx: number) => {
Text(s).fontSize(11).fontColor(this.curStyle === idx ? NP.white : NP.text)
.backgroundColor(this.curStyle === idx ? NP.primary : NP.chip).borderRadius(14)
.padding({ left: 13, right: 13, top: 5, bottom: 5 })
.onClick(() => {
this.curStyle = idx
this.pressedIndex = 0
})
}, (s: string) => s)
}
.width('94%')
.margin({ top: 10 })
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(filterNails(this.nails, NAIL_STYLES[this.curStyle]), (n: NailItem) => {
Column() {
Stack() {
Column().width('100%').height(96).backgroundColor(n.style === '纯色' ? '#FFF1F5' : NP.chip)
.borderRadius({ topLeft: 12, topRight: 12 })
Text(n.icon).fontSize(40)
Text('🔥 热卖').fontSize(8).fontColor(NP.warn).backgroundColor('#FFFFFF')
.borderRadius(6).padding({ left: 5, right: 5, top: 2, bottom: 2 })
.position({ x: 8, y: 8 })
}
.width('100%')
.height(96)
Column() {
Row() {
Text(n.name).fontSize(12).fontWeight(FontWeight.Medium).fontColor(NP.text)
.layoutWeight(1).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
Text(n.style).fontSize(8).fontColor(NP.white)
.backgroundColor(styleColor(n.style)).borderRadius(6)
.padding({ left: 5, right: 5, top: 2, bottom: 2 })
}
.width('100%')
Text(n.duration).fontSize(8).fontColor(NP.textSub).width('100%').margin({ top: 4 })
Row() {
Text('¥' + n.price).fontSize(15).fontWeight(FontWeight.Bold).fontColor(NP.primary)
Text('¥' + n.originPrice).fontSize(8).fontColor(NP.textSub)
.decoration({ type: TextDecorationType.LineThrough }).margin({ left: 4 })
Column().layoutWeight(1)
Text(salesText(n.sales)).fontSize(8).fontColor(NP.textSub)
}
.width('100%')
.margin({ top: 4 })
}
.padding({ left: 10, right: 10, top: 8, bottom: 10 })
}
.width('47%')
.backgroundColor(NP.card)
.borderRadius(12)
.border({ width: 1, color: '#FCE7EE' })
.margin({ top: 12 })
.scale({
x: this.pressedIndex === n.id ? 0.96 : 1,
y: this.pressedIndex === n.id ? 0.96 : 1
})
.animation({ duration: 180, curve: Curve.EaseOut })
.onTouch((e: TouchEvent) => {
if (e.type === TouchType.Down) {
this.pressedIndex = n.id
}
if (e.type === TouchType.Up || e.type === TouchType.Cancel) {
this.pressedIndex = 0
}
})
.onClick(() => {
this.curNail = n
this.buySize = 1
this.buyDuration = 0
this.buyCount = 1
this.showBuy = true
})
}, (n: NailItem) => n.id.toString())
}
.width('94%')
.margin({ top: 2 })
.padding({ bottom: 16 })
Text('— 已经到底啦,去逛逛更多美甲吧 —').fontSize(10).fontColor(NP.textSub)
.width('100%').margin({ bottom: 16 })
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
if (this.showBuy) {
this.buyModal()
}
}
.width('100%')
.height('100%')
}
@Builder
modalOverlay(onClose: () => void) {
Column()
.layoutWeight(1)
.width('100%')
.onClick(() => {
onClose()
})
}
@Builder
buyModal() {
Column() {
this.modalOverlay(() => {
this.showBuy = false
})
Column() {
Row() {
Text('🛒 立即购买').fontSize(16).fontWeight(FontWeight.Bold).fontColor(NP.text)
Column().layoutWeight(1)
Text('✕').fontSize(16).fontColor(NP.textSub).onClick(() => {
this.showBuy = false
})
}
.width('100%')
.padding({ bottom: 12 })
Row() {
Text(this.curNail.icon).fontSize(32).width(58).height(58).backgroundColor(NP.chip)
.borderRadius(12).textAlign(TextAlign.Center)
Column() {
Text(this.curNail.name).fontSize(14).fontWeight(FontWeight.Medium).fontColor(NP.text)
Text(this.curNail.style + ' · ' + this.curNail.duration).fontSize(10).fontColor(styleColor(this.curNail.style))
.margin({ top: 3 })
Text('已售 ' + salesText(this.curNail.sales)).fontSize(10).fontColor(NP.textSub).margin({ top: 3 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12 })
}
.width('100%')
Text('选择甲围').fontSize(11).fontColor(NP.textSub).width('100%').margin({ top: 14 })
Row() {
ForEach(SIZE_OPTIONS, (s: string, idx: number) => {
Text(s).fontSize(11).fontColor(this.buySize === idx ? NP.white : NP.text)
.backgroundColor(this.buySize === idx ? NP.primary : NP.chip).borderRadius(9)
.padding({ left: 16, right: 16, top: 5, bottom: 5 }).margin({ right: 8 })
.onClick(() => {
this.buySize = idx
})
}, (s: string) => s)
}
.width('100%')
.margin({ top: 6 })
Text('穿戴时长').fontSize(11).fontColor(NP.textSub).width('100%').margin({ top: 12 })
Row() {
ForEach(WEAR_DURATIONS, (d: string, idx: number) => {
Text(d).fontSize(11).fontColor(this.buyDuration === idx ? NP.white : NP.text)
.backgroundColor(this.buyDuration === idx ? NP.primary : NP.chip).borderRadius(9)
.padding({ left: 16, right: 16, top: 5, bottom: 5 }).margin({ right: 8 })
.onClick(() => {
this.buyDuration = idx
})
}, (d: string) => d)
}
.width('100%')
.margin({ top: 6 })
Row() {
Text('购买数量').fontSize(12).fontColor(NP.text)
Column().layoutWeight(1)
Text('−').fontSize(17).fontColor(NP.textSub).backgroundColor(NP.chip).borderRadius(8)
.width(30).height(30).textAlign(TextAlign.Center)
.onClick(() => {
if (this.buyCount > 1) {
this.buyCount -= 1
}
})
Text(this.buyCount + ' 件').fontSize(13).fontColor(NP.text).margin({ left: 10, right: 10 })
Text('+').fontSize(17).fontColor(NP.white).backgroundColor(NP.primary).borderRadius(8)
.width(30).height(30).textAlign(TextAlign.Center)
.onClick(() => {
if (this.buyCount < 10) {
this.buyCount += 1
}
})
}
.width('100%')
.margin({ top: 14 })
Divider()
.color('#F5E1E8')
.margin({ top: 14 })
Row() {
Text('合计').fontSize(12).fontColor(NP.textSub)
Column().layoutWeight(1)
Text('¥' + buyTotal(this.curNail.price, this.buyCount))
.fontSize(20).fontWeight(FontWeight.Bold).fontColor(NP.primary)
}
.width('100%')
.margin({ top: 12 })
Text('支持 7 天无理由退换 · 加购满 3 件立减 ¥5').fontSize(10)
.fontColor(NP.textSub).width('100%').margin({ top: 4 })
Text('立即购买').fontSize(15).fontColor(NP.white).backgroundColor(NP.primary).borderRadius(20)
.padding({ left: 40, right: 40, top: 10, bottom: 10 }).margin({ top: 14 })
.onClick(() => {
this.showBuy = false
})
}
.width('100%')
.backgroundColor(NP.card)
.borderRadius({ topLeft: 20, topRight: 20 })
.padding(18)
.padding({ bottom: 26 })
}
.width('100%')
.height('100%')
.backgroundColor('rgba(0,0,0,0.55)')
.zIndex(999)
}
}
// ---------- Tab2 系列:横滑 Banner + 系列列表(折扣进度条) ----------
@Component struct SeriesContent {
@State activeBanner: number = 1
@State showBanner: boolean = false
@State curBanner: SeriesBanner = BANNERS[0]
build() {
Stack() {
Scroll() {
Column() {
Row() {
Column() {
Text('🎀 精选系列').fontSize(16).fontWeight(FontWeight.Bold).fontColor(NP.text)
Text('系列成套购买 · 折扣叠加').fontSize(10).fontColor(NP.textSub).margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
Column().layoutWeight(1)
Text(SERIES.length + ' 个系列').fontSize(10).fontColor(NP.primary)
}
.width('94%')
.margin({ top: 12 })
Scroll() {
Row() {
ForEach(BANNERS, (b: SeriesBanner) => {
Column() {
Row() {
Text(b.icon).fontSize(26)
Column() {
Text(b.name).fontSize(15).fontWeight(FontWeight.Bold).fontColor(NP.white)
Text(b.desc).fontSize(9).fontColor('rgba(255,255,255,0.85)').margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 10 })
}
.width('100%')
Row() {
Text(b.tag).fontSize(8).fontColor('#FFE9A8').border({ width: 1, color: '#FFE9A8' })
.borderRadius(8).padding({ left: 7, right: 7, top: 2, bottom: 2 })
Column().layoutWeight(1)
Text(this.activeBanner === b.id ? '● 查看' : '○').fontSize(10)
.fontColor(this.activeBanner === b.id ? '#FFE9A8' : 'rgba(255,255,255,0.7)')
}
.width('100%')
.margin({ top: 8 })
}
.width(230)
.height(bannerCardH())
.backgroundColor(b.color)
.borderRadius(14)
.padding(14)
.margin({ left: 10 })
.border({ width: this.activeBanner === b.id ? 2 : 0, color: '#FFE9A8' })
.onClick(() => {
this.activeBanner = b.id
this.curBanner = b
this.showBanner = true
})
}, (b: SeriesBanner) => b.id.toString())
}
.padding({ left: 3, right: 3 })
}
.scrollable(ScrollDirection.Horizontal)
.width('100%')
.margin({ top: 10 })
Row() {
Text('📚 全部系列').fontSize(14).fontWeight(FontWeight.Bold).fontColor(NP.text)
Column().layoutWeight(1)
Text('按折扣力度排序').fontSize(10).fontColor(NP.textSub)
}
.width('94%')
.margin({ top: 14 })
ForEach(SERIES, (s: SeriesItem) => {
Row() {
Text(s.icon).fontSize(30).width(58).height(58).backgroundColor(NP.chip).borderRadius(12)
.textAlign(TextAlign.Center)
Column() {
Row() {
Text(s.name).fontSize(13).fontWeight(FontWeight.Medium).fontColor(NP.text)
.layoutWeight(1).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
Text(seriesDiscountLabel(s)).fontSize(10).fontWeight(FontWeight.Bold).fontColor(NP.warn)
}
.width('100%')
Text(s.count + ' 款 · ' + s.priceRange + ' · 已售 ' + salesText(s.sold))
.fontSize(10).fontColor(NP.textSub).width('100%').margin({ top: 3 })
Row() {
Stack() {
Column().width('100%').height(7).backgroundColor(NP.chip).borderRadius(4)
Column().width(seriesPct(s)).height(7).backgroundColor(NP.primary).borderRadius(4)
}
.layoutWeight(1)
.height(7)
Text(s.discount + '%OFF').fontSize(9).fontColor(NP.primary).margin({ left: 6 })
}
.width('100%')
.margin({ top: 6 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12 })
}
.width('94%')
.backgroundColor(NP.card)
.borderRadius(12)
.border({ width: 1, color: '#FCE7EE' })
.padding(12)
.margin({ top: 10 })
}, (s: SeriesItem) => s.id.toString())
Text('成套入手更划算 · 系列内任意混搭').fontSize(10).fontColor(NP.textSub)
.width('94%').margin({ top: 14, bottom: 16 })
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%')
.height('100%')
if (this.showBanner) {
this.bannerModal()
}
}
.width('100%')
.height('100%')
}
@Builder
modalOverlay(onClose: () => void) {
Column()
.layoutWeight(1)
.width('100%')
.onClick(() => {
onClose()
})
}
@Builder
bannerModal() {
Column() {
this.modalOverlay(() => {
this.showBanner = false
})
Column() {
Row() {
Text('🎀 ' + this.curBanner.name).fontSize(16).fontWeight(FontWeight.Bold).fontColor(NP.text)
Column().layoutWeight(1)
Text('✕').fontSize(16).fontColor(NP.textSub).onClick(() => {
this.showBanner = false
})
}
.width('100%')
.padding({ bottom: 12 })
Row() {
Text(this.curBanner.icon).fontSize(34).width(60).height(60).backgroundColor(NP.chip)
.borderRadius(12).textAlign(TextAlign.Center)
Column() {
Text(this.curBanner.name).fontSize(14).fontWeight(FontWeight.Medium).fontColor(NP.text)
Text(this.curBanner.desc).fontSize(10).fontColor(NP.textSub).margin({ top: 3 })
Text('标签:' + this.curBanner.tag).fontSize(10).fontColor(NP.primary).margin({ top: 3 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12 })
}
.width('100%')
.margin({ top: 12 })
Divider()
.color('#F5E1E8')
.margin({ top: 14 })
Text('系列包含多款同主题穿戴甲,支持成套混搭与专属礼盒包装。').fontSize(11)
.fontColor(NP.textSub).width('100%').margin({ top: 12 })
Text('进入系列').fontSize(15).fontColor(NP.white).backgroundColor(NP.primary).borderRadius(20)
.padding({ left: 40, right: 40, top: 10, bottom: 10 }).margin({ top: 14 })
.onClick(() => {
this.showBanner = false
})
}
.width('100%')
.backgroundColor(NP.card)
.borderRadius({ topLeft: 20, topRight: 20 })
.padding(18)
.padding({ bottom: 26 })
}
.width('100%')
.height('100%')
.backgroundColor('rgba(0,0,0,0.55)')
.zIndex(999)
}
}
// ---------- Tab3 定制:定制工作台(预览 + 选项区 + 计价) ----------
@Component struct CustomContent {
@State shapeIdx: number = 0
@State lenIdx: number = 1
@State colorIdx: number = 1
@State decorIdx: number = 0
@State showReq: boolean = false
@State reqName: string = ''
@State refIdx: number = 0
@State budgetIdx: number = 0
@State deadlineIdx: number = 1
@State reqDesc: string = ''
build() {
Stack() {
Scroll() {
Column() {
Row() {
Column() {
Text('✂️ 个性定制').fontSize(16).fontWeight(FontWeight.Bold).fontColor(NP.text)
Text('专属你的独一无二 · 定制工期 3-15 天').fontSize(10).fontColor(NP.textSub).margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
Column().layoutWeight(1)
Text('提交需求').fontSize(11).fontColor(NP.white).backgroundColor(NP.primary).borderRadius(12)
.padding({ left: 14, right: 14, top: 6, bottom: 6 })
.onClick(() => {
this.reqName = ''
this.refIdx = 0
this.budgetIdx = 0
this.deadlineIdx = 1
this.reqDesc = ''
this.showReq = true
})
}
.width('94%')
.margin({ top: 12 })
// 当前设计预览卡
Column() {
Stack() {
Column().width('100%').height(132).backgroundColor(NP.cardDeep).borderRadius({ topLeft: 14, topRight: 14 })
Text(decorIcon(this.decorIdx)).fontSize(56)
Text('💅').fontSize(44).margin({ top: 78 })
}
.width('100%')
.height(132)
Column() {
Row() {
Text('当前设计').fontSize(14).fontWeight(FontWeight.Bold).fontColor(NP.text)
Column().layoutWeight(1)
Text(decorColor(this.decorIdx)).fontSize(9).fontColor(NP.white)
.backgroundColor(decorColor(this.decorIdx)).borderRadius(8)
.padding({ left: 7, right: 7, top: 2, bottom: 2 })
}
.width('100%')
Text(customSummary(this.shapeIdx, this.lenIdx, this.colorIdx, this.decorIdx))
.fontSize(11).fontColor(NP.textSub).width('100%').margin({ top: 6 })
}
.padding(14)
}
.width('94%')
.backgroundColor(NP.card)
.borderRadius(14)
.border({ width: 1, color: '#FCE7EE' })
.margin({ top: 12 })
// 甲型
Column() {
Text('甲型').fontSize(13).fontWeight(FontWeight.Bold).fontColor(NP.text).width('100%')
Row({ space: 10 }) {
ForEach(CUSTOM_SHAPES, (s: string, idx: number) => {
Text(s).fontSize(12).fontColor(this.shapeIdx === idx ? NP.white : NP.text)
.backgroundColor(this.shapeIdx === idx ? NP.primary : NP.chip).borderRadius(12)
.padding({ left: 22, right: 22, top: 7, bottom: 7 })
.scale({
x: this.shapeIdx === idx ? 1.1 : 1,
y: this.shapeIdx === idx ? 1.1 : 1
})
.animation({ duration: 200, curve: Curve.EaseOut })
.onClick(() => {
this.shapeIdx = idx
})
}, (s: string) => s)
}
.width('100%')
.margin({ top: 8 })
}
.width('94%')
.margin({ top: 14 })
// 长度
Column() {
Text('长度').fontSize(13).fontWeight(FontWeight.Bold).fontColor(NP.text).width('100%')
Row({ space: 10 }) {
ForEach(CUSTOM_LENGTHS, (s: string, idx: number) => {
Text(s).fontSize(12).fontColor(this.lenIdx === idx ? NP.white : NP.text)
.backgroundColor(this.lenIdx === idx ? NP.primary : NP.chip).borderRadius(12)
.padding({ left: 24, right: 24, top: 7, bottom: 7 })
.scale({
x: this.lenIdx === idx ? 1.1 : 1,
y: this.lenIdx === idx ? 1.1 : 1
})
.animation({ duration: 200, curve: Curve.EaseOut })
.onClick(() => {
this.lenIdx = idx
})
}, (s: string) => s)
}
.width('100%')
.margin({ top: 8 })
}
.width('94%')
.margin({ top: 14 })
// 底色色点
Column() {
Text('底色').fontSize(13).fontWeight(FontWeight.Bold).fontColor(NP.text).width('100%')
Row() {
ForEach(CUSTOM_COLORS, (c: string, idx: number) => {
Column() {
Column().width(30).height(30).borderRadius(15).backgroundColor(c)
.border({
width: this.colorIdx === idx ? 3 : 1,
color: this.colorIdx === idx ? NP.primary : '#F0D9E1'
})
Text(CUSTOM_COLOR_NAMES[idx]).fontSize(8).fontColor(this.colorIdx === idx ? NP.primary : NP.textSub)
.margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
.onClick(() => {
this.colorIdx = idx
})
}, (c: string) => c)
}
.width('100%')
.margin({ top: 8 })
}
.width('94%')
.margin({ top: 14 })
// 装饰
Column() {
Text('装饰').fontSize(13).fontWeight(FontWeight.Bold).fontColor(NP.text).width('100%')
Row({ space: 8 }) {
ForEach(CUSTOM_DECORS, (d: string, idx: number) => {
Column() {
Text(decorIcon(idx)).fontSize(22)
Text(d).fontSize(10).fontColor(this.decorIdx === idx ? NP.primary : NP.textSub).margin({ top: 4 })
Text('+¥' + CUSTOM_DECOR_PRICES[idx]).fontSize(8).fontColor(NP.textSub).margin({ top: 2 })
}
.layoutWeight(1)
.padding({ top: 10, bottom: 10 })
.backgroundColor(this.decorIdx === idx ? NP.cardDeep : NP.card)
.border({ width: 1, color: this.decorIdx === idx ? NP.primary : '#F5E1E8' })
.borderRadius(12)
.alignItems(HorizontalAlign.Center)
.scale({
x: this.decorIdx === idx ? 1.1 : 1,
y: this.decorIdx === idx ? 1.1 : 1
})
.animation({ duration: 200, curve: Curve.EaseOut })
.onClick(() => {
this.decorIdx = idx
})
}, (d: string) => d)
}
.width('100%')
.margin({ top: 8 })
}
.width('94%')
.margin({ top: 14 })
// 计价卡
Column() {
Row() {
Text('定制费用').fontSize(13).fontWeight(FontWeight.Bold).fontColor(NP.text)
Column().layoutWeight(1)
Text(customPriceText(this.lenIdx, this.decorIdx)).fontSize(20).fontWeight(FontWeight.Bold)
.fontColor(NP.primary)
}
.width('100%')
Divider()
.color('#F5E1E8')
.margin({ top: 10 })
Text('包含:基础甲片 + 长度升级 + ' + CUSTOM_DECORS[this.decorIdx] + '装饰 · 附赠收纳盒与果冻胶')
.fontSize(10).fontColor(NP.textSub).width('100%').margin({ top: 10 })
Text('立即定制').fontSize(15).fontColor(NP.white).backgroundColor(NP.primary).borderRadius(20)
.padding({ left: 40, right: 40, top: 10, bottom: 10 }).margin({ top: 12 })
.onClick(() => {
this.showReq = true
})
}
.width('94%')
.backgroundColor(NP.card)
.borderRadius(14)
.border({ width: 1, color: '#FCE7EE' })
.padding(14)
.margin({ top: 14, bottom: 16 })
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%')
.height('100%')
if (this.showReq) {
this.reqModal()
}
}
.width('100%')
.height('100%')
}
@Builder
modalOverlay(onClose: () => void) {
Column()
.layoutWeight(1)
.width('100%')
.onClick(() => {
onClose()
})
}
@Builder
reqModal() {
Column() {
this.modalOverlay(() => {
this.showReq = false
})
Column() {
Row() {
Text('📝 新增定制需求').fontSize(16).fontWeight(FontWeight.Bold).fontColor(NP.text)
Column().layoutWeight(1)
Text('✕').fontSize(16).fontColor(NP.textSub).onClick(() => {
this.showReq = false
})
}
.width('100%')
.padding({ bottom: 12 })
Text('需求名称').fontSize(11).fontColor(NP.textSub).width('100%')
TextInput({
text: this.reqName,
placeholder: '如:闺蜜婚礼伴娘甲'
})
.fontSize(12)
.fontColor(NP.text)
.backgroundColor(NP.chip)
.height(38)
.width('100%')
.margin({ top: 6 })
.onChange((v: string) => {
this.reqName = v
})
Text('风格参考').fontSize(11).fontColor(NP.textSub).width('100%').margin({ top: 12 })
Row({ space: 8 }) {
ForEach(REF_STYLES, (s: string, idx: number) => {
Text(s).fontSize(11).fontColor(this.refIdx === idx ? NP.white : NP.text)
.backgroundColor(this.refIdx === idx ? NP.primary : NP.chip).borderRadius(9)
.padding({ left: 14, right: 14, top: 5, bottom: 5 })
.onClick(() => {
this.refIdx = idx
})
}, (s: string) => s)
}
.width('100%')
.margin({ top: 6 })
Text('预算范围').fontSize(11).fontColor(NP.textSub).width('100%').margin({ top: 12 })
Row({ space: 8 }) {
ForEach(BUDGET_OPTIONS, (b: string, idx: number) => {
Text(b).fontSize(11).fontColor(this.budgetIdx === idx ? NP.white : NP.text)
.backgroundColor(this.budgetIdx === idx ? NP.primary : NP.chip).borderRadius(9)
.padding({ left: 14, right: 14, top: 5, bottom: 5 })
.onClick(() => {
this.budgetIdx = idx
})
}, (b: string) => b)
}
.width('100%')
.margin({ top: 6 })
Text('期望工期').fontSize(11).fontColor(NP.textSub).width('100%').margin({ top: 12 })
Row({ space: 8 }) {
ForEach(DEADLINE_OPTIONS, (d: string, idx: number) => {
Text(d).fontSize(11).fontColor(this.deadlineIdx === idx ? NP.white : NP.text)
.backgroundColor(this.deadlineIdx === idx ? NP.primary : NP.chip).borderRadius(9)
.padding({ left: 14, right: 14, top: 5, bottom: 5 })
.onClick(() => {
this.deadlineIdx = idx
})
}, (d: string) => d)
}
.width('100%')
.margin({ top: 6 })
Text('需求描述').fontSize(11).fontColor(NP.textSub).width('100%').margin({ top: 12 })
TextArea({
text: this.reqDesc,
placeholder: '补充描述你的想法,如场合、配色偏好…'
})
.fontSize(12)
.fontColor(NP.text)
.backgroundColor(NP.chip)
.height(80)
.width('100%')
.margin({ top: 6 })
.onChange((v: string) => {
this.reqDesc = v
})
Text('提交需求').fontSize(15).fontColor(NP.white).backgroundColor(NP.primary).borderRadius(20)
.padding({ left: 40, right: 40, top: 10, bottom: 10 }).margin({ top: 14 })
.onClick(() => {
this.showReq = false
})
}
.width('100%')
.backgroundColor(NP.card)
.borderRadius({ topLeft: 20, topRight: 20 })
.padding(18)
.padding({ bottom: 26 })
}
.width('100%')
.height('100%')
.backgroundColor('rgba(0,0,0,0.55)')
.zIndex(999)
}
}
// ---------- Tab4 美甲师:左头像右详情列表(星星脉冲 + 订单迷你条) ----------
@Component struct ArtistContent {
@State artists: ArtistItem[] = ARTISTS
@State showBook: boolean = false
@State curArtist: ArtistItem = ARTISTS[0]
@State timeIdx: number = 0
@State serviceIdx: number = 0
@State noteText: string = ''
@State starPulse: boolean = true
build() {
Stack() {
Scroll() {
Column() {
Row() {
Column() {
Text('💆 美甲师').fontSize(16).fontWeight(FontWeight.Bold).fontColor(NP.text)
Text('同城到店 · 好评认证美甲师').fontSize(10).fontColor(NP.textSub).margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
Column().layoutWeight(1)
Text(ARTISTS.length + ' 位在约').fontSize(10).fontColor(NP.primary)
}
.width('94%')
.margin({ top: 12 })
ForEach(this.artists, (a: ArtistItem) => {
Row() {
Column() {
Text(a.avatar).fontSize(26).width(52).height(52).backgroundColor(NP.chip)
.borderRadius(26).textAlign(TextAlign.Center)
Text(a.name).fontSize(9).fontWeight(FontWeight.Bold).fontColor(NP.text).margin({ top: 4 })
}
.width(58)
.alignItems(HorizontalAlign.Center)
Column() {
Row() {
Text(a.studio).fontSize(13).fontWeight(FontWeight.Medium).fontColor(NP.text)
.layoutWeight(1).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
Text(a.price + ' 起').fontSize(12).fontWeight(FontWeight.Bold).fontColor(NP.primary)
}
.width('100%')
Text(a.location).fontSize(9).fontColor(NP.textSub).width('100%').margin({ top: 2 })
Row() {
Text('⭐ ').fontSize(10)
Text(ratingStars(a.rating)).fontSize(10).fontColor(NP.secondary)
.opacity(this.starPulse ? 1 : 0.35)
.animation({ duration: 500, curve: Curve.EaseInOut })
Text(a.rating.toFixed(1)).fontSize(10).fontColor(NP.textSub).margin({ left: 4 })
}
.width('100%')
.margin({ top: 3 })
.onClick(() => {
this.starPulse = !this.starPulse
})
Row() {
Text('📦 ' + a.orders).fontSize(9).fontColor(NP.textSub)
Stack() {
Column().width(52).height(5).backgroundColor(NP.chip).borderRadius(3)
Column().width(orderBarWidth(a.orders)).height(5).backgroundColor(NP.secondary).borderRadius(3)
}
.width(52)
.height(5)
.margin({ left: 6 })
}
.width('100%')
.margin({ top: 5 })
Row() {
Text(a.tags).fontSize(8).fontColor(NP.primary).backgroundColor(NP.chip).borderRadius(6)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
}
.width('100%')
.margin({ top: 5 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 6 })
Text('预约').fontSize(11).fontColor(NP.white).backgroundColor(NP.primary).borderRadius(12)
.padding({ left: 14, right: 14, top: 6, bottom: 6 })
.onClick(() => {
this.curArtist = a
this.timeIdx = 0
this.serviceIdx = 0
this.noteText = ''
this.showBook = true
})
}
.width('94%')
.backgroundColor(NP.card)
.borderRadius(12)
.border({ width: 1, color: '#FCE7EE' })
.padding(12)
.margin({ top: 10 })
}, (a: ArtistItem) => a.id.toString())
Text('— 点击星星点亮评分 · 更多美甲师等你发现 —').fontSize(10).fontColor(NP.textSub)
.width('94%').margin({ top: 14, bottom: 16 })
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%')
.height('100%')
if (this.showBook) {
this.bookModal()
}
}
.width('100%')
.height('100%')
}
@Builder
modalOverlay(onClose: () => void) {
Column()
.layoutWeight(1)
.width('100%')
.onClick(() => {
onClose()
})
}
@Builder
bookModal() {
Column() {
this.modalOverlay(() => {
this.showBook = false
})
Column() {
Row() {
Text('📅 预约美甲师').fontSize(16).fontWeight(FontWeight.Bold).fontColor(NP.text)
Column().layoutWeight(1)
Text('✕').fontSize(16).fontColor(NP.textSub).onClick(() => {
this.showBook = false
})
}
.width('100%')
.padding({ bottom: 12 })
Row() {
Text(this.curArtist.avatar).fontSize(28).width(52).height(52).backgroundColor(NP.chip)
.borderRadius(26).textAlign(TextAlign.Center)
Column() {
Text(this.curArtist.studio).fontSize(14).fontWeight(FontWeight.Bold).fontColor(NP.text)
Text(this.curArtist.location + ' · ⭐ ' + this.curArtist.rating.toFixed(1)).fontSize(10)
.fontColor(NP.textSub).margin({ top: 3 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12 })
}
.width('100%')
.margin({ top: 8 })
Row() {
Text('预约日期').fontSize(12).fontColor(NP.text)
Column().layoutWeight(1)
Text('08-28 周五').fontSize(12).fontWeight(FontWeight.Medium).fontColor(NP.primary)
}
.width('100%')
.margin({ top: 12 })
Text('选择时段').fontSize(11).fontColor(NP.textSub).width('100%').margin({ top: 12 })
Row({ space: 8 }) {
ForEach(TIME_SLOTS, (t: string, idx: number) => {
Text(t).fontSize(11).fontColor(this.timeIdx === idx ? NP.white : NP.text)
.backgroundColor(this.timeIdx === idx ? NP.primary : NP.chip).borderRadius(9)
.padding({ left: 15, right: 15, top: 5, bottom: 5 })
.onClick(() => {
this.timeIdx = idx
})
}, (t: string) => t)
}
.width('100%')
.margin({ top: 6 })
Text('服务项目').fontSize(11).fontColor(NP.textSub).width('100%').margin({ top: 12 })
Row({ space: 8 }) {
ForEach(SERVICE_TYPES, (s: string, idx: number) => {
Text(s).fontSize(11).fontColor(this.serviceIdx === idx ? NP.white : NP.text)
.backgroundColor(this.serviceIdx === idx ? NP.primary : NP.chip).borderRadius(9)
.padding({ left: 15, right: 15, top: 5, bottom: 5 })
.onClick(() => {
this.serviceIdx = idx
})
}, (s: string) => s)
}
.width('100%')
.margin({ top: 6 })
Text('备注信息').fontSize(11).fontColor(NP.textSub).width('100%').margin({ top: 12 })
TextInput({
text: this.noteText,
placeholder: '如:需要贴甲,请准备猫眼款式…'
})
.fontSize(12)
.fontColor(NP.text)
.backgroundColor(NP.chip)
.height(38)
.width('100%')
.margin({ top: 6 })
.onChange((v: string) => {
this.noteText = v
})
Row() {
Text('取消').fontSize(13).fontColor(NP.textSub).border({ width: 1, color: '#E8CDD8' })
.borderRadius(18).padding({ left: 26, right: 26, top: 8, bottom: 8 }).margin({ top: 14 })
.onClick(() => {
this.showBook = false
})
Text('确认预约').fontSize(13).fontColor(NP.white).backgroundColor(NP.primary).borderRadius(18)
.padding({ left: 26, right: 26, top: 8, bottom: 8 }).margin({ top: 14, left: 12 })
.onClick(() => {
this.showBook = false
})
}
.width('100%')
}
.width('88%')
.backgroundColor(NP.card)
.borderRadius(18)
.padding(18)
this.modalOverlay(() => {
this.showBook = false
})
}
.width('100%')
.height('100%')
.backgroundColor('rgba(0,0,0,0.55)')
.zIndex(999)
}
}
// ---------- Tab5 社区:信息流(左头像 + 爱心点赞缩放) ----------
@Component struct CommunityContent {
@State posts: FeedPost[] = POSTS
build() {
Scroll() {
Column() {
Row() {
Column() {
Text('💬 穿戴甲社区').fontSize(16).fontWeight(FontWeight.Bold).fontColor(NP.text)
Text('晒出你的美甲 · 一起变美').fontSize(10).fontColor(NP.textSub).margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
Column().layoutWeight(1)
Text('发帖').fontSize(11).fontColor(NP.white).backgroundColor(NP.primary).borderRadius(12)
.padding({ left: 14, right: 14, top: 6, bottom: 6 })
}
.width('94%')
.margin({ top: 12 })
Row() {
Text('🔥 热门美甲分享').fontSize(13).fontWeight(FontWeight.Bold).fontColor(NP.text)
Column().layoutWeight(1)
Text('按点赞排序').fontSize(10).fontColor(NP.textSub)
}
.width('94%')
.margin({ top: 12 })
ForEach(this.posts, (p: FeedPost) => {
Column() {
Row() {
Text(p.avatar).fontSize(24).width(44).height(44).backgroundColor(NP.chip)
.borderRadius(22).textAlign(TextAlign.Center)
Column() {
Text(p.user).fontSize(12).fontWeight(FontWeight.Medium).fontColor(NP.text)
Text(p.date + ' 发布').fontSize(9).fontColor(NP.textSub).margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 10 })
Text('关注').fontSize(10).fontColor(NP.primary).border({ width: 1, color: NP.primary })
.borderRadius(10).padding({ left: 10, right: 10, top: 4, bottom: 4 })
}
.width('100%')
Text(p.text).fontSize(12).fontColor(NP.text).width('100%').lineHeight(18)
.maxLines(3).textOverflow({ overflow: TextOverflow.Ellipsis }).margin({ top: 8 })
// 图片占位区
Stack() {
Column().width('100%').height(96).backgroundColor(NP.cardDeep).borderRadius(10)
Text('💅').fontSize(34)
Text('作品图').fontSize(9).fontColor(NP.textSub).margin({ top: 54 })
}
.width('100%')
.height(96)
.margin({ top: 8 })
Divider()
.color('#F7E8EE')
.margin({ top: 10 })
Row() {
Text(p.liked ? '❤️' : '🤍').fontSize(18)
.scale({
x: p.liked ? 1.35 : 1,
y: p.liked ? 1.35 : 1
})
.animation({ duration: 250, curve: Curve.EaseOut })
.onClick(() => {
this.posts = withPostLiked(this.posts, p.id)
})
Text(' ' + p.likes).fontSize(10).fontColor(p.liked ? NP.primary : NP.textSub)
.margin({ left: 2 })
Column().layoutWeight(1)
Text('💬 ' + commentCount(p.comments)).fontSize(10).fontColor(NP.textSub)
Text('⭐ 收藏').fontSize(10).fontColor(NP.textSub).margin({ left: 18 })
}
.width('100%')
.margin({ top: 6 })
}
.width('94%')
.backgroundColor(NP.card)
.borderRadius(12)
.border({ width: 1, color: '#FCE7EE' })
.padding(12)
.margin({ top: 10 })
}, (p: FeedPost) => p.id.toString())
Text('— 已经到底啦,快去晒出你的美甲 —').fontSize(10).fontColor(NP.textSub)
.width('94%').margin({ top: 14, bottom: 16 })
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%')
.height('100%')
}
}
// ---------- Tab6 我的:资料卡 + 订单列表 + 积分进度 + 月度消费柱状图 ----------
@Component struct MineContent {
@State orders: MyOrder[] = ORDERS
@State showEditCol: boolean = false
@State showDelOrder: boolean = false
@State delOrder: MyOrder = ORDERS[0]
@State colName: string = '我的收藏'
@State colCover: number = 0
@State colVisible: boolean = true
@State colSort: number = 0
build() {
Stack() {
Scroll() {
Column() {
// 资料卡
Column() {
Row() {
Text('👸').fontSize(34).width(62).height(62).backgroundColor(NP.chip)
.borderRadius(31).textAlign(TextAlign.Center)
Column() {
Row() {
Text('美甲小仙女').fontSize(16).fontWeight(FontWeight.Bold).fontColor(NP.text)
Text('Lv.8 剁手党').fontSize(9).fontColor(NP.white).backgroundColor(NP.primary)
.borderRadius(7).padding({ left: 7, right: 7, top: 2, bottom: 2 }).margin({ left: 8 })
}
.width('100%')
Text('ID: 2026888 · 2025-09 加入多多穿戴美甲').fontSize(10).fontColor(NP.textSub).margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12 })
Text('编辑资料').fontSize(10).fontColor(NP.textSub).border({ width: 1, color: '#E8CDD8' })
.borderRadius(10).padding({ left: 10, right: 10, top: 5, bottom: 5 })
}
.width('100%')
Divider()
.color('#F7E8EE')
.margin({ top: 12 })
Row() {
Column() {
Text('12').fontSize(17).fontWeight(FontWeight.Bold).fontColor(NP.primary)
Text('订单').fontSize(9).fontColor(NP.textSub).margin({ top: 2 })
}
.layoutWeight(1)
Column() {
Text('8').fontSize(17).fontWeight(FontWeight.Bold).fontColor(NP.primary)
Text('收藏').fontSize(9).fontColor(NP.textSub).margin({ top: 2 })
}
.layoutWeight(1)
Column() {
Text('2680').fontSize(17).fontWeight(FontWeight.Bold).fontColor(NP.primary)
Text('积分').fontSize(9).fontColor(NP.textSub).margin({ top: 2 })
}
.layoutWeight(1)
Column() {
Text('¥856').fontSize(17).fontWeight(FontWeight.Bold).fontColor(NP.primary)
Text('已省').fontSize(9).fontColor(NP.textSub).margin({ top: 2 })
}
.layoutWeight(1)
}
.width('100%')
.margin({ top: 4 })
}
.width('94%')
.backgroundColor(NP.card)
.borderRadius(14)
.border({ width: 1, color: '#FCE7EE' })
.padding(14)
.margin({ top: 12 })
// 积分等级进度
Column() {
Row() {
Text('🎯 积分等级').fontSize(13).fontWeight(FontWeight.Bold).fontColor(NP.text)
Column().layoutWeight(1)
Text('2680 / 5000 积分').fontSize(10).fontColor(NP.secondary)
}
.width('100%')
Stack() {
Column().width('100%').height(8).backgroundColor(NP.chip).borderRadius(4)
Column().width(pointsProgress() + '%').height(8).backgroundColor(NP.primary).borderRadius(4)
}
.width('100%')
.height(8)
.margin({ top: 8 })
Text('再攒 2320 积分升级「闪耀名媛」,享专属客服 + 新品优先购')
.fontSize(9).fontColor(NP.textSub).width('100%').margin({ top: 6 })
}
.width('94%')
.backgroundColor(NP.card)
.borderRadius(14)
.border({ width: 1, color: '#FCE7EE' })
.padding(14)
.margin({ top: 10 })
// 月度消费柱状图
Column() {
Row() {
Text('📊 月度消费').fontSize(13).fontWeight(FontWeight.Bold).fontColor(NP.text)
Column().layoutWeight(1)
Text('近 6 个月').fontSize(10).fontColor(NP.textSub)
}
.width('100%')
Row() {
ForEach(MONTH_SPEND, (m: MonthSpend) => {
Column() {
Column().width(16).height(spendBarH(m.amount))
.backgroundColor(m.amount >= 180 ? NP.primary : NP.secondary).borderRadius(4)
Text(m.month).fontSize(9).fontColor(NP.textSub).margin({ top: 4 })
Text('¥' + m.amount).fontSize(8).fontColor(NP.primary).margin({ top: 1 })
}
.layoutWeight(1)
.justifyContent(FlexAlign.End)
.height(96)
.alignItems(HorizontalAlign.Center)
}, (m: MonthSpend) => m.month)
}
.width('100%')
.margin({ top: 8 })
}
.width('94%')
.backgroundColor(NP.card)
.borderRadius(14)
.border({ width: 1, color: '#FCE7EE' })
.padding(14)
.margin({ top: 10 })
// 我的订单
Row() {
Text('📦 我的订单').fontSize(14).fontWeight(FontWeight.Bold).fontColor(NP.text)
Column().layoutWeight(1)
Text(this.orders.length + ' 笔订单').fontSize(10).fontColor(NP.textSub)
Text('编辑收藏夹').fontSize(11).fontColor(NP.primary).margin({ left: 10 })
.onClick(() => {
this.colName = '我的收藏'
this.colCover = 0
this.colVisible = true
this.colSort = 0
this.showEditCol = true
})
}
.width('94%')
.margin({ top: 12 })
ForEach(this.orders, (o: MyOrder) => {
Column() {
Row() {
Text(o.icon).fontSize(26).width(50).height(50).backgroundColor(NP.chip)
.borderRadius(10).textAlign(TextAlign.Center)
Column() {
Text(o.name).fontSize(12).fontWeight(FontWeight.Medium).fontColor(NP.text)
.maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
Text(o.type + ' · ' + o.date).fontSize(9).fontColor(NP.textSub).margin({ top: 3 })
Text('¥' + o.price).fontSize(13).fontWeight(FontWeight.Bold).fontColor(NP.primary).margin({ top: 3 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 10 })
Column() {
Text(o.status).fontSize(9).fontColor(orderStatusColor(o.status))
.backgroundColor(orderStatusBg(o.status)).borderRadius(8)
.padding({ left: 7, right: 7, top: 3, bottom: 3 })
Text('删除').fontSize(10).fontColor(NP.warn).margin({ top: 8 })
.onClick(() => {
this.delOrder = o
this.showDelOrder = true
})
}
.alignItems(HorizontalAlign.Center)
}
.width('100%')
}
.width('94%')
.backgroundColor(NP.card)
.borderRadius(12)
.border({ width: 1, color: '#FCE7EE' })
.padding(12)
.margin({ top: 8 })
}, (o: MyOrder) => o.id.toString())
Text('— 消费满 ¥99 立返 100 积分 —').fontSize(10).fontColor(NP.textSub)
.width('94%').margin({ top: 14, bottom: 16 })
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.width('100%')
.height('100%')
if (this.showEditCol) {
this.editColModal()
}
if (this.showDelOrder) {
this.deleteModal()
}
}
.width('100%')
.height('100%')
}
@Builder
modalOverlay(onClose: () => void) {
Column()
.layoutWeight(1)
.width('100%')
.onClick(() => {
onClose()
})
}
@Builder
editColModal() {
Column() {
this.modalOverlay(() => {
this.showEditCol = false
})
Column() {
Row() {
Text('📁 编辑收藏夹').fontSize(16).fontWeight(FontWeight.Bold).fontColor(NP.text)
Column().layoutWeight(1)
Text('✕').fontSize(16).fontColor(NP.textSub).onClick(() => {
this.showEditCol = false
})
}
.width('100%')
.padding({ bottom: 12 })
Text('收藏夹名称').fontSize(11).fontColor(NP.textSub).width('100%')
TextInput({
text: this.colName,
placeholder: '如:我的宝藏美甲'
})
.fontSize(12)
.fontColor(NP.text)
.backgroundColor(NP.chip)
.height(38)
.width('100%')
.margin({ top: 6 })
.onChange((v: string) => {
this.colName = v
})
Text('封面风格').fontSize(11).fontColor(NP.textSub).width('100%').margin({ top: 12 })
Row({ space: 8 }) {
ForEach(COVER_STYLES, (c: string, idx: number) => {
Text(c).fontSize(11).fontColor(this.colCover === idx ? NP.white : NP.text)
.backgroundColor(this.colCover === idx ? NP.primary : NP.chip).borderRadius(9)
.padding({ left: 15, right: 15, top: 5, bottom: 5 })
.onClick(() => {
this.colCover = idx
})
}, (c: string) => c)
}
.width('100%')
.margin({ top: 6 })
Text('可见性').fontSize(11).fontColor(NP.textSub).width('100%').margin({ top: 12 })
Row({ space: 8 }) {
ForEach(VISIBILITY_OPTIONS, (v: string, idx: number) => {
Text(v).fontSize(11).fontColor((this.colVisible ? 0 : 1) === idx ? NP.white : NP.text)
.backgroundColor((this.colVisible ? 0 : 1) === idx ? NP.primary : NP.chip).borderRadius(9)
.padding({ left: 15, right: 15, top: 5, bottom: 5 })
.onClick(() => {
this.colVisible = idx === 0
})
}, (v: string) => v)
}
.width('100%')
.margin({ top: 6 })
Text('排序方式').fontSize(11).fontColor(NP.textSub).width('100%').margin({ top: 12 })
Row({ space: 8 }) {
ForEach(SORT_OPTIONS, (s: string, idx: number) => {
Text(s).fontSize(11).fontColor(this.colSort === idx ? NP.white : NP.text)
.backgroundColor(this.colSort === idx ? NP.primary : NP.chip).borderRadius(9)
.padding({ left: 15, right: 15, top: 5, bottom: 5 })
.onClick(() => {
this.colSort = idx
})
}, (s: string) => s)
}
.width('100%')
.margin({ top: 6 })
Row() {
Text('取消').fontSize(13).fontColor(NP.textSub).border({ width: 1, color: '#E8CDD8' })
.borderRadius(18).padding({ left: 26, right: 26, top: 8, bottom: 8 }).margin({ top: 14 })
.onClick(() => {
this.showEditCol = false
})
Text('保存').fontSize(13).fontColor(NP.white).backgroundColor(NP.primary).borderRadius(18)
.padding({ left: 26, right: 26, top: 8, bottom: 8 }).margin({ top: 14, left: 12 })
.onClick(() => {
this.showEditCol = false
})
}
.width('100%')
}
.width('100%')
.backgroundColor(NP.card)
.borderRadius({ topLeft: 20, topRight: 20 })
.padding(18)
.padding({ bottom: 26 })
}
.width('100%')
.height('100%')
.backgroundColor('rgba(0,0,0,0.55)')
.zIndex(999)
}
@Builder
deleteModal() {
Column() {
this.modalOverlay(() => {
this.showDelOrder = false
})
Column() {
Text('⚠️').fontSize(44).margin({ top: 4 })
Text('删除订单').fontSize(16).fontWeight(FontWeight.Bold).fontColor(NP.text).margin({ top: 6 })
Text('「' + this.delOrder.name + '」').fontSize(12).fontWeight(FontWeight.Medium).fontColor(NP.text)
.margin({ top: 8 })
Column() {
Text('删除后订单记录将被永久移除,无法恢复!').fontSize(11).fontColor(NP.warn).width('100%')
Text('若订单仍在制作或发货中,建议先联系客服处理售后。').fontSize(10)
.fontColor(NP.textSub).width('100%').margin({ top: 4 })
}
.width('100%')
.backgroundColor('#FDEBEE')
.borderRadius(10)
.padding(10)
.margin({ top: 12 })
Row() {
Text('取消').fontSize(13).fontColor(NP.textSub).backgroundColor(NP.chip).borderRadius(18)
.padding({ left: 28, right: 28, top: 9, bottom: 9 }).margin({ top: 14 })
.onClick(() => {
this.showDelOrder = false
})
Text('确认删除').fontSize(13).fontColor(NP.white).backgroundColor(NP.warn).borderRadius(18)
.padding({ left: 28, right: 28, top: 9, bottom: 9 }).margin({ top: 14, left: 14 })
.onClick(() => {
this.orders = removeOrder(this.orders, this.delOrder.id)
this.showDelOrder = false
})
}
.width('100%')
}
.width('86%')
.backgroundColor(NP.card)
.borderRadius(18)
.padding(18)
.alignItems(HorizontalAlign.Center)
this.modalOverlay(() => {
this.showDelOrder = false
})
}
.width('100%')
.height('100%')
.backgroundColor('rgba(0,0,0,0.55)')
.zIndex(999)
}
}
更多推荐



所有评论(0)