云霞灿烂,织就天衣。南京云锦,中国传统丝制工艺品中的瑰宝,距今已有一千六百多年历史,与成都蜀锦、苏州宋锦、广西壮锦并称"中国四大名锦",并因其丰富的文化和科技内涵被称作"寸锦寸金"。2009年南京云锦织造技艺入选联合国人类非物质文化遗产代表作名录。本文完整记录一个基于鸿蒙 HarmonyOS API 24 与 ArkTS 声明式框架构建的「云锦坊」非遗手作平台,从鎏金朱红的色彩主题,到深金头部🪡translate位移与🌈旋转120度交互动画,再到12点鎏金圆朱红竖线靛蓝圆云锦装饰纹、5Tab差异化布局(云锦本周销量柱状图+云锦陈列/织机云锦TOP6+织机库织造精度/丝线织造工艺热度+丝线坊/订单纹样构成+订单台账/客评口碑列表)、多组件11组件架构、@ObjectLink数据共享、@Observed聚合数据类、5个颜色分级函数、十六进制透明度拼接、鎏金gold与丝白silkWhite装饰、layoutWeight动态横向进度条、云锦图标区丝白圆纹样色边、四大弹窗(定制云锦/修改订单/撤下丝线/丝线详情),附完整源码与运行效果截图。


一、引言:云霞灿烂,织就天衣

南京云锦,中国传统丝制工艺品中的瑰宝,因其美如天上云霞而得名。云锦的历史可追溯至东晋义熙十三年(公元417年),距今已有一千六百多年历史。南京云锦与成都蜀锦、苏州宋锦、广西壮锦并称"中国四大名锦",并因其丰富的文化和科技内涵被称作"寸锦寸金"。2006年南京云锦木机妆花手工织造技艺列入第一批国家级非物质文化遗产名录,2009年入选联合国人类非物质文化遗产代表作名录。

云锦织造工艺极为复杂,其中"通经断纬"是云锦最核心的织造技法——经线贯通整个织物,纬线则根据纹样需要分段挖花,使得云锦可以在同一经面上织出多种不同的色彩和纹样,形成"逐花异色"的独特效果。"妆花"是云锦中最具代表性的品种,采用"通经断纬、挖花盘织"技法,色彩丰富、纹样华丽,是中国传统丝织工艺的巅峰之作。"织金锦"则是在云锦中大量使用金线,使得织物金光闪闪、华贵无比。龙纹、凤穿牡丹、缠枝莲、云鹤、海水江崖、万寿如意、团龙补子、百子图……每一种云锦纹样背后都藏着深厚的文化寓意。

然而,随着现代生活节奏加快,南京云锦这门古老的织造技艺正面临传承困境——年轻人不了解云锦文化,手工织锦匠人缺乏展示和销售渠道,云锦产品标准化程度低、丝线不透明、织造工艺混乱,正成为南京云锦非遗传承的现实痛点。一个优秀的云锦手作平台,不仅需要覆盖从"云锦陈列"到"织机库"再到"丝线坊"的完整产品展示闭环,还要在订单管理、客户评价、匠人评级等维度做到精准而高效。它需要以云锦陈列为核心入口,横向扩展出织机、丝线、订单、客评五大功能模块,每个模块采用完全不同的布局风格,形成一个完整闭环的南京云锦非遗手作生态。

正是在这样的背景下,云锦坊类非遗手作平台应运而生,并迅速成为汉服爱好者和中老年圈层的文化入口。一个优秀的云锦应用,需要以云锦陈列为核心入口,横向扩展出织机、丝线、订单、客评五大功能模块,每个模块采用完全不同的布局风格——云锦=本周销量柱状图+云锦陈列列表、织机=云锦销量TOP6横向进度条+织机库织造精度列表、丝线=织造工艺热度横向进度条+丝线坊列表、订单=纹样构成横向进度条+订单台账列表、客评=客评口碑列表,形成了一个完整闭环的南京云锦非遗手作生态。它不仅是一个功能完备的演示项目,更是一份典型的 ArkUI 工程实践范本,涵盖了多组件架构设计、@ObjectLink数据共享、@Observed聚合数据类、颜色分级函数、十六进制透明度拼接、鎏金gold与丝白silkWhite装饰、头部translate位移动画、奇偶行交替条纹、子组件封装等关键技术点。

云锦坊应用整体运行效果 - 深金头部云锦坊标题与12点鎏金圆朱红竖线靛蓝圆云锦装饰纹及本周销量柱状图云锦陈列列表


二、应用整体架构概览

整个应用采用了"多组件架构 + @ObjectLink数据共享 + 配置驱动 + 子组件封装"的四层设计模式。入口组件 BrocadeApp 作为整个应用的根容器,负责管理当前激活的 Tab 状态、四个弹窗的显隐状态、头部交互动画状态以及聚合数据对象。与单组件内@Builder构建所有页面的架构不同,本应用将整个应用拆分为 11 个独立组件:1 个入口组件 BrocadeApp + 1 个标签子组件 BrocadeTag + 5 个内容子组件(BrocadeContent云锦/LoomContent织机/ThreadContent丝线/BrocadeOrderContent订单/BrocadeReviewContent客评)+ 4 个弹窗子组件(AddBrocadeModal定制云锦/EditBrocadeOrderModal修改订单/DeleteThreadModal撤下丝线/DetailLoomModal丝线详情)。每个子组件独立封装,职责单一,通过 @ObjectLink 共享聚合数据,通过回调函数(onAdd/onDel/onDetail/onEdit/onClose)实现父子组件通信,是本应用工程化设计的最大亮点,也是 ArkUI 多组件架构的典型实践。

应用一共划分为五个一级页面:云锦(BROCADE)、织机(LOOM)、丝线(THREAD)、订单(ORDER)以及客评(REVIEW)。底部 tabBar 采用非遗设计——5个Tab(云锦🧵/织机🪵/丝线🪡/订单📦/客评⭐)单排布局,激活态文字 t.color 加粗 + 背景 t.color+‘14’(10%透明色底)圆角10 + border 1px t.color,非激活态 textHint 灰字 + 透明背景,tabBar 顶部 3px accent 朱红条。五个Tab每个布局完全不同——云锦采用本周销量柱状图+云锦陈列列表(12款云锦,纹样分级着色,奇偶行交替,图标装饰区丝白圆纹样色边)、织机采用云锦销量TOP6横向进度条+织机库列表(12种织机,织造精度进度条,🗑️点击删除弹窗)、丝线采用织造工艺热度横向进度条+丝线坊列表(12种丝线,👁️点击详情弹窗)、订单采用纹样构成横向进度条+订单台账列表(12订单,金额分级着色,✏️点击编辑弹窗)、客评采用客评口碑列表(12条评价,⭐星星repeat,标签分级着色),这种"一页一型"的差异化设计最大程度还原了真实南京云锦非遗手作平台的交互体验。

在数据层面,应用独创了**@Observed聚合数据类**设计——BrocadeData 一个类包含 brocades/looms/threads/orders/reviews 五个数组,每个数组 12 条数据,用对象字面量初始化(非 new 构造函数),@Observed 装饰整个类,配合 @ObjectLink 在子组件间共享引用,实现"一个数据对象贯穿所有页面"的聚合数据模式。这种设计将所有业务数据集中管理,子组件通过 @ObjectLink data: BrocadeData 接收共享引用,修改数据时所有引用该数据的子组件自动响应式刷新,是 ArkUI 跨组件数据共享的高级实践。应用还定义了 9 个元数据接口(BrocadeItem/LoomItem/ThreadItem/BrocadeOrderItem/BrocadeReviewItem/WeekBrocadeMeta/StyleShareMeta/BrocadeHotMeta/BrocadeTopMeta)和 4 个统计常量数组(WEEK_SOLD 7天销量/CLOUD_SHARE 4种纹样构成/BROCADE_HOT 6种织造工艺热度/BROCADE_TOP 6款云锦销量排行),将统计数据与业务数据分离,实现配置驱动设计。

在颜色逻辑层面,应用定义了 5 个颜色分级函数(getStyleColor纹样4类/getLoomColor精度3级/getThreadColor价格4级/getOrderColor金额4级/getReviewTagColor标签5类),将颜色映射逻辑从 UI 层抽离,每个函数根据不同维度(纹样/精度/价格/金额/标签)返回对应的颜色值,实现"数据即配色"的动态着色模式。特别的是,本应用大量使用十六进制透明度拼接技巧——color + 十六进制透明度后缀('14’10%/'40’25%/'55’33%/'66’40%/'80’50%/'0D’5%/'33’20%/'2E’18%),用十六进制透明度后缀实现半透明背景和边框,无需额外定义半透明颜色常量,是 ArkUI 颜色处理的实用技巧。本应用配色最大特色是鎏金 gold #C9A24B 与丝白 silkWhite #F7F3E9——鎏金用于装饰圆(12x12鎏金圆带2px丝白边,模拟金线团花),丝白用于装饰圆边框(模拟丝质光泽),与米黄底 bg #F6F1E7 形成"鎏金朱红米黄"的南京云锦经典配色,是本应用视觉设计的灵魂。这种将所有色彩集中管理、配合5个颜色分级函数和十六进制透明度拼接与鎏金丝白装饰与多维度动态着色的做法保证了全局一致性,降低了维护成本。

在交互层面,应用通过四个独立弹窗子组件实现了完整的增删改查交互闭环:AddBrocadeModal 定制云锦(品名/纹样/预算三行展示,确认织造 primary 鎏金底)、EditBrocadeOrderModal 修改订单(客户/数量900→1000/金额60万,保存修改)、DeleteThreadModal 撤下丝线(@Prop title 传入丝线名,确认撤下 danger 红底)、DetailLoomModal 丝线详情(@Prop name 传入丝线名,大图标+蚕丝描述+丝源+匠人评级,知道了按钮)。四个弹窗各自独立组件,通过 @Prop 接收参数(DeleteThreadModal的title、DetailLoomModal的name),通过 onClose 回调关闭,遮罩用 #66000000(40%黑)硬编码。云锦页➕点击触发 AddBrocadeModal,织机页🗑️点击触发 DeleteThreadModal(delThreadName 记录丝线名),丝线页👁️点击触发 DetailLoomModal(detailLoomName 记录丝线名),订单页✏️点击触发 EditBrocadeOrderModal,形成了完整的"定制云锦→撤下丝线→查看丝线→修改订单"交互闭环。

云锦坊丝线详情弹窗运行效果 - 弹窗叠加丝线页显示大图标丝线名蚕丝描述及丝源匠人评级

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


三、云锦页:本周销量柱状图与云锦陈列

云锦页以"云锦陈列"为核心,采用"本周销量柱状图+云锦陈列列表"的两层布局。

云锦模块 - 本周云锦销量柱状图周六66匹峰值与12款云锦陈列列表纹样分级着色及丝白圆装饰

页面顶部第一部分是本周销量卡,Column 包裹,padding 14,cardBg 白底,圆角16(云锦主题用更小的圆角),border 1px accent+‘55’(33%透明朱红):

  • 装饰行:Row,10x10鎏金圆(gold,圆角5,带2px silkWhite丝白边,模拟金线团花)+ layoutWeight(1) 2px accent+'55’横线 + 6x6靛蓝圆(success,圆角3)+ 10x10朱红圆(accent,圆角5,带2px silkWhite丝白边)+ layoutWeight(1) 2px横线 + 10x10鎏金圆带丝白边,鎏金朱红靛蓝三色交替装饰分隔线。
  • 标题行:Row,"📈 本周云锦销量"textPrimary深褐字13号加粗 + "周六66匹"accent朱红字9号。
  • 柱状图行:Row,ForEach WEEK_SOLD 7天,每天 Column layoutWeight 1 alignItems Center:
    • 销量数字:w.value + ‘’,8号,fontColor w.value>=60 ? accent朱红 : primary鎏金(周末≥60用朱红高亮)。
    • 柱子:Column width 16,height = w.value(直接用数值当高度!最大66px),backgroundColor w.value>=60 ? accent朱红 : primary鎏金,圆角8,margin top 4。
    • 星期:w.day textSecondary灰字9号,margin top 4。
  • 柱状图容器:width 100%,height 150,alignItems VerticalAlign.Bottom(柱子从底部生长),margin top 10。

7 天销量数据:周一26/周二32/周三41/周四48/周五59/周六66(峰值,accent朱红高亮!)/周日60(也≥60,accent朱红高亮)。柱子高度直接用 w.value 数值(最大66px),非比例映射,是本应用柱状图设计的特色——数值即高度,简单直观。南京云锦是高端手工丝织品,周销量26-66匹,与之前象棋周销量42-95副(大众普及)形成对比,体现南京云锦"高端小众"的产品定位。周末(周六66/周日60)用 accent 朱红色高亮,工作日用 primary 鎏金色,形成"周末高峰朱红/工作日鎏金"的视觉区分,周六66匹峰值是本周最高点。

第二部分是云锦陈列,Column,margin top 12:

  • 标题行:Row,"🧵 云锦陈列"textPrimary深褐字13号加粗 + "点击➕织造"textHint灰字9号 + "➕"14号,点击触发 onAdd 回调(showAddBrocade=true)。
  • 云锦列表:ForEach this.data.brocades 12款,每款 Row,padding 10,backgroundColor i%2===0 ? cardBg白 : cardAlt浅米黄(奇偶行交替条纹!)圆角16(云锦主题用更小的圆角),border 1px line,margin top 8:
    • 图标装饰区:Column width 60,padding 上下10/6,backgroundColor getStyleColor(p.style)+‘0D’(5%透明色底),圆角16,border 1px getStyleColor(p.style)+‘33’:
      • 圆形图标:Column width 44 height 44,justifyContent Center,alignItems Center,backgroundColor getStyleColor(p.style)+‘14’(10%透明色底),圆角16(非22!云锦主题用更小的圆角),border 1px getStyleColor(p.style)+‘66’,Text p.emoji 18号居中。
      • 丝白圆纹样色边装饰:Row width 30 justifyContent Center margin top 5,14x14 silkWhite丝白圆(圆角7,带2px getStyleColor(p.style)纹样色边框,模拟云锦团花轮廓)+ 8x8 getStyleColor(p.style)纹样色小圆(圆角4,margin left 4,模拟云锦纹样点)。
    • 信息区:Column layoutWeight 1 alignItems Start margin left 10:
      • 名称价格行:Row width 100% justifyContent SpaceBetween,p.name textPrimary深褐字13号加粗 + “¥”+p.price getThreadColor(p.price)色10号加粗 margin left 8。
      • 标签行:Row width 100% justifyContent Start margin top 6,BrocadeTag({text: p.style, color: getStyleColor(p.style)})(纹样标签,纹样分级色)+ BrocadeTag({text: p.thread, color: success靛蓝})(丝线标签,靛蓝固定色)+ "寸锦寸金"textHint灰字9号 margin left 8。

12 款云锦:龙纹云锦(团花,真丝,¥680,🐉)、凤穿牡丹(龙凤,桑蚕丝,¥880,🦚)、缠枝莲纹(缠枝,真丝,¥520,🌸)、云鹤纹袍(云纹,桑蚕丝,¥980,🕊️)、海水江崖(团花,真丝,¥760,🌊)、万寿如意(缠枝,桑蚕丝,¥620,🎐)、妆花缎(龙凤,真丝,¥1280,👑)、织金锦(云纹,金丝,¥1580,✨)、团龙补子(团花,桑蚕丝,¥1180,🐲)、百子图(云纹,真丝,¥1880,🧒)、四合如意(缠枝,桑蚕丝,¥560,🍀)、云锦围巾(团花,真丝,¥480,🧣)。价格从¥480(云锦围巾)到¥1880(百子图),getThreadColor 四级着色(≥1200 accent朱红/≥800 primary鎏金/≥500 success靛蓝/其他 primaryDark深金),¥1880百子图/¥1580织金锦/¥1280妆花缎用 accent 朱红色(≥1200最高端,百子图是云锦经典人物纹样,织金锦大量用金华贵无比,妆花缎是云锦代表品种),¥980云鹤纹袍/¥880凤穿牡丹用 primary 鎏金色(≥800中高端),¥760海水江崖/¥680龙纹云锦/¥620万寿如意/¥560四合如意用 success 靛蓝色(≥500中端),¥520缠枝莲纹/¥480云锦围巾用 primaryDark 深金色(其他入门级),形成"以价格定颜色"的动态着色模式。奇偶行交替(i%2=0白/i%2=1浅米黄)营造列表条纹效果,图标装饰区的丝白圆纹样色边(14x14丝白圆带2px纹样色边框模拟云锦团花轮廓+8x8纹样色小圆模拟云锦纹样点)是本应用云锦陈列列表设计的核心亮点——丝白圆带纹样色边模拟云锦团花的丝质光泽和纹样轮廓,与南京云锦"寸锦寸金"的华贵气质高度契合。BrocadeTag 子组件封装标签样式(9号字+color+'14’背景+color+'40’边框+圆角10),实现标签组件复用。纹样标签用 getStyleColor 纹样分级着色(团花→鎏金/龙凤→朱红/缠枝→靛蓝/云纹→深金),丝线标签用 success 靛蓝固定色,"寸锦寸金"标注云锦价值,完整呈现云锦的文化信息。12款都是南京云锦经典纹样(龙纹/凤穿牡丹/缠枝莲/云鹤/海水江崖/万寿如意/妆花缎/织金锦/团龙补子/百子图/四合如意/云锦围巾),文化含量极高,是本应用区别于之前主题的最大特色。


