【HarmonyOS 6.1 全场景实战】《灵犀厨房一键推荐》元服务【架构篇】常量体系:从硬编码地狱到原子化设计的架构跃迁

HarmonyOS 6.1 全场景实战|《灵犀厨房一键推荐》元服务【架构篇】常量体系:从硬编码地狱到原子化设计的架构跃迁

摘要 :开发调试阶段,我的《灵犀厨房一键推荐》元服务里,'#FF6B35' 写了 23 次、'{ padding: 20 }' 写了 17 次、'灵犀厨房' 写了 8 次------改一个"换一道"按钮文案要翻 6 个文件,改主色调要全局搜索替换 30+ 处。你可能觉得"不就是几个颜色和尺寸嘛,直接写上去多省事"。但当你的元服务同时运行在手机、折叠屏、平板三种形态上,设计师说"主色调微调 2 度",你就知道什么叫"牵一色而动全身"。本篇将基于 HarmonyOS 6.1.0(API 23),从设计系统的原子化哲学出发,带你完成一场"一行常量,全网统一"的架构升级。


一、引言:从"配色霰弹枪"到"原子级设计系统"

《灵犀厨房一键推荐》作为 HarmonyOS 全场景应用,核心能力是"一键推荐菜谱"。产品迭代过程中,UI 逐渐丰富------季节提示区、毛玻璃协议页、Hero 卡片动画、候选菜谱列表。每加一个视觉元素,顺手就写一组颜色值和尺寸:

场景 痛点 后果
设计师调色 '#FF6B35' 散落在 8 个文件中 逐个文件替换,漏一处就出现"阴阳配色"
多设备适配 硬编码 width: 140radius: 22 折叠屏展开后按钮奇大无比,平板上一行放不下两个卡片
文案修改 "换一道"改成"不感兴趣" 按钮上改了,日志里还是"换一道",排查时一头雾水
新人接手 不知道 'rgba(255,255,255,0.25)' 是什么语义 不敢改颜色,怕破坏毛玻璃效果
组件复用 每个页面自己写一套间距 页面间视觉节奏感不一致,用户感知粗糙

AppConstants 原子常量体系的解决方案

🎯 AppColors.Primary 一次性定义主色 → 全域 30+ 处自动统一。AppTexts.Retry 一处修改文案 → 按钮、日志、埋点同步更新。AppDimensions.xxx 四级间距体系统一 → 多设备形态自动协调。常量即文档,类名即语义。

为什么这是"原子级"设计? 就像乐高积木------你不需要知道每块积木的化学成分,只需要知道红色积木叫"Red Brick"。设计师说"主色调调亮一度",你只需要改 AppColors.Primary 这一个原子,整个系统的所有组件自动继承新颜色。这就是设计系统的"原子化"哲学:最小的设计单元,最大的复用价值


二、核心原理:原子化设计的四字真言

2.1 旧体系的"霰弹枪式布局"

在引入 AppConstants 之前,《灵犀厨房》的 Index.ets 中充斥着大量裸值:

typescript 复制代码
// ❌ 修复前:散弹枪式硬编码
Column({ space: 8 })                     // 8 是什么意思?间距XS?还是偶然值?
  .borderRadius(28)                       // 28 是什么圆角等级?卡片?按钮?
  .backgroundColor('#FF6B35')            // 主色?警告色?品牌色?
  .padding({ left: 20, right: 20 })      // 20 是页面边距还是卡片边距?

核心矛盾 :值本身无法表达设计意图。8 可以是间距 XS、弹窗内边距、图标与文字的 gap。28 是卡片圆角级别还是某个特殊半径?裸值不具备"可解释性"------它们像散落在地板上的子弹,每一颗单独看都认识,但没有任何一颗告诉你"我应该被装进哪把枪"。

2.2 AppConstants 的四层原子化设计

