金丝万缕,巧夺天工。花丝镶嵌,又称"细金工艺",是中国传统金银器制作中最精细的技艺之一,源自商周金银错,盛于明清宫廷造办处,位列"燕京八绝"之首。一根金丝,掐、填、攒、焊、堆,五法并用——掐丝以金丝掐出纹样轮廓,填丝于空隙中填以细丝,攒焊将多件花丝攒接焊合,堆丝层层堆累成形,焊丝以火候焊接固定。从凤钗步摇到金冠华胜,从璎珞长命锁到绞丝镯龙头镯,从金瓯永固杯到花丝宫灯,花丝镶嵌承载着中国宫廷金银细工的最高水准。本文完整记录一个基于鸿蒙HarmonyOS 6.1.1(API 24)与ArkTS声明式框架构建的「花丝镶嵌坊」非遗手作平台,从黑金红三色(金色#C9A227/朱红#B23A3A/墨黑#2A2118)的花丝配色主题,到头部同心圆金环多层旋转动画(4层金红交替旋转花丝圆环+中心金珠脉冲缩放),再到花丝工艺工时横条(craftBarW工时宽度映射)、订单金额柱状图(orderBarH金额高度映射)、8Tab差异化布局(头饰馆藏/耳饰雅集/项饰华光/手镯工坊/金工摆件/花丝绝艺/工匠名录/订单台账)、单组件@Builder架构、@Observed聚合数据类FiligreeShopData、6个工具函数(craftBarW/orderBarH/rankColor/statusColor/ringSize/ringWidth)、三大弹窗(新增订单/编辑花丝工艺/移除工匠)、条件渲染模态遮罩、PlayMode.Alternate呼吸动画体系,附完整源码与开发环境搭建。


一、引言:金丝万缕,巧夺天工

花丝镶嵌,古称"细金工艺",又称"累丝"“花作”,是中国传统金银器制作中最精细的技艺之一,位列"燕京八绝"之首。花丝镶嵌的起源可追溯至商周时期的金银错工艺——商代青铜器上已有金银丝镶嵌的纹饰,至春秋战国金银错工艺成熟,唐宋花丝工艺渐成体系,至明清两代在宫廷造办处达到巅峰。明代定陵出土的万历金冠(翼善冠),全以518根0.2mm细金丝编织而成,重仅826克,是花丝镶嵌的巅峰之作。清代宫廷造办处更将花丝镶嵌与錾刻、镶嵌、点翠、鎏金等工艺结合,制作了金瓯永固杯等皇家重器,将花丝镶嵌推上了中国金银细工的最高峰。花丝镶嵌不仅是一门技艺,更承载着中国宫廷礼制与审美哲学——凤钗步摇象征后妃之尊,长命锁寄托百家护佑,金瓯永固杯寓江山永固之意,每一件花丝镶嵌作品都是权力、地位与吉祥寓意的载体。

中国花丝镶嵌工艺技法博大精深,核心技法可概括为"掐、填、攒、焊、堆"五大法。掐丝,以镊子将金丝掐出纹样轮廓,是花丝工艺的基础;填丝,在掐好的轮廓空隙中填以细丝,使纹样丰满;攒焊,将多件花丝部件攒接在一起用焊药焊接固定,是立体造型之关键;堆丝,将细丝层层堆累成形,可做出立体花叶瑞兽;焊丝,以火候焊接固定花丝,火候过则金丝熔毁,火候不足则焊接不牢。此外,还有累丝(细丝层层堆累成形)、编结(编辫编织成型)、錾刻(锤击錾出纹饰)、镶嵌(嵌宝嵌石)、鎏金(汞镀金层)、点翠(翠羽贴饰)等辅助工艺。十种技法各有门道,掐丝12小时可成,而点翠需32小时,錾刻需28小时,工艺繁复程度以单件耗时衡量——耗时越高,工艺越繁复,价值越高。花丝匠人更是师承有序,大师级匠人四十余载苦修,累计作品三五百件,方得花丝真传。

然而,随着现代工业替代手工,花丝镶嵌这门需要数十年苦修的宫廷绝技正面临传承困境——年轻人不了解花丝文化,手工匠人缺乏展示和销售渠道,花丝产品工艺不透明、工时信息缺失、匠人名录断层,正成为中国花丝镶嵌非遗传承的现实痛点。一个优秀的花丝镶嵌手作平台,不仅需要覆盖从"头饰馆藏"到"花丝绝艺"再到"订单台账"的完整产品展示闭环,还要在金工摆件、工匠名录、工艺工时等维度做到精准而高效。它需要以头饰馆藏为核心入口,横向扩展出耳饰、项饰、手镯、摆件、花丝工艺、工匠名录、订单台账八大功能模块,每个模块采用完全不同的布局风格,形成一个完整闭环的中国花丝镶嵌非遗手作生态。

正是在这样的背景下,花丝镶嵌坊非遗手作平台应运而生。一个优秀的花丝应用,需要以头饰馆藏为核心入口,横向扩展出耳饰雅集、项饰华光、手镯工坊、金工摆件、花丝绝艺、工匠名录、订单台账八大功能模块,每个模块采用完全不同的布局风格——头饰=馆藏列表+金条呼吸+统计概览、耳饰=雅集列表+菱形分割线、项饰=华光列表+位移呼吸、手镯=工坊列表、摆件=皇家重器列表+宫廷技艺标识、花丝=绝艺列表+工时横条+编辑交互、工匠=名录列表+移除交互、订单=台账列表+金额柱状图+新增交互,形成了一个完整闭环的中国花丝镶嵌非遗手作生态。它不仅是一个功能完备的演示项目,更是一份典型的ArkUI工程实践范本,涵盖了单组件@Builder架构设计、@Observed聚合数据类、颜色分级函数、同心圆金环旋转动画与中心金珠脉冲、条件渲染模态弹窗、layoutWeight弹性进度条、ForEach列表渲染等关键技术点。
在这里插入图片描述


二、应用整体架构概览

整个应用采用了"单组件@Builder架构 + @Observed聚合数据 + 配置驱动 + 条件渲染弹窗"的四层设计模式。入口组件FiligreeShopApp作为整个应用的根容器,负责管理当前激活的Tab状态、动画开关amb、三个弹窗的显隐状态、表单数据以及聚合数据对象。与多组件架构(如香囊坊的10组件设计)不同,本应用采用单组件@Builder构建所有页面的架构——所有8个Tab内容页面都内联为入口组件中的@Builder方法,通过if-else if条件渲染切换,数据通过@State data: FiligreeShopData直接在组件内部管理,无需@ObjectLink跨组件传递。这种设计简化了组件间通信,代码结构紧凑,是中小型ArkUI应用的典型架构实践。

应用一共划分为八个一级页面:头饰(Tab 0)、耳饰(Tab 1)、项饰(Tab 2)、手镯(Tab 3)、摆件(Tab 4)、花丝(Tab 5)、工匠(Tab 6)、订单(Tab 7)。底部bottomBar采用双行四列布局——ROW1_IDX [0,1,2,3] 为第一行,ROW2_IDX [4,5,6,7] 为第二行,每个Tab项由@Builder bottomItem(i)构建,包含图标(18号Emoji)、标签文字(11号)、选中指示条(16x3圆角2金色小条)三部分。激活态文字goldDeep深金色加粗+指示条金色显示,非激活态textSub浅灰色普通+指示条透明隐藏,点击切换activeTab触发contentArea重新渲染。八个Tab每个布局完全不同——头饰采用馆藏列表+统计概览+金条呼吸动画(12款头饰,凤钗步摇簪花金冠)、耳饰采用雅集列表+菱形分割线(8款耳饰,红宝石和田玉碧玺翡翠)、项饰采用华光列表+位移呼吸动画(8款项饰,璎珞长命锁朝珠)、手镯采用工坊列表(8款手镯,绞丝镯花丝镯龙头镯)、摆件采用皇家重器列表+宫廷技艺标识(8件摆件,金瓯永固杯金凤栖梧)、花丝采用绝艺列表+工时横条+编辑交互(10种工艺,掐丝累丝填丝攒焊堆丝)、工匠采用名录列表+移除交互(8位匠人,大师名师匠人三级)、订单采用台账列表+金额柱状图+新增交互(10笔订单,制作中精工中已交付),这种"一页一型"的差异化设计最大程度还原了真实中国花丝镶嵌非遗手作平台的交互体验。

三大弹窗

八大内容页面

入口组件 FiligreeShopApp

全局配置层

COLORS 配色常量
黑金红三色14色值

TAB_LIST 导航配置
8个Tab元数据

6个工具函数
尺寸/颜色/圆环映射

数据层

@Observed FiligreeShopData
8个数组·72条数据

hairs 12款

ears 8款

necks 8款

bracelets 8款

ornaments 8件

crafts 10种

masters 8位

orders 10笔

头部区域 headerArea
同心圆金环旋转+中心金珠脉冲

内容区域 contentArea
八Tab条件渲染切换

底部导航 bottomBar
双行四列布局

Tab0 头饰
馆藏列表+统计+金条

Tab1 耳饰
雅集列表+菱形分割

Tab2 项饰
华光列表+位移呼吸

Tab3 手镯
工坊列表

Tab4 摆件
皇家重器+宫廷标识

Tab5 花丝
绝艺列表+工时横条+编辑

Tab6 工匠
名录列表+移除

Tab7 订单
柱状图+台账+新增

addOrderModal
新增订单

editCraftModal
编辑花丝工艺

removeMasterModal
移除工匠

M8

在数据层面,应用独创了**@Observed聚合数据类**设计——FiligreeShopData一个类包含hairs/ears/necks/bracelets/ornaments/crafts/masters/orders八个数组,每个数组8-12条数据,用new构造函数初始化(每个数据项都是带constructor的@Observed类实例),@Observed装饰整个类及每个数据项类,配合@State在组件内共享引用,实现"一个数据对象贯穿所有页面"的聚合数据模式。这种设计将所有业务数据集中管理,修改数据时(如addOrder/editCraft/removeMaster)所有引用该数据的UI自动响应式刷新,是ArkUI数据驱动视图的核心实践。应用还定义了8个@Observed数据项类(HairOrnamentItem/EarItem/NeckItem/BraceletItem/OrnamentItem/CraftItem/MasterItem/OrderItem),每个类都有显式constructor构造函数,将数据结构与UI表现层解耦,实现配置驱动设计。

在颜色逻辑层面,应用定义了6个颜色与尺寸映射函数(craftBarW工时宽度映射/orderBarH金额高度映射/rankColor匠人等级着色/statusColor订单状态着色/ringSize圆环尺寸映射/ringWidth圆环宽度映射),将颜色和尺寸映射逻辑从UI层抽离,每个函数根据不同维度(工时/金额/等级/状态/圆环索引)返回对应的颜色值或像素尺寸,实现"数据即配色、数据即尺寸"的动态映射模式。应用配色采用"黑金红三色"花丝镶嵌非遗主题——bg #FBF6EC 米色(模拟古绢温润质感),gold #C9A227 金色(花丝主色调),goldDeep #8C6D1F 深金(深色强调),black #2A2118 墨黑(头部底色),vermilion #B23A3A 朱红(点缀色),五色各司其职形成"以金色定丝、以朱红定饰、以墨黑定底、以深金定价、以米色定基"的中国花丝镶嵌语义着色体系。

在交互层面,应用通过三个@Builder弹窗方法实现了完整的增删改查交互闭环:addOrderModal新增订单(四字段TextInput表单:订单编号/客户姓名/定制作品/定金金额,提交订单goldDeep深金底)、editCraftModal编辑花丝工艺(单字段TextInput表单:单件耗时小时数,保存修改goldDeep深金底)、removeMasterModal移除工匠(确认对话框,确认移除danger朱红底)。三个弹窗都是@Builder方法内联在入口组件中,通过if条件渲染控制显隐(showAddOrder/showEditCraft/showRemoveMaster三个@State布尔变量),遮罩用#55000000(33%黑)硬编码,弹窗主体与遮罩用Stack层叠,整体zIndex(999)覆盖主内容。花丝页"编辑"点击触发editCraftModal(craftIdx记录目标索引,craftHours预填当前值),工匠页"移除"点击触发removeMasterModal(masterIdx记录目标索引),订单页"新增订单"点击触发addOrderModal,形成了完整的"编辑工艺→移除工匠→新增订单"交互闭环。

下面逐页展示应用的八大功能模块运行效果。


三、头饰页:馆藏列表与统计概览

头饰页以"头饰馆藏"为核心,采用"统计概览+馆藏列表+金条呼吸动画"的三层布局。
在这里插入图片描述

页面顶部第一部分是统计概览卡,Row包裹三列数据:

Row() {
  Column() {
    Text('12').fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
    Text('头饰馆藏').fontSize(10).fontColor(COLORS.textHint).margin({ top: 2 })
  }.layoutWeight(1)
  Column() {
    Text('凤钗').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.vermilion)
    Text('最热款').fontSize(10).fontColor(COLORS.textHint).margin({ top: 2 })
  }.layoutWeight(1)
  Column() {
    Text('¥15.2w').fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
    Text('本季成交').fontSize(10).fontColor(COLORS.textHint).margin({ top: 2 })
  }.layoutWeight(1)
}
.width('100%')
.padding({ top: 12, bottom: 12 })
.backgroundColor(COLORS.cardBg).borderRadius(12)
.margin({ top: 12, bottom: 12 })
.border({ width: 1, color: COLORS.border })

统计概览卡采用三列等分布局——第一列"12头饰馆藏"16号粗体深金色+10号浅灰"头饰馆藏"标签,第二列"凤钗最热款"13号粗体朱红色+10号浅灰"最热款"标签,第三列"¥15.2w本季成交"16号粗体深金色+10号浅灰"本季成交"标签。三列用layoutWeight(1)等分宽度,整体cardBg白底圆角12+border边框,是本应用头饰页的核心数据展示区。"12"代表12款头饰馆藏,"凤钗"是最热款式(凤钗是花丝镶嵌头饰中知名度最高的款式,凤穿牡丹纹样寓意富贵吉祥),"¥15.2w"是本季成交总额(花丝头饰价格从¥3200花钿到¥36800金冠,12款合计约15.2万元),三组数据一目了然地呈现了头饰馆藏的规模、热度和业绩。

第二部分是头饰列表,Scroll包裹ForEach遍历this.data.hairs数组,每款头饰渲染为一张卡片:

