@agile-team/wl-skills-kit 2.4.2 → 2.5.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.
@@ -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">只看必填项</el-checkbox>
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 type="warning" effect="plain" size="small">{{ form.[状态字段] }}</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
- <el-table :data="form.[列表字段]" border size="small">
225
- <el-table-column type="index" label="序号" width="55" align="center" />
226
- <!-- ...更多列 -->
227
- <el-table-column label="操作" width="100" fixed="right">
228
- <template #default="{ $index }">
229
- <el-button type="primary" link size="small">编辑</el-button>
230
- <el-button type="danger" link size="small" @click="removeRecord($index)">删除</el-button>
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 { loading, form, loadDetail, handleSave, handleCancel } = use[PageName]Detail();
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 { /* 标题 + 状态 Tag,灰色背景 */ }
268
- .page-toolbar { /* 按钮行,白底,底部边框 */ }
269
- .detail-form { flex: 1; overflow-y: auto; padding: 0 16px 16px; }
270
- .header-info { padding: 12px 0 4px; border-bottom: 1px solid #f0f2f5; }
271
- .form-section { margin-top: 16px;
272
- .section-title { border-left: 3px solid var(--el-color-primary); padding-left: 10px; font-weight: 600; }
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
- | **主从联动** | 选中上表行 → 调用下表实例的 `selectByPlan(row)` 驱动查询 |
296
- | **可拖拽分隔** | `<jh-drag-row :top-height="N">` 上下分隔,可嵌套 |
297
- | **Tab 切换** | `<el-tabs type="border-card">` 或 `<jh-tabs>` 切换录入/查询视角 |
298
- | **操作区** | 在上下表之间放置 `BaseForm` + 按钮,或 `BaseToolbar` |
299
- | **懒加载** | Tab 切换时才加载对应数据,避免首次全量查询 |
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/工序代码/列不同 | 提取 `src/components/template/XxxTemplate/`,页面仅传 config |
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>; // 各表格 API 端点
327
- processCode: string; // 工序标识,用于查询参数
328
- query?: { plan?: { items: BaseQueryItemDesc<any>[]; defaultParams?: Record<string, any> } };
329
- columns?: { planColumns: TableColumnDesc<any>[]; detailColumns: TableColumnDesc<any>[] };
330
- ui?: Partial<UiConfig>; // 可选 UI 覆盖(Tab 标题、区域标题等)
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 ref="..." :data="..." :columns="..." highlight-current-row @current-change="handleRowClick" />
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 v-if="!selectedRow" description="请先在上方列表中选择一行数据" />
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 { createEntryPage, createEntryBottomPage, createQueryPage } from "./data";
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 } = EntryPage;
503
+ const { tableRef, page, queryParam, list, queryItems, columns, select } =
504
+ EntryPage;
412
505
 
413
506
  const BottomPage = createEntryBottomPage();
414
- const { list: bottomList, columns: bottomColumns, select: bottomSelect, selectByPlan } = BottomPage;
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) => { if (tab === "query") QueryPage.select(); });
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 { display: flex; align-items: center; gap: 6px; margin: 8px 0; }
431
- .section-header .title-bar { width: 3px; height: 14px; background: var(--el-color-primary); border-radius: 1px; }
432
- .section-header .section-title { font-size: 14px; font-weight: 600; margin: 0; }
433
- .empty-tip { padding: 40px 0; }
434
- .operation-area { padding: 8px 0; }
435
- .operation-buttons { display: flex; gap: 8px; margin: 8px 0; }
436
- .results-container { display: flex; gap: 16px; /* 左右分栏时 */ }
437
- .results-container .section { flex: 1; min-width: 0; }
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
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  > 见 SKILL.md 主文件(约束 + 按钮规则 + Mock 规范等共用规则)。
4
4
 
5
-
6
5
  #### data.ts
7
6
 