四、织机页:云锦销量TOP6与织机库织造精度

织机页以"传统织机"为核心,采用"云锦销量TOP6横向进度条+织机库织造精度列表"的两层布局。

织机模块 - 云锦销量TOP6横向进度条龙纹云锦居首与12种织机库列表织造精度进度条

页面顶部第一部分是云锦销量TOP6卡,Column 包裹,padding 14,cardBg 白底,圆角16,border 1px accent+‘55’:

  • 标题行:Row,"🏆 云锦销量 TOP6"textPrimary深褐字13号加粗 + "龙纹云锦居首"textHint灰字9号。
  • TOP6列表:ForEach BROCADE_TOP 6款,每款 Row width 100% margin top 6:
    • 名称:t.name textPrimary深褐字10号,width 90。
    • 横向进度条:Row layoutWeight 1,Column layoutWeight(t.sold) height 12 backgroundColor t.color 圆角6(已售部分,动态layoutWeight!)+ Column layoutWeight(100-t.sold) height 12 backgroundColor line 圆角6(未售部分)。
    • 销量:t.sold+‘匹’ t.color色9号,width 40 textAlign End。

6 款云锦销量排行:龙纹云锦97匹(primary鎏金)、凤穿牡丹92匹(accent朱红)、妆花缎88匹(success靛蓝)、织金锦85匹(primaryLight浅金)、海水江崖81匹(warning赭石)、云鹤纹袍78匹(primaryDark深金)。横向进度条用 layoutWeight 动态分配(t.sold 和 100-t.sold),非固定宽度非百分比,是 ArkUI 特有的弹性布局进度条实现——已售部分占 t.sold 份,未售部分占 100-t.sold 份,总份数100,自动按比例分配宽度。龙纹云锦97匹居首,进度条几乎满格,是本应用织机页TOP6设计的核心亮点。注意这里TOP6标题是"云锦销量"但放在织机页,是因为织机页需要一个统计卡作为页面头部,用云锦销量排行作为引流,引导用户关注名锦搭配的织机。

第二部分是织机库,Column,margin top 12:

  • 标题行:Row,"🪵 织机库"textPrimary深褐字13号加粗 + "点击🗑️撤机"textHint灰字9号。
  • 织机列表:ForEach this.data.looms 12种,每种 Row,padding 10,backgroundColor i%2===0 ? cardBg白 : cardAlt浅米黄(奇偶行交替),圆角16,border 1px line,margin top 8:
    • 图标装饰区:Column width 56,padding 上下10/6,backgroundColor getLoomColor(c.level)+‘0D’,圆角16,border 1px getLoomColor(c.level)+‘33’:
      • 图标:Text c.emoji 18号居中。
      • 装饰点行:Row width 28 justifyContent Center margin top 5,10x10 getLoomColor圆(圆角5,带2px silkWhite丝白边)+ 8x8 silkWhite丝白圆(圆角4,margin left 4)。
    • 信息区:Column layoutWeight 1 alignItems Start margin left 10:
      • 名称织法行:Row width 100% justifyContent SpaceBetween,c.name textPrimary深褐字13号加粗 + c.weave+‘织’ textSecondary灰字9号 margin left 8(大花楼/小花楼/提花等织法)。
      • 精度行:Row width 100% margin top 6,"织造精度"textHint灰字9号 + Row layoutWeight 1 margin left 6,Column layoutWeight(c.level) height 6 backgroundColor getLoomColor(c.level) 圆角3(精度进度条!)+ Column layoutWeight(100-c.level) height 6 backgroundColor line 圆角3。
    • 删除按钮:Text '🗑️’14号,点击触发 onDel(c.name) 回调(delThreadName=c.name,showDeleteThread=true)。

12 种织机:花楼织机(大花楼,96,江宁,🏗️)、束综织机(小花楼,95,苏州,🌉)、提花织机(提花,94,杭州,⚙️)、斜织机(斜织,93,湖州,🪵)、竖织机(竖织,92,嘉兴,🌲)、罗织机(罗纹,91,南京,🕸️)、绫织机(绫纹,94,镇江,🎗️)、绢织机(绢纹,92,常州,🧈)、绒织机(绒纹,90,扬州,🧸)、纱织机(纱纹,91,无锡,🌬️)、缎织机(缎纹,95,绍兴,💎)、双经织机(双经,93,宁波,⚡)。精度从90到96,getLoomColor 三级着色(≥95 primary鎏金/≥92 accent朱红/其他 success靛蓝),花楼织机96/束综织机95/缎织机95用 primary 鎏金(最高精度,花楼织机是云锦专用大花楼木织机,需两人配合织造,束综织机是小花楼织机,缎织机织缎纹),提花织机94/斜织机93/竖织机92/绫织机94/绢织机92/双经织机93用 accent 朱红(中高精度),罗织机91/绒织机90/纱织机91用 success 靛蓝(基础精度),形成"以精度定颜色"的动态着色模式。精度进度条用 layoutWeight 动态分配(c.level 和 100-c.level),内嵌在列表行中,是本应用织机库设计的核心亮点。12种织机覆盖中国传统织机(花楼/束综/提花/斜织/竖织/罗/绫/绢/绒/纱/缎/双经),产地江宁/苏州/杭州/湖州/嘉兴/南京/镇江/常州/扬州/无锡/绍兴/宁波都是江南丝绸重镇,完整呈现中国传统织机生态。点击🗑️触发 DeleteThreadModal 撤下丝线弹窗(@Prop title 传入丝线名,确认后从丝线坊移除——注意源码中织机页删除触发的是DeleteThreadModal撤下丝线弹窗,命名为织机页但弹窗是撤下丝线,这是源码设计),形成"浏览→撤下"交互闭环。


五、丝线页:织造工艺热度与丝线坊

丝线页以"云锦丝线"为核心,采用"织造工艺热度横向进度条+丝线坊列表"的两层布局。

丝线模块 - 织造工艺热度横向进度条通经断纬97居首与12种丝线坊列表

页面顶部第一部分是织造工艺热度卡,Column 包裹,padding 14,cardBg 白底,圆角16,border 1px accent+‘55’:

  • 标题行:Row,"🔥 织造工艺热度"textPrimary深褐字13号加粗 + "通经断纬97"primary鎏金字9号。
  • 热度列表:ForEach BROCADE_HOT 6工艺,每款 Row width 100% margin top 6:
    • 名称:d.name textPrimary深褐字10号,width 80。
    • 横向进度条:Row layoutWeight 1,Column layoutWeight(d.heat) height 14 backgroundColor d.color 圆角7(热度部分)+ Column layoutWeight(100-d.heat) height 14 backgroundColor line 圆角7(剩余部分)。
    • 热度值:d.heat+‘’ d.color色9号,width 32 textAlign End。

6 种织造工艺热度:通经断纬97(primary鎏金)、妆花织造95(accent朱红)、挖花盘织93(success靛蓝)、逐花异色92(primaryLight浅金)、金线显花90(warning赭石)、纬线起花88(primaryDark深金)。横向进度条用 layoutWeight 动态分配(d.heat 和 100-d.heat),通经断纬97居首,进度条几乎满格。6种工艺覆盖云锦核心织造工艺(通经断纬→妆花织造→挖花盘织→逐花异色→金线显花→纬线起花),通经断纬是云锦最核心的织造技法(经线贯通整个织物,纬线根据纹样需要分段挖花),妆花织造是云锦最具代表性的品种(通经断纬挖花盘织),挖花盘织是妆花的核心技法,逐花异色是云锦的独特效果(同一经面织出多种色彩纹样),金线显花是织金锦的特色,纬线起花是云锦的基本技法,完整呈现南京云锦织造工艺的技术多样性,是本应用丝线页工艺热度设计的核心亮点。

第二部分是丝线坊,Column,margin top 12:

  • 标题行:Row,"🪡 丝线坊"textPrimary深褐字13号加粗 + "点击👁️了解"textHint灰字9号。
  • 丝线列表:ForEach this.data.threads 12种,每种 Row,padding 10,backgroundColor i%2===0 ? cardBg白 : cardAlt浅米黄(奇偶行交替),圆角16,border 1px line,margin top 8:
    • 图标装饰区:Column width 56,padding 上下10/6,backgroundColor getLoomColor(d.level)+‘0D’,圆角16,border 1px getLoomColor(d.level)+‘33’:
      • 图标:Text d.emoji 18号居中。
      • 丝线圆:Column width 14 height 14 backgroundColor getLoomColor(d.level) 圆角7,margin top 5。
    • 信息区:Column layoutWeight 1 alignItems Start margin left 10:
      • 名称品级行:Row width 100% justifyContent SpaceBetween,d.name textPrimary深褐字13号加粗 + “品级”+d.level getLoomColor(d.level)色9号 margin left 8。
      • 丝源产地行:Row width 100% justifyContent Start margin top 6,BrocadeTag({text: d.silk+‘丝’, color: textSecondary灰})(蚕种标签,灰色固定色)+ d.tool textHint灰字9号 margin left 8(产地/工坊)。
    • 详情按钮:Text '👁️’14号,点击触发 onDetail(d.name) 回调(detailLoomName=d.name,showDetailLoom=true)。

12 种丝线:湖丝(家蚕,96,辑里村,🐛)、桑蚕丝(桑蚕,95,吴兴坊,🍃)、柞蚕丝(柞蚕,93,丹东场,🌰)、真丝绞(家蚕,94,湖州坊,🌀)、金线(捻金,92,南京工坊,✨)、银线(捻银,91,杭州工坊,🥈)、孔雀羽线(羽捻,90,苏州坊,🦚)、彩绒线(绒捻,93,扬州坊,🧵)、双宫丝(双宫,94,嘉兴场,🥚)、绢丝(下脚茧,89,无锡坊,🧣)、生丝(缫丝,95,海宁坊,🌾)、熟丝(煮练,92,常州坊,♨️)。品级从89到96,getLoomColor 三级着色(≥95 primary鎏金/≥92 accent朱红/其他 success靛蓝),湖丝96/桑蚕丝95/生丝95用 primary 鎏金(最高品级,湖丝是浙江湖州辑里村出产的优质蚕丝,明清时期为皇家御用,桑蚕丝是最常用的云锦丝线,生丝是缫丝后的原丝),柞蚕丝93/真丝绞94/金线92/彩绒线93/双宫丝94/熟丝92用 accent 朱红(中高品级),银线91/孔雀羽线90/绢丝89用 success 靛蓝(基础品级)。12种丝线覆盖蚕丝(湖丝/桑蚕丝/柞蚕丝/真丝绞/双宫丝/绢丝/生丝/熟丝)、金银线(金线/银线)、羽线(孔雀羽线)、绒线(彩绒线),产地辑里村/吴兴坊/丹东场/湖州坊/南京工坊/杭州工坊/苏州坊/扬州坊/嘉兴场/无锡坊/海宁坊/常州坊,完整呈现南京云锦丝线生态。点击👁️触发 DetailLoomModal 丝线详情弹窗(@Prop name 传入丝线名,显示蚕丝缫后煮练脱胶,纺成细匀丝线,捻以金缕银纬,织机上通经断纬,一寸云锦需百余日方成,光华如云霞,丝源家蚕·辑里村,匠人评级甲级·织锦卅载),形成"浏览→查看详情"交互闭环。


六、订单页:纹样构成与订单台账

订单页以"订单管理"为核心,采用"纹样构成横向进度条+订单台账列表"的两层布局。

订单模块 - 纹样构成横向进度条团花占比最高与12笔订单台账列表金额分级着色

页面顶部第一部分是纹样构成卡,Column 包裹,padding 14,cardBg 白底,圆角16,border 1px accent+‘55’:

  • 标题行:Row,"🧩 纹样构成"textPrimary深褐字13号加粗 + "团花占比最高"textHint灰字9号。
  • 构成列表:ForEach CLOUD_SHARE 4种,每款 Row width 100% margin top 6:
    • 色点:Column width 8 height 8 backgroundColor s.color 圆角4。
    • 名称:s.name textPrimary深褐字10号 margin left 6。
    • 数量:‘×’+s.count textSecondary灰字9号 margin left 4。
    • 横向进度条:Row layoutWeight 1 margin left 10,Column layoutWeight(s.count) height 10 backgroundColor s.color 圆角5(占比部分)+ Column layoutWeight(20-s.count) height 10 backgroundColor line 圆角5(剩余部分,总份数20!)。

4 种纹样构成:团花×5(primary鎏金)、龙凤×4(accent朱红)、缠枝×3(success靛蓝)、云纹×2(primaryDark深金)。横向进度条用 layoutWeight 动态分配(s.count 和 20-s.count,总份数20非100),团花×5占比最高,是本应用订单页纹样构成设计的核心亮点。4种纹样是南京云锦的主要纹样类别(团花/龙凤/缠枝/云纹),团花是云锦最常见的纹样(圆形团花,如龙纹云锦/海水江崖/团龙补子)占比最高,龙凤是云锦最高贵的纹样(凤穿牡丹/妆花缎),缠枝是云锦最优美的纹样(缠枝莲/万寿如意/四合如意),云纹是云锦最飘逸的纹样(云鹤纹袍/织金锦/百子图),完整呈现云锦纹样的多样性。

第二部分是订单台账,Column,margin top 12:

  • 标题行:Row,"📦 订单台账"textPrimary深褐字13号加粗 + "点击✏️改单"textHint灰字9号 + "✏️"14号 margin left 8,点击触发 onEdit 回调(showEditOrder=true)。
  • 订单列表:ForEach this.data.orders 12笔,每笔 Row,padding 10,backgroundColor i%2===0 ? cardBg白 : cardAlt浅米黄(奇偶行交替),圆角16,border 1px line,margin top 8:
    • 图标:Text o.emoji 16号。
    • 信息区:Column layoutWeight 1 alignItems Start margin left 10,o.name textPrimary深褐字12号加粗 + o.region+’ · '+o.count+‘匹’ textSecondary灰字9号 margin top 2。
    • 金额:Text ‘¥’+o.amount getOrderColor(o.amount)色12号加粗。
    • 编辑按钮:Text '✏️’12号 margin left 10,点击触发 onEdit 回调。

12 笔订单:故宫文创(华北,900匹,¥540000,🏯)、苏绣研究所(华东,600匹,¥480000,🏛️)、汉服高定品牌(华东,500匹,¥650000,👘)、婚庆礼服馆(华南,400匹,¥560000,💒)、影视剧组(华北,300匹,¥450000,🎬)、博物馆文创店(华中,700匹,¥420000,🗿)、海外丝绸馆(海外,350匹,¥525000,🌏)、高端酒店布草(华东,800匹,¥480000,🏨)、非遗体验馆(西南,500匹,¥300000,🎐)、直播电商团(华东,650匹,¥390000,📺)、旗袍定制店(华南,450匹,¥540000,🧧)、国礼供应处(华北,200匹,¥600000,🎁)。金额从¥300000(非遗体验馆)到¥650000(汉服高定品牌),getOrderColor 四级着色(≥55万 accent朱红/≥45万 primary鎏金/≥35万 success靛蓝/其他 primaryDark深金),¥650000汉服高定/¥600000国礼/¥560000婚庆/¥540000故宫文创/¥540000旗袍定制/¥525000海外丝绸馆用 accent 朱红色(≥55万最大额,汉服高定是云锦最大B端客户),¥480000苏绣研究所/¥450000影视剧组/¥480000高端酒店用 primary 鎏金色(≥45万中大额),¥420000博物馆文创/¥390000直播电商用 success 靛蓝色(≥35万中等),¥300000非遗体验馆用 primaryDark 深金色(其他小额),形成"以金额定颜色"的动态着色模式。点击✏️触发 EditBrocadeOrderModal 修改订单弹窗(显示客户故宫文创,数量900→1000匹,金额¥600000,保存修改),是本应用订单页设计的核心亮点。订单覆盖12种客户类型(故宫文创/苏绣研究所/汉服高定品牌/婚庆礼服馆/影视剧组/博物馆文创店/海外丝绸馆/高端酒店布草/非遗体验馆/直播电商团/旗袍定制店/国礼供应处),完整呈现南京云锦B端客户生态,云锦作为高端丝织品和文化藏品,客户以故宫/汉服高定/婚庆/影视/博物馆/国礼等文化和高端机构为主,与之前象棋的棋院/棋社/学校等大众普及客户形成对比。


