vue3-components-plus 3.0.30 → 3.0.32

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