@agile-team/wl-skills-ui 1.8.1 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +8 -8
- package/docs/governance-long-term.md +25 -12
- package/package.json +3 -1
- package/scanner/__tests__/fixtures/r001-fail.vue +7 -0
- package/scanner/__tests__/fixtures/r001-pass.vue +7 -0
- package/scanner/__tests__/fixtures/r002-fail.vue +5 -0
- package/scanner/__tests__/fixtures/r002-pass.vue +5 -0
- package/scanner/__tests__/fixtures/r006-fail.vue +10 -0
- package/scanner/__tests__/fixtures/r006-pass.vue +10 -0
- package/scanner/__tests__/fixtures/r011-fail.vue +11 -0
- package/scanner/__tests__/fixtures/r011-pass.vue +14 -0
- package/scanner/__tests__/fixtures/r016-fail.vue +9 -0
- package/scanner/__tests__/fixtures/r016-pass.vue +9 -0
- package/scanner/__tests__/fixtures/r021-fail.vue +3 -0
- package/scanner/__tests__/fixtures/r021-pass.vue +3 -0
- package/scanner/__tests__/scan.test.mjs +145 -0
- package/scanner/index.mjs +25 -1
- package/scripts/check-scss.mjs +99 -0
- package/styles/vendors/_base-components.scss +17 -4
- package/styles/vendors/_base-query-toolbar.scss +10 -1
- package/styles/vendors/_jh-ui.scss +3 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ All notable changes to **@agile-team/wl-skills-ui** will be documented in this f
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [1.8.2] - 2026-05-13
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **搜索区字号不一致**:`_base-query-toolbar.scss` 统一 label / input / select / placeholder 为 **12px**,消除 13px→12px 混杂导致的视觉跳动。
|
|
12
|
+
- **必填星号重复 `* *`**:`_base-components.scss` 必填星号改为精确模式——默认只显示 `::before`,`asterisk-right` 仅显示 `::after`,避免 `display: inline-block !important` 同时作用于两个伪元素。`_jh-ui.scss` 同步精简为只声明颜色不控制 `display`。
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **scanner fixture 测试集**:`scanner/__tests__/` 覆盖 R001/R002/R006/R011/R016/R021 + drift 模块共 16 条自动化测试,`npm test` 一键跑。
|
|
17
|
+
- **SCSS 链路检查**:`scripts/check-scss.mjs` 递归验证 `styles/index.scss` 和 `presets/skin.scss` 的 `@forward/@use` 链路完整性。
|
|
18
|
+
- **scan --baseline 一步到位**:`wl-scan scan --target src --baseline .wl-baseline.json` 扫描后自动对比基线输出漂移报告,`--fail-on-error` 同时拦截新增违规。
|
|
19
|
+
- `package.json` 新增 `test` 和 `check:scss` 脚本。
|
|
20
|
+
|
|
7
21
|
## [1.8.1] - 2026-05-13
|
|
8
22
|
|
|
9
23
|
### Added
|
package/README.md
CHANGED
|
@@ -213,14 +213,14 @@ yarn add @agile-team/wl-skills-ui
|
|
|
213
213
|
|
|
214
214
|
## 版本亮点
|
|
215
215
|
|
|
216
|
-
当前 v1.8.
|
|
217
|
-
|
|
218
|
-
-
|
|
219
|
-
- 新增
|
|
220
|
-
-
|
|
221
|
-
-
|
|
222
|
-
-
|
|
223
|
-
-
|
|
216
|
+
当前 v1.8.2:
|
|
217
|
+
|
|
218
|
+
- **修复**搜索区字号 13px/12px 混杂 → 统一 12px;必填星号 `* *` 重复 → 精确单颗控制
|
|
219
|
+
- 新增 **scanner fixture 测试集**(16 条自动化测试,`npm test`)和 **SCSS 链路检查**(`npm run check:scss`)
|
|
220
|
+
- `scan --baseline` 一步到位:扫描后自动对比基线输出漂移报告,CI 增量门槛
|
|
221
|
+
- **漂移检测** `scanner/drift.mjs` + CLI `wl-scan drift` + MCP `wl_ui_drift`
|
|
222
|
+
- **R-rule 单一事实源** `standards/rules.json`(29 条),五向一致性守卫
|
|
223
|
+
- **长效治理方案** `docs/governance-long-term.md`(基线 / 豁免 / 漂移 / 版本钉死 / AI 守护)
|
|
224
224
|
|
|
225
225
|
历史亮点(v1.7.1):
|
|
226
226
|
|
|
@@ -78,24 +78,37 @@ npx wl-ui scan --target src --baseline .wl-baseline.json --fail-on=new-issues
|
|
|
78
78
|
**目标**:让"逐渐变烂"可见,每次升级都能量化。
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
|
-
#
|
|
82
|
-
npx wl-ui
|
|
83
|
-
|
|
81
|
+
# 方式一:scan 一步到位(v1.8.2+)
|
|
82
|
+
npx wl-ui scan --target src --baseline .wl-baseline.json --fail-on-error
|
|
83
|
+
|
|
84
|
+
# 方式二:独立 drift 子命令
|
|
85
|
+
npx wl-ui scan --target src --output json --outFile .wl-current.json
|
|
86
|
+
npx wl-ui drift --baseline .wl-baseline.json --current .wl-current.json
|
|
84
87
|
```
|
|
85
88
|
|
|
86
|
-
|
|
89
|
+
输出示例:
|
|
87
90
|
|
|
88
91
|
```
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
╭──────────────────────────────────────╮
|
|
93
|
+
│ wl-skills-ui 漂移报告 │
|
|
94
|
+
╰──────────────────────────────────────╯
|
|
95
|
+
|
|
96
|
+
基线违规: 120 → 当前违规: 87 (-33)
|
|
97
|
+
|
|
98
|
+
🔴 gained +3 新增违规
|
|
99
|
+
🟢 fixed -36 消化历史
|
|
100
|
+
|
|
101
|
+
新增 Top 规则:
|
|
102
|
+
R017 +2
|
|
103
|
+
R001 +1
|
|
104
|
+
|
|
105
|
+
消化 Top 规则:
|
|
106
|
+
R009 -15
|
|
107
|
+
R004 -12
|
|
108
|
+
R016 -9
|
|
96
109
|
```
|
|
97
110
|
|
|
98
|
-
>
|
|
111
|
+
> 实现入口:`scanner/drift.mjs` 提供 `drift()` / `driftFromFiles()` / `formatDriftText()` / `formatDriftJson()` 四个 API。MCP 工具 `wl_ui_drift` 可供 AI 直接调用。
|
|
99
112
|
|
|
100
113
|
---
|
|
101
114
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agile-team/wl-skills-ui",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
4
4
|
"description": "企业级 UI 风格对齐框架 — Vue + Element Plus 项目通用化妆/原生双模式(tokens / element / vendors / layouts / runtime / scanner / fixer / skills)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./es/index.js",
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
"build": "tsup && npm run sync:tokens",
|
|
42
42
|
"prepare": "npm run build",
|
|
43
43
|
"sync:tokens": "node -e \"import('fs').then(fs=>fs.copyFileSync('design/tokens/base.css','dist/tokens.css'))\"",
|
|
44
|
+
"test": "node --test scanner/__tests__/scan.test.mjs",
|
|
45
|
+
"check:scss": "node scripts/check-scss.mjs",
|
|
44
46
|
"docs:check": "node scripts/check-docs.mjs",
|
|
45
47
|
"scan": "node scanner/index.mjs scan",
|
|
46
48
|
"check": "node scanner/index.mjs check",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-table :data="list" empty-text="暂无数据">
|
|
3
|
+
<el-table-column type="selection" align="center" header-align="center" width="55" />
|
|
4
|
+
<el-table-column prop="name" label="名称" align="center" />
|
|
5
|
+
<el-table-column prop="status" label="状态" align="center" />
|
|
6
|
+
</el-table>
|
|
7
|
+
</template>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-dialog v-model="visible" title="列表">
|
|
3
|
+
<el-table :data="list">
|
|
4
|
+
<el-table-column prop="name" label="名称" align="center" />
|
|
5
|
+
</el-table>
|
|
6
|
+
<template #footer>
|
|
7
|
+
<el-pagination :total="total" />
|
|
8
|
+
<el-button @click="visible = false">取消</el-button>
|
|
9
|
+
</template>
|
|
10
|
+
</el-dialog>
|
|
11
|
+
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-dialog v-model="visible" title="列表">
|
|
3
|
+
<el-table :data="list">
|
|
4
|
+
<el-table-column prop="name" label="名称" align="center" />
|
|
5
|
+
</el-table>
|
|
6
|
+
<div class="popup-pagination">
|
|
7
|
+
<el-pagination :total="total" />
|
|
8
|
+
</div>
|
|
9
|
+
<template #footer>
|
|
10
|
+
<el-button @click="visible = false">取消</el-button>
|
|
11
|
+
<el-button type="primary" @click="save">确定</el-button>
|
|
12
|
+
</template>
|
|
13
|
+
</el-dialog>
|
|
14
|
+
</template>
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scanner/__tests__/scan.test.mjs — scanner 规则 fixture 测试
|
|
3
|
+
*
|
|
4
|
+
* 用法:node --test scanner/__tests__/scan.test.mjs
|
|
5
|
+
*
|
|
6
|
+
* 约定:fixtures/<ruleId>-pass.vue → 0 violations of that rule
|
|
7
|
+
* fixtures/<ruleId>-fail.vue → ≥1 violations of that rule
|
|
8
|
+
*
|
|
9
|
+
* 每条测试只断言对应 rule 的命中数,不受其他规则干扰。
|
|
10
|
+
*/
|
|
11
|
+
import { describe, it } from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
14
|
+
import { join, dirname } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { getRules } from "../rules/index.mjs";
|
|
17
|
+
|
|
18
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const FIXTURES = join(__dirname, "fixtures");
|
|
20
|
+
const rules = getRules();
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 简易单文件扫描:跑所有规则,返回 issues 数组
|
|
24
|
+
*/
|
|
25
|
+
function scanFixture(filename) {
|
|
26
|
+
const content = readFileSync(join(FIXTURES, filename), "utf8");
|
|
27
|
+
const issues = [];
|
|
28
|
+
|
|
29
|
+
// extract template
|
|
30
|
+
const tmplMatch = content.match(/(<template[^>]*>)([\s\S]*?)(<\/template>)/);
|
|
31
|
+
const template = tmplMatch ? tmplMatch[0] : content;
|
|
32
|
+
const tmplOffset = tmplMatch
|
|
33
|
+
? content.slice(0, content.indexOf(tmplMatch[0])).split("\n").length - 1
|
|
34
|
+
: 0;
|
|
35
|
+
|
|
36
|
+
// extract style
|
|
37
|
+
const styleMatch = content.match(/<style[^>]*>([\s\S]*?)<\/style>/);
|
|
38
|
+
const styleBlock = styleMatch ? styleMatch[1] : null;
|
|
39
|
+
const styleOffset = styleMatch
|
|
40
|
+
? content.slice(0, content.indexOf(styleMatch[0])).split("\n").length
|
|
41
|
+
: 0;
|
|
42
|
+
|
|
43
|
+
// extract script
|
|
44
|
+
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
|
|
45
|
+
const scriptBlock = scriptMatch ? scriptMatch[1] : null;
|
|
46
|
+
const scriptOffset = scriptMatch
|
|
47
|
+
? content.slice(0, content.indexOf(scriptMatch[0])).split("\n").length
|
|
48
|
+
: 0;
|
|
49
|
+
|
|
50
|
+
for (const rule of rules) {
|
|
51
|
+
if (typeof rule.check === "function") {
|
|
52
|
+
issues.push(...rule.check(template, filename, tmplOffset));
|
|
53
|
+
}
|
|
54
|
+
if (styleBlock && typeof rule.checkStyle === "function") {
|
|
55
|
+
issues.push(...rule.checkStyle(styleBlock, filename, styleOffset));
|
|
56
|
+
}
|
|
57
|
+
if (scriptBlock && typeof rule.checkScript === "function") {
|
|
58
|
+
issues.push(...rule.checkScript(scriptBlock, filename, scriptOffset));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return issues;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── 自动发现 fixtures 并生成测试 ────────────────────────────────────────────
|
|
65
|
+
const fixtureFiles = readdirSync(FIXTURES).filter((f) => f.endsWith(".vue"));
|
|
66
|
+
const grouped = new Map(); // ruleId → { pass: [], fail: [] }
|
|
67
|
+
|
|
68
|
+
for (const f of fixtureFiles) {
|
|
69
|
+
const m = f.match(/^(r\d+)-(pass|fail)\.vue$/i);
|
|
70
|
+
if (!m) continue;
|
|
71
|
+
const ruleId = m[1].toUpperCase();
|
|
72
|
+
const kind = m[2]; // pass | fail
|
|
73
|
+
if (!grouped.has(ruleId)) grouped.set(ruleId, { pass: [], fail: [] });
|
|
74
|
+
grouped.get(ruleId)[kind].push(f);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const [ruleId, { pass, fail }] of grouped) {
|
|
78
|
+
describe(ruleId, () => {
|
|
79
|
+
for (const f of pass) {
|
|
80
|
+
it(`${f} → 0 violations`, () => {
|
|
81
|
+
const issues = scanFixture(f).filter((i) => i.rule === ruleId);
|
|
82
|
+
assert.equal(
|
|
83
|
+
issues.length,
|
|
84
|
+
0,
|
|
85
|
+
`Expected 0 ${ruleId} issues but got ${issues.length}: ${JSON.stringify(issues, null, 2)}`,
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
for (const f of fail) {
|
|
90
|
+
it(`${f} → ≥1 violations`, () => {
|
|
91
|
+
const issues = scanFixture(f).filter((i) => i.rule === ruleId);
|
|
92
|
+
assert.ok(issues.length > 0, `Expected ≥1 ${ruleId} issues but got 0`);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── drift 模块测试 ──────────────────────────────────────────────────────────
|
|
99
|
+
const { drift } = await import("../drift.mjs");
|
|
100
|
+
|
|
101
|
+
describe("drift", () => {
|
|
102
|
+
it("identical → 0 gained, 0 fixed", () => {
|
|
103
|
+
const data = { issues: [{ file: "a.vue", rule: "R001" }] };
|
|
104
|
+
const r = drift(data, data);
|
|
105
|
+
assert.equal(r.gained.count, 0);
|
|
106
|
+
assert.equal(r.fixed.count, 0);
|
|
107
|
+
assert.equal(r.netDelta, 0);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("new issues → gained > 0", () => {
|
|
111
|
+
const base = { issues: [] };
|
|
112
|
+
const cur = { issues: [{ file: "a.vue", rule: "R001" }] };
|
|
113
|
+
const r = drift(base, cur);
|
|
114
|
+
assert.equal(r.gained.count, 1);
|
|
115
|
+
assert.equal(r.fixed.count, 0);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("removed issues → fixed > 0", () => {
|
|
119
|
+
const base = { issues: [{ file: "a.vue", rule: "R001" }] };
|
|
120
|
+
const cur = { issues: [] };
|
|
121
|
+
const r = drift(base, cur);
|
|
122
|
+
assert.equal(r.gained.count, 0);
|
|
123
|
+
assert.equal(r.fixed.count, 1);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("mixed → both gained and fixed", () => {
|
|
127
|
+
const base = {
|
|
128
|
+
issues: [
|
|
129
|
+
{ file: "a.vue", rule: "R001" },
|
|
130
|
+
{ file: "a.vue", rule: "R001" },
|
|
131
|
+
{ file: "b.vue", rule: "R016" },
|
|
132
|
+
],
|
|
133
|
+
};
|
|
134
|
+
const cur = {
|
|
135
|
+
issues: [
|
|
136
|
+
{ file: "a.vue", rule: "R001" },
|
|
137
|
+
{ file: "c.vue", rule: "R017" },
|
|
138
|
+
{ file: "c.vue", rule: "R017" },
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
const r = drift(base, cur);
|
|
142
|
+
assert.equal(r.gained.count, 2); // c.vue R017 ×2
|
|
143
|
+
assert.equal(r.fixed.count, 2); // a.vue R001 ×1 + b.vue R016 ×1
|
|
144
|
+
});
|
|
145
|
+
});
|
package/scanner/index.mjs
CHANGED
|
@@ -423,6 +423,30 @@ if (subcommand === "all") {
|
|
|
423
423
|
} else {
|
|
424
424
|
console.log(report);
|
|
425
425
|
}
|
|
426
|
+
|
|
427
|
+
// ── baseline 漂移检测(--baseline <file>)────────────────────────────────
|
|
428
|
+
let driftResult = null;
|
|
429
|
+
if (values.baseline) {
|
|
430
|
+
const { existsSync: fileExists } = await import("node:fs");
|
|
431
|
+
const baselinePath = resolve(values.baseline);
|
|
432
|
+
if (fileExists(baselinePath)) {
|
|
433
|
+
const { drift, formatDriftText, formatDriftJson } =
|
|
434
|
+
await import("./drift.mjs");
|
|
435
|
+
const baseline = JSON.parse(readFileSync(baselinePath, "utf8"));
|
|
436
|
+
const currentData = { issues: filtered };
|
|
437
|
+
driftResult = drift(baseline, currentData);
|
|
438
|
+
console.error("");
|
|
439
|
+
if (values.output === "json") {
|
|
440
|
+
console.error(formatDriftJson(driftResult));
|
|
441
|
+
} else {
|
|
442
|
+
console.error(formatDriftText(driftResult));
|
|
443
|
+
}
|
|
444
|
+
} else {
|
|
445
|
+
console.error(`[wl-scan] 基线文件不存在: ${baselinePath},跳过漂移检测`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
426
449
|
const hasError = filtered.some((i) => i.severity === "error");
|
|
427
|
-
|
|
450
|
+
const hasDriftNew = driftResult && driftResult.gained.count > 0;
|
|
451
|
+
process.exit(values["fail-on-error"] && (hasError || hasDriftNew) ? 1 : 0);
|
|
428
452
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scripts/check-scss.mjs — SCSS 链路完整性检查
|
|
4
|
+
*
|
|
5
|
+
* 递归验证 styles/index.scss 的 @forward/@use 链,
|
|
6
|
+
* 确保所有被引入的 SCSS partial 文件真实存在。
|
|
7
|
+
* 跳过 sass: 内置模块和包自引用(@agile-team/... / wl-skills-ui/...)。
|
|
8
|
+
*
|
|
9
|
+
* 用法:npm run check:scss
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
12
|
+
import { join, dirname, resolve, basename } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const ROOT = resolve(__dirname, "..");
|
|
17
|
+
const STYLES = join(ROOT, "styles");
|
|
18
|
+
|
|
19
|
+
let errors = 0;
|
|
20
|
+
|
|
21
|
+
/** 跳过包自引用和 sass 内置 */
|
|
22
|
+
function shouldSkip(ref) {
|
|
23
|
+
if (ref.startsWith("sass:")) return true;
|
|
24
|
+
if (ref.startsWith("~")) return true;
|
|
25
|
+
if (ref.startsWith("@agile-team/")) return true;
|
|
26
|
+
if (ref.startsWith("wl-skills-ui/")) return true;
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 解析 SCSS @forward/@use 引用到实际文件路径 */
|
|
31
|
+
function resolveScssRef(dir, ref) {
|
|
32
|
+
// 已有 .scss 后缀
|
|
33
|
+
if (ref.endsWith(".scss")) {
|
|
34
|
+
const direct = join(dir, ref);
|
|
35
|
+
if (existsSync(direct)) return direct;
|
|
36
|
+
// _partial
|
|
37
|
+
const partial = join(dirname(direct), "_" + basename(direct));
|
|
38
|
+
if (existsSync(partial)) return partial;
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
// 无后缀:SCSS partial 解析规则
|
|
42
|
+
const name = basename(ref);
|
|
43
|
+
const refDir = dirname(ref);
|
|
44
|
+
const base = join(dir, refDir);
|
|
45
|
+
const candidates = [
|
|
46
|
+
join(base, name + ".scss"),
|
|
47
|
+
join(base, "_" + name + ".scss"),
|
|
48
|
+
join(base, name, "index.scss"),
|
|
49
|
+
join(base, name, "_index.scss"),
|
|
50
|
+
];
|
|
51
|
+
return candidates.find((c) => existsSync(c)) || null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function checkImportChain(filePath, visited = new Set()) {
|
|
55
|
+
const norm = resolve(filePath);
|
|
56
|
+
if (visited.has(norm)) return;
|
|
57
|
+
visited.add(norm);
|
|
58
|
+
if (!existsSync(norm)) {
|
|
59
|
+
console.error(`❌ 文件不存在: ${norm}`);
|
|
60
|
+
errors++;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const content = readFileSync(norm, "utf8");
|
|
64
|
+
const re = /@(?:forward|use|import)\s+['"]([^'"]+)['"]/g;
|
|
65
|
+
let m;
|
|
66
|
+
while ((m = re.exec(content)) !== null) {
|
|
67
|
+
const ref = m[1];
|
|
68
|
+
if (shouldSkip(ref)) continue;
|
|
69
|
+
const resolved = resolveScssRef(dirname(norm), ref);
|
|
70
|
+
if (!resolved) {
|
|
71
|
+
console.error(`❌ ${norm} → "${ref}" 无法解析`);
|
|
72
|
+
errors++;
|
|
73
|
+
} else {
|
|
74
|
+
checkImportChain(resolved, visited);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 入口
|
|
80
|
+
const entryPoints = [
|
|
81
|
+
join(STYLES, "index.scss"),
|
|
82
|
+
join(STYLES, "presets", "skin.scss"),
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
for (const entry of entryPoints) {
|
|
86
|
+
if (!existsSync(entry)) {
|
|
87
|
+
console.error(`❌ 入口文件不存在: ${entry}`);
|
|
88
|
+
errors++;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
checkImportChain(entry);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (errors > 0) {
|
|
95
|
+
console.error(`\n❌ SCSS 链路检查失败: ${errors} 个错误`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
} else {
|
|
98
|
+
console.log("✅ SCSS 链路检查通过");
|
|
99
|
+
}
|
|
@@ -508,13 +508,26 @@
|
|
|
508
508
|
word-break: keep-all;
|
|
509
509
|
}
|
|
510
510
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
.el-form-item.is-required:not(.is-no-asterisk)>.el-form-item__label::before
|
|
514
|
-
.el-form-item.is-required:not(.is-no-asterisk)>.el-form-item__label::after {
|
|
511
|
+
// 必填星号:只保留一颗 — 默认 asterisk-left(::before),asterisk-right 用 ::after
|
|
512
|
+
// 旧写法同时 display:inline-block 两个伪元素导致 * * 重复
|
|
513
|
+
.el-form-item.is-required:not(.is-no-asterisk)>.el-form-item__label::before {
|
|
515
514
|
color: var(--el-color-danger, #f56c6c) !important;
|
|
516
515
|
display: inline-block !important;
|
|
517
516
|
}
|
|
517
|
+
// asterisk-right 模式:显示 ::after,隐藏 ::before
|
|
518
|
+
.el-form-item.is-required:not(.is-no-asterisk).asterisk-right>.el-form-item__label::before {
|
|
519
|
+
display: none !important;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.el-form-item.is-required:not(.is-no-asterisk).asterisk-right>.el-form-item__label::after {
|
|
523
|
+
color: var(--el-color-danger, #f56c6c) !important;
|
|
524
|
+
display: inline-block !important;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// 默认模式:隐藏 ::after 防止重复星号
|
|
528
|
+
.el-form-item.is-required:not(.is-no-asterisk):not(.asterisk-right)>.el-form-item__label::after {
|
|
529
|
+
display: none !important;
|
|
530
|
+
}
|
|
518
531
|
|
|
519
532
|
.el-form-item.is-error {
|
|
520
533
|
.el-form-item__label,
|
|
@@ -33,12 +33,21 @@
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// ── 搜索区字号统一 12px(紧凑密度,与 label / button 一致)────────────────
|
|
37
|
+
.el-form-item__label {
|
|
38
|
+
font-size: 12px !important;
|
|
39
|
+
}
|
|
36
40
|
.el-input__inner {
|
|
37
|
-
font-size:
|
|
41
|
+
font-size: 12px;
|
|
38
42
|
color: var(--el-text-color-primary);
|
|
39
43
|
&::placeholder { color: var(--el-text-color-placeholder); font-size: 12px; }
|
|
40
44
|
}
|
|
41
45
|
|
|
46
|
+
.el-select__placeholder,
|
|
47
|
+
.el-select__tags-text,
|
|
48
|
+
.el-select .el-select__selected-item {
|
|
49
|
+
font-size: 12px;
|
|
50
|
+
}
|
|
42
51
|
// ── 选择框 ────────────────────────────────────────────────────────────────
|
|
43
52
|
.el-select .el-select__wrapper {
|
|
44
53
|
border-radius: 6px !important;
|
|
@@ -91,12 +91,9 @@
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
// 6) 必填星号兼容 jh-ui label 结构
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
// `::before` 会作为 flex 项参与排版,必须显式声明颜色/显示。
|
|
94
|
+
// 颜色统一 danger 色;显示/隐藏由 _base-components.scss 总控,
|
|
95
|
+
// 此处只负责颜色,不设 display,避免与总控冲突导致 * * 重复。
|
|
97
96
|
.el-form-item.is-required:not(.is-no-asterisk) > .el-form-item__label::before,
|
|
98
|
-
.el-form-item.is-required:not(.is-no-asterisk)
|
|
99
|
-
.el-form-item.is-required:not(.is-no-asterisk) > .el-form-item__label > .com-text::before,
|
|
100
|
-
.el-form-item.is-required:not(.is-no-asterisk) > .el-form-item__label > .com-text::after {
|
|
97
|
+
.el-form-item.is-required:not(.is-no-asterisk)>.el-form-item__label>.com-text::before {
|
|
101
98
|
color: var(--el-color-danger, #f56c6c) !important;
|
|
102
99
|
}
|