管理端:设计系统 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:
@@ -264,19 +264,20 @@ function handleLogout() {
|
||||
/* 背景图是 public/ 下的运行时资源,必须带 BASE_URL 前缀:
|
||||
写死品牌目录的绝对路径,在产物部署于 /admin/ 子路径时会 404(页面背景变纯色)。
|
||||
用 v-bind 把常量注入 CSS,避免在样式里各写一遍拼接。 */
|
||||
background: #fff7f7 center / cover fixed no-repeat;
|
||||
/* 先给纯色兜底,避免图未加载完成时闪白 */
|
||||
background: var(--admin-coral-lighter) center / cover fixed no-repeat;
|
||||
background-image: url(v-bind('BRAND_BACKDROP'));
|
||||
}
|
||||
|
||||
.scene-header {
|
||||
display: grid;
|
||||
max-width: 1840px;
|
||||
max-width: var(--admin-shell-max-width);
|
||||
height: 188px;
|
||||
margin: 0 auto;
|
||||
grid-template-columns: minmax(640px, 1fr) auto;
|
||||
grid-template-rows: 112px 76px;
|
||||
align-items: center;
|
||||
column-gap: 32px;
|
||||
column-gap: var(--space-8);
|
||||
}
|
||||
|
||||
.scene-brand,
|
||||
@@ -295,23 +296,23 @@ function handleLogout() {
|
||||
|
||||
.scene-slogan {
|
||||
display: flex;
|
||||
padding-right: 24px;
|
||||
padding-right: var(--space-6);
|
||||
align-items: flex-end;
|
||||
flex-direction: column;
|
||||
color: var(--admin-coral);
|
||||
font-family: KaiTi, STKaiti, serif;
|
||||
color: var(--admin-coral-strong);
|
||||
font-family: var(--font-family-brand);
|
||||
line-height: 1.05;
|
||||
transform: rotate(-7deg);
|
||||
}
|
||||
|
||||
.scene-slogan span {
|
||||
font-size: 17px;
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.scene-slogan strong {
|
||||
margin-top: 4px;
|
||||
font-size: 25px;
|
||||
font-weight: 500;
|
||||
margin-top: var(--space-1);
|
||||
font-size: var(--font-size-3xl);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.scene-brand-copy {
|
||||
@@ -319,18 +320,18 @@ function handleLogout() {
|
||||
margin-left: 10px;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
color: var(--admin-coral);
|
||||
color: var(--admin-coral-strong);
|
||||
}
|
||||
|
||||
.scene-brand-copy strong {
|
||||
font-size: 27px;
|
||||
font-size: var(--font-size-4xl);
|
||||
line-height: 1.15;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.scene-brand-copy span {
|
||||
margin-top: 5px;
|
||||
font-size: 12px;
|
||||
font-size: var(--font-size-xs);
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
|
||||
@@ -338,18 +339,18 @@ function handleLogout() {
|
||||
min-width: 370px;
|
||||
margin-left: 28px;
|
||||
padding-left: 28px;
|
||||
border-left: 1px solid rgba(244, 95, 104, 0.22);
|
||||
border-left: 1px solid var(--admin-coral-border);
|
||||
}
|
||||
|
||||
.scene-title h1 {
|
||||
font-size: clamp(26px, 2.2vw, 38px);
|
||||
font-size: clamp(var(--font-size-3xl), 2.2vw, var(--font-size-display));
|
||||
line-height: 1.1;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.scene-title p {
|
||||
margin-top: 9px;
|
||||
color: #66717a;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.scene-benefits {
|
||||
@@ -357,40 +358,41 @@ function handleLogout() {
|
||||
align-self: start;
|
||||
justify-content: flex-start;
|
||||
gap: clamp(38px, 5vw, 92px);
|
||||
padding: 4px 0 0 50px;
|
||||
padding: var(--space-1) 0 0 50px;
|
||||
}
|
||||
|
||||
.scene-benefits span {
|
||||
display: grid;
|
||||
grid-template-columns: 36px auto;
|
||||
column-gap: 8px;
|
||||
color: var(--admin-coral);
|
||||
column-gap: var(--space-2);
|
||||
/* 13px 文字压在摄影背景图上,必须用 -strong 档(5.13:1),基础色只有 3.14:1 */
|
||||
color: var(--admin-coral-strong);
|
||||
}
|
||||
|
||||
.scene-benefits .el-icon {
|
||||
grid-row: 1 / 3;
|
||||
font-size: 30px;
|
||||
font-size: var(--font-size-4xl);
|
||||
}
|
||||
|
||||
.scene-benefits b {
|
||||
font-size: 13px;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.scene-benefits small {
|
||||
margin-top: 2px;
|
||||
color: #7b7f86;
|
||||
font-size: 10px;
|
||||
color: var(--color-text-tertiary);
|
||||
font-size: var(--font-size-micro);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.admin-shell {
|
||||
width: min(1840px, 100%);
|
||||
width: min(var(--admin-shell-max-width), 100%);
|
||||
height: calc(100vh - 238px);
|
||||
min-height: 560px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
background: var(--admin-shell-bg);
|
||||
border: 1px solid rgba(255, 255, 255, 0.9);
|
||||
border: 1px solid var(--color-bg-overlay-strong);
|
||||
border-radius: var(--admin-shell-radius);
|
||||
box-shadow: var(--admin-shadow);
|
||||
backdrop-filter: blur(14px);
|
||||
@@ -401,23 +403,23 @@ function handleLogout() {
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
color: var(--admin-sidebar-text);
|
||||
background: linear-gradient(180deg, #202d38 0%, #192733 100%);
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.06);
|
||||
transition: width 0.2s ease;
|
||||
background: var(--admin-sidebar-gradient);
|
||||
border-right: 1px solid var(--admin-sidebar-hairline-soft);
|
||||
transition: width var(--motion-normal) var(--ease-standard);
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
height: 82px;
|
||||
padding: 17px 16px;
|
||||
padding: 17px var(--space-4);
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-bottom: 1px solid var(--admin-sidebar-hairline);
|
||||
}
|
||||
|
||||
.brand.compact {
|
||||
justify-content: center;
|
||||
padding: 16px 8px;
|
||||
padding: var(--space-4) var(--space-2);
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
@@ -435,15 +437,15 @@ function handleLogout() {
|
||||
}
|
||||
|
||||
.brand-copy strong {
|
||||
color: #fff;
|
||||
font-size: 17px;
|
||||
color: var(--color-text-inverse);
|
||||
font-size: var(--font-size-lg);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.brand-copy span {
|
||||
margin-top: 4px;
|
||||
color: #b5c0c8;
|
||||
font-size: 10px;
|
||||
margin-top: var(--space-1);
|
||||
color: var(--color-text-on-dark-muted);
|
||||
font-size: var(--font-size-micro);
|
||||
letter-spacing: 1.5px;
|
||||
}
|
||||
|
||||
@@ -457,49 +459,54 @@ function handleLogout() {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* 主导航行项:桌面密度下取 44px(兼顾触屏笔记本,且远高于 WCAG 2.5.8 的 24px 下限) */
|
||||
.sidebar-menu :deep(.el-menu-item) {
|
||||
height: 41px;
|
||||
height: var(--tap-min);
|
||||
margin: 2px 0;
|
||||
color: var(--admin-sidebar-text);
|
||||
border-radius: 9px;
|
||||
border-radius: var(--border-radius-sm);
|
||||
transition: background var(--motion-fast) var(--ease-standard),
|
||||
color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.sidebar-menu :deep(.el-menu-item:hover) {
|
||||
color: #fff;
|
||||
color: var(--color-text-inverse);
|
||||
background: var(--admin-sidebar-surface);
|
||||
}
|
||||
|
||||
/* 激活态原先压的是明亮珊瑚渐变(#f45f68→#ee727b),白字只有 3.14:1;
|
||||
改用达标渐变(两端 4.60:1 / 5.33:1)。 */
|
||||
.sidebar-menu :deep(.el-menu-item.is-active) {
|
||||
color: #fff;
|
||||
font-weight: 650;
|
||||
background: linear-gradient(135deg, #f45f68, #ee727b);
|
||||
box-shadow: 0 8px 18px rgba(244, 95, 104, 0.26);
|
||||
color: var(--color-text-inverse);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
background: var(--gradient-brand-solid);
|
||||
box-shadow: var(--shadow-brand);
|
||||
}
|
||||
|
||||
.sidebar-menu :deep(.el-icon) {
|
||||
font-size: 17px;
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.sidebar-motto {
|
||||
display: flex;
|
||||
margin: 8px 15px 2px;
|
||||
padding: 14px 12px;
|
||||
margin: var(--space-2) 15px 2px;
|
||||
padding: 14px var(--space-3);
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
color: #ff969c;
|
||||
font-family: KaiTi, STKaiti, serif;
|
||||
font-size: 15px;
|
||||
color: var(--admin-coral-on-dark);
|
||||
font-family: var(--font-family-brand);
|
||||
font-size: var(--font-size-md);
|
||||
line-height: 1.45;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-top: 1px solid var(--admin-sidebar-hairline);
|
||||
}
|
||||
|
||||
.sidebar-motto .el-icon {
|
||||
font-size: 31px;
|
||||
font-size: var(--font-size-4xl);
|
||||
}
|
||||
|
||||
.collapse-control {
|
||||
display: flex;
|
||||
height: 42px;
|
||||
min-height: var(--tap-min);
|
||||
margin: 2px 10px 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -508,10 +515,14 @@ function handleLogout() {
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: var(--border-radius-sm);
|
||||
transition: color var(--motion-fast) var(--ease-standard),
|
||||
background var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.collapse-control:hover {
|
||||
color: #fff;
|
||||
color: var(--color-text-inverse);
|
||||
background: var(--admin-sidebar-surface);
|
||||
}
|
||||
|
||||
.workspace {
|
||||
@@ -520,12 +531,12 @@ function handleLogout() {
|
||||
|
||||
.admin-topbar {
|
||||
display: flex;
|
||||
height: 64px;
|
||||
padding: 0 24px;
|
||||
height: var(--header-height);
|
||||
padding: 0 var(--space-6);
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
border-bottom: 1px solid #edf0f2;
|
||||
background: var(--color-bg-overlay-strong);
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
.page-context,
|
||||
@@ -537,15 +548,18 @@ function handleLogout() {
|
||||
|
||||
.page-context {
|
||||
min-width: 180px;
|
||||
gap: 12px;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.mobile-collapse {
|
||||
display: none;
|
||||
color: #56616a;
|
||||
min-width: var(--tap-icon);
|
||||
min-height: var(--tap-icon);
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: var(--border-radius-sm);
|
||||
}
|
||||
|
||||
.header-tools {
|
||||
@@ -557,57 +571,59 @@ function handleLogout() {
|
||||
}
|
||||
|
||||
.global-search :deep(.el-input__wrapper) {
|
||||
background: #f6f7f8;
|
||||
border-radius: 9px;
|
||||
box-shadow: inset 0 0 0 1px #e8eaed;
|
||||
background: var(--color-bg-input);
|
||||
border-radius: var(--border-radius-sm);
|
||||
box-shadow: inset 0 0 0 1px var(--color-border);
|
||||
}
|
||||
|
||||
.notification-button {
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
color: #4f5b65;
|
||||
width: var(--tap-icon);
|
||||
height: var(--tap-icon);
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 9px;
|
||||
border-radius: var(--border-radius-sm);
|
||||
place-items: center;
|
||||
transition: background var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.notification-button:hover {
|
||||
background: #f6f7f8;
|
||||
background: var(--color-bg-input);
|
||||
}
|
||||
|
||||
/* 角标字号原为 9px(低于可读下限),提到 11px,容器同步放大以容纳 */
|
||||
.notification-badge {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 1px;
|
||||
min-width: 15px;
|
||||
height: 15px;
|
||||
min-width: var(--space-4);
|
||||
height: var(--space-4);
|
||||
padding: 0 3px;
|
||||
color: #fff;
|
||||
font-size: 9px;
|
||||
line-height: 15px;
|
||||
color: var(--color-text-inverse);
|
||||
font-size: var(--font-size-micro);
|
||||
line-height: var(--space-4);
|
||||
text-align: center;
|
||||
background: var(--admin-coral);
|
||||
border-radius: 999px;
|
||||
background: var(--admin-coral-solid);
|
||||
border-radius: var(--border-radius-pill);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
gap: 8px;
|
||||
gap: var(--space-2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.user-info .el-avatar {
|
||||
color: #fff;
|
||||
background: var(--admin-coral);
|
||||
color: var(--color-text-inverse);
|
||||
background: var(--admin-coral-solid);
|
||||
}
|
||||
|
||||
.user-info strong {
|
||||
max-width: 92px;
|
||||
overflow: hidden;
|
||||
font-size: 13px;
|
||||
font-size: var(--font-size-sm);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -615,27 +631,28 @@ function handleLogout() {
|
||||
.admin-content {
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
background: var(--admin-page-bg);
|
||||
background: var(--color-bg-page);
|
||||
}
|
||||
|
||||
.scene-footer {
|
||||
display: flex;
|
||||
max-width: 1840px;
|
||||
height: 32px;
|
||||
max-width: var(--admin-shell-max-width);
|
||||
height: var(--space-8);
|
||||
margin: 0 auto;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
color: #8d8587;
|
||||
font-size: 11px;
|
||||
color: var(--color-text-tertiary);
|
||||
font-size: var(--font-size-micro);
|
||||
}
|
||||
|
||||
.scene-footer strong {
|
||||
color: var(--admin-coral);
|
||||
font-size: 14px;
|
||||
color: var(--admin-coral-strong);
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
.scene-footer small {
|
||||
margin-left: auto;
|
||||
font-size: var(--font-size-micro);
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
|
||||
@@ -653,15 +670,15 @@ function handleLogout() {
|
||||
}
|
||||
|
||||
.scene-slogan {
|
||||
padding-right: 12px;
|
||||
padding-right: var(--space-3);
|
||||
}
|
||||
|
||||
.scene-slogan span {
|
||||
font-size: 14px;
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
.scene-slogan strong {
|
||||
font-size: 20px;
|
||||
font-size: var(--font-size-2xl);
|
||||
}
|
||||
|
||||
.scene-brand > img {
|
||||
@@ -670,7 +687,7 @@ function handleLogout() {
|
||||
}
|
||||
|
||||
.scene-brand-copy strong {
|
||||
font-size: 22px;
|
||||
font-size: var(--font-size-2xl);
|
||||
}
|
||||
|
||||
.scene-title {
|
||||
@@ -679,8 +696,9 @@ function handleLogout() {
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
/* 紧凑态下正文标题略大于品牌字号,层级保持「页面 → 品牌」的正确指向 */
|
||||
.scene-title h1 {
|
||||
font-size: 27px;
|
||||
font-size: var(--font-size-3xl);
|
||||
}
|
||||
|
||||
.scene-benefits {
|
||||
@@ -707,7 +725,7 @@ function handleLogout() {
|
||||
}
|
||||
|
||||
.admin-sidebar {
|
||||
width: 76px !important;
|
||||
width: var(--sidebar-width-collapsed) !important;
|
||||
}
|
||||
|
||||
.brand-copy,
|
||||
@@ -741,7 +759,7 @@ function handleLogout() {
|
||||
}
|
||||
|
||||
.admin-topbar {
|
||||
padding: 0 12px;
|
||||
padding: 0 var(--space-3);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user