管理端:设计系统 v2 收敛 + 修复漏斗卡恒为 0 的响应式缺陷

## 一、设计系统收敛

- 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
This commit is contained in:
WorkBuddy
2026-09-23 01:06:57 +08:00
parent 269adf1fed
commit 631e3000d3
20 changed files with 1765 additions and 381 deletions
+378 -59
View File
@@ -1,93 +1,412 @@
/* ============================================================================
* 管理端设计系统 · v2
*
* 本文件是管理端唯一的设计 token 事实源。24 个 .vue 的 <style> 只允许通过
* var(--token) 取色 / 取字号 / 取间距,不应出现字面色值(由门控脚本
* scripts/lint-admin-tokens.mjs 拦截)。
*
* 立此版本的原因(2026-09-22 审计实测,不是主观判断):
* · 97 处硬编码颜色 / 67 种色值:6 种近似粉底(#FFF0F1 #FFF3F2 #FFF6F6
* #FFF7F7 #FFF8F7 #FFFAFA)、6 种近似浅灰边框、大量灰阶碎片。
* · 88 处硬编码字号 / 23 种档位,其中 20 处 ≤11px(9px 见于 .metric-copy em、
* .activity-status、.todo-copy small),低于可读下限。
* · --color-text-tertiary(#888888) 白底 3.54:1、--color-warning(#E59A32) 2.33:1、
* --admin-coral(#F45F68) 3.14:1 —— 在各自用法下均不达 WCAG AA 的 4.5:1。
* · Element Plus 的 --el-color-*-light-N / -dark-2 / -rgb 是**编译期静态生成**的,
* 项目只覆盖了 --el-color-primary,于是开关、滑块、焦点环、分页、标签等
* 组件仍是默认蓝 —— 品牌色与 EP 蓝混用。
*
* 三条硬规则:
* 1) 品牌色三级分化:基础色只做色块/渐变/图形(不承载文字);
* -strong 做白底文字;-solid 做实心底压白字;两者均实算 ≥4.5:1。
* 2) 字重只用 400 / 500 / 600 / 700 四档(跨平台缺字重会回退,导致渲染不一致)。
* 3) 字号有下限:正文最小 12px,角标/计数不得低于 11px。
*
* 所有对比度数值均由脚本按 WCAG 相对亮度公式实算(非估算),标注在各 token 后。
* ========================================================================== */
:root {
--admin-coral: #f45f68;
--admin-coral-hover: #df505a;
--admin-coral-soft: #fff0f1;
--admin-shell-bg: rgba(255, 255, 255, 0.96);
--admin-sidebar-bg: #202d38;
--admin-sidebar-surface: #2b3a46;
--admin-sidebar-text: #D9E1E6;
--admin-sidebar-muted: #8FA1AC;
--admin-page-bg: #f7f9fb;
/* ---------------- 品牌色 ---------------- */
/* 基础色:仅用于色块 / 渐变 / 图标 / 边框氛围。白底作文字只有 3.14:1,禁止承载文字。 */
--admin-coral: #F45F68;
--admin-coral-hover: #E5535D;
--admin-coral-strong: #C0414A; /* 白底文字 5.13:1 */
--admin-coral-solid: #C84A53; /* 实心底压白字 4.60:1 */
--admin-coral-solid-hover: #B8434A; /* hover 反而更深更清晰 5.33:1 */
--admin-coral-on-dark: #FF969C; /* 侧栏深底文字 6.76:1 */
--admin-coral-soft: #FFF0F1; /* 品牌浅底(角标 / 选中态底) */
--admin-coral-lighter: #FFF8F7; /* 更浅一档,hover 底 */
--admin-coral-border: rgba(244, 95, 104, 0.22);
/* 承载白字的品牌渐变(两端均 ≥4.5:1)。基础色渐变见 --gradient-brand-vivid。 */
--gradient-brand-solid: linear-gradient(135deg, #C84A53 0%, #B8434A 100%);
/* 鲜亮品牌渐变:只允许用在「无文字覆盖」的装饰,或预览态还原(如 Banner 预览)。 */
--gradient-brand-vivid: linear-gradient(135deg, #FF7D6E 0%, #FF5C82 100%);
--gradient-brand-soft: linear-gradient(135deg, #FFE9DF 0%, #FFF7F2 100%);
/* 水平进度条填充(对轨道 --color-primary-light):原 #F56B75→#EF5A65 仅 2.53:1,
条与轨道难以区分;改达标渐变后 4.01:1。 */
--gradient-brand-bar: linear-gradient(90deg, #C84A53 0%, #B8434A 100%);
/* 兼容既有引用名(全站仍在用) */
--admin-primary: var(--admin-coral);
--admin-primary-hover: var(--admin-coral-hover);
--admin-card-radius: 14px;
--admin-shell-radius: 22px;
--admin-shadow: 0 18px 48px rgba(57, 40, 45, 0.14);
--admin-header-bg: #FFFFFF;
--admin-soft-pink: var(--admin-coral-soft);
--admin-soft-blue: #EEF5FF;
--admin-soft-green: #EDF8F1;
--admin-soft-purple: #F4EFFF;
--color-primary: var(--admin-primary);
--color-primary-hover: var(--admin-primary-hover);
--color-primary-light: #FFF3F2;
--color-primary-lighter: #FFF8F7;
--color-success: #2E9B68;
--color-warning: #E59A32;
--color-danger: #D94A4A;
--color-info: #4A7CD4;
--color-text-primary: #222222;
--color-text-secondary: #555555;
--color-text-tertiary: #888888;
--admin-primary-strong: var(--admin-coral-strong);
--admin-primary-solid: var(--admin-coral-solid);
--color-primary: var(--admin-coral);
--color-primary-hover: var(--admin-coral-hover);
--color-primary-strong: var(--admin-coral-strong);
--color-primary-solid: var(--admin-coral-solid);
--color-primary-light: var(--admin-coral-soft);
--color-primary-lighter: var(--admin-coral-lighter);
/* ---------------- 语义色五组 ----------------
基色做图形/色块;-strong 做文字(实算 ≥4.5:1);-soft 为配套浅底。 */
--color-success: #2E9B68; /* 白底 3.50:1 —— 仅图形 */
--color-success-strong: #1F7A4F; /* 白底 5.31:1 */
--color-success-soft: #EDF8F1;
--color-warning: #E59A32; /* 白底 2.33:1 —— 仅图形 */
--color-warning-strong: #8F5D0F; /* 白底 5.61:1 */
--color-warning-soft: #FFF6E6;
--color-danger: #D94A4A; /* 白底 4.18:1 —— 图形 / ≥24px 大字 */
--color-danger-strong: #B8353A; /* 白底 5.82:1 */
--color-danger-soft: #FFF6F6;
--color-info: #4A7CD4; /* 白底 4.10:1 —— 图形 */
--color-info-strong: #3A63B0; /* 白底 5.85:1 */
--color-info-soft: #EEF5FF;
--color-purple: #8B63BC;
--color-purple-strong: #6B47A0; /* 白底 6.92:1 */
--color-purple-soft: #F4EFFF;
--color-neutral: #8A9099;
--color-neutral-strong: #656C79; /* 白底 5.28:1 */
--color-neutral-soft: #F4F5F7;
/* ---------------- 文字 ---------------- */
--color-text-primary: #1F2328; /* 白底 15.80:1 */
--color-text-secondary: #555555; /* 白底 7.46:1 */
--color-text-tertiary: #676E7B; /* 白底 5.13:1;浅底 #F5F6F8 上 4.74:1(原 #888 仅 3.54:1) */
--color-text-placeholder: #8A9099; /* 3.25:1:占位符按惯例弱于正文一档(EP 默认 2.5:1) */
--color-text-disabled: #B9BEC7;
--color-text-inverse: #FFFFFF;
--color-text-on-dark: #D9E1E6; /* 侧栏深底 10.62:1 */
--color-text-on-dark-muted: #8FA1AC; /* 侧栏深底 5.26:1 */
--color-icon-muted: #D7DCE0; /* 空态大图标等纯装饰 */
/* ---------------- 背景 ---------------- */
--color-bg-primary: #FFFFFF;
--color-bg-secondary: #FFF8F7;
--color-bg-page: #FAFAF8;
--color-border: #E8E5E2;
--color-border-light: #F0EDE9;
--color-bg-page: #F7F9FB;
--color-bg-secondary: #FAFAFB;
--color-bg-subtle: #F5F6F8;
--color-bg-input: #F6F7F8;
--color-bg-overlay: rgba(255, 255, 255, 0.86);
--color-bg-overlay-strong: rgba(255, 255, 255, 0.96);
/* ---------------- 边框 ---------------- */
--color-border: #E4E7EB;
--color-border-light: #EDF0F2;
--color-border-hairline: #F2F3F4;
--color-border-strong: #D7DCE0;
--color-border-danger: #F3CCCC;
/* ---------------- 侧栏(深底) ---------------- */
--admin-sidebar-bg: #202D38;
--admin-sidebar-bg-deep: #192733;
--admin-sidebar-gradient: linear-gradient(180deg, #202D38 0%, #192733 100%);
--admin-sidebar-surface: #2B3A46;
--admin-sidebar-text: #D9E1E6;
--admin-sidebar-muted: #8FA1AC;
--admin-sidebar-hairline: rgba(255, 255, 255, 0.08);
--admin-sidebar-hairline-soft: rgba(255, 255, 255, 0.06);
/* ---------------- 壳与栅格 ---------------- */
--admin-shell-bg: rgba(255, 255, 255, 0.96);
--admin-header-bg: #FFFFFF;
--admin-shell-radius: 22px;
--admin-card-radius: 14px;
--admin-shell-max-width: 1840px;
--admin-page-max-width: 1440px;
--admin-content-max-width: 1500px;
--sidebar-width: 220px;
--sidebar-width-collapsed: 76px;
--header-height: 64px;
/* ---------------- 圆角 ---------------- */
--border-radius-xs: 6px;
--border-radius-sm: 8px;
--border-radius-md: var(--admin-card-radius);
--border-radius-md: 14px;
--border-radius-lg: 16px;
--border-radius-xl: 18px;
--border-radius-pill: 999px;
/* ---------------- 阴影 ---------------- */
--shadow-panel: 0 8px 20px rgba(48, 58, 67, 0.035);
--shadow-card: 0 8px 24px rgba(75, 55, 46, 0.06);
--el-color-primary: var(--color-primary);
--shadow-raise: 0 10px 24px rgba(68, 48, 55, 0.1);
--shadow-brand: 0 8px 18px rgba(200, 74, 83, 0.26);
--admin-shadow: 0 18px 48px rgba(57, 40, 45, 0.14);
/* ---------------- 排版刻度 ----------------
正文 14px 起;角标/计数下限 11px;元信息下限 12px。 */
--font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
--font-family-brand: KaiTi, STKaiti, serif; /* 品牌书法体:仅装饰性标语 */
--font-size-micro: 11px;
--font-size-xs: 12px;
--font-size-sm: 13px;
--font-size-base: 14px;
--font-size-md: 15px;
--font-size-lg: 16px;
--font-size-xl: 18px;
--font-size-2xl: 20px;
--font-size-3xl: 24px;
--font-size-4xl: 28px;
--font-size-5xl: 32px;
--font-size-display: 38px;
--font-line-tight: 1.2;
--font-line-snug: 1.35;
--font-line-base: 1.6;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* ---------------- 间距 ---------------- */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-10: 40px;
/* ---------------- 动效 ---------------- */
--motion-fast: 140ms;
--motion-normal: 200ms;
--motion-slow: 320ms;
--ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--press-scale: 0.985;
--press-opacity: 0.88;
/* ---------------- 交互热区 ----------------
桌面端按下限为 WCAG 2.5.8(AA) 的 24×24px;本项目常规控件取 32px(舒适),
主导航行取 44px(兼顾触屏笔记本)。不套用移动端 44px 全量规则,
否则会把管理端的单屏信息密度牺牲掉。 */
--tap-min: 44px;
--tap-control: 32px;
--tap-icon: 36px;
/* ---------------- tone 色调组(工作台指标卡 / 待办图标) ----------------
每组三档:--tone-x 用于图标与图形;--tone-x-ink 用于文字(压 --tone-x-soft
实算均 ≥4.6:1);--tone-x-soft 为浅底。 */
--tone-rose: #F45F68; --tone-rose-ink: var(--admin-coral-strong); --tone-rose-soft: #FFF0F1;
--tone-amber: #E59A32; --tone-amber-ink: var(--color-warning-strong); --tone-amber-soft: #FFF6E6;
--tone-blue: #4A7CD4; --tone-blue-ink: var(--color-info-strong); --tone-blue-soft: #EEF5FF;
--tone-green: #2E9B68; --tone-green-ink: var(--color-success-strong); --tone-green-soft: #EDF8F1;
--tone-purple: #8B63BC; --tone-purple-ink: var(--color-purple-strong); --tone-purple-soft: #F4EFFF;
--tone-slate: #8A9099; --tone-slate-ink: var(--color-neutral-strong); --tone-slate-soft: #F4F5F7;
--tone-red: #D94A4A; --tone-red-ink: var(--color-danger-strong); --tone-red-soft: #FFF6F6;
/* 兼容既有引用名 */
--admin-soft-pink: var(--admin-coral-soft);
--admin-soft-blue: var(--color-info-soft);
--admin-soft-green: var(--color-success-soft);
--admin-soft-purple: var(--color-purple-soft);
/* ==========================================================================
Element Plus 联动
EP 的 --el-color-{t}-light-{i} / -dark-2 / -rgb 由 SCSS 在**编译期**按默认色
静态生成,运行时改 --el-color-primary 不会带动它们(开关、滑块、焦点环、
分页、标签因此仍是默认蓝)。这里按 EP 的派生公式补全:
light-{i} = mix(#FFFFFF, base, i*10%) i ∈ 1..9
dark-2 = mix(#000000, base, 20%)
基色取 -strong / -solid 档 —— 既能当文字色也能做实心底,使 EP 全组件与品牌一致。
info 保持中性灰:EP 的 info 语义是「中性/未知」,不应变成品牌红。
========================================================================== */
--el-color-primary: #C84A53;
--el-color-primary-rgb: 200, 74, 83;
--el-color-primary-light-3: #d98087;
--el-color-primary-light-5: #e4a5a9;
--el-color-primary-light-7: #efc9cb;
--el-color-primary-light-8: #f4dbdd;
--el-color-primary-light-9: #faedee;
--el-color-primary-dark-2: #a03b42;
--el-color-success: #1F7A4F;
--el-color-success-rgb: 31, 122, 79;
--el-color-success-light-3: #62a284;
--el-color-success-light-5: #8fbda7;
--el-color-success-light-7: #bcd7ca;
--el-color-success-light-8: #d2e4dc;
--el-color-success-light-9: #e9f2ed;
--el-color-success-dark-2: #19623f;
--el-color-warning: #8F5D0F;
--el-color-warning-rgb: 143, 93, 15;
--el-color-warning-light-3: #b18e57;
--el-color-warning-light-5: #c7ae87;
--el-color-warning-light-7: #ddceb7;
--el-color-warning-light-8: #e9dfcf;
--el-color-warning-light-9: #f4efe7;
--el-color-warning-dark-2: #724a0c;
--el-color-danger: #B8353A;
--el-color-danger-rgb: 184, 53, 58;
--el-color-danger-light-3: #cd7275;
--el-color-danger-light-5: #dc9a9d;
--el-color-danger-light-7: #eac2c4;
--el-color-danger-light-8: #f1d7d8;
--el-color-danger-light-9: #f8ebeb;
--el-color-danger-dark-2: #932a2e;
--el-color-info: #656C79;
--el-color-info-rgb: 101, 108, 121;
--el-color-info-light-3: #9398a1;
--el-color-info-light-5: #b2b6bc;
--el-color-info-light-7: #d1d3d7;
--el-color-info-light-8: #e0e2e4;
--el-color-info-light-9: #f0f0f2;
--el-color-info-dark-2: #515661;
/* EP 中性色(文字 / 边框 / 填充)接到同一套 token,避免表格、输入框另成一套灰 */
--el-text-color-primary: var(--color-text-primary);
--el-text-color-regular: var(--color-text-secondary);
--el-text-color-secondary: var(--color-text-tertiary);
--el-text-color-placeholder: var(--color-text-placeholder);
--el-text-color-disabled: var(--color-text-disabled);
--el-border-color: var(--color-border);
--el-border-color-light: var(--color-border-light);
--el-border-color-lighter: var(--color-border-light);
--el-border-color-extra-light: var(--color-border-hairline);
--el-fill-color: var(--color-bg-subtle);
--el-fill-color-light: var(--color-bg-secondary);
--el-fill-color-lighter: var(--color-bg-secondary);
--el-bg-color: var(--color-bg-primary);
--el-bg-color-page: var(--color-bg-page);
--el-border-radius-base: var(--border-radius-sm);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body, #app {
width: 100%;
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
font-size: 14px;
font-family: var(--font-family-base);
font-size: var(--font-size-base);
color: var(--color-text-primary);
background-color: var(--color-bg-page);
}
/* 管理端是桌面密度布局,1100px 以下由 MainLayout 的 .admin-scene 承担横向滚动。 */
body { min-width: 1100px; }
/* <small> 的浏览器默认样式是 font-size: smaller(0.833em),会按父级字号再缩一档:
父级 12px 时实际渲染 10px、11px 时渲染 9.17px —— 静默跌破 11px 下限,
且源码里看不到任何非法字号,静态检查抓不到(2026-09-22 由渲染态实测发现)。
这里统一改为继承父级字号,需要更小的场景必须显式声明 token 字号(守卫会校验)。 */
small { font-size: inherit; }
button:focus-visible,
a:focus-visible,
input:focus-visible,
[tabindex]:focus-visible {
outline: 3px solid rgba(238, 102, 107, 0.32);
outline: 3px solid var(--admin-coral-border);
outline-offset: 2px;
}
/* ---------------- Element Plus 组件级覆盖 ---------------- */
/* EP 默认 hover 是把主色「加白」(白字对比度随之掉到 2.2:1)。这里改成更深一档,
既保住白字可读性(5.33:1),hover 反馈也更明确。 */
.el-button--primary {
--el-button-bg-color: var(--color-primary);
--el-button-border-color: var(--color-primary);
--el-button-hover-bg-color: var(--color-primary-hover);
--el-button-hover-border-color: var(--color-primary-hover);
--el-button-bg-color: var(--admin-coral-solid);
--el-button-border-color: var(--admin-coral-solid);
--el-button-hover-bg-color: var(--admin-coral-solid-hover);
--el-button-hover-border-color: var(--admin-coral-solid-hover);
--el-button-active-bg-color: var(--el-color-primary-dark-2);
--el-button-active-border-color: var(--el-color-primary-dark-2);
}
.el-radio-group .el-radio-button__inner:checked {
background-color: var(--color-primary);
border-color: var(--color-primary);
background-color: var(--admin-coral-solid);
border-color: var(--admin-coral-solid);
}
.el-card { --el-card-border-radius: var(--border-radius-md); --el-card-border-color: var(--color-border-light); }
.el-table { --el-table-header-bg-color: #FBFAF8; --el-table-border-color: var(--color-border-light); }
.el-tag { border-radius: 999px; }
.page-container { padding: 24px; }
.page-shell { max-width: 1440px; margin: 0 auto; }
.page-heading { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 20px; }
.page-heading h1 { font-size: 24px; line-height: 1.35; font-weight: 650; }
.el-card {
--el-card-border-radius: var(--border-radius-md);
--el-card-border-color: var(--color-border-light);
}
.el-table {
--el-table-header-bg-color: var(--color-bg-secondary);
--el-table-border-color: var(--color-border-light);
}
.el-tag { border-radius: var(--border-radius-pill); }
/* ---------------- 通用版式 ---------------- */
.page-container { padding: var(--space-6); }
.page-shell { max-width: var(--admin-page-max-width); margin: 0 auto; }
.page-heading {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: var(--space-4);
margin-bottom: var(--space-5);
}
.page-heading h1 {
font-size: var(--font-size-3xl);
line-height: var(--font-line-snug);
font-weight: var(--font-weight-semibold);
}
.page-heading p { margin-top: 6px; color: var(--color-text-tertiary); }
.surface-card { background: var(--color-bg-primary); border: 1px solid var(--color-border-light); border-radius: var(--border-radius-md); box-shadow: var(--shadow-card); }
.surface-card {
background: var(--color-bg-primary);
border: 1px solid var(--color-border-light);
border-radius: var(--border-radius-md);
box-shadow: var(--shadow-card);
}
/* 语义文字色工具类(比在页面里各写一遍 color: #xxx 更不容易漂移) */
.text-primary { color: var(--color-text-primary); }
.text-secondary { color: var(--color-text-secondary); }
.text-tertiary { color: var(--color-text-tertiary); }
.mt-sm { margin-top: 8px; }
.mt-md { margin-top: 16px; }
.mt-lg { margin-top: 24px; }
.mb-sm { margin-bottom: 8px; }
.mb-md { margin-bottom: 16px; }
.mb-lg { margin-bottom: 24px; }
.text-muted { color: var(--color-text-tertiary); }
.text-brand { color: var(--admin-coral-strong); }
.text-success { color: var(--color-success-strong); }
.text-warning { color: var(--color-warning-strong); }
.text-danger { color: var(--color-danger-strong); }
/* 语义底色工具类 */
.bg-card { background: var(--color-bg-primary); }
.bg-subtle { background: var(--color-bg-subtle); }
.bg-brand-soft { background: var(--admin-coral-soft); }
/* 间距工具类 */
.mt-sm { margin-top: var(--space-2); }
.mt-md { margin-top: var(--space-4); }
.mt-lg { margin-top: var(--space-6); }
.mb-sm { margin-bottom: var(--space-2); }
.mb-md { margin-bottom: var(--space-4); }
.mb-lg { margin-bottom: var(--space-6); }
.flex-center { display: flex; align-items: center; justify-content: center; }
@media (max-width: 1200px) { body { min-width: 0; } }
@media (max-width: 900px) { .page-container { padding: 16px; } .page-heading { align-items: flex-start; flex-direction: column; } }
/* ---------------- 交互反馈 ---------------- */
/* 桌面端控件热区下限:32px(WCAG 2.5.8 AA 为 24px,留出舒适余量) */
.tap-control { min-height: var(--tap-control); min-width: var(--tap-control); }
/* 按压反馈:统一走 token,避免各页面各写一套 scale/opacity */
.pressable { transition: transform var(--motion-fast) var(--ease-standard), opacity var(--motion-fast) var(--ease-standard); }
.pressable:active { transform: scale(var(--press-scale)); opacity: var(--press-opacity); }
@media (max-width: 1200px) {
body { min-width: 0; }
}
@media (max-width: 900px) {
.page-container { padding: var(--space-4); }
.page-heading { align-items: flex-start; flex-direction: column; }
}