8
7
  ```typescript
@@ -14,7 +13,10 @@ import {
14
13
  TableColumnDesc,
15
14
  BusLogicDataType
16
15
  } from "@/types/page";
17
- import { getAction, postAction } from "@jhlc/common-core/src/api/action";
16
+ import { ElMessage } from "element-plus";
17
+ import { defineColumns, renderOps } from "@agile-team/wk-skills-ui/runtime";
18
+
19
+ export const TABLE_CID = "[pageAbbr]-[base36Timestamp]";
18
20
 
19
21
  export const API_CONFIG = {
20
22
  list: "/[服务缩写]/[资源名]/list",
@@ -88,18 +90,24 @@ export function createPage(editModalRef?: any) {
88
90
  label: "新增",
89
91
  plain: true,
90
92
  onClick: () => editModalRef?.value?.open()
93
+ },
94
+ {
95
+ label: "导出",
96
+ plain: true,
97
+ onClick: () => ElMessage.info("导出逻辑待业务确认")
91
98
  }
92
99
  ];
93
100
  }
94
101
 
95
102
  columnsDef(): TableColumnDesc<any>[] {
96
- return [
97
- { type: "selection" },
98
- { type: "index" },
103
+ return defineColumns([
104
+ { type: "selection", width: 55, fixed: "left", align: "center", headerAlign: "center" },
105
+ { type: "index", label: "序号", width: 60, align: "center" },
99
106
  // 普通列
100
107
  {
101
108
  label: "[列名]",
102
109
  name: "[fieldName]",
110
+ cid: `${TABLE_CID}-[fieldName]`,
103
111
  minWidth: 120,
104
112
  sortable: true,
105
113
  filterable: true
@@ -108,6 +116,7 @@ export function createPage(editModalRef?: any) {
108
116
  {
109
117
  label: "[状态名]",
110
118
  name: "[statusField]",
119
+ cid: `${TABLE_CID}-[statusField]`,
111
120
  minWidth: 120,
112
121
  logicType: BusLogicDataType.dict,
113
122
  logicValue: "[dictCode]",
@@ -117,22 +126,18 @@ export function createPage(editModalRef?: any) {
117
126
  // 操作列(如需要行内编辑/删除按钮)
118
127
  {
119
128
  label: "操作",
129
+ name: "_action",
130
+ cid: `${TABLE_CID}-action`,
120
131
  width: 150,
121
132
  fixed: "right",
122
- operations: [
123
- {
124
- name: "edit",
125
- label: "编辑",
126
- onClick: (row: any) => editModalRef?.value?.open(row.id)
127
- },
128
- {
129
- name: "remove",
130
- label: "删除",
131
- onClick: (row: any) => this.remove(row.id)
132
- }
133
- ]
133
+ align: "center",
134
+ defaultSlot: ({ row }: any) =>
135
+ renderOps([
136
+ { type: "edit", onClick: () => editModalRef?.value?.open(row.id) },
137
+ { type: "del", onClick: () => this.remove(row.id) }
138
+ ])
134
139
  }
135
- ];
140
+ ] as any) as TableColumnDesc<any>[];
136
141
  }
137
142
  })();
138
143
 
@@ -152,7 +157,14 @@ export function createPage(editModalRef?: any) {
152
157
  @reset="select"
153
158
  />
154
159
  <BaseToolbar :items="toolbars" />
155
- <BaseTable ref="tableRef" :data="list" :columns="columns" showToolbar />
160
+ <BaseTable
161
+ ref="tableRef"
162
+ render-type="agGrid"
163
+ :cid="TABLE_CID"
164
+ :data="list"
165
+ :columns="columns"
166
+ showToolbar
167
+ />
156
168
  <jh-pagination
157
169
  v-show="page.total && page.total > 0"
158
170
  :total="page.total || 0"
@@ -165,7 +177,7 @@ export function createPage(editModalRef?: any) {
165
177
  </template>
166
178
 
167
179
  <script setup lang="ts">
168
- import { createPage } from "./data";
180
+ import { createPage, TABLE_CID } from "./data";
169
181
 
170
182
  const Page = createPage();
171
183
  const {
@@ -176,7 +188,7 @@ const {
176
188
  queryItems,
177
189
  columns,
178
190
  toolbars,
179
- select
191
+ select,
180
192
  } = Page;
181
193
 
182
194
  onMounted(() => select());
@@ -193,4 +205,81 @@ onMounted(() => select());
193
205
  // 页面特有样式(无特殊需求可留空)
194
206
  ```