七、客评页:客评口碑列表

客评页以"客户评价"为核心,采用"客评口碑列表"的单层布局。

客评模块 - 12条客评口碑列表含星级重复标签分级着色及评价内容

页面顶部第一部分是标题行,Row width 100% justifyContent SpaceBetween margin bottom 6:

  • "⭐ 客评口碑"textPrimary深褐字13号加粗。
  • "12条好评 · 平均4.9"textHint灰字9号。

第二部分是客评列表,ForEach this.data.reviews 12条,每条 Column,padding 12,backgroundColor i%2===0 ? cardBg白 : cardAlt浅米黄(奇偶行交替),圆角16,border 1px line,margin top 8:

  • 用户行:Row width 100%,
    • 头像:Text r.emoji 15号。
    • 用户信息:Column layoutWeight 1 alignItems Start margin left 8,r.name textPrimary深褐字12号加粗 + r.date textHint灰字9号 margin top 1。
    • 星级:Text ‘⭐’.repeat(r.score)(⭐字符串重复r.score次!)10号 warning赭石色。
    • 标签:BrocadeTag({text: r.tag, color: getReviewTagColor(r.tag)})(评价标签,标签分级色)。
  • 评价内容:Text r.content textSecondary灰字11号,lineHeight 16,margin top 6。

12 条客评:故宫文创买手(5星,09-03,龙纹云锦色彩华贵,装裱后展厅里一眼惊艳,寸锦寸金,🏯)、苏绣研究员(5星,09-02,通经断纬工艺纯正,纹样细腻到可分毫,工艺纯正,🏛️)、汉服主理(5星,09-01,妆花缎垂坠流光,客户试穿后当场下单,流光溢彩,👘)、婚庆策划师(5星,08-31,凤穿牡丹用在喜服上,寓意喜庆面料考究,喜庆考究,💒)、影视服装师(5星,08-30,织金锦质感上镜,古装剧补拍一直指定这家的,质感上镜,🎬)、博物馆店长(5星,08-29,云鹤纹围巾做工精湛,游客爱不释手,做工精湛,🕊️)、海外买手(5星,08-28,海外藏家认可度极高,云锦是行走的中国名片,海外认可,🌏)、酒店采购(4星,08-27,床旗配色雅致,若幅面再宽些更佳,配色雅致,🏨)、体验馆馆长(4星,08-26,研学团上手织机体验物料齐全,若加图解更贴心,物料齐全,🎐)、直播选品(5星,08-25,云锦围巾走量飞快,上架两小时售罄补货,走量飞快,📺)、旗袍店长(5星,08-24,四合如意纹配旗袍,端庄里透着灵动,端庄灵动,🧧)、老主顾(5星,08-23,收藏第三幅云锦了,每一寸都是光阴的味道,岁月织就,🎁)。10条5星+2条4星,平均4.9分,星级用 ‘⭐’.repeat(r.score) 字符串重复生成(非循环非组件,纯字符串乘法),是轻量级星级展示的实用技巧。标签 getReviewTagColor 五级着色(寸锦寸金/工艺纯正/流光溢彩/做工精湛用 primary鎏金,喜庆考究/质感上镜/海外认可用 accent朱红,配色雅致/物料齐全/端庄灵动/岁月织就用 success靛蓝,走量飞快用 warning赭石,其他 primaryDark深金),形成"以标签定颜色"的动态着色模式。评价内容都是真实云锦用户的使用反馈,覆盖12种客户类型与订单台账客户类型呼应,从故宫文创买手到苏绣研究员,从汉服主理到老主顾,完整呈现南京云锦客户的口碑生态,云锦作为高端丝织品和文化藏品,评价关键词集中在"寸锦寸金"“工艺纯正”“流光溢彩”“质感上镜"等文化性和华贵性描述,与之前象棋的"字口深峻”"走子顺滑"等实用性描述形成对比。奇偶行交替条纹,是本应用客评页设计的标准布局。


八、色彩主题系统:鎏金朱红米黄与十六进制透明度拼接

8.1 色彩接口定义

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

interface BrocadePalette {
  primary: string; primaryLight: string; primaryDark: string;
  accent: string; accentLight: string;
  bg: string; cardBg: string; cardAlt: string;
  textPrimary: string; textSecondary: string; textHint: string;
  border: string; line: string;
  success: string; warning: string; danger: string;
  white: string; gold: string; silkWhite: string;
}
  • primary:主色 #C9A24B,鎏金,南京云锦非遗主题色,用于标题强调、价格、按钮、柱状图高峰。
  • primaryLight:浅主色 #DDBB72,浅金,用于渐变和浅色强调、入门级价格。
  • primaryDark:深主色 #8F6B2E,深金,用于头部背景(营造浓郁云锦工坊氛围)。
  • accent:辅助色 #A63A2B,朱红,用于tabBar顶部条、纹样龙凤、价格中高端。
  • accentLight:浅辅助色 #C98D82,浅朱红,用于渐变和浅色强调。
  • bg:页面背景 #F6F1E7,米黄,南京云锦浅色背景(模拟宣纸/丝质质感)。
  • cardBg:卡片背景 #FFFFFF,白,用于奇数行卡片和主要卡片。
  • cardAlt:交替卡片背景 #F1E8D8,浅米黄,用于偶数行卡片(奇偶行交替条纹)。
  • textPrimary:文字主色 #3B2F1E,深褐,南京云锦深色文字(非纯黑,更有丝质和文化感)。
  • textSecondary:次要文字 #7A6A52,褐灰,用于次要信息。
  • textHint:提示文字 #A9977C,浅褐灰,用于辅助信息和非激活tab。
  • border:边框色 #E0D3BC,浅米黄边,用于按钮边框。
  • line:分割线 #EDE4D2,极浅米黄,用于卡片边框和进度条背景。
  • success:成功色 #4E7BA8,靛蓝,用于成功状态和缠枝纹样、丝线标签。
  • warning:警告色 #C98A4E,赭石,用于星级评分、金线显花。
  • danger:危险色 #9E2E20,深红,用于删除按钮。
  • gold:鎏金 #C9A24B,与 primary 同色,用于装饰圆(12x12鎏金圆带2px丝白边,模拟金线团花)。
  • silkWhite:丝白 #F7F3E9,用于装饰圆边框(模拟丝质光泽)。

云锦坊撤下丝线弹窗运行效果 - 弹窗叠加织机页显示撤下丝线确认及再想想确认撤下按钮

8.2 色彩实例化

const COLORS: BrocadePalette = {
  primary: '#C9A24B', primaryLight: '#DDBB72', primaryDark: '#8F6B2E',
  accent: '#A63A2B', accentLight: '#C98D82',
  bg: '#F6F1E7', cardBg: '#FFFFFF', cardAlt: '#F1E8D8',
  textPrimary: '#3B2F1E', textSecondary: '#7A6A52', textHint: '#A9977C',
  border: '#E0D3BC', line: '#EDE4D2',
  success: '#4E7BA8', warning: '#C98A4E', danger: '#9E2E20',
  white: '#FFFFFF', gold: '#C9A24B', silkWhite: '#F7F3E9'
};

本应用采用"鎏金朱红米黄"南京云锦非遗主题设计——bg #F6F1E7 米黄(模拟宣纸/丝质质感,云锦文化载体),cardBg #FFFFFF 白,cardAlt #F1E8D8 浅米黄(奇偶行交替用),textPrimary #3B2F1E 深褐(南京云锦深色文字,非纯黑,更有丝质和文化感),primary #C9A24B 鎏金主色,primaryDark #8F6B2E 深金(头部背景,营造浓郁云锦工坊氛围),accent #A63A2B 朱红辅助色,accentLight #C98D82 浅朱红,gold #C9A24B 鎏金(装饰圆用,模拟金线团花),silkWhite #F7F3E9 丝白(装饰圆边框用,模拟丝质光泽)。主色体系采用"鎏金+朱红+靛蓝+米黄"四色组合——鎏金用于主色/价格/按钮/高峰/装饰圆(金线团花),朱红用于辅助/龙凤/价格中高端/tabBar顶部条,靛蓝用于成功/缠枝/丝线标签,米黄用于页面背景/交替卡片,四色各司其职形成"以鎏金定主、以朱红定辅、以靛蓝定饰、以米黄定底"的南京云锦语义着色体系。

本应用最大的色彩处理特色是鎏金 gold #C9A24B 与丝白 silkWhite #F7F3E9——鎏金用于装饰圆(12x12鎏金圆带2px丝白边,模拟金线团花),丝白用于装饰圆边框(模拟丝质光泽),与米黄底形成"鎏金朱红米黄"的南京云锦经典配色。头部12点装饰纹中 r%3=0 用12x12鎏金圆带2px丝白边(模拟金线团花,鎏金圆+丝白边模拟云锦团花的金线和丝质光泽),r%3=1 用6x14朱红竖线(模拟朱红经线,竖线形态模拟织机经线),r%3===2 用8x8靛蓝圆(模拟靛蓝纬线),鎏金圆+朱红竖线+靛蓝圆三色交替,营造浓郁的云锦纹样装饰纹效果。云锦页装饰行用鎏金圆带丝白边+朱红圆带丝白边+靛蓝圆交替,云锦图标装饰区的丝白圆纹样色边(14x14丝白圆带2px纹样色边框模拟云锦团花轮廓+8x8纹样色小圆模拟云锦纹样点),织机库图标装饰区的10x10 getLoomColor圆带2px丝白边+8x8丝白圆,都是"鎏金丝白"云锦纹样的模拟,是本应用视觉设计的核心特色。gold 鎏金与 primary 同色但语义不同——primary 是主题主色,gold 是装饰专用色(模拟金线团花),silkWhite 丝白是独立装饰色(模拟丝质光泽,与white纯白不同,丝白偏米黄更有丝质感),这种"同色异义+独立装饰色"的设计体现了南京云锦"金线团花丝质光泽"的核心视觉元素。

本应用另一个色彩处理特色是十六进制透明度拼接技巧——大量使用 color + 十六进制透明度后缀实现半透明背景和边框,无需额外定义半透明颜色常量:

  • color + ‘14’ = 10% 透明度(用于 BrocadeTag 背景、图标区背景、tabBar激活背景)
  • color + ‘40’ = 25% 透明度(用于 BrocadeTag 边框)
  • color + ‘55’ = 33% 透明度(用于卡片边框、装饰横线)
  • color + ‘66’ = 40% 透明度(用于圆形图标边框)
  • color + ‘80’ = 50% 透明度(用于头部右侧图标区边框)
  • color + ‘0D’ = 5% 透明度(用于云锦图标装饰区背景)
  • color + ‘33’ = 20% 透明度(用于云锦图标装饰区边框)
  • color + ‘2E’ = 18% 透明度(用于头部右侧图标区背景)

这种技巧的原理是:ArkUI 的颜色值支持 #RRGGBBAA 格式(8位十六进制,最后两位是透明度),通过字符串拼接 color(#RRGGBB)+ 透明度后缀(AA),即可动态生成任意透明度的同色系颜色,无需为每个颜色定义多个透明度变体。例如 primary #C9A24B + ‘14’ = #C9A24B14(10%透明鎏金),primary + ‘66’ = #C9A24B66(40%透明鎏金)。这种技巧在本应用中被广泛使用(BrocadeTag子组件、图标装饰区、tabBar激活态、卡片边框、头部图标区等),是 ArkUI 颜色处理的高级实用技巧,大幅减少了颜色常量的定义数量。

本应用的动态颜色逻辑也十分丰富——5个颜色分级函数(getStyleColor纹样4类/getLoomColor精度3级/getThreadColor价格4级/getOrderColor金额4级/getReviewTagColor标签5类),将颜色映射逻辑从 UI 层抽离,每个函数根据不同维度返回对应颜色值,实现"数据即配色"的动态着色模式。getThreadColor 价格四级(≥1200 accent朱红/≥800 primary鎏金/≥500 success靛蓝/其他 primaryDark深金),getOrderColor 金额四级(≥55万 accent朱红/≥45万 primary鎏金/≥35万 success靛蓝/其他 primaryDark深金),getLoomColor 精度三级(≥95 primary鎏金/≥92 accent朱红/其他 success靛蓝),getStyleColor 纹样四类(团花→鎏金/龙凤→朱红/缠枝→靛蓝/云纹→深金),getReviewTagColor 标签五类(品质类→鎏金/工艺类→朱红/外观类→靛蓝/服务类→赭石/其他→深金)。这种将所有色彩集中管理、配合5个颜色分级函数和十六进制透明度拼接与鎏金丝白装饰与多维度动态着色的做法保证了全局一致性,降低了维护成本。

云锦坊定制云锦弹窗运行效果 - 弹窗叠加云锦页显示品名纹样预算三行及取消确认织造按钮


九、类型定义:@Observed聚合数据类与多组件数据共享

9.1 @Observed聚合数据类 BrocadeData

应用独创了 @Observed 聚合数据类设计——BrocadeData 一个类包含 brocades/looms/threads/orders/reviews 五个数组,每个数组 12 条数据,用对象字面量初始化(非 new 构造函数),@Observed 装饰整个类:

@Observed
export class BrocadeData {
  brocades: BrocadeItem[] = [ /* 12款云锦 */ ];
  looms: LoomItem[] = [ /* 12种织机 */ ];
  threads: ThreadItem[] = [ /* 12种丝线 */ ];
  orders: BrocadeOrderItem[] = [ /* 12笔订单 */ ];
  reviews: BrocadeReviewItem[] = [ /* 12条客评 */ ];
}

与每个数据类独立定义+独立常量数组初始化的设计不同,本应用将所有业务数据聚合到一个 BrocadeData 类中,用类属性初始化(brocades: BrocadeItem[] = […]),非 constructor 构造函数。@Observed 装饰整个类,使得 BrocadeData 的所有属性变化都能被观察到。配合 @ObjectLink 在子组件间共享引用,实现"一个数据对象贯穿所有页面"的聚合数据模式。入口组件 BrocadeApp 中 @State data: BrocadeData = new BrocadeData() 创建数据实例,5个内容子组件通过 @ObjectLink data: BrocadeData 接收共享引用,修改数据时所有引用该数据的子组件自动响应式刷新,是 ArkUI 跨组件数据共享的高级实践。

9.2 元数据接口

应用定义了 9 个元数据接口,将数据结构与 UI 表现层解耦:

  • BrocadeItem:云锦项(name/style/thread/price/emoji,12款云锦)
  • LoomItem:织机项(name/weave/level/place/emoji,12种织机)
  • ThreadItem:丝线项(name/silk/level/tool/emoji,12种丝线)
  • BrocadeOrderItem:订单项(name/region/count/amount/emoji,12笔)
  • BrocadeReviewItem:客评项(name/score/date/content/tag/emoji,12条)
  • WeekBrocadeMeta:周销量元数据(day/value,7天)
  • StyleShareMeta:纹样构成元数据(name/count/color,4种)
  • BrocadeHotMeta:织造工艺热度元数据(name/heat/color,6种)
  • BrocadeTopMeta:云锦销量排行元数据(name/sold/color,6款)

9.3 5个颜色分级函数

function getStyleColor(style: string): string { /* 纹样4类:团花鎏金/龙凤朱红/缠枝靛蓝/其他深金 */ }
function getLoomColor(level: number): string { /* 精度3级:≥95鎏金/≥92朱红/其他靛蓝 */ }
function getThreadColor(price: number): string { /* 价格4级:≥1200朱红/≥800鎏金/≥500靛蓝/其他深金 */ }
function getOrderColor(amount: number): string { /* 金额4级:≥55万朱红/≥45万鎏金/≥35万靛蓝/其他深金 */ }
function getReviewTagColor(tag: string): string { /* 标签5类 */ }

