面向设计系统的 AI 迭代:组件一致性审查与设计 Token 自动化管理

AI1周前发布 beixibaobao
12 0 0

面向设计系统的 AI 迭代:组件一致性审查与设计 Token 自动化管理

设计系统在规模化团队中的最大挑战不是"如何创建",而是"如何保持"。随着产品迭代,组件变体不断膨胀,设计 Token 的定义与使用逐渐偏离规范——设计师看到的 Figma 文件与开发者实现的组件之间存在越来越大的鸿沟。AI 的能力恰好可以填补这道鸿沟:通过自动化的一致性和 Token 审查,将"设计规范"从文档变成可执行约束。

一、设计系统迭代的典型痛点

在一个 20+ 人、3 条产品线的团队中,设计系统的维护面临以下核心问题:

  1. Token 漂移:设计师在 Figma 中新增颜色但未同步到代码 Token。
  2. 组件变异:同一组件在不同页面中因"临时需求"而产生行为不一致。
  3. 审查成本:人工审查一个 PR 中的设计合规性需要 15—30 分钟,且高度依赖审查者的经验。
  4. 版本断裂:设计稿更新后,前端代码版本滞后 2—4 周。

二、设计 Token 的自动化管理

2.1 Token 的数据模型

将设计 Token 作为单一数据源,从 Figma 导出并自动同步到代码:

/**
 * 设计 Token 的标准数据结构
 * 遵循 W3C Design Tokens Community Group 规范
 */
interface DesignToken {
  /** Token 名称,遵循 kebab-case 命名 */
  name: string;
  /** Token 值 */
  value: string | number;
  /** Token 类型 */
  type: 'color' | 'spacing' | 'typography' | 'borderRadius' | 'shadow' | 'opacity';
  /** 语义分组 */
  group: string;
  /** 描述 */
  description?: string;
  /** Figma 中的引用路径(可追溯来源) */
  figmaRef?: {
    fileId: string;
    nodeId: string;
    updatedAt: string;
  };
  /** 是否已废弃 */
  deprecated?: boolean;
}
/**
 * Token 集合的完整定义
 */
interface TokenCollection {
  version: string;
  updatedAt: string;
  tokens: DesignToken[];
}

2.2 Token 同步管线

2.3 Token 同步引擎实现

/**
 * Token 同步引擎
 * 从 Figma 提取 Token 并与代码仓库中的 Token 对比
 */
