外圆内方,通宝千年。中国古钱币,是中华文明最具代表性的物质文化遗产之一,从先秦的刀币布币到秦代的半两钱,从汉代的五铢钱到唐代的开元通宝,从宋代的大观通宝到清代的乾隆通宝,一枚枚古钱币承载着三千年的王朝更迭、经济兴衰与文化传承。古钱币外圆内方的形制象征天圆地方,钱文篆刻承载书法艺术,翻砂范铸工艺凝聚匠人智慧,包浆锈色记录岁月痕迹。本文完整记录一个基于鸿蒙 HarmonyOS API 24 与 ArkTS 声明式框架构建的「古钱铺」非遗手作平台,从青铜绿铜金朱砂红的古钱币色彩主题,到青铜绿头部🪙rotate旋转180度与🔥translate炉火升腾动画,再到12点铜金圆丝白边青铜绿圆朱砂红圆古钱装饰纹、5Tab差异化布局(古钱本周销量柱状图+古钱陈列/范模古钱TOP6+范模库铸模精度/钱匣铸造工艺热度+钱匣坊/订单朝代构成+订单台账/客评口碑列表)、多组件11组件架构、@ObjectLink数据共享、@Observed聚合数据类、5个颜色分级函数、十六进制透明度拼接、铜金gold与丝白silkWhite装饰、layoutWeight动态横向进度条、古钱图标区方孔模拟外圆内方、四大弹窗(定制古钱/修改订单/撤下范模/钱匣详情),附完整源码与运行效果截图。


一、引言:外圆内方通宝千年

中国古钱币,是中华文明最具代表性的物质文化遗产之一,从先秦的刀币布币到秦代的半两钱,从汉代的五铢钱到唐代的开元通宝,从宋代的大观通宝到清代的乾隆通宝,一枚枚古钱币承载着三千年的王朝更迭、经济兴衰与文化传承。古钱币外圆内方的形制象征天圆地方——圆形象征天,方孔象征地,外圆内方体现了中国古人"天圆地方"的宇宙观;钱文篆刻承载书法艺术——从秦篆到汉隶,从唐楷到宋瘦金体,每一枚钱币的钱文都是一幅微型书法作品;翻砂范铸工艺凝聚匠人智慧——从陶范石范到铜范母钱,从翻砂铸钱到机制币,铸造工艺的演进见证了中国古代冶金技术的辉煌;包浆锈色记录岁月痕迹——生坑绿锈、熟坑包浆、水银古、朱砂锈,每一枚古钱币的锈色都是岁月独一无二的馈赠。

中国是世界上最早使用金属铸币的国家之一,商代晚期已有青铜贝币,春秋战国时期形成刀币、布币、圜钱、蚁鼻钱四大货币体系,秦统一货币后"半两钱"成为中国第一种统一的圆形方孔钱,汉代"五铢钱"沿用七百余年是中国历史上使用时间最长的货币,唐代"开元通宝"开创了通宝元宝钱制并沿用至清末,宋代钱币艺术达到巅峰(瘦金体大观通宝、御书钱崇宁通宝),清代"康乾盛世"钱币铸造量巨大。古钱币不仅是货币,更是文化载体——钱币上的年号记录王朝纪年,钱文书法承载艺术传承,铸造工艺反映技术水平,出土分布见证历史地理。从宫廷铸钱监到民间钱铺,从收藏鉴赏到学术研究,古钱币承载着中国人对历史与文化的无限敬意。

然而,随着现代收藏市场的快速发展,古钱币这门古老的非遗手作技艺正面临传承困境——年轻人不了解古钱币品种和朝代差异,古钱币产品标准化程度低、真伪难辨、品质参差不齐,范铸匠人缺乏展示和销售渠道,古钱币收藏市场乱象丛生,正成为古钱币非遗传承的现实痛点。一个优秀的古钱币手作平台,不仅需要覆盖从"古钱陈列"到"范模库"再到"钱匣坊"的完整产品展示闭环,还要在订单管理、客户评价、匠人评级等维度做到精准而高效。它需要以古钱陈列为核心入口,横向扩展出范模、钱匣、订单、客评五大功能模块,每个模块采用完全不同的布局风格,形成一个完整闭环的古钱币非遗手作生态。

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

古钱铺应用整体运行效果 - 青铜绿头部古钱铺标题与12点铜金圆丝白边青铜绿圆朱砂红圆古钱装饰纹及本周销量柱状图古钱陈列列表


二、应用整体架构概览

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

应用一共划分为五个一级页面:古钱(COIN)、范模(MOLD)、钱匣(CASE)、订单(ORDER)以及客评(REVIEW)。底部 tabBar 采用古钱币设计——5个Tab(古钱🪙/范模🔨/钱匣🗃️/订单📦/客评⭐)单排布局,激活态文字 t.color 加粗 + 背景 t.color+‘14’(10%透明色底)圆角10(古钱币方孔感,比珍珠的20小,古钱币外圆内方用中等圆角) + border 1px t.color,非激活态 textHint 灰字 + 透明背景,tabBar 顶部 3px accent 铜金条。五个Tab每个布局完全不同——古钱采用本周销量柱状图+古钱陈列列表(12款古钱,朝代分级着色,奇偶行交替,图标装饰区方孔模拟外圆内方,卡片圆角14统一古钱币方孔感)、范模采用古钱销量TOP6横向进度条+范模库列表(12种范模,铸模精度进度条,🗑️点击删除弹窗,卡片圆角14)、钱匣采用铸造工艺热度横向进度条+钱匣坊列表(12种钱匣,👁️点击详情弹窗,卡片圆角14)、订单采用朝代构成横向进度条+订单台账列表(12订单,金额分级着色,✏️点击编辑弹窗,卡片圆角14)、客评采用客评口碑列表(12条评价,⭐星星repeat,标签分级着色,卡片圆角14),这种"一页一型"的差异化设计最大程度还原了真实古钱币非遗手作平台的交互体验。卡片圆角统一14(古钱币方孔感,比珍珠的22小,古钱币外圆内方用中等圆角体现方孔特征),统计卡圆角16(比卡片14大一点形成层次),是本应用视觉设计的特色。

在数据层面,应用独创了**@Observed聚合数据类**设计——CoinData 一个类包含 coins/molds/cases/orders/reviews 五个数组,每个数组 12 条数据,用对象字面量初始化(非 new 构造函数),@Observed 装饰整个类,配合 @ObjectLink 在子组件间共享引用,实现"一个数据对象贯穿所有页面"的聚合数据模式。这种设计将所有业务数据集中管理,子组件通过 @ObjectLink data: CoinData 接收共享引用,修改数据时所有引用该数据的子组件自动响应式刷新,是 ArkUI 跨组件数据共享的高级实践。应用还定义了 9 个元数据接口(CoinItem/MoldItem/CaseItem/CoinOrderItem/CoinReviewItem/WeekCoinMeta/DynastyShareMeta/CoinHotMeta/CoinTopMeta)和 4 个统计常量数组(WEEK_SOLD 7天销量/DYNASTY_SHARE 4种朝代构成/COIN_HOT 6种铸造工艺热度/COIN_TOP 6款古钱销量排行),将统计数据与业务数据分离,实现配置驱动设计。

