stk-table-vue 0.2.0 → 0.2.2
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.
- package/README.md +57 -18
- package/lib/src/StkTable/StkTable.vue.d.ts +27 -10
- package/lib/src/StkTable/types/index.d.ts +8 -3
- package/lib/src/StkTable/useAutoResize.d.ts +2 -3
- package/lib/src/StkTable/useThDrag.d.ts +4 -1
- package/lib/src/StkTable/utils.d.ts +4 -2
- package/lib/stk-table-vue.js +439 -488
- package/lib/style.css +7 -3
- package/package.json +2 -2
- package/src/StkTable/StkTable.vue +29 -12
- package/src/StkTable/style.less +7 -5
- package/src/StkTable/types/index.ts +9 -3
- package/src/StkTable/useAutoResize.ts +2 -3
- package/src/StkTable/useColResize.ts +3 -3
- package/src/StkTable/useFixedCol.ts +1 -1
- package/src/StkTable/useFixedStyle.ts +4 -3
- package/src/StkTable/useHighlight.ts +2 -0
- package/src/StkTable/useThDrag.ts +37 -8
- package/src/StkTable/useVirtualScroll.ts +6 -12
- package/src/StkTable/utils.ts +55 -20
package/lib/style.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@keyframes
|
|
1
|
+
@keyframes stk-table-dim{
|
|
2
2
|
from{
|
|
3
3
|
background-color:var(--highlight-color);
|
|
4
4
|
}
|
|
@@ -32,8 +32,11 @@
|
|
|
32
32
|
overflow:auto;
|
|
33
33
|
display:flex;
|
|
34
34
|
flex-direction:column;
|
|
35
|
+
box-sizing:border-box;
|
|
35
36
|
border-left:1px solid #e8e8f4;
|
|
36
37
|
border-left:1px solid var(--border-color);
|
|
38
|
+
background-image:linear-gradient(180deg, #e8e8f4 1px, transparent 1px), linear-gradient(270deg, #e8e8f4 1px, transparent 1px), linear-gradient(0deg, #e8e8f4 1px, transparent 1px);
|
|
39
|
+
background-image:var(--bg-border-top), var(--bg-border-right), var(--bg-border-bottom);
|
|
37
40
|
}
|
|
38
41
|
.stk-table.dark{
|
|
39
42
|
--th-bgc:#202029;
|
|
@@ -56,6 +59,7 @@
|
|
|
56
59
|
}
|
|
57
60
|
.stk-table.headless{
|
|
58
61
|
border-top:1px solid var(--border-color);
|
|
62
|
+
background-image:var(--bg-border-right), var(--bg-border-bottom);
|
|
59
63
|
}
|
|
60
64
|
.stk-table.col-resizable .stk-table-main{
|
|
61
65
|
width:-moz-fit-content !important;
|
|
@@ -144,7 +148,7 @@
|
|
|
144
148
|
background-color:inherit;
|
|
145
149
|
}
|
|
146
150
|
.stk-table .stk-table-main td.highlight-cell{
|
|
147
|
-
animation:
|
|
151
|
+
animation:stk-table-dim 2s linear;
|
|
148
152
|
}
|
|
149
153
|
.stk-table .stk-table-main td.text-overflow .table-cell-wrapper{
|
|
150
154
|
white-space:nowrap;
|
|
@@ -243,7 +247,7 @@
|
|
|
243
247
|
height:var(--row-height);
|
|
244
248
|
}
|
|
245
249
|
.stk-table .stk-table-main tbody tr.highlight-row{
|
|
246
|
-
animation:
|
|
250
|
+
animation:stk-table-dim 2s linear;
|
|
247
251
|
}
|
|
248
252
|
.stk-table .stk-table-main tbody tr.hover,
|
|
249
253
|
.stk-table .stk-table-main tbody tr:hover{
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stk-table-vue",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Simple realtime virtual table for vue3&vue2.7",
|
|
5
5
|
"main": "./lib/stk-table-vue.js",
|
|
6
6
|
"types": "./lib/StkTable/index.d.ts",
|
|
7
7
|
"packageManager": "pnpm@8.14.3",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
v-for="(col, colIndex) in virtualX_on && rowIndex === tableHeaders.length - 1 ? virtualX_columnPart : row"
|
|
58
58
|
:key="col.dataIndex"
|
|
59
59
|
:data-col-key="colKeyGen(col)"
|
|
60
|
-
:draggable="
|
|
60
|
+
:draggable="isHeaderDraggable(col) ? 'true' : 'false'"
|
|
61
61
|
:rowspan="virtualX_on ? 1 : col.rowSpan"
|
|
62
62
|
:colspan="col.colSpan"
|
|
63
63
|
:style="getCellStyle(1, col, rowIndex)"
|
|
@@ -180,7 +180,7 @@
|
|
|
180
180
|
</div>
|
|
181
181
|
</template>
|
|
182
182
|
|
|
183
|
-
<script setup lang="
|
|
183
|
+
<script setup lang="ts">
|
|
184
184
|
/**
|
|
185
185
|
* @author JA+
|
|
186
186
|
* 不支持低版本浏览器非虚拟滚动表格的表头固定,列固定,因为会卡。
|
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
*/
|
|
192
192
|
import { CSSProperties, onMounted, ref, shallowRef, toRaw, watch } from 'vue';
|
|
193
193
|
import { Default_Row_Height } from './const';
|
|
194
|
-
import { Order, SortOption, StkTableColumn, UniqKey } from './types/index';
|
|
194
|
+
import { Order, SortConfig, SortOption, SortState, StkTableColumn, UniqKey } from './types/index';
|
|
195
195
|
import { useAutoResize } from './useAutoResize';
|
|
196
196
|
import { useColResize } from './useColResize';
|
|
197
197
|
import { useFixedCol } from './useFixedCol';
|
|
@@ -252,8 +252,8 @@ const props = withDefaults(
|
|
|
252
252
|
showOverflow?: boolean;
|
|
253
253
|
/** 是否增加行hover class */
|
|
254
254
|
showTrHoverClass?: boolean;
|
|
255
|
-
/**
|
|
256
|
-
headerDrag?: boolean;
|
|
255
|
+
/** 表头是否可拖动。支持回调函数。 */
|
|
256
|
+
headerDrag?: boolean | ((col: StkTableColumn<DT>) => boolean);
|
|
257
257
|
/**
|
|
258
258
|
* 给行附加className<br>
|
|
259
259
|
* FIXME: 是否需要优化,因为不传此prop会使表格行一直执行空函数,是否有影响
|
|
@@ -285,6 +285,8 @@ const props = withDefaults(
|
|
|
285
285
|
fixedColShadow?: boolean;
|
|
286
286
|
/** 优化vue2 滚动 */
|
|
287
287
|
optimizeVue2Scroll?: boolean;
|
|
288
|
+
/** 排序配置 */
|
|
289
|
+
sortConfig?: SortConfig;
|
|
288
290
|
}>(),
|
|
289
291
|
{
|
|
290
292
|
width: '',
|
|
@@ -317,6 +319,9 @@ const props = withDefaults(
|
|
|
317
319
|
autoResize: true,
|
|
318
320
|
fixedColShadow: false,
|
|
319
321
|
optimizeVue2Scroll: false,
|
|
322
|
+
sortConfig: () => ({
|
|
323
|
+
emptyToBottom: false,
|
|
324
|
+
}),
|
|
320
325
|
},
|
|
321
326
|
);
|
|
322
327
|
|
|
@@ -325,7 +330,7 @@ const emits = defineEmits<{
|
|
|
325
330
|
* 排序变更触发
|
|
326
331
|
* ```(col: StkTableColumn<DT>, order: Order, data: DT[])```
|
|
327
332
|
*/
|
|
328
|
-
(e: 'sort-change', col: StkTableColumn<DT>, order: Order, data: DT[]): void;
|
|
333
|
+
(e: 'sort-change', col: StkTableColumn<DT>, order: Order, data: DT[], sortConfig: SortConfig): void;
|
|
329
334
|
/**
|
|
330
335
|
* 一行点击事件
|
|
331
336
|
* ```(ev: MouseEvent, row: DT)```
|
|
@@ -449,7 +454,7 @@ const { isColResizing, onThResizeMouseDown } = useColResize({
|
|
|
449
454
|
tableHeaderLast,
|
|
450
455
|
});
|
|
451
456
|
|
|
452
|
-
const { onThDragStart, onThDragOver, onThDrop } = useThDrag({ emits });
|
|
457
|
+
const { onThDragStart, onThDragOver, onThDrop, isHeaderDraggable } = useThDrag({ props, emits });
|
|
453
458
|
|
|
454
459
|
const {
|
|
455
460
|
virtualScroll,
|
|
@@ -482,7 +487,7 @@ const { getFixedStyle } = useFixedStyle({
|
|
|
482
487
|
const { setHighlightDimCell, setHighlightDimRow } = useHighlight({ props, tableContainer, rowKeyGen });
|
|
483
488
|
|
|
484
489
|
if (props.autoResize) {
|
|
485
|
-
useAutoResize({ tableContainer, initVirtualScroll,
|
|
490
|
+
useAutoResize({ tableContainer, initVirtualScroll, props, debounceMs: 200 });
|
|
486
491
|
}
|
|
487
492
|
|
|
488
493
|
/** 键盘箭头滚动 */
|
|
@@ -627,7 +632,7 @@ function rowKeyGen(row: DT) {
|
|
|
627
632
|
* 列唯一键
|
|
628
633
|
* @param col
|
|
629
634
|
*/
|
|
630
|
-
function colKeyGen(col: StkTableColumn<
|
|
635
|
+
function colKeyGen(col: StkTableColumn<DT>) {
|
|
631
636
|
return typeof props.colKey === 'function' ? props.colKey(col) : (col as any)[props.colKey];
|
|
632
637
|
}
|
|
633
638
|
|
|
@@ -690,13 +695,13 @@ function onColumnSort(col?: StkTableColumn<any>, click = true, options: { force?
|
|
|
690
695
|
sortOrderIndex.value = sortOrderIndex.value % 3;
|
|
691
696
|
|
|
692
697
|
const order = sortSwitchOrder[sortOrderIndex.value];
|
|
693
|
-
|
|
698
|
+
const sortConfig = props.sortConfig;
|
|
694
699
|
if (!props.sortRemote || options.force) {
|
|
695
|
-
dataSourceCopy.value = tableSort(col, order, props.dataSource);
|
|
700
|
+
dataSourceCopy.value = tableSort(col, order, props.dataSource, sortConfig);
|
|
696
701
|
}
|
|
697
702
|
// 只有点击才触发事件
|
|
698
703
|
if (click || options.emit) {
|
|
699
|
-
emits('sort-change', col, order, toRaw(dataSourceCopy.value));
|
|
704
|
+
emits('sort-change', col, order, toRaw(dataSourceCopy.value), sortConfig);
|
|
700
705
|
}
|
|
701
706
|
}
|
|
702
707
|
|
|
@@ -769,6 +774,9 @@ function onTableScroll(e: Event) {
|
|
|
769
774
|
updateFixedShadow();
|
|
770
775
|
if (virtualX_on.value) {
|
|
771
776
|
updateVirtualScrollX(scrollLeft);
|
|
777
|
+
} else {
|
|
778
|
+
// 非虚拟滚动也记录一下滚动条位置。用于判断isXScroll
|
|
779
|
+
virtualScrollX.value.scrollLeft = scrollLeft;
|
|
772
780
|
}
|
|
773
781
|
}
|
|
774
782
|
|
|
@@ -848,6 +856,13 @@ function getTableData() {
|
|
|
848
856
|
return toRaw(dataSourceCopy.value);
|
|
849
857
|
}
|
|
850
858
|
|
|
859
|
+
/** 获取当前排序列的信息 */
|
|
860
|
+
function getSortColumns(): SortState<DT>[] {
|
|
861
|
+
const sortOrder = sortSwitchOrder[sortOrderIndex.value];
|
|
862
|
+
if (!sortOrder) return [];
|
|
863
|
+
return [{ dataIndex: sortCol.value, order: sortOrder }];
|
|
864
|
+
}
|
|
865
|
+
|
|
851
866
|
defineExpose({
|
|
852
867
|
/** 初始化横向纵向虚拟滚动 */
|
|
853
868
|
initVirtualScroll,
|
|
@@ -863,6 +878,8 @@ defineExpose({
|
|
|
863
878
|
setHighlightDimRow,
|
|
864
879
|
/** 表格排序列dataIndex */
|
|
865
880
|
sortCol,
|
|
881
|
+
/** 获取当前排序状态 */
|
|
882
|
+
getSortColumns,
|
|
866
883
|
/** 设置排序 */
|
|
867
884
|
setSorter,
|
|
868
885
|
/** 重置排序 */
|
package/src/StkTable/style.less
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**高亮渐暗 */
|
|
2
|
-
@keyframes
|
|
2
|
+
@keyframes stk-table-dim {
|
|
3
3
|
from {
|
|
4
4
|
background-color: var(--highlight-color);
|
|
5
5
|
}
|
|
@@ -41,9 +41,11 @@
|
|
|
41
41
|
overflow: auto;
|
|
42
42
|
display: flex;
|
|
43
43
|
flex-direction: column;
|
|
44
|
+
box-sizing: border-box;
|
|
44
45
|
/* border-left: 此方案用于减少cell 中border-left 的css选择。同时利于多级表头border-left问题。利于横向滚动border-left*/
|
|
45
46
|
border-left: 1px solid var(--border-color);
|
|
46
|
-
|
|
47
|
+
/* 下面border用于表格内容不满高度时,绘制表格边界线 */
|
|
48
|
+
background-image: var(--bg-border-top), var(--bg-border-right), var(--bg-border-bottom);
|
|
47
49
|
/**深色模式 */
|
|
48
50
|
&.dark {
|
|
49
51
|
--th-bgc: #202029;
|
|
@@ -75,6 +77,7 @@
|
|
|
75
77
|
|
|
76
78
|
&.headless {
|
|
77
79
|
border-top: 1px solid var(--border-color);
|
|
80
|
+
background-image: var(--bg-border-right), var(--bg-border-bottom);
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
/* 调整列宽的话,表格宽度应当自适应 */
|
|
@@ -112,7 +115,6 @@
|
|
|
112
115
|
tr {
|
|
113
116
|
&:first-child th {
|
|
114
117
|
background-image: var(--bg-border-top), var(--bg-border-right), var(--bg-border-bottom);
|
|
115
|
-
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
}
|
|
@@ -209,7 +211,7 @@
|
|
|
209
211
|
}
|
|
210
212
|
|
|
211
213
|
&.highlight-cell {
|
|
212
|
-
animation:
|
|
214
|
+
animation: stk-table-dim 2s linear;
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
&.text-overflow {
|
|
@@ -349,7 +351,7 @@
|
|
|
349
351
|
|
|
350
352
|
/* td inherit tr bgc*/
|
|
351
353
|
&.highlight-row {
|
|
352
|
-
animation:
|
|
354
|
+
animation: stk-table-dim 2s linear;
|
|
353
355
|
}
|
|
354
356
|
|
|
355
357
|
/* &.highlight-row-transition {
|
|
@@ -21,11 +21,11 @@ export type StkTableColumn<T extends Record<string, any>> = {
|
|
|
21
21
|
/** 筛选 */
|
|
22
22
|
sorter?: Sorter<T>;
|
|
23
23
|
/** 列宽。横向虚拟滚动时必须设置。 */
|
|
24
|
-
width?: string;
|
|
24
|
+
width?: string | number;
|
|
25
25
|
/** 最小列宽。非x虚拟滚动生效。 */
|
|
26
|
-
minWidth?: string;
|
|
26
|
+
minWidth?: string | number;
|
|
27
27
|
/** 最大列宽。非x虚拟滚动生效。 */
|
|
28
|
-
maxWidth?: string;
|
|
28
|
+
maxWidth?: string | number;
|
|
29
29
|
/**th class */
|
|
30
30
|
headerClassName?: string;
|
|
31
31
|
/** td class */
|
|
@@ -68,3 +68,9 @@ export type SortState<T> = {
|
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
export type UniqKey = string | ((param: any) => string);
|
|
71
|
+
|
|
72
|
+
/** 排序配置 */
|
|
73
|
+
export type SortConfig = {
|
|
74
|
+
/** 空值始终排在列表末尾 */
|
|
75
|
+
emptyToBottom?: boolean;
|
|
76
|
+
};
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { Ref, onBeforeUnmount, onMounted, watch } from 'vue';
|
|
2
2
|
|
|
3
3
|
type Options = {
|
|
4
|
+
props: any;
|
|
4
5
|
tableContainer: Ref<HTMLElement | undefined>;
|
|
5
6
|
initVirtualScroll: () => void;
|
|
6
|
-
scrollTo: () => void;
|
|
7
|
-
props: any;
|
|
8
7
|
/** 防抖延时 */
|
|
9
8
|
debounceMs: number;
|
|
10
9
|
};
|
|
@@ -12,7 +11,7 @@ type Options = {
|
|
|
12
11
|
* 窗口变化自动重置虚拟滚动
|
|
13
12
|
* @param param0
|
|
14
13
|
*/
|
|
15
|
-
export function useAutoResize({ tableContainer, initVirtualScroll,
|
|
14
|
+
export function useAutoResize({ tableContainer, initVirtualScroll, props, debounceMs }: Options) {
|
|
16
15
|
let resizeObserver: ResizeObserver | null = null;
|
|
17
16
|
|
|
18
17
|
onMounted(() => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Ref, onBeforeUnmount, onMounted, ref } from 'vue';
|
|
2
|
-
import { Default_Col_Width } from './const';
|
|
3
2
|
import { StkTableColumn } from './types';
|
|
3
|
+
import { getColWidth } from './utils';
|
|
4
4
|
|
|
5
5
|
type ColResizeState<DT extends Record<string, any>> = {
|
|
6
6
|
/** 当前被拖动的列*/
|
|
@@ -115,7 +115,7 @@ export function useColResize<DT extends Record<string, any>>({
|
|
|
115
115
|
const { lastCol, startX, startOffsetTableX } = colResizeState;
|
|
116
116
|
const { clientX } = e;
|
|
117
117
|
let moveX = clientX - startX;
|
|
118
|
-
const currentColWidth =
|
|
118
|
+
const currentColWidth = getColWidth(lastCol);
|
|
119
119
|
// 移动量不小于最小列宽
|
|
120
120
|
if (currentColWidth + moveX < props.colMinWidth) {
|
|
121
121
|
moveX = -currentColWidth;
|
|
@@ -136,7 +136,7 @@ export function useColResize<DT extends Record<string, any>>({
|
|
|
136
136
|
const moveX = clientX - startX;
|
|
137
137
|
|
|
138
138
|
// 移动量不小于最小列宽
|
|
139
|
-
let width =
|
|
139
|
+
let width = getColWidth(lastCol) + moveX;
|
|
140
140
|
if (width < props.colMinWidth) width = props.colMinWidth;
|
|
141
141
|
|
|
142
142
|
const curCol = tableHeaderLast.value.find(it => colKeyGen(it) === colKeyGen(lastCol));
|
|
@@ -31,7 +31,7 @@ export function useFixedCol<DT extends Record<string, any>>({ props, tableHeader
|
|
|
31
31
|
fixedShadowCols = [];
|
|
32
32
|
// 找到最右边的固定列 findLast
|
|
33
33
|
let lastLeftCol = null;
|
|
34
|
-
for (let i = tableHeaderLast.value.length - 1; i
|
|
34
|
+
for (let i = tableHeaderLast.value.length - 1; i >= 0; i--) {
|
|
35
35
|
const col = tableHeaderLast.value[i];
|
|
36
36
|
if (col.fixed === 'left') {
|
|
37
37
|
lastLeftCol = col;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { CSSProperties, Ref, computed } from 'vue';
|
|
2
|
-
import {
|
|
2
|
+
import { Is_Legacy_Mode } from './const';
|
|
3
3
|
import { StkTableColumn } from './types';
|
|
4
4
|
import { VirtualScrollStore, VirtualScrollXStore } from './useVirtualScroll';
|
|
5
|
+
import { getColWidth } from './utils';
|
|
5
6
|
|
|
6
7
|
type Options = {
|
|
7
8
|
props: any;
|
|
@@ -27,7 +28,7 @@ export function useFixedStyle({ props, tableHeaderLast, virtualScroll, virtualSc
|
|
|
27
28
|
const item = cols[i];
|
|
28
29
|
if (item.fixed === 'left') {
|
|
29
30
|
store[item.dataIndex] = left;
|
|
30
|
-
left +=
|
|
31
|
+
left += getColWidth(item);
|
|
31
32
|
}
|
|
32
33
|
if (!rightStartIndex && item.fixed === 'right') {
|
|
33
34
|
rightStartIndex = i;
|
|
@@ -38,7 +39,7 @@ export function useFixedStyle({ props, tableHeaderLast, virtualScroll, virtualSc
|
|
|
38
39
|
const item = cols[i];
|
|
39
40
|
if (item.fixed === 'right') {
|
|
40
41
|
store[item.dataIndex] = right;
|
|
41
|
-
right +=
|
|
42
|
+
right += getColWidth(item);
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -47,6 +47,7 @@ export function useHighlight({ props, tableContainer, rowKeyGen }: Params) {
|
|
|
47
47
|
// void rowEl.offsetHeight; // reflow
|
|
48
48
|
// rowEl.classList.add('highlight-row-transition');
|
|
49
49
|
// }
|
|
50
|
+
|
|
50
51
|
/** 经过的时间 ÷ 高亮持续时间 计算出 颜色过渡进度 (0-1) */
|
|
51
52
|
const progress = (nowTs - row._bgc_progress_ms) / Highlight_Duration;
|
|
52
53
|
// row._bgc_progress = progress;
|
|
@@ -58,6 +59,7 @@ export function useHighlight({ props, tableContainer, rowKeyGen }: Params) {
|
|
|
58
59
|
}
|
|
59
60
|
});
|
|
60
61
|
needDeleteRows.forEach((row: any) => highlightDimRows.delete(row));
|
|
62
|
+
// TODO: shallowRef 时,需要手动更新
|
|
61
63
|
|
|
62
64
|
if (highlightDimRows.size > 0) {
|
|
63
65
|
// 还有高亮的行,则下一次循环
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { StkTableColumn } from './types';
|
|
2
|
+
|
|
1
3
|
type Params = {
|
|
4
|
+
props: any;
|
|
2
5
|
emits: any;
|
|
3
6
|
};
|
|
4
7
|
/**
|
|
@@ -6,28 +9,43 @@ type Params = {
|
|
|
6
9
|
* @param param0
|
|
7
10
|
* @returns
|
|
8
11
|
*/
|
|
9
|
-
export function useThDrag({ emits }: Params) {
|
|
12
|
+
export function useThDrag<DT extends Record<string, any>>({ props, emits }: Params) {
|
|
10
13
|
let dragStartKey: string | undefined = void 0;
|
|
11
14
|
|
|
15
|
+
function findParentTH(el: HTMLElement | Node) {
|
|
16
|
+
let n: any = el;
|
|
17
|
+
while (n) {
|
|
18
|
+
if (n.tagName === 'TH') return n;
|
|
19
|
+
n = n.parentElement;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
12
22
|
/** 开始拖动记录th位置 */
|
|
13
23
|
function onThDragStart(e: MouseEvent) {
|
|
14
24
|
// const i = Array.prototype.indexOf.call(e.target.parentNode.children, e.target); // 得到是第几个子元素
|
|
15
|
-
|
|
25
|
+
const th = findParentTH(e.target as HTMLElement | Node);
|
|
26
|
+
if (!th) return;
|
|
27
|
+
|
|
28
|
+
dragStartKey = th.dataset.colKey;
|
|
16
29
|
emits('th-drag-start', dragStartKey);
|
|
17
30
|
}
|
|
18
31
|
|
|
19
32
|
function onThDragOver(e: MouseEvent) {
|
|
33
|
+
const th = findParentTH(e.target as HTMLElement | Node);
|
|
34
|
+
if (!th) return;
|
|
35
|
+
|
|
36
|
+
const isHeaderDraggable = th.getAttribute('draggable') === 'true';
|
|
37
|
+
if (!isHeaderDraggable) {
|
|
38
|
+
// 不可drag的表头不可被覆盖
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
20
41
|
e.preventDefault();
|
|
21
42
|
}
|
|
22
43
|
|
|
23
44
|
/** th拖动释放时 */
|
|
24
45
|
function onThDrop(e: MouseEvent) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (th.tagName === 'TH') break;
|
|
29
|
-
th = th.parentNode as HTMLElement;
|
|
30
|
-
}
|
|
46
|
+
const th = findParentTH(e.target as HTMLElement | Node);
|
|
47
|
+
if (!th) return;
|
|
48
|
+
|
|
31
49
|
// const i = Array.prototype.indexOf.call(th.parentNode.children, th); // 得到是第几个子元素
|
|
32
50
|
if (dragStartKey !== th.dataset.colKey) {
|
|
33
51
|
emits('col-order-change', dragStartKey, th.dataset.colKey);
|
|
@@ -35,9 +53,20 @@ export function useThDrag({ emits }: Params) {
|
|
|
35
53
|
emits('th-drop', th.dataset.colKey);
|
|
36
54
|
}
|
|
37
55
|
|
|
56
|
+
const isHeaderDragFun = typeof props.headerDrag === 'function';
|
|
57
|
+
/** 是否可拖拽 */
|
|
58
|
+
function isHeaderDraggable(col: StkTableColumn<DT>) {
|
|
59
|
+
if (isHeaderDragFun) {
|
|
60
|
+
return props.headerDrag(col);
|
|
61
|
+
} else {
|
|
62
|
+
return props.headerDrag;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
38
66
|
return {
|
|
39
67
|
onThDragStart,
|
|
40
68
|
onThDragOver,
|
|
41
69
|
onThDrop,
|
|
70
|
+
isHeaderDraggable,
|
|
42
71
|
};
|
|
43
72
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Ref, ShallowRef, computed, ref } from 'vue';
|
|
2
|
-
import {
|
|
2
|
+
import { Default_Table_Height, Default_Table_Width } from './const';
|
|
3
3
|
import { StkTableColumn } from './types';
|
|
4
|
+
import { getColWidth } from './utils';
|
|
4
5
|
|
|
5
6
|
type Option<DT extends Record<string, any>> = {
|
|
6
7
|
tableContainer: Ref<HTMLElement | undefined>;
|
|
@@ -40,11 +41,6 @@ export type VirtualScrollXStore = {
|
|
|
40
41
|
scrollLeft: number;
|
|
41
42
|
};
|
|
42
43
|
|
|
43
|
-
/**获取计算宽度 */
|
|
44
|
-
function getCalcWidth<DT extends Record<string, any>>(col: StkTableColumn<DT>) {
|
|
45
|
-
return parseInt(col.minWidth || col.width || Default_Col_Width);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
44
|
/** vue2 优化滚动回收延时 */
|
|
49
45
|
const VUE2_SCROLL_TIMEOUT_MS = 200;
|
|
50
46
|
|
|
@@ -90,9 +86,7 @@ export function useVirtualScroll<DT extends Record<string, any>>({ props, tableC
|
|
|
90
86
|
});
|
|
91
87
|
|
|
92
88
|
const virtualX_on = computed(() => {
|
|
93
|
-
return (
|
|
94
|
-
props.virtualX && tableHeaderLast.value.reduce((sum, col) => (sum += getCalcWidth(col)), 0) > virtualScrollX.value.containerWidth + 100
|
|
95
|
-
);
|
|
89
|
+
return props.virtualX && tableHeaderLast.value.reduce((sum, col) => (sum += getColWidth(col)), 0) > virtualScrollX.value.containerWidth + 100;
|
|
96
90
|
});
|
|
97
91
|
|
|
98
92
|
const virtualX_columnPart = computed(() => {
|
|
@@ -125,7 +119,7 @@ export function useVirtualScroll<DT extends Record<string, any>>({ props, tableC
|
|
|
125
119
|
for (let i = virtualScrollX.value.endIndex; i < tableHeaderLast.value.length; i++) {
|
|
126
120
|
const col = tableHeaderLast.value[i];
|
|
127
121
|
if (col.fixed !== 'right') {
|
|
128
|
-
width +=
|
|
122
|
+
width += getColWidth(col);
|
|
129
123
|
}
|
|
130
124
|
}
|
|
131
125
|
return width;
|
|
@@ -219,7 +213,7 @@ export function useVirtualScroll<DT extends Record<string, any>>({ props, tableC
|
|
|
219
213
|
const col = tableHeaderLast.value[colIndex];
|
|
220
214
|
// fixed left 不进入计算列宽
|
|
221
215
|
if (col.fixed === 'left') continue;
|
|
222
|
-
const colWidth =
|
|
216
|
+
const colWidth = getColWidth(col);
|
|
223
217
|
colWidthSum += colWidth;
|
|
224
218
|
// 列宽(非固定列)加到超过scrollLeft的时候,表示startIndex从上一个开始下标
|
|
225
219
|
if (colWidthSum >= sLeft) {
|
|
@@ -233,7 +227,7 @@ export function useVirtualScroll<DT extends Record<string, any>>({ props, tableC
|
|
|
233
227
|
let endIndex = headerLength;
|
|
234
228
|
for (let colIndex = startIndex + 1; colIndex < headerLength; colIndex++) {
|
|
235
229
|
const col = tableHeaderLast.value[colIndex];
|
|
236
|
-
colWidthSum +=
|
|
230
|
+
colWidthSum += getColWidth(col);
|
|
237
231
|
// 列宽大于容器宽度则停止
|
|
238
232
|
if (colWidthSum >= virtualScrollX.value.containerWidth) {
|
|
239
233
|
endIndex = colIndex + 1; // TODO:预渲染的列数
|
package/src/StkTable/utils.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Default_Col_Width } from './const';
|
|
2
|
+
import { Order, SortConfig, SortOption, SortState, StkTableColumn } from './types';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* 对有序数组插入新数据
|
|
@@ -62,6 +63,34 @@ function strCompare(a: string, b: string, type: 'number' | 'string'): number {
|
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
/**
|
|
67
|
+
* 分离出空数据和非空数据成两个数组
|
|
68
|
+
* @param sortOption
|
|
69
|
+
* @param targetDataSource
|
|
70
|
+
* @param isNumber 1 数组
|
|
71
|
+
* @return [值数组,空数组]
|
|
72
|
+
*/
|
|
73
|
+
function separatedData(sortOption: SortOption, targetDataSource: any[], isNumber?: boolean) {
|
|
74
|
+
const emptyArr: any[] = [];
|
|
75
|
+
const valueArr: any[] = [];
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < targetDataSource.length; i++) {
|
|
78
|
+
const row = targetDataSource[i];
|
|
79
|
+
const sortField = sortOption.sortField || sortOption.dataIndex;
|
|
80
|
+
let isEmpty = row[sortField] === null || row[sortField] === '';
|
|
81
|
+
if (isNumber) {
|
|
82
|
+
isEmpty ||= typeof row[sortField] === 'boolean' || Number.isNaN(+row[sortField]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (isEmpty) {
|
|
86
|
+
emptyArr.push(row);
|
|
87
|
+
} else {
|
|
88
|
+
valueArr.push(row);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return [valueArr, emptyArr] as const;
|
|
92
|
+
}
|
|
93
|
+
|
|
65
94
|
/**
|
|
66
95
|
* 表格排序抽离
|
|
67
96
|
* 可以在组件外部自己实现表格排序,组件配置remote,使表格不排序。
|
|
@@ -71,9 +100,11 @@ function strCompare(a: string, b: string, type: 'number' | 'string'): number {
|
|
|
71
100
|
* @param order 排序方式
|
|
72
101
|
* @param dataSource 排序的数组
|
|
73
102
|
*/
|
|
74
|
-
export function tableSort(sortOption: SortOption, order: Order, dataSource: any[]): any[] {
|
|
103
|
+
export function tableSort(sortOption: SortOption, order: Order, dataSource: any[], sortConfig: SortConfig = {}): any[] {
|
|
75
104
|
if (!dataSource?.length) return dataSource || [];
|
|
105
|
+
sortConfig = Object.assign({ emptyToBottom: false } as SortConfig, sortConfig);
|
|
76
106
|
let targetDataSource = [...dataSource];
|
|
107
|
+
|
|
77
108
|
if (typeof sortOption.sorter === 'function') {
|
|
78
109
|
const customSorterData = sortOption.sorter(targetDataSource, { order, column: sortOption });
|
|
79
110
|
if (customSorterData) targetDataSource = customSorterData;
|
|
@@ -82,36 +113,32 @@ export function tableSort(sortOption: SortOption, order: Order, dataSource: any[
|
|
|
82
113
|
let { sortType } = sortOption;
|
|
83
114
|
if (!sortType) sortType = typeof dataSource[0][sortField] as 'number' | 'string';
|
|
84
115
|
|
|
116
|
+
const [valueArr, emptyArr] = separatedData(sortOption, targetDataSource, sortType === 'number');
|
|
117
|
+
|
|
85
118
|
if (sortType === 'number') {
|
|
86
119
|
// 按数字类型排序
|
|
87
|
-
const nanArr: any[] = []; // 非数字
|
|
88
|
-
const numArr: any[] = []; // 数字
|
|
89
|
-
|
|
90
|
-
for (let i = 0; i < targetDataSource.length; i++) {
|
|
91
|
-
const row = targetDataSource[i];
|
|
92
|
-
if (row[sortField] === null || row[sortField] === '' || typeof row[sortField] === 'boolean' || Number.isNaN(+row[sortField])) {
|
|
93
|
-
nanArr.push(row);
|
|
94
|
-
} else {
|
|
95
|
-
numArr.push(row);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
120
|
// 非数字当作最小值处理
|
|
99
121
|
if (order === 'asc') {
|
|
100
|
-
|
|
101
|
-
targetDataSource = [...
|
|
122
|
+
valueArr.sort((a, b) => +a[sortField] - +b[sortField]);
|
|
123
|
+
targetDataSource = [...emptyArr, ...valueArr];
|
|
102
124
|
} else {
|
|
103
|
-
|
|
104
|
-
targetDataSource = [...
|
|
125
|
+
valueArr.sort((a, b) => +b[sortField] - +a[sortField]);
|
|
126
|
+
targetDataSource = [...valueArr, ...emptyArr];
|
|
105
127
|
}
|
|
106
|
-
// targetDataSource = [...numArr, ...nanArr]; // 非数字不进入排序,一直排在最后
|
|
107
128
|
} else {
|
|
108
129
|
// 按string 排序
|
|
109
130
|
if (order === 'asc') {
|
|
110
|
-
|
|
131
|
+
valueArr.sort((a, b) => String(a[sortField]).localeCompare(b[sortField]));
|
|
132
|
+
targetDataSource = [...emptyArr, ...valueArr];
|
|
111
133
|
} else {
|
|
112
|
-
|
|
134
|
+
valueArr.sort((a, b) => String(a[sortField]).localeCompare(b[sortField]) * -1);
|
|
135
|
+
targetDataSource = [...valueArr, ...emptyArr];
|
|
113
136
|
}
|
|
114
137
|
}
|
|
138
|
+
|
|
139
|
+
if (sortConfig.emptyToBottom) {
|
|
140
|
+
targetDataSource = [...valueArr, ...emptyArr];
|
|
141
|
+
}
|
|
115
142
|
}
|
|
116
143
|
return targetDataSource;
|
|
117
144
|
}
|
|
@@ -126,3 +153,11 @@ export function howDeepTheHeader(arr: StkTableColumn<any>[], level = 1) {
|
|
|
126
153
|
});
|
|
127
154
|
return Math.max(...levels);
|
|
128
155
|
}
|
|
156
|
+
|
|
157
|
+
/** 获取列宽 */
|
|
158
|
+
export function getColWidth(col: StkTableColumn<any> | null): number {
|
|
159
|
+
if (typeof col?.width === 'number') {
|
|
160
|
+
return Math.floor(col.width ?? Default_Col_Width);
|
|
161
|
+
}
|
|
162
|
+
return parseInt(col?.width ?? Default_Col_Width);
|
|
163
|
+
}
|