在这里插入图片描述

本文是「食刻 (ShiKe)」技术系列第 4 篇,深入解析 Design Token 企业级设计系统 —— 28 个设计变量 + BaseCard 通用容器 + @Extend 装饰器的三层架构实践。


一、问题:硬编码样式为什么糟糕?

传统开发模式

// ❌ 每个页面都有这样的硬编码
Column() {
  Text('标题')
    .fontSize(16)
    .fontColor('#1A1A1A')      // 硬编码颜色
    .margin({ left: 16 })      // 硬编码间距
    .padding({ top: 12, bottom: 12 })
  
  Text('正文')
    .fontSize(14)
    .fontColor('#666666')      // 又一个硬编码颜色
}

硬编码的三大噩梦

  • 维护地狱:改主色调要搜遍整个项目,改一处漏三处
  • 不一致:A 页面间距 16,B 页面间距 14,UI 像拼凑的
  • 换肤困难:想做暗色模式?重写所有页面,工作量爆炸

食刻的解决方案

Design Token + BaseCard + @Extend = 三层设计系统


二、第一层:AppTheme — 28 个 Design Token

什么是 Design Token?

Design Token 是设计决策的原子化存储。它把颜色、间距、字号等设计值抽离成命名变量,让代码引用变量而非具体值。

❌ 硬编码:fontColor('#1A1A1A')
✅ Token:fontColor(AppTheme.COLOR_TEXT_PRIMARY)

AppTheme.ets 核心代码

// theme/AppTheme.ets
export class AppTheme {
  // ==================== 色彩 ====================
  
  // 主色系
  static readonly COLOR_PRIMARY = '#52B788';        // 草绿色(健康/积极)
  static readonly COLOR_PRIMARY_DARK = '#40916C';    // 深绿
  static readonly COLOR_PRIMARY_LIGHT = '#95D5B2';  // 浅绿
  
  // 功能色
  static readonly COLOR_SUCCESS = '#52B788';          // 成功
  static readonly COLOR_WARNING = '#F4A261';          // 警告/超标
  static readonly COLOR_DANGER = '#E07B5A';            // 危险/超量
  static readonly COLOR_INFO = '#457B9D';             // 信息
  
  // 文本色
  static readonly COLOR_TEXT_PRIMARY = '#1A1A1A';      // 主文本
  static readonly COLOR_TEXT_SECONDARY = '#666666';  // 次要文本
  static readonly COLOR_TEXT_TERTIARY = '#999999';    // 占位文本
  static readonly COLOR_TEXT_INVERSE = '#FFFFFF';     // 反色文本
  
  // 背景色
  static readonly COLOR_BG_PAGE = '#F5F6F8';          // 页面背景
  static readonly COLOR_BG_CARD = '#FFFFFF';          // 卡片背景
  static readonly COLOR_BG_NAVBAR = 'rgba(255,255,255,0.85)'; // 导航栏
  
  // 边框色
  static readonly COLOR_BORDER = '#E8E8E8';
  static readonly COLOR_BORDER_LIGHT = '#F0F0F0';
  
  // ==================== 间距 ====================
  
  static readonly SPACING_XS = 4;
  static readonly SPACING_SM = 8;
  static readonly SPACING_MD = 12;
  static readonly SPACING_LG = 16;
  static readonly SPACING_XL = 24;
  static readonly SPACING_XXL = 32;
  
  // 页面水平边距
  static readonly PAGE_HORIZONTAL = 16;
  
  // ==================== 圆角 ====================
  
  static readonly RADIUS_SM = 8;
  static readonly RADIUS_MD = 12;
  static readonly RADIUS_LG = 16;
  static readonly RADIUS_XL = 24;
  static readonly RADIUS_FULL = 9999;  // 全圆角(胶囊按钮)
  
  // ==================== 阴影 ====================
  
  static readonly SHADOW_CARD = '0 2px 8px rgba(0,0,0,0.06)';
  static readonly SHADOW_POPUP = '0 4px 16px rgba(0,0,0,0.1)';
  static readonly SHADOW_BUTTON = '0 2px 4px rgba(82,183,136,0.3)';
  
  // ==================== 字号 ====================
  
  static readonly FONT_SIZE_XS = 10;
  static readonly FONT_SIZE_SM = 12;
  static readonly FONT_SIZE_BASE = 14;
  static readonly FONT_SIZE_LG = 16;
  static readonly FONT_SIZE_XL = 18;
  static readonly FONT_SIZE_XXL = 24;
  static readonly FONT_SIZE_XXXL = 32;
  