195
207
 
208
+ #### mock/[页面kebab-name].ts
209
+
210
+ ```typescript
211
+ import type { MockMethod } from "vite-plugin-mock";
212
+
213
+ const dataPool = Array.from({ length: 23 }).map((_, index) => ({
214
+ id: String(index + 1),
215
+ name: `模拟数据${index + 1}`,
216
+ status: index % 2 === 0 ? "1" : "0",
217
+ remark: "由 vite-plugin-mock 提供,关闭 ENV_MOCK 后直接走真实接口",
218
+ }));
219
+
220
+ function pageList(query: any) {
221
+ const current = Number(query?.current || query?.pageNum || 1);
222
+ const size = Number(query?.size || query?.pageSize || 10);
223
+ const start = (current - 1) * size;
224
+ return {
225
+ code: 2000,
226
+ message: "success",
227
+ data: {
228
+ records: dataPool.slice(start, start + size),
229
+ total: dataPool.length,
230
+ current,
231
+ size,
232
+ },
233
+ };
234
+ }
235
+
236
+ export default [
237
+ {
238
+ url: "/dev-api/[服务缩写]/[资源名]/list",
239
+ method: "get",
240
+ response: ({ query }: any) => pageList(query),
241
+ },
242
+ {
243
+ url: "/dev-api/[服务缩写]/[资源名]/remove",
244
+ method: "post",
245
+ response: ({ body, query }: any) => {
246
+ const id = body?.id || query?.id;
247
+ const ids = body?.ids || (id ? [id] : []);
248
+ ids.forEach((itemId: string) => {
249
+ const index = dataPool.findIndex((item) => item.id === String(itemId));
250
+ if (index >= 0) dataPool.splice(index, 1);
251
+ });
252
+ return { code: 2000, message: "删除成功", data: null };
253
+ },
254
+ },
255
+ {
256
+ url: "/dev-api/[服务缩写]/[资源名]/save",
257
+ method: "post",
258
+ response: ({ body }: any) => {
259
+ const row = { id: String(Date.now()), ...body };
260
+ dataPool.unshift(row);
261
+ return { code: 2000, message: "新增成功", data: row };
262
+ },
263
+ },
264
+ {
265
+ url: "/dev-api/[服务缩写]/[资源名]/update",
266
+ method: "post",
267
+ response: ({ body }: any) => {
268
+ const index = dataPool.findIndex((item) => item.id === String(body?.id));
269
+ if (index >= 0) Object.assign(dataPool[index], body);
270
+ return { code: 2000, message: "更新成功", data: dataPool[index] || body };
271
+ },
272
+ },
273
+ {
274
+ url: "/dev-api/[服务缩写]/[资源名]/getById",
275
+ method: "get",
276
+ response: ({ query }: any) => ({
277
+ code: 2000,
278
+ message: "success",
279
+ data: dataPool.find((item) => item.id === String(query?.id)) || null,
280
+ }),
281
+ },
282
+ ] as MockMethod[];
283
+ ```
284
+
196
285
  ---
@@ -2,18 +2,22 @@
2
2
 
3
3
  > 见 SKILL.md 主文件(约束 + 按钮规则 + Mock 规范等共用规则)。
4
4
 
5
-
6
5
  #### data.ts(额外部分)
7
6
 
8
7
  在标准 createPage 基础上,增加 createBottomPage:
