@agile-team/wl-skills-ui 1.6.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 +207 -0
- package/README.md +623 -0
- package/SKILL.md +632 -0
- package/bin/wl-ui.js +794 -0
- package/design/spec/color.md +41 -0
- package/design/spec/spacing.md +28 -0
- package/design/spec/typography.md +29 -0
- package/design/tokens/base.css +144 -0
- package/design/tokens/index.css +8 -0
- package/dist/ag-grid-override.scss +1 -0
- package/dist/element.scss +1 -0
- package/dist/index.scss +1 -0
- package/dist/portal.scss +1 -0
- package/dist/style-override.scss +1 -0
- package/dist/tokens.css +144 -0
- package/es/chunk-GBB7LDKQ.js +261 -0
- package/es/chunk-MDI2SCUP.js +281 -0
- package/es/chunk-QDTNMM7W.js +286 -0
- package/es/chunk-YLNAFAWE.js +233 -0
- package/es/common-preset.d.ts +38 -0
- package/es/common-preset.js +157 -0
- package/es/index.d.ts +32 -0
- package/es/index.js +1 -0
- package/es/presets/security.d.ts +16 -0
- package/es/presets/security.js +99 -0
- package/es/renderers-BFRSp2BH.d.ts +51 -0
- package/es/renderers-DMZbi_Gs.d.ts +51 -0
- package/es/renderers-DWhUlI2y.d.ts +47 -0
- package/es/types-04qLKHMt.d.ts +62 -0
- package/es/types-BrCPjAH6.d.ts +62 -0
- package/es/types-QZ1gy9KX.d.ts +63 -0
- package/examples/migration-operations-to-renderOps.md +107 -0
- package/examples/wk-exempt.example.json +30 -0
- package/mcp/server.js +394 -0
- package/package.json +130 -0
- package/reference/SelectPopupCom.vue +634 -0
- package/reference/ag-cell-renders.ts +644 -0
- package/reference/define-columns.ts +185 -0
- package/reference/jhlc.d.ts +1 -0
- package/scanner/coverage.mjs +287 -0
- package/scanner/exempt.mjs +127 -0
- package/scanner/fix.mjs +216 -0
- package/scanner/index.mjs +403 -0
- package/scanner/integration.mjs +155 -0
- package/scanner/report.mjs +459 -0
- package/scanner/rules/_shared.mjs +134 -0
- package/scanner/rules/button.mjs +97 -0
- package/scanner/rules/color.mjs +118 -0
- package/scanner/rules/componentFamily.mjs +184 -0
- package/scanner/rules/dialog.mjs +42 -0
- package/scanner/rules/form.mjs +86 -0
- package/scanner/rules/index.mjs +58 -0
- package/scanner/rules/table.mjs +170 -0
- package/scanner/rules/tag.mjs +245 -0
- package/scanner/snapshot.mjs +198 -0
- package/skills/_flows/full-audit.md +65 -0
- package/skills/_flows/legacy-skin-align.md +71 -0
- package/skills/_flows/new-project-init.md +80 -0
- package/skills/_flows/progressive-migrate.md +49 -0
- package/skills/_meta/_compat/README.md +47 -0
- package/skills/_meta/_compat/editors.json +76 -0
- package/skills/_meta/_compat/headers/agents.txt +7 -0
- package/skills/_meta/_compat/headers/claude-code.txt +7 -0
- package/skills/_meta/_compat/headers/cline.txt +7 -0
- package/skills/_meta/_compat/headers/cursor-mdc.txt +5 -0
- package/skills/_meta/_compat/headers/github-copilot.txt +5 -0
- package/skills/_meta/_compat/headers/kiro.txt +4 -0
- package/skills/_meta/_compat/headers/qoder.txt +4 -0
- package/skills/_meta/_compat/headers/trae.txt +3 -0
- package/skills/_meta/_compat/headers/windsurf.txt +5 -0
- package/skills/_meta/_detection.md +81 -0
- package/skills/_meta/_registry.md +96 -0
- package/skills/element/component-family/SKILL.md +71 -0
- package/skills/element/el-dialog/SKILL.md +124 -0
- package/skills/element/el-form/SKILL.md +84 -0
- package/skills/element/el-table/SKILL.md +86 -0
- package/skills/element/el-tag/SKILL.md +127 -0
- package/skills/layouts/detail-page/SKILL.md +99 -0
- package/skills/layouts/form-dialog/SKILL.md +128 -0
- package/skills/layouts/list-page/SKILL.md +111 -0
- package/skills/layouts/tree-list/SKILL.md +108 -0
- package/skills/ops/audit/SKILL.md +109 -0
- package/skills/ops/fix/SKILL.md +52 -0
- package/skills/ops/fix/USAGE.md +24 -0
- package/skills/ops/migrate/SKILL.md +157 -0
- package/skills/ops/scan/SKILL.md +71 -0
- package/skills/ops/scan/USAGE.md +16 -0
- package/skills/runtime/design-tokens/SKILL.md +88 -0
- package/skills/runtime/design-tokens/USAGE.md +19 -0
- package/skills/runtime/migration/SKILL.md +106 -0
- package/skills/runtime/migration/USAGE.md +34 -0
- package/skills/runtime/style-align/SKILL.md +124 -0
- package/skills/runtime/style-align/USAGE.md +52 -0
- package/skills/vendors/ag-grid/SKILL.md +94 -0
- package/skills/vendors/base-table/SKILL.md +57 -0
- package/skills/vendors/c-components/SKILL.md +36 -0
- package/skills/vendors/custom-wrappers/SKILL.md +45 -0
- package/skills/vendors/jh-components/SKILL.md +42 -0
- package/skills/vendors/unknown-wrapper/SKILL.md +44 -0
- package/standards/engineering/01-import-order.md +46 -0
- package/standards/engineering/02-naming.md +54 -0
- package/standards/engineering/03-scss-structure.md +59 -0
- package/standards/ui/01-table.md +111 -0
- package/standards/ui/02-button.md +83 -0
- package/standards/ui/03-form.md +118 -0
- package/standards/ui/04-tag-status.md +110 -0
- package/standards/ui/05-dialog-pagination.md +118 -0
- package/styles/element/_card.scss +26 -0
- package/styles/element/_descriptions.scss +22 -0
- package/styles/element/_dialog.scss +43 -0
- package/styles/element/_drawer.scss +22 -0
- package/styles/element/_feedback.scss +52 -0
- package/styles/element/_form.scss +22 -0
- package/styles/element/_navigation.scss +36 -0
- package/styles/element/_overlay.scss +32 -0
- package/styles/element/_pagination.scss +16 -0
- package/styles/element/_steps.scss +32 -0
- package/styles/element/_table.scss +13 -0
- package/styles/element/_tabs.scss +40 -0
- package/styles/element/_tree.scss +27 -0
- package/styles/element/_upload.scss +34 -0
- package/styles/element/index.scss +17 -0
- package/styles/index.scss +21 -0
- package/styles/layouts/_detail-page.scss +27 -0
- package/styles/layouts/_form-dialog.scss +19 -0
- package/styles/layouts/_list-page.scss +47 -0
- package/styles/layouts/_tree-list.scss +33 -0
- package/styles/layouts/index.scss +6 -0
- package/styles/presets/element-only.scss +6 -0
- package/styles/presets/full.scss +12 -0
- package/styles/presets/security.scss +37 -0
- package/styles/presets/skin.scss +14 -0
- package/styles/presets/tokens-only.scss +7 -0
- package/styles/tokens/index.scss +79 -0
- package/styles/vendors/_ag-grid.scss +273 -0
- package/styles/vendors/_base-components.scss +480 -0
- package/styles/vendors/_base-query-toolbar.scss +253 -0
- package/styles/vendors/_base-table.scss +41 -0
- package/styles/vendors/_c-components.scss +34 -0
- package/styles/vendors/_custom-wrappers.scss +30 -0
- package/styles/vendors/_jh-drag-col.scss +63 -0
- package/styles/vendors/_jh-pagination.scss +81 -0
- package/styles/vendors/_jh-tree.scss +196 -0
- package/styles/vendors/_portal.scss +114 -0
- package/styles/vendors/index.scss +17 -0
- package/templates/ag-grid-page/TPL-AG-GRID.md +76 -0
- package/templates/form-dialog/TPL-FORM-DIALOG.md +165 -0
- package/templates/list-page/TPL-LIST.md +139 -0
- package/templates/tree-list/TPL-TREE-LIST.md +176 -0
- package/templates/ui-optimization-report/TPL-UI-OPTIM-REPORT.md +343 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 接入完整性检查
|
|
3
|
+
* 检查目标项目是否正确引入了 @agile-team/wk-skills-ui 的样式和 runtime
|
|
4
|
+
*
|
|
5
|
+
* 检查项:
|
|
6
|
+
* I001 — index.html 是否引入 tokens.css(或同等 css-var-compile.css)
|
|
7
|
+
* I002 — main.scss / 全局入口是否引入 @agile-team/wk-skills-ui/styles 或 shared/index.scss
|
|
8
|
+
* I003 — src/util/ 是否有 ag-cell-renders.ts 或者 main.ts 中安装了 runtime
|
|
9
|
+
* I004 — element-plus 是否在 dependencies 中(peer 兼容)
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
12
|
+
import { join, dirname } from 'node:path';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} projectRoot — 项目根目录(包含 index.html / src / package.json)
|
|
16
|
+
* @returns {Array<{id, severity, ok, description, suggestion}>}
|
|
17
|
+
*/
|
|
18
|
+
export function checkIntegration(projectRoot) {
|
|
19
|
+
const checks = [];
|
|
20
|
+
|
|
21
|
+
// ── I001: index.html 加载 tokens.css ─────────────────────────────────
|
|
22
|
+
const indexHtml = join(projectRoot, "index.html");
|
|
23
|
+
if (existsSync(indexHtml)) {
|
|
24
|
+
const html = readFileSync(indexHtml, "utf8");
|
|
25
|
+
const hasTokens =
|
|
26
|
+
/(css-var-compile|tokens)\.css/.test(html) ||
|
|
27
|
+
/wk-skills-ui\/(dist\/tokens|design\/tokens)/.test(html) ||
|
|
28
|
+
/@agile-team\/wk-skills-ui/.test(html);
|
|
29
|
+
checks.push({
|
|
30
|
+
id: "I001",
|
|
31
|
+
severity: hasTokens ? "info" : "error",
|
|
32
|
+
ok: hasTokens,
|
|
33
|
+
description: hasTokens
|
|
34
|
+
? "tokens.css 已在 index.html 中引入"
|
|
35
|
+
: "index.html 缺少 tokens.css 引入",
|
|
36
|
+
suggestion: hasTokens
|
|
37
|
+
? ""
|
|
38
|
+
: '在 <head> 最先加载:<link rel="stylesheet" href="/node_modules/@agile-team/wk-skills-ui/design/tokens/base.css" />',
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
checks.push({
|
|
42
|
+
id: "I001",
|
|
43
|
+
severity: "warning",
|
|
44
|
+
ok: false,
|
|
45
|
+
description: "未找到 index.html",
|
|
46
|
+
suggestion: "确认 --project 参数指向项目根目录",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── I002: 全局样式入口引入 @agile-team/wk-skills-ui ─────────────────────────
|
|
51
|
+
const styleEntries = [
|
|
52
|
+
"src/assets/style/main.scss",
|
|
53
|
+
"src/assets/style/index.scss",
|
|
54
|
+
"src/styles/main.scss",
|
|
55
|
+
"src/styles/index.scss",
|
|
56
|
+
"src/main.scss",
|
|
57
|
+
"src/style.scss",
|
|
58
|
+
];
|
|
59
|
+
let styleHit = null;
|
|
60
|
+
for (const rel of styleEntries) {
|
|
61
|
+
const p = join(projectRoot, rel);
|
|
62
|
+
if (existsSync(p)) {
|
|
63
|
+
styleHit = { rel, content: readFileSync(p, "utf8") };
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (styleHit) {
|
|
68
|
+
const referenced =
|
|
69
|
+
/@agile-team\/wk-skills-ui(\/styles)?/.test(styleHit.content) ||
|
|
70
|
+
/wk-skills-ui(\/dist)?/.test(styleHit.content) ||
|
|
71
|
+
/shared\/index/.test(styleHit.content);
|
|
72
|
+
checks.push({
|
|
73
|
+
id: "I002",
|
|
74
|
+
severity: referenced ? "info" : "warning",
|
|
75
|
+
ok: referenced,
|
|
76
|
+
description: referenced
|
|
77
|
+
? `全局样式入口 ${styleHit.rel} 已引入 @agile-team/wk-skills-ui`
|
|
78
|
+
: `全局样式入口 ${styleHit.rel} 未引入 @agile-team/wk-skills-ui/styles`,
|
|
79
|
+
suggestion: referenced
|
|
80
|
+
? ""
|
|
81
|
+
: `在 ${styleHit.rel} 末尾添加:@use '@agile-team/wk-skills-ui/styles' as *;`,
|
|
82
|
+
});
|
|
83
|
+
} else {
|
|
84
|
+
checks.push({
|
|
85
|
+
id: "I002",
|
|
86
|
+
severity: "warning",
|
|
87
|
+
ok: false,
|
|
88
|
+
description: "未找到全局 SCSS 入口(main.scss / index.scss)",
|
|
89
|
+
suggestion:
|
|
90
|
+
"在 src/assets/style/main.scss 中 @use '@agile-team/wk-skills-ui/styles' as *;",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── I003: runtime 是否被引入 ─────────────────────────────────────────
|
|
95
|
+
const utilDir = join(projectRoot, "src/util");
|
|
96
|
+
const utilHit =
|
|
97
|
+
existsSync(utilDir) &&
|
|
98
|
+
(existsSync(join(utilDir, "ag-cell-renders.ts")) ||
|
|
99
|
+
existsSync(join(utilDir, "define-columns.ts")));
|
|
100
|
+
let runtimeReferenced = false;
|
|
101
|
+
// 也检查 main.ts 是否 import runtime
|
|
102
|
+
for (const entry of ["src/main.ts", "src/main.js", "src/main-core.ts"]) {
|
|
103
|
+
const p = join(projectRoot, entry);
|
|
104
|
+
if (
|
|
105
|
+
existsSync(p) &&
|
|
106
|
+
/@agile-team\/wk-skills-ui\/runtime/.test(readFileSync(p, "utf8"))
|
|
107
|
+
) {
|
|
108
|
+
runtimeReferenced = true;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const runtimeOk = utilHit || runtimeReferenced;
|
|
113
|
+
checks.push({
|
|
114
|
+
id: "I003",
|
|
115
|
+
severity: runtimeOk ? "info" : "warning",
|
|
116
|
+
ok: runtimeOk,
|
|
117
|
+
description: runtimeOk
|
|
118
|
+
? "runtime 已可用(util/ag-cell-renders.ts 存在或已 import 包)"
|
|
119
|
+
: "未发现 runtime 引入(无 src/util/ag-cell-renders.ts,main.ts 也未 import runtime)",
|
|
120
|
+
suggestion: runtimeOk
|
|
121
|
+
? ""
|
|
122
|
+
: '推荐:在 main.ts 中 import { installCommonPreset } from "@agile-team/wk-skills-ui/runtime/common-preset"; installCommonPreset();',
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// ── I004: element-plus 已安装 ────────────────────────────────────────
|
|
126
|
+
const pkgPath = join(projectRoot, "package.json");
|
|
127
|
+
if (existsSync(pkgPath)) {
|
|
128
|
+
try {
|
|
129
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
130
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
131
|
+
const hasEp = !!deps["element-plus"];
|
|
132
|
+
const hasVue = !!deps["vue"];
|
|
133
|
+
checks.push({
|
|
134
|
+
id: "I004",
|
|
135
|
+
severity: hasEp && hasVue ? "info" : "warning",
|
|
136
|
+
ok: hasEp && hasVue,
|
|
137
|
+
description:
|
|
138
|
+
hasEp && hasVue
|
|
139
|
+
? `peer 依赖满足(vue ${deps.vue},element-plus ${deps["element-plus"]})`
|
|
140
|
+
: `peer 依赖缺失:${!hasVue ? "vue " : ""}${!hasEp ? "element-plus" : ""}`,
|
|
141
|
+
suggestion: hasEp && hasVue ? "" : "pnpm add vue element-plus",
|
|
142
|
+
});
|
|
143
|
+
} catch {
|
|
144
|
+
checks.push({
|
|
145
|
+
id: "I004",
|
|
146
|
+
severity: "warning",
|
|
147
|
+
ok: false,
|
|
148
|
+
description: "package.json 解析失败",
|
|
149
|
+
suggestion: "",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return checks;
|
|
155
|
+
}
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 报告生成器 v3 — 结构化全量报告模板
|
|
3
|
+
*
|
|
4
|
+
* 自动输出以下章节:
|
|
5
|
+
* 一、接入完整性仪表盘
|
|
6
|
+
* 二、扫描总览(目录/文件/分类/严重度)
|
|
7
|
+
* 三、豁免统计
|
|
8
|
+
* 四、按分类统计(token/表格/表单/按钮/弹窗/标签 …)
|
|
9
|
+
* 五、按规则统计
|
|
10
|
+
* 六、按目录 & 文件明细
|
|
11
|
+
* 七、问题明细(高/中/低)
|
|
12
|
+
* 八、残留问题与后续建议
|
|
13
|
+
* 附录:豁免配置 & 快照说明
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const SEVERITY_LABEL = {
|
|
17
|
+
error: "🔴 高危",
|
|
18
|
+
warning: "🟡 中危",
|
|
19
|
+
info: "🔵 低危",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const CATEGORY_LABEL = {
|
|
23
|
+
color: "色值 Token",
|
|
24
|
+
token: "色值 Token",
|
|
25
|
+
style: "样式规范",
|
|
26
|
+
table: "表格 (el-table)",
|
|
27
|
+
form: "表单 (el-form/input/select)",
|
|
28
|
+
input: "表单 (el-input)",
|
|
29
|
+
dialog: "弹窗 (el-dialog)",
|
|
30
|
+
button: "按钮 / 操作列",
|
|
31
|
+
tag: "标签 (el-tag)",
|
|
32
|
+
pagination: "分页 (pagination)",
|
|
33
|
+
card: "卡片 (el-card)",
|
|
34
|
+
tabs: "标签页 (el-tabs)",
|
|
35
|
+
descriptions: "描述列表 (el-descriptions)",
|
|
36
|
+
tree: "树 (el-tree)",
|
|
37
|
+
drawer: "抽屉 (el-drawer)",
|
|
38
|
+
upload: "上传 (el-upload)",
|
|
39
|
+
steps: "步骤条 (el-steps)",
|
|
40
|
+
overlay: "弹层 (popover/tooltip/dropdown)",
|
|
41
|
+
navigation: "导航 (menu/breadcrumb)",
|
|
42
|
+
feedback: "反馈 (empty/result/alert/badge)",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const RULE_NAME = {
|
|
46
|
+
R001: 'el-table-column 缺少 align="center"',
|
|
47
|
+
R002: "el-table 缺少 empty-text",
|
|
48
|
+
R003: "BaseTable 缺少 empty-text",
|
|
49
|
+
R004: "操作列使用文字 el-button",
|
|
50
|
+
R005: "工具栏 el-button 缺少 icon",
|
|
51
|
+
R006: 'el-input/el-select 缺少 size="small"',
|
|
52
|
+
R007: "el-date-picker 缺少 width:100%",
|
|
53
|
+
R008: "el-form labelWidth 偏小",
|
|
54
|
+
R009: "状态字段纯文本渲染(应使用 renderTagNode/ElTag)",
|
|
55
|
+
R010: '分类字段 ElTag 未使用 effect="plain"',
|
|
56
|
+
R011: "pagination 在 #footer 内(位置错误)",
|
|
57
|
+
R012: "弹窗内 el-table 缺少 empty-text",
|
|
58
|
+
R013: "columnsDef 旧格式 operations: []",
|
|
59
|
+
R014: "selection 列缺少 header-align",
|
|
60
|
+
R015: "弹窗嵌套表格用 el-button link",
|
|
61
|
+
R016: "<style> 块硬编码 hex 颜色",
|
|
62
|
+
R017: "<template> 内硬编码 hex 颜色",
|
|
63
|
+
R018: "<script> 块硬编码 hex 颜色",
|
|
64
|
+
R021: 'BaseTable 缺少 render-type="agGrid"',
|
|
65
|
+
R022: "BaseTable 缺少唯一 cid",
|
|
66
|
+
R031: "详情/统计卡片建议使用统一场景 class",
|
|
67
|
+
R032: "el-tabs 建议明确页面场景",
|
|
68
|
+
R033: "el-descriptions 建议使用 bordered 或统一容器",
|
|
69
|
+
R034: "el-drawer 建议明确 size",
|
|
70
|
+
R035: "el-upload 建议配置 tip/限制说明",
|
|
71
|
+
R036: "el-steps 审批/流程建议明确状态来源",
|
|
72
|
+
R037: "空/异常反馈建议统一操作入口",
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/* ── helpers ─────────────────────────────────────────────────────────── */
|
|
76
|
+
|
|
77
|
+
function buildSummary(issues, fileCount) {
|
|
78
|
+
const byRule = {};
|
|
79
|
+
const bySeverity = { error: 0, warning: 0, info: 0 };
|
|
80
|
+
const byCategory = {};
|
|
81
|
+
for (const i of issues) {
|
|
82
|
+
byRule[i.rule] = (byRule[i.rule] || 0) + 1;
|
|
83
|
+
bySeverity[i.severity] = (bySeverity[i.severity] || 0) + 1;
|
|
84
|
+
const cat = i.category || "other";
|
|
85
|
+
byCategory[cat] = (byCategory[cat] || 0) + 1;
|
|
86
|
+
}
|
|
87
|
+
return { fileCount, total: issues.length, bySeverity, byRule, byCategory };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 从文件路径提取目录(第一级 + 第二级) */
|
|
91
|
+
function dirOf(filePath) {
|
|
92
|
+
const parts = filePath.replace(/\\/g, "/").split("/");
|
|
93
|
+
if (parts.length <= 1) return ".";
|
|
94
|
+
return parts.slice(0, -1).join("/");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 提取模块目录(取 views/xxx/yyy 级别) */
|
|
98
|
+
function moduleOf(filePath) {
|
|
99
|
+
const parts = filePath.replace(/\\/g, "/").split("/");
|
|
100
|
+
// 找到 views 之后取两级
|
|
101
|
+
const vi = parts.indexOf("views");
|
|
102
|
+
if (vi >= 0 && parts.length > vi + 2)
|
|
103
|
+
return parts.slice(vi, vi + 3).join("/");
|
|
104
|
+
if (vi >= 0 && parts.length > vi + 1)
|
|
105
|
+
return parts.slice(vi, vi + 2).join("/");
|
|
106
|
+
// components
|
|
107
|
+
const ci = parts.indexOf("components");
|
|
108
|
+
if (ci >= 0 && parts.length > ci + 1)
|
|
109
|
+
return parts.slice(ci, ci + 2).join("/");
|
|
110
|
+
return parts.slice(0, 2).join("/");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* ── section builders ────────────────────────────────────────────────── */
|
|
114
|
+
|
|
115
|
+
function buildIntegrationSection(integration) {
|
|
116
|
+
if (!integration || integration.length === 0) return [];
|
|
117
|
+
const lines = ["## 一、接入完整性仪表盘", ""];
|
|
118
|
+
const okCount = integration.filter((c) => c.ok).length;
|
|
119
|
+
lines.push(`**通过:${okCount} / ${integration.length}**`);
|
|
120
|
+
lines.push("");
|
|
121
|
+
lines.push("| 编号 | 检查项 | 结果 | 说明 |");
|
|
122
|
+
lines.push("|------|--------|------|------|");
|
|
123
|
+
for (const c of integration) {
|
|
124
|
+
const icon = c.ok
|
|
125
|
+
? "✅ 通过"
|
|
126
|
+
: c.severity === "error"
|
|
127
|
+
? "❌ 未通过"
|
|
128
|
+
: "⚠️ 警告";
|
|
129
|
+
const note = c.ok ? "—" : c.suggestion || "";
|
|
130
|
+
lines.push(`| ${c.id} | ${c.description} | ${icon} | ${note} |`);
|
|
131
|
+
}
|
|
132
|
+
lines.push("");
|
|
133
|
+
return lines;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function buildOverviewSection(issues, fileCount, extras) {
|
|
137
|
+
const summary = buildSummary(issues, fileCount);
|
|
138
|
+
const scannedFiles = fileCount - (extras.exemptFileCount || 0);
|
|
139
|
+
const affectedFiles = new Set(issues.map((i) => i.file));
|
|
140
|
+
const affectedDirs = new Set(issues.map((i) => dirOf(i.file)));
|
|
141
|
+
const cleanFiles = scannedFiles - affectedFiles.size;
|
|
142
|
+
const coverageRate =
|
|
143
|
+
scannedFiles > 0 ? ((cleanFiles / scannedFiles) * 100).toFixed(1) : "100.0";
|
|
144
|
+
|
|
145
|
+
const lines = ["## 二、扫描总览", ""];
|
|
146
|
+
lines.push("| 维度 | 数量 |");
|
|
147
|
+
lines.push("|------|------|");
|
|
148
|
+
lines.push(`| 扫描 .vue 文件总数 | **${fileCount}** |`);
|
|
149
|
+
lines.push(
|
|
150
|
+
`| 实际检查文件数 | **${scannedFiles}**(豁免 ${extras.exemptFileCount || 0}) |`,
|
|
151
|
+
);
|
|
152
|
+
lines.push(`| 涉及问题的文件数 | **${affectedFiles.size}** |`);
|
|
153
|
+
lines.push(`| 涉及问题的目录数 | **${affectedDirs.size}** |`);
|
|
154
|
+
lines.push(
|
|
155
|
+
`| 发现问题总数 | **${summary.total}**(🔴 ${summary.bySeverity.error} / 🟡 ${summary.bySeverity.warning} / 🔵 ${summary.bySeverity.info}) |`,
|
|
156
|
+
);
|
|
157
|
+
lines.push(
|
|
158
|
+
`| 规范覆盖率(无问题文件占比) | **${coverageRate}%**(${cleanFiles} / ${scannedFiles}) |`,
|
|
159
|
+
);
|
|
160
|
+
lines.push(`| 分类数 | **${Object.keys(summary.byCategory).length}** |`);
|
|
161
|
+
lines.push("");
|
|
162
|
+
return { lines, summary };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function buildExemptSection(extras) {
|
|
166
|
+
const lines = [];
|
|
167
|
+
const exemptFileCount = extras.exemptFileCount || 0;
|
|
168
|
+
const exemptedIssueCount = extras.exemptedIssueCount || 0;
|
|
169
|
+
const exemptPaths = extras.exemptPaths || [];
|
|
170
|
+
if (
|
|
171
|
+
exemptFileCount === 0 &&
|
|
172
|
+
exemptedIssueCount === 0 &&
|
|
173
|
+
exemptPaths.length === 0
|
|
174
|
+
)
|
|
175
|
+
return lines;
|
|
176
|
+
|
|
177
|
+
lines.push("## 三、豁免统计");
|
|
178
|
+
lines.push("");
|
|
179
|
+
lines.push("| 维度 | 数量 |");
|
|
180
|
+
lines.push("|------|------|");
|
|
181
|
+
lines.push(`| 豁免文件数 | **${exemptFileCount}** |`);
|
|
182
|
+
lines.push(`| 豁免问题数 | **${exemptedIssueCount}** |`);
|
|
183
|
+
lines.push(`| 豁免路径规则数 | **${exemptPaths.length}** |`);
|
|
184
|
+
lines.push("");
|
|
185
|
+
if (exemptPaths.length > 0) {
|
|
186
|
+
lines.push("豁免路径:");
|
|
187
|
+
lines.push("");
|
|
188
|
+
for (const p of exemptPaths) lines.push(`- \`${p}\``);
|
|
189
|
+
lines.push("");
|
|
190
|
+
}
|
|
191
|
+
return lines;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function buildCategorySection(summary) {
|
|
195
|
+
const lines = ["## 四、按分类统计", ""];
|
|
196
|
+
lines.push("| 分类 | 说明 | 问题数 |");
|
|
197
|
+
lines.push("|------|------|--------|");
|
|
198
|
+
const sorted = Object.entries(summary.byCategory).sort((a, b) => b[1] - a[1]);
|
|
199
|
+
for (const [cat, count] of sorted) {
|
|
200
|
+
const label = CATEGORY_LABEL[cat] || cat;
|
|
201
|
+
lines.push(`| ${cat} | ${label} | **${count}** |`);
|
|
202
|
+
}
|
|
203
|
+
lines.push("");
|
|
204
|
+
return lines;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function buildRuleSection(summary) {
|
|
208
|
+
if (Object.keys(summary.byRule).length === 0) return [];
|
|
209
|
+
const lines = ["## 五、按规则统计", ""];
|
|
210
|
+
lines.push("| 规则 | 名称 | 严重度 | 数量 |");
|
|
211
|
+
lines.push("|------|------|--------|------|");
|
|
212
|
+
const sortedRules = Object.entries(summary.byRule).sort(
|
|
213
|
+
(a, b) => b[1] - a[1],
|
|
214
|
+
);
|
|
215
|
+
for (const [rule, count] of sortedRules) {
|
|
216
|
+
const name = RULE_NAME[rule] || "";
|
|
217
|
+
const sev =
|
|
218
|
+
rule.startsWith("R01") && parseInt(rule.slice(1)) <= 15
|
|
219
|
+
? "🔴/🟡"
|
|
220
|
+
: "🟡/🔵";
|
|
221
|
+
lines.push(`| ${rule} | ${name} | ${sev} | **${count}** |`);
|
|
222
|
+
}
|
|
223
|
+
lines.push("");
|
|
224
|
+
return lines;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function buildDirFileSection(issues) {
|
|
228
|
+
const lines = ["## 六、按目录 & 文件明细", ""];
|
|
229
|
+
|
|
230
|
+
// 按模块分组
|
|
231
|
+
const byModule = {};
|
|
232
|
+
for (const i of issues) {
|
|
233
|
+
const mod = moduleOf(i.file);
|
|
234
|
+
if (!byModule[mod]) byModule[mod] = {};
|
|
235
|
+
if (!byModule[mod][i.file]) byModule[mod][i.file] = [];
|
|
236
|
+
byModule[mod][i.file].push(i);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const sortedModules = Object.keys(byModule).sort();
|
|
240
|
+
for (const mod of sortedModules) {
|
|
241
|
+
const files = byModule[mod];
|
|
242
|
+
const fileCount = Object.keys(files).length;
|
|
243
|
+
const issueCount = Object.values(files).reduce(
|
|
244
|
+
(s, arr) => s + arr.length,
|
|
245
|
+
0,
|
|
246
|
+
);
|
|
247
|
+
lines.push(`### 📁 ${mod}(${fileCount} 个文件,${issueCount} 个问题)`);
|
|
248
|
+
lines.push("");
|
|
249
|
+
lines.push("| 文件 | 问题数 | 高危 | 中危 | 低危 | 主要规则 |");
|
|
250
|
+
lines.push("|------|--------|------|------|------|----------|");
|
|
251
|
+
|
|
252
|
+
for (const [file, fileIssues] of Object.entries(files).sort()) {
|
|
253
|
+
const shortFile = file.split("/").pop();
|
|
254
|
+
const sevCounts = { error: 0, warning: 0, info: 0 };
|
|
255
|
+
const ruleSet = new Set();
|
|
256
|
+
for (const fi of fileIssues) {
|
|
257
|
+
sevCounts[fi.severity]++;
|
|
258
|
+
ruleSet.add(fi.rule);
|
|
259
|
+
}
|
|
260
|
+
const topRules = [...ruleSet].slice(0, 3).join(", ");
|
|
261
|
+
lines.push(
|
|
262
|
+
`| \`${shortFile}\` | ${fileIssues.length} | ${sevCounts.error} | ${sevCounts.warning} | ${sevCounts.info} | ${topRules} |`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
lines.push("");
|
|
266
|
+
}
|
|
267
|
+
return lines;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function buildDetailSection(issues) {
|
|
271
|
+
if (issues.length === 0) {
|
|
272
|
+
return [
|
|
273
|
+
"## 七、问题明细",
|
|
274
|
+
"",
|
|
275
|
+
"✅ **所有风格检查项均已通过,无需修复。**",
|
|
276
|
+
"",
|
|
277
|
+
];
|
|
278
|
+
}
|
|
279
|
+
const lines = ["## 七、问题明细", ""];
|
|
280
|
+
for (const sev of ["error", "warning", "info"]) {
|
|
281
|
+
const group = issues.filter((i) => i.severity === sev);
|
|
282
|
+
if (group.length === 0) continue;
|
|
283
|
+
lines.push(`### ${SEVERITY_LABEL[sev]}(${group.length} 项)`);
|
|
284
|
+
lines.push("");
|
|
285
|
+
lines.push("| 规则 | 分类 | 文件 | 行号 | 问题描述 | 修复建议 |");
|
|
286
|
+
lines.push("|------|------|------|------|----------|----------|");
|
|
287
|
+
for (const i of group) {
|
|
288
|
+
const shortFile = i.file.length > 60 ? "..." + i.file.slice(-55) : i.file;
|
|
289
|
+
lines.push(
|
|
290
|
+
`| ${i.rule} | ${i.category || "-"} | \`${shortFile}\` | ${i.line} | ${i.description} | ${i.suggestion} |`,
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
lines.push("");
|
|
294
|
+
}
|
|
295
|
+
return lines;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function buildCoverageSection(extras) {
|
|
299
|
+
const coverage = extras.coverage;
|
|
300
|
+
const recommendations = extras.recommendations;
|
|
301
|
+
if (!coverage && !recommendations) return [];
|
|
302
|
+
const lines = ["## 八、组件覆盖与 AI 下一步建议", ""];
|
|
303
|
+
|
|
304
|
+
if (coverage) {
|
|
305
|
+
lines.push("### 组件覆盖");
|
|
306
|
+
lines.push("");
|
|
307
|
+
lines.push("| 类型 | 命中 |");
|
|
308
|
+
lines.push("|------|------|");
|
|
309
|
+
lines.push(
|
|
310
|
+
`| Element Plus | ${(coverage.element || []).map((v) => `\`${v}\``).join(", ") || "—"} |`,
|
|
311
|
+
);
|
|
312
|
+
lines.push(
|
|
313
|
+
`| Vendor 封装 | ${(coverage.vendors || []).map((v) => `\`${v}\``).join(", ") || "—"} |`,
|
|
314
|
+
);
|
|
315
|
+
lines.push(
|
|
316
|
+
`| Layout 场景 | ${(coverage.layouts || []).map((v) => `\`${v}\``).join(", ") || "—"} |`,
|
|
317
|
+
);
|
|
318
|
+
lines.push(
|
|
319
|
+
`| B 端业务场景 | ${(coverage.businessScenarios || []).map((v) => `\`${v}\``).join(", ") || "—"} |`,
|
|
320
|
+
);
|
|
321
|
+
lines.push(
|
|
322
|
+
`| 推荐 Skills | ${(coverage.recommendedSkills || []).map((v) => `\`${v}\``).join(", ") || "—"} |`,
|
|
323
|
+
);
|
|
324
|
+
lines.push("");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (recommendations) {
|
|
328
|
+
lines.push("### 推荐流程");
|
|
329
|
+
lines.push("");
|
|
330
|
+
for (const flow of recommendations.recommendedFlows || []) {
|
|
331
|
+
lines.push(`- \`${flow}\``);
|
|
332
|
+
}
|
|
333
|
+
lines.push("");
|
|
334
|
+
lines.push("### 下一步");
|
|
335
|
+
lines.push("");
|
|
336
|
+
for (const action of recommendations.nextActions || []) {
|
|
337
|
+
lines.push(`- ${action}`);
|
|
338
|
+
}
|
|
339
|
+
lines.push("");
|
|
340
|
+
if (recommendations.kitBridge) {
|
|
341
|
+
lines.push("### wl-skills-kit 桥接判断");
|
|
342
|
+
lines.push("");
|
|
343
|
+
lines.push(
|
|
344
|
+
`- 是否建议桥接:**${recommendations.kitBridge.needed ? "是" : "否"}**`,
|
|
345
|
+
);
|
|
346
|
+
lines.push(`- 原因:${recommendations.kitBridge.reason}`);
|
|
347
|
+
if (recommendations.kitBridge.commands?.length) {
|
|
348
|
+
lines.push("- 建议命令:");
|
|
349
|
+
for (const command of recommendations.kitBridge.commands) {
|
|
350
|
+
lines.push(` - \`${command}\``);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
lines.push("");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return lines;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function buildFooter() {
|
|
361
|
+
const lines = [];
|
|
362
|
+
lines.push("## 九、快照与回滚说明");
|
|
363
|
+
lines.push("");
|
|
364
|
+
lines.push("`.wk-snapshot/` 是 `wk-skills-ui` 自动生成的修复前快照目录。");
|
|
365
|
+
lines.push("每次执行 `wk-ui fix` 前会自动备份即将修改的文件,支持一键回退。");
|
|
366
|
+
lines.push("");
|
|
367
|
+
lines.push("```bash");
|
|
368
|
+
lines.push("wk-ui snapshot list # 列出所有快照");
|
|
369
|
+
lines.push("wk-ui snapshot diff --id <id> # 查看某次快照与当前差异");
|
|
370
|
+
lines.push("wk-ui snapshot rollback --id <id> # 回滚到指定快照");
|
|
371
|
+
lines.push("wk-ui snapshot clean --keep 3 # 仅保留最近 3 个快照");
|
|
372
|
+
lines.push("```");
|
|
373
|
+
lines.push("");
|
|
374
|
+
lines.push("---");
|
|
375
|
+
lines.push("");
|
|
376
|
+
lines.push("> 由 `wk-skills-ui` scanner 自动生成。修复前请经用户确认。");
|
|
377
|
+
lines.push("> 回退命令:`npx wk-scan snapshot rollback`");
|
|
378
|
+
return lines;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/* ── 主入口 ──────────────────────────────────────────────────────────── */
|
|
382
|
+
|
|
383
|
+
export function generateReport(
|
|
384
|
+
issues,
|
|
385
|
+
fileCount,
|
|
386
|
+
format = "markdown",
|
|
387
|
+
extras = {},
|
|
388
|
+
) {
|
|
389
|
+
if (format === "json") {
|
|
390
|
+
return JSON.stringify(
|
|
391
|
+
{
|
|
392
|
+
summary: buildSummary(issues, fileCount),
|
|
393
|
+
integration: extras.integration || null,
|
|
394
|
+
componentCoverage: extras.coverage || null,
|
|
395
|
+
recommendedSkills: extras.coverage?.recommendedSkills || [],
|
|
396
|
+
recommendations: extras.recommendations || null,
|
|
397
|
+
issues,
|
|
398
|
+
},
|
|
399
|
+
null,
|
|
400
|
+
2,
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
return buildMarkdown(issues, fileCount, extras);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function buildMarkdown(issues, fileCount, extras = {}) {
|
|
407
|
+
const lines = [];
|
|
408
|
+
const ts = new Date().toISOString().slice(0, 10);
|
|
409
|
+
|
|
410
|
+
lines.push("# UI 风格对齐扫描报告");
|
|
411
|
+
lines.push("");
|
|
412
|
+
lines.push(`> 生成时间:${ts}`);
|
|
413
|
+
lines.push("> 工具版本:@agile-team/wk-skills-ui");
|
|
414
|
+
lines.push("");
|
|
415
|
+
lines.push("---");
|
|
416
|
+
lines.push("");
|
|
417
|
+
|
|
418
|
+
const hasInt = !!(extras.integration && extras.integration.length);
|
|
419
|
+
const hasExempt = !!(
|
|
420
|
+
extras.exemptFileCount ||
|
|
421
|
+
extras.exemptedIssueCount ||
|
|
422
|
+
(extras.exemptPaths && extras.exemptPaths.length)
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
// 一、接入完整性
|
|
426
|
+
if (hasInt) lines.push(...buildIntegrationSection(extras.integration));
|
|
427
|
+
|
|
428
|
+
// 二、扫描总览
|
|
429
|
+
const { lines: overviewLines, summary } = buildOverviewSection(
|
|
430
|
+
issues,
|
|
431
|
+
fileCount,
|
|
432
|
+
extras,
|
|
433
|
+
);
|
|
434
|
+
lines.push(...overviewLines);
|
|
435
|
+
|
|
436
|
+
// 三、豁免统计
|
|
437
|
+
if (hasExempt) lines.push(...buildExemptSection(extras));
|
|
438
|
+
|
|
439
|
+
// 四、按分类统计
|
|
440
|
+
if (Object.keys(summary.byCategory).length > 0)
|
|
441
|
+
lines.push(...buildCategorySection(summary));
|
|
442
|
+
|
|
443
|
+
// 五、按规则统计
|
|
444
|
+
lines.push(...buildRuleSection(summary));
|
|
445
|
+
|
|
446
|
+
// 六、按目录 & 文件明细
|
|
447
|
+
if (issues.length > 0) lines.push(...buildDirFileSection(issues));
|
|
448
|
+
|
|
449
|
+
// 七、问题明细
|
|
450
|
+
lines.push(...buildDetailSection(issues));
|
|
451
|
+
|
|
452
|
+
// 八、组件覆盖与推荐
|
|
453
|
+
lines.push(...buildCoverageSection(extras));
|
|
454
|
+
|
|
455
|
+
// 九、快照说明 & 页脚
|
|
456
|
+
lines.push(...buildFooter());
|
|
457
|
+
|
|
458
|
+
return lines.join("\n");
|
|
459
|
+
}
|