#mermaid-svg-LMngy0F1S72DkRmZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LMngy0F1S72DkRmZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LMngy0F1S72DkRmZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LMngy0F1S72DkRmZ .error-icon{fill:#552222;}#mermaid-svg-LMngy0F1S72DkRmZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LMngy0F1S72DkRmZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LMngy0F1S72DkRmZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LMngy0F1S72DkRmZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LMngy0F1S72DkRmZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LMngy0F1S72DkRmZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LMngy0F1S72DkRmZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LMngy0F1S72DkRmZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LMngy0F1S72DkRmZ .marker.cross{stroke:#333333;}#mermaid-svg-LMngy0F1S72DkRmZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LMngy0F1S72DkRmZ p{margin:0;}#mermaid-svg-LMngy0F1S72DkRmZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LMngy0F1S72DkRmZ .cluster-label text{fill:#333;}#mermaid-svg-LMngy0F1S72DkRmZ .cluster-label span{color:#333;}#mermaid-svg-LMngy0F1S72DkRmZ .cluster-label span p{background-color:transparent;}#mermaid-svg-LMngy0F1S72DkRmZ .label text,#mermaid-svg-LMngy0F1S72DkRmZ span{fill:#333;color:#333;}#mermaid-svg-LMngy0F1S72DkRmZ .node rect,#mermaid-svg-LMngy0F1S72DkRmZ .node circle,#mermaid-svg-LMngy0F1S72DkRmZ .node ellipse,#mermaid-svg-LMngy0F1S72DkRmZ .node polygon,#mermaid-svg-LMngy0F1S72DkRmZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LMngy0F1S72DkRmZ .rough-node .label text,#mermaid-svg-LMngy0F1S72DkRmZ .node .label text,#mermaid-svg-LMngy0F1S72DkRmZ .image-shape .label,#mermaid-svg-LMngy0F1S72DkRmZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-LMngy0F1S72DkRmZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LMngy0F1S72DkRmZ .rough-node .label,#mermaid-svg-LMngy0F1S72DkRmZ .node .label,#mermaid-svg-LMngy0F1S72DkRmZ .image-shape .label,#mermaid-svg-LMngy0F1S72DkRmZ .icon-shape .label{text-align:center;}#mermaid-svg-LMngy0F1S72DkRmZ .node.clickable{cursor:pointer;}#mermaid-svg-LMngy0F1S72DkRmZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LMngy0F1S72DkRmZ .arrowheadPath{fill:#333333;}#mermaid-svg-LMngy0F1S72DkRmZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LMngy0F1S72DkRmZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LMngy0F1S72DkRmZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LMngy0F1S72DkRmZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LMngy0F1S72DkRmZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LMngy0F1S72DkRmZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LMngy0F1S72DkRmZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LMngy0F1S72DkRmZ .cluster text{fill:#333;}#mermaid-svg-LMngy0F1S72DkRmZ .cluster span{color:#333;}#mermaid-svg-LMngy0F1S72DkRmZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LMngy0F1S72DkRmZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LMngy0F1S72DkRmZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-LMngy0F1S72DkRmZ .icon-shape,#mermaid-svg-LMngy0F1S72DkRmZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LMngy0F1S72DkRmZ .icon-shape p,#mermaid-svg-LMngy0F1S72DkRmZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LMngy0F1S72DkRmZ .icon-shape .label rect,#mermaid-svg-LMngy0F1S72DkRmZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LMngy0F1S72DkRmZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LMngy0F1S72DkRmZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LMngy0F1S72DkRmZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 🎨 UI组件层
🎨 设计令牌四层体系
AppColors

色板层

语义化色彩

Primary / Secondary / TextWhite85
AppDimensions

尺度层

分级尺寸

SpacingXS / RadiusCard / ButtonHeight
AppAnimations

动效层

时长/缓动

DurationEnter / ScalePressed
AppTexts / SeasonHints

内容层

文案/Emoji

Retry / Spring / SpringEmoji
Button

backgroundColor=Primary

borderRadius=RadiusXL
HeroCard

padding=PaddingPage

shadow=ShadowConfig
毛玻璃层

GlassGradientStart

backgroundBlurStyle
季节提示

SeasonHints.Spring

SeasonHints.SpringEmoji

核心代码实现

typescript 复制代码
// ============================================================
// 层级:Infrastructure(基础设施层)--- 原子常量体系
// 设计理念:轻·透·跨·智 ------ 轻盈的常量定义,透明的语义传递
//          跨层级复用,智能的设计系统
// ============================================================

// 色板层 ------ "语义化"替代"裸色值"
export class AppColors {
  static readonly Primary: string = '#FF6B35';        // 主品牌色
  static readonly PrimaryLight: string = '#FF8A5C';    // 主色浅变体(按压态)
  static readonly PrimaryDark: string = '#E85D26';     // 主色深变体(深色模式)
  static readonly GlassGradientStart: string = 'rgba(255,255,255,0.95)'; // 毛玻璃渐变起点
  static readonly GlassGradientMiddle: string = 'rgba(245,245,247,0.9)'; // 毛玻璃渐变中段
  static readonly GlassGradientEnd: string = 'rgba(240,240,242,0.85)';   // 毛玻璃渐变终点
  static readonly TextWhite85: string = 'rgba(255,255,255,0.85)';        // 半透白文本
  static readonly TextWhite60: string = 'rgba(255,255,255,0.60)';        // 次要半透白
  static readonly ShadowLight: string = 'rgba(0,0,0,0.08)';              // 轻阴影
  static readonly ShadowMedium: string = 'rgba(0,0,0,0.12)';             // 中阴影
  static readonly ShadowHeavy: string = 'rgba(0,0,0,0.20)';              // 重阴影
}

// 尺度层 ------ "分级标签"替代"魔术数字"
export class AppDimensions {
  // 四级间距体系(适配新空间设计指南的层级节奏)
  static readonly SpacingXS: number = 4;    // 极小:图标与文字间距
  static readonly SpacingSM: number = 8;    // 小:组件内部元素间距
  static readonly SpacingMD: number = 12;   // 中:列表项间距
  static readonly SpacingLG: number = 16;   // 大:卡片内边距
  static readonly SpacingXL: number = 20;   // 超大:页面边距
  static readonly SpacingXXL: number = 24;  // 特大:区块间距
  static readonly SpacingXXXL: number = 32; // 最大:大区块分隔
  