9
8
 
10
9
  ```typescript
11
10
  // ... 同模板 A 的 imports 和 API_CONFIG(增加从表相关 URL)
11
+ import { defineColumns, renderOps } from "@agile-team/wk-skills-ui/runtime";
12
+
13
+ export const TABLE_CID = "[pageAbbr]-[base36Timestamp]";
14
+ export const BOTTOM_TABLE_CID = `${TABLE_CID}-sub1`;
15
+
12
16
  export const API_CONFIG = {
13
17
  list: "/[服务缩写]/[主资源]/list",
14
18
  remove: "/[服务缩写]/[主资源]/remove",
15
19
  // ...标准 CRUD
16
- bottomList: "/[服务缩写]/[从资源]/list" // 从表查询
20
+ bottomList: "/[服务缩写]/[从资源]/list", // 从表查询
17
21
  } as const;
18
22
 
19
23
  export function createPage(/* refs */) {
@@ -24,7 +28,7 @@ export function createPage(/* refs */) {
24
28
  export function handleRowDblclick(
25
29
  row: any,
26
30
  bottomSelect: Function,
27
- BottomPage: any
31
+ BottomPage: any,
28
32
  ) {
29
33
  BottomPage.queryParam.value.mainId = row.id;
30
34
  BottomPage.tableRef.value.loading();
@@ -51,10 +55,16 @@ export function createBottomPage() {
51
55
  return [];
52
56
  }
53
57
  columnsDef(): TableColumnDesc<any>[] {
54
- return [
55
- { type: "index" }
58
+ return defineColumns([
59
+ { type: "index", label: "序号", width: 60, align: "center" },
60
+ {
61
+ label: "[从表字段]",
62
+ name: "[fieldName]",
63
+ cid: `${BOTTOM_TABLE_CID}-[fieldName]`,
64
+ minWidth: 120,
65
+ },
56
66
  // 从表字段
57
- ];
67
+ ] as any) as TableColumnDesc<any>[];
58
68
  }
59
69
  })();
60
70
  return (Page as any).create() as any;
@@ -77,6 +87,8 @@ export function createBottomPage() {
77
87
  <BaseToolbar :items="toolbars" />
78
88
  <BaseTable
79
89
  ref="tableRef"
90
+ render-type="agGrid"
91
+ :cid="TABLE_CID"
80
92
  :data="list"
81
93
  :columns="columns"
82
94
  showToolbar
@@ -97,6 +109,8 @@ export function createBottomPage() {
97
109
  <BaseToolbar :items="bottomToolbars" />
98
110
  <BaseTable
99
111
  ref="bottomTableRef"
112
+ render-type="agGrid"
113
+ :cid="BOTTOM_TABLE_CID"
100
114
  :data="bottomList"
101
115
  :columns="bottomColumns"
102
116
  showToolbar
@@ -107,7 +121,13 @@ export function createBottomPage() {
107
121
  </template>
108
122
 
109
123
  <script setup lang="ts">
110
- import { createPage, createBottomPage, handleRowDblclick } from "./data";
124
+ import {
125
+ createPage,
126
+ createBottomPage,
127
+ handleRowDblclick,
128
+ TABLE_CID,
129
+ BOTTOM_TABLE_CID,
130
+ } from "./data";
111
131
 
112
132
  const Page = createPage();
113
133
  const {
@@ -118,7 +138,7 @@ const {
118
138
  queryItems,
119
139
  columns,
120
140
  toolbars,
121
- select
141
+ select,
122
142
  } = Page;
123
143
 
124
144
  const BottomPage = createBottomPage();
@@ -127,7 +147,7 @@ const {
127
147
  list: bottomList,
128
148
  columns: bottomColumns,
129
149
  select: bottomSelect,
130
- toolbars: bottomToolbars
150
+ toolbars: bottomToolbars,
131
151
  } = BottomPage;
132
152
 
133
153
  onMounted(() => select());