@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,644 @@
1
+ /**
2
+ * AG Grid / BaseTable 全局 Cell Renderer 工具
3
+ *
4
+ * 用途:统一全项目 defaultSlot / defaultNode 配置,避免页面内重复定义映射表和渲染逻辑
5
+ *
6
+ * 使用示例:
7
+ * import { renderEnableStatus, renderRiskLevel, renderBadge, renderRatingLevel, renderOps } from '@/util/ag-cell-renders';
8
+ *
9
+ * columnsDef(): TableColumnDesc<any>[] {
10
+ * return [
11
+ * { label: '状态', name: 'enableStatus', width: 80, fixed: 'right',
12
+ * defaultNode: ({ row }) => renderEnableStatus(row.enableStatus) },
13
+ * { label: '风险分级', name: 'riskLevel', width: 90, fixed: 'right',
14
+ * defaultNode: ({ row }) => renderRiskLevel(row.riskLevel) },
15
+ * { label: '风险编号', name: 'riskNo',
16
+ * defaultSlot: ({ row }) => renderBadge(row.riskNo) },
17
+ * { label: '评价级别', name: 'ratingLevel', width: 80,
18
+ * defaultSlot: ({ row }) => renderRatingLevel(row.ratingLevel) },
19
+ * // 操作列三层渲染:
20
+ * { label: '操作', width: 110, fixed: 'right',
21
+ * defaultSlot: ({ row }) => renderOps([
22
+ * { type: 'view', onClick: () => modal.open(row.id, 'view') },
23
+ * { type: 'edit', onClick: () => modal.open(row.id, 'edit') },
24
+ * { type: 'del', onClick: () => handleDel(row.id) },
25
+ * { type: 'chip', label: '流程', onClick: () => showLog(row) },
26
+ * { type: 'link', label: '维护排查标准', onClick: () => openStd(row) },
27
+ * ])
28
+ * },
29
+ * ]
30
+ * }
31
+ */
32
+ import { h, VNode, Component } from "vue";
33
+ import { ElTag } from "element-plus";
34
+ import {
35
+ View,
36
+ Edit,
37
+ Delete,
38
+ Document,
39
+ Checked,
40
+ Upload,
41
+ CircleCheck,
42
+ } from "@element-plus/icons-vue";
43
+ import useBusinessLogicDataStore from "@jhlc/common-core/src/store/business-logic-data";
44
+ import { BusLogicKey, BusLogicDataType } from "@jhlc/types/src/logical-data";
45
+
46
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
47
+ // 类型定义
48
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
49
+
50
+ /** defaultNode 返回格式(对应 jh-tag 组件的配置结构)*/
51
+ export interface JhTagNode {
52
+ tag: "jh-tag";
53
+ item: Array<{ title: string; type?: string }>;
54
+ }
55
+
56
+ /** 状态映射表中每条记录的格式 */
57
+ export interface TagMapItem {
58
+ label: string;
59
+ /** Element Plus tag type: '' | 'success' | 'warning' | 'danger' | 'info' */
60
+ type: string;
61
+ }
62
+
63
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
64
+ // 内置状态映射字典
65
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
66
+
67
+ /** 启用/停用状态(enableStatus: 0=停用, 1=启用)*/
68
+ export const ENABLE_STATUS_MAP: Record<string | number, TagMapItem> = {
69
+ 0: { label: "停用", type: "danger" },
70
+ 1: { label: "启用", type: "success" },
71
+ };
72
+
73
+ /** 审批状态(approvalStatus: 0=待审批, 1=已通过, 2=已驳回)*/
74
+ export const AUDIT_STATUS_MAP: Record<string | number, TagMapItem> = {
75
+ 0: { label: "待审批", type: "info" },
76
+ 1: { label: "已通过", type: "success" },
77
+ 2: { label: "已驳回", type: "danger" },
78
+ };
79
+
80
+ /** 核实状态(verifyStatus: 0=未核实, 1=已核实)*/
81
+ export const VERIFY_STATUS_MAP: Record<string | number, TagMapItem> = {
82
+ 0: { label: "未核实", type: "warning" },
83
+ 1: { label: "已核实", type: "success" },
84
+ };
85
+
86
+ /** 风险分级(riskLevel: 1=重大, 2=较大, 3=一般, 4=低风险;对应 aq_risk_level 字典)*/
87
+ export const RISK_LEVEL_MAP: Record<string | number, TagMapItem> = {
88
+ 1: { label: "重大风险", type: "danger" },
89
+ 2: { label: "较大风险", type: "warning" },
90
+ 3: { label: "一般风险", type: "" },
91
+ 4: { label: "低风险", type: "success" },
92
+ };
93
+
94
+ /** 评价级别颜色(LEC/MES 等评价级别:一→五 对应 绿→红)*/
95
+ export const RATING_LEVEL_COLORS: Record<
96
+ string,
97
+ { bg: string; color: string }
98
+ > = {
99
+ 一: {
100
+ bg: "var(--wk-rating-lv1-bg, rgba(16,185,129,0.14))",
101
+ color: "var(--wk-rating-lv1-color, #065F46)",
102
+ },
103
+ 二: {
104
+ bg: "var(--wk-rating-lv2-bg, rgba(59,130,246,0.14))",
105
+ color: "var(--wk-rating-lv2-color, #1E40AF)",
106
+ },
107
+ 三: {
108
+ bg: "var(--wk-rating-lv3-bg, rgba(245,158,11,0.14))",
109
+ color: "var(--wk-rating-lv3-color, #78350F)",
110
+ },
111
+ 四: {
112
+ bg: "var(--wk-rating-lv4-bg, rgba(249,115,22,0.14))",
113
+ color: "var(--wk-rating-lv4-color, #9A3412)",
114
+ },
115
+ 五: {
116
+ bg: "var(--wk-rating-lv5-bg, rgba(239,68,68,0.14))",
117
+ color: "var(--wk-rating-lv5-color, #991B1B)",
118
+ },
119
+ // 阿拉伯数字也支持
120
+ "1": {
121
+ bg: "var(--wk-rating-lv1-bg, rgba(16,185,129,0.14))",
122
+ color: "var(--wk-rating-lv1-color, #065F46)",
123
+ },
124
+ "2": {
125
+ bg: "var(--wk-rating-lv2-bg, rgba(59,130,246,0.14))",
126
+ color: "var(--wk-rating-lv2-color, #1E40AF)",
127
+ },
128
+ "3": {
129
+ bg: "var(--wk-rating-lv3-bg, rgba(245,158,11,0.14))",
130
+ color: "var(--wk-rating-lv3-color, #78350F)",
131
+ },
132
+ "4": {
133
+ bg: "var(--wk-rating-lv4-bg, rgba(249,115,22,0.14))",
134
+ color: "var(--wk-rating-lv4-color, #9A3412)",
135
+ },
136
+ "5": {
137
+ bg: "var(--wk-rating-lv5-bg, rgba(239,68,68,0.14))",
138
+ color: "var(--wk-rating-lv5-color, #991B1B)",
139
+ },
140
+ };
141
+
142
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
143
+ // 通用渲染工具
144
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
145
+
146
+ /**
147
+ * 渲染状态标签(defaultNode 格式,使用 jh-tag 组件)
148
+ * @param value 字段值
149
+ * @param map 状态映射表
150
+ * @returns JhTagNode | null
151
+ */
152
+ export function renderTagNode(
153
+ value: string | number | null | undefined,
154
+ map: Record<string | number, TagMapItem>,
155
+ ): JhTagNode | null {
156
+ if (value === null || value === undefined || value === "") return null;
157
+ const item = map[value];
158
+ if (!item) return null;
159
+ return { tag: "jh-tag", item: [{ title: item.label, type: item.type }] };
160
+ }
161
+
162
+ /**
163
+ * 渲染状态标签(defaultSlot 格式,返回 VNode)
164
+ * 使用 .jh-cell-tag CSS 类,颜色通过 BEM modifier 控制
165
+ * @param value 字段值
166
+ * @param map 状态映射表
167
+ * @returns VNode | null
168
+ */
169
+ export function renderTagSlot(
170
+ value: string | number | null | undefined,
171
+ map: Record<string | number, TagMapItem>,
172
+ ): VNode | null {
173
+ if (value === null || value === undefined || value === "") return null;
174
+ const item = map[value];
175
+ if (!item) return null;
176
+ const typeClass = item.type
177
+ ? `jh-cell-tag--${item.type}`
178
+ : "jh-cell-tag--default";
179
+ return h("span", { class: ["jh-cell-tag", typeClass] }, item.label);
180
+ }
181
+
182
+ /**
183
+ * 渲染分类/层级标签(plain outline 样式,defaultSlot 格式)
184
+ * 适用于组织层级、归档属性等非动态分类字段,视觉权重低于实心状态标签。
185
+ * 列定义写法:defaultSlot: ({ row }) => renderClassifyTag(row.xxx, SOME_MAP)
186
+ */
187
+ export function renderClassifyTag(
188
+ value: string | number | null | undefined,
189
+ map: Record<string | number, TagMapItem>,
190
+ ): VNode | null {
191
+ if (value === null || value === undefined || value === "") return null;
192
+ const item = map[value];
193
+ if (!item) return null;
194
+ return h(
195
+ ElTag,
196
+ { type: (item.type || "") as any, size: "small", effect: "plain" },
197
+ () => item.label,
198
+ );
199
+ }
200
+
201
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
202
+ // 快捷函数(直接传字段值即可)
203
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
204
+
205
+ /**
206
+ * 渲染启用/停用状态(defaultNode 格式)
207
+ * 列定义写法:defaultNode: ({ row }) => renderEnableStatus(row.enableStatus)
208
+ */
209
+ export const renderEnableStatus = (value: string | number | null | undefined) =>
210
+ renderTagNode(value, ENABLE_STATUS_MAP);
211
+
212
+ /**
213
+ * 渲染审批状态(defaultNode 格式)
214
+ * 列定义写法:defaultNode: ({ row }) => renderAuditStatus(row.approvalStatus)
215
+ */
216
+ export const renderAuditStatus = (value: string | number | null | undefined) =>
217
+ renderTagNode(value, AUDIT_STATUS_MAP);
218
+
219
+ /**
220
+ * 渲染核实状态(defaultNode 格式)
221
+ * 列定义写法:defaultNode: ({ row }) => renderVerifyStatus(row.verifyStatus)
222
+ */
223
+ export const renderVerifyStatus = (value: string | number | null | undefined) =>
224
+ renderTagNode(value, VERIFY_STATUS_MAP);
225
+
226
+ /**
227
+ * 渲染风险分级(defaultNode 格式)
228
+ * 列定义写法:defaultNode: ({ row }) => renderRiskLevel(row.riskLevel)
229
+ */
230
+ export const renderRiskLevel = (value: string | number | null | undefined) =>
231
+ renderTagNode(value, RISK_LEVEL_MAP);
232
+
233
+ /** 排查层级(aq_check_level 字典:1=公司级, 2=厂级, 3=车间级, 4=班组级)*/
234
+ export const CHECK_LEVEL_MAP: Record<string | number, TagMapItem> = {
235
+ 1: { label: "公司级", type: "" },
236
+ 2: { label: "厂级", type: "warning" },
237
+ 3: { label: "车间级", type: "info" },
238
+ 4: { label: "班组级", type: "info" },
239
+ };
240
+
241
+ /**
242
+ * 渲染排查/检查层级(plain outline 样式,defaultSlot 格式)
243
+ * 层级是分类/归档属性,用 plain outline 降低视觉权重,与动态状态区分。
244
+ * 列定义写法:defaultSlot: ({ row }) => renderCheckLevel(row.checkLevel)
245
+ */
246
+ export const renderCheckLevel = (
247
+ value: string | number | null | undefined,
248
+ ): VNode | null => renderClassifyTag(value, CHECK_LEVEL_MAP);
249
+
250
+ /** 排查/检查任务状态(aq_task_check_status / aq_check_info_status)*/
251
+ export const TASK_STATUS_MAP: Record<string | number, TagMapItem> = {
252
+ "0": { label: "待排查", type: "info" },
253
+ "1": { label: "排查中", type: "warning" },
254
+ "2": { label: "已完成", type: "success" },
255
+ "3": { label: "漏查", type: "danger" },
256
+ };
257
+
258
+ /** 计划状态(aq_plan_status: 1=启用, 0=停用)*/
259
+ export const PLAN_STATUS_MAP: Record<string | number, TagMapItem> = {
260
+ "1": { label: "启用", type: "success" },
261
+ "0": { label: "停用", type: "danger" },
262
+ };
263
+
264
+ /** 排查结果 / 检查结果(aq_handle_result: 1=正常, 2=异常)*/
265
+ export const FLAG_NORMAL_MAP: Record<string | number, TagMapItem> = {
266
+ "1": { label: "正常", type: "success" },
267
+ "2": { label: "异常", type: "danger" },
268
+ };
269
+
270
+ /** 隐患状态(aq_risk_status: 0=暂存, 1=待整改, 2=重新确认, 3=整改中, 4=待验收, 5=已关闭)*/
271
+ export const RISK_STATUS_MAP: Record<string | number, TagMapItem> = {
272
+ "0": { label: "暂存", type: "info" },
273
+ "1": { label: "待整改", type: "warning" },
274
+ "2": { label: "重新确认", type: "warning" },
275
+ "3": { label: "整改中", type: "" },
276
+ "4": { label: "待验收", type: "" },
277
+ "5": { label: "已关闭", type: "success" },
278
+ };
279
+
280
+ /** 整改状态(aq_correct_status: 0=待整改, 1=整改中, 2=已整改)*/
281
+ export const CORRECT_STATUS_MAP: Record<string | number, TagMapItem> = {
282
+ "0": { label: "待整改", type: "info" },
283
+ "1": { label: "整改中", type: "warning" },
284
+ "2": { label: "已整改", type: "success" },
285
+ };
286
+
287
+ export const renderTaskStatus = (value: string | number | null | undefined) =>
288
+ renderTagNode(value, TASK_STATUS_MAP);
289
+
290
+ export const renderPlanStatus = (value: string | number | null | undefined) =>
291
+ renderTagNode(value, PLAN_STATUS_MAP);
292
+
293
+ export const renderFlagNormal = (value: string | number | null | undefined) =>
294
+ renderTagNode(value, FLAG_NORMAL_MAP);
295
+
296
+ export const renderRiskStatus = (value: string | number | null | undefined) =>
297
+ renderTagNode(value, RISK_STATUS_MAP);
298
+
299
+ export const renderCorrectStatus = (
300
+ value: string | number | null | undefined,
301
+ ) => renderTagNode(value, CORRECT_STATUS_MAP);
302
+
303
+ // ── 危险作业票状态(permitStatus: 1=草稿, 2=待审批, 3=作业中, 4=已完工, 5=已撤销)──────
304
+ export const PERMIT_STATUS_MAP: Record<string | number, TagMapItem> = {
305
+ "1": { label: "草稿", type: "info" },
306
+ "2": { label: "待审批", type: "warning" },
307
+ "3": { label: "作业中", type: "" },
308
+ "4": { label: "已完工", type: "success" },
309
+ "5": { label: "已撤销", type: "danger" },
310
+ };
311
+
312
+ export const renderPermitStatus = (value: string | number | null | undefined) =>
313
+ renderTagNode(value, PERMIT_STATUS_MAP);
314
+
315
+ // ── 培训状态(trainStatus: 1=未完成, 2=已完成)──────────────────────────────────
316
+ export const TRAIN_STATUS_MAP: Record<string | number, TagMapItem> = {
317
+ "1": { label: "未完成", type: "warning" },
318
+ "2": { label: "已完成", type: "success" },
319
+ };
320
+
321
+ export const renderTrainStatus = (value: string | number | null | undefined) =>
322
+ renderTagNode(value, TRAIN_STATUS_MAP);
323
+
324
+ // ── 证书状态(credentialStatus: 1=有效, 2=即将过期, 3=已过期)──────────────────
325
+ export const CREDENTIAL_STATUS_MAP: Record<string | number, TagMapItem> = {
326
+ "1": { label: "有效", type: "success" },
327
+ "2": { label: "即将过期", type: "warning" },
328
+ "3": { label: "已过期", type: "danger" },
329
+ };
330
+
331
+ export const renderCredentialStatus = (
332
+ value: string | number | null | undefined,
333
+ ) => renderTagNode(value, CREDENTIAL_STATUS_MAP);
334
+
335
+ // ── 题库状态(unifyQuestionStatus: 1=草稿, 2=已发布, 3=已停用)──────────────────
336
+ export const QUESTION_STATUS_MAP: Record<string | number, TagMapItem> = {
337
+ "1": { label: "草稿", type: "info" },
338
+ "2": { label: "已发布", type: "success" },
339
+ "3": { label: "已停用", type: "danger" },
340
+ };
341
+
342
+ export const renderQuestionStatus = (
343
+ value: string | number | null | undefined,
344
+ ) => renderTagNode(value, QUESTION_STATUS_MAP);
345
+
346
+ /**
347
+ * 渲染风险编号徽标(defaultSlot 格式,蓝色圆角徽标 .jh-riskno-badge)
348
+ * 列定义写法:defaultSlot: ({ row }) => renderBadge(row.riskNo)
349
+ */
350
+ export function renderBadge(
351
+ value: string | number | null | undefined,
352
+ ): VNode | null {
353
+ if (value === null || value === undefined || value === "") return null;
354
+ return h("span", { class: "jh-riskno-badge" }, String(value));
355
+ }
356
+
357
+ /**
358
+ * 渲染评价级别圆形徽标(defaultSlot 格式,彩色圆形 .jh-rating-lv)
359
+ * 列定义写法:defaultSlot: ({ row }) => renderRatingLevel(row.ratingLevel)
360
+ */
361
+ export function renderRatingLevel(
362
+ value: string | null | undefined,
363
+ ): VNode | null {
364
+ if (!value) return null;
365
+ const clr = RATING_LEVEL_COLORS[value] ?? {
366
+ bg: "var(--wk-rating-fallback-bg, rgba(107,114,128,0.12))",
367
+ color: "var(--wk-rating-fallback-color, #374151)",
368
+ };
369
+ return h(
370
+ "span",
371
+ {
372
+ class: "jh-rating-lv",
373
+ style: { "--lv-bg": clr.bg, "--lv-color": clr.color },
374
+ },
375
+ value,
376
+ );
377
+ }
378
+
379
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
380
+ // renderOps — 操作列三层渲染系统
381
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
382
+
383
+ /**
384
+ * 内置图标预设(type: 'view' | 'edit' | 'del')
385
+ * 渲染为 26×26px 纯图标按钮,默认灰色,hover 变语义色
386
+ */
387
+ const ICON_PRESETS = {
388
+ view: { icon: View, cls: "jh-op-view", title: "查看" },
389
+ edit: { icon: Edit, cls: "jh-op-edit", title: "编辑" },
390
+ del: { icon: Delete, cls: "jh-op-del", title: "删除" },
391
+ log: { icon: Document, cls: "jh-op-log", title: "记录" },
392
+ ok: { icon: CircleCheck, cls: "jh-op-ok", title: "审核" },
393
+ send: { icon: Upload, cls: "jh-op-send", title: "提交" },
394
+ } as const;
395
+
396
+ type PresetType = keyof typeof ICON_PRESETS;
397
+
398
+ /** 图标按钮(纯图标,type 为内置预设 key)*/
399
+ export interface OpPreset {
400
+ type: PresetType;
401
+ /** 覆盖默认 tooltip */
402
+ title?: string;
403
+ /** false 时隐藏该按钮 */
404
+ show?: boolean;
405
+ onClick: (e: MouseEvent) => void;
406
+ }
407
+
408
+ /** 胶囊按钮(图标可选 + 短文字,适合流程记录/审核/提交等半通用操作)*/
409
+ export interface OpChip {
410
+ type: "chip";
411
+ label: string;
412
+ /** 可选左侧图标,传入 Element Plus 图标组件 */
413
+ icon?: Component;
414
+ show?: boolean;
415
+ onClick: (e: MouseEvent) => void;
416
+ }
417
+
418
+ /** 文字按钮(纯文字,适合业务专属长名称操作)*/
419
+ export interface OpLink {
420
+ type: "link";
421
+ label: string;
422
+ show?: boolean;
423
+ onClick: (e: MouseEvent) => void;
424
+ }
425
+
426
+ export type OpItem = OpPreset | OpChip | OpLink;
427
+
428
+ /**
429
+ * 渲染操作列按钮组(三层系统)
430
+ *
431
+ * 第一层 — 纯图标按钮(type: 'view'|'edit'|'del'|'log'|'ok'|'send')
432
+ * 26×26px,默认灰色,hover 变语义色,tooltip 兜底
433
+ *
434
+ * 第二层 — 胶囊按钮(type: 'chip')
435
+ * 圆角胶囊,图标+文字,hover 淡蓝背景 + 主色文字
436
+ *
437
+ * 第三层 — 文字按钮(type: 'link')
438
+ * 微边框小按钮,hover 主色边框+背景,有质感,不是普通 <a>
439
+ *
440
+ * 图标组与非图标组之间自动插入竖分隔线
441
+ * 所有按钮自动调用 e.stopPropagation(),页面 onClick 无需手动阻止
442
+ *
443
+ * @example
444
+ * defaultSlot: ({ row }) => renderOps([
445
+ * { type: 'view', onClick: () => modal.open(row.id, 'view') },
446
+ * { type: 'edit', onClick: () => modal.open(row.id, 'edit') },
447
+ * { type: 'del', onClick: () => handleDel(row.id) },
448
+ * { type: 'chip', label: '流程', onClick: () => showLog(row) },
449
+ * { type: 'link', label: '维护排查标准', onClick: () => openStd(row) },
450
+ * ])
451
+ */
452
+ export function renderOps(items: OpItem[]): VNode {
453
+ // 图标类按钮:show=false 时保留不可见占位,确保同列同类按钮上下对齐
454
+ const allIconItems = items.filter(
455
+ (i) => i.type in ICON_PRESETS,
456
+ ) as OpPreset[];
457
+ // chip/link 类按钮:有无 show 都直接过滤,无固定位置要求
458
+ const otherItems = items.filter(
459
+ (i) => !(i.type in ICON_PRESETS) && i.show !== false,
460
+ ) as (OpChip | OpLink)[];
461
+
462
+ const nodes: VNode[] = [];
463
+
464
+ // ── 第一层:纯图标按钮(含不可见占位以保持列对齐)──────────────────────
465
+ for (const item of allIconItems) {
466
+ const preset = ICON_PRESETS[item.type as PresetType];
467
+ const isVisible = item.show !== false;
468
+ nodes.push(
469
+ h(
470
+ "button",
471
+ {
472
+ class: ["jh-op-btn", preset.cls],
473
+ type: "button",
474
+ title: isVisible ? (item.title ?? preset.title) : undefined,
475
+ style: isVisible
476
+ ? undefined
477
+ : { visibility: "hidden", pointerEvents: "none" },
478
+ onClick: isVisible
479
+ ? (e: MouseEvent) => {
480
+ e.stopPropagation();
481
+ item.onClick(e);
482
+ }
483
+ : undefined,
484
+ },
485
+ h(preset.icon as Component),
486
+ ),
487
+ );
488
+ }
489
+
490
+ // ── 分隔线(图标组与文字组之间)──────────────────────────────────────────
491
+ if (allIconItems.length > 0 && otherItems.length > 0) {
492
+ nodes.push(h("span", { class: "jh-op-sep", "aria-hidden": "true" }));
493
+ }
494
+
495
+ // ── 第二/三层:胶囊 & 文字按钮 ───────────────────────────────────────────
496
+ for (const item of otherItems) {
497
+ if (item.type === "chip") {
498
+ const chipChildren: (VNode | string)[] = [];
499
+ if (item.icon) chipChildren.push(h(item.icon as Component));
500
+ chipChildren.push(h("span", null, item.label));
501
+ nodes.push(
502
+ h(
503
+ "button",
504
+ {
505
+ class: "jh-op-chip",
506
+ type: "button",
507
+ onClick: (e: MouseEvent) => {
508
+ e.stopPropagation();
509
+ item.onClick(e);
510
+ },
511
+ },
512
+ chipChildren,
513
+ ),
514
+ );
515
+ } else {
516
+ nodes.push(
517
+ h(
518
+ "button",
519
+ {
520
+ class: "jh-op-link",
521
+ type: "button",
522
+ onClick: (e: MouseEvent) => {
523
+ e.stopPropagation();
524
+ item.onClick(e);
525
+ },
526
+ },
527
+ item.label,
528
+ ),
529
+ );
530
+ }
531
+ }
532
+
533
+ return h("div", { class: "jh-op-group" }, nodes);
534
+ }
535
+
536
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
537
+ // 通用分类/枚举 Tag — 字典驱动
538
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
539
+
540
+ /**
541
+ * 渲染动态字典分类Tag(plain outline 样式)
542
+ *
543
+ * 从 Pinia business-logic-data store 动态获取字典 label,无需 hardcode 编码→文字映射。
544
+ * 适用于所有 logicType=dict 的分类字段(trainLevel、planType、coursewareType 等)。
545
+ *
546
+ * 可选传入 typeColorMap 为特定 code 值指定 El-Tag type 颜色:
547
+ * { '1': 'success', '2': 'warning', '3': 'danger', '4': 'info' }
548
+ * 未命中 typeColorMap 时统一展示 '' (蓝/灰 plain)
549
+ *
550
+ * 列定义写法:
551
+ * defaultSlot: ({ row }) => renderDictClassifyTag(row.trainLevel, 'trainLevel')
552
+ * defaultSlot: ({ row }) => renderDictClassifyTag(row.planType, 'planType', { '1': '', '2': 'warning', '3': 'info' })
553
+ */
554
+ export function renderDictClassifyTag(
555
+ value: string | number | null | undefined,
556
+ dictKey: string,
557
+ typeColorMap?: Record<string, string>,
558
+ ): VNode | null {
559
+ if (value === null || value === undefined || value === "") return null;
560
+ let label = String(value);
561
+ try {
562
+ const store = useBusinessLogicDataStore();
563
+ const key = new BusLogicKey({
564
+ logicType: BusLogicDataType.dict,
565
+ logicValue: dictKey,
566
+ });
567
+ const options = store.get(key) || [];
568
+ const option = options.find((o: any) => String(o.value) === String(value));
569
+ if (option) label = option.label;
570
+ } catch (_) {
571
+ // Pinia 未初始化时降级为原始值
572
+ }
573
+ const tagType = (typeColorMap?.[String(value)] ?? "") as any;
574
+ return h(
575
+ ElTag,
576
+ { type: tagType, size: "small", effect: "plain" },
577
+ () => label,
578
+ );
579
+ }
580
+
581
+ // ── 课件类型(coursewareType: 1=视频, 2=文档)────────────────────────────────
582
+ export const COURSEWARE_TYPE_MAP: Record<string | number, TagMapItem> = {
583
+ "1": { label: "视频", type: "" },
584
+ "2": { label: "文档", type: "info" },
585
+ };
586
+ export const renderCoursewareType = (
587
+ value: string | number | null | undefined,
588
+ ): VNode | null => renderClassifyTag(value, COURSEWARE_TYPE_MAP);
589
+
590
+ // ── 培训级别颜色方案(配合 renderDictClassifyTag 使用)───────────────────────
591
+ export const TRAIN_LEVEL_TYPE_MAP: Record<string, string> = {
592
+ "1": "", // 公司级 — 主色蓝
593
+ "2": "warning", // 部门/厂级 — 橙
594
+ "3": "info", // 班组/车间级 — 灰
595
+ "4": "info", // 岗位级 — 灰
596
+ };
597
+
598
+ // ── 预案分类颜色方案(配合 renderDictClassifyTag 使用)──────────────────────
599
+ export const PLAN_TYPE_COLOR_MAP: Record<string, string> = {
600
+ "1": "", // 综合应急预案 — 蓝
601
+ "2": "warning", // 专项应急预案 — 橙
602
+ "3": "info", // 现场处置方案 — 灰
603
+ };
604
+
605
+ // ── 演练方式颜色方案(配合 renderDictClassifyTag 使用)──────────────────────
606
+ export const DRILL_TYPE_COLOR_MAP: Record<string, string> = {
607
+ "1": "info", // 桌面演练 — 灰
608
+ "2": "warning", // 实战演练 — 橙
609
+ };
610
+
611
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
612
+ // 数值型徽标 — 考核基数/评分等数量值
613
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
614
+
615
+ /**
616
+ * 渲染数值徽标(绿色系 .jh-count-badge,区别于蓝色编号徽标 .jh-riskno-badge)
617
+ * 适用于考核基数、权重分、评分等数量型字段
618
+ * 列定义写法:defaultSlot: ({ row }) => renderCountBadge(row.baseCount)
619
+ */
620
+ export function renderCountBadge(
621
+ value: string | number | null | undefined,
622
+ ): VNode | null {
623
+ if (value === null || value === undefined || value === "") return null;
624
+ return h("span", { class: "jh-count-badge" }, String(value));
625
+ }
626
+
627
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
628
+ // 危险/警示文本 — 考评标准/扣分依据等需要醒目红色的文字列
629
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
630
+
631
+ /**
632
+ * 渲染红色警示文本(语义:考评标准、扣分依据、违规项等)
633
+ * 列定义写法:defaultSlot: ({ row }) => renderDangerText(row.evaluateStandard)
634
+ */
635
+ export function renderDangerText(
636
+ value: string | number | null | undefined,
637
+ ): VNode | null {
638
+ if (value === null || value === undefined || value === "") return null;
639
+ return h(
640
+ "span",
641
+ { style: { color: "var(--el-color-danger)", fontWeight: 500 } },
642
+ String(value),
643
+ );
644
+ }