stk-table-vue 0.0.1-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.
@@ -0,0 +1,26 @@
1
+ export const Default_Col_Width = '100';
2
+
3
+ export const Default_Table_Height = 100;
4
+ export const Default_Table_Width = 200;
5
+
6
+ /** 高亮背景色 */
7
+ export const Highlight_Color = {
8
+ light: { from: '#71a2fd', to: '#fff' },
9
+ dark: { from: '#1e4c99', to: '#181c21' },
10
+ };
11
+ /** 高亮持续时间 */
12
+ export const Highlight_Duration = 2000;
13
+ /** 高亮变更频率 */
14
+ export const Highlight_Color_Change_Freq = 100;
15
+
16
+ let _chromeVersion = 0;
17
+ try {
18
+ const userAgent = navigator.userAgent.match(/chrome\/\d+/i);
19
+ if (userAgent) {
20
+ _chromeVersion = +userAgent[0].split('/')[1];
21
+ }
22
+ } catch (e) {
23
+ console.error('Cannot get Chrome version', e);
24
+ }
25
+ /** 是否兼容低版本模式 */
26
+ export const Is_Legacy_Mode = _chromeVersion < 56;
@@ -0,0 +1,2 @@
1
+ export { default as StkTable } from './StkTable.vue';
2
+ export { tableSort, insertToOrderedArray } from './utils';
@@ -0,0 +1,109 @@
1
+ import { Component, VNode } from 'vue';
2
+
3
+ type Sorter = boolean | Function;
4
+
5
+ export type StkTableColumn<T extends Record<string, any>> = {
6
+ dataIndex: keyof T & string;
7
+ title?: string;
8
+ align?: 'right' | 'left' | 'center';
9
+ headerAlign?: 'right' | 'left' | 'center';
10
+ sorter?: Sorter;
11
+ width?: string;
12
+ minWidth?: string;
13
+ maxWidth?: string;
14
+ headerClassName?: string;
15
+ className?: string;
16
+ sortField?: keyof T;
17
+ sortType?: 'number' | 'string';
18
+ fixed?: 'left' | 'right' | null;
19
+
20
+ /** private */ rowSpan?: number;
21
+ /** private */ colSpan?: number;
22
+ customCell?: Component | VNode;
23
+ customHeaderCell?: Component | VNode;
24
+ children?: StkTableColumn<T>[];
25
+ };
26
+
27
+ export type SortOption = Pick<StkTableColumn<any>, 'sorter' | 'dataIndex' | 'sortField' | 'sortType'>;
28
+
29
+ export type SortState = {
30
+ dataIndex: string;
31
+ order: null | 'asc' | 'desc';
32
+ sortType?: 'number' | 'string';
33
+ };
34
+
35
+ export type UniqKey = string | ((param: any) => string);
36
+
37
+ export type StkProps = Partial<{
38
+ width: string;
39
+
40
+ /** 最小表格宽度 */
41
+ minWidth: string;
42
+
43
+ /** 表格最大宽度*/
44
+ maxWidth: string;
45
+
46
+ /** 是否隐藏表头 */
47
+ headless: boolean;
48
+
49
+ /** 主题,亮、暗 */
50
+ theme: 'light' | 'dark';
51
+
52
+ /** 虚拟滚动 */
53
+ virtual: boolean;
54
+
55
+ /** x轴虚拟滚动 */
56
+ virtualX: boolean;
57
+
58
+ /** 表格列配置 */
59
+ columns: StkTableColumn<any>[];
60
+
61
+ /** 表格数据源 */
62
+ dataSource: any[];
63
+
64
+ /** 行唯一键 */
65
+ rowKey: UniqKey;
66
+
67
+ /** 列唯一键 */
68
+ colKey: UniqKey;
69
+
70
+ /** 空值展示文字 */
71
+ emptyCellText: string;
72
+
73
+ /** 暂无数据兜底高度是否撑满 */
74
+ noDataFull: boolean;
75
+
76
+ /** 是否展示暂无数据 */
77
+ showNoData: boolean;
78
+
79
+ /** 是否服务端排序,true则不排序数据 */
80
+ sortRemote: boolean;
81
+
82
+ /** 表头是否溢出展示... */
83
+ showHeaderOverflow: boolean;
84
+
85
+ /** 表体溢出是否展示... */
86
+ showOverflow: boolean;
87
+
88
+ /** 是否增加行hover class */
89
+ showTrHoverClass: boolean;
90
+
91
+ /** 表头是否可拖动 */
92
+ headerDrag: boolean;
93
+
94
+ /**
95
+ * 给行附加className<br>
96
+ * FIXME: 是否需要优化,因为不传此prop会使表格行一直执行空函数,是否有影响
97
+ */
98
+ rowClassName: (row: any, i: number) => string;
99
+
100
+ /**
101
+ * 列宽是否可拖动<br>
102
+ * **不要设置**列minWidth,**必须**设置width<br>
103
+ * 列宽拖动时,每一列都必须要有width,且minWidth/maxWidth不生效。table width会变为"fit-content"。
104
+ */
105
+ colResizable: boolean;
106
+
107
+ /** 可拖动至最小的列宽 */
108
+ colMinWidth: number;
109
+ }>;
@@ -0,0 +1,172 @@
1
+ import { Ref, onBeforeUnmount, onMounted, ref } from 'vue';
2
+ import { Default_Col_Width } from './const';
3
+ import { StkTableColumn } from './types';
4
+
5
+ type ColResizeState = {
6
+ /** 当前被拖动的列*/
7
+ currentCol: StkTableColumn<any> | null;
8
+ /** 当前被拖动列的下标 */
9
+ currentColIndex: number;
10
+ /** 最后一个叶子列 */
11
+ lastCol: StkTableColumn<any> | null;
12
+ /** 鼠标按下开始位置 */
13
+ startX: number;
14
+ /** 鼠标按下时鼠标对于表格的偏移量 */
15
+ startOffsetTableX: 0;
16
+ };
17
+
18
+ type Params = {
19
+ props: any;
20
+ emit: any;
21
+ tableContainer: Ref<HTMLElement | undefined>;
22
+ tableHeaderLast: Ref<StkTableColumn<any>[]>;
23
+ colResizeIndicator: Ref<HTMLElement | undefined>;
24
+ colKeyGen: (p: any) => string;
25
+ };
26
+
27
+ /** 列宽拖动 */
28
+ export function useColResize({ tableContainer, tableHeaderLast, colResizeIndicator, props, emit, colKeyGen }: Params) {
29
+ /** 列宽是否在拖动 */
30
+ const isColResizing = ref(false);
31
+
32
+ /** 列宽调整状态 */
33
+ let colResizeState: ColResizeState = {
34
+ currentCol: null,
35
+ currentColIndex: 0,
36
+ lastCol: null,
37
+ startX: 0,
38
+ startOffsetTableX: 0,
39
+ };
40
+
41
+ onMounted(() => {
42
+ initColResizeEvent();
43
+ });
44
+
45
+ onBeforeUnmount(() => {
46
+ clearColResizeEvent();
47
+ });
48
+ /** 初始化列宽拖动事件 */
49
+ function initColResizeEvent() {
50
+ window.addEventListener('mousemove', onThResizeMouseMove);
51
+ window.addEventListener('mouseup', onThResizeMouseUp);
52
+ }
53
+
54
+ /** 清除列宽拖动事件 */
55
+ function clearColResizeEvent() {
56
+ window.removeEventListener('mousemove', onThResizeMouseMove);
57
+ window.removeEventListener('mouseup', onThResizeMouseUp);
58
+ }
59
+
60
+ /**
61
+ * 拖动开始
62
+ * @param e
63
+ * @param col 当前列配置
64
+ * @param isPrev 是否要上一列
65
+ */
66
+ function onThResizeMouseDown(e: MouseEvent, col: StkTableColumn<any>, isPrev = false) {
67
+ if (!tableContainer.value) return;
68
+ e.stopPropagation();
69
+ e.preventDefault();
70
+ const { clientX } = e;
71
+ const { scrollLeft } = tableContainer.value;
72
+ const { left } = tableContainer.value.getBoundingClientRect();
73
+ /** 列下标 */
74
+ let colIndex = tableHeaderLast.value.findIndex(it => colKeyGen(it) === colKeyGen(col));
75
+ if (isPrev) {
76
+ // 上一列
77
+ colIndex -= 1;
78
+ col = tableHeaderLast.value[colIndex];
79
+ }
80
+ const offsetTableX = clientX - left + scrollLeft;
81
+
82
+ // 记录拖动状态
83
+ isColResizing.value = true;
84
+ Object.assign(colResizeState, {
85
+ currentCol: col,
86
+ currentColIndex: colIndex,
87
+ lastCol: findLastChildCol(col),
88
+ startX: clientX,
89
+ startOffsetTableX: offsetTableX,
90
+ });
91
+
92
+ // 展示指示线,更新其位置
93
+ if (colResizeIndicator.value) {
94
+ colResizeIndicator.value.style.display = 'block';
95
+ colResizeIndicator.value.style.left = offsetTableX + 'px';
96
+ colResizeIndicator.value.style.top = tableContainer.value.scrollTop + 'px';
97
+ }
98
+ }
99
+
100
+ /**
101
+ * @param {MouseEvent} e
102
+ */
103
+ function onThResizeMouseMove(e: MouseEvent) {
104
+ if (!isColResizing.value) return;
105
+ e.stopPropagation();
106
+ e.preventDefault();
107
+ const { lastCol, startX, startOffsetTableX } = colResizeState;
108
+ const { clientX } = e;
109
+ let moveX = clientX - startX;
110
+ const currentColWidth = parseInt(lastCol?.width || Default_Col_Width);
111
+ // 移动量不小于最小列宽
112
+ if (currentColWidth + moveX < props.colMinWidth) {
113
+ moveX = -currentColWidth;
114
+ }
115
+
116
+ const offsetTableX = startOffsetTableX + moveX;
117
+ if (!colResizeIndicator.value) return;
118
+ colResizeIndicator.value.style.left = offsetTableX + 'px';
119
+ }
120
+
121
+ /**
122
+ * @param {MouseEvent} e
123
+ */
124
+ function onThResizeMouseUp(e: MouseEvent) {
125
+ if (!isColResizing.value) return;
126
+ const { startX, lastCol } = colResizeState;
127
+ const { clientX } = e;
128
+ const moveX = clientX - startX;
129
+
130
+ // 移动量不小于最小列宽
131
+ let width = parseInt(lastCol?.width || Default_Col_Width) + moveX;
132
+ if (width < props.colMinWidth) width = props.colMinWidth;
133
+
134
+ const curCol = tableHeaderLast.value.find(it => colKeyGen(it) === colKeyGen(lastCol));
135
+ if (!curCol) return;
136
+ curCol.width = width + 'px';
137
+
138
+ emit('update:columns', [...props.columns]);
139
+
140
+ // 隐藏指示线
141
+ if (colResizeIndicator.value) {
142
+ colResizeIndicator.value.style.display = 'none';
143
+ colResizeIndicator.value.style.left = '0';
144
+ colResizeIndicator.value.style.top = '0';
145
+ }
146
+ // 清除拖动状态
147
+ isColResizing.value = false;
148
+ colResizeState = {
149
+ currentCol: null,
150
+ currentColIndex: 0,
151
+ lastCol: null,
152
+ startX: 0,
153
+ startOffsetTableX: 0,
154
+ };
155
+ }
156
+
157
+ /**获取最后一个叶子 */
158
+ function findLastChildCol(column: StkTableColumn<any> | null) {
159
+ if (column?.children?.length) {
160
+ const lastChild = column.children.at(-1) as StkTableColumn<any>;
161
+ return findLastChildCol(lastChild);
162
+ }
163
+ return column;
164
+ }
165
+
166
+ return {
167
+ isColResizing,
168
+ onThResizeMouseDown,
169
+ onThResizeMouseMove,
170
+ onThResizeMouseUp,
171
+ };
172
+ }
@@ -0,0 +1,150 @@
1
+ import { interpolateRgb } from 'd3-interpolate';
2
+ import { Ref, computed } from 'vue';
3
+ import { Highlight_Color, Highlight_Color_Change_Freq, Highlight_Duration } from './const';
4
+
5
+ type Params = {
6
+ props: { theme: 'light' | 'dark'; virtual: boolean; dataSource: any[] };
7
+ tableContainer: Ref<HTMLElement | undefined>;
8
+ rowKeyGen: (p: any) => string;
9
+ };
10
+ /**
11
+ * 高亮单元格,行
12
+ * row中新增_bgc_progress_ms 属性控制高亮状态,_bgc控制颜色
13
+ */
14
+ export function useHighlight({ props, tableContainer, rowKeyGen }: Params) {
15
+ const highlightInter = computed(() => {
16
+ return interpolateRgb(Highlight_Color[props.theme].from, Highlight_Color[props.theme].to);
17
+ });
18
+ /** 存放高亮行的对象*/
19
+ const highlightDimRows = new Set<any>();
20
+ /** 高亮后渐暗的行定时器 */
21
+ const highlightDimRowsTimeout = new Map();
22
+ /** 高亮后渐暗的单元格定时器 */
23
+ const highlightDimCellsTimeout = new Map();
24
+ /** 是否正在计算高亮行的循环*/
25
+ let calcHighlightDimLoop = false;
26
+
27
+ /**
28
+ * 计算高亮渐暗颜色的循环
29
+ * FIXME: 相同数据源,相同引用的情况,将颜色值挂在数据源对象上,在多个表格使用相同数据源时会出问题。
30
+ */
31
+ function calcHighlightLoop() {
32
+ if (calcHighlightDimLoop) return;
33
+ calcHighlightDimLoop = true;
34
+ // js计算gradient
35
+ // raf 太频繁。TODO: 考虑setTimeout分段设置颜色,过渡靠css transition 补间
36
+ const recursion = () => {
37
+ window.setTimeout(() => {
38
+ const nowTs = Date.now();
39
+ const needDeleteRows: any = [];
40
+
41
+ highlightDimRows.forEach(row => {
42
+ // const rowKeyValue = rowKeyGen(row);
43
+ // const rowEl = tableContainer.value?.querySelector<HTMLElement>(`[data-row-key="${rowKeyValue}"]`);
44
+ // if (rowEl && row._bgc_progress === 0) {
45
+ // // 开始css transition 补间
46
+ // rowEl.classList.remove('highlight-row-transition');
47
+ // void rowEl.offsetHeight; // reflow
48
+ // rowEl.classList.add('highlight-row-transition');
49
+ // }
50
+ /** 经过的时间 ÷ 高亮持续时间 计算出 颜色过渡进度 (0-1) */
51
+ const progress = (nowTs - row._bgc_progress_ms) / Highlight_Duration;
52
+ // row._bgc_progress = progress;
53
+ if (0 < progress && progress < 1) {
54
+ row._bgc = highlightInter.value(progress);
55
+ } else {
56
+ row._bgc = ''; // 清空颜色
57
+ needDeleteRows.push(row);
58
+ }
59
+ });
60
+ needDeleteRows.forEach((row: any) => highlightDimRows.delete(row));
61
+
62
+ if (highlightDimRows.size > 0) {
63
+ // 还有高亮的行,则下一次循环
64
+ recursion();
65
+ } else {
66
+ // 没有则停止循环
67
+ calcHighlightDimLoop = false;
68
+ }
69
+ }, Highlight_Color_Change_Freq);
70
+ };
71
+ recursion();
72
+ }
73
+
74
+ /** 高亮一个单元格 */
75
+ function setHighlightDimCell(rowKeyValue: string, dataIndex: string) {
76
+ // TODO: 支持动态计算高亮颜色。不易实现。需记录每一个单元格的颜色情况。
77
+ const cellEl = tableContainer.value?.querySelector<HTMLElement>(
78
+ `[data-row-key="${rowKeyValue}"]>[data-index="${dataIndex}"]`,
79
+ );
80
+ if (!cellEl) return;
81
+ if (cellEl.classList.contains('highlight-cell')) {
82
+ cellEl.classList.remove('highlight-cell');
83
+ void cellEl.offsetHeight; // 通知浏览器重绘
84
+ }
85
+ cellEl.classList.add('highlight-cell');
86
+ window.clearTimeout(highlightDimCellsTimeout.get(rowKeyValue));
87
+ highlightDimCellsTimeout.set(
88
+ rowKeyValue,
89
+ window.setTimeout(() => {
90
+ cellEl.classList.remove('highlight-cell');
91
+ highlightDimCellsTimeout.delete(rowKeyValue);
92
+ }, Highlight_Duration),
93
+ );
94
+ }
95
+
96
+ /**
97
+ * 高亮一行
98
+ * @param rowKeyValues
99
+ */
100
+ function setHighlightDimRow(rowKeyValues: Array<string | number>) {
101
+ if (!Array.isArray(rowKeyValues)) rowKeyValues = [rowKeyValues];
102
+ if (props.virtual) {
103
+ // --------虚拟滚动用js计算颜色渐变的高亮方案
104
+ const nowTs = Date.now(); // 重置渐变进度
105
+ for (let i = 0; i < rowKeyValues.length; i++) {
106
+ const rowKeyValue = rowKeyValues[i];
107
+ const row = props.dataSource.find((it: any) => rowKeyGen(it) === rowKeyValue);
108
+ if (!row) continue;
109
+ row._bgc_progress_ms = nowTs;
110
+ // row._bgc_progress = 0;
111
+ highlightDimRows.add(row);
112
+ }
113
+ calcHighlightLoop();
114
+ } else {
115
+ // -------- 普通滚动用css @keyframes动画,实现高亮
116
+ /**是否需要重绘 */
117
+ let needRepaint = false;
118
+
119
+ const rowElTemp: HTMLTableRowElement[] = [];
120
+ for (let i = 0; i < rowKeyValues.length; i++) {
121
+ const rowKeyValue = rowKeyValues[i];
122
+ const rowEl = tableContainer.value?.querySelector<HTMLTableRowElement>(`[data-row-key="${rowKeyValue}"]`);
123
+ if (!rowEl) continue;
124
+ if (rowEl.classList.contains('highlight-row')) {
125
+ rowEl.classList.remove('highlight-row');
126
+ needRepaint = true;
127
+ }
128
+ rowElTemp.push(rowEl);
129
+ // 动画结束移除class
130
+ window.clearTimeout(highlightDimRowsTimeout.get(rowKeyValue));
131
+ highlightDimRowsTimeout.set(
132
+ rowKeyValue,
133
+ window.setTimeout(() => {
134
+ rowEl.classList.remove('highlight-row');
135
+ highlightDimRowsTimeout.delete(rowKeyValue); // 回收内存
136
+ }, Highlight_Duration),
137
+ );
138
+ }
139
+ if (needRepaint) {
140
+ void tableContainer.value?.offsetWidth; //强制浏览器重绘
141
+ }
142
+ rowElTemp.forEach(el => el.classList.add('highlight-row')); // 统一添加动画
143
+ }
144
+ }
145
+
146
+ return {
147
+ setHighlightDimRow,
148
+ setHighlightDimCell,
149
+ };
150
+ }
@@ -0,0 +1,43 @@
1
+ type Params = {
2
+ emit: any;
3
+ };
4
+ /**
5
+ * 列顺序拖动
6
+ * @param param0
7
+ * @returns
8
+ */
9
+ export function useThDrag({ emit }: Params) {
10
+ let dragStartKey: string | undefined = void 0;
11
+
12
+ /** 开始拖动记录th位置 */
13
+ function onThDragStart(e: MouseEvent) {
14
+ // const i = Array.prototype.indexOf.call(e.target.parentNode.children, e.target); // 得到是第几个子元素
15
+ dragStartKey = (e.target as HTMLElement).dataset.colKey;
16
+ emit('th-drag-start', dragStartKey);
17
+ }
18
+
19
+ function onThDragOver(e: MouseEvent) {
20
+ e.preventDefault();
21
+ }
22
+
23
+ /** th拖动释放时 */
24
+ function onThDrop(e: MouseEvent) {
25
+ let th = e.target as HTMLElement;
26
+ // 找到th元素
27
+ while (th) {
28
+ if (th.tagName === 'TH') break;
29
+ th = th.parentNode as HTMLElement;
30
+ }
31
+ // const i = Array.prototype.indexOf.call(th.parentNode.children, th); // 得到是第几个子元素
32
+ if (dragStartKey !== th.dataset.colKey) {
33
+ emit('col-order-change', dragStartKey, th.dataset.colKey);
34
+ }
35
+ emit('th-drop', th.dataset.colKey);
36
+ }
37
+
38
+ return {
39
+ onThDragStart,
40
+ onThDragOver,
41
+ onThDrop,
42
+ };
43
+ }
@@ -0,0 +1,186 @@
1
+ import { Ref, ShallowRef, computed, ref } from 'vue';
2
+ import { StkTableColumn } from './types';
3
+ import { Default_Col_Width, Default_Table_Height, Default_Table_Width } from './const';
4
+
5
+ type Option = {
6
+ tableContainer: Ref<HTMLElement | undefined>;
7
+ props: any;
8
+ dataSourceCopy: ShallowRef<any[]>;
9
+ tableHeaderLast: Ref<StkTableColumn<any>[]>;
10
+ };
11
+
12
+ export function useVirtualScroll({ tableContainer, props, dataSourceCopy, tableHeaderLast }: Option) {
13
+ const virtualScroll = ref({
14
+ containerHeight: 0,
15
+ startIndex: 0, // 数组开始位置
16
+ rowHeight: 28,
17
+ offsetTop: 0, // 表格定位上边距
18
+ scrollTop: 0, // 纵向滚动条位置,用于判断是横向滚动还是纵向
19
+ });
20
+
21
+ const virtualScrollX = ref({
22
+ containerWidth: 0,
23
+ startIndex: 0,
24
+ endIndex: 0,
25
+ offsetLeft: 0,
26
+ scrollLeft: 0, // 横向滚动位置,用于判断是横向滚动还是纵向
27
+ });
28
+
29
+ const virtual_on = computed(() => {
30
+ return props.virtual && dataSourceCopy.value.length > virtual_pageSize.value * 2;
31
+ });
32
+
33
+ const virtual_pageSize = computed(() => {
34
+ // 这里最终+1,因为headless=true无头时,需要上下各预渲染一行。
35
+ return Math.ceil(virtualScroll.value.containerHeight / virtualScroll.value.rowHeight) + 1;
36
+ });
37
+
38
+ const virtual_dataSourcePart = computed(() => {
39
+ if (!virtual_on.value) return dataSourceCopy.value;
40
+ return dataSourceCopy.value.slice(
41
+ virtualScroll.value.startIndex,
42
+ virtualScroll.value.startIndex + virtual_pageSize.value,
43
+ );
44
+ });
45
+
46
+ const virtual_offsetBottom = computed(() => {
47
+ if (!virtual_on.value) return 0;
48
+ return (
49
+ (dataSourceCopy.value.length - virtualScroll.value.startIndex - virtual_dataSourcePart.value.length) *
50
+ virtualScroll.value.rowHeight
51
+ );
52
+ });
53
+
54
+ const virtualX_on = computed(() => {
55
+ return (
56
+ props.virtualX &&
57
+ tableHeaderLast.value.reduce((sum, col) => (sum += parseInt(col.minWidth || col.width || Default_Col_Width)), 0) >
58
+ virtualScrollX.value.containerWidth * 1.5
59
+ );
60
+ });
61
+
62
+ const virtualX_columnPart = computed(() => {
63
+ if (virtualX_on.value) {
64
+ // 虚拟横向滚动,固定列要一直保持存在
65
+ const leftCols = [];
66
+ const rightCols = [];
67
+ // 左侧固定列,如果在左边不可见区。则需要拿出来放在前面
68
+ for (let i = 0; i < virtualScrollX.value.startIndex; i++) {
69
+ const col = tableHeaderLast.value[i];
70
+ if (col.fixed === 'left') leftCols.push(col);
71
+ }
72
+ // 右侧固定列,如果在右边不可见区。则需要拿出来放在后面
73
+ for (let i = virtualScrollX.value.endIndex; i < tableHeaderLast.value.length; i++) {
74
+ const col = tableHeaderLast.value[i];
75
+ if (col.fixed === 'right') rightCols.push(col);
76
+ }
77
+
78
+ const mainColumns = tableHeaderLast.value.slice(virtualScrollX.value.startIndex, virtualScrollX.value.endIndex);
79
+
80
+ return leftCols.concat(mainColumns).concat(rightCols);
81
+ }
82
+ return tableHeaderLast.value;
83
+ });
84
+
85
+ const virtualX_offsetRight = computed(() => {
86
+ if (!virtualX_on.value) return 0;
87
+ let width = 0;
88
+ for (let i = virtualScrollX.value.endIndex; i < tableHeaderLast.value.length; i++) {
89
+ const col = tableHeaderLast.value[i];
90
+ width += parseInt(col.width || col.maxWidth || col.minWidth || Default_Col_Width);
91
+ }
92
+ return width;
93
+ });
94
+
95
+ /**
96
+ * 初始化Y虚拟滚动参数
97
+ * @param {number} [height] 虚拟滚动的高度
98
+ */
99
+ function initVirtualScrollY(height?: number) {
100
+ if (virtual_on.value) {
101
+ virtualScroll.value.containerHeight =
102
+ typeof height === 'number' ? height : tableContainer.value?.offsetHeight || Default_Table_Height;
103
+ updateVirtualScrollY(tableContainer.value?.scrollTop);
104
+ // const { offsetTop, containerHeight, rowHeight } = virtualScroll.value;
105
+ // const tableAllHeight = dataSourceCopy.value.length * rowHeight;
106
+ // const overflowHeight = tableAllHeight - containerHeight;
107
+ // if (overflowHeight < offsetTop && overflowHeight > 0) {
108
+ // virtualScroll.value.offsetTop = overflowHeight;
109
+ // virtualScroll.value.startIndex = Math.ceil(overflowHeight / rowHeight);
110
+ // } else if (overflowHeight <= 0) {
111
+ // virtualScroll.value.offsetTop = 0;
112
+ // virtualScroll.value.startIndex = 0;
113
+ // }
114
+ }
115
+ }
116
+
117
+ function initVirtualScrollX() {
118
+ if (props.virtualX) {
119
+ const { offsetWidth, scrollLeft } = tableContainer.value || {};
120
+ // scrollTo(null, 0);
121
+ virtualScrollX.value.containerWidth = offsetWidth || Default_Table_Width;
122
+ updateVirtualScrollX(scrollLeft);
123
+ }
124
+ }
125
+
126
+ /** 通过滚动条位置,计算虚拟滚动的参数 */
127
+ function updateVirtualScrollY(sTop = 0) {
128
+ const { rowHeight } = virtualScroll.value;
129
+ const startIndex = Math.floor(sTop / rowHeight);
130
+ Object.assign(virtualScroll.value, {
131
+ startIndex,
132
+ offsetTop: startIndex * rowHeight, // startIndex之前的高度
133
+ });
134
+ }
135
+
136
+ /** 通过横向滚动条位置,计算横向虚拟滚动的参数 */
137
+ function updateVirtualScrollX(sLeft = 0) {
138
+ if (!tableHeaderLast.value?.length) return;
139
+ let startIndex = 0;
140
+ let offsetLeft = 0;
141
+
142
+ let colWidthSum = 0;
143
+ for (let colIndex = 0; colIndex < tableHeaderLast.value.length; colIndex++) {
144
+ startIndex++;
145
+ const col = tableHeaderLast.value[colIndex];
146
+ // fixed left 不进入计算列宽
147
+ if (col.fixed === 'left') continue;
148
+ const colWidth = parseInt(col.width || col.maxWidth || col.minWidth || Default_Col_Width);
149
+ colWidthSum += colWidth;
150
+ // 列宽(非固定列)加到超过scrollLeft的时候,表示startIndex从上一个开始下标
151
+ if (colWidthSum >= sLeft) {
152
+ offsetLeft = colWidthSum - colWidth;
153
+ startIndex--;
154
+ break;
155
+ }
156
+ }
157
+ // -----
158
+ colWidthSum = 0;
159
+ let endIndex = tableHeaderLast.value.length;
160
+ for (let colIndex = startIndex; colIndex < tableHeaderLast.value.length - 1; colIndex++) {
161
+ const col = tableHeaderLast.value[colIndex];
162
+ colWidthSum += parseInt(col.width || col.maxWidth || col.minWidth || Default_Col_Width);
163
+ // 列宽大于容器宽度则停止
164
+ if (colWidthSum >= virtualScrollX.value.containerWidth) {
165
+ endIndex = colIndex + 2; // TODO:预渲染的列数
166
+ break;
167
+ }
168
+ }
169
+ Object.assign(virtualScrollX.value, { startIndex, endIndex, offsetLeft });
170
+ }
171
+
172
+ return {
173
+ virtualScroll,
174
+ virtualScrollX,
175
+ virtual_on,
176
+ virtual_dataSourcePart,
177
+ virtual_offsetBottom,
178
+ virtualX_on,
179
+ virtualX_columnPart,
180
+ virtualX_offsetRight,
181
+ initVirtualScrollY,
182
+ initVirtualScrollX,
183
+ updateVirtualScrollY,
184
+ updateVirtualScrollX,
185
+ };
186
+ }