vue2-components-plus 1.0.70 → 1.0.80
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/dist/ComponentDemo/CaptchaTableDemo.vue +281 -0
- package/dist/ComponentDemo/TableDemo.md +60 -41
- package/dist/ComponentDemo/TableDemo.vue +127 -409
- package/dist/vue2-components-plus.css +1 -1
- package/dist/vue2-components-plus.js +300 -271
- package/dist/vue2-components-plus.umd.cjs +1 -1
- package/package.json +1 -1
- package/dist/vue2-components-plus.es5.js +0 -3742
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="posts-demo">
|
|
3
|
+
<el-alert
|
|
4
|
+
class="posts-demo__tip"
|
|
5
|
+
type="success"
|
|
6
|
+
show-icon
|
|
7
|
+
:closable="false"
|
|
8
|
+
title="真实接口示例:GET https://jsonplaceholder.typicode.com/posts"
|
|
9
|
+
description="使用 _page / _limit 真后端分页,X-Total-Count 响应头作为总数;支持 q 关键字搜索、userId 过滤、_sort/_order 排序。"
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
<NsTableContainer
|
|
13
|
+
ref="containerRef"
|
|
14
|
+
class="posts-demo__container"
|
|
15
|
+
:show-search="true"
|
|
16
|
+
:enter-trigger="true"
|
|
17
|
+
:search-items="searchItems"
|
|
18
|
+
:search-props="searchProps"
|
|
19
|
+
:table-data="tableData"
|
|
20
|
+
:columns="columns"
|
|
21
|
+
:total="total"
|
|
22
|
+
:table-props="tableProps"
|
|
23
|
+
@search="loadData"
|
|
24
|
+
@link-click="handleLinkClick"
|
|
25
|
+
>
|
|
26
|
+
<template v-slot:userId="{ row }">
|
|
27
|
+
<el-tag size="small" :type="getUserTagType(row.userId)">用户 {{ row.userId }}</el-tag>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<template v-slot:body="{ row }">
|
|
31
|
+
<el-tooltip effect="dark" :content="row.body" placement="top-start">
|
|
32
|
+
<span class="posts-demo__body">{{ row.body }}</span>
|
|
33
|
+
</el-tooltip>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<template v-slot:header-left>
|
|
37
|
+
<span class="posts-demo__toolbar-left">服务端共 {{ total }} 条</span>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<template v-slot:header-actions>
|
|
41
|
+
<el-button size="small" type="primary" @click="reload">刷新</el-button>
|
|
42
|
+
</template>
|
|
43
|
+
</NsTableContainer>
|
|
44
|
+
</div>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script setup>
|
|
48
|
+
import { getCurrentInstance, nextTick, onMounted, ref } from 'vue'
|
|
49
|
+
|
|
50
|
+
const POSTS_URL = 'https://jsonplaceholder.typicode.com/posts'
|
|
51
|
+
|
|
52
|
+
const containerRef = ref()
|
|
53
|
+
const loading = ref(false)
|
|
54
|
+
const tableData = ref([])
|
|
55
|
+
const total = ref(0)
|
|
56
|
+
|
|
57
|
+
const { proxy } = getCurrentInstance() || {}
|
|
58
|
+
|
|
59
|
+
const searchItems = ref([
|
|
60
|
+
{
|
|
61
|
+
prop: 'q',
|
|
62
|
+
label: '全文搜索',
|
|
63
|
+
span: 8,
|
|
64
|
+
component: 'ElInput',
|
|
65
|
+
attrs: {
|
|
66
|
+
placeholder: '在 title / body 中模糊匹配(?q=)',
|
|
67
|
+
clearable: true,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
prop: 'id',
|
|
72
|
+
label: 'ID',
|
|
73
|
+
span: 6,
|
|
74
|
+
component: 'ElInputNumber',
|
|
75
|
+
attrs: {
|
|
76
|
+
placeholder: '精确匹配(?id=)',
|
|
77
|
+
controlsPosition: 'right',
|
|
78
|
+
min: 1,
|
|
79
|
+
max: 100,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
prop: 'userId',
|
|
84
|
+
label: '所属用户',
|
|
85
|
+
span: 6,
|
|
86
|
+
component: 'ElSelect',
|
|
87
|
+
attrs: {
|
|
88
|
+
placeholder: '精确匹配(?userId=)',
|
|
89
|
+
clearable: true,
|
|
90
|
+
},
|
|
91
|
+
children: Array.from({ length: 10 }, (_, index) => ({
|
|
92
|
+
label: '用户 ' + (index + 1),
|
|
93
|
+
value: index + 1,
|
|
94
|
+
})),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
prop: 'title',
|
|
98
|
+
label: '标题',
|
|
99
|
+
span: 8,
|
|
100
|
+
component: 'ElInput',
|
|
101
|
+
attrs: {
|
|
102
|
+
placeholder: '精确匹配(?title=)',
|
|
103
|
+
clearable: true,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
])
|
|
107
|
+
|
|
108
|
+
const searchProps = {
|
|
109
|
+
labelWidth: '80px',
|
|
110
|
+
size: 'small',
|
|
111
|
+
defaultSpan: 6,
|
|
112
|
+
showCollapse: false,
|
|
113
|
+
actionsAlign: 'left',
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const tableProps = {
|
|
117
|
+
showHeaderToolbar: true,
|
|
118
|
+
showAddButton: false,
|
|
119
|
+
showSelection: false,
|
|
120
|
+
showIndex: true,
|
|
121
|
+
indexWidth: 60,
|
|
122
|
+
indexAlign: 'center',
|
|
123
|
+
loading: false,
|
|
124
|
+
rowKey: 'id',
|
|
125
|
+
showPagination: true,
|
|
126
|
+
pageSizes: [5, 10, 20, 50],
|
|
127
|
+
paginationLayout: 'total, sizes, prev, pager, next, jumper',
|
|
128
|
+
border: true,
|
|
129
|
+
stripe: true,
|
|
130
|
+
highlightCurrentRow: true,
|
|
131
|
+
autoHeight: true,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const columns = ref([
|
|
135
|
+
{
|
|
136
|
+
prop: 'id',
|
|
137
|
+
label: 'ID',
|
|
138
|
+
width: 80,
|
|
139
|
+
sortable: 'custom',
|
|
140
|
+
align: 'center',
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
prop: 'userId',
|
|
144
|
+
label: '用户',
|
|
145
|
+
width: 110,
|
|
146
|
+
sortable: 'custom',
|
|
147
|
+
slot: 'userId',
|
|
148
|
+
align: 'center',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
prop: 'title',
|
|
152
|
+
label: '标题',
|
|
153
|
+
minWidth: 260,
|
|
154
|
+
type: 'link',
|
|
155
|
+
linkText: (row) => row.title,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
prop: 'body',
|
|
159
|
+
label: '正文',
|
|
160
|
+
minWidth: 360,
|
|
161
|
+
slot: 'body',
|
|
162
|
+
},
|
|
163
|
+
])
|
|
164
|
+
|
|
165
|
+
function getUserTagType(userId) {
|
|
166
|
+
const types = ['', 'success', 'warning', 'danger', 'info']
|
|
167
|
+
return types[userId % types.length]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function buildQuery(params) {
|
|
171
|
+
const usp = new URLSearchParams()
|
|
172
|
+
Object.keys(params).forEach((key) => {
|
|
173
|
+
const value = params[key]
|
|
174
|
+
if (value === undefined || value === null || value === '') return
|
|
175
|
+
usp.append(key, String(value))
|
|
176
|
+
})
|
|
177
|
+
return usp.toString()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function fetchPosts(query) {
|
|
181
|
+
const params = query || {}
|
|
182
|
+
const requestParams = {
|
|
183
|
+
_page: params.currentPage || 1,
|
|
184
|
+
_limit: params.pageSize || 10,
|
|
185
|
+
}
|
|
186
|
+
const keyword = String(params.q || '').trim()
|
|
187
|
+
if (keyword) requestParams.q = keyword
|
|
188
|
+
if (params.id) requestParams.id = params.id
|
|
189
|
+
if (params.userId) requestParams.userId = params.userId
|
|
190
|
+
const title = String(params.title || '').trim()
|
|
191
|
+
if (title) requestParams.title = title
|
|
192
|
+
const sort = params.sort || {}
|
|
193
|
+
if (sort.prop && sort.order) {
|
|
194
|
+
requestParams._sort = sort.prop
|
|
195
|
+
requestParams._order = sort.order === 'ascending' ? 'asc' : 'desc'
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const response = await fetch(POSTS_URL + '?' + buildQuery(requestParams), {
|
|
199
|
+
method: 'GET',
|
|
200
|
+
headers: { Accept: 'application/json' },
|
|
201
|
+
})
|
|
202
|
+
if (!response.ok) {
|
|
203
|
+
throw new Error('HTTP ' + response.status)
|
|
204
|
+
}
|
|
205
|
+
const list = await response.json()
|
|
206
|
+
const totalHeader = Number(response.headers.get('x-total-count')) || (Array.isArray(list) ? list.length : 0)
|
|
207
|
+
return { list: Array.isArray(list) ? list : [], total: totalHeader }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function loadData(query) {
|
|
211
|
+
loading.value = true
|
|
212
|
+
try {
|
|
213
|
+
const { list, total: totalCount } = await fetchPosts(query)
|
|
214
|
+
tableData.value = list
|
|
215
|
+
total.value = totalCount
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (proxy && proxy.$message) {
|
|
218
|
+
proxy.$message.error('调用 posts 接口失败:' + (error && error.message ? error.message : '未知错误'))
|
|
219
|
+
}
|
|
220
|
+
tableData.value = []
|
|
221
|
+
total.value = 0
|
|
222
|
+
} finally {
|
|
223
|
+
loading.value = false
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function handleLinkClick(row) {
|
|
228
|
+
if (proxy && proxy.$message) {
|
|
229
|
+
proxy.$message.info('点击了标题:#' + row.id + ' ' + row.title)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function reload() {
|
|
234
|
+
if (containerRef.value && containerRef.value.reload) {
|
|
235
|
+
containerRef.value.reload()
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
onMounted(async () => {
|
|
240
|
+
await nextTick()
|
|
241
|
+
if (containerRef.value && containerRef.value.initSearchAndLoad) {
|
|
242
|
+
containerRef.value.initSearchAndLoad()
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
</script>
|
|
246
|
+
|
|
247
|
+
<style scoped>
|
|
248
|
+
.posts-demo {
|
|
249
|
+
display: flex;
|
|
250
|
+
flex-direction: column;
|
|
251
|
+
gap: 16px;
|
|
252
|
+
height: 100%;
|
|
253
|
+
min-height: 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.posts-demo__tip {
|
|
257
|
+
border-radius: 12px;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.posts-demo__container {
|
|
261
|
+
flex: 1;
|
|
262
|
+
min-height: 420px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.posts-demo__body {
|
|
266
|
+
display: -webkit-box;
|
|
267
|
+
-webkit-box-orient: vertical;
|
|
268
|
+
-webkit-line-clamp: 2;
|
|
269
|
+
line-clamp: 2;
|
|
270
|
+
overflow: hidden;
|
|
271
|
+
text-overflow: ellipsis;
|
|
272
|
+
color: #606266;
|
|
273
|
+
font-size: 13px;
|
|
274
|
+
line-height: 1.5;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.posts-demo__toolbar-left {
|
|
278
|
+
color: #606266;
|
|
279
|
+
font-size: 13px;
|
|
280
|
+
}
|
|
281
|
+
</style>
|
|
@@ -23,12 +23,13 @@
|
|
|
23
23
|
:columns="columns"
|
|
24
24
|
:total="total"
|
|
25
25
|
:table-props="{ rowKey: 'id', showSelection: true, showIndex: true }"
|
|
26
|
-
:load-data="fetchData"
|
|
27
26
|
@search="handleSearch"
|
|
28
27
|
@selection-change="handleSelectionChange"
|
|
29
28
|
/>
|
|
30
29
|
```
|
|
31
30
|
|
|
31
|
+
> 数据加载只需监听 `@search` 事件即可。容器会在搜索、重置、分页、排序变化时统一通过 `emitSearch()` 重新派发该事件,事件参数即可作为请求体直接发出。
|
|
32
|
+
|
|
32
33
|
## 3. `NsTableContainer`
|
|
33
34
|
|
|
34
35
|
### 3.1 Props
|
|
@@ -36,56 +37,70 @@
|
|
|
36
37
|
| 属性 | 类型 | 默认值 | 说明 |
|
|
37
38
|
|---|---|---|---|
|
|
38
39
|
| `showSearch` | `Boolean` | `true` | 是否显示搜索区 |
|
|
39
|
-
| `externalSearchParams` | `Object` | `{}` |
|
|
40
|
+
| `externalSearchParams` | `Object` | `{}` | 搜索默认值或外部回填值(合并到搜索表单初始值) |
|
|
40
41
|
| `searchItems` | `Array` | `[]` | 搜索配置 |
|
|
41
42
|
| `tableData` | `Array` | `[]` | 表格数据 |
|
|
42
43
|
| `columns` | `Array` | `[]` | 列配置 |
|
|
43
|
-
| `actionButtons` | `Array` | `[]` |
|
|
44
|
+
| `actionButtons` | `Array` | `[]` | 预留字段,当前未直接渲染(可由业务方在 `header-actions` 插槽中自行使用) |
|
|
44
45
|
| `total` | `Number` | `0` | 总条数 |
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `pageNumberKey` | `String` | `'currentPage'` | `getPagination()` 返回对象的页码键名 |
|
|
48
|
-
| `pageSizeKey` | `String` | `'pageSize'` | `getPagination()` 返回对象的条数键名 |
|
|
46
|
+
| `pageNumberKey` | `String` | `'currentPage'` | `@search` 事件参数 / `getPagination()` 中的页码键名 |
|
|
47
|
+
| `pageSizeKey` | `String` | `'pageSize'` | `@search` 事件参数 / `getPagination()` 中的条数键名 |
|
|
49
48
|
| `pageTotalKey` | `String` | `'total'` | `getPagination()` 返回对象的总数键名 |
|
|
50
49
|
| `enterTrigger` | `Boolean` | `true` | 是否开启回车触发“查询”按钮 |
|
|
51
50
|
| `searchProps` | `Object` | `{}` | 透传给 `NsSearch` |
|
|
52
51
|
| `tableProps` | `Object` | `{}` | 透传给 `NsTable` |
|
|
53
|
-
| `
|
|
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` 时使用 |
|
|
54
55
|
|
|
55
56
|
### 3.2 事件
|
|
56
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
|
+
|
|
57
74
|
| 事件 | 参数 | 说明 |
|
|
58
75
|
|---|---|---|
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `size-change` | `size` | 每页条数变化 |
|
|
67
|
-
| `current-change` | `page` | 页码变化 |
|
|
68
|
-
| `page-change` | `{ currentPage, pageSize }` | 页码或条数变化后的统一事件 |
|
|
69
|
-
| `update:currentPage` | `page` | 双向绑定页码 |
|
|
70
|
-
| `update:pageSize` | `size` | 双向绑定条数 |
|
|
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` 受控模式使用) |
|
|
71
83
|
|
|
72
84
|
### 3.3 当前实现的关键行为
|
|
73
85
|
|
|
74
86
|
- 搜索或重置时会先清空选中状态
|
|
75
87
|
- `NsSearch` 触发查询时会携带 `_resetPage: true`,容器收到后会把页码重置为 `1`
|
|
76
|
-
- `
|
|
77
|
-
- `
|
|
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`)
|
|
78
92
|
|
|
79
93
|
### 3.4 实例方法
|
|
80
94
|
|
|
81
95
|
| 方法 | 说明 |
|
|
82
96
|
|---|---|
|
|
83
|
-
| `initSearchAndLoad()` |
|
|
97
|
+
| `initSearchAndLoad()` | 初始化查询;优先读取搜索表单数据后派发 `search` 事件 |
|
|
98
|
+
| `reload()` | 以当前搜索条件 + 分页 + 排序重新派发 `search` 事件 |
|
|
84
99
|
| `getSearchFormData()` | 读取搜索表单 |
|
|
85
100
|
| `setSearchFormData(data)` | 回填搜索表单 |
|
|
86
101
|
| `resetSearchForm()` | 重置搜索表单 |
|
|
87
102
|
| `validateSearchForm()` | 校验搜索表单 |
|
|
88
|
-
| `getPagination()` |
|
|
103
|
+
| `getPagination()` | 获取分页对象,键名遵循 `pageNumberKey / pageSizeKey / pageTotalKey` |
|
|
89
104
|
| `getSelectionRows()` | 获取选中行 |
|
|
90
105
|
| `getSelectionKeys()` | 获取选中 key |
|
|
91
106
|
| `setSelectionRows(rows)` | 设置选中行 |
|
|
@@ -465,19 +480,23 @@ AI 生成页面时,建议至少覆盖下列功能点:
|
|
|
465
480
|
|---|---|
|
|
466
481
|
| 搜索区 | `ElInput`、`ElSelect`、`slot` 项、默认值、折叠展开 |
|
|
467
482
|
| 表格列 | 普通列、分组列、tag、image、link、slot、headerSlot、action |
|
|
468
|
-
| 交互 | `search`、`reset`、`add`、`selection-change`、`
|
|
483
|
+
| 交互 | `search`、`reset`、`add`、`selection-change`、`link-click`(其它分页 / 排序事件可不再单独接) |
|
|
469
484
|
| 选择 | `rowKey`、跨页选择、批量操作 |
|
|
470
485
|
| 扩展 | `actions-after-reset`、`header-actions`、`empty` |
|
|
471
486
|
|
|
472
487
|
## 10. AI 生成代码规则
|
|
473
488
|
|
|
474
489
|
- 优先使用 `NsTableContainer`
|
|
490
|
+
- 数据加载统一通过监听 `@search` 事件完成;事件参数已合并 搜索值 + 分页 + 排序,可直接发给后端
|
|
491
|
+
- 不要再向 `NsTableContainer` 传 `:load-data` 属性,组件无此 prop
|
|
492
|
+
- 默认 `useDefaultPage=true` 模式下,不需要绑定 `:current-page.sync` / `:page-size.sync`,也不需要单独监听 `@sort-change / @size-change / @current-change / @page-change`
|
|
475
493
|
- 列配置只写当前实现支持的字段
|
|
476
494
|
- `slotRenderers` 写成 `(scope) => VNode`
|
|
477
495
|
- 动作按钮显隐逻辑用 `show(row)`,不要依赖第二个参数
|
|
478
496
|
- 如需跨页勾选,必须配置 `tableProps.rowKey`
|
|
479
497
|
- 若用自定义 `header-actions`,不要再依赖默认新增按钮
|
|
480
498
|
- 如果使用 `setPagination()`,传入对象必须是 `{ currentPage, pageSize }`
|
|
499
|
+
- 需要服务端排序时,`columns[].sortable` 应写为 `'custom'`,`@search` 事件参数中的 `sort.prop / sort.order` 即为后端入参
|
|
481
500
|
|
|
482
501
|
## 11. 推荐 Prompt
|
|
483
502
|
|
|
@@ -485,11 +504,12 @@ AI 生成页面时,建议至少覆盖下列功能点:
|
|
|
485
504
|
请生成 Vue2.7 + script setup 的 NsTableContainer 页面,要求:
|
|
486
505
|
1) searchItems 覆盖 ElInput、ElSelect、ElDatePicker、slot 项、defaultValue;
|
|
487
506
|
2) columns 覆盖普通文本列、children 分组列、image、link、tag、自定义 slot、headerSlot、action;
|
|
488
|
-
3)
|
|
489
|
-
4)
|
|
490
|
-
5) 提供
|
|
491
|
-
6)
|
|
492
|
-
7)
|
|
507
|
+
3) 仅监听 search、reset、add、selection-change、link-click 这五个事件即可;
|
|
508
|
+
4) 数据加载只通过 @search 事件触发,事件参数中含搜索值、分页(pageNumberKey / pageSizeKey)以及 sort 对象;
|
|
509
|
+
5) 提供 rowKey,并演示 getSelectionKeys、setSelectionKeys、isKeySelected;
|
|
510
|
+
6) 提供 actions-after-reset、header-actions、empty 插槽示例;
|
|
511
|
+
7) 如使用 slotRenderers,按当前项目的 (scope) => VNode 签名实现;
|
|
512
|
+
8) 不使用 TS,不虚构不存在的 props 或方法(例如不要使用已不存在的 :load-data,不要在 useDefaultPage=true 模式下绑定 currentPage.sync / pageSize.sync)。
|
|
493
513
|
```
|
|
494
514
|
|
|
495
515
|
## 12. 标准模板
|
|
@@ -515,8 +535,7 @@ AI 生成页面时,建议至少覆盖下列功能点:
|
|
|
515
535
|
stripe: true,
|
|
516
536
|
paginationLayout: 'total, sizes, prev, pager, next',
|
|
517
537
|
}"
|
|
518
|
-
|
|
519
|
-
@search="onSearch"
|
|
538
|
+
@search="fetchData"
|
|
520
539
|
@reset="onReset"
|
|
521
540
|
@add="onAdd"
|
|
522
541
|
@selection-change="onSelectionChange"
|
|
@@ -574,7 +593,7 @@ const columns = ref([
|
|
|
574
593
|
{ prop: 'name', label: '名称', minWidth: 160, type: 'link' },
|
|
575
594
|
{ prop: 'code', label: '编码', width: 120 },
|
|
576
595
|
{ prop: 'status', label: '状态', width: 100, slot: 'status' },
|
|
577
|
-
{ prop: 'createTime', label: '创建时间', width: 180, sortable:
|
|
596
|
+
{ prop: 'createTime', label: '创建时间', width: 180, sortable: 'custom' },
|
|
578
597
|
{
|
|
579
598
|
type: 'action',
|
|
580
599
|
label: '操作',
|
|
@@ -595,10 +614,14 @@ const columns = ref([
|
|
|
595
614
|
},
|
|
596
615
|
])
|
|
597
616
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
617
|
+
// @search 事件参数 query 已包含搜索表单值 + 分页参数 + 排序信息
|
|
618
|
+
const fetchData = async (query) => {
|
|
619
|
+
console.log('请求体:', query)
|
|
620
|
+
|
|
621
|
+
// 真实场景:将 query 直接发给后端
|
|
622
|
+
// const res = await api.list(query)
|
|
623
|
+
// tableData.value = res.data
|
|
624
|
+
// total.value = res.totalCount
|
|
602
625
|
|
|
603
626
|
tableData.value = [
|
|
604
627
|
{ id: 1, name: '项目一', code: 'P001', status: 1, createTime: '2026-01-01' },
|
|
@@ -607,10 +630,6 @@ const fetchData = async () => {
|
|
|
607
630
|
total.value = 2
|
|
608
631
|
}
|
|
609
632
|
|
|
610
|
-
const onSearch = () => {
|
|
611
|
-
fetchData()
|
|
612
|
-
}
|
|
613
|
-
|
|
614
633
|
const onReset = () => {
|
|
615
634
|
console.log('重置完成')
|
|
616
635
|
}
|