@agile-team/wl-skills-kit 2.4.2 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -1
- package/README.md +65 -44
- package/bin/wl-skills.js +196 -12
- package/files/.github/copilot-instructions.md +361 -322
- package/files/.github/guides/architecture.md +1 -1
- package/files/.github/guides/usage.md +32 -10
- package/files/.github/skills/_registry.md +18 -16
- package/files/.github/skills/core/page-codegen/SKILL.md +149 -74
- package/files/.github/skills/core/page-codegen/USAGE.md +33 -12
- package/files/.github/skills/core/page-codegen/templates/universal/TPL-DETAIL-TABS.md +80 -48
- package/files/.github/skills/core/page-codegen/templates/universal/TPL-FORM-ROUTE.md +183 -55
- package/files/.github/skills/core/page-codegen/templates/universal/TPL-LIST.md +110 -21
- package/files/.github/skills/core/page-codegen/templates/universal/TPL-MASTER-DETAIL.md +29 -9
- package/files/.github/skills/core/page-codegen/templates/universal/TPL-RECORD-FORM.md +93 -48
- package/files/.github/skills/core/page-codegen/templates/universal/TPL-TREE-LIST.md +49 -29
- package/files/.github/skills/sync/menu-sync/SKILL.md +27 -13
- package/mcp/server.js +279 -195
- package/mcp/tools/menuSync.js +416 -96
- package/mcp/tools/projectTools.js +336 -124
- package/package.json +2 -2
|
@@ -81,6 +81,8 @@
|
|
|
81
81
|
</div>
|
|
82
82
|
<BaseTable
|
|
83
83
|
ref="itemTableRef"
|
|
84
|
+
render-type="agGrid"
|
|
85
|
+
:cid="ITEM_TABLE_CID"
|
|
84
86
|
:data="itemList"
|
|
85
87
|
:columns="itemColumns"
|
|
86
88
|
showToolbar
|
|
@@ -110,6 +112,7 @@ import {
|
|
|
110
112
|
itemList,
|
|
111
113
|
itemColumns,
|
|
112
114
|
itemPage,
|
|
115
|
+
ITEM_TABLE_CID,
|
|
113
116
|
formRef,
|
|
114
117
|
itemTableRef,
|
|
115
118
|
handleSave,
|
|
@@ -136,6 +139,9 @@ import { ElMessage } from "element-plus";
|
|
|
136
139
|
import type { FormInstance, FormRules } from "element-plus";
|
|
137
140
|
import type { TableColumnDesc } from "@/types/page";
|
|
138
141
|
import envConfig from "@jhlc/common-core/src/store/env-config";
|
|
142
|
+
import { defineColumns, renderOps } from "@agile-team/wk-skills-ui/runtime";
|
|
143
|
+
|
|
144
|
+
export const ITEM_TABLE_CID = "[pageAbbr]-[base36Timestamp]-sub1";
|
|
139
145
|
|
|
140
146
|
export const API_CONFIG = {
|
|
141
147
|
getById: "/[服务缩写]/[主资源]/getById",
|
|
@@ -184,22 +190,25 @@ export const itemTableRef = ref();
|
|
|
184
190
|
export const itemList = ref<any[]>([]);
|
|
185
191
|
export const itemPage = reactive({ current: 1, size: 10, total: 0 });
|
|
186
192
|
|
|
187
|
-
export const itemColumns: TableColumnDesc<any>[] = [
|
|
188
|
-
{ type: "index", width:
|
|
189
|
-
{
|
|
193
|
+
export const itemColumns: TableColumnDesc<any>[] = defineColumns([
|
|
194
|
+
{ type: "index", label: "序号", width: 60, align: "center" },
|
|
195
|
+
{
|
|
196
|
+
label: "[子项字段]",
|
|
197
|
+
name: "[fieldName]",
|
|
198
|
+
cid: `${ITEM_TABLE_CID}-[fieldName]`,
|
|
199
|
+
minWidth: 120,
|
|
200
|
+
},
|
|
190
201
|
{
|
|
191
202
|
label: "操作",
|
|
203
|
+
name: "_action",
|
|
204
|
+
cid: `${ITEM_TABLE_CID}-action`,
|
|
192
205
|
width: 100,
|
|
193
206
|
fixed: "right",
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
label: "删除",
|
|
198
|
-
onClick: (row: any) => removeItem(row),
|
|
199
|
-
},
|
|
200
|
-
],
|
|
207
|
+
align: "center",
|
|
208
|
+
defaultSlot: ({ row }: any) =>
|
|
209
|
+
renderOps([{ type: "del", onClick: () => removeItem(row) }]),
|
|
201
210
|
},
|
|
202
|
-
];
|
|
211
|
+
] as any) as TableColumnDesc<any>[];
|
|
203
212
|
|
|
204
213
|
// ===== 数据加载 =====
|
|
205
214
|
export async function loadItems() {
|
|
@@ -453,7 +462,14 @@ defineExpose({ open });
|
|
|
453
462
|
@reset="select"
|
|
454
463
|
/>
|
|
455
464
|
<BaseToolbar :items="toolbars" />
|
|
456
|
-
<BaseTable
|
|
465
|
+
<BaseTable
|
|
466
|
+
ref="tableRef"
|
|
467
|
+
render-type="agGrid"
|
|
468
|
+
:cid="TABLE_CID"
|
|
469
|
+
:data="list"
|
|
470
|
+
:columns="columns"
|
|
471
|
+
showToolbar
|
|
472
|
+
/>
|
|
457
473
|
<jh-pagination
|
|
458
474
|
v-show="page.total && page.total > 0"
|
|
459
475
|
:total="page.total || 0"
|
|
@@ -468,7 +484,7 @@ defineExpose({ open });
|
|
|
468
484
|
</template>
|
|
469
485
|
|
|
470
486
|
<script setup lang="ts">
|
|
471
|
-
import { createPage } from "./data";
|
|
487
|
+
import { createPage, TABLE_CID } from "./data";
|
|
472
488
|
import AddModal from "./components/addModal.vue";
|
|
473
489
|
|
|
474
490
|
const addModalRef = ref();
|
|
@@ -547,22 +563,44 @@ let _editModalRef: any = null;
|
|
|
547
563
|
|
|
548
564
|
/** 管理视角列定义 */
|
|
549
565
|
export function managementColumns(): TableColumnDesc<any>[] {
|
|
550
|
-
return [
|
|
551
|
-
{ type: "selection" },
|
|
552
|
-
{ type: "index" },
|
|
566
|
+
return defineColumns([
|
|
567
|
+
{ type: "selection", width: 55, fixed: "left", align: "center", headerAlign: "center" },
|
|
568
|
+
{ type: "index", label: "序号", width: 60, align: "center" },
|
|
553
569
|
// ... 管理视角列(按原型顺序)
|
|
554
|
-
{
|
|
555
|
-
|
|
570
|
+
{
|
|
571
|
+
label: "操作",
|
|
572
|
+
name: "_action",
|
|
573
|
+
cid: `${TABLE_CID}-management-action`,
|
|
574
|
+
width: 120,
|
|
575
|
+
fixed: "right",
|
|
576
|
+
align: "center",
|
|
577
|
+
defaultSlot: ({ row }: any) => renderOps([
|
|
578
|
+
{ type: "edit", onClick: () => _editModalRef?.value?.open(row.id) },
|
|
579
|
+
{ type: "del", onClick: () => Page?.remove(row.id) }
|
|
580
|
+
])
|
|
581
|
+
}
|
|
582
|
+
] as any) as TableColumnDesc<any>[];
|
|
556
583
|
}
|
|
557
584
|
|
|
558
585
|
/** 使用视角列定义(含业务明细字段) */
|
|
559
586
|
export function usageColumns(): TableColumnDesc<any>[] {
|
|
560
|
-
return [
|
|
561
|
-
{ type: "selection" },
|
|
562
|
-
{ type: "index" },
|
|
587
|
+
return defineColumns([
|
|
588
|
+
{ type: "selection", width: 55, fixed: "left", align: "center", headerAlign: "center" },
|
|
589
|
+
{ type: "index", label: "序号", width: 60, align: "center" },
|
|
563
590
|
// ... 使用视角列(按原型顺序,通常比管理视角多出业务字段)
|
|
564
|
-
{
|
|
565
|
-
|
|
591
|
+
{
|
|
592
|
+
label: "操作",
|
|
593
|
+
name: "_action",
|
|
594
|
+
cid: `${TABLE_CID}-usage-action`,
|
|
595
|
+
width: 120,
|
|
596
|
+
fixed: "right",
|
|
597
|
+
align: "center",
|
|
598
|
+
defaultSlot: ({ row }: any) => renderOps([
|
|
599
|
+
{ type: "edit", onClick: () => _editModalRef?.value?.open(row.id) },
|
|
600
|
+
{ type: "del", onClick: () => Page?.remove(row.id) }
|
|
601
|
+
])
|
|
602
|
+
}
|
|
603
|
+
] as any) as TableColumnDesc<any>[];
|
|
566
604
|
}
|
|
567
605
|
|
|
568
606
|
let Page: any = null;
|
|
@@ -756,18 +794,20 @@ export function createPage() {
|
|
|
756
794
|
];
|
|
757
795
|
}
|
|
758
796
|
columnsDef() {
|
|
759
|
-
return [
|
|
797
|
+
return defineColumns([
|
|
760
798
|
// ...
|
|
761
799
|
{
|
|
762
|
-
label: "操作",
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
800
|
+
label: "操作",
|
|
801
|
+
name: "_action",
|
|
802
|
+
cid: `${TABLE_CID}-action`,
|
|
803
|
+
fixed: "right",
|
|
804
|
+
align: "center",
|
|
805
|
+
defaultSlot: ({ row }: any) =>
|
|
806
|
+
renderOps([
|
|
807
|
+
{ type: "edit", onClick: () => navigateToForm({ id: row.id }) }
|
|
808
|
+
])
|
|
769
809
|
}
|
|
770
|
-
];
|
|
810
|
+
] as any) as TableColumnDesc<any>[];
|
|
771
811
|
}
|
|
772
812
|
}
|
|
773
813
|
```
|
|
@@ -846,22 +886,14 @@ onMounted(() => {
|
|
|
846
886
|
### 实现方式
|
|
847
887
|
|
|
848
888
|
```vue
|
|
849
|
-
<!--
|
|
850
|
-
<
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
889
|
+
<!-- 表格列:按 mode 在 data.ts 中切换 columns,仍使用 BaseTable + AGGrid -->
|
|
890
|
+
<BaseTable
|
|
891
|
+
render-type="agGrid"
|
|
892
|
+
:cid="DETAIL_TABLE_CID"
|
|
893
|
+
:data="list"
|
|
894
|
+
:columns="isChange ? changeColumns : addColumns"
|
|
855
895
|
/>
|
|
856
896
|
|
|
857
|
-
<!-- 操作列:变更模式多一个编辑按钮 -->
|
|
858
|
-
<el-table-column v-if="!isView" label="操作" :width="isChange ? 120 : 80">
|
|
859
|
-
<template #default="{ row, $index }">
|
|
860
|
-
<el-button v-if="isChange" type="primary" link @click="editRow(row, $index)">编辑</el-button>
|
|
861
|
-
<el-button type="danger" link @click="list.splice($index, 1)">删除</el-button>
|
|
862
|
-
</template>
|
|
863
|
-
</el-table-column>
|
|
864
|
-
|
|
865
897
|
<!-- 底部新增行链接(变更模式) -->
|
|
866
898
|
<div
|
|
867
899
|
v-if="isChange && !isView"
|
|
@@ -1000,7 +1032,7 @@ spec.features.hiddenMenu === true:
|
|
|
1000
1032
|
✅ query: spec 12 项 = code 12 项,顺序一致
|
|
1001
1033
|
✅ columns: spec 16 项 = code 16 项,顺序一致
|
|
1002
1034
|
✅ toolbar: spec 7 项 = code 7 项,顺序一致,颜色正确
|
|
1003
|
-
✅
|
|
1035
|
+
✅ rowActions: spec 3 项 = code 3 项,顺序一致
|
|
1004
1036
|
✅ tabs: spec 3 项 = code 3 项,顺序一致
|
|
1005
1037
|
✅ subTables: businessInfo(editable) — 有新增/删除
|
|
1006
1038
|
✅ dict 字段: 8 个全部配置 logicType
|
|
@@ -1013,7 +1045,7 @@ spec.features.hiddenMenu === true:
|
|
|
1013
1045
|
```
|
|
1014
1046
|
❌ columns: spec 35 项 ≠ code 34 项 — 缺少 customerName 列 → 已补全
|
|
1015
1047
|
❌ toolbar 顺序: spec [新增申请, 删除, 启用] ≠ code [新增, 启用, 删除] → 已调整
|
|
1016
|
-
❌
|
|
1048
|
+
❌ rowActions: spec [查看, 编辑, 删除] ≠ code [编辑, 删除] — 缺少"查看" → 已补全
|
|
1017
1049
|
```
|
|
1018
1050
|
|
|
1019
1051
|
---
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
> 见 SKILL.md 主文件(约束 + 按钮规则 + Mock 规范等共用规则)。
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
> 复杂表单(多 Tab、多子表、独立布局)使用独立路由而非弹窗。
|
|
7
6
|
> 表单页 `data.ts` **不继承 `AbstractPageQueryHook`**,改为导出 `useXxx` Composable。
|
|
8
7
|
> 需在 `pages.ts` 单独注册路由,路径规则见"FORM_ROUTE 表单页"章节。
|
|
@@ -74,7 +73,9 @@ export function use[PageName]Form(tabsRef: any) {
|
|
|
74
73
|
<div class="page-header">
|
|
75
74
|
<span class="page-title">[页面标题]</span>
|
|
76
75
|
<span class="page-tag page-tag--add">新增</span>
|
|
77
|
-
<el-checkbox v-model="onlyRequired" class="only-required-check"
|
|
76
|
+
<el-checkbox v-model="onlyRequired" class="only-required-check"
|
|
77
|
+
>只看必填项</el-checkbox
|
|
78
|
+
>
|
|
78
79
|
</div>
|
|
79
80
|
<div class="page-toolbar">
|
|
80
81
|
<el-button type="primary" @click="handleSave">保存</el-button>
|
|
@@ -118,6 +119,10 @@ onMounted(() => {
|
|
|
118
119
|
import { getAction, postAction } from "@jhlc/common-core/src/api/action";
|
|
119
120
|
import { ElMessage, ElMessageBox } from "element-plus";
|
|
120
121
|
import { useRouter } from "vue-router";
|
|
122
|
+
import type { TableColumnDesc } from "@/types/page";
|
|
123
|
+
import { defineColumns, renderOps } from "@agile-team/wk-skills-ui/runtime";
|
|
124
|
+
|
|
125
|
+
export const DETAIL_TABLE_CID = "[pageAbbr]-[base36Timestamp]-sub1";
|
|
121
126
|
|
|
122
127
|
export const API_CONFIG = {
|
|
123
128
|
getById: "/sale/[业务名]/getById",
|
|
@@ -130,6 +135,25 @@ export const OPTS = {
|
|
|
130
135
|
// [字段名]: [{ label: "显示文本", value: "值" }]
|
|
131
136
|
};
|
|
132
137
|
|
|
138
|
+
export function createDetailColumns(removeRecord: (row: any) => void): TableColumnDesc<any>[] {
|
|
139
|
+
return defineColumns([
|
|
140
|
+
{ type: "index", label: "序号", width: 60, align: "center" },
|
|
141
|
+
{ label: "[明细字段]", name: "[detailField]", cid: `${DETAIL_TABLE_CID}-[detailField]`, minWidth: 120 },
|
|
142
|
+
{
|
|
143
|
+
label: "操作",
|
|
144
|
+
name: "_action",
|
|
145
|
+
cid: `${DETAIL_TABLE_CID}-action`,
|
|
146
|
+
width: 100,
|
|
147
|
+
fixed: "right",
|
|
148
|
+
align: "center",
|
|
149
|
+
defaultSlot: ({ row }: any) =>
|
|
150
|
+
renderOps([
|
|
151
|
+
{ type: "del", onClick: () => removeRecord(row) }
|
|
152
|
+
])
|
|
153
|
+
}
|
|
154
|
+
] as any) as TableColumnDesc<any>[];
|
|
155
|
+
}
|
|
156
|
+
|
|
133
157
|
export interface [PageName]Form {
|
|
134
158
|
id: string;
|
|
135
159
|
// ...所有字段
|
|
@@ -148,6 +172,16 @@ export function use[PageName]Detail() {
|
|
|
148
172
|
const loading = ref(false);
|
|
149
173
|
const form = reactive<[PageName]Form>(createMockData());
|
|
150
174
|
|
|
175
|
+
function addRecord() {
|
|
176
|
+
form.[列表字段].push({ id: `temp_${Date.now()}` });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function removeRecord(row: any) {
|
|
180
|
+
form.[列表字段] = form.[列表字段].filter((item: any) => item.id !== row.id);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const detailColumns = createDetailColumns(removeRecord);
|
|
184
|
+
|
|
151
185
|
async function loadDetail(id: string) {
|
|
152
186
|
loading.value = true;
|
|
153
187
|
try {
|
|
@@ -170,7 +204,7 @@ export function use[PageName]Detail() {
|
|
|
170
204
|
|
|
171
205
|
function handleCancel() { router.back(); }
|
|
172
206
|
|
|
173
|
-
return { loading, form, loadDetail, handleSave, handleCancel };
|
|
207
|
+
return { loading, form, detailColumns, addRecord, loadDetail, handleSave, handleCancel };
|
|
174
208
|
}
|
|
175
209
|
```
|
|
176
210
|
|
|
@@ -182,7 +216,12 @@ export function use[PageName]Detail() {
|
|
|
182
216
|
<!-- 标题栏 -->
|
|
183
217
|
<div class="title-bar">
|
|
184
218
|
<span class="customer-name">{{ form.[标题字段] }}</span>
|
|
185
|
-
<el-tag
|
|
219
|
+
<el-tag
|
|
220
|
+
type="warning"
|
|
221
|
+
effect="plain"
|
|
222
|
+
size="small"
|
|
223
|
+
>{{ form.[状态字段] }}</el-tag
|
|
224
|
+
>
|
|
186
225
|
</div>
|
|
187
226
|
|
|
188
227
|
<!-- 工具栏 -->
|
|
@@ -221,16 +260,13 @@ export function use[PageName]Detail() {
|
|
|
221
260
|
<!-- 子表格 Section(如跟进记录) -->
|
|
222
261
|
<div class="form-section">
|
|
223
262
|
<div class="section-title">[表格标题]</div>
|
|
224
|
-
<
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
</template>
|
|
232
|
-
</el-table-column>
|
|
233
|
-
</el-table>
|
|
263
|
+
<BaseTable
|
|
264
|
+
render-type="agGrid"
|
|
265
|
+
:cid="DETAIL_TABLE_CID"
|
|
266
|
+
:data="form.[列表字段]"
|
|
267
|
+
:columns="detailColumns"
|
|
268
|
+
:height="300"
|
|
269
|
+
/>
|
|
234
270
|
<div class="add-row-btn" @click="addRecord">+ 新增行</div>
|
|
235
271
|
</div>
|
|
236
272
|
</el-form>
|
|
@@ -239,10 +275,18 @@ export function use[PageName]Detail() {
|
|
|
239
275
|
|
|
240
276
|
<script setup lang="ts">
|
|
241
277
|
import { useRoute } from "vue-router";
|
|
242
|
-
import { use[PageName]Detail, OPTS } from "./data";
|
|
278
|
+
import { use[PageName]Detail, OPTS, DETAIL_TABLE_CID } from "./data";
|
|
243
279
|
|
|
244
280
|
const route = useRoute();
|
|
245
|
-
const {
|
|
281
|
+
const {
|
|
282
|
+
loading,
|
|
283
|
+
form,
|
|
284
|
+
detailColumns,
|
|
285
|
+
addRecord,
|
|
286
|
+
loadDetail,
|
|
287
|
+
handleSave,
|
|
288
|
+
handleCancel
|
|
289
|
+
} = use[PageName]Detail();
|
|
246
290
|
|
|
247
291
|
onMounted(() => {
|
|
248
292
|
const id = route.query.id as string;
|
|
@@ -264,15 +308,37 @@ onMounted(() => {
|
|
|
264
308
|
flex-direction: column;
|
|
265
309
|
overflow: hidden;
|
|
266
310
|
|
|
267
|
-
.title-bar
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
.
|
|
271
|
-
|
|
272
|
-
|
|
311
|
+
.title-bar {
|
|
312
|
+
/* 标题 + 状态 Tag,灰色背景 */
|
|
313
|
+
}
|
|
314
|
+
.page-toolbar {
|
|
315
|
+
/* 按钮行,白底,底部边框 */
|
|
316
|
+
}
|
|
317
|
+
.detail-form {
|
|
318
|
+
flex: 1;
|
|
319
|
+
overflow-y: auto;
|
|
320
|
+
padding: 0 16px 16px;
|
|
321
|
+
}
|
|
322
|
+
.header-info {
|
|
323
|
+
padding: 12px 0 4px;
|
|
324
|
+
border-bottom: 1px solid #f0f2f5;
|
|
325
|
+
}
|
|
326
|
+
.form-section {
|
|
327
|
+
margin-top: 16px;
|
|
328
|
+
.section-title {
|
|
329
|
+
border-left: 3px solid var(--el-color-primary);
|
|
330
|
+
padding-left: 10px;
|
|
331
|
+
font-weight: 600;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
.add-row-btn {
|
|
335
|
+
color: #409eff;
|
|
336
|
+
cursor: pointer;
|
|
337
|
+
margin-top: 8px;
|
|
338
|
+
}
|
|
339
|
+
.el-form-item {
|
|
340
|
+
margin-bottom: 10px;
|
|
273
341
|
}
|
|
274
|
-
.add-row-btn { color: #409eff; cursor: pointer; margin-top: 8px; }
|
|
275
|
-
.el-form-item { margin-bottom: 10px; }
|
|
276
342
|
}
|
|
277
343
|
```
|
|
278
344
|
|
|
@@ -284,26 +350,27 @@ onMounted(() => {
|
|
|
284
350
|
> 典型页面:精整实绩(抛丸/倒棱/矫直/酸洗/剥皮/检验/包装)、加热管理(装炉/出炉)、剔钢操作。
|
|
285
351
|
>
|
|
286
352
|
> 项目中已有两种落地方式:
|
|
353
|
+
>
|
|
287
354
|
> - **配置驱动模板组件**:`FinishingAchievementTemplate`(7 个精整页面共用)
|
|
288
355
|
> - **独立页面编排**:`mmwr-heating-management`、`mmwr-steel-stripping-operations`
|
|
289
356
|
|
|
290
357
|
### D-0 核心特征
|
|
291
358
|
|
|
292
|
-
| 特征
|
|
293
|
-
|
|
294
|
-
| **多 AbstractPageQueryHook 实例** | 每个表格区域一个实例,各自管理 `list/page/queryParam/columns`
|
|
295
|
-
| **主从联动**
|
|
296
|
-
| **可拖拽分隔**
|
|
297
|
-
| **Tab 切换**
|
|
298
|
-
| **操作区**
|
|
299
|
-
| **懒加载**
|
|
359
|
+
| 特征 | 说明 |
|
|
360
|
+
| --------------------------------- | --------------------------------------------------------------- |
|
|
361
|
+
| **多 AbstractPageQueryHook 实例** | 每个表格区域一个实例,各自管理 `list/page/queryParam/columns` |
|
|
362
|
+
| **主从联动** | 选中上表行 → 调用下表实例的 `selectByPlan(row)` 驱动查询 |
|
|
363
|
+
| **可拖拽分隔** | `<jh-drag-row :top-height="N">` 上下分隔,可嵌套 |
|
|
364
|
+
| **Tab 切换** | `<el-tabs type="border-card">` 或 `<jh-tabs>` 切换录入/查询视角 |
|
|
365
|
+
| **操作区** | 在上下表之间放置 `BaseForm` + 按钮,或 `BaseToolbar` |
|
|
366
|
+
| **懒加载** | Tab 切换时才加载对应数据,避免首次全量查询 |
|
|
300
367
|
|
|
301
368
|
### D-1 判断何时使用配置驱动 vs 独立编排
|
|
302
369
|
|
|
303
|
-
| 条件
|
|
304
|
-
|
|
305
|
-
| 3+ 页面布局完全相同,仅 API/工序代码/列不同
|
|
306
|
-
| 页面布局有显著差异(不同 Tab 结构、不同表数量) | 独立页面,在 data.ts 中定义多个 `createXxxPage()`
|
|
370
|
+
| 条件 | 方式 |
|
|
371
|
+
| ----------------------------------------------- | ------------------------------------------------------------ |
|
|
372
|
+
| 3+ 页面布局完全相同,仅 API/工序代码/列不同 | 提取 `src/components/template/XxxTemplate/`,页面仅传 config |
|
|
373
|
+
| 页面布局有显著差异(不同 Tab 结构、不同表数量) | 独立页面,在 data.ts 中定义多个 `createXxxPage()` |
|
|
307
374
|
|
|
308
375
|
### D-2 配置驱动模板组件结构(参考 FinishingAchievementTemplate)
|
|
309
376
|
|
|
@@ -321,17 +388,27 @@ src/views/.../[page-name]/
|
|
|
321
388
|
```
|
|
322
389
|
|
|
323
390
|
**types.ts 要点**:
|
|
391
|
+
|
|
324
392
|
```typescript
|
|
325
393
|
export interface XxxTemplateConfig {
|
|
326
|
-
api: Record<string, string>;
|
|
327
|
-
processCode: string;
|
|
328
|
-
query?: {
|
|
329
|
-
|
|
330
|
-
|
|
394
|
+
api: Record<string, string>; // 各表格 API 端点
|
|
395
|
+
processCode: string; // 工序标识,用于查询参数
|
|
396
|
+
query?: {
|
|
397
|
+
plan?: {
|
|
398
|
+
items: BaseQueryItemDesc<any>[];
|
|
399
|
+
defaultParams?: Record<string, any>;
|
|
400
|
+
};
|
|
401
|
+
};
|
|
402
|
+
columns?: {
|
|
403
|
+
planColumns: TableColumnDesc<any>[];
|
|
404
|
+
detailColumns: TableColumnDesc<any>[];
|
|
405
|
+
};
|
|
406
|
+
ui?: Partial<UiConfig>; // 可选 UI 覆盖(Tab 标题、区域标题等)
|
|
331
407
|
}
|
|
332
408
|
```
|
|
333
409
|
|
|
334
410
|
**页面 data.ts 要点**(仅配置,不写逻辑):
|
|
411
|
+
|
|
335
412
|
```typescript
|
|
336
413
|
import type { XxxTemplateConfig } from "@/components/template/XxxTemplate/types";
|
|
337
414
|
|
|
@@ -345,6 +422,7 @@ export const xxxConfig: XxxTemplateConfig = {
|
|
|
345
422
|
### D-3 独立编排页面结构(参考 mmwr-steel-stripping-operations)
|
|
346
423
|
|
|
347
424
|
**data.ts 要点**(多个 createPage 工厂函数):
|
|
425
|
+
|
|
348
426
|
```typescript
|
|
349
427
|
// 上表
|
|
350
428
|
export function createEntryPage() {
|
|
@@ -375,6 +453,7 @@ export function createEntryBottomPage(rejectForm: any) {
|
|
|
375
453
|
```
|
|
376
454
|
|
|
377
455
|
**index.vue 要点**:
|
|
456
|
+
|
|
378
457
|
```vue
|
|
379
458
|
<template>
|
|
380
459
|
<div class="app-container app-page-container [page-class]">
|
|
@@ -383,12 +462,21 @@ export function createEntryBottomPage(rejectForm: any) {
|
|
|
383
462
|
<jh-drag-row :top-height="420">
|
|
384
463
|
<template #top>
|
|
385
464
|
<BaseQuery :form="..." :items="..." @select="..." @reset="..." />
|
|
386
|
-
<BaseTable
|
|
465
|
+
<BaseTable
|
|
466
|
+
ref="..."
|
|
467
|
+
:data="..."
|
|
468
|
+
:columns="..."
|
|
469
|
+
highlight-current-row
|
|
470
|
+
@current-change="handleRowClick"
|
|
471
|
+
/>
|
|
387
472
|
<jh-pagination ... />
|
|
388
473
|
</template>
|
|
389
474
|
<template #bottom>
|
|
390
475
|
<BaseToolbar v-if="selectedRow" :items="..." />
|
|
391
|
-
<el-empty
|
|
476
|
+
<el-empty
|
|
477
|
+
v-if="!selectedRow"
|
|
478
|
+
description="请先在上方列表中选择一行数据"
|
|
479
|
+
/>
|
|
392
480
|
<BaseTable v-else ref="..." :data="..." :columns="..." />
|
|
393
481
|
<jh-pagination ... />
|
|
394
482
|
</template>
|
|
@@ -402,16 +490,26 @@ export function createEntryBottomPage(rejectForm: any) {
|
|
|
402
490
|
</template>
|
|
403
491
|
|
|
404
492
|
<script setup lang="ts">
|
|
405
|
-
import {
|
|
493
|
+
import {
|
|
494
|
+
createEntryPage,
|
|
495
|
+
createEntryBottomPage,
|
|
496
|
+
createQueryPage,
|
|
497
|
+
} from "./data";
|
|
406
498
|
|
|
407
499
|
const activeTab = ref("entry");
|
|
408
500
|
const selectedRow = ref(null);
|
|
409
501
|
|
|
410
502
|
const EntryPage = createEntryPage();
|
|
411
|
-
const { tableRef, page, queryParam, list, queryItems, columns, select } =
|
|
503
|
+
const { tableRef, page, queryParam, list, queryItems, columns, select } =
|
|
504
|
+
EntryPage;
|
|
412
505
|
|
|
413
506
|
const BottomPage = createEntryBottomPage();
|
|
414
|
-
const {
|
|
507
|
+
const {
|
|
508
|
+
list: bottomList,
|
|
509
|
+
columns: bottomColumns,
|
|
510
|
+
select: bottomSelect,
|
|
511
|
+
selectByPlan,
|
|
512
|
+
} = BottomPage;
|
|
415
513
|
|
|
416
514
|
const handleRowClick = (row: any) => {
|
|
417
515
|
selectedRow.value = row;
|
|
@@ -419,7 +517,9 @@ const handleRowClick = (row: any) => {
|
|
|
419
517
|
};
|
|
420
518
|
|
|
421
519
|
onMounted(() => select());
|
|
422
|
-
watch(activeTab, (tab) => {
|
|
520
|
+
watch(activeTab, (tab) => {
|
|
521
|
+
if (tab === "query") QueryPage.select();
|
|
522
|
+
});
|
|
423
523
|
</script>
|
|
424
524
|
```
|
|
425
525
|
|
|
@@ -427,14 +527,42 @@ watch(activeTab, (tab) => { if (tab === "query") QueryPage.select(); });
|
|
|
427
527
|
|
|
428
528
|
```scss
|
|
429
529
|
.[page-class] {
|
|
430
|
-
.section-header {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
.
|
|
437
|
-
|
|
530
|
+
.section-header {
|
|
531
|
+
display: flex;
|
|
532
|
+
align-items: center;
|
|
533
|
+
gap: 6px;
|
|
534
|
+
margin: 8px 0;
|
|
535
|
+
}
|
|
536
|
+
.section-header .title-bar {
|
|
537
|
+
width: 3px;
|
|
538
|
+
height: 14px;
|
|
539
|
+
background: var(--el-color-primary);
|
|
540
|
+
border-radius: 1px;
|
|
541
|
+
}
|
|
542
|
+
.section-header .section-title {
|
|
543
|
+
font-size: 14px;
|
|
544
|
+
font-weight: 600;
|
|
545
|
+
margin: 0;
|
|
546
|
+
}
|
|
547
|
+
.empty-tip {
|
|
548
|
+
padding: 40px 0;
|
|
549
|
+
}
|
|
550
|
+
.operation-area {
|
|
551
|
+
padding: 8px 0;
|
|
552
|
+
}
|
|
553
|
+
.operation-buttons {
|
|
554
|
+
display: flex;
|
|
555
|
+
gap: 8px;
|
|
556
|
+
margin: 8px 0;
|
|
557
|
+
}
|
|
558
|
+
.results-container {
|
|
559
|
+
display: flex;
|
|
560
|
+
gap: 16px; /* 左右分栏时 */
|
|
561
|
+
}
|
|
562
|
+
.results-container .section {
|
|
563
|
+
flex: 1;
|
|
564
|
+
min-width: 0;
|
|
565
|
+
}
|
|
438
566
|
}
|
|
439
567
|
```
|
|
440
568
|
|