@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.
Files changed (150) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/README.md +623 -0
  3. package/SKILL.md +632 -0
  4. package/bin/wl-ui.js +794 -0
  5. package/design/spec/color.md +41 -0
  6. package/design/spec/spacing.md +28 -0
  7. package/design/spec/typography.md +29 -0
  8. package/design/tokens/base.css +144 -0
  9. package/design/tokens/index.css +8 -0
  10. package/dist/ag-grid-override.scss +1 -0
  11. package/dist/element.scss +1 -0
  12. package/dist/index.scss +1 -0
  13. package/dist/portal.scss +1 -0
  14. package/dist/style-override.scss +1 -0
  15. package/dist/tokens.css +144 -0
  16. package/es/chunk-GBB7LDKQ.js +261 -0
  17. package/es/chunk-MDI2SCUP.js +281 -0
  18. package/es/chunk-QDTNMM7W.js +286 -0
  19. package/es/chunk-YLNAFAWE.js +233 -0
  20. package/es/common-preset.d.ts +38 -0
  21. package/es/common-preset.js +157 -0
  22. package/es/index.d.ts +32 -0
  23. package/es/index.js +1 -0
  24. package/es/presets/security.d.ts +16 -0
  25. package/es/presets/security.js +99 -0
  26. package/es/renderers-BFRSp2BH.d.ts +51 -0
  27. package/es/renderers-DMZbi_Gs.d.ts +51 -0
  28. package/es/renderers-DWhUlI2y.d.ts +47 -0
  29. package/es/types-04qLKHMt.d.ts +62 -0
  30. package/es/types-BrCPjAH6.d.ts +62 -0
  31. package/es/types-QZ1gy9KX.d.ts +63 -0
  32. package/examples/migration-operations-to-renderOps.md +107 -0
  33. package/examples/wk-exempt.example.json +30 -0
  34. package/mcp/server.js +394 -0
  35. package/package.json +130 -0
  36. package/reference/SelectPopupCom.vue +634 -0
  37. package/reference/ag-cell-renders.ts +644 -0
  38. package/reference/define-columns.ts +185 -0
  39. package/reference/jhlc.d.ts +1 -0
  40. package/scanner/coverage.mjs +287 -0
  41. package/scanner/exempt.mjs +127 -0
  42. package/scanner/fix.mjs +216 -0
  43. package/scanner/index.mjs +403 -0
  44. package/scanner/integration.mjs +155 -0
  45. package/scanner/report.mjs +459 -0
  46. package/scanner/rules/_shared.mjs +134 -0
  47. package/scanner/rules/button.mjs +97 -0
  48. package/scanner/rules/color.mjs +118 -0
  49. package/scanner/rules/componentFamily.mjs +184 -0
  50. package/scanner/rules/dialog.mjs +42 -0
  51. package/scanner/rules/form.mjs +86 -0
  52. package/scanner/rules/index.mjs +58 -0
  53. package/scanner/rules/table.mjs +170 -0
  54. package/scanner/rules/tag.mjs +245 -0
  55. package/scanner/snapshot.mjs +198 -0
  56. package/skills/_flows/full-audit.md +65 -0
  57. package/skills/_flows/legacy-skin-align.md +71 -0
  58. package/skills/_flows/new-project-init.md +80 -0
  59. package/skills/_flows/progressive-migrate.md +49 -0
  60. package/skills/_meta/_compat/README.md +47 -0
  61. package/skills/_meta/_compat/editors.json +76 -0
  62. package/skills/_meta/_compat/headers/agents.txt +7 -0
  63. package/skills/_meta/_compat/headers/claude-code.txt +7 -0
  64. package/skills/_meta/_compat/headers/cline.txt +7 -0
  65. package/skills/_meta/_compat/headers/cursor-mdc.txt +5 -0
  66. package/skills/_meta/_compat/headers/github-copilot.txt +5 -0
  67. package/skills/_meta/_compat/headers/kiro.txt +4 -0
  68. package/skills/_meta/_compat/headers/qoder.txt +4 -0
  69. package/skills/_meta/_compat/headers/trae.txt +3 -0
  70. package/skills/_meta/_compat/headers/windsurf.txt +5 -0
  71. package/skills/_meta/_detection.md +81 -0
  72. package/skills/_meta/_registry.md +96 -0
  73. package/skills/element/component-family/SKILL.md +71 -0
  74. package/skills/element/el-dialog/SKILL.md +124 -0
  75. package/skills/element/el-form/SKILL.md +84 -0
  76. package/skills/element/el-table/SKILL.md +86 -0
  77. package/skills/element/el-tag/SKILL.md +127 -0
  78. package/skills/layouts/detail-page/SKILL.md +99 -0
  79. package/skills/layouts/form-dialog/SKILL.md +128 -0
  80. package/skills/layouts/list-page/SKILL.md +111 -0
  81. package/skills/layouts/tree-list/SKILL.md +108 -0
  82. package/skills/ops/audit/SKILL.md +109 -0
  83. package/skills/ops/fix/SKILL.md +52 -0
  84. package/skills/ops/fix/USAGE.md +24 -0
  85. package/skills/ops/migrate/SKILL.md +157 -0
  86. package/skills/ops/scan/SKILL.md +71 -0
  87. package/skills/ops/scan/USAGE.md +16 -0
  88. package/skills/runtime/design-tokens/SKILL.md +88 -0
  89. package/skills/runtime/design-tokens/USAGE.md +19 -0
  90. package/skills/runtime/migration/SKILL.md +106 -0
  91. package/skills/runtime/migration/USAGE.md +34 -0
  92. package/skills/runtime/style-align/SKILL.md +124 -0
  93. package/skills/runtime/style-align/USAGE.md +52 -0
  94. package/skills/vendors/ag-grid/SKILL.md +94 -0
  95. package/skills/vendors/base-table/SKILL.md +57 -0
  96. package/skills/vendors/c-components/SKILL.md +36 -0
  97. package/skills/vendors/custom-wrappers/SKILL.md +45 -0
  98. package/skills/vendors/jh-components/SKILL.md +42 -0
  99. package/skills/vendors/unknown-wrapper/SKILL.md +44 -0
  100. package/standards/engineering/01-import-order.md +46 -0
  101. package/standards/engineering/02-naming.md +54 -0
  102. package/standards/engineering/03-scss-structure.md +59 -0
  103. package/standards/ui/01-table.md +111 -0
  104. package/standards/ui/02-button.md +83 -0
  105. package/standards/ui/03-form.md +118 -0
  106. package/standards/ui/04-tag-status.md +110 -0
  107. package/standards/ui/05-dialog-pagination.md +118 -0
  108. package/styles/element/_card.scss +26 -0
  109. package/styles/element/_descriptions.scss +22 -0
  110. package/styles/element/_dialog.scss +43 -0
  111. package/styles/element/_drawer.scss +22 -0
  112. package/styles/element/_feedback.scss +52 -0
  113. package/styles/element/_form.scss +22 -0
  114. package/styles/element/_navigation.scss +36 -0
  115. package/styles/element/_overlay.scss +32 -0
  116. package/styles/element/_pagination.scss +16 -0
  117. package/styles/element/_steps.scss +32 -0
  118. package/styles/element/_table.scss +13 -0
  119. package/styles/element/_tabs.scss +40 -0
  120. package/styles/element/_tree.scss +27 -0
  121. package/styles/element/_upload.scss +34 -0
  122. package/styles/element/index.scss +17 -0
  123. package/styles/index.scss +21 -0
  124. package/styles/layouts/_detail-page.scss +27 -0
  125. package/styles/layouts/_form-dialog.scss +19 -0
  126. package/styles/layouts/_list-page.scss +47 -0
  127. package/styles/layouts/_tree-list.scss +33 -0
  128. package/styles/layouts/index.scss +6 -0
  129. package/styles/presets/element-only.scss +6 -0
  130. package/styles/presets/full.scss +12 -0
  131. package/styles/presets/security.scss +37 -0
  132. package/styles/presets/skin.scss +14 -0
  133. package/styles/presets/tokens-only.scss +7 -0
  134. package/styles/tokens/index.scss +79 -0
  135. package/styles/vendors/_ag-grid.scss +273 -0
  136. package/styles/vendors/_base-components.scss +480 -0
  137. package/styles/vendors/_base-query-toolbar.scss +253 -0
  138. package/styles/vendors/_base-table.scss +41 -0
  139. package/styles/vendors/_c-components.scss +34 -0
  140. package/styles/vendors/_custom-wrappers.scss +30 -0
  141. package/styles/vendors/_jh-drag-col.scss +63 -0
  142. package/styles/vendors/_jh-pagination.scss +81 -0
  143. package/styles/vendors/_jh-tree.scss +196 -0
  144. package/styles/vendors/_portal.scss +114 -0
  145. package/styles/vendors/index.scss +17 -0
  146. package/templates/ag-grid-page/TPL-AG-GRID.md +76 -0
  147. package/templates/form-dialog/TPL-FORM-DIALOG.md +165 -0
  148. package/templates/list-page/TPL-LIST.md +139 -0
  149. package/templates/tree-list/TPL-TREE-LIST.md +176 -0
  150. package/templates/ui-optimization-report/TPL-UI-OPTIM-REPORT.md +343 -0