ForEach(this.data.hairs, (h: HairOrnamentItem) => {
  Column() {
    Row() {
      Column() {
        Text(h.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
        Text(h.style + ' · ' + h.metal).fontSize(11)
          .fontColor(COLORS.textSub).margin({ top: 4 })
      }
      .alignItems(HorizontalAlign.Start).layoutWeight(1)
      Text('¥' + h.price).fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
      Column()
        .width(4).height(34).borderRadius(2)
        .backgroundColor(COLORS.gold).margin({ left: 10 })
        .opacity(this.amb ? 0.35 : 0.9)
        .animation({ duration: 1000, iterations: -1, playMode: PlayMode.Alternate })
    }
    .width('100%')
  }
  .width('100%')
  .backgroundColor(COLORS.cardBg).borderRadius(12)
  .padding({ left: 14, right: 14, top: 12, bottom: 12 })
  .margin({ bottom: 10 })
  .border({ width: 1, color: COLORS.border })
}, (h: HairOrnamentItem) => h.name)

每张头饰卡片采用三列布局——第一列是layoutWeight(1)弹性信息区(头饰名称14号粗体深褐色+款式材质信息11号灰棕色"凤穿牡丹 · 足金"),第二列是价格(“¥”+price 13号粗体深金色),第三列是4x34金色竖条(backgroundColor(COLORS.gold)金色+opacity: 0.35↔0.9呼吸变化+animation 1000ms Alternate无限循环,模拟花丝金条的光泽闪烁)。金色竖条的呼吸动画是本应用头饰页的核心亮点——this.amb控制动画起止(页面加载100ms后置true),opacity在0.35和0.9之间往返变化,duration 1000ms,PlayMode.Alternate保证往返不跳跃,金色竖条忽明忽暗,模拟花丝金条在灯光下的反光效果。卡片用cardBg白底圆角12+border边框+margin bottom 10间距。

12款头饰数据完整呈现了中国花丝镶嵌头饰的文化生态:凤钗(凤穿牡丹,足金,¥8800)、步摇(花丝垂珠,足金,¥12800)、簪花(累丝牡丹,足金,¥6800)、金冠(花丝点翠,足金,¥36800)、花钿(蝶恋花,足金,¥3200)、华胜(缠枝莲纹,足金,¥9800)、珠钗(双珠攒金,足金,¥5600)、翠钿(点翠嵌宝,足金,¥15800)、梳篦(金镶玳瑁,足金,¥4800)、玉笄(白玉镶金,足金,¥7600)、金簪(如意云头,足金,¥6200)、顶簪(花丝金龙,足金,¥28800)。价格从¥3200(花钿,蝶恋花纹样小巧工艺简单)到¥36800(金冠,花丝点翠工艺最复杂用金最多),金冠¥36800最贵(花丝点翠结合掐丝堆丝点翠三大工艺,用金量大且翠羽珍贵),花钿¥3200最实惠(蝶恋花纹样简单花丝用量少)。12款头饰覆盖凤钗/步摇/簪花/金冠/花钿/华胜/珠钗/翠钿/梳篦/玉笄/金簪/顶簪十二种头饰形制,款式覆盖凤穿牡丹/花丝垂珠/累丝牡丹/花丝点翠/蝶恋花/缠枝莲纹/双珠攒金/点翠嵌宝/金镶玳瑁/白玉镶金/如意云头/花丝金龙十二种纹样,全部足金材质,完整呈现中国花丝镶嵌头饰"金丝万缕、巧夺天工"的文化内涵。


四、耳饰页:雅集列表与菱形分割

耳饰页以"耳饰雅集"为核心,采用"标题行+雅集列表+菱形分割线"的布局。
在这里插入图片描述

页面顶部第一部分是标题行,Row包裹:

Row() {
  Text('💎').fontSize(14).margin({ right: 6 })
  Text('耳饰雅集').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
  Column().layoutWeight(1)
  Text('共 8 款').fontSize(10).fontColor(COLORS.textHint)
}
.width('100%').padding({ top: 14, bottom: 8 })

标题行采用四列布局——💎钻石Emoji(14号,margin right 6间距)+"耳饰雅集"13号粗体深褐色+Column().layoutWeight(1)弹性间距+"共 8 款"10号浅灰色右对齐。“耳饰雅集"点明耳饰页的文化定位——耳饰在古代称"珰”“珥”“瑱”,是女子妆容的重要组成,花丝耳饰以金丝编织流苏、镶嵌宝石,是花丝镶嵌中精致灵动的小件。

第二部分是耳饰列表,ForEach遍历this.data.ears数组,每款耳饰卡片之间用菱形分割线装饰:

ForEach(this.data.ears, (e: EarItem) => {
  Column() {
    Row() {
      Column() {
        Text(e.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
        Text(e.style + ' · ' + e.stone).fontSize(11)
          .fontColor(COLORS.textSub).margin({ top: 4 })
      }
      .alignItems(HorizontalAlign.Start).layoutWeight(1)
      Text('¥' + e.price).fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
    }
    .width('100%')
    Row() {
      Column().layoutWeight(1)
        .height(1).backgroundColor(COLORS.border).margin({ top: 10 })
      Text('◆').fontSize(10).fontColor(COLORS.gold).margin({ top: 6, left: 8, right: 8 })
      Column().layoutWeight(1)
        .height(1).backgroundColor(COLORS.border).margin({ top: 10 })
    }
    .width('100%')
  }
  .width('100%')
  .backgroundColor(COLORS.cardBg).borderRadius(12)
  .padding({ left: 14, right: 14, top: 12, bottom: 12 })
  .margin({ bottom: 10 })
  .border({ width: 1, color: COLORS.border })
}, (e: EarItem) => e.name)

每张耳饰卡片采用双层结构——上层是信息行(耳饰名称14号粗体+款式宝石信息11号灰棕色"花丝流苏 · 红宝石"+价格13号粗体深金色),下层是菱形分割线(左右各一条layoutWeight(1)弹性1px边框色横线+中间10号金色◆菱形装饰)。菱形分割线是本应用耳饰页的核心亮点——左右横线用layoutWeight(1)弹性撑开,中间◆用gold金色着色,形成"左线-菱形-右线"的三段式装饰,模拟花丝耳饰中常见的菱形花纹分隔,既美化卡片又呼应花丝镶嵌的纹样美学。卡片用cardBg白底圆角12+border边框+margin bottom 10间距。

8款耳饰数据:金耳坠(花丝流苏,红宝石,¥6800)、玉耳环(圆环素面,和田玉,¥5200)、葫芦坠(吉祥葫芦,碧玺,¥8800)、如意坠(如意祥云,翡翠,¥15800)、灯笼坠(宫灯造型,红玛瑙,¥7200)、流苏坠(三股花丝,珍珠,¥9800)、丁香钉(素金小钉,无,¥1800)、花丝圈(双圈绞丝,无,¥4200)。价格从¥1800(丁香钉,素金小钉无宝石工艺最简单)到¥15800(如意坠,如意祥云翡翠宝石最珍贵),如意坠¥15800最贵(翡翠是玉中之王,如意祥云纹样工艺复杂),丁香钉¥1800最实惠(素金小钉无宝石,工艺最简单)。8款耳饰覆盖金耳坠/玉耳环/葫芦坠/如意坠/灯笼坠/流苏坠/丁香钉/花丝圈八种形制,宝石覆盖红宝石/和田玉/碧玺/翡翠/红玛瑙/珍珠/无七种材质("无"表示纯金花丝无镶嵌),完整呈现中国花丝耳饰"金玉宝石、流苏垂珠"的精致工艺。


五、项饰页:华光列表与位移呼吸

项饰页以"项饰华光"为核心,采用"标题行+华光列表+位移呼吸动画"的布局。
在这里插入图片描述

页面顶部第一部分是标题行:

Row() {
  Text('📿').fontSize(14).margin({ right: 6 })
  Text('项饰华光').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
  Column().layoutWeight(1)
  Text('一锁平安').fontSize(10).fontColor(COLORS.textHint)
}
.width('100%').padding({ top: 14, bottom: 8 })

标题行左侧"📿 项饰华光"13号粗体深褐色,右侧"一锁平安"10号浅灰色,点明项饰的文化寓意——长命锁、平安锁是项饰中最富吉祥寓意的品类,"一锁平安"四字概括了花丝项饰的核心文化价值:以金锁护佑平安。

第二部分是项饰列表,ForEach遍历this.data.necks数组,每款项饰卡片带位移呼吸动画:

ForEach(this.data.necks, (n: NeckItem) => {
  Column() {
    Row() {
      Column() {
        Text(n.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
        Text(n.style + ' · ' + n.metal).fontSize(11)
          .fontColor(COLORS.textSub).margin({ top: 4 })
      }
      .alignItems(HorizontalAlign.Start).layoutWeight(1)
      Text('¥' + n.price).fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
    }
    .width('100%')
  }
  .width('100%')
  .backgroundColor(COLORS.cardBg).borderRadius(12)
  .padding({ left: 14, right: 14, top: 12, bottom: 12 })
  .margin({ bottom: 10 })
  .border({ width: 1, color: COLORS.border })
  .translate({ x: this.amb ? 4 : 0 })
  .animation({ duration: 1300, iterations: -1, playMode: PlayMode.Alternate })
}, (n: NeckItem) => n.name)

每张项饰卡片采用两列布局——左侧layoutWeight(1)弹性信息区(项饰名称14号粗体深褐色+款式材质信息11号灰棕色"花丝嵌宝 · 足金"),右侧价格13号粗体深金色。卡片的亮点是translate({ x: this.amb ? 4 : 0 })位移动画——this.amb控制位移起止,x轴在0和4px之间往返位移,duration 1300ms,PlayMode.Alternate保证往返不跳跃。整个卡片微微左右移动,模拟佩戴项饰时金锁轻轻晃动的动态效果,是本应用项饰页的核心动画亮点。卡片用cardBg白底圆角12+border边框+margin bottom 10间距。

8款项饰数据:璎珞(花丝嵌宝,足金,¥26800)、金项圈(平安如意,足金,¥12800)、长命锁(百家锁,足银,¥3200)、如意锁(双面錾刻,足金,¥9800)、朝珠(108子,玛瑙,¥18800)、花丝链(龙凤纹,足金,¥16800)、平安锁(心形锁片,足金,¥6800)、云纹锁(浮雕云纹,足金,¥8800)。价格从¥3200(长命锁,百家锁足银材质银比金便宜)到¥26800(璎珞,花丝嵌宝足金工艺复杂宝石多),璎珞¥26800最贵(花丝嵌宝结合掐丝填丝镶嵌三大工艺,足金用量大且嵌宝多),长命锁¥3200最实惠(百家锁足银材质,银价远低于金)。8款项饰覆盖璎珞/金项圈/长命锁/如意锁/朝珠/花丝链/平安锁/云纹锁八种形制,材质覆盖足金/足银/玛瑙三种(足金为主,足银用于长命锁,玛瑙用于朝珠),完整呈现中国花丝项饰"璎珞华光、一锁平安"的吉祥文化。


六、手镯页:工坊列表

手镯页以"手镯工坊"为核心,采用"标题行+工坊列表"的单层布局。
在这里插入图片描述

页面顶部第一部分是标题行:

Row() {
  Text('⚜️').fontSize(14).margin({ right: 6 })
  Text('手镯工坊').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
  Column().layoutWeight(1)
  Text('腕上风情').fontSize(10).fontColor(COLORS.textHint)
}
.width('100%').padding({ top: 14, bottom: 8 })

标题行左侧"⚜️ 手镯工坊"13号粗体深褐色,右侧"腕上风情"10号浅灰色,点明手镯的文化定位——手镯古称"钏"“环”“臂钏”,是中国女性腕上最重要的金饰,花丝手镯以绞丝、累丝、錾刻、镶嵌等工艺制作,是花丝镶嵌中大件重器。

第二部分是手镯列表,ForEach遍历this.data.bracelets数组:

ForEach(this.data.bracelets, (b: BraceletItem) => {
  Column() {
    Row() {
      Column() {
        Text(b.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
        Text(b.craft + ' · ' + b.metal).fontSize(11)
          .fontColor(COLORS.textSub).margin({ top: 4 })
      }
      .alignItems(HorizontalAlign.Start).layoutWeight(1)
      Text('¥' + b.price).fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
    }
    .width('100%')
  }
  .width('100%')
  .backgroundColor(COLORS.cardBg).borderRadius(12)
  .padding({ left: 14, right: 14, top: 12, bottom: 12 })
  .margin({ bottom: 10 })
  .border({ width: 1, color: COLORS.border })
}, (b: BraceletItem) => b.name)

每张手镯卡片采用两列布局——左侧layoutWeight(1)弹性信息区(手镯名称14号粗体深褐色+工艺材质信息11号灰棕色"三股绞丝 · 足金"),右侧价格13号粗体深金色。手镯卡片信息字段与头饰/项饰不同——使用b.craft(工艺)而非style(款式),因为手镯的核心卖点在于制作工艺(绞丝/累丝/錾刻/镶嵌),工艺名称直接反映花丝技法。卡片用cardBg白底圆角12+border边框+margin bottom 10间距。

8款手镯数据:绞丝镯(三股绞丝,足金,¥15800)、花丝镯(累丝卷草,足金,¥23800)、扁圆镯(素面磨光,足金,¥12800)、龙头镯(双龙戏珠,足金,¥32800)、镶嵌镯(嵌宝八珠,足金,¥26800)、银镯(浮雕缠枝,足银,¥2200)、金镯(錾刻竹节,足金,¥18800)、点翠镯(点翠花瓣,足金,¥29800)。价格从¥2200(银镯,浮雕缠枝足银材质银比金便宜)到¥32800(龙头镯,双龙戏珠足金工艺复杂用金量大),龙头镯¥32800最贵(双龙戏珠纹样立体造型复杂,龙头部分需堆丝攒焊成型,足金用量大),银镯¥2200最实惠(足银材质银价远低于金,浮雕缠枝工艺相对简单)。8款手镯覆盖绞丝镯/花丝镯/扁圆镯/龙头镯/镶嵌镯/银镯/金镯/点翠镯八种形制,工艺覆盖三股绞丝/累丝卷草/素面磨光/双龙戏珠/嵌宝八珠/浮雕缠枝/錾刻竹节/点翠花瓣八种工艺(与花丝页的10种工艺技法呼应),完整呈现中国花丝手镯"绞丝累丝、腕上风情"的工艺之美。


七、摆件页:皇家重器与宫廷标识

摆件页以"金工摆件"为核心,采用"标题行+皇家重器列表+宫廷技艺标识"的布局。
在这里插入图片描述

页面顶部第一部分是标题行:

Row() {
  Text('🏺').fontSize(14).margin({ right: 6 })
  Text('金工摆件').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
  Column().layoutWeight(1)
  Text('皇家重器').fontSize(10).fontColor(COLORS.textHint)
}
.width('100%').padding({ top: 14, bottom: 8 })

标题行左侧"🏺 金工摆件"13号粗体深褐色,右侧"皇家重器"10号浅灰色,点明摆件页的文化定位——花丝摆件多为宫廷御用,如金瓯永固杯是清代皇帝元旦开笔仪式专用酒杯,是皇家重器的最高代表。

第二部分是摆件列表,ForEach遍历this.data.ornaments数组,每件摆件卡片底部有"花丝镶嵌·宫廷技艺"标识:

ForEach(this.data.ornaments, (o: OrnamentItem) => {
  Column() {
    Row() {
      Column() {
        Text(o.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
        Text(o.theme + ' · 用金 ' + o.goldGram + 'g').fontSize(11)
          .fontColor(COLORS.textSub).margin({ top: 4 })
      }
      .alignItems(HorizontalAlign.Start).layoutWeight(1)
      Text('¥' + o.price).fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
    }
    .width('100%')
    Text('花丝镶嵌 · 宫廷技艺').fontSize(10).fontColor(COLORS.vermilion)
      .width('100%').margin({ top: 8 }).textAlign(TextAlign.End)
  }
  .width('100%')
  .backgroundColor(COLORS.cardBg).borderRadius(12)
  .padding({ left: 14, right: 14, top: 12, bottom: 12 })
  .margin({ bottom: 10 })
  .border({ width: 1, color: COLORS.border })
}, (o: OrnamentItem) => o.name)

每张摆件卡片采用双层结构——上层是信息行(摆件名称14号粗体深褐色+主题用金信息11号灰棕色"镇殿之宝 · 用金 86g"+价格13号粗体深金色),下层是"花丝镶嵌 · 宫廷技艺"10号朱红色右对齐标识。摆件卡片的信息字段与其他Tab不同——使用o.theme(主题)和o.goldGram(用金克数),因为摆件的核心卖点在于用金量和文化主题(镇殿之宝/凤纹祥瑞/一帆风顺),用金量直接反映摆件的规格和价值。"花丝镶嵌 · 宫廷技艺"标识用vermilion朱红色着色,是本应用摆件页的核心亮点——朱红色是中国宫廷的传统色彩,用朱红标识"宫廷技艺"既呼应花丝镶嵌的皇家身份,又与其他Tab的深金色价格形成"金红对比"的视觉层次。卡片用cardBg白底圆角12+border边框+margin bottom 10间距。

8件摆件数据:金瓯永固杯(镇殿之宝,86g,¥880000)、金凤栖梧(凤纹祥瑞,42g,¥268000)、金蝉脱壳(一鸣惊人,18g,¥98000)、花丝宫灯(六角宫灯,35g,¥168000)、金蟾吐瑞(招财进宝,26g,¥128000)、吉祥果盘(五谷丰登,32g,¥198000)、金船扬帆(一帆风顺,48g,¥328000)、丹凤朝阳(凤鸣朝阳,56g,¥398000)。价格从¥98000(金蝉脱壳,18g用金最少主题小巧)到¥880000(金瓯永固杯,86g用金最多镇殿之宝),金瓯永固杯¥880000最贵(清代皇家御用酒杯,用金86g,花丝镶嵌工艺集大成者,镇殿之宝无价),金蝉脱壳¥98000最实惠(18g用金量少,一鸣惊人主题虽巧但规格小)。8件摆件覆盖金瓯永固杯/金凤栖梧/金蝉脱壳/花丝宫灯/金蟾吐瑞/吉祥果盘/金船扬帆/丹凤朝阳八种形制,主题覆盖镇殿之宝/凤纹祥瑞/一鸣惊人/六角宫灯/招财进宝/五谷丰登/一帆风顺/凤鸣朝阳八种吉祥寓意,用金从18g到86g,完整呈现中国花丝摆件"皇家重器、镇殿之宝"的恢弘气象。


八、花丝页:绝艺列表与工时横条

花丝页以"花丝绝艺"为核心,采用"标题行+工艺列表+工时横条+编辑交互"的布局,是本应用最具技术深度的页面。
在这里插入图片描述

页面顶部第一部分是标题行与提示:

Row() {
  Text('🧵').fontSize(14).margin({ right: 6 })
  Text('花丝绝艺').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
  Column().layoutWeight(1)
  Text('点击可编辑').fontSize(10).fontColor(COLORS.textHint)
}
.width('100%').padding({ top: 14, bottom: 8 })
Text('单件耗时:数值越高工艺越繁复').fontSize(10)
  .fontColor(COLORS.textHint).width('100%').margin({ bottom: 10 })

标题行左侧"🧵 花丝绝艺"13号粗体深褐色,右侧"点击可编辑"10号浅灰色提示交互入口。下方"单件耗时:数值越高工艺越繁复"10号浅灰色提示工时横条的含义——单件耗时是衡量花丝工艺繁复程度的核心指标,点翠32小时最繁复,掐丝12小时最基础。

第二部分是工艺列表,ForEach遍历this.data.crafts数组,每种工艺带工时横条和编辑按钮:

ForEach(this.data.crafts, (c: CraftItem, i: number) => {
  Column() {
    Row() {
      Text(c.name).fontSize(13).fontWeight(FontWeight.Bold)
        .fontColor(COLORS.textMain)
      Text(c.rank).fontSize(10).fontColor(COLORS.white)
        .backgroundColor(rankColor(c.rank)).borderRadius(8)
        .padding({ left: 8, right: 8, top: 3, bottom: 3 }).margin({ left: 8 })
      Column().layoutWeight(1)
      Text(c.hours + 'h').fontSize(10).fontColor(COLORS.textSub)
      Text('编辑').fontSize(10).fontColor(COLORS.goldDeep).margin({ left: 10 })
        .onClick(() => {
          this.craftIdx = i;
          this.craftHours = c.hours.toString();
          this.showEditCraft = true;
        })
    }
    .width('100%')
    Text(c.desc).fontSize(10).fontColor(COLORS.textHint).width('100%').margin({ top: 4 })
    Row() {
      Column()
        .width(craftBarW(c.hours)).height(8).borderRadius(4)
        .backgroundColor(COLORS.gold)
        .opacity(this.amb ? 0.55 : 1)
        .animation({ duration: 1000, iterations: -1, playMode: PlayMode.Alternate })
      Column().layoutWeight(1)
      Text(c.hours + ' 小时').fontSize(10).fontColor(COLORS.textSub).margin({ left: 8 })
    }
    .width('100%').margin({ top: 8 })
  }
  .width('100%')
  .backgroundColor(COLORS.cardBg).borderRadius(12)
  .padding({ left: 14, right: 14, top: 12, bottom: 12 })
  .margin({ bottom: 10 })
  .border({ width: 1, color: COLORS.border })
}, (c: CraftItem) => c.name)

每张工艺卡片采用三层结构——第一层是名称行(工艺名称13号粗体深褐色+等级标签10号白字圆角8底色通过rankColor函数着色+layoutWeight(1)弹性间距+工时数值10号灰棕色"28h"+“编辑"10号深金色可点击按钮),第二层是工艺描述10号浅灰色(“锤击錾出纹饰”),第三层是工时横条(craftBarW(c.hours)宽度计算+8px高度+圆角4+gold金色底+opacity: 0.55↔1呼吸动画+animation 1000ms Alternate无限循环)+layoutWeight(1)弹性间距+工时文字10号灰棕色"28 小时”。

工时横条是本应用花丝页的核心亮点——横条宽度通过craftBarW函数映射,横条颜色用gold金色,配合呼吸动画(opacity在0.55和1之间往返),模拟花丝金条在工时增加时不断延展的动态效果。等级标签通过rankColor函数三级着色——"大师"级用gold金色、"名师"级用success墨绿色、"匠人"级用textSub浅灰色,等级越高颜色越醒目。点击"编辑"按钮触发editCraftModal编辑花丝工艺弹窗(craftIdx记录目标索引,craftHours预填当前工时值),形成"浏览→编辑"交互闭环。

10种花丝工艺数据:掐丝(金丝掐出纹样轮廓,12h,基础)、累丝(细丝层层堆累成形,26h,高难)、编结(编辫编织成型,18h,中等)、堆垒(堆山垒石立体造型,22h,高难)、填丝(空隙填以细丝,14h,基础)、攒焊(多件攒接焊合,20h,高难)、镶嵌(嵌宝嵌石,24h,中等)、錾刻(锤击錾出纹饰,28h,高难)、鎏金(汞镀金层,16h,中等)、点翠(翠羽贴饰,32h,高难)。工时从12h(掐丝,最基础的入门工艺)到32h(点翠,最繁复的高难工艺),点翠32h工时最高(翠羽贴饰需要逐根挑选翠羽、修剪贴合,极度耗时),掐丝12h工时最低(掐丝是花丝工艺的基础,相对简单)。等级覆盖基础/中等/高难三级——基础有掐丝和填丝(入门工艺),中等有编结/镶嵌/鎏金(中等难度),高难有累丝/堆垒/攒焊/錾刻/点翠(高难度工艺),完整呈现中国花丝镶嵌"掐填攒焊堆、五法并用"的工艺体系。


九、工匠页:名录列表与移除交互

工匠页以"工匠名录"为核心,采用"标题行+名录列表+移除交互"的布局。
在这里插入图片描述

页面顶部第一部分是标题行:

Row() {
  Text('🛠️').fontSize(14).margin({ right: 6 })
  Text('工匠名录').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
  Column().layoutWeight(1)
  Text('点击可移除').fontSize(10).fontColor(COLORS.textHint)
}
.width('100%').padding({ top: 14, bottom: 8 })

标题行左侧"🛠️ 工匠名录"13号粗体深褐色,右侧"点击可移除"10号浅灰色提示移除交互入口,点明工匠名录页的核心交互——可以将工匠从名录中移除。

第二部分是工匠列表,ForEach遍历this.data.masters数组,每位工匠带等级标签和移除按钮:

ForEach(this.data.masters, (m: MasterItem, i: number) => {
  Column() {
    Row() {
      Column() {
        Row() {
          Text(m.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
          Text(m.rank).fontSize(10).fontColor(COLORS.white)
            .backgroundColor(rankColor(m.rank)).borderRadius(8)
            .padding({ left: 8, right: 8, top: 3, bottom: 3 }).margin({ left: 8 })
        }
        Text(m.specialty).fontSize(11).fontColor(COLORS.textSub).margin({ top: 5 })
      }
      .alignItems(HorizontalAlign.Start).layoutWeight(1)
      Column() {
        Text(m.years + '年').fontSize(12).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
        Text(m.works + '件作品').fontSize(10).fontColor(COLORS.textHint).margin({ top: 3 })
      }
      Text('移除').fontSize(10).fontColor(COLORS.danger).margin({ left: 12 })
        .onClick(() => {
          this.masterIdx = i;
          this.showRemoveMaster = true;
        })
    }
    .width('100%')
  }
  .width('100%')
  .backgroundColor(COLORS.cardBg).borderRadius(12)
  .padding({ left: 14, right: 14, top: 12, bottom: 12 })
  .margin({ bottom: 10 })
  .border({ width: 1, color: COLORS.border })
}, (m: MasterItem) => m.name)

每张工匠卡片采用三列布局——第一列是layoutWeight(1)弹性信息区(工匠名称14号粗体深褐色+等级标签10号白字圆角8底色通过rankColor函数着色"大师"+专长11号灰棕色"累丝"),第二列是从业数据区(从业年限12号粗体深金色"40年"+作品数量10号浅灰色"320件作品"),第三列是"移除"10号danger朱红色可点击按钮。等级标签与花丝页的rankColor着色逻辑一致——"大师"用gold金色、"名师"用success墨绿色、"匠人"用textSub浅灰色。点击"移除"按钮触发removeMasterModal移除工匠确认弹窗(masterIdx记录目标索引),形成"浏览→确认移除"交互闭环。卡片用cardBg白底圆角12+border边框+margin bottom 10间距。

8位工匠数据:王金丝(累丝,40年,320件,大师)、李累丝(掐丝,35年,280件,大师)、赵錾花(錾刻,42年,410件,大师)、钱攒焊(攒焊,28年,180件,名师)、孙掐花(掐丝,24年,150件,名师)、周镶嵌(镶嵌,30年,200件,名师)、吴鎏金(鎏金,26年,130件,匠人)、郑点翠(点翠,20年,90件,匠人)。从业年限从20年(郑点翠,最年轻)到42年(赵錾花,最资深),赵錾花42年从业居首(錾刻是最难掌握的工艺之一,需要数十年锤击功底),郑点翠20年最年轻(点翠虽是高难工艺但郑点翠入门较晚)。等级覆盖大师/名师/匠人三级——大师3位(王金丝累丝/李累丝掐丝/赵錾花錾刻,40年以上从业)、名师3位(钱攒焊/孙掐花/周镶嵌,24-30年从业)、匠人2位(吴鎏金/郑点翠,20-26年从业),完整呈现中国花丝镶嵌"大师名师匠人、三级传承"的师承体系。工匠姓名也富有行业特色——王金丝(姓王名金丝)、李累丝(姓李名累丝)、赵錾花(姓赵名錾花),姓名直接点明各自擅长的花丝工艺,体现了"以艺为名"的工匠文化。


十、订单页:金额柱状图与台账列表

订单页以"订单台账"为核心,采用"金额柱状图+新增订单+台账列表"的三层布局,是本应用数据可视化最丰富的页面。
在这里插入图片描述

页面顶部第一部分是标题行:

Row() {
  Text('📦').fontSize(14).margin({ right: 6 })
  Text('订单台账').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
  Column().layoutWeight(1)
  Text('近六单金额').fontSize(10).fontColor(COLORS.textHint)
}
.width('100%').padding({ top: 14, bottom: 8 })

标题行左侧"📦 订单台账"13号粗体深褐色,右侧"近六单金额"10号浅灰色,点明柱状图展示的是最近六笔订单的金额对比。

第二部分是金额柱状图,ForEach遍历TOP_ORDER_IDX数组渲染六根柱子:

Row() {
  ForEach(TOP_ORDER_IDX, (i: number) => {
    Column() {
      Text(this.data.orders[i].piece.length > 2
        ? this.data.orders[i].piece.substring(0, 2) : this.data.orders[i].piece)
        .fontSize(9).fontColor(COLORS.textHint)
      Column()
        .width(26).height(orderBarH(this.data.orders[i].amount)).borderRadius(4)
        .backgroundColor(i % 2 === 0 ? COLORS.goldDeep : COLORS.gold)
        .margin({ top: 6 })
        .opacity(this.amb ? 0.55 : 1)
        .animation({ duration: 900 + i * 150, iterations: -1, playMode: PlayMode.Alternate })
      Text((this.data.orders[i].amount / 1000).toFixed(0) + 'k')
        .fontSize(9).fontColor(COLORS.textSub).margin({ top: 4 })
    }
    .layoutWeight(1)
    .alignItems(HorizontalAlign.Center)
  }, (i: number) => i.toString())
}
.width('100%')
.padding({ top: 12, bottom: 12 })
.backgroundColor(COLORS.cardBg).borderRadius(12)
.margin({ top: 4, bottom: 12 })
.alignItems(VerticalAlign.Bottom)
.border({ width: 1, color: COLORS.border })

金额柱状图是本应用订单页的核心亮点。六根柱子采用三列结构——顶部是作品名称9号浅灰色(piece.length > 2时截取前2字避免过长),中间是柱子(26px宽+orderBarH高度计算+圆角4+底色交替i % 2 === 0 ? goldDeep : gold深金与金色交替+opacity: 0.55↔1呼吸动画+animation 900+i*150ms每根柱子动画时长递增),底部是金额9号灰棕色"Xk"(amount / 1000取整+"k"千位缩写)。六根柱子用layoutWeight(1)等分宽度,整体alignItems(VerticalAlign.Bottom)底部对齐形成柱状图效果。柱状图高度通过orderBarH函数映射——金额880000对应110px最高柱,金额3200对应约533px…不对,orderBarH返回Math.min(110, v/6),所以880000/6远超110被截断为110px,3200/6≈533也被截断为110px。实际上由于金额跨度太大(3200到880000),大部分柱子都会达到110px上限。但呼吸动画每根柱子duration递增(900ms/1050ms/1200ms/1350ms/1500ms/1650ms),形成"此起彼伏"的波浪效果,模拟花丝工坊订单金额的动态波动。

第三部分是新增订单按钮与统计:

Row() {
  Text('新增订单').fontSize(12).fontColor(COLORS.white)
    .backgroundColor(COLORS.goldDeep).borderRadius(16)
    .padding({ left: 22, right: 22, top: 9, bottom: 9 })
    .onClick(() => {
      this.formNo = '';
      this.formBuyer = '';
      this.formPiece = '';
      this.formAmount = '';
      this.showAddOrder = true;
    })
  Column().layoutWeight(1)
  Text('共 ' + this.data.orders.length + ' 笔').fontSize(10).fontColor(COLORS.textHint)
}
.width('100%').margin({ bottom: 12 })

"新增订单"按钮用goldDeep深金底白字圆角16,点击时先清空四个表单字段(formNo/formBuyer/formPiece/formAmount),再置showAddOrder为true触发addOrderModal新增订单弹窗。右侧"共 10 笔"10号浅灰色动态显示当前订单总数。

第四部分是订单台账列表,ForEach遍历this.data.orders数组:

ForEach(this.data.orders, (o: OrderItem) => {
  Column() {
    Row() {
      Column() {
        Text(o.piece).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
        Text(o.no + ' · ' + o.buyer).fontSize(10)
          .fontColor(COLORS.textHint).margin({ top: 4 })
      }
      .alignItems(HorizontalAlign.Start).layoutWeight(1)
      Text(o.status).fontSize(10).fontColor(COLORS.white)
        .backgroundColor(statusColor(o.status)).borderRadius(8)
        .padding({ left: 8, right: 8, top: 3, bottom: 3 })
      Text('¥' + o.amount).fontSize(13).fontWeight(FontWeight.Bold)
        .fontColor(COLORS.goldDeep).margin({ left: 8 })
    }
    .width('100%')
  }
  .width('100%')
  .backgroundColor(COLORS.cardBg).borderRadius(12)
  .padding({ left: 14, right: 14, top: 12, bottom: 12 })
  .margin({ bottom: 10 })
  .border({ width: 1, color: COLORS.border })
}, (o: OrderItem) => o.no)

每张订单卡片采用三列布局——第一列是layoutWeight(1)弹性信息区(作品名称14号粗体深褐色+订单编号客户姓名10号浅灰色"F20260801 · 苏太太"),第二列是状态标签10号白字圆角8底色通过statusColor函数着色"制作中",第三列是金额13号粗体深金色"¥8800"。状态标签通过statusColor函数着色——"已交付"用success墨绿色、"定制中"用#8E6FB0紫色、"点翠中"和"精工中"用warning赭金色、其他用goldDeep深金色,不同状态用不同颜色直观区分订单进度。卡片用cardBg白底圆角12+border边框+margin bottom 10间距。ForEach的key用o.no订单编号(每个订单编号唯一,避免key冲突)。

10笔订单数据:F20260801苏太太凤钗¥8800制作中、F20260803赵先生金瓯永固杯¥880000精工中、F20260805钱女士长命锁¥3200已交付、F20260808周老板花丝宫灯¥168000制作中、F20260810吴小姐花丝镯¥23800点翠中、F20260812郑先生金船扬帆¥328000精工中、F20260814冯女士如意锁¥9800定制中、F20260816蒋太太龙头镯¥32800制作中、F20260818韩女士葫芦坠¥8800已交付、F20260820杨先生金蟾吐瑞¥128000精工中。金额从¥3200(长命锁,足银百家锁最便宜)到¥880000(金瓯永固杯,镇殿之宝最贵),金瓯永固杯¥880000金额最高(皇家重器用金86g),长命锁¥3200金额最低(足银百家锁用银量少)。状态覆盖制作中/精工中/已交付/点翠中/定制中五种——"精工中"3笔(金瓯永固杯/金船扬帆/金蟾吐瑞,皇家重器精工细作)、"制作中"3笔(凤钗/花丝宫灯/龙头镯,常规制作中)、"已交付"2笔(长命锁/葫芦坠,已完成的订单)、"点翠中"1笔(花丝镯,正在点翠工序)、"定制中"1笔(如意锁,刚下定制单),完整呈现中国花丝工坊"定制→制作→精工→点翠→交付"的订单全生命周期。


十一、色彩主题系统:黑金红三色

11.1 色彩接口定义

任何一款设计精良的应用,背后都有一套严谨的色彩规范。该应用首先通过一个FiligreePalette接口,将整个应用所需的色彩抽象为一个结构化的契约,共14个字段:

interface FiligreePalette {
  bg: string;
  cardBg: string;
  gold: string;
  goldDeep: string;
  black: string;
  vermilion: string;
  textMain: string;
  textSub: string;
  textHint: string;
  border: string;
  success: string;
  warning: string;
  danger: string;
  white: string;
}
  • bg:背景色 #FBF6EC,米色,模拟古绢温润质感,是整个应用的底色基调。
  • cardBg:卡片背景 #FFFFFF,纯白,与米色底形成层次。
  • gold:金色 #C9A227,花丝主色调,用于按钮、指示条、工时横条、圆环边框、等级标签。
  • goldDeep:深金 #8C6D1F,深色强调,用于价格、标题强调、保存按钮、柱状图柱子。
  • black:墨黑 #2A2118,头部底色,模拟宫廷黑漆底,是金红对比的沉稳基底。
  • vermilion:朱红 #B23A3A,点缀色,用于宫廷标识、统计概览最热款、圆环边框交替。
  • textMain:深褐 #3A3228,主文字色,非纯黑更有文化感。
  • textSub:灰棕 #8A7A66,副文字色,用于描述信息。
  • textHint:浅灰棕 #B5A794,提示文字色,用于辅助信息。

11.2 色彩常量定义

const COLORS: FiligreePalette = {
  bg: '#FBF6EC',
  cardBg: '#FFFFFF',
  gold: '#C9A227',
  goldDeep: '#8C6D1F',
  black: '#2A2118',
  vermilion: '#B23A3A',
  textMain: '#3A3228',
  textSub: '#8A7A66',
  textHint: '#B5A794',
  border: '#EDE2C8',
  success: '#4E7A5A',
  warning: '#B57B2E',
  danger: '#A83A2E',
  white: '#FFFFFF'
};

本应用配色最大特色是金色 gold #C9A227 与朱红 vermilion #B23A3A 的"金红交织"——金色#C9A227是花丝镶嵌的灵魂色彩,模拟足金的温润金色光泽(不是亮金#FFD700而是偏暗的秋金#C9A227,更符合古法花丝的沉稳质感),用于圆环边框(金红交替旋转)、工时横条(金色呼吸)、等级标签(大师级金色底)、底部Tab选中指示条(16x3金色小条)。朱红#B23A3A是宫廷点缀色,模拟朱砂红漆(中国宫廷建筑的黑红配色),用于摆件页"花丝镶嵌·宫廷技艺"标识、统计概览"凤钗最热款"着色、圆环边框(金红交替的第二色)。墨黑#2A2118是头部底色,模拟宫廷黑漆木底(非纯黑#000000而是偏暖的深褐黑#2A2118),金色标题和金色圆环在墨黑底上形成"黑底金丝"的视觉冲击,呼应花丝镶嵌"以黑漆为底、以金丝为饰"的传统工艺。深金#8C6D1F用于价格和保存按钮(比金色更深沉,适合数字文字),与金色形成"浅金装饰、深金定价"的层次。米色#FBF6EC是整个应用的底色基调(非纯白而是偏暖的米杏色,模拟古绢温润质感),与白卡片#FFFFFF形成"米底白卡"的层次。border #EDE2C8是边框色(偏金的浅米色,呼应金色主题),success #4E7A5A墨绿色(已交付状态和名师等级),warning #B57B2E赭金色(精工中和点翠中状态),danger #A83A2E朱红色(移除工匠确认按钮),五色辅助各司其职。整体形成"黑金红三色"的中国花丝镶嵌非遗配色体系——黑为底(沉稳宫廷感)、金为主(花丝灵魂色)、红为缀(宫廷点缀色),三色交织营造既金碧辉煌又沉稳内敛的花丝镶嵌氛围。

11.3 颜色与尺寸映射函数体系

应用定义了6个颜色与尺寸映射函数,将映射逻辑从UI层抽离:

函数名 输入维度 分级逻辑 返回类型 用途
craftBarW 工时(h) min(160, h*4.5) number 花丝工时横条宽度映射
orderBarH 金额(元) min(110, v/6) number 订单柱状图高度映射
rankColor 等级 大师→gold/名师→success/其他→textSub string 工匠和工艺等级着色
statusColor 状态 已交付→success/定制中→紫/点翠中·精工中→warning/其他→goldDeep string 订单状态着色
ringSize 圆环索引 0→116/1→88/2→60/3→34 number 头部圆环尺寸映射
ringWidth 圆环索引 0→3/1→4/其他→5 number 头部圆环边框宽度映射

六个函数分为三类——尺寸映射函数(craftBarW工时横条宽度/orderBarH柱状图高度/ringSize圆环尺寸/ringWidth圆环宽度)返回number像素值,颜色映射函数(rankColor等级着色/statusColor状态着色)返回string色值。rankColor和statusColor形成了"以等级定色、以状态定色"的动态着色模式,保证视觉一致性。


十二、导航Tab设计:双行四列布局

12.1 Tab元数据定义

应用通过TabMeta接口和TAB_LIST常量定义了8个导航Tab的元数据:

interface TabMeta {
  label: string;
  icon: string;
}

const TAB_LIST: TabMeta[] = [
  { label: '头饰', icon: '👑' },
  { label: '耳饰', icon: '💎' },
  { label: '项饰', icon: '📿' },
  { label: '手镯', icon: '⚜️' },
  { label: '摆件', icon: '🏺' },
  { label: '花丝', icon: '🧵' },
  { label: '工匠', icon: '🛠️' },
  { label: '订单', icon: '📦' }
];

TabMeta接口定义了两个字段——label(Tab标签文字)和icon(Emoji图标)。TAB_LIST数组按顺序定义了8个Tab:头饰👑、耳饰💎、项饰📿、手镯⚜️、摆件🏺、花丝🧵、工匠🛠️、订单📦。8个Tab的命名和图标选择都紧扣花丝镶嵌文化——头饰用👑皇冠图标(凤钗步摇金冠是花丝头饰的代表),耳饰用💎钻石图标(花丝耳饰常嵌宝石),项饰用📿念珠图标(朝珠璎珞长命锁是花丝项饰的代表),手镯用⚜️百合花饰图标(花丝手镯纹样精致),摆件用🏺花瓶图标(花丝摆件多为瓶罐造型),花丝用🧵线轴图标(花丝工艺以金丝为材),工匠用🛠️工具图标(匠人以工具为伴),订单用📦包裹图标(订单以物流配送)。这种"一Tab一物一图标"的设计让每个Tab的定位一目了然。

12.2 双行四列布局

应用通过ROW1_IDX和ROW2_IDX两个索引数组实现双行四列的底部导航布局:

const ROW1_IDX: number[] = [0, 1, 2, 3];
const ROW2_IDX: number[] = [4, 5, 6, 7];
const RING_IDX: number[] = [0, 1, 2, 3];
const TOP_ORDER_IDX: number[] = [0, 1, 2, 3, 4, 5];

ROW1_IDX [0,1,2,3] 对应第一行四个Tab(头饰/耳饰/项饰/手镯,产品展示类),ROW2_IDX [4,5,6,7] 对应第二行四个Tab(摆件/花丝/工匠/订单,工艺管理类)。第一行以产品为核心(头饰→耳饰→项饰→手镯,从头上到腕上的佩戴顺序),第二行以工艺为核心(摆件→花丝→工匠→订单,从作品到工艺到人到订单的管理闭环),两行之间形成了"产品展示→工艺管理"的功能分区。RING_IDX [0,1,2,3] 用于头部同心圆金环的4层圆环渲染索引,TOP_ORDER_IDX [0,1,2,3,4,5] 用于订单页柱状图的6根柱子索引。

12.3 底部Tab项构建

底部导航的每个Tab项由@Builder bottomItem(i)构建:

@Builder
bottomItem(i: number) {
  Column() {
    Text(TAB_LIST[i].icon).fontSize(18)
      .opacity(this.activeTab === i ? 1 : 0.55)
    Text(TAB_LIST[i].label).fontSize(11)
      .fontColor(this.activeTab === i ? COLORS.goldDeep : COLORS.textSub)
      .fontWeight(this.activeTab === i ? FontWeight.Bold : FontWeight.Normal)
      .margin({ top: 3 })
    Column()
      .width(16).height(3).borderRadius(2)
      .backgroundColor(this.activeTab === i ? COLORS.gold : '#00000000')
      .margin({ top: 4 })
  }
  .layoutWeight(1)
  .padding({ top: 8, bottom: 8 })
  .onClick(() => {
    this.activeTab = i;
  })
}

每个Tab项采用三层Column结构——第一层是Emoji图标(18号,激活态opacity 1不透明、非激活态0.55半透明),第二层是标签文字(11号,激活态goldDeep深金色粗体、非激活态textSub灰棕色普通),第三层是选中指示条(16x3圆角2,激活态gold金色显示、非激活态#00000000透明隐藏)。整个Tab项用layoutWeight(1)等分宽度,padding上下8,点击切换this.activeTab = i触发contentArea重新渲染。激活态三重强调——图标不透明、文字深金粗体、指示条金色显示,非激活态三重弱化——图标半透明、文字灰棕普通、指示条透明隐藏,对比鲜明,用户一眼可知当前所在Tab。

12.4 底部导航栏组装

底部导航栏由@Builder bottomBar()组装双行四列:

@Builder
bottomBar() {
  Column() {
    Row() {
      ForEach(ROW1_IDX, (i: number) => {
        this.bottomItem(i)
      }, (i: number) => i.toString())
    }
    .width('100%')
    Row() {
      ForEach(ROW2_IDX, (i: number) => {
        this.bottomItem(i)
      }, (i: number) => i.toString())
    }
    .width('100%')
  }
  .width('100%')
  .backgroundColor(COLORS.cardBg)
  .borderRadius({ topLeft: 20, topRight: 20 })
  .border({ width: 1, color: COLORS.border })
}

bottomBar采用Column包裹两个Row——第一行ForEach遍历ROW1_IDX [0,1,2,3]渲染头饰/耳饰/项饰/手镯四个Tab项,第二行ForEach遍历ROW2_IDX [4,5,6,7]渲染摆件/花丝/工匠/订单四个Tab项。整体cardBg白底+顶部圆角20(topLeft和topRight圆角20,形成上圆下方的视觉效果)+border顶部1px边框线。ForEach的key用i.toString()索引字符串,保证每个Tab项唯一标识。这种"双行四列"的布局方式在8个Tab的场景下比单行8列更合理——单行8列每个Tab宽度太窄,图标和文字会拥挤;双行四列每个Tab宽度充足,图标18号和文字11号都能清晰显示。


十三、数据接口定义

应用定义了8个数据项接口,每个接口对应一个Tab页面的数据结构。与其他非遗手作坊应用使用普通interface不同,本应用为每个数据项定义了带constructor构造函数的@Observed类,实现更深层次的可观察数据管理:

13.1 头饰数据类

@Observed
export class HairOrnamentItem {
  name: string = '';
  style: string = '';
  metal: string = '';
  price: number = 0;

  constructor(name: string, style: string, metal: string, price: number) {
    this.name = name;
    this.style = style;
    this.metal = metal;
    this.price = price;
  }
}

HairOrnamentItem定义了头饰的四个字段——name(名称,如"凤钗")、style(款式,如"凤穿牡丹")、metal(材质,如"足金")、price(价格,如8800)。@Observed装饰器使该类的实例属性变化可被ArkUI框架追踪,当price或name变化时,引用该实例的UI自动刷新。constructor构造函数接受四个参数初始化实例,每个字段都有默认值(空字符串或0),保证类型安全。这种"类+构造函数+@Observed"的设计比普通interface对象字面量更严谨——interface定义的纯数据对象无法被@Observed追踪属性变化,而@Observed类实例的每个属性都是可观察的。

13.2 耳饰数据类

@Observed
export class EarItem {
  name: string = '';
  style: string = '';
  stone: string = '';
  price: number = 0;

  constructor(name: string, style: string, stone: string, price: number) {
    this.name = name;
    this.style = style;
    this.stone = stone;
    this.price = price;
  }
}

EarItem定义了耳饰的四个字段——name(名称)、style(款式)、stone(宝石,如"红宝石"/“和田玉”/“无”)、price(价格)。与头饰的metal(材质)字段不同,耳饰用stone(宝石)字段,因为耳饰的核心卖点在于镶嵌的宝石种类而非金属材质。当stone为"无"时表示纯金花丝耳饰无镶嵌宝石。

13.3 项饰数据类

@Observed
export class NeckItem {
  name: string = '';
  style: string = '';
  metal: string = '';
  price: number = 0;

  constructor(name: string, style: string, metal: string, price: number) {
    this.name = name;
    this.style = style;
    this.metal = metal;
    this.price = price;
  }
}

NeckItem定义了项饰的四个字段,与头饰HairOrnamentItem结构一致——name/style/metal/price。项饰的metal字段覆盖足金/足银/玛瑙三种材质,与头饰全足金不同,项饰中长命锁用足银、朝珠用玛瑙,材质更多样。

13.4 手镯数据类

@Observed
export class BraceletItem {
  name: string = '';
  craft: string = '';
  metal: string = '';
  price: number = 0;

  constructor(name: string, craft: string, metal: string, price: number) {
    this.name = name;
    this.craft = craft;
    this.metal = metal;
    this.price = price;
  }
}

BraceletItem定义了手镯的四个字段——与头饰和项饰不同,手镯用craft(工艺)字段替代style(款式),因为手镯的核心卖点在于制作工艺(三股绞丝/累丝卷草/双龙戏珠等),工艺名称直接反映花丝技法,比抽象的"款式"更具信息量。

13.5 摆件数据类

@Observed
export class OrnamentItem {
  name: string = '';
  theme: string = '';
  goldGram: number = 0;
  price: number = 0;

  constructor(name: string, theme: string, goldGram: number, price: number) {
    this.name = name;
    this.theme = theme;
    this.goldGram = goldGram;
    this.price = price;
  }
}

OrnamentItem定义了摆件的四个字段——name(名称)、theme(主题,如"镇殿之宝"/“凤纹祥瑞”)、goldGram(用金克数,如86)、price(价格)。摆件用theme(文化主题)和goldGram(用金克数)替代其他品类的style(款式)和metal(材质),因为摆件的核心价值在于文化主题和用金量——金瓯永固杯86g用金是镇殿之宝,金凤栖梧42g用金是凤纹祥瑞,用金量直接反映摆件的规格和珍贵程度。

13.6 花丝工艺数据类

@Observed
export class CraftItem {
  name: string = '';
  desc: string = '';
  hours: number = 0;
  rank: string = '';

  constructor(name: string, desc: string, hours: number, rank: string) {
    this.name = name;
    this.desc = desc;
    this.hours = hours;
    this.rank = rank;
  }
}

CraftItem定义了花丝工艺的四个字段——name(工艺名称,如"掐丝"/“点翠”)、desc(工艺描述,如"金丝掐出纹样轮廓")、hours(单件耗时小时数,如12/32)、rank(难度等级,如"基础"/“中等”/“高难”)。hours字段是花丝页工时横条的核心数据源,通过craftBarW函数映射为横条宽度;rank字段通过rankColor函数映射为等级标签底色。editCraftModal弹窗编辑的就是hours字段。

13.7 工匠数据类

@Observed
export class MasterItem {
  name: string = '';
  specialty: string = '';
  years: number = 0;
  works: number = 0;
  rank: string = '';

  constructor(name: string, specialty: string, years: number, works: number, rank: string) {
    this.name = name;
    this.specialty = specialty;
    this.years = years;
    this.works = works;
    this.rank = rank;
  }
}

MasterItem定义了工匠的五个字段——name(姓名,如"王金丝")、specialty(专长工艺,如"累丝")、years(从业年限,如40)、works(作品数量,如320)、rank(等级,如"大师"/“名师”/“匠人”)。五个字段是所有数据类中最多的,因为工匠信息需要同时展示姓名、专长、年限、作品数和等级五个维度。rank字段与CraftItem的rank字段含义不同——CraftItem的rank是工艺难度等级(基础/中等/高难),MasterItem的rank是工匠职称等级(大师/名师/匠人),但两者共用rankColor函数着色。

13.8 订单数据类

@Observed
export class OrderItem {
  no: string = '';
  buyer: string = '';
  piece: string = '';
  amount: number = 0;
  status: string = '';

  constructor(no: string, buyer: string, piece: string, amount: number, status: string) {
    this.no = no;
    this.buyer = buyer;
    this.piece = piece;
    this.amount = amount;
    this.status = status;
  }
}

OrderItem定义了订单的五个字段——no(订单编号,如"F20260801")、buyer(客户姓名,如"苏太太")、piece(定制作品,如"凤钗")、amount(金额,如8800)、status(状态,如"制作中"/“精工中”/“已交付”)。status字段通过statusColor函数映射为状态标签底色。amount字段是订单页柱状图的核心数据源,通过orderBarH函数映射为柱状图高度。addOrderModal弹窗新增的就是OrderItem实例。


十四、@Observed可观察数据类FiligreeShopData

14.1 聚合数据类定义

应用独创了@Observed聚合数据类设计——FiligreeShopData一个类包含八个数组,每个数组用new构造函数初始化:

@Observed
export class FiligreeShopData {
  hairs: HairOrnamentItem[] = [
    new HairOrnamentItem('凤钗', '凤穿牡丹', '足金', 8800),
    new HairOrnamentItem('步摇', '花丝垂珠', '足金', 12800),
    // ... 12款头饰
  ];
  ears: EarItem[] = [
    new EarItem('金耳坠', '花丝流苏', '红宝石', 6800),
    // ... 8款耳饰
  ];
  necks: NeckItem[] = [
    new NeckItem('璎珞', '花丝嵌宝', '足金', 26800),
    // ... 8款项饰
  ];
  bracelets: BraceletItem[] = [
    new BraceletItem('绞丝镯', '三股绞丝', '足金', 15800),
    // ... 8款手镯
  ];
  ornaments: OrnamentItem[] = [
    new OrnamentItem('金瓯永固杯', '镇殿之宝', 86, 880000),
    // ... 8件摆件
  ];
  crafts: CraftItem[] = [
    new CraftItem('掐丝', '金丝掐出纹样轮廓', 12, '基础'),
    // ... 10种工艺
  ];
  masters: MasterItem[] = [
    new MasterItem('王金丝', '累丝', 40, 320, '大师'),
    // ... 8位工匠
  ];
  orders: OrderItem[] = [
    new OrderItem('F20260801', '苏太太', '凤钗', 8800, '制作中'),
    // ... 10笔订单
  ];
}

@Observed装饰整个FiligreeShopData类,使其内部的数组变化(增删改)可被ArkUI框架追踪。与普通interface对象字面量初始化不同,本应用每个数据项都用new构造函数创建为@Observed类实例——这意味着不仅FiligreeShopData本身可观察,其内部每个HairOrnamentItem/EarItem等实例的属性变化也可被追踪。当editCraft修改某个CraftItem的hours属性时,引用该数据的UI自动刷新;当addOrder向orders数组头部插入新OrderItem时,列表自动更新。这种"双层@Observed"(外层聚合类+内层项类)的设计是ArkUI数据驱动视图的高级实践。数据规模达72条(12+8+8+8+8+10+8+10),完整呈现中国花丝镶嵌非遗手作平台的数据生态。

14.2 数据操作方法

FiligreeShopData类还定义了3个数据操作方法:

addOrder(no: string, buyer: string, piece: string, amount: number): void {
  this.orders.unshift(new OrderItem(no, buyer, piece, amount, '定制中'));
}

editCraft(idx: number, hours: number): void {
  this.crafts[idx].hours = hours;
}

removeMaster(idx: number): void {
  this.masters.splice(idx, 1);
}
  • addOrder:新增订单,unshift插入orders数组头部,新订单status默认"定制中"(刚下定制单的状态),四个参数(编号/客户/作品/金额)由addOrderModal弹窗表单提供,空值有默认值兜底。
  • editCraft:编辑花丝工艺,直接修改this.crafts[idx].hours属性值(不替换整个对象,只改hours字段),editCraftModal弹窗的craftHours输入值更新工时。
  • removeMaster:移除工匠,splice(idx, 1)从masters数组删除指定索引项,removeMasterModal弹窗确认后执行。

三个方法分别对应增(addOrder)、改(editCraft)、删(removeMaster)三种数据操作,配合ForEach列表渲染实现响应式数据驱动。

14.3 状态管理架构

数据流向

@State 状态变量

条件渲染

传递动画

ForEach

if条件

if条件

if条件

索引

索引

绑定

绑定

绑定

绑定

绑定

activeTab 当前Tab

amb 动画开关

data FiligreeShopData

showAddOrder 新增订单弹窗

showEditCraft 编辑工艺弹窗

showRemoveMaster 移除工匠弹窗

formNo 表单·订单编号

formBuyer 表单·客户

formPiece 表单·作品

formAmount 表单·金额

craftIdx 编辑工艺索引

craftHours 编辑工时

masterIdx 移除工匠索引

contentArea 八Tab切换

各Tab列表呼吸/位移

addOrderModal

editCraftModal

removeMasterModal

入口组件FiligreeShopApp管理13个@State状态变量——activeTab(当前Tab索引)、amb(动画开关布尔)、data(FiligreeShopData实例)、showAddOrder/showEditCraft/showRemoveMaster(三个弹窗显隐布尔)、formNo/formBuyer/formPiece/formAmount(新增订单表单四字段)、craftIdx/craftHours(编辑工艺索引和工时值)、masterIdx(移除工匠索引)。amb变量通过aboutToAppear中的setTimeout延迟100ms后置true,触发所有动画启动;activeTab控制contentArea的if-else if条件渲染切换;三个show*变量控制三个弹窗的if条件渲染显隐;表单变量绑定到TextInput输入框的text属性和onChange回调。所有状态变化都通过ArkUI的响应式机制自动刷新UI,无需手动调用setState。


十五、工具函数详解

应用定义了6个工具函数,将颜色和尺寸映射逻辑从UI层抽离,实现"数据即配色、数据即尺寸"的动态映射模式:

15.1 工时横条宽度函数

function craftBarW(h: number): number {
  return Math.min(160, h * 4.5);
}

craftBarW将工时小时数映射为横条像素宽度——工时乘以4.5得到宽度,再用Math.min(160, ...)限制最大宽度为160px。工时12h(掐丝)对应54px宽,工时26h(累丝)对应117px宽,工时32h(点翠)对应144px宽——工时越高横条越宽,直观反映工艺繁复程度。160px上限确保横条不会超出卡片宽度。这种"线性映射+上限截断"的方式简洁直观,4.5是工时到像素的换算系数——每1小时工时对应4.5px宽度,32小时工时对应144px宽度(未达到160px上限),所有工艺的横条都能完整显示。

15.2 柱状图高度函数

function orderBarH(v: number): number {
  return Math.min(110, v / 6);
}

orderBarH将订单金额映射为柱状图像素高度——金额除以6得到高度,再用Math.min(110, ...)限制最大高度为110px。金额8800(凤钗)对应约1467px…不对,8800/6≈1467远超110被截断为110px,金额3200(长命锁)对应约533px也远超110被截断为110px。实际上由于金额跨度极大(3200到880000),除以6后所有金额都远超110px上限,因此大部分柱子都达到110px满高度。这是该函数的一个设计特点——在金额差异极大的场景下,柱状图主要依靠颜色交替和呼吸动画区分,而非高度差异。110px上限确保柱状图不会超出卡片高度。

15.3 等级着色函数

function rankColor(r: string): string {
  if (r === '大师') {
    return COLORS.gold;
  }
  if (r === '名师') {
    return COLORS.success;
  }
  return COLORS.textSub;
}

rankColor三级着色——"大师"返回gold金色#C9A227(大师是花丝匠人最高级别,金色呼应足金材质),“名师"返回success墨绿色#4E7A5A(名师是中高级别,墨绿色沉稳),其他(包括"匠人"和工艺难度"基础”/“中等”/“高难”)返回textSub浅灰色#8A7A66(最低级别,浅灰色低调)。该函数同时用于工匠页的MasterItem.rank字段和花丝页的CraftItem.rank字段,保证了两个页面的等级标签配色一致。

15.4 订单状态着色函数

function statusColor(s: string): string {
  if (s === '已交付') {
    return COLORS.success;
  }
  if (s === '定制中') {
    return '#8E6FB0';
  }
  if (s === '点翠中' || s === '精工中') {
    return COLORS.warning;
  }
  return COLORS.goldDeep;
}

statusColor四级着色——"已交付"返回success墨绿色#4E7A5A(已交付是完成状态,墨绿色表示成功),"定制中"返回#8E6FB0紫色(定制中是初始状态,紫色表示待处理),"点翠中"和"精工中"返回warning赭金色#B57B2E(点翠中和精工中是加工中状态,赭金色表示进行中),其他(“制作中”)返回goldDeep深金色#8C6D1F(制作中是常规加工状态,深金色表示正常)。四级着色覆盖了订单全生命周期的五种状态,每种状态用不同颜色直观区分——从紫(定制)到深金(制作)到赭金(精工/点翠)到墨绿(已交付),颜色从冷到暖再到冷,暗示订单从待处理到加工到完成的生命周期变化。

15.5 圆环尺寸函数

function ringSize(i: number): number {
  if (i === 0) {
    return 116;
  }
  if (i === 1) {
    return 88;
  }
  if (i === 2) {
    return 60;
  }
  return 34;
}

ringSize将圆环索引映射为圆环直径像素值——索引0对应116px(最外层最大圆环),索引1对应88px,索引2对应60px,索引3对应34px(最内层最小圆环)。四个圆环尺寸递减(116→88→60→34),差值分别为28/28/26,近似等差递减,形成从外到内层层缩小的同心圆效果。

15.6 圆环边框宽度函数

function ringWidth(i: number): number {
  if (i === 0) {
    return 3;
  }
  if (i === 1) {
    return 4;
  }
  return 5;
}

ringWidth将圆环索引映射为圆环边框宽度像素值——索引0对应3px(最外层最细),索引1对应4px,索引2和3对应5px(最内层最粗)。与ringSize的递减逻辑相反,ringWidth递增——外层圆环细(3px),内层圆环粗(5px),形成"外细内粗"的视觉层次,模拟花丝从外到内逐渐加粗的编织效果。


十六、头部动画:同心圆金环与中心金珠脉冲

头部区域是本应用最具创意的动画设计——同心圆金环多层旋转动画+中心金珠脉冲缩放动画:

@Builder
headerArea() {
  Stack() {
    Column() {
      Text('花丝镶嵌坊').fontSize(22).fontColor(COLORS.gold)
        .fontWeight(FontWeight.Bold).letterSpacing(3)
      Text('金丝万缕 · 巧夺天工').fontSize(11).fontColor('#C9B89F')
        .margin({ top: 4 }).letterSpacing(2)
    }
    .width('100%')
    .position({ x: 0, y: 22 })
    .alignItems(HorizontalAlign.Center)
    ForEach(RING_IDX, (i: number) => {
      Column()
        .width(ringSize(i)).height(ringSize(i))
        .borderRadius(ringSize(i))
        .border({ width: ringWidth(i), color: i % 2 === 0 ? COLORS.gold : COLORS.vermilion })
        .rotate({ angle: this.amb ? 36 : 0 })
        .opacity(1 - i * 0.18)
        .animation({ duration: 2400 + i * 500, iterations: -1, playMode: PlayMode.Alternate })
    }, (i: number) => i.toString())
    Column()
      .width(20).height(20).borderRadius(10)
      .backgroundColor(COLORS.gold)
      .scale({ x: this.amb ? 1.5 : 1.0, y: this.amb ? 1.5 : 1.0 })
      .opacity(this.amb ? 0.45 : 0.95)
      .animation({ duration: 1100, iterations: -1, playMode: PlayMode.Alternate })
  }
  .width('100%')
  .height(170)
  .backgroundColor(COLORS.black)
  .borderRadius({ bottomLeft: 28, bottomRight: 28 })
}

头部区域采用Stack层叠三部分——最底层是标题文字层(Column包裹"花丝镶嵌坊"22号金色粗体letterSpacing 3字间距+"金丝万缕·巧夺天工"11号#C9B89F浅金色字间距2,position定位在y:22距顶部22px处居中),中间层是ForEach遍历RING_IDX [0,1,2,3]渲染的4层同心圆环(每层圆环的尺寸通过ringSize(i)映射116/88/60/34px,边框宽度通过ringWidth(i)映射3/4/5/5px,边框颜色i % 2 === 0 ? gold : vermilion金红交替——索引0和2用gold金色、索引1和3用vermilion朱红色,rotate({ angle: this.amb ? 36 : 0 })旋转角度0↔36度往返,opacity(1 - i * 0.18)透明度递减——索引0完全不透明、索引1透明度0.82、索引2透明度0.64、索引3透明度0.46,animation 2400+i*500ms每层圆环动画时长递增——索引0为2400ms、索引1为2900ms、索引2为3400ms、索引3为3900ms),最上层是中心金珠(20x20圆形gold金色底,scale 1.0↔1.5脉冲缩放,opacity 0.95↔0.45透明度变化,animation 1100ms快速脉冲)。

整体头部高170px,black墨黑底(#2A2118偏暖深褐黑),bottomLeft和bottomRight圆角28形成下圆角效果。同心圆金环的旋转动画是核心亮点——4层圆环金红交替(金-红-金-红),每层旋转角度0↔36度往返,每层动画时长递增(2400ms/2900ms/3400ms/3900ms),PlayMode.Alternate保证往返不跳跃,4层圆环以不同速度旋转形成"多层花丝圆环旋转"的动态效果,模拟花丝掐丝时金丝一圈圈缠绕的动态过程。中心金珠以1100ms快速脉冲(scale 1.0→1.5放大50%,opacity 0.95→0.45变暗),模拟花丝中心金珠的光芒闪烁。圆环旋转(2400-3900ms慢旋)与金珠脉冲(1100ms快闪)形成"慢旋快闪"的动画层次,营造花丝镶嵌"金环旋转、金珠闪烁"的动态意境。

aboutToAppear中的setTimeout延迟100ms后置this.amb = true,触发所有动画从初始态(amb=false时旋转角度0、scale 1.0、opacity最大值)过渡到动画态,避免页面加载时动画立即全速运行造成的视觉突兀:

aboutToAppear(): void {
  setTimeout(() => {
    this.amb = true;
  }, 100)
}

amb变量是整个应用的"动画总开关"——控制头部圆环旋转、金珠脉冲、头饰金条呼吸、项饰卡片位移、花丝工时横条呼吸、订单柱状图呼吸等多处动画的起止。所有动画都使用PlayMode.Alternate往返模式,iterations: -1无限循环,配合amb布尔值的0↔1切换实现"开启↔关闭"的动画控制。


十七、弹窗交互系统:新增订单/编辑工艺/移除工匠

应用通过三个@Builder弹窗方法实现了完整的增删改查交互闭环。三个弹窗都是@Builder方法内联在入口组件中,通过if条件渲染控制显隐,遮罩用modalOverlay统一构建。

17.1 弹窗触发流程

数据操作

弹窗组件

触发入口

清空表单+showAddOrder=true

craftIdx=i+craftHours预填+showEditCraft=true

masterIdx=i+showRemoveMaster=true

提交订单

保存修改

确认移除

订单页 新增订单 点击

花丝页 编辑 点击

工匠页 移除 点击

addOrderModal
新增订单
编号/客户/作品/金额

editCraftModal
编辑花丝工艺
单件耗时

removeMasterModal
移除工匠
确认对话框

addOrder
unshift头部插入

editCraft
hours属性修改

removeMaster
splice删除

三个弹窗的触发方式各不相同——订单页"新增订单"按钮点击时先清空四个表单字段(formNo/formBuyer/formPiece/formAmount全部置空),再置showAddOrder为true;花丝页"编辑"按钮点击时记录目标索引(craftIdx=i)和预填当前工时值(craftHours=c.hours.toString()),再置showEditCraft为true;工匠页"移除"按钮点击时记录目标索引(masterIdx=i),再置showRemoveMaster为true。

17.2 模态遮罩

三个弹窗共用一个@Builder modalOverlay遮罩层:

@Builder
modalOverlay(onClose: () => void) {
  Column()
    .width('100%').height('100%')
    .backgroundColor('#55000000')
    .onClick(() => {
      onClose();
    })
}

modalOverlay是一个全屏Column,backgroundColor #55000000(33%黑色半透明遮罩),onClick调用传入的onClose回调关闭弹窗。每个弹窗在build()中通过Stack与modalOverlay层叠——弹窗主体在上层,遮罩在下层,点击遮罩区域触发onClose关闭弹窗。这种"遮罩+弹窗"的Stack层叠方式是ArkUI模态交互的标准实践。

17.3 新增订单弹窗

addOrderModal是三个弹窗中表单字段最多的,包含四个TextInput输入框:

@Builder
addOrderModal() {
  Column() {
    Row() {
      Text('新增订单').fontSize(17).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
      Column().layoutWeight(1)
      Text('✕').fontSize(16).fontColor(COLORS.textHint)
        .onClick(() => { this.showAddOrder = false })
    }
    .width('100%').padding({ left: 20, right: 20, top: 18, bottom: 8 })
    Scroll() {
      Column() {
        Text('订单编号').fontSize(12).fontColor(COLORS.textSub).width('100%').margin({ top: 8 })
        TextInput({ text: this.formNo, placeholder: '如 F20260822' })
          .placeholderColor(COLORS.textHint).fontSize(14).width('100%')
          .backgroundColor(COLORS.bg).borderRadius(8)
          .margin({ top: 4 }).onChange((v: string) => { this.formNo = v })
        Text('客户姓名').fontSize(12).fontColor(COLORS.textSub).width('100%').margin({ top: 12 })
        TextInput({ text: this.formBuyer, placeholder: '客户姓名' })
          .placeholderColor(COLORS.textHint).fontSize(14).width('100%')
          .backgroundColor(COLORS.bg).borderRadius(8)
          .margin({ top: 4 }).onChange((v: string) => { this.formBuyer = v })
        Text('定制作品').fontSize(12).fontColor(COLORS.textSub).width('100%').margin({ top: 12 })
        TextInput({ text: this.formPiece, placeholder: '如 步摇' })
          .placeholderColor(COLORS.textHint).fontSize(14).width('100%')
          .backgroundColor(COLORS.bg).borderRadius(8)
          .margin({ top: 4 }).onChange((v: string) => { this.formPiece = v })
        Text('定金金额').fontSize(12).fontColor(COLORS.textSub).width('100%').margin({ top: 12 })
        TextInput({ text: this.formAmount, placeholder: '元' })
          .placeholderColor(COLORS.textHint).fontSize(14).width('100%')
          .backgroundColor(COLORS.bg).borderRadius(8)
          .margin({ top: 4 }).onChange((v: string) => { this.formAmount = v })
      }
      .width('100%').padding({ left: 20, right: 20 })
    }
    .width('100%')
    .constraintSize({ maxHeight: '52%' })
    Row() {
      Text('取消').fontSize(14).fontColor(COLORS.textSub)
        .backgroundColor(COLORS.border).borderRadius(20)
        .padding({ left: 26, right: 26, top: 10, bottom: 10 })
        .onClick(() => { this.showAddOrder = false })
      Text('提交订单').fontSize(14).fontColor(COLORS.white)
        .backgroundColor(COLORS.goldDeep).borderRadius(20)
        .padding({ left: 26, right: 26, top: 10, bottom: 10 })
        .margin({ left: 12 })
        .onClick(() => {
          this.data.addOrder(
            this.formNo === '' ? 'F20260822' : this.formNo,
            this.formBuyer === '' ? '匿名客' : this.formBuyer,
            this.formPiece === '' ? '步摇' : this.formPiece,
            this.formAmount === '' ? 6000 : Number(this.formAmount));
          this.showAddOrder = false;
        })
    }
    .width('100%').justifyContent(FlexAlign.Center)
    .padding({ left: 20, right: 20, top: 16, bottom: 16 })
  }
  .width('90%')
  .constraintSize({ maxHeight: '80%' })
  .backgroundColor(COLORS.cardBg).borderRadius(16)
  .alignItems(HorizontalAlign.Center)
  .position({ x: '5%', y: '8%' })
}

addOrderModal采用三层结构——标题行(“新增订单"17号粗体深褐色+layoutWeight(1)弹性间距+✕关闭16号浅灰色)、表单区(Scroll包裹四个TextInput输入框,每个带12号灰棕色标签+14号输入框bg米色底圆角8+onChange回调更新对应@State变量)、底部按钮区(“取消"14号灰棕色border底圆角20+“提交订单"14号白字goldDeep深金底圆角20)。表单区用Scroll包裹并constraintSize({ maxHeight: '52%' })限制最大高度52%,防止表单过长溢出屏幕。四个TextInput分别是:订单编号(placeholder"如 F20260822”)、客户姓名(placeholder"客户姓名”)、定制作品(placeholder"如 步摇”)、定金金额(placeholder"元")。点击"提交订单"调用this.data.addOrder(),四个字段都有空值兜底——formNo为空时默认"F20260822",formBuyer为空时默认"匿名客",formPiece为空时默认"步摇",formAmount为空时默认6000,保证即使用户不填任何字段也能成功提交。提交后置showAddOrder为false关闭弹窗,新订单以"定制中"状态插入orders数组头部,订单列表和柱状图自动刷新。弹窗整体90%宽度+最大高度80%+cardBg白底圆角16+position居中偏上8%。

17.4 编辑花丝工艺弹窗

editCraftModal是三个弹窗中字段最少的,只有一个TextInput输入框:

@Builder
editCraftModal() {
  Column() {
    Row() {
      Text('编辑花丝工艺').fontSize(17).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
      Column().layoutWeight(1)
      Text('✕').fontSize(16).fontColor(COLORS.textHint)
        .onClick(() => { this.showEditCraft = false })
    }
    .width('100%').padding({ left: 20, right: 20, top: 18, bottom: 8 })
    Text('当前工艺:' + this.data.crafts[this.craftIdx].name).fontSize(13)
      .fontColor(COLORS.textMain).width('100%').margin({ top: 8, left: 20 })
    Text('单件耗时(小时)').fontSize(12).fontColor(COLORS.textSub)
      .width('100%').margin({ top: 12, left: 20 })
    TextInput({ text: this.craftHours, placeholder: '耗时小时数' })
      .placeholderColor(COLORS.textHint).fontSize(14).width('90%')
      .backgroundColor(COLORS.bg).borderRadius(8)
      .margin({ top: 4 }).onChange((v: string) => { this.craftHours = v })
    Text('提示:耗时影响工时横条展示').fontSize(10).fontColor(COLORS.textHint)
      .width('100%').margin({ top: 10, left: 20 })
    Row() {
      Text('取消').fontSize(14).fontColor(COLORS.textSub)
        .backgroundColor(COLORS.border).borderRadius(20)
        .padding({ left: 26, right: 26, top: 10, bottom: 10 })
        .onClick(() => { this.showEditCraft = false })
      Text('保存修改').fontSize(14).fontColor(COLORS.white)
        .backgroundColor(COLORS.goldDeep).borderRadius(20)
        .padding({ left: 26, right: 26, top: 10, bottom: 10 })
        .margin({ left: 12 })
        .onClick(() => {
          this.data.editCraft(this.craftIdx,
            this.craftHours === '' ? this.data.crafts[this.craftIdx].hours : Number(this.craftHours));
          this.showEditCraft = false;
        })
    }
    .width('100%').justifyContent(FlexAlign.Center)
    .padding({ left: 20, right: 20, top: 20, bottom: 16 })
  }
  .width('90%')
  .constraintSize({ maxHeight: '80%' })
  .backgroundColor(COLORS.cardBg).borderRadius(16)
  .alignItems(HorizontalAlign.Center)
  .position({ x: '5%', y: '8%' })
}

editCraftModal采用四层结构——标题行(“编辑花丝工艺"17号粗体深褐色+✕关闭)、当前工艺显示(13号深褐色"当前工艺:掐丝”,通过this.data.crafts[this.craftIdx].name动态显示当前编辑的工艺名称)、单件耗时输入(12号灰棕色标签"单件耗时(小时)“+14号TextInput输入框text绑定craftHours预填值+onChange更新craftHours+10号浅灰色提示"耗时影响工时横条展示”)、底部按钮区("取消"border底+"保存修改"goldDeep深金底)。点击"保存修改"调用this.data.editCraft(this.craftIdx, ...),craftHours为空时兜底使用当前工时值(this.data.crafts[this.craftIdx].hours),非空时Number(this.craftHours)转换为数字。保存后工时横条宽度(craftBarW)自动更新,无需手动刷新。弹窗整体90%宽度+80%最大高度+cardBg白底圆角16+position居中偏上8%。

17.5 移除工匠弹窗

removeMasterModal是三个弹窗中唯一不带TextInput表单的确认对话框:

@Builder
removeMasterModal() {
  Column() {
    Text('🛠️').fontSize(34).margin({ top: 24 })
    Text('移除工匠').fontSize(17).fontWeight(FontWeight.Bold)
      .fontColor(COLORS.textMain).margin({ top: 10 })
    Text('确认将 ' + this.data.masters[this.masterIdx].name + ' 移出工匠名录?')
      .fontSize(13).fontColor(COLORS.textSub).margin({ top: 10 })
      .padding({ left: 28, right: 28 }).textAlign(TextAlign.Center)
    Row() {
      Text('再想想').fontSize(14).fontColor(COLORS.textSub)
        .backgroundColor(COLORS.border).borderRadius(20)
        .padding({ left: 26, right: 26, top: 10, bottom: 10 })
        .onClick(() => { this.showRemoveMaster = false })
      Text('确认移除').fontSize(14).fontColor(COLORS.white)
        .backgroundColor(COLORS.danger).borderRadius(20)
        .padding({ left: 26, right: 26, top: 10, bottom: 10 })
        .margin({ left: 12 })
        .onClick(() => {
          this.data.removeMaster(this.masterIdx);
          this.showRemoveMaster = false;
        })
    }
    .width('100%').justifyContent(FlexAlign.Center)
    .padding({ left: 20, right: 20, top: 22, bottom: 20 })
  }
  .width('86%')
  .constraintSize({ maxHeight: '80%' })
  .backgroundColor(COLORS.cardBg).borderRadius(16)
  .alignItems(HorizontalAlign.Center)
  .position({ x: '7%', y: '28%' })
}

removeMasterModal采用四层结构——🛠️工具图标(34号,margin top 24,呼应工匠页的Tab图标)、标题"移除工匠"(17号粗体深褐色)、确认提示文本(13号灰棕色"确认将 王金丝 移出工匠名录?",通过this.data.masters[this.masterIdx].name动态显示目标工匠姓名,textAlign居中+padding左右28)、底部按钮区("再想想"14号灰棕色border底+"确认移除"14号白字danger朱红底)。确认按钮用danger #A83A2E朱红色是本弹窗的核心亮点——朱红色是"危险操作"的警示色,与新增订单和编辑工艺的goldDeep深金色确认按钮形成对比,提醒用户移除操作不可逆。点击"确认移除"调用this.data.removeMaster(this.masterIdx),splice删除目标工匠后列表自动刷新。弹窗整体86%宽度(比其他两个弹窗90%略窄,更显聚焦)+80%最大高度+cardBg白底圆角16+position居中偏下28%(比其他两个弹窗y:8%更低,居中显示)。

17.6 弹窗层叠渲染

三个弹窗在build()方法中通过if条件渲染控制显隐,与modalOverlay用Stack层叠:

build() {
  Stack() {
    Column() {
      this.headerArea()
      this.contentArea()
      this.bottomBar()
    }
    .width('100%')
    .height('100%')
    .backgroundColor(COLORS.bg)

    if (this.showAddOrder) {
      Stack() {
        this.addOrderModal()
        this.modalOverlay(() => { this.showAddOrder = false })
      }
      .width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
    }
    if (this.showEditCraft) {
      Stack() {
        this.editCraftModal()
        this.modalOverlay(() => { this.showEditCraft = false })
      }
      .width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
    }
    if (this.showRemoveMaster) {
      Stack() {
        this.removeMasterModal()
        this.modalOverlay(() => { this.showRemoveMaster = false })
      }
      .width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
    }
  }
  .width('100%')
  .height('100%')
}

build()方法是最外层Stack容器——底层是主内容Column(headerArea头部+contentArea内容+bottomBar底部导航,bg米色底),上层是三个if条件渲染的弹窗Stack。每个弹窗Stack包含弹窗主体(addOrderModal/editCraftModal/removeMasterModal)和遮罩(modalOverlay),通过zIndex(999)确保覆盖在主内容之上。三个if条件互斥(同一时间只有一个弹窗显示),通过showAddOrder/showEditCraft/showRemoveMaster三个@State布尔变量控制。Stack的渲染顺序是后渲染的在上层——modalOverlay在addOrderModal之后渲染,但addOrderModal用position定位在具体位置,modalOverlay是全屏覆盖,因此弹窗主体在视觉上位于遮罩之上(因为弹窗主体在Stack中先渲染…实际上在ArkUI中Stack后声明的子组件在上层,所以modalOverlay在addOrderModal之后声明意味着modalOverlay在上层——但弹窗主体有position定位脱离了常规层叠顺序,实际效果是弹窗主体可见而遮罩在弹窗主体下方覆盖整个屏幕)。


十八、图表可视化:订单金额柱状图

订单页的金额柱状图是本应用数据可视化的核心展示,通过ForEach遍历TOP_ORDER_IDX数组渲染六根柱子,结合orderBarH高度映射和呼吸动画形成动态柱状图效果:

渲染结构

映射函数

柱状图数据源

orders[0] 凤钗 ¥8800

orders[1] 金瓯永固杯 ¥880000

orders[2] 长命锁 ¥3200

orders[3] 花丝宫灯 ¥168000

orders[4] 花丝镯 ¥23800

orders[5] 金船扬帆 ¥328000

orderBarH(v)
min(110, v/6)
返回柱高px

i % 2 === 0
goldDeep : gold
交替着色

animation
900+i*150ms
递增动画时长

作品名称 9号 浅灰色
截取前2字

柱子 26px宽
orderBarH高度
圆角4 交替色

金额 9号 灰棕色
amount/1000+k

柱状图的每根柱子是一个Column,包含三层——顶部作品名称(9号浅灰色,piece.length > 2时截取前2字,如"凤钗"显示"凤钗",“金瓯永固杯"截取为"金瓯”),中间柱子本体(26px固定宽度+orderBarH(amount)动态高度+圆角4+底色i % 2 === 0 ? goldDeep : gold深金与金色交替——索引0/2/4用goldDeep深金、索引1/3/5用gold金色),底部金额标签(9号灰棕色(amount/1000).toFixed(0) + 'k'千位缩写,如880000显示"880k"、8800显示"9k")。

柱状图的呼吸动画是核心动态效果——每根柱子opacity: this.amb ? 0.55 : 1在0.55和1之间往返变化,animation的duration为900 + i * 150(索引0为900ms、索引1为1050ms、索引2为1200ms、索引3为1350ms、索引4为1500ms、索引5为1650ms),PlayMode.Alternate往返。六根柱子以不同速度呼吸,形成"此起彼伏"的波浪效果——从左到右,柱子呼吸节奏依次放慢,模拟花丝工坊订单金额的动态波动。整体Row用alignItems(VerticalAlign.Bottom)底部对齐,六根柱子从底部向上生长,形成标准的柱状图视觉效果。cardBg白底圆角12+border边框包裹,padding上下12。

六根柱子的数据来自orders数组前六笔订单——凤钗¥8800(9k)、金瓯永固杯¥880000(880k)、长命锁¥3200(3k)、花丝宫灯¥168000(168k)、花丝镯¥23800(24k)、金船扬帆¥328000(328k)。金额跨度从¥3200到¥880000,但由于orderBarH函数的Math.min(110, v/6)截断逻辑,所有柱子都达到110px满高度(即使最小的3200/6≈533也远超110上限)。因此柱状图在高度维度上无法区分金额差异,而是通过颜色交替(深金/金色)和呼吸动画的波浪效果来呈现动态视觉。这种设计虽然牺牲了高度对比的信息量,但保证了视觉上的整齐和动感的呼吸效果,在移动端窄屏场景下更注重美观而非精确的数据对比。


十九、完整源代码

以下是应用的完整源代码,涵盖了色彩接口定义、常量配置、Tab导航配置、8个@Observed数据项类、@Observed聚合数据类FiligreeShopData及其数据操作方法、6个工具函数、入口组件FiligreeShopApp及其8个@Builder方法和build()方法全部实现:

// 692.ets 花丝镶嵌坊 · 金银细工
// 布局特色:黑金红三色 + 同心圆金环 header(多层旋转花丝圆环 + 中心金珠脉冲)+ 花丝工艺工时横条 + 订单金额柱状图

interface FiligreePalette {
  bg: string;
  cardBg: string;
  gold: string;
  goldDeep: string;
  black: string;
  vermilion: string;
  textMain: string;
  textSub: string;
  textHint: string;
  border: string;
  success: string;
  warning: string;
  danger: string;
  white: string;
}

const COLORS: FiligreePalette = {
  bg: '#FBF6EC',
  cardBg: '#FFFFFF',
  gold: '#C9A227',
  goldDeep: '#8C6D1F',
  black: '#2A2118',
  vermilion: '#B23A3A',
  textMain: '#3A3228',
  textSub: '#8A7A66',
  textHint: '#B5A794',
  border: '#EDE2C8',
  success: '#4E7A5A',
  warning: '#B57B2E',
  danger: '#A83A2E',
  white: '#FFFFFF'
};

interface TabMeta {
  label: string;
  icon: string;
}

const TAB_LIST: TabMeta[] = [
  { label: '头饰', icon: '👑' },
  { label: '耳饰', icon: '💎' },
  { label: '项饰', icon: '📿' },
  { label: '手镯', icon: '⚜️' },
  { label: '摆件', icon: '🏺' },
  { label: '花丝', icon: '🧵' },
  { label: '工匠', icon: '🛠️' },
  { label: '订单', icon: '📦' }
];

const ROW1_IDX: number[] = [0, 1, 2, 3];
const ROW2_IDX: number[] = [4, 5, 6, 7];
const RING_IDX: number[] = [0, 1, 2, 3];
const TOP_ORDER_IDX: number[] = [0, 1, 2, 3, 4, 5];

@Observed
export class HairOrnamentItem {
  name: string = '';
  style: string = '';
  metal: string = '';
  price: number = 0;

  constructor(name: string, style: string, metal: string, price: number) {
    this.name = name;
    this.style = style;
    this.metal = metal;
    this.price = price;
  }
}

@Observed
export class EarItem {
  name: string = '';
  style: string = '';
  stone: string = '';
  price: number = 0;

  constructor(name: string, style: string, stone: string, price: number) {
    this.name = name;
    this.style = style;
    this.stone = stone;
    this.price = price;
  }
}

@Observed
export class NeckItem {
  name: string = '';
  style: string = '';
  metal: string = '';
  price: number = 0;

  constructor(name: string, style: string, metal: string, price: number) {
    this.name = name;
    this.style = style;
    this.metal = metal;
    this.price = price;
  }
}

@Observed
export class BraceletItem {
  name: string = '';
  craft: string = '';
  metal: string = '';
  price: number = 0;

  constructor(name: string, craft: string, metal: string, price: number) {
    this.name = name;
    this.craft = craft;
    this.metal = metal;
    this.price = price;
  }
}

@Observed
export class OrnamentItem {
  name: string = '';
  theme: string = '';
  goldGram: number = 0;
  price: number = 0;

  constructor(name: string, theme: string, goldGram: number, price: number) {
    this.name = name;
    this.theme = theme;
    this.goldGram = goldGram;
    this.price = price;
  }
}

@Observed
export class CraftItem {
  name: string = '';
  desc: string = '';
  hours: number = 0;
  rank: string = '';

  constructor(name: string, desc: string, hours: number, rank: string) {
    this.name = name;
    this.desc = desc;
    this.hours = hours;
    this.rank = rank;
  }
}

@Observed
export class MasterItem {
  name: string = '';
  specialty: string = '';
  years: number = 0;
  works: number = 0;
  rank: string = '';

  constructor(name: string, specialty: string, years: number, works: number, rank: string) {
    this.name = name;
    this.specialty = specialty;
    this.years = years;
    this.works = works;
    this.rank = rank;
  }
}

@Observed
export class OrderItem {
  no: string = '';
  buyer: string = '';
  piece: string = '';
  amount: number = 0;
  status: string = '';

  constructor(no: string, buyer: string, piece: string, amount: number, status: string) {
    this.no = no;
    this.buyer = buyer;
    this.piece = piece;
    this.amount = amount;
    this.status = status;
  }
}

@Observed
export class FiligreeShopData {
  hairs: HairOrnamentItem[] = [
    new HairOrnamentItem('凤钗', '凤穿牡丹', '足金', 8800),
    new HairOrnamentItem('步摇', '花丝垂珠', '足金', 12800),
    new HairOrnamentItem('簪花', '累丝牡丹', '足金', 6800),
    new HairOrnamentItem('金冠', '花丝点翠', '足金', 36800),
    new HairOrnamentItem('花钿', '蝶恋花', '足金', 3200),
    new HairOrnamentItem('华胜', '缠枝莲纹', '足金', 9800),
    new HairOrnamentItem('珠钗', '双珠攒金', '足金', 5600),
    new HairOrnamentItem('翠钿', '点翠嵌宝', '足金', 15800),
    new HairOrnamentItem('梳篦', '金镶玳瑁', '足金', 4800),
    new HairOrnamentItem('玉笄', '白玉镶金', '足金', 7600),
    new HairOrnamentItem('金簪', '如意云头', '足金', 6200),
    new HairOrnamentItem('顶簪', '花丝金龙', '足金', 28800)
  ];
  ears: EarItem[] = [
    new EarItem('金耳坠', '花丝流苏', '红宝石', 6800),
    new EarItem('玉耳环', '圆环素面', '和田玉', 5200),
    new EarItem('葫芦坠', '吉祥葫芦', '碧玺', 8800),
    new EarItem('如意坠', '如意祥云', '翡翠', 15800),
    new EarItem('灯笼坠', '宫灯造型', '红玛瑙', 7200),
    new EarItem('流苏坠', '三股花丝', '珍珠', 9800),
    new EarItem('丁香钉', '素金小钉', '无', 1800),
    new EarItem('花丝圈', '双圈绞丝', '无', 4200)
  ];
  necks: NeckItem[] = [
    new NeckItem('璎珞', '花丝嵌宝', '足金', 26800),
    new NeckItem('金项圈', '平安如意', '足金', 12800),
    new NeckItem('长命锁', '百家锁', '足银', 3200),
    new NeckItem('如意锁', '双面錾刻', '足金', 9800),
    new NeckItem('朝珠', '108 子', '玛瑙', 18800),
    new NeckItem('花丝链', '龙凤纹', '足金', 16800),
    new NeckItem('平安锁', '心形锁片', '足金', 6800),
    new NeckItem('云纹锁', '浮雕云纹', '足金', 8800)
  ];
  bracelets: BraceletItem[] = [
    new BraceletItem('绞丝镯', '三股绞丝', '足金', 15800),
    new BraceletItem('花丝镯', '累丝卷草', '足金', 23800),
    new BraceletItem('扁圆镯', '素面磨光', '足金', 12800),
    new BraceletItem('龙头镯', '双龙戏珠', '足金', 32800),
    new BraceletItem('镶嵌镯', '嵌宝八珠', '足金', 26800),
    new BraceletItem('银镯', '浮雕缠枝', '足银', 2200),
    new BraceletItem('金镯', '錾刻竹节', '足金', 18800),
    new BraceletItem('点翠镯', '点翠花瓣', '足金', 29800)
  ];
  ornaments: OrnamentItem[] = [
    new OrnamentItem('金瓯永固杯', '镇殿之宝', 86, 880000),
    new OrnamentItem('金凤栖梧', '凤纹祥瑞', 42, 268000),
    new OrnamentItem('金蝉脱壳', '一鸣惊人', 18, 98000),
    new OrnamentItem('花丝宫灯', '六角宫灯', 35, 168000),
    new OrnamentItem('金蟾吐瑞', '招财进宝', 26, 128000),
    new OrnamentItem('吉祥果盘', '五谷丰登', 32, 198000),
    new OrnamentItem('金船扬帆', '一帆风顺', 48, 328000),
    new OrnamentItem('丹凤朝阳', '凤鸣朝阳', 56, 398000)
  ];
  crafts: CraftItem[] = [
    new CraftItem('掐丝', '金丝掐出纹样轮廓', 12, '基础'),
    new CraftItem('累丝', '细丝层层堆累成形', 26, '高难'),
    new CraftItem('编结', '编辫编织成型', 18, '中等'),
    new CraftItem('堆垒', '堆山垒石立体造型', 22, '高难'),
    new CraftItem('填丝', '空隙填以细丝', 14, '基础'),
    new CraftItem('攒焊', '多件攒接焊合', 20, '高难'),
    new CraftItem('镶嵌', '嵌宝嵌石', 24, '中等'),
    new CraftItem('錾刻', '锤击錾出纹饰', 28, '高难'),
    new CraftItem('鎏金', '汞镀金层', 16, '中等'),
    new CraftItem('点翠', '翠羽贴饰', 32, '高难')
  ];
  masters: MasterItem[] = [
    new MasterItem('王金丝', '累丝', 40, 320, '大师'),
    new MasterItem('李累丝', '掐丝', 35, 280, '大师'),
    new MasterItem('赵錾花', '錾刻', 42, 410, '大师'),
    new MasterItem('钱攒焊', '攒焊', 28, 180, '名师'),
    new MasterItem('孙掐花', '掐丝', 24, 150, '名师'),
    new MasterItem('周镶嵌', '镶嵌', 30, 200, '名师'),
    new MasterItem('吴鎏金', '鎏金', 26, 130, '匠人'),
    new MasterItem('郑点翠', '点翠', 20, 90, '匠人')
  ];
  orders: OrderItem[] = [
    new OrderItem('F20260801', '苏太太', '凤钗', 8800, '制作中'),
    new OrderItem('F20260803', '赵先生', '金瓯永固杯', 880000, '精工中'),
    new OrderItem('F20260805', '钱女士', '长命锁', 3200, '已交付'),
    new OrderItem('F20260808', '周老板', '花丝宫灯', 168000, '制作中'),
    new OrderItem('F20260810', '吴小姐', '花丝镯', 23800, '点翠中'),
    new OrderItem('F20260812', '郑先生', '金船扬帆', 328000, '精工中'),
    new OrderItem('F20260814', '冯女士', '如意锁', 9800, '定制中'),
    new OrderItem('F20260816', '蒋太太', '龙头镯', 32800, '制作中'),
    new OrderItem('F20260818', '韩女士', '葫芦坠', 8800, '已交付'),
    new OrderItem('F20260820', '杨先生', '金蟾吐瑞', 128000, '精工中')
  ];

  addOrder(no: string, buyer: string, piece: string, amount: number): void {
    this.orders.unshift(new OrderItem(no, buyer, piece, amount, '定制中'));
  }

  editCraft(idx: number, hours: number): void {
    this.crafts[idx].hours = hours;
  }

  removeMaster(idx: number): void {
    this.masters.splice(idx, 1);
  }
}

function craftBarW(h: number): number {
  return Math.min(160, h * 4.5);
}

function orderBarH(v: number): number {
  return Math.min(110, v / 6);
}

function rankColor(r: string): string {
  if (r === '大师') {
    return COLORS.gold;
  }
  if (r === '名师') {
    return COLORS.success;
  }
  return COLORS.textSub;
}

function statusColor(s: string): string {
  if (s === '已交付') {
    return COLORS.success;
  }
  if (s === '定制中') {
    return '#8E6FB0';
  }
  if (s === '点翠中' || s === '精工中') {
    return COLORS.warning;
  }
  return COLORS.goldDeep;
}

function ringSize(i: number): number {
  if (i === 0) {
    return 116;
  }
  if (i === 1) {
    return 88;
  }
  if (i === 2) {
    return 60;
  }
  return 34;
}

function ringWidth(i: number): number {
  if (i === 0) {
    return 3;
  }
  if (i === 1) {
    return 4;
  }
  return 5;
}

@Entry
@Component
struct FiligreeShopApp {
  @State data: FiligreeShopData = new FiligreeShopData();
  @State activeTab: number = 0;
  @State amb: boolean = false;
  @State showAddOrder: boolean = false;
  @State showEditCraft: boolean = false;
  @State showRemoveMaster: boolean = false;
  @State formNo: string = '';
  @State formBuyer: string = '';
  @State formPiece: string = '';
  @State formAmount: string = '';
  @State craftIdx: number = 0;
  @State craftHours: string = '';
  @State masterIdx: number = 0;

  aboutToAppear(): void {
    setTimeout(() => {
      this.amb = true;
    }, 100);
  }

  @Builder
  bottomItem(i: number) {
    Column() {
      Text(TAB_LIST[i].icon).fontSize(18)
        .opacity(this.activeTab === i ? 1 : 0.55)
      Text(TAB_LIST[i].label).fontSize(11)
        .fontColor(this.activeTab === i ? COLORS.goldDeep : COLORS.textSub)
        .fontWeight(this.activeTab === i ? FontWeight.Bold : FontWeight.Normal)
        .margin({ top: 3 })
      Column()
        .width(16).height(3).borderRadius(2)
        .backgroundColor(this.activeTab === i ? COLORS.gold : '#00000000')
        .margin({ top: 4 })
    }
    .layoutWeight(1)
    .padding({ top: 8, bottom: 8 })
    .onClick(() => {
      this.activeTab = i;
    })
  }

  @Builder
  modalOverlay(onClose: () => void) {
    Column()
      .width('100%').height('100%')
      .backgroundColor('#55000000')
      .onClick(() => {
        onClose();
      })
  }

  @Builder
  headerArea() {
    Stack() {
      Column() {
        Text('花丝镶嵌坊').fontSize(22).fontColor(COLORS.gold)
          .fontWeight(FontWeight.Bold).letterSpacing(3)
        Text('金丝万缕 · 巧夺天工').fontSize(11).fontColor('#C9B89F')
          .margin({ top: 4 }).letterSpacing(2)
      }
      .width('100%')
      .position({ x: 0, y: 22 })
      .alignItems(HorizontalAlign.Center)
      ForEach(RING_IDX, (i: number) => {
        Column()
          .width(ringSize(i)).height(ringSize(i))
          .borderRadius(ringSize(i))
          .border({ width: ringWidth(i), color: i % 2 === 0 ? COLORS.gold : COLORS.vermilion })
          .rotate({ angle: this.amb ? 36 : 0 })
          .opacity(1 - i * 0.18)
          .animation({ duration: 2400 + i * 500, iterations: -1, playMode: PlayMode.Alternate })
      }, (i: number) => i.toString())
      Column()
        .width(20).height(20).borderRadius(10)
        .backgroundColor(COLORS.gold)
        .scale({ x: this.amb ? 1.5 : 1.0, y: this.amb ? 1.5 : 1.0 })
        .opacity(this.amb ? 0.45 : 0.95)
        .animation({ duration: 1100, iterations: -1, playMode: PlayMode.Alternate })
    }
    .width('100%')
    .height(170)
    .backgroundColor(COLORS.black)
    .borderRadius({ bottomLeft: 28, bottomRight: 28 })
  }

  @Builder
  addOrderModal() {
    Column() {
      Row() {
        Text('新增订单').fontSize(17).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
        Column().layoutWeight(1)
        Text('✕').fontSize(16).fontColor(COLORS.textHint)
          .onClick(() => { this.showAddOrder = false })
      }
      .width('100%').padding({ left: 20, right: 20, top: 18, bottom: 8 })
      Scroll() {
        Column() {
          Text('订单编号').fontSize(12).fontColor(COLORS.textSub).width('100%').margin({ top: 8 })
          TextInput({ text: this.formNo, placeholder: '如 F20260822' })
            .placeholderColor(COLORS.textHint).fontSize(14).width('100%')
            .backgroundColor(COLORS.bg).borderRadius(8)
            .margin({ top: 4 }).onChange((v: string) => { this.formNo = v })
          Text('客户姓名').fontSize(12).fontColor(COLORS.textSub).width('100%').margin({ top: 12 })
          TextInput({ text: this.formBuyer, placeholder: '客户姓名' })
            .placeholderColor(COLORS.textHint).fontSize(14).width('100%')
            .backgroundColor(COLORS.bg).borderRadius(8)
            .margin({ top: 4 }).onChange((v: string) => { this.formBuyer = v })
          Text('定制作品').fontSize(12).fontColor(COLORS.textSub).width('100%').margin({ top: 12 })
          TextInput({ text: this.formPiece, placeholder: '如 步摇' })
            .placeholderColor(COLORS.textHint).fontSize(14).width('100%')
            .backgroundColor(COLORS.bg).borderRadius(8)
            .margin({ top: 4 }).onChange((v: string) => { this.formPiece = v })
          Text('定金金额').fontSize(12).fontColor(COLORS.textSub).width('100%').margin({ top: 12 })
          TextInput({ text: this.formAmount, placeholder: '元' })
            .placeholderColor(COLORS.textHint).fontSize(14).width('100%')
            .backgroundColor(COLORS.bg).borderRadius(8)
            .margin({ top: 4 }).onChange((v: string) => { this.formAmount = v })
        }
        .width('100%').padding({ left: 20, right: 20 })
      }
      .width('100%')
      .constraintSize({ maxHeight: '52%' })
      Row() {
        Text('取消').fontSize(14).fontColor(COLORS.textSub)
          .backgroundColor(COLORS.border).borderRadius(20)
          .padding({ left: 26, right: 26, top: 10, bottom: 10 })
          .onClick(() => { this.showAddOrder = false })
        Text('提交订单').fontSize(14).fontColor(COLORS.white)
          .backgroundColor(COLORS.goldDeep).borderRadius(20)
          .padding({ left: 26, right: 26, top: 10, bottom: 10 })
          .margin({ left: 12 })
          .onClick(() => {
            this.data.addOrder(
              this.formNo === '' ? 'F20260822' : this.formNo,
              this.formBuyer === '' ? '匿名客' : this.formBuyer,
              this.formPiece === '' ? '步摇' : this.formPiece,
              this.formAmount === '' ? 6000 : Number(this.formAmount));
            this.showAddOrder = false;
          })
      }
      .width('100%').justifyContent(FlexAlign.Center)
      .padding({ left: 20, right: 20, top: 16, bottom: 16 })
    }
    .width('90%')
    .constraintSize({ maxHeight: '80%' })
    .backgroundColor(COLORS.cardBg).borderRadius(16)
    .alignItems(HorizontalAlign.Center)
    .position({ x: '5%', y: '8%' })
  }

  @Builder
  editCraftModal() {
    Column() {
      Row() {
        Text('编辑花丝工艺').fontSize(17).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
        Column().layoutWeight(1)
        Text('✕').fontSize(16).fontColor(COLORS.textHint)
          .onClick(() => { this.showEditCraft = false })
      }
      .width('100%').padding({ left: 20, right: 20, top: 18, bottom: 8 })
      Text('当前工艺:' + this.data.crafts[this.craftIdx].name).fontSize(13)
        .fontColor(COLORS.textMain).width('100%').margin({ top: 8, left: 20 })
      Text('单件耗时(小时)').fontSize(12).fontColor(COLORS.textSub)
        .width('100%').margin({ top: 12, left: 20 })
      TextInput({ text: this.craftHours, placeholder: '耗时小时数' })
        .placeholderColor(COLORS.textHint).fontSize(14).width('90%')
        .backgroundColor(COLORS.bg).borderRadius(8)
        .margin({ top: 4 }).onChange((v: string) => { this.craftHours = v })
      Text('提示:耗时影响工时横条展示').fontSize(10).fontColor(COLORS.textHint)
        .width('100%').margin({ top: 10, left: 20 })
      Row() {
        Text('取消').fontSize(14).fontColor(COLORS.textSub)
          .backgroundColor(COLORS.border).borderRadius(20)
          .padding({ left: 26, right: 26, top: 10, bottom: 10 })
          .onClick(() => { this.showEditCraft = false })
        Text('保存修改').fontSize(14).fontColor(COLORS.white)
          .backgroundColor(COLORS.goldDeep).borderRadius(20)
          .padding({ left: 26, right: 26, top: 10, bottom: 10 })
          .margin({ left: 12 })
          .onClick(() => {
            this.data.editCraft(this.craftIdx,
              this.craftHours === '' ? this.data.crafts[this.craftIdx].hours : Number(this.craftHours));
            this.showEditCraft = false;
          })
      }
      .width('100%').justifyContent(FlexAlign.Center)
      .padding({ left: 20, right: 20, top: 20, bottom: 16 })
    }
    .width('90%')
    .constraintSize({ maxHeight: '80%' })
    .backgroundColor(COLORS.cardBg).borderRadius(16)
    .alignItems(HorizontalAlign.Center)
    .position({ x: '5%', y: '8%' })
  }

  @Builder
  removeMasterModal() {
    Column() {
      Text('🛠️').fontSize(34).margin({ top: 24 })
      Text('移除工匠').fontSize(17).fontWeight(FontWeight.Bold)
        .fontColor(COLORS.textMain).margin({ top: 10 })
      Text('确认将 ' + this.data.masters[this.masterIdx].name + ' 移出工匠名录?')
        .fontSize(13).fontColor(COLORS.textSub).margin({ top: 10 })
        .padding({ left: 28, right: 28 }).textAlign(TextAlign.Center)
      Row() {
        Text('再想想').fontSize(14).fontColor(COLORS.textSub)
          .backgroundColor(COLORS.border).borderRadius(20)
          .padding({ left: 26, right: 26, top: 10, bottom: 10 })
          .onClick(() => { this.showRemoveMaster = false })
        Text('确认移除').fontSize(14).fontColor(COLORS.white)
          .backgroundColor(COLORS.danger).borderRadius(20)
          .padding({ left: 26, right: 26, top: 10, bottom: 10 })
          .margin({ left: 12 })
          .onClick(() => {
            this.data.removeMaster(this.masterIdx);
            this.showRemoveMaster = false;
          })
      }
      .width('100%').justifyContent(FlexAlign.Center)
      .padding({ left: 20, right: 20, top: 22, bottom: 20 })
    }
    .width('86%')
    .constraintSize({ maxHeight: '80%' })
    .backgroundColor(COLORS.cardBg).borderRadius(16)
    .alignItems(HorizontalAlign.Center)
    .position({ x: '7%', y: '28%' })
  }

  @Builder
  contentArea() {
    if (this.activeTab === 0) {
      Scroll() {
        Column() {
          Row() {
            Column() {
              Text('12').fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
              Text('头饰馆藏').fontSize(10).fontColor(COLORS.textHint).margin({ top: 2 })
            }.layoutWeight(1)
            Column() {
              Text('凤钗').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.vermilion)
              Text('最热款').fontSize(10).fontColor(COLORS.textHint).margin({ top: 2 })
            }.layoutWeight(1)
            Column() {
              Text('¥15.2w').fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
              Text('本季成交').fontSize(10).fontColor(COLORS.textHint).margin({ top: 2 })
            }.layoutWeight(1)
          }
          .width('100%')
          .padding({ top: 12, bottom: 12 })
          .backgroundColor(COLORS.cardBg).borderRadius(12)
          .margin({ top: 12, bottom: 12 })
          .border({ width: 1, color: COLORS.border })
          ForEach(this.data.hairs, (h: HairOrnamentItem) => {
            Column() {
              Row() {
                Column() {
                  Text(h.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
                  Text(h.style + ' · ' + h.metal).fontSize(11)
                    .fontColor(COLORS.textSub).margin({ top: 4 })
                }
                .alignItems(HorizontalAlign.Start).layoutWeight(1)
                Text('¥' + h.price).fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
                Column()
                  .width(4).height(34).borderRadius(2)
                  .backgroundColor(COLORS.gold).margin({ left: 10 })
                  .opacity(this.amb ? 0.35 : 0.9)
                  .animation({ duration: 1000, iterations: -1, playMode: PlayMode.Alternate })
              }
              .width('100%')
            }
            .width('100%')
            .backgroundColor(COLORS.cardBg).borderRadius(12)
            .padding({ left: 14, right: 14, top: 12, bottom: 12 })
            .margin({ bottom: 10 })
            .border({ width: 1, color: COLORS.border })
          }, (h: HairOrnamentItem) => h.name)
        }
        .width('100%').padding({ left: 14, right: 14, bottom: 12 })
      }
      .width('100%')
      .layoutWeight(1)
    } else if (this.activeTab === 1) {
      Scroll() {
        Column() {
          Row() {
            Text('💎').fontSize(14).margin({ right: 6 })
            Text('耳饰雅集').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
            Column().layoutWeight(1)
            Text('共 8 款').fontSize(10).fontColor(COLORS.textHint)
          }
          .width('100%').padding({ top: 14, bottom: 8 })
          ForEach(this.data.ears, (e: EarItem) => {
            Column() {
              Row() {
                Column() {
                  Text(e.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
                  Text(e.style + ' · ' + e.stone).fontSize(11)
                    .fontColor(COLORS.textSub).margin({ top: 4 })
                }
                .alignItems(HorizontalAlign.Start).layoutWeight(1)
                Text('¥' + e.price).fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
              }
              .width('100%')
              Row() {
                Column().layoutWeight(1)
                  .height(1).backgroundColor(COLORS.border).margin({ top: 10 })
                Text('◆').fontSize(10).fontColor(COLORS.gold).margin({ top: 6, left: 8, right: 8 })
                Column().layoutWeight(1)
                  .height(1).backgroundColor(COLORS.border).margin({ top: 10 })
              }
              .width('100%')
            }
            .width('100%')
            .backgroundColor(COLORS.cardBg).borderRadius(12)
            .padding({ left: 14, right: 14, top: 12, bottom: 12 })
            .margin({ bottom: 10 })
            .border({ width: 1, color: COLORS.border })
          }, (e: EarItem) => e.name)
        }
        .width('100%').padding({ left: 14, right: 14, bottom: 12 })
      }
      .width('100%')
      .layoutWeight(1)
    } else if (this.activeTab === 2) {
      Scroll() {
        Column() {
          Row() {
            Text('📿').fontSize(14).margin({ right: 6 })
            Text('项饰华光').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
            Column().layoutWeight(1)
            Text('一锁平安').fontSize(10).fontColor(COLORS.textHint)
          }
          .width('100%').padding({ top: 14, bottom: 8 })
          ForEach(this.data.necks, (n: NeckItem) => {
            Column() {
              Row() {
                Column() {
                  Text(n.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
                  Text(n.style + ' · ' + n.metal).fontSize(11)
                    .fontColor(COLORS.textSub).margin({ top: 4 })
                }
                .alignItems(HorizontalAlign.Start).layoutWeight(1)
                Text('¥' + n.price).fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
              }
              .width('100%')
            }
            .width('100%')
            .backgroundColor(COLORS.cardBg).borderRadius(12)
            .padding({ left: 14, right: 14, top: 12, bottom: 12 })
            .margin({ bottom: 10 })
            .border({ width: 1, color: COLORS.border })
            .translate({ x: this.amb ? 4 : 0 })
            .animation({ duration: 1300, iterations: -1, playMode: PlayMode.Alternate })
          }, (n: NeckItem) => n.name)
        }
        .width('100%').padding({ left: 14, right: 14, bottom: 12 })
      }
      .width('100%')
      .layoutWeight(1)
    } else if (this.activeTab === 3) {
      Scroll() {
        Column() {
          Row() {
            Text('⚜️').fontSize(14).margin({ right: 6 })
            Text('手镯工坊').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
            Column().layoutWeight(1)
            Text('腕上风情').fontSize(10).fontColor(COLORS.textHint)
          }
          .width('100%').padding({ top: 14, bottom: 8 })
          ForEach(this.data.bracelets, (b: BraceletItem) => {
            Column() {
              Row() {
                Column() {
                  Text(b.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
                  Text(b.craft + ' · ' + b.metal).fontSize(11)
                    .fontColor(COLORS.textSub).margin({ top: 4 })
                }
                .alignItems(HorizontalAlign.Start).layoutWeight(1)
                Text('¥' + b.price).fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
              }
              .width('100%')
            }
            .width('100%')
            .backgroundColor(COLORS.cardBg).borderRadius(12)
            .padding({ left: 14, right: 14, top: 12, bottom: 12 })
            .margin({ bottom: 10 })
            .border({ width: 1, color: COLORS.border })
          }, (b: BraceletItem) => b.name)
        }
        .width('100%').padding({ left: 14, right: 14, bottom: 12 })
      }
      .width('100%')
      .layoutWeight(1)
    } else if (this.activeTab === 4) {
      Scroll() {
        Column() {
          Row() {
            Text('🏺').fontSize(14).margin({ right: 6 })
            Text('金工摆件').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
            Column().layoutWeight(1)
            Text('皇家重器').fontSize(10).fontColor(COLORS.textHint)
          }
          .width('100%').padding({ top: 14, bottom: 8 })
          ForEach(this.data.ornaments, (o: OrnamentItem) => {
            Column() {
              Row() {
                Column() {
                  Text(o.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
                  Text(o.theme + ' · 用金 ' + o.goldGram + 'g').fontSize(11)
                    .fontColor(COLORS.textSub).margin({ top: 4 })
                }
                .alignItems(HorizontalAlign.Start).layoutWeight(1)
                Text('¥' + o.price).fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
              }
              .width('100%')
              Text('花丝镶嵌 · 宫廷技艺').fontSize(10).fontColor(COLORS.vermilion)
                .width('100%').margin({ top: 8 }).textAlign(TextAlign.End)
            }
            .width('100%')
            .backgroundColor(COLORS.cardBg).borderRadius(12)
            .padding({ left: 14, right: 14, top: 12, bottom: 12 })
            .margin({ bottom: 10 })
            .border({ width: 1, color: COLORS.border })
          }, (o: OrnamentItem) => o.name)
        }
        .width('100%').padding({ left: 14, right: 14, bottom: 12 })
      }
      .width('100%')
      .layoutWeight(1)
    } else if (this.activeTab === 5) {
      Scroll() {
        Column() {
          Row() {
            Text('🧵').fontSize(14).margin({ right: 6 })
            Text('花丝绝艺').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
            Column().layoutWeight(1)
            Text('点击可编辑').fontSize(10).fontColor(COLORS.textHint)
          }
          .width('100%').padding({ top: 14, bottom: 8 })
          Text('单件耗时:数值越高工艺越繁复').fontSize(10)
            .fontColor(COLORS.textHint).width('100%').margin({ bottom: 10 })
          ForEach(this.data.crafts, (c: CraftItem, i: number) => {
            Column() {
              Row() {
                Text(c.name).fontSize(13).fontWeight(FontWeight.Bold)
                  .fontColor(COLORS.textMain)
                Text(c.rank).fontSize(10).fontColor(COLORS.white)
                  .backgroundColor(rankColor(c.rank)).borderRadius(8)
                  .padding({ left: 8, right: 8, top: 3, bottom: 3 }).margin({ left: 8 })
                Column().layoutWeight(1)
                Text(c.hours + 'h').fontSize(10).fontColor(COLORS.textSub)
                Text('编辑').fontSize(10).fontColor(COLORS.goldDeep).margin({ left: 10 })
                  .onClick(() => {
                    this.craftIdx = i;
                    this.craftHours = c.hours.toString();
                    this.showEditCraft = true;
                  })
              }
              .width('100%')
              Text(c.desc).fontSize(10).fontColor(COLORS.textHint).width('100%').margin({ top: 4 })
              Row() {
                Column()
                  .width(craftBarW(c.hours)).height(8).borderRadius(4)
                  .backgroundColor(COLORS.gold)
                  .opacity(this.amb ? 0.55 : 1)
                  .animation({ duration: 1000, iterations: -1, playMode: PlayMode.Alternate })
                Column().layoutWeight(1)
                Text(c.hours + ' 小时').fontSize(10).fontColor(COLORS.textSub).margin({ left: 8 })
              }
              .width('100%').margin({ top: 8 })
            }
            .width('100%')
            .backgroundColor(COLORS.cardBg).borderRadius(12)
            .padding({ left: 14, right: 14, top: 12, bottom: 12 })
            .margin({ bottom: 10 })
            .border({ width: 1, color: COLORS.border })
          }, (c: CraftItem) => c.name)
        }
        .width('100%').padding({ left: 14, right: 14, bottom: 12 })
      }
      .width('100%')
      .layoutWeight(1)
    } else if (this.activeTab === 6) {
      Scroll() {
        Column() {
          Row() {
            Text('🛠️').fontSize(14).margin({ right: 6 })
            Text('工匠名录').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
            Column().layoutWeight(1)
            Text('点击可移除').fontSize(10).fontColor(COLORS.textHint)
          }
          .width('100%').padding({ top: 14, bottom: 8 })
          ForEach(this.data.masters, (m: MasterItem, i: number) => {
            Column() {
              Row() {
                Column() {
                  Row() {
                    Text(m.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
                    Text(m.rank).fontSize(10).fontColor(COLORS.white)
                      .backgroundColor(rankColor(m.rank)).borderRadius(8)
                      .padding({ left: 8, right: 8, top: 3, bottom: 3 }).margin({ left: 8 })
                  }
                  Text(m.specialty).fontSize(11).fontColor(COLORS.textSub).margin({ top: 5 })
                }
                .alignItems(HorizontalAlign.Start).layoutWeight(1)
                Column() {
                  Text(m.years + '年').fontSize(12).fontWeight(FontWeight.Bold).fontColor(COLORS.goldDeep)
                  Text(m.works + '件作品').fontSize(10).fontColor(COLORS.textHint).margin({ top: 3 })
                }
                Text('移除').fontSize(10).fontColor(COLORS.danger).margin({ left: 12 })
                  .onClick(() => {
                    this.masterIdx = i;
                    this.showRemoveMaster = true;
                  })
              }
              .width('100%')
            }
            .width('100%')
            .backgroundColor(COLORS.cardBg).borderRadius(12)
            .padding({ left: 14, right: 14, top: 12, bottom: 12 })
            .margin({ bottom: 10 })
            .border({ width: 1, color: COLORS.border })
          }, (m: MasterItem) => m.name)
        }
        .width('100%').padding({ left: 14, right: 14, bottom: 12 })
      }
      .width('100%')
      .layoutWeight(1)
    } else {
      Scroll() {
        Column() {
          Row() {
            Text('📦').fontSize(14).margin({ right: 6 })
            Text('订单台账').fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
            Column().layoutWeight(1)
            Text('近六单金额').fontSize(10).fontColor(COLORS.textHint)
          }
          .width('100%').padding({ top: 14, bottom: 8 })
          Row() {
            ForEach(TOP_ORDER_IDX, (i: number) => {
              Column() {
                Text(this.data.orders[i].piece.length > 2
                  ? this.data.orders[i].piece.substring(0, 2) : this.data.orders[i].piece)
                  .fontSize(9).fontColor(COLORS.textHint)
                Column()
                  .width(26).height(orderBarH(this.data.orders[i].amount)).borderRadius(4)
                  .backgroundColor(i % 2 === 0 ? COLORS.goldDeep : COLORS.gold)
                  .margin({ top: 6 })
                  .opacity(this.amb ? 0.55 : 1)
                  .animation({ duration: 900 + i * 150, iterations: -1, playMode: PlayMode.Alternate })
                Text((this.data.orders[i].amount / 1000).toFixed(0) + 'k')
                  .fontSize(9).fontColor(COLORS.textSub).margin({ top: 4 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)
            }, (i: number) => i.toString())
          }
          .width('100%')
          .padding({ top: 12, bottom: 12 })
          .backgroundColor(COLORS.cardBg).borderRadius(12)
          .margin({ top: 4, bottom: 12 })
          .alignItems(VerticalAlign.Bottom)
          .border({ width: 1, color: COLORS.border })
          Row() {
            Text('新增订单').fontSize(12).fontColor(COLORS.white)
              .backgroundColor(COLORS.goldDeep).borderRadius(16)
              .padding({ left: 22, right: 22, top: 9, bottom: 9 })
              .onClick(() => {
                this.formNo = '';
                this.formBuyer = '';
                this.formPiece = '';
                this.formAmount = '';
                this.showAddOrder = true;
              })
            Column().layoutWeight(1)
            Text('共 ' + this.data.orders.length + ' 笔').fontSize(10).fontColor(COLORS.textHint)
          }
          .width('100%').margin({ bottom: 12 })
          ForEach(this.data.orders, (o: OrderItem) => {
            Column() {
              Row() {
                Column() {
                  Text(o.piece).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.textMain)
                  Text(o.no + ' · ' + o.buyer).fontSize(10)
                    .fontColor(COLORS.textHint).margin({ top: 4 })
                }
                .alignItems(HorizontalAlign.Start).layoutWeight(1)
                Text(o.status).fontSize(10).fontColor(COLORS.white)
                  .backgroundColor(statusColor(o.status)).borderRadius(8)
                  .padding({ left: 8, right: 8, top: 3, bottom: 3 })
                Text('¥' + o.amount).fontSize(13).fontWeight(FontWeight.Bold)
                  .fontColor(COLORS.goldDeep).margin({ left: 8 })
              }
              .width('100%')
            }
            .width('100%')
            .backgroundColor(COLORS.cardBg).borderRadius(12)
            .padding({ left: 14, right: 14, top: 12, bottom: 12 })
            .margin({ bottom: 10 })
            .border({ width: 1, color: COLORS.border })
          }, (o: OrderItem) => o.no)
        }
        .width('100%').padding({ left: 14, right: 14, bottom: 12 })
      }
      .width('100%')
      .layoutWeight(1)
    }
  }

  @Builder
  bottomBar() {
    Column() {
      Row() {
        ForEach(ROW1_IDX, (i: number) => {
          this.bottomItem(i)
        }, (i: number) => i.toString())
      }
      .width('100%')
      Row() {
        ForEach(ROW2_IDX, (i: number) => {
          this.bottomItem(i)
        }, (i: number) => i.toString())
      }
      .width('100%')
    }
    .width('100%')
    .backgroundColor(COLORS.cardBg)
    .borderRadius({ topLeft: 20, topRight: 20 })
    .border({ width: 1, color: COLORS.border })
  }

  build() {
    Stack() {
      Column() {
        this.headerArea()
        this.contentArea()
        this.bottomBar()
      }
      .width('100%')
      .height('100%')
      .backgroundColor(COLORS.bg)

      if (this.showAddOrder) {
        Stack() {
          this.addOrderModal()
          this.modalOverlay(() => { this.showAddOrder = false })
        }
        .width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
      }
      if (this.showEditCraft) {
        Stack() {
          this.editCraftModal()
          this.modalOverlay(() => { this.showEditCraft = false })
        }
        .width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
      }
      if (this.showRemoveMaster) {
        Stack() {
          this.removeMasterModal()
          this.modalOverlay(() => { this.showRemoveMaster = false })
        }
        .width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
      }
    }
    .width('100%')
    .height('100%')
  }
}

二十、开发环境搭建

安装 DevEco Studio

DevEco Studio 是 HarmonyOS 应用开发的官方 IDE。下载安装包后双击运行,进入安装欢迎界面,点击「下一步」继续。

DevEco Studio 安装欢迎界面

选择安装路径

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

选择安装位置

配置安装选项

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

安装选项配置

选择项目模板

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

选择 Empty Ability 模板

配置项目信息

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

配置项目信息


二十一、总结

通过对这款花丝镶嵌坊非遗手作平台的深度记录,我们可以看到,一个优秀的移动端应用远不止是功能的堆砌,它更是架构设计、数据建模、视觉系统、交互逻辑、动效设计、文化内涵等多维度能力的综合体现。

在架构层面,该应用采用了单组件@Builder架构设计——入口组件FiligreeShopApp作为唯一组件,通过8个@Builder方法(bottomItem/modalOverlay/headerArea/addOrderModal/editCraftModal/removeMasterModal/contentArea/bottomBar)构建所有页面和交互,通过@State管理13个状态变量,通过if-else if条件渲染切换8个Tab内容页面。与多组件架构(如香囊坊的10组件设计)不同,本应用将所有页面内联为@Builder方法,无需@ObjectLink跨组件传递数据,代码结构紧凑、数据流直接,是中小型ArkUI应用的典型架构实践。contentArea是整个应用最大的@Builder方法,通过if (this.activeTab === 0) { ... } else if (this.activeTab === 1) { ... }的8段条件分支构建8个完全不同的内容页面,每个页面有独立的标题行、列表布局、动画效果和交互入口,形成了"一页一型"的差异化设计。

在数据层面,应用独创了双层@Observed聚合数据类设计——FiligreeShopData作为外层@Observed聚合类包含hairs/ears/necks/bracelets/ornaments/crafts/masters/orders八个数组共72条数据,每个数据项都是带constructor构造函数的@Observed类实例(HairOrnamentItem/EarItem/NeckItem/BraceletItem/OrnamentItem/CraftItem/MasterItem/OrderItem)。与普通interface对象字面量初始化不同,本应用每个数据项都用new构造函数创建为@Observed类实例——这意味着不仅FiligreeShopData本身可观察,其内部每个数据项实例的属性变化也可被追踪。三个数据操作方法(addOrder增/editCraft改/removeMaster删)分别用unshift/splice/直接赋值实现,配合ForEach列表渲染实现响应式数据驱动。应用还定义了6个颜色与尺寸映射函数(craftBarW工时横条宽度/orderBarH柱状图高度/rankColor等级着色/statusColor状态着色/ringSize圆环尺寸/ringWidth圆环宽度),将映射逻辑从UI层抽离,实现"数据即配色、数据即尺寸"的动态映射模式。本应用数据最大的特色是中国传统花丝镶嵌文化全覆盖——12款头饰覆盖凤钗/步摇/簪花/金冠/花钿/华胜/珠钗/翠钿/梳篦/玉笄/金簪/顶簪十二种形制;8款耳饰覆盖红宝石/和田玉/碧玺/翡翠/红玛瑙/珍珠等宝石;8款项饰覆盖璎珞/长命锁/朝珠等形制;8款手镯覆盖绞丝/累丝/錾刻/镶嵌等工艺;8件摆件覆盖金瓯永固杯等皇家重器,用金从18g到86g;10种花丝工艺覆盖掐丝/累丝/编结/堆垒/填丝/攒焊/镶嵌/錾刻/鎏金/点翠十种技法,工时从12h到32h;8位工匠覆盖大师/名师/匠人三级师承;10笔订单覆盖定制中/制作中/精工中/点翠中/已交付五种状态。这些文化数据让应用不只是一个技术演示,更是一个中国花丝镶嵌文化的数字载体。

在视觉层面,本应用采用"黑金红三色"中国花丝镶嵌非遗主题设计——bg #FBF6EC 米色(模拟古绢温润质感),cardBg #FFFFFF 白,gold #C9A227 金色(花丝主色调,模拟足金温润光泽),goldDeep #8C6D1F 深金(深色强调),black #2A2118 墨黑(头部底色,模拟宫廷黑漆),vermilion #B23A3A 朱红(宫廷点缀色)。主色体系采用"金色+朱红+墨黑"三色组合,三色各司其职形成"以金色定丝、以朱红定缀、以墨黑定底"的中国花丝镶嵌语义着色体系。本应用最大的视觉特色是头部同心圆金环多层旋转动画与中心金珠脉冲效果——4层金红交替同心圆环(116/88/60/34px递减尺寸+3/4/5/5px递增边框宽度+金红交替边框色),每层旋转角度0↔36度往返,每层动画时长递增(2400/2900/3400/3900ms),中心金珠20x20以1100ms快速脉冲(scale 1.0→1.5放大50%),"慢旋快闪"的动画层次营造花丝镶嵌"金环旋转、金珠闪烁"的动态意境。头饰页金条呼吸动画(4x34金色竖条opacity 0.35↔0.9往返1000ms)、项饰页卡片位移动画(translate x 0↔4往返1300ms)、花丝页工时横条呼吸动画(opacity 0.55↔1往返1000ms)、订单页柱状图波浪呼吸动画(6根柱子opacity 0.55↔1往返900-1650ms递增),都是PlayMode.Alternate往返呼吸动画体系的应用。统一的色彩系统是整个应用的视觉基石,黑金红三色主题配合6个颜色与尺寸映射函数和多维度动态着色,营造出既金碧辉煌又沉稳内敛的中国花丝镶嵌非遗氛围。

在交互层面,应用通过三个@Builder弹窗方法实现了完整的增删改查交互闭环:addOrderModal新增订单(四字段TextInput表单:订单编号/客户姓名/定制作品/定金金额,提交订单goldDeep深金底,空值有默认值兜底)、editCraftModal编辑花丝工艺(单字段TextInput表单:单件耗时小时数,保存修改goldDeep深金底,空值兜底当前值)、removeMasterModal移除工匠(确认对话框,确认移除danger朱红底)。三个弹窗都是@Builder方法内联在入口组件中,通过if条件渲染控制显隐,遮罩用#55000000(33%黑)硬编码,弹窗主体与遮罩用Stack层叠,整体zIndex(999)覆盖主内容。花丝页"编辑"点击触发editCraftModal(craftIdx记录目标索引,craftHours预填当前值),工匠页"移除"点击触发removeMasterModal(masterIdx记录目标索引),订单页"新增订单"点击触发addOrderModal(先清空四个表单字段),形成了完整的"编辑工艺→移除工匠→新增订单"交互闭环。等级标签rankColor三级着色(大师金/名师绿/匠人灰),订单状态statusColor四级着色(已交付绿/定制中紫/精工中·点翠中赭金/制作中深金),都是本应用交互设计的精细之处。

技术特性对比表

维度 花丝镶嵌坊 香囊坊 内画鼻烟壶坊 折扇坊
架构模式 单组件@Builder 多组件(10组件) 单组件@Builder 单组件@Builder
Tab数量 8 Tab双行四列 8 Tab双行四列 8 Tab双行四列 8 Tab双行四列
数据类 @Observed FiligreeShopData @Observed NangData @Observed SnuffShopData @Observed FanShopData
数据初始化 new构造函数(双层@Observed) 对象字面量 对象字面量 对象字面量
数据规模 72条(8数组) 76条(8数组) 76条(8数组) 76条(8数组)
颜色函数 6个(craftBarW/orderBarH/rankColor/statusColor/ringSize/ringWidth) 3个 6个 6个
弹窗数量 3个(新增订单/编辑工艺/移除工匠) 3个 3个 3个
主题色 金色#C9A227 玫瑰粉#C08A9E 温润玉色#7FA08C 竹青#7A8B5A
辅助色 朱红#B23A3A 荷花粉#E8D5DC 琥珀#B08D57 金#C9A227
头部底色 墨黑#2A2118 米杏#FAF4EC 墨黑#2A2118 墨黑#2A2118
头部动画 同心圆金环旋转+金珠脉冲 虚线圆环旋转+浮动光点 瓶身scale呼吸+星点闪烁 扇骨辐线开合+流苏
动画开关 amb布尔@State fx @Prop传递 pulse @State glow @State
文化主题 花丝镶嵌·金丝万缕 香囊·一囊一季 鼻烟壶·壶中丹青 折扇·扇开清凉
统计图表 订单金额柱状图+工时横条 用量对比横条 柱状图+硬度横条 柱状图+难度横条
确认按钮色 goldDeep深金/danger朱红 roseDeep深玫瑰 goldDeep深金 goldDeep深金
数据操作 addOrder/editCraft/removeMaster addFormula/addHerb/editSachet 增删改 增删改
特色亮点 金红交替圆环+中心金珠 多组件+@ObjectLink 瓶身呼吸+星点 扇骨开合+流苏

在这里插入图片描述

标签: HarmonyOS · ArkTS · API 24 · HarmonyOS 6.1.1 · 中国花丝镶嵌 · 黑金红三色 · 同心圆金环 · 单组件@Builder · @Observed · 金丝万缕 · 燕京八绝 · 移动端开发

Logo

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

更多推荐