#!/usr/bin/env node /** * 批量替换硬编码色值为 SCSS 令牌(CSS style 块内,零视觉风险) * 用法: * node scripts/replace-hex-to-token.js --batch gray # 仅灰阶 * node scripts/replace-hex-to-token.js --batch brand # 仅品牌/状态色 * node scripts/replace-hex-to-token.js # 全部 */ const fs = require('fs') const path = require('path') const ROOT = path.resolve(__dirname, '..') const EXCLUDE_DIRS = new Set(['node_modules', 'dist', 'unpackage', '.git', 'docs']) const ALL_RULES = [ // ---- 灰阶(neutral)---- ['#FFFFFF', '$app-surface'], ['#ffffff', '$app-surface'], ['#FFF', '$app-surface'], ['#fff', '$app-surface'], ['#333333', '$facade-text-strong'], ['#333', '$facade-text-strong'], ['#666666', '$facade-text-body'], ['#666', '$facade-text-body'], ['#999999', '$facade-text-muted'], ['#999', '$facade-text-muted'], ['#F5F5F5', '$facade-fill-2'], ['#f5f5f5', '$facade-fill-2'], ['#F0F0F0', '$facade-fill'], ['#f0f0f0', '$facade-fill'], ['#FAFAFA', '$facade-fill-ghost'], ['#fafafa', '$facade-fill-ghost'], ['#F9FAFB', '$facade-fill-gray-50'], ['#f9fafb', '$facade-fill-gray-50'], ['#BBBBBB', '$facade-text-ghost-light'], ['#bbbbbb', '$facade-text-ghost-light'], ['#BBB', '$facade-text-ghost-light'], ['#bbb', '$facade-text-ghost-light'], ['#CCCCCC', '$facade-text-ghost'], ['#cccccc', '$facade-text-ghost'], ['#CCC', '$facade-text-ghost'], ['#ccc', '$facade-text-ghost'], ['#DDDDDD', '$facade-border-soft'], ['#dddddd', '$facade-border-soft'], ['#DDD', '$facade-border-soft'], ['#ddd', '$facade-border-soft'], ['#EEEEEE', '$facade-hairline-2'], ['#eeeeee', '$facade-hairline-2'], ['#EEE', '$facade-hairline-2'], ['#eee', '$facade-hairline-2'], // ---- 品牌/状态色(brand/status)---- ['#F97316', '$brand-primary'], ['#f97316', '$brand-primary'], ['#FF8C42', '$dim-body'], ['#ff8c42', '$dim-body'], ['#FFF7ED', '$uni-primary-pale'], ['#fff7ed', '$uni-primary-pale'], ['#FB923C', '$brand-primary-light'], ['#fb923c', '$brand-primary-light'], ['#10B981', '$status-success'], ['#10b981', '$status-success'], ['#EF4444', '$status-danger'], ['#ef4444', '$status-danger'], ['#6366F1', '$dim-wisdom'], ['#6366f1', '$dim-wisdom'], ['#C62828', '$status-danger-strong-2'], ['#c62828', '$status-danger-strong-2'], // ---- 第二轮(高频色,2026-10-20 批量收割)---- ['#94A3B8', '$app-text-subtle'], ['#94a3b8', '$app-text-subtle'], ['#1E293B', '$uni-text-color'], ['#1e293b', '$uni-text-color'], ['#64748B', '$uni-text-color-secondary'], ['#64748b', '$uni-text-color-secondary'], ['#E2E8F0', '$uni-border-color-light'], ['#e2e8f0', '$uni-border-color-light'], ['#F5F7FA', '$app-bg'], ['#f5f7fa', '$app-bg'], ['#667EEA', '$accent-indigo'], ['#667eea', '$accent-indigo'], ['#9CA3AF', '$facade-text-gray-400'], ['#9ca3af', '$facade-text-gray-400'], ['#FFF3E0', '$fortune-1-tint'], ['#fff3e0', '$fortune-1-tint'], ['#FED7AA', '$brand-primary-light-2'], ['#fed7aa', '$brand-primary-light-2'], ['#F1F5F9', '$facade-fill-slate'], ['#f1f5f9', '$facade-fill-slate'], ['#4CAF50', '$mat-green'], ['#4caf50', '$mat-green'], ['#E8F5E9', '$mat-green-tint'], ['#e8f5e9', '$mat-green-tint'], ['#F59E0B', '$dim-wealth'], ['#f59e0b', '$dim-wealth'], ['#F8F8F8', '$facade-fill-page'], ['#f8f8f8', '$facade-fill-page'], ['#FEF3C7', '$status-warning-soft'], ['#fef3c7', '$status-warning-soft'], ['#E65100', '$mat-orange-strong'], ['#e65100', '$mat-orange-strong'], ['#2E7D32', '$mat-green-strong'], ['#2e7d32', '$mat-green-strong'], ['#888888', '$facade-text-faint'], ['#888', '$facade-text-faint'], ['#F3F4F6', '$facade-fill-gray-100'], ['#f3f4f6', '$facade-fill-gray-100'], ['#E3F2FD', '$mat-blue-tint'], ['#e3f2fd', '$mat-blue-tint'], ['#EA580C', '$brand-primary-dark'], ['#ea580c', '$brand-primary-dark'], ['#E5E7EB', '$facade-gray-200'], ['#e5e7eb', '$facade-gray-200'], ['#E0E0E0', '$facade-hairline'], ['#e0e0e0', '$facade-hairline'], ['#1F2937', '$facade-text-gray-800'], ['#1f2937', '$facade-text-gray-800'], ['#6B7280', '$facade-text-slate'], ['#6b7280', '$facade-text-slate'], ['#F5FAFE', '$facade-fill-blue-ghost'], ['#f5fafe', '$facade-fill-blue-ghost'], ['#CBD5E1', '$uni-border-color'], ['#cbd5e1', '$uni-border-color'], ['#374151', '$facade-text-gray-700'], ['#374151', '$facade-text-gray-700'], ['#764BA2', '$accent-indigo-deep'], ['#764ba2', '$accent-indigo-deep'], ['#F8FAFC', '$facade-fill-slate-50'], ['#f8fafc', '$facade-fill-slate-50'], ['#FFD700', '$accent-gold'], ['#ffd700', '$accent-gold'], ['#1565C0', '$mat-blue-strong'], ['#1565c0', '$mat-blue-strong'], ['#DC2626', '$status-danger-deep'], ['#dc2626', '$status-danger-deep'], ['#475569', '$facade-text-slate-deep'], ['#475569', '$facade-text-slate-deep'], ['#8B5CF6', '$facade-violet'], ['#8b5cf6', '$facade-violet'], ['#334155', '$facade-text-slate-700'], ['#334155', '$facade-text-slate-700'], ['#FFB366', '$dim-body-light'], ['#ffb366', '$dim-body-light'], ['#FFEBEE', '$fortune-0-tint'], ['#ffebee', '$fortune-0-tint'], ['#34D399', '$status-success-light-3'], ['#34d399', '$status-success-light-3'], ['#1A1A2E', '$app-ink-night'], ['#1a1a2e', '$app-ink-night'], ['#E8E8E8', '$facade-fill-4'], ['#e8e8e8', '$facade-fill-4'], ['#16A34A', '$status-success-strong-2'], ['#16a34a', '$status-success-strong-2'], ['#FEE2E2', '$status-danger-soft'], ['#fee2e2', '$status-danger-soft'], ['#C2410C', '$brand-primary-deep'], ['#c2410c', '$brand-primary-deep'], ['#FFF7E6', '$brand-primary-tint-2'], ['#fff7e6', '$brand-primary-tint-2'], ['#22C55E', '$status-success-2'], ['#22c55e', '$status-success-2'], ['#3B82F6', '$status-info'], ['#3b82f6', '$status-info'], ['#2196F3', '$mat-blue-base'], ['#2196f3', '$mat-blue-base'], ['#D97706', '$status-warning-deep'], ['#d97706', '$status-warning-deep'], ['#0EA5E9', '$uni-info'], ['#0ea5e9', '$uni-info'], ['#DCFCE7', '$status-success-soft-2'], ['#dcfce7', '$status-success-soft-2'], ['#555555', '$facade-text-deep'], ['#555', '$facade-text-deep'], ['#818CF8', '$dim-wisdom-light'], ['#818cf8', '$dim-wisdom-light'], ['#FF9800', '$mat-orange-base'], ['#ff9800', '$mat-orange-base'], ['#F5F9FC', '$uni-bg-color'], ['#f5f9fc', '$uni-bg-color'], ['#F0FDF4', '$status-success-tint-2'], ['#f0fdf4', '$status-success-tint-2'], ['#D1FAE5', '$status-success-soft'], ['#d1fae5', '$status-success-soft'], ['#DBEAFE', '$status-info-soft'], ['#dbeafe', '$status-info-soft'], ['#92400E', '$status-warning-strong'], ['#92400e', '$status-warning-strong'], ].sort((a, b) => b[0].length - a[0].length) const GRAY_RULES = ALL_RULES.filter(r => { const i = ALL_RULES.indexOf(r); return i < 30 }) const BRAND_RULES = ALL_RULES.filter(r => { const i = ALL_RULES.indexOf(r); return i >= 30 }) const SECOND_RULES = ALL_RULES.filter(r => { const i = ALL_RULES.indexOf(r); return i >= 30 }) const batch = process.argv.find(a => a.startsWith('--batch='))?.split('=')[1] const rules = batch === 'brand' ? BRAND_RULES : batch === 'gray' ? GRAY_RULES : ALL_RULES const label = batch === 'brand' ? '品牌/状态色' : batch === 'gray' ? '灰阶' : '全部' console.log(`\n批次: ${label} 规则数: ${rules.length}\n`) function walk(dir, out = []) { for (const name of fs.readdirSync(dir)) { if (EXCLUDE_DIRS.has(name)) continue const p = path.join(dir, name) const st = fs.statSync(p) if (st.isDirectory()) walk(p, out) else if (name.endsWith('.vue')) out.push(p) } return out } function countReplacements(text, rules) { let n = 0 for (const [hex] of rules) { const escaped = hex.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') n += (text.match(new RegExp(escaped + '(?![0-9a-fA-F])', 'g')) || []).length } return n } function applyRules(text, rules) { let result = text for (const [hex, token] of rules) { const escaped = hex.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') result = result.replace(new RegExp(escaped + '(?![0-9a-fA-F])', 'g'), token) } return result } function processFile(filePath) { const src = fs.readFileSync(filePath, 'utf8') // 按 " const replCount = countReplacements(body, rules) if (replCount === 0) continue const newBody = applyRules(body, rules) totalRepl += replCount // 若