vue2-components-plus 1.0.2 → 1.0.5

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.
@@ -0,0 +1,539 @@
1
+ <template>
2
+ <div class="table-demo">
3
+ <el-card shadow="never" class="table-demo__mode-card">
4
+ <div class="table-demo__mode-header">
5
+ <div>
6
+ <div class="table-demo__mode-title">分页模式演示</div>
7
+ <div class="table-demo__mode-tip">
8
+ 当前共 {{ mockUserCount }} 条模拟数据,当前为{{ paginationMode === 'frontend' ? '前端分页' : '后端分页' }}模式。
9
+ </div>
10
+ <div class="table-demo__mode-desc">
11
+ {{ paginationMode === 'frontend' ? '一次加载全部筛选结果,再在本地完成翻页。' : '每次切页都会按当前页码重新请求模拟数据。' }}
12
+ </div>
13
+ </div>
14
+ <el-radio-group v-model="paginationMode" size="small" @change="handlePaginationModeChange">
15
+ <el-radio-button label="backend">后端分页</el-radio-button>
16
+ <el-radio-button label="frontend">前端分页</el-radio-button>
17
+ </el-radio-group>
18
+ </div>
19
+ </el-card>
20
+
21
+ <NsTableContainer
22
+ ref="containerRef"
23
+ page-number-key="currentPage1"
24
+ page-size-key="pageSize1"
25
+ page-total-key="total1"
26
+ :search-items="searchItems"
27
+ :external-search-params="externalSearchParams"
28
+ :search-props="searchProps"
29
+ :table-data="tableData"
30
+ :columns="columns"
31
+ :total="total"
32
+ :table-props="mergedTableProps"
33
+ :load-data="loadData"
34
+ @search="handleSearch"
35
+ @reset="handleReset"
36
+ @add="handleAdd"
37
+ @selection-change="handleSelectionChange"
38
+ >
39
+ <template v-slot:status="{ row }">
40
+ <el-tag size="small" :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
41
+ </template>
42
+
43
+ <template v-slot:gender="{ row }">
44
+ <el-tag size="small" :type="row.gender === 1 ? 'primary' : 'danger'">
45
+ {{ row.gender === 1 ? '男' : '女' }}
46
+ </el-tag>
47
+ </template>
48
+
49
+ <template v-slot:department="{ row }">
50
+ <el-tag size="small" effect="plain">{{ getDepartmentText(row.department) }}</el-tag>
51
+ </template>
52
+
53
+ <template v-slot:delete-action="{ row }">
54
+ <el-button
55
+ type="text"
56
+ style="color: #f56c6c"
57
+ :disabled="row.status === 0"
58
+ @click="handleDelete(row)"
59
+ >
60
+ 删除
61
+ </el-button>
62
+ </template>
63
+ </NsTableContainer>
64
+
65
+ <el-card shadow="never" class="table-demo__actions">
66
+ <template v-slot:header>选择能力演示</template>
67
+ <div class="table-demo__action-list">
68
+ <el-button @click="getSelectedRows">获取选中行</el-button>
69
+ <el-button @click="getSelectedKeys">获取选中 ID</el-button>
70
+ <el-button @click="selectRows([3,7])">选中 ID 为 3 / 7 的行</el-button>
71
+ <el-button @click="clearSelection">清空选择</el-button>
72
+ <el-button @click="selectAll">全选当前页</el-button>
73
+ <el-button @click="checkSelection">检查选择状态</el-button>
74
+ </div>
75
+ </el-card>
76
+
77
+ </div>
78
+ </template>
79
+
80
+ <script lang="ts">
81
+
82
+
83
+ import { departmentOptions, fetchDepartmentOptions, fetchStatusOptions, filterUsers, mockUsers } from './mockData'
84
+
85
+ function createSearchItems() {
86
+ return [
87
+ {
88
+ prop: 'month',
89
+ label: '归属月',
90
+ span: 6,
91
+ component: 'ElSelect',
92
+ attrs: {
93
+ placeholder: '请选择归属月',
94
+ clearable: true,
95
+ },
96
+ children: Array.from({ length: 12 }, function (_, index) {
97
+ return {
98
+ label: index + 1 + '月',
99
+ value: String(index + 1),
100
+ }
101
+ }),
102
+
103
+ },
104
+ {
105
+ prop: 'username',
106
+ label: '用户名',
107
+ span: 6,
108
+ component: 'ElInput',
109
+ attrs: {
110
+ placeholder: '请输入用户名',
111
+ clearable: true,
112
+ },
113
+ events: {},
114
+ },
115
+ {
116
+ prop: 'realName',
117
+ label: '真实姓名',
118
+ span: 6,
119
+ component: 'ElInput',
120
+ attrs: {
121
+ placeholder: '请输入真实姓名',
122
+ clearable: true,
123
+ },
124
+ },
125
+ {
126
+ prop: 'status',
127
+ label: '状态',
128
+ span: 6,
129
+ component: 'ElSelect',
130
+ attrs: {
131
+ placeholder: '请选择状态',
132
+ clearable: true,
133
+ },
134
+ children: [],
135
+ },
136
+ {
137
+ prop: 'department',
138
+ label: '部门',
139
+ span: 6,
140
+ component: 'ElSelect',
141
+ attrs: {
142
+ placeholder: '请选择部门',
143
+ clearable: true,
144
+ filterable: true,
145
+ },
146
+ children: [],
147
+ },
148
+ {
149
+ prop: 'gender',
150
+ label: '性别',
151
+ span: 6,
152
+ component: 'ElSelect',
153
+ attrs: {
154
+ placeholder: '请选择性别',
155
+ clearable: true,
156
+ },
157
+ children: [
158
+ { label: '全部', value: '' },
159
+ { label: '男', value: 1 },
160
+ { label: '女', value: 2 },
161
+ ],
162
+ },
163
+ {
164
+ prop: 'createTime',
165
+ label: '创建时间',
166
+ span: 6,
167
+ component: 'ElDatePicker',
168
+ attrs: {
169
+ type: 'daterange',
170
+ clearable: true,
171
+ rangeSeparator: '至',
172
+ startPlaceholder: '开始日期',
173
+ endPlaceholder: '结束日期',
174
+ valueFormat: 'yyyy-MM-dd',
175
+ },
176
+ },
177
+ {
178
+ prop: 'phone',
179
+ label: '手机号',
180
+ span: 6,
181
+ component: 'ElInput',
182
+ attrs: {
183
+ placeholder: '请输入手机号',
184
+ clearable: true,
185
+ },
186
+ },
187
+ {
188
+ prop: 'active',
189
+ label: '是否激活',
190
+ span: 6,
191
+ component: 'ElSwitch',
192
+ attrs: {
193
+ activeText: '是',
194
+ inactiveText: '否',
195
+ },
196
+ defaultValue: true,
197
+ },
198
+ ]
199
+ }
200
+
201
+ function createColumns(context) {
202
+ return [
203
+ {
204
+ prop: 'id',
205
+ label: 'ID',
206
+ sortable: true,
207
+ },
208
+ {
209
+ label: '基本信息',
210
+ children: [
211
+ {
212
+ prop: 'username',
213
+ label: '用户名',
214
+ width: 130,
215
+ formatter: function (row, column, cellValue) {
216
+ return cellValue ? '@' + cellValue : '-'
217
+ },
218
+ },
219
+ {
220
+ prop: 'realName',
221
+ label: '真实姓名',
222
+ width: 120,
223
+ },
224
+ {
225
+ prop: 'gender',
226
+ label: '性别',
227
+ width: 90,
228
+ slot: 'gender',
229
+ },
230
+ ],
231
+ },
232
+ {
233
+ label: '组织信息',
234
+ children: [
235
+ {
236
+ prop: 'department',
237
+ label: '部门',
238
+ width: 120,
239
+ slot: 'department',
240
+ },
241
+ {
242
+ prop: 'status',
243
+ label: '状态',
244
+ width: 100,
245
+ slot: 'status',
246
+ },
247
+ ],
248
+ },
249
+ {
250
+ label: '联系方式',
251
+ children: [
252
+ {
253
+ prop: 'phone',
254
+ label: '手机号',
255
+ width: 140,
256
+ },
257
+ {
258
+ prop: 'email',
259
+ label: '邮箱',
260
+ minWidth: 220,
261
+ },
262
+ ],
263
+ },
264
+ {
265
+ prop: 'createTime',
266
+ label: '创建时间',
267
+ width: 180,
268
+ sortable: true,
269
+ },
270
+ {
271
+ type: 'action',
272
+ label: '操作',
273
+ width: 260,
274
+ fixed: 'right',
275
+ buttons: [
276
+ {
277
+ label: '查看',
278
+ type: 'text',
279
+ icon: 'el-icon-view',
280
+ handler: function (row) {
281
+ context.handleView(row)
282
+ },
283
+ },
284
+ {
285
+ label: '编辑',
286
+ type: 'text',
287
+ icon: 'el-icon-edit',
288
+ handler: function (row) {
289
+ context.handleEdit(row)
290
+ },
291
+ },
292
+ {
293
+ label: '删除',
294
+ type: 'text',
295
+ icon: 'el-icon-delete',
296
+ slot: 'delete-action',
297
+ },
298
+ ],
299
+ },
300
+ ]
301
+ }
302
+
303
+ export default {
304
+ name: 'NsTableDemo',
305
+ data() {
306
+ return {
307
+ loading: false,
308
+ total: 0,
309
+ tableData: [],
310
+ searchParams: {},
311
+ paginationMode: 'backend',
312
+ mockUserCount: mockUsers.length,
313
+ externalSearchParams: {
314
+ source: 'vue2-demo',
315
+ },
316
+ searchProps: {
317
+ labelWidth: '90px',
318
+ },
319
+ searchItems: createSearchItems(),
320
+ columns: [],
321
+ }
322
+ },
323
+ computed: {
324
+ mergedTableProps() {
325
+ return {
326
+ showSelection: true,
327
+ showIndex: true,
328
+ loading: this.loading,
329
+ rowKey: 'id',
330
+ showPagination: true,
331
+ pageSizes: [5, 10, 20],
332
+ stripe: true,
333
+ }
334
+ },
335
+ },
336
+ created() {
337
+ this.columns = createColumns(this)
338
+ this.searchItems[1].events.keyup = this.handleKeywordEnter
339
+ },
340
+ async mounted() {
341
+ const statusOptions = await fetchStatusOptions()
342
+ const departmentList = await fetchDepartmentOptions()
343
+ this.searchItems[3].children = statusOptions
344
+ this.searchItems[4].children = departmentList
345
+ this.$nextTick(() => {
346
+ if (this.$refs.containerRef) {
347
+ this.$refs.containerRef.initSearchAndLoad()
348
+ }
349
+ })
350
+ },
351
+
352
+ methods: {
353
+ handleKeywordEnter(event) {
354
+ if (event && event.key === 'Enter') {
355
+ this.handleSearch(this.$refs.containerRef ? this.$refs.containerRef.getSearchFormData() : {})
356
+ }
357
+ },
358
+ getStatusType(status) {
359
+ return status === 1 ? 'success' : 'danger'
360
+ },
361
+ getStatusText(status) {
362
+ return status === 1 ? '启用' : '禁用'
363
+ },
364
+ getDepartmentText(value) {
365
+ const matched = departmentOptions.find(function (item) {
366
+ return item.value === value
367
+ })
368
+ return matched ? matched.label : value
369
+ },
370
+ getCurrentPagination() {
371
+ return this.$refs.containerRef && this.$refs.containerRef.getPagination
372
+ ? this.$refs.containerRef.getPagination()
373
+ : { currentPage1: 1, pageSize1: 10 }
374
+ },
375
+ paginateList(list, pagination) {
376
+ const currentPage = Number(pagination.currentPage1 || 1)
377
+ const pageSize = Number(pagination.pageSize1 || 10)
378
+ const start = (currentPage - 1) * pageSize
379
+ return (list || []).slice(start, start + pageSize)
380
+ },
381
+ resetContainerPage() {
382
+ if (this.$refs.containerRef && this.$refs.containerRef.internalPagination) {
383
+ this.$refs.containerRef.internalPagination.currentPage = 1
384
+ }
385
+ },
386
+ handlePaginationModeChange() {
387
+ if (this.$refs.containerRef) {
388
+ this.$refs.containerRef.clearAllSelection()
389
+ }
390
+ this.resetContainerPage()
391
+ this.loadData()
392
+ this.$message.success('已切换为' + (this.paginationMode === 'frontend' ? '前端分页' : '后端分页'))
393
+ },
394
+ async loadData() {
395
+ this.loading = true
396
+ try {
397
+ await new Promise(function (resolve) {
398
+ setTimeout(resolve, 300)
399
+ })
400
+ const pagination = this.getCurrentPagination()
401
+ const pageConfig = {
402
+ pageNumberKey: 'currentPage1',
403
+ pageSizeKey: 'pageSize1',
404
+ }
405
+ if (this.paginationMode === 'frontend') {
406
+ const result = filterUsers(
407
+ mockUsers,
408
+ this.searchParams,
409
+ { currentPage1: 1, pageSize1: this.mockUserCount || 10 },
410
+ pageConfig,
411
+ )
412
+ this.tableData = this.paginateList(result.list, pagination)
413
+ this.total = result.total
414
+ return
415
+ }
416
+ const result = filterUsers(mockUsers, this.searchParams, pagination, pageConfig)
417
+ this.tableData = result.list
418
+ this.total = result.total
419
+ } catch {
420
+ this.$message.error('加载表格数据失败')
421
+ } finally {
422
+
423
+ this.loading = false
424
+ }
425
+ },
426
+ handleSearch(params) {
427
+ this.searchParams = Object.assign({}, params)
428
+ this.loadData()
429
+ },
430
+ handleReset() {
431
+ this.$message.info('搜索条件已重置')
432
+ },
433
+ handleSelectionChange(selection) {
434
+ if (selection && selection.length) {
435
+ this.$message.success('当前选中 ' + selection.length + ' 行')
436
+ }
437
+ },
438
+ getSelectedRows() {
439
+ const rows = this.$refs.containerRef ? this.$refs.containerRef.getSelectionRows() : []
440
+ this.$alert(JSON.stringify(rows, null, 2), '当前选中行', {
441
+ confirmButtonText: '知道了',
442
+ })
443
+ },
444
+ getSelectedKeys() {
445
+ const keys = this.$refs.containerRef ? this.$refs.containerRef.getSelectionKeys() : []
446
+ this.$message.success('当前选中 ID:' + (keys.length ? keys.join(', ') : '无'))
447
+ },
448
+ selectRows(ids) {
449
+ if (!this.$refs.containerRef) return
450
+ this.$refs.containerRef.setSelectionKeys(ids)
451
+ this.$message.success('已尝试选中 ID:' + ids.join(', '))
452
+ },
453
+ clearSelection() {
454
+ if (!this.$refs.containerRef) return
455
+ this.$refs.containerRef.clearAllSelection()
456
+ this.$message.success('已清空选中状态')
457
+ },
458
+ selectAll() {
459
+ if (!this.$refs.containerRef) return
460
+ this.$refs.containerRef.selectAll()
461
+ this.$message.success('已全选当前页')
462
+ },
463
+ checkSelection() {
464
+ if (!this.$refs.containerRef || !this.tableData.length) return
465
+ const firstSelected = this.$refs.containerRef.isRowSelected(this.tableData[0])
466
+ const keySelected = this.$refs.containerRef.isKeySelected(3)
467
+ this.$message.info('第一行选中:' + (firstSelected ? '是' : '否') + ';ID=3 选中:' + (keySelected ? '是' : '否'))
468
+ },
469
+ handleAdd() {
470
+ this.$message.success('点击了新增按钮')
471
+ },
472
+ handleView(row) {
473
+ this.$message.info('查看:' + row.username)
474
+ },
475
+ handleEdit(row) {
476
+ this.$message.success('编辑:' + row.username)
477
+ },
478
+ handleDelete(row) {
479
+ if (row.status === 0) {
480
+ this.$message.warning('禁用状态用户不可删除')
481
+ return
482
+ }
483
+ this.$confirm('确认删除用户“' + row.username + '”吗?', '提示', {
484
+ type: 'warning',
485
+ })
486
+ .then(() => {
487
+ this.$message.success('已模拟删除:' + row.username)
488
+ this.loadData()
489
+ })
490
+ .catch(function () {})
491
+ },
492
+ },
493
+ }
494
+ </script>
495
+
496
+ <style scoped>
497
+ .table-demo {
498
+ display: flex;
499
+ flex-direction: column;
500
+ gap: 20px;
501
+ }
502
+
503
+ .table-demo__mode-card,
504
+ .table-demo__actions {
505
+ border-radius: 12px;
506
+ }
507
+
508
+ .table-demo__mode-header {
509
+ display: flex;
510
+ align-items: center;
511
+ justify-content: space-between;
512
+ gap: 16px;
513
+ flex-wrap: wrap;
514
+ }
515
+
516
+ .table-demo__mode-title {
517
+ font-size: 16px;
518
+ font-weight: 600;
519
+ color: #303133;
520
+ }
521
+
522
+ .table-demo__mode-tip {
523
+ margin-top: 6px;
524
+ color: #606266;
525
+ }
526
+
527
+ .table-demo__mode-desc {
528
+ margin-top: 4px;
529
+ font-size: 13px;
530
+ color: #909399;
531
+ }
532
+
533
+ .table-demo__action-list {
534
+ display: flex;
535
+ flex-wrap: wrap;
536
+ gap: 12px;
537
+ }
538
+ </style>
539
+
@@ -0,0 +1,117 @@
1
+ export const departmentOptions = [
2
+ { label: '技术部', value: 'tech' },
3
+ { label: '产品部', value: 'product' },
4
+ { label: '运营部', value: 'operation' },
5
+ { label: '人力资源部', value: 'hr' },
6
+ { label: '财务部', value: 'finance' },
7
+ ]
8
+
9
+ export const statusOptions = [
10
+ { label: '全部', value: '' },
11
+ { label: '启用', value: 1 },
12
+ { label: '禁用', value: 0 },
13
+ ]
14
+
15
+ const nameSeed = [
16
+ ['zhangsan', '张三'],
17
+ ['lisi', '李四'],
18
+ ['wangwu', '王五'],
19
+ ['zhaoliu', '赵六'],
20
+ ['sunqi', '孙七'],
21
+ ['zhouba', '周八'],
22
+ ['wujiu', '吴九'],
23
+ ['zhengshi', '郑十'],
24
+ ]
25
+
26
+ function pad(value) {
27
+ return String(value).padStart(2, '0')
28
+ }
29
+
30
+ export const mockUsers = Array.from({ length: 20 }, function (_, index) {
31
+
32
+ const id = index + 1
33
+ const seed = nameSeed[index % nameSeed.length]
34
+ const department = departmentOptions[index % departmentOptions.length]
35
+ const month = (index % 12) + 1
36
+ const day = (index % 28) + 1
37
+ const gender = id % 2 === 0 ? 2 : 1
38
+ const status = id % 5 === 0 ? 0 : 1
39
+ return {
40
+
41
+ id,
42
+ username: seed[0] + id,
43
+ realName: seed[1],
44
+ gender,
45
+ department: department.value,
46
+ status,
47
+ phone: '1380013' + pad(id) + pad((id * 3) % 100),
48
+ email: seed[0] + id + '@example.com',
49
+ avatar: id % 2 === 0
50
+ ? 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'
51
+ : 'https://cube.elemecdn.com/0/88/03b0d39583f_87968.png',
52
+ createTime: '2024-' + pad(month) + '-' + pad(day) + ' 10:00:00',
53
+ updateTime: '2024-' + pad(month) + '-' + pad(day) + ' 18:30:00',
54
+ month: String(month),
55
+ }
56
+ })
57
+
58
+ export function fetchStatusOptions() {
59
+ return new Promise(function (resolve) {
60
+ setTimeout(function () {
61
+ resolve(statusOptions)
62
+ }, 300)
63
+ })
64
+ }
65
+
66
+ export function fetchDepartmentOptions() {
67
+ return new Promise(function (resolve) {
68
+ setTimeout(function () {
69
+ resolve([{ label: '全部', value: '' }].concat(departmentOptions))
70
+ }, 300)
71
+ })
72
+ }
73
+
74
+ export function filterUsers(users, searchParams, pagination, keyConfig) {
75
+ const params = searchParams || {}
76
+ const pager = pagination || {}
77
+ const config = keyConfig || {}
78
+ const pageNumberKey = config.pageNumberKey || 'currentPage'
79
+ const pageSizeKey = config.pageSizeKey || 'pageSize'
80
+
81
+ let list = (users || []).filter(function (item) {
82
+ if (params.month && item.month !== String(params.month)) return false
83
+ if (params.username && item.username.toLowerCase().indexOf(String(params.username).toLowerCase()) === -1) return false
84
+ if (params.realName && item.realName.indexOf(params.realName) === -1) return false
85
+ if (params.status !== '' && params.status !== undefined && params.status !== null && item.status !== params.status) return false
86
+ if (params.department && item.department !== params.department) return false
87
+ if (params.gender !== '' && params.gender !== undefined && params.gender !== null && item.gender !== params.gender) return false
88
+ if (params.phone && item.phone.indexOf(String(params.phone)) === -1) return false
89
+ if (params.active !== undefined && params.active !== null) {
90
+ const active = params.active === true || params.active === 'true'
91
+ if (active && item.status !== 1) return false
92
+ if (!active && item.status !== 0) return false
93
+ }
94
+ if (params.createTime && Array.isArray(params.createTime) && params.createTime.length === 2) {
95
+ const start = new Date(params.createTime[0]).getTime()
96
+ const end = new Date(params.createTime[1]).getTime()
97
+ const current = new Date(item.createTime).getTime()
98
+ if (!Number.isNaN(start) && !Number.isNaN(end) && (current < start || current > end)) {
99
+ return false
100
+ }
101
+ }
102
+ return true
103
+ })
104
+
105
+ const total = list.length
106
+ const currentPage = Number(pager[pageNumberKey] || 1)
107
+ const pageSize = Number(pager[pageSizeKey] || 10)
108
+ const start = (currentPage - 1) * pageSize
109
+ const end = start + pageSize
110
+
111
+ list = list.slice(start, end)
112
+
113
+ return {
114
+ list,
115
+ total,
116
+ }
117
+ }