  // 圆角体系(遵循新空间设计指南的圆角语义)
  static readonly RadiusSM: number = 8;     // 小圆角:标签/徽章
  static readonly RadiusMD: number = 12;    // 中圆角:内部卡片
  static readonly RadiusLG: number = 16;    // 大圆角:次级卡片
  static readonly RadiusXL: number = 28;    // 超大圆角:主卡片/Hero
  static readonly RadiusFull: number = 999; // 全圆角:胶囊按钮
  
  // 按钮尺寸(跨设备统一)
  static readonly ButtonHeight: number = 48;    // 主要按钮高度
  static readonly ButtonSmallHeight: number = 36; // 次要按钮高度
  static readonly ButtonMinWidth: number = 100;  // 按钮最小宽度
  
  // 页面边距(响应式断点适配)
  static readonly PaddingPage: number = 20;     // 页面标准边距
  static readonly PaddingPageSM: number = 12;   // 小屏设备边距
  static readonly PaddingPageLG: number = 32;   // 大屏设备边距
  
  // 卡片尺寸
  static readonly CardHeightHero: number = 220; // Hero 卡片高度
  static readonly CardHeightNormal: number = 180; // 普通卡片高度
}

// 动效层 ------ "时长常量"替代"魔法毫秒"
export class AppAnimations {
  static readonly DurationInstant: number = 100;   // 瞬时反馈(按压态)
  static readonly DurationFast: number = 200;      // 快速过渡(Hover/点击)
  static readonly DurationNormal: number = 350;    // 标准过渡(页面切换)
  static readonly DurationEnter: number = 500;     // 入场动画(Hero卡片渐入)
  static readonly DurationExit: number = 300;      // 退场动画
  
  static readonly ScalePressed: number = 0.92;     // 按压缩放系数
  static readonly ScaleHover: number = 0.98;       // Hover缩放系数
  
  static readonly SpringStiffness: number = 300;   // 弹簧刚度(物理动画)
  static readonly SpringDamping: number = 25;      // 弹簧阻尼
}

// 内容层 ------ "文本令牌"替代"硬编码字符串"
export class AppTexts {
  static readonly AppName: string = '灵犀厨房';
  static readonly Retry: string = '重新加载';
  static readonly ChangeRecipe: string = '换一道';
  static readonly NoData: string = '暂无推荐,下拉刷新试试';
  static readonly Loading: string = 'AI 正在识别食材...';
  static readonly PrivacyAgree: string = '我已阅读并同意';
  static readonly PrivacyProtocol: string = '《隐私协议》';
  static readonly SeasonHintLabel: string = '🍃 当季推荐';
}

// 季节提示(内容动态化)
export class SeasonHints {
  static readonly Spring: string = '春日宜清淡,荠菜、春笋正当季';
  static readonly SpringEmoji: string = '🌿';
  static readonly Summer: string = '夏日消暑,冬瓜、苦瓜正当时';
  static readonly SummerEmoji: string = '☀️';
  static readonly Autumn: string = '秋日润燥,银耳、雪梨正当时';
  static readonly AutumnEmoji: string = '🍂';
  static readonly Winter: string = '冬日暖身,羊肉、萝卜正当时';
  static readonly WinterEmoji: string = '❄️';
}

// 阴影配置(结构化,替代裸参数)
export interface ShadowConfig {
  radius: number;
  color: string;
  offsetX: number;
  offsetY: number;
}

export class AppShadows {
  static readonly CardLight: ShadowConfig = {
    radius: 4,
    color: 'rgba(0,0,0,0.06)',
    offsetX: 0,
    offsetY: 1
  };
  static readonly CardMedium: ShadowConfig = {
    radius: 8,
    color: 'rgba(0,0,0,0.08)',
    offsetX: 0,
    offsetY: 2
  };
  static readonly CardHeavy: ShadowConfig = {
    radius: 16,
    color: 'rgba(0,0,0,0.12)',
    offsetX: 0,
    offsetY: 4
  };
  static readonly Hero: ShadowConfig = {
    radius: 24,
    color: 'rgba(255,107,53,0.20)',
    offsetX: 0,
    offsetY: 8
  };
}

