HarmonyOS 6.1 实战:对比@State、@Prop、AppStorage等装饰器差异,详解页面与应用级状态共享更新机制
引言
当编发从传统手艺进化为街头潮流符号,一场关于"夜店霓虹"与"街头潮流"的视觉革命正在发廊圈蔓延。BRAID KINGS 编发王国正是这样一款概念应用——它将拳击辫的利落、盒辫的层次、脏辫的自由、彩辫的绚烂、渐变染的梦幻,全部浓缩进一个 ArkTS 单文件之中。
不同于传统美发 App 的预约工具路线,BRAID KINGS 选择了"潮流编发·脏辫·彩辫工作室"的定位:深紫黑底色上,霓虹粉 #FF4FA3 与霓虹青 #38E8D8 对撞,鎏金 #F5C842 点缀其间,如同夜店霓虹灯在深夜发廊中闪烁。霓虹呼吸、灯柱旋转、星星脉动、流光滑动——约 5 组属性动画让整个界面如同夜店舞池的灯光秀。
8 个 Tab 覆盖完整业务链路:门面查看今日主打与现场排号、发型库浏览 16 款潮流编发、预约选择发型技师时段、技师榜查看 9 位编发师天梯、会员管理金卡与次卡、色板浏览 16 色进口染膏、动态浏览编发社区、我的管理个人档案。7 套弹框各具特色:多区块预约编发、输入式新增收藏、底部抽屉式修改预约、粉色警告式取消预约、招牌卡式技师详情、色卡宫格染膏选色、票据档位式会员充值。
本文将从项目概述、色彩系统、开发环境、架构设计、数据模型、八大 Tab、七大弹框、属性动画、门面与发型库、预约与技师榜、会员与色板、动态与我的、性能优化、总结展望共十四个维度,对 BRAID KINGS 编发王国进行深度解析。

第一章 项目概述与设计理念
1.1 项目定位
BRAID KINGS 编发王国是一款面向潮流编发·脏辫·彩辫工作室的概念应用,核心定位是"预约+电商+社区"三位一体的得物 App 风格。目标用户是 18-35 岁的潮流青年、街头文化爱好者、夜店派对人群、Z 世代时尚消费者。应用场景包括:浏览潮流发型、预约编发服务、选择心仪技师、查看技师排行、管理会员卡与次卡、挑选染膏颜色、浏览编发社区动态、管理个人头皮档案。
1.2 设计理念
夜店霓虹发廊美学:以夜店霓虹灯为视觉灵感来源,霓虹粉 #FF4FA3 作为主强调色贯穿按钮、Tab 激活、价格、热门标签,霓虹青 #38E8D8 作为辅助强调色用于可约状态、确认按钮、色板品牌,鎏金 #F5C842 用于会员金卡、技师排行、热门标记。深紫黑(#17101F 主背景 / #221830 卡片 / #0E0914 极深黑)构建沉浸感,如同深夜夜店的暗调空间。
街头潮流风:大量使用色卡瀑布流、技师圆头像横滚、排号进度条、周历时段表、社区信息流,营造潮流电商的快节奏视觉。发型库的三列色卡瀑布流直接借鉴得物 App 的商品展示模式,每款发型用双色渐变色块模拟发色效果,价格和时长一目了然。
得物 App 电商风:顶部搜索栏 + 消息铃铛角标、发型库商品卡瀑布流、技师榜排行、会员充值档位、色板商品展示,完全复现潮流电商的购物体验。预约弹框的多区块选择(发型宫格+技师chips+时段chips+备注+合计)如同电商下单流程。
1.3 核心功能矩阵
| 模块 | 核心功能 | 数据量 |
|---|---|---|
| 门面 | 今日主打横幅、现场排号、潮流风格chips、在店技师横滚、今日时段格 | 9 技师 / 6 时段 |
| 发型库 | 10 分类 chips、三列色卡瀑布流、收录新发型入口 | 16 款发型 |
| 预约 | 周历时段表、本月预约量柱状图、我的预约列表(改约/取消) | 7 条预约 |
| 技师榜 | 大序号排行、技师评分分布条、预约入口 | 9 位技师 |
| 会员 | 票据式金卡、次卡进度、8 项权益 2x2、充值入口 | 8 权益 / 4 档位 |
| 色板 | 4 列染膏色卡、热门染发风格横条占比榜、流光特效 | 16 色染膏 |
| 动态 | 社区信息流、点赞/评论、同款发型跳转 | 8 条动态 |
| 我的 | 个人卡、数据四格、设置项列表 | 4 数据 / 6 设置 |
1.4 技术亮点
- 单文件 ArkTS 零依赖:约 1800 行代码在一个 .ets 文件中,复制即运行
- 约 5 组属性动画:霓虹呼吸(Alternate)、灯柱 360° 旋转(Normal 循环)、星星脉动(Alternate)、色板流光滑动(Alternate)
- 10 纯函数业务逻辑:发型价格/时长查询、技师按名查询、热门发型筛选、周峰值、日历状态/文本、星级、热门标签
- 7 差异化弹框:多区块预约式 / 输入式收藏 / 底部抽屉式改约 / 粉色警告式取消 / 招牌卡式技师详情 / 色卡宫格选色 / 票据档位式充值
- 票据式金卡设计:金色卡片+锯齿票孔行+次卡进度,如同夜店入场券
- 周历时段表:7 天 × 6 时段矩阵,空闲/已约/过期三态配色
- 色板流光特效:translate 动画模拟霓虹灯管流光,增加夜店氛围
第二章 色彩系统与视觉风格
2.1 色彩体系
BRAID KINGS 编发王国定义了 13 色完整调色板(BKColorPalette 接口),分为四个层级:
基础层(深紫黑):
- bg #17101F:主背景,深紫黑
- card #221830:卡片背景,稍浅紫黑
- deep #0E0914:极深黑,用于输入框、弹框内层、表头
- line #352848:分割线,暗紫灰
强调层(霓虹粉 × 霓虹青 × 鎏金):
- neonPink #FF4FA3:霓虹粉,主按钮、Tab 激活、价格、热门标签、VIP 标签、点赞红心
- pinkDeep #D63384:深粉,渐变终点、边框、选中态背景
- neonCyan #38E8D8:霓虹青,可约状态、确认按钮、色板品牌、技能标签、评论
- cyanDeep #1AA398:深青,渐变终点、边框、可约边框
- gold #F5C842:鎏金,会员金卡、技师排行前三、热门标记、星级、充值档位选中
文字层:
- textMain #F4EEFA:主文字,近白带紫
- textSub #A794BC:次要文字,中紫灰
- textHint #6B5A7E:提示文字,暗紫灰
2.2 色彩应用策略
霓虹粉 #FF4FA3 是应用的灵魂色,承担"行动召唤与潮流标识"角色:底部 Tab 激活态文字和指示条、主操作按钮(确认预约/立即充值/确认取消)、价格显示、热门标签、VIP 标签、点赞红心、技师头像边框、发型卡价格。霓虹粉在深紫黑背景上具有极强的视觉穿透力,如同夜店霓虹灯在黑暗中闪烁。
霓虹青 #38E8D8 承担"可用与确认"角色:可约时段状态、确认预约按钮、色板品牌标签、技师技能标签、评论图标、可约边框、搜索框聚焦。霓虹青与霓虹粉形成冷暖对撞,在数据可视化中区分"可用/确认"与"行动/热门"。
鎏金 #F5C842 承担"尊贵与排行"角色:会员金卡整体背景、技师排行前三序号和边框、热门标记🔥、星级评分、充值档位选中态、发型标签。鎏金在粉青对撞中起到"第三极"尊贵感作用,增加高级感和价值感。
2.3 霓虹渐变与光影
应用大量使用 linearGradient 构建视觉层次:
- 今日主打横幅:#FF4FA3 → #D63384 → #38E8D8 135° 三色渐变,粉青对撞如同霓虹灯牌
- 发型卡色块:c1 上半 + c2 下半双色拼接,模拟发色渐变效果
- 动态卡色块:c1 左半 + c2 右半双色拼接,模拟发型双色效果
- 会员金卡:纯金色 #F5C842,如同夜店 VIP 入场券
- 进度条:霓虹粉填充 + 深紫灰背景,简洁有力
2.4 霓虹呼吸与流光特效
项目通过属性动画模拟霓虹灯效果:
- 霓虹呼吸:今日主打横幅标题文字 opacity 0.45↔1.0 交替,1400ms,如同霓虹灯闪烁
- 灯柱旋转:横幅左侧金色+粉色灯柱 360° 旋转,6000ms Normal+Linear,如同夜店旋转灯柱
- 星星脉动:横幅右上角 ✦✧ 星星 scale 0.8↔1.25,900ms Alternate,如同舞台灯光闪烁
- 色板流光:色板标题栏青色光条 translate -40↔40,1600ms Alternate,如同霓虹灯管流光
这些动画共同营造夜店霓虹发廊的氛围,让静态界面"活"起来。

第三章 开发环境搭建
3.1 开发工具准备
BRAID KINGS 编发王国基于 HarmonyOS ArkTS 声明式 UI 框架开发,推荐使用 DevEco Studio 作为 IDE。DevEco Studio 是华为官方推出的 HarmonyOS 应用开发工具,基于 IntelliJ IDEA 社区版构建,提供 ArkTS 代码高亮、智能补全、实时预览、模拟器调试等完整开发能力。
系统要求:Windows 10/11 64 位或 macOS 12+,内存建议 16GB 以上,磁盘空间 10GB 以上。下载安装包后运行安装向导,选择安装路径和组件,完成后启动 DevEco Studio。

3.2 SDK 配置
首次启动 DevEco Studio 后,需要配置 HarmonyOS SDK。进入 File → Settings → SDK,选择 HarmonyOS 标签页,勾选需要的 SDK 版本(建议 API 10 及以上),点击 Apply 开始下载。SDK 包含 ArkTS 编译器、系统组件库、模拟器镜像等。
同时需要配置 Node.js 环境,DevEco Studio 内置了 Node.js,也可以使用本地安装的版本。在 Settings → Build, Execution, Deployment → Build Tools → Node.js 中配置 Node 路径。

3.3 创建项目
打开 DevEco Studio,选择 Create Project,在模板选择界面选择 Empty Ability(ArkTS),点击 Next。填写项目名称(如 BraidKings)、包名(如 com.example.braidkings)、保存路径,选择 Compile SDK 版本(建议 API 10),Model 选择 Stage,点击 Finish 创建项目。
项目创建完成后,目录结构如下:
- AppScope/:应用全局配置
- entry/src/main/ets/:ArkTS 源码目录
- entry/src/main/ets/entryability/:Ability 入口
- entry/src/main/ets/pages/:页面目录
- entry/src/main/resources/:资源文件
本项目为单文件架构,所有代码写在 entry/src/main/ets/pages/Index.ets 中。

3.4 运行与调试
将源码复制到 Index.ets 后,可以通过以下方式运行:
- 模拟器运行:点击工具栏的 Device Manager,创建一个 Phone 模拟器,启动后点击 Run 按钮,应用将安装到模拟器并运行。
- 真机运行:连接 HarmonyOS 手机,开启开发者模式和 USB 调试,在工具栏选择设备后点击 Run。
- 预览器:DevEco Studio 提供 Previewer 实时预览功能,在编辑代码时可以实时查看 UI 效果,无需运行到设备。

3.5 预览调试技巧
ArkTS 声明式 UI 的开发效率很大程度上依赖预览器。建议在开发过程中保持 Previewer 开启,实时查看布局和样式效果。对于属性动画,可以在预览器中点击组件查看动画效果。对于弹框,可以临时将 showXxx 状态设为 true 来预览弹框布局。
调试时可以使用 console.log() 输出调试信息,在 LogCat 窗口查看。对于状态管理问题,可以在状态变量变更处添加日志,追踪状态变化流程。预约和会员功能建议连接真实支付 SDK 进行测试,预览器中使用硬编码数据模拟。

DevEco Studio 预览器支持实时预览 ArkTS 声明式 UI 效果,左侧代码编辑器与右侧 Previewer 面板联动,修改代码后预览界面即时刷新,支持多设备尺寸切换、深色模式预览、组件树 inspect 等调试能力,大幅提升 ArkTS UI 开发效率。下图为 BRAID KINGS 编发王国"我的"Tab 在 Previewer 中的实时预览效果。

第四章 项目架构与文件组织
4.1 单文件组件化架构
BRAID KINGS 编发王国采用"单文件组件化"架构,所有代码在一个 Index.ets 文件中,通过 @Component 装饰器划分为约 17 个独立组件(8 Tab + 7 弹框 + 1 根入口 + 1 内联取消弹框)。代码量约 1800 行,处于单文件架构的合理范围。优势在于零配置运行、低理解成本、快速原型验证,适合概念展示和教学演示场景。
4.2 组件层次结构
BKKingsApp (@Entry 根组件)
├── 顶部标题栏(BRAID粉/KINGS青/编发王国金 + 搜索 + 铃铛角标)
├── 内容区 Stack
│ ├── 8 个 Tab 内容组件(8 选 1)
│ │ ├── BKHomeContent(门面)
│ │ ├── BKStylesContent(发型库)
│ │ ├── BKBookContent(预约)
│ │ ├── BKRankContent(技师榜)
│ │ ├── BKVipContent(会员)
│ │ ├── BKColorContent(色板)
│ │ ├── BKFeedContent(动态)
│ │ └── BKProfileContent(我的)
│ └── 7 个弹框(按需显示,zIndex 999)
│ ├── BKBookForm(预约编发·多区块)
│ ├── BKAddStyleForm(新增收藏·输入式)
│ ├── BKEditBookForm(修改预约·底部抽屉式)
│ ├── 取消预约弹框(内联·粉色警告式)
│ ├── BKStylistCard(技师详情·招牌卡式)
│ ├── BKColorPickForm(染膏选色·色卡宫格)
│ └── BKVipRechargeForm(会员充值·票据档位式)
└── 底部 Tab 导航栏(8 项)
根组件管理当前 Tab(activeTab)、7 个弹框显隐状态(showBook/showAddStyle/showEditBook/showCancelBook/showStylist/showColor/showVip)、弹框间传递数据(bookStyleName/editStyleName/editStylistName/editTimeText/cancelStyleName/stylistName/colorCode/colorName)。通过 contentArea() Builder 分发 Tab 内容和弹框层。每个 Tab 组件负责自身 UI 和局部状态,通过回调函数(onBook/onStylist/onEditBook/onCancelBook/onRecharge/onPickColor)与根组件通信。
4.3 @Builder 自定义构建函数
项目使用约 9 个 @Builder 方法封装可复用 UI 片段:modalOverlay(弹框遮罩层)、bookModal/addStyleModal/editBookModal/cancelBookModal/stylistModal/colorModal/vipModal(7 个弹框结构)、contentArea(内容区分发)。@Builder 减少重复代码,提高可读性,支持参数传递和条件渲染。
4.4 弹框架构设计
弹框采用"遮罩层 + 内容层"Stack 叠加模式,统一在根组件管理。7 套弹框各具特色:
- 预约编发(多区块):①选发型宫格(金边选中)+ ②选技师chips + ③选时段chips + ④备注 + 合计(发型价+20服务费)
- 新增收藏发型(输入式):发型名称输入 + 风格分类chips + 价格/时长输入 + 描述文本域
- 修改预约(底部抽屉式):顶部拖拽条 + ①改时段点钟stepper + ②换技师单选行 + ③改备注
- 取消预约(粉色警告式):⚠️图标 + 违约金说明(24小时内20%/超过全额退)+ 取消/确认
- 技师详情(招牌卡式):大圆头像 + 名称/头衔/评分 + 技能标签 + 三数据格(作品/年限/口碑)+ 代表作品横滚 + 指定TA按钮
- 染膏选色(色卡宫格):4列色卡宫格 + 已选颜色预览 + 确认用这个色
- 会员充值(票据档位式):4档位横滚(充300送30/充500送80/充1000送200/充2000送500)+ 支付方式三选一 + 实付合计
第五章 数据模型与全局状态管理
5.1 接口定义体系
项目定义 14 个 TypeScript 接口描述数据模型:BKColorPalette(色彩)、BKTabItem(导航)、BKStyle(发型)、BKStylist(技师)、BKBooking(预约)、BKDye(染膏)、BKFeed(动态)、BKDayCount(日统计)、BKPerk(权益)、BKTier(充值档位)、BKStyleRank(发型排行)、BKRateBand(评分区间)。
5.2 全局数据常量
约 16 组全局 const 常量:核心业务数据(BK_STYLES 16 款 / BK_STYLISTS 9 位 / BK_BOOKINGS 7 条 / BK_DYES 16 色 / BK_FEEDS 8 条 / BK_WEEK 7 日 / BK_PERKS 8 权益 / BK_TIERS 4 档位 / BK_RANK_BARS 6 发型 / BK_RATE_BANDS 4 评分)、配置数据(BK_TABS 8 项 / BK_STYLE_CATS 10 分类 / BK_FLOW_TAGS 8 标签 / BK_CAL_DAYS 7 天 / BK_CAL_SLOTS 6 时段 / BK_TIME_CHIPS 8 时段)。
5.3 三级状态管理策略
- 第一级 全局常量(const):业务数据和配置,任何组件可直接读取,运行时不可变
- 第二级 组件局部状态(@State):组件内交互状态,如 selStyle/selStylist/selTime/note(预约)、name/selCat/price/dur/desc(收藏)、hourIdx/pickStylist/note(改约)、selTier/selPay(充值)、selIdx(选色)、likedId(动态点赞)、neonOp/pillarAngle/starScale(门面动画)、shineX(色板流光)
- 第三级 根组件协调状态(@State):跨组件状态,如 activeTab(当前 Tab)、7 个弹框显隐开关、弹框间传递数据(bookStyleName/editStyleName/editStylistName/editTimeText/cancelStyleName/stylistName/colorCode/colorName)
5.4 全局纯函数
10 个全局纯函数处理数据转换和业务逻辑:
- bkStylePrice(name):按发型名查询价格,遍历 BK_STYLES 匹配
- bkStyleDur(name):按发型名查询时长
- bkStylistByName(name):按技师名查询技师对象
- bkHotStyles():筛选热门发型(hot>1000)
- bkWeekMax():返回周预约峰值 102
- bkCalStatus(day, slot):计算日历时段状态(0空闲/1已约/2过期),基于数学公式模拟
- bkCalText(day, slot):返回日历状态文本(空闲/已约/过期)
- bkStars(rating):返回星级字符串(固定★★★★★)
- bkHotLabel(hot):按热度返回🔥标签(≥1500三火/≥1000两火/其他一火)
纯函数相同输入永远产生相同输出,无副作用,易于测试和复用。bkCalStatus 使用数学公式 (day7+slot3)%5 和 (day+slot)%4 模拟真实预约分布,无需后端数据即可展示丰富的日历状态。
第六章 八大 Tab 页面深度解析
6.1 门面 Tab(BKHomeContent)
门面是首页,承担"查看今日主打→了解排号→浏览风格→选择技师→查看时段→预约"路径。页面结构:
-
今日主打大横幅(粉青渐变 + 霓虹呼吸 + 旋转灯柱 + 星星脉动)
- 135° 三色渐变 #FF4FA3→#D63384→#38E8D8
- TODAY·今日主打金色标签
- 旋转灯柱:金色10vp+粉色4vp双柱 360° 旋转(pillarAngle,6000ms Normal+Linear)
- 渐变彩辫 OMBRE 标题白色 opacity 呼吸(neonOp 0.45↔1.0,1400ms Alternate)
- 霓虹粉→电光青·全场人气王NO.1
- 🔥1132热度 + ¥528/160分钟
- 立即预约黑色按钮
- ✦✧ 星星 scale 脉动(starScale 0.8↔1.25,900ms Alternate)
-
现场排号进度条
- 🎫现场排号 + 叫号中青标签
- BK-042 当前叫号粉色26vp + 前方3位金色 + 预计等待45分钟
- 4节点进度条(3粉点已叫+1青点当前)
- 已叫39/您在42号/共45号
-
潮流风格 chips 横滚:🔥拳击辫/🌈渐变彩辫/🧶盒辫/💀脏辫/✨星空闪片/🍑蜜桃粉/🌀拧转辫/⚡快编通道
-
在店技师圆头像横滚(9 位):58vp圆头像(粉边框)+ emoji + 名称 + 头衔 + ★评分
-
今日时段状态格(6 时段 3 列):10:00/12:00/14:00/16:00/18:00/20:00,空闲青边框/已约粉填充/过期深灰
三组属性动画是门面的核心视觉亮点:霓虹呼吸模拟霓虹灯闪烁、灯柱旋转模拟夜店旋转灯、星星脉动模拟舞台灯光,共同营造夜店霓虹发廊的氛围。

