vue2-components-plus 1.0.67 → 1.0.69

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.
@@ -1,644 +1,644 @@
1
- # NsTable 组件族使用说明(`NsTableContainer` / `NsSearch` / `NsTable`)
2
-
3
- 本文档只描述当前仓库表格体系的真实能力,重点帮助 AI 生成“搜索、表格、分页、列渲染、跨页选择”一体化页面代码。
4
-
5
- ## 1. 组件关系
6
-
7
- | 组件 | 路径 | 作用 |
8
- |---|---|---|
9
- | `NsTableContainer` | `packages/components/NsTable/PageContainer.vue` | 搜索 + 表格 + 分页 + 选中状态总控 |
10
- | `NsSearch` | `packages/components/NsTable/PageSearch.vue` | 搜索区 |
11
- | `NsTable` | `packages/components/NsTable/PageTable.vue` | 表格主体与分页条 |
12
- | `TableColumn` | `packages/components/NsTable/TableColumn.js` | 递归列渲染器 |
13
-
14
- 推荐优先使用 `NsTableContainer`,这样搜索、分页、选中同步逻辑都交给容器维护。
15
-
16
- ## 2. 最小接入示例
17
-
18
- ```vue
19
- <NsTableContainer
20
- ref="tableRef"
21
- :search-items="searchItems"
22
- :table-data="tableData"
23
- :columns="columns"
24
- :total="total"
25
- :table-props="{ rowKey: 'id', showSelection: true, showIndex: true, showLoading: true }"
26
- :load-data="fetchData"
27
- @search="handleSearch"
28
- @selection-change="handleSelectionChange"
29
- />
30
- ```
31
-
32
- ## 3. `NsTableContainer`
33
-
34
- ### 3.1 Props
35
-
36
- | 属性 | 类型 | 默认值 | 说明 |
37
- |---|---|---|---|
38
- | `showSearch` | `Boolean` | `true` | 是否显示搜索区 |
39
- | `externalSearchParams` | `Object` | `{}` | 搜索默认值或外部回填值 |
40
- | `searchItems` | `Array` | `[]` | 搜索配置 |
41
- | `tableData` | `Array` | `[]` | 表格数据 |
42
- | `columns` | `Array` | `[]` | 列配置 |
43
- | `actionButtons` | `Array` | `[]` | 预留字段,当前未直接渲染 |
44
- | `total` | `Number` | `0` | 总条数 |
45
- | `currentPage` | `Number \| null` | `null` | 受控页码 |
46
- | `pageSize` | `Number \| null` | `null` | 受控每页条数 |
47
- | `pageNumberKey` | `String` | `'currentPage'` | `getPagination()` 返回对象的页码键名 |
48
- | `pageSizeKey` | `String` | `'pageSize'` | `getPagination()` 返回对象的条数键名 |
49
- | `pageTotalKey` | `String` | `'total'` | `getPagination()` 返回对象的总数键名 |
50
- | `enterTrigger` | `Boolean` | `true` | 是否开启回车触发“查询”按钮 |
51
- | `searchProps` | `Object` | `{}` | 透传给 `NsSearch` |
52
- | `tableProps` | `Object` | `{}` | 透传给 `NsTable` |
53
- | `loadData` | `Function \| null` | `null` | 分页变化时调用 |
54
-
55
- ### 3.2 事件
56
-
57
- | 事件 | 参数 | 说明 |
58
- |---|---|---|
59
- | `search` | `params` | 查询触发 |
60
- | `reset` | - | 搜索区重置完成 |
61
- | `add` | - | 顶部新增按钮点击 |
62
- | `selection-change` | `selection` | 选中行变化 |
63
- | `sort-change` | `sort` | 排序变化 |
64
- | `row-click` | `row, column, event` | 行点击 |
65
- | `link-click` | `row, column` | link 列点击 |
66
- | `size-change` | `size` | 每页条数变化 |
67
- | `current-change` | `page` | 页码变化 |
68
- | `page-change` | `{ currentPage, pageSize }` | 页码或条数变化后的统一事件 |
69
- | `update:currentPage` | `page` | 双向绑定页码 |
70
- | `update:pageSize` | `size` | 双向绑定条数 |
71
-
72
- ### 3.3 当前实现的关键行为
73
-
74
- - 搜索或重置时会先清空选中状态
75
- - `NsSearch` 触发查询时会携带 `_resetPage: true`,容器收到后会把页码重置为 `1`
76
- - `handleSizeChange` 与 `handleCurrentChange` 会自动调用 `loadData()`
77
- - `initSearchAndLoad()` 在 `showSearch=true` 时优先触发 `search` 事件,而不是直接调用 `loadData()`
78
-
79
- ### 3.4 实例方法
80
-
81
- | 方法 | 说明 |
82
- |---|---|
83
- | `initSearchAndLoad()` | 初始化查询 |
84
- | `getSearchFormData()` | 读取搜索表单 |
85
- | `setSearchFormData(data)` | 回填搜索表单 |
86
- | `resetSearchForm()` | 重置搜索表单 |
87
- | `validateSearchForm()` | 校验搜索表单 |
88
- | `getPagination()` | 获取分页对象 |
89
- | `getSelectionRows()` | 获取选中行 |
90
- | `getSelectionKeys()` | 获取选中 key |
91
- | `setSelectionRows(rows)` | 设置选中行 |
92
- | `setSelectionKeys(keys)` | 设置选中 key |
93
- | `clearAllSelection()` | 清空选中 |
94
- | `selectAll()` | 全选当前页 |
95
- | `isRowSelected(row)` | 判断某行是否选中 |
96
- | `isKeySelected(key)` | 判断某 key 是否选中 |
97
-
98
- ### 3.5 插槽
99
-
100
- | 插槽名 | 说明 |
101
- |---|---|
102
- | `extend` | 位于搜索区与表格之间的扩展区域,可放置业务提示、统计信息或额外操作 |
103
-
104
- ```vue
105
- <NsTableContainer
106
- :search-items="searchItems"
107
- :table-data="tableData"
108
- :columns="columns"
109
- :total="total"
110
- >
111
- <template v-slot:extend>
112
- <el-alert
113
- type="info"
114
- :closable="false"
115
- show-icon
116
- title="这里是 extend 插槽"
117
- />
118
- </template>
119
- </NsTableContainer>
120
- ```
121
-
122
- ## 4. `NsSearch`
123
-
124
- ### 4.1 Props
125
-
126
- | 属性 | 类型 | 默认值 | 说明 |
127
- |---|---|---|---|
128
- | `items` | `Array` | `[]` | 搜索项配置 |
129
- | `externalParams` | `Object` | `{}` | 默认值或外部注入值 |
130
- | `defaultSpan` | `Number` | `6` | 每项默认栅格 |
131
- | `showCollapse` | `Boolean` | `true` | 是否显示展开/收起 |
132
- | `collapseLimit` | `Number` | `3` | 折叠时显示数量 |
133
- | `slotRenderers` | `Object` | `{}` | 外部插槽渲染器映射 |
134
- | `actionsAlign` | `String` | `'left'` | 查询/重置按钮对齐方式,支持 `left / center / right` |
135
- | `actionsSpan` | `Number \| null` | `null` | 查询/重置按钮栏独立栅格,未设置时回退 `defaultSpan` |
136
- | `actionsWidth` | `String \| Number` | `''` | 查询/重置按钮栏独立宽度,支持如 `320px`、`30%`、`280` |
137
- | `collapseToggleText` | `Array \| String` | `['展开','收起']` | 展开/收起按钮文案,推荐传数组 `[展开文案, 收起文案]` |
138
- | `enterTrigger` | `Boolean` | `true` | 是否给“查询”按钮挂载 `v-enterClick` |
139
-
140
- ### 4.2 事件
141
-
142
- | 事件 | 参数 | 说明 |
143
- |---|---|---|
144
- | `search` | `formData + _resetPage` | 查询触发 |
145
- | `reset` | - | 重置触发 |
146
-
147
- ### 4.3 按钮栏布局配置
148
-
149
- `NsSearch` 默认按钮居左;可通过 `actionsAlign` 配置:
150
-
151
- - `left`:按钮居左(默认)
152
- - `center`:按钮居中
153
- - `right`:按钮居右
154
-
155
- 还可单独控制按钮栏宽度:
156
-
157
- - `actionsSpan`:按栅格系统控制按钮栏占位
158
- - `actionsWidth`:按固定宽度控制按钮栏展示宽度
159
- - `collapseToggleText`:推荐使用数组 `[展开文案, 收起文案]`,也兼容字符串 `展开文案/收起文案`
160
- - `enterTrigger`:默认 `true`;设为 `false` 时,回车不会自动触发查询按钮点击
161
- - 同时设置时:`actionsSpan` 与 `actionsWidth` 会同时生效,实际显示宽度通常以 `actionsWidth` 为主
162
- - `TableDemo` 示例页中,`actionsSpan` 与 `actionsWidth` 放在同一行联动演示;`actionsSpan` 输入值会在示例代码里转成数字后再透传给 `NsSearch`
163
-
164
- 示例:
165
-
166
- ```vue
167
- <NsSearch
168
- :items="searchItems"
169
- :actionsAlign="'right'"
170
- :actionsSpan="8"
171
- actionsWidth="320px"
172
- :collapseToggleText="['更多', '收起']"
173
- />
174
- ```
175
-
176
- ### 4.4 实例方法
177
-
178
- | 方法 | 返回 | 说明 |
179
- |---|---|---|
180
- | `getFormData()` | `Object` | 获取搜索值 |
181
- | `setFormData(data)` | `void` | 合并回填 |
182
- | `resetForm()` | `void` | 重置并触发查询 |
183
- | `validate()` | `Promise<Boolean>` | 表单校验 |
184
- | `clearValidate(props?)` | `void` | 清除校验 |
185
-
186
- ### 4.5 搜索项配置 `items[]`
187
-
188
- | 字段 | 类型 | 说明 |
189
- |---|---|---|
190
- | `prop` | `String` | 表单字段名 |
191
- | `label` | `String` | 标签 |
192
- | `span` | `Number` | 栅格宽度 |
193
- | `component` | `String \| Component` | 组件 |
194
- | `attrs` | `Object` | 透传给组件的属性 |
195
- | `events` | `Object` | 透传给组件的事件 |
196
- | `children` | `Array` | `ElSelect` 的选项源 |
197
- | `defaultValue` | `Any` | 默认值 |
198
- | `formItemAttrs` | `Object` | 透传给 `el-form-item` |
199
- | `type` | `String` | `slot` 表示该项用插槽渲染 |
200
- | `slot` | `Boolean \| String` | 插槽开关或插槽名 |
201
-
202
- ### 4.6 搜索区插槽
203
-
204
- | 插槽名 | scope |
205
- |---|---|
206
- | `actions-after-reset` | `{ formData, handleSearch, handleReset, isCollapsed }` |
207
- | 自定义搜索项插槽 | `{ formData, item }` |
208
-
209
- ```vue
210
- <template v-slot:actions-after-reset="{ formData, handleSearch }">
211
- <el-button type="text" @click="() => { formData.status = 1; handleSearch() }">仅看启用</el-button>
212
- </template>
213
- ```
214
-
215
- ## 5. `NsTable`
216
-
217
- ### 5.1 Props
218
-
219
- | 属性 | 类型 | 默认值 | 说明 |
220
- |---|---|---|---|
221
- | `tableData` | `Array` | `[]` | 表格数据 |
222
- | `columns` | `Array` | `[]` | 列配置 |
223
- | `actionButtons` | `Array` | `[]` | 预留字段 |
224
- | `showAddButton` | `Boolean` | `true` | 是否显示默认新增按钮 |
225
- | `addButtonText` | `String` | `'新增'` | 新增按钮文案 |
226
- | `showHeaderToolbar` | `Boolean` | `true` | 是否显示头部工具栏 |
227
- | `showSelection` | `Boolean` | `false` | 是否显示多选列 |
228
- | `showIndex` | `Boolean` | `false` | 是否显示序号列 |
229
- | `indexWidth` | `String \| Number` | `60` | 序号列宽度 |
230
- | `indexAlign` | `String` | `''` | 序号列单元格对齐方式,支持 `left / center / right` |
231
- | `indexHeaderAlign` | `String` | `''` | 序号列表头对齐方式;未设置时回退 `indexAlign` |
232
- | `border` | `Boolean` | `true` | 边框 |
233
- | `stripe` | `Boolean` | `false` | 斑马纹 |
234
- | `height` | `String \| Number` | `undefined` | 固定高度 |
235
- | `maxHeight` | `String \| Number` | `undefined` | 最大高度 |
236
- | `autoHeight` | `Boolean` | `true` | 未指定高度时自动填充 |
237
- | `rowKey` | `String \| Function` | `undefined` | 行唯一键 |
238
- | `defaultExpandAll` | `Boolean` | `false` | 树表默认展开 |
239
- | `highlightCurrentRow` | `Boolean` | `false` | 高亮当前行 |
240
- | `loading` | `Boolean` | `false` | 加载状态 |
241
- | `showPagination` | `Boolean` | `true` | 是否显示分页 |
242
- | `total` | `Number` | `0` | 总数 |
243
- | `currentPage` | `Number \| null` | `null` | 受控页码 |
244
- | `pageSize` | `Number \| null` | `null` | 受控页大小 |
245
- | `pageSizes` | `Array` | `[10,20,50,100]` | 条数选项 |
246
- | `paginationLayout` | `String` | `'total, sizes, prev, pager, next, jumper'` | 分页布局 |
247
- | `pageNumberKey` | `String` | `'currentPage'` | 分页对象页码键名 |
248
- | `pageSizeKey` | `String` | `'pageSize'` | 分页对象条数键名 |
249
- | `pageTotalKey` | `String` | `'total'` | 分页对象总数字段 |
250
- | `slotRenderers` | `Object` | `{}` | 外部插槽渲染器 |
251
-
252
- ### 5.2 事件
253
-
254
- | 事件 | 参数 |
255
- |---|---|
256
- | `add` | - |
257
- | `selection-change` | `selection` |
258
- | `sort-change` | `sort` |
259
- | `row-click` | `row, column, event` |
260
- | `size-change` | `size` |
261
- | `current-change` | `page` |
262
- | `link-click` | `row, column` |
263
- | `update:currentPage` | `page` |
264
- | `update:pageSize` | `size` |
265
-
266
- ### 5.3 透传能力
267
-
268
- - `$attrs` 会透传给内部 `el-table`
269
- - `$listeners` 也会透传,但会排除保留事件
270
- - 因此可直接继续传 `default-sort`、`row-class-name`、`cell-class-name` 等原生能力
271
-
272
- ### 5.4 实例方法
273
-
274
- 分为三类:
275
-
276
- - 选择相关:`getSelectionRows`、`getSelectionKeys`、`setSelectionRows`、`setSelectionKeys`、`clearSelection`、`toggleRowSelection`、`toggleAllSelection`、`selectAll`、`clearAllSelection`、`isRowSelected`、`isKeySelected`
277
- - 表格控制:`clearSort`、`clearFilter`、`doLayout`、`sort`
278
- - 分页控制:`resetPage`、`setPage`、`setPageSize`、`getPagination`、`setPagination`
279
-
280
- ### 5.5 真实限制
281
-
282
- - `setPagination(pagination)` 只读取 `pagination.currentPage` 和 `pagination.pageSize`
283
- - 它不会识别自定义键名 `pageNumberKey / pageSizeKey`
284
- - 若使用 `header-actions` 插槽,默认新增按钮会隐藏
285
-
286
- ## 6. 列配置 `columns[]`
287
-
288
- `TableColumn.js` 会递归渲染列,因此支持普通列、分组列、操作列、自定义渲染列。
289
-
290
- ### 6.1 可直接透传的原生列属性
291
-
292
- `el-table-column` 的常规字段都可以直接写,例如:
293
-
294
- - `prop`
295
- - `label`
296
- - `width`
297
- - `minWidth`
298
- - `sortable`
299
- - `fixed`
300
- - `align`
301
-
302
- ### 6.2 扩展字段
303
-
304
- | 字段 | 类型 | 说明 |
305
- |---|---|---|
306
- | `children` | `Array<Column>` | 子列 |
307
- | `type` | `String` | `action` / `tag` / `image` / `link` |
308
- | `slot` | `String` | 单元格插槽名 |
309
- | `headerSlot` | `String` | 表头插槽名 |
310
- | `buttons` | `Array` | `action` 列按钮列表 |
311
- | `enum` | `Array` | 枚举映射 |
312
- | `options` | `Array` | 同样可用于 tag 枚举映射 |
313
- | `formatter` | `Function` | 自定义显示函数 |
314
- | `tagType` | `String \| Function` | tag 类型 |
315
- | `tagSize` | `String` | tag 大小 |
316
- | `imageWidth` | `String \| Number` | 图片宽度 |
317
- | `imageHeight` | `String \| Number` | 图片高度 |
318
- | `linkText` | `String` | link 文案 |
319
-
320
- ### 6.3 `action` 列按钮配置
321
-
322
- | 字段 | 类型 | 说明 |
323
- |---|---|---|
324
- | `label` | `String` | 按钮文本 |
325
- | `type` | `String` | 按钮类型 |
326
- | `icon` | `String` | 图标类名 |
327
- | `size` | `String` | 尺寸 |
328
- | `link` | `Boolean` | 为真时渲染成 `text` 风格 |
329
- | `show` | `Boolean \| Function` | 是否显示 |
330
- | `disabled` | `Boolean \| Function` | 是否禁用 |
331
- | `handler` | `Function` | 点击回调 |
332
- | `slot` | `String` | 自定义按钮渲染器名称 |
333
-
334
- ### 6.4 当前实现细节
335
-
336
- - `show(row)` 和 `disabled(row)` 当前只接收 `row`,不要假设一定有 `index`
337
- - `handler(row, index)` 会收到行数据与行序号
338
- - `button.slot` 对应的渲染器会收到 `{ row, column, button, $index }`
339
- - `button.style` 当前实现不会被渲染到按钮上,生成代码时不要依赖它
340
-
341
- ### 6.5 常用列示例
342
-
343
- ```js
344
- const columns = [
345
- { prop: 'name', label: '名称', minWidth: 160 },
346
- {
347
- prop: 'status',
348
- label: '状态',
349
- type: 'tag',
350
- options: [
351
- { label: '启用', value: 1 },
352
- { label: '禁用', value: 0 },
353
- ],
354
- tagType: (row) => (row.status === 1 ? 'success' : 'info'),
355
- },
356
- {
357
- prop: 'avatar',
358
- label: '头像',
359
- type: 'image',
360
- width: 90,
361
- imageWidth: 40,
362
- imageHeight: 40,
363
- },
364
- {
365
- prop: 'title',
366
- label: '标题',
367
- type: 'link',
368
- minWidth: 180,
369
- linkText: '查看详情',
370
- },
371
- {
372
- prop: 'customValue',
373
- label: '自定义',
374
- slot: 'custom-cell',
375
- },
376
- {
377
- type: 'action',
378
- label: '操作',
379
- width: 180,
380
- fixed: 'right',
381
- buttons: [
382
- {
383
- label: '编辑',
384
- type: 'text',
385
- handler: (row) => handleEdit(row),
386
- },
387
- {
388
- label: '删除',
389
- type: 'text',
390
- show: (row) => row.status !== -1,
391
- handler: (row) => handleDelete(row),
392
- },
393
- ],
394
- },
395
- {
396
- label: '分组信息',
397
- children: [
398
- { prop: 'groupName', label: '组名', width: 120 },
399
- { prop: 'groupLeader', label: '组长', width: 120 },
400
- ],
401
- },
402
- ]
403
- ```
404
-
405
- ## 7. 插槽与 `slotRenderers`
406
-
407
- ### 7.1 推荐优先级
408
-
409
- 优先使用普通 Vue 具名插槽;只有在需要把渲染函数直接写进配置对象时,才使用 `slotRenderers`。
410
-
411
- ### 7.2 支持的插槽名
412
-
413
- | 插槽名 | 位置 | scope |
414
- |---|---|---|
415
- | `header-left` | 表格头部左侧 | `{ tableData }` |
416
- | `header-actions` | 表格头部右侧 | `{ tableData }` |
417
- | `empty` | 空状态 | `{ tableData }` |
418
- | `actions-after-reset` | 搜索区重置按钮后 | `{ formData, handleSearch, handleReset, isCollapsed }` |
419
- | 搜索项 slot | `NsSearch` | `{ formData, item }` |
420
- | 列 `slot` | `NsTable` | `{ row, column, $index }` |
421
- | 列 `headerSlot` | `NsTable` | Element 表头 scope |
422
- | `button.slot` | `action` 按钮 | `{ row, column, button, $index }` |
423
-
424
- ### 7.3 `slotRenderers` 的真实签名
425
-
426
- 当前实现不是 `(type, context) => VNode`,而是直接:
427
-
428
- ```js
429
- (scope) => VNode
430
- ```
431
-
432
- 也就是说,渲染函数直接收到 scope 对象。
433
-
434
- ```js
435
- const slotRenderers = {
436
- statusTag: ({ row }) => h('el-tag', { props: { type: row.status === 1 ? 'success' : 'info' } }, row.status === 1 ? '启用' : '禁用'),
437
- }
438
- ```
439
-
440
- ## 8. 跨页选择
441
-
442
- 如果 `NsTableContainer` 配置了 `tableProps.rowKey`,容器会自动维护一份跨页选中 key 集合。
443
-
444
- ### 8.1 必要条件
445
-
446
- - 必须设置 `tableProps.rowKey`
447
- - `rowKey` 可以是字符串字段名,也可以是函数
448
-
449
- ### 8.2 容器会帮你做什么
450
-
451
- - 翻页后自动把当前页里已选中的行重新勾上
452
- - `getSelectionKeys()` 返回跨页累计 key
453
- - `getSelectionRows()` 返回当前已缓存的选中行对象
454
-
455
- ### 8.3 AI 生成代码约束
456
-
457
- - 若要稳定使用 `getSelectionKeys / setSelectionKeys / isKeySelected`,一定提供 `rowKey`
458
- - 若业务需要跨页全量批量操作,建议以 key 集合为主,不要只依赖当前页 selection 数组
459
-
460
- ## 9. Demo 应覆盖的能力
461
-
462
- AI 生成页面时,建议至少覆盖下列功能点:
463
-
464
- | 模块 | 建议能力 |
465
- |---|---|
466
- | 搜索区 | `ElInput`、`ElSelect`、`slot` 项、默认值、折叠展开 |
467
- | 表格列 | 普通列、分组列、tag、image、link、slot、headerSlot、action |
468
- | 交互 | `search`、`reset`、`add`、`selection-change`、`sort-change`、`row-click`、`link-click` |
469
- | 选择 | `rowKey`、跨页选择、批量操作 |
470
- | 扩展 | `actions-after-reset`、`header-actions`、`empty` |
471
-
472
- ## 10. AI 生成代码规则
473
-
474
- - 优先使用 `NsTableContainer`
475
- - 列配置只写当前实现支持的字段
476
- - `slotRenderers` 写成 `(scope) => VNode`
477
- - 动作按钮显隐逻辑用 `show(row)`,不要依赖第二个参数
478
- - 如需跨页勾选,必须配置 `tableProps.rowKey`
479
- - 若用自定义 `header-actions`,不要再依赖默认新增按钮
480
- - 如果使用 `setPagination()`,传入对象必须是 `{ currentPage, pageSize }`
481
-
482
- ## 11. 推荐 Prompt
483
-
484
- ```text
485
- 请生成 Vue2.7 + script setup 的 NsTableContainer 页面,要求:
486
- 1) searchItems 覆盖 ElInput、ElSelect、ElDatePicker、slot 项、defaultValue;
487
- 2) columns 覆盖普通文本列、children 分组列、image、link、tag、自定义 slot、headerSlot、action;
488
- 3) 监听 search、reset、add、selection-change、sort-change、row-click、size-change、current-change、page-change、link-click;
489
- 4) 提供 rowKey,并演示 getSelectionKeys、setSelectionKeys、isKeySelected;
490
- 5) 提供 actions-after-reset、header-actions、empty 插槽示例;
491
- 6) 如使用 slotRenderers,按当前项目的 (scope) => VNode 签名实现;
492
- 7) 不使用 TS,不虚构不存在的 props 或方法。
493
- ```
494
-
495
- ## 12. 标准模板
496
-
497
- ```vue
498
- <template>
499
- <NsTableContainer
500
- ref="tableRef"
501
- page-number-key="pageNo"
502
- page-size-key="pageSize"
503
- page-total-key="totalCount"
504
- :search-items="searchItems"
505
- :table-data="tableData"
506
- :columns="columns"
507
- :total="total"
508
- :table-props="{
509
- rowKey: 'id',
510
- showSelection: true,
511
- showIndex: true,
512
- indexWidth: 72,
513
- indexAlign: 'center',
514
- border: true,
515
- stripe: true,
516
- paginationLayout: 'total, sizes, prev, pager, next',
517
- }"
518
- :load-data="fetchData"
519
- @search="onSearch"
520
- @reset="onReset"
521
- @add="onAdd"
522
- @selection-change="onSelectionChange"
523
- @link-click="onLinkClick"
524
- >
525
- <template v-slot:actions-after-reset="{ formData, handleSearch }">
526
- <el-button type="text" @click="() => { formData.status = 1; handleSearch() }">仅看启用</el-button>
527
- </template>
528
-
529
- <template v-slot:header-actions>
530
- <el-button type="primary" @click="onAdd">新增</el-button>
531
- <el-button :disabled="!selectedIds.length" @click="batchDelete">批量删除</el-button>
532
- </template>
533
-
534
- <template v-slot:status="{ row }">
535
- <el-tag :type="row.status === 1 ? 'success' : 'info'">
536
- {{ row.status === 1 ? '启用' : '禁用' }}
537
- </el-tag>
538
- </template>
539
-
540
- <template v-slot:empty>
541
- <el-empty description="暂无数据" />
542
- </template>
543
- </NsTableContainer>
544
- </template>
545
-
546
- <script setup>
547
- import { ref, onMounted } from 'vue'
548
-
549
- const tableRef = ref(null)
550
- const tableData = ref([])
551
- const total = ref(0)
552
- const selectedIds = ref([])
553
-
554
- const searchItems = ref([
555
- {
556
- prop: 'keyword',
557
- label: '关键字',
558
- component: 'ElInput',
559
- attrs: { clearable: true, placeholder: '请输入关键字' },
560
- },
561
- {
562
- prop: 'status',
563
- label: '状态',
564
- component: 'ElSelect',
565
- attrs: { clearable: true },
566
- children: [
567
- { label: '启用', value: 1 },
568
- { label: '禁用', value: 0 },
569
- ],
570
- },
571
- ])
572
-
573
- const columns = ref([
574
- { prop: 'name', label: '名称', minWidth: 160, type: 'link' },
575
- { prop: 'code', label: '编码', width: 120 },
576
- { prop: 'status', label: '状态', width: 100, slot: 'status' },
577
- { prop: 'createTime', label: '创建时间', width: 180, sortable: true },
578
- {
579
- type: 'action',
580
- label: '操作',
581
- width: 160,
582
- fixed: 'right',
583
- buttons: [
584
- {
585
- label: '编辑',
586
- type: 'text',
587
- handler: (row) => console.log('编辑', row),
588
- },
589
- {
590
- label: '删除',
591
- type: 'text',
592
- handler: (row) => console.log('删除', row),
593
- },
594
- ],
595
- },
596
- ])
597
-
598
- const fetchData = async () => {
599
- const searchParams = tableRef.value.getSearchFormData()
600
- const pagination = tableRef.value.getPagination()
601
- console.log({ ...searchParams, ...pagination })
602
-
603
- tableData.value = [
604
- { id: 1, name: '项目一', code: 'P001', status: 1, createTime: '2026-01-01' },
605
- { id: 2, name: '项目二', code: 'P002', status: 0, createTime: '2026-01-02' },
606
- ]
607
- total.value = 2
608
- }
609
-
610
- const onSearch = () => {
611
- fetchData()
612
- }
613
-
614
- const onReset = () => {
615
- console.log('重置完成')
616
- }
617
-
618
- const onAdd = () => {
619
- console.log('新增')
620
- }
621
-
622
- const onSelectionChange = (selection) => {
623
- selectedIds.value = selection.map((item) => item.id)
624
- }
625
-
626
- const onLinkClick = (row) => {
627
- console.log('查看详情', row)
628
- }
629
-
630
- const batchDelete = () => {
631
- console.log('批量删除', selectedIds.value)
632
- }
633
-
634
- onMounted(() => {
635
- tableRef.value?.initSearchAndLoad()
636
- })
637
- </script>
638
-
639
- <style scoped>
640
- .table-demo /deep/ .page-table__pagination {
641
- justify-content: center;
642
- }
643
- </style>
644
- ```
1
+ # NsTable 组件族使用说明(`NsTableContainer` / `NsSearch` / `NsTable`)
2
+
3
+ 本文档只描述当前仓库表格体系的真实能力,重点帮助 AI 生成“搜索、表格、分页、列渲染、跨页选择”一体化页面代码。
4
+
5
+ ## 1. 组件关系
6
+
7
+ | 组件 | 路径 | 作用 |
8
+ |---|---|---|
9
+ | `NsTableContainer` | `packages/components/NsTable/PageContainer.vue` | 搜索 + 表格 + 分页 + 选中状态总控 |
10
+ | `NsSearch` | `packages/components/NsTable/PageSearch.vue` | 搜索区 |
11
+ | `NsTable` | `packages/components/NsTable/PageTable.vue` | 表格主体与分页条 |
12
+ | `TableColumn` | `packages/components/NsTable/TableColumn.js` | 递归列渲染器 |
13
+
14
+ 推荐优先使用 `NsTableContainer`,这样搜索、分页、选中同步逻辑都交给容器维护。
15
+
16
+ ## 2. 最小接入示例
17
+
18
+ ```vue
19
+ <NsTableContainer
20
+ ref="tableRef"
21
+ :search-items="searchItems"
22
+ :table-data="tableData"
23
+ :columns="columns"
24
+ :total="total"
25
+ :table-props="{ rowKey: 'id', showSelection: true, showIndex: true }"
26
+ :load-data="fetchData"
27
+ @search="handleSearch"
28
+ @selection-change="handleSelectionChange"
29
+ />
30
+ ```
31
+
32
+ ## 3. `NsTableContainer`
33
+
34
+ ### 3.1 Props
35
+
36
+ | 属性 | 类型 | 默认值 | 说明 |
37
+ |---|---|---|---|
38
+ | `showSearch` | `Boolean` | `true` | 是否显示搜索区 |
39
+ | `externalSearchParams` | `Object` | `{}` | 搜索默认值或外部回填值 |
40
+ | `searchItems` | `Array` | `[]` | 搜索配置 |
41
+ | `tableData` | `Array` | `[]` | 表格数据 |
42
+ | `columns` | `Array` | `[]` | 列配置 |
43
+ | `actionButtons` | `Array` | `[]` | 预留字段,当前未直接渲染 |
44
+ | `total` | `Number` | `0` | 总条数 |
45
+ | `currentPage` | `Number \| null` | `null` | 受控页码 |
46
+ | `pageSize` | `Number \| null` | `null` | 受控每页条数 |
47
+ | `pageNumberKey` | `String` | `'currentPage'` | `getPagination()` 返回对象的页码键名 |
48
+ | `pageSizeKey` | `String` | `'pageSize'` | `getPagination()` 返回对象的条数键名 |
49
+ | `pageTotalKey` | `String` | `'total'` | `getPagination()` 返回对象的总数键名 |
50
+ | `enterTrigger` | `Boolean` | `true` | 是否开启回车触发“查询”按钮 |
51
+ | `searchProps` | `Object` | `{}` | 透传给 `NsSearch` |
52
+ | `tableProps` | `Object` | `{}` | 透传给 `NsTable` |
53
+ | `loadData` | `Function \| null` | `null` | 分页变化时调用 |
54
+
55
+ ### 3.2 事件
56
+
57
+ | 事件 | 参数 | 说明 |
58
+ |---|---|---|
59
+ | `search` | `params` | 查询触发 |
60
+ | `reset` | - | 搜索区重置完成 |
61
+ | `add` | - | 顶部新增按钮点击 |
62
+ | `selection-change` | `selection` | 选中行变化 |
63
+ | `sort-change` | `sort` | 排序变化 |
64
+ | `row-click` | `row, column, event` | 行点击 |
65
+ | `link-click` | `row, column` | link 列点击 |
66
+ | `size-change` | `size` | 每页条数变化 |
67
+ | `current-change` | `page` | 页码变化 |
68
+ | `page-change` | `{ currentPage, pageSize }` | 页码或条数变化后的统一事件 |
69
+ | `update:currentPage` | `page` | 双向绑定页码 |
70
+ | `update:pageSize` | `size` | 双向绑定条数 |
71
+
72
+ ### 3.3 当前实现的关键行为
73
+
74
+ - 搜索或重置时会先清空选中状态
75
+ - `NsSearch` 触发查询时会携带 `_resetPage: true`,容器收到后会把页码重置为 `1`
76
+ - `handleSizeChange` 与 `handleCurrentChange` 会自动调用 `loadData()`
77
+ - `initSearchAndLoad()` 在 `showSearch=true` 时优先触发 `search` 事件,而不是直接调用 `loadData()`
78
+
79
+ ### 3.4 实例方法
80
+
81
+ | 方法 | 说明 |
82
+ |---|---|
83
+ | `initSearchAndLoad()` | 初始化查询 |
84
+ | `getSearchFormData()` | 读取搜索表单 |
85
+ | `setSearchFormData(data)` | 回填搜索表单 |
86
+ | `resetSearchForm()` | 重置搜索表单 |
87
+ | `validateSearchForm()` | 校验搜索表单 |
88
+ | `getPagination()` | 获取分页对象 |
89
+ | `getSelectionRows()` | 获取选中行 |
90
+ | `getSelectionKeys()` | 获取选中 key |
91
+ | `setSelectionRows(rows)` | 设置选中行 |
92
+ | `setSelectionKeys(keys)` | 设置选中 key |
93
+ | `clearAllSelection()` | 清空选中 |
94
+ | `selectAll()` | 全选当前页 |
95
+ | `isRowSelected(row)` | 判断某行是否选中 |
96
+ | `isKeySelected(key)` | 判断某 key 是否选中 |
97
+
98
+ ### 3.5 插槽
99
+
100
+ | 插槽名 | 说明 |
101
+ |---|---|
102
+ | `extend` | 位于搜索区与表格之间的扩展区域,可放置业务提示、统计信息或额外操作 |
103
+
104
+ ```vue
105
+ <NsTableContainer
106
+ :search-items="searchItems"
107
+ :table-data="tableData"
108
+ :columns="columns"
109
+ :total="total"
110
+ >
111
+ <template v-slot:extend>
112
+ <el-alert
113
+ type="info"
114
+ :closable="false"
115
+ show-icon
116
+ title="这里是 extend 插槽"
117
+ />
118
+ </template>
119
+ </NsTableContainer>
120
+ ```
121
+
122
+ ## 4. `NsSearch`
123
+
124
+ ### 4.1 Props
125
+
126
+ | 属性 | 类型 | 默认值 | 说明 |
127
+ |---|---|---|---|
128
+ | `items` | `Array` | `[]` | 搜索项配置 |
129
+ | `externalParams` | `Object` | `{}` | 默认值或外部注入值 |
130
+ | `defaultSpan` | `Number` | `6` | 每项默认栅格 |
131
+ | `showCollapse` | `Boolean` | `true` | 是否显示展开/收起 |
132
+ | `collapseLimit` | `Number` | `3` | 折叠时显示数量 |
133
+ | `slotRenderers` | `Object` | `{}` | 外部插槽渲染器映射 |
134
+ | `actionsAlign` | `String` | `'left'` | 查询/重置按钮对齐方式,支持 `left / center / right` |
135
+ | `actionsSpan` | `Number \| null` | `null` | 查询/重置按钮栏独立栅格,未设置时回退 `defaultSpan` |
136
+ | `actionsWidth` | `String \| Number` | `''` | 查询/重置按钮栏独立宽度,支持如 `320px`、`30%`、`280` |
137
+ | `collapseToggleText` | `Array \| String` | `['展开','收起']` | 展开/收起按钮文案,推荐传数组 `[展开文案, 收起文案]` |
138
+ | `enterTrigger` | `Boolean` | `true` | 是否给“查询”按钮挂载 `v-enterClick` |
139
+
140
+ ### 4.2 事件
141
+
142
+ | 事件 | 参数 | 说明 |
143
+ |---|---|---|
144
+ | `search` | `formData + _resetPage` | 查询触发 |
145
+ | `reset` | - | 重置触发 |
146
+
147
+ ### 4.3 按钮栏布局配置
148
+
149
+ `NsSearch` 默认按钮居左;可通过 `actionsAlign` 配置:
150
+
151
+ - `left`:按钮居左(默认)
152
+ - `center`:按钮居中
153
+ - `right`:按钮居右
154
+
155
+ 还可单独控制按钮栏宽度:
156
+
157
+ - `actionsSpan`:按栅格系统控制按钮栏占位
158
+ - `actionsWidth`:按固定宽度控制按钮栏展示宽度
159
+ - `collapseToggleText`:推荐使用数组 `[展开文案, 收起文案]`,也兼容字符串 `展开文案/收起文案`
160
+ - `enterTrigger`:默认 `true`;设为 `false` 时,回车不会自动触发查询按钮点击
161
+ - 同时设置时:`actionsSpan` 与 `actionsWidth` 会同时生效,实际显示宽度通常以 `actionsWidth` 为主
162
+ - `TableDemo` 示例页中,`actionsSpan` 与 `actionsWidth` 放在同一行联动演示;`actionsSpan` 输入值会在示例代码里转成数字后再透传给 `NsSearch`
163
+
164
+ 示例:
165
+
166
+ ```vue
167
+ <NsSearch
168
+ :items="searchItems"
169
+ :actionsAlign="'right'"
170
+ :actionsSpan="8"
171
+ actionsWidth="320px"
172
+ :collapseToggleText="['更多', '收起']"
173
+ />
174
+ ```
175
+
176
+ ### 4.4 实例方法
177
+
178
+ | 方法 | 返回 | 说明 |
179
+ |---|---|---|
180
+ | `getFormData()` | `Object` | 获取搜索值 |
181
+ | `setFormData(data)` | `void` | 合并回填 |
182
+ | `resetForm()` | `void` | 重置并触发查询 |
183
+ | `validate()` | `Promise<Boolean>` | 表单校验 |
184
+ | `clearValidate(props?)` | `void` | 清除校验 |
185
+
186
+ ### 4.5 搜索项配置 `items[]`
187
+
188
+ | 字段 | 类型 | 说明 |
189
+ |---|---|---|
190
+ | `prop` | `String` | 表单字段名 |
191
+ | `label` | `String` | 标签 |
192
+ | `span` | `Number` | 栅格宽度 |
193
+ | `component` | `String \| Component` | 组件 |
194
+ | `attrs` | `Object` | 透传给组件的属性 |
195
+ | `events` | `Object` | 透传给组件的事件 |
196
+ | `children` | `Array` | `ElSelect` 的选项源 |
197
+ | `defaultValue` | `Any` | 默认值 |
198
+ | `formItemAttrs` | `Object` | 透传给 `el-form-item` |
199
+ | `type` | `String` | `slot` 表示该项用插槽渲染 |
200
+ | `slot` | `Boolean \| String` | 插槽开关或插槽名 |
201
+
202
+ ### 4.6 搜索区插槽
203
+
204
+ | 插槽名 | scope |
205
+ |---|---|
206
+ | `actions-after-reset` | `{ formData, handleSearch, handleReset, isCollapsed }` |
207
+ | 自定义搜索项插槽 | `{ formData, item }` |
208
+
209
+ ```vue
210
+ <template v-slot:actions-after-reset="{ formData, handleSearch }">
211
+ <el-button type="text" @click="() => { formData.status = 1; handleSearch() }">仅看启用</el-button>
212
+ </template>
213
+ ```
214
+
215
+ ## 5. `NsTable`
216
+
217
+ ### 5.1 Props
218
+
219
+ | 属性 | 类型 | 默认值 | 说明 |
220
+ |---|---|---|---|
221
+ | `tableData` | `Array` | `[]` | 表格数据 |
222
+ | `columns` | `Array` | `[]` | 列配置 |
223
+ | `actionButtons` | `Array` | `[]` | 预留字段 |
224
+ | `showAddButton` | `Boolean` | `true` | 是否显示默认新增按钮 |
225
+ | `addButtonText` | `String` | `'新增'` | 新增按钮文案 |
226
+ | `showHeaderToolbar` | `Boolean` | `true` | 是否显示头部工具栏 |
227
+ | `showSelection` | `Boolean` | `false` | 是否显示多选列 |
228
+ | `showIndex` | `Boolean` | `false` | 是否显示序号列 |
229
+ | `indexWidth` | `String \| Number` | `60` | 序号列宽度 |
230
+ | `indexAlign` | `String` | `''` | 序号列单元格对齐方式,支持 `left / center / right` |
231
+ | `indexHeaderAlign` | `String` | `''` | 序号列表头对齐方式;未设置时回退 `indexAlign` |
232
+ | `border` | `Boolean` | `true` | 边框 |
233
+ | `stripe` | `Boolean` | `false` | 斑马纹 |
234
+ | `height` | `String \| Number` | `undefined` | 固定高度 |
235
+ | `maxHeight` | `String \| Number` | `undefined` | 最大高度 |
236
+ | `autoHeight` | `Boolean` | `true` | 未指定高度时自动填充 |
237
+ | `rowKey` | `String \| Function` | `undefined` | 行唯一键 |
238
+ | `defaultExpandAll` | `Boolean` | `false` | 树表默认展开 |
239
+ | `highlightCurrentRow` | `Boolean` | `false` | 高亮当前行 |
240
+ | `loading` | `Boolean` | `false` | 加载状态 |
241
+ | `showPagination` | `Boolean` | `true` | 是否显示分页 |
242
+ | `total` | `Number` | `0` | 总数 |
243
+ | `currentPage` | `Number \| null` | `null` | 受控页码 |
244
+ | `pageSize` | `Number \| null` | `null` | 受控页大小 |
245
+ | `pageSizes` | `Array` | `[10,20,50,100]` | 条数选项 |
246
+ | `paginationLayout` | `String` | `'total, sizes, prev, pager, next, jumper'` | 分页布局 |
247
+ | `pageNumberKey` | `String` | `'currentPage'` | 分页对象页码键名 |
248
+ | `pageSizeKey` | `String` | `'pageSize'` | 分页对象条数键名 |
249
+ | `pageTotalKey` | `String` | `'total'` | 分页对象总数字段 |
250
+ | `slotRenderers` | `Object` | `{}` | 外部插槽渲染器 |
251
+
252
+ ### 5.2 事件
253
+
254
+ | 事件 | 参数 |
255
+ |---|---|
256
+ | `add` | - |
257
+ | `selection-change` | `selection` |
258
+ | `sort-change` | `sort` |
259
+ | `row-click` | `row, column, event` |
260
+ | `size-change` | `size` |
261
+ | `current-change` | `page` |
262
+ | `link-click` | `row, column` |
263
+ | `update:currentPage` | `page` |
264
+ | `update:pageSize` | `size` |
265
+
266
+ ### 5.3 透传能力
267
+
268
+ - `$attrs` 会透传给内部 `el-table`
269
+ - `$listeners` 也会透传,但会排除保留事件
270
+ - 因此可直接继续传 `default-sort`、`row-class-name`、`cell-class-name` 等原生能力
271
+
272
+ ### 5.4 实例方法
273
+
274
+ 分为三类:
275
+
276
+ - 选择相关:`getSelectionRows`、`getSelectionKeys`、`setSelectionRows`、`setSelectionKeys`、`clearSelection`、`toggleRowSelection`、`toggleAllSelection`、`selectAll`、`clearAllSelection`、`isRowSelected`、`isKeySelected`
277
+ - 表格控制:`clearSort`、`clearFilter`、`doLayout`、`sort`
278
+ - 分页控制:`resetPage`、`setPage`、`setPageSize`、`getPagination`、`setPagination`
279
+
280
+ ### 5.5 真实限制
281
+
282
+ - `setPagination(pagination)` 只读取 `pagination.currentPage` 和 `pagination.pageSize`
283
+ - 它不会识别自定义键名 `pageNumberKey / pageSizeKey`
284
+ - 若使用 `header-actions` 插槽,默认新增按钮会隐藏
285
+
286
+ ## 6. 列配置 `columns[]`
287
+
288
+ `TableColumn.js` 会递归渲染列,因此支持普通列、分组列、操作列、自定义渲染列。
289
+
290
+ ### 6.1 可直接透传的原生列属性
291
+
292
+ `el-table-column` 的常规字段都可以直接写,例如:
293
+
294
+ - `prop`
295
+ - `label`
296
+ - `width`
297
+ - `minWidth`
298
+ - `sortable`
299
+ - `fixed`
300
+ - `align`
301
+
302
+ ### 6.2 扩展字段
303
+
304
+ | 字段 | 类型 | 说明 |
305
+ |---|---|---|
306
+ | `children` | `Array<Column>` | 子列 |
307
+ | `type` | `String` | `action` / `tag` / `image` / `link` |
308
+ | `slot` | `String` | 单元格插槽名 |
309
+ | `headerSlot` | `String` | 表头插槽名 |
310
+ | `buttons` | `Array` | `action` 列按钮列表 |
311
+ | `enum` | `Array` | 枚举映射 |
312
+ | `options` | `Array` | 同样可用于 tag 枚举映射 |
313
+ | `formatter` | `Function` | 自定义显示函数 |
314
+ | `tagType` | `String \| Function` | tag 类型 |
315
+ | `tagSize` | `String` | tag 大小 |
316
+ | `imageWidth` | `String \| Number` | 图片宽度 |
317
+ | `imageHeight` | `String \| Number` | 图片高度 |
318
+ | `linkText` | `String` | link 文案 |
319
+
320
+ ### 6.3 `action` 列按钮配置
321
+
322
+ | 字段 | 类型 | 说明 |
323
+ |---|---|---|
324
+ | `label` | `String` | 按钮文本 |
325
+ | `type` | `String` | 按钮类型 |
326
+ | `icon` | `String` | 图标类名 |
327
+ | `size` | `String` | 尺寸 |
328
+ | `link` | `Boolean` | 为真时渲染成 `text` 风格 |
329
+ | `show` | `Boolean \| Function` | 是否显示 |
330
+ | `disabled` | `Boolean \| Function` | 是否禁用 |
331
+ | `handler` | `Function` | 点击回调 |
332
+ | `slot` | `String` | 自定义按钮渲染器名称 |
333
+
334
+ ### 6.4 当前实现细节
335
+
336
+ - `show(row)` 和 `disabled(row)` 当前只接收 `row`,不要假设一定有 `index`
337
+ - `handler(row, index)` 会收到行数据与行序号
338
+ - `button.slot` 对应的渲染器会收到 `{ row, column, button, $index }`
339
+ - `button.style` 当前实现不会被渲染到按钮上,生成代码时不要依赖它
340
+
341
+ ### 6.5 常用列示例
342
+
343
+ ```js
344
+ const columns = [
345
+ { prop: 'name', label: '名称', minWidth: 160 },
346
+ {
347
+ prop: 'status',
348
+ label: '状态',
349
+ type: 'tag',
350
+ options: [
351
+ { label: '启用', value: 1 },
352
+ { label: '禁用', value: 0 },
353
+ ],
354
+ tagType: (row) => (row.status === 1 ? 'success' : 'info'),
355
+ },
356
+ {
357
+ prop: 'avatar',
358
+ label: '头像',
359
+ type: 'image',
360
+ width: 90,
361
+ imageWidth: 40,
362
+ imageHeight: 40,
363
+ },
364
+ {
365
+ prop: 'title',
366
+ label: '标题',
367
+ type: 'link',
368
+ minWidth: 180,
369
+ linkText: '查看详情',
370
+ },
371
+ {
372
+ prop: 'customValue',
373
+ label: '自定义',
374
+ slot: 'custom-cell',
375
+ },
376
+ {
377
+ type: 'action',
378
+ label: '操作',
379
+ width: 180,
380
+ fixed: 'right',
381
+ buttons: [
382
+ {
383
+ label: '编辑',
384
+ type: 'text',
385
+ handler: (row) => handleEdit(row),
386
+ },
387
+ {
388
+ label: '删除',
389
+ type: 'text',
390
+ show: (row) => row.status !== -1,
391
+ handler: (row) => handleDelete(row),
392
+ },
393
+ ],
394
+ },
395
+ {
396
+ label: '分组信息',
397
+ children: [
398
+ { prop: 'groupName', label: '组名', width: 120 },
399
+ { prop: 'groupLeader', label: '组长', width: 120 },
400
+ ],
401
+ },
402
+ ]
403
+ ```
404
+
405
+ ## 7. 插槽与 `slotRenderers`
406
+
407
+ ### 7.1 推荐优先级
408
+
409
+ 优先使用普通 Vue 具名插槽;只有在需要把渲染函数直接写进配置对象时,才使用 `slotRenderers`。
410
+
411
+ ### 7.2 支持的插槽名
412
+
413
+ | 插槽名 | 位置 | scope |
414
+ |---|---|---|
415
+ | `header-left` | 表格头部左侧 | `{ tableData }` |
416
+ | `header-actions` | 表格头部右侧 | `{ tableData }` |
417
+ | `empty` | 空状态 | `{ tableData }` |
418
+ | `actions-after-reset` | 搜索区重置按钮后 | `{ formData, handleSearch, handleReset, isCollapsed }` |
419
+ | 搜索项 slot | `NsSearch` | `{ formData, item }` |
420
+ | 列 `slot` | `NsTable` | `{ row, column, $index }` |
421
+ | 列 `headerSlot` | `NsTable` | Element 表头 scope |
422
+ | `button.slot` | `action` 按钮 | `{ row, column, button, $index }` |
423
+
424
+ ### 7.3 `slotRenderers` 的真实签名
425
+
426
+ 当前实现不是 `(type, context) => VNode`,而是直接:
427
+
428
+ ```js
429
+ (scope) => VNode
430
+ ```
431
+
432
+ 也就是说,渲染函数直接收到 scope 对象。
433
+
434
+ ```js
435
+ const slotRenderers = {
436
+ statusTag: ({ row }) => h('el-tag', { props: { type: row.status === 1 ? 'success' : 'info' } }, row.status === 1 ? '启用' : '禁用'),
437
+ }
438
+ ```
439
+
440
+ ## 8. 跨页选择
441
+
442
+ 如果 `NsTableContainer` 配置了 `tableProps.rowKey`,容器会自动维护一份跨页选中 key 集合。
443
+
444
+ ### 8.1 必要条件
445
+
446
+ - 必须设置 `tableProps.rowKey`
447
+ - `rowKey` 可以是字符串字段名,也可以是函数
448
+
449
+ ### 8.2 容器会帮你做什么
450
+
451
+ - 翻页后自动把当前页里已选中的行重新勾上
452
+ - `getSelectionKeys()` 返回跨页累计 key
453
+ - `getSelectionRows()` 返回当前已缓存的选中行对象
454
+
455
+ ### 8.3 AI 生成代码约束
456
+
457
+ - 若要稳定使用 `getSelectionKeys / setSelectionKeys / isKeySelected`,一定提供 `rowKey`
458
+ - 若业务需要跨页全量批量操作,建议以 key 集合为主,不要只依赖当前页 selection 数组
459
+
460
+ ## 9. Demo 应覆盖的能力
461
+
462
+ AI 生成页面时,建议至少覆盖下列功能点:
463
+
464
+ | 模块 | 建议能力 |
465
+ |---|---|
466
+ | 搜索区 | `ElInput`、`ElSelect`、`slot` 项、默认值、折叠展开 |
467
+ | 表格列 | 普通列、分组列、tag、image、link、slot、headerSlot、action |
468
+ | 交互 | `search`、`reset`、`add`、`selection-change`、`sort-change`、`row-click`、`link-click` |
469
+ | 选择 | `rowKey`、跨页选择、批量操作 |
470
+ | 扩展 | `actions-after-reset`、`header-actions`、`empty` |
471
+
472
+ ## 10. AI 生成代码规则
473
+
474
+ - 优先使用 `NsTableContainer`
475
+ - 列配置只写当前实现支持的字段
476
+ - `slotRenderers` 写成 `(scope) => VNode`
477
+ - 动作按钮显隐逻辑用 `show(row)`,不要依赖第二个参数
478
+ - 如需跨页勾选,必须配置 `tableProps.rowKey`
479
+ - 若用自定义 `header-actions`,不要再依赖默认新增按钮
480
+ - 如果使用 `setPagination()`,传入对象必须是 `{ currentPage, pageSize }`
481
+
482
+ ## 11. 推荐 Prompt
483
+
484
+ ```text
485
+ 请生成 Vue2.7 + script setup 的 NsTableContainer 页面,要求:
486
+ 1) searchItems 覆盖 ElInput、ElSelect、ElDatePicker、slot 项、defaultValue;
487
+ 2) columns 覆盖普通文本列、children 分组列、image、link、tag、自定义 slot、headerSlot、action;
488
+ 3) 监听 search、reset、add、selection-change、sort-change、row-click、size-change、current-change、page-change、link-click;
489
+ 4) 提供 rowKey,并演示 getSelectionKeys、setSelectionKeys、isKeySelected;
490
+ 5) 提供 actions-after-reset、header-actions、empty 插槽示例;
491
+ 6) 如使用 slotRenderers,按当前项目的 (scope) => VNode 签名实现;
492
+ 7) 不使用 TS,不虚构不存在的 props 或方法。
493
+ ```
494
+
495
+ ## 12. 标准模板
496
+
497
+ ```vue
498
+ <template>
499
+ <NsTableContainer
500
+ ref="tableRef"
501
+ page-number-key="pageNo"
502
+ page-size-key="pageSize"
503
+ page-total-key="totalCount"
504
+ :search-items="searchItems"
505
+ :table-data="tableData"
506
+ :columns="columns"
507
+ :total="total"
508
+ :table-props="{
509
+ rowKey: 'id',
510
+ showSelection: true,
511
+ showIndex: true,
512
+ indexWidth: 72,
513
+ indexAlign: 'center',
514
+ border: true,
515
+ stripe: true,
516
+ paginationLayout: 'total, sizes, prev, pager, next',
517
+ }"
518
+ :load-data="fetchData"
519
+ @search="onSearch"
520
+ @reset="onReset"
521
+ @add="onAdd"
522
+ @selection-change="onSelectionChange"
523
+ @link-click="onLinkClick"
524
+ >
525
+ <template v-slot:actions-after-reset="{ formData, handleSearch }">
526
+ <el-button type="text" @click="() => { formData.status = 1; handleSearch() }">仅看启用</el-button>
527
+ </template>
528
+
529
+ <template v-slot:header-actions>
530
+ <el-button type="primary" @click="onAdd">新增</el-button>
531
+ <el-button :disabled="!selectedIds.length" @click="batchDelete">批量删除</el-button>
532
+ </template>
533
+
534
+ <template v-slot:status="{ row }">
535
+ <el-tag :type="row.status === 1 ? 'success' : 'info'">
536
+ {{ row.status === 1 ? '启用' : '禁用' }}
537
+ </el-tag>
538
+ </template>
539
+
540
+ <template v-slot:empty>
541
+ <el-empty description="暂无数据" />
542
+ </template>
543
+ </NsTableContainer>
544
+ </template>
545
+
546
+ <script setup>
547
+ import { ref, onMounted } from 'vue'
548
+
549
+ const tableRef = ref(null)
550
+ const tableData = ref([])
551
+ const total = ref(0)
552
+ const selectedIds = ref([])
553
+
554
+ const searchItems = ref([
555
+ {
556
+ prop: 'keyword',
557
+ label: '关键字',
558
+ component: 'ElInput',
559
+ attrs: { clearable: true, placeholder: '请输入关键字' },
560
+ },
561
+ {
562
+ prop: 'status',
563
+ label: '状态',
564
+ component: 'ElSelect',
565
+ attrs: { clearable: true },
566
+ children: [
567
+ { label: '启用', value: 1 },
568
+ { label: '禁用', value: 0 },
569
+ ],
570
+ },
571
+ ])
572
+
573
+ const columns = ref([
574
+ { prop: 'name', label: '名称', minWidth: 160, type: 'link' },
575
+ { prop: 'code', label: '编码', width: 120 },
576
+ { prop: 'status', label: '状态', width: 100, slot: 'status' },
577
+ { prop: 'createTime', label: '创建时间', width: 180, sortable: true },
578
+ {
579
+ type: 'action',
580
+ label: '操作',
581
+ width: 160,
582
+ fixed: 'right',
583
+ buttons: [
584
+ {
585
+ label: '编辑',
586
+ type: 'text',
587
+ handler: (row) => console.log('编辑', row),
588
+ },
589
+ {
590
+ label: '删除',
591
+ type: 'text',
592
+ handler: (row) => console.log('删除', row),
593
+ },
594
+ ],
595
+ },
596
+ ])
597
+
598
+ const fetchData = async () => {
599
+ const searchParams = tableRef.value.getSearchFormData()
600
+ const pagination = tableRef.value.getPagination()
601
+ console.log({ ...searchParams, ...pagination })
602
+
603
+ tableData.value = [
604
+ { id: 1, name: '项目一', code: 'P001', status: 1, createTime: '2026-01-01' },
605
+ { id: 2, name: '项目二', code: 'P002', status: 0, createTime: '2026-01-02' },
606
+ ]
607
+ total.value = 2
608
+ }
609
+
610
+ const onSearch = () => {
611
+ fetchData()
612
+ }
613
+
614
+ const onReset = () => {
615
+ console.log('重置完成')
616
+ }
617
+
618
+ const onAdd = () => {
619
+ console.log('新增')
620
+ }
621
+
622
+ const onSelectionChange = (selection) => {
623
+ selectedIds.value = selection.map((item) => item.id)
624
+ }
625
+
626
+ const onLinkClick = (row) => {
627
+ console.log('查看详情', row)
628
+ }
629
+
630
+ const batchDelete = () => {
631
+ console.log('批量删除', selectedIds.value)
632
+ }
633
+
634
+ onMounted(() => {
635
+ tableRef.value?.initSearchAndLoad()
636
+ })
637
+ </script>
638
+
639
+ <style scoped>
640
+ .table-demo /deep/ .page-table__pagination {
641
+ justify-content: center;
642
+ }
643
+ </style>
644
+ ```