interface SyncReport {
  added: DesignToken[];
  modified: DesignToken[];
  deprecated: DesignToken[];
  unchanged: number;
  conflicts: Array<{ token: DesignToken; reason: string }>;
}
class TokenSyncEngine {
  private figmaToken: string;
  private repoTokenPath: string;
  constructor(figmaToken: string, repoTokenPath: string) {
    this.figmaToken = figmaToken;
    this.repoTokenPath = repoTokenPath;
  }
  /**
   * 执行完整的同步流程
   */
  async sync(figmaFileId: string): Promise<SyncReport> {
    // 1. 从 Figma 提取 Token
    const figmaTokens = await this.extractFromFigma(figmaFileId);
    // 2. 读取代码仓库中的当前 Token
    const repoTokens = await this.readRepoTokens();
    // 3. 执行对比
    const report = this.compareTokens(figmaTokens, repoTokens);
    // 4. 验证 Token 命名规范
    const namingViolations = this.validateNamingConvention(figmaTokens);
    if (namingViolations.length > 0) {
      report.conflicts.push(
        ...namingViolations.map(v => ({
          token: v,
          reason: `命名不符合规范: ${v.name}`,
        }))
      );
    }
    return report;
  }
  /**
   * 从 Figma API 提取 Token
   */
  private async extractFromFigma(fileId: string): Promise<DesignToken[]> {
    try {
      const response = await fetch(
        `https://api.figma.com/v1/files/${fileId}`,
        { headers: { 'X-Figma-Token': this.figmaToken } }
      );
      if (!response.ok) {
        throw new Error(`Figma API 请求失败: ${response.status}`);
      }
      const data = await response.json();
      return this.parseFigmaTokens(data);
    } catch (error) {
      console.error('[TokenSync] Figma Token 提取失败', error);
      throw error;
    }
  }
  /**
   * 解析 Figma 响应中的 Token
   */
  private parseFigmaTokens(figmaData: any): DesignToken[] {
    const tokens: DesignToken[] = [];
    // 实际实现需要递归遍历 Figma 的节点树
    // 查找带有 "token/" 前缀的样式和变量
    const styles = figmaData.styles || {};
    for (const [id, style] of Object.entries(styles) as [string, any][]) {
      // 只提取命名符合 Token 规范的样式
      if (style.name.startsWith('token/')) {
        tokens.push({
          name: style.name.replace('token/', ''),
          value: style.description || '', // Figma 样式的 value 需要从 fill/stroke 中解析
          type: this.inferTokenType(style),
          group: this.extractGroup(style.name),
          description: style.description,
          figmaRef: {
            fileId: figmaData.documentId,
            nodeId: id,
            updatedAt: style.updatedAt || new Date().toISOString(),
          },
        });
      }
    }
    return tokens;
  }
  /**
   * 对比 Figma Token 与仓库 Token
   */
  private compareTokens(figmaTokens: DesignToken[], repoTokens: DesignToken[]): SyncReport {
    const report: SyncReport = {
      added: [],
      modified: [],
      deprecated: [],
      unchanged: 0,
      conflicts: [],
    };
    const repoMap = new Map(repoTokens.map(t => [t.name, t]));
    const figmaMap = new Map(figmaTokens.map(t => [t.name, t]));
    // 检测新增和修改
    for (const figmaToken of figmaTokens) {
      const repoToken = repoMap.get(figmaToken.name);
      if (!repoToken) {
        report.added.push(figmaToken);
      } else if (repoToken.value !== figmaToken.value) {
        report.modified.push(figmaToken);
      } else {
        report.unchanged++;
      }
    }
    // 检测废弃:在仓库中存在但 Figma 中已删除的 Token
    for (const repoToken of repoTokens) {
      if (!figmaMap.has(repoToken.name) && !repoToken.deprecated) {
        report.deprecated.push({ ...repoToken, deprecated: true });
      }
    }
    return report;
  }
  /**
   * 验证 Token 命名是否遵循规范
   * 规范:{group}-{property}-{variant}-{state}
   */
  private validateNamingConvention(tokens: DesignToken[]): DesignToken[] {
    const pattern = /^[a-z]+(-[a-z0-9]+){2,4}$/;
    return tokens.filter(t => !pattern.test(t.name));
  }
  private inferTokenType(style: any): DesignToken['type'] {
    if (style.styleType === 'FILL') return 'color';
    if (style.styleType === 'TEXT') return 'typography';
    return 'color';
  }
  private extractGroup(name: string): string {
    return name.split('/')[0] || 'global';
  }
  private async readRepoTokens(): Promise<DesignToken[]> {
    // 实际实现中从文件系统读取 JSON/YAML 格式的 Token 文件
    return [];
  }
}

三、AI 驱动的组件一致性审查

3.1 审查维度

组件一致性审查覆盖三个维度:视觉一致性(颜色、间距、圆角是否匹配 Token)、行为一致性(交互状态是否完整)、语义一致性(组件含义是否与设计定义一致)。

/**
 * 组件一致性的审查规则
 */
interface ConsistencyRule {
  id: string;
  name: string;
  description: string;
  /** 检查逻辑 */
  check: (component: ComponentInfo, tokens: DesignToken[]) => ConsistencyViolation[];
  /** 严重级别 */
  severity: 'error' | 'warning' | 'info';
}
interface ComponentInfo {
  name: string;
  filePath: string;
  /** 使用的 CSS 属性 */
  styles: Record<string, string>;
  /** 组件的交互状态列表 */
  states: string[];
  /** 相关联的 Token 名称 */
  usedTokens: string[];
}
interface ConsistencyViolation {
  ruleId: string;
  component: string;
  filePath: string;
  message: string;
  severity: 'error' | 'warning' | 'info';
  /** 修复建议 */
  suggestion: string;
  /** 涉及的 Token */
  affectedToken?: string;
  /** 当前值 */
  currentValue?: string;
  /** 期望值 */
  expectedValue?: string;
}

