@agile-team/wl-skills-ui 1.9.6 → 1.9.7
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 +13 -0
- package/README.md +7 -3
- package/docs/compat-matrix.md +2 -2
- package/es/{chunk-MPJ6QDYX.js → chunk-XDGTJ7LE.js} +21 -8
- package/es/common-preset.js +2 -2
- package/es/index.js +2 -2
- package/es/presets/security.js +1 -1
- package/package.json +1 -1
- package/reference/define-columns.ts +38 -6
- package/scanner/fix.mjs +97 -5
- package/scanner/report.mjs +2 -0
- package/scanner/rules/button.mjs +55 -1
- package/scanner/rules/table.mjs +36 -1
- package/skills/element/el-table/SKILL.md +21 -2
- package/skills/layouts/list-page/SKILL.md +6 -0
- package/skills/vendors/base-table/SKILL.md +4 -1
- package/standards/rules.json +25 -0
- package/standards/ui/01-table.md +21 -0
- package/standards/ui/02-button.md +6 -1
- package/styles/element/_table.scss +8 -0
- package/templates/ag-grid-page/TPL-AG-GRID.md +2 -1
- package/templates/list-page/TPL-LIST.md +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ 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.9.7] - 2026-07-28
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- 新增 R038 创建类主按钮门禁与自动修复:新增/新建/添加/创建类操作统一使用客户主题 `primary` 填充按钮,自动移除误用的 `plain`。
|
|
12
|
+
- 新增 R039 表格长文本门禁与自动修复:原生 `el-table-column` 普通数据列统一补齐 `show-overflow-tooltip`。
|
|
13
|
+
- `defineColumns()` 为 BaseTable/AG Grid 普通文本列递归补齐超长省略与悬停完整提示;结构列、自定义渲染列、换行列及显式配置保持业务声明。
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- 表格样式仅对启用 overflow tooltip 的单元格补齐单行省略,避免影响 Tag、操作列和自定义内容。
|
|
18
|
+
- 列表页、按钮、Element Table、BaseTable 规范与模板同步声明主按钮和长文本交互契约。
|
|
19
|
+
|
|
7
20
|
## [1.9.6] - 2026-07-23
|
|
8
21
|
|
|
9
22
|
### Fixed
|
package/README.md
CHANGED
|
@@ -200,7 +200,7 @@ yarn add @agile-team/wl-skills-ui
|
|
|
200
200
|
|---|---|---|
|
|
201
201
|
| `element-plus` | **`2.2.6-prod.3`** | 集团 jh- 定制版;EP 2.3.0 起引入 `.el-input__wrapper`,与 jh-ui 3.x 不兼容 |
|
|
202
202
|
| `@jhlc/jh-ui` | **`3.1.0`** | SCSS 皮肤包,`.com-text` label 包裹 + `.has-colon` 冒号注入(wl-skills-ui 已统一屏蔽表单冒号) |
|
|
203
|
-
| `@agile-team/wl-skills-ui` | `^1.9.
|
|
203
|
+
| `@agile-team/wl-skills-ui` | `^1.9.7` | 已对齐上述组合的 DOM 假设、客户主题锁、圆角契约与定制页样式边界 |
|
|
204
204
|
|
|
205
205
|
三种识别方式,任选其一:
|
|
206
206
|
|
|
@@ -214,8 +214,10 @@ yarn add @agile-team/wl-skills-ui
|
|
|
214
214
|
|
|
215
215
|
## 版本亮点
|
|
216
216
|
|
|
217
|
-
当前 v1.9.
|
|
217
|
+
当前 v1.9.7:
|
|
218
218
|
|
|
219
|
+
- **主操作按钮不再漏色**:新增/新建/添加/创建类按钮强制使用客户主题 `primary` 填充样式,扫描器可识别并自动修复漏写 `type` 或误带 `plain` 的存量代码。
|
|
220
|
+
- **长文本交互统一**:普通数据列在空间不足时单行省略,悬停显示完整内容;Tag、操作列、自定义渲染和主动换行列不被机械覆盖。
|
|
219
221
|
- **Vite 4 运行时兼容**:浏览器 ESM 产物不再暴露可被开发转换误替换的 Node 环境变量成员链,预构建与直接加载均保持合法语法。
|
|
220
222
|
- **登录页精准退出**:内置识别 `.lp-root` 与 `.session-login`,其内部表单、按钮、焦点和校验态保留登录页自身设计;业务页面仍由包高权重统一管控。
|
|
221
223
|
- **通用定制页边界**:其他定制页在根节点增加 `.wl-ui-skin-exempt` 或 `data-wl-ui-skin="off"` 即可退出组件级强覆盖,不需要删除全局化妆包。
|
|
@@ -676,7 +678,7 @@ export const myRules = [
|
|
|
676
678
|
|
|
677
679
|
## 规范清单
|
|
678
680
|
|
|
679
|
-
### UI 规则(R001-
|
|
681
|
+
### UI 规则(R001-R039,按 layer 自动分组)
|
|
680
682
|
|
|
681
683
|
| Rule | Layer | Vendor | 说明 |
|
|
682
684
|
| ---- | ----- | --------- | ------------------------------------ |
|
|
@@ -707,6 +709,8 @@ export const myRules = [
|
|
|
707
709
|
| R035 | L1 | element | el-upload 建议配置 tip/限制说明 |
|
|
708
710
|
| R036 | L1 | element | el-steps 建议明确状态来源 |
|
|
709
711
|
| R037 | L1 | element | 空/异常反馈建议统一操作入口 |
|
|
712
|
+
| R038 | L1 | element | 创建类主按钮缺 primary 填充主题色 |
|
|
713
|
+
| R039 | L1 | element | 普通数据列缺省略与悬停完整提示 |
|
|
710
714
|
|
|
711
715
|
### 工程规范
|
|
712
716
|
|
package/docs/compat-matrix.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
| `element-plus` | **`2.2.6-prod.3`** | 集团 jh- 定制版,搭配 `@jhlc/jh-ui` 使用,DOM 仍为 `.el-input > .el-input__inner` 直挂结构(**EP 2.3.0 起才引入 `.el-input__wrapper`**) |
|
|
12
12
|
| `@jhlc/jh-ui` | **`3.1.0`** | SCSS 皮肤包,`.com-text` label 包裹、`.has-colon ::after` 冒号注入、`.el-form-item--default { margin-bottom: 24px }` 等强约束 |
|
|
13
13
|
| `@jhlc/common-core` | `3.1.0` 或 `3.1.0-prod.x` | 基础 util/types,`@jhlc/jh-ui` 间接依赖 |
|
|
14
|
-
| `@agile-team/wl-skills-ui` | `^1.9.
|
|
14
|
+
| `@agile-team/wl-skills-ui` | `^1.9.7` | 已对齐 jh-ui 3.1.0 + EP 2.2.6-prod.3 的 DOM 假设、客户主题锁、6px 基础圆角、定制页样式边界与 Vite 4 开发转换 |
|
|
15
15
|
|
|
16
16
|
## 项目集群当前实测
|
|
17
17
|
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
## 适配建议
|
|
37
37
|
|
|
38
|
-
- 集团内项目:**统一锚定 `element-plus@2.2.6-prod.3` + `@jhlc/jh-ui@3.1.0`**,与 wl-skills-ui v1.9.
|
|
38
|
+
- 集团内项目:**统一锚定 `element-plus@2.2.6-prod.3` + `@jhlc/jh-ui@3.1.0`**,与 wl-skills-ui v1.9.7 三方对齐。
|
|
39
39
|
- 升级 `element-plus` 到 2.3+ 前,必须同步升级 `@jhlc/jh-ui` 到对应支持版本,否则 `.com-text` / `.el-input__inner` DOM 与 EP `__wrapper` 体系不兼容。
|
|
40
40
|
- 新项目接入:见 README 「快速接入」章节,默认会按本表声明 peerDependency 范围。
|
|
41
41
|
|
|
@@ -279,24 +279,37 @@ function clearColumnAutoMap(name) {
|
|
|
279
279
|
}
|
|
280
280
|
function defineColumns(columns) {
|
|
281
281
|
return columns.map((col) => {
|
|
282
|
-
const
|
|
282
|
+
const normalized = Array.isArray(col.children) ? {
|
|
283
|
+
...col,
|
|
284
|
+
children: defineColumns(col.children)
|
|
285
|
+
} : col;
|
|
286
|
+
const fieldName = normalized.name ?? normalized.label ?? "";
|
|
283
287
|
const preset = COLUMN_AUTO_MAP[fieldName];
|
|
284
|
-
const hasRenderer =
|
|
285
|
-
if (hasRenderer) return
|
|
286
|
-
if (!preset && isDictColumn(
|
|
288
|
+
const hasRenderer = normalized.defaultNode !== void 0 || normalized.defaultSlot !== void 0;
|
|
289
|
+
if (hasRenderer) return normalized;
|
|
290
|
+
if (!preset && isDictColumn(normalized)) {
|
|
287
291
|
return {
|
|
288
|
-
...
|
|
289
|
-
defaultSlot: ({ row }) => renderDictClassifyTag(
|
|
292
|
+
...normalized,
|
|
293
|
+
defaultSlot: ({ row }) => renderDictClassifyTag(
|
|
294
|
+
row?.[fieldName],
|
|
295
|
+
String(normalized.logicValue)
|
|
296
|
+
)
|
|
290
297
|
};
|
|
291
298
|
}
|
|
292
|
-
if (!preset) return
|
|
293
|
-
return { ...preset, ...
|
|
299
|
+
if (!preset) return withOverflowTooltip(normalized);
|
|
300
|
+
return withOverflowTooltip({ ...preset, ...normalized });
|
|
294
301
|
});
|
|
295
302
|
}
|
|
296
303
|
function isDictColumn(col) {
|
|
297
304
|
const logicType = String(col.logicType ?? "").toLowerCase();
|
|
298
305
|
return Boolean(col.logicValue) && logicType.includes("dict");
|
|
299
306
|
}
|
|
307
|
+
function withOverflowTooltip(col) {
|
|
308
|
+
if (!col.name || col.showOverflowTooltip !== void 0 || col.tooltipValueGetter !== void 0 || col.wrapText === true || col.autoHeight === true || ["selection", "index", "radio", "expand"].includes(String(col.type ?? "")) || col.defaultNode !== void 0 || col.defaultSlot !== void 0) {
|
|
309
|
+
return col;
|
|
310
|
+
}
|
|
311
|
+
return { ...col, showOverflowTooltip: true };
|
|
312
|
+
}
|
|
300
313
|
registerColumnAutoMaps({
|
|
301
314
|
enableStatus: {
|
|
302
315
|
width: 80,
|
package/es/common-preset.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { renderTagNode, renderClassifyTag, installBrandThemeLock, registerColumnAutoMaps, renderRatingLevel, renderBadge } from './chunk-
|
|
2
|
-
export { registerDictColorMap, registerDictColorMaps, renderDictClassifyTag, setDictResolver } from './chunk-
|
|
1
|
+
import { renderTagNode, renderClassifyTag, installBrandThemeLock, registerColumnAutoMaps, renderRatingLevel, renderBadge } from './chunk-XDGTJ7LE.js';
|
|
2
|
+
export { registerDictColorMap, registerDictColorMaps, renderDictClassifyTag, setDictResolver } from './chunk-XDGTJ7LE.js';
|
|
3
3
|
|
|
4
4
|
// runtime/presets/common.ts
|
|
5
5
|
var RISK_LEVEL_MAP = {
|
package/es/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { registerColumnAutoMaps } from './chunk-
|
|
2
|
-
export { AUDIT_STATUS_MAP, BRAND_THEME_TOKENS, ENABLE_STATUS_MAP, RATING_LEVEL_COLORS, VERIFY_STATUS_MAP, clearColumnAutoMap, defineColumns, getColumnAutoMap, installBrandThemeLock, registerColumnAutoMap, registerColumnAutoMaps, registerDictColorMap, registerDictColorMaps, renderAuditStatus, renderBadge, renderClassifyTag, renderCountBadge, renderDangerText, renderDictClassifyTag, renderEnableStatus, renderOps, renderRatingLevel, renderTagNode, renderTagSlot, renderVerifyStatus, setDictResolver } from './chunk-
|
|
1
|
+
import { registerColumnAutoMaps } from './chunk-XDGTJ7LE.js';
|
|
2
|
+
export { AUDIT_STATUS_MAP, BRAND_THEME_TOKENS, ENABLE_STATUS_MAP, RATING_LEVEL_COLORS, VERIFY_STATUS_MAP, clearColumnAutoMap, defineColumns, getColumnAutoMap, installBrandThemeLock, registerColumnAutoMap, registerColumnAutoMaps, registerDictColorMap, registerDictColorMaps, renderAuditStatus, renderBadge, renderClassifyTag, renderCountBadge, renderDangerText, renderDictClassifyTag, renderEnableStatus, renderOps, renderRatingLevel, renderTagNode, renderTagSlot, renderVerifyStatus, setDictResolver } from './chunk-XDGTJ7LE.js';
|
|
3
3
|
|
|
4
4
|
// runtime/presets/registry.ts
|
|
5
5
|
var _installedPresets = /* @__PURE__ */ new Set();
|
package/es/presets/security.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { renderTagNode, installBrandThemeLock, registerDictColorMaps, registerColumnAutoMaps } from '../chunk-
|
|
1
|
+
import { renderTagNode, installBrandThemeLock, registerDictColorMaps, registerColumnAutoMaps } from '../chunk-XDGTJ7LE.js';
|
|
2
2
|
|
|
3
3
|
// runtime/presets/security.ts
|
|
4
4
|
var VIOLATION_STATUS_MAP = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agile-team/wl-skills-ui",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.7",
|
|
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",
|
|
@@ -172,14 +172,46 @@ export function defineColumns<T = any>(
|
|
|
172
172
|
columns: TableColumnDesc<T>[]
|
|
173
173
|
): TableColumnDesc<T>[] {
|
|
174
174
|
return columns.map((col) => {
|
|
175
|
+
const normalized = Array.isArray(col.children)
|
|
176
|
+
? ({
|
|
177
|
+
...col,
|
|
178
|
+
children: defineColumns(col.children),
|
|
179
|
+
} as TableColumnDesc<T>)
|
|
180
|
+
: col;
|
|
175
181
|
// 多选列:统一宽度 55px(页面已手写 width 时不覆盖)
|
|
176
|
-
if ((
|
|
177
|
-
return {
|
|
182
|
+
if ((normalized as any).type === "selection" && !(normalized as any).width) {
|
|
183
|
+
return {
|
|
184
|
+
width: 55,
|
|
185
|
+
...(normalized as any),
|
|
186
|
+
} as unknown as TableColumnDesc<T>;
|
|
178
187
|
}
|
|
179
|
-
if (!
|
|
180
|
-
const auto = COLUMN_AUTO_MAP[
|
|
181
|
-
if (!auto) return
|
|
188
|
+
if (!normalized.name) return normalized;
|
|
189
|
+
const auto = COLUMN_AUTO_MAP[normalized.name as string];
|
|
190
|
+
if (!auto) return withOverflowTooltip(normalized);
|
|
182
191
|
// 自动配置 < 手写配置(手写的属性不被覆盖)
|
|
183
|
-
return {
|
|
192
|
+
return withOverflowTooltip({
|
|
193
|
+
...auto,
|
|
194
|
+
...normalized,
|
|
195
|
+
} as TableColumnDesc<T>);
|
|
184
196
|
});
|
|
185
197
|
}
|
|
198
|
+
|
|
199
|
+
function withOverflowTooltip<T>(
|
|
200
|
+
col: TableColumnDesc<T>
|
|
201
|
+
): TableColumnDesc<T> {
|
|
202
|
+
if (
|
|
203
|
+
!col.name ||
|
|
204
|
+
col.showOverflowTooltip !== undefined ||
|
|
205
|
+
col.tooltipValueGetter !== undefined ||
|
|
206
|
+
col.wrapText === true ||
|
|
207
|
+
col.autoHeight === true ||
|
|
208
|
+
["selection", "index", "radio", "expand"].includes(
|
|
209
|
+
String((col as any).type ?? "")
|
|
210
|
+
) ||
|
|
211
|
+
col.defaultNode !== undefined ||
|
|
212
|
+
col.defaultSlot !== undefined
|
|
213
|
+
) {
|
|
214
|
+
return col;
|
|
215
|
+
}
|
|
216
|
+
return { ...col, showOverflowTooltip: true };
|
|
217
|
+
}
|
package/scanner/fix.mjs
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* - el-input / el-select: 缺 size="small" → 添加
|
|
8
8
|
* - el-date-picker: 缺 style="width:100%" → 添加
|
|
9
9
|
* - el-table: 缺 empty-text="暂无数据" → 添加
|
|
10
|
+
* - 创建类主按钮: 缺 primary / 误用 plain → 纠正
|
|
11
|
+
* - el-table 普通数据列: 缺 overflow tooltip → 添加
|
|
10
12
|
* - <style>/<template> 内可映射 hex 颜色 → 替换为 var(--el-color-*)
|
|
11
13
|
*/
|
|
12
14
|
import { readFileSync, writeFileSync, readdirSync } from "node:fs";
|
|
@@ -14,6 +16,15 @@ import { join, relative, resolve } from "node:path";
|
|
|
14
16
|
import { createSnapshot } from "./snapshot.mjs";
|
|
15
17
|
import { TOKEN_MAP } from "./rules/_shared.mjs";
|
|
16
18
|
|
|
19
|
+
const CREATE_ACTION_LABEL =
|
|
20
|
+
/(?:新增|新建|添加|创建)(?:申请|记录|数据|客户|项目|任务|明细|行)?/;
|
|
21
|
+
|
|
22
|
+
function hasTrueBooleanAttr(tagText, attr) {
|
|
23
|
+
return new RegExp(
|
|
24
|
+
`(?:^|\\s)(?::${attr}\\s*=\\s*["']true["']|${attr}(?:\\s*=\\s*["'](?:true|)["'])?)(?=\\s|/?>)`,
|
|
25
|
+
).test(tagText);
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
const FIXES = {
|
|
18
29
|
"el-input": [{ attr: "size", value: "small" }],
|
|
19
30
|
"el-select": [{ attr: "size", value: "small" }],
|
|
@@ -37,6 +48,8 @@ export const FIXED_RULE_IDS = Object.freeze([
|
|
|
37
48
|
"R016",
|
|
38
49
|
"R017",
|
|
39
50
|
"R021",
|
|
51
|
+
"R038",
|
|
52
|
+
"R039",
|
|
40
53
|
]);
|
|
41
54
|
|
|
42
55
|
function parseTag(content, pos, tagName) {
|
|
@@ -57,10 +70,24 @@ function parseTag(content, pos, tagName) {
|
|
|
57
70
|
return { text: content.slice(pos), end: content.length };
|
|
58
71
|
}
|
|
59
72
|
|
|
60
|
-
function addAttrIfMissing(
|
|
73
|
+
function addAttrIfMissing(
|
|
74
|
+
tagText,
|
|
75
|
+
tagName,
|
|
76
|
+
attr,
|
|
77
|
+
value,
|
|
78
|
+
replaceStatic = false,
|
|
79
|
+
booleanAttr = false,
|
|
80
|
+
) {
|
|
61
81
|
// 已存在(含动态绑定 :attr=)则跳过
|
|
82
|
+
const escapedAttr = attr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
83
|
+
if (
|
|
84
|
+
booleanAttr &&
|
|
85
|
+
new RegExp(`(?:^|\\s):?${escapedAttr}(?=\\s|=|/?>)`).test(tagText)
|
|
86
|
+
) {
|
|
87
|
+
return { text: tagText, changed: false };
|
|
88
|
+
}
|
|
62
89
|
const re = new RegExp(
|
|
63
|
-
`:?${
|
|
90
|
+
`:?${escapedAttr}\\s*=`,
|
|
64
91
|
);
|
|
65
92
|
if (re.test(tagText)) {
|
|
66
93
|
if (!replaceStatic) return { text: tagText, changed: false };
|
|
@@ -71,6 +98,12 @@ function addAttrIfMissing(tagText, tagName, attr, value, replaceStatic = false)
|
|
|
71
98
|
const text = tagText.replace(staticRe, `$1"${value}"`);
|
|
72
99
|
return { text, changed: text !== tagText };
|
|
73
100
|
}
|
|
101
|
+
if (booleanAttr) {
|
|
102
|
+
return {
|
|
103
|
+
text: tagText.replace(`<${tagName}`, `<${tagName} ${attr}`),
|
|
104
|
+
changed: true,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
74
107
|
const newTag = tagText.replace(
|
|
75
108
|
`<${tagName}`,
|
|
76
109
|
`<${tagName} ${attr}="${value}"`,
|
|
@@ -105,8 +138,26 @@ function fixTemplateAttrs(content) {
|
|
|
105
138
|
if (tagName === "el-table-column" && text.includes('type="selection"')) {
|
|
106
139
|
fixes.push({ attr: "header-align", value: "center" });
|
|
107
140
|
}
|
|
108
|
-
|
|
109
|
-
|
|
141
|
+
if (
|
|
142
|
+
tagName === "el-table-column" &&
|
|
143
|
+
/(?:^|\s):?prop\s*=/.test(text) &&
|
|
144
|
+
!/(?:^|\s)type\s*=\s*["'](?:selection|index|expand)["']/.test(text)
|
|
145
|
+
) {
|
|
146
|
+
fixes.push({
|
|
147
|
+
attr: "show-overflow-tooltip",
|
|
148
|
+
value: "",
|
|
149
|
+
booleanAttr: true,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
for (const { attr, value, replaceStatic, booleanAttr } of fixes) {
|
|
153
|
+
const r = addAttrIfMissing(
|
|
154
|
+
text,
|
|
155
|
+
tagName,
|
|
156
|
+
attr,
|
|
157
|
+
value,
|
|
158
|
+
replaceStatic,
|
|
159
|
+
booleanAttr,
|
|
160
|
+
);
|
|
110
161
|
text = r.text;
|
|
111
162
|
if (r.changed) changes++;
|
|
112
163
|
}
|
|
@@ -116,6 +167,44 @@ function fixTemplateAttrs(content) {
|
|
|
116
167
|
return { content: result, changes };
|
|
117
168
|
}
|
|
118
169
|
|
|
170
|
+
function fixPrimaryActionButtons(content) {
|
|
171
|
+
let changes = 0;
|
|
172
|
+
const result = content.replace(
|
|
173
|
+
/<el-button\b[\s\S]*?<\/el-button>/g,
|
|
174
|
+
(button) => {
|
|
175
|
+
const openEnd = button.indexOf(">");
|
|
176
|
+
if (openEnd < 0) return button;
|
|
177
|
+
let opening = button.slice(0, openEnd + 1);
|
|
178
|
+
const label = button.slice(openEnd + 1, button.lastIndexOf("</el-button>"));
|
|
179
|
+
if (!CREATE_ACTION_LABEL.test(label)) return button;
|
|
180
|
+
if (/:type\s*=/.test(opening)) return button;
|
|
181
|
+
if (hasTrueBooleanAttr(opening, "link") || hasTrueBooleanAttr(opening, "text"))
|
|
182
|
+
return button;
|
|
183
|
+
|
|
184
|
+
const typeResult = addAttrIfMissing(
|
|
185
|
+
opening,
|
|
186
|
+
"el-button",
|
|
187
|
+
"type",
|
|
188
|
+
"primary",
|
|
189
|
+
true,
|
|
190
|
+
);
|
|
191
|
+
opening = typeResult.text;
|
|
192
|
+
if (typeResult.changed) changes++;
|
|
193
|
+
|
|
194
|
+
const withoutPlain = opening
|
|
195
|
+
.replace(/\s+:plain\s*=\s*["']true["']/g, "")
|
|
196
|
+
.replace(/\s+plain\s*=\s*["'](?:true|)["']/g, "")
|
|
197
|
+
.replace(/\s+plain(?=\s|\/?>)/g, "");
|
|
198
|
+
if (withoutPlain !== opening) {
|
|
199
|
+
opening = withoutPlain;
|
|
200
|
+
changes++;
|
|
201
|
+
}
|
|
202
|
+
return opening + button.slice(openEnd + 1);
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
return { content: result, changes };
|
|
206
|
+
}
|
|
207
|
+
|
|
119
208
|
function fixHexColors(content) {
|
|
120
209
|
let changes = 0;
|
|
121
210
|
let result = content;
|
|
@@ -180,9 +269,12 @@ export function runFix({
|
|
|
180
269
|
const r1 = fixTemplateAttrs(content);
|
|
181
270
|
content = r1.content;
|
|
182
271
|
changes += r1.changes;
|
|
183
|
-
const r2 =
|
|
272
|
+
const r2 = fixPrimaryActionButtons(content);
|
|
184
273
|
content = r2.content;
|
|
185
274
|
changes += r2.changes;
|
|
275
|
+
const r3 = fixHexColors(content);
|
|
276
|
+
content = r3.content;
|
|
277
|
+
changes += r3.changes;
|
|
186
278
|
if (changes > 0 && content !== original) {
|
|
187
279
|
pending.push({ filePath, original, content, changes });
|
|
188
280
|
changedAbsPaths.push(filePath);
|
package/scanner/report.mjs
CHANGED
|
@@ -63,6 +63,8 @@ const RULE_NAME = {
|
|
|
63
63
|
R018: "<script> 块硬编码 hex 颜色",
|
|
64
64
|
R021: 'BaseTable 缺少 render-type="agGrid"',
|
|
65
65
|
R022: "BaseTable 缺少唯一 cid",
|
|
66
|
+
R038: "创建类主操作缺少 primary 填充主题色",
|
|
67
|
+
R039: "普通数据列缺少超长省略与悬停完整提示",
|
|
66
68
|
R031: "详情/统计卡片建议使用统一场景 class",
|
|
67
69
|
R032: "el-tabs 建议明确页面场景",
|
|
68
70
|
R033: "el-descriptions 建议使用 bordered 或统一容器",
|
package/scanner/rules/button.mjs
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
/** scanner/rules/button.mjs — 按钮规则:R004 R005 R015 */
|
|
1
|
+
/** scanner/rules/button.mjs — 按钮规则:R004 R005 R015 R038 */
|
|
2
2
|
import { lineOf, issue, findTags } from "./_shared.mjs";
|
|
3
3
|
|
|
4
|
+
const CREATE_ACTION_LABEL = /(?:新增|新建|添加|创建)(?:申请|记录|数据|客户|项目|任务|明细|行)?/;
|
|
5
|
+
|
|
6
|
+
function buttonContent(template, tag) {
|
|
7
|
+
const start = tag.index + tag.text.length;
|
|
8
|
+
const end = template.indexOf("</el-button>", start);
|
|
9
|
+
return end < 0 ? "" : template.slice(start, end);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function hasTrueBooleanAttr(tagText, attr) {
|
|
13
|
+
return new RegExp(
|
|
14
|
+
`(?:^|\\s)(?::${attr}\\s*=\\s*["']true["']|${attr}(?:\\s*=\\s*["'](?:true|)["'])?)(?=\\s|/?>)`,
|
|
15
|
+
).test(tagText);
|
|
16
|
+
}
|
|
17
|
+
|
|
4
18
|
export const buttonRules = [
|
|
5
19
|
// R004: 操作列文字按钮
|
|
6
20
|
{
|
|
@@ -59,6 +73,46 @@ export const buttonRules = [
|
|
|
59
73
|
},
|
|
60
74
|
},
|
|
61
75
|
|
|
76
|
+
// R038: 新增/新建/添加/创建类主操作必须使用填充主题色
|
|
77
|
+
{
|
|
78
|
+
id: "R038",
|
|
79
|
+
category: "button",
|
|
80
|
+
severity: "error",
|
|
81
|
+
name: "创建类主操作缺少 primary 填充主题色",
|
|
82
|
+
check(template, file, lineOffset) {
|
|
83
|
+
const issues = [];
|
|
84
|
+
for (const tag of findTags(template, "el-button")) {
|
|
85
|
+
const label = buttonContent(template, tag);
|
|
86
|
+
if (!CREATE_ACTION_LABEL.test(label)) continue;
|
|
87
|
+
if (/:type\s*=/.test(tag.text)) continue;
|
|
88
|
+
if (
|
|
89
|
+
hasTrueBooleanAttr(tag.text, "link") ||
|
|
90
|
+
hasTrueBooleanAttr(tag.text, "text")
|
|
91
|
+
) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const isPrimary = /(?:^|\s)type\s*=\s*["']primary["']/.test(
|
|
95
|
+
tag.text,
|
|
96
|
+
);
|
|
97
|
+
const isPlain = hasTrueBooleanAttr(tag.text, "plain");
|
|
98
|
+
if (!isPrimary || isPlain) {
|
|
99
|
+
issues.push(
|
|
100
|
+
issue(
|
|
101
|
+
file,
|
|
102
|
+
lineOf(template, tag.index, lineOffset),
|
|
103
|
+
"R038",
|
|
104
|
+
"button",
|
|
105
|
+
"error",
|
|
106
|
+
"新增/新建/添加/创建类主操作必须是 primary 填充按钮",
|
|
107
|
+
'设置 type="primary" 并移除 plain;行内次级操作应显式使用 link/text',
|
|
108
|
+
),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return issues;
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
|
|
62
116
|
// R015: 弹窗嵌套表格操作列用 el-button link
|
|
63
117
|
{
|
|
64
118
|
id: "R015",
|
package/scanner/rules/table.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** scanner/rules/table.mjs — 表格相关规则:R001 R002 R003 R014 R021 R022 */
|
|
1
|
+
/** scanner/rules/table.mjs — 表格相关规则:R001 R002 R003 R014 R021 R022 R039 */
|
|
2
2
|
import { lineOf, issue, findTags } from "./_shared.mjs";
|
|
3
3
|
|
|
4
4
|
export const tableRules = [
|
|
@@ -35,6 +35,41 @@ export const tableRules = [
|
|
|
35
35
|
},
|
|
36
36
|
},
|
|
37
37
|
|
|
38
|
+
// R039: 原生 el-table 普通数据列必须支持超长省略与悬停完整提示
|
|
39
|
+
{
|
|
40
|
+
id: "R039",
|
|
41
|
+
category: "table",
|
|
42
|
+
severity: "warning",
|
|
43
|
+
name: "el-table 普通数据列缺少 show-overflow-tooltip",
|
|
44
|
+
check(template, file, lineOffset) {
|
|
45
|
+
const issues = [];
|
|
46
|
+
for (const tag of findTags(template, "el-table-column")) {
|
|
47
|
+
if (!/(?:^|\s):?prop\s*=/.test(tag.text)) continue;
|
|
48
|
+
if (
|
|
49
|
+
/(?:^|\s)type\s*=\s*["'](?:selection|index|expand)["']/.test(
|
|
50
|
+
tag.text,
|
|
51
|
+
)
|
|
52
|
+
) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (/(?:^|\s):?show-overflow-tooltip(?:\s|=|\/?>)/.test(tag.text))
|
|
56
|
+
continue;
|
|
57
|
+
issues.push(
|
|
58
|
+
issue(
|
|
59
|
+
file,
|
|
60
|
+
lineOf(template, tag.index, lineOffset),
|
|
61
|
+
"R039",
|
|
62
|
+
"table",
|
|
63
|
+
"warning",
|
|
64
|
+
"普通数据列缺少超长省略与悬停完整内容提示",
|
|
65
|
+
"添加 show-overflow-tooltip;BaseTable/AG Grid 列统一使用 defineColumns() 自动补齐",
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return issues;
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
|
|
38
73
|
// R002: el-table 缺少 empty-text
|
|
39
74
|
{
|
|
40
75
|
id: "R002",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: |
|
|
3
3
|
表格组件规范 Skill — el-table / BaseTable / el-table-column 的对齐、空状态、selection 列标准。
|
|
4
|
-
覆盖规则:R001 R002 R003 R014。
|
|
4
|
+
覆盖规则:R001 R002 R003 R014 R039。
|
|
5
5
|
applyTo: "**/*.vue"
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -55,6 +55,19 @@ applyTo: "**/*.vue"
|
|
|
55
55
|
|
|
56
56
|
---
|
|
57
57
|
|
|
58
|
+
## R039 — 普通数据列必须超长省略并悬停显示完整内容 【中危】
|
|
59
|
+
|
|
60
|
+
```diff
|
|
61
|
+
- <el-table-column prop="customerName" label="客户名称" align="center" />
|
|
62
|
+
+ <el-table-column prop="customerName" label="客户名称" align="center" show-overflow-tooltip />
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
BaseTable / AG Grid 使用 `defineColumns()` 后由 runtime 自动为普通文本列补齐
|
|
66
|
+
`showOverflowTooltip: true`。自定义渲染、结构列、换行列不强制;需要主动关闭时
|
|
67
|
+
显式声明 `showOverflowTooltip: false`。
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
58
71
|
## 完整标准写法示例
|
|
59
72
|
|
|
60
73
|
```html
|
|
@@ -67,7 +80,13 @@ applyTo: "**/*.vue"
|
|
|
67
80
|
header-align="center"
|
|
68
81
|
/>
|
|
69
82
|
<el-table-column type="index" align="center" width="60" label="序号" />
|
|
70
|
-
<el-table-column
|
|
83
|
+
<el-table-column
|
|
84
|
+
align="center"
|
|
85
|
+
prop="name"
|
|
86
|
+
label="名称"
|
|
87
|
+
min-width="120"
|
|
88
|
+
show-overflow-tooltip
|
|
89
|
+
/>
|
|
71
90
|
<el-table-column align="center" prop="status" label="状态" width="90">
|
|
72
91
|
<template #default="{ row }">
|
|
73
92
|
<!-- 使用 renderTagSlot 渲染彩色 Tag,见 tag-status/SKILL.md -->
|
|
@@ -53,6 +53,8 @@ applyTo: "**/*.vue"
|
|
|
53
53
|
|
|
54
54
|
- R001 ~ R003:表格列对齐 + empty-text
|
|
55
55
|
- R005:工具栏按钮缺 icon
|
|
56
|
+
- R038:新增/新建/添加/创建类主按钮缺 primary 填充主题色
|
|
57
|
+
- R039:普通数据列缺少省略号与悬停完整内容提示
|
|
56
58
|
- R006:搜索区输入框缺 `size="small"`
|
|
57
59
|
- R011:分页在 footer 内(严重)
|
|
58
60
|
|
|
@@ -97,6 +99,10 @@ applyTo: "**/*.vue"
|
|
|
97
99
|
</template>
|
|
98
100
|
```
|
|
99
101
|
|
|
102
|
+
“新增”类按钮必须保持 `type="primary"` 且不得带 `plain`;生成完成后运行
|
|
103
|
+
`wl-ui scan` / `wl-ui fix` 复核 R038。表格列统一由 `defineColumns()` 包裹,
|
|
104
|
+
普通文本列的 R039 行为由 runtime 自动补齐。
|
|
105
|
+
|
|
100
106
|
### SCSS 对应(由 styles/layouts/\_list-page.scss 提供)
|
|
101
107
|
|
|
102
108
|
```scss
|
|
@@ -28,12 +28,14 @@ applyTo: "**/*.vue"
|
|
|
28
28
|
- ❌ 用 `<el-button type="primary">` 当操作按钮(应用 `is-text` 文字按钮,沿用 R004)
|
|
29
29
|
- ❌ 列头未居中(同 R001)
|
|
30
30
|
- ❌ 缺 `empty-text="暂无数据"`(同 R003)
|
|
31
|
+
- ❌ 普通文本列未通过 `defineColumns()` 获得超长省略与悬停完整提示(同 R039)
|
|
31
32
|
- ❌ 在 `<BaseTable>` 外层包裹自定义 padding 容器,破坏整体节奏
|
|
32
33
|
|
|
33
34
|
## Repair(修复)
|
|
34
35
|
|
|
35
36
|
### A 类(自动修)
|
|
36
37
|
- 缺失 `empty-text` → 补 `empty-text="暂无数据"`
|
|
38
|
+
- 普通文本列统一经 `defineColumns()` 自动补 `showOverflowTooltip: true`
|
|
37
39
|
- 操作列按钮 `type="primary"` → 改为 `link` + `is-text`
|
|
38
40
|
- 内联 hex → CSS 变量
|
|
39
41
|
|
|
@@ -51,7 +53,8 @@ applyTo: "**/*.vue"
|
|
|
51
53
|
|
|
52
54
|
## 全局样式来源
|
|
53
55
|
|
|
54
|
-
`styles/vendors/_base-table.scss` —
|
|
56
|
+
`styles/vendors/_base-table.scss` — 已注入容器圆角、表头颜色、操作按钮间距;
|
|
57
|
+
`runtime.defineColumns()` 统一控制普通文本列的 overflow tooltip。
|
|
55
58
|
项目侧无需重复写样式,遵循 DOM 约定即可。
|
|
56
59
|
|
|
57
60
|
## 未来源码迁移
|
package/standards/rules.json
CHANGED
|
@@ -107,6 +107,17 @@
|
|
|
107
107
|
"scanner": "scanner/rules/button.mjs",
|
|
108
108
|
"skills": ["layouts/list-page"]
|
|
109
109
|
},
|
|
110
|
+
{
|
|
111
|
+
"id": "R038",
|
|
112
|
+
"category": "button",
|
|
113
|
+
"severity": "error",
|
|
114
|
+
"title": "新增/新建/添加/创建类主操作必须使用 primary 填充按钮",
|
|
115
|
+
"rationale": "创建类动作是列表页首要操作,必须稳定使用客户主题深蓝填充,禁止漏写 type 或误用 plain 退化为线框。",
|
|
116
|
+
"appliesTo": ["el-button[label~=新增|新建|添加|创建]"],
|
|
117
|
+
"autoFixable": true,
|
|
118
|
+
"scanner": "scanner/rules/button.mjs",
|
|
119
|
+
"skills": ["layouts/list-page"]
|
|
120
|
+
},
|
|
110
121
|
{
|
|
111
122
|
"id": "R006",
|
|
112
123
|
"category": "form",
|
|
@@ -313,6 +324,20 @@
|
|
|
313
324
|
"scanner": "scanner/rules/table.mjs",
|
|
314
325
|
"skills": ["vendors/base-table"]
|
|
315
326
|
},
|
|
327
|
+
{
|
|
328
|
+
"id": "R039",
|
|
329
|
+
"category": "table",
|
|
330
|
+
"severity": "warning",
|
|
331
|
+
"title": "普通数据列必须支持超长省略与悬停完整提示",
|
|
332
|
+
"rationale": "列宽不足时统一单行省略,悬停显示完整内容,避免长名称撑破布局或信息不可读。",
|
|
333
|
+
"appliesTo": [
|
|
334
|
+
"el-table-column[prop]",
|
|
335
|
+
"BaseTable defineColumns ordinary data column"
|
|
336
|
+
],
|
|
337
|
+
"autoFixable": true,
|
|
338
|
+
"scanner": "scanner/rules/table.mjs",
|
|
339
|
+
"skills": ["element/el-table", "vendors/base-table"]
|
|
340
|
+
},
|
|
316
341
|
{
|
|
317
342
|
"id": "R031",
|
|
318
343
|
"category": "family",
|
package/standards/ui/01-table.md
CHANGED
|
@@ -32,6 +32,27 @@
|
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
35
|
+
## 规则 R039:长文本统一省略并悬停显示完整内容
|
|
36
|
+
|
|
37
|
+
原生 Element Plus 普通数据列必须声明 `show-overflow-tooltip`:
|
|
38
|
+
|
|
39
|
+
```vue
|
|
40
|
+
<el-table-column
|
|
41
|
+
prop="customerName"
|
|
42
|
+
label="客户名称"
|
|
43
|
+
min-width="160"
|
|
44
|
+
align="center"
|
|
45
|
+
show-overflow-tooltip
|
|
46
|
+
/>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
BaseTable / AG Grid 列必须通过 `defineColumns()` 声明。普通文本列会自动补
|
|
50
|
+
`showOverflowTooltip: true`;自定义渲染列、selection/index/expand、`wrapText`
|
|
51
|
+
或 `autoHeight` 列不会被强制。业务可显式设置 `showOverflowTooltip: false`
|
|
52
|
+
退出。
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
35
56
|
## 规则 R004:操作列使用 renderOps / jh-op-btn
|
|
36
57
|
|
|
37
58
|
操作列按钮**不得**直接使用 `<el-button>` 或裸文本,必须通过 `renderOps` 渲染:
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
|
|
12
12
|
### 类型规则
|
|
13
13
|
|
|
14
|
+
**R038 强制约束**:凡按钮文案表达“新增 / 新建 / 添加 / 创建”主操作,
|
|
15
|
+
必须使用 `type="primary"` 的填充样式,不得漏写 `type`,也不得同时设置
|
|
16
|
+
`plain`。只有明确属于行内次级交互时,才允许显式改用 `link` / `text`。
|
|
17
|
+
`wl-ui scan` 会报错,`wl-ui fix` 可机械纠正静态按钮。
|
|
18
|
+
|
|
14
19
|
主按钮色值严格遵循客户 UI 规范,禁止使用 `#2254F4`、`#4368FF` 或 Element Plus 默认蓝替代:
|
|
15
20
|
|
|
16
21
|
| 状态 | Token | 规范色值 |
|
|
@@ -36,7 +41,7 @@
|
|
|
36
41
|
|
|
37
42
|
```vue
|
|
38
43
|
<!-- ✅ 工具栏按钮组 -->
|
|
39
|
-
<el-button type="primary" @click="handleCreate">新增</el-button>
|
|
44
|
+
<el-button type="primary" icon="Plus" @click="handleCreate">新增</el-button>
|
|
40
45
|
<el-button type="warning" plain icon="Edit" @click="handleEdit">修改</el-button>
|
|
41
46
|
<el-button type="success" icon="Save" @click="handleSave">保存</el-button>
|
|
42
47
|
<el-button icon="Download" @click="handleExport">导出</el-button>
|
|
@@ -12,6 +12,14 @@ html body .el-table {
|
|
|
12
12
|
--el-table-tr-bg-color: var(--el-fill-color-lighter, #fafafa);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
// 仅命中 show-overflow-tooltip 生成的单元格,不压制 Tag/操作列等自定义内容。
|
|
16
|
+
.el-table__body .cell.el-tooltip {
|
|
17
|
+
min-width: 0;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
text-overflow: ellipsis;
|
|
20
|
+
white-space: nowrap;
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
.el-table__empty-block {
|
|
16
24
|
display: flex;
|
|
17
25
|
align-items: center;
|
|
@@ -24,7 +24,7 @@ columnsDef(): TableColumnDesc<any>[] {
|
|
|
24
24
|
|
|
25
25
|
// ── 数据列(已注册字段自动映射,无需 defaultNode)──
|
|
26
26
|
{ name: 'riskNo', label: '编号', width: 100 }, // → renderBadge
|
|
27
|
-
{ name: 'name', label: '名称', minWidth: 150 },
|
|
27
|
+
{ name: 'name', label: '名称', minWidth: 150 }, // → 自动省略 + 悬停完整提示
|
|
28
28
|
{ name: 'riskLevel', label: '风险分级', width: 90 }, // → renderRiskLevel Tag
|
|
29
29
|
{ name: 'riskStatus', label: '状态', width: 90 }, // → renderRiskStatus Tag
|
|
30
30
|
|
|
@@ -74,3 +74,4 @@ toolbarDef() {
|
|
|
74
74
|
2. 操作列用 `renderOps([...])` — 图标按钮 + 自动 stopPropagation
|
|
75
75
|
3. selection 列必须同时设置 `align: 'center'` + `headerAlign: 'center'`(R014)
|
|
76
76
|
4. 分类字段(type/level)用 `renderClassifyTag`,状态字段用 `renderTagNode`
|
|
77
|
+
5. `defineColumns()` 自动为普通文本列补 `showOverflowTooltip: true`;自定义渲染、结构列和换行列除外
|
|
@@ -107,6 +107,7 @@ const modal = ref();
|
|
|
107
107
|
function columnsDef() {
|
|
108
108
|
return defineColumns([
|
|
109
109
|
{ type: "index", label: "序号", width: 60, align: "center" },
|
|
110
|
+
// 普通文本列由 defineColumns 自动补 showOverflowTooltip: true
|
|
110
111
|
{ name: "name", label: "名称", minWidth: 120 },
|
|
111
112
|
{ name: "status", label: "状态", width: 80 }, // enableStatus 已自动映射
|
|
112
113
|
{ name: "remark", label: "备注", minWidth: 160 },
|
|
@@ -141,3 +142,5 @@ async function handleDel(id: string) {
|
|
|
141
142
|
3. `enableStatus` 字段在 `installCommonPreset()` 后自动渲染为彩色 Tag,无需手写 defaultNode
|
|
142
143
|
4. 工具栏按钮必须带 `icon`(R005 规则)
|
|
143
144
|
5. 标准顺序固定为“查询区 → 工具栏 → 列表标题 → 表格 → 分页器”;工具栏不得放到标题右侧或标题下方
|
|
145
|
+
6. 新增/新建/添加/创建类主按钮必须 `type="primary"` 且不得使用 `plain`(R038)
|
|
146
|
+
7. 普通文本列由 `defineColumns()` 自动启用省略号与悬停完整提示;原生 `el-table-column` 必须加 `show-overflow-tooltip`(R039)
|