引言

当编发从传统手艺进化为街头潮流符号,一场关于"夜店霓虹"与"街头潮流"的视觉革命正在发廊圈蔓延。BRAID KINGS 编发王国正是这样一款概念应用——它将拳击辫的利落、盒辫的层次、脏辫的自由、彩辫的绚烂、渐变染的梦幻,全部浓缩进一个 ArkTS 单文件之中。

不同于传统美发 App 的预约工具路线,BRAID KINGS 选择了"潮流编发·脏辫·彩辫工作室"的定位:深紫黑底色上,霓虹粉 #FF4FA3 与霓虹青 #38E8D8 对撞,鎏金 #F5C842 点缀其间,如同夜店霓虹灯在深夜发廊中闪烁。霓虹呼吸、灯柱旋转、星星脉动、流光滑动——约 5 组属性动画让整个界面如同夜店舞池的灯光秀。

8 个 Tab 覆盖完整业务链路:门面查看今日主打与现场排号、发型库浏览 16 款潮流编发、预约选择发型技师时段、技师榜查看 9 位编发师天梯、会员管理金卡与次卡、色板浏览 16 色进口染膏、动态浏览编发社区、我的管理个人档案。7 套弹框各具特色:多区块预约编发、输入式新增收藏、底部抽屉式修改预约、粉色警告式取消预约、招牌卡式技师详情、色卡宫格染膏选色、票据档位式会员充值。

本文将从项目概述、色彩系统、开发环境、架构设计、数据模型、八大 Tab、七大弹框、属性动画、门面与发型库、预约与技师榜、会员与色板、动态与我的、性能优化、总结展望共十四个维度,对 BRAID KINGS 编发王国进行深度解析。

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,如同霓虹灯管流光

这些动画共同营造夜店霓虹发廊的氛围,让静态界面"活"起来。

BRAID KINGS 色彩与视觉风格


第三章 开发环境搭建

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。

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 路径。

SDK 配置界面

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 后,可以通过以下方式运行:

  1. 模拟器运行:点击工具栏的 Device Manager,创建一个 Phone 模拟器,启动后点击 Run 按钮,应用将安装到模拟器并运行。
  2. 真机运行:连接 HarmonyOS 手机,开启开发者模式和 USB 调试,在工具栏选择设备后点击 Run。
  3. 预览器: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 中的实时预览效果。

DevEco Studio 预览器实际运行界面


第四章 项目架构与文件组织

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 套弹框各具特色:

  1. 预约编发(多区块):①选发型宫格(金边选中)+ ②选技师chips + ③选时段chips + ④备注 + 合计(发型价+20服务费)
  2. 新增收藏发型(输入式):发型名称输入 + 风格分类chips + 价格/时长输入 + 描述文本域
  3. 修改预约(底部抽屉式):顶部拖拽条 + ①改时段点钟stepper + ②换技师单选行 + ③改备注
  4. 取消预约(粉色警告式):⚠️图标 + 违约金说明(24小时内20%/超过全额退)+ 取消/确认
  5. 技师详情(招牌卡式):大圆头像 + 名称/头衔/评分 + 技能标签 + 三数据格(作品/年限/口碑)+ 代表作品横滚 + 指定TA按钮
  6. 染膏选色(色卡宫格):4列色卡宫格 + 已选颜色预览 + 确认用这个色
  7. 会员充值(票据档位式):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)

门面是首页,承担"查看今日主打→了解排号→浏览风格→选择技师→查看时段→预约"路径。页面结构:

  1. 今日主打大横幅(粉青渐变 + 霓虹呼吸 + 旋转灯柱 + 星星脉动)

    • 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)
  2. 现场排号进度条

    • 🎫现场排号 + 叫号中青标签
    • BK-042 当前叫号粉色26vp + 前方3位金色 + 预计等待45分钟
    • 4节点进度条(3粉点已叫+1青点当前)
    • 已叫39/您在42号/共45号
  3. 潮流风格 chips 横滚:🔥拳击辫/🌈渐变彩辫/🧶盒辫/💀脏辫/✨星空闪片/🍑蜜桃粉/🌀拧转辫/⚡快编通道

  4. 在店技师圆头像横滚(9 位):58vp圆头像(粉边框)+ emoji + 名称 + 头衔 + ★评分

  5. 今日时段状态格(6 时段 3 列):10:00/12:00/14:00/16:00/18:00/20:00,空闲青边框/已约粉填充/过期深灰

