@agile-team/wl-skills-kit 2.2.0 → 2.3.1

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 (101) hide show
  1. package/CHANGELOG.md +33 -22
  2. package/README.md +11 -103
  3. package/bin/wl-skills.js +2 -42
  4. package/files/.github/guides/README.md +13 -13
  5. package/files/.github/guides/architecture.md +555 -555
  6. package/files/.github/guides/usage.md +176 -173
  7. package/files/.github/reports/README.md +65 -65
  8. package/files/.github/reports/SYS_DICT_INFO.md +50 -50
  9. package/files/.github/reports/SYS_MENU_INFO.md +247 -247
  10. package/files/.github/reports/SYS_PERMISSION_INFO.md +20 -20
  11. package/files/.github/reports//347/273/204/344/273/266/346/217/220/345/217/226/345/273/272/350/256/256.md +33 -33
  12. package/files/.github/reports//350/247/204/350/214/203/345/256/241/346/237/245/346/212/245/345/221/212.md +44 -44
  13. package/files/.github/skills/_compat/README.md +108 -108
  14. package/files/.github/skills/_compat/headers/agents.txt +8 -8
  15. package/files/.github/skills/_compat/headers/claude-code.txt +7 -7
  16. package/files/.github/skills/_compat/headers/cline.txt +7 -7
  17. package/files/.github/skills/_compat/headers/cursor-mdc.txt +16 -16
  18. package/files/.github/skills/_compat/headers/cursor-rules.txt +7 -7
  19. package/files/.github/skills/_compat/headers/github-copilot.txt +1 -1
  20. package/files/.github/skills/_compat/headers/kiro.txt +10 -10
  21. package/files/.github/skills/_compat/headers/trae.txt +11 -11
  22. package/files/.github/skills/_compat/headers/windsurf.txt +7 -7
  23. package/files/.github/skills/_registry.md +81 -81
  24. package/files/.github/skills/core/api-contract/SKILL.md +344 -344
  25. package/files/.github/skills/core/api-contract/USAGE.md +110 -110
  26. package/files/.github/skills/core/convention-audit/SKILL.md +189 -189
  27. package/files/.github/skills/core/convention-audit/USAGE.md +99 -99
  28. package/files/.github/skills/core/page-codegen/SKILL.md +973 -973
  29. package/files/.github/skills/core/page-codegen/USAGE.md +102 -102
  30. package/files/.github/skills/core/page-codegen/templates/_index.md +46 -46
  31. package/files/.github/skills/core/page-codegen/templates/domains/_CONTRIBUTING.md +107 -107
  32. package/files/.github/skills/core/page-codegen/templates/domains/produce/TPL-OPERATION-STATION.md +442 -442
  33. package/files/.github/skills/core/page-codegen/templates/domains/sale/README.md +26 -26
  34. package/files/.github/skills/core/page-codegen/templates/universal/TPL-CHANGE-HISTORY.md +276 -276
  35. package/files/.github/skills/core/page-codegen/templates/universal/TPL-DETAIL-TABS.md +1145 -1145
  36. package/files/.github/skills/core/page-codegen/templates/universal/TPL-DRIVEN.md +124 -124
  37. package/files/.github/skills/core/page-codegen/templates/universal/TPL-FORM-ROUTE.md +436 -436
  38. package/files/.github/skills/core/page-codegen/templates/universal/TPL-LIST.md +191 -191
  39. package/files/.github/skills/core/page-codegen/templates/universal/TPL-MASTER-DETAIL.md +148 -148
  40. package/files/.github/skills/core/page-codegen/templates/universal/TPL-RECORD-FORM.md +376 -376
  41. package/files/.github/skills/core/page-codegen/templates/universal/TPL-TREE-LIST.md +186 -186
  42. package/files/.github/skills/core/prototype-scan/SKILL.md +498 -498
  43. package/files/.github/skills/core/prototype-scan/USAGE.md +95 -95
  44. package/files/.github/skills/core/template-extract/SKILL.md +139 -139
  45. package/files/.github/skills/core/template-extract/USAGE.md +93 -93
  46. package/files/.github/skills/domain/README.md +51 -51
  47. package/files/.github/skills/sync/env.local.json +2 -1
  48. package/files/.github/skills/sync/menu-sync/SKILL.md +263 -263
  49. package/files/.github/skills/sync/menu-sync/USAGE.md +104 -104
  50. package/files/.github/skills/sync/menu-sync/env/env.local.json +7 -7
  51. package/files/.github/skills/sync/menu-sync/env/guide.md +99 -99
  52. package/files/.github/skills/sync/permission-sync/SKILL.draft.md +91 -91
  53. package/files/.github/standards/01-toolchain.md +57 -57
  54. package/files/.github/standards/02-code-structure.md +111 -111
  55. package/files/.github/standards/03-comments.md +53 -53
  56. package/files/.github/standards/04-coding-basics.md +33 -33
  57. package/files/.github/standards/05-logging.md +38 -38
  58. package/files/.github/standards/06-security.md +44 -44
  59. package/files/.github/standards/07-config.md +52 -52
  60. package/files/.github/standards/08-git.md +60 -60
  61. package/files/.github/standards/09-typescript.md +71 -71
  62. package/files/.github/standards/10-pinia.md +57 -57
  63. package/files/.github/standards/11-form-validation.md +81 -81
  64. package/files/.github/standards/12-base-table.md +153 -153
  65. package/files/.github/standards/13-platform-components.md +123 -123
  66. package/files/.github/standards/index.md +89 -89
  67. package/files/demo/produce/aiflow/mmwr-customer-apply-add/api.md +1 -1
  68. package/files/demo/produce/aiflow/mmwr-customer-apply-change-history/data.ts +196 -196
  69. package/files/demo/produce/aiflow/mmwr-customer-apply-change-history/index.scss +150 -150
  70. package/files/demo/produce/aiflow/mmwr-customer-apply-change-history/index.vue +79 -79
  71. package/files/docs/jh-date-range.md +257 -257
  72. package/files/docs/jh-date.md +222 -222
  73. package/files/docs/jh-dept-picker.md +190 -190
  74. package/files/docs/jh-drag-row.md +590 -590
  75. package/files/docs/jh-file-upload.md +216 -216
  76. package/files/docs/jh-picker.md +218 -218
  77. package/files/docs/jh-select.md +148 -148
  78. package/files/docs/jh-text.md +248 -248
  79. package/files/docs/jh-user-picker.md +197 -197
  80. package/files/docs/request.md +24 -9
  81. package/files/src/components/global/C_RightToolbar/data.ts +228 -0
  82. package/files/src/components/global/C_RightToolbar/index.scss +44 -0
  83. package/files/src/components/global/C_RightToolbar/index.vue +34 -336
  84. package/files/src/components/global/C_Splitter/index.scss +61 -0
  85. package/files/src/components/global/C_Splitter/index.vue +2 -64
  86. package/files/src/components/global/C_SvgIcon/index.scss +15 -0
  87. package/files/src/components/global/C_SvgIcon/index.vue +20 -50
  88. package/files/src/components/global/C_TagStatus/index.scss +20 -0
  89. package/files/src/components/global/C_TagStatus/index.vue +1 -22
  90. package/files/src/components/global/C_Tree/data.ts +61 -0
  91. package/files/src/components/global/C_Tree/index.vue +12 -53
  92. package/files/src/components/local/c_listModal/index.scss +4 -0
  93. package/files/src/components/local/c_listModal/index.vue +1 -1
  94. package/mcp/api/client.js +76 -0
  95. package/mcp/api/dictApi.js +40 -0
  96. package/mcp/api/menuApi.js +32 -0
  97. package/mcp/config.js +47 -0
  98. package/mcp/server.js +210 -0
  99. package/mcp/tools/dictSync.js +173 -0
  100. package/mcp/tools/menuSync.js +96 -0
  101. package/package.json +6 -9