5 个颜色分级函数将颜色映射逻辑从 UI 层抽离,每个函数根据不同维度(纹样/精度/价格/金额/标签)返回对应颜色值,实现"数据即配色"的动态着色模式。getLoomColor 被织机页和丝线页共用(精度/品级都用三级着色),getThreadColor 用于云锦页价格着色,getOrderColor 用于订单页金额着色,getReviewTagColor 用于客评页标签着色,getStyleColor 用于云锦页纹样标签着色。

所有数据类用对象字面量初始化(非 new 构造函数),@Observed 装饰聚合类,@ObjectLink 在子组件间共享引用,5个颜色分级函数将逻辑从 UI 层抽离,9个元数据接口实现数据结构规范化,4个统计常量数组(WEEK_SOLD/CLOUD_SHARE/BROCADE_HOT/BROCADE_TOP)将统计数据与业务数据分离,是本应用数据层设计的核心亮点。数据规模:5个业务数组各12条=60条业务数据,4个统计数组共23条统计数据,总计83条数据,完整呈现南京云锦非遗手作平台的数据生态。12款云锦都是南京云锦经典纹样(龙纹/凤穿牡丹/缠枝莲/云鹤/海水江崖/万寿如意/妆花缎/织金锦/团龙补子/百子图/四合如意/云锦围巾),12种织机覆盖中国传统织机(花楼/束综/提花/斜织/竖织/罗/绫/绢/绒/纱/缎/双经),12种丝线覆盖蚕丝/金银线/羽线/绒线,6种织造工艺覆盖核心工艺(通经断纬/妆花织造/挖花盘织/逐花异色/金线显花/纬线起花),4种纹样构成(团花/龙凤/缠枝/云纹),数据文化含量极高,是本应用区别于之前主题的最大特色。


十、多组件架构与@ObjectLink数据共享:11组件协同设计

本应用采用了多组件架构设计,将整个应用拆分为 11 个独立组件,组件数量多、架构复杂。

10.1 11个组件清单

  1. BrocadeApp(入口组件):@Entry装饰,管理curTab/data/4个弹窗显隐/2个头部动画状态/delThreadName/detailLoomName,包含头部+内容区切换+tabBar+4个弹窗条件渲染。
  2. BrocadeTag(标签子组件):@Component,@Prop text+@Prop color,封装标签样式(9号字+color+'14’背景+color+'40’边框+圆角10),被云锦/织机/丝线/客评页共用。
  3. BrocadeContent(云锦内容子组件):@Component,@ObjectLink data+onAdd回调,本周销量柱状图+云锦陈列列表。
  4. LoomContent(织机内容子组件):@Component,@ObjectLink data+onDel回调,云锦销量TOP6横向进度条+织机库列表。
  5. ThreadContent(丝线内容子组件):@Component,@ObjectLink data+onDetail回调,织造工艺热度横向进度条+丝线坊列表。
  6. BrocadeOrderContent(订单内容子组件):@Component,@ObjectLink data+onEdit回调,纹样构成横向进度条+订单台账列表。
  7. BrocadeReviewContent(客评内容子组件):@Component,@ObjectLink data,客评口碑列表。
  8. AddBrocadeModal(定制云锦弹窗子组件):@Component,onClose回调,品名/纹样/预算三行展示+确认织造。
  9. EditBrocadeOrderModal(修改订单弹窗子组件):@Component,onClose回调,客户/数量/金额三行+保存修改。
  10. DeleteThreadModal(撤下丝线弹窗子组件):@Component,@Prop title+onClose回调,撤下确认+确认撤下danger红底。
  11. DetailLoomModal(丝线详情弹窗子组件):@Component,@Prop name+onClose回调,大图标+蚕丝描述+丝源+匠人评级+知道了。

10.2 @ObjectLink数据共享机制

入口组件 BrocadeApp 中 @State data: BrocadeData = new BrocadeData() 创建聚合数据实例,5个内容子组件通过 @ObjectLink data: BrocadeData 接收共享引用:

// 入口组件传递
BrocadeContent({ data: this.data, onAdd: () => { this.showAddBrocade = true; } })

// 子组件接收
@Component
struct BrocadeContent {
  @ObjectLink data: BrocadeData;
  onAdd: () => void = () => {};
}

@ObjectLink 是 ArkUI 中用于跨组件共享 @Observed 装饰对象引用的装饰器,与 @Prop(值传递,单向同步)不同,@ObjectLink 是引用传递,双向同步——子组件修改 data.brocades 等属性时,入口组件和其他引用同一 data 的子组件自动响应式刷新。这种设计实现了"一个数据对象贯穿所有页面"的聚合数据模式,避免了每个页面独立 @State 数据数组的重复定义,是 ArkUI 跨组件数据共享的高级实践。

10.3 回调函数父子通信

4个内容子组件通过回调函数与入口组件通信,实现弹窗触发:

  • BrocadeContent 的 onAdd:点击➕触发,入口组件设置 showAddBrocade=true。
  • LoomContent 的 onDel(n: string):点击🗑️触发,入口组件设置 delThreadName=n + showDeleteThread=true。
  • ThreadContent 的 onDetail(n: string):点击👁️触发,入口组件设置 detailLoomName=n + showDetailLoom=true。
  • BrocadeOrderContent 的 onEdit:点击✏️触发,入口组件设置 showEditOrder=true。

4个弹窗子组件通过 onClose 回调与入口组件通信,实现弹窗关闭:

  • AddBrocadeModal/EditBrocadeOrderModal/DeleteThreadModal/DetailLoomModal 都有 onClose: () => void 回调,点击取消/确认/✕/知道了时调用 onClose(),入口组件设置对应弹窗显隐为 false。

DeleteThreadModal 和 DetailLoomModal 通过 @Prop 接收参数(title/name),@Prop 是值传递单向同步,入口组件设置 delThreadName/detailLoomName 后传递给弹窗子组件,弹窗内显示对应丝线名。

10.4 头部交互动画

头部右侧 🪡🌈🧵 三个图标,其中 🪡 和 🌈 可点击触发动画:

  • 🪡 translate位移动画@State needleFloat: boolean = false,点击🪡 this.needleFloat = !this.needleFloat.translate({ x: 0, y: this.needleFloat ? -10 : 4 }).animation({ duration: 640, curve: Curve.EaseOut }),点击🪡从y=4下移到y=-10上移,640ms EaseOut 动画,模拟绣针上下浮动(绣针在织机上上下穿梭,是云锦织造的核心动作)。
  • 🌈 旋转120度动画@State cloudSpin: boolean = false,点击🌈 this.cloudSpin = !this.cloudSpin.rotate({ angle: this.cloudSpin ? 120 : 0 }).animation({ duration: 700, curve: Curve.EaseOut }),点击🌈旋转120度,700ms EaseOut 动画,模拟云锦彩虹旋转(🌈是云锦云霞灿烂的意象,旋转模拟云锦纹样流转)。

头部🪡translate位移和🌈旋转120度动画是本应用最具创意的交互设计,点击🪡translate位移模拟绣针上下浮动(绣针在织机上上下穿梭,是云锦织造的核心动作,y从4到-10模拟绣针刺入和抽出),点击🌈旋转120度模拟云锦彩虹旋转(🌈是云锦云霞灿烂的意象,120度旋转模拟云锦纹样流转,非90度非180度,120度是三分之一圈,模拟云锦三色纹样流转),增加了应用的趣味性和互动感,也体现了 ArkUI 显式动画(animation属性配合状态变化,支持rotate/translate/scale多种变换)的使用。与之前826龙泉剑的⚔️旋转90度+✨scale缩放不同,本应用用了🪡translate位移+🌈旋转120度,每次主题换一种动画组合,且旋转角度从90度变为120度(云锦三色纹样流转需要120度),translate位移从x/y双轴变为y单轴(绣针只上下浮动),体现了动画与主题文化的深度结合。🧵 图标纯装饰不可点击,三个图标区用 primaryLight+‘2E’(18%透明浅金底)+border primaryLight+‘80’(50%透明浅金边)+圆角24,营造云锦工坊装饰感。

10.5 12点云锦装饰纹

头部下方 12 个云锦装饰点(BROCADE_COL 0-11),用 ForEach 遍历,每个 Row width 14 justifyContent Center,根据 r%3 显示不同形状:

  • r%3===0:12x12 鎏金圆(gold,圆角6,带2px silkWhite丝白边,模拟金线团花,鎏金圆+丝白边模拟云锦团花的金线和丝质光泽)。
  • r%3===1:6x14 朱红竖线(accent,圆角3,模拟朱红经线,竖线形态模拟织机经线,是云锦织造的核心元素)。
  • r%3===2:8x8 靛蓝圆(success,圆角4,模拟靛蓝纬线)。

12 个装饰点用 SpaceBetween 均匀分布,鎏金圆+朱红竖线+靛蓝圆三色交替,鎏金圆模拟金线团花(云锦最华贵的元素),朱红竖线模拟织机经线(云锦织造的核心元素),靛蓝圆模拟靛蓝纬线,营造浓郁的云锦纹样装饰纹效果,是本应用头部设计的特色细节。与之前826龙泉剑的钢蓝竖线+剑红圆(模拟剑身剑首)不同,本应用用了鎏金圆+朱红竖线+靛蓝圆(模拟金线团花+经线+纬线),装饰纹形态与主题高度契合——南京云锦的核心视觉元素就是金线团花和织机经纬线,用鎏金圆和朱红竖线靛蓝圆交替模拟云锦纹样,是本应用装饰纹设计的精妙之处。下方"✨ 云霞灿烂 · 织就天衣 🧵"分隔线,用左右两条 primaryLight+‘66’(40%透明浅金)横线+中间文字,"云霞灿烂,织就天衣"是南京云锦经典意象,✨🧵是云锦符号,营造云锦文化氛围。


十一、四大弹窗子组件:增删改查交互闭环

应用通过四个独立弹窗子组件实现了完整的增删改查交互闭环,每个弹窗都是独立 @Component 组件,通过 @Prop 接收参数、onClose 回调关闭。

11.1 AddBrocadeModal 定制云锦

@Component
struct AddBrocadeModal {
  onClose: () => void = () => {};
  build() {
    Column() { // 全屏遮罩 #66000000
      Column() { // 弹窗卡片 width 86%
        Row() { Text('🧵 定制云锦') + Text('✕').onClick(onClose) }
        Row() { Text('品名') + Text('龙纹云锦') } // cardAlt底
        Row() { Text('纹样') + Text('团花 · 真丝 · 通经断纬') } // cardAlt底
        Row() { Text('预算') + Text('¥680') } // primary鎏金色
        Row() { Text('取消') + Text('确认织造') } // primary鎏金底
      }
    }
  }
}

定制云锦弹窗显示品名(龙纹云锦)、纹样(团花·真丝·通经断纬)、预算(¥680,primary鎏金色)三行信息,每行用 cardAlt 浅米黄底,确认织造按钮用 primary 鎏金底白字。点击✕/取消/确认织造都调用 onClose() 关闭弹窗。本弹窗为展示型弹窗(无输入框),预设定制信息,点击确认即关闭。

11.2 EditBrocadeOrderModal 修改订单

修改订单弹窗显示客户(故宫文创)、数量(900 → 1000 匹,primary鎏金色显示变更)、金额(¥600000,accent朱红色)三行信息,每行 cardAlt 浅米黄底,数量变更用 primary 鎏金色突出显示(从900增至1000),金额用 accent 朱红色。保存修改按钮用 primary 鎏金底白字。点击✕/取消/保存修改都调用 onClose() 关闭。本弹窗同样为展示型,预设修改信息(数量从900增至1000,金额对应增至60万)。

11.3 DeleteThreadModal 撤下丝线

@Component
struct DeleteThreadModal {
  @Prop title: string; // 接收丝线名
  onClose: () => void = () => {};
  build() {
    Column() { // 全屏遮罩
      Column() { // 弹窗卡片
        Text('🗑️ 撤下丝线')
        Text('确定撤下「' + this.title + '」吗?') // 动态丝线名
        Text('撤下的丝线将从丝线坊移除。')
        Row() { Text('再想想') + Text('确认撤下') } // danger红底!
      }
    }
  }
}

撤下丝线弹窗通过 @Prop title 接收丝线名(入口组件设置 delThreadName 后传入),显示"确定撤下「丝线名」吗?"动态文案,确认撤下按钮用 danger 深红底白字(删除操作的危险色),再想想按钮用 border 边框。点击再想想/确认撤下都调用 onClose() 关闭。@Prop 是值传递单向同步,入口组件 delThreadName 变化时自动同步到弹窗。

11.4 DetailLoomModal 丝线详情

丝线详情弹窗通过 @Prop name 接收丝线名,显示大图标(🪡30号)+丝线名(this.name,textPrimary深褐字加粗)+描述文字(蚕丝缫后煮练脱胶,纺成细匀丝线,捻以金缕银纬,织机上通经断纬,一寸云锦需百余日方成,光华如云霞。)+丝源(家蚕·辑里村)+匠人评级(甲级·织锦卅载,primary鎏金色),知道了按钮用 primary 鎏金底白字。描述文字是固定的蚕丝描述(本项目未根据丝线名动态切换描述,预设蚕丝文案),丝源和匠人评级也是预设值。点击✕/知道了调用 onClose() 关闭。@Prop name 接收丝线名用于标题显示。

四个弹窗遮罩都用 #66000000(40%黑色,硬编码非 COLORS.mask),弹窗卡片 width 86%,cardBg 白底,圆角18,constraintSize maxHeight 78%。四个弹窗各自独立组件,通过 @Prop 接收参数(DeleteThreadModal的title、DetailLoomModal的name),通过 onClose 回调关闭,形成了完整的"定制云锦→撤下丝线→查看丝线→修改订单"增删改查交互闭环,是本应用多组件架构设计的核心亮点。


十二、关键技术特性对比