6.2 发型库 Tab(BKStylesContent)
发型库是商品展示中心,承担"筛选分类→浏览发型→预约→收录新发型"路径。页面结构:
-
顶部小分类 chips(10 分类):全部/街头/非洲/彩辫/脏辫/派对/仙系/混搭/增发/假发,选中青色背景深字
-
三列色卡瀑布流(16 款发型)
- 每卡 31.5% 宽,Flex Wrap 自动换行
- 双色渐变色块:上半 c1 52vp + 下半 c2 30vp,模拟发色效果
- 标签角标(HOT/上新/爆款/口碑/大师/经典/出片/定制/少女/夜店/进阶)金色背景
- 发型名 + 风格·🔥热度 + ¥价格粉色 + 时长
- 16 款覆盖:拳击辫¥388/无痕接发¥1280/经典盒辫¥458/渐变彩辫¥528/加纳辫¥368/雷鬼脏辫¥888/半编彩辫¥298/玉米辫¥328/鱼骨辫¥268/波西米亚编发¥318/蕾丝辫假发¥980/脏辫接发¥1080/拧转辫¥348/双色扭博辫¥458/蜜桃粉彩辫¥388/星空闪片辫¥498
-
收录新发型入口:青色描边按钮"➕没找到心仪的?收录新发型"
发型库的三列色卡瀑布流是核心设计,双色渐变色块模拟发色效果,无需真实发型图片即可展示丰富的视觉效果,标签角标增加潮流感和购买引导。
6.3 预约 Tab(BKBookContent)
预约是服务预约中心,承担"查看周历→了解预约量→管理我的预约(改约/取消)"路径。页面结构:
-
周历时段表(7 天 × 6 时段矩阵)
- 表头:时段 + 周一至周日(周末粉色)
- 每格:bkCalText 返回空闲/已约/过期
- 配色:空闲青边框深字/已约粉填充白字/过期深灰
- bkCalStatus 数学公式模拟真实分布
-
本月预约量 7 日柱状图
- 周一32/周二48/周三41/周四66/周五88/周六102(金色峰值)/周日79
- 峰值周六102单金色提示
- 柱子高度 count/102*92 动态计算
-
我的预约列表(7 条)
- 每条:发型名 + 技师 + 时间 + 状态标签(待到店青/已确认青/待支付青/进行中青/已完成灰)+ ¥费用粉色
- 操作:改约(金描边)+ 取消(粉描边)
- 7 条覆盖:渐变彩辫Kiko今天19:00待到店/经典盒辫蕾娜明天14:00已确认/雷鬼脏辫阿钉周六10:00待支付/鱼骨辫Momo 08-30进行中/无痕接发Riri 09-02待到店/星空闪片辫Kiko 08-20已完成/拳击辫BB Boy 08-12已完成
周历时段表是预约 Tab 的核心设计,7 天 × 6 时段矩阵通过 bkCalStatus 纯函数动态计算状态,空闲/已约/过期三态配色直观展示可预约时段。

6.4 技师榜 Tab(BKRankContent)
技师榜是技师排行中心,承担"查看天梯榜→了解评分分布→预约技师"路径。页面结构:
-
榜单说明条:🏆BRAID KINGS编发师天梯榜 + 每月1日刷新,金色背景深字
-
排行榜大序号布局(9 位技师)
- 前三名:01/02/03 金色32vp大号 + 金色头像边框 + 第一名金色卡片边框
- 其他:序号暗紫灰24vp + 粉色头像边框
- 每卡:50vp圆头像 + emoji + 名称 + 头衔 + 技能标签(青色)+ ★评分金色 + 订单数·年限 + 预约青色按钮
- 9 位覆盖:蕾娜Naena主理人非洲辫大师4.9/1862单/9年、Kiko首席彩辫师4.8/1543单/7年、阿钉Dreadlock教父4.9/1204单/11年、Momo少女系编手4.7/1891单/5年、Tariq非洲辫正统传承4.8/976单/8年、小鹿新锐造型师4.6/651单/3年、Riri明星指定假发师4.9/445单/10年、BB Boy街头快编手4.5/1310单/4年、佐伊Zoe盖辫专家4.7/897单/6年
-
技师评分分布条(4 区间)
- 4.9-5.0铁粉级3人金色/4.7-4.8口碑级3人粉色/4.5-4.6稳健级2人青色/4.4以下潜力级1人灰色
- 横条宽度 count/3*100% 动态计算
大序号排行是技师榜的核心设计,前三名金色大号序号+金色边框突出领奖台位置,技能标签青色增加专业感,预约按钮直接引导转化。
6.5 会员 Tab(BKVipContent)
会员是商业化中心,承担"查看金卡→管理次卡→浏览权益→充值"路径。页面结构:
-
票据式金卡
- 上半金色卡片:👑GOLD KING + NO.BK-8823-0042 + 卡内余额¥2,680 28vp + 金卡有效期2027-08-26
- 锯齿票孔行:10 个深色小圆点均匀分布,模拟票据撕线
- 下半深色卡片:🎟️10次编发次卡 + 剩余6/10次金色 + 60%粉色进度条 + 2026-02-26到期可转赠 + 立即充值青色按钮
-
金卡专属权益 2x2 Grid(8 项)
- 🎟️黄金时段优先(周五周六晚场优先锁位)
- 💊养护精华礼(每次编发赠头皮精华)
- 🎈生日免单(生日当月送编发一次)
- 🧴染膏88折(全部染膏色卡会员价)
- 📸免费出片(店内霓虹棚写真拍摄)
- 🔁7天免费补辫(辫体松脱免费返修)
- 🚗到店泊车(商圈停车2小时报销)
- 🎁积分兑换(积分换染膏/饰品)
票据式金卡是会员 Tab 的核心设计,金色卡片+锯齿票孔行模拟夜店 VIP 入场券,次卡进度条直观展示使用情况,8 项权益增加会员价值感。

6.6 色板 Tab(BKColorContent)
色板是染膏商品中心,承担"浏览色卡→查看热门风格→选色"路径。页面结构:
-
色板标题 + 流光特效
- 深色卡片背景 + 🎨染膏色板 + 进口染膏·16色现货青色
- 流光特效:60vp×4vp 青色半透明光条 translate -40↔40(shineX,1600ms Alternate),模拟霓虹灯管流光
-
4 列色块 Grid(16 色染膏)
- 每卡 23.5% 宽,Flex Wrap 自动换行
- 44vp 纯色色块 + 🔥热度角标
- 色名 + 色号 + 品牌(IGORA/丝蕴PRO/Pravana)
- 选中态:深粉背景+金色边框
- 16 色覆盖:霓虹粉BK-P01/电光青BK-C02/鎏金BK-G03/脏粉紫BK-P04/深海青BK-C05/暗夜紫BK-V06/警报红BK-R07/街球蓝BK-B08/日落橙BK-O09/鸢尾紫BK-M10/蜜桃气泡BK-P11/月光银BK-S12/荧光绿BK-G13/加勒比BK-T14/奶白雾BK-W15/墨黑雾BK-K16
-
热门染发风格横条占比榜(6 风格)
- 渐变彩辫32%粉/拳击辫26%青/经典盒辫18%金/雷鬼脏辫12%深粉/鱼骨辫7%深青/其他5%灰
- 近30天·3268单
流光特效是色板 Tab 的核心视觉亮点,青色光条横向滑动模拟霓虹灯管流光,增加夜店氛围和科技感。4 列色块 Grid 直观展示 16 色染膏,品牌标签增加专业感。
6.7 动态 Tab(BKFeedContent)
动态是社区中心,承担"浏览编发动态→点赞评论→跳转同款发型"路径。页面结构:
-
编发社区标题:📣编发社区 + 今日128条新动态
-
动态信息流(8 条)
- 每条:38vp圆头像(首字粉色)+ 昵称 + VIP金色标签 + 时间·到店消费 + #发型标签青色描边
- 动态文字(2行省略)
- 双色渐变色块图(左c1+右c2,模拟发型效果)
- 互动行:❤️/🤍点赞切换(likedId状态)+ 点赞数 + 💬评论数 + 同款发型>粉色跳转
- 8 条覆盖:霓虹小卷渐变彩辫夜店发光/Dread老炮雷鬼脏辫终身指定/桃子汽水蜜桃粉彩辫出片率100%/球鞋收藏家Ken拳击辫配AJ街头感/Vvvvivi加纳辫层次绝了/鱼骨辫女孩鱼骨辫婚礼造型/星空收藏家星空闪片辫夜店搭讪三次/短发电波半编彩辫告别帽子
动态信息流是社区 Tab 的核心设计,点赞切换(❤️/🤍)增加互动感,同款发型跳转直接引导预约转化,双色渐变色块图模拟发型效果无需真实图片。
6.8 我的 Tab(BKProfileContent)
我的是个人中心,承担"查看个人卡→了解数据→管理设置→充值"路径。页面结构:
-
个人卡
- 66vp圆头像(粉边框3vp)+ 🧶emoji
- 霓虹小卷 NEON.C 16vp白色
- 标签行:👑金卡KING金色/📸出片达人粉色/💬理发室话痨青色
-
数据统计四格
- 26累计编发粉/48收藏发型青/8,620积分金/31到店次数深粉
-
设置项列表(6 项)
- 📍常用门店(霓虹旗舰店)/💳会员卡与充值(余额¥2,680金色)/🧵头皮档案(油性·粗硬发质)/🔗绑定手机(138****6688)/🛡️隐私与账号安全/☎️联系客服(9:00-23:00在线)
个人卡的三标签(金卡/出片达人/理发室话痨)增加个性化和潮流感,数据四格直观展示用户活跃度,头皮档案增加专业服务感。

第七章 七大弹框组件设计
弹框是 BRAID KINGS 编发王国交互体系的核心组成部分。项目共设计了 7 套弹框组件,覆盖预约编发、新增收藏发型、修改预约、取消预约、技师详情、染膏选色、会员充值七大业务场景。