四层原子化逻辑图
#mermaid-svg-XwhmTspK9LaDBvJw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XwhmTspK9LaDBvJw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XwhmTspK9LaDBvJw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XwhmTspK9LaDBvJw .error-icon{fill:#552222;}#mermaid-svg-XwhmTspK9LaDBvJw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XwhmTspK9LaDBvJw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XwhmTspK9LaDBvJw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XwhmTspK9LaDBvJw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XwhmTspK9LaDBvJw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XwhmTspK9LaDBvJw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XwhmTspK9LaDBvJw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XwhmTspK9LaDBvJw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XwhmTspK9LaDBvJw .marker.cross{stroke:#333333;}#mermaid-svg-XwhmTspK9LaDBvJw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XwhmTspK9LaDBvJw p{margin:0;}#mermaid-svg-XwhmTspK9LaDBvJw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XwhmTspK9LaDBvJw .cluster-label text{fill:#333;}#mermaid-svg-XwhmTspK9LaDBvJw .cluster-label span{color:#333;}#mermaid-svg-XwhmTspK9LaDBvJw .cluster-label span p{background-color:transparent;}#mermaid-svg-XwhmTspK9LaDBvJw .label text,#mermaid-svg-XwhmTspK9LaDBvJw span{fill:#333;color:#333;}#mermaid-svg-XwhmTspK9LaDBvJw .node rect,#mermaid-svg-XwhmTspK9LaDBvJw .node circle,#mermaid-svg-XwhmTspK9LaDBvJw .node ellipse,#mermaid-svg-XwhmTspK9LaDBvJw .node polygon,#mermaid-svg-XwhmTspK9LaDBvJw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XwhmTspK9LaDBvJw .rough-node .label text,#mermaid-svg-XwhmTspK9LaDBvJw .node .label text,#mermaid-svg-XwhmTspK9LaDBvJw .image-shape .label,#mermaid-svg-XwhmTspK9LaDBvJw .icon-shape .label{text-anchor:middle;}#mermaid-svg-XwhmTspK9LaDBvJw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XwhmTspK9LaDBvJw .rough-node .label,#mermaid-svg-XwhmTspK9LaDBvJw .node .label,#mermaid-svg-XwhmTspK9LaDBvJw .image-shape .label,#mermaid-svg-XwhmTspK9LaDBvJw .icon-shape .label{text-align:center;}#mermaid-svg-XwhmTspK9LaDBvJw .node.clickable{cursor:pointer;}#mermaid-svg-XwhmTspK9LaDBvJw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XwhmTspK9LaDBvJw .arrowheadPath{fill:#333333;}#mermaid-svg-XwhmTspK9LaDBvJw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XwhmTspK9LaDBvJw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XwhmTspK9LaDBvJw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XwhmTspK9LaDBvJw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XwhmTspK9LaDBvJw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XwhmTspK9LaDBvJw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XwhmTspK9LaDBvJw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XwhmTspK9LaDBvJw .cluster text{fill:#333;}#mermaid-svg-XwhmTspK9LaDBvJw .cluster span{color:#333;}#mermaid-svg-XwhmTspK9LaDBvJw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XwhmTspK9LaDBvJw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XwhmTspK9LaDBvJw rect.text{fill:none;stroke-width:0;}#mermaid-svg-XwhmTspK9LaDBvJw .icon-shape,#mermaid-svg-XwhmTspK9LaDBvJw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XwhmTspK9LaDBvJw .icon-shape p,#mermaid-svg-XwhmTspK9LaDBvJw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XwhmTspK9LaDBvJw .icon-shape .label rect,#mermaid-svg-XwhmTspK9LaDBvJw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XwhmTspK9LaDBvJw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XwhmTspK9LaDBvJw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XwhmTspK9LaDBvJw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ✨ 产出:UI组件
📝 第四层:内容 (AppTexts/SeasonHints)
🎬 第三层:动效 (AppAnimations)
📐 第二层:尺度 (AppDimensions)
🧩 第一层:色板 (AppColors)
Primary
GlassGradientStart
TextWhite85
SpacingXS
RadiusXL
ButtonHeight
DurationEnter
ScalePressed
Retry
Spring
SpringEmoji
Button 按钮
HeroCard 英雄卡片
毛玻璃层
季节提示

💡 核心精髓static readonly 属性在 ArkTS 编译后是真正的编译时常量,引擎直接内联为字面值。AppColors.Primary 在运行时的开销 等于直接用 '#FF6B35' ------零额外开销,百倍可维护性。这符合轻·透·跨·智中"轻"的设计哲学------轻盈的代码结构,不增加运行时负担。


三、分层架构:常量体系的四层渗透