特性维度 实现方式 技术要点 设计价值
色彩管理 BrocadePalette接口 + COLORS常量 集中定义20种语义色,含鎏金朱红米黄主题 保证视觉一致性,支持主题切换
鎏金朱红米黄主题 primary #C9A24B + accent #A63A2B + bg #F6F1E7 鎏金主色/朱红辅助/靛蓝装饰/米黄背景,textPrimary #3B2F1E深褐非纯黑 南京云锦非遗氛围,鎏金朱红经典配色
深金头部 primaryDark #8F6B2E底 + 白字标题 + primaryLight浅金副标题 深金背景营造浓郁云锦工坊氛围,右侧🪡🌈🧵图标区 头部视觉焦点,云锦工坊氛围
头部🪡translate位移动画 needleFloat状态 + translate y从4到-10 + animation 640ms EaseOut 点击🪡上下浮动,模拟绣针在织机上上下穿梭(云锦织造核心动作) 交互趣味性,绣针浮动特色动画
头部🌈旋转120度动画 cloudSpin状态 + rotate angle 120 + animation 700ms EaseOut 点击🌈旋转120度,模拟云锦彩虹旋转(云霞灿烂意象,120度三分之一圈模拟三色纹样流转) 交互趣味性,彩虹旋转特色动画
12点云锦装饰纹 BROCADE_COL 0-11 + r%3三种形状 12x12鎏金圆带2px丝白边(模拟金线团花)/6x14朱红竖线(模拟织机经线)/8x8靛蓝圆(模拟靛蓝纬线) 云锦纹样模拟,金线团花经纬线核心元素
鎏金gold装饰 #C9A24B用于装饰圆(12x12带丝白边) 与primary同色但语义不同,模拟金线团花(云锦最华贵元素) 装饰专用色,金线团花模拟
丝白silkWhite装饰 #F7F3E9用于装饰圆边框 独立装饰色,模拟丝质光泽(与white纯白不同,丝白偏米黄更有丝质感) 装饰专用色,丝质光泽模拟
云锦图标区丝白圆纹样色边 14x14 silkWhite圆 + 2px getStyleColor纹样色边框 + 8x8纹样色小圆 云锦页特色,丝白圆带纹样色边模拟云锦团花轮廓+纹样色小圆模拟纹样点 云锦团花模拟,主题高度契合
卡片圆角16 所有卡片圆角16(非18/20) 云锦主题用更小的圆角,更精致 主题契合,精致感
十六进制透明度拼接 color+‘14’/‘40’/‘55’/‘66’/‘80’/‘0D’/‘33’/‘2E’ #RRGGBBAA格式,字符串拼接动态生成半透明色 减少颜色常量,高级技巧
多组件架构 1入口+1标签+5内容+4弹窗=11组件 每个组件独立封装,职责单一,@Component装饰 高内聚低耦合,工程化
@Observed聚合数据类 BrocadeData一个类含5个数组 @Observed装饰整个类,对象字面量初始化非constructor 数据集中管理,聚合模式
@ObjectLink数据共享 子组件@ObjectLink data: BrocadeData 引用传递双向同步,一个数据对象贯穿所有页面 跨组件数据共享,高级实践
回调函数父子通信 onAdd/onDel/onDetail/onEdit/onClose 子组件通过回调触发入口弹窗,入口通过onClose关闭 组件解耦,通信清晰
@Prop参数传递 DeleteThreadModal的title/DetailLoomModal的name 值传递单向同步,入口设置后自动同步到弹窗 弹窗动态参数,灵活
BrocadeTag子组件 @Prop text + @Prop color 封装标签样式,color+'14’背景+color+'40’边框 标签组件复用,一致性
5个颜色分级函数 getStyleColor/getLoomColor/getThreadColor/getOrderColor/getReviewTagColor 按纹样/精度/价格/金额/标签分级返回颜色 数据即配色,逻辑抽离
纹样四级着色 getStyleColor 团花鎏金/龙凤朱红/缠枝靛蓝/其他深金 南京云锦4种主要纹样,团花最常见用鎏金 以纹样定颜色,形制着色
价格四级着色 getThreadColor ≥1200朱红/≥800鎏金/≥500靛蓝/其他深金 云锦价格480-1880,百子图1880用accent朱红最高端 以价格定颜色,动态着色
金额四级着色 getOrderColor ≥55万朱红/≥45万鎏金/≥35万靛蓝/其他深金 订单金额30万-65万,汉服高定65万用accent朱红最大额 以金额定颜色,动态着色
精度三级着色 getLoomColor ≥95鎏金/≥92朱红/其他靛蓝 织机精度90-96,花楼织机96用鎏金最高(云锦专用大花楼木织机) 以精度定颜色,动态着色
本周销量柱状图 WEEK_SOLD 7天 + height=w.value直接用数值 柱子高度=数值非比例映射,周末≥60用accent朱红/其他primary鎏金,最大66px 数值即高度,云锦高端小众低销量
横向进度条layoutWeight layoutWeight(t.sold) + layoutWeight(100-t.sold) 弹性布局动态分配宽度,非固定宽度非百分比 ArkUI特有进度条实现
纹样构成进度条 layoutWeight(s.count) + layoutWeight(20-s.count) 总份数20非100,团花×5占比最高(云锦最常见纹样) 灵活比例,纹样形制价值
云锦销量TOP6 BROCADE_TOP 6款 + 横向进度条 龙纹云锦97居首,进度条几乎满格 云锦排行一目了然
织造工艺热度 BROCADE_HOT 6工艺 + 横向进度条 通经断纬97居首,覆盖云锦核心织造工艺6种(通经断纬/妆花织造/挖花盘织/逐花异色/金线显花/纬线起花) 工艺热度排行,云锦文化
奇偶行交替条纹 i%2=0 cardBg白 / i%2=1 cardAlt浅米黄 所有列表页共用,营造条纹效果 列表可读性,视觉层次
织机装饰圆带丝白边 10x10 getLoomColor圆 + 2px silkWhite丝白边框 织机页图标装饰区,模拟织机丝质光泽 丝质光泽模拟,特色
精度内嵌进度条 内嵌列表行 + layoutWeight(c.level) 织机库每行内嵌精度进度条,getLoomColor着色 精度可视化,专业感
星级字符串重复 ‘⭐’.repeat(r.score) 纯字符串乘法生成星级,非循环非组件 轻量级星级展示,实用技巧
tabBar顶部条用accent朱红 3px accent #A63A2B 本应用特色,朱红更符合云锦华贵主题 主题契合,朱红特色
tabBar激活态圆角10 t.color文字+背景t.color+'14’圆角10+border 1px t.color 5个Tab各自颜色激活,圆角10(比剑的12小,比象棋的6大) 非遗导航,色彩丰富
四大弹窗子组件 AddBrocadeModal/EditBrocadeOrderModal/DeleteThreadModal/DetailLoomModal 独立@Component,@Prop参数,onClose回调,#66000000遮罩 增删改查闭环,组件复用
弹窗@Prop动态参数 DeleteThreadModal的title / DetailLoomModal的name 入口设置delThreadName/detailLoomName后@Prop同步 弹窗内容动态化,灵活
modalOverlay特殊实现 width1 height1 Column+透明Text+透明Button 占位用,实际弹窗用独立背景#66000000 兼容框架要求,特殊处理
12款云锦经典纹样 龙纹云锦/凤穿牡丹/缠枝莲纹/云鹤纹袍/海水江崖/万寿如意/妆花缎/织金锦/团龙补子/百子图/四合如意/云锦围巾 价格480-1880,百子图1880最高端,真丝/桑蚕丝/金丝 南京云锦经典纹样,文化全收录
12种传统织机 花楼织机/束综织机/提花织机/斜织机/竖织机/罗织机/绫织机/绢织机/绒织机/纱织机/缎织机/双经织机 精度90-96,花楼织机96最高(云锦专用大花楼木织机需两人配合),产地江南丝绸重镇 中国传统织机生态,江南产地
12种丝线全品类 湖丝/桑蚕丝/柞蚕丝/真丝绞/金线/银线/孔雀羽线/彩绒线/双宫丝/绢丝/生丝/熟丝 品级89-96,湖丝96最高(浙江湖州辑里村皇家御用),覆盖蚕丝/金银线/羽线/绒线 云锦丝线生态,全品类
6种织造核心工艺 通经断纬/妆花织造/挖花盘织/逐花异色/金线显花/纬线起花 热度97-88,通经断纬最核心(云锦核心技法经线贯通纬线分段挖花) 云锦织造工艺文化,技术多样
4种纹样构成 团花×5/龙凤×4/缠枝×3/云纹×2 南京云锦4种主要纹样类别,团花最常见占比最高 云锦纹样文化,多样性
12种B端客户生态 故宫文创/苏绣研究所/汉服高定品牌/婚庆礼服馆/影视剧组/博物馆文创店/海外丝绸馆/高端酒店布草/非遗体验馆/直播电商团/旗袍定制店/国礼供应处 云锦高端客户以故宫/汉服高定/婚庆/影视/博物馆/国礼等文化高端机构为主 云锦商业生态,高端小众
数据规模 5业务数组各12条=60条+4统计数组23条=83条 云锦/织机/丝线/订单/客评各12,统计7+4+6+6 完整数据生态,南京云锦非遗

完整代码