@@ -0,0 +1,185 @@
1
+ /**
2
+ * defineColumns — 全局列自动渲染器
3
+ *
4
+ * 原理:维护一张「字段名 → 渲染配置」的全局映射表(COLUMN_AUTO_MAP)。
5
+ * 调用 defineColumns(colList) 时,对每一列检查 name 是否命中映射:
6
+ * - 命中 → 自动补充 width / fixed / defaultNode / defaultSlot
7
+ * - 未命中 → 原样返回
8
+ * - 用户在列定义里手写了对应属性 → 手写优先(不会被覆盖)
9
+ *
10
+ * 使用方式(替换原来的 return [...]):
11
+ * import { defineColumns } from '@/util/define-columns';
12
+ *
13
+ * columnsDef(): TableColumnDesc<any>[] {
14
+ * return defineColumns([
15
+ * { label: '启用状态', name: 'enableStatus' }, // 自动:width=80 fixed=right 彩色Tag
16
+ * { label: '风险分级', name: 'riskLevel' }, // 自动:width=90 fixed=right 彩色Tag
17
+ * { label: '风险编号', name: 'riskNo' }, // 自动:蓝色圆角徽标
18
+ * { label: '评价级别', name: 'ratingLevel' }, // 自动:彩色圆形徽标
19
+ * { label: '审批状态', name: 'approvalStatus' }, // 自动:彩色Tag
20
+ * ]);
21
+ * }
22
+ *
23
+ * 如需某列不走自动逻辑,手写 defaultNode/defaultSlot 即可覆盖自动配置:
24
+ * { label: '风险分级', name: 'riskLevel', defaultNode: ({ row }: { row: any }) => myCustomRender(row) }
25
+ *
26
+ * 新增全局字段映射 → 只改本文件的 COLUMN_AUTO_MAP,全项目所有页面立即生效。
27
+ */
28
+ import type { TableColumnDesc } from "@jhlc/common-core/src/components/table/base-table/type";
29
+ import {
30
+ renderEnableStatus,
31
+ renderAuditStatus,
32
+ renderVerifyStatus,
33
+ renderRiskLevel,
34
+ renderBadge,
35
+ renderRatingLevel,
36
+ renderTaskStatus,
37
+ renderPlanStatus,
38
+ renderFlagNormal,
39
+ renderRiskStatus,
40
+ renderCorrectStatus,
41
+ renderPermitStatus,
42
+ renderTrainStatus,
43
+ renderCredentialStatus,
44
+ renderQuestionStatus
45
+ } from "./ag-cell-renders";
46
+
47
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
48
+ // 全局字段渲染映射表
49
+ // key = 字段名(与 columnsDef 中的 name 完全匹配)
50
+ // value = 要自动补充的列配置(不含 label,label 由页面自定义)
51
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
52
+ const COLUMN_AUTO_MAP: Record<string, Partial<TableColumnDesc<any>>> = {
53
+ // ── 启用/停用 ────────────────────────────────────────────────────────────
54
+ enableStatus: {
55
+ width: 80,
56
+ fixed: "right",
57
+ defaultNode: ({ row }: { row: any }) =>
58
+ renderEnableStatus(row.enableStatus),
59
+ },
60
+
61
+ // ── 审批状态 ─────────────────────────────────────────────────────────────
62
+ approvalStatus: {
63
+ width: 90,
64
+ fixed: "right",
65
+ defaultNode: ({ row }: { row: any }) =>
66
+ renderAuditStatus(row.approvalStatus),
67
+ },
68
+
69
+ // ── 核实状态 ─────────────────────────────────────────────────────────────
70
+ verifyStatus: {
71
+ width: 90,
72
+ fixed: "right",
73
+ defaultNode: ({ row }: { row: any }) =>
74
+ renderVerifyStatus(row.verifyStatus),
75
+ },
76
+
77
+ // ── 风险分级(aq_risk_level 字典:1重大/2较大/3一般/4低)──────────────────
78
+ riskLevel: {
79
+ width: 90,
80
+ fixed: "right",
81
+ defaultNode: ({ row }: { row: any }) => renderRiskLevel(row.riskLevel),
82
+ },
83
+
84
+ // ── 风险编号:蓝色圆角徽标 ───────────────────────────────────────────────
85
+ riskNo: {
86
+ defaultSlot: ({ row }: { row: any }) => renderBadge(row.riskNo),
87
+ },
88
+
89
+ // ── 检查表编号:蓝色圆角徽标 ─────────────────────────────────────────────
90
+ checkNo: {
91
+ defaultSlot: ({ row }: { row: any }) => renderBadge(row.checkNo),
92
+ },
93
+
94
+ // ── 评价级别(LEC/MES 等):彩色圆形徽标 ────────────────────────────────
95
+ ratingLevel: {
96
+ width: 80,
97
+ defaultSlot: ({ row }: { row: any }) => renderRatingLevel(row.ratingLevel),
98
+ },
99
+
100
+ // ── 排查/检查任务状态(aq_task_check_status / aq_check_info_status)────────
101
+ taskStatus: {
102
+ width: 90,
103
+ fixed: "right",
104
+ defaultNode: ({ row }: { row: any }) => renderTaskStatus(row.taskStatus),
105
+ },
106
+
107
+ // ── 计划状态(aq_plan_status: 1=启用, 0=停用)────────────────────────────
108
+ planStatus: {
109
+ width: 90,
110
+ fixed: "right",
111
+ defaultNode: ({ row }: { row: any }) => renderPlanStatus(row.planStatus),
112
+ },
113
+
114
+ // ── 排查/检查结果(aq_handle_result: 1=正常, 2=异常)────────────────────
115
+ flagNormal: {
116
+ width: 90,
117
+ fixed: "right",
118
+ defaultNode: ({ row }: { row: any }) => renderFlagNormal(row.flagNormal),
119
+ },
120
+
121
+ // ── 隐患状态(aq_risk_status)────────────────────────────────────────────
122
+ riskStatus: {
123
+ width: 90,
124
+ fixed: "right",
125
+ defaultNode: ({ row }: { row: any }) => renderRiskStatus(row.riskStatus),
126
+ },
127
+
128
+ // ── 整改状态(aq_correct_status)──────────────────────────────────────────
129
+ correctStatus: {
130
+ width: 90,
131
+ fixed: "right",
132
+ defaultNode: ({ row }: { row: any }) =>
133
+ renderCorrectStatus(row.correctStatus),
134
+ },
135
+
136
+ // ── 危险作业票状态(permitStatus: 1=草稿, 2=待审批, 3=作业中, 4=已完工, 5=已撤销)──
137
+ permitStatus: {
138
+ width: 90,
139
+ fixed: "right",
140
+ defaultNode: ({ row }: { row: any }) =>
141
+ renderPermitStatus(row.permitStatus),
142
+ },
143
+
144
+ // ── 培训状态(trainStatus: 1=未完成, 2=已完成)────────────────────────────
145
+ trainStatus: {
146
+ width: 90,
147
+ fixed: "right",
148
+ defaultNode: ({ row }: { row: any }) => renderTrainStatus(row.trainStatus),
149
+ },
150
+
151
+ // ── 证书状态(credentialStatus: 1=有效, 2=即将过期, 3=已过期)──────────────
152
+ credentialStatus: {
153
+ width: 90,
154
+ fixed: "right",
155
+ defaultNode: ({ row }: { row: any }) =>
156
+ renderCredentialStatus(row.credentialStatus),
157
+ },
158
+
159
+ // ── 题库状态(unifyQuestionStatus: 1=草稿, 2=已发布, 3=已停用)──────────────
160
+ unifyQuestionStatus: {
161
+ width: 90,
162
+ fixed: "right",
163
+ defaultNode: ({ row }: { row: any }) =>
164
+ renderQuestionStatus(row.unifyQuestionStatus),
165
+ },
166
+ };
167
+
168
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
169
+ // 主函数
170
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
171
+ export function defineColumns<T = any>(
172
+ columns: TableColumnDesc<T>[]
173
+ ): TableColumnDesc<T>[] {
174
+ return columns.map((col) => {
175
+ // 多选列:统一宽度 55px(页面已手写 width 时不覆盖)
176
+ if ((col as any).type === "selection" && !(col as any).width) {
177
+ return { width: 55, ...(col as any) } as unknown as TableColumnDesc<T>;
178
+ }
179
+ if (!col.name) return col;
180
+ const auto = COLUMN_AUTO_MAP[col.name as string];
181
+ if (!auto) return col;
182
+ // 自动配置 < 手写配置(手写的属性不被覆盖)
183
+ return { ...auto, ...col } as TableColumnDesc<T>;
184
+ });
185
+ }
@@ -0,0 +1 @@
1
+ // Type stubs moved to types/jhlc.d.ts
@@ -0,0 +1,287 @@
1
+ const ELEMENT_SKILL_MAP = {
2
+ "el-table": "element/el-table",
3
+ "el-table-column": "element/el-table",
4
+ "el-form": "element/el-form",
5
+ "el-form-item": "element/el-form",
6
+ "el-input": "element/el-form",
7
+ "el-select": "element/el-form",
8
+ "el-date-picker": "element/el-form",
9
+ "el-button": "element/el-button",
10
+ "el-dialog": "element/el-dialog",
11
+ "el-message-box": "element/el-dialog",
12
+ "el-tag": "element/el-tag",
13
+ "el-pagination": "element/el-pagination",
14
+ "el-card": "element/el-card",
15
+ "el-tabs": "element/el-tabs",
16
+ "el-tab-pane": "element/el-tabs",
17
+ "el-descriptions": "element/el-descriptions",
18
+ "el-descriptions-item": "element/el-descriptions",
19
+ "el-tree": "element/el-tree",
20
+ "el-drawer": "element/el-drawer",
21
+ "el-upload": "element/el-upload",
22
+ "el-steps": "element/el-steps",
23
+ "el-step": "element/el-steps",
24
+ "el-popover": "element/el-overlay",
25
+ "el-tooltip": "element/el-overlay",
26
+ "el-dropdown": "element/el-overlay",
27
+ "el-menu": "element/el-navigation",
28
+ "el-menu-item": "element/el-navigation",
29
+ "el-sub-menu": "element/el-navigation",
30
+ "el-breadcrumb": "element/el-navigation",
31
+ "el-empty": "element/el-feedback",
32
+ "el-result": "element/el-feedback",
33
+ "el-alert": "element/el-feedback",
34
+ "el-badge": "element/el-feedback",
35
+ "el-avatar": "element/el-feedback",
36
+ "el-timeline": "element/el-feedback",
37
+ "el-collapse": "element/el-feedback",
38
+ };
39
+
40
+ const VENDOR_PATTERNS = [
41
+ {
42
+ pattern: /<\/?Base[A-Z][\w-]*/g,
43
+ vendor: "Base*",
44
+ skill: "vendors/base-table",
45
+ },
46
+ {
47
+ pattern: /<\/?base-[\w-]+/g,
48
+ vendor: "base-*",
49
+ skill: "vendors/base-table",
50
+ },
51
+ { pattern: /<\/?jh-[\w-]+/g, vendor: "jh-*", skill: "vendors/jh-components" },
52
+ { pattern: /<\/?C_[\w-]+/g, vendor: "C_*", skill: "vendors/c-components" },
53
+ { pattern: /<\/?c-[\w-]+/g, vendor: "c-*", skill: "vendors/c-components" },
54
+ {
55
+ pattern: /ag-grid|agGrid|\.ag-root-wrapper/g,
56
+ vendor: "AG Grid",
57
+ skill: "vendors/ag-grid",
58
+ },
59
+ ];
60
+
61
+ const LAYOUT_PATTERNS = [
62
+ {
63
+ pattern: /class=["'][^"']*list-page[^"']*["']/g,
64
+ layout: "list-page",
65
+ skill: "layouts/list-page",
66
+ },
67
+ {
68
+ pattern: /class=["'][^"']*(tree-list|drag-col-container)[^"']*["']/g,
69
+ layout: "tree-list",
70
+ skill: "layouts/tree-list",
71
+ },
72
+ {
73
+ pattern: /<el-dialog[\s\S]*?<el-form\b/g,
74
+ layout: "form-dialog",
75
+ skill: "layouts/form-dialog",
76
+ },
77
+ {
78
+ pattern: /<el-descriptions\b|class=["'][^"']*detail-page[^"']*["']/g,
79
+ layout: "detail-page",
80
+ skill: "layouts/detail-page",
81
+ },
82
+ ];
83
+
84
+ const BUSINESS_SCENARIO_PATTERNS = [
85
+ {
86
+ pattern: /<el-form[\s\S]*?<el-table\b|<BaseQuery\b|<base-query\b/g,
87
+ scenario: "query-table",
88
+ skill: "layouts/list-page",
89
+ },
90
+ {
91
+ pattern:
92
+ /<BaseToolbar\b|<base-toolbar\b|class=["'][^"']*(toolbar|operation-bar|table-tools)[^"']*["']/g,
93
+ scenario: "toolbar-actions",
94
+ skill: "layouts/list-page",
95
+ },
96
+ {
97
+ pattern: /<el-tree[\s\S]*?(<el-table\b|<BaseTable\b|<base-table\b)/g,
98
+ scenario: "tree-table",
99
+ skill: "layouts/tree-list",
100
+ },
101
+ {
102
+ pattern: /<el-dialog[\s\S]*?<el-form\b/g,
103
+ scenario: "dialog-form",
104
+ skill: "layouts/form-dialog",
105
+ },
106
+ {
107
+ pattern: /<el-drawer[\s\S]*?(<el-form\b|<el-descriptions\b)/g,
108
+ scenario: "drawer-detail",
109
+ skill: "layouts/detail-page",
110
+ },
111
+ {
112
+ pattern:
113
+ /<el-descriptions\b[\s\S]*?<el-card\b|<el-card\b[\s\S]*?<el-descriptions\b/g,
114
+ scenario: "detail-card",
115
+ skill: "layouts/detail-page",
116
+ },
117
+ {
118
+ pattern: /<el-tabs[\s\S]*?(<el-table\b|<el-descriptions\b|<el-form\b)/g,
119
+ scenario: "tab-workbench",
120
+ skill: "element/component-family",
121
+ },
122
+ {
123
+ pattern: /<el-upload\b/g,
124
+ scenario: "attachment-upload",
125
+ skill: "element/component-family",
126
+ },
127
+ {
128
+ pattern: /<el-steps\b|<el-timeline\b/g,
129
+ scenario: "process-flow",
130
+ skill: "element/component-family",
131
+ },
132
+ {
133
+ pattern: /<el-empty\b|<el-result\b|<el-alert\b/g,
134
+ scenario: "feedback-state",
135
+ skill: "element/component-family",
136
+ },
137
+ ];
138
+
139
+ function uniqueSorted(values) {
140
+ return [...new Set(values)].sort();
141
+ }
142
+
143
+ function collectElementTags(template) {
144
+ const tags = [];
145
+ const re = /<\/?(el-[a-z0-9-]+)/g;
146
+ let match;
147
+ while ((match = re.exec(template)) !== null) tags.push(match[1]);
148
+ return tags;
149
+ }
150
+
151
+ export function createCoverageCollector() {
152
+ const files = {};
153
+ const element = [];
154
+ const vendors = [];
155
+ const layouts = [];
156
+ const businessScenarios = [];
157
+ const skills = [];
158
+
159
+ function addFile(file, template, content = template) {
160
+ const fileElement = collectElementTags(template);
161
+ const fileVendors = [];
162
+ const fileLayouts = [];
163
+ const fileBusinessScenarios = [];
164
+ const fileSkills = [];
165
+
166
+ for (const tag of fileElement) {
167
+ element.push(tag);
168
+ if (ELEMENT_SKILL_MAP[tag]) {
169
+ skills.push(ELEMENT_SKILL_MAP[tag]);
170
+ fileSkills.push(ELEMENT_SKILL_MAP[tag]);
171
+ }
172
+ }
173
+
174
+ for (const item of VENDOR_PATTERNS) {
175
+ item.pattern.lastIndex = 0;
176
+ if (item.pattern.test(content)) {
177
+ vendors.push(item.vendor);
178
+ skills.push(item.skill);
179
+ fileVendors.push(item.vendor);
180
+ fileSkills.push(item.skill);
181
+ }
182
+ }
183
+
184
+ for (const item of LAYOUT_PATTERNS) {
185
+ item.pattern.lastIndex = 0;
186
+ if (item.pattern.test(template)) {
187
+ layouts.push(item.layout);
188
+ skills.push(item.skill);
189
+ fileLayouts.push(item.layout);
190
+ fileSkills.push(item.skill);
191
+ }
192
+ }
193
+
194
+ for (const item of BUSINESS_SCENARIO_PATTERNS) {
195
+ item.pattern.lastIndex = 0;
196
+ if (item.pattern.test(template)) {
197
+ businessScenarios.push(item.scenario);
198
+ skills.push(item.skill);
199
+ fileBusinessScenarios.push(item.scenario);
200
+ fileSkills.push(item.skill);
201
+ }
202
+ }
203
+
204
+ files[file] = {
205
+ element: uniqueSorted(fileElement),
206
+ vendors: uniqueSorted(fileVendors),
207
+ layouts: uniqueSorted(fileLayouts),
208
+ businessScenarios: uniqueSorted(fileBusinessScenarios),
209
+ recommendedSkills: uniqueSorted(fileSkills),
210
+ };
211
+ }
212
+
213
+ function result() {
214
+ return {
215
+ element: uniqueSorted(element),
216
+ vendors: uniqueSorted(vendors),
217
+ layouts: uniqueSorted(layouts),
218
+ businessScenarios: uniqueSorted(businessScenarios),
219
+ recommendedSkills: uniqueSorted(skills),
220
+ files,
221
+ };
222
+ }
223
+
224
+ return { addFile, result };
225
+ }
226
+
227
+ export function recommendFlows({ issues = [], coverage = {} } = {}) {
228
+ const categories = new Set(issues.map((i) => i.category));
229
+ const rules = new Set(issues.map((i) => i.rule));
230
+ const flows = [];
231
+ const nextActions = [];
232
+
233
+ if (issues.length === 0) {
234
+ flows.push("full-audit");
235
+ nextActions.push(
236
+ "保持 wk-skills-ui 样式入口,定期运行 wks_ui_scan 做只读审计",
237
+ );
238
+ } else {
239
+ flows.push("legacy-skin-align");
240
+ nextActions.push("先运行 wks_ui_scan --mode skin 确认 L0/L1/L2 样式偏差");
241
+ nextActions.push(
242
+ "修复前运行 wks_ui_fix_dry_run 预览改动,不直接写入业务文件",
243
+ );
244
+ }
245
+
246
+ if (categories.has("color") || categories.has("token")) {
247
+ flows.push("progressive-migrate");
248
+ nextActions.push(
249
+ "将硬编码颜色迁移到 runtime/design-tokens 或 Element Plus token",
250
+ );
251
+ }
252
+
253
+ if (rules.has("R013") || rules.has("R021") || rules.has("R022")) {
254
+ flows.push("progressive-migrate");
255
+ nextActions.push(
256
+ "表格列定义建议升级为 defineColumns + renderOps,并补齐 BaseTable render-type/cid",
257
+ );
258
+ }
259
+
260
+ if (coverage.businessScenarios?.length > 0) {
261
+ nextActions.push(
262
+ `已识别 B 端业务场景:${coverage.businessScenarios.join(", ")},建议按场景加载对应 layout/element Skill 做统一治理`,
263
+ );
264
+ }
265
+
266
+ const shouldUseKit =
267
+ rules.has("R013") ||
268
+ rules.has("R021") ||
269
+ rules.has("R022") ||
270
+ coverage.layouts?.length > 0 ||
271
+ (coverage.businessScenarios || []).some((item) =>
272
+ ["query-table", "tree-table", "dialog-form"].includes(item),
273
+ );
274
+ return {
275
+ recommendedFlows: uniqueSorted(flows),
276
+ nextActions: uniqueSorted(nextActions),
277
+ kitBridge: {
278
+ needed: shouldUseKit,
279
+ reason: shouldUseKit
280
+ ? "扫描结果涉及页面结构、BaseTable 或操作列规范,建议在视觉统一后使用 wl-skills-kit 做规范化生成/修复。"
281
+ : "当前更适合由 wk-skills-ui 先完成样式绝对管控,无需强制切换到 wl-skills-kit。",
282
+ commands: shouldUseKit
283
+ ? ["wl-skills validate-page <page>", "wl-skills doctor-ui <project>"]
284
+ : [],
285
+ },
286
+ };
287
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * scanner/exempt.mjs — 豁免配置
3
+ *
4
+ * 支持两种配置方式:
5
+ * 1. 项目根目录下 .wk-exempt.json
6
+ * 2. CLI --exempt <path> 指定配置文件
7
+ *
8
+ * 配置格式:
9
+ * {
10
+ * "exemptPaths": [
11
+ * "src/views/security/big-screen/**",
12
+ * "src/views/security/map-view/**",
13
+ * "src/views/security/flow-designer/**",
14
+ * "src/views/security/topology/**",
15
+ * "src/views/security/dashboard/**"
16
+ * ],
17
+ * "exemptRules": {
18
+ * "R016": ["src/views/security/big-screen/**"],
19
+ * "R017": ["src/views/security/big-screen/**"]
20
+ * },
21
+ * "exemptCategories": ["dashboard", "big-screen", "map", "chart", "topology", "flow-designer", "3d", "canvas", "report-designer", "rich-editor"],
22
+ * "description": "大屏、地图、图谱、流程设计器等强个性化页面豁免风格统一扫描"
23
+ * }
24
+ */
25
+ import { existsSync, readFileSync } from "node:fs";
26
+ import { join, resolve } from "node:path";
27
+
28
+ /**
29
+ * 简易 glob 匹配(仅支持 ** 和 * 通配符)
30
+ */
31
+ function matchGlob(filePath, pattern) {
32
+ const normalized = filePath.replace(/\\/g, "/");
33
+ const escaped = pattern
34
+ .replace(/\\/g, "/")
35
+ .replace(/[.+^${}()|[\]]/g, "\\$&")
36
+ .replace(/\*\*/g, "{{GLOBSTAR}}")
37
+ .replace(/\*/g, "[^/]*")
38
+ .replace(/\{\{GLOBSTAR\}\}/g, ".*");
39
+ return new RegExp(`^${escaped}$`).test(normalized);
40
+ }
41
+
42
+ /**
43
+ * 加载豁免配置
44
+ * @param {string} projectRoot
45
+ * @param {string} [exemptPath] CLI 指定的配置文件路径
46
+ * @returns {object} { exemptPaths, exemptRules, exemptCategories, isExempt(file, rule?) }
47
+ */
48
+ export function loadExemptConfig(projectRoot, exemptPath) {
49
+ const defaultPath = join(projectRoot, ".wk-exempt.json");
50
+ const configPath = exemptPath ? resolve(exemptPath) : defaultPath;
51
+
52
+ const empty = {
53
+ exemptPaths: [],
54
+ exemptRules: {},
55
+ exemptCategories: [],
56
+ isExempt: () => false,
57
+ };
58
+
59
+ if (!existsSync(configPath)) return empty;
60
+
61
+ let config;
62
+ try {
63
+ config = JSON.parse(readFileSync(configPath, "utf8"));
64
+ } catch {
65
+ console.warn(`[wk-scan] 豁免配置解析失败: ${configPath}`);
66
+ return empty;
67
+ }
68
+
69
+ const exemptPaths = config.exemptPaths || [];
70
+ const exemptRules = config.exemptRules || {};
71
+ const exemptCategories = config.exemptCategories || [];
72
+
73
+ /**
74
+ * 判断文件+规则是否被豁免
75
+ * @param {string} relFile 相对路径
76
+ * @param {string} [ruleId] 规则 ID
77
+ */
78
+ function isExempt(relFile, ruleId) {
79
+ // 全局路径豁免
80
+ if (exemptPaths.some((p) => matchGlob(relFile, p))) return true;
81
+ // 规则级路径豁免
82
+ if (ruleId && exemptRules[ruleId]) {
83
+ if (exemptRules[ruleId].some((p) => matchGlob(relFile, p))) return true;
84
+ }
85
+ return false;
86
+ }
87
+
88
+ return { exemptPaths, exemptRules, exemptCategories, isExempt };
89
+ }
90
+
91
+ /**
92
+ * 生成默认豁免配置模板
93
+ */
94
+ export function generateExemptTemplate() {
95
+ return JSON.stringify(
96
+ {
97
+ exemptPaths: [
98
+ "src/views/**/big-screen/**",
99
+ "src/views/**/dashboard/**",
100
+ "src/views/**/map-view/**",
101
+ "src/views/**/topology/**",
102
+ "src/views/**/flow-designer/**",
103
+ "src/views/**/report-designer/**",
104
+ "src/views/**/chart/**",
105
+ "src/views/**/3d/**",
106
+ "src/views/**/canvas/**",
107
+ ],
108
+ exemptRules: {},
109
+ exemptCategories: [
110
+ "dashboard",
111
+ "big-screen",
112
+ "map",
113
+ "chart",
114
+ "topology",
115
+ "flow-designer",
116
+ "3d",
117
+ "canvas",
118
+ "report-designer",
119
+ "rich-editor",
120
+ ],
121
+ description:
122
+ "大屏、地图、图谱、流程设计器等强个性化页面豁免风格统一扫描",
123
+ },
124
+ null,
125
+ 2,
126
+ );
127
+ }