@agile-team/wl-skills-ui 1.8.2 → 1.8.4

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 CHANGED
@@ -4,6 +4,13 @@ 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.3] - 2026-05-13
8
+
9
+ ### Added
10
+
11
+ - **scan --only / --skip**:支持规则级过滤,`--only R001,R016` 仅跑指定规则,`--skip R031-R037` 排除范围(支持连字符范围展开)。
12
+ - **exempt init 脚手架**:`wl-scan exempt init --target src` 智能扫描 src 下 big-screen/dashboard/chart 等个性化目录,自动生成 `.wl-exempt.json` 模板。
13
+
7
14
  ## [1.8.2] - 2026-05-13
8
15
 
9
16
  ### Fixed
package/README.md CHANGED
@@ -213,14 +213,15 @@ yarn add @agile-team/wl-skills-ui
213
213
 
214
214
  ## 版本亮点
215
215
 
216
- 当前 v1.8.2
216
+ 当前 v1.8.3
217
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`
218
+ - `scan --only R001,R016` / `--skip R031-R037` 规则级过滤(支持范围展开)
219
+ - `exempt init --target src` 智能扫描个性化目录,自动生成 `.wl-exempt.json`
220
+ - **修复**搜索区字号统一 12px;必填星号 `* *` 去重
221
+ - **scanner fixture 测试集**(16 条,`npm test`)+ **SCSS 链路检查**(`npm run check:scss`)
222
+ - `scan --baseline` 一步漂移对比 + **drift.mjs** / MCP `wl_ui_drift`
222
223
  - **R-rule 单一事实源** `standards/rules.json`(29 条),五向一致性守卫
223
- - **长效治理方案** `docs/governance-long-term.md`(基线 / 豁免 / 漂移 / 版本钉死 / AI 守护)
224
+ - **长效治理方案** `docs/governance-long-term.md`
224
225
 
225
226
  历史亮点(v1.7.1):
226
227
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agile-team/wl-skills-ui",
3
- "version": "1.8.2",
3
+ "version": "1.8.4",
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",
package/scanner/index.mjs CHANGED
@@ -4,6 +4,8 @@
4
4
  *
5
5
  * 用法:
6
6
  * wl-scan scan --target <path> # 风格扫描
7
+ * wl-scan scan --target <path> --only R001,R016
8
+ * wl-scan scan --target <path> --skip R031-R037
7
9
  * wl-scan scan --target <path> --outFile report.md
8
10
  * wl-scan scan --target <path> --output json
9
11
  * wl-scan check --project <path> # 接入完整性检查
@@ -12,6 +14,7 @@
12
14
  * wl-scan all --project <path> # 接入检查 + 风格扫描 + 报告
13
15
  * wl-scan init # 打印接入指引
14
16
  * wl-scan drift --baseline <f> --current <f> # 漂移检测
17
+ * wl-scan exempt init --target <path> # 生成 .wl-exempt.json 豁免模板
15
18
  * wl-scan snapshot list # 列出快照
16
19
  * wl-scan snapshot rollback [--id <id>] # 回退到快照(默认最新)
17
20
  * wl-scan snapshot diff [--id <id>] # 查看快照与当前差异
@@ -45,6 +48,7 @@ const SUBCOMMANDS = new Set([
45
48
  "all",
46
49
  "init",
47
50
  "drift",
51
+ "exempt",
48
52
  "snapshot",
49
53
  ]);
50
54
  let subcommand = "scan";
@@ -71,6 +75,8 @@ const { values } = parseArgs({
71
75
  exempt: { type: "string", default: "" },
72
76
  baseline: { type: "string", default: "" },
73
77
  current: { type: "string", default: "" },
78
+ only: { type: "string", default: "" },
79
+ skip: { type: "string", default: "" },
74
80
  },
75
81
  strict: false,
76
82
  });
@@ -199,6 +205,24 @@ function runScan(targetDir, excludeDirs, exemptConfig) {
199
205
  };
200
206
  }
201
207
 
208
+ // ── 公共:规则范围展开(R031-R037 → R031,R032,...,R037)────────────────────
209
+ function expandRuleRange(input) {
210
+ const set = new Set();
211
+ for (const part of input.split(",").map((s) => s.trim())) {
212
+ const rangeMatch = part.match(/^(R)(\d+)-(R)?(\d+)$/i);
213
+ if (rangeMatch) {
214
+ const start = parseInt(rangeMatch[2]);
215
+ const end = parseInt(rangeMatch[4]);
216
+ for (let i = Math.min(start, end); i <= Math.max(start, end); i++) {
217
+ set.add("R" + String(i).padStart(3, "0"));
218
+ }
219
+ } else {
220
+ set.add(part.toUpperCase());
221
+ }
222
+ }
223
+ return set;
224
+ }
225
+
202
226
  // ── 公共:按 layer / vendor / mode 过滤 ─────────────────────────────────────
203
227
  function applyFilters(issues) {
204
228
  let out = issues;
@@ -216,6 +240,16 @@ function applyFilters(issues) {
216
240
  } else if (values.mode === "native") {
217
241
  // 原生模式:全部 layer
218
242
  }
243
+ // --only R001,R016 → 仅保留指定规则
244
+ if (values.only) {
245
+ const allow = expandRuleRange(values.only);
246
+ out = out.filter((i) => allow.has(i.rule));
247
+ }
248
+ // --skip R031-R037 → 排除指定规则
249
+ if (values.skip) {
250
+ const deny = expandRuleRange(values.skip);
251
+ out = out.filter((i) => !deny.has(i.rule));
252
+ }
219
253
  return out;
220
254
  }
221
255
 
@@ -267,6 +301,76 @@ if (subcommand === "check") {
267
301
  process.exit(values["fail-on-error"] && hasError ? 1 : 0);
268
302
  }
269
303
 
304
+ if (subcommand === "exempt") {
305
+ const sub = args[0] || "init";
306
+ if (sub === "init") {
307
+ const { existsSync } = await import("node:fs");
308
+ const projectRoot = resolve(values.project);
309
+ const targetDir = resolve(values.target);
310
+ const outPath = join(projectRoot, ".wl-exempt.json");
311
+ if (existsSync(outPath)) {
312
+ console.log(`⚠️ ${outPath} 已存在,跳过生成。如需重新生成请先删除。`);
313
+ process.exit(0);
314
+ }
315
+ // 智能扫描目标目录下常见的个性化子目录
316
+ const EXEMPT_KEYWORDS = [
317
+ "big-screen",
318
+ "dashboard",
319
+ "map-view",
320
+ "topology",
321
+ "flow-designer",
322
+ "report-designer",
323
+ "chart",
324
+ "3d",
325
+ "canvas",
326
+ "screen",
327
+ "monitor",
328
+ "cockpit",
329
+ "visual",
330
+ ];
331
+ const smartPaths = [];
332
+ function walkForExempt(dir, depth = 0) {
333
+ if (depth > 4) return;
334
+ try {
335
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
336
+ if (!entry.isDirectory()) continue;
337
+ if (["node_modules", "dist", ".git"].includes(entry.name)) continue;
338
+ const rel = relative(targetDir, join(dir, entry.name)).replace(
339
+ /\\/g,
340
+ "/",
341
+ );
342
+ if (
343
+ EXEMPT_KEYWORDS.some((kw) => entry.name.toLowerCase().includes(kw))
344
+ ) {
345
+ smartPaths.push(rel + "/**");
346
+ } else {
347
+ walkForExempt(join(dir, entry.name), depth + 1);
348
+ }
349
+ }
350
+ } catch {
351
+ /* ignore permission errors */
352
+ }
353
+ }
354
+ if (existsSync(targetDir)) walkForExempt(targetDir);
355
+ const { generateExemptTemplate } = await import("./exempt.mjs");
356
+ const template = JSON.parse(generateExemptTemplate());
357
+ if (smartPaths.length > 0) {
358
+ template.exemptPaths = [
359
+ ...new Set([...smartPaths, ...template.exemptPaths]),
360
+ ];
361
+ template.description += `(自动扫描 ${targetDir} 发现 ${smartPaths.length} 个候选目录)`;
362
+ }
363
+ writeFileSync(outPath, JSON.stringify(template, null, 2) + "\n", "utf8");
364
+ console.log(`✅ 已生成 ${outPath}`);
365
+ if (smartPaths.length > 0) {
366
+ console.log(` 自动发现 ${smartPaths.length} 个候选豁免目录:`);
367
+ for (const p of smartPaths) console.log(` ${p}`);
368
+ }
369
+ console.log(` 请人工审核后提交至版本库。`);
370
+ }
371
+ process.exit(0);
372
+ }
373
+
270
374
  if (subcommand === "drift") {
271
375
  const { driftFromFiles, formatDriftText, formatDriftJson } =
272
376
  await import("./drift.mjs");
@@ -1,20 +1,20 @@
1
1
  // styles/element/_dialog.scss — el-dialog / el-message-box 覆盖
2
2
 
3
- .el-message-box .el-message-box__status {
3
+ html body .el-message-box .el-message-box__status {
4
4
  top: 12px !important;
5
5
  }
6
6
 
7
7
  // jh-ui dialog.scss 标题色为硬编码 #262626,覆盖为 Token
8
- html .el-dialog__title {
8
+ html body .el-dialog__title {
9
9
  color: var(--el-text-color-primary);
10
10
  }
11
11
 
12
12
  // EP 原生 fullscreen prop 场景:全屏与关闭两个 headerbtn 间距收紧
13
13
  // 注:自定义全屏图标(.dialog-fullscreen-action)不受此影响
14
- .el-dialog__headerbtn {
14
+ html body .el-dialog__headerbtn {
15
15
  right: 8px;
16
16
  }
17
- .el-dialog__headerbtn + .el-dialog__headerbtn {
17
+ html body .el-dialog__headerbtn+.el-dialog__headerbtn {
18
18
  right: 30px;
19
19
  }
20
20
 
@@ -4,24 +4,24 @@ html body {
4
4
  --wk-form-control-radius: var(--el-border-radius-base, 6px);
5
5
  }
6
6
 
7
- .el-form-item--small {
7
+ html body .el-form-item--small {
8
8
  margin-bottom: 8px;
9
9
  }
10
10
 
11
11
  // 解决 el-form-item 出现错误提示时底部 margin 增加的问题
12
- .is-error.el-form-item--small {
12
+ html body .is-error.el-form-item--small {
13
13
  margin-bottom: 18px !important;
14
14
  }
15
15
 
16
16
  // ── 全局输入框圆角统一(6px,与搜索区保持一致)──────────────────────────────
17
- .el-input__wrapper,
18
- .el-select__wrapper,
19
- .el-select .el-select__wrapper,
20
- .el-date-editor.el-input__wrapper,
21
- .el-date-editor .el-input__wrapper,
22
- .el-input-number .el-input__wrapper,
23
- .el-cascader .el-input__wrapper,
24
- .el-autocomplete .el-input__wrapper,
25
- .el-textarea__inner {
17
+ html body .el-input__wrapper,
18
+ html body .el-select__wrapper,
19
+ html body .el-select .el-select__wrapper,
20
+ html body .el-date-editor.el-input__wrapper,
21
+ html body .el-date-editor .el-input__wrapper,
22
+ html body .el-input-number .el-input__wrapper,
23
+ html body .el-cascader .el-input__wrapper,
24
+ html body .el-autocomplete .el-input__wrapper,
25
+ html body .el-textarea__inner {
26
26
  border-radius: var(--wk-form-control-radius) !important;
27
27
  }
@@ -1,6 +1,6 @@
1
1
  // styles/element/_pagination.scss — el-pagination 精致化覆盖
2
2
 
3
- .el-pagination {
3
+ html body .el-pagination {
4
4
  --el-pagination-button-width: 24px;
5
5
  --el-pagination-button-height: 24px;
6
6
  --el-pagination-button-font-size: 12px;
@@ -1,7 +1,7 @@
1
1
  // styles/element/_table.scss — el-table 表头颜色覆盖
2
2
  // jh-ui table.scss 表头颜色为硬编码,此处用 CSS 变量覆盖
3
3
 
4
- html .el-table {
4
+ html body .el-table {
5
5
  .el-table__header-wrapper th,
6
6
  .el-table__fixed-header-wrapper th {
7
7
  color: var(--el-text-color-primary, #303133);
@@ -28,4 +28,4 @@ html .el-table {
28
28
  width: 100%;
29
29
  margin: 0 auto;
30
30
  }
31
- }
31
+ }
@@ -12,7 +12,7 @@
12
12
  @forward '../element/index';
13
13
  @forward '../vendors/index';
14
14
 
15
- :root {
15
+ :root body {
16
16
  --el-color-primary: #2254f4;
17
17
  --el-color-primary-light-1: #3865f5;
18
18
  --el-color-primary-light-2: #5178f6;
@@ -77,3 +77,154 @@ $wk-component-size-large: var(--el-component-size-large) !default;
77
77
  $wk-sidebar-bg: var(--el-sidebar-bg) !default;
78
78
  $wk-sidebar-bg-active: var(--el-sidebar-bg-active) !default;
79
79
  $wk-sidebar-width: var(--el-sidebar-width) !default;
80
+
81
+ // ── CSS 自定义属性输出 ─────────────────────────────────────────────────────
82
+ // 与 design/tokens/base.css 保持同步。
83
+ // 作用:让任何 @forward tokens/index 的 preset 自动将 CSS 变量打入构建产物,
84
+ // 不再依赖 index.html <link> 或 JS import tokens.css。
85
+ // 选择器 :root body 特异性 (0,1,1) > Element Plus 的 :root (0,1,0),
86
+ // 保证无论 CSS 加载顺序如何,我们的变量始终优先。
87
+ :root body {
88
+ --el-color-primary: #2254f4;
89
+ --el-color-primary-light-1: #3865f5;
90
+ --el-color-primary-light-2: #5178f6;
91
+ --el-color-primary-light-3: #7a98f8;
92
+ --el-color-primary-light-4: #91aaf9;
93
+ --el-color-primary-light-5: #a8bbfa;
94
+ --el-color-primary-light-6: #bfccfc;
95
+ --el-color-primary-light-7: #d3ddfd;
96
+ --el-color-primary-light-8: #e6ebfe;
97
+ --el-color-primary-light-9: #f2f5ff;
98
+ --el-color-primary-dark-1: #1b43c3;
99
+ --el-color-primary-dark-2: #1738a3;
100
+ --el-color-primary-dark-3: #102873;
101
+ --el-color-primary-dark-4: #0a1a4d;
102
+ --el-color-success: #2bb268;
103
+ --el-color-success-light-1: #40ba77;
104
+ --el-color-success-light-2: #55c286;
105
+ --el-color-success-light-3: #6bcb95;
106
+ --el-color-success-light-4: #80d3a4;
107
+ --el-color-success-light-5: #95dbb4;
108
+ --el-color-success-light-6: #aae3c3;
109
+ --el-color-success-light-7: #bfebd2;
110
+ --el-color-success-light-8: #d5f4e1;
111
+ --el-color-success-light-9: #eaf9f0;
112
+ --el-color-success-dark-1: #229051;
113
+ --el-color-success-dark-2: #1b733f;
114
+ --el-color-success-dark-3: #14562f;
115
+ --el-color-success-dark-4: #0d3a20;
116
+ --el-color-warning: #ea9a13;
117
+ --el-color-warning-light-1: #eca42b;
118
+ --el-color-warning-light-2: #efae42;
119
+ --el-color-warning-light-3: #f1b85a;
120
+ --el-color-warning-light-4: #f3c171;
121
+ --el-color-warning-light-5: #f5cb89;
122
+ --el-color-warning-light-6: #f7d5a1;
123
+ --el-color-warning-light-7: #f9dfb8;
124
+ --el-color-warning-light-8: #fce8d0;
125
+ --el-color-warning-light-9: #fef2e7;
126
+ --el-color-warning-dark-1: #c47f0e;
127
+ --el-color-warning-dark-2: #9d650a;
128
+ --el-color-warning-dark-3: #754a07;
129
+ --el-color-warning-dark-4: #4e3004;
130
+ --el-color-danger: #bb2d3f;
131
+ --el-color-danger-light-1: #c34253;
132
+ --el-color-danger-light-2: #ca5766;
133
+ --el-color-danger-light-3: #d16b78;
134
+ --el-color-danger-light-4: #d8808b;
135
+ --el-color-danger-light-5: #dd969f;
136
+ --el-color-danger-light-6: #e4abb2;
137
+ --el-color-danger-light-7: #e8c0c5;
138
+ --el-color-danger-light-8: #f0d5d9;
139
+ --el-color-danger-light-9: #f7eaec;
140
+ --el-color-danger-dark-1: #962433;
141
+ --el-color-danger-dark-2: #711b26;
142
+ --el-color-danger-dark-3: #4b1219;
143
+ --el-color-danger-dark-4: #26090d;
144
+ --el-color-error: #bb2d3f;
145
+ --el-color-error-light-1: #c34253;
146
+ --el-color-error-light-2: #ca5766;
147
+ --el-color-error-light-3: #d16b78;
148
+ --el-color-error-light-4: #d8808b;
149
+ --el-color-error-light-5: #dd969f;
150
+ --el-color-error-light-6: #e4abb2;
151
+ --el-color-error-light-7: #e8c0c5;
152
+ --el-color-error-light-8: #f0d5d9;
153
+ --el-color-error-light-9: #f7eaec;
154
+ --el-color-error-dark-1: #962433;
155
+ --el-color-error-dark-2: #711b26;
156
+ --el-color-error-dark-3: #4b1219;
157
+ --el-color-error-dark-4: #26090d;
158
+ --el-color-info: #909399;
159
+ --el-color-info-light-1: #9b9ea3;
160
+ --el-color-info-light-2: #a7a9ae;
161
+ --el-color-info-light-3: #b2b4b8;
162
+ --el-color-info-light-4: #bdbfc3;
163
+ --el-color-info-light-5: #c9cacd;
164
+ --el-color-info-light-6: #d4d5d7;
165
+ --el-color-info-light-7: #dfe0e2;
166
+ --el-color-info-light-8: #ebebec;
167
+ --el-color-info-light-9: #f6f6f7;
168
+ --el-color-info-dark-1: #909399;
169
+ --el-color-info-dark-2: #909399;
170
+ --el-color-info-dark-3: #909399;
171
+ --el-color-info-dark-4: #909399;
172
+ --el-text-color-primary: rgba(0, 0, 0, 0.85);
173
+ --el-text-color-regular: rgba(0, 0, 0, 0.65);
174
+ --el-text-color-secondary: rgba(0, 0, 0, 0.45);
175
+ --el-text-color-placeholder: rgba(0, 0, 0, 0.25);
176
+ --el-text-color-disabled: rgba(0, 0, 0, 0.25);
177
+ --el-border-color: #d9d9d9;
178
+ --el-border-color-light: #f0f0f0;
179
+ --el-border-color-lighter: #f5f5f5;
180
+ --el-border-color-extra-light: rgba(0, 0, 0, 0.04);
181
+ --el-border-color-dark: rgba(0, 0, 0, 0.06);
182
+ --el-border-color-darker: rgba(0, 0, 0, 0.15);
183
+ --el-fill-color: #f0f0f0 !important;
184
+ --el-fill-color-light: rgba(0, 0, 0, 0.02) !important;
185
+ --el-fill-color-lighter: rgba(0, 0, 0, 0.04) !important;
186
+ --el-fill-color-dark: rgba(0, 0, 0, 0.45) !important;
187
+ --el-fill-color-darker: rgba(0, 0, 0, 0.75) !important;
188
+ --el-fill-color-blank: #ffffff !important;
189
+ --el-mask-color: rgba(0, 0, 0, 0.45);
190
+ --el-mask-color-extra-light: rgba(0, 0, 0, 0.30);
191
+ --el-font-size-extra-large: 32px;
192
+ --el-font-size-large: 24px;
193
+ --el-font-size-medium: 16px;
194
+ --el-font-size-base: 14px;
195
+ --el-font-size-small: 12px;
196
+ --el-font-size-extra-small: 10px;
197
+ --el-font-weight-primary: 500;
198
+ --el-font-weight-light: 300;
199
+ --el-font-weight-regular: 400;
200
+ --el-font-weight-medium: 500;
201
+ --el-font-weight-semi: 600;
202
+ --el-box-shadow: 0px 3px 8px 0px rgba(0, 0, 0, 0.05);
203
+ --el-box-shadow-light: 0px 4px 10px 0px rgba(0, 0, 0, 0.1);
204
+ --el-box-shadow-lighter: 0px 6px 12px 0px rgba(0, 0, 0, 0.12);
205
+ --el-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, 0.08), 0px 12px 32px rgba(0, 0, 0, 0.12), 0px 8px 16px -8px rgba(0, 0, 0, 0.16);
206
+ --el-border-radius-base: 4px;
207
+ --el-border-radius-small: 2px;
208
+ --el-border-radius-round: 20px;
209
+ --el-border-radius-circle: 100%;
210
+ --el-line-height-1: 20px;
211
+ --el-line-height-2: 20px;
212
+ --el-line-height-3: 20px;
213
+ --el-line-height-4: 20px;
214
+ --el-line-height-5: 20px;
215
+ --el-line-height-6: 20px;
216
+ --el-line-height-7: 20px;
217
+ --el-line-height-8: 20px;
218
+ --el-line-height-9: 20px;
219
+ --el-component-size-small: 28px;
220
+ --el-component-size-large: 40px;
221
+ --el-component-size-default: 32px;
222
+ --el-padding: 12px;
223
+ --el-padding-large: 24px;
224
+ --el-padding-small: 12px;
225
+ --el-sidebar-bg: #10182C;
226
+ --el-sidebar-bg-active: rgba(255, 255, 255, 0.11);
227
+ --el-sidebar-width: 240px;
228
+ --el-menu-item-font-size: 16px;
229
+ --el-menu-item-height: 40px;
230
+ }