interface BrocadePalette {
  primary: string;
  primaryLight: string;
  primaryDark: string;
  accent: string;
  accentLight: string;
  bg: string;
  cardBg: string;
  cardAlt: string;
  textPrimary: string;
  textSecondary: string;
  textHint: string;
  border: string;
  line: string;
  success: string;
  warning: string;
  danger: string;
  white: string;
  gold: string;
  silkWhite: string;
}
const COLORS: BrocadePalette = {
  primary: '#C9A24B',
  primaryLight: '#DDBB72',
  primaryDark: '#8F6B2E',
  accent: '#A63A2B',
  accentLight: '#C98D82',
  bg: '#F6F1E7',
  cardBg: '#FFFFFF',
  cardAlt: '#F1E8D8',
  textPrimary: '#3B2F1E',
  textSecondary: '#7A6A52',
  textHint: '#A9977C',
  border: '#E0D3BC',
  line: '#EDE4D2',
  success: '#4E7BA8',
  warning: '#C98A4E',
  danger: '#9E2E20',
  white: '#FFFFFF',
  gold: '#C9A24B',
  silkWhite: '#F7F3E9'
};
enum BrocadeTab {
  BROCADE = 0,
  LOOM = 1,
  THREAD = 2,
  ORDER = 3,
  REVIEW = 4
}
interface TabMeta {
  key: string;
  icon: string;
  label: string;
  color: string;
}
const TAB_LIST: TabMeta[] = [
  { key: 'brocade', icon: '🧵', label: '云锦', color: '#C9A24B' },
  { key: 'loom', icon: '🪵', label: '织机', color: '#A63A2B' },
  { key: 'thread', icon: '🪡', label: '丝线', color: '#4E7BA8' },
  { key: 'order', icon: '📦', label: '订单', color: '#C98A4E' },
  { key: 'review', icon: '⭐', label: '客评', color: '#8F6B2E' }
];
const BROCADE_COL: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
interface BrocadeItem {
  name: string;
  style: string;
  thread: string;
  price: number;
  emoji: string;
}
interface LoomItem {
  name: string;
  weave: string;
  level: number;
  place: string;
  emoji: string;
}
interface ThreadItem {
  name: string;
  silk: string;
  level: number;
  tool: string;
  emoji: string;
}
interface BrocadeOrderItem {
  name: string;
  region: string;
  count: number;
  amount: number;
  emoji: string;
}
interface BrocadeReviewItem {
  name: string;
  score: number;
  date: string;
  content: string;
  tag: string;
  emoji: string;
}
interface WeekBrocadeMeta {
  day: string;
  value: number;
}
interface StyleShareMeta {
  name: string;
  count: number;
  color: string;
}
interface BrocadeHotMeta {
  name: string;
  heat: number;
  color: string;
}
interface BrocadeTopMeta {
  name: string;
  sold: number;
  color: string;
}
const WEEK_SOLD: WeekBrocadeMeta[] = [
  { day: '周一', value: 26 },
  { day: '周二', value: 32 },
  { day: '周三', value: 41 },
  { day: '周四', value: 48 },
  { day: '周五', value: 59 },
  { day: '周六', value: 66 },
  { day: '周日', value: 60 }
];
const CLOUD_SHARE: StyleShareMeta[] = [
  { name: '团花', count: 5, color: '#C9A24B' },
  { name: '龙凤', count: 4, color: '#A63A2B' },
  { name: '缠枝', count: 3, color: '#4E7BA8' },
  { name: '云纹', count: 2, color: '#8F6B2E' }
];
const BROCADE_HOT: BrocadeHotMeta[] = [
  { name: '通经断纬', heat: 97, color: '#C9A24B' },
  { name: '妆花织造', heat: 95, color: '#A63A2B' },
  { name: '挖花盘织', heat: 93, color: '#4E7BA8' },
  { name: '逐花异色', heat: 92, color: '#DDBB72' },
  { name: '金线显花', heat: 90, color: '#C98A4E' },
  { name: '纬线起花', heat: 88, color: '#8F6B2E' }
];
const BROCADE_TOP: BrocadeTopMeta[] = [
  { name: '龙纹云锦', sold: 97, color: '#C9A24B' },
  { name: '凤穿牡丹', sold: 92, color: '#A63A2B' },
  { name: '妆花缎', sold: 88, color: '#4E7BA8' },
  { name: '织金锦', sold: 85, color: '#DDBB72' },
  { name: '海水江崖', sold: 81, color: '#C98A4E' },
  { name: '云鹤纹袍', sold: 78, color: '#8F6B2E' }
];
@Observed
export class BrocadeData {
  brocades: BrocadeItem[] = [
    { name: '龙纹云锦', style: '团花', thread: '真丝', price: 680, emoji: '🐉' },
    { name: '凤穿牡丹', style: '龙凤', thread: '桑蚕丝', price: 880, emoji: '🦚' },
    { name: '缠枝莲纹', style: '缠枝', thread: '真丝', price: 520, emoji: '🌸' },
    { name: '云鹤纹袍', style: '云纹', thread: '桑蚕丝', price: 980, emoji: '🕊️' },
    { name: '海水江崖', style: '团花', thread: '真丝', price: 760, emoji: '🌊' },
    { name: '万寿如意', style: '缠枝', thread: '桑蚕丝', price: 620, emoji: '🎐' },
    { name: '妆花缎', style: '龙凤', thread: '真丝', price: 1280, emoji: '👑' },
    { name: '织金锦', style: '云纹', thread: '金丝', price: 1580, emoji: '✨' },
    { name: '团龙补子', style: '团花', thread: '桑蚕丝', price: 1180, emoji: '🐲' },
    { name: '百子图', style: '云纹', thread: '真丝', price: 1880, emoji: '🧒' },
    { name: '四合如意', style: '缠枝', thread: '桑蚕丝', price: 560, emoji: '🍀' },
    { name: '云锦围巾', style: '团花', thread: '真丝', price: 480, emoji: '🧣' }
  ];
  looms: LoomItem[] = [
    { name: '花楼织机', weave: '大花楼', level: 96, place: '江宁', emoji: '🏗️' },
    { name: '束综织机', weave: '小花楼', level: 95, place: '苏州', emoji: '🌉' },
    { name: '提花织机', weave: '提花', level: 94, place: '杭州', emoji: '⚙️' },
    { name: '斜织机', weave: '斜织', level: 93, place: '湖州', emoji: '🪵' },
    { name: '竖织机', weave: '竖织', level: 92, place: '嘉兴', emoji: '🌲' },
    { name: '罗织机', weave: '罗纹', level: 91, place: '南京', emoji: '🕸️' },
    { name: '绫织机', weave: '绫纹', level: 94, place: '镇江', emoji: '🎗️' },
    { name: '绢织机', weave: '绢纹', level: 92, place: '常州', emoji: '🧈' },
    { name: '绒织机', weave: '绒纹', level: 90, place: '扬州', emoji: '🧸' },
    { name: '纱织机', weave: '纱纹', level: 91, place: '无锡', emoji: '🌬️' },
    { name: '缎织机', weave: '缎纹', level: 95, place: '绍兴', emoji: '💎' },
    { name: '双经织机', weave: '双经', level: 93, place: '宁波', emoji: '⚡' }
  ];
  threads: ThreadItem[] = [
    { name: '湖丝', silk: '家蚕', level: 96, tool: '辑里村', emoji: '🐛' },
    { name: '桑蚕丝', silk: '桑蚕', level: 95, tool: '吴兴坊', emoji: '🍃' },
    { name: '柞蚕丝', silk: '柞蚕', level: 93, tool: '丹东场', emoji: '🌰' },
    { name: '真丝绞', silk: '家蚕', level: 94, tool: '湖州坊', emoji: '🌀' },
    { name: '金线', silk: '捻金', level: 92, tool: '南京工坊', emoji: '✨' },
    { name: '银线', silk: '捻银', level: 91, tool: '杭州工坊', emoji: '🥈' },
    { name: '孔雀羽线', silk: '羽捻', level: 90, tool: '苏州坊', emoji: '🦚' },
    { name: '彩绒线', silk: '绒捻', level: 93, tool: '扬州坊', emoji: '🧵' },
    { name: '双宫丝', silk: '双宫', level: 94, tool: '嘉兴场', emoji: '🥚' },
    { name: '绢丝', silk: '下脚茧', level: 89, tool: '无锡坊', emoji: '🧣' },
    { name: '生丝', silk: '缫丝', level: 95, tool: '海宁坊', emoji: '🌾' },
    { name: '熟丝', silk: '煮练', level: 92, tool: '常州坊', emoji: '♨️' }
  ];
  orders: BrocadeOrderItem[] = [
    { name: '故宫文创', region: '华北', count: 900, amount: 540000, emoji: '🏯' },
    { name: '苏绣研究所', region: '华东', count: 600, amount: 480000, emoji: '🏛️' },
    { name: '汉服高定品牌', region: '华东', count: 500, amount: 650000, emoji: '👘' },
    { name: '婚庆礼服馆', region: '华南', count: 400, amount: 560000, emoji: '💒' },
    { name: '影视剧组', region: '华北', count: 300, amount: 450000, emoji: '🎬' },
    { name: '博物馆文创店', region: '华中', count: 700, amount: 420000, emoji: '🗿' },
    { name: '海外丝绸馆', region: '海外', count: 350, amount: 525000, emoji: '🌏' },
    { name: '高端酒店布草', region: '华东', count: 800, amount: 480000, emoji: '🏨' },
    { name: '非遗体验馆', region: '西南', count: 500, amount: 300000, emoji: '🎐' },
    { name: '直播电商团', region: '华东', count: 650, amount: 390000, emoji: '📺' },
    { name: '旗袍定制店', region: '华南', count: 450, amount: 540000, emoji: '🧧' },
    { name: '国礼供应处', region: '华北', count: 200, amount: 600000, emoji: '🎁' }
  ];
  reviews: BrocadeReviewItem[] = [
    { name: '故宫文创买手', score: 5, date: '09-03', content: '龙纹云锦色彩华贵,装裱后展厅里一眼惊艳。', tag: '寸锦寸金', emoji: '🏯' },
    { name: '苏绣研究员', score: 5, date: '09-02', content: '通经断纬工艺纯正,纹样细腻到可分毫。', tag: '工艺纯正', emoji: '🏛️' },
    { name: '汉服主理', score: 5, date: '09-01', content: '妆花缎垂坠流光,客户试穿后当场下单。', tag: '流光溢彩', emoji: '👘' },
    { name: '婚庆策划师', score: 5, date: '08-31', content: '凤穿牡丹用在喜服上,寓意喜庆面料考究。', tag: '喜庆考究', emoji: '💒' },
    { name: '影视服装师', score: 5, date: '08-30', content: '织金锦质感上镜,古装剧补拍一直指定这家的。', tag: '质感上镜', emoji: '🎬' },
    { name: '博物馆店长', score: 5, date: '08-29', content: '云鹤纹围巾做工精湛,游客爱不释手。', tag: '做工精湛', emoji: '🕊️' },
    { name: '海外买手', score: 5, date: '08-28', content: '海外藏家认可度极高,云锦是行走的中国名片。', tag: '海外认可', emoji: '🌏' },
    { name: '酒店采购', score: 4, date: '08-27', content: '床旗配色雅致,若幅面再宽些更佳。', tag: '配色雅致', emoji: '🏨' },
    { name: '体验馆馆长', score: 4, date: '08-26', content: '研学团上手织机体验物料齐全,若加图解更贴心。', tag: '物料齐全', emoji: '🎐' },
    { name: '直播选品', score: 5, date: '08-25', content: '云锦围巾走量飞快,上架两小时售罄补货。', tag: '走量飞快', emoji: '📺' },
    { name: '旗袍店长', score: 5, date: '08-24', content: '四合如意纹配旗袍,端庄里透着灵动。', tag: '端庄灵动', emoji: '🧧' },
    { name: '老主顾', score: 5, date: '08-23', content: '收藏第三幅云锦了,每一寸都是光阴的味道。', tag: '岁月织就', emoji: '🎁' }
  ];
}
function getStyleColor(style: string): string {
  if (style === '团花') {
    return '#C9A24B';
  } else if (style === '龙凤') {
    return '#A63A2B';
  } else if (style === '缠枝') {
    return '#4E7BA8';
  }
  return '#8F6B2E';
}
function getLoomColor(level: number): string {
  if (level >= 95) {
    return '#C9A24B';
  } else if (level >= 92) {
    return '#A63A2B';
  }
  return '#4E7BA8';
}
function getThreadColor(price: number): string {
  if (price >= 1200) {
    return '#A63A2B';
  } else if (price >= 800) {
    return '#C9A24B';
  } else if (price >= 500) {
    return '#4E7BA8';
  }
  return '#8F6B2E';
}
function getOrderColor(amount: number): string {
  if (amount >= 550000) {
    return '#A63A2B';
  } else if (amount >= 450000) {
    return '#C9A24B';
  } else if (amount >= 350000) {
    return '#4E7BA8';
  }
  return '#8F6B2E';
}
function getReviewTagColor(tag: string): string {
  if (tag === '寸锦寸金' || tag === '工艺纯正' || tag === '流光溢彩' || tag === '做工精湛') {
    return '#C9A24B';
  } else if (tag === '喜庆考究' || tag === '质感上镜' || tag === '海外认可') {
    return '#A63A2B';
  } else if (tag === '配色雅致' || tag === '物料齐全' || tag === '端庄灵动' || tag === '岁月织就') {
    return '#4E7BA8';
  } else if (tag === '走量飞快') {
    return '#C98A4E';
  }
  return '#8F6B2E';
}
@Entry
@Component
struct BrocadeApp {
  @State curTab: number = 0;
  @State data: BrocadeData = new BrocadeData();
  @State showAddBrocade: boolean = false;
  @State showEditOrder: boolean = false;
  @State showDeleteThread: boolean = false;
  @State showDetailLoom: boolean = false;
  @State delThreadName: string = '';
  @State detailLoomName: string = '';
  @State needleFloat: boolean = false;
  @State cloudSpin: boolean = false;
  @Builder modalOverlay(onClose: () => void) {
    Column() {
      Text('')
        .width(0)
        .height(0)
        .opacity(0)
      Button('')
        .width(1)
        .height(1)
        .opacity(0)
        .onClick(() => {
          onClose();
        })
    }
    .width(1)
    .height(1)
  }
  build() {
    Column() {
      Column() {
        Column() {
          Row() {
            Column() {
              Text('🧵 云锦坊')
                .fontSize(20)
                .fontWeight(FontWeight.Bold)
                .fontColor(COLORS.white)
              Text('Brocade · 通经断纬 寸锦寸金')
                .fontSize(10)
                .fontColor(COLORS.primaryLight)
                .margin({ top: 3 })
            }
            .alignItems(HorizontalAlign.Start)
            .layoutWeight(1)
            Row() {
              Text('🪡')
                .fontSize(18)
                .onClick(() => {
                  this.needleFloat = !this.needleFloat;
                })
                .translate({ x: this.needleFloat ? 0 : 0, y: this.needleFloat ? -10 : 4 })
                .animation({ duration: 640, curve: Curve.EaseOut })
              Text('🌈')
                .fontSize(16)
                .margin({ left: 10 })
                .onClick(() => {
                  this.cloudSpin = !this.cloudSpin;
                })
                .rotate({ angle: this.cloudSpin ? 120 : 0 })
                .animation({ duration: 700, curve: Curve.EaseOut })
              Text('🧵')
                .fontSize(14)
                .margin({ left: 6 })
            }
            .padding({ left: 10, right: 10, top: 6, bottom: 6 })
            .backgroundColor(COLORS.primaryLight + '2E')
            .borderRadius(24)
            .border({ width: 1, color: COLORS.primaryLight + '80' })
          }
          .width('100%')
          Row() {
            ForEach(BROCADE_COL, (r: number) => {
              Row() {
                if (r % 3 === 0) {
                  Column()
                    .width(12)
                    .height(12)
                    .backgroundColor(COLORS.gold)
                    .borderRadius(6)
                    .border({ width: 2, color: COLORS.silkWhite })
                } else if (r % 3 === 1) {
                  Column()
                    .width(6)
                    .height(14)
                    .backgroundColor(COLORS.accent)
                    .borderRadius(3)
                } else {
                  Column()
                    .width(8)
                    .height(8)
                    .backgroundColor(COLORS.success)
                    .borderRadius(4)
                }
              }
              .width(14)
              .justifyContent(FlexAlign.Center)
            }, (r: number) => 'brocade' + r)
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .margin({ top: 12 })
          Row() {
            Column()
              .layoutWeight(1)
              .height(1)
              .backgroundColor(COLORS.primaryLight + '66')
            Text('✨ 云霞灿烂 · 织就天衣 🧵')
              .fontSize(9)
              .fontColor(COLORS.primaryLight)
              .margin({ left: 8, right: 8 })
            Column()
              .layoutWeight(1)
              .height(1)
              .backgroundColor(COLORS.primaryLight + '66')
          }
          .width('100%')
          .margin({ top: 10 })
        }
        .width('100%')
        .padding({ left: 16, right: 16, top: 12, bottom: 12 })
        .backgroundColor(COLORS.primaryDark)
        if (this.curTab === BrocadeTab.BROCADE) {
          BrocadeContent({ data: this.data, onAdd: () => {
            this.showAddBrocade = true;
          } })
        } else if (this.curTab === BrocadeTab.LOOM) {
          LoomContent({ data: this.data, onDel: (n: string) => {
            this.delThreadName = n;
            this.showDeleteThread = true;
          } })
        } else if (this.curTab === BrocadeTab.THREAD) {
          ThreadContent({ data: this.data, onDetail: (n: string) => {
            this.detailLoomName = n;
            this.showDetailLoom = true;
          } })
        } else if (this.curTab === BrocadeTab.ORDER) {
          BrocadeOrderContent({ data: this.data, onEdit: () => {
            this.showEditOrder = true;
          } })
        } else {
          BrocadeReviewContent({ data: this.data })
        }
      }
      .width('100%')
      .height('100%')
      Column() {
        Column()
          .width('100%')
          .height(3)
          .backgroundColor(COLORS.accent)
        Row() {
          ForEach(TAB_LIST, (t: TabMeta) => {
            Column() {
              Text(t.icon)
                .fontSize(19)
              Text(t.label)
                .fontSize(10)
                .fontColor(this.curTab === TAB_LIST.indexOf(t) ? t.color : COLORS.textHint)
                .fontWeight(this.curTab === TAB_LIST.indexOf(t) ? FontWeight.Bold : FontWeight.Normal)
                .margin({ top: 2 })
            }
            .layoutWeight(1)
            .justifyContent(FlexAlign.Center)
            .padding({ top: 7, bottom: 7 })
            .backgroundColor(this.curTab === TAB_LIST.indexOf(t) ? t.color + '14' : '#00000000')
            .borderRadius(10)
            .border(this.curTab === TAB_LIST.indexOf(t) ? { width: 1, color: t.color } : { width: 0 })
            .onClick(() => {
              this.curTab = TAB_LIST.indexOf(t);
            })
          }, (t: TabMeta) => t.key)
        }
        .width('100%')
        .height(60)
        .padding({ left: 8, right: 8 })
        .backgroundColor(COLORS.cardBg)
      }
      .width('100%')
      if (this.showAddBrocade) {
        AddBrocadeModal({
          onClose: () => {
            this.showAddBrocade = false;
          }
        })
      }
      if (this.showEditOrder) {
        EditBrocadeOrderModal({
          onClose: () => {
            this.showEditOrder = false;
          }
        })
      }
      if (this.showDeleteThread) {
        DeleteThreadModal({
          title: this.delThreadName, onClose: () => {
            this.showDeleteThread = false;
          }
        })
      }
      if (this.showDetailLoom) {
        DetailLoomModal({
          name: this.detailLoomName, onClose: () => {
            this.showDetailLoom = false;
          }
        })
      }
    }
  }
}
@Component
struct BrocadeTag {
  @Prop text: string;
  @Prop color: string;
  build() {
    Text(this.text)
      .fontSize(9)
      .fontColor(this.color)
      .padding({ left: 7, right: 7, top: 2, bottom: 2 })
      .backgroundColor(this.color + '14')
      .borderRadius(10)
      .border({ width: 1, color: this.color + '40' })
  }
}
@Component
struct BrocadeContent {
  @ObjectLink data: BrocadeData;
  onAdd: () => void = () => {};
  build() {
    Scroll() {
      Column() {
        Column() {
          Row() {
            Column()
              .width(10)
              .height(10)
              .backgroundColor(COLORS.gold)
              .borderRadius(5)
              .border({ width: 2, color: COLORS.silkWhite })
            Column()
              .layoutWeight(1)
              .height(2)
              .backgroundColor(COLORS.accent + '55')
              .margin({ left: 4, right: 4 })
            Column()
              .width(6)
              .height(6)
              .backgroundColor(COLORS.success)
              .borderRadius(3)
              .margin({ right: 4 })
            Column()
              .width(10)
              .height(10)
              .backgroundColor(COLORS.accent)
              .borderRadius(5)
              .border({ width: 2, color: COLORS.silkWhite })
            Column()
              .layoutWeight(1)
              .height(2)
              .backgroundColor(COLORS.accent + '55')
              .margin({ left: 4, right: 4 })
            Column()
              .width(10)
              .height(10)
              .backgroundColor(COLORS.gold)
              .borderRadius(5)
              .border({ width: 2, color: COLORS.silkWhite })
          }
          .width('100%')
          .margin({ bottom: 10 })
          Row() {
            Text('📈 本周云锦销量')
              .fontSize(13)
              .fontWeight(FontWeight.Bold)
              .fontColor(COLORS.textPrimary)
            Text('周六66匹')
              .fontSize(9)
              .fontColor(COLORS.accent)
              .margin({ left: 8 })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          Row() {
            ForEach(WEEK_SOLD, (w: WeekBrocadeMeta) => {
              Column() {
                Text(w.value + '')
                  .fontSize(8)
                  .fontColor(w.value >= 60 ? COLORS.accent : COLORS.primary)
                Column()
                  .width(16)
                  .height(w.value)
                  .backgroundColor(w.value >= 60 ? COLORS.accent : COLORS.primary)
                  .borderRadius(8)
                  .margin({ top: 4 })
                Text(w.day)
                  .fontSize(9)
                  .fontColor(COLORS.textSecondary)
                  .margin({ top: 4 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Center)
            }, (w: WeekBrocadeMeta) => w.day)
          }
          .width('100%')
          .height(150)
          .alignItems(VerticalAlign.Bottom)
          .margin({ top: 10 })
        }
        .width('100%')
        .padding(14)
        .backgroundColor(COLORS.cardBg)
        .borderRadius(16)
        .border({ width: 1, color: COLORS.accent + '55' })
        Column() {
          Row() {
            Text('🧵 云锦陈列')
              .fontSize(13)
              .fontWeight(FontWeight.Bold)
              .fontColor(COLORS.textPrimary)
            Text('点击➕织造')
              .fontSize(9)
              .fontColor(COLORS.textHint)
              .margin({ left: 8 })
            Text('➕')
              .fontSize(14)
              .margin({ left: 8 })
              .onClick(() => {
                this.onAdd();
              })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .margin({ bottom: 10 })
          ForEach(this.data.brocades, (p: BrocadeItem, i: number) => {
            Row() {
              Column() {
                Column() {
                  Text(p.emoji)
                    .fontSize(18)
                    .textAlign(TextAlign.Center)
                }
                .width(44)
                .height(44)
                .justifyContent(FlexAlign.Center)
                .alignItems(HorizontalAlign.Center)
                .backgroundColor(getStyleColor(p.style) + '14')
                .borderRadius(16)
                .border({ width: 1, color: getStyleColor(p.style) + '66' })
                Row() {
                  Column()
                    .width(14)
                    .height(14)
                    .backgroundColor(COLORS.silkWhite)
                    .borderRadius(7)
                    .border({ width: 2, color: getStyleColor(p.style) })
                  Column()
                    .width(8)
                    .height(8)
                    .backgroundColor(getStyleColor(p.style))
                    .borderRadius(4)
                    .margin({ left: 4 })
                }
                .width(30)
                .justifyContent(FlexAlign.Center)
              }
              .width(60)
              .padding({ top: 10, bottom: 6 })
              .backgroundColor(getStyleColor(p.style) + '0D')
              .borderRadius(16)
              .border({ width: 1, color: getStyleColor(p.style) + '33' })
              Column() {
                Row() {
                  Text(p.name)
                    .fontSize(13)
                    .fontWeight(FontWeight.Bold)
                    .fontColor(COLORS.textPrimary)
                  Text('¥' + p.price)
                    .fontSize(10)
                    .fontWeight(FontWeight.Bold)
                    .fontColor(getThreadColor(p.price))
                    .margin({ left: 8 })
                }
                .width('100%')
                .justifyContent(FlexAlign.SpaceBetween)
                Row() {
                  BrocadeTag({ text: p.style, color: getStyleColor(p.style) })
                  BrocadeTag({ text: p.thread, color: COLORS.success })
                  Text('寸锦寸金')
                    .fontSize(9)
                    .fontColor(COLORS.textHint)
                    .margin({ left: 8 })
                }
                .width('100%')
                .justifyContent(FlexAlign.Start)
                .margin({ top: 6 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)
              .margin({ left: 10 })
            }
            .width('100%')
            .padding(10)
            .backgroundColor(i % 2 === 0 ? COLORS.cardBg : COLORS.cardAlt)
            .borderRadius(16)
            .border({ width: 1, color: COLORS.line })
            .margin({ top: 8 })
          }, (p: BrocadeItem, i: number) => 'bc' + p.name + i)
        }
        .width('100%')
        .margin({ top: 12 })
      }
      .width('100%')
      .constraintSize({ maxHeight: '100%' })
    }
    .width('100%')
    .constraintSize({ maxHeight: '100%' })
    .scrollable(ScrollDirection.Vertical)
    .scrollBar(BarState.Off)
  }
}
@Component
struct LoomContent {
  @ObjectLink data: BrocadeData;
  onDel: (n: string) => void = () => {};
  build() {
    Scroll() {
      Column() {
        Column() {
          Row() {
            Text('🏆 云锦销量 TOP6')
              .fontSize(13)
              .fontWeight(FontWeight.Bold)
              .fontColor(COLORS.textPrimary)
            Text('龙纹云锦居首')
              .fontSize(9)
              .fontColor(COLORS.textHint)
              .margin({ left: 8 })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .margin({ bottom: 10 })
          ForEach(BROCADE_TOP, (t: BrocadeTopMeta) => {
            Row() {
              Text(t.name)
                .fontSize(10)
                .fontColor(COLORS.textPrimary)
                .width(90)
              Row() {
                Column()
                  .layoutWeight(t.sold)
                  .height(12)
                  .backgroundColor(t.color)
                  .borderRadius(6)
                Column()
                  .layoutWeight(100 - t.sold)
                  .height(12)
                  .backgroundColor(COLORS.line)
                  .borderRadius(6)
              }
              .layoutWeight(1)
              Text(t.sold + '匹')
                .fontSize(9)
                .fontColor(t.color)
                .width(40)
                .textAlign(TextAlign.End)
            }
            .width('100%')
            .margin({ top: 6 })
          }, (t: BrocadeTopMeta) => t.name)
        }
        .width('100%')
        .padding(14)
        .backgroundColor(COLORS.cardBg)
        .borderRadius(16)
        .border({ width: 1, color: COLORS.accent + '55' })
        Column() {
          Row() {
            Text('🪵 织机库')
              .fontSize(13)
              .fontWeight(FontWeight.Bold)
              .fontColor(COLORS.textPrimary)
            Text('点击🗑️撤机')
              .fontSize(9)
              .fontColor(COLORS.textHint)
              .margin({ left: 8 })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .margin({ bottom: 10 })
          ForEach(this.data.looms, (c: LoomItem, i: number) => {
            Row() {
              Column() {
                Text(c.emoji)
                  .fontSize(18)
                  .textAlign(TextAlign.Center)
                Row() {
                  Column()
                    .width(10)
                    .height(10)
                    .backgroundColor(getLoomColor(c.level))
                    .borderRadius(5)
                    .border({ width: 2, color: COLORS.silkWhite })
                  Column()
                    .width(8)
                    .height(8)
                    .backgroundColor(COLORS.silkWhite)
                    .borderRadius(4)
                    .margin({ left: 4 })
                }
                .width(28)
                .justifyContent(FlexAlign.Center)
                .margin({ top: 5 })
              }
              .width(56)
              .padding({ top: 10, bottom: 6 })
              .backgroundColor(getLoomColor(c.level) + '0D')
              .borderRadius(16)
              .border({ width: 1, color: getLoomColor(c.level) + '33' })
              Column() {
                Row() {
                  Text(c.name)
                    .fontSize(13)
                    .fontWeight(FontWeight.Bold)
                    .fontColor(COLORS.textPrimary)
                  Text(c.weave + '织')
                    .fontSize(9)
                    .fontColor(COLORS.textSecondary)
                    .margin({ left: 8 })
                }
                .width('100%')
                .justifyContent(FlexAlign.SpaceBetween)
                Row() {
                  Text('织造精度')
                    .fontSize(9)
                    .fontColor(COLORS.textHint)
                  Row() {
                    Column()
                      .layoutWeight(c.level)
                      .height(6)
                      .backgroundColor(getLoomColor(c.level))
                      .borderRadius(3)
                    Column()
                      .layoutWeight(100 - c.level)
                      .height(6)
                      .backgroundColor(COLORS.line)
                      .borderRadius(3)
                  }
                  .layoutWeight(1)
                  .margin({ left: 6 })
                }
                .width('100%')
                .margin({ top: 6 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)
              .margin({ left: 10 })
              Text('🗑️')
                .fontSize(14)
                .onClick(() => {
                  this.onDel(c.name);
                })
            }
            .width('100%')
            .padding(10)
            .backgroundColor(i % 2 === 0 ? COLORS.cardBg : COLORS.cardAlt)
            .borderRadius(16)
            .border({ width: 1, color: COLORS.line })
            .margin({ top: 8 })
          }, (c: LoomItem, i: number) => 'lm' + c.name + i)
        }
        .width('100%')
        .margin({ top: 12 })
      }
      .width('100%')
      .constraintSize({ maxHeight: '100%' })
    }
    .width('100%')
    .constraintSize({ maxHeight: '100%' })
    .scrollable(ScrollDirection.Vertical)
    .scrollBar(BarState.Off)
  }
}
@Component
struct ThreadContent {
  @ObjectLink data: BrocadeData;
  onDetail: (n: string) => void = () => {};
  build() {
    Scroll() {
      Column() {
        Column() {
          Row() {
            Text('🔥 织造工艺热度')
              .fontSize(13)
              .fontWeight(FontWeight.Bold)
              .fontColor(COLORS.textPrimary)
            Text('通经断纬97')
              .fontSize(9)
              .fontColor(COLORS.primary)
              .margin({ left: 8 })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .margin({ bottom: 10 })
          ForEach(BROCADE_HOT, (d: BrocadeHotMeta) => {
            Row() {
              Text(d.name)
                .fontSize(10)
                .fontColor(COLORS.textPrimary)
                .width(80)
              Row() {
                Column()
                  .layoutWeight(d.heat)
                  .height(14)
                  .backgroundColor(d.color)
                  .borderRadius(7)
                Column()
                  .layoutWeight(100 - d.heat)
                  .height(14)
                  .backgroundColor(COLORS.line)
                  .borderRadius(7)
              }
              .layoutWeight(1)
              Text(d.heat + '')
                .fontSize(9)
                .fontColor(d.color)
                .width(32)
                .textAlign(TextAlign.End)
            }
            .width('100%')
            .margin({ top: 6 })
          }, (d: BrocadeHotMeta) => d.name)
        }
        .width('100%')
        .padding(14)
        .backgroundColor(COLORS.cardBg)
        .borderRadius(16)
        .border({ width: 1, color: COLORS.accent + '55' })
        Column() {
          Row() {
            Text('🪡 丝线坊')
              .fontSize(13)
              .fontWeight(FontWeight.Bold)
              .fontColor(COLORS.textPrimary)
            Text('点击👁️了解')
              .fontSize(9)
              .fontColor(COLORS.textHint)
              .margin({ left: 8 })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .margin({ bottom: 10 })
          ForEach(this.data.threads, (d: ThreadItem, i: number) => {
            Row() {
              Column() {
                Text(d.emoji)
                  .fontSize(18)
                  .textAlign(TextAlign.Center)
                Column()
                  .width(14)
                  .height(14)
                  .backgroundColor(getLoomColor(d.level))
                  .borderRadius(7)
                  .margin({ top: 5 })
              }
              .width(56)
              .padding({ top: 10, bottom: 6 })
              .backgroundColor(getLoomColor(d.level) + '0D')
              .borderRadius(16)
              .border({ width: 1, color: getLoomColor(d.level) + '33' })
              Column() {
                Row() {
                  Text(d.name)
                    .fontSize(13)
                    .fontWeight(FontWeight.Bold)
                    .fontColor(COLORS.textPrimary)
                  Text('品级' + d.level)
                    .fontSize(9)
                    .fontColor(getLoomColor(d.level))
                    .margin({ left: 8 })
                }
                .width('100%')
                .justifyContent(FlexAlign.SpaceBetween)
                Row() {
                  BrocadeTag({ text: d.silk + '丝', color: COLORS.textSecondary })
                  Text(d.tool)
                    .fontSize(9)
                    .fontColor(COLORS.textHint)
                    .margin({ left: 8 })
                }
                .width('100%')
                .justifyContent(FlexAlign.Start)
                .margin({ top: 6 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)
              .margin({ left: 10 })
              Text('👁️')
                .fontSize(14)
                .onClick(() => {
                  this.onDetail(d.name);
                })
            }
            .width('100%')
            .padding(10)
            .backgroundColor(i % 2 === 0 ? COLORS.cardBg : COLORS.cardAlt)
            .borderRadius(16)
            .border({ width: 1, color: COLORS.line })
            .margin({ top: 8 })
          }, (d: ThreadItem, i: number) => 'th' + d.name + i)
        }
        .width('100%')
        .margin({ top: 12 })
      }
      .width('100%')
      .constraintSize({ maxHeight: '100%' })
    }
    .width('100%')
    .constraintSize({ maxHeight: '100%' })
    .scrollable(ScrollDirection.Vertical)
    .scrollBar(BarState.Off)
  }
}
@Component
struct BrocadeOrderContent {
  @ObjectLink data: BrocadeData;
  onEdit: () => void = () => {};
  build() {
    Scroll() {
      Column() {
        Column() {
          Row() {
            Text('🧩 纹样构成')
              .fontSize(13)
              .fontWeight(FontWeight.Bold)
              .fontColor(COLORS.textPrimary)
            Text('团花占比最高')
              .fontSize(9)
              .fontColor(COLORS.textHint)
              .margin({ left: 8 })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .margin({ bottom: 10 })
          ForEach(CLOUD_SHARE, (s: StyleShareMeta) => {
            Row() {
              Column()
                .width(8)
                .height(8)
                .backgroundColor(s.color)
                .borderRadius(4)
              Text(s.name)
                .fontSize(10)
                .fontColor(COLORS.textPrimary)
                .margin({ left: 6 })
              Text('×' + s.count)
                .fontSize(9)
                .fontColor(COLORS.textSecondary)
                .margin({ left: 4 })
              Row() {
                Column()
                  .layoutWeight(s.count)
                  .height(10)
                  .backgroundColor(s.color)
                  .borderRadius(5)
                Column()
                  .layoutWeight(20 - s.count)
                  .height(10)
                  .backgroundColor(COLORS.line)
                  .borderRadius(5)
              }
              .layoutWeight(1)
              .margin({ left: 10 })
            }
            .width('100%')
            .margin({ top: 6 })
          }, (s: StyleShareMeta) => s.name)
        }
        .width('100%')
        .padding(14)
        .backgroundColor(COLORS.cardBg)
        .borderRadius(16)
        .border({ width: 1, color: COLORS.accent + '55' })
        Column() {
          Row() {
            Text('📦 订单台账')
              .fontSize(13)
              .fontWeight(FontWeight.Bold)
              .fontColor(COLORS.textPrimary)
            Text('点击✏️改单')
              .fontSize(9)
              .fontColor(COLORS.textHint)
              .margin({ left: 8 })
            Text('✏️')
              .fontSize(14)
              .margin({ left: 8 })
              .onClick(() => {
                this.onEdit();
              })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .margin({ bottom: 10 })
          ForEach(this.data.orders, (o: BrocadeOrderItem, i: number) => {
            Row() {
              Text(o.emoji)
                .fontSize(16)
              Column() {
                Text(o.name)
                  .fontSize(12)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(COLORS.textPrimary)
                Text(o.region + ' · ' + o.count + '匹')
                  .fontSize(9)
                  .fontColor(COLORS.textSecondary)
                  .margin({ top: 2 })
              }
              .alignItems(HorizontalAlign.Start)
              .layoutWeight(1)
              .margin({ left: 10 })
              Text('¥' + o.amount)
                .fontSize(12)
                .fontWeight(FontWeight.Bold)
                .fontColor(getOrderColor(o.amount))
              Text('✏️')
                .fontSize(12)
                .margin({ left: 10 })
                .onClick(() => {
                  this.onEdit();
                })
            }
            .width('100%')
            .padding(10)
            .backgroundColor(i % 2 === 0 ? COLORS.cardBg : COLORS.cardAlt)
            .borderRadius(16)
            .border({ width: 1, color: COLORS.line })
            .margin({ top: 8 })
          }, (o: BrocadeOrderItem, i: number) => 'od' + o.name + i)
        }
        .width('100%')
        .margin({ top: 12 })
      }
      .width('100%')
      .constraintSize({ maxHeight: '100%' })
    }
    .width('100%')
    .constraintSize({ maxHeight: '100%' })
    .scrollable(ScrollDirection.Vertical)
    .scrollBar(BarState.Off)
  }
}
@Component
struct BrocadeReviewContent {
  @ObjectLink data: BrocadeData;
  build() {
    Scroll() {
      Column() {
        Row() {
          Text('⭐ 客评口碑')
            .fontSize(13)
            .fontWeight(FontWeight.Bold)
            .fontColor(COLORS.textPrimary)
          Text('12条好评 · 平均4.9')
            .fontSize(9)
            .fontColor(COLORS.textHint)
            .margin({ left: 8 })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        .margin({ bottom: 6 })
        ForEach(this.data.reviews, (r: BrocadeReviewItem, i: number) => {
          Column() {
            Row() {
              Text(r.emoji)
                .fontSize(15)
              Column() {
                Text(r.name)
                  .fontSize(12)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(COLORS.textPrimary)
                Text(r.date)
                  .fontSize(9)
                  .fontColor(COLORS.textHint)
                  .margin({ top: 1 })
              }
              .alignItems(HorizontalAlign.Start)
              .layoutWeight(1)
              .margin({ left: 8 })
              Text('⭐'.repeat(r.score))
                .fontSize(10)
                .fontColor(COLORS.warning)
              BrocadeTag({ text: r.tag, color: getReviewTagColor(r.tag) })
            }
            .width('100%')
            Text(r.content)
              .fontSize(11)
              .fontColor(COLORS.textSecondary)
              .lineHeight(16)
              .margin({ top: 6 })
          }
          .width('100%')
          .padding(12)
          .backgroundColor(i % 2 === 0 ? COLORS.cardBg : COLORS.cardAlt)
          .borderRadius(16)
          .border({ width: 1, color: COLORS.line })
          .margin({ top: 8 })
        }, (r: BrocadeReviewItem, i: number) => 'rv' + r.name + i)
      }
      .width('100%')
      .constraintSize({ maxHeight: '100%' })
    }
    .width('100%')
    .constraintSize({ maxHeight: '100%' })
    .scrollable(ScrollDirection.Vertical)
    .scrollBar(BarState.Off)
  }
}
@Component
struct AddBrocadeModal {
  onClose: () => void = () => {};
  build() {
    Column() {
      Column() {
        Row() {
          Text('🧵 定制云锦')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor(COLORS.textPrimary)
          Text('✕')
            .fontSize(16)
            .fontColor(COLORS.textHint)
            .onClick(() => {
              this.onClose();
            })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        Row() {
          Text('品名')
            .fontSize(11)
            .fontColor(COLORS.textSecondary)
            .width(56)
          Text('龙纹云锦')
            .fontSize(11)
            .fontColor(COLORS.textPrimary)
        }
        .width('100%')
        .padding(10)
        .backgroundColor(COLORS.cardAlt)
        .borderRadius(10)
        .margin({ top: 14 })
        Row() {
          Text('纹样')
            .fontSize(11)
            .fontColor(COLORS.textSecondary)
            .width(56)
          Text('团花 · 真丝 · 通经断纬')
            .fontSize(11)
            .fontColor(COLORS.textPrimary)
        }
        .width('100%')
        .padding(10)
        .backgroundColor(COLORS.cardAlt)
        .borderRadius(10)
        .margin({ top: 8 })
        Row() {
          Text('预算')
            .fontSize(11)
            .fontColor(COLORS.textSecondary)
            .width(56)
          Text('¥680')
            .fontSize(11)
            .fontColor(COLORS.primary)
        }
        .width('100%')
        .padding(10)
        .backgroundColor(COLORS.cardAlt)
        .borderRadius(10)
        .margin({ top: 8 })
        Row() {
          Text('取消')
            .fontSize(12)
            .fontColor(COLORS.textSecondary)
            .padding({ left: 16, right: 16, top: 7, bottom: 7 })
            .borderRadius(12)
            .border({ width: 1, color: COLORS.border })
            .onClick(() => {
              this.onClose();
            })
          Text('确认织造')
            .fontSize(12)
            .fontWeight(FontWeight.Bold)
            .fontColor(COLORS.white)
            .padding({ left: 16, right: 16, top: 7, bottom: 7 })
            .backgroundColor(COLORS.primary)
            .borderRadius(12)
            .onClick(() => {
              this.onClose();
            })
        }
        .width('100%')
        .justifyContent(FlexAlign.End)
        .margin({ top: 14 })
      }
      .width('86%')
      .padding(16)
      .backgroundColor(COLORS.cardBg)
      .borderRadius(18)
      .constraintSize({ maxHeight: '78%' })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#66000000')
  }
}
@Component
struct EditBrocadeOrderModal {
  onClose: () => void = () => {};
  build() {
    Column() {
      Column() {
        Row() {
          Text('✏️ 修改订单')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor(COLORS.textPrimary)
          Text('✕')
            .fontSize(16)
            .fontColor(COLORS.textHint)
            .onClick(() => {
              this.onClose();
            })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        Row() {
          Text('客户')
            .fontSize(11)
            .fontColor(COLORS.textSecondary)
            .width(56)
          Text('故宫文创')
            .fontSize(11)
            .fontColor(COLORS.textPrimary)
        }
        .width('100%')
        .padding(10)
        .backgroundColor(COLORS.cardAlt)
        .borderRadius(10)
        .margin({ top: 14 })
        Row() {
          Text('数量')
            .fontSize(11)
            .fontColor(COLORS.textSecondary)
            .width(56)
          Text('900 → 1000 匹')
            .fontSize(11)
            .fontColor(COLORS.primary)
        }
        .width('100%')
        .padding(10)
        .backgroundColor(COLORS.cardAlt)
        .borderRadius(10)
        .margin({ top: 8 })
        Row() {
          Text('金额')
            .fontSize(11)
            .fontColor(COLORS.textSecondary)
            .width(56)
          Text('¥600000')
            .fontSize(11)
            .fontColor(COLORS.accent)
        }
        .width('100%')
        .padding(10)
        .backgroundColor(COLORS.cardAlt)
        .borderRadius(10)
        .margin({ top: 8 })
        Row() {
          Text('取消')
            .fontSize(12)
            .fontColor(COLORS.textSecondary)
            .padding({ left: 16, right: 16, top: 7, bottom: 7 })
            .borderRadius(12)
            .border({ width: 1, color: COLORS.border })
            .onClick(() => {
              this.onClose();
            })
          Text('保存修改')
            .fontSize(12)
            .fontWeight(FontWeight.Bold)
            .fontColor(COLORS.white)
            .padding({ left: 16, right: 16, top: 7, bottom: 7 })
            .backgroundColor(COLORS.primary)
            .borderRadius(12)
            .onClick(() => {
              this.onClose();
            })
        }
        .width('100%')
        .justifyContent(FlexAlign.End)
        .margin({ top: 14 })
      }
      .width('86%')
      .padding(16)
      .backgroundColor(COLORS.cardBg)
      .borderRadius(18)
      .constraintSize({ maxHeight: '78%' })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#66000000')
  }
}
@Component
struct DeleteThreadModal {
  @Prop title: string;
  onClose: () => void = () => {};
  build() {
    Column() {
      Column() {
        Text('🗑️ 撤下丝线')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor(COLORS.textPrimary)
        Text('确定撤下「' + this.title + '」吗?')
          .fontSize(12)
          .fontColor(COLORS.textSecondary)
          .margin({ top: 10 })
        Text('撤下的丝线将从丝线坊移除。')
          .fontSize(10)
          .fontColor(COLORS.textHint)
          .margin({ top: 4 })
        Row() {
          Text('再想想')
            .fontSize(12)
            .fontColor(COLORS.textSecondary)
            .padding({ left: 16, right: 16, top: 7, bottom: 7 })
            .borderRadius(12)
            .border({ width: 1, color: COLORS.border })
            .onClick(() => {
              this.onClose();
            })
          Text('确认撤下')
            .fontSize(12)
            .fontWeight(FontWeight.Bold)
            .fontColor(COLORS.white)
            .padding({ left: 16, right: 16, top: 7, bottom: 7 })
            .backgroundColor(COLORS.danger)
            .borderRadius(12)
            .onClick(() => {
              this.onClose();
            })
        }
        .width('100%')
        .justifyContent(FlexAlign.End)
        .margin({ top: 16 })
      }
      .width('86%')
      .padding(16)
      .backgroundColor(COLORS.cardBg)
      .borderRadius(18)
      .constraintSize({ maxHeight: '78%' })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#66000000')
  }
}
@Component
struct DetailLoomModal {
  @Prop name: string;
  onClose: () => void = () => {};
  build() {
    Column() {
      Column() {
        Row() {
          Text('👁️ 丝线详情')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor(COLORS.textPrimary)
          Text('✕')
            .fontSize(16)
            .fontColor(COLORS.textHint)
            .onClick(() => {
              this.onClose();
            })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        Column() {
          Text('🪡')
            .fontSize(30)
          Text(this.name)
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor(COLORS.textPrimary)
            .margin({ top: 6 })
        }
        .width('100%')
        .padding(14)
        .backgroundColor(COLORS.cardAlt)
        .borderRadius(12)
        .margin({ top: 12 })
        Text('蚕丝缫后煮练脱胶,纺成细匀丝线,捻以金缕银纬,织机上通经断纬,一寸云锦需百余日方成,光华如云霞。')
          .fontSize(11)
          .fontColor(COLORS.textSecondary)
          .lineHeight(17)
          .margin({ top: 10 })
        Row() {
          Text('丝源')
            .fontSize(11)
            .fontColor(COLORS.textSecondary)
            .width(56)
          Text('家蚕 · 辑里村')
            .fontSize(11)
            .fontColor(COLORS.textPrimary)
        }
        .width('100%')
        .padding(10)
        .backgroundColor(COLORS.cardAlt)
        .borderRadius(10)
        .margin({ top: 8 })
        Row() {
          Text('匠人评级')
            .fontSize(11)
            .fontColor(COLORS.textSecondary)
            .width(56)
          Text('甲级 · 织锦卅载')
            .fontSize(11)
            .fontColor(COLORS.primary)
        }
        .width('100%')
        .padding(10)
        .backgroundColor(COLORS.cardAlt)
        .borderRadius(10)
        .margin({ top: 8 })
        Text('知道了')
          .fontSize(12)
          .fontWeight(FontWeight.Bold)
          .fontColor(COLORS.white)
          .padding({ left: 18, right: 18, top: 7, bottom: 7 })
          .backgroundColor(COLORS.primary)
          .borderRadius(12)
          .margin({ top: 12 })
          .onClick(() => {
            this.onClose();
          })
      }
      .width('86%')
      .padding(16)
      .backgroundColor(COLORS.cardBg)
      .borderRadius(18)
      .constraintSize({ maxHeight: '78%' })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#66000000')
  }
}

十四、开发环境搭建

14.1 安装 DevEco Studio

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

DevEco Studio 安装欢迎界面

14.2 选择安装路径

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

选择安装位置

14.3 配置安装选项

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

安装选项配置

14.4 选择项目模板

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

选择 Empty Ability 模板

14.5 配置项目信息

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

配置项目信息


十五、总结

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

在架构层面,该应用采用了多组件架构设计——将整个应用拆分为 11 个独立组件(1入口+1标签+5内容+4弹窗),每个组件独立封装、职责单一,通过 @ObjectLink 共享聚合数据、通过回调函数实现父子通信、通过 @Prop 传递弹窗参数,组件数量多、架构复杂。与单组件内@Builder构建所有页面的架构不同,本应用将内容页和弹窗都抽离为独立 @Component 子组件,入口组件 BrocadeApp 只负责状态管理和组件调度,实现了真正的高内聚低耦合。这种多组件架构设计虽然增加了组件间通信的复杂度(@ObjectLink/@Prop/回调函数),但大幅提升了代码的可维护性和可复用性,是中大型 ArkUI 应用的标准架构实践,也是本应用工程化设计的最大亮点。

在数据层面,应用独创了**@Observed 聚合数据类设计——BrocadeData 一个类包含 brocades/looms/threads/orders/reviews 五个数组,每个数组 12 条数据,用对象字面量初始化(非 new 构造函数),@Observed 装饰整个类,配合 @ObjectLink 在子组件间共享引用,实现"一个数据对象贯穿所有页面"的聚合数据模式。这种设计将所有业务数据集中管理,子组件通过 @ObjectLink data: BrocadeData 接收共享引用,修改数据时所有引用该数据的子组件自动响应式刷新,避免了每个页面独立 @State 数据数组的重复定义,是 ArkUI 跨组件数据共享的高级实践。应用还定义了 5 个颜色分级函数(getStyleColor纹样4类/getLoomColor精度3级/getThreadColor价格4级/getOrderColor金额4级/getReviewTagColor标签5类),将颜色映射逻辑从 UI 层抽离,每个函数根据不同维度返回对应颜色值,实现"数据即配色"的动态着色模式。9 个元数据接口实现数据结构规范化,4 个统计常量数组将统计数据与业务数据分离,数据规模达 83 条(60业务+23统计),完整呈现南京云锦非遗手作平台的数据生态。本应用数据最大的特色是文化含量极高**——12款云锦都是南京云锦经典纹样(龙纹/凤穿牡丹/缠枝莲/云鹤/海水江崖/万寿如意/妆花缎/织金锦/团龙补子/百子图/四合如意/云锦围巾),百子图¥1880最高端(云锦经典人物纹样),织金锦¥1580(大量用金华贵无比),妆花缎¥1280(云锦代表品种);12种织机覆盖中国传统织机(花楼/束综/提花/斜织/竖织/罗/绫/绢/绒/纱/缎/双经),花楼织机96精度最高(云锦专用大花楼木织机,需两人配合织造),产地都是江南丝绸重镇;12种丝线覆盖蚕丝(湖丝/桑蚕丝/柞蚕丝/真丝绞/双宫丝/绢丝/生丝/熟丝)、金银线(金线/银线)、羽线(孔雀羽线)、绒线(彩绒线),湖丝96品级最高(浙江湖州辑里村皇家御用);6种织造工艺覆盖核心工艺(通经断纬/妆花织造/挖花盘织/逐花异色/金线显花/纬线起花),通经断纬97热度最高(云锦最核心技法);4种纹样构成(团花/龙凤/缠枝/云纹),团花×5占比最高(云锦最常见纹样)。这些文化数据让应用不只是一个技术演示,更是一个南京云锦文化的数字载体。

在视觉层面,本应用采用"鎏金朱红米黄"南京云锦非遗主题设计——bg #F6F1E7 米黄(模拟宣纸/丝质质感,云锦文化载体),cardBg #FFFFFF 白,cardAlt #F1E8D8 浅米黄(奇偶行交替用),textPrimary #3B2F1E 深褐(南京云锦深色文字,非纯黑,更有丝质和文化感),primary #C9A24B 鎏金主色,primaryDark #8F6B2E 深金(头部背景,营造浓郁云锦工坊氛围),accent #A63A2B 朱红辅助色,accentLight #C98D82 浅朱红,gold #C9A24B 鎏金(装饰圆用,模拟金线团花),silkWhite #F7F3E9 丝白(装饰圆边框用,模拟丝质光泽)。主色体系采用"鎏金+朱红+靛蓝+米黄"四色组合,四色各司其职形成"以鎏金定主、以朱红定辅、以靛蓝定饰、以米黄定底"的南京云锦语义着色体系。本应用最大的视觉特色是鎏金 gold 与丝白 silkWhite——鎏金用于装饰圆(12x12鎏金圆带2px丝白边,模拟金线团花),丝白用于装饰圆边框(模拟丝质光泽,与white纯白不同,丝白偏米黄更有丝质感),与米黄底形成"鎏金朱红米黄"的南京云锦经典配色。头部12点装饰纹中 r%3=0 用12x12鎏金圆带2px丝白边(模拟金线团花),r%3=1 用6x14朱红竖线(模拟织机经线),r%3===2 用8x8靛蓝圆(模拟靛蓝纬线),鎏金圆+朱红竖线+靛蓝圆三色交替,营造浓郁的云锦纹样装饰纹效果。云锦图标装饰区的丝白圆纹样色边(14x14丝白圆带2px纹样色边框模拟云锦团花轮廓+8x8纹样色小圆模拟云锦纹样点)是本应用云锦陈列列表设计的核心亮点——丝白圆带纹样色边模拟云锦团花的丝质光泽和纹样轮廓,与南京云锦"寸锦寸金"的华贵气质高度契合。gold 鎏金与 primary 同色但语义不同(primary是主题主色,gold是装饰专用色模拟金线团花),silkWhite 丝白是独立装饰色(模拟丝质光泽),这种"同色异义+独立装饰色"的设计体现了南京云锦"金线团花丝质光泽"的核心视觉元素。本应用另一个色彩处理特色是十六进制透明度拼接技巧——大量使用 color + 十六进制透明度后缀实现半透明背景和边框,无需额外定义半透明颜色常量,是 ArkUI 颜色处理的高级实用技巧。注意本应用tabBar顶部条用accent朱红(非primary鎏金),tabBar激活态圆角10,卡片圆角16(比之前的18/20更小更精致),这些细节都体现了朱红更符合云锦华贵主题、更小圆角更精致的设计考量。统一的色彩系统是整个应用的视觉基石,鎏金朱红米黄主题配合十六进制透明度拼接和鎏金丝白装饰与5个颜色分级函数与多维度动态着色,营造出既鎏金华贵又朱红热烈的南京云锦非遗氛围。

在交互层面,应用通过四个独立弹窗子组件实现了完整的增删改查交互闭环:AddBrocadeModal 定制云锦(品名/纹样/预算三行展示,确认织造 primary 鎏金底)、EditBrocadeOrderModal 修改订单(客户/数量900→1000/金额60万,保存修改)、DeleteThreadModal 撤下丝线(@Prop title 传入丝线名,确认撤下 danger 红底)、DetailLoomModal 丝线详情(@Prop name 传入丝线名,大图标+蚕丝描述+丝源+匠人评级,知道了按钮)。四个弹窗各自独立组件,通过 @Prop 接收参数(DeleteThreadModal的title、DetailLoomModal的name),通过 onClose 回调关闭,遮罩用 #66000000(40%黑)硬编码。云锦页➕点击触发 AddBrocadeModal,织机页🗑️点击触发 DeleteThreadModal(delThreadName 记录丝线名),丝线页👁️点击触发 DetailLoomModal(detailLoomName 记录丝线名),订单页✏️点击触发 EditBrocadeOrderModal,形成了完整的"定制云锦→撤下丝线→查看丝线→修改订单"交互闭环。头部🪡translate位移动画(点击🪡y从4到-10上下浮动,模拟绣针在织机上上下穿梭,是云锦织造的核心动作)和🌈旋转120度动画(点击🌈旋转120度,模拟云锦彩虹旋转,120度三分之一圈模拟三色纹样流转)是本应用最具创意的交互设计,点击图标触发显式动画,增加了应用的趣味性和互动感,也体现了 ArkUI 显式动画(animation属性配合状态变化,支持rotate/translate/scale多种变换)的使用。与之前826龙泉剑的⚔️旋转90度+✨scale缩放不同,本应用用了🪡translate位移+🌈旋转120度,旋转角度从90度变为120度(云锦三色纹样流转需要120度),translate位移从x/y双轴变为y单轴(绣针只上下浮动),体现了动画与主题文化的深度结合。本周销量柱状图柱子高度直接用数值(最大66px,非比例映射,南京云锦是高端手工丝织品周销量26-66匹,与之前象棋周销量42-95副大众普及形成对比,体现南京云锦"高端小众"定位),周末≥60用 accent 朱红高亮,横向进度条用 layoutWeight 动态分配(非固定宽度非百分比,是 ArkUI 特有的弹性布局进度条实现),纹样构成进度条总份数20非100(灵活适配不同基数,团花×5占比最高体现云锦最常见纹样),精度进度条内嵌在列表行中,星级用 ‘⭐’.repeat(r.score) 字符串重复生成(非循环非组件,纯字符串乘法),奇偶行交替条纹(i%2=0白/i%2=1浅米黄),tabBar激活态用 t.color 文字+背景 t.color+'14’圆角10+border 1px t.color(5个Tab各自颜色激活,圆角10),顶部3px accent 朱红条,都是本应用交互设计的精细之处。5个内容子组件通过回调函数(onAdd/onDel/onDetail/onEdit)与入口组件通信触发弹窗,4个弹窗子组件通过 onClose 回调关闭,@Prop 实现弹窗动态参数,形成了完整的多组件交互通信体系。

云锦坊应用整体运行效果 - 深金头部云锦坊标题与12点鎏金圆朱红竖线靛蓝圆云锦装饰纹及本周销量柱状图云锦陈列列表


标签: HarmonyOS · ArkTS · API 24 · 南京云锦 · 鎏金朱红 · 多组件架构 · @ObjectLink · 通经断纬 · 移动端开发

Logo

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

更多推荐