3.2 审查引擎

/**
 * AI 驱动的组件一致性审查引擎
 */
class ConsistencyAuditor {
  private rules: ConsistencyRule[] = [];
  private tokens: DesignToken[] = [];
  private llmEndpoint: string;
  constructor(llmEndpoint: string = '/api/ai/consistency-audit') {
    this.llmEndpoint = llmEndpoint;
    this.registerDefaultRules();
  }
  /**
   * 对组件列表执行全面审查
   */
  async audit(components: ComponentInfo[]): Promise<ConsistencyViolation[]> {
    const violations: ConsistencyViolation[] = [];
    // 1. 基于规则的静态检查
    for (const component of components) {
      for (const rule of this.rules) {
        try {
          const result = rule.check(component, this.tokens);
          violations.push(...result);
        } catch (error) {
          console.error(`[Auditor] 规则 ${rule.id} 执行异常`, error);
        }
      }
    }
    // 2. AI 辅助的语义一致性检查
    try {
      const aiViolations = await this.aiConsistencyCheck(components);
      violations.push(...aiViolations);
    } catch (error) {
      console.error('[Auditor] AI 审查失败,仅返回规则检查结果', error);
    }
    return violations;
  }
  /**
   * 注册默认审查规则
   */
  private registerDefaultRules(): void {
    // 规则:检查颜色值是否来自 Token 而非硬编码
    this.rules.push({
      id: 'color-hardcoded',
      name: '颜色硬编码检测',
      description: '检测组件中是否使用了未定义 Token 的颜色值',
      severity: 'error',
      check: (component, tokens) => {
        const violations: ConsistencyViolation[] = [];
        const tokenColors = new Set(tokens.filter(t => t.type === 'color').map(t => t.value));
        // 检查 CSS 中的颜色值
        const colorProps = ['color', 'backgroundColor', 'borderColor', 'boxShadow'];
        for (const prop of colorProps) {
          const value = component.styles[prop];
          if (value && !value.startsWith('var(--') && !tokenColors.has(value)) {
            violations.push({
              ruleId: 'color-hardcoded',
              component: component.name,
              filePath: component.filePath,
              message: `${component.name} 的 ${prop} 使用了硬编码颜色值 "${value}"`,
              severity: 'error',
              suggestion: `使用对应的 CSS 变量替代硬编码颜色,或新增 Token 后引用`,
              currentValue: value,
            });
          }
        }
        return violations;
      },
    });
    // 规则:检查交互状态完整性
    this.rules.push({
      id: 'interaction-states',
      name: '交互状态完整性检查',
      description: '检查交互组件是否包含必要的状态(hover、active、focus、disabled)',
      severity: 'warning',
      check: (component) => {
        const violations: ConsistencyViolation[] = [];
        const requiredStates = ['hover', 'active', 'focus', 'disabled'];
        // 仅对交互类组件检查
        const interactiveComponents = ['Button', 'Input', 'Select', 'Checkbox', 'Radio', 'Link'];
        if (!interactiveComponents.some(name => component.name.includes(name))) {
          return violations;
        }
        for (const state of requiredStates) {
          if (!component.states.includes(state)) {
            violations.push({
              ruleId: 'interaction-states',
              component: component.name,
              filePath: component.filePath,
              message: `${component.name} 缺少 "${state}" 交互状态`,
              severity: 'warning',
              suggestion: `为组件添加 ${state} 状态的样式定义`,
            });
          }
        }
        return violations;
      },
    });
    // 规则:检查是否使用了已废弃的 Token
    this.rules.push({
      id: 'deprecated-token',
      name: '废弃 Token 引用检测',
      description: '检测组件是否引用了标记为 deprecated 的 Token',
      severity: 'error',
      check: (component, tokens) => {
        const violations: ConsistencyViolation[] = [];
        const deprecatedTokens = tokens.filter(t => t.deprecated);
        for (const token of deprecatedTokens) {
          if (component.usedTokens.includes(token.name)) {
            violations.push({
              ruleId: 'deprecated-token',
              component: component.name,
              filePath: component.filePath,
              message: `${component.name} 引用了已废弃的 Token "${token.name}"`,
              severity: 'error',
              suggestion: '迁移至替代 Token 或联系设计团队确认',
              affectedToken: token.name,
            });
          }
        }
        return violations;
      },
    });
  }
  /**
   * AI 语义一致性检查
   * 使用 LLM 对比组件的实际渲染效果与设计定义的语义
   */
  private async aiConsistencyCheck(components: ComponentInfo[]): Promise<ConsistencyViolation[]> {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 15000);
    try {
      const response = await fetch(this.llmEndpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          messages: [{
            role: 'user',
            content: JSON.stringify({
              task: 'component_consistency_audit',
              components: components.map(c => ({
                name: c.name,
                states: c.states,
                tokens: c.usedTokens,
              })),
              instructions: [
                '检查组件命名是否与设计系统规范一致',
                '识别可能存在语义混淆的组件(如不同命名但功能相同的组件)',
                '发现 Token 使用不当的情况(如使用 spacing token 作为颜色值)',
              ],
            }),
          }],
          temperature: 0.1,
          response_format: { type: 'json_object' },
        }),
        signal: controller.signal,
      });
      if (!response.ok) {
        throw new Error(`AI 审查服务异常: ${response.status}`);
      }
      const data = await response.json();
      return (data.violations || []) as ConsistencyViolation[];
    } finally {
      clearTimeout(timeoutId);
    }
  }
}

