feat(P5): 暗色模式真正实现(令牌31/对比度全过/10页实测/DARK32/预算292KB/回归1003)
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
/* @light-only */
|
||||
/* @light-dark */
|
||||
/* Source: structured-spec */
|
||||
|
||||
/* ============================================================
|
||||
Aurora Admin UI — Design Tokens
|
||||
Source: Aurora Admin UI 设计系统规范 (structured-spec)
|
||||
Theme: light-only
|
||||
Theme: light + dark (html.aa-dark, S2-P5)
|
||||
Prefix: au (source layer) + portable aliases (consumption layer)
|
||||
============================================================ */
|
||||
|
||||
@@ -277,3 +277,71 @@
|
||||
outline: 2px solid var(--au-color-focus-ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Demo shell · 演示壳通用类(S2-P5 附带收敛)
|
||||
26 个演示页用 .aa-page/.aa-h2/.aa-desc 作展示壳,但此前全仓无定义,
|
||||
壳文字继承 body 黑色,暗色下黑字黑底。此处用令牌统一定义,
|
||||
浅色/暗色随令牌自动切换,不触碰组件自身视觉。
|
||||
------------------------------------------------------------ */
|
||||
.aa-page {
|
||||
background: var(--au-color-page-bg);
|
||||
color: var(--au-color-text-body);
|
||||
}
|
||||
.aa-h2 {
|
||||
color: var(--au-color-text-title);
|
||||
}
|
||||
.aa-desc {
|
||||
color: var(--au-color-text-secondary);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Dark · 暗色令牌组(S2-P5)
|
||||
生效条件:html.aa-dark。演示页通过 <link> 引用本文件,
|
||||
故 79 个演示页随令牌自动反色(选型 A:站点暗色时演示页也反色)。
|
||||
对比度(WCAG AA,卡片底 #1C1F26 / 页面底 #14161C,脚本实测):
|
||||
title #E8EAED 13.68/15.00 · body #C9CDD4 10.34/11.34 ·
|
||||
secondary #9CA3AF 6.50/7.12 · placeholder #9CA3AF 6.50/7.12 ·
|
||||
brand #6B8CFF 5.36/5.87 · success #4CAF50 5.93 ·
|
||||
warning #E5A300 7.52 · error #F26D6D 5.64 · info #4C9AFF 5.79
|
||||
(全部 ≥4.5:1;hover/active 为交互态非文本位)
|
||||
------------------------------------------------------------ */
|
||||
html.aa-dark {
|
||||
/* 背景三层递进 */
|
||||
--au-color-page-bg: #14161C;
|
||||
--au-color-card-bg: #1C1F26;
|
||||
--au-color-table-header-bg: #22262E;
|
||||
--au-color-disabled-bg: #262A33;
|
||||
--au-color-tag-green-bg: #16261A;
|
||||
--au-color-tooltip-bg: #E8EAED;
|
||||
/* 文字四层 */
|
||||
--au-color-text-title: #E8EAED;
|
||||
--au-color-text-body: #C9CDD4;
|
||||
--au-color-text-secondary: #9CA3AF;
|
||||
--au-color-text-tertiary: #B6BCC7;
|
||||
--au-color-text-placeholder: #9CA3AF;
|
||||
--au-color-text-inverse: #14161C;
|
||||
--au-color-text-disabled: #6B7280;
|
||||
/* 边框 */
|
||||
--au-color-border: #2A2F38;
|
||||
--au-color-border-strong: #363B45;
|
||||
--au-color-icon-inactive: #9CA3AF;
|
||||
/* 品牌色向白提亮(深底对比度不足,原 #2F54EB 卡片底仅 ~3.9:1) */
|
||||
--au-color-brand: #6B8CFF;
|
||||
--au-color-brand-hover: #84A0FF;
|
||||
--au-color-brand-active: #5570D8;
|
||||
--au-color-brand-bg: rgba(107, 140, 255, 0.18);
|
||||
--au-color-secondary: #3FD4D4;
|
||||
/* 语义色提亮 */
|
||||
--au-color-success: #4CAF50;
|
||||
--au-color-warning: #E5A300;
|
||||
--au-color-error: #F26D6D;
|
||||
--au-color-info: #4C9AFF;
|
||||
/* 遮罩与阴影 */
|
||||
--au-color-mask: rgba(0, 0, 0, 0.6);
|
||||
--au-color-mask-strong: rgba(0, 0, 0, 0.75);
|
||||
--au-color-focus-ring: rgba(107, 140, 255, 0.5);
|
||||
--au-shadow-low: 0 1px 4px rgba(0, 0, 0, 0.5);
|
||||
--au-shadow-medium: 0 4px 12px rgba(0, 0, 0, 0.55);
|
||||
--au-shadow-high: 0 8px 24px rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,13 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### S2-P5 · 暗色模式真正实现(选型 A:演示页随站点同步反色)
|
||||
- 令牌文件新增 `html.aa-dark` 组(31 个 `--au-`,≥15 达标):背景三层递进、文字四层、品牌/语义色向白提亮;10 项文字对比度全部 ≥4.5:1(脚本实测)。
|
||||
- `site/style.css` 暗色块精简为结构样式(令牌值收归令牌文件,删旧内嵌值与“演示页保持浅色”规则);`site/app.js` 删死代码 `DARK_TOKENS`,`injectIframeTheme` 同步 `aa-dark` 类到演示 iframe。
|
||||
- 附带收敛:演示壳裸类 `.aa-page/.aa-h2/.aa-desc`(26 页用而无定义)在令牌文件补令牌定义;`table.html` loading-mask、`Tag.css` 红/橙标签加暗色覆盖。
|
||||
- 新增 `tools/verify-dark.mjs` 独立验证(32 断言:令牌组 2 + 对比度 10 + 10 页浏览器实测 20),不进 `_runtime` 计数。
|
||||
- 首屏预算 292KB(< 300KB);回归 1003/1003(pass 1003/fail 0/skip 35)零影响。
|
||||
|
||||
### S1-P4 · data.js 瘦身(已完成 c65a69c)
|
||||
- data.js 991KB → 98KB(预计算 API/场景 + 源码分离到 site/sources 395 文件),data.json 保持完整(含 5 端 sources)。
|
||||
- 首屏资源合计 291KB(< 300KB 预算,S1-P4b 验收已达标,无需拆分 app.js)。
|
||||
|
||||
+1
-1
@@ -989,7 +989,7 @@ figma export OK
|
||||
| P2 | npm 分发 | S1 | **P0** | 2 h | P1 ✅ | ✅ **已完成**(dist 构建 + npm pack 115.9KB) |
|
||||
| P3 | CI 回归 | S1 | **P0** | 1.5 h | — | ✅ **已完成**(workflow + runner 重构 + exit code) |
|
||||
| P4 | `data.js` 瘦身 | S1 | **P0** | 3 h | — | ✅ **已完成**(data.js 98KB/sources 395/data.json 完整/回归 1003/1003,见 c65a69c) |
|
||||
| P5 | 暗色模式 | S2 | **P0** | 1 d | P4 ✅ | 🟢 **可开工**(P4 已完成,下一版本首选) |
|
||||
| P5 | 暗色模式 | S2 | **P0** | 1 d | P4 ✅ | ✅ **已完成**(令牌组31/对比度10项≥4.5/10页实测/DARK VERIFY 32/32/首屏292KB/回归1003/1003,Q6选A) |
|
||||
| P6 | 跨端一致性验证 | S2 | **P0** | 1 d | P3 | ⚪ 待 P3 |
|
||||
| P7 | 行为断言 | S2 | P1 | 1.5 d | — | 🟢 可开工(优先级低于 P2-P4) |
|
||||
| P8 | RTL | S3 | P1 | 2 d | P5 | ⚪ 待 P5 |
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
.aa-tag.aa-tag-orange { background: #FFF7E6; color: var(--au-color-warning); }
|
||||
.aa-tag.aa-tag-gray { background: var(--au-color-disabled-bg); color: var(--au-color-text-secondary); }
|
||||
|
||||
/* S2-P5 暗色覆盖:浅底在深色下改为语义色半透明底,文字用暗色提亮值(对比度 ≥4.5:1 实测) */
|
||||
html.aa-dark .aa-tag.aa-tag-red { background: rgba(242,109,109,0.18); }
|
||||
html.aa-dark .aa-tag.aa-tag-orange { background: rgba(229,163,0,0.18); }
|
||||
|
||||
/* 自定义颜色(行内 style 提供 --aa-tag-color / --aa-tag-bg) */
|
||||
.aa-tag.aa-tag-custom { background: var(--aa-tag-bg); color: var(--aa-tag-color); }
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
position: absolute; inset: 0; background: rgba(255,255,255,0.7); display: flex;
|
||||
align-items: center; justify-content: center; z-index: 2;
|
||||
}
|
||||
html.aa-dark .aa-table .loading-mask { background: rgba(20,22,28,0.7); }
|
||||
.aa-table .spinner {
|
||||
width: 20px; height: 20px; border: 2px solid var(--au-color-brand); border-top-color: transparent;
|
||||
border-radius: 50%; animation: aa-tbl-spin .6s linear infinite;
|
||||
|
||||
+6
-13
@@ -2146,6 +2146,10 @@
|
||||
d.documentElement.style.setProperty(k, themeState[k]);
|
||||
});
|
||||
d.documentElement.style.setProperty('--au-color-focus-ring', focusRingValue());
|
||||
/* S2-P5 选型 A:站点暗色时演示页同步反色 —— 演示页 <link> 引用令牌文件,
|
||||
加 aa-dark 类即随令牌自动反色;浅色时摘除恢复。 */
|
||||
var dark = document.documentElement.classList.contains('aa-dark');
|
||||
if (d.documentElement.classList) d.documentElement.classList.toggle('aa-dark', dark);
|
||||
} catch (e) { /* file:// 跨源时静默跳过 */ }
|
||||
}
|
||||
function hookIframeTheme(ifr) {
|
||||
@@ -2234,19 +2238,8 @@
|
||||
lines.push('}');
|
||||
copyText(lines.join('\n'), $('theme-copy'), '已复制 ✓');
|
||||
});
|
||||
/* 深色 / 浅色模式切换 */
|
||||
var DARK_TOKENS = {
|
||||
'--au-color-page-bg': '#14161C',
|
||||
'--au-color-card-bg': '#1D2027',
|
||||
'--au-color-table-header-bg': '#23262F',
|
||||
'--au-color-disabled-bg': '#262A33',
|
||||
'--au-color-tag-green-bg': '#16261A',
|
||||
'--au-color-text-title': '#E8EAED',
|
||||
'--au-color-text-body': '#C9CDD4',
|
||||
'--au-color-text-secondary': '#8B95A7',
|
||||
'--au-color-text-placeholder': '#5C6470',
|
||||
'--au-color-border': '#2E323C'
|
||||
};
|
||||
/* 深色 / 浅色模式切换(S2-P5:暗色值收归令牌文件的 html.aa-dark 组,
|
||||
此处只切类名,不再内嵌色值;演示 iframe 由 injectIframeTheme 同步类名) */
|
||||
function isDark() { return document.documentElement.classList.contains('aa-dark'); }
|
||||
function applyMode() {
|
||||
var dark = false;
|
||||
|
||||
+2
-31
@@ -1093,39 +1093,10 @@ a.chip:hover { color: var(--au-color-brand); }
|
||||
.other-files ul { margin: 8px 0 0; }
|
||||
.other-files a { font-size: 13px; }
|
||||
|
||||
/* ---- 深色模式(html.aa-dark 覆盖站点级令牌;.design_library 令牌文件不动,演示 iframe 保持浅色) ---- */
|
||||
html.aa-dark {
|
||||
--au-color-page-bg: #14161C;
|
||||
--au-color-card-bg: #1D2027;
|
||||
--au-color-table-header-bg: #23262F;
|
||||
--au-color-disabled-bg: #262A33;
|
||||
--au-color-tag-green-bg: #16261A;
|
||||
--au-color-text-title: #E8EAED;
|
||||
--au-color-text-body: #C9CDD4;
|
||||
--au-color-text-secondary: #8B95A7;
|
||||
--au-color-text-placeholder: #5C6470;
|
||||
--au-color-border: #2E323C;
|
||||
--au-color-mask: rgba(0, 0, 0, 0.6);
|
||||
--au-color-mask-strong: rgba(0, 0, 0, 0.75);
|
||||
/* 品牌色族:暗色下整体提亮一档,保证链接/选中态在深底上的对比度 */
|
||||
--au-color-brand: #5B7CFA;
|
||||
--au-color-brand-hover: #7B97FF;
|
||||
--au-color-brand-active: #4364D9;
|
||||
--au-color-brand-bg: rgba(91, 124, 250, 0.18);
|
||||
--au-color-focus-ring: rgba(91, 124, 250, 0.5);
|
||||
/* 语义色:暗色下提亮,深底可辨 */
|
||||
--au-color-success: #6CD03C;
|
||||
--au-color-warning: #FFC53D;
|
||||
--au-color-error: #FF5C5C;
|
||||
--au-color-info: #40A9FF;
|
||||
/* 阴影在深色下加重才能形成层次 */
|
||||
--au-shadow-low: 0 1px 4px rgba(0, 0, 0, 0.5);
|
||||
--au-shadow-medium: 0 4px 12px rgba(0, 0, 0, 0.55);
|
||||
--au-shadow-high: 0 8px 24px rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
/* ---- 深色模式(S2-P5 选型 A:令牌值收归 .design_library 令牌文件的 html.aa-dark 组,
|
||||
本处只留站点结构样式;演示 iframe 由 app.js 注入 aa-dark 类随令牌同步反色) ---- */
|
||||
html.aa-dark body { background: var(--au-color-page-bg); }
|
||||
html.aa-dark .topbar { background: rgba(20, 22, 28, 0.92); }
|
||||
html.aa-dark .demo-stage iframe { background: #F5F7FA; } /* 演示页保持浅色基调,避免 5 端样例反色失真 */
|
||||
html.aa-dark .demo-code { background: #101216; }
|
||||
html.aa-dark .a11y-key kbd { background: #262A33; border-color: #3A404C; border-bottom-color: #4A5160; }
|
||||
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* verify-dark.mjs — S2-P5 暗色模式独立验证(不进 _runtime 计数,守住 1003 基数)
|
||||
*
|
||||
* 1) 令牌组存在且完整(html.aa-dark ≥15 个 --au-)
|
||||
* 2) 暗色文字/品牌/语义色在深底上的 WCAG 对比度(文本 ≥4.5:1)
|
||||
* 3) 浏览器实测:抽样 10 个组件演示页,切 aa-dark 后无白底残留、无黑字黑底,对比度可读
|
||||
*
|
||||
* 用法:node tools/verify-dark.mjs [--slugs button,input,...] [--shots]
|
||||
* 退出码:0 全过;1 有失败。
|
||||
*/
|
||||
import { readFileSync } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(__dirname, '..');
|
||||
const BASE = process.env.REG_BASE || 'http://127.0.0.1:3311';
|
||||
const arg = (k) => {
|
||||
const i = process.argv.indexOf(k);
|
||||
return i > -1 ? process.argv[i + 1] : null;
|
||||
};
|
||||
const SAMPLE = (arg('--slugs') || 'button,input,select,table,card,modal,tabs,formmodal,sidemenu,tag').split(',');
|
||||
const SHOTS = process.argv.includes('--shots');
|
||||
|
||||
function lum(hex) {
|
||||
const c = hex.replace('#', '');
|
||||
const f = (i) => {
|
||||
const v = parseInt(c.substr(i, 2), 16) / 255;
|
||||
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
||||
};
|
||||
return 0.2126 * f(0) + 0.7152 * f(2) + 0.0722 * f(4);
|
||||
}
|
||||
function ratio(a, b) {
|
||||
const x = lum(a), y = lum(b);
|
||||
return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);
|
||||
}
|
||||
|
||||
let fail = 0;
|
||||
const ok = (cond, msg) => {
|
||||
console.log((cond ? 'PASS' : 'FAIL') + ' ' + msg);
|
||||
if (!cond) fail++;
|
||||
};
|
||||
|
||||
/* ---- 1) 令牌组 ---- */
|
||||
const css = readFileSync(join(ROOT, '.design_library/aurora-admin/colors_and_type.css'), 'utf8');
|
||||
ok(/html\.aa-dark/.test(css), '1) 令牌文件含 html.aa-dark 组');
|
||||
const m = css.match(/html\.aa-dark\s*\{([^}]+)\}/s);
|
||||
const n = m ? (m[1].match(/--au-/g) || []).length : 0;
|
||||
ok(n >= 15, '1) 暗色令牌数 ' + n + '(预期 ≥15)');
|
||||
|
||||
/* ---- 2) 对比度 ---- */
|
||||
const checks = [
|
||||
['E8EAED', '1C1F26', 'title/卡片底'], ['C9CDD4', '1C1F26', 'body/卡片底'],
|
||||
['9CA3AF', '1C1F26', 'secondary/卡片底'], ['9CA3AF', '14161C', 'placeholder/页面底'],
|
||||
['6B8CFF', '1C1F26', 'brand/卡片底'], ['6B8CFF', '14161C', 'brand/页面底'],
|
||||
['4CAF50', '1C1F26', 'success'], ['E5A300', '1C1F26', 'warning'],
|
||||
['F26D6D', '1C1F26', 'error'], ['4C9AFF', '1C1F26', 'info']
|
||||
];
|
||||
for (const [f, b, label] of checks) {
|
||||
const r = ratio('#' + f, '#' + b);
|
||||
ok(r >= 4.5, '2) #' + f + '/#' + b + ' ' + r.toFixed(2) + ':1 ' + label);
|
||||
}
|
||||
|
||||
/* ---- 3) 浏览器实测 ---- */
|
||||
let chromium;
|
||||
try {
|
||||
({ chromium } = await import('playwright'));
|
||||
} catch {
|
||||
console.error('[FATAL] 未安装 playwright');
|
||||
process.exit(2);
|
||||
}
|
||||
const browser = await chromium.launch();
|
||||
/* slug → 演示文件名:data.json 的 files.html 即权威路径(小写 slug.html) */
|
||||
let prefixOf = null;
|
||||
try {
|
||||
const idx = await (await fetch(BASE + '/site/data.json')).json();
|
||||
const map = {};
|
||||
(idx.components || []).forEach((c) => { map[c.slug] = (c.files && c.files.html || '').split('/').pop() || (c.slug + '.html'); });
|
||||
prefixOf = (slug) => map[slug] || (slug + '.html');
|
||||
} catch (e) {
|
||||
prefixOf = (slug) => slug + '.html';
|
||||
}
|
||||
try {
|
||||
for (const slug of SAMPLE) {
|
||||
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
|
||||
const url = BASE + '/frameworks/' + prefixOf(slug);
|
||||
let status = 0;
|
||||
try {
|
||||
const resp = await page.goto(url, { waitUntil: 'load', timeout: 15000 });
|
||||
status = resp ? resp.status() : 0;
|
||||
} catch (e) {
|
||||
ok(false, '3) ' + slug + ' 页面加载失败 ' + e.message);
|
||||
await page.close();
|
||||
continue;
|
||||
}
|
||||
if (status !== 200) {
|
||||
ok(false, '3) ' + slug + ' 演示页 HTTP ' + status);
|
||||
await page.close();
|
||||
continue;
|
||||
}
|
||||
await page.evaluate(() => document.documentElement.classList.add('aa-dark'));
|
||||
await page.waitForTimeout(400);
|
||||
const res = await page.evaluate(() => {
|
||||
const bad = [];
|
||||
const els = document.querySelectorAll('body, body *');
|
||||
const lumH = (rgb) => {
|
||||
const m = rgb.match(/rgba?\(([^)]+)\)/);
|
||||
if (!m) return null;
|
||||
const p = m[1].split(',').map((x) => parseFloat(x));
|
||||
const f = (v) => { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); };
|
||||
return 0.2126 * f(p[0]) + 0.7152 * f(p[1]) + 0.0722 * f(p[2]);
|
||||
};
|
||||
const parse = (s) => {
|
||||
if (/^#([0-9a-fA-F]{6})$/.test(s)) {
|
||||
const c = s.slice(1);
|
||||
const f = (i) => { const v = parseInt(c.substr(i, 2), 16) / 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); };
|
||||
return 0.2126 * f(0) + 0.7152 * f(2) + 0.0722 * f(4);
|
||||
}
|
||||
return lumH(s);
|
||||
};
|
||||
let checked = 0;
|
||||
/* rgba 解析为 [r,g,b,a];半透明背景沿父链合成后算对比度 */
|
||||
const toRGBA = (s) => {
|
||||
const m = (s || '').match(/rgba?\(([^)]+)\)/);
|
||||
if (!m) {
|
||||
if (/^#([0-9a-fA-F]{6})$/.test(s)) {
|
||||
const c = s.slice(1);
|
||||
return [parseInt(c.substr(0, 2), 16), parseInt(c.substr(2, 2), 16), parseInt(c.substr(4, 2), 16), 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const p = m[1].split(',').map((x) => parseFloat(x));
|
||||
return [p[0], p[1], p[2], p.length > 3 ? p[3] : 1];
|
||||
};
|
||||
const lumOf = (rgb) => {
|
||||
const f = (v) => { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); };
|
||||
return 0.2126 * f(rgb[0]) + 0.7152 * f(rgb[1]) + 0.0722 * f(rgb[2]);
|
||||
};
|
||||
const compBg = (el) => {
|
||||
let acc = null, p = el, d = 0;
|
||||
while (p && d < 8) {
|
||||
const rgba = toRGBA(getComputedStyle(p).backgroundColor);
|
||||
if (rgba) {
|
||||
if (!acc) acc = rgba.slice();
|
||||
else {
|
||||
const a = rgba[3] + acc[3] * (1 - rgba[3]);
|
||||
acc = [
|
||||
(rgba[0] * rgba[3] + acc[0] * acc[3] * (1 - rgba[3])) / (a || 1),
|
||||
(rgba[1] * rgba[3] + acc[1] * acc[3] * (1 - rgba[3])) / (a || 1),
|
||||
(rgba[2] * rgba[3] + acc[2] * acc[3] * (1 - rgba[3])) / (a || 1),
|
||||
a
|
||||
];
|
||||
}
|
||||
if (acc[3] >= 0.999) break;
|
||||
}
|
||||
p = p.parentElement; d++;
|
||||
}
|
||||
return acc && acc[3] > 0 ? lumOf(acc) : null;
|
||||
};
|
||||
for (const el of els) {
|
||||
if (checked > 400) break;
|
||||
const cs = getComputedStyle(el);
|
||||
if (cs.display === 'none' || cs.visibility === 'hidden' || parseFloat(cs.opacity) === 0) continue;
|
||||
const rect = el.getBoundingClientRect();
|
||||
if (rect.width === 0 && rect.height === 0) continue;
|
||||
const txt = (el.textContent || '').trim();
|
||||
if (!txt || el.children.length > 0) continue;
|
||||
const fgRGBA = toRGBA(cs.color);
|
||||
if (!fgRGBA) continue;
|
||||
const fg = lumOf(fgRGBA);
|
||||
const bg = compBg(el);
|
||||
if (bg === null) continue;
|
||||
const r = (Math.max(fg, bg) + 0.05) / (Math.min(fg, bg) + 0.05);
|
||||
checked++;
|
||||
if (r < 3.0) bad.push(el.tagName + '.' + (el.className || '').toString().slice(0, 20) + ' "' + txt.slice(0, 12) + '" ' + r.toFixed(2));
|
||||
if (bad.length >= 5) break;
|
||||
}
|
||||
// 白底残留:暗色下仍有大面积 #fff 背景
|
||||
const whites = [];
|
||||
for (const el of document.querySelectorAll('body, div, section, main, table, thead')) {
|
||||
const bg = getComputedStyle(el).backgroundColor;
|
||||
if (/255,\s*255,\s*255/.test(bg)) {
|
||||
const r = el.getBoundingClientRect();
|
||||
if (r.width > 300 && r.height > 100) whites.push(el.tagName + ' ' + Math.round(r.width) + 'x' + Math.round(r.height));
|
||||
if (whites.length >= 3) break;
|
||||
}
|
||||
}
|
||||
return { checked, bad, whites };
|
||||
});
|
||||
ok(res.bad.length === 0, '3) ' + slug + ' 暗色对比度抽查 ' + res.checked + ' 节点' + (res.bad.length ? ' 不足:' + res.bad.join(';') : ''));
|
||||
ok(res.whites.length === 0, '3) ' + slug + ' 无大面积白底残留' + (res.whites.length ? ':' + res.whites.join(';') : ''));
|
||||
if (SHOTS) await page.screenshot({ path: 'dark-' + slug + '.png' });
|
||||
await page.close();
|
||||
}
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
console.log(fail ? 'DARK VERIFY FAIL (' + fail + ')' : 'DARK VERIFY OK');
|
||||
process.exit(fail ? 1 : 0);
|
||||
Reference in New Issue
Block a user