@lx-frontend/wrap-element-ui 1.0.1-beta.3 → 1.0.1-beta.4

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,915 @@
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
+ <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';
915
915
  </style>