四、CI/CD 集成:将审查自动化

将一致性审查作为 CI 流水线的一个门禁步骤:

# .github/workflows/design-consistency.yml
name: 设计系统一致性审查
on:
  pull_request:
    paths:
      - 'src/components/**'
      - 'src/tokens/**'
      - 'src/styles/**'
jobs:
  consistency-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: 安装依赖
        run: npm ci
      - name: 运行一致性审查
        run: npx design-audit --strict
        env:
          FIGMA_TOKEN: ${{ secrets.FIGMA_TOKEN }}
          AI_AUDIT_ENDPOINT: ${{ secrets.AI_AUDIT_ENDPOINT }}
      - name: 生成审查报告
        if: always()
        run: npx design-audit --report --output design-audit-report.json
      - name: PR 评论中展示报告
        if: always()
        uses: actions/github-script@v7
        with:
          script: |
            const report = require('./design-audit-report.json');
            const body = generateReportComment(report);
            github.rest.issues.createComment({
              ...context.repo,
              issue_number: context.issue.number,
              body
            });

五、效果度量

一致性审查的价值需要量化:

建议将一致性得分作为团队技术看板的固定指标,按周跟踪趋势。当得分连续两周下降时,触发设计系统专项治理。

总结

AI 驱动的设计系统迭代将"规范文档"转变为"可执行的自动化约束":

  1. Token 自动化管理:从 Figma 提取 Token 作为单一数据源,自动对比差异并生成同步 PR。
  2. 一致性审查:规则引擎 + AI 语义检查,覆盖颜色硬编码、交互状态遗漏、废弃 Token 引用等常见问题。
  3. CI/CD 集成:将一致性审查作为 PR 门禁,从源头阻断不合规代码的合入。
  4. 持续度量:建立 Token 硬编码率、组件一致性得分等核心指标,基于数据驱动迭代。

设计系统的价值不在于"一次性创建",而在于"持续治理"。自动化工具让治理成本从"每 PR 15 分钟的人工审核"降低到"秒级的自动检查",使团队能够将注意力从"是否合规"转向"如何更好地表达"。

© 版权声明

相关文章