7.1 预约编发弹框(BKBookForm·多区块)
定位:编发服务预约入口,多区块分步式设计。
核心功能——四步预约流程:
- ① 选发型(金边选中):16 款发型 Flex Wrap 宫格,每格含 30vp 双色渐变色块 + 发型名 + ¥价格,选中态金色半透明背景+1.5vp金色边框
- ② 选技师:9 位技师横滚 chips,选中粉色背景深字
- ③ 选时段:8 个时段 chips(11:00/12:30/14:00/15:30/17:00/18:30/20:00/21:30),选中青色背景深字
- ④ 备注:多行文本域(发质/忌讳/想要的效果)
底部合计栏:合计 ¥(发型价+20服务费) 金色19vp + 取消 + 确认预约XX时 青色按钮。发型价格通过 bkStylePrice(selStyle) 纯函数动态查询,服务费固定 ¥20。
多区块分步式设计将复杂的预约流程拆解为四个清晰步骤,金边选中态增加尊贵感,合计栏动态计算价格增加透明度。
7.2 新增收藏发型弹框(BKAddStyleForm·输入式)
定位:用户自定义收藏发型入口,输入式设计。
核心功能:
- 发型名称:文本输入(如:玫瑰荆棘辫)
- 风格分类:10 分类 Flex Wrap chips(全部/街头/非洲/彩辫/脏辫/派对/仙系/混搭/增发/假发),选中粉色背景深字
- 参考价格 + 时长:双列文本输入
- 描述:多行文本域(想要的编法、配色、参考图链接)
底部操作:放弃 + 收藏到我的发型库 金色按钮。输入式设计让用户可以收藏心仪的发型,即使发型库中没有,也能记录下来后续与技师沟通。
7.3 修改预约弹框(BKEditBookForm·底部抽屉式)
定位:已有预约的修改入口,底部抽屉式设计。
核心设计——底部抽屉式:
- 顶部 40vp×4vp 深色拖拽条,模拟底部抽屉的拖拽手柄
- 顶部圆角 22vp,底部直角,从底部滑出的视觉效果
- position y:36%,maxHeight 64%
核心功能——三步修改:
- ① 改时段(点钟 stepper):−/+ 按钮调节小时(10-21点),青色22vp大号数字显示 XX:00,aboutToAppear 回显原技师
- ② 换技师(单选行):9 位技师列表,每行 emoji + 名称 + 头衔·评分 + ●已选/○选择,选中青色半透明背景
- ③ 改备注:多行文本域
底部操作:不改了 + 保存修改 粉色按钮。底部抽屉式设计是本弹框的特色,从底部滑出的交互模式符合移动端改约的轻量操作场景,拖拽条增加可操作性暗示。
7.4 取消预约弹框(内联·粉色警告式)
定位:取消预约的二次确认,粉色警告式设计。
核心设计——粉色警告:
- ⚠️ 图标 42vp
- 确认取消这条预约?粉色17vp标题
- 发型名白色加粗
违约金说明卡(粉色半透明背景+深粉边框):
- 违约金说明金色标题
- 距开始不足24小时取消,收取订单金额20%作为违约金(技师档期已锁定)
- 距开始超过24小时取消,全额退款至会员卡,积分不退
底部操作:再想想(深灰)+ 确认取消(粉色白字)。粉色警告式设计与取消操作的"风险"属性匹配,违约金说明增加透明度,让用户清楚了解取消后果,减少误操作。
7.5 技师详情弹框(BKStylistCard·招牌卡式)
定位:技师详细信息展示入口,招牌卡式设计。
核心功能:
- 技师招牌卡头:76vp圆头像(金色3vp边框)+ emoji + 名称18vp + 头衔金色 + ★评分金色 + 订单数评价
- 技能标签:#技能 青色标签横滚
- 三数据格:累计作品粉色 / 从业年限青色 / 本月口碑金色,竖线分隔
- 代表作品横滚:16 款发型 76vp 色卡横滚,模拟技师作品集
- 指定 TA 帮我编发:粉色大按钮,点击后关闭技师详情并自动打开预约弹框(onBook 回调链式触发)
招牌卡式设计将技师信息如同理发店招牌般展示,大圆头像+金色边框增加尊贵感,三数据格直观展示技师实力,代表作品横滚增加说服力。指定TA按钮的链式触发(关闭技师详情→打开预约弹框)是本弹框的交互亮点,减少用户操作步骤。
7.6 染膏选色弹框(BKColorPickForm·色卡宫格)
定位:染膏颜色选择入口,色卡宫格式设计。
核心功能:
- 4 列色卡宫格(16 色):每卡 23% 宽,44vp 纯色色块 + 色名 + 色号,选中金色半透明背景+1.5vp金色边框
- 已选颜色预览:34vp 圆形色块(金色2vp边框)+ 已选:色名+色号 + 品牌·热度
- 底部操作:再看看 + 确认用这个色 青色按钮
aboutToAppear 生命周期根据 enterCode 遍历 BK_DYES 找到对应色索引并回显 selIdx。色卡宫格式设计直观展示 16 色染膏,纯色色块让颜色一目了然,已选预览增加确认感。
7.7 会员充值弹框(BKVipRechargeForm·票据档位式)
定位:会员卡充值入口,票据档位式设计。
核心功能——四档充值:
- 充值档位横滚(4 档):
- 体验档:充¥300送¥30,到账¥330
- 入门档:充¥500送¥80,到账¥580
- 人气档:充¥1000送¥200,到账¥1200
- 霸王档:充¥2000送¥500,到账¥2500
- 选中态:金色半透明背景+1.5vp金色边框+档位标签金色背景深字+金额金色+赠送青色
- 支付方式(三选一):💚微信支付 / 💳会员卡余额支付(¥2,680)/ 🏦银行卡支付,选中青色半透明背景+●青色
- 实付合计:¥selTier 金色19vp
底部操作:取消 + 立即充值 粉色按钮。票据档位式设计将充值档位如同票据般展示,充得多送得多的激励机制清晰可见,支付方式三选一符合移动端支付习惯。
第八章 属性动画与交互体验
8.1 ArkTS 属性动画机制
ArkTS 通过 animateTo() 实现属性动画,基本用法:
this.getUIContext().animateTo({
duration: 1400, iterations: -1,
playMode: PlayMode.Alternate, curve: Curve.EaseInOut
}, () => { this.neonOp = 1.0 })
iterations: -1 表示无限循环。playMode 有两种关键模式:
- PlayMode.Normal:到达目标值后立即跳回初始值重新开始,配合 Curve.Linear 可实现无缝循环(如灯柱 360° 旋转)
- PlayMode.Alternate:到达目标值后反向执行回初始值,形成往返效果(如呼吸、脉动、流光)
框架自动处理中间帧和缓动曲线,开发者只需声明初始值和目标值。组件销毁时动画自动停止,无需手动管理定时器。
8.2 约五组属性动画详解
| 动画 | 组件 | 属性 | 时长 | 模式 | 效果 |
|---|---|---|---|---|---|
| 霓虹呼吸 | 门面 | neonOp(opacity) | 1400ms | Alternate | 标题文字 0.45↔1.0 闪烁 |
| 灯柱旋转 | 门面 | pillarAngle(rotate) | 6000ms | Normal+Linear | 灯柱 0→360° 无缝旋转 |
| 星星脉动 | 门面 | starScale(scale) | 900ms | Alternate | ✦✧ 星星 0.8↔1.25 缩放 |
| 色板流光 | 色板 | shineX(translate) | 1600ms | Alternate | 青色光条 -40↔40 横向滑动 |
霓虹呼吸动画:门面今日主打横幅的"渐变彩辫 OMBRE"标题文字,opacity 从 0.45 到 1.0 往返,1400ms 中等时长,EaseInOut 曲线。文字在半透明和全亮之间缓慢闪烁,如同霓虹灯在夜空中闪烁,增加夜店氛围和科技感。白色文字在粉青渐变背景上的呼吸效果尤为明显。
灯柱 360° 旋转动画:门面横幅左侧的金色+粉色双灯柱,rotate 从 0° 到 360°,6000ms 较长时长,Normal 循环+Linear 曲线。Normal 模式到达 360° 后立即跳回 0°,由于 0° 和 360° 视觉相同,配合 Linear 匀速曲线实现完全无缝的旋转效果,如同夜店旋转灯柱持续转动。6000ms 较长时长使旋转缓慢有力,符合夜店灯柱的节奏感。这是全应用唯一使用 Normal 模式的动画。
星星脉动动画:门面横幅右上角的 ✦ 和 ✧ 两个星星装饰,scale 从 0.8 到 1.25 往返,900ms 较快时长,EaseOut 曲线。星星在小尺寸和大尺寸之间快速脉动,如同舞台灯光闪烁,增加活泼感和潮流感。较快的脉动频率与门面的"今日主打"紧迫感匹配。
色板流光动画:色板 Tab 标题栏的青色半透明光条(60vp×4vp),translate X 从 -40 到 40 往返,1600ms 较长时长,EaseInOut 曲线。光条在标题栏上横向滑动,如同霓虹灯管内的流光效果,增加夜店氛围和科技感。较长的滑动频率使流光变化缓慢流畅,符合色板 Tab 的"选色"沉浸感。
8.3 无定时器循环动画的优势
传统循环动画使用 setInterval 逐帧更新,需手动管理定时器、频繁状态更新可能导致性能问题、组件销毁时需手动清除定时器否则内存泄漏。ArkTS 属性动画通过 iterations:-1 实现零定时器循环动画:框架底层自动调度动画帧、组件销毁时动画自动停止、多个动画可同时运行互不干扰、Normal 和 Alternate 两种模式覆盖无缝循环和往返呼吸两种场景。BRAID KINGS 约 5 组动画全部采用此方式,确保流畅性和性能。
8.4 交互反馈设计
项目通过多种方式提供即时交互反馈:
- 按钮点击:立即触发状态变更(显示弹框/切换选中态),响应式机制自动更新 UI
- 选中态:明确视觉变化(颜色/背景/边框/●标记),发型宫格选中金色边框+金色半透明背景
- Tab 切换:图标透明度(1.0/0.45)+ 文字颜色(粉/灰)+ 底部指示条(青/深灰)三重反馈
- stepper:−/+ 点击后数字立即变化,边界值自动限制(小时10-21)
- 点赞切换:❤️/🤍 点击切换,点赞数+1动态计算
- 日历状态:bkCalStatus 纯函数动态计算,空闲/已约/过期三态配色
- 充值档位:选中态金色边框+金色半透明背景+金额变色,档位标签背景翻转
- 支付方式:选中青色半透明背景+●青色标记
这些即时反馈确保用户操作的"确定性",提升交互流畅度和愉悦感。发型宫格的金边选中态是本应用的特色交互,金色边框增加尊贵感和选中确认。
第九章 门面与发型库系统
9.1 发型数据模型
每款发型(BKStyle)包含:ID、发型名(name)、风格分类(style:街头/非洲/彩辫/脏辫/派对/仙系/混搭/增发/假发)、主色 c1(hex)、副色 c2(hex)、价格(price,元)、时长(dur,分钟)、热度(hot,如1280)、标签(tag:HOT/上新/爆款/口碑/大师/经典/出片/定制/少女/夜店/进阶)。
16 款发型覆盖多种风格:拳击辫¥388/90min/街头/1280热/HOT、无痕接发¥1280/150min/增发/862热/上新、经典盒辫¥458/180min/非洲/1543热/HOT、渐变彩辫¥528/160min/彩辫/1132热/爆款、加纳辫¥368/120min/非洲/976热/口碑、雷鬼脏辫¥888/240min/脏辫/651热/大师、半编彩辫¥298/80min/混搭/1408热/HOT、玉米辫¥328/100min/非洲/1204热/经典、鱼骨辫¥268/70min/仙系/1891热/爆款、波西米亚编发¥318/90min/派对/1024热/出片、蕾丝辫假发¥980/60min/假发/445热/定制、脏辫接发¥1080/200min/脏辫/530热/进阶、拧转辫¥348/130min/非洲/897热/口碑、双色扭博辫¥458/150min/彩辫/768热/上新、蜜桃粉彩辫¥388/85min/彩辫/1310热/少女、星空闪片辫¥498/95min/派对/1093热/夜店。
9.2 门面今日主打横幅
门面今日主打横幅是应用的视觉核心,包含:
- 135° 三色渐变 #FF4FA3→#D63384→#38E8D8,粉青对撞如同霓虹灯牌
- TODAY·今日主打金色标签
- 旋转灯柱:金色10vp+粉色4vp双柱 360° 旋转(pillarAngle,6000ms Normal+Linear)
- 渐变彩辫 OMBRE 标题白色 opacity 呼吸(neonOp 0.45↔1.0,1400ms Alternate)
- 霓虹粉→电光青·全场人气王NO.1 深色文字
- 🔥1132热度(半透明白色背景)+ ¥528/160分钟
- 立即预约黑色圆角按钮
- ✦✧ 星星 scale 脉动(starScale 0.8↔1.25,900ms Alternate)
三组属性动画(霓虹呼吸+灯柱旋转+星星脉动)共同营造夜店霓虹发廊的氛围,粉青渐变背景如同霓虹灯牌,旋转灯柱如同夜店旋转灯,星星脉动如同舞台灯光闪烁。今日主打横幅是用户进入应用的第一视觉印象,三组动画确保"第一眼就被吸引"。
9.3 现场排号系统
现场排号进度条展示实时排队状态:
- 🎫现场排号 + 叫号中青标签
- BK-042 当前叫号粉色26vp大号 + 前方3位金色 + 预计等待45分钟
- 4节点进度条:3个粉色圆点(已叫39/40/41)+ 连接线 + 1个青色圆点(当前42)
- 底部统计:已叫39/您在42号/共45号
现场排号系统增加线下门店的真实感和紧迫感,4节点进度条直观展示排队位置,预计等待时间帮助用户决策是否等待。叫号中青色标签增加实时感。
9.4 在店技师横滚
在店技师圆头像横滚展示 9 位在岗技师:
- 每技师:58vp圆头像(深紫灰背景+粉色2vp边框)+ emoji + 名称(白色加粗)+ 头衔(暗紫灰)+ ★评分(金色)
- 横滚 Scroll + Row,支持左右滑动
- 点击技师头像触发 onStylist 回调,打开技师详情弹框
在店技师横滚增加"实时在岗"的真实感,圆头像+粉色边框统一视觉风格,评分金色增加专业感。点击直接打开技师详情减少操作步骤。
9.5 今日时段格
今日时段状态格展示当天 6 个时段的可预约状态:
- 6 时段 3 列 Flex Wrap:10:00/12:00/14:00/16:00/18:00/20:00
- 每格:时段 + bkCalText(0, si) 状态文本
- 配色:空闲(青色边框+青色文字)/已约(粉色填充+白色文字)/过期(深灰背景+暗紫灰文字)
- 图例:已满(粉色方块)/可约(青色描边方块)
bkCalStatus(day, slot) 纯函数通过数学公式模拟真实预约分布:day<=1&&slot<=1 返回2(过期),(day7+slot3)%5=0 返回1(已约),(day+slot)%4=0 返回1(已约),其他返回0(空闲)。今日时段格让用户快速查看当天可预约时段,直接点击触发预约。
9.6 发型库三列色卡瀑布流
发型库三列色卡瀑布流是商品展示核心:
- 每卡 31.5% 宽,Flex({wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween}) 自动换行
- 双色渐变色块:上半 c1 52vp + 下半 c2 30vp,clip(true) 圆角裁剪,模拟发色渐变效果
- 标签角标:金色背景深字,position 左上角,HOT/上新/爆款/口碑/大师/经典/出片/定制/少女/夜店/进阶
- 发型名(白色加粗12vp)+ 风格·🔥热度(暗紫灰9vp)+ ¥价格(粉色13vp加粗)+ 时长(暗紫灰9vp)
- 点击卡片触发 onBook(styleName) 回调,打开预约弹框并自动选中该发型
双色渐变色块是发型库的核心视觉创新,无需真实发型图片即可展示丰富的发色效果,c1+c2 双色拼接模拟渐变染效果,不同发型的配色组合形成视觉多样性。标签角标增加潮流感和购买引导,价格粉色突出显示。
9.7 发型分类筛选
顶部小分类 chips 提供 10 种风格筛选:
- 全部/街头/非洲/彩辫/脏辫/派对/仙系/混搭/增发/假发
- 选中态:青色背景深字加粗
- 未选中:深紫灰背景暗紫灰字
- 横滚 Scroll + Row,支持左右滑动
selCat 状态变量控制当前分类,虽然演示项目中未实现实际筛选逻辑(所有发型始终显示),但架构已预留筛选能力,后续可通过纯函数筛选发型列表。10 种分类覆盖编发全风格,满足不同用户需求。
9.8 收录新发型
收录新发型入口让用户可以收藏心仪的发型:
- 青色描边按钮:➕没找到心仪的?收录新发型
- 青色半透明背景+深青1vp边框+青色文字
- 点击触发 onAddStyle 回调,打开新增收藏发型弹框
收录新发型功能增加用户参与感和个性化,即使发型库中没有用户想要的发型,也能记录下来后续与技师沟通。输入式弹框让用户可以详细描述发型名称、风格、价格、时长和描述。
第十章 预约与技师榜系统
10.1 预约数据模型
每条预约(BKBooking)包含:ID、发型名(style)、技师名(stylist)、时间(time,如今天19:00)、状态(status:待到店/已确认/待支付/进行中/已完成)、费用(fee,元)。
7 条预约覆盖多种状态:渐变彩辫Kiko今天19:00待到店¥528、经典盒辫蕾娜明天14:00已确认¥458、雷鬼脏辫阿钉周六10:00待支付¥888、鱼骨辫Momo 08-30 15:30进行中¥268、无痕接发Riri 09-02 11:00待到店¥1280、星空闪片辫Kiko 08-20 20:00已完成¥498、拳击辫BB Boy 08-12 18:30已完成¥388。
10.2 周历时段表
周历时段表是预约 Tab 的核心设计,展示 7 天 × 6 时段的可预约状态矩阵:
- 表头:时段列 + 周一至周日(周末粉色文字)
- 6 个时段:10:00/12:00/14:00/16:00/18:00/20:00
- 每格 30vp 高,bkCalText(di, si) 返回状态文本
- 配色:空闲(深紫灰背景+青色1vp边框+青色文字)/已约(深粉填充+白色文字)/过期(极深黑背景+暗紫灰文字)
bkCalStatus(day, slot) 纯函数通过数学公式模拟真实预约分布:
- day<=1 && slot<=1:返回2(过期,周一10:00和12:00已过期)
- (day7+slot3)%5===0:返回1(已约,约20%时段已约)
- (day+slot)%4===0:返回1(已约,约25%时段已约)
- 其他:返回0(空闲,约55%时段空闲)
周历时段表让用户一目了然地查看整周可预约时段,直接点击空闲时段触发预约。周末粉色文字突出周末高峰时段。
10.3 本月预约量柱状图
本月预约量 7 日柱状图展示一周预约分布:
- 周一32/周二48/周三41/周四66/周五88/周六102(金色峰值)/周日79
- 柱子高度 count/bkWeekMax()*92 动态计算,bkWeekMax() 返回102
- 峰值周六102单金色提示
- 周六柱子金色,其他粉色
- 底部对齐,柱子从底部向上生长
柱状图直观展示一周预约量分布,周六峰值102单符合周末发廊高峰规律,金色峰值突出最忙时段,帮助用户避开高峰选择空闲时段。
10.4 我的预约列表
我的预约列表展示 7 条预约,每条包含:
- 左侧:发型名(白色加粗14vp)+ 💈技师 + 🕐时间(暗紫灰10vp)
- 右侧:状态标签(待到店/已确认/待支付/进行中 青色半透明背景;已完成 深紫灰背景)+ ¥费用(粉色14vp加粗)+ 改约(金色描边按钮)+ 取消(粉色描边按钮)
改约按钮触发 onEditBook 回调,打开底部抽屉式修改预约弹框,回显原发型/技师/时间。取消按钮触发 onCancelBook 回调,打开粉色警告式取消预约弹框,显示违约金说明。状态标签直观展示预约进度,改约/取消双操作满足用户管理需求。
10.5 技师数据模型
每位技师(BKStylist)包含:名称(name)、头衔(title)、评分(rating,如4.9)、订单数(orders)、技能标签(skills,字符串数组)、从业年限(years)、emoji(图标)。
9 位技师覆盖多种风格:蕾娜Naena主理人非洲辫大师4.9/1862单/9年/盒辫脏辫无痕接发、Kiko首席彩辫师4.8/1543单/7年/渐变彩辫半编彩辫星空闪片、阿钉Dreadlock教父4.9/1204单/11年/雷鬼脏辫脏辫接发、Momo少女系编手4.7/1891单/5年/鱼骨辫波西米亚蜜桃粉、Tariq非洲辫正统传承4.8/976单/8年/加纳辫拧转辫玉米辫、小鹿新锐造型师4.6/651单/3年/拳击辫双色扭博、Riri明星指定假发师4.9/445单/10年/蕾丝辫假发无痕接发、BB Boy街头快编手4.5/1310单/4年/拳击辫半编彩辫、佐伊Zoe盖辫专家4.7/897单/6年/盒辫双色扭博辫。
10.6 技师天梯榜
技师天梯榜是技师榜 Tab 的核心设计,大序号布局展示 9 位技师:
- 前三名:01/02/03 金色32vp大号序号 + 金色头像边框 + 第一名金色卡片边框(border width 1)
- 其他:序号暗紫灰24vp + 粉色头像边框
- 每卡:50vp圆头像(深紫灰背景+2vp边框)+ emoji + 名称(白色加粗14vp)+ 头衔(暗紫灰9vp)+ 技能标签(青色半透明背景,横滚)+ ★评分(金色13vp加粗)+ 订单数·年限(暗紫灰9vp)+ 预约青色按钮
- 点击卡片触发 onStylist 回调,打开技师详情弹框
大序号排行是技师榜的核心视觉创新,前三名金色大号序号+金色边框突出领奖台位置,技能标签青色增加专业感,预约按钮直接引导转化。9 位技师按评分和订单综合排序,蕾娜Naena和阿钉并列4.9分,蕾娜以主理人头衔排第一。
10.7 技师评分分布
技师评分分布条展示 9 位技师的评分区间分布:
- 4 个区间:4.9-5.0铁粉级3人金色/4.7-4.8口碑级3人粉色/4.5-4.6稳健级2人青色/4.4以下潜力级1人灰色
- 每区间:区间名 + 横条(宽度 count/3*100%,区间色)+ 人数
- 横条背景极深黑,圆角5vp
评分分布条直观展示技师团队整体水平,4.9-5.0铁粉级3人占比最高(蕾娜/阿钉/Riri),说明团队整体水平较高。4.4以下潜力级1人(BB Boy 4.5),有提升空间。
10.8 技师详情弹框
技师详情弹框(招牌卡式)展示技师完整信息:
- 招牌卡头:76vp圆头像(金色3vp边框)+ emoji + 名称18vp + 头衔金色 + ★评分金色 + 订单数评价
- 技能标签:#技能 青色标签横滚
- 三数据格:累计作品粉色 / 从业年限青色 / 本月口碑金色,竖线分隔
- 代表作品横滚:16 款发型 76vp 色卡横滚,模拟技师作品集
- 指定 TA 帮我编发:粉色大按钮,点击后关闭技师详情并自动打开预约弹框(onBook 回调链式触发)
bkStylistByName(stylistName) 纯函数按技师名查询技师对象,弹框中所有技师信息通过该函数动态获取。指定TA按钮的链式触发是本弹框的交互亮点,用户查看技师详情后可直接一键预约,减少操作步骤。
第十一章 会员与色板系统
11.1 会员权益数据模型
每项会员权益(BKPerk)包含:图标(icon,emoji)、权益名(name)、描述(desc)。
8 项会员权益:🎟️黄金时段优先(周五周六晚场优先锁位)、💊养护精华礼(每次编发赠头皮精华)、🎈生日免单(生日当月送编发一次)、🧴染膏88折(全部染膏色卡会员价)、📸免费出片(店内霓虹棚写真拍摄)、🔁7天免费补辫(辫体松脱免费返修)、🚗到店泊车(商圈停车2小时报销)、🎁积分兑换(积分换染膏/饰品)。
11.2 票据式金卡
票据式金卡是会员 Tab 的核心设计,分为上下两部分:
- 上半金色卡片:👑GOLD KING 16vp深字 + NO.BK-8823-0042 卡号 + 卡内余额¥2,680 28vp深字大号 + 金卡有效期2027-08-26
- 锯齿票孔行:10 个深色小圆点(10vp×6vp,圆角4vp)均匀分布,模拟票据撕线,SpaceBetween 布局
- 下半深色卡片:🎟️10次编发次卡 + 剩余6/10次金色 + 60%粉色进度条 + 2026-02-26到期可转赠 + 立即充值青色按钮
票据式金卡设计如同夜店 VIP 入场券,金色卡片增加尊贵感,锯齿票孔行增加票据真实感,次卡进度条直观展示使用情况。上金下黑的配色对比强烈,金色部分突出会员卡身份,深色部分展示次卡实用信息。
11.3 充值档位
4 档充值档位(BKTier):体验档充¥300送¥30到账¥330、入门档充¥500送¥80到账¥580、人气档充¥1000送¥200到账¥1200、霸王档充¥2000送¥500到账¥2500。
充值弹框中档位横滚展示,每档 104vp 宽:
- 档位标签(选中金色背景深字/未选中深紫灰背景金色字)
- 金额 20vp加粗(选中金色/未选中白色)
- 赠送(选中青色/未选中暗紫灰)
- 到账金额(暗紫灰9vp)
- 选中态:金色半透明背景+1.5vp金色边框
充得多送得多的激励机制清晰可见,霸王档充2000送500(赠送比例25%)最划算,体验档充300送30(赠送比例10%)门槛最低。用户可根据消费频率选择合适档位。
11.4 支付方式
3 种支付方式可选:
- 💚微信支付
- 💳会员卡余额支付(¥2,680)
- 🏦银行卡支付
每种支付方式一行:emoji + 支付方式名 + ●/○ 选择标记,选中态青色半透明背景+●青色标记。selPay 状态变量(0/1/2)控制当前选中支付方式。微信支付作为默认首选,会员卡余额支付方便已有余额的用户,银行卡支付作为通用选项。
11.5 染膏数据模型
每款染膏(BKDye)包含:色号(code,如BK-P01)、色值(hex)、色名(name)、品牌(brand:IGORA/丝蕴PRO/Pravana)、热度(hot)。
16 色染膏覆盖多种色系:霓虹粉BK-P01 #FF4FA3 IGORA 1832热、电光青BK-C02 #38E8D8 IGORA 1650热、鎏金BK-G03 #F5C842 丝蕴PRO 1421热、脏粉紫BK-P04 #D63384 IGORA 1288热、深海青BK-C05 #1AA398 丝蕴PRO 1104热、暗夜紫BK-V06 #7B4BFF Pravana 1022热、警报红BK-R07 #FF3B3B Pravana 987热、街球蓝BK-B08 #2E6BFF IGORA 913热、日落橙BK-O09 #FF7A29 丝蕴PRO 846热、鸢尾紫BK-M10 #9B59B6 Pravana 780热、蜜桃气泡BK-P11 #FF9EC9 IGORA 735热、月光银BK-S12 #C0C0C0 丝蕴PRO 690热、荧光绿BK-G13 #2ECC71 Pravana 604热、加勒比BK-T14 #00CED1 IGORA 552热、奶白雾BK-W15 #F4EEFA 丝蕴PRO 498热、墨黑雾BK-K16 #0E0914 IGORA 455热。
11.6 色板 4 列色卡 Grid
色板 4 列色卡 Grid 展示 16 色染膏:
- 每卡 23% 宽,Flex({wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween}) 自动换行
- 44vp 纯色色块(圆角10vp)+ 🔥热度角标(白色7vp,position右上角)
- 色名(白色加粗9vp)+ 色号(暗紫灰8vp)
- 选中态:深粉背景+1.5vp金色边框
- 点击色卡触发 onPickColor(code, name) 回调,打开染膏选色弹框并回显该色
4 列色卡 Grid 直观展示 16 色染膏,纯色色块让颜色一目了然,热度角标增加潮流感和购买引导,品牌标签增加专业感。16 色覆盖粉/青/金/紫/红/蓝/橙/银/绿/黑等主流色系,满足不同用户需求。
11.7 色板流光特效
色板标题栏的流光特效是色板 Tab 的核心视觉亮点:
- 60vp×4vp 青色半透明光条(rgba(56,232,216,0.55)),圆角2vp
- translate X 从 -40 到 40 往返(shineX,1600ms Alternate,EaseInOut 曲线)
- position x:10 y:22,位于标题栏上方
- 深色卡片背景 + 🎨染膏色板标题 + 进口染膏·16色现货青色
流光特效模拟霓虹灯管内的流光效果,青色光条在深色背景上横向滑动,增加夜店氛围和科技感。1600ms 较长时长使流光变化缓慢流畅,符合色板 Tab 的"选色"沉浸感。
11.8 热门染发风格占比榜
热门染发风格横条占比榜展示 6 种风格的近 30 天占比:
- 渐变彩辫32%粉色/拳击辫26%青色/经典盒辫18%金色/雷鬼脏辫12%深粉/鱼骨辫7%深青/其他5%灰色
- 每风格:风格名 + 百分比(风格色加粗)+ 横条(百分比宽度,风格色)
- 近30天·3268单
热门风格占比榜帮助用户了解当前潮流趋势,渐变彩辫以32%占比最高成为最热门风格,拳击辫26%紧随其后。用户可根据热门榜单选择发型,也可选择小众风格展现个性。
第十二章 动态与我的系统
12.1 动态数据模型
每条动态(BKFeed)包含:ID、昵称(nick)、时间(time)、动态文字(text)、主色 c1(hex)、副色 c2(hex)、点赞数(likes)、评论数(comments)、标签(tag,发型名)。
8 条动态覆盖多种发型:霓虹小卷渐变彩辫夜店发光328赞/46评论、Dread老炮雷鬼脏辫终身指定214赞/31评论、桃子汽水蜜桃粉彩辫出片率100%486赞/72评论、球鞋收藏家Ken拳击辫配AJ街头感173赞/19评论、Vvvvivi加纳辫层次绝了259赞/28评论、鱼骨辫女孩鱼骨辫婚礼造型341赞/55评论、星空收藏家星空闪片辫夜店搭讪三次197赞/23评论、短发电波半编彩辫告别帽子152赞/17评论。
12.2 动态信息流
动态信息流是动态 Tab 的核心设计,展示 8 条用户动态:
- 每条动态卡片:深紫灰背景圆角14vp
- 头部:38vp圆头像(深紫灰背景+粉色2vp边框)+ 昵称首字粉色15vp加粗 + 昵称(白色加粗12vp)+ VIP金色标签 + 时间·到店消费(暗紫灰9vp)+ #发型标签(青色描边按钮,点击触发预约)
- 动态文字:暗紫灰12vp,2行省略
- 双色渐变色块图:左半 c1 + 右半 c2,64vp高,圆角10vp,clip(true),模拟发型双色效果
- 互动行:❤️/🤍点赞切换(likedId状态,点击切换红心/白心,点赞数+1动态计算)+ 点赞数 + 💬评论图标 + 评论数 + 同款发型>粉色跳转(点击触发预约)
动态信息流增加社区感和用户粘性,点赞切换增加互动感,同款发型跳转直接引导预约转化,双色渐变色块图模拟发型效果无需真实图片。#发型标签青色描边增加潮流感,VIP金色标签突出尊贵用户。
12.3 点赞交互
点赞交互是动态 Tab 的核心交互:
- likedId 状态变量记录当前点赞的动态ID(0表示未点赞任何)
- 点击❤️/🤍图标切换 likedId = f.id
- 显示逻辑:likedId === f.id 显示❤️红色,否则显示🤍白色
- 点赞数动态计算:f.likes + (likedId === f.id ? 1 : 0)
- 一次只能点赞一条动态(单选模式),再次点击同一条取消点赞
点赞交互增加社区互动感,红色心形和白色心形的视觉切换明确反馈点赞状态,点赞数+1的动态计算让用户看到自己的贡献。单选模式简化交互,避免复杂的多选状态管理。
12.4 我的个人卡
我的个人卡是我的 Tab 的核心设计:
- 66vp圆头像(深紫灰背景+粉色3vp边框)+ 🧶emoji
- 霓虹小卷 NEON.C 16vp白色加粗
- 标签行:👑金卡KING金色背景深字 / 📸出片达人粉色半透明背景粉色字 / 💬理发室话痨青色半透明背景青色字
个人卡的三标签增加个性化和潮流感,金卡标签突出会员身份,出片达人和理发室话痨标签增加趣味性和社区属性。粉色边框统一视觉风格,圆头像增加亲和力。
12.5 数据统计四格
数据统计四格展示用户核心数据:
- 26累计编发(粉色22vp加粗)
- 48收藏发型(青色22vp加粗)
- 8,620积分(金色22vp加粗)
- 31到店次数(深粉22vp加粗)
每格 24% 宽,深紫灰背景圆角14vp,居中对齐。四色数据对应应用四色体系(粉/青/金/深粉),直观展示用户活跃度和消费情况。累计编发26次说明用户是中度消费者,收藏发型48个说明用户关注潮流,积分8620说明用户活跃度高,到店31次说明用户忠诚度高。
12.6 设置项列表
设置项列表展示 6 项设置:
- 📍常用门店(霓虹旗舰店>)
- 💳会员卡与充值(余额¥2,680金色>,点击触发充值)
- 🧵头皮档案(油性·粗硬发质>)
- 🔗绑定手机(138****6688>)
- 🛡️隐私与账号安全(>)
- ☎️联系客服(9:00-23:00在线>)
每项:emoji + 设置名(白色13vp)+ 右侧值/箭头(暗紫灰11vp),底部1vp分割线。深紫灰背景圆角16vp。会员卡与充值项金色余额突出显示,点击直接触发充值弹框,减少操作步骤。头皮档案增加专业服务感,记录用户发质信息为后续个性化推荐做准备。
第十三章 性能优化与扩展方向
13.1 性能优化策略
- 合理使用 @State:只有需触发 UI 更新的状态用 @State,静态数据用 const,减少响应式开销。项目中约 30 个 @State 变量,均为必要的交互状态和动画属性。
- 列表渲染优化:使用 ForEach 框架自动 diff 复用,列表项固定尺寸避免布局抖动。16 发型/9 技师/7 预约/16 染膏/8 动态等列表均使用 ForEach。
- 动画性能:使用属性动画而非逐帧动画,动画属性限制在 opacity/rotate/scale/translate 等合成层属性避免重排,无限循环用 iterations:-1 零定时器。约 5 组动画全部采用此方式。灯柱旋转使用 Normal+Linear 实现无缝循环,其他使用 Alternate 实现往返呼吸/流光。
- 减少重渲染:弹框条件渲染未显示不渲染,避免 build() 中复杂计算。7 个弹框均通过 if 条件渲染。
- 资源优化:使用 emoji 作图标无需图片,渐变背景用 linearGradient 无需图片,双色渐变色块模拟发型效果无需真实发型图片,减少网络请求和内存占用。
- 纯函数封装:10 个纯函数处理数据转换和业务逻辑,无副作用易测试,相同输入永远产生相同输出,可被编译器优化。
13.2 代码规范
命名规范:组件名大驼峰(BKHomeContent)、变量函数小驼峰(activeTab)、常量大写下划线(BK_COLORS)、接口大驼峰(BKStyle)、统一 BK 前缀。文件结构:接口→全局常量→纯函数→弹框组件→Tab 组件→根入口。代码风格:2 空格缩进、语句末尾不加分号、对象字面量每行一属性。
13.3 可维护性与可扩展性
组件化拆分约 17 个独立组件职责单一。数据与视图分离(数据在 const 常量,UI 只读渲染)。逻辑与视图分离(业务逻辑封装在 10 个纯函数中)。配置化设计(Tab/分类/标签/时段/染膏/权益/档位均通过配置数据驱动)。新增发型/技师/染膏/动态只需在对应常量数组中添加数据,无需修改 UI 代码。
13.4 功能扩展方向
门面深化:实时排号系统(WebSocket推送)、门店地图导航、到店签到、等待时间预估、现场直播。
发型库深化:真实发型图片、发型3D预览、AI试发型(上传照片虚拟试戴)、发型收藏夹、发型对比、发型教程视频。
预约深化:在线支付、预约提醒推送、到店扫码核销、服务进度追踪、技师排班表、预约评价。
技师榜深化:技师详情页、技师作品集、技师评价、技师预约日历、技师直播、技师培训认证。
会员深化:会员等级体系(银卡/金卡/黑卡)、积分商城、会员专属活动、生日特权、会员日折扣、邀请好友奖励。
色板深化:染膏商品详情页、用户评价、色卡对比、肤色适配推荐、染膏库存、染膏试用装。
动态深化:动态发布、图片/视频上传、评论回复、关注/粉丝、话题标签、热门推荐、内容审核。
我的深化:头皮档案详细记录、消费历史、预约历史、收藏夹、优惠券、消息通知、个人资料编辑。
13.5 技术演进方向
短期(1-3 月):对接后端 API 替换硬编码、引入状态管理库、增加本地存储、完善错误处理、真机性能测试、支付 SDK 集成。
中期(3-6 月):引入路由管理、推送通知预约提醒、接入支付 SDK 实现在线支付和充值、性能优化懒加载、适配平板折叠屏、多语言国际化、图片上传 CDN。
长期(6-12 月):AI 试发型、发型 3D 预览、AR 增强现实试戴、智能推荐系统、直播功能、社区内容生态、跨设备协同(手机预约/平板看效果/智慧屏直播)、云存档同步。
13.6 鸿蒙生态融合
原子化服务:将预约编发/查看排号/选择发型封装为原子化服务,无需安装即用。万能卡片:提供今日预约/排号状态/热门发型/会员余额卡片,桌面实时查看。多设备协同:手机预约/平板看发型效果/手表到店提醒/智慧屏门店直播。小艺语音:语音查询预约/选择发型/提醒到店。健康数据:与华为运动健康 App 数据互通,头皮健康数据自动同步。
第十四章 编发文化与头皮护理提示
14.1 编发文化
编发是人类最古老的美发技艺之一,起源可追溯到古埃及时期,法老和贵族佩戴复杂的编发和假发。非洲大陆是编发文化的重要发源地,盒辫(Box Braids)、玉米辫(Cornrows)、加纳辫(Ghana Braids)、拧转辫(Twists)等传统编发样式在非洲文化中具有深厚的社会和文化意义,不同编发样式代表不同的部落、年龄、婚姻状况和社会地位。
20 世纪 70 年代,脏辫(Dreadlocks)通过雷鬼音乐和拉斯塔法里文化在全球流行,成为自由、反叛和精神追求的象征。90 年代,拳击辫(Boxer Braids)因运动员在比赛中佩戴而流行,兼具实用性和时尚感。2010 年代以来,彩辫(Color Braids)、渐变染(Ombre)、星空闪片辫等潮流编发样式在 Z 世代中流行,成为街头文化和夜店文化的重要组成部分。
BRAID KINGS 编发王国正是这一文化的产物:16 款发型覆盖非洲传统编发(盒辫/玉米辫/加纳辫/拧转辫)、街头潮流编发(拳击辫/半编彩辫)、脏辫文化(雷鬼脏辫/脏辫接发)、潮流彩辫(渐变彩辫/双色扭博辫/蜜桃粉彩辫/星空闪片辫)、仙系编发(鱼骨辫)、派对编发(波西米亚编发)、增发假发(无痕接发/蕾丝辫假发)。9 位技师各有专长,从主理人到新锐造型师,构成完整的编发师团队。
14.2 编发类型与特点
盒辫(Box Braids):将头发分成方形小区,每区编出三股辫,是最经典的非洲编发样式。优点是保护头发、持久度高(4-8 周)、造型多样。BRAID KINGS 经典盒辫¥458/180分钟,热度1543,是最受欢迎的发型之一。
拳击辫(Boxer Braids):将头发从额头到后脑勺编成两条紧贴头皮的辫子,因运动员在拳击比赛中佩戴而得名。优点是清爽利落、运动时不挡脸、街头感强。BRAID KINGS 拳击辫¥388/90分钟,热度1280,HOT标签。
脏辫(Dreadlocks):通过捻转和打结让头发形成绳状绺,是雷鬼文化的标志性发型。优点是个性鲜明、持久度极高(数月)、无需频繁打理。缺点是制作时间长、拆除困难。BRAID KINGS 雷鬼脏辫¥888/240分钟,大师标签,阿钉Dreadlock教父专长。
渐变彩辫(Ombre Braids):在编发中使用两种或多种颜色的假发,从发根到发梢颜色渐变。优点是色彩丰富、视觉冲击力强、适合夜店和派对。BRAID KINGS 渐变彩辫¥528/160分钟,热度1132,爆款标签,Kiko首席彩辫师专长,是今日主打发型。
鱼骨辫(Fishtail Braid):将头发分成两部分,不断从两侧取少量头发交叉编织,形成鱼骨纹理。优点是精致优雅、适合婚礼和正式场合、仙系风格。BRAID KINGS 鱼骨辫¥268/70分钟,热度1891,是热度最高的发型,爆款标签,Momo少女系编手专长。
无痕接发(Tape-in Extensions):使用胶带将假发片粘贴在真发根部,增加发量和长度。优点是自然无痕、佩戴舒适、可重复使用。BRAID KINGS 无痕接发¥1280/150分钟,上新标签,Riri明星指定假发师专长。
14.3 头皮护理提示
- 编发前护理:编发前一周加强头皮护理,使用温和洗发水清洁头皮,避免使用含硅油的护发素(会影响编发持久度)。编发当天确保头发完全干燥,湿发编发容易滋生细菌和产生异味。
- 编发后护理:编发后 3 天内不要洗头,让辫子定型。之后可每周洗头 1-2 次,使用稀释的洗发水直接涂抹头皮,用指腹轻轻按摩,不要用指甲抓挠。洗完后用毛巾轻轻按压吸干水分,用吹风机冷风彻底吹干头皮(潮湿头皮容易滋生细菌)。
- 头皮保湿:编发期间头皮容易干燥发痒,可使用头皮精华油或保湿喷雾,直接涂抹在头皮和辫子根部。BRAID KINGS 会员每次编发赠送头皮精华养护。避免使用含酒精的产品(会加重干燥)。
- 睡眠护理:睡觉时佩戴丝绸或缎面睡帽,减少辫子与枕头的摩擦,防止毛躁和断裂。也可以在枕头上铺丝绸枕套。
- 持久度维护:编发一般可维持 4-8 周,具体取决于编发类型和个人头发生长速度。新发生长超过 2cm 时建议重新编发或补辫。BRAID KINGS 会员享受 7 天免费补辫服务(辫体松脱免费返修)。
- 拆除护理:拆除编发时不要用力拉扯,先用护发素或油涂抹辫子根部,等待 10-15 分钟软化后,用手指或尖尾梳慢慢拆开。拆除后会有较多断发(正常现象,是编发期间自然脱落的头发被卡在辫子中),用宽齿梳轻轻梳理。拆除后立即进行深层护理(发膜/精油),补充编发期间流失的营养。
- 头皮健康警示:如果编发期间出现严重头皮疼痛、红肿、流脓、脱发增多等情况,应立即拆除编发并咨询皮肤科医生。编发不宜过紧(过紧会导致牵引性脱发),BRAID KINGS 技师会根据客户头皮状况调整编发松紧度。
14.4 染膏选择与染发安全
- 染膏品牌:BRAID KINGS 使用三大品牌染膏:IGORA(德国施华蔻专业线,色彩饱和度高,持久度好)、丝蕴PRO(日本专业线,温和低刺激,适合敏感头皮)、Pravana(美国专业线,色彩鲜艳,适合潮流亮色)。16 色染膏覆盖粉/青/金/紫/红/蓝/橙/银/绿/黑等主流色系。
- 肤色适配:冷色调肤色(偏白/偏青)适合冷色系染膏(电光青/街球蓝/月光银),暖色调肤色(偏黄/偏红)适合暖色系染膏(鎏金/日落橙/蜜桃气泡),中性肤色适合任意色系。BRAID KINGS 技师会根据客户肤色推荐合适颜色。
- 染发安全:染发前 48 小时进行皮肤过敏测试(将少量染膏涂抹在耳后或手臂内侧,观察是否有红肿瘙痒)。头皮有伤口、炎症、孕期、哺乳期不建议染发。染发时避免染膏直接接触头皮(编发染色一般只染发梢和假发,相对安全)。染发后使用固色洗发水和护发素,延长色彩持久度。
- 色彩持久度:红色系褪色最快(2-4 周明显褪色),蓝色系次之(4-6 周),粉色系中等(4-8 周),金色系持久度较好(6-8 周),黑色系最持久(8 周以上)。频繁洗头、高温造型、紫外线照射会加速褪色。BRAID KINGS 色板 Tab 展示每款染膏的热度,帮助用户选择流行颜色。
总结与展望
项目总结
BRAID KINGS 编发王国是一款融合夜店霓虹发廊与街头潮流的潮流编发·脏辫·彩辫工作室概念应用。设计价值:首创"夜店霓虹发廊 × 街头潮流风"视觉风格,约 5 组属性动画让界面"活"起来(霓虹呼吸/灯柱旋转/星星脉动/色板流光),7 套差异化弹框每种场景有专属交互语言,8 大 Tab 覆盖编发工作室全链路业务。技术价值:单文件 ArkTS 零依赖复制即运行,14 接口 + 约 16 常量 + 10 纯函数构建完整类型系统,数据驱动 UI 渲染,三级状态管理简单高效,发型浏览/预约管理/技师排行/会员充值/染膏选色/社区动态/个人档案核心逻辑完整实现。业务价值:覆盖了浏览发型→预约服务→选择技师→查看排行→管理会员→挑选颜色→社区互动→个人管理完整用户旅程,发型库激发消费欲望,预约系统连接线下服务,技师榜建立专业信任,会员系统实现商业化闭环,社区动态增加用户粘性。
技术亮点
- 夜店霓虹三组动画:门面横幅同时运行霓虹呼吸(opacity)+ 灯柱旋转(rotate Normal循环)+ 星星脉动(scale),营造夜店灯光秀氛围
- 灯柱无缝旋转动画:Normal 循环 + Linear 曲线实现 0→360° 无缝旋转,是全应用唯一 Normal 模式动画,6000ms 缓慢有力
- 色板流光特效:translate 动画模拟霓虹灯管流光,青色光条横向滑动,增加夜店氛围和科技感
- 票据式金卡设计:金色卡片+锯齿票孔行+次卡进度,如同夜店 VIP 入场券
- 周历时段表:7 天 × 6 时段矩阵,bkCalStatus 数学公式模拟真实预约分布,空闲/已约/过期三态配色
- 双色渐变色块模拟发型:发型库和动态卡使用 c1+c2 双色拼接色块模拟发色效果,无需真实发型图片
- 10 纯函数业务逻辑:发型价格/时长查询、技师按名查询、热门筛选、周峰值、日历状态/文本、星级、热门标签
- 技师详情链式预约:指定TA按钮点击后关闭技师详情并自动打开预约弹框,减少用户操作步骤
完整代码
// ============================================================
// BRAID KINGS 编发王国 —— 潮流编发·脏辫·彩辫工作室
// 夜店霓虹发廊 × 街头潮流风(得物App风格:预约+电商+社区)
// ArkTS 单文件
// ============================================================
// ============ 配色(夜店霓虹发廊 × 街头潮流) ============
interface BKColorPalette {
bg: string
card: string
deep: string
line: string
neonPink: string
pinkDeep: string
neonCyan: string
cyanDeep: string
gold: string
textMain: string
textSub: string
textHint: string
}
const BK_COLORS: BKColorPalette = {
bg: '#17101F',
card: '#221830',
deep: '#0E0914',
line: '#352848',
neonPink: '#FF4FA3',
pinkDeep: '#D63384',
neonCyan: '#38E8D8',
cyanDeep: '#1AA398',
gold: '#F5C842',
textMain: '#F4EEFA',
textSub: '#A794BC',
textHint: '#6B5A7E'
}
// ============ Tab 定义 ============
enum BKTab {
Home = 0,
Styles = 1,
Book = 2,
Rank = 3,
Vip = 4,
Dye = 5,
Feed = 6,
Me = 7
}
interface BKTabItem {
tab: BKTab
icon: string
label: string
}
const BK_TABS: BKTabItem[] = [
{ tab: BKTab.Home, icon: '✨', label: '门面' },
{ tab: BKTab.Styles, icon: '💇', label: '发型库' },
{ tab: BKTab.Book, icon: '📅', label: '预约' },
{ tab: BKTab.Rank, icon: '🏆', label: '技师榜' },
{ tab: BKTab.Vip, icon: '💳', label: '会员' },
{ tab: BKTab.Dye, icon: '🎨', label: '色板' },
{ tab: BKTab.Feed, icon: '📣', label: '动态' },
{ tab: BKTab.Me, icon: '👤', label: '我的' }
]
// ============ 数据接口 ============
interface BKStyle {
id: number
name: string
style: string
c1: string
c2: string
price: number
dur: number
hot: number
tag: string
}
interface BKStylist {
name: string
title: string
rating: number
orders: number
skills: string[]
years: number
emoji: string
}
interface BKBooking {
id: number
style: string
stylist: string
time: string
status: string
fee: number
}
interface BKDye {
code: string
hex: string
name: string
brand: string
hot: number
}
interface BKFeed {
id: number
nick: string
time: string
text: string
c1: string
c2: string
likes: number
comments: number
tag: string
}
interface BKDayCount {
day: string
count: number
}
interface BKPerk {
icon: string
name: string
desc: string
}
interface BKTier {
amount: number
bonus: number
label: string
}
interface BKStyleRank {
name: string
pct: number
color: string
}
interface BKRateBand {
band: string
count: number
color: string
}
// ============ 全局硬编码数据 ============
const BK_STYLES: BKStyle[] = [
{ id: 1, name: '拳击辫', style: '街头', c1: '#FF4FA3', c2: '#D63384', price: 388, dur: 90, hot: 1280, tag: 'HOT' },
{ id: 2, name: '无痕接发', style: '增发', c1: '#38E8D8', c2: '#1AA398', price: 1280, dur: 150, hot: 862, tag: '上新' },
{ id: 3, name: '经典盒辫', style: '非洲', c1: '#F5C842', c2: '#A87A1E', price: 458, dur: 180, hot: 1543, tag: 'HOT' },
{ id: 4, name: '渐变彩辫', style: '彩辫', c1: '#FF4FA3', c2: '#38E8D8', price: 528, dur: 160, hot: 1132, tag: '爆款' },
{ id: 5, name: '加纳辫', style: '非洲', c1: '#D63384', c2: '#17101F', price: 368, dur: 120, hot: 976, tag: '口碑' },
{ id: 6, name: '雷鬼脏辫', style: '脏辫', c1: '#6B5A7E', c2: '#352848', price: 888, dur: 240, hot: 651, tag: '大师' },
{ id: 7, name: '半编彩辫', style: '混搭', c1: '#FF4FA3', c2: '#F5C842', price: 298, dur: 80, hot: 1408, tag: 'HOT' },
{ id: 8, name: '玉米辫', style: '非洲', c1: '#F5C842', c2: '#D63384', price: 328, dur: 100, hot: 1204, tag: '经典' },
{ id: 9, name: '鱼骨辫', style: '仙系', c1: '#38E8D8', c2: '#F4EEFA', price: 268, dur: 70, hot: 1891, tag: '爆款' },
{ id: 10, name: '波西米亚编发', style: '派对', c1: '#F5C842', c2: '#FF4FA3', price: 318, dur: 90, hot: 1024, tag: '出片' },
{ id: 11, name: '蕾丝辫假发', style: '假发', c1: '#17101F', c2: '#FF4FA3', price: 980, dur: 60, hot: 445, tag: '定制' },
{ id: 12, name: '脏辫接发', style: '脏辫', c1: '#352848', c2: '#1AA398', price: 1080, dur: 200, hot: 530, tag: '进阶' },
{ id: 13, name: '拧转辫', style: '非洲', c1: '#1AA398', c2: '#0E0914', price: 348, dur: 130, hot: 897, tag: '口碑' },
{ id: 14, name: '双色扭博辫', style: '彩辫', c1: '#FF4FA3', c2: '#1AA398', price: 458, dur: 150, hot: 768, tag: '上新' },
{ id: 15, name: '蜜桃粉彩辫', style: '彩辫', c1: '#FF9EC9', c2: '#FF4FA3', price: 388, dur: 85, hot: 1310, tag: '少女' },
{ id: 16, name: '星空闪片辫', style: '派对', c1: '#6B5A7E', c2: '#38E8D8', price: 498, dur: 95, hot: 1093, tag: '夜店' }
]
const BK_STYLISTS: BKStylist[] = [
{ name: '蕾娜 Naena', title: '主理人·非洲辫大师', rating: 4.9, orders: 1862, skills: ['盒辫', '脏辫', '无痕接发'], years: 9, emoji: '👑' },
{ name: 'Kiko', title: '首席彩辫师', rating: 4.8, orders: 1543, skills: ['渐变彩辫', '半编彩辫', '星空闪片'], years: 7, emoji: '🦄' },
{ name: '阿钉', title: 'Dreadlock 教父', rating: 4.9, orders: 1204, skills: ['雷鬼脏辫', '脏辫接发'], years: 11, emoji: '🎸' },
{ name: 'Momo', title: '少女系编手', rating: 4.7, orders: 1891, skills: ['鱼骨辫', '波西米亚', '蜜桃粉'], years: 5, emoji: '🍑' },
{ name: 'Tariq', title: '非洲辫正统传承', rating: 4.8, orders: 976, skills: ['加纳辫', '拧转辫', '玉米辫'], years: 8, emoji: '🌍' },
{ name: '小鹿', title: '新锐造型师', rating: 4.6, orders: 651, skills: ['拳击辫', '双色扭博'], years: 3, emoji: '🦌' },
{ name: 'Riri', title: '明星指定假发师', rating: 4.9, orders: 445, skills: ['蕾丝辫假发', '无痕接发'], years: 10, emoji: '💎' },
{ name: 'BB Boy', title: '街头快编手', rating: 4.5, orders: 1310, skills: ['拳击辫', '半编彩辫'], years: 4, emoji: '⚡' },
{ name: '佐伊 Zoe', title: '盖辫专家', rating: 4.7, orders: 897, skills: ['盒辫', '双色扭博辫'], years: 6, emoji: '🌀' }
]
const BK_BOOKINGS: BKBooking[] = [
{ id: 1, style: '渐变彩辫', stylist: 'Kiko', time: '今天 19:00', status: '待到店', fee: 528 },
{ id: 2, style: '经典盒辫', stylist: '蕾娜 Naena', time: '明天 14:00', status: '已确认', fee: 458 },
{ id: 3, style: '雷鬼脏辫', stylist: '阿钉', time: '周六 10:00', status: '待支付', fee: 888 },
{ id: 4, style: '鱼骨辫', stylist: 'Momo', time: '08-30 15:30', status: '进行中', fee: 268 },
{ id: 5, style: '无痕接发', stylist: 'Riri', time: '09-02 11:00', status: '待到店', fee: 1280 },
{ id: 6, style: '星空闪片辫', stylist: 'Kiko', time: '08-20 20:00', status: '已完成', fee: 498 },
{ id: 7, style: '拳击辫', stylist: 'BB Boy', time: '08-12 18:30', status: '已完成', fee: 388 }
]
const BK_DYES: BKDye[] = [
{ code: 'BK-P01', hex: '#FF4FA3', name: '霓虹粉', brand: 'IGORA', hot: 1832 },
{ code: 'BK-C02', hex: '#38E8D8', name: '电光青', brand: 'IGORA', hot: 1650 },
{ code: 'BK-G03', hex: '#F5C842', name: '鎏金', brand: '丝蕴PRO', hot: 1421 },
{ code: 'BK-P04', hex: '#D63384', name: '脏粉紫', brand: 'IGORA', hot: 1288 },
{ code: 'BK-C05', hex: '#1AA398', name: '深海青', brand: '丝蕴PRO', hot: 1104 },
{ code: 'BK-V06', hex: '#7B4BFF', name: '暗夜紫', brand: 'Pravana', hot: 1022 },
{ code: 'BK-R07', hex: '#FF3B3B', name: '警报红', brand: 'Pravana', hot: 987 },
{ code: 'BK-B08', hex: '#2E6BFF', name: '街球蓝', brand: 'IGORA', hot: 913 },
{ code: 'BK-O09', hex: '#FF7A29', name: '日落橙', brand: '丝蕴PRO', hot: 846 },
{ code: 'BK-M10', hex: '#9B59B6', name: '鸢尾紫', brand: 'Pravana', hot: 780 },
{ code: 'BK-P11', hex: '#FF9EC9', name: '蜜桃气泡', brand: 'IGORA', hot: 735 },
{ code: 'BK-S12', hex: '#C0C0C0', name: '月光银', brand: '丝蕴PRO', hot: 690 },
{ code: 'BK-G13', hex: '#2ECC71', name: '荧光绿', brand: 'Pravana', hot: 604 },
{ code: 'BK-T14', hex: '#00CED1', name: '加勒比', brand: 'IGORA', hot: 552 },
{ code: 'BK-W15', hex: '#F4EEFA', name: '奶白雾', brand: '丝蕴PRO', hot: 498 },
{ code: 'BK-K16', hex: '#0E0914', name: '墨黑雾', brand: 'IGORA', hot: 455 }
]
const BK_FEEDS: BKFeed[] = [
{ id: 1, nick: '霓虹小卷', time: '12分钟前', text: 'Kiko 给我编的渐变彩辫也太好看了!!夜店灯光下直接发光', c1: '#FF4FA3', c2: '#38E8D8', likes: 328, comments: 46, tag: '渐变彩辫' },
{ id: 2, nick: 'Dread 老炮', time: '1小时前', text: '阿钉老师的手艺没话说,脏辫第三次回购,终身指定。', c1: '#6B5A7E', c2: '#352848', likes: 214, comments: 31, tag: '雷鬼脏辫' },
{ id: 3, nick: '桃子汽水', time: '3小时前', text: '蜜桃粉彩辫 + 半编,出片率100%,闺蜜已经预约同款', c1: '#FF9EC9', c2: '#FF4FA3', likes: 486, comments: 72, tag: '蜜桃粉彩辫' },
{ id: 4, nick: '球鞋收藏家Ken', time: '5小时前', text: '拳击辫配AJ,街头感直接拉满,BB Boy 20分钟神速出活。', c1: '#FF4FA3', c2: '#D63384', likes: 173, comments: 19, tag: '拳击辫' },
{ id: 5, nick: 'Vvvvivi', time: '8小时前', text: 'Tariq 的加纳辫层次绝了,头皮毫无拉扯感,睡觉都不疼。', c1: '#D63384', c2: '#17101F', likes: 259, comments: 28, tag: '加纳辫' },
{ id: 6, nick: '鱼骨辫女孩', time: '昨天', text: 'Momo 手真的巧,鱼骨辫细到发丝,婚礼造型就选它了。', c1: '#38E8D8', c2: '#F4EEFA', likes: 341, comments: 55, tag: '鱼骨辫' },
{ id: 7, nick: '星空收藏家', time: '昨天', text: '星空闪片辫在夜店被搭讪三次,闪片掉得也不多,好评!', c1: '#6B5A7E', c2: '#38E8D8', likes: 197, comments: 23, tag: '星空闪片辫' },
{ id: 8, nick: '短发电波', time: '2天前', text: '短头发也能编彩辫!半编彩辫实测,从此告别帽子。', c1: '#FF4FA3', c2: '#F5C842', likes: 152, comments: 17, tag: '半编彩辫' }
]
const BK_WEEK: BKDayCount[] = [
{ day: '周一', count: 32 },
{ day: '周二', count: 48 },
{ day: '周三', count: 41 },
{ day: '周四', count: 66 },
{ day: '周五', count: 88 },
{ day: '周六', count: 102 },
{ day: '周日', count: 79 }
]
const BK_PERKS: BKPerk[] = [
{ icon: '🎟️', name: '黄金时段优先', desc: '周五/周六晚场优先锁位' },
{ icon: '💊', name: '养护精华礼', desc: '每次编发赠头皮精华' },
{ icon: '🎈', name: '生日免单', desc: '生日当月送编发一次' },
{ icon: '🧴', name: '染膏88折', desc: '全部染膏色卡会员价' },
{ icon: '📸', name: '免费出片', desc: '店内霓虹棚写真拍摄' },
{ icon: '🔁', name: '7天免费补辫', desc: '辫体松脱免费返修' },
{ icon: '🚗', name: '到店泊车', desc: '商圈停车2小时报销' },
{ icon: '🎁', name: '积分兑换', desc: '积分换染膏/饰品' }
]
const BK_TIERS: BKTier[] = [
{ amount: 300, bonus: 30, label: '体验档' },
{ amount: 500, bonus: 80, label: '入门档' },
{ amount: 1000, bonus: 200, label: '人气档' },
{ amount: 2000, bonus: 500, label: '霸王档' }
]
const BK_RANK_BARS: BKStyleRank[] = [
{ name: '渐变彩辫', pct: 32, color: '#FF4FA3' },
{ name: '拳击辫', pct: 26, color: '#38E8D8' },
{ name: '经典盒辫', pct: 18, color: '#F5C842' },
{ name: '雷鬼脏辫', pct: 12, color: '#D63384' },
{ name: '鱼骨辫', pct: 7, color: '#1AA398' },
{ name: '其他风格', pct: 5, color: '#6B5A7E' }
]
const BK_RATE_BANDS: BKRateBand[] = [
{ band: '4.9-5.0 铁粉级', count: 3, color: '#F5C842' },
{ band: '4.7-4.8 口碑级', count: 3, color: '#FF4FA3' },
{ band: '4.5-4.6 稳健级', count: 2, color: '#38E8D8' },
{ band: '4.4 以下 潜力级', count: 1, color: '#6B5A7E' }
]
const BK_STYLE_CATS: string[] = ['全部', '街头', '非洲', '彩辫', '脏辫', '派对', '仙系', '混搭', '增发', '假发']
const BK_FLOW_TAGS: string[] = ['🔥 拳击辫', '🌈 渐变彩辫', '🧶 盒辫', '💀 脏辫', '✨ 星空闪片', '🍑 蜜桃粉', '🌀 拧转辫', '⚡ 快编通道']
const BK_CAL_DAYS: string[] = ['一', '二', '三', '四', '五', '六', '日']
const BK_CAL_SLOTS: string[] = ['10:00', '12:00', '14:00', '16:00', '18:00', '20:00']
const BK_TIME_CHIPS: string[] = ['11:00', '12:30', '14:00', '15:30', '17:00', '18:30', '20:00', '21:30']
// ============ 全局纯函数 ============
function bkStylePrice(name: string): number {
for (let i = 0; i < BK_STYLES.length; i++) {
if (BK_STYLES[i].name === name) {
return BK_STYLES[i].price
}
}
return 388
}
function bkStyleDur(name: string): number {
for (let i = 0; i < BK_STYLES.length; i++) {
if (BK_STYLES[i].name === name) {
return BK_STYLES[i].dur
}
}
return 90
}
function bkStylistByName(name: string): BKStylist {
for (let i = 0; i < BK_STYLISTS.length; i++) {
if (BK_STYLISTS[i].name === name) {
return BK_STYLISTS[i]
}
}
return BK_STYLISTS[0]
}
function bkHotStyles(): BKStyle[] {
let r: BKStyle[] = []
for (let i = 0; i < BK_STYLES.length; i++) {
if (BK_STYLES[i].hot > 1000) {
r.push(BK_STYLES[i])
}
}
return r
}
function bkWeekMax(): number {
return 102
}
function bkCalStatus(day: number, slot: number): number {
if (day <= 1 && slot <= 1) {
return 2
}
if ((day * 7 + slot * 3) % 5 === 0) {
return 1
}
if ((day + slot) % 4 === 0) {
return 1
}
return 0
}
function bkCalText(day: number, slot: number): string {
if (bkCalStatus(day, slot) === 1) {
return '已约'
}
if (bkCalStatus(day, slot) === 2) {
return '过期'
}
return '空闲'
}
function bkStars(rating: number): string {
return '★★★★★'
}
function bkHotLabel(hot: number): string {
if (hot >= 1500) {
return '🔥🔥🔥'
}
if (hot >= 1000) {
return '🔥🔥'
}
return '🔥'
}
// ============ Tab1 门面:今日主打横幅 + 排号 + 风格chips + 技师横滚 + 时段格 ============
@Component
struct BKHomeContent {
@State neonOp: number = 0.45
@State pillarAngle: number = 0
@State starScale: number = 0.8
onBook: () => void = () => {}
onStylist: (name: string) => void = (name: string) => {}
aboutToAppear() {
this.getUIContext().animateTo({
duration: 1400,
iterations: -1,
playMode: PlayMode.Alternate,
curve: Curve.EaseInOut
}, () => { this.neonOp = 1.0 })
this.getUIContext().animateTo({
duration: 6000,
iterations: -1,
playMode: PlayMode.Normal,
curve: Curve.Linear
}, () => { this.pillarAngle = 360 })
this.getUIContext().animateTo({
duration: 900,
iterations: -1,
playMode: PlayMode.Alternate,
curve: Curve.EaseOut
}, () => { this.starScale = 1.25 })
}
build() {
Scroll() {
Column() {
// ===== 今日主打大横幅(粉青渐变 + 霓虹呼吸 + 旋转灯柱 + 星星脉动) =====
Stack({ alignContent: Alignment.TopEnd }) {
Column() {
Text('TODAY · 今日主打').fontSize(10).fontColor('#17101F')
.backgroundColor('#F5C842').borderRadius(8)
.padding({ left: 8, right: 8, top: 3, bottom: 3 })
Row() {
Stack() {
Column()
.width(10).height(86)
.backgroundColor('#F5C842').borderRadius(5)
.rotate({ angle: this.pillarAngle })
Column()
.width(4).height(86)
.backgroundColor('#FF4FA3').borderRadius(2)
.rotate({ angle: this.pillarAngle })
}
.width(40).height(92)
Column() {
Text('渐变彩辫 OMBRE').fontSize(24).fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF').opacity(this.neonOp)
Text('霓虹粉 → 电光青 · 全场人气王 NO.1').fontSize(11).fontColor('#221830')
.margin({ top: 4 })
Row() {
Text('🔥 1132').fontSize(11).fontColor('#0E0914')
.backgroundColor('rgba(244,238,250,0.75)').borderRadius(10)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
Text('¥528 / 160分钟').fontSize(12).fontWeight(FontWeight.Bold).fontColor('#17101F')
.margin({ left: 8 })
}
.margin({ top: 10 })
Text('立即预约').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
.backgroundColor('#17101F').borderRadius(18)
.padding({ left: 26, right: 26, top: 8, bottom: 8 })
.margin({ top: 12 })
.onClick(() => { this.onBook() })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.padding({ left: 8 })
}
.width('100%').margin({ top: 10 })
}
.alignItems(HorizontalAlign.Start)
.padding(16)
.linearGradient({
angle: 135,
colors: [['#FF4FA3', 0.0], ['#D63384', 0.55], ['#38E8D8', 1.0]]
})
.width('100%').borderRadius(18)
Text('✦').fontSize(22).fontColor('#F5C842')
.scale({ x: this.starScale, y: this.starScale })
.position({ x: 18, y: 10 })
Text('✧').fontSize(14).fontColor('#FFFFFF')
.scale({ x: this.starScale, y: this.starScale })
.position({ x: 46, y: 88 })
}
.width('100%')
.margin({ top: 10 })
// ===== 排号进度条 =====
Column() {
Row() {
Text('🎫 现场排号').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('叫号中').fontSize(10).fontColor('#38E8D8')
.backgroundColor('rgba(56,232,216,0.12)').borderRadius(8)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
}
.width('100%')
Row() {
Text('BK-042').fontSize(26).fontWeight(FontWeight.Bold).fontColor('#FF4FA3')
Column() {
Text('当前叫号').fontSize(9).fontColor('#6B5A7E')
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 6 })
Column().layoutWeight(1)
Text('前方 3 位').fontSize(13).fontWeight(FontWeight.Bold).fontColor('#F5C842')
Text('预计等待 45 分钟').fontSize(10).fontColor('#A794BC').margin({ left: 10 })
}
.width('100%').margin({ top: 10 })
Row() {
Column()
.width(10).height(10).borderRadius(5)
.backgroundColor('#FF4FA3')
Column()
.layoutWeight(1).height(4)
.backgroundColor('#352848').borderRadius(2)
Column()
.width(10).height(10).borderRadius(5)
.backgroundColor('#FF4FA3')
Column()
.layoutWeight(1).height(4)
.backgroundColor('#352848').borderRadius(2)
Column()
.width(10).height(10).borderRadius(5)
.backgroundColor('#FF4FA3')
Column()
.layoutWeight(1).height(4)
.backgroundColor('#352848').borderRadius(2)
Column()
.width(10).height(10).borderRadius(5)
.backgroundColor('#38E8D8')
}
.width('100%').alignItems(VerticalAlign.Center)
.margin({ top: 12 })
Row() {
Text('已叫 39').fontSize(9).fontColor('#6B5A7E')
Column().layoutWeight(1)
Text('您在 42 号').fontSize(9).fontColor('#A794BC')
Column().layoutWeight(1)
Text('共 45 号').fontSize(9).fontColor('#6B5A7E')
}
.width('100%').margin({ top: 6 })
}
.width('100%').padding(14)
.backgroundColor('#221830').borderRadius(16)
.margin({ top: 12 })
// ===== 潮流风格 chips 横滚 =====
Column() {
Row() {
Text('🌊 潮流风格').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('全部 >').fontSize(11).fontColor('#FF4FA3')
.onClick(() => { this.onBook() })
}
.width('100%').padding({ left: 2, right: 2 })
Scroll() {
Row({ space: 8 }) {
ForEach(BK_FLOW_TAGS, (t: string) => {
Text(t)
.fontSize(12).fontColor('#F4EEFA')
.backgroundColor('#221830').borderRadius(16)
.border({ width: 1, color: '#352848' })
.padding({ left: 14, right: 14, top: 8, bottom: 8 })
.onClick(() => { this.onBook() })
})
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%').margin({ top: 10 })
}
.width('100%').margin({ top: 14 })
// ===== 技师圆头像横滚 =====
Column() {
Row() {
Text('💈 在店技师').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('9 位在岗').fontSize(11).fontColor('#38E8D8')
}
.width('100%').padding({ left: 2, right: 2 })
Scroll() {
Row({ space: 14 }) {
ForEach(BK_STYLISTS, (s: BKStylist) => {
Column() {
Stack() {
Column()
.width(58).height(58).borderRadius(29)
.backgroundColor('#352848')
.border({ width: 2, color: '#FF4FA3' })
Text(s.emoji).fontSize(26)
}
.width(58).height(58)
Text(s.name).fontSize(11).fontColor('#F4EEFA')
.fontWeight(FontWeight.Bold).maxLines(1)
.margin({ top: 6 })
Text(s.title).fontSize(8).fontColor('#6B5A7E').maxLines(1)
Text('★ ' + s.rating.toString()).fontSize(10).fontColor('#F5C842')
.margin({ top: 2 })
}
.width(70)
.alignItems(HorizontalAlign.Center)
.onClick(() => { this.onStylist(s.name) })
})
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%').margin({ top: 10 })
}
.width('100%').margin({ top: 14 })
// ===== 今日时段状态格 =====
Column() {
Row() {
Text('🕘 今日时段').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Row() {
Column().width(8).height(8).borderRadius(2).backgroundColor('#FF4FA3')
Text('已满').fontSize(9).fontColor('#A794BC').margin({ left: 4 })
Column().width(8).height(8).borderRadius(2).backgroundColor('#221830')
.border({ width: 1, color: '#38E8D8' }).margin({ left: 10 })
Text('可约').fontSize(9).fontColor('#A794BC').margin({ left: 4 })
}
}
.width('100%')
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(BK_CAL_SLOTS, (slot: string, si: number) => {
Column() {
Text(slot).fontSize(11).fontColor('#F4EEFA').fontWeight(FontWeight.Bold)
Text(bkCalText(0, si)).fontSize(9)
.fontColor(bkCalStatus(0, si) === 0 ? '#38E8D8' : (bkCalStatus(0, si) === 1 ? '#FF4FA3' : '#6B5A7E'))
.margin({ top: 3 })
}
.width('30%')
.padding({ top: 10, bottom: 10 })
.borderRadius(12)
.backgroundColor(bkCalStatus(0, si) === 2 ? '#0E0914' : '#221830')
.border({
width: 1,
color: bkCalStatus(0, si) === 0 ? '#38E8D8' : '#352848'
})
.margin({ top: 8 })
.alignItems(HorizontalAlign.Center)
.onClick(() => { this.onBook() })
})
}
.width('100%').margin({ top: 4 })
}
.width('100%').padding(14)
.backgroundColor('#17101F').borderRadius(16)
.border({ width: 1, color: '#352848' })
.margin({ top: 14 })
}
.padding({ left: 12, right: 12, bottom: 16 })
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.width('100%').height('100%')
.backgroundColor('#17101F')
.edgeEffect(EdgeEffect.Spring)
}
}
// ============ Tab2 发型库:三列色卡瀑布 ============
@Component
struct BKStylesContent {
@State selCat: string = '全部'
onBook: (styleName: string) => void = (styleName: string) => {}
onAddStyle: () => void = () => {}
build() {
Column() {
// 顶部小分类 chips
Scroll() {
Row({ space: 8 }) {
ForEach(BK_STYLE_CATS, (c: string) => {
Text(c)
.fontSize(11)
.fontColor(this.selCat === c ? '#17101F' : '#A794BC')
.backgroundColor(this.selCat === c ? '#38E8D8' : '#221830')
.borderRadius(14)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.onClick(() => { this.selCat = c })
})
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
.padding({ left: 12, right: 12 })
Scroll() {
Column() {
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(BK_STYLES, (s: BKStyle) => {
Column() {
Stack({ alignContent: Alignment.BottomStart }) {
Column() {
Column()
.width('100%').height(52)
.backgroundColor(s.c1)
Column()
.width('100%').height(30)
.backgroundColor(s.c2)
}
.width('100%').borderRadius(12)
.clip(true)
Text(s.tag)
.fontSize(8).fontWeight(FontWeight.Bold).fontColor('#17101F')
.backgroundColor('#F5C842').borderRadius(7)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.margin(6)
}
.width('100%').height(82)
.borderRadius(12)
Text(s.name).fontSize(12).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
.maxLines(1).margin({ top: 6 })
Text(s.style + ' · ' + bkHotLabel(s.hot) + ' ' + s.hot.toString()).fontSize(9).fontColor('#6B5A7E')
.maxLines(1).margin({ top: 2 })
Row() {
Text('¥' + s.price.toString()).fontSize(13).fontWeight(FontWeight.Bold).fontColor('#FF4FA3')
Text(s.dur.toString() + '分钟').fontSize(9).fontColor('#A794BC').margin({ left: 6 })
}
.width('100%').margin({ top: 4 })
}
.width('31.5%')
.padding(8)
.backgroundColor('#221830').borderRadius(14)
.margin({ top: 8 })
.onClick(() => { this.onBook(s.name) })
})
}
.width('100%')
.padding({ left: 12, right: 12 })
Row() {
Text('➕ 没找到心仪的?收录新发型').fontSize(12).fontColor('#38E8D8')
.backgroundColor('rgba(56,232,216,0.1)').borderRadius(16)
.border({ width: 1, color: '#1AA398' })
.padding({ left: 16, right: 16, top: 9, bottom: 9 })
.margin({ top: 4, bottom: 16 })
.onClick(() => { this.onAddStyle() })
}
.width('100%').justifyContent(FlexAlign.Center)
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.layoutWeight(1)
.margin({ top: 10 })
}
.width('100%').height('100%')
.backgroundColor('#17101F')
}
}
// ============ Tab3 预约:周历时段表 + 7日柱状图 + 我的预约列表 ============
@Component
struct BKBookContent {
onEditBook: (styleName: string, stylist: string, time: string) => void = (styleName: string, stylist: string, time: string) => {}
onCancelBook: (styleName: string) => void = (styleName: string) => {}
build() {
Scroll() {
Column() {
// ===== 周历时段表 =====
Column() {
Row() {
Text('📅 本周时段表').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('08/24 - 08/30').fontSize(10).fontColor('#6B5A7E')
}
.width('100%')
Row() {
Text('时段').fontSize(9).fontColor('#6B5A7E')
.width(52).textAlign(TextAlign.Center)
ForEach(BK_CAL_DAYS, (d: string) => {
Text('周' + d)
.fontSize(10).fontColor(d === '六' || d === '日' ? '#FF4FA3' : '#A794BC')
.layoutWeight(1).textAlign(TextAlign.Center)
})
}
.width('100%').margin({ top: 12 })
ForEach(BK_CAL_SLOTS, (slot: string, si: number) => {
Row() {
Text(slot)
.fontSize(9).fontColor('#A794BC')
.width(52).textAlign(TextAlign.Center)
ForEach(BK_CAL_DAYS, (d: string, di: number) => {
Column() {
Text(bkCalText(di, si))
.fontSize(8)
.fontColor(bkCalStatus(di, si) === 0 ? '#38E8D8' : (bkCalStatus(di, si) === 1 ? '#F4EEFA' : '#6B5A7E'))
}
.layoutWeight(1).height(30)
.margin({ left: 2, right: 2, top: 3 })
.borderRadius(6)
.backgroundColor(bkCalStatus(di, si) === 1 ? '#D63384' : (bkCalStatus(di, si) === 2 ? '#0E0914' : '#221830'))
.border({
width: 1,
color: bkCalStatus(di, si) === 0 ? '#1AA398' : '#352848'
})
.justifyContent(FlexAlign.Center)
})
}
.width('100%')
})
}
.width('100%').padding(12)
.backgroundColor('#221830').borderRadius(16)
.margin({ top: 12 })
// ===== 本月预约量 7 日柱状图 =====
Column() {
Row() {
Text('📊 本月预约量').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('峰值 周六 102 单').fontSize(10).fontColor('#F5C842')
}
.width('100%')
Row() {
ForEach(BK_WEEK, (w: BKDayCount) => {
Column() {
Column()
.width('46%')
.height(w.count / bkWeekMax() * 92)
.borderRadius(5)
.backgroundColor(w.count === bkWeekMax() ? '#F5C842' : '#FF4FA3')
Text(w.count.toString())
.fontSize(8).fontColor('#A794BC').margin({ top: 4 })
Text(w.day).fontSize(9)
.fontColor(w.day === '周六' || w.day === '周日' ? '#38E8D8' : '#6B5A7E')
.margin({ top: 1 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.End)
})
}
.width('100%').height(140)
.alignItems(VerticalAlign.Bottom)
.margin({ top: 10 })
}
.width('100%').padding(12)
.backgroundColor('#17101F').borderRadius(16)
.border({ width: 1, color: '#352848' })
.margin({ top: 12 })
// ===== 我的预约列表 =====
Row() {
Text('🎫 我的预约').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('共 ' + BK_BOOKINGS.length.toString() + ' 条').fontSize(10).fontColor('#6B5A7E')
}
.width('100%').padding({ left: 2, right: 2, top: 16, bottom: 6 })
Column() {
ForEach(BK_BOOKINGS, (b: BKBooking) => {
Row() {
Column() {
Text(b.style).fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Row() {
Text('💈 ' + b.stylist).fontSize(10).fontColor('#A794BC')
Text('🕐 ' + b.time).fontSize(10).fontColor('#A794BC').margin({ left: 10 })
}
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Column() {
Text(b.status).fontSize(9)
.fontColor(b.status === '已完成' ? '#6B5A7E' : '#38E8D8')
.backgroundColor(b.status === '已完成' ? '#221830' : 'rgba(56,232,216,0.12)')
.borderRadius(8)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
Text('¥' + b.fee.toString()).fontSize(14).fontWeight(FontWeight.Bold).fontColor('#FF4FA3')
.margin({ top: 6 })
Row({ space: 6 }) {
Text('改约')
.fontSize(10).fontColor('#F5C842')
.border({ width: 1, color: '#F5C842' })
.borderRadius(10)
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
.onClick(() => { this.onEditBook(b.style, b.stylist, b.time) })
Text('取消')
.fontSize(10).fontColor('#FF4FA3')
.border({ width: 1, color: '#FF4FA3' })
.borderRadius(10)
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
.onClick(() => { this.onCancelBook(b.style) })
}
.margin({ top: 6 })
}
.alignItems(HorizontalAlign.End)
}
.width('100%')
.padding(12)
.backgroundColor('#221830').borderRadius(14)
.margin({ top: 8 })
})
}
.width('100%')
}
.padding({ left: 12, right: 12, bottom: 16 })
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.width('100%').height('100%')
.backgroundColor('#17101F')
.edgeEffect(EdgeEffect.Spring)
}
}
// ============ Tab4 技师榜:大序号排行 + 评分分布 ============
@Component
struct BKRankContent {
onStylist: (name: string) => void = (name: string) => {}
onBook: () => void = () => {}
build() {
Scroll() {
Column() {
// 榜单说明条
Row() {
Text('🏆 BRAID KINGS 编发师天梯榜').fontSize(13).fontWeight(FontWeight.Bold).fontColor('#17101F')
Column().layoutWeight(1)
Text('每月1日刷新').fontSize(9).fontColor('#4A3325')
}
.width('100%').padding({ left: 12, right: 12, top: 10, bottom: 10 })
.backgroundColor('#F5C842').borderRadius(12)
.margin({ top: 12 })
// 排行榜大序号布局
Column() {
ForEach(BK_STYLISTS, (s: BKStylist, idx: number) => {
Row() {
Text(idx < 3 ? '0' + (idx + 1).toString() : (idx + 1).toString())
.fontSize(idx < 3 ? 32 : 24)
.fontWeight(FontWeight.Bold)
.fontColor(idx < 3 ? '#F5C842' : '#352848')
.width(56)
Stack() {
Column()
.width(50).height(50).borderRadius(25)
.backgroundColor('#352848')
.border({ width: 2, color: idx < 3 ? '#F5C842' : '#FF4FA3' })
Text(s.emoji).fontSize(22)
}
.width(50).height(50)
Column() {
Text(s.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Text(s.title).fontSize(9).fontColor('#6B5A7E').margin({ top: 2 })
Row({ space: 4 }) {
ForEach(s.skills, (sk: string) => {
Text(sk)
.fontSize(8).fontColor('#38E8D8')
.backgroundColor('rgba(56,232,216,0.1)').borderRadius(7)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
})
}
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.padding({ left: 10 })
Column() {
Row() {
Text('★').fontSize(11).fontColor('#F5C842')
Text(s.rating.toString()).fontSize(13).fontWeight(FontWeight.Bold).fontColor('#F5C842')
}
Text(s.orders.toString() + '单 · ' + s.years.toString() + '年')
.fontSize(9).fontColor('#6B5A7E').margin({ top: 2 })
Text('预约')
.fontSize(11).fontColor('#17101F')
.backgroundColor('#38E8D8').borderRadius(11)
.padding({ left: 14, right: 14, top: 4, bottom: 4 })
.margin({ top: 6 })
.onClick(() => { this.onBook() })
}
.alignItems(HorizontalAlign.End)
}
.width('100%')
.padding(12)
.backgroundColor('#221830').borderRadius(14)
.border({ width: idx === 0 ? 1 : 0, color: '#F5C842' })
.margin({ top: 8 })
.onClick(() => { this.onStylist(s.name) })
})
}
.width('100%')
// 技师评分分布条
Column() {
Row() {
Text('📉 技师评分分布').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('共 9 位在岗').fontSize(10).fontColor('#6B5A7E')
}
.width('100%')
Column() {
ForEach(BK_RATE_BANDS, (r: BKRateBand) => {
Row() {
Text(r.band).fontSize(10).fontColor('#A794BC').width(96)
Row() {
Column()
.height(10)
.width(r.count / 3 * 100 + '%')
.borderRadius(5)
.backgroundColor(r.color)
}
.layoutWeight(1).height(10)
.backgroundColor('#0E0914').borderRadius(5)
.margin({ left: 8, right: 8 })
Text(r.count.toString() + '人').fontSize(10).fontColor('#F4EEFA').width(28)
}
.width('100%').margin({ top: 8 })
})
}
.width('100%').margin({ top: 4 })
}
.width('100%').padding(12)
.backgroundColor('#17101F').borderRadius(16)
.border({ width: 1, color: '#352848' })
.margin({ top: 12 })
}
.padding({ left: 12, right: 12, bottom: 16 })
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.width('100%').height('100%')
.backgroundColor('#17101F')
.edgeEffect(EdgeEffect.Spring)
}
}
// ============ Tab5 会员:票据式会员卡 + 次卡进度 + 权益 2x2 ============
@Component
struct BKVipContent {
onRecharge: () => void = () => {}
build() {
Scroll() {
Column() {
// ===== 票据式金卡 =====
Column() {
Column() {
Row() {
Text('👑 GOLD KING').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#17101F')
Column().layoutWeight(1)
Text('NO. BK-8823-0042').fontSize(10).fontColor('#4A3325')
}
.width('100%')
Row() {
Column() {
Text('卡内余额').fontSize(9).fontColor('#4A3325')
Text('¥ 2,680').fontSize(28).fontWeight(FontWeight.Bold).fontColor('#17101F')
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
Column().layoutWeight(1)
Column() {
Text('金卡有效期').fontSize(9).fontColor('#4A3325')
Text('2027-08-26').fontSize(12).fontWeight(FontWeight.Bold).fontColor('#17101F')
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.End)
}
.width('100%').margin({ top: 14 })
}
.width('100%').padding(16)
.backgroundColor('#F5C842').borderRadius(16)
// 锯齿票孔行(色点行)
Row() {
ForEach([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], (n: number) => {
Column()
.width(10).height(6).borderRadius(4)
.backgroundColor('#17101F')
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({ left: 6, right: 6 })
Column() {
Row() {
Text('🎟️ 10次编发次卡').fontSize(13).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('剩余 6 / 10 次').fontSize(10).fontColor('#F5C842')
}
.width('100%')
Row() {
Column()
.width('60%').height(8).borderRadius(4)
.backgroundColor('#FF4FA3')
Column()
.layoutWeight(1).height(8).borderRadius(4)
.backgroundColor('#352848')
}
.width('100%').margin({ top: 10 })
Row() {
Text('2026-02-26 到期 · 可转赠好友').fontSize(9).fontColor('#6B5A7E')
Column().layoutWeight(1)
Text('立即充值').fontSize(11).fontColor('#17101F')
.backgroundColor('#38E8D8').borderRadius(13)
.padding({ left: 16, right: 16, top: 5, bottom: 5 })
.onClick(() => { this.onRecharge() })
}
.width('100%').margin({ top: 10 })
}
.width('100%').padding(14)
.backgroundColor('#221830')
.border({
width: { top: 2 },
color: { top: '#F5C842' }
})
}
.width('100%')
.borderRadius(16)
.margin({ top: 12 })
.clip(true)
// ===== 权益 2x2 Grid =====
Row() {
Text('🎁 金卡专属权益').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('8 项已解锁').fontSize(10).fontColor('#6B5A7E')
}
.width('100%').padding({ left: 2, right: 2, top: 16, bottom: 6 })
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(BK_PERKS, (p: BKPerk) => {
Row() {
Text(p.icon).fontSize(24)
Column() {
Text(p.name).fontSize(12).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Text(p.desc).fontSize(9).fontColor('#6B5A7E').margin({ top: 2 }).maxLines(1)
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.padding({ left: 10 })
}
.width('48%')
.padding(12)
.backgroundColor('#221830').borderRadius(14)
.margin({ top: 8 })
.onClick(() => { this.onRecharge() })
})
}
.width('100%')
.padding({ left: 2, right: 2 })
}
.padding({ left: 12, right: 12, bottom: 16 })
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.width('100%').height('100%')
.backgroundColor('#17101F')
.edgeEffect(EdgeEffect.Spring)
}
}
// ============ Tab6 色板:4列染膏色卡 + 热门风格横条榜(流光特效) ============
@Component
struct BKColorContent {
@State shineX: number = -40
@State selCode: string = 'BK-P01'
onPickColor: (code: string, name: string) => void = (code: string, name: string) => {}
aboutToAppear() {
this.getUIContext().animateTo({
duration: 1600,
iterations: -1,
playMode: PlayMode.Alternate,
curve: Curve.EaseInOut
}, () => { this.shineX = 40 })
}
build() {
Scroll() {
Column() {
// 色板标题 + 流光
Stack({ }) {
Column()
.width('100%').padding({ top: 14, bottom: 14, left: 14, right: 14 })
.backgroundColor('#221830').borderRadius(14)
Column()
.width(60).height(4).borderRadius(2)
.backgroundColor('rgba(56,232,216,0.55)')
.translate({ x: this.shineX })
.position({ x: 10, y: 22 })
Row() {
Text('🎨 染膏色板').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('进口染膏 · 16 色现货').fontSize(10).fontColor('#38E8D8')
}
.width('100%').padding({ left: 14, right: 14 })
}
.width('100%').margin({ top: 12 })
// 4 列色块 Grid
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(BK_DYES, (d: BKDye) => {
Column() {
Stack({ alignContent: Alignment.TopEnd }) {
Column()
.width('100%').height(52)
.backgroundColor(d.hex).borderRadius(10)
Text('🔥' + d.hot.toString())
.fontSize(7).fontColor('#F4EEFA')
.margin(5)
}
.width('100%')
Text(d.name).fontSize(11).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
.margin({ top: 5 }).maxLines(1)
Text(d.code).fontSize(8).fontColor('#6B5A7E')
Text(d.brand).fontSize(8).fontColor('#38E8D8').margin({ top: 1 })
}
.width('23.5%')
.padding(6)
.backgroundColor(this.selCode === d.code ? '#D63384' : '#221830')
.borderRadius(12)
.margin({ top: 8 })
.onClick(() => {
this.selCode = d.code
this.onPickColor(d.code, d.name)
})
})
}
.width('100%')
.padding({ left: 2, right: 2 })
// 热门染发风格横条占比榜
Column() {
Row() {
Text('📶 热门染发风格占比').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('近30天 · 3268 单').fontSize(10).fontColor('#6B5A7E')
}
.width('100%')
Column() {
ForEach(BK_RANK_BARS, (r: BKStyleRank) => {
Column() {
Row() {
Text(r.name).fontSize(11).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text(r.pct.toString() + '%').fontSize(11).fontWeight(FontWeight.Bold).fontColor(r.color)
}
.width('100%')
Row() {
Column()
.width(r.pct + '%').height(8)
.borderRadius(4).backgroundColor(r.color)
Column().layoutWeight(1)
}
.width('100%').margin({ top: 5 })
}
.width('100%').margin({ top: 10 })
})
}
.width('100%').margin({ top: 2 })
}
.width('100%').padding(12)
.backgroundColor('#221830').borderRadius(16)
.margin({ top: 16 })
}
.padding({ left: 12, right: 12, bottom: 16 })
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.width('100%').height('100%')
.backgroundColor('#17101F')
.edgeEffect(EdgeEffect.Spring)
}
}
// ============ Tab7 动态:社区信息流 ============
@Component
struct BKFeedContent {
@State likedId: number = 0
onBook: (styleName: string) => void = (styleName: string) => {}
build() {
Scroll() {
Column() {
Row() {
Text('📣 编发社区').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('今日 128 条新动态').fontSize(10).fontColor('#6B5A7E')
}
.width('100%').padding({ left: 2, right: 2, top: 12, bottom: 6 })
Column() {
ForEach(BK_FEEDS, (f: BKFeed) => {
Column() {
Row() {
Stack() {
Column()
.width(38).height(38).borderRadius(19)
.backgroundColor('#352848')
.border({ width: 2, color: '#FF4FA3' })
Text(f.nick.substring(0, 1)).fontSize(15).fontWeight(FontWeight.Bold).fontColor('#FF4FA3')
}
.width(38).height(38)
Column() {
Row() {
Text(f.nick).fontSize(12).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Text('VIP').fontSize(7).fontColor('#17101F')
.backgroundColor('#F5C842').borderRadius(5)
.padding({ left: 5, right: 5, top: 1, bottom: 1 })
.margin({ left: 6 })
}
Text(f.time + ' · 到店消费').fontSize(9).fontColor('#6B5A7E').margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.padding({ left: 10 })
Text('#' + f.tag).fontSize(9).fontColor('#38E8D8')
.border({ width: 1, color: '#1AA398' })
.borderRadius(9)
.padding({ left: 8, right: 8, top: 3, bottom: 3 })
.onClick(() => { this.onBook(f.tag) })
}
.width('100%')
Text(f.text).fontSize(12).fontColor('#A794BC')
.maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ top: 8 })
Row() {
Stack() {
Column()
.width('100%').height(64)
.backgroundColor(f.c1).borderRadius(10)
Column()
.width('46%').height(64)
.backgroundColor(f.c2)
.borderRadius({ topLeft: 0, topRight: 10, bottomLeft: 10, bottomRight: 10 })
}
.width('100%').height(64)
.borderRadius(10).clip(true)
}
.width('100%').margin({ top: 8 })
Row() {
Text(this.likedId === f.id ? '❤️' : '🤍').fontSize(14)
.onClick(() => { this.likedId = f.id })
Text((f.likes + (this.likedId === f.id ? 1 : 0)).toString())
.fontSize(10).fontColor('#A794BC').margin({ left: 4 })
Text('💬').fontSize(13).margin({ left: 18 })
Text(f.comments.toString() + ' 条评论')
.fontSize(10).fontColor('#A794BC').margin({ left: 4 })
Column().layoutWeight(1)
Text('同款发型 >').fontSize(10).fontColor('#FF4FA3')
.onClick(() => { this.onBook(f.tag) })
}
.width('100%').margin({ top: 8 })
}
.width('100%')
.padding(12)
.backgroundColor('#221830').borderRadius(14)
.margin({ top: 8 })
})
}
.width('100%')
}
.padding({ left: 12, right: 12, bottom: 16 })
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.width('100%').height('100%')
.backgroundColor('#17101F')
.edgeEffect(EdgeEffect.Spring)
}
}
// ============ Tab8 我的:个人卡 + 数据四格 + 设置项 ============
@Component
struct BKProfileContent {
onRecharge: () => void = () => {}
build() {
Scroll() {
Column() {
// 个人卡
Row() {
Stack() {
Column()
.width(66).height(66).borderRadius(33)
.backgroundColor('#352848')
.border({ width: 3, color: '#FF4FA3' })
Text('🧶').fontSize(30)
}
.width(66).height(66)
Column() {
Row() {
Text('霓虹小卷 NEON.C').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
}
Row({ space: 6 }) {
Text('👑 金卡 KING').fontSize(8).fontColor('#17101F')
.backgroundColor('#F5C842').borderRadius(7)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
Text('📸 出片达人').fontSize(8).fontColor('#FF4FA3')
.backgroundColor('rgba(255,79,163,0.12)').borderRadius(7)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
Text('💬 理发室话痨').fontSize(8).fontColor('#38E8D8')
.backgroundColor('rgba(56,232,216,0.12)').borderRadius(7)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
}
.margin({ top: 6 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.padding({ left: 12 })
}
.width('100%').padding(16)
.backgroundColor('#221830').borderRadius(16)
.margin({ top: 12 })
// 数据统计四格
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
Column() {
Text('26').fontSize(22).fontWeight(FontWeight.Bold).fontColor('#FF4FA3')
Text('累计编发').fontSize(10).fontColor('#6B5A7E').margin({ top: 3 })
}
.width('24%').padding({ top: 14, bottom: 14 })
.backgroundColor('#221830').borderRadius(14)
.margin({ top: 10 })
.alignItems(HorizontalAlign.Center)
Column() {
Text('48').fontSize(22).fontWeight(FontWeight.Bold).fontColor('#38E8D8')
Text('收藏发型').fontSize(10).fontColor('#6B5A7E').margin({ top: 3 })
}
.width('24%').padding({ top: 14, bottom: 14 })
.backgroundColor('#221830').borderRadius(14)
.margin({ top: 10 })
.alignItems(HorizontalAlign.Center)
Column() {
Text('8,620').fontSize(22).fontWeight(FontWeight.Bold).fontColor('#F5C842')
Text('积分').fontSize(10).fontColor('#6B5A7E').margin({ top: 3 })
}
.width('24%').padding({ top: 14, bottom: 14 })
.backgroundColor('#221830').borderRadius(14)
.margin({ top: 10 })
.alignItems(HorizontalAlign.Center)
Column() {
Text('31').fontSize(22).fontWeight(FontWeight.Bold).fontColor('#D63384')
Text('到店次数').fontSize(10).fontColor('#6B5A7E').margin({ top: 3 })
}
.width('24%').padding({ top: 14, bottom: 14 })
.backgroundColor('#221830').borderRadius(14)
.margin({ top: 10 })
.alignItems(HorizontalAlign.Center)
}
.width('100%')
// 设置项列表
Column() {
Row() {
Text('📍').fontSize(16)
Text('常用门店').fontSize(13).fontColor('#F4EEFA').margin({ left: 10 })
Column().layoutWeight(1)
Text('霓虹旗舰店 >').fontSize(11).fontColor('#6B5A7E')
}
.width('100%').padding({ top: 14, bottom: 14 })
.border({ width: { bottom: 1 }, color: '#352848' })
Row() {
Text('💳').fontSize(16)
Text('会员卡与充值').fontSize(13).fontColor('#F4EEFA').margin({ left: 10 })
Column().layoutWeight(1)
Text('余额 ¥2,680 >').fontSize(11).fontColor('#F5C842')
}
.width('100%').padding({ top: 14, bottom: 14 })
.border({ width: { bottom: 1 }, color: '#352848' })
.onClick(() => { this.onRecharge() })
Row() {
Text('🧵').fontSize(16)
Text('头皮档案').fontSize(13).fontColor('#F4EEFA').margin({ left: 10 })
Column().layoutWeight(1)
Text('油性 · 粗硬发质 >').fontSize(11).fontColor('#6B5A7E')
}
.width('100%').padding({ top: 14, bottom: 14 })
.border({ width: { bottom: 1 }, color: '#352848' })
Row() {
Text('🔗').fontSize(16)
Text('绑定手机').fontSize(13).fontColor('#F4EEFA').margin({ left: 10 })
Column().layoutWeight(1)
Text('138****6688 >').fontSize(11).fontColor('#6B5A7E')
}
.width('100%').padding({ top: 14, bottom: 14 })
.border({ width: { bottom: 1 }, color: '#352848' })
Row() {
Text('🛡️').fontSize(16)
Text('隐私与账号安全').fontSize(13).fontColor('#F4EEFA').margin({ left: 10 })
Column().layoutWeight(1)
Text('>').fontSize(11).fontColor('#6B5A7E')
}
.width('100%').padding({ top: 14, bottom: 14 })
.border({ width: { bottom: 1 }, color: '#352848' })
Row() {
Text('☎️').fontSize(16)
Text('联系客服').fontSize(13).fontColor('#F4EEFA').margin({ left: 10 })
Column().layoutWeight(1)
Text('9:00 - 23:00 在线 >').fontSize(11).fontColor('#6B5A7E')
}
.width('100%').padding({ top: 14, bottom: 14 })
}
.width('100%').padding({ left: 16, right: 16 })
.backgroundColor('#221830').borderRadius(16)
.margin({ top: 12 })
}
.padding({ left: 12, right: 12, bottom: 16 })
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.width('100%').height('100%')
.backgroundColor('#17101F')
.edgeEffect(EdgeEffect.Spring)
}
}
// ============ 弹框表单1:预约编发(多区块:发型宫格 + 技师chips + 时段chips + 备注 + 合计) ============
@Component
struct BKBookForm {
@State selStyle: string = '渐变彩辫'
@State selStylist: string = 'Kiko'
@State selTime: string = '19:00'
@State note: string = ''
onConfirm: () => void = () => {}
onClose: () => void = () => {}
build() {
Column() {
Row() {
Text('💇 预约编发').fontSize(17).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('✕').fontSize(17).fontColor('#A794BC')
.onClick(() => { this.onClose() })
}
.width('100%').padding({ left: 16, right: 16, top: 16, bottom: 8 })
Scroll() {
Column() {
Text('① 选发型(金边选中)').fontSize(11).fontColor('#A794BC').width('100%')
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(BK_STYLES, (s: BKStyle) => {
Row() {
Column()
.width(30).height(30).borderRadius(8)
.backgroundColor(s.c1)
.border({ width: 1, color: s.c2 })
Column() {
Text(s.name).fontSize(11).fontColor('#F4EEFA').maxLines(1)
Text('¥' + s.price.toString()).fontSize(9).fontColor('#FF4FA3').margin({ top: 1 })
}
.alignItems(HorizontalAlign.Start)
.padding({ left: 8 })
}
.width('48%')
.padding(8)
.backgroundColor(this.selStyle === s.name ? 'rgba(245,200,66,0.12)' : '#17101F')
.borderRadius(12)
.border({ width: this.selStyle === s.name ? 1.5 : 1, color: this.selStyle === s.name ? '#F5C842' : '#352848' })
.margin({ top: 6 })
.onClick(() => { this.selStyle = s.name })
})
}
.width('100%')
Text('② 选技师').fontSize(11).fontColor('#A794BC').width('100%').margin({ top: 12 })
Scroll() {
Row({ space: 8 }) {
ForEach(BK_STYLISTS, (s: BKStylist) => {
Text(s.name)
.fontSize(11)
.fontColor(this.selStylist === s.name ? '#17101F' : '#A794BC')
.backgroundColor(this.selStylist === s.name ? '#FF4FA3' : '#221830')
.borderRadius(13)
.padding({ left: 12, right: 12, top: 7, bottom: 7 })
.onClick(() => { this.selStylist = s.name })
})
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%').margin({ top: 8 })
Text('③ 选时段').fontSize(11).fontColor('#A794BC').width('100%').margin({ top: 12 })
Scroll() {
Row({ space: 8 }) {
ForEach(BK_TIME_CHIPS, (t: string) => {
Text(t)
.fontSize(11)
.fontColor(this.selTime === t ? '#17101F' : '#38E8D8')
.backgroundColor(this.selTime === t ? '#38E8D8' : '#221830')
.borderRadius(13)
.padding({ left: 12, right: 12, top: 7, bottom: 7 })
.onClick(() => { this.selTime = t })
})
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%').margin({ top: 8 })
Text('④ 备注(发质 / 忌讳 / 想要的效果)').fontSize(11).fontColor('#A794BC').width('100%').margin({ top: 12 })
TextArea({ placeholder: '如:细软发质,想要蓬松感,别编太紧...' })
.placeholderColor('#6B5A7E').fontSize(12).width('100%').height(64)
.backgroundColor('#17101F').borderRadius(10)
.margin({ top: 6 })
.onChange((v: string) => { this.note = v })
}
.padding({ left: 16, right: 16, bottom: 8 })
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.layoutWeight(1)
.constraintSize({ maxHeight: '48%' })
Row() {
Column() {
Text('合计').fontSize(9).fontColor('#6B5A7E')
Text('¥' + (bkStylePrice(this.selStyle) + 20).toString())
.fontSize(19).fontWeight(FontWeight.Bold).fontColor('#F5C842')
}
.alignItems(HorizontalAlign.Start)
Column().layoutWeight(1)
Text('取消').fontSize(13).fontColor('#A794BC')
.backgroundColor('#221830').borderRadius(18)
.padding({ left: 20, right: 20, top: 9, bottom: 9 })
.onClick(() => { this.onClose() })
Text('确认预约 ' + this.selTime)
.fontSize(13).fontColor('#17101F')
.backgroundColor('#38E8D8').borderRadius(18)
.padding({ left: 18, right: 18, top: 9, bottom: 9 })
.margin({ left: 10 })
.onClick(() => { this.onConfirm() })
}
.width('100%')
.padding({ left: 16, right: 16, top: 10, bottom: 16 })
}
.width('100%')
.backgroundColor('#221830').borderRadius(18)
}
}
// ============ 弹框表单2:新增收藏发型(输入式) ============
@Component
struct BKAddStyleForm {
@State name: string = ''
@State selCat: string = '街头'
@State price: string = ''
@State dur: string = ''
@State desc: string = ''
onConfirm: () => void = () => {}
onClose: () => void = () => {}
build() {
Column() {
Row() {
Text('➕ 新增收藏发型').fontSize(17).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('✕').fontSize(17).fontColor('#A794BC')
.onClick(() => { this.onClose() })
}
.width('100%').padding({ left: 16, right: 16, top: 16, bottom: 8 })
Text('发型名称').fontSize(11).fontColor('#A794BC').width('100%').margin({ top: 6 })
TextInput({ placeholder: '如:玫瑰荆棘辫' })
.placeholderColor('#6B5A7E').fontSize(13).width('100%')
.backgroundColor('#17101F').borderRadius(10)
.margin({ top: 6 })
.onChange((v: string) => { this.name = v })
Text('风格分类').fontSize(11).fontColor('#A794BC').width('100%').margin({ top: 12 })
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(BK_STYLE_CATS, (c: string) => {
Text(c)
.fontSize(11)
.fontColor(this.selCat === c ? '#17101F' : '#A794BC')
.backgroundColor(this.selCat === c ? '#FF4FA3' : '#221830')
.borderRadius(13)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.margin({ right: 8, top: 8 })
.onClick(() => { this.selCat = c })
})
}
.width('100%')
Row() {
Column() {
Text('参考价格(元)').fontSize(11).fontColor('#A794BC')
TextInput({ placeholder: '如:388' })
.placeholderColor('#6B5A7E').fontSize(13).width('100%')
.backgroundColor('#17101F').borderRadius(10)
.margin({ top: 6 })
.onChange((v: string) => { this.price = v })
}
.layoutWeight(1)
Column() {
Text('时长(分钟)').fontSize(11).fontColor('#A794BC')
TextInput({ placeholder: '如:90' })
.placeholderColor('#6B5A7E').fontSize(13).width('100%')
.backgroundColor('#17101F').borderRadius(10)
.margin({ top: 6 })
.onChange((v: string) => { this.dur = v })
}
.layoutWeight(1)
.padding({ left: 10 })
}
.width('100%').margin({ top: 12 })
Text('描述').fontSize(11).fontColor('#A794BC').width('100%').margin({ top: 12 })
TextArea({ placeholder: '想要的编法、配色、参考图链接...' })
.placeholderColor('#6B5A7E').fontSize(12).width('100%').height(64)
.backgroundColor('#17101F').borderRadius(10)
.margin({ top: 6 })
.onChange((v: string) => { this.desc = v })
Row() {
Text('放弃').fontSize(13).fontColor('#A794BC')
.backgroundColor('#221830').borderRadius(18)
.padding({ left: 22, right: 22, top: 9, bottom: 9 })
.onClick(() => { this.onClose() })
Text('收藏到我的发型库')
.fontSize(13).fontColor('#17101F')
.backgroundColor('#F5C842').borderRadius(18)
.padding({ left: 18, right: 18, top: 9, bottom: 9 })
.margin({ left: 10 })
.onClick(() => { this.onConfirm() })
}
.width('100%').justifyContent(FlexAlign.Center)
.padding({ top: 16, bottom: 16 })
}
.width('100%')
.backgroundColor('#221830').borderRadius(18)
}
}
// ============ 弹框表单3:修改预约(底部抽屉式) ============
@Component
struct BKEditBookForm {
bookStyle: string = '渐变彩辫'
bookStylist: string = 'Kiko'
bookTime: string = '今天 19:00'
@State hourIdx: number = 19
@State pickStylist: string = 'Kiko'
@State note: string = ''
onSave: () => void = () => {}
onClose: () => void = () => {}
aboutToAppear() {
this.pickStylist = this.bookStylist
}
build() {
Column() {
Row() {
Column()
.width(40).height(4).borderRadius(2)
.backgroundColor('#352848')
}
.width('100%').justifyContent(FlexAlign.Center)
.padding({ top: 10, bottom: 10 })
Row() {
Column() {
Text('✏️ 修改预约').fontSize(17).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Text(this.bookStyle + ' · ' + this.bookTime).fontSize(10).fontColor('#6B5A7E').margin({ top: 3 })
}
.alignItems(HorizontalAlign.Start)
Column().layoutWeight(1)
Text('✕').fontSize(17).fontColor('#A794BC')
.onClick(() => { this.onClose() })
}
.width('100%').padding({ left: 16, right: 16, bottom: 10 })
Text('① 改时段(点钟 stepper)').fontSize(11).fontColor('#A794BC').width('100%').margin({ top: 4 })
.padding({ left: 16, right: 16 })
Column() {
Row() {
Text('−').fontSize(20).fontColor('#F4EEFA')
.width(36).height(36).borderRadius(18)
.backgroundColor('#221830').textAlign(TextAlign.Center)
.onClick(() => {
if (this.hourIdx > 10) {
this.hourIdx = this.hourIdx - 1
}
})
Text(this.hourIdx.toString() + ' : 00')
.fontSize(22).fontWeight(FontWeight.Bold).fontColor('#38E8D8')
.margin({ left: 22, right: 22 })
Text('+').fontSize(18).fontColor('#F4EEFA')
.width(36).height(36).borderRadius(18)
.backgroundColor('#221830').textAlign(TextAlign.Center)
.onClick(() => {
if (this.hourIdx < 21) {
this.hourIdx = this.hourIdx + 1
}
})
Column().layoutWeight(1)
Text(this.hourIdx.toString() + ' 点到店')
.fontSize(11).fontColor('#A794BC')
}
.width('100%').margin({ top: 10 })
}
.width('100%')
.padding({ left: 16, right: 16 })
Text('② 换技师(单选行)').fontSize(11).fontColor('#A794BC').width('100%').margin({ top: 14 })
.padding({ left: 16, right: 16 })
Column() {
ForEach(BK_STYLISTS, (s: BKStylist) => {
Row() {
Text(s.emoji).fontSize(20)
Column() {
Text(s.name).fontSize(12).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Text(s.title + ' · ★' + s.rating.toString()).fontSize(9).fontColor('#6B5A7E').margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.padding({ left: 10 })
Text(this.pickStylist === s.name ? '● 已选' : '○ 选择')
.fontSize(11)
.fontColor(this.pickStylist === s.name ? '#38E8D8' : '#6B5A7E')
.onClick(() => { this.pickStylist = s.name })
}
.width('100%').padding({ top: 10, bottom: 10 })
.backgroundColor(this.pickStylist === s.name ? 'rgba(56,232,216,0.08)' : '#17101F')
.borderRadius(12)
.margin({ top: 6 })
})
}
.width('100%')
.padding({ left: 16, right: 16 })
.constraintSize({ maxHeight: '30%' })
Text('③ 改备注').fontSize(11).fontColor('#A794BC').width('100%').margin({ top: 12 })
.padding({ left: 16, right: 16 })
TextArea({ placeholder: '补充需求,如:想加闪片、改双色...' })
.placeholderColor('#6B5A7E').fontSize(12).width('100%').height(56)
.backgroundColor('#17101F').borderRadius(10)
.margin({ top: 6 })
.onChange((v: string) => { this.note = v })
Row() {
Text('不改了').fontSize(13).fontColor('#A794BC')
.backgroundColor('#221830').borderRadius(18)
.padding({ left: 20, right: 20, top: 9, bottom: 9 })
.onClick(() => { this.onClose() })
Text('保存修改')
.fontSize(13).fontColor('#17101F')
.backgroundColor('#FF4FA3').borderRadius(18)
.padding({ left: 20, right: 20, top: 9, bottom: 9 })
.margin({ left: 10 })
.onClick(() => { this.onSave() })
}
.width('100%').justifyContent(FlexAlign.Center)
.padding({ top: 14, bottom: 18 })
}
.width('100%')
.backgroundColor('#221830')
.borderRadius({ topLeft: 22, topRight: 22, bottomLeft: 0, bottomRight: 0 })
}
}
// ============ 弹框表单5:技师详情(招牌卡式) ============
@Component
struct BKStylistCard {
stylistName: string = '蕾娜 Naena'
onBook: () => void = () => {}
onClose: () => void = () => {}
build() {
Column() {
Row() {
Column().layoutWeight(1)
Text('✕').fontSize(17).fontColor('#A794BC')
.onClick(() => { this.onClose() })
}
.width('100%').padding({ left: 16, right: 16, top: 14 })
Row() {
Stack() {
Column()
.width(76).height(76).borderRadius(38)
.backgroundColor('#352848')
.border({ width: 3, color: '#F5C842' })
Text(bkStylistByName(this.stylistName).emoji).fontSize(34)
}
.width(76).height(76)
Column() {
Text(bkStylistByName(this.stylistName).name).fontSize(18).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Text(bkStylistByName(this.stylistName).title).fontSize(11).fontColor('#F5C842').margin({ top: 3 })
Row() {
Text(bkStars(bkStylistByName(this.stylistName).rating))
.fontSize(13).fontColor('#F5C842')
Text(bkStylistByName(this.stylistName).rating.toString())
.fontSize(13).fontWeight(FontWeight.Bold).fontColor('#F5C842')
.margin({ left: 6 })
Text('(' + bkStylistByName(this.stylistName).orders.toString() + ' 单评价)')
.fontSize(9).fontColor('#6B5A7E').margin({ left: 4 })
}
.margin({ top: 5 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.padding({ left: 14 })
}
.width('100%').padding({ left: 16, right: 16, top: 8 })
Row() {
ForEach(bkStylistByName(this.stylistName).skills, (sk: string) => {
Text('# ' + sk)
.fontSize(10).fontColor('#38E8D8')
.backgroundColor('rgba(56,232,216,0.1)').borderRadius(10)
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
.margin({ right: 8 })
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 12 })
Row() {
Column() {
Text(bkStylistByName(this.stylistName).orders.toString())
.fontSize(18).fontWeight(FontWeight.Bold).fontColor('#FF4FA3')
Text('累计作品').fontSize(9).fontColor('#6B5A7E').margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column()
.width(1).height(28).backgroundColor('#352848')
Column() {
Text(bkStylistByName(this.stylistName).years.toString() + ' 年')
.fontSize(18).fontWeight(FontWeight.Bold).fontColor('#38E8D8')
Text('从业年限').fontSize(9).fontColor('#6B5A7E').margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column()
.width(1).height(28).backgroundColor('#352848')
Column() {
Text('4.9').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#F5C842')
Text('本月口碑').fontSize(9).fontColor('#6B5A7E').margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
}
.width('100%').margin({ top: 14 })
Text(' 代表作品').fontSize(11).fontColor('#A794BC').width('100%').margin({ top: 14 })
Scroll() {
Row({ space: 8 }) {
ForEach(BK_STYLES, (s: BKStyle) => {
Column() {
Column()
.width(76).height(50).borderRadius(10)
.backgroundColor(s.c1)
Text(s.name).fontSize(9).fontColor('#A794BC').margin({ top: 4 }).maxLines(1)
}
.width(76)
})
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%').margin({ top: 8 })
Text('指定 TA 帮我编发')
.fontSize(14).fontWeight(FontWeight.Bold).fontColor('#17101F')
.backgroundColor('#FF4FA3').borderRadius(20)
.padding({ left: 40, right: 40, top: 11, bottom: 11 })
.margin({ top: 16, bottom: 18 })
.onClick(() => { this.onBook() })
}
.width('100%')
.backgroundColor('#221830').borderRadius(18)
.alignItems(HorizontalAlign.Center)
}
}
// ============ 弹框表单6:染膏选色(色卡宫格) ============
@Component
struct BKColorPickForm {
enterCode: string = 'BK-P01'
@State selIdx: number = 0
onConfirm: () => void = () => {}
onClose: () => void = () => {}
aboutToAppear() {
for (let i = 0; i < BK_DYES.length; i++) {
if (BK_DYES[i].code === this.enterCode) {
this.selIdx = i
}
}
}
build() {
Column() {
Row() {
Text('🎨 染膏选色').fontSize(17).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('✕').fontSize(17).fontColor('#A794BC')
.onClick(() => { this.onClose() })
}
.width('100%').padding({ left: 16, right: 16, top: 16, bottom: 8 })
Text('进口色卡 · 点选心仪颜色').fontSize(11).fontColor('#6B5A7E').width('100%')
.padding({ left: 16, right: 16 })
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(BK_DYES, (d: BKDye, di: number) => {
Column() {
Column()
.width('100%').height(44).borderRadius(10)
.backgroundColor(d.hex)
Text(d.name).fontSize(9).fontColor('#F4EEFA').margin({ top: 4 }).maxLines(1)
Text(d.code).fontSize(8).fontColor('#6B5A7E')
}
.width('23%')
.padding(5)
.backgroundColor(this.selIdx === di ? 'rgba(245,200,66,0.14)' : '#17101F')
.borderRadius(12)
.border({ width: this.selIdx === di ? 1.5 : 1, color: this.selIdx === di ? '#F5C842' : '#352848' })
.margin({ top: 8 })
.alignItems(HorizontalAlign.Center)
.onClick(() => { this.selIdx = di })
})
}
.width('100%')
.padding({ left: 16, right: 16 })
.constraintSize({ maxHeight: '40%' })
Row() {
Column()
.width(34).height(34).borderRadius(17)
.backgroundColor(BK_DYES[this.selIdx].hex)
.border({ width: 2, color: '#F5C842' })
Column() {
Text('已选:' + BK_DYES[this.selIdx].name + ' ' + BK_DYES[this.selIdx].code)
.fontSize(13).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Text(BK_DYES[this.selIdx].brand + ' · 热度 🔥' + BK_DYES[this.selIdx].hot.toString())
.fontSize(10).fontColor('#A794BC').margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.padding({ left: 10 })
}
.width('100%')
.padding({ left: 16, right: 16, top: 14 })
Row() {
Text('再看看').fontSize(13).fontColor('#A794BC')
.backgroundColor('#221830').borderRadius(18)
.padding({ left: 20, right: 20, top: 9, bottom: 9 })
.onClick(() => { this.onClose() })
Text('确认用这个色')
.fontSize(13).fontColor('#17101F')
.backgroundColor('#38E8D8').borderRadius(18)
.padding({ left: 20, right: 20, top: 9, bottom: 9 })
.margin({ left: 10 })
.onClick(() => { this.onConfirm() })
}
.width('100%').justifyContent(FlexAlign.Center)
.padding({ top: 14, bottom: 16 })
}
.width('100%')
.backgroundColor('#221830').borderRadius(18)
}
}
// ============ 弹框表单7:会员充值(票据档位式) ============
@Component
struct BKVipRechargeForm {
@State selTier: number = 500
@State selPay: number = 0
onConfirm: () => void = () => {}
onClose: () => void = () => {}
build() {
Column() {
Row() {
Text('💳 会员充值').fontSize(17).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
Column().layoutWeight(1)
Text('✕').fontSize(17).fontColor('#A794BC')
.onClick(() => { this.onClose() })
}
.width('100%').padding({ left: 16, right: 16, top: 16, bottom: 8 })
Text('选择充值档位(充得多送得多)').fontSize(11).fontColor('#A794BC').width('100%')
.padding({ left: 16, right: 16 })
Scroll() {
Row({ space: 10 }) {
ForEach(BK_TIERS, (t: BKTier) => {
Column() {
Text(t.label).fontSize(9).fontColor(this.selTier === t.amount ? '#17101F' : '#F5C842')
.backgroundColor(this.selTier === t.amount ? '#F5C842' : '#221830')
.borderRadius(8)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
Text('¥' + t.amount.toString()).fontSize(20).fontWeight(FontWeight.Bold)
.fontColor(this.selTier === t.amount ? '#F5C842' : '#F4EEFA')
.margin({ top: 6 })
Text('送 ¥' + t.bonus.toString()).fontSize(10)
.fontColor(this.selTier === t.amount ? '#38E8D8' : '#A794BC')
.margin({ top: 3 })
Text('到账 ¥' + (t.amount + t.bonus).toString())
.fontSize(9).fontColor('#6B5A7E').margin({ top: 3 })
}
.width(104)
.padding({ top: 12, bottom: 12 })
.backgroundColor(this.selTier === t.amount ? 'rgba(245,200,66,0.12)' : '#17101F')
.borderRadius(14)
.border({
width: this.selTier === t.amount ? 1.5 : 1,
color: this.selTier === t.amount ? '#F5C842' : '#352848'
})
.alignItems(HorizontalAlign.Center)
.onClick(() => { this.selTier = t.amount })
})
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%').margin({ top: 10 })
.padding({ left: 16, right: 16 })
Text('支付方式').fontSize(11).fontColor('#A794BC').width('100%').margin({ top: 14 })
.padding({ left: 16, right: 16 })
Column() {
Row() {
Text('💚').fontSize(18)
Text('微信支付').fontSize(13).fontColor('#F4EEFA').margin({ left: 10 })
Column().layoutWeight(1)
Text(this.selPay === 0 ? '●' : '○')
.fontSize(15).fontColor(this.selPay === 0 ? '#38E8D8' : '#6B5A7E')
}
.width('100%').padding({ top: 12, bottom: 12 })
.borderRadius(12)
.backgroundColor(this.selPay === 0 ? 'rgba(56,232,216,0.08)' : '#17101F')
.margin({ top: 6 })
.onClick(() => { this.selPay = 0 })
Row() {
Text('💳').fontSize(18)
Text('会员卡余额支付(¥2,680)').fontSize(13).fontColor('#F4EEFA').margin({ left: 10 })
Column().layoutWeight(1)
Text(this.selPay === 1 ? '●' : '○')
.fontSize(15).fontColor(this.selPay === 1 ? '#38E8D8' : '#6B5A7E')
}
.width('100%').padding({ top: 12, bottom: 12 })
.borderRadius(12)
.backgroundColor(this.selPay === 1 ? 'rgba(56,232,216,0.08)' : '#17101F')
.margin({ top: 6 })
.onClick(() => { this.selPay = 1 })
Row() {
Text('🏦').fontSize(18)
Text('银行卡支付').fontSize(13).fontColor('#F4EEFA').margin({ left: 10 })
Column().layoutWeight(1)
Text(this.selPay === 2 ? '●' : '○')
.fontSize(15).fontColor(this.selPay === 2 ? '#38E8D8' : '#6B5A7E')
}
.width('100%').padding({ top: 12, bottom: 12 })
.borderRadius(12)
.backgroundColor(this.selPay === 2 ? 'rgba(56,232,216,0.08)' : '#17101F')
.margin({ top: 6 })
.onClick(() => { this.selPay = 2 })
}
.width('100%')
.padding({ left: 16, right: 16 })
Row() {
Column() {
Text('实付').fontSize(9).fontColor('#6B5A7E')
Text('¥' + this.selTier.toString()).fontSize(19).fontWeight(FontWeight.Bold).fontColor('#F5C842')
}
.alignItems(HorizontalAlign.Start)
Column().layoutWeight(1)
Text('取消').fontSize(13).fontColor('#A794BC')
.backgroundColor('#221830').borderRadius(18)
.padding({ left: 20, right: 20, top: 9, bottom: 9 })
.onClick(() => { this.onClose() })
Text('立即充值')
.fontSize(13).fontColor('#17101F')
.backgroundColor('#FF4FA3').borderRadius(18)
.padding({ left: 20, right: 20, top: 9, bottom: 9 })
.margin({ left: 10 })
.onClick(() => { this.onConfirm() })
}
.width('100%')
.padding({ left: 16, right: 16, top: 14, bottom: 16 })
}
.width('100%')
.backgroundColor('#221830').borderRadius(18)
}
}
// ============ 主入口 ============
@Entry
struct BKKingsApp {
@State activeTab: BKTab = BKTab.Home
@State showBook: boolean = false
@State showAddStyle: boolean = false
@State showEditBook: boolean = false
@State showCancelBook: boolean = false
@State showStylist: boolean = false
@State showColor: boolean = false
@State showVip: boolean = false
@State bookStyleName: string = '渐变彩辫'
@State editStyleName: string = '渐变彩辫'
@State editStylistName: string = 'Kiko'
@State editTimeText: string = '今天 19:00'
@State cancelStyleName: string = '渐变彩辫'
@State stylistName: string = '蕾娜 Naena'
@State colorCode: string = 'BK-P01'
@State colorName: string = '霓虹粉'
// ===== 全屏遮罩 Builder =====
@Builder modalOverlay(onClose: () => void) {
Column()
.width('100%').height('100%')
.backgroundColor('rgba(14,9,20,0.6)')
.onClick(onClose)
}
// ===== 弹框1:预约编发 =====
@Builder bookModal() {
Column() {
this.modalOverlay(() => { this.showBook = false })
Column() {
BKBookForm({
selStyle: this.bookStyleName,
onClose: () => { this.showBook = false },
onConfirm: () => { this.showBook = false }
})
}
.width('92%')
.backgroundColor('#221830')
.borderRadius(18)
.position({ x: '4%', y: '10%' })
}
.width('100%').height('100%')
.position({ x: 0, y: 0 }).zIndex(999)
}
// ===== 弹框2:新增收藏发型 =====
@Builder addStyleModal() {
Column() {
this.modalOverlay(() => { this.showAddStyle = false })
Column() {
BKAddStyleForm({
onClose: () => { this.showAddStyle = false },
onConfirm: () => { this.showAddStyle = false }
})
}
.width('90%')
.backgroundColor('#221830')
.borderRadius(18)
.position({ x: '5%', y: '14%' })
}
.width('100%').height('100%')
.position({ x: 0, y: 0 }).zIndex(999)
}
// ===== 弹框3:修改预约(底部抽屉式) =====
@Builder editBookModal() {
Column() {
this.modalOverlay(() => { this.showEditBook = false })
Column() {
BKEditBookForm({
bookStyle: this.editStyleName,
bookStylist: this.editStylistName,
bookTime: this.editTimeText,
onClose: () => { this.showEditBook = false },
onSave: () => { this.showEditBook = false }
})
}
.width('100%')
.backgroundColor('#221830')
.position({ x: 0, y: '36%' })
.constraintSize({ maxHeight: '64%' })
}
.width('100%').height('100%')
.position({ x: 0, y: 0 }).zIndex(999)
}
// ===== 弹框4:取消预约(粉色警告式) =====
@Builder cancelBookModal() {
Column() {
this.modalOverlay(() => { this.showCancelBook = false })
Column() {
Text('⚠️').fontSize(42).margin({ top: 20 })
Text('确认取消这条预约?').fontSize(17).fontWeight(FontWeight.Bold).fontColor('#FF4FA3')
.margin({ top: 8 })
Text(this.cancelStyleName).fontSize(13).fontWeight(FontWeight.Bold).fontColor('#F4EEFA')
.margin({ top: 4 })
Column() {
Row() {
Text('违约金说明').fontSize(11).fontWeight(FontWeight.Bold).fontColor('#F5C842')
}
.width('100%')
Text('距开始不足 24 小时取消,将收取订单金额 20% 作为违约金(技师档期已锁定)。').fontSize(11).fontColor('#A794BC')
.margin({ top: 6 }).lineHeight(16)
Text('距开始超过 24 小时取消,全额退款至会员卡,积分不退。').fontSize(11).fontColor('#A794BC')
.margin({ top: 4 }).lineHeight(16)
}
.width('100%').padding(12)
.backgroundColor('rgba(255,79,163,0.1)').borderRadius(12)
.border({ width: 1, color: '#D63384' })
.margin({ top: 14 })
Row() {
Text('再想想').fontSize(13).fontColor('#A794BC')
.backgroundColor('#221830').borderRadius(18)
.padding({ left: 24, right: 24, top: 9, bottom: 9 })
.onClick(() => { this.showCancelBook = false })
Text('确认取消')
.fontSize(13).fontColor('#FFFFFF')
.backgroundColor('#FF4FA3').borderRadius(18)
.padding({ left: 24, right: 24, top: 9, bottom: 9 })
.margin({ left: 10 })
.onClick(() => { this.showCancelBook = false })
}
.width('100%').justifyContent(FlexAlign.Center)
.padding({ top: 18, bottom: 20 })
}
.width('84%')
.backgroundColor('#221830')
.borderRadius(16)
.border({ width: 1, color: '#FF4FA3' })
.alignItems(HorizontalAlign.Center)
.position({ x: '8%', y: '26%' })
}
.width('100%').height('100%')
.position({ x: 0, y: 0 }).zIndex(999)
}
// ===== 弹框5:技师详情 =====
@Builder stylistModal() {
Column() {
this.modalOverlay(() => { this.showStylist = false })
Column() {
BKStylistCard({
stylistName: this.stylistName,
onClose: () => { this.showStylist = false },
onBook: () => {
this.showStylist = false
this.showBook = true
}
})
}
.width('90%')
.backgroundColor('#221830')
.borderRadius(18)
.position({ x: '5%', y: '12%' })
}
.width('100%').height('100%')
.position({ x: 0, y: 0 }).zIndex(999)
}
// ===== 弹框6:染膏选色 =====
@Builder colorModal() {
Column() {
this.modalOverlay(() => { this.showColor = false })
Column() {
BKColorPickForm({
enterCode: this.colorCode,
onClose: () => { this.showColor = false },
onConfirm: () => { this.showColor = false }
})
}
.width('90%')
.backgroundColor('#221830')
.borderRadius(18)
.position({ x: '5%', y: '14%' })
}
.width('100%').height('100%')
.position({ x: 0, y: 0 }).zIndex(999)
}
// ===== 弹框7:会员充值 =====
@Builder vipModal() {
Column() {
this.modalOverlay(() => { this.showVip = false })
Column() {
BKVipRechargeForm({
onClose: () => { this.showVip = false },
onConfirm: () => { this.showVip = false }
})
}
.width('92%')
.backgroundColor('#221830')
.borderRadius(18)
.position({ x: '4%', y: '10%' })
}
.width('100%').height('100%')
.position({ x: 0, y: 0 }).zIndex(999)
}
// ===== 内容区分发 =====
@Builder contentArea() {
Stack() {
if (this.activeTab === BKTab.Home) {
BKHomeContent({
onBook: () => { this.showBook = true },
onStylist: (name: string) => {
this.stylistName = name
this.showStylist = true
}
})
} else if (this.activeTab === BKTab.Styles) {
BKStylesContent({
onBook: (styleName: string) => {
this.bookStyleName = styleName
this.showBook = true
},
onAddStyle: () => { this.showAddStyle = true }
})
} else if (this.activeTab === BKTab.Book) {
BKBookContent({
onEditBook: (styleName: string, stylist: string, time: string) => {
this.editStyleName = styleName
this.editStylistName = stylist
this.editTimeText = time
this.showEditBook = true
},
onCancelBook: (styleName: string) => {
this.cancelStyleName = styleName
this.showCancelBook = true
}
})
} else if (this.activeTab === BKTab.Rank) {
BKRankContent({
onStylist: (name: string) => {
this.stylistName = name
this.showStylist = true
},
onBook: () => { this.showBook = true }
})
} else if (this.activeTab === BKTab.Vip) {
BKVipContent({
onRecharge: () => { this.showVip = true }
})
} else if (this.activeTab === BKTab.Dye) {
BKColorContent({
onPickColor: (code: string, name: string) => {
this.colorCode = code
this.colorName = name
this.showColor = true
}
})
} else if (this.activeTab === BKTab.Feed) {
BKFeedContent({
onBook: (styleName: string) => {
this.bookStyleName = styleName
this.showBook = true
}
})
} else {
BKProfileContent({
onRecharge: () => { this.showVip = true }
})
}
// ===== 弹框层 =====
if (this.showBook) { this.bookModal() }
if (this.showAddStyle) { this.addStyleModal() }
if (this.showEditBook) { this.editBookModal() }
if (this.showCancelBook) { this.cancelBookModal() }
if (this.showStylist) { this.stylistModal() }
if (this.showColor) { this.colorModal() }
if (this.showVip) { this.vipModal() }
}
.width('100%').height('100%')
}
build() {
Column() {
// ===== 头部:霓虹灯管感 LOGO + 搜索 + 到号铃铛(无动画) =====
Row() {
Column() {
Row() {
Text('BRAID').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#FF4FA3')
Text('KINGS').fontSize(14).fontWeight(FontWeight.Bold).fontColor('#38E8D8')
.margin({ left: 2 })
Text('编发王国').fontSize(11).fontWeight(FontWeight.Bold).fontColor('#F5C842')
.margin({ left: 5 })
}
.alignItems(VerticalAlign.Center)
Text('NEON BRAID STUDIO · 22:00 打烊').fontSize(8).fontColor('#6B5A7E')
.margin({ top: 1 })
}
.alignItems(HorizontalAlign.Start)
.padding({ left: 12 })
Row() {
Text('🔍').fontSize(12).margin({ left: 10 })
TextInput({ placeholder: '搜发型 / 技师 / 色号' })
.placeholderColor('#6B5A7E').fontSize(11)
.backgroundColor('#0E0914').borderRadius(16)
.layoutWeight(1).height(32)
.margin({ left: 6, right: 6 })
.padding({ left: 10, right: 10 })
}
.layoutWeight(1)
.margin({ left: 10, right: 8 })
Stack() {
Text('🔔').fontSize(18)
Text('5').fontSize(9).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
.backgroundColor('#FF4FA3')
.width(14).height(14).borderRadius(7)
.textAlign(TextAlign.Center)
.position({ x: 20, y: -2 })
}
.width(30).height(24)
.margin({ right: 10 })
.onClick(() => { this.showBook = true })
}
.width('100%').height(54)
.backgroundColor('#0E0914')
.border({ width: { bottom: 1 }, color: '#352848' })
.alignItems(VerticalAlign.Center)
// ===== 内容区 Stack =====
Column() {
this.contentArea()
}
.layoutWeight(1)
.backgroundColor('#17101F')
// ===== 底部 Tab(8个一排,可点击切换) =====
Row() {
ForEach(BK_TABS, (item: BKTabItem) => {
Column() {
Text(item.icon).fontSize(16)
.opacity(this.activeTab === item.tab ? 1.0 : 0.45)
Text(item.label).fontSize(8)
.fontColor(this.activeTab === item.tab ? '#FF4FA3' : '#6B5A7E')
.fontWeight(this.activeTab === item.tab ? FontWeight.Bold : FontWeight.Normal)
.margin({ top: 1 })
if (this.activeTab === item.tab) {
Column().width(14).height(2)
.backgroundColor('#38E8D8').borderRadius(1)
.margin({ top: 2 })
} else {
Column().width(14).height(2)
.backgroundColor('#0E0914').borderRadius(1)
.margin({ top: 2 })
}
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
.padding({ top: 4, bottom: 4 })
.onClick(() => { this.activeTab = item.tab })
})
}
.width('100%')
.backgroundColor('#0E0914')
.border({ width: { top: 1 }, color: '#352848' })
.padding({ top: 2, bottom: 3 })
}
.width('100%').height('100%')
.backgroundColor('#17101F')
}
}

本文基于 BRAID KINGS 编发王国 ArkTS 单文件源码深度解析,全文约 3 万字。项目采用 HarmonyOS ArkTS 声明式 UI 框架,8 Tab + 7 弹框 + 约 5 组属性动画 + 10 个纯函数,完整实现潮流编发·脏辫·彩辫工作室核心业务。
更多推荐



所有评论(0)