news 2026/7/15 21:10:50

AI 辅助设计规范检查:自动验证间距、字号与颜色的一致性

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI 辅助设计规范检查:自动验证间距、字号与颜色的一致性

AI 辅助设计规范检查:自动验证间距、字号与颜色的一致性

一、那个用 13px 字号的表单和用 14px 的侧边栏——没人注意到,直到审计那天

一个后台管理系统的"设计一致性审计"发现了一个让人脸红的数字:全站 270 个页面中,使用的字号值有 18 种(从 11px 到 48px),但设计系统只定义了 7 种。间距值出现了 23 种(从 2px 到 120px),但 Token 只定义了 9 种。颜色值出现了 57 种 HEX 值,Token 只定义了 32 种。这些"非法值"不是恶意添加的——是 7 位开发者在 3 年时间里,每次解决一个"这里间距看起来不太够"的问题时,随手敲下的margin-left: 14pxcolor: #5B6C7D

代码可以跑、页面视觉也"还行",但"还行"是设计规范的慢性死亡。每一次硬编码都让 Token 的权威性下降一点。AI 在设计规范检查中的角色是充当一个"永不疲劳的规范警察"——扫描所有 CSS 和 JSX 文件,找出每一个不在 Token 注册表中的样式值,生成合规报告,并在 CI 中拦截新的违反。

二、设计规范的自动审计模型

flowchart TD A["扫描源码<br/>(CSS/JSX/TSX/Vue)"] --> B["提取样式值"] B --> C["分类"] C --> C1["间距值<br/>(margin/padding/gap)"] C --> C2["字号值<br/>(font-size)"] C --> C3["颜色值<br/>(HEX/RGB/HSL)"] C --> C4["圆角值<br/>(border-radius)"] C1 --> D{"在 Token 表中?"} C2 --> D C3 --> D C4 --> D D -->|"是"| E["✅ 合规"] D -->|"否"| F["❌ 违反规范<br/>标记 + 建议替代"] F --> G["生成合规报告"]

三、设计规范自动检查器

