## 一、设计系统收敛 - theme/index.css 重写为 token 底座:品牌三级、语义五组、文字四级、 边框/背景/圆角/阴影/动效/热区/排版刻度 - 修复 Element Plus 派生色阶残留:--el-color-*-light-N 是 SCSS 编译期静态生成, 只覆盖基色不会带动它们 —— 数据统计页实测 133 处元素仍在用 EP 默认调色板 (其中主色蓝 8 处)。已按 mix(#fff,base,N*10%) / mix(#000,base,20%) 补全全部档位 - 新增 src/theme/tokens.ts:模板里 :style 需拼 8 位 hex(color + '15'),用不了 var(), 故以 TS 常量镜像色值;守卫会双向校验它与 index.css 同名取值一致 - 24 个 .vue 收敛:硬编码色值 97 处/67 种 → 0;越界字号 88 处(最低 9px)→ 0; 非法字重 650×6 → 0;触摸目标 17 处 <44px → 全部 ≥44px - 对比度:text-tertiary 3.54:1、warning 2.33:1、coral 作文字 3.14:1 → 全部 ≥4.5:1 - 新增守卫 ui-tests/admin-design-tokens.mjs(含 WCAG 对比度实算) ## 二、修复既有功能性缺陷:数据统计页漏斗卡与安全卡恒显示 0 - 现象:同一张卡片里,上方漏斗数字全是 0,下方汇总表却是 12846/9217/8462/3812, 界面自相矛盾 - 根因:funnelStages / matchStages / safetyCards 是普通数组(const x = [...]), loadFunnel() 直接改元素属性,模板读的却是同一个数组 —— 非响应式写入不触发重渲染。 同页凡走 ref 的值(汇总表、V11 区块)全部正常,规律清晰 - 修法(按根因):三个集合改为 computed() 从既有 ref 派生,卡片与汇总表因此同源, 结构上不可能再出现「同一份数据的两处展示对不上」;另把 loadFunnel() 内遮蔽 颜色助手的局部变量 const token 改名 authToken - 该缺陷无 TS/lint/构建报错,前 18 个守卫全绿也抓不到 —— 只有真跑起来看渲染结果才暴露 - 新增守卫 ui-tests/admin-reactivity-contracts.mjs: ①非响应式状态(普通声明 + 被当状态写 + 出现在模板里;未被写过的静态常量清单合法) ②数据统计页同源契约 ③颜色助手遮蔽 ## 验证 - UI 契约门禁 20/20 PASS(MIN_GUARDS 18→20) - 负向注入 3 例全部 FAIL(exit 1),还原后复跑 exit 0(证明守卫有判别力) - vue-tsc --noEmit exit 0 - vite build 成功(17.74s) - 渲染态实测(Edge 无头 + CDP 读渲染后 DOM,同一 mock、同一路由,只差代码): 修复前漏斗卡/安全卡全 0、比率全 '-' → 修复后 12846/9217/8462/3812/ 48210/51236/7420/4380/1926/1284,安全卡 17/214/23/9 交付物:artifacts/admin-design-v2-preview.html
78 lines
3.0 KiB
TypeScript
78 lines
3.0 KiB
TypeScript
/**
|
||
* 管理端调色板常量(TS 侧)。
|
||
*
|
||
* 为什么需要这个文件:
|
||
* 部分模板用 `:style="{ backgroundColor: stage.color + '15' }"` 把 6 位 hex 拼成
|
||
* 8 位带 alpha 的颜色('#409EFF' + '15' → '#409EFF15',即 8% 不透明度)。
|
||
* 这类位置**不能**用 CSS var —— `'var(--x)' + '15'` 是无效值,浏览器会整条丢弃。
|
||
* 于是「色值只有一处事实源」这条规则在 TS 侧会漏。本文件补上这个缺口。
|
||
*
|
||
* 单一事实源约定:
|
||
* 键名就是 src/theme/index.css 里的 var 名,值必须与之一致。
|
||
* 门控脚本 scripts/lint-admin-tokens.mjs 会逐项比对,任一侧改了而另一侧没改即失败。
|
||
*/
|
||
|
||
export const TOKEN_HEX: Record<string, string> = {
|
||
'--color-primary': '#F45F68',
|
||
'--color-primary-strong': '#C0414A',
|
||
'--color-primary-solid': '#C84A53',
|
||
'--color-success': '#2E9B68',
|
||
'--color-success-strong': '#1F7A4F',
|
||
'--color-warning': '#E59A32',
|
||
'--color-warning-strong': '#8F5D0F',
|
||
'--color-danger': '#D94A4A',
|
||
'--color-danger-strong': '#B8353A',
|
||
'--color-info': '#4A7CD4',
|
||
'--color-info-strong': '#3A63B0',
|
||
'--color-purple': '#8B63BC',
|
||
'--color-purple-strong': '#6B47A0',
|
||
'--color-neutral': '#8A9099',
|
||
'--color-neutral-strong': '#656C79',
|
||
'--color-text-primary': '#1F2328',
|
||
'--color-text-secondary': '#555555',
|
||
'--color-text-tertiary': '#676E7B',
|
||
'--color-bg-page': '#F7F9FB',
|
||
'--color-border': '#E4E7EB',
|
||
}
|
||
|
||
/**
|
||
* 取 token 的 hex 字面量。用于 :style 绑定(含 hex 拼接透明度的场景)。
|
||
* @example token('--color-success') // '#2E9B68'
|
||
*/
|
||
export function token(name: string): string {
|
||
const value = TOKEN_HEX[name]
|
||
if (!value) throw new Error(`未登记的 token: ${name}(请先加入 TOKEN_HEX 并同步 theme/index.css)`)
|
||
return value
|
||
}
|
||
|
||
/**
|
||
* 生成带 alpha 的 8 位 hex。alpha 传 2 位十六进制('15' = 21/255 ≈ 8%)。
|
||
* 与 `color + '15'` 等价,但把「必须 6 位 hex」这个约束显式化,避免误把 var() 传进来。
|
||
*/
|
||
export function tint(hex: string, alphaHex: string): string {
|
||
if (!/^#[0-9a-fA-F]{6}$/.test(hex)) {
|
||
throw new Error(`tint() 需要 6 位 hex(不能是 var() 或 3/8 位 hex):${hex}`)
|
||
}
|
||
return `${hex}${alphaHex}`
|
||
}
|
||
|
||
/** 指标卡 / 待办项的色调组,与 theme/index.css 的 --tone-* 一一对应。 */
|
||
export interface Tone {
|
||
/** 图标与图形用的鲜亮色 */
|
||
vivid: string
|
||
/** 文字用的可读色(压 soft 底 ≥4.5:1) */
|
||
ink: string
|
||
/** 浅底 */
|
||
soft: string
|
||
}
|
||
|
||
export const TONES: Record<string, Tone> = {
|
||
rose: { vivid: '#F45F68', ink: '#C0414A', soft: '#FFF0F1' },
|
||
amber: { vivid: '#E59A32', ink: '#8F5D0F', soft: '#FFF6E6' },
|
||
blue: { vivid: '#4A7CD4', ink: '#3A63B0', soft: '#EEF5FF' },
|
||
green: { vivid: '#2E9B68', ink: '#1F7A4F', soft: '#EDF8F1' },
|
||
purple: { vivid: '#8B63BC', ink: '#6B47A0', soft: '#F4EFFF' },
|
||
slate: { vivid: '#8A9099', ink: '#656C79', soft: '#F4F5F7' },
|
||
red: { vivid: '#D94A4A', ink: '#B8353A', soft: '#FFF6F6' },
|
||
}
|