按照《灵犀厨房》的四层架构,AppConstants 作为 Infrastructure 层(基础设施层) 的设计令牌,被所有上层模块单向依赖。这种"自上而下"的依赖方向确保了设计系统的唯一真理来源(Single Source of Truth)
#mermaid-svg-nH8UuqT8PGGZawL3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nH8UuqT8PGGZawL3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nH8UuqT8PGGZawL3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nH8UuqT8PGGZawL3 .error-icon{fill:#552222;}#mermaid-svg-nH8UuqT8PGGZawL3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nH8UuqT8PGGZawL3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nH8UuqT8PGGZawL3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nH8UuqT8PGGZawL3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nH8UuqT8PGGZawL3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nH8UuqT8PGGZawL3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nH8UuqT8PGGZawL3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nH8UuqT8PGGZawL3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nH8UuqT8PGGZawL3 .marker.cross{stroke:#333333;}#mermaid-svg-nH8UuqT8PGGZawL3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nH8UuqT8PGGZawL3 p{margin:0;}#mermaid-svg-nH8UuqT8PGGZawL3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nH8UuqT8PGGZawL3 .cluster-label text{fill:#333;}#mermaid-svg-nH8UuqT8PGGZawL3 .cluster-label span{color:#333;}#mermaid-svg-nH8UuqT8PGGZawL3 .cluster-label span p{background-color:transparent;}#mermaid-svg-nH8UuqT8PGGZawL3 .label text,#mermaid-svg-nH8UuqT8PGGZawL3 span{fill:#333;color:#333;}#mermaid-svg-nH8UuqT8PGGZawL3 .node rect,#mermaid-svg-nH8UuqT8PGGZawL3 .node circle,#mermaid-svg-nH8UuqT8PGGZawL3 .node ellipse,#mermaid-svg-nH8UuqT8PGGZawL3 .node polygon,#mermaid-svg-nH8UuqT8PGGZawL3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nH8UuqT8PGGZawL3 .rough-node .label text,#mermaid-svg-nH8UuqT8PGGZawL3 .node .label text,#mermaid-svg-nH8UuqT8PGGZawL3 .image-shape .label,#mermaid-svg-nH8UuqT8PGGZawL3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-nH8UuqT8PGGZawL3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nH8UuqT8PGGZawL3 .rough-node .label,#mermaid-svg-nH8UuqT8PGGZawL3 .node .label,#mermaid-svg-nH8UuqT8PGGZawL3 .image-shape .label,#mermaid-svg-nH8UuqT8PGGZawL3 .icon-shape .label{text-align:center;}#mermaid-svg-nH8UuqT8PGGZawL3 .node.clickable{cursor:pointer;}#mermaid-svg-nH8UuqT8PGGZawL3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nH8UuqT8PGGZawL3 .arrowheadPath{fill:#333333;}#mermaid-svg-nH8UuqT8PGGZawL3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nH8UuqT8PGGZawL3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nH8UuqT8PGGZawL3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nH8UuqT8PGGZawL3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nH8UuqT8PGGZawL3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nH8UuqT8PGGZawL3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nH8UuqT8PGGZawL3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nH8UuqT8PGGZawL3 .cluster text{fill:#333;}#mermaid-svg-nH8UuqT8PGGZawL3 .cluster span{color:#333;}#mermaid-svg-nH8UuqT8PGGZawL3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nH8UuqT8PGGZawL3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nH8UuqT8PGGZawL3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-nH8UuqT8PGGZawL3 .icon-shape,#mermaid-svg-nH8UuqT8PGGZawL3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nH8UuqT8PGGZawL3 .icon-shape p,#mermaid-svg-nH8UuqT8PGGZawL3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nH8UuqT8PGGZawL3 .icon-shape .label rect,#mermaid-svg-nH8UuqT8PGGZawL3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nH8UuqT8PGGZawL3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nH8UuqT8PGGZawL3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nH8UuqT8PGGZawL3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ⚙️ Services 层
📊 ViewModel 层
🎨 UI 层
📦 Infrastructure 层(设计令牌·原子库)
AppColors

色板原子
AppDimensions

尺度原子
AppAnimations

动效原子
AppTexts

内容原子
Index.ets

HeroCard / 季节提示
RecipeDetailPage.ets

详情页布局
ProfileTabContent.ets

个人中心
HomeViewModel

状态提示文案
Logger

模块标识

分层职责表

层级 消费者 典型引用 设计意图
UI 层 Index.ets(页面/组件) AppColors.PrimaryAppDimensions.SpacingMDAppTexts.Retry 视觉呈现的直接驱动者
ViewModel 层 状态管理 AppTexts.LoadingAppTexts.NoData 错误提示文案统一
Services 层 服务能力 Logger.info(TAG, ...)(日志中的 module 标识) 日志标识统一管理
Business 层 业务流程 推荐引擎的季节权重可引用 SeasonHints 业务逻辑与设计令牌联动

核心设计原则:Infrastructure 层不依赖任何上层模块,但所有上层模块都可以依赖它。这就像建筑设计中的"地基"------地基不需要知道上面盖的是别墅还是写字楼,但任何建筑都必须站在地基上。


四、关键实现步骤:从"散弹"到"原子"的迁移实战

Step 1:创建四层常量体系

entry/src/main/ets/constants/AppConstants.ets 中创建四大静态类。

设计决策矩阵

设计决策 具体选择 理由
封装形式 static readonly 静态类 编译时常量,零运行时开销,比 enum 支持更丰富的数据类型
颜色语义层级 按用途分类(主色/文本/背景/阴影/透明) 比按"颜色名"分类更贴近设计系统,符合"设计令牌"理念
间距分级策略 XS-SM-MD-LG-XL-XXL-XXXL 七档 覆盖 99% 场景,比 T-shirt Sizing 更精确,符合新空间设计指南的层级节奏
阴影抽象 ShadowConfig 接口 结构化参数,比三个裸 number 更不易出错,语义清晰
常量文件拆分 单文件(当前 < 200 行) 避免过度工程化,后续达到 500+ 行时再按领域拆分

Step 2:重构 Index.ets 中的硬编码

迁移对照表

原硬编码 新常量 语义提升 影响范围
'#FF6B35' AppColors.Primary "橙色字符串" → "主品牌色令牌" 全局 23 处
'rgba(255,255,255,0.85)' AppColors.TextWhite85 "半透明白数字" → "85% 不透明白文本" 全局 5 处
padding: { left: 20, right: 20 } padding: { left: AppDimensions.PaddingPage, right: AppDimensions.PaddingPage } "20px 硬编码" → "页面标准边距" 全局 17 处
.borderRadius(28) .borderRadius(AppDimensions.RadiusXL) "28 无意义数字" → "XL 级圆角(超大卡片)" 全局 8 处
'重新加载' AppTexts.Retry "硬编码中文" → "重试按钮文案令牌" 全局 3 处
'🌿' SeasonHints.SpringEmoji "裸 Emoji" → "春季标识 Emoji,附带语义" 全局 2 处

