diff --git a/.ua/visual-baseline.json b/.ua/visual-baseline.json index a5184d4..1e1d809 100644 --- a/.ua/visual-baseline.json +++ b/.ua/visual-baseline.json @@ -1,8 +1,8 @@ { "$schema": "视觉漂移基线:五类硬编码视觉字面量的允许上限,任一类超出即 ua audit:visual 退出码 1;迁移后用 --baseline 重写", "file": "packages/skin/ui/ui.css", - "commit": "089527f", - "generatedAt": "2026-10-26T09:36:10.619Z", + "commit": "551b2f6", + "generatedAt": "2026-10-26T09:40:14.168Z", "lines": 4780, "total": 40, "counts": { @@ -14,6 +14,7 @@ "space-literal": 13, "control-h-literal": 0, "weight-literal": 0, - "leading-literal": 0 + "leading-literal": 0, + "z-literal": 0 } } diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index 7a5b743..c9dbf28 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -381,7 +381,7 @@ body.ua-col-resizing { } .ua-select-pop { position: absolute; - z-index: 20; + z-index: var(--ua-z-dropdown); top: calc(100% + 4px); left: 0; right: 0; @@ -486,7 +486,7 @@ body.ua-col-resizing { } .ua-menu-pop { position: absolute; - z-index: 20; + z-index: var(--ua-z-dropdown); top: calc(100% + 4px); left: 0; min-width: 160px; @@ -619,7 +619,7 @@ body.ua-col-resizing { .ua-pop { position: absolute; left: 0; - z-index: 30; + z-index: var(--ua-z-dropdown); min-width: 200px; max-width: 280px; padding: var(--ua-space-3); @@ -1415,7 +1415,7 @@ button.ua-step-dot:hover { position: fixed; top: var(--ua-space-4); right: var(--ua-space-4); - z-index: 50; + z-index: var(--ua-z-toast); display: flex; flex-direction: column; align-items: flex-end; @@ -1729,7 +1729,7 @@ button.ua-step-dot:hover { .ua-drawer-overlay { position: fixed; inset: 0; - z-index: 90; + z-index: var(--ua-z-overlay); background: rgba(0, 0, 0, 0.36); opacity: 0; visibility: hidden; @@ -1828,7 +1828,7 @@ button.ua-step-dot:hover { display: none; position: fixed; inset: 0; - z-index: 100; + z-index: var(--ua-z-overlay); background: rgba(0, 0, 0, 0.4); align-items: center; justify-content: center; @@ -1911,7 +1911,7 @@ button.ua-step-dot:hover { } .ua-tip { position: absolute; - z-index: 30; + z-index: var(--ua-z-dropdown); max-width: 220px; padding: var(--ua-space-1) var(--ua-space-2); border-radius: var(--ua-radius-sm); @@ -2053,7 +2053,7 @@ button.ua-step-dot:hover { .ua-image-mask { position: fixed; inset: 0; - z-index: 1100; + z-index: var(--ua-z-modal); display: flex; align-items: center; justify-content: center; @@ -2802,7 +2802,7 @@ button.ua-step-dot:hover { display: flex; flex-direction: column; gap: var(--ua-space-2); - z-index: 60; + z-index: var(--ua-z-toast); pointer-events: none; /* 容器不挡点击,卡片自身恢复 */ } .ua-notice { @@ -3067,7 +3067,7 @@ button.ua-step-dot:hover { } .ua-cascader-pop { position: absolute; - z-index: 40; + z-index: var(--ua-z-dropdown); display: none; margin-top: var(--ua-space-15); border: 1px solid var(--ua-color-border); @@ -3225,7 +3225,7 @@ button.ua-step-dot:hover { } .ua-autocomplete-pop { position: absolute; - z-index: 40; + z-index: var(--ua-z-dropdown); left: 0; right: 0; display: none; @@ -3326,7 +3326,7 @@ button.ua-step-dot:hover { } .ua-datepicker-pop { position: absolute; - z-index: 40; + z-index: var(--ua-z-dropdown); display: none; width: 264px; margin-top: var(--ua-space-15); @@ -3468,7 +3468,7 @@ button.ua-step-dot:hover { position: fixed; right: 24px; bottom: 24px; - z-index: 60; + z-index: var(--ua-z-sticky); display: flex; align-items: center; justify-content: center; @@ -3569,7 +3569,7 @@ button.ua-step-dot:hover { } .ua-colorpicker-pop { position: absolute; - z-index: 40; + z-index: var(--ua-z-dropdown); display: none; width: 232px; margin-top: var(--ua-space-15); @@ -3647,7 +3647,7 @@ button.ua-step-dot:hover { .ua-tour { position: fixed; inset: 0; - z-index: 70; + z-index: var(--ua-z-overlay); display: none; pointer-events: none; } @@ -3756,7 +3756,7 @@ button.ua-step-dot:hover { } .ua-affix-inner.is-fixed { position: fixed; - z-index: 50; + z-index: var(--ua-z-sticky); } .ua-multiselect { position: relative; @@ -3833,7 +3833,7 @@ button.ua-step-dot:hover { } .ua-multiselect-pop { position: absolute; - z-index: 40; + z-index: var(--ua-z-dropdown); left: 0; right: 0; display: none; @@ -4321,7 +4321,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } /* —— 悬浮操作按钮:fixed 停靠角落,hover 上浮 + 按压回缩 —— */ .ua-fab-wrap { position: fixed; - z-index: 40; /* 低于弹层(60+),高于正文 */ + z-index: var(--ua-z-dropdown); /* 低于弹层(60+),高于正文 */ display: flex; gap: var(--ua-space-2); } @@ -4433,7 +4433,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } left: 0; right: 0; top: calc(100% + 4px); - z-index: 30; + z-index: var(--ua-z-dropdown); display: none; max-height: 220px; overflow-y: auto; @@ -4532,7 +4532,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } left: 0; right: 0; top: calc(100% + 4px); - z-index: 30; + z-index: var(--ua-z-dropdown); display: none; max-height: 280px; overflow: auto; diff --git a/packages/tools/cli/lib/visual-rules.mjs b/packages/tools/cli/lib/visual-rules.mjs index 314026d..ae0bd5f 100644 --- a/packages/tools/cli/lib/visual-rules.mjs +++ b/packages/tools/cli/lib/visual-rules.mjs @@ -56,6 +56,11 @@ export const KIND_META = [ label: '行高字面值', hint: 'line-height 写死数值,应改用 --ua-leading-*(px 行高是图形对齐技巧,不入此口径)', }, + { + key: 'z-literal', + label: '层级字面值', + hint: 'z-index 写死数字,应改用 --ua-z-* 五档(组件内 1/2 微排序豁免)', + }, ]; /*** 作用:需要扫描的 CSS 属性白名单(其余属性本轮不动;用白名单顺带避开伪类选择器误判) ***/ @@ -86,6 +91,7 @@ const SCAN_PROPS = new Set([ 'height', 'font-weight', 'line-height', + 'z-index', ]); /*** 作用:内距类属性集合(迁移与统计共用同一判定) ***/ @@ -183,6 +189,9 @@ const LEADING_TOKENS = new Map([ ['1.65', 'var(--ua-leading-normal)'], ]); +/*** 作用:下拉类 z-index 字面值集合(历史上下拉/气泡弹层用的 20/30/40 档,统一归 --ua-z-dropdown) ***/ +const Z_DROPDOWN_LIKE = new Set(['20', '30', '40']); + /*** 作用:压缩空白,便于用稳定字符串比对(不改语义) ***/ export function squash(value) { return String(value).replace(/\s+/g, ' ').trim(); @@ -208,7 +217,7 @@ export function blankComments(src) { /*** 作用:逐条产出「属性 + 值」及其在原文中的偏移;只认白名单属性且必须以分号收尾(避开伪类选择器) ***/ export function eachDeclaration(code) { - const re = /(padding-top|padding-right|padding-bottom|padding-left|padding|margin-top|margin-right|margin-bottom|margin-left|margin|row-gap|column-gap|gap|font-weight|line-height|min-height|max-height|height|transition-duration|animation-duration|transition|animation|font-size|box-shadow|border-radius|outline)\s*:\s*([^;{}]*);/gi; + const re = /(padding-top|padding-right|padding-bottom|padding-left|padding|margin-top|margin-right|margin-bottom|margin-left|margin|row-gap|column-gap|gap|font-weight|line-height|z-index|min-height|max-height|height|transition-duration|animation-duration|transition|animation|font-size|box-shadow|border-radius|outline)\s*:\s*([^;{}]*);/gi; const list = []; for (const m of code.matchAll(re)) { const prop = m[1].toLowerCase(); @@ -334,6 +343,13 @@ function migrateLeading(core) { return { kind: 'leading-literal', value: token }; } +/*** 作用:迁移单条层级:下拉类常见字面值(20/30/40)归 dropdown 档;其余取值层级语义需按选择器人工归档,留给门禁拦截 ***/ +function migrateZ(core) { + const token = Z_DROPDOWN_LIKE.has(squash(core)) ? 'var(--ua-z-dropdown)' : null; + if (!token) return null; + return { kind: 'z-literal', value: token }; +} + /*** 作用:判定单条声明是否需要迁移,返回 { kind, value } 或 null(幂等:已是令牌就返回 null) ***/ export function migrateDeclaration(prop, rawValue) { const { core, tail } = splitImportant(rawValue); @@ -356,6 +372,8 @@ export function migrateDeclaration(prop, rawValue) { result = migrateWeight(core); } else if (prop === 'line-height') { result = migrateLeading(core); + } else if (prop === 'z-index') { + result = migrateZ(core); } if (!result) return null; return { kind: result.kind, value: result.value + tail }; @@ -417,6 +435,12 @@ function detect(prop, rawValue, key) { if (/px|em|rem|%|calc/.test(value)) return 0; // px 行高是图形对齐技巧(图标 / 角标),不入此口径 return /^\d+(\.\d+)?$/.test(value) ? 1 : 0; // unitless 数值归三档令牌 } + if (key === 'z-literal') { + if (prop !== 'z-index') return 0; + if (/^var\(/.test(value)) return 0; + if (value === '1' || value === '2') return 0; // 组件内微排序(表头压行、水印叠层、头像悬停),不是全局层级 + return /^\d+$/.test(value) ? 1 : 0; + } return 0; }