三组属性动画是门面的核心视觉亮点:霓虹呼吸模拟霓虹灯闪烁、灯柱旋转模拟夜店旋转灯、星星脉动模拟舞台灯光,共同营造夜店霓虹发廊的氛围。

门面Tab

6.2 发型库 Tab(BKStylesContent)

发型库是商品展示中心,承担"筛选分类→浏览发型→预约→收录新发型"路径。页面结构:

  1. 顶部小分类 chips(10 分类):全部/街头/非洲/彩辫/脏辫/派对/仙系/混搭/增发/假发,选中青色背景深字

  2. 三列色卡瀑布流(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
  3. 收录新发型入口:青色描边按钮"➕没找到心仪的?收录新发型"

发型库的三列色卡瀑布流是核心设计,双色渐变色块模拟发色效果,无需真实发型图片即可展示丰富的视觉效果,标签角标增加潮流感和购买引导。

6.3 预约 Tab(BKBookContent)

预约是服务预约中心,承担"查看周历→了解预约量→管理我的预约(改约/取消)"路径。页面结构:

  1. 周历时段表(7 天 × 6 时段矩阵)

    • 表头:时段 + 周一至周日(周末粉色)
    • 每格:bkCalText 返回空闲/已约/过期
    • 配色:空闲青边框深字/已约粉填充白字/过期深灰
    • bkCalStatus 数学公式模拟真实分布
  2. 本月预约量 7 日柱状图

    • 周一32/周二48/周三41/周四66/周五88/周六102(金色峰值)/周日79
    • 峰值周六102单金色提示
    • 柱子高度 count/102*92 动态计算
  3. 我的预约列表(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 纯函数动态计算状态,空闲/已约/过期三态配色直观展示可预约时段。

预约Tab

6.4 技师榜 Tab(BKRankContent)

技师榜是技师排行中心,承担"查看天梯榜→了解评分分布→预约技师"路径。页面结构:

  1. 榜单说明条:🏆BRAID KINGS编发师天梯榜 + 每月1日刷新,金色背景深字

  2. 排行榜大序号布局(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年
  3. 技师评分分布条(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)

会员是商业化中心,承担"查看金卡→管理次卡→浏览权益→充值"路径。页面结构:

  1. 票据式金卡

    • 上半金色卡片:👑GOLD KING + NO.BK-8823-0042 + 卡内余额¥2,680 28vp + 金卡有效期2027-08-26
    • 锯齿票孔行:10 个深色小圆点均匀分布,模拟票据撕线
    • 下半深色卡片:🎟️10次编发次卡 + 剩余6/10次金色 + 60%粉色进度条 + 2026-02-26到期可转赠 + 立即充值青色按钮
  2. 金卡专属权益 2x2 Grid(8 项)

    • 🎟️黄金时段优先(周五周六晚场优先锁位)
    • 💊养护精华礼(每次编发赠头皮精华)
    • 🎈生日免单(生日当月送编发一次)
    • 🧴染膏88折(全部染膏色卡会员价)
    • 📸免费出片(店内霓虹棚写真拍摄)
    • 🔁7天免费补辫(辫体松脱免费返修)
    • 🚗到店泊车(商圈停车2小时报销)
    • 🎁积分兑换(积分换染膏/饰品)

票据式金卡是会员 Tab 的核心设计,金色卡片+锯齿票孔行模拟夜店 VIP 入场券,次卡进度条直观展示使用情况,8 项权益增加会员价值感。

会员Tab

6.6 色板 Tab(BKColorContent)

色板是染膏商品中心,承担"浏览色卡→查看热门风格→选色"路径。页面结构:

  1. 色板标题 + 流光特效

    • 深色卡片背景 + 🎨染膏色板 + 进口染膏·16色现货青色
    • 流光特效:60vp×4vp 青色半透明光条 translate -40↔40(shineX,1600ms Alternate),模拟霓虹灯管流光
  2. 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
  3. 热门染发风格横条占比榜(6 风格)

    • 渐变彩辫32%粉/拳击辫26%青/经典盒辫18%金/雷鬼脏辫12%深粉/鱼骨辫7%深青/其他5%灰
    • 近30天·3268单

流光特效是色板 Tab 的核心视觉亮点,青色光条横向滑动模拟霓虹灯管流光,增加夜店氛围和科技感。4 列色块 Grid 直观展示 16 色染膏,品牌标签增加专业感。

6.7 动态 Tab(BKFeedContent)

动态是社区中心,承担"浏览编发动态→点赞评论→跳转同款发型"路径。页面结构:

  1. 编发社区标题:📣编发社区 + 今日128条新动态

  2. 动态信息流(8 条)

    • 每条:38vp圆头像(首字粉色)+ 昵称 + VIP金色标签 + 时间·到店消费 + #发型标签青色描边
    • 动态文字(2行省略)
    • 双色渐变色块图(左c1+右c2,模拟发型效果)
    • 互动行:❤️/🤍点赞切换(likedId状态)+ 点赞数 + 💬评论数 + 同款发型>粉色跳转
    • 8 条覆盖:霓虹小卷渐变彩辫夜店发光/Dread老炮雷鬼脏辫终身指定/桃子汽水蜜桃粉彩辫出片率100%/球鞋收藏家Ken拳击辫配AJ街头感/Vvvvivi加纳辫层次绝了/鱼骨辫女孩鱼骨辫婚礼造型/星空收藏家星空闪片辫夜店搭讪三次/短发电波半编彩辫告别帽子

动态信息流是社区 Tab 的核心设计,点赞切换(❤️/🤍)增加互动感,同款发型跳转直接引导预约转化,双色渐变色块图模拟发型效果无需真实图片。

6.8 我的 Tab(BKProfileContent)

我的是个人中心,承担"查看个人卡→了解数据→管理设置→充值"路径。页面结构:

  1. 个人卡

    • 66vp圆头像(粉边框3vp)+ 🧶emoji
    • 霓虹小卷 NEON.C 16vp白色
    • 标签行:👑金卡KING金色/📸出片达人粉色/💬理发室话痨青色
  2. 数据统计四格

    • 26累计编发粉/48收藏发型青/8,620积分金/31到店次数深粉
  3. 设置项列表(6 项)

    • 📍常用门店(霓虹旗舰店)/💳会员卡与充值(余额¥2,680金色)/🧵头皮档案(油性·粗硬发质)/🔗绑定手机(138****6688)/🛡️隐私与账号安全/☎️联系客服(9:00-23:00在线)

个人卡的三标签(金卡/出片达人/理发室话痨)增加个性化和潮流感,数据四格直观展示用户活跃度,头皮档案增加专业服务感。

我的Tab


第七章 七大弹框组件设计

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

弹框体系

7.1 预约编发弹框(BKBookForm·多区块)

定位:编发服务预约入口,多区块分步式设计。

核心功能——四步预约流程

  1. ① 选发型(金边选中):16 款发型 Flex Wrap 宫格,每格含 30vp 双色渐变色块 + 发型名 + ¥价格,选中态金色半透明背景+1.5vp金色边框
  2. ② 选技师:9 位技师横滚 chips,选中粉色背景深字
  3. ③ 选时段:8 个时段 chips(11:00/12:30/14:00/15:30/17:00/18:30/20:00/21:30),选中青色背景深字
  4. ④ 备注:多行文本域(发质/忌讳/想要的效果)

底部合计栏:合计 ¥(发型价+20服务费) 金色19vp + 取消 + 确认预约XX时 青色按钮。发型价格通过 bkStylePrice(selStyle) 纯函数动态查询,服务费固定 ¥20。

多区块分步式设计将复杂的预约流程拆解为四个清晰步骤,金边选中态增加尊贵感,合计栏动态计算价格增加透明度。

7.2 新增收藏发型弹框(BKAddStyleForm·输入式)

定位:用户自定义收藏发型入口,输入式设计。

核心功能

  1. 发型名称:文本输入(如:玫瑰荆棘辫)
  2. 风格分类:10 分类 Flex Wrap chips(全部/街头/非洲/彩辫/脏辫/派对/仙系/混搭/增发/假发),选中粉色背景深字
  3. 参考价格 + 时长:双列文本输入
  4. 描述:多行文本域(想要的编法、配色、参考图链接)

底部操作:放弃 + 收藏到我的发型库 金色按钮。输入式设计让用户可以收藏心仪的发型,即使发型库中没有,也能记录下来后续与技师沟通。

7.3 修改预约弹框(BKEditBookForm·底部抽屉式)

定位:已有预约的修改入口,底部抽屉式设计。

核心设计——底部抽屉式

  • 顶部 40vp×4vp 深色拖拽条,模拟底部抽屉的拖拽手柄
  • 顶部圆角 22vp,底部直角,从底部滑出的视觉效果
  • position y:36%,maxHeight 64%

核心功能——三步修改

  1. ① 改时段(点钟 stepper):−/+ 按钮调节小时(10-21点),青色22vp大号数字显示 XX:00,aboutToAppear 回显原技师
  2. ② 换技师(单选行):9 位技师列表,每行 emoji + 名称 + 头衔·评分 + ●已选/○选择,选中青色半透明背景
  3. ③ 改备注:多行文本域

底部操作:不改了 + 保存修改 粉色按钮。底部抽屉式设计是本弹框的特色,从底部滑出的交互模式符合移动端改约的轻量操作场景,拖拽条增加可操作性暗示。

7.4 取消预约弹框(内联·粉色警告式)

定位:取消预约的二次确认,粉色警告式设计。

核心设计——粉色警告

  • ⚠️ 图标 42vp
  • 确认取消这条预约?粉色17vp标题
  • 发型名白色加粗

违约金说明卡(粉色半透明背景+深粉边框):

  • 违约金说明金色标题
  • 距开始不足24小时取消,收取订单金额20%作为违约金(技师档期已锁定)
  • 距开始超过24小时取消,全额退款至会员卡,积分不退

底部操作:再想想(深灰)+ 确认取消(粉色白字)。粉色警告式设计与取消操作的"风险"属性匹配,违约金说明增加透明度,让用户清楚了解取消后果,减少误操作。

7.5 技师详情弹框(BKStylistCard·招牌卡式)

定位:技师详细信息展示入口,招牌卡式设计。

核心功能

  1. 技师招牌卡头:76vp圆头像(金色3vp边框)+ emoji + 名称18vp + 头衔金色 + ★评分金色 + 订单数评价
  2. 技能标签:#技能 青色标签横滚
  3. 三数据格:累计作品粉色 / 从业年限青色 / 本月口碑金色,竖线分隔
  4. 代表作品横滚:16 款发型 76vp 色卡横滚,模拟技师作品集
  5. 指定 TA 帮我编发:粉色大按钮,点击后关闭技师详情并自动打开预约弹框(onBook 回调链式触发)

招牌卡式设计将技师信息如同理发店招牌般展示,大圆头像+金色边框增加尊贵感,三数据格直观展示技师实力,代表作品横滚增加说服力。指定TA按钮的链式触发(关闭技师详情→打开预约弹框)是本弹框的交互亮点,减少用户操作步骤。

7.6 染膏选色弹框(BKColorPickForm·色卡宫格)

定位:染膏颜色选择入口,色卡宫格式设计。

核心功能

  1. 4 列色卡宫格(16 色):每卡 23% 宽,44vp 纯色色块 + 色名 + 色号,选中金色半透明背景+1.5vp金色边框
  2. 已选颜色预览:34vp 圆形色块(金色2vp边框)+ 已选:色名+色号 + 品牌·热度
  3. 底部操作:再看看 + 确认用这个色 青色按钮

aboutToAppear 生命周期根据 enterCode 遍历 BK_DYES 找到对应色索引并回显 selIdx。色卡宫格式设计直观展示 16 色染膏,纯色色块让颜色一目了然,已选预览增加确认感。

7.7 会员充值弹框(BKVipRechargeForm·票据档位式)

定位:会员卡充值入口,票据档位式设计。

核心功能——四档充值

  1. 充值档位横滚(4 档):
    • 体验档:充¥300送¥30,到账¥330
    • 入门档:充¥500送¥80,到账¥580
    • 人气档:充¥1000送¥200,到账¥1200
    • 霸王档:充¥2000送¥500,到账¥2500
    • 选中态:金色半透明背景+1.5vp金色边框+档位标签金色背景深字+金额金色+赠送青色
  2. 支付方式(三选一):💚微信支付 / 💳会员卡余额支付(¥2,680)/ 🏦银行卡支付,选中青色半透明背景+●青色
  3. 实付合计:¥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 性能优化策略

  1. 合理使用 @State:只有需触发 UI 更新的状态用 @State,静态数据用 const,减少响应式开销。项目中约 30 个 @State 变量,均为必要的交互状态和动画属性。
  2. 列表渲染优化:使用 ForEach 框架自动 diff 复用,列表项固定尺寸避免布局抖动。16 发型/9 技师/7 预约/16 染膏/8 动态等列表均使用 ForEach。
  3. 动画性能:使用属性动画而非逐帧动画,动画属性限制在 opacity/rotate/scale/translate 等合成层属性避免重排,无限循环用 iterations:-1 零定时器。约 5 组动画全部采用此方式。灯柱旋转使用 Normal+Linear 实现无缝循环,其他使用 Alternate 实现往返呼吸/流光。
  4. 减少重渲染:弹框条件渲染未显示不渲染,避免 build() 中复杂计算。7 个弹框均通过 if 条件渲染。
  5. 资源优化:使用 emoji 作图标无需图片,渐变背景用 linearGradient 无需图片,双色渐变色块模拟发型效果无需真实发型图片,减少网络请求和内存占用。
  6. 纯函数封装: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 渲染,三级状态管理简单高效,发型浏览/预约管理/技师排行/会员充值/染膏选色/社区动态/个人档案核心逻辑完整实现。业务价值:覆盖了浏览发型→预约服务→选择技师→查看排行→管理会员→挑选颜色→社区互动→个人管理完整用户旅程,发型库激发消费欲望,预约系统连接线下服务,技师榜建立专业信任,会员系统实现商业化闭环,社区动态增加用户粘性。

技术亮点

  1. 夜店霓虹三组动画:门面横幅同时运行霓虹呼吸(opacity)+ 灯柱旋转(rotate Normal循环)+ 星星脉动(scale),营造夜店灯光秀氛围
  2. 灯柱无缝旋转动画:Normal 循环 + Linear 曲线实现 0→360° 无缝旋转,是全应用唯一 Normal 模式动画,6000ms 缓慢有力
  3. 色板流光特效:translate 动画模拟霓虹灯管流光,青色光条横向滑动,增加夜店氛围和科技感
  4. 票据式金卡设计:金色卡片+锯齿票孔行+次卡进度,如同夜店 VIP 入场券
  5. 周历时段表:7 天 × 6 时段矩阵,bkCalStatus 数学公式模拟真实预约分布,空闲/已约/过期三态配色
  6. 双色渐变色块模拟发型:发型库和动态卡使用 c1+c2 双色拼接色块模拟发色效果,无需真实发型图片
  7. 10 纯函数业务逻辑:发型价格/时长查询、技师按名查询、热门筛选、周峰值、日历状态/文本、星级、热门标签
  8. 技师详情链式预约:指定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 编发王国总结


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

Logo

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

更多推荐