@agile-team/wl-skills-ui 1.8.4 → 1.8.6
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 +7 -9
- package/es/{chunk-QDTNMM7W.js → chunk-FMVZ54BY.js} +32 -0
- package/es/common-preset.js +2 -2
- package/es/index.js +1 -1
- package/es/presets/security.js +1 -1
- package/package.json +1 -1
- package/scanner/__tests__/fixtures/r025-fail.vue +7 -0
- package/scanner/__tests__/fixtures/r025-pass.vue +7 -0
- package/scanner/__tests__/fixtures/r026-fail.vue +13 -0
- package/scanner/__tests__/fixtures/r026-pass.vue +11 -0
- package/scanner/rules/index.mjs +2 -0
- package/scanner/rules/semantic.mjs +94 -0
- package/standards/rules.json +24 -0
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.6] - 2026-05-17
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`renderOps` 运行时类型守门**:dev 模式下检测到 `OpItem.type` 不在 `view | edit | del | danger | log | ok | send | chip | link` 集合中时,`console.warn` 一次性提示并按 `link` 兜底渲染。修复下游项目(`strict: false`)传错 type 时图标静默降级为纯文字、毫无察觉的隐性体验问题。production 环境无任何副作用。
|
|
12
|
+
|
|
13
|
+
## [1.8.5] - 2026-05-17
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **R025**:检测 defineColumns 列中 `options:[]` 纯文本退化,提示升级为 `renderTagSlot` / `renderDictClassifyTag` 彩色标签。
|
|
18
|
+
- **R026**:检测模板中原生 HTML 元素(`<table>/<input>/<select>/<button>/<textarea>`),提示替换为对应 Element Plus 组件以纳入统一风格体系。
|
|
19
|
+
- fixture 测试覆盖 R025/R026(20 条自动化测试)。
|
|
20
|
+
|
|
7
21
|
## [1.8.3] - 2026-05-13
|
|
8
22
|
|
|
9
23
|
### Added
|
package/README.md
CHANGED
|
@@ -213,15 +213,13 @@ yarn add @agile-team/wl-skills-ui
|
|
|
213
213
|
|
|
214
214
|
## 版本亮点
|
|
215
215
|
|
|
216
|
-
当前 v1.8.
|
|
217
|
-
|
|
218
|
-
- `
|
|
219
|
-
-
|
|
220
|
-
-
|
|
221
|
-
-
|
|
222
|
-
- `scan --baseline`
|
|
223
|
-
- **R-rule 单一事实源** `standards/rules.json`(29 条),五向一致性守卫
|
|
224
|
-
- **长效治理方案** `docs/governance-long-term.md`
|
|
216
|
+
当前 v1.8.5:
|
|
217
|
+
|
|
218
|
+
- **R025** 语义合规:`options:[]` 退化检测 → 提示升级 `renderTagSlot` / `renderDictClassifyTag`
|
|
219
|
+
- **R026** 原生 HTML 拦截:`<table>/<input>/<select>/<button>/<textarea>` → 提示替换 el-* 组件
|
|
220
|
+
- `scan --only/--skip` 规则过滤 + `exempt init` 智能豁免脚手架
|
|
221
|
+
- 搜索区字号统一 12px / 必填星号去重 / 20 条 fixture 自动化测试
|
|
222
|
+
- `scan --baseline` 漂移对比 + SCSS 链路检查 + R-rule 单一事实源(31 条)
|
|
225
223
|
|
|
226
224
|
历史亮点(v1.7.1):
|
|
227
225
|
|
|
@@ -166,8 +166,40 @@ function isOpVisible(item) {
|
|
|
166
166
|
if (typeof item.show === "function") return item.show();
|
|
167
167
|
return item.show !== false;
|
|
168
168
|
}
|
|
169
|
+
var KNOWN_OP_TYPES = /* @__PURE__ */ new Set([
|
|
170
|
+
"view",
|
|
171
|
+
"edit",
|
|
172
|
+
"del",
|
|
173
|
+
"danger",
|
|
174
|
+
"log",
|
|
175
|
+
"ok",
|
|
176
|
+
"send",
|
|
177
|
+
"chip",
|
|
178
|
+
"link"
|
|
179
|
+
]);
|
|
180
|
+
var __opWarned = /* @__PURE__ */ new Set();
|
|
181
|
+
function warnUnknownOpType(item) {
|
|
182
|
+
let isDev = true;
|
|
183
|
+
try {
|
|
184
|
+
const g = globalThis;
|
|
185
|
+
if (g.process && g.process.env && g.process.env.NODE_ENV === "production") {
|
|
186
|
+
isDev = false;
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
if (!isDev) return;
|
|
191
|
+
const t = item.type;
|
|
192
|
+
if (t && KNOWN_OP_TYPES.has(t)) return;
|
|
193
|
+
const key = String(t);
|
|
194
|
+
if (__opWarned.has(key)) return;
|
|
195
|
+
__opWarned.add(key);
|
|
196
|
+
console.warn(
|
|
197
|
+
'[@agile-team/wl-skills-ui renderOps] \u68C0\u6D4B\u5230\u672A\u77E5\u7684 type="' + key + '"\uFF0C\u5C06\u6309 link \u515C\u5E95\u6E32\u67D3\u4E3A\u6587\u5B57\u6309\u94AE\u3002\n \u8BF7\u6539\u4E3A\uFF1Aview | edit | del | danger | log | ok | send | chip | link\n \u8BE6\u89C1 runtime/core/types.ts OpItem \u5B9A\u4E49\u3002'
|
|
198
|
+
);
|
|
199
|
+
}
|
|
169
200
|
function renderOps(items) {
|
|
170
201
|
const visible = items.filter(isOpVisible);
|
|
202
|
+
if (visible.length) visible.forEach(warnUnknownOpType);
|
|
171
203
|
const iconItems = visible.filter((i) => i.type in ICON_PRESETS);
|
|
172
204
|
const otherItems = visible.filter((i) => !(i.type in ICON_PRESETS));
|
|
173
205
|
const nodes = [];
|
package/es/common-preset.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { renderTagNode, renderClassifyTag, registerColumnAutoMaps, renderRatingLevel, renderBadge } from './chunk-
|
|
2
|
-
export { registerDictColorMap, registerDictColorMaps, renderDictClassifyTag, setDictResolver } from './chunk-
|
|
1
|
+
import { renderTagNode, renderClassifyTag, registerColumnAutoMaps, renderRatingLevel, renderBadge } from './chunk-FMVZ54BY.js';
|
|
2
|
+
export { registerDictColorMap, registerDictColorMaps, renderDictClassifyTag, setDictResolver } from './chunk-FMVZ54BY.js';
|
|
3
3
|
|
|
4
4
|
// runtime/presets/common.ts
|
|
5
5
|
var RISK_LEVEL_MAP = {
|
package/es/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { AUDIT_STATUS_MAP, ENABLE_STATUS_MAP, RATING_LEVEL_COLORS, VERIFY_STATUS_MAP, clearColumnAutoMap, defineColumns, getColumnAutoMap, registerColumnAutoMap, registerColumnAutoMaps, registerDictColorMap, registerDictColorMaps, renderAuditStatus, renderBadge, renderClassifyTag, renderCountBadge, renderDangerText, renderDictClassifyTag, renderEnableStatus, renderOps, renderRatingLevel, renderTagNode, renderTagSlot, renderVerifyStatus, setDictResolver } from './chunk-
|
|
1
|
+
export { AUDIT_STATUS_MAP, ENABLE_STATUS_MAP, RATING_LEVEL_COLORS, VERIFY_STATUS_MAP, clearColumnAutoMap, defineColumns, getColumnAutoMap, registerColumnAutoMap, registerColumnAutoMaps, registerDictColorMap, registerDictColorMaps, renderAuditStatus, renderBadge, renderClassifyTag, renderCountBadge, renderDangerText, renderDictClassifyTag, renderEnableStatus, renderOps, renderRatingLevel, renderTagNode, renderTagSlot, renderVerifyStatus, setDictResolver } from './chunk-FMVZ54BY.js';
|
package/es/presets/security.js
CHANGED
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.6",
|
|
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",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<table>
|
|
4
|
+
<tr><td>数据</td></tr>
|
|
5
|
+
</table>
|
|
6
|
+
<input type="text" v-model="name" />
|
|
7
|
+
<select v-model="type">
|
|
8
|
+
<option value="a">A</option>
|
|
9
|
+
</select>
|
|
10
|
+
<button @click="submit">提交</button>
|
|
11
|
+
<textarea v-model="remark"></textarea>
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-form>
|
|
3
|
+
<el-form-item label="名称">
|
|
4
|
+
<el-input v-model="name" />
|
|
5
|
+
</el-form-item>
|
|
6
|
+
<el-form-item label="类型">
|
|
7
|
+
<el-select v-model="type"><el-option label="A" value="a" /></el-select>
|
|
8
|
+
</el-form-item>
|
|
9
|
+
<el-button type="primary">提交</el-button>
|
|
10
|
+
</el-form>
|
|
11
|
+
</template>
|
package/scanner/rules/index.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import { colorRules } from "./color.mjs";
|
|
|
17
17
|
import { dialogRules } from "./dialog.mjs";
|
|
18
18
|
import { tagRules } from "./tag.mjs";
|
|
19
19
|
import { componentFamilyRules } from "./componentFamily.mjs";
|
|
20
|
+
import { semanticRules } from "./semantic.mjs";
|
|
20
21
|
|
|
21
22
|
export const BUILT_IN_RULES = [
|
|
22
23
|
...tableRules,
|
|
@@ -26,6 +27,7 @@ export const BUILT_IN_RULES = [
|
|
|
26
27
|
...dialogRules,
|
|
27
28
|
...tagRules,
|
|
28
29
|
...componentFamilyRules,
|
|
30
|
+
...semanticRules,
|
|
29
31
|
];
|
|
30
32
|
|
|
31
33
|
const _externalRules = [];
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/** scanner/rules/semantic.mjs — 语义合规规则:R025 R026
|
|
2
|
+
*
|
|
3
|
+
* R025: defineColumns 列定义中 options:[] 退化为纯文本(应用 renderTagSlot / renderDictClassifyTag)
|
|
4
|
+
* R026: 模板中使用原生 HTML 元素(<table>/<input>/<select>/<button>/<textarea>),
|
|
5
|
+
* 应替换为 Element Plus 组件以纳入统一风格体系
|
|
6
|
+
*/
|
|
7
|
+
import { lineOf, issue, findTags } from "./_shared.mjs";
|
|
8
|
+
|
|
9
|
+
export const semanticRules = [
|
|
10
|
+
// R025: options:[] 退化检测
|
|
11
|
+
{
|
|
12
|
+
id: "R025",
|
|
13
|
+
category: "table",
|
|
14
|
+
severity: "warning",
|
|
15
|
+
name: "defineColumns 列使用 options:[] 纯文本渲染状态/分类(应使用 renderTagSlot / renderDictClassifyTag)",
|
|
16
|
+
checkScript(script, file, lineOffset) {
|
|
17
|
+
const issues = [];
|
|
18
|
+
const lines = script.split("\n");
|
|
19
|
+
for (let i = 0; i < lines.length; i++) {
|
|
20
|
+
// 检测 options: [...] 或 options: SOME_VAR
|
|
21
|
+
if (!/\boptions\s*:\s*[\[A-Z]/.test(lines[i])) continue;
|
|
22
|
+
// 排除 parseArgs / CLI 等非列定义上下文
|
|
23
|
+
const blockStart = Math.max(0, i - 8);
|
|
24
|
+
const blockEnd = Math.min(lines.length, i + 5);
|
|
25
|
+
const block = lines.slice(blockStart, blockEnd).join("\n");
|
|
26
|
+
// 必须在列定义上下文中(有 label: 或 name: 或 prop:)
|
|
27
|
+
if (!/\blabel\s*:/.test(block) && !/\bname\s*:/.test(block) && !/\bprop\s*:/.test(block)) continue;
|
|
28
|
+
// 已有渲染器则跳过
|
|
29
|
+
if (/defaultSlot|defaultNode|renderTag|renderDictClassify|renderBadge|renderEnable|cellRenderer/.test(block)) continue;
|
|
30
|
+
// 排除 type: "selection" / "index" / "expand"
|
|
31
|
+
if (/type\s*:\s*["'](selection|index|expand)["']/.test(block)) continue;
|
|
32
|
+
const labelMatch = block.match(/label\s*:\s*["']([^"']+)["']/);
|
|
33
|
+
const label = labelMatch ? labelMatch[1] : "(未知列)";
|
|
34
|
+
issues.push({
|
|
35
|
+
file,
|
|
36
|
+
line: lineOffset + i + 1,
|
|
37
|
+
rule: "R025",
|
|
38
|
+
category: "table",
|
|
39
|
+
severity: "warning",
|
|
40
|
+
description: `列 "${label}" 使用 options:[] 纯文本渲染,视觉无颜色区分`,
|
|
41
|
+
suggestion: `替换为 defaultSlot: ({ row }) => renderDictClassifyTag(row.xxx, 'dictKey') 或 renderTagSlot(row.xxx, MAP),移除 options`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return issues;
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// R026: 原生 HTML 元素应换 Element Plus 组件
|
|
49
|
+
{
|
|
50
|
+
id: "R026",
|
|
51
|
+
category: "form",
|
|
52
|
+
severity: "warning",
|
|
53
|
+
name: "模板使用原生 HTML 元素,应替换为 Element Plus 组件以纳入统一风格体系",
|
|
54
|
+
check(template, file, lineOffset) {
|
|
55
|
+
const issues = [];
|
|
56
|
+
const NATIVE_TO_EL = {
|
|
57
|
+
table: { el: "el-table", reason: "原生 <table> 不受 wl-skills-ui 表格样式覆盖" },
|
|
58
|
+
input: { el: "el-input", reason: "原生 <input> 不受统一 size/密度/圆角控制" },
|
|
59
|
+
select: { el: "el-select", reason: "原生 <select> 不受统一下拉样式控制" },
|
|
60
|
+
textarea: { el: "el-input type=\"textarea\"", reason: "原生 <textarea> 不受统一输入框样式控制" },
|
|
61
|
+
button: { el: "el-button", reason: "原生 <button> 不受统一按钮样式控制" },
|
|
62
|
+
};
|
|
63
|
+
for (const [nativeTag, { el, reason }] of Object.entries(NATIVE_TO_EL)) {
|
|
64
|
+
// 匹配 <table / <input / <select 等原生标签,排除 el-table / el-input 等
|
|
65
|
+
const re = new RegExp(`<${nativeTag}(?:\\s|>|/)`, "g");
|
|
66
|
+
let m;
|
|
67
|
+
while ((m = re.exec(template)) !== null) {
|
|
68
|
+
// 排除已是组件前缀的情况:el-table, el-input 等
|
|
69
|
+
const before = template.slice(Math.max(0, m.index - 10), m.index);
|
|
70
|
+
if (/[-\w]$/.test(before)) continue;
|
|
71
|
+
// 排除注释
|
|
72
|
+
const lineStart = template.lastIndexOf("\n", m.index) + 1;
|
|
73
|
+
const lineText = template.slice(lineStart, m.index);
|
|
74
|
+
if (/<!--/.test(lineText) && !(/-->/.test(lineText))) continue;
|
|
75
|
+
if (/^\s*\/\//.test(lineText)) continue;
|
|
76
|
+
// 排除 slot/template 内嵌的特殊情况(如 <table> 在 rich-text/markdown 中)
|
|
77
|
+
// 但不做过度排除,让开发者自行决定豁免
|
|
78
|
+
issues.push(
|
|
79
|
+
issue(
|
|
80
|
+
file,
|
|
81
|
+
lineOf(template, m.index, lineOffset),
|
|
82
|
+
"R026",
|
|
83
|
+
"form",
|
|
84
|
+
"warning",
|
|
85
|
+
`${reason},建议替换为 <${el}>`,
|
|
86
|
+
`将 <${nativeTag}> 替换为 <${el}>,确保纳入 wl-skills-ui 风格体系`,
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return issues;
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
];
|
package/standards/rules.json
CHANGED
|
@@ -387,6 +387,30 @@
|
|
|
387
387
|
"autoFixable": false,
|
|
388
388
|
"scanner": "scanner/rules/componentFamily.mjs",
|
|
389
389
|
"skills": ["element/component-family"]
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
"id": "R025",
|
|
393
|
+
"category": "table",
|
|
394
|
+
"severity": "warning",
|
|
395
|
+
"title": "defineColumns 列禁止用 options:[] 纯文本渲染状态/分类",
|
|
396
|
+
"rationale": "options:[] 是 BaseTable 内建的最简字典渲染,只出文字无颜色区分;应升级为 renderTagSlot / renderDictClassifyTag 以获得彩色标签。",
|
|
397
|
+
"appliesTo": ["defineColumns", "columnsDef"],
|
|
398
|
+
"autoFixable": false,
|
|
399
|
+
"manualReason": "需确定字典 key 和颜色映射",
|
|
400
|
+
"scanner": "scanner/rules/semantic.mjs",
|
|
401
|
+
"skills": ["vendors/base-table", "element/el-table"]
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
"id": "R026",
|
|
405
|
+
"category": "form",
|
|
406
|
+
"severity": "warning",
|
|
407
|
+
"title": "模板禁止使用原生 HTML 元素(table/input/select/button/textarea)",
|
|
408
|
+
"rationale": "原生 HTML 元素不受 wl-skills-ui 样式体系覆盖(size/密度/圆角/token),必须替换为对应 Element Plus 组件。",
|
|
409
|
+
"appliesTo": ["<table>", "<input>", "<select>", "<button>", "<textarea>"],
|
|
410
|
+
"autoFixable": false,
|
|
411
|
+
"manualReason": "需根据业务场景选择正确的 el-* 组件和属性",
|
|
412
|
+
"scanner": "scanner/rules/semantic.mjs",
|
|
413
|
+
"skills": ["element/el-form", "element/el-table"]
|
|
390
414
|
}
|
|
391
415
|
]
|
|
392
416
|
}
|