@@ -1,442 +1,442 @@
1
- # OPERATION_STATION:工序操作站
2
-
3
- > **适用场景**:生产域工序操作页,有"待处理清单"与"已完成清单"两个联动表格,选中行后填写操作表单执行动作(完轧/取消完轧/入炉/出炉等)。
4
- > **识别特征**:原型中出现"待完X/待入炉/待处理"清单 + 操作区表单 + "已完X/已入炉"清单;操作按钮的可用性依赖当前选中行状态。
5
- > **布局核心**:`jh-drag-row` 嵌套(待处理↔已处理↔操作区),或 `el-tabs` 包裹分功能区域。
6
- > **⚠️ 重要约束**:此模板 index.vue 包含大量业务逻辑(computed 可用状态、watch 联动、多列表协调),**不同于 Template A/B 的薄 index.vue 风格**。data.ts 导出多个 `createXxxPage()`,不使用 `c_formModal`,改用内联 `BaseForm` + `el-button`。
7
- > **参考标杆**:`src/views/produce/production-mmwr/sjgl/mmwr-rolling-management/`
8
-
9
- ---
10
-
11
- ## 识别规则
12
-
13
- AI 识别此模式时需满足以下条件:
14
-
15
- 1. **双清单联动**:原型中有两个数据列表(待处理/已处理),且两个列表共享同一套查询条件
16
- 2. **操作表单内联**:表单字段不在弹窗内,直接展示在页面操作区域
17
- 3. **条件按钮**:主操作按钮(如"完轧"/"取消完轧")的 `disabled` 状态取决于选中行 + 表单字段
18
- 4. **状态切换**:执行操作后两个清单同时刷新,选中状态清空
19
-
20
- ---
21
-
22
- ## data.ts 结构
23
-
24
- ```typescript
25
- import {
26
- AbstractPageQueryHook,
27
- BaseQueryItemDesc,
28
- TableColumnDesc,
29
- ActionButtonDesc
30
- } from "@/types/page";
31
- import { postAction } from "@jhlc/common-core/src/api/action";
32
- import { ElMessage, ElMessageBox } from "element-plus";
33
-
34
- export const API_CONFIG = {
35
- pendingList: "/[服务缩写]/[资源名]/pendingList", // 待处理清单
36
- completedList: "/[服务缩写]/[资源名]/completedList", // 已完成清单
37
- detailList: "/[服务缩写]/[资源名]/detailList", // 明细(可选)
38
- doAction: "/[服务缩写]/[资源名]/[操作名]", // 主操作(完轧/入炉等)
39
- cancelAction: "/[服务缩写]/[资源名]/cancel[操作名]" // 取消操作
40
- } as const;
41
-
42
- // ===== 操作表单数据重置 =====
43
- export function resetOperationFormData() {
44
- return {
45
- mainId: "",
46
- [formField1]: "",
47
- [formField2]: ""
48
- };
49
- }
50
-
51
- // ===== 操作表单字段配置 =====
52
- export const operationFormItems = [
53
- { name: "[formField1]", label: "[字段名]", disabled: true },
54
- { name: "[formField2]", label: "[字段名2]" }
55
- ];
56
-
57
- // ===== 待处理清单 =====
58
- export function createPendingPage() {
59
- let Page = new (class extends AbstractPageQueryHook {
60
- constructor() {
61
- super({ url: { list: API_CONFIG.pendingList } });
62
- }
63
- queryDef(): BaseQueryItemDesc<any>[] {
64
- return [
65
- { name: "[queryField]", label: "[查询字段]", placeholder: "请输入" }
66
- ];
67
- }
68
- toolbarDef(): ActionButtonDesc[] { return []; }
69
- columnsDef(): TableColumnDesc<any>[] {
70
- return [
71
- { type: "index" },
72
- { label: "[列名]", name: "[fieldName]", minWidth: 120 }
73
- ];
74
- }
75
- })();
76
- return (Page as any).create() as any;
77
- }
78
-
79
- // ===== 已完成清单 =====
80
- export function createCompletedPage() {
81
- let Page = new (class extends AbstractPageQueryHook {
82
- constructor() {
83
- super({ url: { list: API_CONFIG.completedList } });
84
- }
85
- queryDef(): BaseQueryItemDesc<any>[] { return []; }
86
- toolbarDef(): ActionButtonDesc[] { return []; }
87
- columnsDef(): TableColumnDesc<any>[] {
88
- return [
89
- { type: "index" },
90
- { label: "[列名]", name: "[fieldName]", minWidth: 120 }
91
- ];
92
- }
93
- })();
94
- return (Page as any).create() as any;
95
- }
96
-
97
- // ===== 明细清单(可选,如有第三级明细) =====
98
- export function createDetailPage() {
99
- let Page = new (class extends AbstractPageQueryHook {
100
- constructor() {
101
- super({ url: { list: API_CONFIG.detailList } });
102
- }
103
- queryDef(): BaseQueryItemDesc<any>[] { return []; }
104
- toolbarDef(): ActionButtonDesc[] { return []; }
105
- columnsDef(): TableColumnDesc<any>[] {
106
- return [{ type: "index" }, { label: "[明细列]", name: "[fieldName]", minWidth: 120 }];
107
- }
108
- })();
109
- const created = (Page as any).create() as any;
110
- // 按主记录查询明细
111
- function selectByMain(row: any) {
112
- created.queryParam.value.mainId = row.id;
113
- created.select();
114
- }
115
- return { ...created, selectByMain };
116
- }
117
-
118
- // ===== 主操作(需回调刷新页面) =====
119
- export async function handleDoAction(form: any, onSuccess: () => void) {
120
- await ElMessageBox.confirm("确认执行[操作名]?", "提示", { type: "warning" });
121
- await postAction(API_CONFIG.doAction, form);
122
- ElMessage.success("操作成功");
123
- onSuccess();
124
- }
125
-
126
- export async function handleCancelAction(form: any, onSuccess: () => void) {
127
- await ElMessageBox.confirm("确认取消[操作名]?", "提示", { type: "warning" });
128
- await postAction(API_CONFIG.cancelAction, form);
129
- ElMessage.success("取消成功");
130
- onSuccess();
131
- }
132
- ```
133
-
134
- ---
135
-
136
- ## index.vue
137
-
138
- ```vue
139
- <template>
140
- <div class="app-container app-page-container [page-class]">
141
- <jh-drag-row :top-height="300">
142
- <template #top>
143
- <!-- 查询区 -->
144
- <BaseQuery
145
- :form="queryParam"
146
- :items="queryItems"
147
- :columns="5"
148
- :auto-select="false"
149
- @select="handleQuery"
150
- @reset="handleQuery"
151
- />
152
- <!-- 待处理清单 -->
153
- <div class="list-section">
154
- <div class="section-header">
155
- <div class="title-bar"></div>
156
- <h3 class="section-title">待[处理/完X]清单</h3>
157
- </div>
158
- <BaseTable
159
- ref="pendingTableRef"
160
- :data="pendingList"
161
- :columns="pendingColumns"
162
- showToolbar
163
- highlight-current-row
164
- @current-change="handlePendingCurrentChange"
165
- />
166
- <jh-pagination
167
- v-show="pendingPage.total > 0"
168
- :total="pendingPage.total || 0"
169
- v-model:currentPage="pendingPage.current"
170
- v-model:pageSize="pendingPage.size"
171
- @current-change="pendingSelect"
172
- @size-change="pendingSelect"
173
- />
174
- </div>
175
- </template>
176
-
177
- <template #bottom>
178
- <jh-drag-row :top-height="280">
179
- <template #top>
180
- <!-- 已完成清单 -->
181
- <div class="list-section">
182
- <div class="section-header">
183
- <div class="title-bar"></div>
184
- <h3 class="section-title">已[完X/入炉]清单</h3>
185
- </div>
186
- <BaseTable
187
- ref="completedTableRef"
188
- :data="completedList"
189
- :columns="completedColumns"
190
- showToolbar
191
- highlight-current-row
192
- @current-change="handleCompletedCurrentChange"
193
- />
194
- <jh-pagination
195
- v-show="completedPage.total > 0"
196
- :total="completedPage.total || 0"
197
- v-model:currentPage="completedPage.current"
198
- v-model:pageSize="completedPage.size"
199
- @current-change="completedSelect"
200
- @size-change="completedSelect"
201
- />
202
- </div>
203
- </template>
204
-
205
- <template #bottom>
206
- <!-- 操作区 -->
207
- <div class="operation-area">
208
- <div class="operation-form">
209
- <BaseForm
210
- :form="operationForm"
211
- :items="operationFormItems"
212
- :columns="4"
213
- :label-width="120"
214
- />
215
- </div>
216
- <div class="operation-buttons">
217
- <!-- 选中待处理行时显示 -->
218
- <el-button
219
- v-show="currentListType === 'pending'"
220
- type="primary"
221
- :disabled="!canDoAction"
222
- @click="handleDoActionClick"
223
- >
224
- [操作名]
225
- </el-button>
226
- <!-- 选中已完成行时显示 -->
227
- <el-button
228
- v-show="currentListType === 'completed'"
229
- type="danger"
230
- :disabled="!canCancelAction"
231
- @click="handleCancelActionClick"
232
- >
233
- 取消[操作名]
234
- </el-button>
235
- </div>
236
- <!-- 可选:明细区 -->
237
- <div class="detail-section">
238
- <div class="section-header">
239
- <div class="title-bar"></div>
240
- <h3 class="section-title">[明细名]</h3>
241
- </div>
242
- <div v-if="!selectedRow" class="empty-tip">
243
- <el-empty description="请先在上方选择一行数据" />
244
- </div>
245
- <BaseTable v-else :data="detailList" :columns="detailColumns" showToolbar />
246
- </div>
247
- </div>
248
- </template>
249
- </jh-drag-row>
250
- </template>
251
- </jh-drag-row>
252
- </div>
253
- </template>
254
-
255
- <script setup lang="ts">
256
- import { ref, computed, onMounted } from "vue";
257
- import { ElMessage } from "element-plus";
258
- import {
259
- createPendingPage,
260
- createCompletedPage,
261
- createDetailPage,
262
- handleDoAction,
263
- handleCancelAction,
264
- operationFormItems,
265
- resetOperationFormData
266
- } from "./data";
267
-
268
- // ===== 状态 =====
269
- const currentListType = ref<"pending" | "completed" | "">("");
270
- const selectedRow = ref<any>(null);
271
-
272
- // ===== 页面实例 =====
273
- const PendingPage = createPendingPage();
274
- const {
275
- tableRef: pendingTableRef,
276
- page: pendingPage,
277
- queryParam,
278
- list: pendingList,
279
- queryItems,
280
- columns: pendingColumns,
281
- select: pendingSelect
282
- } = PendingPage;
283
-
284
- const CompletedPage = createCompletedPage();
285
- const {
286
- tableRef: completedTableRef,
287
- page: completedPage,
288
- list: completedList,
289
- columns: completedColumns,
290
- select: completedSelect
291
- } = CompletedPage;
292
-
293
- const DetailPage = createDetailPage();
294
- const { list: detailList, columns: detailColumns, selectByMain } = DetailPage;
295
-
296
- // ===== 操作表单 =====
297
- const operationForm = ref(resetOperationFormData());
298
-
299
- // ===== 可用状态 =====
300
- const canDoAction = computed(
301
- () => currentListType.value === "pending" && selectedRow.value && !!operationForm.value.mainId
302
- );
303
- const canCancelAction = computed(
304
- () => currentListType.value === "completed" && selectedRow.value && !!operationForm.value.mainId
305
- );
306
-
307
- // ===== 清空选中 =====
308
- const clearSelection = () => {
309
- selectedRow.value = null;
310
- currentListType.value = "";
311
- operationForm.value = resetOperationFormData();
312
- };
313
-
314
- // ===== 查询(同步刷新两个清单) =====
315
- const handleQuery = () => {
316
- clearSelection();
317
- pendingSelect();
318
- completedSelect();
319
- };
320
-
321
- // ===== 行选中处理 =====
322
- const handleRowChange = (row: any, listType: "pending" | "completed") => {
323
- selectedRow.value = row;
324
- currentListType.value = listType;
325
- if (row) {
326
- operationForm.value = {
327
- mainId: row.id || row.[mainIdField],
328
- [formField1]: row.[sourceField1] || "",
329
- [formField2]: row.[sourceField2] || ""
330
- };
331
- selectByMain(row);
332
- } else {
333
- clearSelection();
334
- }
335
- };
336
-
337
- const handlePendingCurrentChange = (row: any) => handleRowChange(row, "pending");
338
- const handleCompletedCurrentChange = (row: any) => handleRowChange(row, "completed");
339
-
340
- // ===== 操作按钮 =====
341
- const handleDoActionClick = async () => {
342
- await handleDoAction(operationForm.value, () => {
343
- pendingSelect();
344
- completedSelect();
345
- clearSelection();
346
- });
347
- };
348
-
349
- const handleCancelActionClick = async () => {
350
- await handleCancelAction(operationForm.value, () => {
351
- pendingSelect();
352
- completedSelect();
353
- clearSelection();
354
- });
355
- };
356
-
357
- // ===== 初始化 =====
358
- onMounted(() => handleQuery());
359
- </script>
360
-
361
- <style scoped lang="scss">
362
- @import "./index.scss";
363
- </style>
364
- ```
365
-
366
- ---
367
-
368
- ## index.scss
369
-
370
- ```scss
371
- .[page-class] {
372
- .list-section {
373
- .section-header {
374
- display: flex;
375
- align-items: center;
376
- margin-bottom: 8px;
377
-
378
- .title-bar {
379
- width: 4px;
380
- height: 16px;
381
- background: var(--el-color-primary);
382
- border-radius: 2px;
383
- margin-right: 8px;
384
- }
385
-
386
- .section-title {
387
- font-size: 14px;
388
- font-weight: 600;
389
- color: #303133;
390
- margin: 0;
391
- }
392
- }
393
- }
394
-
395
- .operation-area {
396
- padding: 12px;
397
- display: flex;
398
- flex-direction: column;
399
- gap: 12px;
400
-
401
- .operation-buttons {
402
- display: flex;
403
- gap: 8px;
404
- }
405
- }
406
-
407
- .empty-tip {
408
- display: flex;
409
- justify-content: center;
410
- padding: 24px 0;
411
- }
412
- }
413
- ```
414
-
415
- ---
416
-
417
- ## 变体:el-tabs 包裹多功能区
418
-
419
- 当同一页面有"录入"与"查询"两个功能区(分 Tab 切换),在最外层包一个 `el-tabs`,每个 tab pane 内各自包含独立的查询/列表结构。参考 `mmwr-steel-stripping-operations`:
420
-
421
- ```vue
422
- <el-tabs v-model="activeTab" type="border-card">
423
- <el-tab-pane label="[操作功能]" name="entry">
424
- <!-- jh-drag-row 内放待处理+操作区 -->
425
- </el-tab-pane>
426
- <el-tab-pane label="[查询功能]" name="query">
427
- <!-- 标准 BaseQuery + BaseTable + jh-pagination (Template A 结构) -->
428
- </el-tab-pane>
429
- </el-tabs>
430
- ```
431
-
432
- `watch(activeTab, (tab) => { if(tab === 'entry') entrySelect(); else querySelect(); })`
433
-
434
- ---
435
-
436
- ## 注意事项
437
-
438
- - **不使用 c_formModal**:操作表单是内联的,不是弹窗
439
- - **不使用 BaseToolbar 传递主操作按钮**:主操作按钮(完轧/取消完轧)用 `el-button` + `v-show` + `:disabled` 直接控制
440
- - **两个清单同步刷新**:操作成功后 `pendingSelect()` 和 `completedSelect()` 同时调用
441
- - **行选中互斥**:选中待处理行时清除已完成行的高亮,反之亦然(`highlight-current-row` 配合状态管理实现)
442
- - **查询区只属于待处理清单**:已完成清单跟随相同的 queryParam,但不单独渲染 BaseQuery
1
+ # OPERATION_STATION:工序操作站
2
+
3
+ > **适用场景**:生产域工序操作页,有"待处理清单"与"已完成清单"两个联动表格,选中行后填写操作表单执行动作(完轧/取消完轧/入炉/出炉等)。
4
+ > **识别特征**:原型中出现"待完X/待入炉/待处理"清单 + 操作区表单 + "已完X/已入炉"清单;操作按钮的可用性依赖当前选中行状态。
5
+ > **布局核心**:`jh-drag-row` 嵌套(待处理↔已处理↔操作区),或 `el-tabs` 包裹分功能区域。
6
+ > **⚠️ 重要约束**:此模板 index.vue 包含大量业务逻辑(computed 可用状态、watch 联动、多列表协调),**不同于 Template A/B 的薄 index.vue 风格**。data.ts 导出多个 `createXxxPage()`,不使用 `c_formModal`,改用内联 `BaseForm` + `el-button`。
7
+ > **参考标杆**:`src/views/produce/production-mmwr/sjgl/mmwr-rolling-management/`
8
+
9
+ ---
10
+
11
+ ## 识别规则
12
+
13
+ AI 识别此模式时需满足以下条件:
14
+
15
+ 1. **双清单联动**:原型中有两个数据列表(待处理/已处理),且两个列表共享同一套查询条件
16
+ 2. **操作表单内联**:表单字段不在弹窗内,直接展示在页面操作区域
17
+ 3. **条件按钮**:主操作按钮(如"完轧"/"取消完轧")的 `disabled` 状态取决于选中行 + 表单字段
18
+ 4. **状态切换**:执行操作后两个清单同时刷新,选中状态清空
19
+
20
+ ---
21
+
22
+ ## data.ts 结构
23
+
24
+ ```typescript
25
+ import {
26
+ AbstractPageQueryHook,
27
+ BaseQueryItemDesc,
28
+ TableColumnDesc,
29
+ ActionButtonDesc
30
+ } from "@/types/page";
31
+ import { postAction } from "@jhlc/common-core/src/api/action";
32
+ import { ElMessage, ElMessageBox } from "element-plus";
33
+
34
+ export const API_CONFIG = {
35
+ pendingList: "/[服务缩写]/[资源名]/pendingList", // 待处理清单
36
+ completedList: "/[服务缩写]/[资源名]/completedList", // 已完成清单
37
+ detailList: "/[服务缩写]/[资源名]/detailList", // 明细(可选)
38
+ doAction: "/[服务缩写]/[资源名]/[操作名]", // 主操作(完轧/入炉等)
39
+ cancelAction: "/[服务缩写]/[资源名]/cancel[操作名]" // 取消操作
40
+ } as const;
41
+
42
+ // ===== 操作表单数据重置 =====
43
+ export function resetOperationFormData() {
44
+ return {
45
+ mainId: "",
46
+ [formField1]: "",
47
+ [formField2]: ""
48
+ };
49
+ }
50
+
51
+ // ===== 操作表单字段配置 =====
52
+ export const operationFormItems = [
53
+ { name: "[formField1]", label: "[字段名]", disabled: true },
54
+ { name: "[formField2]", label: "[字段名2]" }
55
+ ];
56
+
57
+ // ===== 待处理清单 =====
58
+ export function createPendingPage() {
59
+ let Page = new (class extends AbstractPageQueryHook {
60
+ constructor() {
61
+ super({ url: { list: API_CONFIG.pendingList } });
62
+ }
63
+ queryDef(): BaseQueryItemDesc<any>[] {
64
+ return [
65
+ { name: "[queryField]", label: "[查询字段]", placeholder: "请输入" }
66
+ ];
67
+ }
68
+ toolbarDef(): ActionButtonDesc[] { return []; }
69
+ columnsDef(): TableColumnDesc<any>[] {
70
+ return [
71
+ { type: "index" },
72
+ { label: "[列名]", name: "[fieldName]", minWidth: 120 }
73
+ ];
74
+ }
75
+ })();
76
+ return (Page as any).create() as any;
77
+ }
78
+
79
+ // ===== 已完成清单 =====
80
+ export function createCompletedPage() {
81
+ let Page = new (class extends AbstractPageQueryHook {
82
+ constructor() {
83
+ super({ url: { list: API_CONFIG.completedList } });
84
+ }
85
+ queryDef(): BaseQueryItemDesc<any>[] { return []; }
86
+ toolbarDef(): ActionButtonDesc[] { return []; }
87
+ columnsDef(): TableColumnDesc<any>[] {
88
+ return [
89
+ { type: "index" },
90
+ { label: "[列名]", name: "[fieldName]", minWidth: 120 }
91
+ ];
92
+ }
93
+ })();
94
+ return (Page as any).create() as any;
95
+ }
96
+
97
+ // ===== 明细清单(可选,如有第三级明细) =====
98
+ export function createDetailPage() {
99
+ let Page = new (class extends AbstractPageQueryHook {
100
+ constructor() {
101
+ super({ url: { list: API_CONFIG.detailList } });
102
+ }
103
+ queryDef(): BaseQueryItemDesc<any>[] { return []; }
104
+ toolbarDef(): ActionButtonDesc[] { return []; }
105
+ columnsDef(): TableColumnDesc<any>[] {
106
+ return [{ type: "index" }, { label: "[明细列]", name: "[fieldName]", minWidth: 120 }];
107
+ }
108
+ })();
109
+ const created = (Page as any).create() as any;
110
+ // 按主记录查询明细
111
+ function selectByMain(row: any) {
112
+ created.queryParam.value.mainId = row.id;
113
+ created.select();
114
+ }
115
+ return { ...created, selectByMain };
116
+ }
117
+
118
+ // ===== 主操作(需回调刷新页面) =====
119
+ export async function handleDoAction(form: any, onSuccess: () => void) {
120
+ await ElMessageBox.confirm("确认执行[操作名]?", "提示", { type: "warning" });
121
+ await postAction(API_CONFIG.doAction, form);
122
+ ElMessage.success("操作成功");
123
+ onSuccess();
124
+ }
125
+
126
+ export async function handleCancelAction(form: any, onSuccess: () => void) {
127
+ await ElMessageBox.confirm("确认取消[操作名]?", "提示", { type: "warning" });
128
+ await postAction(API_CONFIG.cancelAction, form);
129
+ ElMessage.success("取消成功");
130
+ onSuccess();
131
+ }
132
+ ```
133
+
134
+ ---
135
+
136
+ ## index.vue
137
+
138
+ ```vue
139
+ <template>
140
+ <div class="app-container app-page-container [page-class]">
141
+ <jh-drag-row :top-height="300">
142
+ <template #top>
143
+ <!-- 查询区 -->
144
+ <BaseQuery
145
+ :form="queryParam"
146
+ :items="queryItems"
147
+ :columns="5"
148
+ :auto-select="false"
149
+ @select="handleQuery"
150
+ @reset="handleQuery"
151
+ />
152
+ <!-- 待处理清单 -->
153
+ <div class="list-section">
154
+ <div class="section-header">
155
+ <div class="title-bar"></div>
156
+ <h3 class="section-title">待[处理/完X]清单</h3>
157
+ </div>
158
+ <BaseTable
159
+ ref="pendingTableRef"
160
+ :data="pendingList"
161
+ :columns="pendingColumns"
162
+ showToolbar
163
+ highlight-current-row
164
+ @current-change="handlePendingCurrentChange"
165
+ />
166
+ <jh-pagination
167
+ v-show="pendingPage.total > 0"
168
+ :total="pendingPage.total || 0"
169
+ v-model:currentPage="pendingPage.current"
170
+ v-model:pageSize="pendingPage.size"
171
+ @current-change="pendingSelect"
172
+ @size-change="pendingSelect"
173
+ />
174
+ </div>
175
+ </template>
176
+
177
+ <template #bottom>
178
+ <jh-drag-row :top-height="280">
179
+ <template #top>
180
+ <!-- 已完成清单 -->
181
+ <div class="list-section">
182
+ <div class="section-header">
183
+ <div class="title-bar"></div>
184
+ <h3 class="section-title">已[完X/入炉]清单</h3>
185
+ </div>
186
+ <BaseTable
187
+ ref="completedTableRef"
188
+ :data="completedList"
189
+ :columns="completedColumns"
190
+ showToolbar
191
+ highlight-current-row
192
+ @current-change="handleCompletedCurrentChange"
193
+ />
194
+ <jh-pagination
195
+ v-show="completedPage.total > 0"
196
+ :total="completedPage.total || 0"
197
+ v-model:currentPage="completedPage.current"
198
+ v-model:pageSize="completedPage.size"
199
+ @current-change="completedSelect"
200
+ @size-change="completedSelect"
201
+ />
202
+ </div>
203
+ </template>
204
+
205
+ <template #bottom>
206
+ <!-- 操作区 -->
207
+ <div class="operation-area">
208
+ <div class="operation-form">
209
+ <BaseForm
210
+ :form="operationForm"
211
+ :items="operationFormItems"
212
+ :columns="4"
213
+ :label-width="120"
214
+ />
215
+ </div>
216
+ <div class="operation-buttons">
217
+ <!-- 选中待处理行时显示 -->
218
+ <el-button
219
+ v-show="currentListType === 'pending'"
220
+ type="primary"
221
+ :disabled="!canDoAction"
222
+ @click="handleDoActionClick"
223
+ >
224
+ [操作名]
225
+ </el-button>
226
+ <!-- 选中已完成行时显示 -->
227
+ <el-button
228
+ v-show="currentListType === 'completed'"
229
+ type="danger"
230
+ :disabled="!canCancelAction"
231
+ @click="handleCancelActionClick"
232
+ >
233
+ 取消[操作名]
234
+ </el-button>
235
+ </div>
236
+ <!-- 可选:明细区 -->
237
+ <div class="detail-section">
238
+ <div class="section-header">
239
+ <div class="title-bar"></div>
240
+ <h3 class="section-title">[明细名]</h3>
241
+ </div>
242
+ <div v-if="!selectedRow" class="empty-tip">
243
+ <el-empty description="请先在上方选择一行数据" />
244
+ </div>
245
+ <BaseTable v-else :data="detailList" :columns="detailColumns" showToolbar />
246
+ </div>
247
+ </div>
248
+ </template>
249
+ </jh-drag-row>
250
+ </template>
251
+ </jh-drag-row>
252
+ </div>
253
+ </template>
254
+
255
+ <script setup lang="ts">
256
+ import { ref, computed, onMounted } from "vue";
257
+ import { ElMessage } from "element-plus";
258
+ import {
259
+ createPendingPage,
260
+ createCompletedPage,
261
+ createDetailPage,
262
+ handleDoAction,
263
+ handleCancelAction,
264
+ operationFormItems,
265
+ resetOperationFormData
266
+ } from "./data";
267
+
268
+ // ===== 状态 =====
269
+ const currentListType = ref<"pending" | "completed" | "">("");
270
+ const selectedRow = ref<any>(null);
271
+
272
+ // ===== 页面实例 =====
273
+ const PendingPage = createPendingPage();
274
+ const {
275
+ tableRef: pendingTableRef,
276
+ page: pendingPage,
277
+ queryParam,
278
+ list: pendingList,
279
+ queryItems,
280
+ columns: pendingColumns,
281
+ select: pendingSelect
282
+ } = PendingPage;
283
+
284
+ const CompletedPage = createCompletedPage();
285
+ const {
286
+ tableRef: completedTableRef,
287
+ page: completedPage,
288
+ list: completedList,
289
+ columns: completedColumns,
290
+ select: completedSelect
291
+ } = CompletedPage;
292
+
293
+ const DetailPage = createDetailPage();
294
+ const { list: detailList, columns: detailColumns, selectByMain } = DetailPage;
295
+
296
+ // ===== 操作表单 =====
297
+ const operationForm = ref(resetOperationFormData());
298
+
299
+ // ===== 可用状态 =====
300
+ const canDoAction = computed(
301
+ () => currentListType.value === "pending" && selectedRow.value && !!operationForm.value.mainId
302
+ );
303
+ const canCancelAction = computed(
304
+ () => currentListType.value === "completed" && selectedRow.value && !!operationForm.value.mainId
305
+ );
306
+
307
+ // ===== 清空选中 =====
308
+ const clearSelection = () => {
309
+ selectedRow.value = null;
310
+ currentListType.value = "";
311
+ operationForm.value = resetOperationFormData();
312
+ };
313
+
314
+ // ===== 查询(同步刷新两个清单) =====
315
+ const handleQuery = () => {
316
+ clearSelection();
317
+ pendingSelect();
318
+ completedSelect();
319
+ };
320
+
321
+ // ===== 行选中处理 =====
322
+ const handleRowChange = (row: any, listType: "pending" | "completed") => {
323
+ selectedRow.value = row;
324
+ currentListType.value = listType;
325
+ if (row) {
326
+ operationForm.value = {
327
+ mainId: row.id || row.[mainIdField],
328
+ [formField1]: row.[sourceField1] || "",
329
+ [formField2]: row.[sourceField2] || ""
330
+ };
331
+ selectByMain(row);
332
+ } else {
333
+ clearSelection();
334
+ }
335
+ };
336
+
337
+ const handlePendingCurrentChange = (row: any) => handleRowChange(row, "pending");
338
+ const handleCompletedCurrentChange = (row: any) => handleRowChange(row, "completed");
339
+
340
+ // ===== 操作按钮 =====
341
+ const handleDoActionClick = async () => {
342
+ await handleDoAction(operationForm.value, () => {
343
+ pendingSelect();
344
+ completedSelect();
345
+ clearSelection();
346
+ });
347
+ };
348
+
349
+ const handleCancelActionClick = async () => {
350
+ await handleCancelAction(operationForm.value, () => {
351
+ pendingSelect();
352
+ completedSelect();
353
+ clearSelection();
354
+ });
355
+ };
356
+
357
+ // ===== 初始化 =====
358
+ onMounted(() => handleQuery());
359
+ </script>
360
+
361
+ <style scoped lang="scss">
362
+ @import "./index.scss";
363
+ </style>
364
+ ```
365
+
366
+ ---
367
+
368
+ ## index.scss
369
+
370
+ ```scss
371
+ .[page-class] {
372
+ .list-section {
373
+ .section-header {
374
+ display: flex;
375
+ align-items: center;
376
+ margin-bottom: 8px;
377
+
378
+ .title-bar {
379
+ width: 4px;
380
+ height: 16px;
381
+ background: var(--el-color-primary);
382
+ border-radius: 2px;
383
+ margin-right: 8px;
384
+ }
385
+
386
+ .section-title {
387
+ font-size: 14px;
388
+ font-weight: 600;
389
+ color: #303133;
390
+ margin: 0;
391
+ }
392
+ }
393
+ }
394
+
395
+ .operation-area {
396
+ padding: 12px;
397
+ display: flex;
398
+ flex-direction: column;
399
+ gap: 12px;
400
+
401
+ .operation-buttons {
402
+ display: flex;
403
+ gap: 8px;
404
+ }
405
+ }
406
+
407
+ .empty-tip {
408
+ display: flex;
409
+ justify-content: center;
410
+ padding: 24px 0;
411
+ }
412
+ }
413
+ ```
414
+
415
+ ---
416
+
417
+ ## 变体:el-tabs 包裹多功能区
418
+
419
+ 当同一页面有"录入"与"查询"两个功能区(分 Tab 切换),在最外层包一个 `el-tabs`,每个 tab pane 内各自包含独立的查询/列表结构。参考 `mmwr-steel-stripping-operations`:
420
+
421
+ ```vue
422
+ <el-tabs v-model="activeTab" type="border-card">
423
+ <el-tab-pane label="[操作功能]" name="entry">
424
+ <!-- jh-drag-row 内放待处理+操作区 -->
425
+ </el-tab-pane>
426
+ <el-tab-pane label="[查询功能]" name="query">
427
+ <!-- 标准 BaseQuery + BaseTable + jh-pagination (Template A 结构) -->
428
+ </el-tab-pane>
429
+ </el-tabs>
430
+ ```
431
+
432
+ `watch(activeTab, (tab) => { if(tab === 'entry') entrySelect(); else querySelect(); })`
433
+
434
+ ---
435
+
436
+ ## 注意事项
437
+
438
+ - **不使用 c_formModal**:操作表单是内联的,不是弹窗
439
+ - **不使用 BaseToolbar 传递主操作按钮**:主操作按钮(完轧/取消完轧)用 `el-button` + `v-show` + `:disabled` 直接控制
440
+ - **两个清单同步刷新**:操作成功后 `pendingSelect()` 和 `completedSelect()` 同时调用
441
+ - **行选中互斥**:选中待处理行时清除已完成行的高亮,反之亦然(`highlight-current-row` 配合状态管理实现)
442
+ - **查询区只属于待处理清单**:已完成清单跟随相同的 queryParam,但不单独渲染 BaseQuery