stk-table-vue 0.8.14 → 0.9.0-beta.1

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