@agile-team/wl-skills-kit 2.11.1 → 2.11.3

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 (91) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +38 -21
  3. package/bin/wl-skills.js +27 -3
  4. package/files/.wl-skills/docs/jh-pagination.md +505 -505
  5. package/files/.wl-skills/docs/request.md +940 -940
  6. package/files/.wl-skills/docs/validate-exempt.md +113 -0
  7. package/files/.wl-skills/guides/architecture.md +1 -1
  8. package/files/.wl-skills/skills/_compat/headers/cursor-mdc.txt +1 -1
  9. package/files/.wl-skills/skills/_compat/headers/kiro.txt +1 -1
  10. package/files/.wl-skills/skills/_compat/headers/trae.txt +1 -1
  11. package/files/.wl-skills/skills/core/convention-audit/SKILL.md +3 -3
  12. package/files/.wl-skills/skills/core/spec-doc-parse/SKILL.md +332 -332
  13. package/files/.wl-skills/skills/core/spec-doc-parse/USAGE.md +97 -97
  14. package/files/.wl-skills/skills/sync/permission-sync/USAGE.md +107 -107
  15. package/files/.wl-skills/src/components/global/C_ParentView/index.vue +3 -3
  16. package/files/.wl-skills/src/components/global/C_RightToolbar/index.vue +157 -157
  17. package/files/.wl-skills/src/components/global/C_SvgIcon/index.vue +31 -31
  18. package/files/.wl-skills/src/components/global/C_SvgIcon/svgicon.js +10 -10
  19. package/files/.wl-skills/src/components/global/C_TagStatus/README.md +264 -264
  20. package/files/.wl-skills/src/components/global/C_TagStatus/config.ts +192 -192
  21. package/files/.wl-skills/src/components/global/C_TagStatus/index.vue +106 -106
  22. package/files/.wl-skills/src/components/global/C_TagStatus/types.ts +64 -64
  23. package/files/.wl-skills/src/components/global/C_Tree/README.md +153 -153
  24. package/files/.wl-skills/src/components/global/C_Tree/index.scss +42 -42
  25. package/files/.wl-skills/src/components/global/C_Tree/index.vue +78 -78
  26. package/files/.wl-skills/src/components/global/C_Tree/types.ts +59 -59
  27. package/files/.wl-skills/src/components/local/c_formModal/README.md +235 -235
  28. package/files/.wl-skills/src/components/local/c_formModal/data.ts +95 -95
  29. package/files/.wl-skills/src/components/local/c_formModal/index.scss +8 -8
  30. package/files/.wl-skills/src/components/local/c_formModal/index.vue +107 -107
  31. package/files/.wl-skills/src/components/local/c_formSections/data.ts +175 -175
  32. package/files/.wl-skills/src/components/local/c_formSections/index.scss +280 -280
  33. package/files/.wl-skills/src/components/local/c_formSections/index.vue +429 -429
  34. package/files/.wl-skills/src/components/local/c_listModal/data.ts +41 -41
  35. package/files/.wl-skills/src/components/local/c_listModal/index.vue +136 -136
  36. package/files/.wl-skills/src/components/local/c_spliterTitle/index.scss +25 -25
  37. package/files/.wl-skills/src/components/local/c_spliterTitle/index.vue +21 -21
  38. package/files/.wl-skills/src/components/remote/AGGrid/README.md +530 -530
  39. package/files/.wl-skills/src/components/remote/BaseForm/README.md +508 -508
  40. package/files/.wl-skills/src/components/remote/BaseQuery/README.md +865 -865
  41. package/files/.wl-skills/src/components/remote/BaseTable/README.md +941 -941
  42. package/files/.wl-skills/src/components/remote/BaseToolbar/README.md +496 -496
  43. package/files/.wl-skills/src/types/page.ts +24 -24
  44. package/files/.wl-skills/standards/04-coding-basics.md +39 -1
  45. package/files/.wl-skills/standards/09-typescript.md +26 -3
  46. package/files/.wl-skills/standards/12-base-table.md +56 -4
  47. package/files/.wl-skills/standards/13-platform-components.md +1 -0
  48. package/files/.wl-skills/standards/index.md +2 -2
  49. package/files/.wl-skills/templates/README.md +44 -44
  50. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add/api.md +54 -54
  51. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add/data.ts +346 -346
  52. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add/index.scss +1 -1
  53. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add/index.vue +28 -28
  54. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add-form/data.ts +115 -115
  55. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add-form/index.scss +44 -44
  56. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add-form/index.vue +43 -43
  57. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change/data.ts +338 -338
  58. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change/index.scss +1 -1
  59. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change/index.vue +28 -28
  60. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change-form/data.ts +115 -115
  61. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change-form/index.scss +44 -44
  62. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change-form/index.vue +43 -43
  63. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-archive/api.md +88 -88
  64. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-archive/data.ts +601 -601
  65. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-archive/index.scss +1 -1
  66. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-archive/index.vue +64 -64
  67. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-detail/api.md +67 -67
  68. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-detail/data.ts +286 -286
  69. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-detail/index.scss +139 -139
  70. package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-detail/index.vue +318 -318
  71. package/files/.wl-skills/templates/produce/aiflow/mmwr-temp-customer-archive/api.md +98 -98
  72. package/files/.wl-skills/templates/produce/aiflow/mmwr-temp-customer-archive/data.ts +543 -543
  73. package/files/.wl-skills/templates/produce/aiflow/mmwr-temp-customer-archive/index.scss +1 -1
  74. package/files/.wl-skills/templates/produce/aiflow/mmwr-temp-customer-archive/index.vue +52 -52
  75. package/files/.wl-skills/templates/sale/demo/add-demo/data.ts +518 -518
  76. package/files/.wl-skills/templates/sale/demo/billet-flame-cut-plan/data.ts +524 -524
  77. package/files/.wl-skills/templates/sale/demo/billet-flame-cut-plan/index.scss +154 -154
  78. package/files/.wl-skills/templates/sale/demo/billet-flame-cut-plan/index.vue +117 -117
  79. package/files/.wl-skills/templates/sale/demo/domestic-trade-order/data.ts +308 -308
  80. package/files/.wl-skills/templates/sale/demo/domestic-trade-order/index.scss +99 -99
  81. package/files/.wl-skills/templates/sale/demo/domestic-trade-order/index.vue +77 -77
  82. package/files/.wl-skills/templates/sale/demo/heat-batch-return/data.ts +367 -367
  83. package/files/.wl-skills/templates/sale/demo/heat-batch-return/index.scss +100 -100
  84. package/files/.wl-skills/templates/sale/demo/heat-batch-return/index.vue +170 -170
  85. package/files/.wl-skills/templates/sale/demo/heat-batch-return/meltDialog.vue +320 -320
  86. package/files/.wl-skills/templates/sale/demo/metallurgical-spec/data.ts +824 -824
  87. package/lib/ast-rules.js +395 -12
  88. package/mcp/config.js +46 -46
  89. package/mcp/registry.js +6 -1
  90. package/mcp/tools/projectTools.js +9 -1
  91. package/package.json +2 -2
