chore: directory migration - gateway→server, web→client/electron

This commit is contained in:
2026-09-08 19:16:35 +08:00
parent fd9f99a3f9
commit f9d757b5e5
243 changed files with 5095 additions and 228 deletions

View File

@ -0,0 +1,84 @@
/**
* 强调色可见性验收:真实渲染 + 取实际计算色 + 算 WCAG 对比度。
*
* 结构性断言(test/theme.test.mjs)只能保证变量存在、档位达标;
* 「按钮到底看得见吗」必须在真浏览器里量 —— 上一次那个 bug 正是
* 所有静态检查都过、只有肉眼能发现。
*/
import { openApp, WIDE } from './narrow-probe-helper.mjs';
const srgb = c => { c /= 255; return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4; };
const L = ([r, g, b]) => 0.2126 * srgb(r) + 0.7152 * srgb(g) + 0.0722 * srgb(b);
const ratio = (a, b) => { const l1 = L(a), l2 = L(b); const [h, o] = l1 > l2 ? [l1, l2] : [l2, l1]; return (h + 0.05) / (o + 0.05); };
const parse = s => (s.match(/\d+/g) || []).slice(0, 3).map(Number);
// 把一批强调色类名注入页面,量它们的实际计算值
// hover 档(bg-red-700 / bg-blue-700)不在这里:源码里它们只以 `hover:` 变体
// 出现,Tailwind 因此不生成基础类 —— 探它会得到透明背景,那是探针的假阳性
// 而不是真 bug。它们由 theme.test.mjs 的档位断言覆盖(--s-* 两模式同值)。
const PROBES = [
// [类名组合, 说明, 期望最低对比度]
['bg-red-600 text-white', '危险实心按钮(确认归档 / 删除)', 3.0],
['bg-red-500 text-white', '未读徽标', 3.0],
['bg-green-600 text-white', '同意按钮', 3.0],
['bg-orange-700 text-white', '待决策徽标', 4.5],
['bg-blue-600 text-white', '主按钮', 4.5],
['bg-red-50 text-red-600', '错误提示条', 4.5],
['bg-red-50 text-red-700', '危险区块文字', 4.5],
['bg-green-50 text-green-700', '成功提示条', 4.5],
['bg-orange-100 text-orange-700', '橙色 chip', 4.5],
['bg-amber-100 text-amber-700', '警告 chip', 4.5],
['bg-amber-50 text-amber-800', '警告条', 4.5],
['bg-yellow-100 text-yellow-700', '黄色 chip', 4.5],
['bg-blue-50 text-blue-700', '信息条', 4.5],
['bg-red-100 text-red-700', '红色 chip', 4.5],
['bg-orange-200 text-orange-800', '深橙 chip', 4.5],
['bg-white text-gray-900', '卡片正文', 4.5],
['bg-white text-gray-500', '卡片次要文字', 4.5],
];
const { browser, page } = await openApp(WIDE);
let fail = 0;
for (const mode of ['light', 'dark']) {
await page.evaluate(m => {
document.documentElement.classList.toggle('dark', m === 'dark');
let host = document.getElementById('__probe');
if (host) host.remove();
host = document.createElement('div');
host.id = '__probe';
host.style.position = 'fixed';
host.style.top = '0';
host.style.left = '0';
host.style.zIndex = '99999';
document.body.appendChild(host);
}, mode);
console.log(`\n─── ${mode === 'dark' ? '深色' : '浅色'} ───`);
for (const [cls, label, min] of PROBES) {
const got = await page.evaluate(c => {
const host = document.getElementById('__probe');
host.innerHTML = `<span id="__p" class="${c}">测试</span>`;
const el = document.getElementById('__p');
const s = getComputedStyle(el);
return { bg: s.backgroundColor, fg: s.color };
}, cls);
// 透明背景 = 声明失效(正是上次那个 bug 的指纹)
const transparent = /rgba\(0,\s*0,\s*0,\s*0\)|transparent/.test(got.bg);
if (transparent) {
console.log(` 失败 ${label} — 背景透明(${cls} 的 background-color 声明失效)`);
fail++;
continue;
}
const r = ratio(parse(got.fg), parse(got.bg));
const ok = r >= min;
if (!ok) fail++;
console.log(` ${ok ? '通过' : '失败'} ${label.padEnd(22)} ${r.toFixed(2)}:1 (需 ${min}) ${got.bg} / ${got.fg}`);
}
}
await page.evaluate(() => document.getElementById('__probe')?.remove());
await browser.close();
console.log(fail ? `\n!! ${fail} 项不达标` : '\n全部达标');
process.exit(fail ? 1 : 0);