  // ==================== 字重 ====================
  
  static readonly FONT_WEIGHT_NORMAL = FontWeight.Normal;
  static readonly FONT_WEIGHT_MEDIUM = FontWeight.Medium;
  static readonly FONT_WEIGHT_BOLD = FontWeight.Bold;
}

Token 命名规范

我们遵循 W3C Design Token 社区规范

{category}-{variant}-{state}
  ↓
COLOR_TEXT_PRIMARY
     ↓
语义化命名 > 视觉化命名

❌ COLOR_GREEN (#52B788)
✅ COLOR_PRIMARY (主色)

为什么?因为"绿色"是视觉描述,"主色"是语义含义。
改主题时,只需改 Token 值,语义不变,代码不动。

三、第二层:BaseCard — @BuilderParam 通用容器

问题

每个页面都要重复写卡片样式:

// ❌ 重复
Column() {
  Text('标题')
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(12)
.padding(16)
.shadow('0 2px 8px rgba(0,0,0,0.06)')

解法:BaseCard 组件

// theme/BaseCard.ets
@Component
export struct BaseCard {
  // ★ @BuilderParam:内容注入插槽 ★
  @BuilderParam content: () => void = () => {};
  
  padding: number = AppTheme.SPACING_LG;
  
  build() {
    Column() {
      // ★ 注入的内容 ★
      this.content();
    }
    .width('100%')
    .backgroundColor(AppTheme.COLOR_BG_CARD)
    .borderRadius(AppTheme.RADIUS_MD)
    .padding(this.padding)
    .shadow(AppTheme.SHADOW_CARD)
  }
}

使用方式

// ❌ 之前:重复样式
Column() {
  Text('标题')
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(12)
.padding(16)
.shadow('0 2px 8px rgba(0,0,0,0.06)')

// ✅ 现在:简洁复用
BaseCard() {
  Text('标题')
}

// ✅ 带参数:自定义内边距
BaseCard({ padding: 20 }) {
  Column() {
    Text('更多内边距的卡片')
  }
}

BaseCard 带来的改变

维度 之前 之后
代码行数 8 行 2 行
样式一致性 人为保证 自动保证
新增页面 复制粘贴改改改 5 分钟搞定
改全局圆角 搜 50 处 改 1 处

四、第三层:GlobalStyles — @Extend 排版装饰器

问题

相同类型的文本样式也要重复:

// ❌ 每个页面都要这样
Text('模块标题')
  .fontSize(16)
  .fontWeight(FontWeight.Bold)
  .fontColor('#1A1A1A')

Text('次要说明')
  .fontSize(12)
  .fontColor('#666666')

解法:@Extend 装饰器

// theme/GlobalStyles.ets

// 模块标题:16px 加粗主色
@Extend(Text) function moduleTitle() {
  .fontSize(AppTheme.FONT_SIZE_LG)
  .fontWeight(AppTheme.FONT_WEIGHT_BOLD)
  .fontColor(AppTheme.COLOR_TEXT_PRIMARY)
}

// 次要文本:12px 灰色
@Extend(Text) function mutedText() {
  .fontSize(AppTheme.FONT_SIZE_SM)
  .fontColor(AppTheme.COLOR_TEXT_SECONDARY)
}

// 主要按钮样式
@Extend(Button) function primaryButton() {
  .fontSize(AppTheme.FONT_SIZE_BASE)
  .fontWeight(AppTheme.FONT_WEIGHT_MEDIUM)
  .fontColor(AppTheme.COLOR_TEXT_INVERSE)
  .backgroundColor(AppTheme.COLOR_PRIMARY)
  .borderRadius(AppTheme.RADIUS_FULL)
  .shadow(AppTheme.SHADOW_BUTTON)
}

// 次要按钮样式
@Extend(Button) function secondaryButton() {
  .fontSize(AppTheme.FONT_SIZE_BASE)
  .fontWeight(AppTheme.FONT_WEIGHT_MEDIUM)
  .fontColor(AppTheme.COLOR_PRIMARY)
  .backgroundColor(Color.Transparent)
  .border({
    width: 1,
    color: AppTheme.COLOR_PRIMARY,
    radius: AppTheme.RADIUS_FULL
  })
}

使用方式

// 之前
Text('模块标题')
  .fontSize(16)
  .fontWeight(FontWeight.Bold)
  .fontColor('#1A1A1A')

// 现在
Text('模块标题')
  .moduleTitle()

// 之前
Button('确认')
  .fontSize(14)
  .fontWeight(FontWeight.Medium)
  .fontColor('#FFFFFF')
  .backgroundColor('#52B788')

// 现在
Button('确认')
  .primaryButton()

五、三层系统协作示例

完整页面代码

// pages/StatisticsPage.ets
@Entry
@Component
struct StatisticsPage {
  @State monthlyData: MonthlyStats = new MonthlyStats();
  
  aboutToAppear(): void {
    this.monthlyData = HeatmapViewModel.loadMonthData();
  }
  
  build() {
    Column() {
      // ★ 第1层:BaseCard 通用容器 ★
      BaseCard() {
        Column({ space: AppTheme.SPACING_MD }) {
          // ★ 第2层:AppTheme Token 配色 ★
          Text('月度热量统计')
            .moduleTitle()  // ★ 第3层:GlobalStyles 排版 ★
          
          // 热力图组件
          HeatmapGrid({ data: this.monthlyData.heatmapData })
          
          // 统计数据
          Row() {
            StatItem({ label: '平均摄入', value: '1650', unit: 'kcal' })
            StatItem({ label: '最高摄入', value: '2300', unit: 'kcal' })
            StatItem({ label: '记录天数', value: '28', unit: '天' })
          }
          .justifyContent(FlexAlign.SpaceEvenly)
          .width('100%')
        }
      }
      
      // 第二个卡片
      BaseCard({ padding: AppTheme.SPACING_XL }) {
        Column() {
          Text('营养分布')
            .moduleTitle()
          
          NutrientRings({ data: this.monthlyData.nutrients })
        }
      }
    }
    .padding({
      left: AppTheme.PAGE_HORIZONTAL,
      right: AppTheme.PAGE_HORIZONTAL,
      top: AppTheme.SPACING_LG
    })
    .backgroundColor(AppTheme.COLOR_BG_PAGE)
  }
}

代码行数对比

维度 硬编码方式 三层系统
单个页面行数 ~150 行 ~80 行
颜色引用 10+ 处硬编码 0 处
新增页面时间 30 分钟 5 分钟
全局改主题 不可能 改 28 个 Token

六、暗色模式支持

有了 Design Token,暗色模式只需改 Token 值

// theme/AppTheme.ets

// 亮色主题
export class AppThemeLight {
  static readonly COLOR_BG_PAGE = '#F5F6F8';
  static readonly COLOR_BG_CARD = '#FFFFFF';
  static readonly COLOR_TEXT_PRIMARY = '#1A1A1A';
}

// 暗色主题
export class AppThemeDark {
  static readonly COLOR_BG_PAGE = '#121212';
  static readonly COLOR_BG_CARD = '#1E1E1E';
  static readonly COLOR_TEXT_PRIMARY = '#FFFFFF';
}

// 根据系统设置切换
@State currentTheme: typeof AppThemeLight = AppThemeLight;

// 页面中使用
Column() {
  Text('标题')
    .fontColor(this.currentTheme.COLOR_TEXT_PRIMARY)
    .backgroundColor(this.currentTheme.COLOR_BG_CARD)
}

七、总结

三层设计系统核心价值

  • Token 层:设计决策原子存储,28 个变量在 AppTheme.ets
  • BaseCard 层@BuilderParam 通用容器,1 个组件复用所有卡片
  • @Extend 层:排版样式复用,4 个装饰器统一文本/按钮样式

效果

  • 新增页面:5 分钟(继承 + 写业务)
  • 全局改主题:改 28 个 Token
  • 样式一致性:代码级别保证
  • 零硬编码:100% Token 引用

系列总结

「食刻 (ShiKe)」技术系列完结

技术系列目录

  • 第 1 篇 | AI 双引擎架构 | 豆包 + DeepSeek 协同方案
  • 第 2 篇 | Neumorphism 环形图 | 7 层 ArkUI 组件叠加
  • 第 3 篇 | 全场景 Widget | 跨进程数据同步 + 一键直达
  • 第 4 篇 | Design Token 系统 | 28 Token + BaseCard + @Extend

项目地址:https://atomgit.com/VON-/cxs-demo1


📌 项目仓库:https://atomgit.com/VON-/cxs-demo1

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