// 甄房业务系统 · 三套色彩体系对比页
// 结构:一套 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) =>
'';
// ── 三套色彩令牌 ────────────────────────────────────────────────
// 说明:品牌墨蓝 #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, '&').replace(//g, '>');
// ── UI 演示结构(只写一次,三套主题注入同一份)──
const demoBody = () => `
| 房源编码 | 楼盘 / 房号 | 面积 | 售价 | 状态 | 维护人 | 操作 |
| SH-ES-202609-00037 |
滨江壹号 3幢 1802滨江区 · 钱塘江畔 |
128.5 ㎡ | 1,285 万 | 有效 | 张伟 |
查看编辑 |
| SH-ES-202609-00041 |
钱江新城 7幢 2201上城区 · 万象城 |
156.0 ㎡ | 1,860 万 | 待检核 | 李敏 |
查看编辑 |
| SH-ES-202609-00052 |
滨江壹号 5幢 901滨江区 · 钱塘江畔 |
98.2 ㎡ | 920 万 | 独家 | 王强 |
查看编辑 |
| SH-ES-202609-00058 |
绿城桂语 2幢 1503西湖区 · 文三路 |
142.6 ㎡ | 1,420 万 | 维护中 | 张伟 |
查看编辑 |
| SH-ES-202609-00063 |
万科未来 1幢 608余杭区 · 未来科技城 |
89.0 ㎡ | 610 万 | 已下架 | 赵磊 |
查看删除 |
| SH-ES-202609-00071 |
滨江壹号 2幢 1101滨江区 · 钱塘江畔 |
110.4 ㎡ | 1,080 万 | 草稿 | 李敏 |
查看编辑 |
ℹ提示:该房源委托期将于 7 天后到期,请及时续签。
✓成功:房源 SH-ES-202609-00037 已提交检核。
⚠警告:钥匙编号与系统记录不一致,请核对。
✕错误:售价不能低于政府指导价,请修改后重试。
`;
// 元素级对照(每行一个元素,三列并排)
const EL_ROWS = [
['主按钮', t => ''],
['次要按钮', t => ''],
['危险按钮', t => ''],
['状态标签', t => '有效 维护中 已下架 待检核 独家
'],
['表单', t => ''],
['表单错误态', t => ''],
['表头 / 行', t => '| 房源编码 | 状态 |
|---|
| SH-...037 | 有效 |
| SH-...052 | 独家 |
'],
['分页 / 页签', t => ''],
['提示条', t => '']
];
const posOf = t => THEMES.findIndex(x => x.id === t) + 2; // 第 1 列是元素名
const html = `
甄房业务系统 · 色彩体系选型对比(Carbon / Fiori / Ant Design)
甄房业务系统 · 色彩体系选型对比
面向「经纪人高频长时使用」的业务平台,从全球主流 SaaS / ERP 设计系统中筛选出综合评分最高的三套,
各自结合已定稿的 LOGO(框内 Forte Z + 甄房墨蓝 #0C2340)开发完整界面色彩方案。
三套方案的界面结构完全相同,只替换色彩令牌 —— 保证对比公平。所有对比度数值由页面运行时真实计算,非人工填写。
一、调研结论数据来源:三家官方设计系统文档
筛选维度与权重:数据密度适配 25% | 长时使用舒适度 20% | 无障碍可推导性 15% | 品牌融合度 15% | 落地迁移成本 15% | 行业验证广度 10%。
注意:「无障碍」一列评的是体系提供了多少可推导的对比度规则,与默认色盘的实际通过率并不同向 —— Carbon 规则最硬,但它把 Yellow 30 定位成警报条底色,被当文字色用就会不达标;AntD 规则最少,默认色盘实测反而是问题最多的。所以下面每一套都给出实测通过率与生产落地必改项。
| 体系 | 定位与代表产品 | 密度25% | 舒适20% | 无障碍15% | 品牌15% | 成本15% | 验证10% | 加权总分 |
${THEMES.map(t => `| ${t.sys} | ${
t.id === 'carbon' ? 'IBM 全线产品 · 数据密集型企业级标杆'
: t.id === 'fiori' ? 'SAP S/4HANA、Ariba 等 ERP 全线'
: '阿里/蚂蚁中后台生态 · 国内事实标准'} |
${['dense','comfort','a11y','brand','cost','proven'].map(k => '' + t.score[k].toFixed(1) + ' | ').join('')}
${t.total.toFixed(2)} |
`).join('')}
依据:Carbon 官方明确「Neutral Gray family is dominant」并规定「避免使用中间调」,Blue 60
#0f62fe 为全局 primary action,四套官方主题(White / Gray 10 / Gray 90 / Gray 100);
Fiori Horizon 提供 S0–S8 表面、T1–T9 文字、边框、语义、强调五组语义令牌,并给出「相隔 4 级 = 3:1、相隔 5 级 = 4.5:1」的可推导对比度规则;
Ant Design 5 以
colorPrimary #1677ff 为种子令牌,色板由算法生成 12 色 × 10 阶,第 6 阶满足 WCAG 4.5:1。
参考:
Carbon Color Tokens ·
SAP Fiori Theming ·
Ant Design 色彩
二、三套方案的完整界面
核心策略:品牌色与交互色分离 —— 三套方案都把甄房墨蓝 #0C2340 固定在品牌/导航区(侧栏、Shell Bar、登录页),
交互色各自沿用体系的官方主色(#0F62FE / #0070F2 / #1677FF)。
这样既保住品牌识别,又拿到该体系已验证的交互反馈与无障碍保障。
${THEMES.map(t => `
${t.name}
${t.sys}${t.final ? '✓ 已定稿' : ''}加权 ${t.total.toFixed(2)} / 10
${t.desc}
${demoBody()}
色彩令牌
${t.tags.map(([n, v, r]) => '
' + n + '' + v + '' + r + '
').join('')}
对比度实测(WCAG,AA 正文 4.5:1 / AA 大字与图形 3:1)
优点
${t.pros.map(x => '- ' + x + '
').join('')}
代价与不足
${t.cons.map(x => '- ' + x + '
').join('')}
适用场景
${t.fit.map(x => '- ' + x + '
').join('')}
生产落地必改项依据上表实测结果
${t.fixes.length
? '
| 用途 | 原值 | 建议改为 | 理由 |
' +
t.fixes.map(f => '| ' + f[0] + ' | ' + f[1] + ' | ' + f[2] +
' | ' + f[3] + ' |
').join('') + '
'
: '
本方案 12 项对比度检查全部达标,无需为无障碍做任何色值改动 —— 三套中唯一「开箱即合规」的方案。
'}
`).join('')}
三、主要界面元素横向对照同一元素 · 三套色彩并排
| 元素 | ${THEMES.map(t => '' + t.name.split('·')[1].trim() + ' | ').join('')}
${EL_ROWS.map(([name, fn]) => '| ' + name + ' | ' + THEMES.map(t => '' + fn(t.id) + ' | ').join('') + '
').join('')}
四、高频长时使用的统一优化层三套方案共用
这是针对「经纪人每天连续使用 6-8 小时」这一前提,在三套体系之上叠加的四条约束。三套方案均已落实。
| 优化项 | 做法 | 解决什么 |
| 深色品牌侧栏 | 侧栏统一用 #0C2340 而非浅灰 | 减少整屏亮度、形成稳定视觉锚点;浅灰侧栏在长时间使用中会与内容区「糊在一起」,找不到导航边界 |
| 斑马纹极低对比 | 条纹底色与白底差值控制在 2–3%(#F4F4F4/#F5F6F7/#F5F5F5) | 帮助横向追行不串行,但不会产生条纹眩光;这是数据表最高频的疲劳源 |
| 语义色降饱和 | 状态标签用「描边+浅底」而非实心色块,浅底透明度压到 8–12% | 一屏内可能有几十个状态标签,实心高饱和色块是最大的刺眼来源 |
| 金色只做点 | 品牌金 #C6A15B 只用于 logo、关键数字、选中指示条,占比 ≤5% | 保住品牌质感,同时避免金色大面积铺陈显廉价 |
五、选型建议
本次结论:已定稿 方案 B · Fiori 商务派(2026-09-19)。定稿理由:三套中唯一 12/12 对比度开箱合规、语义令牌分层最完整、选中态用 8% 蓝底而非实色块(长时使用最不刺眼),且官方 Quick Theming 就是「替换 logo + 主品牌色」,与已定稿的甄房墨蓝最契合。下表保留完整选型逻辑,供后续复盘与其他产品线参考。
| 如果…… | 建议 | 理由 |
| 团队人少、迭代快,希望最快落地并降低长期维护成本 | 方案 C · AntD 亲和派 | 迁移成本最低、中文生态最全;代价是要按「生产落地必改项」把 6 处色值各下移一阶(改完视觉几乎无差别,但差别在无障碍上) |
| 需要严谨的层级与无障碍,且能接受工程感外观 | 方案 A · Carbon 严谨派 | 灰阶分层最严谨、对比度有官方硬约束;数据密度最高。仅「黄色被当文字色」与「圆角偏硬」两处需按建议调整 |
| 交易与佣金等强流程模块、或需与财务/ERP 打通 | 方案 B · Fiori 商务派 | 状态与选中态表达最清楚,对比度规则可推导,符合企业级规范;三套中唯一 12/12 开箱合规 |
| 想要「一次到位」但不想赌 | 混合:Carbon 骨架 + AntD 语义色 | 用 Carbon 的灰阶分层与密度打底,语义色采用国内用户熟悉的 AntD 色相并降饱和(沿用 C 的必改项色值);这是我推荐的落地路径 |
`;
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)));