@@ -1,825 +1,825 @@
1
- /*
2
- * @Author: ChenYu ycyplus@gmail.com
3
- * @Date: 2025-12-31 14:20:15
4
- * @LastEditors: ChenYu ycyplus@gmail.com
5
- * @LastEditTime: 2026-01-02 10:30:22
6
- * @FilePath: \cx-ui-sale\src\views\sale\demo\metallurgical-spec\data.ts
7
- * @Description: 冶金规范模板 - 数据逻辑层(精简优化版)
8
- * Copyright (c) 2025 by CHENY, All Rights Reserved 😎.
9
- */
10
-
11
- import { ref, reactive, h } from "vue";
12
- import { ElMessage, ElMessageBox } from "element-plus";
13
- import { BaseQueryItemDesc, ActionButtonDesc, TableColumnDesc, BusLogicDataType } from "@/types/page";
14
- import request from "@jhlc/common-core/src/util/request";
15
-
16
- // ========== 类型定义 ==========
17
- // 统一查询表单
18
- interface UnifiedQueryForm {
19
- specCode: string;
20
- managementType: string;
21
- productStandard: string;
22
- buckleType: string;
23
- productName: string;
24
- manufacturingMethod: string;
25
- steelGrade: string;
26
- deliveryStatus: string;
27
- productSpecCode: string;
28
- indexNumber: string;
29
- standardType: string;
30
- usageDefinition: string;
31
- }
32
-
33
- // 详情查询表单
34
- interface DetailQueryForm {
35
- indexNumber: string;
36
- steelGrade: string;
37
- standardType: string;
38
- productStandard: string;
39
- productName: string;
40
- usageDefinition: string;
41
- }
42
-
43
- export interface TableRow {
44
- id: string;
45
- serialNumber: number;
46
- indexNumber: string;
47
- standardType: string;
48
- productStandard: string;
49
- revisionNumber: string;
50
- productName: string;
51
- }
52
-
53
- export interface ExperimentRow {
54
- id: string;
55
- serialNumber: number;
56
- testCategory: string;
57
- testItem: string;
58
- judgmentLogic: string;
59
- absLowerSymbol: string;
60
- absLowerValue: number;
61
- absUpperSymbol: string;
62
- absUpperValue: number;
63
- internalLowerSymbol: string;
64
- internalLowerValue: number;
65
- internalUpperSymbol: string;
66
- internalUpperValue: number;
67
- }
68
-
69
- interface TreeNode {
70
- id: string;
71
- label: string;
72
- children?: TreeNode[];
73
- }
74
-
75
- // ========== 状态管理 ==========
76
- // 活跃标签
77
- export const activeTab = ref("list");
78
- export const treeActiveTab = ref("tab1");
79
- export const detailActiveTab = ref("basic");
80
-
81
- // 加载状态
82
- export const loading = ref(false);
83
- export const listLoading = ref(false);
84
-
85
- // 数据状态
86
- export const treeData = ref<TreeNode[]>([]);
87
- export const tableData = ref<TableRow[]>([]);
88
- export const listTableData = ref<any[]>([]);
89
- export const experimentData = ref<ExperimentRow[]>([]);
90
- export const selectedRows = ref<TableRow[]>([]);
91
- export const currentSelectedRow = ref<TableRow | null>(null);
92
- export const updateKey = ref(0);
93
- // 树节点选择状态
94
- export const isTreeNodeSelected = ref(false);
95
-
96
- // 数据缓存
97
- const dataCache = ref({
98
- treeData: null as TreeNode[] | null,
99
- treeLoadTime: 0,
100
- experimentDataCache: new Map<string, { data: ExperimentRow[]; loadTime: number; total: number }>()
101
- });
102
- const CACHE_DURATION = 5 * 60 * 1000; // 5分钟
103
-
104
- // ========== 通用工具函数 ==========
105
- const createPagination = () => reactive({ pageNum: 1, pageSize: 10, total: 0 });
106
-
107
- // 分页配置
108
- export const pagination = createPagination();
109
- export const listPagination = createPagination();
110
- export const experimentPagination = createPagination();
111
-
112
- // ========== 表单数据 ==========
113
- // 表单初始化工具
114
- const createEmptyForm = <T extends Record<string, any>>(fields: (keyof T)[]): T =>
115
- reactive(Object.fromEntries(fields.map(field => [field, ""])) as T);
116
-
117
- // 表单配置
118
- export const unifiedQueryForm = createEmptyForm<UnifiedQueryForm>([
119
- 'specCode', 'managementType', 'productStandard', 'buckleType',
120
- 'productName', 'manufacturingMethod', 'steelGrade', 'deliveryStatus',
121
- 'productSpecCode', 'indexNumber', 'standardType', 'usageDefinition'
122
- ]);
123
-
124
- export const detailQueryForm = createEmptyForm<DetailQueryForm>([
125
- 'indexNumber', 'steelGrade', 'standardType', 'productStandard', 'productName', 'usageDefinition'
126
- ]);
127
-
128
- // ========== 配置常量 ==========
129
- const createTabConfig = (items: Array<[string, string]>) =>
130
- items.map(([label, name]) => ({ label, name }));
131
-
132
- export const mainTabsConfig = createTabConfig([["列表数据", "list"], ["详情数据", "detail"]]);
133
- export const treeTabsConfig = createTabConfig([["基本信息", "tab1"], ["交付标准", "tab2"]]);
134
- export const detailTabsConfig = createTabConfig([["基本信息", "basic"], ["明细信息", "detail"]]);
135
-
136
- export const treeProps = { children: "children", label: "label" } as const;
137
-
138
- // ========== 查询配置 ==========
139
- const createDictItem = (name: string, label: string, logicValue: string) => ({
140
- name, label, placeholder: "请选择", logicType: BusLogicDataType.dict, logicValue
141
- });
142
-
143
- const createInputItem = (name: string, label: string) => ({
144
- name, label, placeholder: "请输入"
145
- });
146
-
147
- export const listQueryItemsConfig: BaseQueryItemDesc<UnifiedQueryForm>[] = [
148
- createInputItem("specCode", "规范代码"),
149
- createInputItem("productStandard", "产品标准"),
150
- createDictItem("productName", "品名", "PRODUCT_NAME"),
151
- createInputItem("steelGrade", "钢级"),
152
- createDictItem("managementType", "管理类型", "MANAGEMENT_TYPE"),
153
- createInputItem("buckleType", "扣型"),
154
- createDictItem("manufacturingMethod", "制造方法", "MANUFACTURING_METHOD"),
155
- createDictItem("deliveryStatus", "交货状态", "DELIVERY_STATUS"),
156
- createDictItem("productSpecCode", "产品规范", "PRODUCT_SPEC_CODE")
157
- ];
158
-
159
- export const detailQueryItemsConfig: BaseQueryItemDesc<DetailQueryForm>[] = [
160
- createInputItem("indexNumber", "索引号"),
161
- createDictItem("standardType", "标准类型", "STANDARD_TYPE"),
162
- createInputItem("productStandard", "产品标准"),
163
- createInputItem("steelGrade", "钢级"),
164
- createDictItem("productName", "品名", "PRODUCT_NAME"),
165
- createInputItem("usageDefinition", "用途定义")
166
- ];
167
-
168
- // ========== 表格列配置 ==========
169
- export const listTableColumnsConfig: TableColumnDesc[] = [
170
- { type: "index", label: "序号", width: 80 },
171
- {
172
- label: "冶金规范代码",
173
- name: "specCode",
174
- width: 150,
175
- defaultSlot: ({ row }: any) => {
176
- return h(
177
- "span",
178
- {
179
- style: "color: #409eff; cursor: pointer; text-decoration: underline;",
180
- onClick: () => handleListRowClick(row)
181
- },
182
- row.specCode
183
- );
184
- }
185
- },
186
- { label: "品名", name: "productName", width: 120 },
187
- { label: "钢级/牌号(公制)", name: "steelGradeMetric", width: 150 },
188
- { label: "钢级/牌号(英制)", name: "steelGradeImperial", width: 150 },
189
- { label: "产品标准", name: "productStandard", width: 200 },
190
- { label: "客户编码", name: "customerCode", width: 120 },
191
- { label: "用途码", name: "usageCode", width: 100 },
192
- { label: "扣形", name: "buckleType", width: 100 },
193
- { label: "管端类型", name: "pipeEndType", width: 100 },
194
- { label: "制造方法", name: "manufacturingMethod", width: 120 },
195
- { label: "交货状态", name: "deliveryStatus", width: 120 },
196
- { label: "产品规范代码", name: "productSpecCode", minWidth: 150 }
197
- ];
198
-
199
- export const tableColumnsConfig: TableColumnDesc[] = [
200
- { type: "selection", width: 50 },
201
- { label: "序号", name: "serialNumber", width: 110 },
202
- { label: "索引号", name: "indexNumber", width: 180 },
203
- { label: "标准类型", name: "standardType", width: 120 },
204
- { label: "产品标准", name: "productStandard", width: 250 },
205
- { label: "修订号", name: "revisionNumber", width: 100 },
206
- { label: "品名", name: "productName", minWidth: 150 },
207
- {
208
- label: "操作",
209
- width: 220,
210
- fixed: "right",
211
- operations: [
212
- {
213
- name: "gaToGc",
214
- label: "GA->GC",
215
- onClick: (row: TableRow) => handleGaToGcRow(row)
216
- },
217
- {
218
- name: "copy",
219
- label: "复制",
220
- onClick: (row: TableRow) => handleCopyRow(row)
221
- },
222
- {
223
- name: "edit",
224
- label: "编辑",
225
- onClick: (row: TableRow) => handleEditRow(row)
226
- },
227
- {
228
- name: "remove",
229
- label: "删除",
230
- onClick: (row: TableRow) => handleDeleteRow(row)
231
- }
232
- ]
233
- }
234
- ];
235
-
236
- export const experimentColumnsConfig: TableColumnDesc[] = [
237
- {
238
- label: "序号",
239
- name: "serialNumber",
240
- width: 80,
241
- align: "center",
242
- // 使用列级 span 函数实现行合并
243
- span: ({ rowIndex, data }: any) => {
244
- if (!data || data.length === 0) return { rowSpan: 1, colSpan: 1 };
245
-
246
- const currentCategory = data[rowIndex]?.testCategory;
247
- const prevCategory =
248
- rowIndex > 0 ? data[rowIndex - 1]?.testCategory : null;
249
-
250
- // 如果是分组的第一行,计算需要合并的行数
251
- if (rowIndex === 0 || currentCategory !== prevCategory) {
252
- let rowSpan = 1;
253
- for (let i = rowIndex + 1; i < data.length; i++) {
254
- if (data[i].testCategory === currentCategory) {
255
- rowSpan++;
256
- } else {
257
- break;
258
- }
259
- }
260
- return { rowSpan, colSpan: 1 };
261
- }
262
-
263
- // 非第一行则隐藏
264
- return { rowSpan: 0, colSpan: 0 };
265
- },
266
- defaultSlot: ({ row, $index }: { row: ExperimentRow; $index: number }) => {
267
- return h("span", String($index + 1));
268
- }
269
- },
270
- {
271
- label: "试验种类",
272
- name: "testCategory",
273
- width: 150,
274
- align: "center",
275
- // 使用列级 span 函数实现行合并
276
- span: ({ rowIndex, data }: any) => {
277
- if (!data || data.length === 0) return { rowSpan: 1, colSpan: 1 };
278
-
279
- const currentCategory = data[rowIndex]?.testCategory;
280
- const prevCategory =
281
- rowIndex > 0 ? data[rowIndex - 1]?.testCategory : null;
282
-
283
- // 如果是分组的第一行,计算需要合并的行数
284
- if (rowIndex === 0 || currentCategory !== prevCategory) {
285
- let rowSpan = 1;
286
- for (let i = rowIndex + 1; i < data.length; i++) {
287
- if (data[i].testCategory === currentCategory) {
288
- rowSpan++;
289
- } else {
290
- break;
291
- }
292
- }
293
- return { rowSpan, colSpan: 1 };
294
- }
295
-
296
- // 非第一行则隐藏
297
- return { rowSpan: 0, colSpan: 0 };
298
- }
299
- },
300
- { label: "试验项目", name: "testItem", width: 150 },
301
- { label: "判断逻辑", name: "judgmentLogic", width: 120 },
302
- {
303
- label: "绝对判断",
304
- children: [
305
- { label: "下限符号", name: "absLowerSymbol", width: 80 },
306
- { label: "下限值", name: "absLowerValue", width: 80 },
307
- { label: "上限符号", name: "absUpperSymbol", width: 80 },
308
- { label: "上限值", name: "absUpperValue", width: 80 }
309
- ]
310
- },
311
- {
312
- label: "内控判断",
313
- children: [
314
- { label: "下限符号", name: "internalLowerSymbol", width: 80 },
315
- { label: "下限值", name: "internalLowerValue", width: 80 },
316
- { label: "上限符号", name: "internalUpperSymbol", width: 80 },
317
- { label: "上限值", name: "internalUpperValue", width: 80 }
318
- ]
319
- }
320
- ];
321
-
322
- // ========== 工具栏配置 ==========
323
- const createToolbarButton = (label: string, type = "", plain = false) => ({
324
- label, ...(type && { type }), ...(plain && { plain }),
325
- onClick: () => ElMessage.info(`${label}功能待实现`)
326
- });
327
-
328
- const createToolbar = (buttons: Array<[string, string?, boolean?]>, includeDelete = true) => [
329
- ...buttons.map(([label, type, plain]) => createToolbarButton(label, type, plain)),
330
- ...(includeDelete ? [{ label: "删除", type: "danger", onClick: () => handleDelete() }] : [])
331
- ];
332
-
333
- export const toolbarItems = createToolbar([
334
- ["新增", "primary"], ["复制新增", "", true], ["GA->GC", "", true],
335
- ["导入", "", true], ["导出", "", true], ["模版导出", "", true]
336
- ]);
337
-
338
- export const detailToolbarItems = createToolbar([["新增", "primary"], ["复制", "", true]]);
339
-
340
- // ========== 主表格编辑弹窗配置 ==========
341
- export const mainModalConfig = {
342
- titlePrefix: "基本信息",
343
- width: "1000px",
344
- columns: 2,
345
- labelWidth: "120px",
346
- formItems: [
347
- {
348
- name: "serialNumber",
349
- label: "序号",
350
- placeholder: "请输入序号",
351
- required: true
352
- },
353
- {
354
- name: "indexNumber",
355
- label: "索引号",
356
- placeholder: "请输入索引号",
357
- required: true
358
- },
359
- {
360
- name: "standardType",
361
- label: "标准类型",
362
- placeholder: "请选择标准类型",
363
- required: true
364
- },
365
- {
366
- name: "productStandard",
367
- label: "产品标准",
368
- placeholder: "请输入产品标准",
369
- required: true,
370
- span: 24
371
- },
372
- {
373
- name: "revisionNumber",
374
- label: "修订号",
375
- placeholder: "请输入修订号"
376
- },
377
- {
378
- name: "productName",
379
- label: "品名",
380
- placeholder: "请输入品名",
381
- required: true
382
- }
383
- ],
384
- api: {
385
- getById: "/api/metallurgical/getById" as any,
386
- save: "/api/metallurgical/save" as any,
387
- update: "/api/metallurgical/update" as any
388
- }
389
- };
390
-
391
- // ========== 实验项目编辑弹窗配置 ==========
392
- export const experimentModalConfig = {
393
- titlePrefix: "实验项目",
394
- width: "900px",
395
- columns: 2,
396
- labelWidth: "120px",
397
- formItems: [
398
- {
399
- name: "testCategory",
400
- label: "试验种类",
401
- placeholder: "请输入试验种类",
402
- required: true,
403
- span: 24
404
- },
405
- {
406
- name: "testItem",
407
- label: "试验项目",
408
- placeholder: "请输入试验项目",
409
- required: true
410
- },
411
- {
412
- name: "judgmentLogic",
413
- label: "判断逻辑",
414
- placeholder: "请输入判断逻辑"
415
- },
416
- // 绝对判断组
417
- {
418
- name: "absLowerSymbol",
419
- label: "绝对下限符号",
420
- placeholder: "请输入符号",
421
- span: 6
422
- },
423
- {
424
- name: "absLowerValue",
425
- label: "绝对下限值",
426
- placeholder: "请输入数值",
427
- span: 6
428
- },
429
- {
430
- name: "absUpperSymbol",
431
- label: "绝对上限符号",
432
- placeholder: "请输入符号",
433
- span: 6
434
- },
435
- {
436
- name: "absUpperValue",
437
- label: "绝对上限值",
438
- placeholder: "请输入数值",
439
- span: 6
440
- },
441
- // 内控判断组
442
- {
443
- name: "internalLowerSymbol",
444
- label: "内控下限符号",
445
- placeholder: "请输入符号",
446
- span: 6
447
- },
448
- {
449
- name: "internalLowerValue",
450
- label: "内控下限值",
451
- placeholder: "请输入数值",
452
- span: 6
453
- },
454
- {
455
- name: "internalUpperSymbol",
456
- label: "内控上限符号",
457
- placeholder: "请输入符号",
458
- span: 6
459
- },
460
- {
461
- name: "internalUpperValue",
462
- label: "内控上限值",
463
- placeholder: "请输入数值",
464
- span: 6
465
- }
466
- ],
467
- api: {
468
- getById: "/api/experiment/getById" as any,
469
- save: "/api/experiment/save" as any,
470
- update: "/api/experiment/update" as any
471
- }
472
- };
473
-
474
- // ========== API配置 ==========
475
- const API = {
476
- tree: "/api/metallurgical/tree",
477
- list: "/api/metallurgical/list",
478
- listPage: "/api/metallurgical/spec/list",
479
- experimentList: "/api/metallurgical/experiment/list"
480
- } as const;
481
-
482
- // ========== 通用数据加载工具 ==========
483
- const handleApiError = (error: any, message: string) => {
484
- console.error(`${message}:`, error);
485
- ElMessage.error(`${message}: ${error instanceof Error ? error.message : "未知错误"}`);
486
- };
487
-
488
- const extractApiData = (res: any) => res.data.data || res.data;
489
-
490
- // ========== 数据加载函数 ==========
491
- export const loadTreeData = async () => {
492
- try {
493
- const now = Date.now();
494
- const cachedData = dataCache.value.treeData;
495
- const cacheTime = dataCache.value.treeLoadTime;
496
-
497
- if (cachedData && now - cacheTime < CACHE_DURATION) {
498
- treeData.value = cachedData;
499
- return;
500
- }
501
-
502
- const res = await request({ url: API.tree, method: "get" });
503
- const data = extractApiData(res);
504
-
505
- treeData.value = data;
506
- dataCache.value.treeData = data;
507
- dataCache.value.treeLoadTime = now;
508
- } catch (error) {
509
- handleApiError(error, "加载树形数据失败");
510
- }
511
- };
512
-
513
- // 加载主表数据
514
- export const loadTableData = async () => {
515
- loading.value = true;
516
- try {
517
- const res = await request({
518
- url: API.list,
519
- method: "post",
520
- data: {
521
- ...unifiedQueryForm,
522
- ...detailQueryForm,
523
- pageNum: pagination.pageNum,
524
- pageSize: pagination.pageSize
525
- }
526
- });
527
- const responseData = extractApiData(res);
528
- tableData.value = responseData.list || responseData.records || [];
529
- updateKey.value++;
530
- pagination.total = responseData.total || 0;
531
- } catch (error) {
532
- handleApiError(error, "加载表格数据失败");
533
- } finally {
534
- loading.value = false;
535
- }
536
- };
537
-
538
- // 加载子表数据(实验数据)
539
- export const loadExperimentData = async (row: TableRow, pageNum = 1) => {
540
- loading.value = true;
541
- console.log("加载子表数据:", row.id, pageNum);
542
- try {
543
- const cacheKey = `${row.id}_${pageNum}`;
544
- const cachedData = dataCache.value.experimentDataCache.get(cacheKey);
545
- const now = Date.now();
546
-
547
- if (cachedData && now - cachedData.loadTime < CACHE_DURATION) {
548
- console.log("使用缓存数据");
549
- experimentData.value = cachedData.data;
550
- updateKey.value++;
551
- experimentPagination.total = cachedData.total;
552
- experimentPagination.pageNum = pageNum;
553
- return;
554
- }
555
-
556
- const res = await request({
557
- url: API.experimentList,
558
- method: "post",
559
- data: {
560
- parentId: row.id,
561
- pageNum,
562
- pageSize: experimentPagination.pageSize
563
- }
564
- });
565
-
566
- const responseData = res.data?.data || res.data || res;
567
- const data = responseData.list || responseData.records || [];
568
- const total = responseData.total || 0;
569
-
570
- console.log("子表数据加载结果:", { data, total, pageNum });
571
-
572
- experimentData.value = data;
573
- updateKey.value++;
574
- experimentPagination.total = total;
575
- experimentPagination.pageNum = pageNum;
576
-
577
- dataCache.value.experimentDataCache.set(cacheKey, {
578
- data,
579
- total,
580
- loadTime: now
581
- });
582
- } catch (error) {
583
- console.error("加载子表数据失败:", error);
584
- ElMessage.error(
585
- `加载子表数据失败: ${error instanceof Error ? error.message : "未知错误"}`
586
- );
587
- } finally {
588
- loading.value = false;
589
- }
590
- };
591
-
592
- // 加载列表数据
593
- export const loadListData = async () => {
594
- listLoading.value = true;
595
- try {
596
- const res = await request({
597
- url: API.listPage,
598
- method: "post",
599
- data: {
600
- ...unifiedQueryForm,
601
- pageNum: listPagination.pageNum,
602
- pageSize: listPagination.pageSize
603
- }
604
- });
605
- const responseData = res.data.data || res.data;
606
- listTableData.value = responseData.list || responseData.records || [];
607
- listPagination.total = responseData.total || 0;
608
- } catch (error) {
609
- console.error("加载列表数据失败:", error);
610
- ElMessage.error(
611
- `加载列表数据失败: ${error instanceof Error ? error.message : "未知错误"}`
612
- );
613
- } finally {
614
- listLoading.value = false;
615
- }
616
- };
617
-
618
- // ========== 事件处理 ==========
619
- // 通用重置表单方法
620
- const resetForm = (form: any) =>
621
- Object.keys(form).forEach((key) => (form[key] = ""));
622
-
623
- // 查询和重置
624
- export const handleUnifiedQuerySearch = () => {
625
- if (activeTab.value === "list") {
626
- listPagination.pageNum = 1;
627
- loadListData();
628
- } else {
629
- pagination.pageNum = 1;
630
- loadTableData();
631
- }
632
- };
633
-
634
- export const handleUnifiedQueryReset = () => {
635
- resetForm(unifiedQueryForm);
636
- handleUnifiedQuerySearch();
637
- };
638
-
639
- export const handleDetailQuerySearch = () => {
640
- pagination.pageNum = 1;
641
- loadExperimentData(currentSelectedRow.value, 1);
642
- };
643
-
644
- export const handleDetailQueryReset = () => {
645
- resetForm(detailQueryForm);
646
- handleDetailQuerySearch();
647
- };
648
-
649
- // 分页处理
650
- const createPageHandler =
651
- (pagination: any, loadFn: Function) => (page: number) => {
652
- pagination.pageNum = page;
653
- loadFn();
654
- };
655
-
656
- const createSizeHandler =
657
- (pagination: any, loadFn: Function) => (size: number) => {
658
- pagination.pageSize = size;
659
- pagination.pageNum = 1;
660
- loadFn();
661
- };
662
-
663
- // 通用事件处理器工厂
664
- const createEventHandlers = () => ({
665
- // 表格事件
666
- handleSelectionChange: (selection: TableRow[]) => { selectedRows.value = selection; },
667
-
668
- handleRowClick: (row: TableRow) => {
669
- console.log("点击行数据:", row);
670
- currentSelectedRow.value = row;
671
- detailActiveTab.value = "detail";
672
- loadExperimentData(row);
673
- },
674
-
675
- // 树节点事件
676
- handleNodeClick: (data: TreeNode) => {
677
- console.log('选中树节点:', data.label);
678
- isTreeNodeSelected.value = true;
679
- pagination.pageNum = 1;
680
- loadTableData();
681
- },
682
-
683
- handleTreeTabChange: (tabName: string) => {
684
- treeActiveTab.value = tabName;
685
- loadTreeData();
686
- },
687
-
688
- // 列表行点击
689
- handleListRowClick: (row: any) => {
690
- activeTab.value = "detail";
691
- unifiedQueryForm.specCode = row.specCode || "";
692
- ElMessage.info(`已切换到详情页面: ${row.specCode}`);
693
- }
694
- });
695
-
696
- // 导出事件处理器
697
- export const {
698
- handleSelectionChange, handleRowClick, handleNodeClick,
699
- handleTreeTabChange, handleListRowClick
700
- } = createEventHandlers();
701
-
702
- export const handlePageChange = createPageHandler(pagination, loadTableData);
703
- export const handleSizeChange = createSizeHandler(pagination, loadTableData);
704
- export const handleListPageChange = createPageHandler(listPagination, loadListData);
705
- export const handleListSizeChange = createSizeHandler(listPagination, loadListData);
706
-
707
- // 实验项目分页处理
708
- export const handleExperimentPageChange = (page: number) => {
709
- if (currentSelectedRow.value) loadExperimentData(currentSelectedRow.value, page);
710
- };
711
-
712
- export const handleExperimentSizeChange = (size: number) => {
713
- experimentPagination.pageSize = size;
714
- handleExperimentPageChange(1);
715
- };
716
-
717
- // 行级操作
718
- const createRowAction = (action: string) => (row: TableRow) => {
719
- ElMessage.info(`${action}: ${row.indexNumber}`);
720
- };
721
-
722
- // 删除操作
723
- export const handleDelete = () => {
724
- if (selectedRows.value.length === 0) {
725
- ElMessage.warning("请先选择要删除的数据");
726
- return;
727
- }
728
- ElMessageBox.confirm("确定要删除选中的数据吗?", "提示", {
729
- type: "warning"
730
- }).then(() => {
731
- ElMessage.success("删除成功");
732
- loadTableData();
733
- });
734
- };
735
-
736
- export const handleGaToGcRow = createRowAction("GA->GC");
737
- export const handleCopyRow = createRowAction("复制行");
738
-
739
- // 主表格编辑事件
740
- export const handleEditRow = (row: TableRow) => {
741
- console.log("编辑基本信息:", row);
742
- // 检查是否在浏览器环境中并且有全局的弹窗引用
743
- if (typeof window !== "undefined" && (window as any).mainModalRef) {
744
- (window as any).mainModalRef.edit(row.id);
745
- } else {
746
- ElMessage.info(`编辑基本信息: ${row.indexNumber} (弹窗功能演示)`);
747
- }
748
- };
749
- export const handleDeleteRow = (row: TableRow) => {
750
- ElMessageBox.confirm(
751
- `确定要删除索引号为 ${row.indexNumber} 的数据吗?`,
752
- "提示",
753
- { type: "warning" }
754
- ).then(() => {
755
- ElMessage.success("删除成功");
756
- loadTableData();
757
- });
758
- };
759
-
760
- // ========== 实验项目编辑相关 ==========
761
- // 实验项目编辑事件
762
- export const handleExperimentEdit = (row: ExperimentRow) => {
763
- console.log("编辑实验项目:", row);
764
- // 检查是否在浏览器环境中并且有全局的弹窗引用
765
- if (typeof window !== "undefined" && (window as any).experimentModalRef) {
766
- (window as any).experimentModalRef.edit(row.id);
767
- } else {
768
- ElMessage.info(`编辑实验项目: ${row.testItem} (弹窗功能演示)`);
769
- }
770
- };
771
-
772
- // 主表格弹窗确认事件
773
- export const handleMainModalOk = () => {
774
- console.log("基本信息编辑完成");
775
- // 重新加载表格数据
776
- loadTableData();
777
- };
778
-
779
- // 弹窗确认事件
780
- export const handleExperimentModalOk = () => {
781
- console.log("实验项目编辑完成");
782
- // 重新加载实验数据
783
- if (currentSelectedRow.value) {
784
- loadExperimentData(currentSelectedRow.value);
785
- }
786
- };
787
-
788
- // ========== 弹窗引用管理 ==========
789
- // 定义全局变量用于存储弹窗引用
790
- let mainModalRef: any = null;
791
- let experimentModalRef: any = null;
792
-
793
- // 设置主表格弹窗引用(由Vue组件调用)
794
- export const setMainModalRef = (ref: any) => {
795
- mainModalRef = ref;
796
- };
797
-
798
- // 设置实验项目弹窗引用(由Vue组件调用)
799
- export const setExperimentModalRef = (ref: any) => {
800
- experimentModalRef = ref;
801
- };
802
-
803
- // 行合并逻辑已移至列配置的 span 函数中,无需手动处理
804
-
805
- // ========== 生命周期管理 ==========
806
- export const initData = () => {
807
- loadTreeData();
808
- // 重置状态:详情页面初始不加载表格数据,等待用户点击树节点
809
- isTreeNodeSelected.value = false;
810
- tableData.value = [];
811
- experimentData.value = [];
812
- updateKey.value = 0;
813
- };
814
-
815
- export const cleanup = () => {
816
- // 清空缓存
817
- dataCache.value.treeData = null;
818
- dataCache.value.treeLoadTime = 0;
819
- dataCache.value.experimentDataCache.clear();
820
-
821
- // 清空选择状态
822
- selectedRows.value = [];
823
- currentSelectedRow.value = null;
824
- isTreeNodeSelected.value = false;
1
+ /*
2
+ * @Author: ChenYu ycyplus@gmail.com
3
+ * @Date: 2025-12-31 14:20:15
4
+ * @LastEditors: ChenYu ycyplus@gmail.com
5
+ * @LastEditTime: 2026-01-02 10:30:22
6
+ * @FilePath: \cx-ui-sale\src\views\sale\demo\metallurgical-spec\data.ts
7
+ * @Description: 冶金规范模板 - 数据逻辑层(精简优化版)
8
+ * Copyright (c) 2025 by CHENY, All Rights Reserved 😎.
9
+ */
10
+
11
+ import { ref, reactive, h } from "vue";
12
+ import { ElMessage, ElMessageBox } from "element-plus";
13
+ import { BaseQueryItemDesc, ActionButtonDesc, TableColumnDesc, BusLogicDataType } from "@/types/page";
14
+ import request from "@jhlc/common-core/src/util/request";
15
+
16
+ // ========== 类型定义 ==========
17
+ // 统一查询表单
18
+ interface UnifiedQueryForm {
19
+ specCode: string;
20
+ managementType: string;
21
+ productStandard: string;
22
+ buckleType: string;
23
+ productName: string;
24
+ manufacturingMethod: string;
25
+ steelGrade: string;
26
+ deliveryStatus: string;
27
+ productSpecCode: string;
28
+ indexNumber: string;
29
+ standardType: string;
30
+ usageDefinition: string;
31
+ }
32
+
33
+ // 详情查询表单
34
+ interface DetailQueryForm {
35
+ indexNumber: string;
36
+ steelGrade: string;
37
+ standardType: string;
38
+ productStandard: string;
39
+ productName: string;
40
+ usageDefinition: string;
41
+ }
42
+
43
+ export interface TableRow {
44
+ id: string;
45
+ serialNumber: number;
46
+ indexNumber: string;
47
+ standardType: string;
48
+ productStandard: string;
49
+ revisionNumber: string;
50
+ productName: string;
51
+ }
52
+
53
+ export interface ExperimentRow {
54
+ id: string;
55
+ serialNumber: number;
56
+ testCategory: string;
57
+ testItem: string;
58
+ judgmentLogic: string;
59
+ absLowerSymbol: string;
60
+ absLowerValue: number;
61
+ absUpperSymbol: string;
62
+ absUpperValue: number;
63
+ internalLowerSymbol: string;
64
+ internalLowerValue: number;
65
+ internalUpperSymbol: string;
66
+ internalUpperValue: number;
67
+ }
68
+
69
+ interface TreeNode {
70
+ id: string;
71
+ label: string;
72
+ children?: TreeNode[];
73
+ }
74
+
75
+ // ========== 状态管理 ==========
76
+ // 活跃标签
77
+ export const activeTab = ref("list");
78
+ export const treeActiveTab = ref("tab1");
79
+ export const detailActiveTab = ref("basic");
80
+
81
+ // 加载状态
82
+ export const loading = ref(false);
83
+ export const listLoading = ref(false);
84
+
85
+ // 数据状态
86
+ export const treeData = ref<TreeNode[]>([]);
87
+ export const tableData = ref<TableRow[]>([]);
88
+ export const listTableData = ref<any[]>([]);
89
+ export const experimentData = ref<ExperimentRow[]>([]);
90
+ export const selectedRows = ref<TableRow[]>([]);
91
+ export const currentSelectedRow = ref<TableRow | null>(null);
92
+ export const updateKey = ref(0);
93
+ // 树节点选择状态
94
+ export const isTreeNodeSelected = ref(false);
95
+
96
+ // 数据缓存
97
+ const dataCache = ref({
98
+ treeData: null as TreeNode[] | null,
99
+ treeLoadTime: 0,
100
+ experimentDataCache: new Map<string, { data: ExperimentRow[]; loadTime: number; total: number }>()
101
+ });
102
+ const CACHE_DURATION = 5 * 60 * 1000; // 5分钟
103
+
104
+ // ========== 通用工具函数 ==========
105
+ const createPagination = () => reactive({ pageNum: 1, pageSize: 10, total: 0 });
106
+
107
+ // 分页配置
108
+ export const pagination = createPagination();
109
+ export const listPagination = createPagination();
110
+ export const experimentPagination = createPagination();
111
+
112
+ // ========== 表单数据 ==========
113
+ // 表单初始化工具
114
+ const createEmptyForm = <T extends Record<string, any>>(fields: (keyof T)[]): T =>
115
+ reactive(Object.fromEntries(fields.map(field => [field, ""])) as T);
116
+
117
+ // 表单配置
118
+ export const unifiedQueryForm = createEmptyForm<UnifiedQueryForm>([
119
+ 'specCode', 'managementType', 'productStandard', 'buckleType',
120
+ 'productName', 'manufacturingMethod', 'steelGrade', 'deliveryStatus',
121
+ 'productSpecCode', 'indexNumber', 'standardType', 'usageDefinition'
122
+ ]);
123
+
124
+ export const detailQueryForm = createEmptyForm<DetailQueryForm>([
125
+ 'indexNumber', 'steelGrade', 'standardType', 'productStandard', 'productName', 'usageDefinition'
126
+ ]);
127
+
128
+ // ========== 配置常量 ==========
129
+ const createTabConfig = (items: Array<[string, string]>) =>
130
+ items.map(([label, name]) => ({ label, name }));
131
+
132
+ export const mainTabsConfig = createTabConfig([["列表数据", "list"], ["详情数据", "detail"]]);
133
+ export const treeTabsConfig = createTabConfig([["基本信息", "tab1"], ["交付标准", "tab2"]]);
134
+ export const detailTabsConfig = createTabConfig([["基本信息", "basic"], ["明细信息", "detail"]]);
135
+
136
+ export const treeProps = { children: "children", label: "label" } as const;
137
+
138
+ // ========== 查询配置 ==========
139
+ const createDictItem = (name: string, label: string, logicValue: string) => ({
140
+ name, label, placeholder: "请选择", logicType: BusLogicDataType.dict, logicValue
141
+ });
142
+
143
+ const createInputItem = (name: string, label: string) => ({
144
+ name, label, placeholder: "请输入"
145
+ });
146
+
147
+ export const listQueryItemsConfig: BaseQueryItemDesc<UnifiedQueryForm>[] = [
148
+ createInputItem("specCode", "规范代码"),
149
+ createInputItem("productStandard", "产品标准"),
150
+ createDictItem("productName", "品名", "PRODUCT_NAME"),
151
+ createInputItem("steelGrade", "钢级"),
152
+ createDictItem("managementType", "管理类型", "MANAGEMENT_TYPE"),
153
+ createInputItem("buckleType", "扣型"),
154
+ createDictItem("manufacturingMethod", "制造方法", "MANUFACTURING_METHOD"),
155
+ createDictItem("deliveryStatus", "交货状态", "DELIVERY_STATUS"),
156
+ createDictItem("productSpecCode", "产品规范", "PRODUCT_SPEC_CODE")
157
+ ];
158
+
159
+ export const detailQueryItemsConfig: BaseQueryItemDesc<DetailQueryForm>[] = [
160
+ createInputItem("indexNumber", "索引号"),
161
+ createDictItem("standardType", "标准类型", "STANDARD_TYPE"),
162
+ createInputItem("productStandard", "产品标准"),
163
+ createInputItem("steelGrade", "钢级"),
164
+ createDictItem("productName", "品名", "PRODUCT_NAME"),
165
+ createInputItem("usageDefinition", "用途定义")
166
+ ];
167
+
168
+ // ========== 表格列配置 ==========
169
+ export const listTableColumnsConfig: TableColumnDesc[] = [
170
+ { type: "index", label: "序号", width: 80 },
171
+ {
172
+ label: "冶金规范代码",
173
+ name: "specCode",
174
+ width: 150,
175
+ defaultSlot: ({ row }: any) => {
176
+ return h(
177
+ "span",
178
+ {
179
+ style: "color: #409eff; cursor: pointer; text-decoration: underline;",
180
+ onClick: () => handleListRowClick(row)
181
+ },
182
+ row.specCode
183
+ );
184
+ }
185
+ },
186
+ { label: "品名", name: "productName", width: 120 },
187
+ { label: "钢级/牌号(公制)", name: "steelGradeMetric", width: 150 },
188
+ { label: "钢级/牌号(英制)", name: "steelGradeImperial", width: 150 },
189
+ { label: "产品标准", name: "productStandard", width: 200 },
190
+ { label: "客户编码", name: "customerCode", width: 120 },
191
+ { label: "用途码", name: "usageCode", width: 100 },
192
+ { label: "扣形", name: "buckleType", width: 100 },
193
+ { label: "管端类型", name: "pipeEndType", width: 100 },
194
+ { label: "制造方法", name: "manufacturingMethod", width: 120 },
195
+ { label: "交货状态", name: "deliveryStatus", width: 120 },
196
+ { label: "产品规范代码", name: "productSpecCode", minWidth: 150 }
197
+ ];
198
+
199
+ export const tableColumnsConfig: TableColumnDesc[] = [
200
+ { type: "selection", width: 50 },
201
+ { label: "序号", name: "serialNumber", width: 110 },
202
+ { label: "索引号", name: "indexNumber", width: 180 },
203
+ { label: "标准类型", name: "standardType", width: 120 },
204
+ { label: "产品标准", name: "productStandard", width: 250 },
205
+ { label: "修订号", name: "revisionNumber", width: 100 },
206
+ { label: "品名", name: "productName", minWidth: 150 },
207
+ {
208
+ label: "操作",
209
+ width: 220,
210
+ fixed: "right",
211
+ operations: [
212
+ {
213
+ name: "gaToGc",
214
+ label: "GA->GC",
215
+ onClick: (row: TableRow) => handleGaToGcRow(row)
216
+ },
217
+ {
218
+ name: "copy",
219
+ label: "复制",
220
+ onClick: (row: TableRow) => handleCopyRow(row)
221
+ },
222
+ {
223
+ name: "edit",
224
+ label: "编辑",
225
+ onClick: (row: TableRow) => handleEditRow(row)
226
+ },
227
+ {
228
+ name: "remove",
229
+ label: "删除",
230
+ onClick: (row: TableRow) => handleDeleteRow(row)
231
+ }
232
+ ]
233
+ }
234
+ ];
235
+
236
+ export const experimentColumnsConfig: TableColumnDesc[] = [
237
+ {
238
+ label: "序号",
239
+ name: "serialNumber",
240
+ width: 80,
241
+ align: "center",
242
+ // 使用列级 span 函数实现行合并
243
+ span: ({ rowIndex, data }: any) => {
244
+ if (!data || data.length === 0) return { rowSpan: 1, colSpan: 1 };
245
+
246
+ const currentCategory = data[rowIndex]?.testCategory;
247
+ const prevCategory =
248
+ rowIndex > 0 ? data[rowIndex - 1]?.testCategory : null;
249
+
250
+ // 如果是分组的第一行,计算需要合并的行数
251
+ if (rowIndex === 0 || currentCategory !== prevCategory) {
252
+ let rowSpan = 1;
253
+ for (let i = rowIndex + 1; i < data.length; i++) {
254
+ if (data[i].testCategory === currentCategory) {
255
+ rowSpan++;
256
+ } else {
257
+ break;
258
+ }
259
+ }
260
+ return { rowSpan, colSpan: 1 };
261
+ }
262
+
263
+ // 非第一行则隐藏
264
+ return { rowSpan: 0, colSpan: 0 };
265
+ },
266
+ defaultSlot: ({ row, $index }: { row: ExperimentRow; $index: number }) => {
267
+ return h("span", String($index + 1));
268
+ }
269
+ },
270
+ {
271
+ label: "试验种类",
272
+ name: "testCategory",
273
+ width: 150,
274
+ align: "center",
275
+ // 使用列级 span 函数实现行合并
276
+ span: ({ rowIndex, data }: any) => {
277
+ if (!data || data.length === 0) return { rowSpan: 1, colSpan: 1 };
278
+
279
+ const currentCategory = data[rowIndex]?.testCategory;
280
+ const prevCategory =
281
+ rowIndex > 0 ? data[rowIndex - 1]?.testCategory : null;
282
+
283
+ // 如果是分组的第一行,计算需要合并的行数
284
+ if (rowIndex === 0 || currentCategory !== prevCategory) {
285
+ let rowSpan = 1;
286
+ for (let i = rowIndex + 1; i < data.length; i++) {
287
+ if (data[i].testCategory === currentCategory) {
288
+ rowSpan++;
289
+ } else {
290
+ break;
291
+ }
292
+ }
293
+ return { rowSpan, colSpan: 1 };
294
+ }
295
+
296
+ // 非第一行则隐藏
297
+ return { rowSpan: 0, colSpan: 0 };
298
+ }
299
+ },
300
+ { label: "试验项目", name: "testItem", width: 150 },
301
+ { label: "判断逻辑", name: "judgmentLogic", width: 120 },
302
+ {
303
+ label: "绝对判断",
304
+ children: [
305
+ { label: "下限符号", name: "absLowerSymbol", width: 80 },
306
+ { label: "下限值", name: "absLowerValue", width: 80 },
307
+ { label: "上限符号", name: "absUpperSymbol", width: 80 },
308
+ { label: "上限值", name: "absUpperValue", width: 80 }
309
+ ]
310
+ },
311
+ {
312
+ label: "内控判断",
313
+ children: [
314
+ { label: "下限符号", name: "internalLowerSymbol", width: 80 },
315
+ { label: "下限值", name: "internalLowerValue", width: 80 },
316
+ { label: "上限符号", name: "internalUpperSymbol", width: 80 },
317
+ { label: "上限值", name: "internalUpperValue", width: 80 }
318
+ ]
319
+ }
320
+ ];
321
+
322
+ // ========== 工具栏配置 ==========
323
+ const createToolbarButton = (label: string, type = "", plain = false) => ({
324
+ label, ...(type && { type }), ...(plain && { plain }),
325
+ onClick: () => ElMessage.info(`${label}功能待实现`)
326
+ });
327
+
328
+ const createToolbar = (buttons: Array<[string, string?, boolean?]>, includeDelete = true) => [
329
+ ...buttons.map(([label, type, plain]) => createToolbarButton(label, type, plain)),
330
+ ...(includeDelete ? [{ label: "删除", type: "danger", onClick: () => handleDelete() }] : [])
331
+ ];
332
+
333
+ export const toolbarItems = createToolbar([
334
+ ["新增", "primary"], ["复制新增", "", true], ["GA->GC", "", true],
335
+ ["导入", "", true], ["导出", "", true], ["模版导出", "", true]
336
+ ]);
337
+
338
+ export const detailToolbarItems = createToolbar([["新增", "primary"], ["复制", "", true]]);
339
+
340
+ // ========== 主表格编辑弹窗配置 ==========
341
+ export const mainModalConfig = {
342
+ titlePrefix: "基本信息",
343
+ width: "1000px",
344
+ columns: 2,
345
+ labelWidth: "120px",
346
+ formItems: [
347
+ {
348
+ name: "serialNumber",
349
+ label: "序号",
350
+ placeholder: "请输入序号",
351
+ required: true
352
+ },
353
+ {
354
+ name: "indexNumber",
355
+ label: "索引号",
356
+ placeholder: "请输入索引号",
357
+ required: true
358
+ },
359
+ {
360
+ name: "standardType",
361
+ label: "标准类型",
362
+ placeholder: "请选择标准类型",
363
+ required: true
364
+ },
365
+ {
366
+ name: "productStandard",
367
+ label: "产品标准",
368
+ placeholder: "请输入产品标准",
369
+ required: true,
370
+ span: 24
371
+ },
372
+ {
373
+ name: "revisionNumber",
374
+ label: "修订号",
375
+ placeholder: "请输入修订号"
376
+ },
377
+ {
378
+ name: "productName",
379
+ label: "品名",
380
+ placeholder: "请输入品名",
381
+ required: true
382
+ }
383
+ ],
384
+ api: {
385
+ getById: "/api/metallurgical/getById" as any,
386
+ save: "/api/metallurgical/save" as any,
387
+ update: "/api/metallurgical/update" as any
388
+ }
389
+ };
390
+
391
+ // ========== 实验项目编辑弹窗配置 ==========
392
+ export const experimentModalConfig = {
393
+ titlePrefix: "实验项目",
394
+ width: "900px",
395
+ columns: 2,
396
+ labelWidth: "120px",
397
+ formItems: [
398
+ {
399
+ name: "testCategory",
400
+ label: "试验种类",
401
+ placeholder: "请输入试验种类",
402
+ required: true,
403
+ span: 24
404
+ },
405
+ {
406
+ name: "testItem",
407
+ label: "试验项目",
408
+ placeholder: "请输入试验项目",
409
+ required: true
410
+ },
411
+ {
412
+ name: "judgmentLogic",
413
+ label: "判断逻辑",
414
+ placeholder: "请输入判断逻辑"
415
+ },
416
+ // 绝对判断组
417
+ {
418
+ name: "absLowerSymbol",
419
+ label: "绝对下限符号",
420
+ placeholder: "请输入符号",
421
+ span: 6
422
+ },
423
+ {
424
+ name: "absLowerValue",
425
+ label: "绝对下限值",
426
+ placeholder: "请输入数值",
427
+ span: 6
428
+ },
429
+ {
430
+ name: "absUpperSymbol",
431
+ label: "绝对上限符号",
432
+ placeholder: "请输入符号",
433
+ span: 6
434
+ },
435
+ {
436
+ name: "absUpperValue",
437
+ label: "绝对上限值",
438
+ placeholder: "请输入数值",
439
+ span: 6
440
+ },
441
+ // 内控判断组
442
+ {
443
+ name: "internalLowerSymbol",
444
+ label: "内控下限符号",
445
+ placeholder: "请输入符号",
446
+ span: 6
447
+ },
448
+ {
449
+ name: "internalLowerValue",
450
+ label: "内控下限值",
451
+ placeholder: "请输入数值",
452
+ span: 6
453
+ },
454
+ {
455
+ name: "internalUpperSymbol",
456
+ label: "内控上限符号",
457
+ placeholder: "请输入符号",
458
+ span: 6
459
+ },
460
+ {
461
+ name: "internalUpperValue",
462
+ label: "内控上限值",
463
+ placeholder: "请输入数值",
464
+ span: 6
465
+ }
466
+ ],
467
+ api: {
468
+ getById: "/api/experiment/getById" as any,
469
+ save: "/api/experiment/save" as any,
470
+ update: "/api/experiment/update" as any
471
+ }
472
+ };
473
+
474
+ // ========== API配置 ==========
475
+ const API = {
476
+ tree: "/api/metallurgical/tree",
477
+ list: "/api/metallurgical/list",
478
+ listPage: "/api/metallurgical/spec/list",
479
+ experimentList: "/api/metallurgical/experiment/list"
480
+ } as const;
481
+
482
+ // ========== 通用数据加载工具 ==========
483
+ const handleApiError = (error: any, message: string) => {
484
+ console.error(`${message}:`, error);
485
+ ElMessage.error(`${message}: ${error instanceof Error ? error.message : "未知错误"}`);
486
+ };
487
+
488
+ const extractApiData = (res: any) => res.data.data || res.data;
489
+
490
+ // ========== 数据加载函数 ==========
491
+ export const loadTreeData = async () => {
492
+ try {
493
+ const now = Date.now();
494
+ const cachedData = dataCache.value.treeData;
495
+ const cacheTime = dataCache.value.treeLoadTime;
496
+
497
+ if (cachedData && now - cacheTime < CACHE_DURATION) {
498
+ treeData.value = cachedData;
499
+ return;
500
+ }
501
+
502
+ const res = await request({ url: API.tree, method: "get" });
503
+ const data = extractApiData(res);
504
+
505
+ treeData.value = data;
506
+ dataCache.value.treeData = data;
507
+ dataCache.value.treeLoadTime = now;
508
+ } catch (error) {
509
+ handleApiError(error, "加载树形数据失败");
510
+ }
511
+ };
512
+
513
+ // 加载主表数据
514
+ export const loadTableData = async () => {
515
+ loading.value = true;
516
+ try {
517
+ const res = await request({
518
+ url: API.list,
519
+ method: "post",
520
+ data: {
521
+ ...unifiedQueryForm,
522
+ ...detailQueryForm,
523
+ pageNum: pagination.pageNum,
524
+ pageSize: pagination.pageSize
525
+ }
526
+ });
527
+ const responseData = extractApiData(res);
528
+ tableData.value = responseData.list || responseData.records || [];
529
+ updateKey.value++;
530
+ pagination.total = responseData.total || 0;
531
+ } catch (error) {
532
+ handleApiError(error, "加载表格数据失败");
533
+ } finally {
534
+ loading.value = false;
535
+ }
536
+ };
537
+
538
+ // 加载子表数据(实验数据)
539
+ export const loadExperimentData = async (row: TableRow, pageNum = 1) => {
540
+ loading.value = true;
541
+ console.log("加载子表数据:", row.id, pageNum);
542
+ try {
543
+ const cacheKey = `${row.id}_${pageNum}`;
544
+ const cachedData = dataCache.value.experimentDataCache.get(cacheKey);
545
+ const now = Date.now();
546
+
547
+ if (cachedData && now - cachedData.loadTime < CACHE_DURATION) {
548
+ console.log("使用缓存数据");
549
+ experimentData.value = cachedData.data;
550
+ updateKey.value++;
551
+ experimentPagination.total = cachedData.total;
552
+ experimentPagination.pageNum = pageNum;
553
+ return;
554
+ }
555
+
556
+ const res = await request({
557
+ url: API.experimentList,
558
+ method: "post",
559
+ data: {
560
+ parentId: row.id,
561
+ pageNum,
562
+ pageSize: experimentPagination.pageSize
563
+ }
564
+ });
565
+
566
+ const responseData = res.data?.data || res.data || res;
567
+ const data = responseData.list || responseData.records || [];
568
+ const total = responseData.total || 0;
569
+
570
+ console.log("子表数据加载结果:", { data, total, pageNum });
571
+
572
+ experimentData.value = data;
573
+ updateKey.value++;
574
+ experimentPagination.total = total;
575
+ experimentPagination.pageNum = pageNum;
576
+
577
+ dataCache.value.experimentDataCache.set(cacheKey, {
578
+ data,
579
+ total,
580
+ loadTime: now
581
+ });
582
+ } catch (error) {
583
+ console.error("加载子表数据失败:", error);
584
+ ElMessage.error(
585
+ `加载子表数据失败: ${error instanceof Error ? error.message : "未知错误"}`
586
+ );
587
+ } finally {
588
+ loading.value = false;
589
+ }
590
+ };
591
+
592
+ // 加载列表数据
593
+ export const loadListData = async () => {
594
+ listLoading.value = true;
595
+ try {
596
+ const res = await request({
597
+ url: API.listPage,
598
+ method: "post",
599
+ data: {
600
+ ...unifiedQueryForm,
601
+ pageNum: listPagination.pageNum,
602
+ pageSize: listPagination.pageSize
603
+ }
604
+ });
605
+ const responseData = res.data.data || res.data;
606
+ listTableData.value = responseData.list || responseData.records || [];
607
+ listPagination.total = responseData.total || 0;
608
+ } catch (error) {
609
+ console.error("加载列表数据失败:", error);
610
+ ElMessage.error(
611
+ `加载列表数据失败: ${error instanceof Error ? error.message : "未知错误"}`
612
+ );
613
+ } finally {
614
+ listLoading.value = false;
615
+ }
616
+ };
617
+
618
+ // ========== 事件处理 ==========
619
+ // 通用重置表单方法
620
+ const resetForm = (form: any) =>
621
+ Object.keys(form).forEach((key) => (form[key] = ""));
622
+
623
+ // 查询和重置
624
+ export const handleUnifiedQuerySearch = () => {
625
+ if (activeTab.value === "list") {
626
+ listPagination.pageNum = 1;
627
+ loadListData();
628
+ } else {
629
+ pagination.pageNum = 1;
630
+ loadTableData();
631
+ }
632
+ };
633
+
634
+ export const handleUnifiedQueryReset = () => {
635
+ resetForm(unifiedQueryForm);
636
+ handleUnifiedQuerySearch();
637
+ };
638
+
639
+ export const handleDetailQuerySearch = () => {
640
+ pagination.pageNum = 1;
641
+ loadExperimentData(currentSelectedRow.value, 1);
642
+ };
643
+
644
+ export const handleDetailQueryReset = () => {
645
+ resetForm(detailQueryForm);
646
+ handleDetailQuerySearch();
647
+ };
648
+
649
+ // 分页处理
650
+ const createPageHandler =
651
+ (pagination: any, loadFn: Function) => (page: number) => {
652
+ pagination.pageNum = page;
653
+ loadFn();
654
+ };
655
+
656
+ const createSizeHandler =
657
+ (pagination: any, loadFn: Function) => (size: number) => {
658
+ pagination.pageSize = size;
659
+ pagination.pageNum = 1;
660
+ loadFn();
661
+ };
662
+
663
+ // 通用事件处理器工厂
664
+ const createEventHandlers = () => ({
665
+ // 表格事件
666
+ handleSelectionChange: (selection: TableRow[]) => { selectedRows.value = selection; },
667
+
668
+ handleRowClick: (row: TableRow) => {
669
+ console.log("点击行数据:", row);
670
+ currentSelectedRow.value = row;
671
+ detailActiveTab.value = "detail";
672
+ loadExperimentData(row);
673
+ },
674
+
675
+ // 树节点事件
676
+ handleNodeClick: (data: TreeNode) => {
677
+ console.log('选中树节点:', data.label);
678
+ isTreeNodeSelected.value = true;
679
+ pagination.pageNum = 1;
680
+ loadTableData();
681
+ },
682
+
683
+ handleTreeTabChange: (tabName: string) => {
684
+ treeActiveTab.value = tabName;
685
+ loadTreeData();
686
+ },
687
+
688
+ // 列表行点击
689
+ handleListRowClick: (row: any) => {
690
+ activeTab.value = "detail";
691
+ unifiedQueryForm.specCode = row.specCode || "";
692
+ ElMessage.info(`已切换到详情页面: ${row.specCode}`);
693
+ }
694
+ });
695
+
696
+ // 导出事件处理器
697
+ export const {
698
+ handleSelectionChange, handleRowClick, handleNodeClick,
699
+ handleTreeTabChange, handleListRowClick
700
+ } = createEventHandlers();
701
+
702
+ export const handlePageChange = createPageHandler(pagination, loadTableData);
703
+ export const handleSizeChange = createSizeHandler(pagination, loadTableData);
704
+ export const handleListPageChange = createPageHandler(listPagination, loadListData);
705
+ export const handleListSizeChange = createSizeHandler(listPagination, loadListData);
706
+
707
+ // 实验项目分页处理
708
+ export const handleExperimentPageChange = (page: number) => {
709
+ if (currentSelectedRow.value) loadExperimentData(currentSelectedRow.value, page);
710
+ };
711
+
712
+ export const handleExperimentSizeChange = (size: number) => {
713
+ experimentPagination.pageSize = size;
714
+ handleExperimentPageChange(1);
715
+ };
716
+
717
+ // 行级操作
718
+ const createRowAction = (action: string) => (row: TableRow) => {
719
+ ElMessage.info(`${action}: ${row.indexNumber}`);
720
+ };
721
+
722
+ // 删除操作
723
+ export const handleDelete = () => {
724
+ if (selectedRows.value.length === 0) {
725
+ ElMessage.warning("请先选择要删除的数据");
726
+ return;
727
+ }
728
+ ElMessageBox.confirm("确定要删除选中的数据吗?", "提示", {
729
+ type: "warning"
730
+ }).then(() => {
731
+ ElMessage.success("删除成功");
732
+ loadTableData();
733
+ });
734
+ };
735
+
736
+ export const handleGaToGcRow = createRowAction("GA->GC");
737
+ export const handleCopyRow = createRowAction("复制行");
738
+
739
+ // 主表格编辑事件
740
+ export const handleEditRow = (row: TableRow) => {
741
+ console.log("编辑基本信息:", row);
742
+ // 检查是否在浏览器环境中并且有全局的弹窗引用
743
+ if (typeof window !== "undefined" && (window as any).mainModalRef) {
744
+ (window as any).mainModalRef.edit(row.id);
745
+ } else {
746
+ ElMessage.info(`编辑基本信息: ${row.indexNumber} (弹窗功能演示)`);
747
+ }
748
+ };
749
+ export const handleDeleteRow = (row: TableRow) => {
750
+ ElMessageBox.confirm(
751
+ `确定要删除索引号为 ${row.indexNumber} 的数据吗?`,
752
+ "提示",
753
+ { type: "warning" }
754
+ ).then(() => {
755
+ ElMessage.success("删除成功");
756
+ loadTableData();
757
+ });
758
+ };
759
+
760
+ // ========== 实验项目编辑相关 ==========
761
+ // 实验项目编辑事件
762
+ export const handleExperimentEdit = (row: ExperimentRow) => {
763
+ console.log("编辑实验项目:", row);
764
+ // 检查是否在浏览器环境中并且有全局的弹窗引用
765
+ if (typeof window !== "undefined" && (window as any).experimentModalRef) {
766
+ (window as any).experimentModalRef.edit(row.id);
767
+ } else {
768
+ ElMessage.info(`编辑实验项目: ${row.testItem} (弹窗功能演示)`);
769
+ }
770
+ };
771
+
772
+ // 主表格弹窗确认事件
773
+ export const handleMainModalOk = () => {
774
+ console.log("基本信息编辑完成");
775
+ // 重新加载表格数据
776
+ loadTableData();
777
+ };
778
+
779
+ // 弹窗确认事件
780
+ export const handleExperimentModalOk = () => {
781
+ console.log("实验项目编辑完成");
782
+ // 重新加载实验数据
783
+ if (currentSelectedRow.value) {
784
+ loadExperimentData(currentSelectedRow.value);
785
+ }
786
+ };
787
+
788
+ // ========== 弹窗引用管理 ==========
789
+ // 定义全局变量用于存储弹窗引用
790
+ let mainModalRef: any = null;
791
+ let experimentModalRef: any = null;
792
+
793
+ // 设置主表格弹窗引用(由Vue组件调用)
794
+ export const setMainModalRef = (ref: any) => {
795
+ mainModalRef = ref;
796
+ };
797
+
798
+ // 设置实验项目弹窗引用(由Vue组件调用)
799
+ export const setExperimentModalRef = (ref: any) => {
800
+ experimentModalRef = ref;
801
+ };
802
+
803
+ // 行合并逻辑已移至列配置的 span 函数中,无需手动处理
804
+
805
+ // ========== 生命周期管理 ==========
806
+ export const initData = () => {
807
+ loadTreeData();
808
+ // 重置状态:详情页面初始不加载表格数据,等待用户点击树节点
809
+ isTreeNodeSelected.value = false;
810
+ tableData.value = [];
811
+ experimentData.value = [];
812
+ updateKey.value = 0;
813
+ };
814
+
815
+ export const cleanup = () => {
816
+ // 清空缓存
817
+ dataCache.value.treeData = null;
818
+ dataCache.value.treeLoadTime = 0;
819
+ dataCache.value.experimentDataCache.clear();
820
+
821
+ // 清空选择状态
822
+ selectedRows.value = [];
823
+ currentSelectedRow.value = null;
824
+ isTreeNodeSelected.value = false;
825
825
  };