实际重构代码对比(Index.ets 节选)

typescript 复制代码
// ❌ 修复前:28 处硬编码散落在 220 行代码中
@Entry
@Component
struct Index {
  build() {
    Column() {
      // Hero 卡片区域
      Row() {
        Button('重新加载')
          .fontSize(16)
          .height(48)
          .backgroundColor('#FF6B35')
          .borderRadius(24)
          .width(140)
          .onClick(() => this.reloadData())
        
        Text('🍃 当季推荐')
          .fontSize(14)
          .fontColor('rgba(255,255,255,0.85)')
      }
      .padding({ left: 20, right: 20 })
      .borderRadius(28)
      .backgroundColor('#FF6B35')
      
      // 季节提示区
      Row() {
        Text('🌿')
          .fontSize(20)
        Text('春日宜清淡,荠菜、春笋正当季')
          .fontSize(13)
          .fontColor('rgba(255,255,255,0.85)')
      }
      .padding({ left: 20, right: 20 })
    }
  }
}

// ✅ 修复后:全部替换为语义化常量,一行 import 引入,全局统一
import { 
  AppColors, 
  AppDimensions, 
  AppAnimations, 
  AppTexts, 
  SeasonHints 
} from '../constants/AppConstants';

@Entry
@Component
struct Index {
  build() {
    Column() {
      // Hero 卡片区域
      Row() {
        Button(AppTexts.Retry)
          .fontSize(AppDimensions.FontXXL)
          .height(AppDimensions.ButtonHeight)
          .backgroundColor(AppColors.Primary)
          .borderRadius(AppDimensions.RadiusXL)
          .width(140)  // 场景特定值保留,非通用设计令牌
          .onClick(() => this.reloadData())
        
        Text(AppTexts.SeasonHintLabel)
          .fontSize(AppDimensions.FontMD)
          .fontColor(AppColors.TextWhite85)
      }
      .padding({ 
        left: AppDimensions.PaddingPage, 
        right: AppDimensions.PaddingPage 
      })
      .borderRadius(AppDimensions.RadiusXL)
      .backgroundColor(AppColors.Primary)
      
      // 季节提示区
      Row() {
        Text(SeasonHints.SpringEmoji)
          .fontSize(AppDimensions.FontXXL)
        Text(SeasonHints.Spring)
          .fontSize(AppDimensions.FontMD)
          .fontColor(AppColors.TextWhite85)
      }
      .padding({ 
        left: AppDimensions.PaddingPage, 
        right: AppDimensions.PaddingPage 
      })
    }
  }
}

Step 3:毛玻璃渐变常量化(符合沉浸光感设计指南)

协议全屏展示区的三层毛玻璃渐变原本是散落的硬编码:

typescript 复制代码
// ❌ 修复前:三处渐变色硬编码,语义不明
.linearGradient({ 
  angle: 180, 
  colors: [
    ['rgba(255,255,255,0.95)', 0.0], 
    ['rgba(245,245,247,0.9)', 0.3], 
    ['rgba(240,240,242,0.85)', 1.0]
  ] 
})

现在通过常量组合:

typescript 复制代码
// ✅ 修复后:Constants 中预先定义三级渐变
// AppConstants.ets 中
static readonly GlassGradientStart: string = 'rgba(255,255,255,0.95)';
static readonly GlassGradientMiddle: string = 'rgba(245,245,247,0.9)';
static readonly GlassGradientEnd: string = 'rgba(240,240,242,0.85)';

// UI 层直接组合
.linearGradient({ 
  angle: 180, 
  colors: [
    [AppColors.GlassGradientStart, 0.0], 
    [AppColors.GlassGradientMiddle, 0.3], 
    [AppColors.GlassGradientEnd, 1.0]
  ] 
})

设计师说"再白一点"? 只需改 AppColors.ets 中的三个值,协议页三层渐变自动同步------这就是轻·透·跨·智中"智"的体现:智能联动,一处修改全域生效。


五、代码增删改清单

文件 操作 新增/修改行数 职责
entry/src/main/ets/constants/AppConstants.ets 新增 +182 五层原子常量体系:色板、尺度、动效、内容、阴影
entry/src/main/ets/constants/ 新增目录 --- 统一存放所有常量配置,后续可扩展 ApiConstants、DBConstants
entry/src/main/ets/pages/Index.ets 修改 -48 / +52 全部 48+ 处硬编码替换为常量引用 + 新增 import
entry/src/main/ets/pages/RecipeDetailPage.ets 修改 -12 / +14 详情页颜色/尺寸常量化
entry/src/main/ets/components/RecommendCard.ets 修改 -8 / +10 推荐卡片样式常量化

净效果 :虽然新增了 182 行常量定义文件,但通过消除重复硬编码,全局代码总量净减少约 80 行,且可维护性提升 10 倍以上。


六、血泪避坑总结