/** * 设计规范自动检查引擎 */ import { readFileSync } from 'fs'; import { glob } from 'glob'; import postcss from 'postcss'; interface TokenRegistry { spacing: number[]; // 允许的间距值(px): [4, 8, 12, 16, 20, 24, 32, 40, 48, 64] fontSize: number[]; // 允许的字号值(px): [12, 14, 16, 18, 20, 24, 30, 36, 48] colors: string[]; // 允许的 HEX 颜色: ['#3B82F6', '#F8FAFC', ...] radius: number[]; // 允许的圆角值(px): [0, 4, 8, 12, 16, 9999] } interface DesignViolation { file: string; line: number; property: string; actualValue: string; // 实际使用的值 suggestion: string; // 推荐使用的 Token severity: 'error' | 'warning'; } const TOKEN_REGISTRY: TokenRegistry = { spacing: [4, 8, 12, 16, 20, 24, 32, 40, 48, 64], fontSize: [12, 14, 16, 18, 20, 24, 30, 36, 48], colors: [ '#3B82F6', '#2563EB', '#1D4ED8', '#0F172A', '#475569', '#94A3B8', '#FFFFFF', '#F8FAFC', '#F1F5F9', '#E2E8F0', '#CBD5E1', '#EF4444', '#22C55E', ], radius: [0, 4, 8, 12, 16, 9999], }; /** * 检查 CSS 文件中的设计规范违反 */ function checkCSSFile(filePath: string): DesignViolation[] { const violations: DesignViolation[] = []; const css = readFileSync(filePath, 'utf-8'); const root = postcss.parse(css); root.walkDecls((decl) => { const prop = decl.prop; const value = decl.value; // 间距检查(跳过 CSS 变量赋值和 0 值) if (prop.match(/^(margin|padding|gap)/) && !value.includes('var(') && value !== '0') { checkSpacing({ prop, value, filePath, source: decl.source, violations, }); } // 字号检查 if (prop === 'font-size' && !value.includes('var(')) { checkFontSize({ prop, value, filePath, source: decl.source, violations }); } // 颜色检查 if (prop.match(/^(color|background|border-color)/) && value.match(/^#[0-9a-fA-F]+/)) { checkColor({ prop, value: value.trim().split(' ')[0], filePath, source: decl.source, violations }); } // 圆角检查 if (prop === 'border-radius' && value.match(/^\d+px$/)) { checkRadius({ prop, value, filePath, source: decl.source, violations }); } }); return violations; } function checkSpacing({ prop, value, filePath, source, violations }: any) { const pxMatch = value.match(/^(\d+)px/); if (!pxMatch) return; const px = parseInt(pxMatch[1]); if (!TOKEN_REGISTRY.spacing.includes(px)) { const nearest = findNearest(px, TOKEN_REGISTRY.spacing); violations.push({ file: filePath, line: source?.start?.line || 0, property: prop, actualValue: `${px}px`, suggestion: `使用 var(--space-${nearest}) (${nearest}px)`, severity: 'error', }); } } function checkFontSize({ prop, value, filePath, source, violations }: any) { const pxMatch = value.match(/^(\d+)px/); if (!pxMatch) return; const px = parseInt(pxMatch[1]); if (!TOKEN_REGISTRY.fontSize.includes(px)) { const nearest = findNearest(px, TOKEN_REGISTRY.fontSize); violations.push({ file: filePath, line: source?.start?.line || 0, property: prop, actualValue: `${px}px`, suggestion: `使用 var(--font-size-${nearest}) (${nearest}px)`, severity: 'warning', }); } } function checkColor({ value, filePath, source, violations }: any) { const hex = value.toUpperCase(); if (!TOKEN_REGISTRY.colors.map(c => c.toUpperCase()).includes(hex)) { violations.push({ file: filePath, line: source?.start?.line || 0, property: 'color', actualValue: hex, suggestion: `颜色 "${hex}" 不在 Token 注册表中。请添加到 Token 或使用已有颜色。`, severity: 'error', }); } } function checkRadius({ value, filePath, source, violations }: any) { const px = parseInt(value); if (!TOKEN_REGISTRY.radius.includes(px)) { const nearest = findNearest(px, TOKEN_REGISTRY.radius); violations.push({ file: filePath, line: source?.start?.line || 0, property: 'border-radius', actualValue: value, suggestion: `使用 var(--radius-${nearest}) (${nearest}px)`, severity: 'warning', }); } } function findNearest(target: number, allowed: number[]): number { return allowed.reduce((prev, curr) => Math.abs(curr - target) < Math.abs(prev - target) ? curr : prev ); } /** * 扫描整个项目的设计规范合规性 */ async function auditDesignCompliance(projectDir: string) { const cssFiles = await glob(`${projectDir}/**/*.{css,scss,less}`, { ignore: ['**/node_modules/**', '**/dist/**'], }); const allViolations: DesignViolation[] = []; for (const file of cssFiles) { const violations = checkCSSFile(file); allViolations.push(...violations); } // 按严重性分组统计 const errors = allViolations.filter(v => v.severity === 'error'); const warnings = allViolations.filter(v => v.severity === 'warning'); return { total: allViolations.length, errors: errors.length, warnings: warnings.length, violations: allViolations, report: generateComplianceReport(allViolations), }; } function generateComplianceReport(violations: DesignViolation[]): string { const byProperty: Record<string, DesignViolation[]> = {}; for (const v of violations) { const key = v.property; if (!byProperty[key]) byProperty[key] = []; byProperty[key].push(v); } const lines = ['## 设计规范合规审计报告\n']; for (const [prop, items] of Object.entries(byProperty)) { lines.push(`### ${prop}(${items.length} 项违反)`); for (const item of items.slice(0, 5)) { lines.push(`- \`${item.file}:${item.line}\` — ${item.actualValue} → ${item.suggestion}`); } if (items.length > 5) lines.push(`- ... 及其他 ${items.length - 5} 项`); lines.push(''); } return lines.join('\n'); } export { auditDesignCompliance, checkCSSFile, TOKEN_REGISTRY }; export type { DesignViolation, TokenRegistry };

四、检查器的边界

"合理违反"与"误报"的差异。有些设计规范违反是故意的——比如一个折扣标签需要border-radius: 20px来形成胶囊形状,而 Token 只定义了0/4/8/12/16/9999。在 Token 中20px不在表内,但 9999 会形成完全的半圆角,不符合设计需求。这种情况下,团队的策略应该是"将 20px 增加到 Token 注册表"而非"警告但不处理"——如果它是合理需求,就应该被正式化。

Token 的演进和检查的同步。当设计师在 Figma 中新增了一个间距值--space-14: 56px,Token 注册表需要同步更新。如果检查器的 TOKEN_REGISTRY 是手动维护的,它会在 3 天内落后于真实的 Token 文件。正确的做法是检查器从 Token 的 JSON 文件自动读取允许值——Token 文件是唯一真值源。

五、总结

  1. 设计规范检查器 = 提取样式值 → 与 Token 注册表对比 → 标记违规项。
  2. 四个核心检查维度:间距(margin/padding/gap)、字号(font-size)、颜色(HEX)、圆角(border-radius)。
  3. 硬编码的像素值和 HEX 颜色是设计规范腐败的起点——每一处"随手写的"都是未来的雷。
  4. 最佳替代建议 = 在 Token 注册表中查找最接近的值——14px → --space-4 (16px)
  5. 检查器的 Token 注册表应从 Token JSON 文件自动生成——避免手动维护不一致。
  6. 如果某个"违反"是合理需求——将该值加入 Token 注册表,而非在检查报告中标记豁免。
  7. CI 阻断策略:error(严格违反)阻断合入,warning(轻微偏差)仅在 PR 评论中提示。
  8. 颜色检查需要将 HEX 值统一大写后进行对比——#3b82f6#3B82F6在逻辑上是相同的。
  9. 0auto等特殊值不应被标记——它们在任何间距系统中都是合法的。
  10. 设计规范检查的目标是让 Token 成为样式修改的唯一入口——任何绕过 Token 的修改都应该在 PR 阶段被发现。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/15 21:08:20

Mac Mini部署多AI智能体:模型选择与资源分配实战指南

如果你也想过用一台入门级设备跑起多个 AI 智能体&#xff0c;让它们帮你自动处理任务&#xff0c;那 Mac Mini 确实是个不错的选择。它功耗低、体积小&#xff0c;适合 7x24 小时开机&#xff0c;但能不能稳定跑起 4 个 AI 员工&#xff0c;关键不在硬件本身&#xff0c;而在你…

作者头像 李华
网站建设 2026/7/15 21:07:32

现代C++ TOML解析器toml++:从使用到贡献代码的完整指南

1. 项目概述&#xff1a;为什么我们需要一个现代的C TOML解析器&#xff1f;如果你最近在折腾C项目&#xff0c;尤其是那些需要配置文件的项目&#xff0c;大概率已经对INI、JSON、XML这些格式感到厌倦了。INI太简单&#xff0c;缺乏层次和类型&#xff1b;JSON虽然流行&#x…

作者头像 李华
网站建设 2026/7/15 21:04:55

RTX5090 32G 八卡算力集群技术特性与全行业落地应用场景详解

在当前AI大模型与多模态技术高速迭代的背景下&#xff0c;中端旗舰多卡算力集群凭借高性价比、高并行算力、灵活部署的优势&#xff0c;成为科研与企业研发的核心算力选型方案。RTX5090-32G作为新一代消费级旗舰算力卡&#xff0c;单卡超大显存、强劲CUDA算力适配绝大多数中小规…

作者头像 李华
网站建设 2026/7/15 21:03:10

盘锦瑜伽馆报名,先看课程和老师

盘锦瑜伽馆报名前&#xff0c;先看课程和老师&#xff0c;比单纯看价格、装修更重要。因为真正影响你能不能坚持、练得是否舒服、动作是否安全的&#xff0c;不是“课表看起来多”&#xff0c;而是课程强度是否适合你&#xff0c;老师能不能观察动作细节&#xff0c;课堂人数会…

作者头像 李华
网站建设 2026/7/15 21:01:53

龙卷风EF等级系统详解:从EF0到EF5的破坏特征与风速范围

这次我们来快速了解龙卷风的强度等级系统。龙卷风作为自然界最具破坏力的天气现象之一&#xff0c;其强度分级直接关系到预警发布、灾害评估和防护措施。掌握这套分级标准&#xff0c;不仅能帮助我们在面对极端天气时做出正确判断&#xff0c;还能更好地理解气象部门的预警信息…

作者头像 李华
网站建设 2026/7/15 21:00:15

500行C++实现高性能无锁队列:原理、实战与性能优化

1. 项目概述&#xff1a;为什么500行C代码的无锁队列能撬动系统性能&#xff1f;在后台服务、游戏服务器或者高频交易系统里摸爬滚打过的朋友&#xff0c;对“性能瓶颈”这个词一定不陌生。很多时候&#xff0c;系统卡顿、响应延迟的罪魁祸首&#xff0c;并不是CPU算力不够&…

作者头像 李华