feat: 初始化画个标仓库 - AI驱动设计系统(BYOK模式) - 224源/371504图标+25海报源+130技能

This commit is contained in:
sgp
2026-10-03 21:44:18 +08:00
commit 9e222877ea
497 changed files with 43611 additions and 0 deletions
+641
View File
@@ -0,0 +1,641 @@
// 甄房业务系统 · 三套色彩体系对比页
// 结构:一套 UI 演示结构 + 三套 CSS 变量作用域([data-theme]),保证对比公平
// 对比度由页面运行时真实计算(非手写)
// 用法: node _design-test/theme/build-theme-compare.js
const fs = require('fs');
const path = require('path');
const HERE = __dirname;
const LOGO = path.join(HERE, '..', 'logo');
const FZ = JSON.parse(fs.readFileSync(path.join(LOGO, 'final-z.json'), 'utf8'));
const H04 = 'M3 11.99v2.51c0 3.3 0 4.95 1.025 5.975S6.7 21.5 10 21.5h4c3.3 0 4.95 0 5.975-1.025S21 17.8 21 14.5v-2.51c0-1.682 0-2.522-.356-3.25s-1.02-1.244-2.346-2.276l-2-1.555C14.233 3.303 13.2 2.5 12 2.5s-2.233.803-4.298 2.409l-2 1.555C4.375 7.496 3.712 8.012 3.356 8.74S3 10.308 3 11.99';
const FG = 'translate(5.3304 5.3294) scale(2.22222)';
// 品牌标识(框 + Z),c1=框色 c2=Z色
const mark = (c1, c2, size) =>
'<svg viewBox="0 0 64 64" width="' + size + '" height="' + size + '">' +
'<g transform="' + FG + '"><path d="' + H04 + '" fill="none" stroke="' + c1 +
'" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></g>' +
'<path d="' + FZ.final + '" fill="' + c2 + '"/></svg>';
// ── 三套色彩令牌 ────────────────────────────────────────────────
// 说明:品牌墨蓝 #0C2340 在三套方案中都用于「品牌/导航区」,
// 交互色各自采用该体系的官方主色 —— 即「品牌色与交互色分离」。
const THEMES = [
{
id: 'carbon', name: '方案 A · Carbon 严谨派', sys: 'IBM Carbon Design System',
score: { dense: 9.5, comfort: 9.5, a11y: 10, brand: 8.5, cost: 7.5, proven: 9.0 },
radius: '2px', font: '"IBM Plex Sans", "PingFang SC", "Microsoft YaHei", sans-serif',
shadow: 'none',
desc: '以中性灰主导、蓝色只承担「行动色」的经典企业级体系。四套官方主题(White / Gray 10 / Gray 90 / Gray 100)与分层模型,为数据密集型界面而生。',
tone: { brand:'#0C2340', primary:'#0F62FE', bg:'#FFFFFF', bg2:'#F4F4F4' },
tags: [
['品牌 / 导航底', '#0C2340', '甄房墨蓝(品牌区、侧栏、登录页)'],
['交互主色', '#0F62FE', 'Blue 60 · 官方 primary action'],
['主色悬停', '#0043CE', 'Blue 70'],
['主色按下', '#002D9C', 'Blue 80'],
['主色浅底', '#EDF5FF', 'Blue 10 · 高亮/选中浅底'],
['页面底色', '#FFFFFF', '$background(White 主题)'],
['分层底色', '#F4F4F4', 'Gray 10 · $layer-01'],
['悬停底色', '#E8E8E8', 'Gray 10 hover'],
['选中底色', '#E0E0E0', 'Gray 20 · $layer-selected-01'],
['边框', '#E0E0E0', 'Gray 20'],
['边框-强', '#8D8D8D', 'Gray 50'],
['一级文字', '#161616', 'Gray 100'],
['二级文字', '#525252', 'Gray 70'],
['三级文字', '#6F6F6F', 'Gray 60'],
['成功', '#198038', 'Green 60'],
['警告', '#F1C21B', 'Yellow 30'],
['错误', '#DA1E28', 'Red 60'],
['信息', '#0F62FE', 'Blue 60'],
['品牌金(点缀)', '#C6A15B', '仅 logo / 关键数字,占比 ≤5%']
],
pros: ['中性灰主导(官方原文 “Neutral Gray family is dominant”),长时间盯表格最不累',
'明确要求「避免使用中间调」,界面层次靠灰阶深浅而非色块,密度最高',
'无障碍是一等公民:官方每个令牌都标注对比度要求(如 $layer-selected-inverse 标 4.5:1 AA)',
'四套官方主题 + 分层模型,深色模式与高对比模式有现成规范'],
cons: ['圆角基本为 0(2px),偏工程感,国内用户可能觉得「硬」',
'品牌金几乎没有落点,品牌温度靠墨蓝承担',
'与 ElementUI 的默认圆角/间距差异大,改造成本三套中最高'],
fit: ['数据密度极高的作业台(房源批量检核、佣金结算)',
'需要长时间连续录入/核对的岗位',
'对无障碍有硬性要求的场景(政企、大客户交付)'],
// 生产落地必改项:[用途, 原值, 建议改为, 理由]
fixes: [
['警告色用法', '#F1C21B 作文字色', '黄只作底色(#F1C21B 底 + #161616 字)',
'Yellow 30 在白底上仅 1.68:1;Carbon 官方本就把黄色用于警报条底色而非文字色'],
['圆角', '2px', '按国内习惯可放宽到 4px',
'不是超标项,属外观取舍;Carbon 的 0–2px 方角在国内后台会显「硬」']
]
},
{
id: 'fiori', name: '方案 B · Fiori 商务派', sys: 'SAP Fiori · Morning Horizon',
final: true, // ✓ 已定稿(2026-09-19)
score: { dense: 9.0, comfort: 8.8, a11y: 9.5, brand: 8.0, cost: 7.0, proven: 9.5 },
radius: '8px', font: '"72", "PingFang SC", "Microsoft YaHei", sans-serif',
shadow: '0 1px 2px rgba(34,53,72,.08)',
desc: 'ERP 领域事实标准。语义令牌分层最完整:表面 S0–S8、文字 T1–T9、边框、语义色、强调色五组,并给出可推导的对比度规则。',
tone: { brand:'#0C2340', primary:'#0070F2', bg:'#FFFFFF', bg2:'#F5F6F7' },
tags: [
['品牌 / Shell Bar', '#0C2340', '甄房墨蓝(Shell Bar、应用头)'],
['交互主色', '#0070F2', 'Blue 7 · S2 Secondary / T4 CTA'],
['主色悬停', '#0057D2', 'Blue 8 · T7 Selected'],
['主色按下', '#0040B0', '同体系推导(Blue 8 再压一档)'],
['主色浅底', '#EBF4FE', 'S5 Selected Surface(官方 rgba(0,112,242,.08) 在白底上的合成值)'],
['页面底色', '#FFFFFF', 'S0 Base Background'],
['次级底色', '#F5F6F7', 'S4 Quaternary · Grey 1'],
['边框', '#D5DADD', 'Grey 3'],
['边框-强', '#A9B4BE', 'Grey 4'],
['一级文字', '#223548', 'T1 · Grey 9'],
['二级文字', '#475E75', 'T2 · Grey 7'],
['三级文字', '#5B738B', 'T3 · Grey 6'],
['成功', '#256F3A', 'Positive(同体系参考值)'],
['警告', '#E76500', 'Critical · Mango(同体系参考值)'],
['错误', '#D20A0A', 'Red 7 · Negative(官方确认)'],
['信息', '#0070F2', 'Blue 7'],
['品牌金(点缀)', '#C6A15B', '仅 logo / 关键数字,占比 ≤5%']
],
pros: ['唯一为 ERP 事务场景设计的体系,表格/表单/Shell 的层级划分最成熟',
'「相隔 4 级 = 3:1、相隔 5 级 = 4.5:1」——对比度可推导,选色不用猜',
'选中态用 8% 蓝底(而非实色块),长时间使用不刺眼',
'品牌化路径最清晰:官方 Quick Theming 就是「替换 logo + 主品牌色」'],
cons: ['Shell Bar + 应用头 + 页面头三层结构,垂直空间占用大,密度略低于 Carbon',
'蓝白对比偏冷、现代感一般,年轻经纪人可能觉得「像老系统」',
'部分语义色官方文档未完整公开,落地前需核对准确值'],
fit: ['交易全流程、佣金结算这类强流程、强状态的模块',
'与财务/ERP 打通、需要符合企业级规范的场景',
'多角色、多层级权限的后台(选中态与层级表达最清楚)'],
fixes: [] // 12/12 全部通过,无需改动
},
{
id: 'antd', name: '方案 C · AntD 亲和派', sys: 'Ant Design 5',
score: { dense: 8.0, comfort: 7.5, a11y: 8.0, brand: 8.5, cost: 9.5, proven: 9.0 },
radius: '6px', font: '-apple-system, "PingFang SC", "Microsoft YaHei", sans-serif',
shadow: '0 1px 2px rgba(0,0,0,.06)',
desc: '国内中后台事实标准。12 色 × 10 阶色板 + 算法生成,可在品牌色基础上自动产出整条色阶;中文文档与团队熟悉度最高。',
tone: { brand:'#0C2340', primary:'#1677FF', bg:'#FFFFFF', bg2:'#F5F5F5' },
tags: [
['品牌 / 侧栏', '#0C2340', '甄房墨蓝(侧栏、品牌区)'],
['交互主色', '#1677FF', 'colorPrimary · blue-6(官方默认)'],
['主色悬停', '#4096FF', 'blue-5'],
['主色按下', '#0958D9', 'blue-7'],
['主色浅底', '#E6F4FF', 'blue-1 · 选中行浅底'],
['页面底色', '#FFFFFF', '官方容器色'],
['次级底色', '#F5F5F5', '官方中性灰'],
['边框', '#D9D9D9', '官方边框色'],
['一级文字', '#1F1F1F', 'rgba(0,0,0,.88) 合成值'],
['二级文字', '#595959', 'rgba(0,0,0,.65) 合成值'],
['三级文字', '#8C8C8C', 'rgba(0,0,0,.45) 合成值'],
['成功', '#52C41A', 'colorSuccess'],
['警告', '#FAAD14', 'colorWarning'],
['错误', '#FF4D4F', 'colorError'],
['信息', '#1677FF', 'colorInfo'],
['品牌金(点缀)', '#C6A15B', '仅 logo / 关键数字,占比 ≤5%']
],
pros: ['团队最熟、中文文档最全,与现有 Vue2 + ElementUI 生态迁移成本最低',
'色板算法可由品牌色直接生成整条色阶(12 阶),扩展与派生最省事',
'色彩语义与国内用户预期一致(成功绿/警告黄/错误红),零学习成本',
'品牌金在三套中最好安排(圆角卡片 + 金色数字自然)'],
cons: ['默认色饱和偏高:#FF4D4F 错误色、#52C41A 成功色长时间看刺激较强',
'官方定位偏「轻盈」,高数据密度下需要额外收紧间距与层级',
'中性灰层级不如 Carbon/Fiori 严谨,容易出现「灰得不够分明」'],
fit: ['经纪人日常高频作业(房源/客源/跟进),要求上手快、认知零负担',
'迭代节奏快、需要大量复用生态组件的中后台',
'团队以国内前端为主、希望降低长期维护成本的项目'],
// 生产落地必改项:[用途, 原值, 建议改为, 理由]
fixes: [
['主色(按钮底 / 链接字)', '#1677FF', '#0958D9(blue-7)',
'官方默认主色在白底上实测仅 4.10:1,差 0.4 未达 AA 4.5:1 —— 「第 6 阶满足 4.5:1」只对部分色相成立;下移一阶到 blue-7 即 6.16:1,肉眼几乎无差别'],
['成功色', '#52C41A', '#389E0D(green-7)',
'#52C41A 白底仅 2.27:1,连图形类 3:1 都不到;图标与文字改 green-7(3.46:1),浅底仍可保留亮绿'],
['警告色', '#FAAD14', '文字与描边改 #AD6800(gold-8)',
'#FAAD14 白底仅 1.90:1;沿用「亮色只作底、深色作字」的做法,gold-8 为 4.41:1,几乎直接满足正文标准'],
['错误色(正文)', '#FF4D4F', '正文与链接改 #CF1322(red-7)',
'#FF4D4F 作正文仅 3.27:1;作为图形勉强及格,但表格里的错误提示是正文级文字,改 red-7 后 5.57:1;描边与底色可继续用亮红'],
['三级文字', '#8C8C8C', '#6E6E6E',
'rgba(0,0,0,.45) 的合成值白底 3.36:1,未达 AA;加深到 #6E6E6E 即 5.10:1'],
['灰阶层级', '仅三级文字灰', '补一级 #BFBFBF 专用于占位符 / 禁用',
'AntD 只有三级文字灰,导致占位符被迫复用 #8C8C8C —— 既不够浅(有对比度不达标风险)又会被误读为正文;补一级浅灰后三级文字才能安心加深']
]
}
];
// 加权评分(权重:密度25 舒适20 无障碍15 品牌15 成本15 验证10)
const W = { dense: .25, comfort: .20, a11y: .15, brand: .15, cost: .15, proven: .10 };
THEMES.forEach(t => { t.total = Object.keys(W).reduce((s, k) => s + t.score[k] * W[k], 0); });
const CSS = `
*{box-sizing:border-box;margin:0;padding:0}
body{background:#0a0d13;color:#e9edf5;font-family:"PingFang SC","Microsoft YaHei",system-ui,sans-serif;
padding:40px 24px 90px;line-height:1.6}
.wrap{max-width:1500px;margin:0 auto}
h1{font-size:27px;letter-spacing:.2px}h1 b{color:#E3CB99}
.lead{color:#8b94a7;font-size:13.5px;margin:10px 0 26px;line-height:1.95}
h2{font-size:19px;margin:46px 0 6px;padding-left:11px;border-left:4px solid #C6A15B}
h2 span{font-size:11px;font-weight:400;color:#8b94a7;margin-left:10px}
h3{font-size:15px;margin:26px 0 10px;color:#E3CB99}
p.note{font-size:12.5px;color:#8b94a7;margin:8px 0 16px;line-height:1.9}
/* ── 调研结论 ── */
table.cmp{width:100%;border-collapse:collapse;font-size:12.5px;margin-top:14px}
table.cmp th,table.cmp td{border:1px solid rgba(255,255,255,.09);padding:9px 11px;text-align:left}
table.cmp th{background:rgba(255,255,255,.045);font-weight:600;color:#cfd6e4}
table.cmp td.num{text-align:center;font-variant-numeric:tabular-nums}
table.cmp tr.win td{background:rgba(198,161,91,.10)}
table.cmp td.tot{font-weight:700;color:#E3CB99}
.badge{display:inline-block;font-size:10px;padding:1px 7px;border-radius:20px;margin-left:7px;
border:1px solid rgba(198,161,91,.45);color:#E3CB99}
.src{font-size:11px;color:#6d768a;margin-top:8px;line-height:1.9}
.src a{color:#8ab4f8;text-decoration:none}
/* ── 方案卡 ── */
.plan{border:1px solid rgba(255,255,255,.09);border-radius:16px;padding:22px;margin-top:18px;
background:linear-gradient(180deg,rgba(255,255,255,.03),rgba(255,255,255,.012))}
.plan .hd{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
.plan .hd h3{margin:0;font-size:18px;color:#fff}
.plan .hd .sys{font-size:11.5px;color:#8b94a7}
.plan .hd .sc{margin-left:auto;font-size:13px;color:#E3CB99;font-weight:700}
.plan .hd .finalmark{font-size:11px;font-weight:700;padding:2px 9px;border-radius:20px;
background:rgba(94,224,138,.13);border:1px solid rgba(94,224,138,.5);color:#5ee08a}
.plan.final{border-color:rgba(94,224,138,.42);box-shadow:0 0 0 1px rgba(94,224,138,.16) inset}
.plan .d{font-size:12.5px;color:#9aa4b8;margin:9px 0 16px;line-height:1.9}
.pc{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:14px}
.pc>div{background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.07);border-radius:11px;padding:13px 15px}
.pc h4{font-size:12.5px;margin-bottom:8px}
.pc ul{list-style:none;font-size:12px;color:#98a1b3;line-height:1.95}
.pc ul li{padding-left:15px;position:relative}
.pc ul li:before{position:absolute;left:0;top:0}
.pc .good li:before{content:'+';color:#5ee08a}
.pc .bad li:before{content:'−';color:#f0a55c}
.pc .fit li:before{content:'·';color:#8ab4f8}
/* ── 生产落地必改项 ── */
table.fix{margin-top:10px;font-size:12px}
table.fix th:nth-child(1),table.fix td:nth-child(1){width:132px}
table.fix th:nth-child(2),table.fix td:nth-child(2){width:150px}
table.fix th:nth-child(3),table.fix td:nth-child(3){width:250px}
table.fix code{background:rgba(255,255,255,.06);padding:1px 5px;border-radius:4px;font-size:11px}
table.fix td:nth-child(2) code{color:#f0a55c}
table.fix td:nth-child(3) code{color:#5ee08a}
.fixok{font-size:12.5px;color:#5ee08a;background:rgba(94,224,138,.07);
border:1px solid rgba(94,224,138,.28);border-radius:9px;padding:10px 13px;margin-top:10px}
.sumline{display:flex;gap:10px;flex-wrap:wrap;margin-top:12px}
.sumline div{flex:1;min-width:230px;background:rgba(255,255,255,.035);border:1px solid rgba(255,255,255,.08);
border-radius:10px;padding:10px 13px;font-size:12px;color:#98a1b3}
.sumline div b{color:#e9edf5}
.sumline div em{font-style:normal;font-weight:700}
.sumline div em.ok{color:#5ee08a}.sumline div em.no{color:#f0a55c}
/* ── 令牌表 ── */
.tok{display:grid;grid-template-columns:repeat(auto-fill,minmax(232px,1fr));gap:7px;margin-top:12px}
.tk{display:flex;align-items:center;gap:9px;background:rgba(255,255,255,.035);border:1px solid rgba(255,255,255,.07);
border-radius:9px;padding:7px 10px;font-size:11.5px}
.tk i{width:22px;height:22px;border-radius:6px;flex:none;border:1px solid rgba(255,255,255,.16)}
.tk .n{flex:1;min-width:0}
.tk .n b{display:block;font-weight:600;font-size:11.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tk .n code{color:#7f8899;font-size:10px;letter-spacing:.2px}
.tk .r{font-size:9.5px;padding:1px 6px;border-radius:20px;border:1px solid;flex:none;white-space:nowrap}
.tk .r.pass{color:#5ee08a;border-color:rgba(94,224,138,.4)}
.tk .r.fail{color:#f0a55c;border-color:rgba(240,165,92,.4)}
/* ── UI 演示(三套作用域)── */
.demo{border-radius:14px;overflow:hidden;margin-top:14px;border:1px solid rgba(255,255,255,.12);background:var(--bg);
font-family:var(--font);color:var(--text1);font-size:12.5px}
.demo[data-theme=carbon]{--brand:#0C2340;--primary:#0F62FE;--primary-h:#0043CE;--primary-a:#002D9C;--primary-s:#EDF5FF;
--bg:#FFFFFF;--bg2:#F4F4F4;--hover:#E8E8E8;--sel:#E0E0E0;--bd:#E0E0E0;--bd-s:#8D8D8D;--text1:#161616;--text2:#525252;--text3:#6F6F6F;
--ok:#198038;--warn:#F1C21B;--err:#DA1E28;--radius:2px;--sh:none;--gold:#C6A15B;
--font:"IBM Plex Sans","PingFang SC","Microsoft YaHei",sans-serif}
.demo[data-theme=fiori]{--brand:#0C2340;--primary:#0070F2;--primary-h:#0057D2;--primary-a:#0040B0;--primary-s:rgba(0,112,242,.08);
--bg:#FFFFFF;--bg2:#F5F6F7;--hover:rgba(0,112,242,.06);--sel:rgba(0,112,242,.10);--bd:#D5DADD;--bd-s:#A9B4BE;--text1:#223548;--text2:#475E75;--text3:#5B738B;
--ok:#256F3A;--warn:#E76500;--err:#D20A0A;--radius:8px;--sh:0 1px 2px rgba(34,53,72,.08);--gold:#C6A15B;
--font:"72","PingFang SC","Microsoft YaHei",sans-serif}
.demo[data-theme=antd]{--brand:#0C2340;--primary:#1677FF;--primary-h:#4096FF;--primary-a:#0958D9;--primary-s:#E6F4FF;
--bg:#FFFFFF;--bg2:#F5F5F5;--hover:#F5F5F5;--sel:#E6F4FF;--bd:#D9D9D9;--bd-s:#BFBFBF;--text1:#1F1F1F;--text2:#595959;--text3:#8C8C8C;
--ok:#52C41A;--warn:#FAAD14;--err:#FF4D4F;--radius:6px;--sh:0 1px 2px rgba(0,0,0,.06);--gold:#C6A15B;
--font:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif}
.app{display:grid;grid-template-columns:196px 1fr;min-height:540px}
.app .side{background:var(--brand);padding:14px 0;display:flex;flex-direction:column;gap:2px}
.app .side .brand{display:flex;align-items:center;gap:9px;padding:2px 14px 16px;color:#fff}
.app .side .brand b{font-size:15px;font-weight:600;letter-spacing:1px}
.app .side .brand em{font-size:8px;letter-spacing:1.4px;opacity:.62;font-style:normal;display:block}
.app .side a{display:flex;align-items:center;gap:9px;color:rgba(255,255,255,.72);text-decoration:none;
font-size:12.5px;padding:9px 14px;border-left:3px solid transparent}
.app .side a.on{background:rgba(255,255,255,.10);color:#fff;border-left-color:var(--gold);font-weight:600}
.app .side a .d{width:6px;height:6px;border-radius:2px;background:currentColor;opacity:.55;flex:none}
.app .main{display:flex;flex-direction:column;min-width:0}
.app .top{display:flex;align-items:center;gap:14px;padding:11px 18px;border-bottom:1px solid var(--bd);background:var(--bg)}
.app .top .crumb{font-size:12.5px;color:var(--text2)}
.app .top .crumb b{color:var(--text1);font-weight:600}
.app .top .usr{margin-left:auto;font-size:12px;color:var(--text2);display:flex;align-items:center;gap:8px}
.app .top .usr .av{width:24px;height:24px;border-radius:50%;background:var(--primary-s);color:var(--primary);
display:flex;align-items:center;justify-content:center;font-size:10.5px;font-weight:600}
.app .body{padding:14px 18px 20px;background:var(--bg2);flex:1;display:flex;flex-direction:column;gap:12px}
.kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}
.kpi{background:var(--bg);border:1px solid var(--bd);border-radius:var(--radius);padding:11px 13px;box-shadow:var(--sh)}
.kpi .t{font-size:11px;color:var(--text3)}
.kpi .v{font-size:20px;font-weight:600;color:var(--text1);margin-top:3px;font-variant-numeric:tabular-nums}
.kpi .v em{font-size:11px;font-style:normal;color:var(--text3);font-weight:400;margin-left:3px}
.kpi .d{font-size:10.5px;margin-top:2px}
.kpi .d.up{color:var(--ok)}.kpi .d.dn{color:var(--err)}
.kpi.gold .v{color:var(--gold)}
.card{background:var(--bg);border:1px solid var(--bd);border-radius:var(--radius);box-shadow:var(--sh)}
.filters{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end;padding:13px}
.f{display:flex;flex-direction:column;gap:4px}
.f label{font-size:11px;color:var(--text3)}
.f input,.f select{font-family:inherit;font-size:12.5px;color:var(--text1);background:var(--bg);
border:1px solid var(--bd);border-radius:var(--radius);padding:6px 9px;min-width:132px}
.f input::placeholder{color:var(--text3)}
.f input:focus,.f select:focus{outline:2px solid var(--primary);outline-offset:-1px;border-color:var(--primary)}
.f.err input{border-color:var(--err)}
.f .msg{font-size:10.5px;color:var(--err)}
.btns{display:flex;gap:8px;margin-left:auto}
.btn{font-family:inherit;font-size:12.5px;border-radius:var(--radius);padding:6px 15px;cursor:default;
border:1px solid var(--bd);background:var(--bg);color:var(--text1);white-space:nowrap}
.btn.pri{background:var(--primary);border-color:var(--primary);color:#fff;font-weight:500}
.btn.pri.h{background:var(--primary-h);border-color:var(--primary-h)}
.btn.pri.a{background:var(--primary-a);border-color:var(--primary-a)}
.btn.dan{background:var(--err);border-color:var(--err);color:#fff}
.btn.gho{border-color:var(--primary);color:var(--primary);background:transparent}
.btn.tx{border-color:transparent;background:transparent;color:var(--primary);padding:6px 6px}
.btn.dis{background:var(--bg2);border-color:var(--bd);color:var(--text3);opacity:.75}
.tbl{width:100%;border-collapse:collapse;font-size:12.5px;font-variant-numeric:tabular-nums}
.tbl th{text-align:left;font-weight:600;font-size:11.5px;color:var(--text2);background:var(--bg2);
padding:9px 11px;border-bottom:1px solid var(--bd);white-space:nowrap}
.tbl td{padding:9px 11px;border-bottom:1px solid var(--bd);color:var(--text1)}
.tbl tbody tr:nth-child(even){background:var(--bg2)}
.tbl tbody tr.hov{background:var(--hover)}
.tbl tbody tr.sel{background:var(--sel)}
.tbl tbody tr.sel td:first-child{box-shadow:inset 2px 0 0 var(--primary)}
.tbl .code{color:var(--primary);font-weight:500}
.tbl .sub{color:var(--text3);font-size:11px;display:block}
.tbl .op a{color:var(--primary);text-decoration:none;margin-right:9px;font-size:12px}
.tbl .op a.dan{color:var(--err)}
.tag{display:inline-block;font-size:10.5px;padding:1px 8px;border-radius:20px;border:1px solid;white-space:nowrap}
.tag.ok{color:var(--ok);border-color:var(--ok);background:color-mix(in srgb,var(--ok) 9%,transparent)}
.tag.wn{color:var(--warn);border-color:var(--warn);background:color-mix(in srgb,var(--warn) 12%,transparent)}
.tag.er{color:var(--err);border-color:var(--err);background:color-mix(in srgb,var(--err) 9%,transparent)}
.tag.pri{color:var(--primary);border-color:var(--primary);background:var(--primary-s)}
.tag.nu{color:var(--text2);border-color:var(--bd-s);background:transparent}
.tag.gold{color:#8a6a2f;border-color:var(--gold);background:color-mix(in srgb,var(--gold) 14%,transparent)}
.pager{display:flex;align-items:center;gap:7px;padding:11px 13px;font-size:12px;color:var(--text2)}
.pg{min-width:26px;height:26px;display:flex;align-items:center;justify-content:center;border:1px solid var(--bd);
border-radius:var(--radius);background:var(--bg);color:var(--text1)}
.pg.on{background:var(--primary);border-color:var(--primary);color:#fff;font-weight:600}
.tabs{display:flex;gap:2px;border-bottom:1px solid var(--bd);padding:0 13px}
.tabs a{font-size:12.5px;padding:9px 13px;color:var(--text2);text-decoration:none;border-bottom:2px solid transparent}
.tabs a.on{color:var(--primary);border-bottom-color:var(--primary);font-weight:600}
.alerts{display:grid;grid-template-columns:1fr 1fr;gap:9px}
.al{display:flex;gap:8px;align-items:flex-start;font-size:12px;padding:9px 12px;border-radius:var(--radius);border:1px solid}
.al b{font-weight:600}
.al.i{border-color:var(--primary);background:var(--primary-s);color:var(--text1)}
.al.s{border-color:var(--ok);background:color-mix(in srgb,var(--ok) 8%,transparent);color:var(--text1)}
.al.w{border-color:var(--warn);background:color-mix(in srgb,var(--warn) 11%,transparent);color:var(--text1)}
.al.e{border-color:var(--err);background:color-mix(in srgb,var(--err) 8%,transparent);color:var(--text1)}
/* 元素级对照 */
.elcmp{margin-top:12px;border:1px solid rgba(255,255,255,.09);border-radius:12px;overflow:hidden}
table.el{width:100%;border-collapse:collapse;font-size:12px}
table.el th,table.el td{border-bottom:1px solid rgba(255,255,255,.08);padding:8px 12px;vertical-align:middle}
table.el th{background:rgba(255,255,255,.04);color:#cfd6e4;font-size:11.5px;text-align:left}
table.el td:first-child{color:#8b94a7;font-size:11.5px;width:118px}
table.el td .demo{display:inline-block;margin:0;padding:9px 12px;border:none;border-radius:8px;min-width:0}
`;
const esc = s => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
// ── UI 演示结构(只写一次,三套主题注入同一份)──
const demoBody = () => `
<div class="app">
<aside class="side">
<div class="brand">${mark('#FFFFFF', '#FFFFFF', 30)}<span><b>甄房</b><em>ZHENFANG</em></span></div>
<a class="on"><span class="d"></span>工作台</a>
<a><span class="d"></span>房源管理</a>
<a><span class="d"></span>客源管理</a>
<a><span class="d"></span>交易管理</a>
<a><span class="d"></span>钥匙管理</a>
<a><span class="d"></span>我的佣金</a>
<a><span class="d"></span>系统设置</a>
</aside>
<div class="main">
<div class="top">
<div class="crumb">房源管理 / <b>二手房房源</b></div>
<div class="usr"><span class="av">张</span>张伟 · 滨江门店</div>
</div>
<div class="body">
<div class="kpis">
<div class="kpi"><div class="t">在售房源</div><div class="v">1,284<em>套</em></div><div class="d up">↑ 3.2% 较上周</div></div>
<div class="kpi"><div class="t">本周新增客源</div><div class="v">96<em>位</em></div><div class="d up">↑ 12 位</div></div>
<div class="kpi"><div class="t">待跟进</div><div class="v">23<em>条</em></div><div class="d dn">↓ 5 条</div></div>
<div class="kpi gold"><div class="t">本月预估佣金</div><div class="v">86.4<em>万</em></div><div class="d up">↑ 8.6%</div></div>
</div>
<div class="card filters">
<div class="f"><label>房源编码</label><input placeholder="SH-ES-..."></div>
<div class="f"><label>所属楼盘</label><select><option>滨江壹号</option><option>钱江新城</option></select></div>
<div class="f"><label>委托起始日</label><input value="2026-09-01"></div>
<div class="f err"><label>维护人</label><input value="张"><span class="msg">请输入完整姓名</span></div>
<div class="btns">
<button class="btn pri">查询</button>
<button class="btn">重置</button>
<button class="btn gho">高级筛选</button>
</div>
</div>
<div class="card">
<div class="tabs">
<a class="on">全部 1284</a><a>有效 892</a><a>维护中 236</a><a>已成交 121</a><a>暂缓 35</a>
</div>
<table class="tbl">
<thead><tr>
<th>房源编码</th><th>楼盘 / 房号</th><th>面积</th><th>售价</th><th>状态</th><th>维护人</th><th>操作</th>
</tr></thead>
<tbody>
<tr><td><span class="code">SH-ES-202609-00037</span></td>
<td>滨江壹号 3幢 1802<span class="sub">滨江区 · 钱塘江畔</span></td>
<td>128.5 ㎡</td><td>1,285 万</td><td><span class="tag ok">有效</span></td><td>张伟</td>
<td class="op"><a>查看</a><a>编辑</a></td></tr>
<tr class="hov"><td><span class="code">SH-ES-202609-00041</span></td>
<td>钱江新城 7幢 2201<span class="sub">上城区 · 万象城</span></td>
<td>156.0 ㎡</td><td>1,860 万</td><td><span class="tag pri">待检核</span></td><td>李敏</td>
<td class="op"><a>查看</a><a>编辑</a></td></tr>
<tr class="sel"><td><span class="code">SH-ES-202609-00052</span></td>
<td>滨江壹号 5幢 901<span class="sub">滨江区 · 钱塘江畔</span></td>
<td>98.2 ㎡</td><td>920 万</td><td><span class="tag gold">独家</span></td><td>王强</td>
<td class="op"><a>查看</a><a>编辑</a></td></tr>
<tr><td><span class="code">SH-ES-202609-00058</span></td>
<td>绿城桂语 2幢 1503<span class="sub">西湖区 · 文三路</span></td>
<td>142.6 ㎡</td><td>1,420 万</td><td><span class="tag wn">维护中</span></td><td>张伟</td>
<td class="op"><a>查看</a><a>编辑</a></td></tr>
<tr><td><span class="code">SH-ES-202609-00063</span></td>
<td>万科未来 1幢 608<span class="sub">余杭区 · 未来科技城</span></td>
<td>89.0 ㎡</td><td>610 万</td><td><span class="tag er">已下架</span></td><td>赵磊</td>
<td class="op"><a>查看</a><a class="dan">删除</a></td></tr>
<tr><td><span class="code">SH-ES-202609-00071</span></td>
<td>滨江壹号 2幢 1101<span class="sub">滨江区 · 钱塘江畔</span></td>
<td>110.4 ㎡</td><td>1,080 万</td><td><span class="tag nu">草稿</span></td><td>李敏</td>
<td class="op"><a>查看</a><a>编辑</a></td></tr>
</tbody>
</table>
<div class="pager">共 1,284 条
<span class="pg on">1</span><span class="pg">2</span><span class="pg">3</span>
<span class="pg">…</span><span class="pg">65</span>
<span style="margin-left:auto"></span>
<span style="font-size:11.5px">每页 20 条</span>
</div>
</div>
<div class="alerts">
<div class="al i"><span>ℹ</span><span><b>提示:</b>该房源委托期将于 7 天后到期,请及时续签。</span></div>
<div class="al s"><span>✓</span><span><b>成功:</b>房源 SH-ES-202609-00037 已提交检核。</span></div>
<div class="al w"><span>⚠</span><span><b>警告:</b>钥匙编号与系统记录不一致,请核对。</span></div>
<div class="al e"><span>✕</span><span><b>错误:</b>售价不能低于政府指导价,请修改后重试。</span></div>
</div>
<div class="card" style="padding:13px;display:flex;gap:9px;flex-wrap:wrap;align-items:center">
<button class="btn pri">主按钮</button>
<button class="btn pri h">悬停态</button>
<button class="btn pri a">按下态</button>
<button class="btn gho">次要按钮</button>
<button class="btn dan">危险操作</button>
<button class="btn tx">文字按钮</button>
<button class="btn dis">禁用</button>
</div>
</div>
</div>
</div>`;
// 元素级对照(每行一个元素,三列并排)
const EL_ROWS = [
['主按钮', t => '<div class="demo" data-theme="' + t + '"><button class="btn pri">查询房源</button></div>'],
['次要按钮', t => '<div class="demo" data-theme="' + t + '"><button class="btn gho">高级筛选</button></div>'],
['危险按钮', t => '<div class="demo" data-theme="' + t + '"><button class="btn dan">删除房源</button></div>'],
['状态标签', t => '<div class="demo" data-theme="' + t + '"><span class="tag ok">有效</span> <span class="tag wn">维护中</span> <span class="tag er">已下架</span> <span class="tag pri">待检核</span> <span class="tag gold">独家</span></div>'],
['表单', t => '<div class="demo" data-theme="' + t + '"><div class="f" style="width:150px"><label>房源编码</label><input placeholder="请输入"></div></div>'],
['表单错误态', t => '<div class="demo" data-theme="' + t + '"><div class="f err" style="width:150px"><label>维护人</label><input value="张"><span class="msg">请输入完整姓名</span></div></div>'],
['表头 / 行', t => '<div class="demo" data-theme="' + t + '"><table class="tbl" style="width:290px"><thead><tr><th>房源编码</th><th>状态</th></tr></thead><tbody><tr><td><span class="code">SH-...037</span></td><td><span class="tag ok">有效</span></td></tr><tr class="sel"><td><span class="code">SH-...052</span></td><td><span class="tag gold">独家</span></td></tr></tbody></table></div>'],
['分页 / 页签', t => '<div class="demo" data-theme="' + t + '"><div class="tabs" style="padding:0"><a class="on">全部</a><a>有效</a></div><div class="pager" style="padding:9px 0"><span class="pg on">1</span><span class="pg">2</span><span class="pg">3</span></div></div>'],
['提示条', t => '<div class="demo" data-theme="' + t + '"><div class="al w"><span>⚠</span><span>钥匙编号不一致,请核对。</span></div></div>']
];
const posOf = t => THEMES.findIndex(x => x.id === t) + 2; // 第 1 列是元素名
const html = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<title>甄房业务系统 · 色彩体系选型对比(Carbon / Fiori / Ant Design)</title>
<style>${CSS}</style></head><body><div class="wrap">
<h1>甄房业务系统 · <b>色彩体系选型对比</b></h1>
<div class="lead">
面向「经纪人高频长时使用」的业务平台,从全球主流 SaaS / ERP 设计系统中筛选出综合评分最高的三套,
各自结合已定稿的 LOGO(框内 Forte Z + 甄房墨蓝 <code>#0C2340</code>)开发完整界面色彩方案。<br>
三套方案的界面结构完全相同,只替换色彩令牌 —— 保证对比公平。<b>所有对比度数值由页面运行时真实计算</b>,非人工填写。
</div>
<h2>一、调研结论<span>数据来源:三家官方设计系统文档</span></h2>
<p class="note">筛选维度与权重:数据密度适配 25% | 长时使用舒适度 20% | 无障碍可推导性 15% | 品牌融合度 15% | 落地迁移成本 15% | 行业验证广度 10%。<br>
注意:「无障碍」一列评的是<b>体系提供了多少可推导的对比度规则</b>,与默认色盘的实际通过率并不同向 —— Carbon 规则最硬,但它把 Yellow 30 定位成警报条底色,被当文字色用就会不达标;AntD 规则最少,默认色盘实测反而是问题最多的。所以下面每一套都给出<b>实测通过率</b>与<b>生产落地必改项</b>。</p>
<table class="cmp">
<thead><tr><th>体系</th><th>定位与代表产品</th><th class="num">密度25%</th><th class="num">舒适20%</th><th class="num">无障碍15%</th><th class="num">品牌15%</th><th class="num">成本15%</th><th class="num">验证10%</th><th class="num">加权总分</th></tr></thead>
<tbody>
${THEMES.map(t => `<tr><td><b>${t.sys}</b></td><td style="color:#98a1b3">${
t.id === 'carbon' ? 'IBM 全线产品 · 数据密集型企业级标杆'
: t.id === 'fiori' ? 'SAP S/4HANA、Ariba 等 ERP 全线'
: '阿里/蚂蚁中后台生态 · 国内事实标准'}</td>
${['dense','comfort','a11y','brand','cost','proven'].map(k => '<td class="num">' + t.score[k].toFixed(1) + '</td>').join('')}
<td class="num tot">${t.total.toFixed(2)}</td></tr>`).join('')}
</tbody></table>
<div class="src">
依据:Carbon 官方明确「Neutral Gray family is dominant」并规定「避免使用中间调」,Blue 60 <code>#0f62fe</code> 为全局 primary action,四套官方主题(White / Gray 10 / Gray 90 / Gray 100);<br>
Fiori Horizon 提供 S0–S8 表面、T1–T9 文字、边框、语义、强调五组语义令牌,并给出「相隔 4 级 = 3:1、相隔 5 级 = 4.5:1」的可推导对比度规则;<br>
Ant Design 5 以 <code>colorPrimary #1677ff</code> 为种子令牌,色板由算法生成 12 色 × 10 阶,第 6 阶满足 WCAG 4.5:1。<br>
参考:<a href="https://carbondesignsystem.com/elements/color/tokens/">Carbon Color Tokens</a> ·
<a href="https://experience.sap.com/fiori-design-web/v1-110/theming/">SAP Fiori Theming</a> ·
<a href="https://ant-design.antgroup.com/docs/spec/colors-cn">Ant Design 色彩</a>
</div>
<h2>二、三套方案的完整界面</h2>
<p class="note">
核心策略:<b>品牌色与交互色分离</b> —— 三套方案都把甄房墨蓝 <code>#0C2340</code> 固定在品牌/导航区(侧栏、Shell Bar、登录页),
交互色各自沿用体系的官方主色(<code>#0F62FE</code> / <code>#0070F2</code> / <code>#1677FF</code>)。
这样既保住品牌识别,又拿到该体系已验证的交互反馈与无障碍保障。
</p>
<div class="sumline" id="wcag-summary"></div>
${THEMES.map(t => `
<div class="plan${t.final ? ' final' : ''}">
<div class="hd"><h3>${t.name}</h3><span class="sys">${t.sys}</span>${t.final ? '<span class="finalmark">✓ 已定稿</span>' : ''}<span class="sc">加权 ${t.total.toFixed(2)} / 10</span></div>
<div class="d">${t.desc}</div>
<div class="demo" data-theme="${t.id}">${demoBody()}</div>
<h3>色彩令牌</h3>
<div class="tok">
${t.tags.map(([n, v, r]) => '<div class="tk"><i style="background:' + v + '"></i><span class="n"><b>' + n + '</b><code>' + v + '</code></span><span class="r" style="color:#7f8899;border-color:rgba(255,255,255,.14)">' + r + '</span></div>').join('')}
</div>
<h3>对比度实测(WCAG,AA 正文 4.5:1 / AA 大字与图形 3:1)</h3>
<div class="tok" id="wcag-${t.id}"></div>
<div class="pc">
<div><h4 style="color:#5ee08a">优点</h4><ul class="good">${t.pros.map(x => '<li>' + x + '</li>').join('')}</ul></div>
<div><h4 style="color:#f0a55c">代价与不足</h4><ul class="bad">${t.cons.map(x => '<li>' + x + '</li>').join('')}</ul></div>
</div>
<div class="pc" style="grid-template-columns:1fr"><div><h4 style="color:#8ab4f8">适用场景</h4><ul class="fit">${t.fit.map(x => '<li>' + x + '</li>').join('')}</ul></div></div>
<h3>生产落地必改项<span style="font-size:11px;font-weight:400;color:#8b94a7;margin-left:8px">依据上表实测结果</span></h3>
${t.fixes.length
? '<table class="cmp fix"><thead><tr><th>用途</th><th>原值</th><th>建议改为</th><th>理由</th></tr></thead><tbody>' +
t.fixes.map(f => '<tr><td><b>' + f[0] + '</b></td><td><code>' + f[1] + '</code></td><td><code>' + f[2] +
'</code></td><td style="color:#98a1b3">' + f[3] + '</td></tr>').join('') + '</tbody></table>'
: '<div class="fixok">本方案 12 项对比度检查全部达标,无需为无障碍做任何色值改动 —— 三套中唯一「开箱即合规」的方案。</div>'}
</div>`).join('')}
<h2>三、主要界面元素横向对照<span>同一元素 · 三套色彩并排</span></h2>
<div class="elcmp"><table class="el">
<thead><tr><th>元素</th>${THEMES.map(t => '<th>' + t.name.split('·')[1].trim() + '</th>').join('')}</tr></thead>
<tbody>
${EL_ROWS.map(([name, fn]) => '<tr><td>' + name + '</td>' + THEMES.map(t => '<td>' + fn(t.id) + '</td>').join('') + '</tr>').join('')}
</tbody>
</table></div>
<h2>四、高频长时使用的统一优化层<span>三套方案共用</span></h2>
<p class="note">这是针对「经纪人每天连续使用 6-8 小时」这一前提,在三套体系之上叠加的四条约束。三套方案均已落实。</p>
<table class="cmp">
<thead><tr><th>优化项</th><th>做法</th><th>解决什么</th></tr></thead>
<tbody>
<tr><td><b>深色品牌侧栏</b></td><td>侧栏统一用 <code>#0C2340</code> 而非浅灰</td><td>减少整屏亮度、形成稳定视觉锚点;浅灰侧栏在长时间使用中会与内容区「糊在一起」,找不到导航边界</td></tr>
<tr><td><b>斑马纹极低对比</b></td><td>条纹底色与白底差值控制在 2–3%(<code>#F4F4F4</code>/<code>#F5F6F7</code>/<code>#F5F5F5</code>)</td><td>帮助横向追行不串行,但不会产生条纹眩光;这是数据表最高频的疲劳源</td></tr>
<tr><td><b>语义色降饱和</b></td><td>状态标签用「描边+浅底」而非实心色块,浅底透明度压到 8–12%</td><td>一屏内可能有几十个状态标签,实心高饱和色块是最大的刺眼来源</td></tr>
<tr><td><b>金色只做点</b></td><td>品牌金 <code>#C6A15B</code> 只用于 logo、关键数字、选中指示条,占比 ≤5%</td><td>保住品牌质感,同时避免金色大面积铺陈显廉价</td></tr>
</tbody></table>
<h2>五、选型建议</h2>
<p class="note"><b style="color:#5ee08a">本次结论:已定稿 方案 B · Fiori 商务派</b>(2026-09-19)。定稿理由:三套中唯一 12/12 对比度开箱合规、语义令牌分层最完整、选中态用 8% 蓝底而非实色块(长时使用最不刺眼),且官方 Quick Theming 就是「替换 logo + 主品牌色」,与已定稿的甄房墨蓝最契合。下表保留完整选型逻辑,供后续复盘与其他产品线参考。</p>
<table class="cmp">
<thead><tr><th>如果……</th><th>建议</th><th>理由</th></tr></thead>
<tbody>
<tr><td>团队人少、迭代快,希望最快落地并降低长期维护成本</td><td><b>方案 C · AntD 亲和派</b></td><td>迁移成本最低、中文生态最全;代价是要按「生产落地必改项」把 6 处色值各下移一阶(改完视觉几乎无差别,但差别在无障碍上)</td></tr>
<tr><td>需要严谨的层级与无障碍,且能接受工程感外观</td><td><b>方案 A · Carbon 严谨派</b></td><td>灰阶分层最严谨、对比度有官方硬约束;数据密度最高。仅「黄色被当文字色」与「圆角偏硬」两处需按建议调整</td></tr>
<tr><td>交易与佣金等强流程模块、或需与财务/ERP 打通</td><td><b>方案 B · Fiori 商务派</b></td><td>状态与选中态表达最清楚,对比度规则可推导,符合企业级规范;三套中唯一 12/12 开箱合规</td></tr>
<tr><td>想要「一次到位」但不想赌</td><td><b>混合:Carbon 骨架 + AntD 语义色</b></td><td>用 Carbon 的灰阶分层与密度打底,语义色采用国内用户熟悉的 AntD 色相并降饱和(沿用 C 的必改项色值);这是我推荐的落地路径</td></tr>
</tbody></table>
</div>
<script>
// ── 令牌数据(由 Node 侧注入)──
var THEMES = ${JSON.stringify(THEMES.map(t => ({ id: t.id, name: t.name, sys: t.sys, total: t.total })))};
// ── 真实计算 WCAG 对比度 ──────────────────────────────────────
function toRgb(c){
if (c.indexOf('rgba') === 0) {
return c.slice(5, c.length - 1).split(',').slice(0, 3).map(Number);
}
var h = c.replace('#', '');
if (h.length === 3) h = h.split('').map(function(x){ return x + x; }).join('');
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
}
function lum(c){
var v = toRgb(c).map(function(x){ x/=255; return x <= .03928 ? x/12.92 : Math.pow((x+.055)/1.055, 2.4); });
return .2126*v[0] + .7152*v[1] + .0722*v[2];
}
function ratio(a,b){
var l1 = lum(a), l2 = lum(b);
return (Math.max(l1,l2) + .05) / (Math.min(l1,l2) + .05);
}
function blend(fg, bg, alpha){
var f = toRgb(fg), b = toRgb(bg);
return '#' + f.map(function(x,i){ return Math.round(x*alpha + b[i]*(1-alpha)).toString(16).padStart(2,'0'); }).join('');
}
var PAIRS = [
['一级文字 / 白底', 'text1', '#FFFFFF', 4.5],
['二级文字 / 白底', 'text2', '#FFFFFF', 4.5],
['三级文字 / 白底', 'text3', '#FFFFFF', 4.5],
['一级文字 / 次级底', 'text1', 'bg2', 4.5],
['白字 / 交互主色(按钮)', '#FFFFFF', 'primary', 4.5],
['主色文字 / 白底(链接)', 'primary', '#FFFFFF', 4.5],
['成功 / 白底', 'ok', '#FFFFFF', 3],
['警告 / 白底', 'warn', '#FFFFFF', 3],
['错误 / 白底', 'err', '#FFFFFF', 3],
['错误 / 白底(正文)', 'err', '#FFFFFF', 4.5],
['白字 / 品牌墨蓝(侧栏)', '#FFFFFF', 'brand', 4.5],
['侧栏二级字 / 品牌墨蓝', null, 'brand', 4.5, '#B9C2D0']
];
var SUMMARY = [];
THEMES.forEach(function(t){
var el = document.getElementById('wcag-' + t.id);
if (!el) return;
var dummy = document.querySelector('.demo[data-theme="' + t.id + '"]');
var cs = getComputedStyle(dummy);
var val = function(k){ return k.charAt(0) === '#' ? k : cs.getPropertyValue('--' + k).trim(); };
var out = [], bad = [];
PAIRS.forEach(function(p){
var fg = p[4] ? p[4] : val(p[1]); // p[4] 为直接色值覆盖(可选)
var bg = val(p[2]);
if (bg.indexOf('rgba') === 0) { bg = blend(bg, '#FFFFFF', parseFloat(bg.split(',')[3])); }
var r = ratio(fg, bg);
var req = p[3];
if (r < req) bad.push(p[0] + ' ' + r.toFixed(2) + ':1');
out.push('<div class="tk"><i style="background:' + bg + ';border-color:rgba(255,255,255,.2)"></i>' +
'<span class="n"><b>' + p[0] + '</b><code>' + r.toFixed(2) + ':1 要求 ≥' + req + ':1</code></span>' +
'<span class="r ' + (r >= req ? 'pass' : 'fail') + '">' + (r >= req ? '通过' : '未达标') + '</span></div>');
});
el.innerHTML = out.join('');
SUMMARY.push({ id: t.id, name: t.name, sys: t.sys, total: t.total, pass: PAIRS.length - bad.length, n: PAIRS.length, bad: bad });
});
var sumEl = document.getElementById('wcag-summary');
if (sumEl) {
sumEl.innerHTML = SUMMARY.map(function(s){
return '<div><b>' + s.sys + '</b> WCAG 实测 <em class="' + (s.bad.length ? 'no' : 'ok') + '">' +
s.pass + ' / ' + s.n + ' 通过</em>' + (s.bad.length ? ',需修正:' + s.bad.join('、') : ',全部达标') + '</div>';
}).join('');
}
</script>
</body></html>`;
fs.writeFileSync(path.join(HERE, 'index.html'), html);
console.log('已生成 theme/index.html ·', THEMES.length, '套方案');
THEMES.forEach(t => console.log(' ', t.name, '加权', t.total.toFixed(2)));
File diff suppressed because one or more lines are too long