stk-table-vue 0.6.12 → 0.6.14

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,1546 +1,1550 @@
1
- <!-- eslint-disable vue/attribute-hyphenation -->
2
- <template>
3
- <div
4
- ref="tableContainerRef"
5
- class="stk-table"
6
- :class="{
7
- virtual,
8
- 'virtual-x': virtualX,
9
- 'vt-on': virtual_on,
10
- light: theme === 'light',
11
- dark: theme === 'dark',
12
- headless,
13
- 'is-col-resizing': isColResizing,
14
- 'col-resizable': props.colResizable,
15
- border: props.bordered,
16
- 'border-h': props.bordered === 'h',
17
- 'border-v': props.bordered === 'v',
18
- 'border-body-v': props.bordered === 'body-v',
19
- stripe: props.stripe,
20
- 'cell-hover': props.cellHover,
21
- 'cell-active': props.cellActive,
22
- 'row-hover': props.rowHover,
23
- 'row-active': props.rowActive,
24
- 'text-overflow': props.showOverflow,
25
- 'header-text-overflow': props.showHeaderOverflow,
26
- 'fixed-relative-mode': isRelativeMode,
27
- 'auto-row-height': props.autoRowHeight,
28
- 'scroll-row-by-row': props.scrollRowByRow,
29
- }"
30
- :style="{
31
- '--row-height': props.autoRowHeight ? void 0 : virtualScroll.rowHeight + 'px',
32
- '--header-row-height': props.headerRowHeight + 'px',
33
- '--highlight-duration': props.highlightConfig.duration && props.highlightConfig.duration + 's',
34
- '--highlight-timing-function': highlightSteps ? `steps(${highlightSteps})` : '',
35
- }"
36
- @scroll="onTableScroll"
37
- @wheel="onTableWheel"
38
- >
39
- <!-- 这个元素用于整数行虚拟滚动时,撑开父容器的高度) -->
40
- <div
41
- v-if="props.scrollRowByRow && virtual"
42
- class="row-by-row-table-height"
43
- :style="{ height: dataSourceCopy.length * virtualScroll.rowHeight + 'px' }"
44
- ></div>
45
-
46
- <div v-if="colResizable" ref="colResizeIndicatorRef" class="column-resize-indicator"></div>
47
- <!-- 表格主体 -->
48
- <table
49
- class="stk-table-main"
50
- :style="{ width, minWidth, maxWidth }"
51
- :class="{
52
- 'fixed-mode': props.fixedMode,
53
- }"
54
- >
55
- <!-- transform: virtualX_on ? `translateX(${virtualScrollX.offsetLeft}px)` : null, 用transform控制虚拟滚动左边距,sticky会有问题 -->
56
- <thead v-if="!headless" ref="theadRef">
57
- <tr v-for="(row, rowIndex) in tableHeaders" :key="rowIndex" @contextmenu="e => onHeaderMenu(e)">
58
- <!-- 这个th用于横向虚拟滚动表格左边距,width、maxWidth 用于兼容低版本浏览器 -->
59
- <th
60
- v-if="virtualX_on"
61
- class="vt-x-left"
62
- :style="`min-width:${virtualScrollX.offsetLeft}px;width:${virtualScrollX.offsetLeft}px`"
63
- ></th>
64
- <!-- v for中最后一行才用 切割。TODO:不支持多级表头虚拟横向滚动 -->
65
- <th
66
- v-for="(col, colIndex) in virtualX_on && rowIndex === tableHeaders.length - 1 ? virtualX_columnPart : row"
67
- :key="colKeyGen(col)"
68
- :data-col-key="colKeyGen(col)"
69
- :draggable="isHeaderDraggable(col) ? 'true' : 'false'"
70
- :rowspan="virtualX_on ? 1 : col.rowSpan"
71
- :colspan="col.colSpan"
72
- :style="cellStyleMap[TagType.TH].get(colKeyGen(col))"
73
- :title="getHeaderTitle(col)"
74
- :class="[
75
- col.sorter ? 'sortable' : '',
76
- colKeyGen(col) === sortCol && sortOrderIndex !== 0 && 'sorter-' + sortSwitchOrder[sortOrderIndex],
77
- col.headerClassName,
78
- fixedColClassMap.get(colKeyGen(col)),
79
- ]"
80
- @click="
81
- e => {
82
- onColumnSort(col);
83
- onHeaderCellClick(e, col);
84
- }
85
- "
86
- @dragstart="onThDragStart"
87
- @drop="onThDrop"
88
- @dragover="onThDragOver"
89
- >
90
- <!-- 列宽拖动handler -->
91
- <div
92
- v-if="colResizeOn(col) && colIndex > 0"
93
- class="table-header-resizer left"
94
- @mousedown="e => onThResizeMouseDown(e, col, true)"
95
- ></div>
96
- <div class="table-header-cell-wrapper" :style="{ '--row-span': virtualX_on ? 1 : col.rowSpan }">
97
- <component :is="col.customHeaderCell" v-if="col.customHeaderCell" :col="col" :colIndex="colIndex" :rowIndex="rowIndex" />
98
- <template v-else>
99
- <slot name="tableHeader" :col="col">
100
- <span class="table-header-title">{{ col.title }}</span>
101
- </slot>
102
- </template>
103
- <SortIcon v-if="col.sorter" class="table-header-sorter" />
104
- </div>
105
- <!-- 列宽拖动handler -->
106
- <div v-if="colResizeOn(col)" class="table-header-resizer right" @mousedown="e => onThResizeMouseDown(e, col)"></div>
107
- </th>
108
- <!-- 这个th用于横向虚拟滚动表格右边距 width, min-width 用于兼容低版本浏览器-->
109
- <th v-if="virtualX_on" class="vt-x-right" :style="`min-width:${virtualX_offsetRight}px;width:${virtualX_offsetRight}px`"></th>
110
- </tr>
111
- </thead>
112
-
113
- <!-- 用于虚拟滚动表格内容定位 @deprecated 有兼容问题-->
114
- <!-- <tbody v-if="virtual_on" :style="{ height: `${virtualScroll.offsetTop}px` }"></tbody> -->
115
- <!-- <tbody :style="{ transform: `translateY(${virtualScroll.offsetTop}px)` }"> -->
116
- <tbody class="stk-tbody-main" @dragover="onTrDragOver" @dragenter="onTrDragEnter" @dragend="onTrDragEnd">
117
- <tr v-if="virtual_on && !props.scrollRowByRow" :style="`height:${virtualScroll.offsetTop}px`" class="padding-top-tr">
118
- <!--这个td用于配合虚拟滚动的th对应,防止列错位-->
119
- <td v-if="virtualX_on && fixedMode && headless" class="vt-x-left"></td>
120
- <template v-if="fixedMode && headless">
121
- <td v-for="col in virtualX_columnPart" :key="colKeyGen(col)" :style="cellStyleMap[TagType.TD].get(colKeyGen(col))"></td>
122
- </template>
123
- </tr>
124
- <tr
125
- v-for="(row, rowIndex) in virtual_dataSourcePart"
126
- :id="stkTableId + '-' + (rowKey ? rowKeyGen(row) : (virtual_on ? virtualScroll.startIndex : 0) + rowIndex)"
127
- ref="trRef"
128
- :key="rowKey ? rowKeyGen(row) : (virtual_on ? virtualScroll.startIndex : 0) + rowIndex"
129
- :data-row-key="rowKey ? rowKeyGen(row) : (virtual_on ? virtualScroll.startIndex : 0) + rowIndex"
130
- :class="{
131
- active: rowKey ? rowKeyGen(row) === currentRowKey : row === currentRow,
132
- hover: props.showTrHoverClass && (rowKey ? rowKeyGen(row) === currentHoverRowKey : row === currentHoverRowKey),
133
- [rowClassName(row, (virtual_on ? virtualScroll.startIndex : 0) + rowIndex)]: true,
134
- expanded: row?.__EXPANDED__,
135
- 'expanded-row': row && row.__EXPANDED_ROW__,
136
- }"
137
- :style="{
138
- '--row-height':
139
- row && row.__EXPANDED_ROW__ && props.virtual && props.expandConfig?.height && props.expandConfig?.height + 'px',
140
- }"
141
- @click="e => onRowClick(e, row)"
142
- @dblclick="e => onRowDblclick(e, row)"
143
- @contextmenu="e => onRowMenu(e, row)"
144
- @mouseover="e => onTrMouseOver(e, row)"
145
- @drop="e => onTrDrop(e, (virtual_on ? virtualScroll.startIndex : 0) + rowIndex)"
146
- >
147
- <!--这个td用于配合虚拟滚动的th对应,防止列错位-->
148
- <td v-if="virtualX_on" class="vt-x-left"></td>
149
- <td v-if="row && row.__EXPANDED_ROW__" :colspan="virtualX_columnPart.length">
150
- <!-- TODO: support wheel -->
151
- <div class="table-cell-wrapper">
152
- <slot name="expand" :row="row.__EXPANDED_ROW__" :col="row.__EXPANDED_COL__">
153
- {{ row.__EXPANDED_ROW__?.[row.__EXPANDED_COL__.dataIndex] ?? '' }}
154
- </slot>
155
- </div>
156
- </td>
157
- <template v-else>
158
- <td
159
- v-for="(col, colIndex) in virtualX_columnPart"
160
- :key="colKeyGen(col)"
161
- :data-cell-key="cellKeyGen(row, col)"
162
- :style="cellStyleMap[TagType.TD].get(colKeyGen(col))"
163
- :class="[
164
- col.className,
165
- fixedColClassMap.get(colKeyGen(col)),
166
- {
167
- 'seq-column': col.type === 'seq',
168
- active: currentSelectedCellKey === cellKeyGen(row, col),
169
- 'expand-cell': col.type === 'expand',
170
- expanded: col.type === 'expand' && colKeyGen(row?.__EXPANDED__) === colKeyGen(col),
171
- 'drag-row-cell': col.type === 'dragRow',
172
- },
173
- ]"
174
- @click="e => onCellClick(e, row, col)"
175
- @mousedown="e => onCellMouseDown(e, row, col)"
176
- @mouseenter="e => onCellMouseEnter(e, row, col)"
177
- @mouseleave="e => onCellMouseLeave(e, row, col)"
178
- @mouseover="e => onCellMouseOver(e, row, col)"
179
- >
180
- <component
181
- :is="col.customCell"
182
- v-if="col.customCell"
183
- class="table-cell-wrapper"
184
- :col="col"
185
- :row="row"
186
- :rowIndex="(virtual_on ? virtualScroll.startIndex : 0) + rowIndex"
187
- :colIndex="colIndex"
188
- :cellValue="row?.[col.dataIndex]"
189
- :expanded="row?.__EXPANDED__ || null"
190
- />
191
- <div
192
- v-else
193
- class="table-cell-wrapper"
194
- :class="{ 'expanded-cell-wrapper': col.type === 'expand' }"
195
- :title="col.type !== 'seq' ? row?.[col.dataIndex] : ''"
196
- >
197
- <template v-if="col.type === 'seq'">
198
- {{ (props.seqConfig.startIndex || 0) + (virtual_on ? virtualScroll.startIndex : 0) + rowIndex + 1 }}
199
- </template>
200
- <span v-else-if="col.type === 'expand'">
201
- {{ row?.[col.dataIndex] ?? '' }}
202
- </span>
203
- <template v-else-if="col.type === 'dragRow'">
204
- <DragHandle @dragstart="e => onTrDragStart(e, (virtual_on ? virtualScroll.startIndex : 0) + rowIndex)" />
205
- <span>
206
- {{ row?.[col.dataIndex] ?? '' }}
207
- </span>
208
- </template>
209
- <template v-else>
210
- {{ row?.[col.dataIndex] ?? getEmptyCellText(col, row) }}
211
- </template>
212
- </div>
213
- </td>
214
- </template>
215
- </tr>
216
- <tr v-if="virtual_on && !props.scrollRowByRow" :style="`height: ${virtual_offsetBottom}px`"></tr>
217
- </tbody>
218
- </table>
219
- <div v-if="(!dataSourceCopy || !dataSourceCopy.length) && showNoData" class="stk-table-no-data" :class="{ 'no-data-full': noDataFull }">
220
- <slot name="empty">暂无数据</slot>
221
- </div>
222
- <slot name="customBottom"></slot>
223
- </div>
224
- </template>
225
-
226
- <script setup lang="ts">
227
- /**
228
- * @author japlus
229
- */
230
- import { CSSProperties, computed, nextTick, onMounted, ref, shallowRef, toRaw, watch } from 'vue';
231
- import DragHandle from './components/DragHandle.vue';
232
- import SortIcon from './components/SortIcon.vue';
233
- import { CELL_KEY_SEPARATE, DEFAULT_ROW_HEIGHT, DEFAULT_SMOOTH_SCROLL, EXPANDED_ROW_KEY_PREFIX, IS_LEGACY_MODE } from './const';
234
- import {
235
- AutoRowHeightConfig,
236
- DragRowConfig,
237
- ExpandConfig,
238
- ExpandedRow,
239
- HeaderDragConfig,
240
- HighlightConfig,
241
- Order,
242
- PrivateRowDT,
243
- PrivateStkTableColumn,
244
- SeqConfig,
245
- SortConfig,
246
- SortOption,
247
- StkTableColumn,
248
- TagType,
249
- UniqKeyProp,
250
- ColResizableConfig,
251
- } from './types/index';
252
- import { useAutoResize } from './useAutoResize';
253
- import { useColResize } from './useColResize';
254
- import { useFixedCol } from './useFixedCol';
255
- import { useFixedStyle } from './useFixedStyle';
256
- import { useGetFixedColPosition } from './useGetFixedColPosition';
257
- import { useHighlight } from './useHighlight';
258
- import { useKeyboardArrowScroll } from './useKeyboardArrowScroll';
259
- import { useThDrag } from './useThDrag';
260
- import { useTrDrag } from './useTrDrag';
261
- import { useVirtualScroll } from './useVirtualScroll';
262
- import { createStkTableId, getCalculatedColWidth, getColWidth } from './utils/constRefUtils';
263
- import { howDeepTheHeader, tableSort, transformWidthToStr } from './utils/index';
264
-
265
- /** Generic stands for DataType */
266
- type DT = any & PrivateRowDT;
267
-
268
- /** generate table instance id */
269
- const stkTableId = createStkTableId();
270
-
271
- /**
272
- * props cannot be placed in a separate file. It will cause compilation errors with vue 2.7 compiler.
273
- */
274
- const props = withDefaults(
275
- defineProps<{
276
- width?: string;
277
- /** 最小表格宽度 */
278
- minWidth?: string;
279
- /** 表格最大宽度*/
280
- maxWidth?: string;
281
- /** 斑马线条纹 */
282
- stripe?: boolean;
283
- /** 是否使用 table-layout:fixed(低版本浏览器需要设置table) */
284
- fixedMode?: boolean;
285
- /** 是否隐藏表头 */
286
- headless?: boolean;
287
- /** 主题,亮、暗 */
288
- theme?: 'light' | 'dark';
289
- /**
290
- * 行高
291
- * - `props.autoRowHeight` 为 `true` 时,将表示为期望行高,用于计算。不再影响实际行高。
292
- */
293
- rowHeight?: number;
294
- /**
295
- * 是否可变行高
296
- * - 设置为 `true` 时, `props.rowHeight` 将表示为期望行高,用于计算。不再影响实际行高。
297
- */
298
- autoRowHeight?: boolean | AutoRowHeightConfig<DT>;
299
- /** 是否高亮鼠标悬浮的行 */
300
- rowHover?: boolean;
301
- /** 是否高亮选中的行 */
302
- rowActive?: boolean;
303
- /** 当前行再次点击否可以取消 (rowActive=true)*/
304
- rowCurrentRevokable?: boolean;
305
- /** 表头行高。default = rowHeight */
306
- headerRowHeight?: number | null;
307
- /** 虚拟滚动 */
308
- virtual?: boolean;
309
- /** x轴虚拟滚动(必须设置列宽)*/
310
- virtualX?: boolean;
311
- /** 表格列配置 */
312
- columns?: StkTableColumn<DT>[];
313
- /** 表格数据源 */
314
- dataSource?: DT[];
315
- /** 行唯一键 (行唯一值不能为undefined) */
316
- rowKey?: UniqKeyProp;
317
- /** 列唯一键 */
318
- colKey?: UniqKeyProp;
319
- /** 空值展示文字 */
320
- emptyCellText?: string | ((option: { row: DT; col: StkTableColumn<DT> }) => string);
321
- /** 暂无数据兜底高度是否撑满 */
322
- noDataFull?: boolean;
323
- /** 是否展示暂无数据 */
324
- showNoData?: boolean;
325
- /** 是否服务端排序,true则不排序数据 */
326
- sortRemote?: boolean;
327
- /** 表头是否溢出展示... */
328
- showHeaderOverflow?: boolean;
329
- /** 表体溢出是否展示... */
330
- showOverflow?: boolean;
331
- /** 是否增加行hover class $*$ rename*/
332
- showTrHoverClass?: boolean;
333
- /** 是否高亮鼠标悬浮的单元格 */
334
- cellHover?: boolean;
335
- /** 是否高亮选中的单元格 */
336
- cellActive?: boolean;
337
- /** 单元格再次点击否可以取消选中 (cellActive=true)*/
338
- selectedCellRevokable?: boolean;
339
- /** 表头是否可拖动。支持回调函数。 */
340
- headerDrag?: HeaderDragConfig;
341
- /**
342
- * 给行附加className<br>
343
- * FIXME: 是否需要优化,因为不传此prop会使表格行一直执行空函数,是否有影响
344
- */
345
- rowClassName?: (row: DT, i: number) => string;
346
- /**
347
- * 列宽是否可拖动(需要设置v-model:columns)<br>
348
- * **不要设置**列minWidth,**必须**设置width<br>
349
- * 列宽拖动时,每一列都必须要有width,且minWidth/maxWidth不生效。table width会变为"fit-content"。
350
- * - 会自动更新props.columns中的with属性
351
- */
352
- colResizable?: boolean | ColResizableConfig<DT>;
353
- /** 可拖动至最小的列宽 */
354
- colMinWidth?: number;
355
- /**
356
- * 单元格分割线。
357
- * 默认横竖都有
358
- * "h" - 仅展示横线
359
- * "v" - 仅展示竖线
360
- * "body-v" - 仅表体展示竖线
361
- */
362
- bordered?: boolean | 'h' | 'v' | 'body-v';
363
- /**
364
- * 自动重新计算虚拟滚动高度宽度。默认true
365
- * [非响应式]
366
- * 传入方法表示resize后的回调
367
- */
368
- autoResize?: boolean | (() => void);
369
- /** 是否展示固定列阴影。为节省性能,默认false。 */
370
- fixedColShadow?: boolean;
371
- /** 优化vue2 滚动 */
372
- optimizeVue2Scroll?: boolean;
373
- /** 排序配置 */
374
- sortConfig?: SortConfig<DT>;
375
- /** 隐藏头部title。可传入colKey数组 */
376
- hideHeaderTitle?: boolean | string[];
377
- /** 高亮配置 */
378
- highlightConfig?: HighlightConfig;
379
- /** 序号列配置 */
380
- seqConfig?: SeqConfig;
381
- /** 展开行配置 */
382
- expandConfig?: ExpandConfig;
383
- /** 行拖动配置 */
384
- dragRowConfig?: DragRowConfig;
385
- /**
386
- * 固定头,固定列实现方式。(非响应式)
387
- *
388
- * relative:固定列只会放在props.columns的两侧。
389
- * - 如果列宽会变动则谨慎使用。
390
- * - 多级表头固定列慎用
391
- *
392
- * 低版本浏览器强制为'relative',
393
- */
394
- cellFixedMode?: 'sticky' | 'relative';
395
- /**
396
- * 是否平滑滚动。default: chrome < 85 || chrome > 120 ? true : false
397
- * - false: 使用 onwheel 滚动。为了防止滚动过快导致白屏。
398
- * - true: 不使用 onwheel 滚动。鼠标滚轮滚动时更加平滑。滚动过快时会白屏。
399
- */
400
- smoothScroll?: boolean;
401
- /** 按整数行纵向滚动 */
402
- scrollRowByRow?: boolean;
403
- }>(),
404
- {
405
- width: '',
406
- fixedMode: false,
407
- stripe: false,
408
- minWidth: '',
409
- maxWidth: '',
410
- headless: false,
411
- theme: 'light',
412
- rowHeight: DEFAULT_ROW_HEIGHT,
413
- autoRowHeight: false,
414
- rowHover: true,
415
- rowActive: true,
416
- rowCurrentRevokable: true,
417
- headerRowHeight: DEFAULT_ROW_HEIGHT,
418
- virtual: false,
419
- virtualX: false,
420
- columns: () => [],
421
- dataSource: () => [],
422
- rowKey: '',
423
- colKey: 'dataIndex',
424
- emptyCellText: '--',
425
- noDataFull: false,
426
- showNoData: true,
427
- sortRemote: false,
428
- showHeaderOverflow: false,
429
- showOverflow: false,
430
- showTrHoverClass: false,
431
- cellHover: false,
432
- cellActive: false,
433
- selectedCellRevokable: true,
434
- headerDrag: false,
435
- rowClassName: () => '',
436
- colResizable: false,
437
- colMinWidth: 10,
438
- bordered: true,
439
- autoResize: true,
440
- fixedColShadow: false,
441
- optimizeVue2Scroll: false,
442
- sortConfig: () => ({
443
- emptyToBottom: false,
444
- stringLocaleCompare: false,
445
- }),
446
- hideHeaderTitle: false,
447
- highlightConfig: () => ({}),
448
- seqConfig: () => ({}),
449
- expandConfig: () => ({}),
450
- dragRowConfig: () => ({}),
451
- cellFixedMode: 'sticky',
452
- smoothScroll: DEFAULT_SMOOTH_SCROLL,
453
- scrollRowByRow: false,
454
- },
455
- );
456
-
457
- const emits = defineEmits<{
458
- /**
459
- * 排序变更触发。defaultSort.dataIndex 找不到时,col 将返回null。
460
- *
461
- * ```(col: StkTableColumn<DT> | null, order: Order, data: DT[], sortConfig: SortConfig<DT>)```
462
- */
463
- (e: 'sort-change', col: StkTableColumn<DT> | null, order: Order, data: DT[], sortConfig: SortConfig<DT>): void;
464
- /**
465
- * 一行点击事件
466
- *
467
- * ```(ev: MouseEvent, row: DT)```
468
- */
469
- (e: 'row-click', ev: MouseEvent, row: DT): void;
470
- /**
471
- * 选中一行触发。ev返回null表示不是点击事件触发的
472
- *
473
- * ```(ev: MouseEvent | null, row: DT | undefined, data: { select: boolean })```
474
- */
475
- (e: 'current-change', ev: MouseEvent | null, row: DT | undefined, data: { select: boolean }): void;
476
- /**
477
- * 选中单元格触发。ev返回null表示不是点击事件触发的
478
- *
479
- * ```(ev: MouseEvent | null, data: { select: boolean; row: DT | undefined; col: StkTableColumn<DT> | null })```
480
- */
481
- (e: 'cell-selected', ev: MouseEvent | null, data: { select: boolean; row: DT | undefined; col: StkTableColumn<DT> | undefined }): void;
482
- /**
483
- * 行双击事件
484
- *
485
- * ```(ev: MouseEvent, row: DT)```
486
- */
487
- (e: 'row-dblclick', ev: MouseEvent, row: DT): void;
488
- /**
489
- * 表头右键事件
490
- *
491
- * ```(ev: MouseEvent)```
492
- */
493
- (e: 'header-row-menu', ev: MouseEvent): void;
494
- /**
495
- * 表体行右键点击事件
496
- *
497
- * ```(ev: MouseEvent, row: DT)```
498
- */
499
- (e: 'row-menu', ev: MouseEvent, row: DT): void;
500
- /**
501
- * 单元格点击事件
502
- *
503
- * ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>)```
504
- */
505
- (e: 'cell-click', ev: MouseEvent, row: DT, col: StkTableColumn<DT>): void;
506
- /**
507
- * 单元格鼠标进入事件
508
- *
509
- * ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>)```
510
- */
511
- (e: 'cell-mouseenter', ev: MouseEvent, row: DT, col: StkTableColumn<DT>): void;
512
- /**
513
- * 单元格鼠标移出事件
514
- *
515
- * ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>)```
516
- */
517
- (e: 'cell-mouseleave', ev: MouseEvent, row: DT, col: StkTableColumn<DT>): void;
518
- /**
519
- * 单元格悬浮事件
520
- *
521
- * ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>)```
522
- */
523
- (e: 'cell-mouseover', ev: MouseEvent, row: DT, col: StkTableColumn<DT>): void;
524
- /**
525
- * 单元格鼠标按下事件
526
- *
527
- * ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>)```
528
- */
529
- (e: 'cell-mousedown', ev: MouseEvent, row: DT, col: StkTableColumn<DT>): void;
530
- /**
531
- * 表头单元格点击事件
532
- *
533
- * ```(ev: MouseEvent, col: StkTableColumn<DT>)```
534
- */
535
- (e: 'header-cell-click', ev: MouseEvent, col: StkTableColumn<DT>): void;
536
- /**
537
- * 表格滚动事件
538
- *
539
- * ```(ev: Event, data: { startIndex: number; endIndex: number })```
540
- */
541
- (e: 'scroll', ev: Event, data: { startIndex: number; endIndex: number }): void;
542
- /**
543
- * 表格横向滚动事件
544
- *
545
- * ```(ev: Event)```
546
- */
547
- (e: 'scroll-x', ev: Event): void;
548
- /**
549
- * 表头列拖动事件
550
- *
551
- * ```(dragStartKey: string, targetColKey: string)```
552
- */
553
- (e: 'col-order-change', dragStartKey: string, targetColKey: string): void;
554
- /**
555
- * 表头列拖动开始
556
- *
557
- * ```(dragStartKey: string)```
558
- */
559
- (e: 'th-drag-start', dragStartKey: string): void;
560
- /**
561
- * 表头列拖动drop
562
- *
563
- * ```(targetColKey: string)```
564
- */
565
- (e: 'th-drop', targetColKey: string): void;
566
- /**
567
- * 行拖动事件
568
- *
569
- * ```(dragStartKey: string, targetRowKey: string)```
570
- */
571
- (e: 'row-order-change', dragStartKey: string, targetRowKey: string): void;
572
- /**
573
- * 列宽变动时触发
574
- *
575
- * ```(col: StkTableColumn<DT>)```
576
- */
577
- (e: 'col-resize', col: StkTableColumn<DT>): void;
578
- /**
579
- * 展开行触发
580
- *
581
- * ```( data: { expanded: boolean; row: DT; col: StkTableColumn<DT> })```
582
- */
583
- (e: 'toggle-row-expand', data: { expanded: boolean; row: DT; col: StkTableColumn<DT> | null }): void;
584
- /**
585
- * v-model:columns col resize 时更新宽度
586
- */
587
- (e: 'update:columns', cols: StkTableColumn<DT>[]): void;
588
- }>();
589
-
590
- // 仅支持vue3.3+
591
- // const slots = defineSlots<{
592
- // /** 表头插槽 */
593
- // tableHeader(props: { col: StkTableColumn<DT> }): void;
594
- // /** 空状态插槽 */
595
- // empty(): void;
596
- // }>();
597
-
598
- const tableContainerRef = ref<HTMLDivElement>();
599
- const theadRef = ref<HTMLElement>();
600
- const colResizeIndicatorRef = ref<HTMLDivElement>();
601
- const trRef = ref<HTMLTableRowElement[]>();
602
-
603
- /** 是否使用 relative 固定头和列 */
604
- const isRelativeMode = ref(IS_LEGACY_MODE ? true : props.cellFixedMode === 'relative');
605
-
606
- /**
607
- * 当前选中的一行
608
- * - shallowRef: 使 currentRow.value === row 地址相同。防止rowKeyGen 的WeakMap key不一致。
609
- */
610
- const currentRow = shallowRef<DT>();
611
- /**
612
- * 保存当前选中行的key<br>
613
- * 原因:vue3 不用ref包dataSource时,row为原始对象,与currentItem(Ref)相比会不相等。
614
- */
615
- const currentRowKey = ref<any>(void 0);
616
- /** 当前选中的单元格key */
617
- const currentSelectedCellKey = ref<any>(void 0);
618
- /** 当前hover行 */
619
- let currentHoverRow: DT | null = null;
620
- /** 当前hover的行的key */
621
- const currentHoverRowKey = ref(null);
622
- /** 当前hover的列的key */
623
- // const currentColHoverKey = ref(null);
624
-
625
- /** sort colKey*/
626
- let sortCol = ref<keyof DT>();
627
- let sortOrderIndex = ref(0);
628
-
629
- /** 排序切换顺序 */
630
- const sortSwitchOrder: Order[] = [null, 'desc', 'asc'];
631
-
632
- /**
633
- * 表头.内容是 props.columns 的引用集合
634
- * @eg
635
- * ```js
636
- * [
637
- * [{dataInex:'id',...}], // 第0行列配置
638
- * [], // 第一行列配置
639
- * //...
640
- * ]
641
- * ```
642
- */
643
- const tableHeaders = shallowRef<StkTableColumn<DT>[][]>([]);
644
-
645
- /**
646
- * 用于计算多级表头的tableHeaders。模拟rowSpan 位置的辅助数组。用于计算固定列。
647
- * @eg
648
- * ```
649
- * | colspan3 |
650
- * | rowspan2 | colspan2 |
651
- * | rowspan2 | colspan1 | colspan1 |
652
- * ```
653
- * ---
654
- * expect arr:
655
- * ```
656
- * const arr = [
657
- * [col],
658
- * [col2, col3],
659
- * [col2, col4, col5],
660
- * ]
661
- * ```
662
- */
663
- const tableHeadersForCalc = shallowRef<PrivateStkTableColumn<DT>[][]>([]);
664
-
665
- /** 最后一行的tableHeaders.内容是 props.columns 的引用集合 */
666
- const tableHeaderLast = computed(() => tableHeadersForCalc.value.slice(-1)[0] || []);
667
-
668
- const dataSourceCopy = shallowRef<DT[]>(props.dataSource.slice());
669
-
670
- const rowKeyGenComputed = computed(() => {
671
- const { rowKey } = props;
672
- if (typeof rowKey === 'function') {
673
- return (row: DT) => (rowKey as (row: DT) => string)(row);
674
- } else {
675
- return (row: DT) => (row as any)[rowKey];
676
- }
677
- });
678
-
679
- const colKeyGen = computed<(col: StkTableColumn<DT>) => string>(() => {
680
- const { colKey } = props;
681
- if (typeof colKey === 'function') {
682
- return col => (colKey as (col: StkTableColumn<DT>) => string)(col);
683
- } else {
684
- return col => (col as any)[colKey];
685
- }
686
- });
687
-
688
- const getEmptyCellText = computed(() => {
689
- const { emptyCellText } = props;
690
- if (typeof emptyCellText === 'string') {
691
- return () => emptyCellText;
692
- } else {
693
- return (col: StkTableColumn<DT>, row: DT) => emptyCellText({ row, col });
694
- }
695
- });
696
-
697
- /** rowKey缓存 */
698
- const rowKeyGenStore = new WeakMap();
699
-
700
- const { onThDragStart, onThDragOver, onThDrop, isHeaderDraggable } = useThDrag({ props, emits, colKeyGen });
701
-
702
- const { onTrDragStart, onTrDrop, onTrDragOver, onTrDragEnd, onTrDragEnter } = useTrDrag({ props, emits, dataSourceCopy });
703
-
704
- const {
705
- virtualScroll,
706
- virtualScrollX,
707
- virtual_on,
708
- virtual_dataSourcePart,
709
- virtual_offsetBottom,
710
- virtualX_on,
711
- virtualX_columnPart,
712
- virtualX_offsetRight,
713
- initVirtualScroll,
714
- initVirtualScrollY,
715
- initVirtualScrollX,
716
- updateVirtualScrollY,
717
- updateVirtualScrollX,
718
- setAutoHeight,
719
- clearAllAutoHeight,
720
- } = useVirtualScroll({ tableContainerRef, trRef, props, dataSourceCopy, tableHeaderLast, tableHeaders, rowKeyGen });
721
-
722
- /** 获取固定列的位置 */
723
- const getFixedColPosition = useGetFixedColPosition({ colKeyGen, tableHeadersForCalc });
724
-
725
- const getFixedStyle = useFixedStyle<DT>({
726
- props,
727
- isRelativeMode,
728
- getFixedColPosition,
729
- virtualScroll,
730
- virtualScrollX,
731
- virtualX_on,
732
- virtualX_offsetRight,
733
- });
734
-
735
- /**
736
- * 高亮行,高亮单元格
737
- */
738
- const { highlightSteps, setHighlightDimCell, setHighlightDimRow } = useHighlight({ props, stkTableId, tableContainerRef });
739
-
740
- if (props.autoResize) {
741
- useAutoResize({ tableContainerRef, initVirtualScroll, props, debounceMs: 200 });
742
- }
743
-
744
- /** 键盘箭头滚动 */
745
- useKeyboardArrowScroll(tableContainerRef, {
746
- props,
747
- scrollTo,
748
- virtualScroll,
749
- virtualScrollX,
750
- tableHeaders,
751
- virtual_on,
752
- });
753
-
754
- /** 固定列处理 */
755
- const { fixedCols, fixedColClassMap, updateFixedShadow } = useFixedCol({
756
- props,
757
- colKeyGen,
758
- getFixedColPosition,
759
- tableContainerRef,
760
- tableHeaders,
761
- tableHeadersForCalc,
762
- });
763
-
764
- const { isColResizing, onThResizeMouseDown, colResizeOn } = useColResize({
765
- props,
766
- emits,
767
- colKeyGen,
768
- colResizeIndicatorRef,
769
- tableContainerRef,
770
- tableHeaderLast,
771
- fixedCols,
772
- });
773
-
774
- watch(
775
- () => props.columns,
776
- () => {
777
- dealColumns();
778
- // initVirtualScrollX 需要获取容器滚动宽度等。必须等渲染完成后再调用。因此使用nextTick。
779
- nextTick(() => {
780
- initVirtualScrollX();
781
- updateFixedShadow();
782
- });
783
- },
784
- );
785
- watch(
786
- () => props.virtual,
787
- () => {
788
- nextTick(() => {
789
- initVirtualScrollY();
790
- });
791
- },
792
- );
793
- watch(
794
- () => props.virtualX,
795
- () => {
796
- dealColumns();
797
- // initVirtualScrollX 需要获取容器滚动宽度等。必须等渲染完成后再调用。因此使用nextTick。
798
- nextTick(() => {
799
- initVirtualScrollX();
800
- updateFixedShadow();
801
- });
802
- },
803
- );
804
-
805
- watch(
806
- () => props.dataSource,
807
- val => {
808
- if (!val) {
809
- console.warn('invalid dataSource');
810
- return;
811
- }
812
- /** 是否需要更新ScrollY,这里由于watch newValue与oldValue 的长度一样,因此需要这样使用 */
813
- let needInitVirtualScrollY = false;
814
- if (dataSourceCopy.value.length !== val.length) {
815
- needInitVirtualScrollY = true;
816
- }
817
- dataSourceCopy.value = val.slice(); // 浅拷贝
818
- // 数据长度没变则不计算虚拟滚动
819
- if (needInitVirtualScrollY) {
820
- // 表格渲染后再执行。initVirtualScrollY 中有获取dom的操作。
821
- nextTick(() => initVirtualScrollY());
822
- }
823
- const sortColValue = sortCol.value;
824
- if (sortColValue) {
825
- // 排序
826
- const colKey = colKeyGen.value;
827
- const column = tableHeaderLast.value.find(it => colKey(it) === sortColValue);
828
- onColumnSort(column, false);
829
- }
830
- },
831
- );
832
-
833
- watch(
834
- () => props.fixedColShadow,
835
- () => updateFixedShadow(),
836
- );
837
-
838
- dealColumns();
839
-
840
- onMounted(() => {
841
- initVirtualScroll();
842
- updateFixedShadow();
843
- dealDefaultSorter();
844
- });
845
-
846
- /** 处理默认排序 */
847
- function dealDefaultSorter() {
848
- if (!props.sortConfig.defaultSort) return;
849
- const { key, dataIndex, order, silent } = { silent: false, ...props.sortConfig.defaultSort };
850
- setSorter((key || dataIndex) as string, order, { force: false, silent });
851
- }
852
-
853
- /**
854
- * 处理多级表头
855
- */
856
- function dealColumns() {
857
- // reset
858
- const tableHeadersTemp: StkTableColumn<DT>[][] = [];
859
- const tableHeadersForCalcTemp: StkTableColumn<DT>[][] = [];
860
- let copyColumn = props.columns; // do not deep clone
861
- // relative 模式下不支持sticky列。因此就放在左右两侧。
862
- if (isRelativeMode.value) {
863
- let leftCol: StkTableColumn<DT>[] = [];
864
- let centerCol: StkTableColumn<DT>[] = [];
865
- let rightCol: StkTableColumn<DT>[] = [];
866
- copyColumn.forEach(col => {
867
- if (col.fixed === 'left') {
868
- leftCol.push(col);
869
- } else if (col.fixed === 'right') {
870
- rightCol.push(col);
871
- } else {
872
- centerCol.push(col);
873
- }
874
- });
875
- copyColumn = leftCol.concat(centerCol).concat(rightCol);
876
- }
877
- const maxDeep = howDeepTheHeader(copyColumn);
878
-
879
- if (maxDeep > 0 && props.virtualX) {
880
- console.error('StkTableVue:多级表头不支持横向虚拟滚动!');
881
- }
882
-
883
- for (let i = 0; i <= maxDeep; i++) {
884
- tableHeadersTemp[i] = [];
885
- tableHeadersForCalcTemp[i] = [];
886
- }
887
-
888
- /**
889
- * 展开columns
890
- * @param arr
891
- * @param depth 深度
892
- * @param parent 父节点引用,用于构建双向链表。
893
- * @param parentFixed 父节点固定列继承。
894
- */
895
- function flat(
896
- arr: PrivateStkTableColumn<DT>[],
897
- parent: PrivateStkTableColumn<DT> | null,
898
- depth = 0 /* , parentFixed: 'left' | 'right' | null = null */,
899
- ) {
900
- /** 所有子节点数量 */
901
- let allChildrenLen = 0;
902
- let allChildrenWidthSum = 0;
903
- arr.forEach(col => {
904
- // TODO: 继承父节点固定列配置
905
- // if (parentFixed) {
906
- // col.fixed = parentFixed;
907
- // }
908
- // 构建指向父节点的引用
909
- col.__PARENT__ = parent;
910
- /** 一列中的子节点数量 */
911
- let colChildrenLen = 1;
912
- /** 多级表头的父节点宽度,通过叶子节点宽度计算得到 */
913
- let colWidth = 0;
914
- if (col.children) {
915
- // DFS
916
- const [len, widthSum] = flat(col.children, col, depth + 1 /* , col.fixed */);
917
- colChildrenLen = len;
918
- colWidth = widthSum;
919
- tableHeadersForCalcTemp[depth].push(col);
920
- } else {
921
- colWidth = getColWidth(col);
922
- for (let i = depth; i <= maxDeep; i++) {
923
- // 如有rowSpan 向下复制一个表头col,用于计算固定列
924
- tableHeadersForCalcTemp[i].push(col);
925
- }
926
- }
927
- // 回溯
928
- col.__WIDTH__ = colWidth; //记录计算的列宽
929
- tableHeadersTemp[depth].push(col);
930
- const rowSpan = col.children ? 1 : maxDeep - depth + 1;
931
- const colSpan = colChildrenLen;
932
- if (rowSpan > 1) {
933
- col.rowSpan = rowSpan;
934
- }
935
- if (colSpan > 1) {
936
- col.colSpan = colSpan;
937
- }
938
-
939
- allChildrenLen += colChildrenLen;
940
- allChildrenWidthSum += colWidth;
941
- });
942
- return [allChildrenLen, allChildrenWidthSum];
943
- }
944
-
945
- flat(copyColumn, null);
946
- tableHeaders.value = tableHeadersTemp;
947
- tableHeadersForCalc.value = tableHeadersForCalcTemp;
948
- }
949
-
950
- /**
951
- * 行唯一值生成
952
- */
953
- function rowKeyGen(row: DT | null | undefined) {
954
- if (!row) return row;
955
- let key = rowKeyGenStore.get(row) || (row as PrivateRowDT).__ROW_KEY__;
956
- if (!key) {
957
- key = rowKeyGenComputed.value(row);
958
-
959
- if (key === void 0) {
960
- // key为undefined时,不应该高亮行。因此重新生成key
961
- key = Math.random().toString();
962
- }
963
- rowKeyGenStore.set(row, key);
964
- }
965
- return key;
966
- }
967
-
968
- /** 单元格唯一值 */
969
- function cellKeyGen(row: DT | null | undefined, col: StkTableColumn<DT>) {
970
- return rowKeyGen(row) + CELL_KEY_SEPARATE + colKeyGen.value(col);
971
- }
972
-
973
- /** 单元格样式 */
974
- const cellStyleMap = computed(() => {
975
- const thMap = new Map();
976
- const tdMap = new Map();
977
- const { virtualX, colResizable } = props;
978
- tableHeaders.value.forEach((cols, depth) => {
979
- cols.forEach(col => {
980
- const width = virtualX ? getCalculatedColWidth(col) + 'px' : transformWidthToStr(col.width);
981
- const style: CSSProperties = {
982
- width,
983
- };
984
- if (colResizable) {
985
- // 如果要调整列宽,列宽必须固定。
986
- style.minWidth = width;
987
- style.maxWidth = width;
988
- } else {
989
- style.minWidth = transformWidthToStr(col.minWidth) ?? width;
990
- style.maxWidth = transformWidthToStr(col.maxWidth) ?? width;
991
- }
992
- const colKey = colKeyGen.value(col);
993
- thMap.set(colKey, Object.assign({ textAlign: col.headerAlign }, style, getFixedStyle(TagType.TH, col, depth)));
994
- tdMap.set(colKey, Object.assign({ textAlign: col.align }, style, getFixedStyle(TagType.TD, col, depth)));
995
- });
996
- });
997
- return {
998
- [TagType.TH]: thMap,
999
- [TagType.TD]: tdMap,
1000
- };
1001
- });
1002
-
1003
- /** th title */
1004
- function getHeaderTitle(col: StkTableColumn<DT>): string {
1005
- const colKey = colKeyGen.value(col);
1006
- // hide title
1007
- if (props.hideHeaderTitle === true || (Array.isArray(props.hideHeaderTitle) && props.hideHeaderTitle.includes(colKey))) {
1008
- return '';
1009
- }
1010
- return col.title || '';
1011
- }
1012
-
1013
- /**
1014
- * 表头点击排序
1015
- * @param click 是否为点击表头触发
1016
- * @param options.force sort-remote 开启后是否强制排序
1017
- * @param options.emit 是否触发回调
1018
- */
1019
- function onColumnSort(col: StkTableColumn<DT> | undefined | null, click = true, options: { force?: boolean; emit?: boolean } = {}) {
1020
- if (!col) {
1021
- console.warn('onColumnSort: not found col:', col);
1022
- return;
1023
- }
1024
- if (!col.sorter && click) {
1025
- // 点击表头触发的排序,如果列没有配置sorter则不处理。setSorter 触发的排序则保持通行。
1026
- return;
1027
- }
1028
- options = { force: false, emit: false, ...options };
1029
- const colKey = colKeyGen.value(col);
1030
- if (sortCol.value !== colKey) {
1031
- // 改变排序的列时,重置排序
1032
- sortCol.value = colKey;
1033
- sortOrderIndex.value = 0;
1034
- }
1035
- const prevOrder = sortSwitchOrder[sortOrderIndex.value];
1036
- if (click) sortOrderIndex.value++;
1037
- sortOrderIndex.value = sortOrderIndex.value % 3;
1038
-
1039
- let order = sortSwitchOrder[sortOrderIndex.value];
1040
- const sortConfig = { ...props.sortConfig, ...col.sortConfig };
1041
- const defaultSort = sortConfig.defaultSort;
1042
- const colKeyGenValue = colKeyGen.value;
1043
-
1044
- if (!order && defaultSort) {
1045
- // if no order ,use default order
1046
- const defaultColKey = defaultSort.key || defaultSort.dataIndex;
1047
- if (!defaultColKey) {
1048
- console.error('sortConfig.defaultSort key or dataIndex is required');
1049
- return;
1050
- }
1051
- if (colKey === defaultColKey && prevOrder === defaultSort.order) {
1052
- order = sortSwitchOrder.find(o => o !== defaultSort.order && o) as Order;
1053
- } else {
1054
- order = defaultSort.order;
1055
- }
1056
- sortOrderIndex.value = sortSwitchOrder.indexOf(order);
1057
- sortCol.value = defaultColKey as string;
1058
- col = null;
1059
- for (const row of tableHeaders.value) {
1060
- const c = row.find(item => colKeyGenValue(item) === defaultColKey);
1061
- if (c) {
1062
- col = c;
1063
- break;
1064
- }
1065
- }
1066
- }
1067
- if (!props.sortRemote || options.force) {
1068
- const sortOption = col || defaultSort;
1069
- if (sortOption) {
1070
- dataSourceCopy.value = tableSort(sortOption, order, props.dataSource, sortConfig);
1071
- }
1072
- }
1073
- // 只有点击才触发事件
1074
- if (click || options.emit) {
1075
- emits('sort-change', col, order, toRaw(dataSourceCopy.value), sortConfig);
1076
- }
1077
- }
1078
-
1079
- function onRowClick(e: MouseEvent, row: DT) {
1080
- emits('row-click', e, row);
1081
- const isCurrentRow = props.rowKey ? currentRowKey.value === rowKeyGen(row) : currentRow.value === row;
1082
- if (isCurrentRow) {
1083
- if (!props.rowCurrentRevokable) {
1084
- // 不可取消
1085
- return;
1086
- }
1087
- // 点击同一行,取消当前选中行。
1088
- currentRow.value = void 0;
1089
- currentRowKey.value = void 0;
1090
- } else {
1091
- currentRow.value = row;
1092
- currentRowKey.value = rowKeyGen(row);
1093
- }
1094
- emits('current-change', e, row, { select: !isCurrentRow });
1095
- }
1096
-
1097
- function onRowDblclick(e: MouseEvent, row: DT) {
1098
- emits('row-dblclick', e, row);
1099
- }
1100
-
1101
- /** 表头行右键 */
1102
- function onHeaderMenu(e: MouseEvent) {
1103
- emits('header-row-menu', e);
1104
- }
1105
-
1106
- /** 表体行右键 */
1107
- function onRowMenu(e: MouseEvent, row: DT) {
1108
- emits('row-menu', e, row);
1109
- }
1110
-
1111
- /**
1112
- * proxy events
1113
- */
1114
- // function onTbodyClick(e: MouseEvent, type: 1 | 2) {
1115
- // const el = (e.target as HTMLElement).closest<HTMLElement>('td,tr');
1116
- // if (!el) return;
1117
- // if (el.tagName === 'TD') {
1118
- // const [rowKey, colKey] = el.dataset.cellKey?.split(CELL_KEY_SEPARATE) || [];
1119
- // const row = dataSourceCopy.value.find(item => rowKeyGen(item) === rowKey);
1120
- // const col = tableHeaderLast.value.find(item => colKeyGen.value(item) === colKey);
1121
- // if (col) {
1122
- // if (col.type === 'expand') {
1123
- // toggleExpandRow(row, col);
1124
- // }
1125
- // if (type === 1) {
1126
- // onCellClick(e, row, col);
1127
- // } else if (type === 2) {
1128
- // onCellMouseDown(e, row, col);
1129
- // }
1130
- // }
1131
- // if (type === 1) {
1132
- // onRowClick(e, row);
1133
- // }
1134
- // } else if (el.tagName === 'TR') {
1135
- // const rowKey = el.dataset.rowKey;
1136
- // const row = dataSourceCopy.value.find(item => rowKeyGen(item) === rowKey);
1137
- // onRowClick(e, row);
1138
- // }
1139
- // }
1140
-
1141
- /** 单元格单击 */
1142
- function onCellClick(e: MouseEvent, row: DT, col: StkTableColumn<DT>) {
1143
- if (col.type === 'expand') {
1144
- toggleExpandRow(row, col);
1145
- }
1146
- if (props.cellActive) {
1147
- const cellKey = cellKeyGen(row, col);
1148
- const result = { row, col, select: false };
1149
- if (props.selectedCellRevokable && currentSelectedCellKey.value === cellKey) {
1150
- currentSelectedCellKey.value = void 0;
1151
- } else {
1152
- currentSelectedCellKey.value = cellKey;
1153
- result.select = true;
1154
- }
1155
- emits('cell-selected', e, result);
1156
- }
1157
- emits('cell-click', e, row, col);
1158
- }
1159
-
1160
- /** 表头单元格单击 */
1161
- function onHeaderCellClick(e: MouseEvent, col: StkTableColumn<DT>) {
1162
- emits('header-cell-click', e, col);
1163
- }
1164
-
1165
- /** td mouseenter */
1166
- function onCellMouseEnter(e: MouseEvent, row: DT, col: StkTableColumn<DT>) {
1167
- emits('cell-mouseenter', e, row, col);
1168
- }
1169
-
1170
- /** td mouseleave */
1171
- function onCellMouseLeave(e: MouseEvent, row: DT, col: StkTableColumn<DT>) {
1172
- emits('cell-mouseleave', e, row, col);
1173
- }
1174
-
1175
- /** td mouseover event */
1176
- function onCellMouseOver(e: MouseEvent, row: DT, col: StkTableColumn<DT>) {
1177
- emits('cell-mouseover', e, row, col);
1178
- }
1179
-
1180
- function onCellMouseDown(e: MouseEvent, row: DT, col: StkTableColumn<DT>) {
1181
- emits('cell-mousedown', e, row, col);
1182
- }
1183
-
1184
- /**
1185
- * 鼠标滚轮事件监听。代理滚轮事件,防止滚动过快出现白屏。
1186
- * @param e
1187
- */
1188
- function onTableWheel(e: WheelEvent) {
1189
- if (props.smoothScroll) {
1190
- return;
1191
- }
1192
- if (isColResizing.value) {
1193
- // 正在调整列宽时,不允许用户滚动
1194
- e.stopPropagation();
1195
- return;
1196
- }
1197
- // #region ---- 控制滚动,防止出现白屏--
1198
- const dom = tableContainerRef.value;
1199
- if (!dom) return;
1200
- const { containerHeight, scrollTop, scrollHeight } = virtualScroll.value;
1201
- const { containerWidth, scrollLeft, scrollWidth } = virtualScrollX.value;
1202
- /** 是否滚动在下面 */
1203
- const isScrollBottom = scrollHeight - containerHeight - scrollTop < 10;
1204
- /** 是否滚动在右侧 */
1205
- const isScrollRight = scrollWidth - containerWidth - scrollLeft < 10;
1206
- const { deltaY, deltaX } = e;
1207
-
1208
- /**
1209
- * 只有虚拟滚动时,才要用 wheel 代理scroll,防止滚动过快导致的白屏。
1210
- * 滚动条在边界情况时,not preventDefault 。因为会阻塞父级滚动条滚动。
1211
- */
1212
- if (virtual_on && deltaY) {
1213
- if ((deltaY > 0 && !isScrollBottom) || (deltaY < 0 && scrollTop > 0)) {
1214
- e.preventDefault();
1215
- }
1216
- dom.scrollTop += deltaY;
1217
- }
1218
- if (virtualX_on && deltaX) {
1219
- if ((deltaX > 0 && !isScrollRight) || (deltaX < 0 && scrollLeft > 0)) {
1220
- e.preventDefault();
1221
- }
1222
- dom.scrollLeft += deltaX;
1223
- }
1224
- //#endregion
1225
- }
1226
-
1227
- /**
1228
- * 滚动条监听
1229
- * @param e scrollEvent
1230
- */
1231
- function onTableScroll(e: Event) {
1232
- if (!e?.target) return;
1233
-
1234
- const { scrollTop, scrollLeft } = e.target as HTMLElement;
1235
- const { scrollTop: vScrollTop, startIndex, endIndex } = virtualScroll.value;
1236
- const { scrollLeft: vScrollLeft } = virtualScrollX.value;
1237
- const isYScroll = scrollTop !== vScrollTop;
1238
- const isXScroll = scrollLeft !== vScrollLeft;
1239
-
1240
- // 纵向滚动有变化
1241
- if (isYScroll) {
1242
- updateVirtualScrollY(scrollTop);
1243
- }
1244
-
1245
- // 横向滚动有变化
1246
- if (isXScroll) {
1247
- if (virtualX_on.value) {
1248
- updateVirtualScrollX(scrollLeft);
1249
- } else {
1250
- // 非虚拟滚动也记录一下滚动条位置。用于判断isXScroll
1251
- virtualScrollX.value.scrollLeft = scrollLeft;
1252
- }
1253
- updateFixedShadow(virtualScrollX);
1254
- }
1255
-
1256
- if (isYScroll) {
1257
- emits('scroll', e, { startIndex, endIndex });
1258
- }
1259
- if (isXScroll) {
1260
- emits('scroll-x', e);
1261
- }
1262
- }
1263
-
1264
- /** tr hover */
1265
- function onTrMouseOver(_e: MouseEvent, row: DT) {
1266
- if (currentHoverRow === row) return;
1267
- currentHoverRow = row;
1268
- currentHoverRowKey.value = rowKeyGen(row);
1269
- }
1270
-
1271
- /**
1272
- * 选中一行
1273
- * @param {string} rowKeyOrRow selected rowKey, undefined to unselect
1274
- * @param {boolean} option.silent if set true not emit `current-change`. default:false
1275
- */
1276
- function setCurrentRow(rowKeyOrRow: string | undefined | DT, option = { silent: false }) {
1277
- if (!dataSourceCopy.value.length) return;
1278
- const select = rowKeyOrRow !== void 0;
1279
- if (!select) {
1280
- currentRow.value = void 0;
1281
- currentRowKey.value = void 0;
1282
- } else if (typeof rowKeyOrRow === 'string') {
1283
- const row = dataSourceCopy.value.find(it => rowKeyGen(it) === rowKeyOrRow);
1284
- if (!row) {
1285
- console.warn('setCurrentRow failed.rowKey:', rowKeyOrRow);
1286
- return;
1287
- }
1288
- currentRow.value = row;
1289
- currentRowKey.value = rowKeyOrRow;
1290
- } else {
1291
- currentRow.value = rowKeyOrRow;
1292
- currentRowKey.value = rowKeyGen(rowKeyOrRow);
1293
- }
1294
- if (!option.silent) {
1295
- emits('current-change', /** no Event */ null, currentRow.value, { select });
1296
- }
1297
- }
1298
-
1299
- /**
1300
- * set highlight active cell (props.cellActive=true)
1301
- * @param row row if undefined, clear highlight
1302
- * @param col column
1303
- * @param option.silent if emit current-change. default:false(not emit `current-change`)
1304
- */
1305
- function setSelectedCell(row?: DT, col?: StkTableColumn<DT>, option = { silent: false }) {
1306
- if (!dataSourceCopy.value.length) return;
1307
- const select = row !== void 0 && col !== void 0;
1308
- currentSelectedCellKey.value = select ? cellKeyGen(row, col) : void 0;
1309
- if (!option.silent) {
1310
- emits('cell-selected', /** no Event */ null, { row, col, select });
1311
- }
1312
- }
1313
-
1314
- /**
1315
- * 设置表头排序状态。
1316
- * @param colKey 列唯一键字段。如果你想要取消排序状态,请使用`resetSorter`
1317
- * @param order 正序倒序
1318
- * @param option.sortOption 指定排序参数。同 StkTableColumn 中排序相关字段。建议从columns中find得到。
1319
- * @param option.sort 是否触发排序-默认true
1320
- * @param option.silent 是否禁止触发回调-默认true
1321
- * @param option.force 是否触发排序-默认true
1322
- * @return 表格数据
1323
- */
1324
- function setSorter(colKey: string, order: Order, option: { sortOption?: SortOption<DT>; force?: boolean; silent?: boolean; sort?: boolean } = {}) {
1325
- const newOption = { silent: true, sortOption: null, sort: true, ...option };
1326
- sortCol.value = colKey;
1327
- sortOrderIndex.value = sortSwitchOrder.indexOf(order);
1328
- const colKeyGenValue = colKeyGen.value;
1329
-
1330
- if (newOption.sort && dataSourceCopy.value?.length) {
1331
- // 如果表格有数据,则进行排序
1332
- const column = newOption.sortOption || tableHeaderLast.value.find(it => colKeyGenValue(it) === sortCol.value);
1333
- if (column) onColumnSort(column, false, { force: option.force ?? true, emit: !newOption.silent });
1334
- else console.warn('Can not find column by key:', sortCol.value);
1335
- }
1336
- return dataSourceCopy.value;
1337
- }
1338
-
1339
- function resetSorter() {
1340
- sortCol.value = void 0;
1341
- sortOrderIndex.value = 0;
1342
- dataSourceCopy.value = props.dataSource.slice();
1343
- }
1344
-
1345
- /**
1346
- * set scroll bar position
1347
- * @param top null to not change
1348
- * @param left null to not change
1349
- */
1350
- function scrollTo(top: number | null = 0, left: number | null = 0) {
1351
- if (!tableContainerRef.value) return;
1352
- if (top !== null) tableContainerRef.value.scrollTop = top;
1353
- if (left !== null) tableContainerRef.value.scrollLeft = left;
1354
- }
1355
-
1356
- /** get current table data */
1357
- function getTableData() {
1358
- return toRaw(dataSourceCopy.value);
1359
- }
1360
-
1361
- /**
1362
- * get current sort info
1363
- * @return {{key:string,order:Order}[]}
1364
- */
1365
- function getSortColumns() {
1366
- const sortOrder = sortSwitchOrder[sortOrderIndex.value];
1367
- if (!sortOrder) return [];
1368
- return [{ key: sortCol.value, order: sortOrder }];
1369
- }
1370
-
1371
- /** click expended icon to toggle expand row */
1372
- function toggleExpandRow(row: DT, col: StkTableColumn<DT>) {
1373
- const isExpand = row?.__EXPANDED__ === col ? !row?.__EXPANDED__ : true;
1374
- setRowExpand(row, isExpand, { col });
1375
- }
1376
-
1377
- /**
1378
- *
1379
- * @param rowKeyOrRow rowKey or row
1380
- * @param expand expand or collapse
1381
- * @param data { col?: StkTableColumn<DT> }
1382
- * @param data.silent if set true, not emit `toggle-row-expand`, default:false
1383
- */
1384
- function setRowExpand(rowKeyOrRow: string | undefined | DT, expand?: boolean, data?: { col?: StkTableColumn<DT>; silent?: boolean }) {
1385
- let rowKey: string;
1386
- if (typeof rowKeyOrRow === 'string') {
1387
- rowKey = rowKeyOrRow;
1388
- } else {
1389
- rowKey = rowKeyGen(rowKeyOrRow);
1390
- }
1391
- const tempData = dataSourceCopy.value.slice();
1392
- const index = tempData.findIndex(it => rowKeyGen(it) === rowKey);
1393
- if (index === -1) {
1394
- console.warn('expandRow failed.rowKey:', rowKey);
1395
- return;
1396
- }
1397
-
1398
- // delete other expanded row below the target row
1399
- for (let i = index + 1; i < tempData.length; i++) {
1400
- const item: PrivateRowDT = tempData[i];
1401
- const rowKey = item.__ROW_KEY__;
1402
- if (rowKey?.startsWith(EXPANDED_ROW_KEY_PREFIX)) {
1403
- tempData.splice(i, 1);
1404
- i--;
1405
- } else {
1406
- break;
1407
- }
1408
- }
1409
-
1410
- const row = tempData[index];
1411
- const col = data?.col || null;
1412
-
1413
- if (expand) {
1414
- // insert new expanded row
1415
- const newExpandRow: ExpandedRow = {
1416
- __ROW_KEY__: EXPANDED_ROW_KEY_PREFIX + rowKey,
1417
- __EXPANDED_ROW__: row,
1418
- __EXPANDED_COL__: col,
1419
- };
1420
- tempData.splice(index + 1, 0, newExpandRow);
1421
- }
1422
-
1423
- if (row) {
1424
- row.__EXPANDED__ = expand ? col : null;
1425
- }
1426
-
1427
- dataSourceCopy.value = tempData;
1428
- if (!data?.silent) {
1429
- emits('toggle-row-expand', { expanded: Boolean(expand), row, col });
1430
- }
1431
- }
1432
-
1433
- defineExpose({
1434
- /**
1435
- * 重新计算虚拟列表宽高
1436
- *
1437
- * en: calc virtual scroll x & y info
1438
- * @see {@link initVirtualScroll}
1439
- */
1440
- initVirtualScroll,
1441
- /**
1442
- * 重新计算虚拟列表宽度
1443
- *
1444
- * en: calc virtual scroll x
1445
- * @see {@link initVirtualScrollX}
1446
- */
1447
- initVirtualScrollX,
1448
- /**
1449
- * 重新计算虚拟列表高度
1450
- *
1451
- * en: calc virtual scroll y
1452
- * @see {@link initVirtualScrollY}
1453
- */
1454
- initVirtualScrollY,
1455
- /**
1456
- * 选中一行
1457
- *
1458
- * en:select a row
1459
- * @see {@link setCurrentRow}
1460
- */
1461
- setCurrentRow,
1462
- /**
1463
- * 取消选中单元格
1464
- *
1465
- * en: set highlight active cell (props.cellActive=true)
1466
- * @see {@link setSelectedCell}
1467
- */
1468
- setSelectedCell,
1469
- /**
1470
- * 设置高亮单元格
1471
- *
1472
- * en: Set highlight cell
1473
- * @see {@link setHighlightDimCell}
1474
- */
1475
- setHighlightDimCell,
1476
- /**
1477
- * 设置高亮行
1478
- *
1479
- * en: Set highlight row
1480
- * @see {@link setHighlightDimRow}
1481
- */
1482
- setHighlightDimRow,
1483
- /**
1484
- * 表格排序列colKey
1485
- *
1486
- * en: Table sort column colKey
1487
- */
1488
- sortCol,
1489
- /**
1490
- * 表格排序列顺序
1491
- *
1492
- * en: get current sort info
1493
- * @see {@link getSortColumns}
1494
- */
1495
- getSortColumns,
1496
- /**
1497
- * 设置表头排序状态
1498
- *
1499
- * en: Set the sort status of the table header
1500
- * @see {@link setSorter}
1501
- */
1502
- setSorter,
1503
- /**
1504
- * 重置sorter状态
1505
- *
1506
- * en: Reset the sorter status
1507
- * @see {@link resetSorter}
1508
- */
1509
- resetSorter,
1510
- /**
1511
- * 滚动至
1512
- *
1513
- * en: Scroll to
1514
- * @see {@link scrollTo}
1515
- */
1516
- scrollTo,
1517
- /**
1518
- * 获取表格数据
1519
- *
1520
- * en: Get table data
1521
- * @see {@link getTableData}
1522
- */
1523
- getTableData,
1524
- /**
1525
- * 设置展开的行
1526
- *
1527
- * en: Set expanded rows
1528
- * @see {@link setRowExpand}
1529
- */
1530
- setRowExpand,
1531
- /**
1532
- * 不定行高时,如果行高有变化,则调用此方法更新行高。
1533
- *
1534
- * en: When the row height is not fixed, call this method to update the row height if the row height changes.
1535
- * @see {@link setAutoHeight}
1536
- */
1537
- setAutoHeight,
1538
- /**
1539
- * 清除所有行高
1540
- *
1541
- * en: Clear all row heights
1542
- * @see {@link clearAllAutoHeight}
1543
- */
1544
- clearAllAutoHeight,
1545
- });
1546
- </script>
1
+ <!-- eslint-disable vue/attribute-hyphenation -->
2
+ <template>
3
+ <div
4
+ ref="tableContainerRef"
5
+ class="stk-table"
6
+ :class="{
7
+ virtual,
8
+ 'virtual-x': virtualX,
9
+ 'vt-on': virtual_on,
10
+ light: theme === 'light',
11
+ dark: theme === 'dark',
12
+ headless,
13
+ 'is-col-resizing': isColResizing,
14
+ 'col-resizable': props.colResizable,
15
+ border: props.bordered,
16
+ 'border-h': props.bordered === 'h',
17
+ 'border-v': props.bordered === 'v',
18
+ 'border-body-v': props.bordered === 'body-v',
19
+ stripe: props.stripe,
20
+ 'cell-hover': props.cellHover,
21
+ 'cell-active': props.cellActive,
22
+ 'row-hover': props.rowHover,
23
+ 'row-active': props.rowActive,
24
+ 'text-overflow': props.showOverflow,
25
+ 'header-text-overflow': props.showHeaderOverflow,
26
+ 'fixed-relative-mode': isRelativeMode,
27
+ 'auto-row-height': props.autoRowHeight,
28
+ 'scroll-row-by-row': props.scrollRowByRow,
29
+ }"
30
+ :style="{
31
+ '--row-height': props.autoRowHeight ? void 0 : virtualScroll.rowHeight + 'px',
32
+ '--header-row-height': props.headerRowHeight + 'px',
33
+ '--highlight-duration': props.highlightConfig.duration && props.highlightConfig.duration + 's',
34
+ '--highlight-timing-function': highlightSteps ? `steps(${highlightSteps})` : '',
35
+ }"
36
+ @scroll="onTableScroll"
37
+ @wheel="onTableWheel"
38
+ >
39
+ <!-- 这个元素用于整数行虚拟滚动时,撑开父容器的高度) -->
40
+ <div
41
+ v-if="props.scrollRowByRow && virtual"
42
+ class="row-by-row-table-height"
43
+ :style="{ height: dataSourceCopy.length * virtualScroll.rowHeight + 'px' }"
44
+ ></div>
45
+
46
+ <div v-if="colResizable" ref="colResizeIndicatorRef" class="column-resize-indicator"></div>
47
+ <!-- 表格主体 -->
48
+ <table
49
+ class="stk-table-main"
50
+ :style="{ width, minWidth, maxWidth }"
51
+ :class="{
52
+ 'fixed-mode': props.fixedMode,
53
+ }"
54
+ >
55
+ <!-- transform: virtualX_on ? `translateX(${virtualScrollX.offsetLeft}px)` : null, 用transform控制虚拟滚动左边距,sticky会有问题 -->
56
+ <thead v-if="!headless" ref="theadRef">
57
+ <tr v-for="(row, rowIndex) in tableHeaders" :key="rowIndex" @contextmenu="e => onHeaderMenu(e)">
58
+ <!-- 这个th用于横向虚拟滚动表格左边距,width、maxWidth 用于兼容低版本浏览器 -->
59
+ <th
60
+ v-if="virtualX_on"
61
+ class="vt-x-left"
62
+ :style="`min-width:${virtualScrollX.offsetLeft}px;width:${virtualScrollX.offsetLeft}px`"
63
+ ></th>
64
+ <!-- v for中最后一行才用 切割。-->
65
+ <th
66
+ v-for="(col, colIndex) in virtualX_on && rowIndex === tableHeaders.length - 1 ? virtualX_columnPart : row"
67
+ :key="colKeyGen(col)"
68
+ :data-col-key="colKeyGen(col)"
69
+ :draggable="isHeaderDraggable(col) ? 'true' : 'false'"
70
+ :rowspan="virtualX_on ? 1 : col.rowSpan"
71
+ :colspan="col.colSpan"
72
+ :style="cellStyleMap[TagType.TH].get(colKeyGen(col))"
73
+ :title="getHeaderTitle(col)"
74
+ :class="[
75
+ col.sorter ? 'sortable' : '',
76
+ colKeyGen(col) === sortCol && sortOrderIndex !== 0 && 'sorter-' + sortSwitchOrder[sortOrderIndex],
77
+ col.headerClassName,
78
+ fixedColClassMap.get(colKeyGen(col)),
79
+ ]"
80
+ @click="
81
+ e => {
82
+ onColumnSort(col);
83
+ onHeaderCellClick(e, col);
84
+ }
85
+ "
86
+ @dragstart="onThDragStart"
87
+ @drop="onThDrop"
88
+ @dragover="onThDragOver"
89
+ >
90
+ <!-- 列宽拖动handler -->
91
+ <div
92
+ v-if="colResizeOn(col) && colIndex > 0"
93
+ class="table-header-resizer left"
94
+ @mousedown="e => onThResizeMouseDown(e, col, true)"
95
+ ></div>
96
+ <div class="table-header-cell-wrapper" :style="{ '--row-span': virtualX_on ? 1 : col.rowSpan }">
97
+ <component :is="col.customHeaderCell" v-if="col.customHeaderCell" :col="col" :colIndex="colIndex" :rowIndex="rowIndex" />
98
+ <template v-else>
99
+ <slot name="tableHeader" :col="col">
100
+ <span class="table-header-title">{{ col.title }}</span>
101
+ </slot>
102
+ </template>
103
+ <SortIcon v-if="col.sorter" class="table-header-sorter" />
104
+ </div>
105
+ <!-- 列宽拖动handler -->
106
+ <div v-if="colResizeOn(col)" class="table-header-resizer right" @mousedown="e => onThResizeMouseDown(e, col)"></div>
107
+ </th>
108
+ <!-- 这个th用于横向虚拟滚动表格右边距 width, min-width 用于兼容低版本浏览器-->
109
+ <th v-if="virtualX_on" class="vt-x-right" :style="`min-width:${virtualX_offsetRight}px;width:${virtualX_offsetRight}px`"></th>
110
+ </tr>
111
+ </thead>
112
+
113
+ <!-- 用于虚拟滚动表格内容定位 @deprecated 有兼容问题-->
114
+ <!-- <tbody v-if="virtual_on" :style="{ height: `${virtualScroll.offsetTop}px` }"></tbody> -->
115
+ <!-- <tbody :style="{ transform: `translateY(${virtualScroll.offsetTop}px)` }"> -->
116
+ <tbody class="stk-tbody-main" @dragover="onTrDragOver" @dragenter="onTrDragEnter" @dragend="onTrDragEnd">
117
+ <tr v-if="virtual_on && !props.scrollRowByRow" :style="`height:${virtualScroll.offsetTop}px`" class="padding-top-tr">
118
+ <!--这个td用于配合虚拟滚动的th对应,防止列错位-->
119
+ <td v-if="virtualX_on && fixedMode && headless" class="vt-x-left"></td>
120
+ <template v-if="fixedMode && headless">
121
+ <td v-for="col in virtualX_columnPart" :key="colKeyGen(col)" :style="cellStyleMap[TagType.TD].get(colKeyGen(col))"></td>
122
+ </template>
123
+ </tr>
124
+ <tr
125
+ v-for="(row, rowIndex) in virtual_dataSourcePart"
126
+ :id="stkTableId + '-' + (rowKey ? rowKeyGen(row) : getRowIndex(rowIndex))"
127
+ ref="trRef"
128
+ :key="rowKey ? rowKeyGen(row) : getRowIndex(rowIndex)"
129
+ :data-row-key="rowKey ? rowKeyGen(row) : getRowIndex(rowIndex)"
130
+ :class="{
131
+ active: rowKey ? rowKeyGen(row) === currentRowKey : row === currentRow,
132
+ hover: props.showTrHoverClass && (rowKey ? rowKeyGen(row) === currentHoverRowKey : row === currentHoverRowKey),
133
+ [rowClassName(row, getRowIndex(rowIndex))]: true,
134
+ expanded: row?.__EXPANDED__,
135
+ 'expanded-row': row && row.__EXPANDED_ROW__,
136
+ }"
137
+ :style="{
138
+ '--row-height':
139
+ row && row.__EXPANDED_ROW__ && props.virtual && props.expandConfig?.height && props.expandConfig?.height + 'px',
140
+ }"
141
+ @click="e => onRowClick(e, row, getRowIndex(rowIndex))"
142
+ @dblclick="e => onRowDblclick(e, row, getRowIndex(rowIndex))"
143
+ @contextmenu="e => onRowMenu(e, row, getRowIndex(rowIndex))"
144
+ @mouseover="e => onTrMouseOver(e, row)"
145
+ @drop="e => onTrDrop(e, getRowIndex(rowIndex))"
146
+ >
147
+ <!--这个td用于配合虚拟滚动的th对应,防止列错位-->
148
+ <td v-if="virtualX_on" class="vt-x-left"></td>
149
+ <td v-if="row && row.__EXPANDED_ROW__" :colspan="virtualX_columnPart.length">
150
+ <!-- TODO: support wheel -->
151
+ <div class="table-cell-wrapper">
152
+ <slot name="expand" :row="row.__EXPANDED_ROW__" :col="row.__EXPANDED_COL__">
153
+ {{ row.__EXPANDED_ROW__?.[row.__EXPANDED_COL__.dataIndex] ?? '' }}
154
+ </slot>
155
+ </div>
156
+ </td>
157
+ <template v-else>
158
+ <td
159
+ v-for="(col, colIndex) in virtualX_columnPart"
160
+ :key="colKeyGen(col)"
161
+ :data-cell-key="cellKeyGen(row, col)"
162
+ :style="cellStyleMap[TagType.TD].get(colKeyGen(col))"
163
+ :class="[
164
+ col.className,
165
+ fixedColClassMap.get(colKeyGen(col)),
166
+ {
167
+ 'seq-column': col.type === 'seq',
168
+ active: currentSelectedCellKey === cellKeyGen(row, col),
169
+ 'expand-cell': col.type === 'expand',
170
+ expanded: col.type === 'expand' && (row.__EXPANDED__ ? colKeyGen(row.__EXPANDED__) === colKeyGen(col) : false),
171
+ 'drag-row-cell': col.type === 'dragRow',
172
+ },
173
+ ]"
174
+ @click="e => onCellClick(e, row, col, getRowIndex(rowIndex))"
175
+ @mousedown="e => onCellMouseDown(e, row, col, getRowIndex(rowIndex))"
176
+ @mouseenter="e => onCellMouseEnter(e, row, col)"
177
+ @mouseleave="e => onCellMouseLeave(e, row, col)"
178
+ @mouseover="e => onCellMouseOver(e, row, col)"
179
+ >
180
+ <component
181
+ :is="col.customCell"
182
+ v-if="col.customCell"
183
+ class="table-cell-wrapper"
184
+ :col="col"
185
+ :row="row"
186
+ :rowIndex="getRowIndex(rowIndex)"
187
+ :colIndex="colIndex"
188
+ :cellValue="row && row[col.dataIndex]"
189
+ :expanded="(row && row.__EXPANDED__) || null"
190
+ />
191
+ <div
192
+ v-else
193
+ class="table-cell-wrapper"
194
+ :class="{ 'expanded-cell-wrapper': col.type === 'expand' }"
195
+ :title="col.type !== 'seq' ? row?.[col.dataIndex] : ''"
196
+ >
197
+ <template v-if="col.type === 'seq'">
198
+ {{ (props.seqConfig.startIndex || 0) + getRowIndex(rowIndex) + 1 }}
199
+ </template>
200
+ <span v-else-if="col.type === 'expand'">
201
+ {{ row?.[col.dataIndex] ?? '' }}
202
+ </span>
203
+ <template v-else-if="col.type === 'dragRow'">
204
+ <DragHandle @dragstart="e => onTrDragStart(e, getRowIndex(rowIndex))" />
205
+ <span>
206
+ {{ row?.[col.dataIndex] ?? '' }}
207
+ </span>
208
+ </template>
209
+ <template v-else>
210
+ {{ row?.[col.dataIndex] ?? getEmptyCellText(col, row) }}
211
+ </template>
212
+ </div>
213
+ </td>
214
+ </template>
215
+ </tr>
216
+ <tr v-if="virtual_on && !props.scrollRowByRow" :style="`height: ${virtual_offsetBottom}px`"></tr>
217
+ </tbody>
218
+ </table>
219
+ <div v-if="(!dataSourceCopy || !dataSourceCopy.length) && showNoData" class="stk-table-no-data" :class="{ 'no-data-full': noDataFull }">
220
+ <slot name="empty">暂无数据</slot>
221
+ </div>
222
+ <slot name="customBottom"></slot>
223
+ </div>
224
+ </template>
225
+
226
+ <script setup lang="ts">
227
+ /**
228
+ * @author japlus
229
+ */
230
+ import { CSSProperties, computed, nextTick, onMounted, ref, shallowRef, toRaw, watch } from 'vue';
231
+ import DragHandle from './components/DragHandle.vue';
232
+ import SortIcon from './components/SortIcon.vue';
233
+ import { CELL_KEY_SEPARATE, DEFAULT_ROW_HEIGHT, DEFAULT_SMOOTH_SCROLL, EXPANDED_ROW_KEY_PREFIX, IS_LEGACY_MODE } from './const';
234
+ import {
235
+ AutoRowHeightConfig,
236
+ DragRowConfig,
237
+ ExpandConfig,
238
+ ExpandedRow,
239
+ HeaderDragConfig,
240
+ HighlightConfig,
241
+ Order,
242
+ PrivateRowDT,
243
+ PrivateStkTableColumn,
244
+ SeqConfig,
245
+ SortConfig,
246
+ SortOption,
247
+ StkTableColumn,
248
+ TagType,
249
+ UniqKeyProp,
250
+ ColResizableConfig,
251
+ } from './types/index';
252
+ import { useAutoResize } from './useAutoResize';
253
+ import { useColResize } from './useColResize';
254
+ import { useFixedCol } from './useFixedCol';
255
+ import { useFixedStyle } from './useFixedStyle';
256
+ import { useGetFixedColPosition } from './useGetFixedColPosition';
257
+ import { useHighlight } from './useHighlight';
258
+ import { useKeyboardArrowScroll } from './useKeyboardArrowScroll';
259
+ import { useThDrag } from './useThDrag';
260
+ import { useTrDrag } from './useTrDrag';
261
+ import { useVirtualScroll } from './useVirtualScroll';
262
+ import { createStkTableId, getCalculatedColWidth, getColWidth } from './utils/constRefUtils';
263
+ import { howDeepTheHeader, tableSort, transformWidthToStr } from './utils/index';
264
+
265
+ /** Generic stands for DataType */
266
+ type DT = any & PrivateRowDT;
267
+
268
+ /** generate table instance id */
269
+ const stkTableId = createStkTableId();
270
+
271
+ /**
272
+ * props cannot be placed in a separate file. It will cause compilation errors with vue 2.7 compiler.
273
+ */
274
+ const props = withDefaults(
275
+ defineProps<{
276
+ width?: string;
277
+ /** 最小表格宽度 */
278
+ minWidth?: string;
279
+ /** 表格最大宽度*/
280
+ maxWidth?: string;
281
+ /** 斑马线条纹 */
282
+ stripe?: boolean;
283
+ /** 是否使用 table-layout:fixed(低版本浏览器需要设置table) */
284
+ fixedMode?: boolean;
285
+ /** 是否隐藏表头 */
286
+ headless?: boolean;
287
+ /** 主题,亮、暗 */
288
+ theme?: 'light' | 'dark';
289
+ /**
290
+ * 行高
291
+ * - `props.autoRowHeight` 为 `true` 时,将表示为期望行高,用于计算。不再影响实际行高。
292
+ */
293
+ rowHeight?: number;
294
+ /**
295
+ * 是否可变行高
296
+ * - 设置为 `true` 时, `props.rowHeight` 将表示为期望行高,用于计算。不再影响实际行高。
297
+ */
298
+ autoRowHeight?: boolean | AutoRowHeightConfig<DT>;
299
+ /** 是否高亮鼠标悬浮的行 */
300
+ rowHover?: boolean;
301
+ /** 是否高亮选中的行 */
302
+ rowActive?: boolean;
303
+ /** 当前行再次点击否可以取消 (rowActive=true)*/
304
+ rowCurrentRevokable?: boolean;
305
+ /** 表头行高。default = rowHeight */
306
+ headerRowHeight?: number | null;
307
+ /** 虚拟滚动 */
308
+ virtual?: boolean;
309
+ /** x轴虚拟滚动(必须设置列宽)*/
310
+ virtualX?: boolean;
311
+ /** 表格列配置 */
312
+ columns?: StkTableColumn<DT>[];
313
+ /** 表格数据源 */
314
+ dataSource?: DT[];
315
+ /** 行唯一键 (行唯一值不能为undefined) */
316
+ rowKey?: UniqKeyProp;
317
+ /** 列唯一键 */
318
+ colKey?: UniqKeyProp;
319
+ /** 空值展示文字 */
320
+ emptyCellText?: string | ((option: { row: DT; col: StkTableColumn<DT> }) => string);
321
+ /** 暂无数据兜底高度是否撑满 */
322
+ noDataFull?: boolean;
323
+ /** 是否展示暂无数据 */
324
+ showNoData?: boolean;
325
+ /** 是否服务端排序,true则不排序数据 */
326
+ sortRemote?: boolean;
327
+ /** 表头是否溢出展示... */
328
+ showHeaderOverflow?: boolean;
329
+ /** 表体溢出是否展示... */
330
+ showOverflow?: boolean;
331
+ /** 是否增加行hover class $*$ rename*/
332
+ showTrHoverClass?: boolean;
333
+ /** 是否高亮鼠标悬浮的单元格 */
334
+ cellHover?: boolean;
335
+ /** 是否高亮选中的单元格 */
336
+ cellActive?: boolean;
337
+ /** 单元格再次点击否可以取消选中 (cellActive=true)*/
338
+ selectedCellRevokable?: boolean;
339
+ /** 表头是否可拖动。支持回调函数。 */
340
+ headerDrag?: HeaderDragConfig;
341
+ /**
342
+ * 给行附加className<br>
343
+ * FIXME: 是否需要优化,因为不传此prop会使表格行一直执行空函数,是否有影响
344
+ */
345
+ rowClassName?: (row: DT, i: number) => string;
346
+ /**
347
+ * 列宽是否可拖动(需要设置v-model:columns)<br>
348
+ * **不要设置**列minWidth,**必须**设置width<br>
349
+ * 列宽拖动时,每一列都必须要有width,且minWidth/maxWidth不生效。table width会变为"fit-content"。
350
+ * - 会自动更新props.columns中的with属性
351
+ */
352
+ colResizable?: boolean | ColResizableConfig<DT>;
353
+ /** 可拖动至最小的列宽 */
354
+ colMinWidth?: number;
355
+ /**
356
+ * 单元格分割线。
357
+ * 默认横竖都有
358
+ * "h" - 仅展示横线
359
+ * "v" - 仅展示竖线
360
+ * "body-v" - 仅表体展示竖线
361
+ */
362
+ bordered?: boolean | 'h' | 'v' | 'body-v';
363
+ /**
364
+ * 自动重新计算虚拟滚动高度宽度。默认true
365
+ * [非响应式]
366
+ * 传入方法表示resize后的回调
367
+ */
368
+ autoResize?: boolean | (() => void);
369
+ /** 是否展示固定列阴影。为节省性能,默认false。 */
370
+ fixedColShadow?: boolean;
371
+ /** 优化vue2 滚动 */
372
+ optimizeVue2Scroll?: boolean;
373
+ /** 排序配置 */
374
+ sortConfig?: SortConfig<DT>;
375
+ /** 隐藏头部title。可传入colKey数组 */
376
+ hideHeaderTitle?: boolean | string[];
377
+ /** 高亮配置 */
378
+ highlightConfig?: HighlightConfig;
379
+ /** 序号列配置 */
380
+ seqConfig?: SeqConfig;
381
+ /** 展开行配置 */
382
+ expandConfig?: ExpandConfig;
383
+ /** 行拖动配置 */
384
+ dragRowConfig?: DragRowConfig;
385
+ /**
386
+ * 固定头,固定列实现方式。(非响应式)
387
+ *
388
+ * relative:固定列只会放在props.columns的两侧。
389
+ * - 如果列宽会变动则谨慎使用。
390
+ * - 多级表头固定列慎用
391
+ *
392
+ * 低版本浏览器强制为'relative',
393
+ */
394
+ cellFixedMode?: 'sticky' | 'relative';
395
+ /**
396
+ * 是否平滑滚动。default: chrome < 85 || chrome > 120 ? true : false
397
+ * - false: 使用 onwheel 滚动。为了防止滚动过快导致白屏。
398
+ * - true: 不使用 onwheel 滚动。鼠标滚轮滚动时更加平滑。滚动过快时会白屏。
399
+ */
400
+ smoothScroll?: boolean;
401
+ /** 按整数行纵向滚动 */
402
+ scrollRowByRow?: boolean;
403
+ }>(),
404
+ {
405
+ width: '',
406
+ fixedMode: false,
407
+ stripe: false,
408
+ minWidth: '',
409
+ maxWidth: '',
410
+ headless: false,
411
+ theme: 'light',
412
+ rowHeight: DEFAULT_ROW_HEIGHT,
413
+ autoRowHeight: false,
414
+ rowHover: true,
415
+ rowActive: true,
416
+ rowCurrentRevokable: true,
417
+ headerRowHeight: DEFAULT_ROW_HEIGHT,
418
+ virtual: false,
419
+ virtualX: false,
420
+ columns: () => [],
421
+ dataSource: () => [],
422
+ rowKey: '',
423
+ colKey: 'dataIndex',
424
+ emptyCellText: '--',
425
+ noDataFull: false,
426
+ showNoData: true,
427
+ sortRemote: false,
428
+ showHeaderOverflow: false,
429
+ showOverflow: false,
430
+ showTrHoverClass: false,
431
+ cellHover: false,
432
+ cellActive: false,
433
+ selectedCellRevokable: true,
434
+ headerDrag: false,
435
+ rowClassName: () => '',
436
+ colResizable: false,
437
+ colMinWidth: 10,
438
+ bordered: true,
439
+ autoResize: true,
440
+ fixedColShadow: false,
441
+ optimizeVue2Scroll: false,
442
+ sortConfig: () => ({
443
+ emptyToBottom: false,
444
+ stringLocaleCompare: false,
445
+ }),
446
+ hideHeaderTitle: false,
447
+ highlightConfig: () => ({}),
448
+ seqConfig: () => ({}),
449
+ expandConfig: () => ({}),
450
+ dragRowConfig: () => ({}),
451
+ cellFixedMode: 'sticky',
452
+ smoothScroll: DEFAULT_SMOOTH_SCROLL,
453
+ scrollRowByRow: false,
454
+ },
455
+ );
456
+
457
+ const emits = defineEmits<{
458
+ /**
459
+ * 排序变更触发。defaultSort.dataIndex 找不到时,col 将返回null。
460
+ *
461
+ * ```(col: StkTableColumn<DT> | null, order: Order, data: DT[], sortConfig: SortConfig<DT>)```
462
+ */
463
+ (e: 'sort-change', col: StkTableColumn<DT> | null, order: Order, data: DT[], sortConfig: SortConfig<DT>): void;
464
+ /**
465
+ * 一行点击事件
466
+ *
467
+ * ```(ev: MouseEvent, row: DT, data: { rowIndex: number })```
468
+ */
469
+ (e: 'row-click', ev: MouseEvent, row: DT, data: { rowIndex: number }): void;
470
+ /**
471
+ * 选中一行触发。ev返回null表示不是点击事件触发的
472
+ *
473
+ * ```(ev: MouseEvent | null, row: DT | undefined, data: { select: boolean} })```
474
+ */
475
+ (e: 'current-change', ev: MouseEvent | null, row: DT | undefined, data: { select: boolean }): void;
476
+ /**
477
+ * 选中单元格触发。ev返回null表示不是点击事件触发的
478
+ *
479
+ * ```(ev: MouseEvent | null, data: { select: boolean; row: DT | undefined; col: StkTableColumn<DT> | null })```
480
+ */
481
+ (e: 'cell-selected', ev: MouseEvent | null, data: { select: boolean; row: DT | undefined; col: StkTableColumn<DT> | undefined }): void;
482
+ /**
483
+ * 行双击事件
484
+ *
485
+ * ```(ev: MouseEvent, row: DT, data: { rowIndex: number })```
486
+ */
487
+ (e: 'row-dblclick', ev: MouseEvent, row: DT, data: { rowIndex: number }): void;
488
+ /**
489
+ * 表头右键事件
490
+ *
491
+ * ```(ev: MouseEvent)```
492
+ */
493
+ (e: 'header-row-menu', ev: MouseEvent): void;
494
+ /**
495
+ * 表体行右键点击事件
496
+ *
497
+ * ```(ev: MouseEvent, row: DT, data: { rowIndex: number })```
498
+ */
499
+ (e: 'row-menu', ev: MouseEvent, row: DT, data: { rowIndex: number }): void;
500
+ /**
501
+ * 单元格点击事件
502
+ *
503
+ * ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>, data: { rowIndex: number })```
504
+ */
505
+ (e: 'cell-click', ev: MouseEvent, row: DT, col: StkTableColumn<DT>, data: { rowIndex: number }): void;
506
+ /**
507
+ * 单元格鼠标进入事件
508
+ *
509
+ * ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>)```
510
+ */
511
+ (e: 'cell-mouseenter', ev: MouseEvent, row: DT, col: StkTableColumn<DT>): void;
512
+ /**
513
+ * 单元格鼠标移出事件
514
+ *
515
+ * ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>)```
516
+ */
517
+ (e: 'cell-mouseleave', ev: MouseEvent, row: DT, col: StkTableColumn<DT>): void;
518
+ /**
519
+ * 单元格悬浮事件
520
+ *
521
+ * ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>)```
522
+ */
523
+ (e: 'cell-mouseover', ev: MouseEvent, row: DT, col: StkTableColumn<DT>): void;
524
+ /**
525
+ * 单元格鼠标按下事件
526
+ *
527
+ * ```(ev: MouseEvent, row: DT, col: StkTableColumn<DT>, data: { rowIndex: number })```
528
+ */
529
+ (e: 'cell-mousedown', ev: MouseEvent, row: DT, col: StkTableColumn<DT>, data: { rowIndex: number }): void;
530
+ /**
531
+ * 表头单元格点击事件
532
+ *
533
+ * ```(ev: MouseEvent, col: StkTableColumn<DT>)```
534
+ */
535
+ (e: 'header-cell-click', ev: MouseEvent, col: StkTableColumn<DT>): void;
536
+ /**
537
+ * 表格滚动事件
538
+ *
539
+ * ```(ev: Event, data: { startIndex: number; endIndex: number })```
540
+ */
541
+ (e: 'scroll', ev: Event, data: { startIndex: number; endIndex: number }): void;
542
+ /**
543
+ * 表格横向滚动事件
544
+ *
545
+ * ```(ev: Event)```
546
+ */
547
+ (e: 'scroll-x', ev: Event): void;
548
+ /**
549
+ * 表头列拖动事件
550
+ *
551
+ * ```(dragStartKey: string, targetColKey: string)```
552
+ */
553
+ (e: 'col-order-change', dragStartKey: string, targetColKey: string): void;
554
+ /**
555
+ * 表头列拖动开始
556
+ *
557
+ * ```(dragStartKey: string)```
558
+ */
559
+ (e: 'th-drag-start', dragStartKey: string): void;
560
+ /**
561
+ * 表头列拖动drop
562
+ *
563
+ * ```(targetColKey: string)```
564
+ */
565
+ (e: 'th-drop', targetColKey: string): void;
566
+ /**
567
+ * 行拖动事件
568
+ *
569
+ * ```(dragStartKey: string, targetRowKey: string)```
570
+ */
571
+ (e: 'row-order-change', dragStartKey: string, targetRowKey: string): void;
572
+ /**
573
+ * 列宽变动时触发
574
+ *
575
+ * ```(col: StkTableColumn<DT>)```
576
+ */
577
+ (e: 'col-resize', col: StkTableColumn<DT>): void;
578
+ /**
579
+ * 展开行触发
580
+ *
581
+ * ```( data: { expanded: boolean; row: DT; col: StkTableColumn<DT> })```
582
+ */
583
+ (e: 'toggle-row-expand', data: { expanded: boolean; row: DT; col: StkTableColumn<DT> | null }): void;
584
+ /**
585
+ * v-model:columns col resize 时更新宽度
586
+ */
587
+ (e: 'update:columns', cols: StkTableColumn<DT>[]): void;
588
+ }>();
589
+
590
+ // 仅支持vue3.3+
591
+ // const slots = defineSlots<{
592
+ // /** 表头插槽 */
593
+ // tableHeader(props: { col: StkTableColumn<DT> }): void;
594
+ // /** 空状态插槽 */
595
+ // empty(): void;
596
+ // }>();
597
+
598
+ const tableContainerRef = ref<HTMLDivElement>();
599
+ const theadRef = ref<HTMLElement>();
600
+ const colResizeIndicatorRef = ref<HTMLDivElement>();
601
+ const trRef = ref<HTMLTableRowElement[]>();
602
+
603
+ /** 是否使用 relative 固定头和列 */
604
+ const isRelativeMode = ref(IS_LEGACY_MODE ? true : props.cellFixedMode === 'relative');
605
+
606
+ /**
607
+ * 当前选中的一行
608
+ * - shallowRef: 使 currentRow.value === row 地址相同。防止rowKeyGen 的WeakMap key不一致。
609
+ */
610
+ const currentRow = shallowRef<DT>();
611
+ /**
612
+ * 保存当前选中行的key<br>
613
+ * 原因:vue3 不用ref包dataSource时,row为原始对象,与currentItem(Ref)相比会不相等。
614
+ */
615
+ const currentRowKey = ref<any>(void 0);
616
+ /** 当前选中的单元格key */
617
+ const currentSelectedCellKey = ref<any>(void 0);
618
+ /** 当前hover行 */
619
+ let currentHoverRow: DT | null = null;
620
+ /** 当前hover的行的key */
621
+ const currentHoverRowKey = ref(null);
622
+ /** 当前hover的列的key */
623
+ // const currentColHoverKey = ref(null);
624
+
625
+ /** sort colKey*/
626
+ let sortCol = ref<keyof DT>();
627
+ let sortOrderIndex = ref(0);
628
+
629
+ /** 排序切换顺序 */
630
+ const sortSwitchOrder: Order[] = [null, 'desc', 'asc'];
631
+
632
+ /**
633
+ * 表头.内容是 props.columns 的引用集合
634
+ * @eg
635
+ * ```js
636
+ * [
637
+ * [{dataInex:'id',...}], // 第0行列配置
638
+ * [], // 第一行列配置
639
+ * //...
640
+ * ]
641
+ * ```
642
+ */
643
+ const tableHeaders = shallowRef<StkTableColumn<DT>[][]>([]);
644
+
645
+ /**
646
+ * 用于计算多级表头的tableHeaders。模拟rowSpan 位置的辅助数组。用于计算固定列。
647
+ * @eg
648
+ * ```
649
+ * | colspan3 |
650
+ * | rowspan2 | colspan2 |
651
+ * | rowspan2 | colspan1 | colspan1 |
652
+ * ```
653
+ * ---
654
+ * expect arr:
655
+ * ```
656
+ * const arr = [
657
+ * [col],
658
+ * [col2, col3],
659
+ * [col2, col4, col5],
660
+ * ]
661
+ * ```
662
+ */
663
+ const tableHeadersForCalc = shallowRef<PrivateStkTableColumn<DT>[][]>([]);
664
+
665
+ /** 最后一行的tableHeaders.内容是 props.columns 的引用集合 */
666
+ const tableHeaderLast = computed(() => tableHeadersForCalc.value.slice(-1)[0] || []);
667
+
668
+ const dataSourceCopy = shallowRef<DT[]>(props.dataSource.slice());
669
+
670
+ const rowKeyGenComputed = computed(() => {
671
+ const { rowKey } = props;
672
+ if (typeof rowKey === 'function') {
673
+ return (row: DT) => (rowKey as (row: DT) => string)(row);
674
+ } else {
675
+ return (row: DT) => (row as any)[rowKey];
676
+ }
677
+ });
678
+
679
+ const colKeyGen = computed<(col: StkTableColumn<DT>) => string>(() => {
680
+ const { colKey } = props;
681
+ if (typeof colKey === 'function') {
682
+ return col => (colKey as (col: StkTableColumn<DT>) => string)(col);
683
+ } else {
684
+ return col => (col as any)[colKey];
685
+ }
686
+ });
687
+
688
+ const getEmptyCellText = computed(() => {
689
+ const { emptyCellText } = props;
690
+ if (typeof emptyCellText === 'string') {
691
+ return () => emptyCellText;
692
+ } else {
693
+ return (col: StkTableColumn<DT>, row: DT) => emptyCellText({ row, col });
694
+ }
695
+ });
696
+
697
+ /** rowKey缓存 */
698
+ const rowKeyGenStore = new WeakMap();
699
+
700
+ const { onThDragStart, onThDragOver, onThDrop, isHeaderDraggable } = useThDrag({ props, emits, colKeyGen });
701
+
702
+ const { onTrDragStart, onTrDrop, onTrDragOver, onTrDragEnd, onTrDragEnter } = useTrDrag({ props, emits, dataSourceCopy });
703
+
704
+ const {
705
+ virtualScroll,
706
+ virtualScrollX,
707
+ virtual_on,
708
+ virtual_dataSourcePart,
709
+ virtual_offsetBottom,
710
+ virtualX_on,
711
+ virtualX_columnPart,
712
+ virtualX_offsetRight,
713
+ initVirtualScroll,
714
+ initVirtualScrollY,
715
+ initVirtualScrollX,
716
+ updateVirtualScrollY,
717
+ updateVirtualScrollX,
718
+ setAutoHeight,
719
+ clearAllAutoHeight,
720
+ } = useVirtualScroll({ tableContainerRef, trRef, props, dataSourceCopy, tableHeaderLast, tableHeaders, rowKeyGen });
721
+
722
+ /** 获取固定列的位置 */
723
+ const getFixedColPosition = useGetFixedColPosition({ colKeyGen, tableHeadersForCalc });
724
+
725
+ const getFixedStyle = useFixedStyle<DT>({
726
+ props,
727
+ isRelativeMode,
728
+ getFixedColPosition,
729
+ virtualScroll,
730
+ virtualScrollX,
731
+ virtualX_on,
732
+ virtualX_offsetRight,
733
+ });
734
+
735
+ /**
736
+ * 高亮行,高亮单元格
737
+ */
738
+ const { highlightSteps, setHighlightDimCell, setHighlightDimRow } = useHighlight({ props, stkTableId, tableContainerRef });
739
+
740
+ if (props.autoResize) {
741
+ useAutoResize({ tableContainerRef, initVirtualScroll, props, debounceMs: 200 });
742
+ }
743
+
744
+ /** 键盘箭头滚动 */
745
+ useKeyboardArrowScroll(tableContainerRef, {
746
+ props,
747
+ scrollTo,
748
+ virtualScroll,
749
+ virtualScrollX,
750
+ tableHeaders,
751
+ virtual_on,
752
+ });
753
+
754
+ /** 固定列处理 */
755
+ const { fixedCols, fixedColClassMap, updateFixedShadow } = useFixedCol({
756
+ props,
757
+ colKeyGen,
758
+ getFixedColPosition,
759
+ tableContainerRef,
760
+ tableHeaders,
761
+ tableHeadersForCalc,
762
+ });
763
+
764
+ const { isColResizing, onThResizeMouseDown, colResizeOn } = useColResize({
765
+ props,
766
+ emits,
767
+ colKeyGen,
768
+ colResizeIndicatorRef,
769
+ tableContainerRef,
770
+ tableHeaderLast,
771
+ fixedCols,
772
+ });
773
+
774
+ watch(
775
+ () => props.columns,
776
+ () => {
777
+ dealColumns();
778
+ // initVirtualScrollX 需要获取容器滚动宽度等。必须等渲染完成后再调用。因此使用nextTick。
779
+ nextTick(() => {
780
+ initVirtualScrollX();
781
+ updateFixedShadow();
782
+ });
783
+ },
784
+ );
785
+ watch(
786
+ () => props.virtual,
787
+ () => {
788
+ nextTick(() => {
789
+ initVirtualScrollY();
790
+ });
791
+ },
792
+ );
793
+ watch(
794
+ () => props.virtualX,
795
+ () => {
796
+ dealColumns();
797
+ // initVirtualScrollX 需要获取容器滚动宽度等。必须等渲染完成后再调用。因此使用nextTick。
798
+ nextTick(() => {
799
+ initVirtualScrollX();
800
+ updateFixedShadow();
801
+ });
802
+ },
803
+ );
804
+
805
+ watch(
806
+ () => props.dataSource,
807
+ val => {
808
+ if (!val) {
809
+ console.warn('invalid dataSource');
810
+ return;
811
+ }
812
+ /** 是否需要更新ScrollY,这里由于watch newValue与oldValue 的长度一样,因此需要这样使用 */
813
+ let needInitVirtualScrollY = false;
814
+ if (dataSourceCopy.value.length !== val.length) {
815
+ needInitVirtualScrollY = true;
816
+ }
817
+ dataSourceCopy.value = val.slice(); // 浅拷贝
818
+ // 数据长度没变则不计算虚拟滚动
819
+ if (needInitVirtualScrollY) {
820
+ // 表格渲染后再执行。initVirtualScrollY 中有获取dom的操作。
821
+ nextTick(() => initVirtualScrollY());
822
+ }
823
+ const sortColValue = sortCol.value;
824
+ if (sortColValue) {
825
+ // 排序
826
+ const colKey = colKeyGen.value;
827
+ const column = tableHeaderLast.value.find(it => colKey(it) === sortColValue);
828
+ onColumnSort(column, false);
829
+ }
830
+ },
831
+ );
832
+
833
+ watch(
834
+ () => props.fixedColShadow,
835
+ () => updateFixedShadow(),
836
+ );
837
+
838
+ dealColumns();
839
+
840
+ onMounted(() => {
841
+ initVirtualScroll();
842
+ updateFixedShadow();
843
+ dealDefaultSorter();
844
+ });
845
+
846
+ /** 处理默认排序 */
847
+ function dealDefaultSorter() {
848
+ if (!props.sortConfig.defaultSort) return;
849
+ const { key, dataIndex, order, silent } = { silent: false, ...props.sortConfig.defaultSort };
850
+ setSorter((key || dataIndex) as string, order, { force: false, silent });
851
+ }
852
+
853
+ /**
854
+ * 处理多级表头
855
+ */
856
+ function dealColumns() {
857
+ // reset
858
+ const tableHeadersTemp: StkTableColumn<DT>[][] = [];
859
+ const tableHeadersForCalcTemp: StkTableColumn<DT>[][] = [];
860
+ let copyColumn = props.columns; // do not deep clone
861
+ // relative 模式下不支持sticky列。因此就放在左右两侧。
862
+ if (isRelativeMode.value) {
863
+ let leftCol: StkTableColumn<DT>[] = [];
864
+ let centerCol: StkTableColumn<DT>[] = [];
865
+ let rightCol: StkTableColumn<DT>[] = [];
866
+ copyColumn.forEach(col => {
867
+ if (col.fixed === 'left') {
868
+ leftCol.push(col);
869
+ } else if (col.fixed === 'right') {
870
+ rightCol.push(col);
871
+ } else {
872
+ centerCol.push(col);
873
+ }
874
+ });
875
+ copyColumn = leftCol.concat(centerCol).concat(rightCol);
876
+ }
877
+ const maxDeep = howDeepTheHeader(copyColumn);
878
+
879
+ if (maxDeep > 0 && props.virtualX) {
880
+ console.error('StkTableVue:多级表头不支持横向虚拟滚动!');
881
+ }
882
+
883
+ for (let i = 0; i <= maxDeep; i++) {
884
+ tableHeadersTemp[i] = [];
885
+ tableHeadersForCalcTemp[i] = [];
886
+ }
887
+
888
+ /**
889
+ * 展开columns
890
+ * @param arr
891
+ * @param depth 深度
892
+ * @param parent 父节点引用,用于构建双向链表。
893
+ * @param parentFixed 父节点固定列继承。
894
+ */
895
+ function flat(
896
+ arr: PrivateStkTableColumn<DT>[],
897
+ parent: PrivateStkTableColumn<DT> | null,
898
+ depth = 0 /* , parentFixed: 'left' | 'right' | null = null */,
899
+ ) {
900
+ /** 所有子节点数量 */
901
+ let allChildrenLen = 0;
902
+ let allChildrenWidthSum = 0;
903
+ arr.forEach(col => {
904
+ // TODO: 继承父节点固定列配置
905
+ // if (parentFixed) {
906
+ // col.fixed = parentFixed;
907
+ // }
908
+ // 构建指向父节点的引用
909
+ col.__PARENT__ = parent;
910
+ /** 一列中的子节点数量 */
911
+ let colChildrenLen = 1;
912
+ /** 多级表头的父节点宽度,通过叶子节点宽度计算得到 */
913
+ let colWidth = 0;
914
+ if (col.children) {
915
+ // DFS
916
+ const [len, widthSum] = flat(col.children, col, depth + 1 /* , col.fixed */);
917
+ colChildrenLen = len;
918
+ colWidth = widthSum;
919
+ tableHeadersForCalcTemp[depth].push(col);
920
+ } else {
921
+ colWidth = getColWidth(col);
922
+ for (let i = depth; i <= maxDeep; i++) {
923
+ // 如有rowSpan 向下复制一个表头col,用于计算固定列
924
+ tableHeadersForCalcTemp[i].push(col);
925
+ }
926
+ }
927
+ // 回溯
928
+ col.__WIDTH__ = colWidth; //记录计算的列宽
929
+ tableHeadersTemp[depth].push(col);
930
+ const rowSpan = col.children ? 1 : maxDeep - depth + 1;
931
+ const colSpan = colChildrenLen;
932
+ if (rowSpan > 1) {
933
+ col.rowSpan = rowSpan;
934
+ }
935
+ if (colSpan > 1) {
936
+ col.colSpan = colSpan;
937
+ }
938
+
939
+ allChildrenLen += colChildrenLen;
940
+ allChildrenWidthSum += colWidth;
941
+ });
942
+ return [allChildrenLen, allChildrenWidthSum];
943
+ }
944
+
945
+ flat(copyColumn, null);
946
+ tableHeaders.value = tableHeadersTemp;
947
+ tableHeadersForCalc.value = tableHeadersForCalcTemp;
948
+ }
949
+
950
+ /**
951
+ * 行唯一值生成
952
+ */
953
+ function rowKeyGen(row: DT | null | undefined) {
954
+ if (!row) return row;
955
+ let key = rowKeyGenStore.get(row) || (row as PrivateRowDT).__ROW_KEY__;
956
+ if (!key) {
957
+ key = rowKeyGenComputed.value(row);
958
+
959
+ if (key === void 0) {
960
+ // key为undefined时,不应该高亮行。因此重新生成key
961
+ key = Math.random().toString();
962
+ }
963
+ rowKeyGenStore.set(row, key);
964
+ }
965
+ return key;
966
+ }
967
+
968
+ /** 单元格唯一值 */
969
+ function cellKeyGen(row: DT | null | undefined, col: StkTableColumn<DT>) {
970
+ return rowKeyGen(row) + CELL_KEY_SEPARATE + colKeyGen.value(col);
971
+ }
972
+
973
+ /** 单元格样式 */
974
+ const cellStyleMap = computed(() => {
975
+ const thMap = new Map();
976
+ const tdMap = new Map();
977
+ const { virtualX, colResizable } = props;
978
+ tableHeaders.value.forEach((cols, depth) => {
979
+ cols.forEach(col => {
980
+ const width = virtualX ? getCalculatedColWidth(col) + 'px' : transformWidthToStr(col.width);
981
+ const style: CSSProperties = {
982
+ width,
983
+ };
984
+ if (colResizable) {
985
+ // 如果要调整列宽,列宽必须固定。
986
+ style.minWidth = width;
987
+ style.maxWidth = width;
988
+ } else {
989
+ style.minWidth = transformWidthToStr(col.minWidth) ?? width;
990
+ style.maxWidth = transformWidthToStr(col.maxWidth) ?? width;
991
+ }
992
+ const colKey = colKeyGen.value(col);
993
+ thMap.set(colKey, Object.assign({ textAlign: col.headerAlign }, style, getFixedStyle(TagType.TH, col, depth)));
994
+ tdMap.set(colKey, Object.assign({ textAlign: col.align }, style, getFixedStyle(TagType.TD, col, depth)));
995
+ });
996
+ });
997
+ return {
998
+ [TagType.TH]: thMap,
999
+ [TagType.TD]: tdMap,
1000
+ };
1001
+ });
1002
+
1003
+ function getRowIndex(rowIndex: number) {
1004
+ return rowIndex + (virtual_on ? virtualScroll.value.startIndex : 0);
1005
+ }
1006
+
1007
+ /** th title */
1008
+ function getHeaderTitle(col: StkTableColumn<DT>): string {
1009
+ const colKey = colKeyGen.value(col);
1010
+ // hide title
1011
+ if (props.hideHeaderTitle === true || (Array.isArray(props.hideHeaderTitle) && props.hideHeaderTitle.includes(colKey))) {
1012
+ return '';
1013
+ }
1014
+ return col.title || '';
1015
+ }
1016
+
1017
+ /**
1018
+ * 表头点击排序
1019
+ * @param click 是否为点击表头触发
1020
+ * @param options.force sort-remote 开启后是否强制排序
1021
+ * @param options.emit 是否触发回调
1022
+ */
1023
+ function onColumnSort(col: StkTableColumn<DT> | undefined | null, click = true, options: { force?: boolean; emit?: boolean } = {}) {
1024
+ if (!col) {
1025
+ console.warn('onColumnSort: not found col:', col);
1026
+ return;
1027
+ }
1028
+ if (!col.sorter && click) {
1029
+ // 点击表头触发的排序,如果列没有配置sorter则不处理。setSorter 触发的排序则保持通行。
1030
+ return;
1031
+ }
1032
+ options = { force: false, emit: false, ...options };
1033
+ const colKey = colKeyGen.value(col);
1034
+ if (sortCol.value !== colKey) {
1035
+ // 改变排序的列时,重置排序
1036
+ sortCol.value = colKey;
1037
+ sortOrderIndex.value = 0;
1038
+ }
1039
+ const prevOrder = sortSwitchOrder[sortOrderIndex.value];
1040
+ if (click) sortOrderIndex.value++;
1041
+ sortOrderIndex.value = sortOrderIndex.value % 3;
1042
+
1043
+ let order = sortSwitchOrder[sortOrderIndex.value];
1044
+ const sortConfig = { ...props.sortConfig, ...col.sortConfig };
1045
+ const defaultSort = sortConfig.defaultSort;
1046
+ const colKeyGenValue = colKeyGen.value;
1047
+
1048
+ if (!order && defaultSort) {
1049
+ // if no order ,use default order
1050
+ const defaultColKey = defaultSort.key || defaultSort.dataIndex;
1051
+ if (!defaultColKey) {
1052
+ console.error('sortConfig.defaultSort key or dataIndex is required');
1053
+ return;
1054
+ }
1055
+ if (colKey === defaultColKey && prevOrder === defaultSort.order) {
1056
+ order = sortSwitchOrder.find(o => o !== defaultSort.order && o) as Order;
1057
+ } else {
1058
+ order = defaultSort.order;
1059
+ }
1060
+ sortOrderIndex.value = sortSwitchOrder.indexOf(order);
1061
+ sortCol.value = defaultColKey as string;
1062
+ col = null;
1063
+ for (const row of tableHeaders.value) {
1064
+ const c = row.find(item => colKeyGenValue(item) === defaultColKey);
1065
+ if (c) {
1066
+ col = c;
1067
+ break;
1068
+ }
1069
+ }
1070
+ }
1071
+ if (!props.sortRemote || options.force) {
1072
+ const sortOption = col || defaultSort;
1073
+ if (sortOption) {
1074
+ dataSourceCopy.value = tableSort(sortOption, order, props.dataSource, sortConfig);
1075
+ }
1076
+ }
1077
+ // 只有点击才触发事件
1078
+ if (click || options.emit) {
1079
+ emits('sort-change', col, order, toRaw(dataSourceCopy.value), sortConfig);
1080
+ }
1081
+ }
1082
+
1083
+ function onRowClick(e: MouseEvent, row: DT, rowIndex: number) {
1084
+ emits('row-click', e, row, { rowIndex });
1085
+ const isCurrentRow = props.rowKey ? currentRowKey.value === rowKeyGen(row) : currentRow.value === row;
1086
+ if (isCurrentRow) {
1087
+ if (!props.rowCurrentRevokable) {
1088
+ // 不可取消
1089
+ return;
1090
+ }
1091
+ // 点击同一行,取消当前选中行。
1092
+ currentRow.value = void 0;
1093
+ currentRowKey.value = void 0;
1094
+ } else {
1095
+ currentRow.value = row;
1096
+ currentRowKey.value = rowKeyGen(row);
1097
+ }
1098
+ emits('current-change', e, row, { select: !isCurrentRow });
1099
+ }
1100
+
1101
+ function onRowDblclick(e: MouseEvent, row: DT, rowIndex: number) {
1102
+ emits('row-dblclick', e, row, { rowIndex });
1103
+ }
1104
+
1105
+ /** 表头行右键 */
1106
+ function onHeaderMenu(e: MouseEvent) {
1107
+ emits('header-row-menu', e);
1108
+ }
1109
+
1110
+ /** 表体行右键 */
1111
+ function onRowMenu(e: MouseEvent, row: DT, rowIndex: number) {
1112
+ emits('row-menu', e, row, { rowIndex });
1113
+ }
1114
+
1115
+ /**
1116
+ * proxy events
1117
+ */
1118
+ // function onTbodyClick(e: MouseEvent, type: 1 | 2) {
1119
+ // const el = (e.target as HTMLElement).closest<HTMLElement>('td,tr');
1120
+ // if (!el) return;
1121
+ // if (el.tagName === 'TD') {
1122
+ // const [rowKey, colKey] = el.dataset.cellKey?.split(CELL_KEY_SEPARATE) || [];
1123
+ // const row = dataSourceCopy.value.find(item => rowKeyGen(item) === rowKey);
1124
+ // const col = tableHeaderLast.value.find(item => colKeyGen.value(item) === colKey);
1125
+ // if (col) {
1126
+ // if (col.type === 'expand') {
1127
+ // toggleExpandRow(row, col);
1128
+ // }
1129
+ // if (type === 1) {
1130
+ // onCellClick(e, row, col);
1131
+ // } else if (type === 2) {
1132
+ // onCellMouseDown(e, row, col);
1133
+ // }
1134
+ // }
1135
+ // if (type === 1) {
1136
+ // onRowClick(e, row);
1137
+ // }
1138
+ // } else if (el.tagName === 'TR') {
1139
+ // const rowKey = el.dataset.rowKey;
1140
+ // const row = dataSourceCopy.value.find(item => rowKeyGen(item) === rowKey);
1141
+ // onRowClick(e, row);
1142
+ // }
1143
+ // }
1144
+
1145
+ /** 单元格单击 */
1146
+ function onCellClick(e: MouseEvent, row: DT, col: StkTableColumn<DT>, rowIndex: number) {
1147
+ if (col.type === 'expand') {
1148
+ toggleExpandRow(row, col);
1149
+ }
1150
+ if (props.cellActive) {
1151
+ const cellKey = cellKeyGen(row, col);
1152
+ const result = { row, col, select: false, rowIndex };
1153
+ if (props.selectedCellRevokable && currentSelectedCellKey.value === cellKey) {
1154
+ currentSelectedCellKey.value = void 0;
1155
+ } else {
1156
+ currentSelectedCellKey.value = cellKey;
1157
+ result.select = true;
1158
+ }
1159
+ emits('cell-selected', e, result);
1160
+ }
1161
+ emits('cell-click', e, row, col, { rowIndex });
1162
+ }
1163
+
1164
+ /** 表头单元格单击 */
1165
+ function onHeaderCellClick(e: MouseEvent, col: StkTableColumn<DT>) {
1166
+ emits('header-cell-click', e, col);
1167
+ }
1168
+
1169
+ /** td mouseenter */
1170
+ function onCellMouseEnter(e: MouseEvent, row: DT, col: StkTableColumn<DT>) {
1171
+ emits('cell-mouseenter', e, row, col);
1172
+ }
1173
+
1174
+ /** td mouseleave */
1175
+ function onCellMouseLeave(e: MouseEvent, row: DT, col: StkTableColumn<DT>) {
1176
+ emits('cell-mouseleave', e, row, col);
1177
+ }
1178
+
1179
+ /** td mouseover event */
1180
+ function onCellMouseOver(e: MouseEvent, row: DT, col: StkTableColumn<DT>) {
1181
+ emits('cell-mouseover', e, row, col);
1182
+ }
1183
+
1184
+ function onCellMouseDown(e: MouseEvent, row: DT, col: StkTableColumn<DT>, rowIndex: number) {
1185
+ emits('cell-mousedown', e, row, col, { rowIndex });
1186
+ }
1187
+
1188
+ /**
1189
+ * 鼠标滚轮事件监听。代理滚轮事件,防止滚动过快出现白屏。
1190
+ * @param e
1191
+ */
1192
+ function onTableWheel(e: WheelEvent) {
1193
+ if (props.smoothScroll) {
1194
+ return;
1195
+ }
1196
+ if (isColResizing.value) {
1197
+ // 正在调整列宽时,不允许用户滚动
1198
+ e.stopPropagation();
1199
+ return;
1200
+ }
1201
+ // #region ---- 控制滚动,防止出现白屏--
1202
+ const dom = tableContainerRef.value;
1203
+ if (!dom) return;
1204
+ const { containerHeight, scrollTop, scrollHeight } = virtualScroll.value;
1205
+ const { containerWidth, scrollLeft, scrollWidth } = virtualScrollX.value;
1206
+ /** 是否滚动在下面 */
1207
+ const isScrollBottom = scrollHeight - containerHeight - scrollTop < 10;
1208
+ /** 是否滚动在右侧 */
1209
+ const isScrollRight = scrollWidth - containerWidth - scrollLeft < 10;
1210
+ const { deltaY, deltaX } = e;
1211
+
1212
+ /**
1213
+ * 只有虚拟滚动时,才要用 wheel 代理scroll,防止滚动过快导致的白屏。
1214
+ * 滚动条在边界情况时,not preventDefault 。因为会阻塞父级滚动条滚动。
1215
+ */
1216
+ if (virtual_on && deltaY) {
1217
+ if ((deltaY > 0 && !isScrollBottom) || (deltaY < 0 && scrollTop > 0)) {
1218
+ e.preventDefault();
1219
+ }
1220
+ dom.scrollTop += deltaY;
1221
+ }
1222
+ if (virtualX_on && deltaX) {
1223
+ if ((deltaX > 0 && !isScrollRight) || (deltaX < 0 && scrollLeft > 0)) {
1224
+ e.preventDefault();
1225
+ }
1226
+ dom.scrollLeft += deltaX;
1227
+ }
1228
+ //#endregion
1229
+ }
1230
+
1231
+ /**
1232
+ * 滚动条监听
1233
+ * @param e scrollEvent
1234
+ */
1235
+ function onTableScroll(e: Event) {
1236
+ if (!e?.target) return;
1237
+
1238
+ const { scrollTop, scrollLeft } = e.target as HTMLElement;
1239
+ const { scrollTop: vScrollTop, startIndex, endIndex } = virtualScroll.value;
1240
+ const { scrollLeft: vScrollLeft } = virtualScrollX.value;
1241
+ const isYScroll = scrollTop !== vScrollTop;
1242
+ const isXScroll = scrollLeft !== vScrollLeft;
1243
+
1244
+ // 纵向滚动有变化
1245
+ if (isYScroll) {
1246
+ updateVirtualScrollY(scrollTop);
1247
+ }
1248
+
1249
+ // 横向滚动有变化
1250
+ if (isXScroll) {
1251
+ if (virtualX_on.value) {
1252
+ updateVirtualScrollX(scrollLeft);
1253
+ } else {
1254
+ // 非虚拟滚动也记录一下滚动条位置。用于判断isXScroll
1255
+ virtualScrollX.value.scrollLeft = scrollLeft;
1256
+ }
1257
+ updateFixedShadow(virtualScrollX);
1258
+ }
1259
+
1260
+ if (isYScroll) {
1261
+ emits('scroll', e, { startIndex, endIndex });
1262
+ }
1263
+ if (isXScroll) {
1264
+ emits('scroll-x', e);
1265
+ }
1266
+ }
1267
+
1268
+ /** tr hover */
1269
+ function onTrMouseOver(_e: MouseEvent, row: DT) {
1270
+ if (currentHoverRow === row) return;
1271
+ currentHoverRow = row;
1272
+ currentHoverRowKey.value = rowKeyGen(row);
1273
+ }
1274
+
1275
+ /**
1276
+ * 选中一行
1277
+ * @param {string} rowKeyOrRow selected rowKey, undefined to unselect
1278
+ * @param {boolean} option.silent if set true not emit `current-change`. default:false
1279
+ */
1280
+ function setCurrentRow(rowKeyOrRow: string | undefined | DT, option = { silent: false }) {
1281
+ if (!dataSourceCopy.value.length) return;
1282
+ const select = rowKeyOrRow !== void 0;
1283
+ if (!select) {
1284
+ currentRow.value = void 0;
1285
+ currentRowKey.value = void 0;
1286
+ } else if (typeof rowKeyOrRow === 'string') {
1287
+ const row = dataSourceCopy.value.find(it => rowKeyGen(it) === rowKeyOrRow);
1288
+ if (!row) {
1289
+ console.warn('setCurrentRow failed.rowKey:', rowKeyOrRow);
1290
+ return;
1291
+ }
1292
+ currentRow.value = row;
1293
+ currentRowKey.value = rowKeyOrRow;
1294
+ } else {
1295
+ currentRow.value = rowKeyOrRow;
1296
+ currentRowKey.value = rowKeyGen(rowKeyOrRow);
1297
+ }
1298
+ if (!option.silent) {
1299
+ emits('current-change', /** no Event */ null, currentRow.value, { select });
1300
+ }
1301
+ }
1302
+
1303
+ /**
1304
+ * set highlight active cell (props.cellActive=true)
1305
+ * @param row row if undefined, clear highlight
1306
+ * @param col column
1307
+ * @param option.silent if emit current-change. default:false(not emit `current-change`)
1308
+ */
1309
+ function setSelectedCell(row?: DT, col?: StkTableColumn<DT>, option = { silent: false }) {
1310
+ if (!dataSourceCopy.value.length) return;
1311
+ const select = row !== void 0 && col !== void 0;
1312
+ currentSelectedCellKey.value = select ? cellKeyGen(row, col) : void 0;
1313
+ if (!option.silent) {
1314
+ emits('cell-selected', /** no Event */ null, { row, col, select });
1315
+ }
1316
+ }
1317
+
1318
+ /**
1319
+ * 设置表头排序状态。
1320
+ * @param colKey 列唯一键字段。如果你想要取消排序状态,请使用`resetSorter`
1321
+ * @param order 正序倒序
1322
+ * @param option.sortOption 指定排序参数。同 StkTableColumn 中排序相关字段。建议从columns中find得到。
1323
+ * @param option.sort 是否触发排序-默认true
1324
+ * @param option.silent 是否禁止触发回调-默认true
1325
+ * @param option.force 是否触发排序-默认true
1326
+ * @return 表格数据
1327
+ */
1328
+ function setSorter(colKey: string, order: Order, option: { sortOption?: SortOption<DT>; force?: boolean; silent?: boolean; sort?: boolean } = {}) {
1329
+ const newOption = { silent: true, sortOption: null, sort: true, ...option };
1330
+ sortCol.value = colKey;
1331
+ sortOrderIndex.value = sortSwitchOrder.indexOf(order);
1332
+ const colKeyGenValue = colKeyGen.value;
1333
+
1334
+ if (newOption.sort && dataSourceCopy.value?.length) {
1335
+ // 如果表格有数据,则进行排序
1336
+ const column = newOption.sortOption || tableHeaderLast.value.find(it => colKeyGenValue(it) === sortCol.value);
1337
+ if (column) onColumnSort(column, false, { force: option.force ?? true, emit: !newOption.silent });
1338
+ else console.warn('Can not find column by key:', sortCol.value);
1339
+ }
1340
+ return dataSourceCopy.value;
1341
+ }
1342
+
1343
+ function resetSorter() {
1344
+ sortCol.value = void 0;
1345
+ sortOrderIndex.value = 0;
1346
+ dataSourceCopy.value = props.dataSource.slice();
1347
+ }
1348
+
1349
+ /**
1350
+ * set scroll bar position
1351
+ * @param top null to not change
1352
+ * @param left null to not change
1353
+ */
1354
+ function scrollTo(top: number | null = 0, left: number | null = 0) {
1355
+ if (!tableContainerRef.value) return;
1356
+ if (top !== null) tableContainerRef.value.scrollTop = top;
1357
+ if (left !== null) tableContainerRef.value.scrollLeft = left;
1358
+ }
1359
+
1360
+ /** get current table data */
1361
+ function getTableData() {
1362
+ return toRaw(dataSourceCopy.value);
1363
+ }
1364
+
1365
+ /**
1366
+ * get current sort info
1367
+ * @return {{key:string,order:Order}[]}
1368
+ */
1369
+ function getSortColumns() {
1370
+ const sortOrder = sortSwitchOrder[sortOrderIndex.value];
1371
+ if (!sortOrder) return [];
1372
+ return [{ key: sortCol.value, order: sortOrder }];
1373
+ }
1374
+
1375
+ /** click expended icon to toggle expand row */
1376
+ function toggleExpandRow(row: DT, col: StkTableColumn<DT>) {
1377
+ const isExpand = row?.__EXPANDED__ === col ? !row?.__EXPANDED__ : true;
1378
+ setRowExpand(row, isExpand, { col });
1379
+ }
1380
+
1381
+ /**
1382
+ *
1383
+ * @param rowKeyOrRow rowKey or row
1384
+ * @param expand expand or collapse
1385
+ * @param data { col?: StkTableColumn<DT> }
1386
+ * @param data.silent if set true, not emit `toggle-row-expand`, default:false
1387
+ */
1388
+ function setRowExpand(rowKeyOrRow: string | undefined | DT, expand?: boolean, data?: { col?: StkTableColumn<DT>; silent?: boolean }) {
1389
+ let rowKey: string;
1390
+ if (typeof rowKeyOrRow === 'string') {
1391
+ rowKey = rowKeyOrRow;
1392
+ } else {
1393
+ rowKey = rowKeyGen(rowKeyOrRow);
1394
+ }
1395
+ const tempData = dataSourceCopy.value.slice();
1396
+ const index = tempData.findIndex(it => rowKeyGen(it) === rowKey);
1397
+ if (index === -1) {
1398
+ console.warn('expandRow failed.rowKey:', rowKey);
1399
+ return;
1400
+ }
1401
+
1402
+ // delete other expanded row below the target row
1403
+ for (let i = index + 1; i < tempData.length; i++) {
1404
+ const item: PrivateRowDT = tempData[i];
1405
+ const rowKey = item.__ROW_KEY__;
1406
+ if (rowKey?.startsWith(EXPANDED_ROW_KEY_PREFIX)) {
1407
+ tempData.splice(i, 1);
1408
+ i--;
1409
+ } else {
1410
+ break;
1411
+ }
1412
+ }
1413
+
1414
+ const row = tempData[index];
1415
+ const col = data?.col || null;
1416
+
1417
+ if (expand) {
1418
+ // insert new expanded row
1419
+ const newExpandRow: ExpandedRow = {
1420
+ __ROW_KEY__: EXPANDED_ROW_KEY_PREFIX + rowKey,
1421
+ __EXPANDED_ROW__: row,
1422
+ __EXPANDED_COL__: col,
1423
+ };
1424
+ tempData.splice(index + 1, 0, newExpandRow);
1425
+ }
1426
+
1427
+ if (row) {
1428
+ row.__EXPANDED__ = expand ? col : null;
1429
+ }
1430
+
1431
+ dataSourceCopy.value = tempData;
1432
+ if (!data?.silent) {
1433
+ emits('toggle-row-expand', { expanded: Boolean(expand), row, col });
1434
+ }
1435
+ }
1436
+
1437
+ defineExpose({
1438
+ /**
1439
+ * 重新计算虚拟列表宽高
1440
+ *
1441
+ * en: calc virtual scroll x & y info
1442
+ * @see {@link initVirtualScroll}
1443
+ */
1444
+ initVirtualScroll,
1445
+ /**
1446
+ * 重新计算虚拟列表宽度
1447
+ *
1448
+ * en: calc virtual scroll x
1449
+ * @see {@link initVirtualScrollX}
1450
+ */
1451
+ initVirtualScrollX,
1452
+ /**
1453
+ * 重新计算虚拟列表高度
1454
+ *
1455
+ * en: calc virtual scroll y
1456
+ * @see {@link initVirtualScrollY}
1457
+ */
1458
+ initVirtualScrollY,
1459
+ /**
1460
+ * 选中一行
1461
+ *
1462
+ * en:select a row
1463
+ * @see {@link setCurrentRow}
1464
+ */
1465
+ setCurrentRow,
1466
+ /**
1467
+ * 取消选中单元格
1468
+ *
1469
+ * en: set highlight active cell (props.cellActive=true)
1470
+ * @see {@link setSelectedCell}
1471
+ */
1472
+ setSelectedCell,
1473
+ /**
1474
+ * 设置高亮单元格
1475
+ *
1476
+ * en: Set highlight cell
1477
+ * @see {@link setHighlightDimCell}
1478
+ */
1479
+ setHighlightDimCell,
1480
+ /**
1481
+ * 设置高亮行
1482
+ *
1483
+ * en: Set highlight row
1484
+ * @see {@link setHighlightDimRow}
1485
+ */
1486
+ setHighlightDimRow,
1487
+ /**
1488
+ * 表格排序列colKey
1489
+ *
1490
+ * en: Table sort column colKey
1491
+ */
1492
+ sortCol,
1493
+ /**
1494
+ * 表格排序列顺序
1495
+ *
1496
+ * en: get current sort info
1497
+ * @see {@link getSortColumns}
1498
+ */
1499
+ getSortColumns,
1500
+ /**
1501
+ * 设置表头排序状态
1502
+ *
1503
+ * en: Set the sort status of the table header
1504
+ * @see {@link setSorter}
1505
+ */
1506
+ setSorter,
1507
+ /**
1508
+ * 重置sorter状态
1509
+ *
1510
+ * en: Reset the sorter status
1511
+ * @see {@link resetSorter}
1512
+ */
1513
+ resetSorter,
1514
+ /**
1515
+ * 滚动至
1516
+ *
1517
+ * en: Scroll to
1518
+ * @see {@link scrollTo}
1519
+ */
1520
+ scrollTo,
1521
+ /**
1522
+ * 获取表格数据
1523
+ *
1524
+ * en: Get table data
1525
+ * @see {@link getTableData}
1526
+ */
1527
+ getTableData,
1528
+ /**
1529
+ * 设置展开的行
1530
+ *
1531
+ * en: Set expanded rows
1532
+ * @see {@link setRowExpand}
1533
+ */
1534
+ setRowExpand,
1535
+ /**
1536
+ * 不定行高时,如果行高有变化,则调用此方法更新行高。
1537
+ *
1538
+ * en: When the row height is not fixed, call this method to update the row height if the row height changes.
1539
+ * @see {@link setAutoHeight}
1540
+ */
1541
+ setAutoHeight,
1542
+ /**
1543
+ * 清除所有行高
1544
+ *
1545
+ * en: Clear all row heights
1546
+ * @see {@link clearAllAutoHeight}
1547
+ */
1548
+ clearAllAutoHeight,
1549
+ });
1550
+ </script>