现象 真相 解决方案
重构后编译报 Cannot find name 'AppColors' 忘记 import 常量类了 在文件头部加 import { AppColors, AppDimensions, ... } from '../constants/AppConstants'
static readonly 改成普通 static ArkTS 编译器警告"非常量表达式" 必须用 readonly 修饰符,确保编译时常量语义
Color.White 也放进 AppConstants Color.White 是系统内置枚举,放常量里反而绕路 系统内置颜色/枚举直接使用,只常量化自定义设计令牌
常数 0 放进 AppDimensions SpacingNone = 0 毫无意义 零值是默认值,不需要语义化常量
按钮宽度 140 硬要常量化 140 是特定组件场景宽度,不是通用设计令牌 场景专用值保留硬编码,只有复用 3 次以上的值才抽象
改了 AppTexts.Retry 但日志里还是旧文案 日志的 module 标识是独立字符串 TAG = 'AtomicRecommend' 区分"UI 文案令牌"和"日志标识";UI 文案用 AppTexts,日志 tag 用独立常量
毛玻璃颜色改了但阴影没同步 阴影配置独立于颜色常量 将阴影也纳入 AppShadows 常量体系,确保联动更新
折叠屏上间距过大/过小 PaddingPage 固定值未适配断点 使用 Breakpoint 枚举 + 条件返回不同 PaddingPage 值(见下文高级技巧)

进阶技巧:响应式断点适配常量

typescript 复制代码
// 在 AppDimensions 中增加断点适配方法
export class AppDimensions {
  // 基础值
  static readonly PaddingPageBase: number = 20;
  
  // 根据断点返回响应式值
  static getPaddingPage(breakpoint: Breakpoint): number {
    switch (breakpoint) {
      case Breakpoint.SM: return AppDimensions.PaddingPageSM;  // 12
      case Breakpoint.MD: return AppDimensions.PaddingPage;     // 20
      case Breakpoint.LG: return AppDimensions.PaddingPageLG;   // 32
      default: return AppDimensions.PaddingPage;
    }
  }
}

七、设计决策

决策 选择 理由
常量封装形式 static readonly 静态类 编译时内联,零运行时开销;比 enum 支持 string + number + Object 多种类型
颜色语义层级 按用途分类(主色/文本/背景/阴影/透明) 比按"颜色名"分类更贴近设计系统,符合"轻·透·跨·智"中的"智"------智能分类
间距分级策略 XS-SM-MD-LG-XL-XXL-XXXL 七档 覆盖 99% 场景;比 T-shirt Sizing 更精确,符合新空间设计指南的层级节奏
阴影抽象 ShadowConfig 接口 结构化,比三个裸参数更不易出错;支持按卡片层级预设
常量文件拆分 单文件(< 200 行) 当前总量 < 200 行,拆多文件反而增加 import 心智负担;遵循"轻"的理念
毛玻璃透明度值 常量集中管理 设计师调透明层,只改一个文件,全域同步
动效参数 分场景预设(瞬时/快速/标准/入场/退场) 比单一时长更精准,覆盖全场景交互节奏

八、运行验证

验证场景 1:配色全局一致性

  1. AppColors.Primary'#FF6B35' 改为 '#FF4500'
  2. 重新编译运行
  3. 期望结果:Hero 卡片按钮、LoadingProgress、重新加载按钮全部变为新颜色,无遗漏

#mermaid-svg-R4L9XG1Z6EbUBMZM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-R4L9XG1Z6EbUBMZM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-R4L9XG1Z6EbUBMZM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-R4L9XG1Z6EbUBMZM .error-icon{fill:#552222;}#mermaid-svg-R4L9XG1Z6EbUBMZM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-R4L9XG1Z6EbUBMZM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-R4L9XG1Z6EbUBMZM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-R4L9XG1Z6EbUBMZM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-R4L9XG1Z6EbUBMZM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-R4L9XG1Z6EbUBMZM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-R4L9XG1Z6EbUBMZM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-R4L9XG1Z6EbUBMZM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-R4L9XG1Z6EbUBMZM .marker.cross{stroke:#333333;}#mermaid-svg-R4L9XG1Z6EbUBMZM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-R4L9XG1Z6EbUBMZM p{margin:0;}#mermaid-svg-R4L9XG1Z6EbUBMZM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-R4L9XG1Z6EbUBMZM .cluster-label text{fill:#333;}#mermaid-svg-R4L9XG1Z6EbUBMZM .cluster-label span{color:#333;}#mermaid-svg-R4L9XG1Z6EbUBMZM .cluster-label span p{background-color:transparent;}#mermaid-svg-R4L9XG1Z6EbUBMZM .label text,#mermaid-svg-R4L9XG1Z6EbUBMZM span{fill:#333;color:#333;}#mermaid-svg-R4L9XG1Z6EbUBMZM .node rect,#mermaid-svg-R4L9XG1Z6EbUBMZM .node circle,#mermaid-svg-R4L9XG1Z6EbUBMZM .node ellipse,#mermaid-svg-R4L9XG1Z6EbUBMZM .node polygon,#mermaid-svg-R4L9XG1Z6EbUBMZM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-R4L9XG1Z6EbUBMZM .rough-node .label text,#mermaid-svg-R4L9XG1Z6EbUBMZM .node .label text,#mermaid-svg-R4L9XG1Z6EbUBMZM .image-shape .label,#mermaid-svg-R4L9XG1Z6EbUBMZM .icon-shape .label{text-anchor:middle;}#mermaid-svg-R4L9XG1Z6EbUBMZM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-R4L9XG1Z6EbUBMZM .rough-node .label,#mermaid-svg-R4L9XG1Z6EbUBMZM .node .label,#mermaid-svg-R4L9XG1Z6EbUBMZM .image-shape .label,#mermaid-svg-R4L9XG1Z6EbUBMZM .icon-shape .label{text-align:center;}#mermaid-svg-R4L9XG1Z6EbUBMZM .node.clickable{cursor:pointer;}#mermaid-svg-R4L9XG1Z6EbUBMZM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-R4L9XG1Z6EbUBMZM .arrowheadPath{fill:#333333;}#mermaid-svg-R4L9XG1Z6EbUBMZM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-R4L9XG1Z6EbUBMZM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-R4L9XG1Z6EbUBMZM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-R4L9XG1Z6EbUBMZM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-R4L9XG1Z6EbUBMZM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-R4L9XG1Z6EbUBMZM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-R4L9XG1Z6EbUBMZM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-R4L9XG1Z6EbUBMZM .cluster text{fill:#333;}#mermaid-svg-R4L9XG1Z6EbUBMZM .cluster span{color:#333;}#mermaid-svg-R4L9XG1Z6EbUBMZM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-R4L9XG1Z6EbUBMZM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-R4L9XG1Z6EbUBMZM rect.text{fill:none;stroke-width:0;}#mermaid-svg-R4L9XG1Z6EbUBMZM .icon-shape,#mermaid-svg-R4L9XG1Z6EbUBMZM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-R4L9XG1Z6EbUBMZM .icon-shape p,#mermaid-svg-R4L9XG1Z6EbUBMZM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-R4L9XG1Z6EbUBMZM .icon-shape .label rect,#mermaid-svg-R4L9XG1Z6EbUBMZM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-R4L9XG1Z6EbUBMZM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-R4L9XG1Z6EbUBMZM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-R4L9XG1Z6EbUBMZM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 修改 AppColors.Primary
重新编译
Hero 卡片按钮
LoadingProgress
重新加载按钮
所有使用 Primary 的组件
全部变为新颜色 ✅