在颜色逻辑层面,应用定义了 5 个颜色分级函数(getDynastyColor朝代4类/getMoldColor精度3级/getPriceColor价格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 #B0893F 与丝白 silkWhite #F6F2E8——铜金用于装饰圆(12x12铜金圆+2px丝白边模拟古钱币,铜金圆模拟铜钱,丝白边模拟钱币边缘),丝白用于方孔模拟(12x12丝白方模拟古钱币方孔,方孔是古钱币的核心特征),与米黄底 bg #F1EFE7 形成"青铜绿铜金朱砂红+铜金丝白"的古钱币经典配色,是本应用视觉设计的灵魂。这种将所有色彩集中管理、配合5个颜色分级函数和十六进制透明度拼接与铜金丝白装饰与多维度动态着色的做法保证了全局一致性,降低了维护成本。

在交互层面,应用通过四个独立弹窗子组件实现了完整的增删改查交互闭环:AddCoinModal 定制古钱(品名/钱文/预算三行展示,确认铸钱 primary 青铜绿底)、EditCoinOrderModal 修改订单(客户/数量900→1000/金额50万,保存修改)、DeleteMoldModal 撤下范模(@Prop title 传入范模名,确认撤下 danger 红底)、DetailCaseModal 钱匣详情(@Prop name 传入钱匣名,大图标🗃️+钱匣描述+木料产地+匠人评级,知道了按钮)。四个弹窗各自独立组件,通过 @Prop 接收参数(DeleteMoldModal的title、DetailCaseModal的name),通过 onClose 回调关闭,遮罩用 #66000000(40%黑)硬编码。古钱页➕点击触发 AddCoinModal,范模页🗑️点击触发 DeleteMoldModal(delMoldName 记录范模名),钱匣页👁️点击触发 DetailCaseModal(detailCaseName 记录钱匣名),订单页✏️点击触发 EditCoinOrderModal,形成了完整的"定制古钱→撤下范模→查看钱匣→修改订单"交互闭环。

古钱铺钱匣详情弹窗运行效果 - 弹窗叠加钱匣页显示大图标钱匣名钱匣描述及木料产地匠人评级

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


三、古钱页:本周销量柱状图与古钱陈列

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

古钱模块 - 本周古钱销量柱状图周六65枚峰值与12款古钱陈列列表朝代分级着色及方孔模拟装饰

页面顶部第一部分是本周销量卡,Column 包裹,padding 14,cardBg 白底,圆角16(统计卡比卡片14大一点形成层次),border 1px accent+‘55’(33%透明铜金):

  • 装饰行:Row,12x12 gold铜金圆(圆角6,+2px silkWhite丝白边,模拟古钱币)+ layoutWeight(1) 2px accent+'55’横线 + 6x6 primary青铜绿圆(圆角3)+ 12x12 danger朱砂红圆(圆角6,+2px silkWhite丝白边,模拟朱砂红钱)+ layoutWeight(1) 2px横线 + 12x12 gold铜金圆(圆角6,+2px silkWhite丝白边,模拟古钱币),铜金圆丝白边+青铜绿圆+朱砂红圆丝白边三色交替装饰分隔线。
  • 标题行:Row,"📈 本周古钱销量"textPrimary深绿字13号加粗 + "周六65枚"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(直接用数值当高度!最大65px),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 天销量数据:周一20/周二28/周三36/周四44/周五57/周六65(峰值,accent铜金高亮!)/周日59。柱子高度直接用 w.value 数值(最大65px),非比例映射,是本应用柱状图设计的特色——数值即高度,简单直观。柱子圆角8,古钱币圆润感。古钱币是收藏文物,周销量20-65枚,与之前珍珠周销量18-62颗(高端珠宝)接近,与象棋周销量42-95副(大众普及)相比略低,体现古钱币"收藏文物"的产品定位。周末(周六65)用 accent 铜金色高亮,工作日用 primary 青铜绿色,形成"周末高峰铜金/工作日青铜绿"的视觉区分,周六65枚峰值是本周最高点。

第二部分是古钱陈列,Column,margin top 12:

  • 标题行:Row,"🪙 古钱陈列"textPrimary深绿字13号加粗 + "点击➕铸钱"textHint灰字9号 + "➕"14号,点击触发 onAdd 回调(showAddCoin=true)。
  • 古钱列表:ForEach this.data.coins 12款,每款 Row,padding 10,backgroundColor i%2===0 ? cardBg白 : cardAlt浅米黄(奇偶行交替条纹!)圆角14(统一古钱币方孔感),border 1px line,margin top 8:
    • 图标装饰区:Column width 60,padding 上下10/6,backgroundColor getDynastyColor(p.dynasty)+‘0D’(5%透明色底),圆角30(古钱币圆润感,大圆角),border 1px getDynastyColor(p.dynasty)+‘33’:
      • 圆形图标:Column width 44 height 44,justifyContent Center,alignItems Center,backgroundColor getDynastyColor(p.dynasty)+‘14’(10%透明色底),圆角22(圆形,古钱币是圆形用圆形图标),border 1px getDynastyColor(p.dynasty)+‘66’,Text p.emoji 18号居中。
      • 方孔模拟外圆内方:Row width 24 justifyContent Center margin top 5,12x12 silkWhite丝白方(圆角2,非圆形!模拟古钱币方孔,方孔是古钱币的核心特征,外圆内方)+2px getDynastyColor(p.dynasty)朝代色边(方孔边缘用朝代色,模拟钱币方孔的铜色边缘),丝白方模拟方孔(方孔是古钱币穿绳的孔,外圆内方是中国古钱币的核心形制)。
    • 信息区:Column layoutWeight 1 alignItems Start margin left 10:
      • 名称价格行:Row width 100% justifyContent SpaceBetween,p.name textPrimary深绿字13号加粗 + “¥”+p.price getPriceColor(p.price)色10号加粗 margin left 8。
      • 标签行:Row width 100% justifyContent Start margin top 6,CoinTag({text: p.dynasty+‘钱’, color: getDynastyColor(p.dynasty)})(朝代标签,朝代分级色)+ CoinTag({text: p.craft, color: success蓝})(工艺标签,蓝色固定色)+ "外圆内方"textHint灰字9号 margin left 8。

12 款古钱:开元通宝(唐,翻砂,¥880,🪙)、乾隆通宝(清,翻砂,¥260,🏮)、五铢钱(汉,范铸,¥360,⚖️)、半两钱(汉,范铸,¥680,🪨)、崇宁通宝(宋,翻砂,¥720,🏯)、大观通宝(宋,母钱,¥1580,🦅)、永乐通宝(明,翻砂,¥560,🌏)、顺治通宝(清,翻砂,¥420,🐉)、光绪通宝(清,机制,¥380,🐎)、货布(汉,叠铸,¥1280,🪓)、刀币(战国,范铸,¥2380,🗡️)、布币(战国,范铸,¥1880,🌾)。价格从¥260(乾隆通宝)到¥2380(刀币),getPriceColor 四级着色(≥1500 danger朱砂红/≥800 accent铜金/≥400 primary青铜绿/其他 #4E7BA8蓝),¥2380刀币/¥1880布币/¥1580大观通宝用 danger 朱砂红色(≥1500最高端,刀币布币是战国古钱稀有异形币,大观通宝瘦金体精美),¥1280货布/¥880开元通宝用 accent 铜金色(≥800中高端),¥720崇宁通宝/¥680半两钱/¥560永乐通宝/¥420顺治通宝/¥380光绪通宝用 primary 青铜绿色(≥400中端),¥360五铢钱/¥260乾隆通宝用 #4E7BA8 蓝色(其他入门级),形成"以价格定颜色"的动态着色模式。奇偶行交替(i%2=0白/i%2=1浅米黄)营造列表条纹效果,图标装饰区的方孔模拟外圆内方(12x12 silkWhite丝白方+2px朝代色边,圆角2非圆形,模拟古钱币方孔,方孔是古钱币穿绳的孔,外圆内方是中国古钱币的核心形制,象征天圆地方)是本应用古钱陈列列表设计的核心亮点——方孔模拟古钱币外圆内方形制,与古钱币"外圆内方通宝千年"的文化意象高度契合。CoinTag 子组件封装标签样式(9号字+color+'14’背景+color+'40’边框+圆角10),实现标签组件复用。朝代标签用 getDynastyColor 朝代分级着色(唐/清→铜金#B0893F/宋/明→青铜绿#4E7A63/战国→朱砂红#8E3B36/其他→#4E7BA8蓝),工艺标签用 success 蓝色固定色,"外圆内方"标注古钱币形制文化意象,完整呈现古钱币的文化信息。12款都是中国古钱币名品(开元通宝/乾隆通宝/五铢钱/半两钱/崇宁通宝/大观通宝/永乐通宝/顺治通宝/光绪通宝/货布/刀币/布币),朝代覆盖唐/清/汉/宋/明/战国,工艺覆盖翻砂/范铸/母钱/机制/叠铸,文化含量极高,是本应用区别于之前主题的最大特色。


四、范模页:古钱销量TOP6与范模库铸模精度

范模页以"古钱范模"为核心,采用"古钱销量TOP6横向进度条+范模库铸模精度列表"的两层布局。

范模模块 - 古钱销量TOP6横向进度条开元通宝居首与12种范模库列表铸模精度进度条

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

  • 标题行:Row,"🏆 古钱销量 TOP6"textPrimary深绿字13号加粗 + "开元通宝居首"textHint灰字9号。
  • TOP6列表:ForEach COIN_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枚(accent铜金)、五铢钱93枚(primary青铜绿)、乾隆通宝90枚(danger朱砂红)、大观通宝87枚(accentLight浅铜金)、崇宁通宝84枚(success蓝)、刀币80枚(warning赭石)。横向进度条用 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.molds 12种,每种 Row,padding 10,backgroundColor i%2===0 ? cardBg白 : cardAlt浅米黄(奇偶行交替)圆角14,border 1px line,margin top 8:
    • 图标装饰区:Column width 56,padding 上下10/6,backgroundColor getMoldColor(c.level)+‘0D’,圆角16,border 1px getMoldColor(c.level)+‘33’:
      • 图标:Text c.emoji 18号居中。
      • 装饰点行:Row width 28 justifyContent Center margin top 5,12x4 getMoldColor横条(圆角2,模拟范模横条)+ 8x8 silkWhite丝白圆(圆角4,margin left 4,模拟丝白装饰)。
    • 信息区:Column layoutWeight 1 alignItems Start margin left 10:
      • 名称材质行:Row width 100% justifyContent SpaceBetween,c.name textPrimary深绿字13号加粗 + c.material+‘范’ 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 getMoldColor(c.level) 圆角3(铸模精度进度条!)+ Column layoutWeight(100-c.level) height 6 backgroundColor line 圆角3。
    • 删除按钮:Text '🗑️’14号,点击触发 onDel(c.name) 回调(delMoldName=c.name,showDeleteMold=true)。

12 种范模:陶范(陶,96,洛阳,🏺)、石范(石,93,临淄,🪨)、铜范(铜,95,长安,🥉)、母钱范(精铜,96,建业,🔱)、子范(陶,92,成都,🥣)、叠铸范(陶,94,长沙,📚)、翻砂模(砂,95,汴京,🏖️)、雕母(精铜,97,北京,🎯)、铁范(铁,93,邯郸,⚙️)、泥范(泥,90,南阳,🧱)、钱树范(陶,92,洛阳,🌳)、蜡模(蜡,94,杭州,🕯️)。精度从90到97,getMoldColor 三级着色(≥95 accent铜金/≥93 primary青铜绿/其他 danger朱砂红),陶范96/母钱范96/雕母97/铜范95/翻砂模95用 accent 铜金色(≥95最高,雕母97最高精铜雕刻母钱,是古钱币铸造的最高工艺),石范93/铁范93/叠铸范94/蜡模94用 primary 青铜绿色(≥93),子范92/钱树范92/泥范90用 danger 朱砂红色(其他),形成"以精度定颜色"的动态着色模式。铸模精度进度条用 layoutWeight 动态分配(c.level 和 100-c.level),内嵌在列表行中,是本应用范模库设计的核心亮点——范模以铸模精度为核心品质,用进度条可视化铸模精度,专业感十足。12种范模材质覆盖陶/石/铜/精铜/砂/铁/泥/蜡,产地洛阳/临淄/长安/建业/成都/长沙/汴京/北京/邯郸/南阳/杭州,完整呈现古钱币范铸工艺生态,从陶范石范到铜范母钱,从翻砂模到雕母,范模材质丰富多样,体现古钱币"翻砂范铸"的铸造工艺文化。点击🗑️触发 DeleteMoldModal 撤下范模弹窗(@Prop title 传入范模名,确认后从范模库移除),形成"浏览→撤下"交互闭环。


五、钱匣页:铸造工艺热度与钱匣坊

钱匣页以"古钱钱匣"为核心,采用"铸造工艺热度横向进度条+钱匣坊列表"的两层布局。

钱匣模块 - 铸造工艺热度横向进度条翻砂铸钱96居首与12种钱匣坊列表

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

  • 标题行:Row,"🔥 铸造工艺热度"textPrimary深绿字13号加粗 + "翻砂铸钱96"primary青铜绿字9号。
  • 热度列表:ForEach COIN_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 种铸造工艺热度:翻砂铸钱96(accent铜金)、母钱修模95(primary青铜绿)、范铸叠铸94(danger朱砂红)、钱文篆刻93(accentLight浅铜金)、鎏金锉边91(success蓝)、包浆养护90(warning赭石)。横向进度条用 layoutWeight 动态分配(d.heat 和 100-d.heat),翻砂铸钱96居首,进度条几乎满格。6种工艺覆盖古钱币核心铸造工艺(翻砂铸钱→母钱修模→范铸叠铸→钱文篆刻→鎏金锉边→包浆养护),翻砂铸钱是古钱币最主要的铸造工艺(用砂型翻铸钱币,从唐代沿用至清末),母钱修模是古钱币铸造的关键步骤(用母钱翻铸子钱,母钱精度决定子钱品质),范铸叠铸是先秦至汉代的主要铸造工艺(用陶范叠铸钱币,一次可铸多枚),钱文篆刻是古钱币的艺术核心(钱币上的钱文书法,从秦篆到宋瘦金体),鎏金锉边是古钱币的精加工工艺(鎏金装饰、锉边修整),包浆养护是古钱币收藏的后期工艺(养护包浆锈色),完整呈现古钱币铸造工艺的技术多样性,是本应用钱匣页工艺热度设计的核心亮点。

第二部分是钱匣坊,Column,margin top 12:

  • 标题行:Row,"🗃️ 钱匣坊"textPrimary深绿字13号加粗 + "点击👁️了解"textHint灰字9号。
  • 钱匣列表:ForEach this.data.cases 12种,每种 Row,padding 10,backgroundColor i%2===0 ? cardBg白 : cardAlt浅米黄(奇偶行交替)圆角14,border 1px line,margin top 8:
    • 图标装饰区:Column width 56,padding 上下10/6,backgroundColor getMoldColor(b.level)+‘0D’,圆角18,border 1px getMoldColor(b.level)+‘33’:
      • 图标:Text b.emoji 18号居中。
      • 横条装饰:Column width 12 height 4 backgroundColor getMoldColor(b.level) 圆角2,margin top 5(横条模拟钱匣边缘)。
    • 信息区:Column layoutWeight 1 alignItems Start margin left 10:
      • 名称品级行:Row width 100% justifyContent SpaceBetween,b.name textPrimary深绿字13号加粗 + “品级”+b.level getMoldColor(b.level)色9号 margin left 8。
      • 材质产地行:Row width 100% justifyContent Start margin top 6,CoinTag({text: b.wood+‘匣’, color: textSecondary灰绿})(材质标签,灰色固定色)+ b.place textHint灰字9号 margin left 8(产地)。
    • 详情按钮:Text '👁️’14号,点击触发 onDetail(b.name) 回调(detailCaseName=b.name,showDetailCase=true)。

12 种钱匣:红木钱匣(红木,96,苏州,🪵)、楠木钱匣(金丝楠,95,成都,🌲)、锦盒(织锦,94,南京,🧧)、铜钱匣(黄铜,93,大理,🥉)、竹编钱篓(竹,92,安吉,🎋)、漆器钱匣(大漆,95,福州,🖌️)、樟木匣(樟木,94,宜春,🍃)、书匣(花梨,93,海南,📚)、抽屉匣(乌木,92,东阳,🗄️)、宝匣(紫檀,96,北京,💰)、圆匣(楠木,93,扬州,⭕)、首饰匣(檀木,94,广州,🗃️)。精度从92到96,getMoldColor 三级着色(≥95 accent铜金/≥93 primary青铜绿/其他 danger朱砂红),红木钱匣96/宝匣96/楠木钱匣95/漆器钱匣95用 accent 铜金色(≥95最高,红木钱匣苏州工艺,宝匣紫檀北京宫廷工艺),锦盒94/樟木匣94/首饰匣94/铜钱匣93/书匣93/圆匣93用 primary 青铜绿色(≥93),竹编钱篓92/抽屉匣92用 danger 朱砂红色(其他)。12种钱匣材质覆盖红木/金丝楠/织锦/黄铜/竹/大漆/樟木/花梨/乌木/紫檀/楠木/檀木,产地苏州/成都/南京/大理/安吉/福州/宜春/海南/东阳/北京/扬州/广州,完整呈现古钱币收藏匣生态,从红木钱匣到紫檀宝匣,从漆器钱匣到竹编钱篓,钱匣材质丰富多样,体现古钱币"一匣藏尽千秋通宝"的收藏文化。点击👁️触发 DetailCaseModal 钱匣详情弹窗(@Prop name 传入钱匣名,显示选老料整木开板,榫卯相接不用一钉,内衬绒布防磨,盖面镶铜扣铜环。铜钱入匣叮当作响,一匣藏尽千秋通宝,木料产地苏州·老料坊,匠人评级甲级·木作卅载),形成"浏览→查看详情"交互闭环。


六、订单页:朝代构成与订单台账

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

订单模块 - 朝代构成横向进度条唐钱占比最高与12笔订单台账列表金额分级着色

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

  • 标题行:Row,"🧩 朝代构成"textPrimary深绿字13号加粗 + "唐钱占比最高"textHint灰字9号。
  • 构成列表:ForEach DYNASTY_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(accent铜金)、宋×4(primary青铜绿)、清×3(danger朱砂红)、汉×2(success蓝)。横向进度条用 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浅米黄(奇偶行交替)圆角14,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枚,¥450000,🏛️)、收藏协会(华东,500枚,¥750000,🎖️)、钱币研究所(华北,400枚,¥800000,🔬)、拍卖行(华东,300枚,¥900000,🔨)、海外藏家(海外,350枚,¥875000,🌏)、古玩城店铺(华中,600枚,¥360000,🏮)、直播鉴宝(华东,750枚,¥375000,📺)、文创礼盒商(华南,650枚,¥390000,🎁)、大学历史系(华北,450枚,¥225000,🎓)、电视剧组(华北,500枚,¥250000,🎬)、钱庄博物馆(华东,550枚,¥440000,🏦)、私人典藏(海外,250枚,¥625000,💎)。金额从¥225000(大学历史系)到¥900000(拍卖行),getOrderColor 四级着色(≥80万 danger朱砂红/≥60万 accent铜金/≥40万 primary青铜绿/其他 #4E7BA8蓝),¥90万拍卖行/¥87.5万海外藏家/¥80万钱币研究所用 danger 朱砂红色(≥80万最大额,拍卖行是古钱币最大B端客户),¥75万收藏协会/¥62.5万私人典藏用 accent 铜金色(≥60万中大额),¥45万博物馆文创/¥44万钱庄博物馆/¥39万文创礼盒商/¥37.5万直播鉴宝/¥36万古玩城店铺用 primary 青铜绿色(≥40万中等),¥25万电视剧组/¥22.5万大学历史系用 #4E7BA8 蓝色(其他),形成"以金额定颜色"的动态着色模式。点击✏️触发 EditCoinOrderModal 修改订单弹窗(显示客户博物馆文创,数量900→1000枚,金额¥500000,保存修改),是本应用订单页设计的核心亮点。订单覆盖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浅米黄(奇偶行交替)圆角14,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赭石色。
    • 标签:CoinTag({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,五铢钱包浆自然,海外出货很受欢迎,包浆自然,🌏)、古玩店主(4星,08-29,崇宁通宝量足价实,若附拓片更佳,量足价实,🏮)、鉴宝主播(5星,08-28,半两钱走量飞快,鉴宝专场人气爆棚,走量飞快,📺)、礼盒经理(5星,08-27,文创礼盒套装精致,开箱即见用心,开箱用心,🎁)、历史系教授(4星,08-26,布币授课教具齐全,若加铭文考释更好,教具齐全,🎓)、道具师(5星,08-25,顺治通宝做道具质感足,镜头下很真实,质感真实,🎬)、钱庄馆长(5星,08-24,钱匣做工考究,展陈效果极佳,做工考究,🏦)、老主顾(5星,08-23,收第三匣古钱了,一币一史,越藏越有味,一币一史,💎)。10条5星+2条4星,平均4.9分,星级用 ‘⭐’.repeat(r.score) 字符串重复生成(非循环非组件,纯字符串乘法),是轻量级星级展示的实用技巧。标签 getReviewTagColor 五级着色(品相完整/形制标准/一币一史/做工考究用 accent铜金,稀世难得/钱文精美/包浆自然用 primary青铜绿,量足价实/走量飞快/开箱用心/质感真实用 danger朱砂红,教具齐全用 success蓝,其他 warning赭石),形成"以标签定颜色"的动态着色模式。评价内容都是真实古钱币用户的使用反馈,覆盖12种客户类型与订单台账客户类型呼应,从博物馆馆长到老主顾,从收藏家到鉴宝主播,完整呈现古钱币客户的口碑生态,古钱币作为收藏文物,评价关键词集中在"品相完整"“稀世难得”“形制标准”“钱文精美”“包浆自然”“一币一史"等古钱币品质和历史文化描述,与之前珍珠的"色泽温润”“品相一流”"珠圆玉润"等珍珠品质描述形成对比。奇偶行交替条纹,是本应用客评页设计的标准布局。


八、色彩主题系统:青铜绿铜金朱砂红与十六进制透明度拼接

8.1 色彩接口定义

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

interface CoinPalette {
  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:主色 #4E7A63,青铜绿,古钱币非遗主题色,用于标题强调、价格、按钮、柱状图高峰。
  • primaryLight:浅主色 #8AA996,浅青铜绿,用于渐变和浅色强调。
  • primaryDark:深主色 #2F4A3C,深青铜绿,用于头部背景(营造浓郁青铜古钱氛围)。
  • accent:辅助色 #B0893F,铜金,用于tabBar顶部条、开元通宝、价格中高端。
  • accentLight:浅辅助色 #D2B477,浅铜金,用于渐变和浅色强调。
  • bg:页面背景 #F1EFE7,米黄,古钱币浅色背景(模拟宣纸/铜锈质感)。
  • cardBg:卡片背景 #FFFFFF,白,用于奇数行卡片和主要卡片。
  • cardAlt:交替卡片背景 #E9E3D3,浅米黄,用于偶数行卡片(奇偶行交替条纹)。
  • textPrimary:文字主色 #2E332B,深绿灰,古钱币深色文字(非纯黑,更有青铜和历史感)。
  • textSecondary:次要文字 #6E7264,灰绿,用于次要信息。
  • textHint:提示文字 #A09F90,浅灰绿,用于辅助信息和非激活tab。
  • border:边框色 #D8D4C2,浅米黄边,用于按钮边框。
  • line:分割线 #E7E4D8,极浅米黄,用于卡片边框和进度条背景。
  • success:成功色 #4E7BA8,蓝,用于成功状态和工艺标签。
  • warning:警告色 #C98A4E,赭石,用于星级评分、刀币。
  • danger:危险色 #8E3B36,朱砂红,用于删除按钮、战国古钱、价格最高端。
  • white:白色 #FFFFFF。
  • gold:铜金 #B0893F,用于装饰圆(12x12铜金圆+2px丝白边模拟古钱币,铜金圆模拟铜钱)。
  • silkWhite:丝白 #F6F2E8,用于方孔模拟(12x12丝白方模拟古钱币方孔,方孔是古钱币的核心特征)。

古钱铺撤下范模弹窗运行效果 - 弹窗叠加范模页显示撤下范模确认及再想想确认撤下按钮

8.2 色彩实例化

const COLORS: CoinPalette = {
  primary: '#4E7A63', primaryLight: '#8AA996', primaryDark: '#2F4A3C',
  accent: '#B0893F', accentLight: '#D2B477',
  bg: '#F1EFE7', cardBg: '#FFFFFF', cardAlt: '#E9E3D3',
  textPrimary: '#2E332B', textSecondary: '#6E7264', textHint: '#A09F90',
  border: '#D8D4C2', line: '#E7E4D8',
  success: '#4E7BA8', warning: '#C98A4E', danger: '#8E3B36',
  white: '#FFFFFF', gold: '#B0893F', silkWhite: '#F6F2E8'
};

本应用采用"青铜绿铜金朱砂红"古钱币非遗主题设计——bg #F1EFE7 米黄(模拟宣纸/铜锈质感,古钱币文化载体),cardBg #FFFFFF 白,cardAlt #E9E3D3 浅米黄(奇偶行交替用),textPrimary #2E332B 深绿灰(古钱币深色文字,非纯黑,更有青铜和历史感),primary #4E7A63 青铜绿主色,primaryDark #2F4A3C 深青铜绿(头部背景,营造浓郁青铜古钱氛围),accent #B0893F 铜金辅助色,accentLight #D2B477 浅铜金,gold #B0893F 铜金(装饰圆用,模拟铜钱,gold字段名与accent同色,铜金是古钱币的本色),silkWhite #F6F2E8 丝白(方孔模拟用,模拟方孔)。主色体系采用"青铜绿+铜金+朱砂红+蓝+米黄"五色组合——青铜绿用于主色/价格/按钮/高峰/宋代钱,铜金用于辅助/唐代钱/价格中高端/tabBar顶部条,朱砂红用于危险/战国钱/价格最高端,蓝用于成功/工艺标签/汉代钱,米黄用于页面背景/交替卡片,五色各司其职形成"以青铜绿定主、以铜金定辅、以朱砂红定战、以蓝定工、以米黄定底"的古钱币语义着色体系。

本应用最大的视觉特色是铜金 gold 与丝白 silkWhite——铜金用于装饰圆(12x12铜金圆+2px丝白边模拟古钱币,铜金圆模拟铜钱,丝白边模拟钱币边缘,铜金是古钱币的本色),丝白用于方孔模拟(12x12丝白方模拟古钱币方孔,方孔是古钱币穿绳的孔,外圆内方是中国古钱币的核心形制,象征天圆地方),与米黄底形成"青铜绿铜金朱砂红+铜金丝白"的古钱币经典配色。头部12点装饰纹中 r%3=0 用12x12 gold铜金圆+2px silkWhite丝白边(模拟古钱币,铜金圆模拟铜钱,丝白边模拟钱币边缘),r%3=1 用8x8 primary青铜绿圆(模拟青铜钱,青铜绿是古钱币铜锈的颜色),r%3===2 用8x8 danger朱砂红圆(模拟朱砂红钱,朱砂红是古钱币朱砂锈的颜色),铜金圆丝白边+青铜绿圆+朱砂红圆三色交替,营造浓郁的古钱币装饰纹效果。古钱图标装饰区的方孔模拟外圆内方(12x12 silkWhite丝白方+2px朝代色边,圆角2非圆形,模拟古钱币方孔,方孔是古钱币穿绳的孔,外圆内方是中国古钱币的核心形制,象征天圆地方)是本应用古钱陈列列表设计的核心亮点——方孔模拟古钱币外圆内方形制,与古钱币"外圆内方通宝千年"的文化意象高度契合。gold 铜金用于装饰圆(模拟铜钱),silkWhite 丝白用于方孔模拟(模拟方孔),这种"铜金定圆+丝白定方"的设计体现了古钱币"外圆内方、天圆地方"的核心视觉元素。

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

  • color + ‘14’ = 10% 透明度(用于 CoinTag 背景、图标区背景、tabBar激活背景)
  • color + ‘40’ = 25% 透明度(用于 CoinTag 边框)
  • 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 #4E7A63 + ‘14’ = #4E7A6314(10%透明青铜绿),primary + ‘66’ = #4E7A6366(40%透明青铜绿)。这种技巧在本应用中被广泛使用(CoinTag子组件、图标装饰区、tabBar激活态、卡片边框、头部图标区等),是 ArkUI 颜色处理的高级实用技巧,大幅减少了颜色常量的定义数量。

本应用的动态颜色逻辑也十分丰富——5个颜色分级函数(getDynastyColor朝代4类/getMoldColor精度3级/getPriceColor价格4级/getOrderColor金额4级/getReviewTagColor标签5类),将颜色映射逻辑从 UI 层抽离,每个函数根据不同维度返回对应颜色值,实现"数据即配色"的动态着色模式。getPriceColor 价格四级(≥1500 danger朱砂红/≥800 accent铜金/≥400 primary青铜绿/其他 #4E7BA8蓝),getOrderColor 金额四级(≥80万 danger朱砂红/≥60万 accent铜金/≥40万 primary青铜绿/其他 #4E7BA8蓝),getMoldColor 精度三级(≥95 accent铜金/≥93 primary青铜绿/其他 danger朱砂红),getDynastyColor 朝代四类(唐/清→铜金#B0893F/宋/明→青铜绿#4E7A63/战国→朱砂红#8E3B36/其他→#4E7BA8蓝),getReviewTagColor 标签五类(品质类→铜金/稀有类→青铜绿/服务类→朱砂红/教具类→蓝/其他→赭石)。这种将所有色彩集中管理、配合5个颜色分级函数和十六进制透明度拼接与铜金丝白装饰与多维度动态着色的做法保证了全局一致性,降低了维护成本。

古钱铺定制古钱弹窗运行效果 - 弹窗叠加古钱页显示品名钱文预算三行及取消确认铸钱按钮


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

9.1 @Observed聚合数据类 CoinData

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

@Observed
export class CoinData {
  coins: CoinItem[] = [ /* 12款古钱 */ ];
  molds: MoldItem[] = [ /* 12种范模 */ ];
  cases: CaseItem[] = [ /* 12种钱匣 */ ];
  orders: CoinOrderItem[] = [ /* 12笔订单 */ ];
  reviews: CoinReviewItem[] = [ /* 12条客评 */ ];
}

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

9.2 元数据接口

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

  • CoinItem:古钱项(name/dynasty/craft/price/emoji,12款古钱)
  • MoldItem:范模项(name/material/level/place/emoji,12种范模)
  • CaseItem:钱匣项(name/wood/level/place/emoji,12种钱匣)
  • CoinOrderItem:订单项(name/region/count/amount/emoji,12笔)
  • CoinReviewItem:客评项(name/score/date/content/tag/emoji,12条)
  • WeekCoinMeta:周销量元数据(day/value,7天)
  • DynastyShareMeta:朝代构成元数据(name/count/color,4种)
  • CoinHotMeta:铸造工艺热度元数据(name/heat/color,6种)
  • CoinTopMeta:古钱销量排行元数据(name/sold/color,6款)

9.3 5个颜色分级函数

function getDynastyColor(dynasty: string): string { /* 朝代4类:唐/清→铜金/宋/明→青铜绿/战国→朱砂红/其他→蓝 */ }
function getMoldColor(level: number): string { /* 精度3级:≥95铜金/≥93青铜绿/其他朱砂红 */ }
function getPriceColor(price: number): string { /* 价格4级:≥1500朱砂红/≥800铜金/≥400青铜绿/其他蓝 */ }
function getOrderColor(amount: number): string { /* 金额4级:≥80万朱砂红/≥60万铜金/≥40万青铜绿/其他蓝 */ }
function getReviewTagColor(tag: string): string { /* 标签5类 */ }

5 个颜色分级函数将颜色映射逻辑从 UI 层抽离,每个函数根据不同维度(朝代/精度/价格/金额/标签)返回对应颜色值,实现"数据即配色"的动态着色模式。getMoldColor 被范模页和钱匣页共用(精度/品级都用三级着色),getPriceColor 用于古钱页价格着色,getOrderColor 用于订单页金额着色,getReviewTagColor 用于客评页标签着色,getDynastyColor 用于古钱页朝代标签着色。

所有数据类用对象字面量初始化(非 new 构造函数),@Observed 装饰聚合类,@ObjectLink 在子组件间共享引用,5个颜色分级函数将逻辑从 UI 层抽离,9个元数据接口实现数据结构规范化,4个统计常量数组(WEEK_SOLD/DYNASTY_SHARE/COIN_HOT/COIN_TOP)将统计数据与业务数据分离,是本应用数据层设计的核心亮点。数据规模:5个业务数组各12条=60条业务数据,4个统计数组共23条统计数据,总计83条数据,完整呈现古钱币非遗手作平台的数据生态。12款都是中国古钱币名品(开元通宝/乾隆通宝/五铢钱/半两钱/崇宁通宝/大观通宝/永乐通宝/顺治通宝/光绪通宝/货布/刀币/布币),朝代覆盖唐/清/汉/宋/明/战国,工艺覆盖翻砂/范铸/母钱/机制/叠铸,12种范模材质覆盖陶/石/铜/精铜/砂/铁/泥/蜡,12种钱匣材质覆盖红木/金丝楠/织锦/黄铜/竹/大漆/樟木/花梨/乌木/紫檀/楠木/檀木,6种铸造工艺覆盖核心工艺(翻砂铸钱/母钱修模/范铸叠铸/钱文篆刻/鎏金锉边/包浆养护),4种朝代构成(唐/宋/清/汉),数据文化含量极高,是本应用区别于之前主题的最大特色。


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

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

10.1 11个组件清单

  1. CoinApp(入口组件):@Entry装饰,管理curTab/data/4个弹窗显隐/2个头部动画状态/delMoldName/detailCaseName,包含头部+内容区切换+tabBar+4个弹窗条件渲染。
  2. CoinTag(标签子组件):@Component,@Prop text+@Prop color,封装标签样式(9号字+color+'14’背景+color+'40’边框+圆角10),被古钱/范模/钱匣/客评页共用。
  3. CoinContent(古钱内容子组件):@Component,@ObjectLink data+onAdd回调,本周销量柱状图+古钱陈列列表。
  4. MoldContent(范模内容子组件):@Component,@ObjectLink data+onDel回调,古钱销量TOP6横向进度条+范模库列表。
  5. CaseContent(钱匣内容子组件):@Component,@ObjectLink data+onDetail回调,铸造工艺热度横向进度条+钱匣坊列表。
  6. CoinOrderContent(订单内容子组件):@Component,@ObjectLink data+onEdit回调,朝代构成横向进度条+订单台账列表。
  7. CoinReviewContent(客评内容子组件):@Component,@ObjectLink data,客评口碑列表。
  8. AddCoinModal(定制古钱弹窗子组件):@Component,onClose回调,品名/钱文/预算三行展示+确认铸钱。
  9. EditCoinOrderModal(修改订单弹窗子组件):@Component,onClose回调,客户/数量/金额三行+保存修改。
  10. DeleteMoldModal(撤下范模弹窗子组件):@Component,@Prop title+onClose回调,撤下确认+确认撤下danger红底。
  11. DetailCaseModal(钱匣详情弹窗子组件):@Component,@Prop name+onClose回调,大图标🗃️+钱匣描述+木料产地+匠人评级+知道了。

10.2 @ObjectLink数据共享机制

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

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

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

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

10.3 回调函数父子通信

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

  • CoinContent 的 onAdd:点击➕触发,入口组件设置 showAddCoin=true。
  • MoldContent 的 onDel(n: string):点击🗑️触发,入口组件设置 delMoldName=n + showDeleteMold=true。
  • CaseContent 的 onDetail(n: string):点击👁️触发,入口组件设置 detailCaseName=n + showDetailCase=true。
  • CoinOrderContent 的 onEdit:点击✏️触发,入口组件设置 showEditOrder=true。

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

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

DeleteMoldModal 和 DetailCaseModal 通过 @Prop 接收参数(title/name),@Prop 是值传递单向同步,入口组件设置 delMoldName/detailCaseName 后传递给弹窗子组件,弹窗内显示对应范模名/钱匣名。

10.4 头部交互动画

头部右侧 🪙🔥🗃️ 三个图标,其中 🪙 和 🔥 可点击触发动画:

  • 🪙 rotate旋转180度动画@State coinSpin: boolean = false,点击🪙 this.coinSpin = !this.coinSpin.rotate({ angle: this.coinSpin ? 180 : 0 }).animation({ duration: 720, curve: Curve.EaseOut }),点击🪙旋转180度,720ms EaseOut 动画,模拟古钱币翻转(古钱币是圆形,翻转180度从正面到背面,模拟古钱币正反面翻转,180度是古钱币翻转的标准角度)。
  • 🔥 translate位移动画@State fireFloat: boolean = false,点击🔥 this.fireFloat = !this.fireFloat.translate({ x: 0, y: this.fireFloat ? -10 : 4 }).animation({ duration: 640, curve: Curve.EaseOut }),点击🔥从y=4下移到y=-10上移,640ms EaseOut 动画,模拟炉火升腾(🔥是炉火,升腾模拟铸钱炉火向上燃烧,是古钱币铸造主题的核心意象)。

头部🪙rotate旋转180度动画和🔥translate位移动画是本应用最具创意的交互设计,点击🪙rotate旋转180度模拟古钱币翻转(古钱币是圆形,翻转180度从正面到背面,模拟古钱币正反面翻转,180度是古钱币翻转的标准角度,720ms比之前的650ms稍长,古钱币翻转更稳重),点击🔥translate位移模拟炉火升腾(🔥是炉火,升腾模拟铸钱炉火向上燃烧,y从4到-10模拟炉火上升轨迹,是古钱币铸造主题的核心意象),增加了应用的趣味性和互动感,也体现了 ArkUI 显式动画(animation属性配合状态变化,支持rotate/translate/scale多种变换)的使用。与之前830珍珠的🫧translate+✨scale不同,本应用用了🪙rotate旋转180+🔥translate位移,每次主题换一种动画组合,且rotate角度从90度(筷子横竖变换)变为180度(古钱币正反面翻转),translate位移保持y单轴(炉火只上下升腾),体现了动画与主题文化的深度结合。🗃️ 图标纯装饰不可点击,三个图标区用 primaryLight+‘2E’(18%透明浅青铜绿底)+border primaryLight+‘80’(50%透明浅青铜绿边)+圆角24,营造古钱币装饰感。

10.5 12点古钱装饰纹

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

  • r%3===0:12x12 gold铜金圆+2px silkWhite丝白边(圆角6,模拟古钱币,铜金圆模拟铜钱,丝白边模拟钱币边缘,铜金是古钱币的本色)。
  • r%3===1:8x8 primary青铜绿圆(圆角4,模拟青铜钱,青铜绿是古钱币铜锈的颜色)。
  • r%3===2:8x8 danger朱砂红圆(圆角4,模拟朱砂红钱,朱砂红是古钱币朱砂锈的颜色)。

12 个装饰点用 SpaceBetween 均匀分布,铜金圆丝白边+青铜绿圆+朱砂红圆三色交替,铜金圆丝白边模拟古钱币(铜金圆模拟铜钱,丝白边模拟钱币边缘,铜金是古钱币的本色),青铜绿圆模拟青铜钱(青铜绿是古钱币铜锈的颜色),朱砂红圆模拟朱砂红钱(朱砂红是古钱币朱砂锈的颜色),营造浓郁的古钱币装饰纹效果,是本应用头部设计的特色细节。与之前830珍珠的丝白圆玫瑰金边+玫瑰粉圆+深海蓝圆(模拟珍珠+南洋金珠+大溪地黑珠)不同,本应用用了铜金圆丝白边+青铜绿圆+朱砂红圆(模拟古钱币+青铜钱+朱砂红钱),装饰纹形态与主题高度契合——古钱币的核心视觉元素就是圆形铜钱和铜锈朱砂锈,用铜金圆丝白边和青铜绿圆朱砂红圆交替模拟古钱币,是本应用装饰纹设计的精妙之处。下方"🔥 炉火千载 · 通宝万年 🪙"分隔线,用左右两条 primaryLight+‘66’(40%透明浅青铜绿)横线+中间文字,"炉火千载"是古钱币铸造文化意象(铸钱炉火千年不息,从先秦到清末铸钱工艺传承千年),"通宝万年"是古钱币钱文文化意象(通宝钱制从唐代沿用至清末,万年传承),🔥🪙是古钱币符号,营造古钱币文化氛围。


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

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

11.1 AddCoinModal 定制古钱

@Component
struct AddCoinModal {
  onClose: () => void = () => {};
  build() {
    Column() { // 全屏遮罩 #66000000
      Column() { // 弹窗卡片 width 86%,圆角18
        Row() { Text('🪙 定制古钱') + Text('✕').onClick(onClose) }
        Row() { Text('品名') + Text('开元通宝') } // cardAlt底
        Row() { Text('钱文') + Text('唐 · 翻砂 · 隶书') } // cardAlt底
        Row() { Text('预算') + Text('¥880') } // primary青铜绿色
        Row() { Text('取消') + Text('确认铸钱') } // primary青铜绿底
      }
    }
  }
}

定制古钱弹窗显示品名(开元通宝)、钱文(唐·翻砂·隶书)、预算(¥880,primary青铜绿色)三行信息,每行用 cardAlt 浅米黄底,确认铸钱按钮用 primary 青铜绿底白字。点击✕/取消/确认铸钱都调用 onClose() 关闭弹窗。本弹窗为展示型弹窗(无输入框),预设定制信息,点击确认即关闭。“确认铸钱"是古钱币定制的专业术语(古钱币铸造称"铸钱”),与之前珍珠的"确认入珠"、筷子的"确认制箸"形成主题化按钮文案。弹窗卡片圆角18。

11.2 EditCoinOrderModal 修改订单

修改订单弹窗显示客户(博物馆文创)、数量(900 → 1000 枚,primary青铜绿色显示变更)、金额(¥500000,accent铜金色)三行信息,每行 cardAlt 浅米黄底,数量变更用 primary 青铜绿色突出显示(从900增至1000),金额用 accent 铜金色。保存修改按钮用 primary 青铜绿底白字。点击✕/取消/保存修改都调用 onClose() 关闭。本弹窗同样为展示型,预设修改信息(数量从900增至1000,金额对应增至50万)。弹窗卡片圆角18。

11.3 DeleteMoldModal 撤下范模

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

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

11.4 DetailCaseModal 钱匣详情

钱匣详情弹窗通过 @Prop name 接收钱匣名,显示大图标(🗃️30号)+钱匣名(this.name,textPrimary深绿字加粗)+描述文字(选老料整木开板,榫卯相接不用一钉,内衬绒布防磨,盖面镶铜扣铜环。铜钱入匣叮当作响,一匣藏尽千秋通宝。)+木料产地(苏州·老料坊)+匠人评级(甲级·木作卅载,primary青铜绿色),知道了按钮用 primary 青铜绿底白字。描述文字是固定的钱匣描述(本项目未根据钱匣名动态切换描述,预设钱匣文案),木料产地和匠人评级也是预设值。点击✕/知道了调用 onClose() 关闭。@Prop name 接收钱匣名用于标题显示。"一匣藏尽千秋通宝"是古钱币收藏文化的经典描述(一个钱匣收藏各个朝代的通宝钱币,一匣藏尽千年历史)。弹窗卡片圆角18。

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


十二、关键技术特性对比

特性维度实现方式技术要点设计价值
色彩管理CoinPalette接口 + COLORS常量集中定义20种语义色,含青铜绿铜金朱砂红主题保证视觉一致性,支持主题切换
青铜绿铜金朱砂红主题primary #4E7A63 + accent #B0893F + bg #F1EFE7青铜绿主色/铜金辅助/朱砂红战国/蓝工艺/米黄背景,textPrimary #2E332B深绿灰非纯黑古钱币非遗氛围,青铜绿铜金经典配色
深青铜绿头部primaryDark #2F4A3C底 + 白字标题 + primaryLight浅青铜绿副标题深青铜绿背景营造浓郁青铜古钱氛围,右侧🪙🔥🗃️图标区头部视觉焦点,青铜古钱氛围
头部🪙rotate旋转180度动画coinSpin状态 + rotate angle 180 + animation 720ms EaseOut点击🪙旋转180度,模拟古钱币翻转(正反面翻转,180度是古钱币翻转标准角度)交互趣味性,古钱币翻转特色动画
头部🔥translate位移动画fireFloat状态 + translate y从4到-10 + animation 640ms EaseOut点击🔥上下浮动,模拟炉火升腾(铸钱炉火向上燃烧,古钱币铸造主题核心意象)交互趣味性,炉火升腾特色动画
12点古钱装饰纹COIN_COL 0-11 + r%3三种形状12x12 gold铜金圆+2px silkWhite丝白边(模拟古钱币)/8x8 primary青铜绿圆(模拟青铜钱)/8x8 danger朱砂红圆(模拟朱砂红钱)古钱币形态模拟,铜金圆丝白边核心元素
铜金gold装饰#B0893F用于装饰圆(12x12铜金圆+丝白边模拟古钱币,gold与accent同色)铜金是古钱币的本色,铜金圆模拟铜钱装饰专用色,铜钱本色模拟
丝白silkWhite装饰#F6F2E8用于方孔模拟(12x12丝白方模拟古钱币方孔,方孔是古钱币核心特征)独立装饰色,模拟古钱币方孔(外圆内方)装饰专用色,方孔形制模拟
古钱图标区方孔模拟外圆内方12x12 silkWhite丝白方+2px朝代色边(圆角2非圆形)古钱页特色,丝白方模拟古钱币方孔(外圆内方,方孔是古钱币穿绳的孔,象征天圆地方)古钱币形制模拟,主题高度契合
圆形图标非方形44x44圆角22圆形(非10方形)古钱币是圆形,用圆形图标模拟(非方形,方形不适合古钱币主题)主题契合,圆形古钱
卡片圆角统一14所有列表卡片圆角14(统一古钱币方孔感,比珍珠的22小)古钱币外圆内方,用中等圆角体现方孔特征主题契合,方孔感
统计卡圆角16统计卡圆角16(比卡片14大一点形成层次)统计卡与列表卡形成圆角层次视觉层次,方孔感
弹窗卡片圆角18四个弹窗卡片圆角18弹窗圆润感主题契合,圆润感
tabBar激活态圆角10t.color文字+背景t.color+'14’圆角10+border 1px t.color5个Tab各自颜色激活,圆角10(古钱币方孔感,比珍珠的20小)古钱导航,色彩丰富,方孔感
柱状图圆角8柱子圆角8(古钱币圆润感)古钱币是圆形,柱子用大圆角模拟圆润主题契合,圆润感
十六进制透明度拼接color+‘14’/‘40’/‘55’/‘66’/‘80’/‘0D’/‘33’/‘2E’#RRGGBBAA格式,字符串拼接动态生成半透明色减少颜色常量,高级技巧
多组件架构1入口+1标签+5内容+4弹窗=11组件每个组件独立封装,职责单一,@Component装饰高内聚低耦合,工程化
@Observed聚合数据类CoinData一个类含5个数组@Observed装饰整个类,对象字面量初始化非constructor数据集中管理,聚合模式
@ObjectLink数据共享子组件@ObjectLink data: CoinData引用传递双向同步,一个数据对象贯穿所有页面跨组件数据共享,高级实践
回调函数父子通信onAdd/onDel/onDetail/onEdit/onClose子组件通过回调触发入口弹窗,入口通过onClose关闭组件解耦,通信清晰
@Prop参数传递DeleteMoldModal的title/DetailCaseModal的name值传递单向同步,入口设置后自动同步到弹窗弹窗动态参数,灵活
CoinTag子组件@Prop text + @Prop color封装标签样式,color+'14’背景+color+'40’边框标签组件复用,一致性
5个颜色分级函数getDynastyColor/getMoldColor/getPriceColor/getOrderColor/getReviewTagColor按朝代/精度/价格/金额/标签分级返回颜色数据即配色,逻辑抽离
朝代四级着色getDynastyColor 唐/清→铜金/宋/明→青铜绿/战国→朱砂红/其他→蓝古钱币4个主要朝代,唐代开元通宝最经典用铜金以朝代定颜色,形制着色
价格四级着色getPriceColor ≥1500朱砂红/≥800铜金/≥400青铜绿/其他蓝古钱价格260-2380,刀币2380用danger朱砂红最高端(战国稀有异形币)以价格定颜色,动态着色
金额四级着色getOrderColor ≥80万朱砂红/≥60万铜金/≥40万青铜绿/其他蓝订单金额22.5万-90万,拍卖行90万用danger朱砂红最大额以金额定颜色,动态着色
精度三级着色getMoldColor ≥95铜金/≥93青铜绿/其他朱砂红范模/钱匣精度90-97,雕母97用铜金最高(精铜雕刻母钱最高工艺)以精度定颜色,动态着色
本周销量柱状图WEEK_SOLD 7天 + height=w.value直接用数值柱子高度=数值非比例映射,周末≥60用accent铜金/其他primary青铜绿,最大65px,柱子圆角8数值即高度,古钱收藏文物
横向进度条layoutWeightlayoutWeight(t.sold) + layoutWeight(100-t.sold)弹性布局动态分配宽度,非固定宽度非百分比ArkUI特有进度条实现
朝代构成进度条layoutWeight(s.count) + layoutWeight(20-s.count)总份数20非100,唐×5占比最高(唐代开元通宝最经典)灵活比例,朝代形制价值
古钱销量TOP6COIN_TOP 6款 + 横向进度条开元通宝97居首,进度条几乎满格古钱排行一目了然
铸造工艺热度COIN_HOT 6工艺 + 横向进度条翻砂铸钱96居首,覆盖古钱币核心铸造工艺6种(翻砂铸钱/母钱修模/范铸叠铸/钱文篆刻/鎏金锉边/包浆养护)工艺热度排行,古钱文化
铸模精度内嵌进度条内嵌列表行 + layoutWeight(c.level)范模库每行内嵌铸模精度进度条,getMoldColor着色铸模精度可视化,专业感
奇偶行交替条纹i%2=0 cardBg白 / i%2=1 cardAlt浅米黄所有列表页共用,营造条纹效果列表可读性,视觉层次
星级字符串重复‘⭐’.repeat(r.score)纯字符串乘法生成星级,非循环非组件轻量级星级展示,实用技巧
tabBar顶部条用accent铜金3px accent #B0893F本应用特色,铜金更符合古钱币本色主题主题契合,铜金特色
四大弹窗子组件AddCoinModal/EditCoinOrderModal/DeleteMoldModal/DetailCaseModal独立@Component,@Prop参数,onClose回调,#66000000遮罩,弹窗圆角18增删改查闭环,组件复用
弹窗@Prop动态参数DeleteMoldModal的title / DetailCaseModal的name入口设置delMoldName/detailCaseName后@Prop同步弹窗内容动态化,灵活
modalOverlay特殊实现width1 height1 Column+透明Text+透明Button占位用,实际弹窗用独立背景#66000000兼容框架要求,特殊处理
12款中国古钱币名品开元通宝/乾隆通宝/五铢钱/半两钱/崇宁通宝/大观通宝/永乐通宝/顺治通宝/光绪通宝/货布/刀币/布币价格260-2380,刀币2380最高端(战国稀有异形币),朝代覆盖唐/清/汉/宋/明/战国中国古钱名品全收录,文化全收录
12种范模材质全品类陶/石/铜/精铜/砂/铁/泥/蜡精度90-97,雕母97最高(精铜雕刻母钱最高工艺),产地洛阳/临淄/长安等古钱范铸工艺生态,全品类
12种钱匣材质全覆盖红木/金丝楠/织锦/黄铜/竹/大漆/樟木/花梨/乌木/紫檀/楠木/檀木精度92-96,红木钱匣96最高(苏州工艺),产地苏州/成都/南京等古钱收藏匣生态,场景全覆盖
6种铸造核心工艺翻砂铸钱/母钱修模/范铸叠铸/钱文篆刻/鎏金锉边/包浆养护热度96-90,翻砂铸钱96最核心(从唐代沿用至清末的主要工艺)古钱铸造工艺文化,技术多样
4种朝代构成唐×5/宋×4/清×3/汉×2古钱币4个主要朝代类别,唐代开元通宝最经典占比最高古钱朝代文化,多样性
12种B端客户生态博物馆文创/收藏协会/钱币研究所/拍卖行/海外藏家/古玩城店铺/直播鉴宝/文创礼盒商/大学历史系/电视剧组/钱庄博物馆/私人典藏古钱币作为收藏文物,客户以拍卖行/收藏协会/钱币研究所/博物馆等收藏机构为主古钱商业生态,收藏文物定位
数据规模5业务数组各12条=60条+4统计数组23条=83条古钱/范模/钱匣/订单/客评各12,统计7+4+6+6完整数据生态,古钱币非遗

完整代码

interface CoinPalette {
  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: CoinPalette = {
  primary: '#4E7A63',
  primaryLight: '#8AA996',
  primaryDark: '#2F4A3C',
  accent: '#B0893F',
  accentLight: '#D2B477',
  bg: '#F1EFE7',
  cardBg: '#FFFFFF',
  cardAlt: '#E9E3D3',
  textPrimary: '#2E332B',
  textSecondary: '#6E7264',
  textHint: '#A09F90',
  border: '#D8D4C2',
  line: '#E7E4D8',
  success: '#4E7BA8',
  warning: '#C98A4E',
  danger: '#8E3B36',
  white: '#FFFFFF',
  gold: '#B0893F',
  silkWhite: '#F6F2E8'
};

enum CoinTab {
  COIN = 0,
  MOLD = 1,
  CASE = 2,
  ORDER = 3,
  REVIEW = 4
}

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

const TAB_LIST: TabMeta[] = [
  { key: 'coin', icon: '🪙', label: '古钱', color: '#B0893F' },
  { key: 'mold', icon: '🔨', label: '范模', color: '#4E7A63' },
  { key: 'case', icon: '🗃️', label: '钱匣', color: '#8E3B36' },
  { key: 'order', icon: '📦', label: '订单', color: '#4E7BA8' },
  { key: 'review', icon: '⭐', label: '客评', color: '#C98A4E' }
];

const COIN_COL: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];

interface CoinItem {
  name: string;
  dynasty: string;
  craft: string;
  price: number;
  emoji: string;
}

interface MoldItem {
  name: string;
  material: string;
  level: number;
  place: string;
  emoji: string;
}

interface CaseItem {
  name: string;
  wood: string;
  level: number;
  place: string;
  emoji: string;
}

interface CoinOrderItem {
  name: string;
  region: string;
  count: number;
  amount: number;
  emoji: string;
}

interface CoinReviewItem {
  name: string;
  score: number;
  date: string;
  content: string;
  tag: string;
  emoji: string;
}

interface WeekCoinMeta {
  day: string;
  value: number;
}

interface DynastyShareMeta {
  name: string;
  count: number;
  color: string;
}

interface CoinHotMeta {
  name: string;
  heat: number;
  color: string;
}

interface CoinTopMeta {
  name: string;
  sold: number;
  color: string;
}

const WEEK_SOLD: WeekCoinMeta[] = [
  { day: '周一', value: 20 },
  { day: '周二', value: 28 },
  { day: '周三', value: 36 },
  { day: '周四', value: 44 },
  { day: '周五', value: 57 },
  { day: '周六', value: 65 },
  { day: '周日', value: 59 }
];

const DYNASTY_SHARE: DynastyShareMeta[] = [
  { name: '唐', count: 5, color: '#B0893F' },
  { name: '宋', count: 4, color: '#4E7A63' },
  { name: '清', count: 3, color: '#8E3B36' },
  { name: '汉', count: 2, color: '#4E7BA8' }
];

const COIN_HOT: CoinHotMeta[] = [
  { name: '翻砂铸钱', heat: 96, color: '#B0893F' },
  { name: '母钱修模', heat: 95, color: '#4E7A63' },
  { name: '范铸叠铸', heat: 94, color: '#8E3B36' },
  { name: '钱文篆刻', heat: 93, color: '#D2B477' },
  { name: '鎏金锉边', heat: 91, color: '#4E7BA8' },
  { name: '包浆养护', heat: 90, color: '#C98A4E' }
];

const COIN_TOP: CoinTopMeta[] = [
  { name: '开元通宝', sold: 97, color: '#B0893F' },
  { name: '五铢钱', sold: 93, color: '#4E7A63' },
  { name: '乾隆通宝', sold: 90, color: '#8E3B36' },
  { name: '大观通宝', sold: 87, color: '#D2B477' },
  { name: '崇宁通宝', sold: 84, color: '#4E7BA8' },
  { name: '刀币', sold: 80, color: '#C98A4E' }
];

@Observed
export class CoinData {
  coins: CoinItem[] = [
    { name: '开元通宝', dynasty: '唐', craft: '翻砂', price: 880, emoji: '🪙' },
    { name: '乾隆通宝', dynasty: '清', craft: '翻砂', price: 260, emoji: '🏮' },
    { name: '五铢钱', dynasty: '汉', craft: '范铸', price: 360, emoji: '⚖️' },
    { name: '半两钱', dynasty: '汉', craft: '范铸', price: 680, emoji: '🪨' },
    { name: '崇宁通宝', dynasty: '宋', craft: '翻砂', price: 720, emoji: '🏯' },
    { name: '大观通宝', dynasty: '宋', craft: '母钱', price: 1580, emoji: '🦅' },
    { name: '永乐通宝', dynasty: '明', craft: '翻砂', price: 560, emoji: '🌏' },
    { name: '顺治通宝', dynasty: '清', craft: '翻砂', price: 420, emoji: '🐉' },
    { name: '光绪通宝', dynasty: '清', craft: '机制', price: 380, emoji: '🐎' },
    { name: '货布', dynasty: '汉', craft: '叠铸', price: 1280, emoji: '🪓' },
    { name: '刀币', dynasty: '战国', craft: '范铸', price: 2380, emoji: '🗡️' },
    { name: '布币', dynasty: '战国', craft: '范铸', price: 1880, emoji: '🌾' }
  ];

  molds: MoldItem[] = [
    { name: '陶范', material: '陶', level: 96, place: '洛阳', emoji: '🏺' },
    { name: '石范', material: '石', level: 93, place: '临淄', emoji: '🪨' },
    { name: '铜范', material: '铜', level: 95, place: '长安', emoji: '🥉' },
    { name: '母钱范', material: '精铜', level: 96, place: '建业', emoji: '🔱' },
    { name: '子范', material: '陶', level: 92, place: '成都', emoji: '🥣' },
    { name: '叠铸范', material: '陶', level: 94, place: '长沙', emoji: '📚' },
    { name: '翻砂模', material: '砂', level: 95, place: '汴京', emoji: '🏖️' },
    { name: '雕母', material: '精铜', level: 97, place: '北京', emoji: '🎯' },
    { name: '铁范', material: '铁', level: 93, place: '邯郸', emoji: '⚙️' },
    { name: '泥范', material: '泥', level: 90, place: '南阳', emoji: '🧱' },
    { name: '钱树范', material: '陶', level: 92, place: '洛阳', emoji: '🌳' },
    { name: '蜡模', material: '蜡', level: 94, place: '杭州', emoji: '🕯️' }
  ];

  cases: CaseItem[] = [
    { name: '红木钱匣', wood: '红木', level: 96, place: '苏州', emoji: '🪵' },
    { name: '楠木钱匣', wood: '金丝楠', level: 95, place: '成都', emoji: '🌲' },
    { name: '锦盒', wood: '织锦', level: 94, place: '南京', emoji: '🧧' },
    { name: '铜钱匣', wood: '黄铜', level: 93, place: '大理', emoji: '🥉' },
    { name: '竹编钱篓', wood: '竹', level: 92, place: '安吉', emoji: '🎋' },
    { name: '漆器钱匣', wood: '大漆', level: 95, place: '福州', emoji: '🖌️' },
    { name: '樟木匣', wood: '樟木', level: 94, place: '宜春', emoji: '🍃' },
    { name: '书匣', wood: '花梨', level: 93, place: '海南', emoji: '📚' },
    { name: '抽屉匣', wood: '乌木', level: 92, place: '东阳', emoji: '🗄️' },
    { name: '宝匣', wood: '紫檀', level: 96, place: '北京', emoji: '💰' },
    { name: '圆匣', wood: '楠木', level: 93, place: '扬州', emoji: '⭕' },
    { name: '首饰匣', wood: '檀木', level: 94, place: '广州', emoji: '🗃️' }
  ];

  orders: CoinOrderItem[] = [
    { name: '博物馆文创', region: '华北', count: 900, amount: 450000, emoji: '🏛️' },
    { name: '收藏协会', region: '华东', count: 500, amount: 750000, emoji: '🎖️' },
    { name: '钱币研究所', region: '华北', count: 400, amount: 800000, emoji: '🔬' },
    { name: '拍卖行', region: '华东', count: 300, amount: 900000, emoji: '🔨' },
    { name: '海外藏家', region: '海外', count: 350, amount: 875000, emoji: '🌏' },
    { name: '古玩城店铺', region: '华中', count: 600, amount: 360000, emoji: '🏮' },
    { name: '直播鉴宝', region: '华东', count: 750, amount: 375000, emoji: '📺' },
    { name: '文创礼盒商', region: '华南', count: 650, amount: 390000, emoji: '🎁' },
    { name: '大学历史系', region: '华北', count: 450, amount: 225000, emoji: '🎓' },
    { name: '电视剧组', region: '华北', count: 500, amount: 250000, emoji: '🎬' },
    { name: '钱庄博物馆', region: '华东', count: 550, amount: 440000, emoji: '🏦' },
    { name: '私人典藏', region: '海外', count: 250, amount: 625000, emoji: '💎' }
  ];

  reviews: CoinReviewItem[] = [
    { 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: 4, date: '08-29', content: '崇宁通宝量足价实,若附拓片更佳。', tag: '量足价实', emoji: '🏮' },
    { name: '鉴宝主播', score: 5, date: '08-28', content: '半两钱走量飞快,鉴宝专场人气爆棚。', tag: '走量飞快', emoji: '📺' },
    { name: '礼盒经理', score: 5, 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 getDynastyColor(dynasty: string): string {
  if (dynasty === '唐' || dynasty === '清') {
    return '#B0893F';
  } else if (dynasty === '宋' || dynasty === '明') {
    return '#4E7A63';
  } else if (dynasty === '战国') {
    return '#8E3B36';
  }
  return '#4E7BA8';
}

function getMoldColor(level: number): string {
  if (level >= 95) {
    return '#B0893F';
  } else if (level >= 93) {
    return '#4E7A63';
  }
  return '#8E3B36';
}

function getPriceColor(price: number): string {
  if (price >= 1500) {
    return '#8E3B36';
  } else if (price >= 800) {
    return '#B0893F';
  } else if (price >= 400) {
    return '#4E7A63';
  }
  return '#4E7BA8';
}

function getOrderColor(amount: number): string {
  if (amount >= 800000) {
    return '#8E3B36';
  } else if (amount >= 600000) {
    return '#B0893F';
  } else if (amount >= 400000) {
    return '#4E7A63';
  }
  return '#4E7BA8';
}

function getReviewTagColor(tag: string): string {
  if (tag === '品相完整' || tag === '形制标准' || tag === '一币一史' || tag === '做工考究') {
    return '#B0893F';
  } else if (tag === '稀世难得' || tag === '钱文精美' || tag === '包浆自然') {
    return '#4E7A63';
  } else if (tag === '量足价实' || tag === '走量飞快' || tag === '开箱用心' || tag === '质感真实') {
    return '#8E3B36';
  } else if (tag === '教具齐全') {
    return '#4E7BA8';
  }
  return '#C98A4E';
}

@Entry
@Component
struct CoinApp {
  @State curTab: number = 0;
  @State data: CoinData = new CoinData();
  @State showAddCoin: boolean = false;
  @State showEditOrder: boolean = false;
  @State showDeleteMold: boolean = false;
  @State showDetailCase: boolean = false;
  @State delMoldName: string = '';
  @State detailCaseName: string = '';
  @State coinSpin: boolean = false;
  @State fireFloat: 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('Coin · 外圆内方 通宝千年')
                .fontSize(10)
                .fontColor(COLORS.primaryLight)
                .margin({ top: 3 })
            }
            .alignItems(HorizontalAlign.Start)
            .layoutWeight(1)
            Row() {
              Text('🪙')
                .fontSize(18)
                .onClick(() => {
                  this.coinSpin = !this.coinSpin;
                })
                .rotate({ angle: this.coinSpin ? 180 : 0 })
                .animation({ duration: 720, curve: Curve.EaseOut })
              Text('🔥')
                .fontSize(16)
                .margin({ left: 10 })
                .onClick(() => {
                  this.fireFloat = !this.fireFloat;
                })
                .translate({ x: 0, y: this.fireFloat ? -10 : 4 })
                .animation({ duration: 640, 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(COIN_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(8)
                    .height(8)
                    .backgroundColor(COLORS.primary)
                    .borderRadius(4)
                } else {
                  Column()
                    .width(8)
                    .height(8)
                    .backgroundColor(COLORS.danger)
                    .borderRadius(4)
                }
              }
              .width(14)
              .justifyContent(FlexAlign.Center)
            }, (r: number) => 'coin' + 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 === CoinTab.COIN) {
          CoinContent({ data: this.data, onAdd: () => {
            this.showAddCoin = true;
          } })
        } else if (this.curTab === CoinTab.MOLD) {
          MoldContent({ data: this.data, onDel: (n: string) => {
            this.delMoldName = n;
            this.showDeleteMold = true;
          } })
        } else if (this.curTab === CoinTab.CASE) {
          CaseContent({ data: this.data, onDetail: (n: string) => {
            this.detailCaseName = n;
            this.showDetailCase = true;
          } })
        } else if (this.curTab === CoinTab.ORDER) {
          CoinOrderContent({ data: this.data, onEdit: () => {
            this.showEditOrder = true;
          } })
        } else {
          CoinReviewContent({ 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.showAddCoin) {
        AddCoinModal({
          onClose: () => {
            this.showAddCoin = false;
          }
        })
      }
      if (this.showEditOrder) {
        EditCoinOrderModal({
          onClose: () => {
            this.showEditOrder = false;
          }
        })
      }
      if (this.showDeleteMold) {
        DeleteMoldModal({
          title: this.delMoldName, onClose: () => {
            this.showDeleteMold = false;
          }
        })
      }
      if (this.showDetailCase) {
        DetailCaseModal({
          name: this.detailCaseName, onClose: () => {
            this.showDetailCase = false;
          }
        })
      }
    }
  }
}

@Component
struct CoinTag {
  @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 CoinContent {
  @ObjectLink data: CoinData;
  onAdd: () => void = () => {};

  build() {
    Scroll() {
      Column() {
        Column() {
          Row() {
            Column()
              .width(12)
              .height(12)
              .backgroundColor(COLORS.gold)
              .borderRadius(6)
              .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.primary)
              .borderRadius(3)
              .margin({ right: 4 })
            Column()
              .width(12)
              .height(12)
              .backgroundColor(COLORS.danger)
              .borderRadius(6)
              .border({ width: 2, color: COLORS.silkWhite })
            Column()
              .layoutWeight(1)
              .height(2)
              .backgroundColor(COLORS.accent + '55')
              .margin({ left: 4, right: 4 })
            Column()
              .width(12)
              .height(12)
              .backgroundColor(COLORS.gold)
              .borderRadius(6)
              .border({ width: 2, color: COLORS.silkWhite })
          }
          .width('100%')
          .margin({ bottom: 10 })
          Row() {
            Text('📈 本周古钱销量')
              .fontSize(13)
              .fontWeight(FontWeight.Bold)
              .fontColor(COLORS.textPrimary)
            Text('周六65枚')
              .fontSize(9)
              .fontColor(COLORS.accent)
              .margin({ left: 8 })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          Row() {
            ForEach(WEEK_SOLD, (w: WeekCoinMeta) => {
              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: WeekCoinMeta) => 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.coins, (p: CoinItem, i: number) => {
            Row() {
              Column() {
                Column() {
                  Text(p.emoji)
                    .fontSize(18)
                    .textAlign(TextAlign.Center)
                }
                .width(44)
                .height(44)
                .justifyContent(FlexAlign.Center)
                .alignItems(HorizontalAlign.Center)
                .backgroundColor(getDynastyColor(p.dynasty) + '14')
                .borderRadius(22)
                .border({ width: 1, color: getDynastyColor(p.dynasty) + '66' })
                Row() {
                  Column()
                    .width(12)
                    .height(12)
                    .backgroundColor(COLORS.silkWhite)
                    .borderRadius(2)
                    .border({ width: 2, color: getDynastyColor(p.dynasty) })
                }
                .width(24)
                .justifyContent(FlexAlign.Center)
              }
              .width(60)
              .padding({ top: 10, bottom: 6 })
              .backgroundColor(getDynastyColor(p.dynasty) + '0D')
              .borderRadius(30)
              .border({ width: 1, color: getDynastyColor(p.dynasty) + '33' })

              Column() {
                Row() {
                  Text(p.name)
                    .fontSize(13)
                    .fontWeight(FontWeight.Bold)
                    .fontColor(COLORS.textPrimary)
                  Text('¥' + p.price)
                    .fontSize(10)
                    .fontWeight(FontWeight.Bold)
                    .fontColor(getPriceColor(p.price))
                    .margin({ left: 8 })
                }
                .width('100%')
                .justifyContent(FlexAlign.SpaceBetween)
                Row() {
                  CoinTag({ text: p.dynasty + '钱', color: getDynastyColor(p.dynasty) })
                  CoinTag({ text: p.craft, 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(14)
            .border({ width: 1, color: COLORS.line })
            .margin({ top: 8 })
          }, (p: CoinItem, i: number) => 'cn' + 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 MoldContent {
  @ObjectLink data: CoinData;
  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(COIN_TOP, (t: CoinTopMeta) => {
            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: CoinTopMeta) => 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.molds, (c: MoldItem, i: number) => {
            Row() {
              Column() {
                Text(c.emoji)
                  .fontSize(18)
                  .textAlign(TextAlign.Center)
                Row() {
                  Column()
                    .width(12)
                    .height(4)
                    .backgroundColor(getMoldColor(c.level))
                    .borderRadius(2)
                  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(getMoldColor(c.level) + '0D')
              .borderRadius(16)
              .border({ width: 1, color: getMoldColor(c.level) + '33' })

              Column() {
                Row() {
                  Text(c.name)
                    .fontSize(13)
                    .fontWeight(FontWeight.Bold)
                    .fontColor(COLORS.textPrimary)
                  Text(c.material + '范')
                    .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(getMoldColor(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(14)
            .border({ width: 1, color: COLORS.line })
            .margin({ top: 8 })
          }, (c: MoldItem, i: number) => 'md' + 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 CaseContent {
  @ObjectLink data: CoinData;
  onDetail: (n: string) => void = () => {};

  build() {
    Scroll() {
      Column() {
        Column() {
          Row() {
            Text('🔥 铸造工艺热度')
              .fontSize(13)
              .fontWeight(FontWeight.Bold)
              .fontColor(COLORS.textPrimary)
            Text('翻砂铸钱96')
              .fontSize(9)
              .fontColor(COLORS.primary)
              .margin({ left: 8 })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .margin({ bottom: 10 })
          ForEach(COIN_HOT, (d: CoinHotMeta) => {
            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: CoinHotMeta) => 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.cases, (b: CaseItem, i: number) => {
            Row() {
              Column() {
                Text(b.emoji)
                  .fontSize(18)
                  .textAlign(TextAlign.Center)
                Column()
                  .width(12)
                  .height(4)
                  .backgroundColor(getMoldColor(b.level))
                  .borderRadius(2)
                  .margin({ top: 5 })
              }
              .width(56)
              .padding({ top: 10, bottom: 6 })
              .backgroundColor(getMoldColor(b.level) + '0D')
              .borderRadius(18)
              .border({ width: 1, color: getMoldColor(b.level) + '33' })

              Column() {
                Row() {
                  Text(b.name)
                    .fontSize(13)
                    .fontWeight(FontWeight.Bold)
                    .fontColor(COLORS.textPrimary)
                  Text('品级' + b.level)
                    .fontSize(9)
                    .fontColor(getMoldColor(b.level))
                    .margin({ left: 8 })
                }
                .width('100%')
                .justifyContent(FlexAlign.SpaceBetween)
                Row() {
                  CoinTag({ text: b.wood + '匣', color: COLORS.textSecondary })
                  Text(b.place)
                    .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(b.name);
                })
            }
            .width('100%')
            .padding(10)
            .backgroundColor(i % 2 === 0 ? COLORS.cardBg : COLORS.cardAlt)
            .borderRadius(14)
            .border({ width: 1, color: COLORS.line })
            .margin({ top: 8 })
          }, (b: CaseItem, i: number) => 'cs' + b.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 CoinOrderContent {
  @ObjectLink data: CoinData;
  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(DYNASTY_SHARE, (s: DynastyShareMeta) => {
            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: DynastyShareMeta) => 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: CoinOrderItem, 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(14)
            .border({ width: 1, color: COLORS.line })
            .margin({ top: 8 })
          }, (o: CoinOrderItem, 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 CoinReviewContent {
  @ObjectLink data: CoinData;

  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: CoinReviewItem, 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)
              CoinTag({ 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(14)
          .border({ width: 1, color: COLORS.line })
          .margin({ top: 8 })
        }, (r: CoinReviewItem, i: number) => 'rv' + r.name + i)
      }
      .width('100%')
      .constraintSize({ maxHeight: '100%' })
    }
    .width('100%')
    .constraintSize({ maxHeight: '100%' })
    .scrollable(ScrollDirection.Vertical)
    .scrollBar(BarState.Off)
  }
}

@Component
struct AddCoinModal {
  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('¥880')
            .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 EditCoinOrderModal {
  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('¥500000')
            .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 DeleteMoldModal {
  @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 DetailCaseModal {
  @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 子组件,入口组件 CoinApp 只负责状态管理和组件调度,实现了真正的高内聚低耦合。这种多组件架构设计虽然增加了组件间通信的复杂度(@ObjectLink/@Prop/回调函数),但大幅提升了代码的可维护性和可复用性,是中大型 ArkUI 应用的标准架构实践,也是本应用工程化设计的最大亮点。

在数据层面,应用独创了**@Observed 聚合数据类设计——CoinData 一个类包含 coins/molds/cases/orders/reviews 五个数组,每个数组 12 条数据,用对象字面量初始化(非 new 构造函数),@Observed 装饰整个类,配合 @ObjectLink 在子组件间共享引用,实现"一个数据对象贯穿所有页面"的聚合数据模式。这种设计将所有业务数据集中管理,子组件通过 @ObjectLink data: CoinData 接收共享引用,修改数据时所有引用该数据的子组件自动响应式刷新,避免了每个页面独立 @State 数据数组的重复定义,是 ArkUI 跨组件数据共享的高级实践。应用还定义了 5 个颜色分级函数(getDynastyColor朝代4类/getMoldColor精度3级/getPriceColor价格4级/getOrderColor金额4级/getReviewTagColor标签5类),将颜色映射逻辑从 UI 层抽离,每个函数根据不同维度返回对应颜色值,实现"数据即配色"的动态着色模式。9 个元数据接口实现数据结构规范化,4 个统计常量数组将统计数据与业务数据分离,数据规模达 83 条(60业务+23统计),完整呈现古钱币非遗手作平台的数据生态。本应用数据最大的特色是中国古钱币名品全覆盖**——12款都是中国古钱币名品(开元通宝/乾隆通宝/五铢钱/半两钱/崇宁通宝/大观通宝/永乐通宝/顺治通宝/光绪通宝/货布/刀币/布币),刀币¥2380最高端(战国稀有异形币),朝代覆盖唐/清/汉/宋/明/战国,工艺覆盖翻砂/范铸/母钱/机制/叠铸;12种范模材质覆盖陶/石/铜/精铜/砂/铁/泥/蜡;12种钱匣材质覆盖红木/金丝楠/织锦/黄铜/竹/大漆/樟木/花梨/乌木/紫檀/楠木/檀木;6种铸造工艺覆盖核心工艺(翻砂铸钱/母钱修模/范铸叠铸/钱文篆刻/鎏金锉边/包浆养护);4种朝代构成(唐/宋/清/汉)。这些文化数据让应用不只是一个技术演示,更是一个中国古钱币文化的数字载体。

在视觉层面,本应用采用"青铜绿铜金朱砂红"古钱币非遗主题设计——bg #F1EFE7 米黄(模拟宣纸/铜锈质感,古钱币文化载体),cardBg #FFFFFF 白,cardAlt #E9E3D3 浅米黄(奇偶行交替用),textPrimary #2E332B 深绿灰(古钱币深色文字,非纯黑,更有青铜和历史感),primary #4E7A63 青铜绿主色,primaryDark #2F4A3C 深青铜绿(头部背景,营造浓郁青铜古钱氛围),accent #B0893F 铜金辅助色,accentLight #D2B477 浅铜金,gold #B0893F 铜金(装饰圆用,模拟铜钱,gold字段名与accent同色,铜金是古钱币的本色),silkWhite #F6F2E8 丝白(方孔模拟用,模拟方孔)。主色体系采用"青铜绿+铜金+朱砂红+蓝+米黄"五色组合,五色各司其职形成"以青铜绿定主、以铜金定辅、以朱砂红定战、以蓝定工、以米黄定底"的古钱币语义着色体系。本应用最大的视觉特色是铜金 gold 与丝白 silkWhite——铜金用于装饰圆(12x12铜金圆+2px丝白边模拟古钱币,铜金圆模拟铜钱,丝白边模拟钱币边缘,铜金是古钱币的本色),丝白用于方孔模拟(12x12丝白方模拟古钱币方孔,方孔是古钱币穿绳的孔,外圆内方是中国古钱币的核心形制,象征天圆地方),与米黄底形成"青铜绿铜金朱砂红+铜金丝白"的古钱币经典配色。头部12点装饰纹中 r%3=0 用12x12 gold铜金圆+2px silkWhite丝白边(模拟古钱币),r%3=1 用8x8 primary青铜绿圆(模拟青铜钱),r%3===2 用8x8 danger朱砂红圆(模拟朱砂红钱),铜金圆丝白边+青铜绿圆+朱砂红圆三色交替,营造浓郁的古钱币装饰纹效果。古钱图标装饰区的方孔模拟外圆内方(12x12 silkWhite丝白方+2px朝代色边,圆角2非圆形,模拟古钱币方孔,方孔是古钱币穿绳的孔,外圆内方是中国古钱币的核心形制,象征天圆地方)是本应用古钱陈列列表设计的核心亮点——方孔模拟古钱币外圆内方形制,与古钱币"外圆内方通宝千年"的文化意象高度契合。本应用另一个视觉特色是卡片圆角统一14(古钱币方孔感,比珍珠的22小,古钱币外圆内方用中等圆角体现方孔特征),统计卡圆角16(比卡片14大一点形成层次),弹窗卡片圆角18,tabBar激活态圆角10(古钱币方孔感),柱状图柱子圆角8(古钱币圆润感),圆形图标非方形(44x44圆角22圆形,古钱币是圆形用圆形图标),这些圆角细节都体现了古钱币"外圆内方"的主题设计。本应用另一个色彩处理特色是十六进制透明度拼接技巧——大量使用 color + 十六进制透明度后缀实现半透明背景和边框,无需额外定义半透明颜色常量,是 ArkUI 颜色处理的高级实用技巧。统一的色彩系统是整个应用的视觉基石,青铜绿铜金朱砂红主题配合十六进制透明度拼接和铜金丝白装饰与5个颜色分级函数与多维度动态着色,营造出既青铜绿沉稳又铜金璀璨的古钱币非遗氛围。

在交互层面,应用通过四个独立弹窗子组件实现了完整的增删改查交互闭环:AddCoinModal 定制古钱(品名/钱文/预算三行展示,确认铸钱 primary 青铜绿底,“确认铸钱"是古钱币铸造专业术语)、EditCoinOrderModal 修改订单(客户/数量900→1000/金额50万,保存修改)、DeleteMoldModal 撤下范模(@Prop title 传入范模名,确认撤下 danger 红底)、DetailCaseModal 钱匣详情(@Prop name 传入钱匣名,大图标🗃️+钱匣描述"一匣藏尽千秋通宝”+木料产地+匠人评级,知道了按钮)。四个弹窗各自独立组件,通过 @Prop 接收参数(DeleteMoldModal的title、DetailCaseModal的name),通过 onClose 回调关闭,遮罩用 #66000000(40%黑)硬编码。古钱页➕点击触发 AddCoinModal,范模页🗑️点击触发 DeleteMoldModal(delMoldName 记录范模名),钱匣页👁️点击触发 DetailCaseModal(detailCaseName 记录钱匣名),订单页✏️点击触发 EditCoinOrderModal,形成了完整的"定制古钱→撤下范模→查看钱匣→修改订单"交互闭环。头部🪙rotate旋转180度动画(点击🪙旋转180度,模拟古钱币翻转,古钱币是圆形翻转180度从正面到背面,180度是古钱币翻转的标准角度,720ms)和🔥translate位移动画(点击🔥从y=4下移到y=-10上移,模拟炉火升腾,🔥是炉火升腾模拟铸钱炉火向上燃烧,是古钱币铸造主题的核心意象,640ms)是本应用最具创意的交互设计,点击图标触发显式动画,增加了应用的趣味性和互动感,也体现了 ArkUI 显式动画(animation属性配合状态变化,支持rotate/translate/scale多种变换)的使用。与之前830珍珠的🫧translate+✨scale不同,本应用用了🪙rotate旋转180+🔥translate位移,每次主题换一种动画组合,且rotate角度从90度(筷子横竖变换)变为180度(古钱币正反面翻转),translate位移保持y单轴(炉火只上下升腾),体现了动画与主题文化的深度结合。本周销量柱状图柱子高度直接用数值(最大65px,非比例映射,古钱币是收藏文物周销量20-65枚,与之前珍珠周销量18-62颗高端珠宝接近,与象棋周销量42-95副大众普及相比略低,体现古钱币"收藏文物"定位),柱子圆角8(古钱币圆润感),周末≥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、测试、元服务和应用上架分发等。

更多推荐