@lx-frontend/wrap-element-ui 1.0.1-beta.5 → 1.0.1-beta.6

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,915 +1,886 @@
1
- <template>
2
- <div class="editable-table">
3
- <div class="view-setting">
4
- <div
5
- v-if="!hideViewSettingBtn"
6
- class="view-setting__btn-wrapper"
7
- >
8
- <div
9
- class="view-setting__btn btn-pointer"
10
- @click="handleViewSettingShow"
11
- >
12
- <i class="el-icon-setting" />
13
- <div class="view-setting__btn-text">
14
- 显示设置
15
- </div>
16
- </div>
17
- </div>
18
-
19
- <el-dialog
20
- title="显示设置"
21
- :visible.sync="viewSettingVisible"
22
- width="750px"
23
- top="12vh"
24
- :close-on-click-modal="false"
25
- :append-to-body="true"
26
- custom-class="view-setting__dialog"
27
- >
28
- <div class="view-setting__content">
29
- <div class="view-setting__content-left">
30
- <div class="view-setting__content-left-title">
31
- 表头设置
32
- </div>
33
- <div class="view-setting__checkbox-wrapper">
34
- <el-checkbox-group v-model="columnsToBeShown">
35
- <el-checkbox
36
- v-for="item in columnConfig"
37
- :key="item.label"
38
- :label="item.prop"
39
- :disabled="item.isAlwaysShow"
40
- >
41
- <div class="view-setting__content-left-item">
42
- {{ item.label }}
43
- </div>
44
- </el-checkbox>
45
- </el-checkbox-group>
46
- </div>
47
- </div>
48
- <div class="view-setting__content-right">
49
- <div class="view-setting__content-right-title">
50
- 已选择
51
- <div class="view-setting__selected-count">
52
- {{ columnsToBeShown.length }}
53
- </div>
54
- </div>
55
- <div class="view-setting__content-right-frize">
56
- 冻结前
57
- <el-input
58
- class="view-setting__content-right-input"
59
- :value="tempLeftFixedColumnCount"
60
- @input="handleInputTempLeftFixedColumnCount"
61
- />
62
-
63
- </div>
64
- <div class="view-setting__content-right-selected">
65
- <div
66
- v-for="(item, index) in viewSettingDragSortOptions"
67
- :key="item.prop"
68
- class="view-setting__selected-item view-setting-draggable-item"
69
- >
70
- <div class="view-setting__selected-item-left">
71
- <div
72
- class="view-setting-drag-target view-setting__icon-wrapper"
73
- :data-index="index"
74
- >
75
- <div
76
- class="view-setting-drag-target editable-table-drag-icon"
77
- :data-index="index"
78
- />
79
- </div>
80
- <div class="view-setting__selected-item-name">
81
- {{ item.label }}
82
- </div>
83
- </div>
84
- <div
85
- :class="['view-setting__selected-item-close', item.isAlwaysShow ? 'view-setting__selected-item-close--disabled' : '']"
86
- @click="handleColumnClose(item)"
87
- >
88
- <i class="el-icon-close" />
89
- </div>
90
- </div>
91
- </div>
92
- </div>
93
- </div>
94
- <template #footer>
95
- <el-button @click="handleViewSettingClose">
96
- 取消
97
- </el-button>
98
- <el-button
99
- type="primary"
100
- @click="handleViewSettingConfirm"
101
- >
102
- 确认
103
- </el-button>
104
- </template>
105
- </el-dialog>
106
- </div>
107
-
108
- <!-- 列表展示,属性透传,列编辑 -->
109
- <el-table
110
- ref="tableDomRef"
111
- v-loading="loading"
112
- :data="dataList"
113
- :row-style="setRowStyle"
114
- :row-class-name="setRowClassName"
115
- :cell-class-name="setCellClassName"
116
- :show-summary="summaryList.length > 0"
117
- :summary-method="tableSummaryMethod"
118
- v-bind="$attrs"
119
- border
120
- @selection-change="handleSelectionChange"
121
- @cell-mouse-enter="debouncedHoverHandler"
122
- @header-dragend="doTableLayout"
123
- >
124
- <el-table-column
125
- v-if="rowDragAble"
126
- width="30px"
127
- class-name="editable-table__drag-cell no-inner-cell-border"
128
- :fixed="leftFixedColumnCount > 0 ? 'left' : ''"
129
- >
130
- <template #default="scope">
131
- <div
132
- class="row-drag-target editable-table__drag-icon"
133
- :data-index="scope.$index"
134
- @mousedown="currScope = scope"
135
- >
136
- <div
137
- :data-index="scope.$index"
138
- class="row-drag-target editable-table-drag-icon"
139
- />
140
- </div>
141
- </template>
142
- </el-table-column>
143
- <!-- 展开行 -->
144
- <el-table-column
145
- v-if="hasExpandRow"
146
- width="30px"
147
- type="expand"
148
- :fixed="leftFixedColumnCount > 0 ? 'left' : ''"
149
- class-name="no-inner-cell-border"
150
- >
151
- <template #default="scope">
152
- <slot
153
- name="expand"
154
- v-bind="scope"
155
- />
156
- </template>
157
- </el-table-column>
158
- <!-- 选择列 -->
159
- <el-table-column
160
- v-if="hasSelectionColumn"
161
- width="45px"
162
- align="center"
163
- type="selection"
164
- :fixed="leftFixedColumnCount > 0 ? 'left' : ''"
165
- class-name="no-inner-cell-border"
166
- />
167
- <!-- 编号列 -->
168
- <el-table-column
169
- v-if="hasIndexColumn"
170
- min-width="30px"
171
- type="index"
172
- :fixed="leftFixedColumnCount > 0 ? 'left' : ''"
173
- class-name="no-inner-cell-border"
174
- />
175
- <!-- 颜色选择列 -->
176
- <el-table-column
177
- v-if="colorList && colorList.length > 0"
178
- width="22px"
179
- class-name="editable-table__color-column no-inner-cell-border"
180
- :fixed="leftFixedColumnCount > 0 ? 'left' : ''"
181
- >
182
- <template #header>
183
- <div class="editable-table__color-icon" />
184
- </template>
185
- <template #default="scope">
186
- <el-popover
187
- ref="colorPopoverRef"
188
- placement="right"
189
- trigger="click"
190
- popper-class="color-popover"
191
- >
192
- <div class="color-list">
193
- <div
194
- v-for="color in colorList"
195
- :key="color.id"
196
- class="color-list__item"
197
- :style="{ backgroundColor: color.sampleColor }"
198
- @click="handleColorChange(color.id, scope)"
199
- >
200
- <span :style="{color: color.textColor}">{{ color.name }}</span>
201
- </div>
202
- </div>
203
- <template slot="reference">
204
- <!-- 没有这个包裹的div标签,在拖动换行之后,无法调起颜色选择弹窗 -->
205
- <div>
206
- <div
207
- v-if="isDefaultColor(scope.row.colorId)"
208
- class="editable-table__color-icon"
209
- />
210
- <div
211
- v-else
212
- class="editable-table__selected-color"
213
- :style="{ backgroundColor: getColorById(scope.row.colorId, 'sample') }"
214
- />
215
- </div>
216
- </template>
217
- </el-popover>
218
- </template>
219
- </el-table-column>
220
- <!-- 这里的key很重要,必须保证每次生成的key都不一样,这样列排序功能才能生效,否则,即使actualColumns数组元素顺序发生变化,列顺序也不会改变。原因未知 -->
221
- <el-table-column
222
- v-for="(column, index) in actualColumns"
223
- :key="column.prop + index"
224
- resizable
225
- class-name="editable-table__data-column"
226
- :filtered-value="Array.isArray(filteredValue[column.prop]) ? filteredValue[column.prop] : []"
227
- v-bind="getColumnBindProps(column)"
228
- >
229
- <template
230
- v-if="showColumnHeadSortIcon(column)"
231
- #header="scope"
232
- >
233
- <el-popover
234
- ref="sortFilterPopoverRef"
235
- placement="bottom"
236
- trigger="click"
237
- popper-class="editable-table__sort-filter"
238
- :data-prop="column.prop"
239
- @show="handleHeaderPopoverShow(column)"
240
- >
241
- <template slot="reference">
242
- <!-- 筛选中,或排序中,高亮 -->
243
- <span :class="['editable-table__sort-reference', isColumnHeadActive(column) && 'editable-table__sort-reference--active']">
244
- {{ column.label }}
245
- <div :class="['editable-table__sort-icon', isColumnHeadActive(column) && 'editable-table__sort-icon--active']" />
246
- </span>
247
- </template>
248
- <div class="sort-filter">
249
- <div class="sort-filter__column-title">
250
- {{ column.label }}
251
- </div>
252
- <div
253
- v-if="column.isColumnSortable"
254
- class="sort-filter__sort"
255
- >
256
- <div class="sort-filter__sort-title">
257
- 排序
258
- </div>
259
- <div class="sort-filter__sort-btns">
260
- <el-button
261
- :class="['sort-filter__sort-btn', tempSortingColumn?.prop === column.prop && tempSortType === 'ascending' && 'sort-filter__sort-btn--active']"
262
- @click="handleSort('ascending', column)"
263
- >
264
- 升序
265
- </el-button>
266
- <el-button
267
- :class="['sort-filter__sort-btn', tempSortingColumn?.prop === column.prop && tempSortType === 'descending' && 'sort-filter__sort-btn--active']"
268
- @click="handleSort('descending', column)"
269
- >
270
- 降序
271
- </el-button>
272
- </div>
273
- </div>
274
- <div
275
- v-if="column.search && !Array.isArray(column.search)"
276
- class="sort-filter__search"
277
- >
278
- <div class="sort-filter__search-title">
279
- 搜索
280
- </div>
281
- <el-input
282
- v-model="tempSearchValue[column.prop]"
283
- class="sort-filter__search-input"
284
- placeholder="请输入内容"
285
- />
286
- </div>
287
-
288
- <div
289
- v-if="column.search && Array.isArray(column.search)"
290
- class="sort-filter__search"
291
- style="display: flex;flex-direction: column;gap: 12px;"
292
- >
293
- <div
294
- v-for="item in column.search"
295
- :key="item.prop"
296
- >
297
- <div class="sort-filter__search-title">
298
- {{ item.label }}
299
- </div>
300
- <el-input
301
- v-model="tempSearchValue[item.prop]"
302
- class="sort-filter__search-input"
303
- placeholder="请输入内容"
304
- />
305
- </div>
306
- </div>
307
-
308
- <div
309
- v-if="column.filters && ((Array.isArray(column.filters) ? column.filters : column.filters.options).length > 0)"
310
- class="sort-filter__filter"
311
- >
312
- <div class="sort-filter__filter-title">
313
- 筛选
314
- </div>
315
- <el-checkbox-group
316
- v-if="column.filters && (Array.isArray(column.filters) || column.filters.type === 'checkbox')"
317
- v-model="tempFilteredValue[column.prop]"
318
- class="sort-filter__filter-checkbox-group"
319
- >
320
- <el-checkbox
321
- v-for="item in (Array.isArray(column.filters) ? column.filters : column.filters.options)"
322
- :key="item.value"
323
- :label="item.value"
324
- class="sort-filter__filter-checkbox"
325
- >
326
- <slot
327
- :name="column.prop + '-filter-item'"
328
- v-bind="item"
329
- >
330
- {{ item.text }}
331
- </slot>
332
- </el-checkbox>
333
- </el-checkbox-group>
334
-
335
- <el-radio-group
336
- v-if="column.filters && !Array.isArray(column.filters) && column.filters.type === 'radio'"
337
- v-model="tempFilteredValue[column.prop]"
338
- style="display: flex;flex-direction: column;gap: 6px;"
339
- >
340
- <el-radio
341
- v-for="item in column.filters.options"
342
- :key="item.value"
343
- :label="item.value"
344
- >
345
- <slot
346
- :name="column.prop + '-filter-item'"
347
- v-bind="item"
348
- >
349
- {{ item.text }}
350
- </slot>
351
- </el-radio>
352
- </el-radio-group>
353
- </div>
354
- <div
355
- v-if="column.summary"
356
- class="sort-filter__filter"
357
- >
358
- <div class="sort-filter__filter-title">
359
- 统计
360
- </div>
361
- <el-checkbox-group
362
- v-model="tempSummaryList"
363
- class="sort-filter__filter-checkbox-group"
364
- >
365
- <el-checkbox
366
- :label="column.prop"
367
- class="sort-filter__filter-checkbox"
368
- >
369
- <slot
370
- :name="column.prop + '-summay-item'"
371
- v-bind="column"
372
- >
373
- {{ column.label }}
374
- </slot>
375
- </el-checkbox>
376
- </el-checkbox-group>
377
- </div>
378
- <div class="sort-filter__footer">
379
- <el-button
380
- class="sort-filter__reset-btn"
381
- @click="handleHeaderOperationReset(column, scope)"
382
- >
383
- 重置
384
- </el-button>
385
- <el-button
386
- class="sort-filter__confirm-btn"
387
- type="primary"
388
- @click="handleHeaderOperationConfirm(column, scope)"
389
- >
390
- 确定
391
- </el-button>
392
- </div>
393
- </div>
394
- </el-popover>
395
- </template>
396
- <!-- 默认操作按钮,defaultOperations属性不为空数组时展示。编辑状态下隐藏 -->
397
- <template
398
- v-if="column.prop === '$$operation'"
399
- #default="scope"
400
- >
401
- <el-popover
402
- v-if="editingRowIndex !== scope.$index"
403
- ref="operationPopoverRef"
404
- placement="bottom"
405
- trigger="click"
406
- popper-class="operation-popover"
407
- >
408
- <div
409
- slot="reference"
410
- class="operation-popover__operation-reference btn-pointer"
411
- >
412
- <el-button :class="['operation-popover__operation-btn', hoveringCellInfo.rowIndex === scope.$index && 'operation-popover__operation-btn--active']">
413
- 操作
414
- </el-button>
415
- </div>
416
- <div class="operation-popover__operation">
417
- <div
418
- v-if="defaultOperations.includes('delete')"
419
- class="operation-popover__operation-item btn-pointer"
420
- @click="handleDelete(scope.row, scope.$index)"
421
- >
422
- 删除
423
- </div>
424
- <div
425
- v-if="defaultOperations.includes('edit')"
426
- class="operation-popover__operation-item btn-pointer"
427
- @click="handleEdit(scope)"
428
- >
429
- 编辑
430
- </div>
431
- <div
432
- v-if="defaultOperations.includes('top')"
433
- class="operation-popover__operation-item btn-pointer"
434
- @click="handleRowPinToTop(scope)"
435
- >
436
- 置顶
437
- </div>
438
- <slot
439
- name="custom-operation"
440
- v-bind="scope"
441
- />
442
- </div>
443
- </el-popover>
444
- <div
445
- v-else
446
- class="operation-popover__save-cancel"
447
- >
448
- <div
449
- class="btn-pointer operation-popover__btn"
450
- @click="handleEditSave(scope.row)"
451
- >
452
- 保存
453
- </div>
454
- <div
455
- class="btn-pointer operation-popover__btn"
456
- @click="handleEditCancel(scope.row)"
457
- >
458
- 取消
459
- </div>
460
- </div>
461
- </template>
462
- <!-- 默认渲染、非编辑态,不需要特殊处理,el-table负责渲染 -->
463
- <!-- 自定义插槽 -->
464
- <template
465
- v-else-if="column.slotName"
466
- #default="scope"
467
- >
468
- <!-- 非编辑态 -->
469
- <slot
470
- v-if="scope.$index !== editingRowIndex"
471
- v-bind="scope"
472
- :name="column.slotName"
473
- />
474
- <!-- 编辑态 -->
475
- <template v-else-if="scope.$index === editingRowIndex">
476
- <!-- 自定义编辑 -->
477
- <slot
478
- v-if="column.editSlotName"
479
- v-bind="{scope, column}"
480
- :name="column.editSlotName"
481
- />
482
- <!-- 内置编辑类型 -->
483
- <div v-else-if="column.editType">
484
- <div v-if="column.editType === 'input'">
485
- <el-input
486
- v-model="editingRowData[column.prop]"
487
- clearable
488
- />
489
- </div>
490
- <div v-if="column.editType === 'select'">
491
- <el-select v-model="editingRowData[column.prop]">
492
- <el-option
493
- v-for="item in column.selectOptions"
494
- :key="item.label"
495
- :label="item.label"
496
- :value="item.value"
497
- />
498
- </el-select>
499
- </div>
500
- <el-date-picker
501
- v-if="column.editType === 'date'"
502
- v-model="editingRowData[column.prop]"
503
- type="date"
504
- placeholder="选择日期"
505
- />
506
- </div>
507
- <!-- 不支持编辑 -->
508
- <slot
509
- v-else
510
- v-bind="scope"
511
- :name="column.slotName"
512
- />
513
- </template>
514
- </template>
515
- <!-- 默认渲染列,编辑态 -->
516
- <template
517
- v-else-if="editingRowIndex !== -1"
518
- #default="scope"
519
- >
520
- <!-- 当前行编辑中,内置编辑类型 -->
521
- <template v-if="editingRowIndex === scope.$index && column.editType">
522
- <div v-if="column.editType === 'input'">
523
- <el-input
524
- v-model="editingRowData[column.prop]"
525
- clearable
526
- type="text"
527
- />
528
- </div>
529
- <div v-if="column.editType === 'select'">
530
- <el-select v-model="editingRowData[column.prop]">
531
- <el-option
532
- v-for="item in column.selectOptions"
533
- :key="item.value"
534
- :label="item.label"
535
- :value="item.value"
536
- />
537
- </el-select>
538
- </div>
539
- <div v-if="column.editType === 'date'">
540
- <el-date-picker
541
- v-model="editingRowData[column.prop]"
542
- type="date"
543
- placeholder="选择日期"
544
- />
545
- </div>
546
- </template>
547
- <!-- 当前行编辑中,自定义编辑类型 -->
548
- <slot
549
- v-else-if="column.editSlotName && scope.$index === editingRowIndex"
550
- v-bind="scope"
551
- :name="column.editSlotName"
552
- />
553
- <!-- 当前行非编辑中 -->
554
- <template v-else>
555
- {{ column.formatter ? column.formatter(scope.row, column, scope.row[column.prop], scope.$index) : scope.row[column.prop] }}
556
- </template>
557
- </template>
558
- <!-- hover状态渲染 -->
559
- <template
560
- v-else-if="column.hoverSlotName"
561
- #default="scope"
562
- >
563
- <slot
564
- v-if="scope.$index === hoveringCellInfo.rowIndex && column.prop === hoveringCellInfo.columnProperty"
565
- v-bind="scope"
566
- :name="column.hoverSlotName"
567
- />
568
- <slot
569
- v-else-if="column.slotName"
570
- v-bind="scope"
571
- :name="column.slotName"
572
- />
573
- <template v-else>
574
- {{ column.formatter ? column.formatter(scope.row, column, scope.row[column.prop], scope.$index) : scope.row[column.prop] }}
575
- </template>
576
- </template>
577
- </el-table-column>
578
- </el-table>
579
- <div class="pagination-wrap">
580
- <div>共{{ total }}项数据</div>
581
- <el-pagination
582
- background
583
- layout="sizes, prev, pager, next, jumper"
584
- :page-sizes="[10, 15, 30, 60, 100]"
585
- :page-size.sync="pageSize"
586
- :pager-count="11"
587
- :current-page="currentPage"
588
- :total="total"
589
- @size-change="handlePageSizeChange"
590
- @current-change="handleCurrPageChange"
591
- />
592
- </div>
593
- </div>
594
- </template>
595
-
596
- <script lang="ts" setup>
597
- import { computed, nextTick, ref, watch } from 'vue';
598
- import { Message } from 'element-ui'
599
- import usePagination from './usePagination';
600
- import useCellHover from './useCellHover';
601
- import useViewSetting from './useViewSetting';
602
- import useRowBgColor from './useRowBgColor';
603
- import useDefaultOperation from './useDefaultOperation'
604
- import useColumnHeaderOperation from './useColumnHeaderOperation';
605
- import useDragSort from './useDragSort';
606
- import { ITableDataItem, IColumnConfig, IDefaultOperationType, IColorList } from './types';
607
-
608
- // defineProps泛型参数如果从外部传入,编译报错
609
- interface IProps {
610
- /** 表格数据 */
611
- dataList: ITableDataItem[];
612
- /** 列配置 */
613
- columnConfig: IColumnConfig[];
614
- /** 是否展示展开行 */
615
- hasExpandRow?: boolean;
616
- /** 是否展示序号 */
617
- hasIndexColumn?: boolean;
618
- /** 是否展示选择列 */
619
- hasSelectionColumn?: boolean;
620
- /** 是否支持行拖拽 */
621
- rowDragAble?: boolean;
622
- /** 表格总条数 */
623
- total: number;
624
- /** 自定义列操作,当前支持行编辑(edit),删除(delete),置顶(top) */
625
- defaultOperations?: IDefaultOperationType[];
626
- /** 行背景色列表 */
627
- colorList?: IColorList;
628
- /** 左侧固定列数 */
629
- leftFixedCount?: number;
630
- /** 性能优化参数,调整拖拽的范围 */
631
- dragSemiRange?: number;
632
- /** 是否显示加载 */
633
- loading?: boolean;
634
- /** 是否隐藏显示设置按钮 */
635
- hideViewSettingBtn?: boolean
636
- /** 设置的缓存的key */
637
- settingStorgeKey?: string
638
- /** 前端排序,默认关闭 */
639
- localSort?: boolean
640
- /** 前端过滤,默认关闭 */
641
- localFilter?: boolean
642
- /** 页码 */
643
- currentPage: number
644
- }
645
-
646
- interface IEmits {
647
- /** 行选中 */
648
- (e: 'selection-change', selection: any): void
649
- /** 修改行背景色 */
650
- (e: 'row-bg-change', param: {colorId: number; row: ITableDataItem; rowIndex: number}): void
651
- /** 行拖拽放置事件 */
652
- (e: 'row-drag-drop', param: { row: any; fromIndex: number; toIndex: number; page: number; size: number;}): void
653
- /** 行删除 */
654
- (e: 'row-delete', param: { row: any; index: number; page: number; size: number; }): void
655
- /** 点击编辑按钮立即触发 */
656
- (e: 'row-edit', param: { row: any, index: number; page: number; size: number;}): void
657
- /** 行置顶 */
658
- (e: 'row-pin-to-top', param: { row: any, rawIndex: number; page: number; size: number;}): void
659
- /** 行编辑保存 */
660
- (e: 'row-edit-save', param: { page: number; size: number; row: any; changedData: Record<string, any>; }): void
661
- /** 行编辑取消 */
662
- (e: 'row-edit-cancel', param: { row: any; page: number; size: number;}): void
663
- /** 页码改变 */
664
- (e: 'page-change', param: { page: number, size: number }): void
665
- /** 查询 */
666
- (e: 'search', param: Record<string, any>): void
667
- /** 排序 */
668
- (e: 'sort-change', param: { order: 'descending' | 'ascending' | null, prop: string }): void
669
- }
670
-
671
- const props = withDefaults(defineProps<IProps>(), {
672
- dataList: () => [],
673
- columnConfig: () => [],
674
- hasExpandRow: false,
675
- hasIndexColumn: false,
676
- hasSelectionColumn: false,
677
- rowDragAble: false,
678
- total: 0,
679
- defaultOperations: () => [],
680
- colorList: () => [],
681
- leftFixedCount: 1,
682
- dragSemiRange: 15,
683
- loading: false,
684
- hideViewSettingBtn: false,
685
- settingStorgeKey: '',
686
- localSort: false,
687
- localFilter: false,
688
- currentPage: 1,
689
- })
690
-
691
- // 同defineProps一样,不支持泛型参数从外部导入
692
- const emit = defineEmits<IEmits>()
693
-
694
- const showingColumns = ref<string[]>([]); // 表格中实际展示的列
695
-
696
- const actualColumns = computed(() => {
697
- const res: IColumnConfig[] = [];
698
- let cnt = leftFixedColumnCount.value
699
-
700
- // 列排序和列过滤
701
- for (const prop of showingColumns.value) {
702
- const rawItem = props.columnConfig.find(c => c.prop === prop) ?? {} as IColumnConfig;
703
- const item: IColumnConfig = {
704
- ...rawItem,
705
- isColumnSortable: rawItem.sortable,
706
- sortable: inSorting.value ? rawItem.sortable : false
707
- };
708
- if (cnt > 0) {
709
- item.fixed = 'left';
710
- // eslint-disable-next-line no-plusplus
711
- cnt--;
712
- } else {
713
- item.fixed = undefined;
714
- }
715
- res.push(item);
716
- }
717
-
718
- // 使用默认操作项,添加默认操作列。该列在编辑模式下隐藏
719
- if (props.defaultOperations && props.defaultOperations.length > 0) {
720
- res.push({
721
- label: '操作',
722
- prop: '$$operation',
723
- 'min-width': '100px',
724
- fixed: 'right'
725
- });
726
- }
727
-
728
- return res;
729
- })
730
-
731
- const sortFilterPopoverRef = ref<any>(null);
732
- const tableDomRef = ref<any>(null);
733
- const currScope = ref<any>(null);
734
-
735
- /************ 分页相关 *************/
736
- const beforePageChange = () => {
737
- searchValue.value = {};
738
- }
739
- const {
740
- pageSize,
741
- handleCurrPageChange,
742
- handlePageSizeChange,
743
- } = usePagination({
744
- emit,
745
- beforePageChange
746
- })
747
-
748
- /************ 表格单元格hover事件相关 ************ */
749
- const {
750
- hoveringCellInfo,
751
- setCellClassName,
752
- debouncedHoverHandler
753
- } = useCellHover(tableDomRef)
754
-
755
- /************ 行背景色设置相关 ************ */
756
- const {
757
- isDefaultColor,
758
- getColorById,
759
- setRowStyle,
760
- handleColorChange,
761
- colorPopoverRef
762
- } = useRowBgColor({
763
- colorList: props.colorList,
764
- emit
765
- })
766
-
767
- /************ 默认的操作相关 ************ */
768
- const {
769
- operationPopoverRef,
770
- editingRowData,
771
- editingRowIndex,
772
- handleDelete,
773
- handleEdit,
774
- handleEditSave,
775
- handleEditCancel,
776
- handleRowPinToTop,
777
- closeAllExpandedRows
778
- } = useDefaultOperation({
779
- emit,
780
- tableDomRef,
781
- pageSize,
782
- props,
783
- hasExpandRow: props.hasExpandRow
784
- })
785
-
786
- /************ 显示设置相关 ************ */
787
- const {
788
- viewSettingDragSortOptions,
789
- columnsToBeShown,
790
- viewSettingVisible,
791
- leftFixedColumnCount,
792
- tempLeftFixedColumnCount,
793
- handleInputTempLeftFixedColumnCount,
794
- handleViewSettingShow,
795
- handleViewSettingClose,
796
- handleViewSettingConfirm
797
- } = useViewSetting({
798
- tableDomRef,
799
- showingColumns,
800
- actualColumns,
801
- props
802
- })
803
-
804
- /************ 列头部操作相关 ************ */
805
- const {
806
- setSort,
807
- clearSort,
808
- setSearchParams,
809
- isColumnHeadActive,
810
- handleSort,
811
- handleHeaderPopoverShow,
812
- handleHeaderOperationConfirm,
813
- handleHeaderOperationReset,
814
- summaryList,
815
- tableSummaryMethod,
816
- filteredValue,
817
- showColumnHeadSortIcon,
818
- tempSortingColumn,
819
- tempSearchValue,
820
- tempFilteredValue,
821
- tempSummaryList,
822
- tempSortType,
823
- sortingColumn,
824
- isColumnFiltering,
825
- searchValue,
826
- inSorting,
827
- } = useColumnHeaderOperation({
828
- tableDomRef,
829
- sortFilterPopoverRef,
830
- props,
831
- emit,
832
- showingColumns,
833
- })
834
-
835
- /************ 表格行拖拽和显示设置列拖拽 ************ */
836
- const beforeDragStart = () => {
837
- // 如果有列存在排序,不允许拖拽
838
- if (sortingColumn.value) {
839
- Message.warning('已有列正在排序,不允许拖拽。');
840
- return false;
841
- }
842
-
843
- // 如果有列存在筛选,不允许拖拽
844
- if (isColumnFiltering.value) {
845
- Message.warning('已有列正在筛选,不允许拖拽。');
846
- return false;
847
- }
848
-
849
- editingRowIndex.value = -1; // 关闭编辑状态
850
-
851
- return true
852
- }
853
-
854
- useDragSort({
855
- emit,
856
- props,
857
- viewSettingDragSortOptions,
858
- beforeDragStart,
859
- pageSize,
860
- currScope,
861
- tableDomRef,
862
- })
863
-
864
- const doTableLayout = async () => {
865
- await nextTick();
866
- tableDomRef.value?.doLayout();
867
- }
868
-
869
- // 过滤出自定义属性,将其它属性全部透传给 el-table-column
870
- const getColumnBindProps = (column: IColumnConfig) => {
871
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
872
- const { editAble, editType, slotName, inputType, options, filters, ...rest } = column;
873
- return rest;
874
- }
875
-
876
- const setRowClassName = (scope) => {
877
- return `
878
- custom-row-classname
879
- custom-row-classname-${scope.rowIndex}
880
- ${scope.row.isPinned ? 'custom-row-classname-pinned' : ''}
881
- `
882
- }
883
-
884
- const handleSelectionChange = (e) => {
885
- emit('selection-change', e);
886
- }
887
-
888
- const handleColumnClose = (item) => {
889
- if (item.isAlwaysShow) return;
890
- columnsToBeShown.value = columnsToBeShown.value.filter(c => c !== item.prop);
891
- }
892
-
893
- defineExpose({
894
- closeAllExpandedRows,
895
- openViewSetting: handleViewSettingShow,
896
- elTableRef: tableDomRef,
897
- setSort,
898
- clearSort,
899
- setSearchParams,
900
- })
901
-
902
- // loading 结束和页码变化时滚动到顶部
903
- watch([
904
- () => props.loading,
905
- () => props.currentPage,
906
- ], ([loading]) => {
907
- if (loading) return;
908
- tableDomRef.value.$el.querySelector('.el-table__body-wrapper').scrollTop = 0
909
- })
910
-
911
- </script>
912
-
913
- <style lang="less">
914
- @import './index.less';
1
+ <template>
2
+ <div class="editable-table">
3
+ <div class="view-setting">
4
+ <div
5
+ v-if="!hideViewSettingBtn"
6
+ class="view-setting__btn-wrapper"
7
+ >
8
+ <div
9
+ class="view-setting__btn btn-pointer"
10
+ @click="handleViewSettingShow"
11
+ >
12
+ <i class="el-icon-setting" />
13
+ <div class="view-setting__btn-text">
14
+ 显示设置
15
+ </div>
16
+ </div>
17
+ </div>
18
+
19
+ <el-dialog
20
+ title="显示设置"
21
+ :visible.sync="viewSettingVisible"
22
+ width="750px"
23
+ top="12vh"
24
+ :close-on-click-modal="false"
25
+ :append-to-body="true"
26
+ custom-class="view-setting__dialog"
27
+ >
28
+ <div class="view-setting__content">
29
+ <div class="view-setting__content-left">
30
+ <!-- <div class="view-setting__content-left-title">
31
+ 表头设置
32
+ </div> -->
33
+ <div class="view-setting__checkbox-wrapper">
34
+ <el-checkbox-group v-model="columnsToBeShown">
35
+ <el-checkbox
36
+ v-for="item in columnConfig"
37
+ :key="item.label"
38
+ :label="item.prop"
39
+ :disabled="item.isAlwaysShow"
40
+ >
41
+ <div class="view-setting__content-left-item">
42
+ {{ item.label }}
43
+ </div>
44
+ </el-checkbox>
45
+ </el-checkbox-group>
46
+ </div>
47
+ </div>
48
+ <div class="view-setting__content-right">
49
+ <div class="view-setting__content-right-title">
50
+ 已选择
51
+ <div class="view-setting__selected-count">
52
+ {{ columnsToBeShown.length }}
53
+ </div>
54
+ </div>
55
+ <div class="view-setting__content-right-frize">
56
+ 冻结前
57
+ <el-input
58
+ class="view-setting__content-right-input"
59
+ :value="tempLeftFixedColumnCount"
60
+ @input="handleInputTempLeftFixedColumnCount"
61
+ />
62
+
63
+ </div>
64
+ <div class="view-setting__content-right-selected">
65
+ <div
66
+ v-for="(item, index) in viewSettingDragSortOptions"
67
+ :key="item.prop"
68
+ class="view-setting__selected-item view-setting-draggable-item"
69
+ >
70
+ <div class="view-setting__selected-item-left">
71
+ <div
72
+ class="view-setting-drag-target view-setting__icon-wrapper"
73
+ :data-index="index"
74
+ >
75
+ <div
76
+ class="view-setting-drag-target editable-table-drag-icon"
77
+ :data-index="index"
78
+ />
79
+ </div>
80
+ <div class="view-setting__selected-item-name">
81
+ {{ item.label }}
82
+ </div>
83
+ </div>
84
+ <div
85
+ :class="['view-setting__selected-item-close', item.isAlwaysShow ? 'view-setting__selected-item-close--disabled' : '']"
86
+ @click="handleColumnClose(item)"
87
+ >
88
+ <i class="el-icon-close" />
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ <template #footer>
95
+ <el-button @click="handleViewSettingClose">
96
+ 取消
97
+ </el-button>
98
+ <el-button
99
+ type="primary"
100
+ @click="handleViewSettingConfirm"
101
+ >
102
+ 确认
103
+ </el-button>
104
+ </template>
105
+ </el-dialog>
106
+ </div>
107
+
108
+ <!-- 列表展示,属性透传,列编辑 -->
109
+ <el-table
110
+ ref="tableDomRef"
111
+ v-loading="loading"
112
+ :data="dataList"
113
+ :row-style="setRowStyle"
114
+ :row-class-name="setRowClassName"
115
+ :cell-class-name="setCellClassName"
116
+ :show-summary="summaryList.length > 0"
117
+ :summary-method="tableSummaryMethod"
118
+ v-bind="$attrs"
119
+ border
120
+ @selection-change="handleSelectionChange"
121
+ @cell-mouse-enter="debouncedHoverHandler"
122
+ @header-dragend="doTableLayout"
123
+ >
124
+ <el-table-column
125
+ v-if="rowDragAble"
126
+ width="30px"
127
+ class-name="editable-table__drag-cell no-inner-cell-border"
128
+ :fixed="leftFixedColumnCount > 0 ? 'left' : ''"
129
+ >
130
+ <template #default="scope">
131
+ <div
132
+ class="row-drag-target editable-table__drag-icon"
133
+ :data-index="scope.$index"
134
+ @mousedown="currScope = scope"
135
+ >
136
+ <div
137
+ :data-index="scope.$index"
138
+ class="row-drag-target editable-table-drag-icon"
139
+ />
140
+ </div>
141
+ </template>
142
+ </el-table-column>
143
+ <!-- 展开行 -->
144
+ <el-table-column
145
+ v-if="hasExpandRow"
146
+ width="30px"
147
+ type="expand"
148
+ :fixed="leftFixedColumnCount > 0 ? 'left' : ''"
149
+ class-name="no-inner-cell-border"
150
+ >
151
+ <template #default="scope">
152
+ <slot
153
+ name="expand"
154
+ v-bind="scope"
155
+ />
156
+ </template>
157
+ </el-table-column>
158
+ <!-- 选择列 -->
159
+ <el-table-column
160
+ v-if="hasSelectionColumn"
161
+ width="45px"
162
+ align="center"
163
+ type="selection"
164
+ :fixed="leftFixedColumnCount > 0 ? 'left' : ''"
165
+ class-name="no-inner-cell-border"
166
+ />
167
+ <!-- 编号列 -->
168
+ <el-table-column
169
+ v-if="hasIndexColumn"
170
+ min-width="30px"
171
+ type="index"
172
+ :fixed="leftFixedColumnCount > 0 ? 'left' : ''"
173
+ class-name="no-inner-cell-border"
174
+ />
175
+ <!-- 颜色选择列 -->
176
+ <el-table-column
177
+ v-if="colorList && colorList.length > 0"
178
+ width="22px"
179
+ class-name="editable-table__color-column no-inner-cell-border"
180
+ :fixed="leftFixedColumnCount > 0 ? 'left' : ''"
181
+ >
182
+ <template #header>
183
+ <div class="editable-table__color-icon" />
184
+ </template>
185
+ <template #default="scope">
186
+ <view-color-select
187
+ :color-list="colorList"
188
+ :scope="scope"
189
+ @row-bg-change="(params) => emit('row-bg-change', params)"
190
+ />
191
+ </template>
192
+ </el-table-column>
193
+ <!-- 这里的key很重要,必须保证每次生成的key都不一样,这样列排序功能才能生效,否则,即使actualColumns数组元素顺序发生变化,列顺序也不会改变。原因未知 -->
194
+ <el-table-column
195
+ v-for="(column, index) in actualColumns"
196
+ :key="column.prop + index"
197
+ resizable
198
+ class-name="editable-table__data-column"
199
+ :filtered-value="Array.isArray(filteredValue[column.prop]) ? filteredValue[column.prop] : []"
200
+ v-bind="getColumnBindProps(column)"
201
+ >
202
+ <template
203
+ v-if="showColumnHeadSortIcon(column)"
204
+ #header="scope"
205
+ >
206
+ <el-popover
207
+ ref="sortFilterPopoverRef"
208
+ placement="bottom"
209
+ trigger="click"
210
+ popper-class="editable-table__sort-filter"
211
+ :data-prop="column.prop"
212
+ @show="handleHeaderPopoverShow(column)"
213
+ >
214
+ <template slot="reference">
215
+ <!-- 筛选中,或排序中,高亮 -->
216
+ <span :class="['editable-table__sort-reference', isColumnHeadActive(column) && 'editable-table__sort-reference--active']">
217
+ {{ column.label }}
218
+ <div :class="['editable-table__sort-icon', isColumnHeadActive(column) && 'editable-table__sort-icon--active']" />
219
+ </span>
220
+ </template>
221
+ <div class="sort-filter">
222
+ <div class="sort-filter__column-title">
223
+ {{ column.label }}
224
+ </div>
225
+ <div
226
+ v-if="column.isColumnSortable"
227
+ class="sort-filter__sort"
228
+ >
229
+ <div class="sort-filter__sort-title">
230
+ 排序
231
+ </div>
232
+ <div class="sort-filter__sort-btns">
233
+ <el-button
234
+ :class="['sort-filter__sort-btn', tempSortingColumn?.prop === column.prop && tempSortType === 'ascending' && 'sort-filter__sort-btn--active']"
235
+ @click="handleSort('ascending', column)"
236
+ >
237
+ 升序
238
+ </el-button>
239
+ <el-button
240
+ :class="['sort-filter__sort-btn', tempSortingColumn?.prop === column.prop && tempSortType === 'descending' && 'sort-filter__sort-btn--active']"
241
+ @click="handleSort('descending', column)"
242
+ >
243
+ 降序
244
+ </el-button>
245
+ </div>
246
+ </div>
247
+ <div
248
+ v-if="column.search && !Array.isArray(column.search)"
249
+ class="sort-filter__search"
250
+ >
251
+ <div class="sort-filter__search-title">
252
+ 搜索
253
+ </div>
254
+ <el-input
255
+ v-model.trim="tempSearchValue[column.prop]"
256
+ class="sort-filter__search-input"
257
+ placeholder="请输入内容"
258
+ />
259
+ </div>
260
+
261
+ <div
262
+ v-if="column.search && Array.isArray(column.search)"
263
+ class="sort-filter__search"
264
+ style="display: flex;flex-direction: column;gap: 12px;"
265
+ >
266
+ <div
267
+ v-for="item in column.search"
268
+ :key="item.prop"
269
+ >
270
+ <div class="sort-filter__search-title">
271
+ {{ item.label }}
272
+ </div>
273
+ <el-input
274
+ v-model.trim="tempSearchValue[item.prop]"
275
+ class="sort-filter__search-input"
276
+ placeholder="请输入内容"
277
+ />
278
+ </div>
279
+ </div>
280
+
281
+ <div
282
+ v-if="column.filters && ((Array.isArray(column.filters) ? column.filters : column.filters.options).length > 0)"
283
+ class="sort-filter__filter"
284
+ >
285
+ <div class="sort-filter__filter-title">
286
+ 筛选
287
+ </div>
288
+ <el-checkbox-group
289
+ v-if="column.filters && (Array.isArray(column.filters) || column.filters.type === 'checkbox')"
290
+ v-model="tempFilteredValue[column.prop]"
291
+ class="sort-filter__filter-checkbox-group"
292
+ >
293
+ <el-checkbox
294
+ v-for="item in (Array.isArray(column.filters) ? column.filters : column.filters.options)"
295
+ :key="item.value"
296
+ :label="item.value"
297
+ class="sort-filter__filter-checkbox"
298
+ >
299
+ <slot
300
+ :name="column.prop + '-filter-item'"
301
+ v-bind="item"
302
+ >
303
+ {{ item.text }}
304
+ </slot>
305
+ </el-checkbox>
306
+ </el-checkbox-group>
307
+
308
+ <el-radio-group
309
+ v-if="column.filters && !Array.isArray(column.filters) && column.filters.type === 'radio'"
310
+ v-model="tempFilteredValue[column.prop]"
311
+ style="display: flex;flex-direction: column;gap: 6px;"
312
+ >
313
+ <el-radio
314
+ v-for="item in column.filters.options"
315
+ :key="item.value"
316
+ :label="item.value"
317
+ >
318
+ <slot
319
+ :name="column.prop + '-filter-item'"
320
+ v-bind="item"
321
+ >
322
+ {{ item.text }}
323
+ </slot>
324
+ </el-radio>
325
+ </el-radio-group>
326
+ </div>
327
+ <div
328
+ v-if="column.summary"
329
+ class="sort-filter__filter"
330
+ >
331
+ <div class="sort-filter__filter-title">
332
+ 统计
333
+ </div>
334
+ <el-checkbox-group
335
+ v-model="tempSummaryList"
336
+ class="sort-filter__filter-checkbox-group"
337
+ >
338
+ <el-checkbox
339
+ :label="column.prop"
340
+ class="sort-filter__filter-checkbox"
341
+ >
342
+ <slot
343
+ :name="column.prop + '-summay-item'"
344
+ v-bind="column"
345
+ >
346
+ {{ column.label }}
347
+ </slot>
348
+ </el-checkbox>
349
+ </el-checkbox-group>
350
+ </div>
351
+ <div class="sort-filter__footer">
352
+ <el-button
353
+ class="sort-filter__reset-btn"
354
+ @click="handleHeaderOperationReset(column, scope)"
355
+ >
356
+ 重置
357
+ </el-button>
358
+ <el-button
359
+ class="sort-filter__confirm-btn"
360
+ type="primary"
361
+ @click="handleHeaderOperationConfirm(column, scope)"
362
+ >
363
+ 确定
364
+ </el-button>
365
+ </div>
366
+ </div>
367
+ </el-popover>
368
+ </template>
369
+ <!-- 默认操作按钮,defaultOperations属性不为空数组时展示。编辑状态下隐藏 -->
370
+ <template
371
+ v-if="column.prop === '$$operation'"
372
+ #default="scope"
373
+ >
374
+ <el-popover
375
+ v-if="editingRowIndex !== scope.$index"
376
+ ref="operationPopoverRef"
377
+ placement="bottom"
378
+ trigger="click"
379
+ popper-class="operation-popover"
380
+ >
381
+ <div
382
+ slot="reference"
383
+ class="operation-popover__operation-reference btn-pointer"
384
+ >
385
+ <el-button :class="['operation-popover__operation-btn', hoveringCellInfo.rowIndex === scope.$index && 'operation-popover__operation-btn--active']">
386
+ 操作
387
+ </el-button>
388
+ </div>
389
+ <div class="operation-popover__operation">
390
+ <div
391
+ v-if="defaultOperations.includes('delete')"
392
+ class="operation-popover__operation-item btn-pointer"
393
+ @click="handleDelete(scope.row, scope.$index)"
394
+ >
395
+ 删除
396
+ </div>
397
+ <div
398
+ v-if="defaultOperations.includes('edit')"
399
+ class="operation-popover__operation-item btn-pointer"
400
+ @click="handleEdit(scope)"
401
+ >
402
+ 编辑
403
+ </div>
404
+ <div
405
+ v-if="defaultOperations.includes('top')"
406
+ class="operation-popover__operation-item btn-pointer"
407
+ @click="handleRowPinToTop(scope)"
408
+ >
409
+ 置顶
410
+ </div>
411
+ <slot
412
+ name="custom-operation"
413
+ v-bind="scope"
414
+ />
415
+ </div>
416
+ </el-popover>
417
+ <div
418
+ v-else
419
+ class="operation-popover__save-cancel"
420
+ >
421
+ <div
422
+ class="btn-pointer operation-popover__btn"
423
+ @click="handleEditSave(scope.row)"
424
+ >
425
+ 保存
426
+ </div>
427
+ <div
428
+ class="btn-pointer operation-popover__btn"
429
+ @click="handleEditCancel(scope.row)"
430
+ >
431
+ 取消
432
+ </div>
433
+ </div>
434
+ </template>
435
+ <!-- 默认渲染、非编辑态,不需要特殊处理,el-table负责渲染 -->
436
+ <!-- 自定义插槽 -->
437
+ <template
438
+ v-else-if="column.slotName"
439
+ #default="scope"
440
+ >
441
+ <!-- 非编辑态 -->
442
+ <slot
443
+ v-if="scope.$index !== editingRowIndex"
444
+ v-bind="scope"
445
+ :name="column.slotName"
446
+ />
447
+ <!-- 编辑态 -->
448
+ <template v-else-if="scope.$index === editingRowIndex">
449
+ <!-- 自定义编辑 -->
450
+ <slot
451
+ v-if="column.editSlotName"
452
+ v-bind="{scope, column}"
453
+ :name="column.editSlotName"
454
+ />
455
+ <!-- 内置编辑类型 -->
456
+ <div v-else-if="column.editType">
457
+ <div v-if="column.editType === 'input'">
458
+ <el-input
459
+ v-model="editingRowData[column.prop]"
460
+ clearable
461
+ />
462
+ </div>
463
+ <div v-if="column.editType === 'select'">
464
+ <el-select v-model="editingRowData[column.prop]">
465
+ <el-option
466
+ v-for="item in column.selectOptions"
467
+ :key="item.label"
468
+ :label="item.label"
469
+ :value="item.value"
470
+ />
471
+ </el-select>
472
+ </div>
473
+ <el-date-picker
474
+ v-if="column.editType === 'date'"
475
+ v-model="editingRowData[column.prop]"
476
+ type="date"
477
+ placeholder="选择日期"
478
+ />
479
+ </div>
480
+ <!-- 不支持编辑 -->
481
+ <slot
482
+ v-else
483
+ v-bind="scope"
484
+ :name="column.slotName"
485
+ />
486
+ </template>
487
+ </template>
488
+ <!-- 默认渲染列,编辑态 -->
489
+ <template
490
+ v-else-if="editingRowIndex !== -1"
491
+ #default="scope"
492
+ >
493
+ <!-- 当前行编辑中,内置编辑类型 -->
494
+ <template v-if="editingRowIndex === scope.$index && column.editType">
495
+ <div v-if="column.editType === 'input'">
496
+ <el-input
497
+ v-model="editingRowData[column.prop]"
498
+ clearable
499
+ type="text"
500
+ />
501
+ </div>
502
+ <div v-if="column.editType === 'select'">
503
+ <el-select v-model="editingRowData[column.prop]">
504
+ <el-option
505
+ v-for="item in column.selectOptions"
506
+ :key="item.value"
507
+ :label="item.label"
508
+ :value="item.value"
509
+ />
510
+ </el-select>
511
+ </div>
512
+ <div v-if="column.editType === 'date'">
513
+ <el-date-picker
514
+ v-model="editingRowData[column.prop]"
515
+ type="date"
516
+ placeholder="选择日期"
517
+ />
518
+ </div>
519
+ </template>
520
+ <!-- 当前行编辑中,自定义编辑类型 -->
521
+ <slot
522
+ v-else-if="column.editSlotName && scope.$index === editingRowIndex"
523
+ v-bind="scope"
524
+ :name="column.editSlotName"
525
+ />
526
+ <!-- 当前行非编辑中 -->
527
+ <template v-else>
528
+ {{ column.formatter ? column.formatter(scope.row, column, scope.row[column.prop], scope.$index) : scope.row[column.prop] }}
529
+ </template>
530
+ </template>
531
+ <!-- hover状态渲染 -->
532
+ <template
533
+ v-else-if="column.hoverSlotName"
534
+ #default="scope"
535
+ >
536
+ <slot
537
+ v-if="scope.$index === hoveringCellInfo.rowIndex && column.prop === hoveringCellInfo.columnProperty"
538
+ v-bind="scope"
539
+ :name="column.hoverSlotName"
540
+ />
541
+ <slot
542
+ v-else-if="column.slotName"
543
+ v-bind="scope"
544
+ :name="column.slotName"
545
+ />
546
+ <template v-else>
547
+ {{ column.formatter ? column.formatter(scope.row, column, scope.row[column.prop], scope.$index) : scope.row[column.prop] }}
548
+ </template>
549
+ </template>
550
+ </el-table-column>
551
+ </el-table>
552
+ <div class="pagination-wrap">
553
+ <div>共{{ total }}项数据</div>
554
+ <el-pagination
555
+ background
556
+ layout="sizes, prev, pager, next, jumper"
557
+ :page-sizes="[10, 15, 30, 60, 100]"
558
+ :page-size.sync="pageSize"
559
+ :pager-count="11"
560
+ :current-page="currentPage"
561
+ :total="total"
562
+ @size-change="handlePageSizeChange"
563
+ @current-change="handleCurrPageChange"
564
+ />
565
+ </div>
566
+ </div>
567
+ </template>
568
+
569
+ <script lang="ts" setup>
570
+ import ViewColorSelect from './viewColorSelect.vue';
571
+
572
+ import { computed, nextTick, ref, watch } from 'vue';
573
+ import { Message } from 'element-ui';
574
+ import usePagination from './usePagination';
575
+ import useCellHover from './useCellHover';
576
+ import useViewSetting from './useViewSetting';
577
+ import useRowBgColor from './useRowBgColor';
578
+ import useDefaultOperation from './useDefaultOperation';
579
+ import useColumnHeaderOperation from './useColumnHeaderOperation';
580
+ import useDragSort from './useDragSort';
581
+ import { ITableDataItem, IColumnConfig, IDefaultOperationType, IColorList } from './types';
582
+
583
+ // defineProps泛型参数如果从外部传入,编译报错
584
+ interface IProps {
585
+ /** 表格数据 */
586
+ dataList: ITableDataItem[];
587
+ /** 列配置 */
588
+ columnConfig: IColumnConfig[];
589
+ /** 是否展示展开行 */
590
+ hasExpandRow?: boolean;
591
+ /** 是否展示序号 */
592
+ hasIndexColumn?: boolean;
593
+ /** 是否展示选择列 */
594
+ hasSelectionColumn?: boolean;
595
+ /** 是否支持行拖拽 */
596
+ rowDragAble?: boolean;
597
+ /** 表格总条数 */
598
+ total: number;
599
+ /** 自定义列操作,当前支持行编辑(edit),删除(delete),置顶(top) */
600
+ defaultOperations?: IDefaultOperationType[];
601
+ /** 行背景色列表 */
602
+ colorList?: IColorList;
603
+ /** 左侧固定列数 */
604
+ leftFixedCount?: number;
605
+ /** 性能优化参数,调整拖拽的范围 */
606
+ dragSemiRange?: number;
607
+ /** 是否显示加载 */
608
+ loading?: boolean;
609
+ /** 是否隐藏显示设置按钮 */
610
+ hideViewSettingBtn?: boolean
611
+ /** 设置的缓存的key */
612
+ settingStorgeKey?: string
613
+ /** 前端排序,默认关闭 */
614
+ localSort?: boolean
615
+ /** 前端过滤,默认关闭 */
616
+ localFilter?: boolean
617
+ /** 页码 */
618
+ currentPage: number
619
+ }
620
+
621
+ interface IEmits {
622
+ /** 行选中 */
623
+ (e: 'selection-change', selection: any): void
624
+ /** 修改行背景色 */
625
+ (e: 'row-bg-change', param: {colorId: number; row: ITableDataItem; rowIndex: number}): void
626
+ /** 行拖拽放置事件 */
627
+ (e: 'row-drag-drop', param: { row: any; fromIndex: number; toIndex: number; page: number; size: number;}): void
628
+ /** 行删除 */
629
+ (e: 'row-delete', param: { row: any; index: number; page: number; size: number; }): void
630
+ /** 点击编辑按钮立即触发 */
631
+ (e: 'row-edit', param: { row: any, index: number; page: number; size: number;}): void
632
+ /** 行置顶 */
633
+ (e: 'row-pin-to-top', param: { row: any, rawIndex: number; page: number; size: number;}): void
634
+ /** 行编辑保存 */
635
+ (e: 'row-edit-save', param: { page: number; size: number; row: any; changedData: Record<string, any>; }): void
636
+ /** 行编辑取消 */
637
+ (e: 'row-edit-cancel', param: { row: any; page: number; size: number;}): void
638
+ /** 页码改变 */
639
+ (e: 'page-change', param: { page: number, size: number }): void
640
+ /** 查询 */
641
+ (e: 'search', param: Record<string, any>): void
642
+ /** 排序 */
643
+ (e: 'sort-change', param: { order: 'descending' | 'ascending' | null, prop: string }): void
644
+ }
645
+
646
+ const props = withDefaults(defineProps<IProps>(), {
647
+ dataList: () => [],
648
+ columnConfig: () => [],
649
+ hasExpandRow: false,
650
+ hasIndexColumn: false,
651
+ hasSelectionColumn: false,
652
+ rowDragAble: false,
653
+ total: 0,
654
+ defaultOperations: () => [],
655
+ colorList: () => [],
656
+ leftFixedCount: 1,
657
+ dragSemiRange: 15,
658
+ loading: false,
659
+ hideViewSettingBtn: false,
660
+ settingStorgeKey: '',
661
+ localSort: false,
662
+ localFilter: false,
663
+ currentPage: 1,
664
+ });
665
+
666
+ // 同defineProps一样,不支持泛型参数从外部导入
667
+ const emit = defineEmits<IEmits>();
668
+
669
+ const showingColumns = ref<string[]>([]); // 表格中实际展示的列
670
+
671
+ const actualColumns = computed(() => {
672
+ const res: IColumnConfig[] = [];
673
+ let cnt = leftFixedColumnCount.value;
674
+
675
+ // 列排序和列过滤
676
+ for (const prop of showingColumns.value) {
677
+ const rawItem = props.columnConfig.find(c => c.prop === prop) ?? {} as IColumnConfig;
678
+ const item: IColumnConfig = {
679
+ ...rawItem,
680
+ isColumnSortable: rawItem.sortable,
681
+ sortable: inSorting.value ? rawItem.sortable : false
682
+ };
683
+ if (cnt > 0) {
684
+ item.fixed = 'left';
685
+ // eslint-disable-next-line no-plusplus
686
+ cnt--;
687
+ } else {
688
+ item.fixed = undefined;
689
+ }
690
+ res.push(item);
691
+ }
692
+
693
+ // 使用默认操作项,添加默认操作列。该列在编辑模式下隐藏
694
+ if (props.defaultOperations && props.defaultOperations.length > 0) {
695
+ res.push({
696
+ label: '操作',
697
+ prop: '$$operation',
698
+ 'min-width': '100px',
699
+ fixed: 'right'
700
+ });
701
+ }
702
+
703
+ return res;
704
+ });
705
+
706
+ const sortFilterPopoverRef = ref<any>(null);
707
+ const tableDomRef = ref<any>(null);
708
+ const currScope = ref<any>(null);
709
+
710
+ /************ 分页相关 *************/
711
+ const beforePageChange = () => {
712
+ searchValue.value = {};
713
+ };
714
+ const {
715
+ pageSize,
716
+ handleCurrPageChange,
717
+ handlePageSizeChange,
718
+ } = usePagination({
719
+ emit,
720
+ beforePageChange
721
+ });
722
+
723
+ /************ 表格单元格hover事件相关 ************ */
724
+ const {
725
+ hoveringCellInfo,
726
+ setCellClassName,
727
+ debouncedHoverHandler
728
+ } = useCellHover(tableDomRef);
729
+
730
+ /************ 行背景色设置相关 ************ */
731
+ const {
732
+ setRowStyle,
733
+ } = useRowBgColor({
734
+ colorList: props.colorList,
735
+ emit
736
+ });
737
+
738
+ /************ 默认的操作相关 ************ */
739
+ const {
740
+ operationPopoverRef,
741
+ editingRowData,
742
+ editingRowIndex,
743
+ handleDelete,
744
+ handleEdit,
745
+ handleEditSave,
746
+ handleEditCancel,
747
+ handleRowPinToTop,
748
+ closeAllExpandedRows
749
+ } = useDefaultOperation({
750
+ emit,
751
+ tableDomRef,
752
+ pageSize,
753
+ props,
754
+ hasExpandRow: props.hasExpandRow
755
+ });
756
+
757
+ /************ 显示设置相关 ************ */
758
+ const {
759
+ viewSettingDragSortOptions,
760
+ columnsToBeShown,
761
+ viewSettingVisible,
762
+ leftFixedColumnCount,
763
+ tempLeftFixedColumnCount,
764
+ handleInputTempLeftFixedColumnCount,
765
+ handleViewSettingShow,
766
+ handleViewSettingClose,
767
+ handleViewSettingConfirm
768
+ } = useViewSetting({
769
+ tableDomRef,
770
+ showingColumns,
771
+ actualColumns,
772
+ props
773
+ });
774
+
775
+ /************ 列头部操作相关 ************ */
776
+ const {
777
+ setSort,
778
+ clearSort,
779
+ setSearchParams,
780
+ isColumnHeadActive,
781
+ handleSort,
782
+ handleHeaderPopoverShow,
783
+ handleHeaderOperationConfirm,
784
+ handleHeaderOperationReset,
785
+ summaryList,
786
+ tableSummaryMethod,
787
+ filteredValue,
788
+ showColumnHeadSortIcon,
789
+ tempSortingColumn,
790
+ tempSearchValue,
791
+ tempFilteredValue,
792
+ tempSummaryList,
793
+ tempSortType,
794
+ sortingColumn,
795
+ isColumnFiltering,
796
+ searchValue,
797
+ inSorting,
798
+ } = useColumnHeaderOperation({
799
+ tableDomRef,
800
+ sortFilterPopoverRef,
801
+ props,
802
+ emit,
803
+ showingColumns,
804
+ });
805
+
806
+ /************ 表格行拖拽和显示设置列拖拽 ************ */
807
+ const beforeDragStart = () => {
808
+ // 如果有列存在排序,不允许拖拽
809
+ if (sortingColumn.value) {
810
+ Message.warning('已有列正在排序,不允许拖拽。');
811
+ return false;
812
+ }
813
+
814
+ // 如果有列存在筛选,不允许拖拽
815
+ if (isColumnFiltering.value) {
816
+ Message.warning('已有列正在筛选,不允许拖拽。');
817
+ return false;
818
+ }
819
+
820
+ editingRowIndex.value = -1; // 关闭编辑状态
821
+
822
+ return true;
823
+ };
824
+
825
+ useDragSort({
826
+ emit,
827
+ props,
828
+ viewSettingDragSortOptions,
829
+ beforeDragStart,
830
+ pageSize,
831
+ currScope,
832
+ tableDomRef,
833
+ });
834
+
835
+ const doTableLayout = async () => {
836
+ await nextTick();
837
+ tableDomRef.value?.doLayout();
838
+ };
839
+
840
+ // 过滤出自定义属性,将其它属性全部透传给 el-table-column
841
+ const getColumnBindProps = (column: IColumnConfig) => {
842
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
843
+ const { editAble, editType, slotName, inputType, options, filters, ...rest } = column;
844
+ return rest;
845
+ };
846
+
847
+ const setRowClassName = (scope) => {
848
+ return `
849
+ custom-row-classname
850
+ custom-row-classname-${scope.rowIndex}
851
+ ${scope.row.isPinned ? 'custom-row-classname-pinned' : ''}
852
+ `;
853
+ };
854
+
855
+ const handleSelectionChange = (e) => {
856
+ emit('selection-change', e);
857
+ };
858
+
859
+ const handleColumnClose = (item) => {
860
+ if (item.isAlwaysShow) return;
861
+ columnsToBeShown.value = columnsToBeShown.value.filter(c => c !== item.prop);
862
+ };
863
+
864
+ defineExpose({
865
+ closeAllExpandedRows,
866
+ openViewSetting: handleViewSettingShow,
867
+ elTableRef: tableDomRef,
868
+ setSort,
869
+ clearSort,
870
+ setSearchParams,
871
+ });
872
+
873
+ // loading 结束和页码变化时滚动到顶部
874
+ watch([
875
+ () => props.loading,
876
+ () => props.currentPage,
877
+ ], ([loading]) => {
878
+ if (loading) return;
879
+ tableDomRef.value.$el.querySelector('.el-table__body-wrapper').scrollTop = 0;
880
+ });
881
+
882
+ </script>
883
+
884
+ <style lang="less">
885
+ @import './index.less';
915
886
  </style>