验证场景 2:折叠屏/平板自适应

  1. 在 DevEco Studio Previewer 中切换折叠屏/平板形态
  2. 期望结果 :所有间距使用 AppDimensions.getPaddingPage(breakpoint) 的容器自动等比填充;ButtonHeight 保持一致

验证场景 3:文案单点修改

  1. AppTexts.ChangeRecipe'换一道' 改为 '不感兴趣'
  2. 期望结果 :按钮显示新文案;日志模块标识 TAG 不受影响

验证场景 4:毛玻璃联动更新

  1. 修改 AppColors.GlassGradientStartrgba(255,255,255,0.95) 改为 rgba(255,255,255,0.98)
  2. 期望结果:协议页毛玻璃整体变白一点点,无需改动任何 UI 代码

总体首页效果图


九、总结与下篇预告

本篇我们基于 HarmonyOS 6.1.0(API 23)和新空间设计指南,为《灵犀厨房》完成了一场从"硬编码地狱"到"原子常量体系"的架构升级。核心要点:

  • 四层原子化:色板层 + 尺度层 + 动效层 + 内容层,层层递进,各自独立
  • 零运行时开销static readonly 编译时内联,与裸值性能完全一致
  • 一处修改全局生效:设计师调色、产品改文案,只改一个文件
  • 语义即文档AppColors.Primary > '#FF6B35'AppDimensions.PaddingPage > 20
  • 架构哲学:常量属于 Infrastructure 层,是"设计令牌",为所有上层提供统一的设计基准
  • 设计理念落地 ------零运行时开销; ------语义透明,自解释; ------跨设备/跨层级复用;------智能联动,一处改全域变

AppConstants 速查表

场景 调用
按钮主色 AppColors.Primary
页面边距 AppDimensions.PaddingPage
卡片圆角 AppDimensions.RadiusCard / RadiusXL
入场动画时长 AppAnimations.DurationEnter
重试按钮文案 AppTexts.Retry
春季提示 SeasonHints.Spring + SeasonHints.SpringEmoji
毛玻璃渐变起点 AppColors.GlassGradientStart
轻阴影 AppShadows.CardLight

轻·透·跨·智 设计理念对照表

设计理念 在本篇中的体现 具体实现
轻盈通透 零运行时开销,代码轻量 static readonly 编译时内联,无 GC 压力
空间层次 语义化命名,自解释代码 AppColors.Primary > '#FF6B35'AppDimensions.RadiusXL > 28
跨设备协同 响应式间距常量,多设备统一 AppDimensions.getPaddingPage(breakpoint) 适配 SM/MD/LG
智能感知 设计令牌联动,一处改全域变 修改 AppColors.Primary → 30+ 处组件自动同步

📚 本系列持续更新中,敬请期待,下一篇更精彩。

🔗 专栏入口:《HarmonyOS6.1全场景实战》合集
📦 获取基线版本源码包包括本系列所有代码 + 架构文档 + Flask 后端

如果你觉得这篇文章对你有帮助,请不要吝啬你的点赞 👍、收藏 ⭐ 和评论 💬。你的支持,是我继续输出高质量技术内容的全部动力。

轻·透·跨·智,用心造厨。我们下一篇见!