stk-table-vue 0.2.9 → 0.3.0
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 +79 -7
- package/lib/src/StkTable/StkTable.vue.d.ts +16 -3
- package/lib/src/StkTable/const.d.ts +2 -1
- package/lib/src/StkTable/types/index.d.ts +13 -12
- package/lib/src/StkTable/useAutoResize.d.ts +2 -2
- package/lib/src/StkTable/useColResize.d.ts +5 -5
- package/lib/src/StkTable/useFixedCol.d.ts +5 -5
- package/lib/src/StkTable/useFixedStyle.d.ts +3 -3
- package/lib/src/StkTable/useHighlight.d.ts +11 -11
- package/lib/src/StkTable/useKeyboardArrowScroll.d.ts +4 -3
- package/lib/src/StkTable/useVirtualScroll.d.ts +4 -4
- package/lib/src/StkTable/utils.d.ts +11 -3
- package/lib/stk-table-vue.js +317 -187
- package/lib/style.css +7 -2
- package/package.json +4 -5
- package/src/StkTable/StkTable.vue +70 -43
- package/src/StkTable/const.ts +3 -1
- package/src/StkTable/style.less +10 -6
- package/src/StkTable/types/index.ts +14 -6
- package/src/StkTable/useAutoResize.ts +5 -5
- package/src/StkTable/useColResize.ts +18 -18
- package/src/StkTable/useFixedCol.ts +6 -6
- package/src/StkTable/useFixedStyle.ts +11 -8
- package/src/StkTable/useHighlight.ts +252 -107
- package/src/StkTable/useKeyboardArrowScroll.ts +19 -7
- package/src/StkTable/useVirtualScroll.ts +14 -11
- package/src/StkTable/utils.ts +27 -9
- package/src/vite-env.d.ts +4 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { CSSProperties, Ref, computed } from 'vue';
|
|
1
|
+
import { CSSProperties, Ref, ShallowRef, computed } from 'vue';
|
|
2
2
|
import { IS_LEGACY_MODE } from './const';
|
|
3
3
|
import { StkTableColumn, TagType } from './types';
|
|
4
4
|
import { VirtualScrollStore, VirtualScrollXStore } from './useVirtualScroll';
|
|
5
|
-
import {
|
|
5
|
+
import { getCalculatedColWidth } from './utils';
|
|
6
6
|
|
|
7
7
|
type Options<T extends Record<string, any>> = {
|
|
8
8
|
props: any;
|
|
9
|
-
tableHeaders:
|
|
9
|
+
tableHeaders: ShallowRef<StkTableColumn<T>[][]>;
|
|
10
10
|
virtualScroll: Ref<VirtualScrollStore>;
|
|
11
11
|
virtualScrollX: Ref<VirtualScrollXStore>;
|
|
12
12
|
virtualX_on: Ref<boolean>;
|
|
@@ -42,7 +42,7 @@ export function useFixedStyle<DT extends Record<string, any>>({
|
|
|
42
42
|
} else {
|
|
43
43
|
refStore.set(item, left);
|
|
44
44
|
}
|
|
45
|
-
left +=
|
|
45
|
+
left += getCalculatedColWidth(item);
|
|
46
46
|
}
|
|
47
47
|
if (!rightStartIndex && item.fixed === 'right') {
|
|
48
48
|
rightStartIndex = i;
|
|
@@ -57,7 +57,7 @@ export function useFixedStyle<DT extends Record<string, any>>({
|
|
|
57
57
|
} else {
|
|
58
58
|
refStore.set(item, right);
|
|
59
59
|
}
|
|
60
|
-
right +=
|
|
60
|
+
right += getCalculatedColWidth(item);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
});
|
|
@@ -71,8 +71,10 @@ export function useFixedStyle<DT extends Record<string, any>>({
|
|
|
71
71
|
* @param col
|
|
72
72
|
* @param depth 深度。tagType = 1时使用
|
|
73
73
|
*/
|
|
74
|
-
function getFixedStyle(tagType: TagType, col: StkTableColumn<DT>, depth = 0): CSSProperties {
|
|
74
|
+
function getFixedStyle(tagType: TagType, col: StkTableColumn<DT>, depth = 0): CSSProperties | null {
|
|
75
75
|
const { fixed } = col;
|
|
76
|
+
if (tagType === TagType.TD && !fixed) return null;
|
|
77
|
+
|
|
76
78
|
const isFixedLeft = fixed === 'left';
|
|
77
79
|
const style: CSSProperties = {};
|
|
78
80
|
const { colKeyStore, refStore } = fixedColumnsPositionStore.value;
|
|
@@ -82,6 +84,7 @@ export function useFixedStyle<DT extends Record<string, any>>({
|
|
|
82
84
|
} else {
|
|
83
85
|
style.position = 'sticky';
|
|
84
86
|
}
|
|
87
|
+
|
|
85
88
|
if (tagType === TagType.TH) {
|
|
86
89
|
// TH
|
|
87
90
|
if (IS_LEGACY_MODE) {
|
|
@@ -89,10 +92,10 @@ export function useFixedStyle<DT extends Record<string, any>>({
|
|
|
89
92
|
} else {
|
|
90
93
|
style.top = depth * props.rowHeight + 'px';
|
|
91
94
|
}
|
|
92
|
-
style.zIndex = isFixedLeft ? '
|
|
95
|
+
style.zIndex = isFixedLeft ? '3' : '2'; // 保证固定列高于其他单元格
|
|
93
96
|
} else {
|
|
94
97
|
// TD
|
|
95
|
-
style.zIndex = isFixedLeft ? '
|
|
98
|
+
style.zIndex = isFixedLeft ? '2' : '1';
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
if (fixed === 'left' || fixed === 'right') {
|
|
@@ -1,183 +1,328 @@
|
|
|
1
1
|
import { interpolateRgb } from 'd3-interpolate';
|
|
2
|
-
import { Ref, computed
|
|
3
|
-
import { HIGHLIGHT_CELL_CLASS, HIGHLIGHT_COLOR, HIGHLIGHT_DURATION, HIGHLIGHT_FREQ, HIGHLIGHT_ROW_CLASS } from './const';
|
|
2
|
+
import { Ref, computed } from 'vue';
|
|
3
|
+
import { HIGHLIGHT_CELL_CLASS, HIGHLIGHT_COLOR, HIGHLIGHT_DURATION, HIGHLIGHT_FREQ, HIGHLIGHT_ROW_CLASS, IS_LEGACY_MODE } from './const';
|
|
4
4
|
import { HighlightConfig, UniqKey } from './types';
|
|
5
5
|
|
|
6
6
|
type Params = {
|
|
7
7
|
props: any;
|
|
8
|
-
|
|
8
|
+
stkTableId: string;
|
|
9
|
+
tableContainerRef: Ref<HTMLDivElement | undefined>;
|
|
9
10
|
};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
/** 存放高亮行信息 */
|
|
12
|
+
type HighlightDimRowStore = {
|
|
13
|
+
/** 动画开始时间戳 */
|
|
14
|
+
readonly ts: number;
|
|
15
|
+
/** 行是否可见 */
|
|
16
|
+
visible: boolean;
|
|
17
|
+
/** 动画关键帧 */
|
|
18
|
+
keyframe: Parameters<Animatable['animate']>['0'];
|
|
19
|
+
/** 动画初始持续时间 */
|
|
20
|
+
readonly duration: number;
|
|
16
21
|
};
|
|
17
22
|
|
|
18
23
|
/**
|
|
19
24
|
* 高亮单元格,行
|
|
20
|
-
* row中新增_bgc_progress_ms 属性控制高亮状态,_bgc控制颜色
|
|
21
25
|
*/
|
|
22
|
-
export function useHighlight({ props,
|
|
26
|
+
export function useHighlight({ props, stkTableId, tableContainerRef }: Params) {
|
|
23
27
|
const config: HighlightConfig = props.highlightConfig;
|
|
24
|
-
|
|
25
|
-
* 高亮行记录 key-rowKey, value-obj
|
|
26
|
-
*/
|
|
27
|
-
const highlightRowStore = ref<Record<UniqKey, HighlightRowStore>>({});
|
|
28
|
+
|
|
28
29
|
/** 持续时间 */
|
|
29
|
-
const
|
|
30
|
-
/**
|
|
31
|
-
const
|
|
30
|
+
const highlightDuration = config.duration ? config.duration * 1000 : HIGHLIGHT_DURATION;
|
|
31
|
+
/** 高亮频率(仅虚拟滚动生效) */
|
|
32
|
+
const highlightFrequency = config.fps ? 1000 / config.fps : HIGHLIGHT_FREQ;
|
|
33
|
+
/** 高亮颜色 */
|
|
32
34
|
const highlightColor = {
|
|
33
|
-
light:
|
|
34
|
-
dark:
|
|
35
|
+
light: HIGHLIGHT_COLOR.light,
|
|
36
|
+
dark: HIGHLIGHT_COLOR.dark,
|
|
35
37
|
};
|
|
38
|
+
|
|
39
|
+
/** css 高亮的次数,用于css animation steps() */
|
|
40
|
+
const highlightSteps = highlightDuration / highlightFrequency;
|
|
36
41
|
/** 高亮开始 */
|
|
37
42
|
const highlightFrom = computed(() => highlightColor[props.theme as 'light' | 'dark'].from);
|
|
38
43
|
/** 高亮结束 */
|
|
39
44
|
const highlightTo = computed(() => highlightColor[props.theme as 'light' | 'dark'].to);
|
|
40
45
|
const highlightInter = computed(() => interpolateRgb(highlightFrom.value, highlightTo.value));
|
|
41
46
|
|
|
42
|
-
/**
|
|
43
|
-
|
|
47
|
+
/**
|
|
48
|
+
* 存放高亮行的状态-使用js计算颜色
|
|
49
|
+
* @key 行唯一键
|
|
50
|
+
* @value 记录高亮开始时间
|
|
51
|
+
*/
|
|
52
|
+
const highlightDimRowsJs = new Map<UniqKey, number>();
|
|
53
|
+
/** 是否正在计算高亮行的循环-使用js计算颜色 */
|
|
54
|
+
let calcHighlightDimLoopJs = false;
|
|
55
|
+
/**
|
|
56
|
+
* 存放高亮行的状态-使用animation api实现
|
|
57
|
+
* @key 行唯一键
|
|
58
|
+
* @value 记录高亮配置
|
|
59
|
+
*/
|
|
60
|
+
const highlightDimRowsAnimation = new Map<UniqKey, HighlightDimRowStore>();
|
|
61
|
+
/** 是否正在计算高亮行的循环-使用animation api实现 */
|
|
62
|
+
let calcHighlightDimLoopAnimation = false;
|
|
63
|
+
|
|
44
64
|
/** 高亮后渐暗的行定时器 */
|
|
45
65
|
const highlightDimRowsTimeout = new Map();
|
|
46
66
|
/** 高亮后渐暗的单元格定时器 */
|
|
47
67
|
const highlightDimCellsTimeout = new Map();
|
|
48
|
-
/** 是否正在计算高亮行的循环*/
|
|
49
|
-
let calcHighlightDimLoop = false;
|
|
50
68
|
|
|
51
69
|
/**
|
|
52
70
|
* 计算高亮渐暗颜色的循环
|
|
53
|
-
* FIXME: 相同数据源,相同引用的情况,将颜色值挂在数据源对象上,在多个表格使用相同数据源时会出问题。
|
|
54
71
|
*/
|
|
55
|
-
function
|
|
56
|
-
if (
|
|
57
|
-
|
|
72
|
+
function calcRowHighlightLoop() {
|
|
73
|
+
if (calcHighlightDimLoopAnimation) return;
|
|
74
|
+
calcHighlightDimLoopAnimation = true;
|
|
75
|
+
const recursion = () => {
|
|
76
|
+
window.requestAnimationFrame(
|
|
77
|
+
() => {
|
|
78
|
+
const nowTs = Date.now();
|
|
79
|
+
highlightDimRowsAnimation.forEach((store, rowKeyValue) => {
|
|
80
|
+
const { ts, duration } = store;
|
|
81
|
+
const timeOffset = nowTs - ts;
|
|
82
|
+
if (nowTs - ts < duration) {
|
|
83
|
+
updateRowBgc(rowKeyValue, store, timeOffset);
|
|
84
|
+
} else {
|
|
85
|
+
highlightDimRowsAnimation.delete(rowKeyValue);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (highlightDimRowsAnimation.size > 0) {
|
|
90
|
+
// 还有高亮的行,则下一次循环
|
|
91
|
+
recursion();
|
|
92
|
+
} else {
|
|
93
|
+
// 没有则停止循环
|
|
94
|
+
calcHighlightDimLoopAnimation = false;
|
|
95
|
+
highlightDimRowsAnimation.clear();
|
|
96
|
+
}
|
|
97
|
+
} /* , highlightFrequency */,
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
recursion();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* js计算高亮渐暗颜色的循环
|
|
105
|
+
*/
|
|
106
|
+
function calcRowHighlightLoopJs() {
|
|
107
|
+
if (calcHighlightDimLoopJs) return;
|
|
108
|
+
calcHighlightDimLoopJs = true;
|
|
58
109
|
// js计算gradient
|
|
59
|
-
// raf 太频繁。TODO: 考虑setTimeout分段设置颜色,过渡靠css transition 补间
|
|
60
110
|
const recursion = () => {
|
|
61
111
|
window.setTimeout(() => {
|
|
62
112
|
const nowTs = Date.now();
|
|
63
|
-
|
|
64
|
-
highlightDimRowKeys.forEach(rowKeyValue => {
|
|
65
|
-
// const rowKeyValue = rowKeyGen(row);
|
|
66
|
-
// const rowEl = tableContainer.value?.querySelector<HTMLElement>(`[data-row-key="${rowKeyValue}"]`);
|
|
67
|
-
// if (rowEl && row._bgc_progress === 0) {
|
|
68
|
-
// // 开始css transition 补间
|
|
69
|
-
// rowEl.classList.remove('highlight-row-transition');
|
|
70
|
-
// void rowEl.offsetHeight; // reflow
|
|
71
|
-
// rowEl.classList.add('highlight-row-transition');
|
|
72
|
-
// }
|
|
73
|
-
const highlightItem = highlightRowStore.value[rowKeyValue];
|
|
113
|
+
highlightDimRowsJs.forEach((highlightStart, rowKeyValue) => {
|
|
74
114
|
/** 经过的时间 ÷ 高亮持续时间 计算出 颜色过渡进度 (0-1) */
|
|
75
|
-
const progress = (nowTs -
|
|
115
|
+
const progress = (nowTs - highlightStart) / highlightDuration;
|
|
116
|
+
let bgc = '';
|
|
76
117
|
if (0 < progress && progress < 1) {
|
|
77
|
-
|
|
118
|
+
bgc = highlightInter.value(progress);
|
|
78
119
|
} else {
|
|
79
|
-
|
|
80
|
-
highlightDimRowKeys.delete(rowKeyValue);
|
|
120
|
+
highlightDimRowsJs.delete(rowKeyValue);
|
|
81
121
|
}
|
|
122
|
+
updateRowBgcJs(rowKeyValue, bgc);
|
|
82
123
|
});
|
|
83
124
|
|
|
84
|
-
|
|
85
|
-
highlightRowStore.value = { ...highlightRowStore.value };
|
|
86
|
-
|
|
87
|
-
if (highlightDimRowKeys.size > 0) {
|
|
125
|
+
if (highlightDimRowsJs.size > 0) {
|
|
88
126
|
// 还有高亮的行,则下一次循环
|
|
89
127
|
recursion();
|
|
90
128
|
} else {
|
|
91
129
|
// 没有则停止循环
|
|
92
|
-
|
|
130
|
+
calcHighlightDimLoopJs = false;
|
|
131
|
+
highlightDimRowsJs.clear();
|
|
93
132
|
}
|
|
94
|
-
},
|
|
133
|
+
}, highlightFrequency);
|
|
95
134
|
};
|
|
96
135
|
recursion();
|
|
97
136
|
}
|
|
98
137
|
|
|
138
|
+
/** 高亮函数的默认参数 */
|
|
139
|
+
const defaultHighlightDimOption = {
|
|
140
|
+
keyframe: [{ backgroundColor: highlightFrom.value }, { backgroundColor: highlightTo.value }],
|
|
141
|
+
duration: highlightDuration,
|
|
142
|
+
};
|
|
143
|
+
|
|
99
144
|
/**
|
|
100
145
|
* 高亮一个单元格
|
|
101
146
|
* @param rowKeyValue 一行的key
|
|
102
147
|
* @param dataIndex 列key
|
|
103
|
-
* @param
|
|
148
|
+
* @param options.method css-使用css渲染,animation-使用animation api。默认css;
|
|
149
|
+
* @param option.className 自定义css动画的class。
|
|
150
|
+
* @param option.keyframe 同Keyframe https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Animations_API/Keyframe_Formats
|
|
151
|
+
* @param option.duration 动画时长。method='css'状态下,用于移除class,如果传入了className则需要与自定义的动画时间一致。
|
|
104
152
|
*/
|
|
105
|
-
function setHighlightDimCell(
|
|
153
|
+
function setHighlightDimCell(
|
|
154
|
+
rowKeyValue: string,
|
|
155
|
+
dataIndex: string,
|
|
156
|
+
option: { className?: string; method?: 'css' | 'animation'; keyframe?: Parameters<Animatable['animate']>['0']; duration?: number } = {},
|
|
157
|
+
) {
|
|
106
158
|
// TODO: 支持动态计算高亮颜色。不易实现。需记录每一个单元格的颜色情况。
|
|
107
|
-
const cellEl =
|
|
108
|
-
const
|
|
159
|
+
const cellEl = tableContainerRef.value?.querySelector<HTMLElement>(`[data-row-key="${rowKeyValue}"]>[data-index="${dataIndex}"]`);
|
|
160
|
+
const { className, method, duration, keyframe } = {
|
|
161
|
+
className: HIGHLIGHT_CELL_CLASS,
|
|
162
|
+
method: 'css',
|
|
163
|
+
...defaultHighlightDimOption,
|
|
164
|
+
...option,
|
|
165
|
+
};
|
|
109
166
|
if (!cellEl) return;
|
|
110
|
-
if (
|
|
111
|
-
cellEl.
|
|
112
|
-
|
|
167
|
+
if (method === 'animation') {
|
|
168
|
+
cellEl.animate(keyframe, duration);
|
|
169
|
+
} else {
|
|
170
|
+
highlightCellsInCssKeyFrame(cellEl, rowKeyValue, className, duration);
|
|
113
171
|
}
|
|
114
|
-
cellEl.classList.add(opt.className);
|
|
115
|
-
window.clearTimeout(highlightDimCellsTimeout.get(rowKeyValue));
|
|
116
|
-
highlightDimCellsTimeout.set(
|
|
117
|
-
rowKeyValue,
|
|
118
|
-
window.setTimeout(() => {
|
|
119
|
-
cellEl.classList.remove(opt.className);
|
|
120
|
-
highlightDimCellsTimeout.delete(rowKeyValue);
|
|
121
|
-
}, duration),
|
|
122
|
-
);
|
|
123
172
|
}
|
|
124
173
|
|
|
125
174
|
/**
|
|
126
175
|
* 高亮一行
|
|
127
176
|
* @param rowKeyValues 行唯一键的数组
|
|
128
|
-
* @param option.
|
|
177
|
+
* @param option.method css-使用css渲染,animation-使用animation api,js-使用js计算颜色
|
|
178
|
+
* @param option.className 自定义css动画的class。
|
|
179
|
+
* @param option.keyframe 同Keyframe,无法控制帧率。 https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Animations_API/Keyframe_Formats
|
|
180
|
+
* @param option.duration 动画时长。method='css'状态下,用于移除class,如果传入了className则需要与自定义的动画时间一致。。
|
|
129
181
|
*/
|
|
130
|
-
function setHighlightDimRow(
|
|
182
|
+
function setHighlightDimRow(
|
|
183
|
+
rowKeyValues: UniqKey[],
|
|
184
|
+
option: {
|
|
185
|
+
method?: 'css' | 'animation' | 'js';
|
|
186
|
+
/** @deprecated 请使用method */
|
|
187
|
+
useCss?: boolean;
|
|
188
|
+
className?: string;
|
|
189
|
+
keyframe?: Parameters<Animatable['animate']>['0'];
|
|
190
|
+
duration?: number;
|
|
191
|
+
} = {},
|
|
192
|
+
) {
|
|
131
193
|
if (!Array.isArray(rowKeyValues)) rowKeyValues = [rowKeyValues];
|
|
132
|
-
const { className,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
194
|
+
const { className, method, useCss, keyframe, duration } = {
|
|
195
|
+
className: HIGHLIGHT_ROW_CLASS,
|
|
196
|
+
method: props.virtual ? 'js' : 'css',
|
|
197
|
+
...defaultHighlightDimOption,
|
|
198
|
+
...option,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const nowTs = Date.now();
|
|
202
|
+
if (method === 'css' || useCss) {
|
|
203
|
+
// -------- use css keyframe
|
|
204
|
+
highlightRowsInCssKeyframe(rowKeyValues, className, duration);
|
|
205
|
+
} else if (method === 'animation') {
|
|
206
|
+
if (props.virtual) {
|
|
207
|
+
// -------- 用animation 接口实现动画
|
|
208
|
+
for (let i = 0; i < rowKeyValues.length; i++) {
|
|
209
|
+
const rowKeyValue = rowKeyValues[i];
|
|
210
|
+
const store: HighlightDimRowStore = { ts: nowTs, visible: false, keyframe, duration };
|
|
211
|
+
highlightDimRowsAnimation.set(rowKeyValue, store);
|
|
212
|
+
updateRowBgc(rowKeyValue, store, 0);
|
|
213
|
+
}
|
|
214
|
+
calcRowHighlightLoop();
|
|
215
|
+
} else {
|
|
216
|
+
// -------- use Element.animate
|
|
217
|
+
for (let i = 0; i < rowKeyValues.length; i++) {
|
|
218
|
+
const rowEl = document.getElementById(stkTableId + '-' + String(rowKeyValues[i])) as HTMLTableRowElement | null;
|
|
219
|
+
if (!rowEl) continue;
|
|
220
|
+
rowEl.animate(keyframe, duration);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
} else if (method === 'js') {
|
|
224
|
+
// -------- 用js计算颜色渐变的高亮方案
|
|
136
225
|
for (let i = 0; i < rowKeyValues.length; i++) {
|
|
137
226
|
const rowKeyValue = rowKeyValues[i];
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
bgc_progress: 0,
|
|
141
|
-
bgc_progress_ms: nowTs,
|
|
142
|
-
};
|
|
143
|
-
highlightDimRowKeys.add(rowKeyValue);
|
|
227
|
+
highlightDimRowsJs.set(rowKeyValue, nowTs);
|
|
228
|
+
updateRowBgcJs(rowKeyValue, highlightFrom.value);
|
|
144
229
|
}
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**是否需要重绘 */
|
|
149
|
-
let needRepaint = false;
|
|
230
|
+
calcRowHighlightLoopJs();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
150
233
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
rowEl.classList.remove(className);
|
|
167
|
-
highlightDimRowsTimeout.delete(rowKeyValue); // 回收内存
|
|
168
|
-
}, duration),
|
|
169
|
-
);
|
|
234
|
+
/**
|
|
235
|
+
* 使用css @keyframes动画,实现高亮行动画
|
|
236
|
+
* 此方案作为兼容方式。v0.3.0 将使用Element.animate 接口实现动画。
|
|
237
|
+
*/
|
|
238
|
+
function highlightRowsInCssKeyframe(rowKeyValues: UniqKey[], className: string, duration: number) {
|
|
239
|
+
/**是否需要重绘 */
|
|
240
|
+
let needRepaint = false;
|
|
241
|
+
const rowElTemp: HTMLTableRowElement[] = [];
|
|
242
|
+
for (let i = 0; i < rowKeyValues.length; i++) {
|
|
243
|
+
const rowKeyValue = rowKeyValues[i];
|
|
244
|
+
const rowEl = document.getElementById(stkTableId + '-' + String(rowKeyValue)) as HTMLTableRowElement | null;
|
|
245
|
+
if (!rowEl) continue;
|
|
246
|
+
if (rowEl.classList.contains(className)) {
|
|
247
|
+
rowEl.classList.remove(className);
|
|
248
|
+
needRepaint = true;
|
|
170
249
|
}
|
|
171
|
-
|
|
172
|
-
|
|
250
|
+
rowElTemp.push(rowEl);
|
|
251
|
+
// 动画结束移除class
|
|
252
|
+
window.clearTimeout(highlightDimRowsTimeout.get(rowKeyValue));
|
|
253
|
+
highlightDimRowsTimeout.set(
|
|
254
|
+
rowKeyValue,
|
|
255
|
+
window.setTimeout(() => {
|
|
256
|
+
rowEl.classList.remove(className);
|
|
257
|
+
highlightDimRowsTimeout.delete(rowKeyValue); // 回收内存
|
|
258
|
+
}, duration),
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
if (needRepaint) {
|
|
262
|
+
void tableContainerRef.value?.offsetWidth; //强制浏览器重绘
|
|
263
|
+
}
|
|
264
|
+
rowElTemp.forEach(el => el.classList.add(className)); // 统一添加动画
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 使用css @keyframes动画,实现高亮单元格动画
|
|
269
|
+
* 此方案作为兼容方式。v0.3.0 将使用Element.animate 接口实现动画。
|
|
270
|
+
*/
|
|
271
|
+
function highlightCellsInCssKeyFrame(cellEl: HTMLElement, rowKeyValue: UniqKey, className: string, duration: number) {
|
|
272
|
+
if (cellEl.classList.contains(className)) {
|
|
273
|
+
cellEl.classList.remove(className);
|
|
274
|
+
void cellEl.offsetHeight; // 通知浏览器重绘
|
|
275
|
+
}
|
|
276
|
+
cellEl.classList.add(className);
|
|
277
|
+
window.clearTimeout(highlightDimCellsTimeout.get(rowKeyValue));
|
|
278
|
+
highlightDimCellsTimeout.set(
|
|
279
|
+
rowKeyValue,
|
|
280
|
+
window.setTimeout(() => {
|
|
281
|
+
cellEl.classList.remove(className);
|
|
282
|
+
highlightDimCellsTimeout.delete(rowKeyValue);
|
|
283
|
+
}, duration),
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 更新行状态
|
|
289
|
+
* @param rowKeyValue 行唯一键
|
|
290
|
+
* @param store highlightDimRowStore 的引用对象
|
|
291
|
+
* @param timeOffset 距动画开始经过的时长
|
|
292
|
+
*/
|
|
293
|
+
function updateRowBgc(rowKeyValue: UniqKey, store: HighlightDimRowStore, timeOffset: number) {
|
|
294
|
+
const rowEl = document.getElementById(stkTableId + '-' + String(rowKeyValue));
|
|
295
|
+
const { visible, keyframe, duration: initialDuration } = store;
|
|
296
|
+
if (!rowEl) {
|
|
297
|
+
if (visible) {
|
|
298
|
+
store.visible = false; // 标记为不可见
|
|
173
299
|
}
|
|
174
|
-
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
// 只有元素 不可见 -> 可见 时才需要更新
|
|
303
|
+
if (!visible) {
|
|
304
|
+
store.visible = true; // 标记为可见
|
|
305
|
+
/** 经过的时间 ÷ 高亮持续时间 计算出 颜色过渡进度 (0-1) */
|
|
306
|
+
const iterationStart = timeOffset / initialDuration;
|
|
307
|
+
rowEl.animate(keyframe, {
|
|
308
|
+
duration: initialDuration - timeOffset,
|
|
309
|
+
/** 从什么时候开始,0-1 */
|
|
310
|
+
iterationStart,
|
|
311
|
+
/** 持续多久 0-1 */
|
|
312
|
+
iterations: 1 - iterationStart,
|
|
313
|
+
});
|
|
175
314
|
}
|
|
176
315
|
}
|
|
177
316
|
|
|
317
|
+
/** 更新行状态 */
|
|
318
|
+
function updateRowBgcJs(rowKeyValue: UniqKey, color: string) {
|
|
319
|
+
const rowEl = document.getElementById(stkTableId + '-' + String(rowKeyValue));
|
|
320
|
+
if (!rowEl) return;
|
|
321
|
+
rowEl.style.backgroundColor = color;
|
|
322
|
+
}
|
|
323
|
+
|
|
178
324
|
return {
|
|
179
|
-
|
|
180
|
-
highlightFrom,
|
|
325
|
+
highlightSteps,
|
|
181
326
|
setHighlightDimRow,
|
|
182
327
|
setHighlightDimCell,
|
|
183
328
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Ref, onBeforeUnmount, onMounted } from 'vue';
|
|
1
|
+
import { ComputedRef, Ref, ShallowRef, onBeforeUnmount, onMounted, watch } from 'vue';
|
|
2
2
|
import { StkTableColumn } from './types';
|
|
3
3
|
import { VirtualScrollStore, VirtualScrollXStore } from './useVirtualScroll';
|
|
4
4
|
|
|
@@ -10,7 +10,8 @@ type Options<DT extends Record<string, any>> = {
|
|
|
10
10
|
scrollTo: (y: number | null, x: number | null) => void;
|
|
11
11
|
virtualScroll: Ref<VirtualScrollStore>;
|
|
12
12
|
virtualScrollX: Ref<VirtualScrollXStore>;
|
|
13
|
-
tableHeaders:
|
|
13
|
+
tableHeaders: ShallowRef<StkTableColumn<DT>[][]>;
|
|
14
|
+
virtual_on: ComputedRef<boolean>;
|
|
14
15
|
};
|
|
15
16
|
/**
|
|
16
17
|
* 按下键盘箭头滚动。只有悬浮在表体上才能生效键盘。
|
|
@@ -19,24 +20,35 @@ type Options<DT extends Record<string, any>> = {
|
|
|
19
20
|
*/
|
|
20
21
|
export function useKeyboardArrowScroll<DT extends Record<string, any>>(
|
|
21
22
|
targetElement: Ref<HTMLElement | undefined>,
|
|
22
|
-
{ props, scrollTo, virtualScroll, virtualScrollX, tableHeaders }: Options<DT>,
|
|
23
|
+
{ props, scrollTo, virtualScroll, virtualScrollX, tableHeaders, virtual_on }: Options<DT>,
|
|
23
24
|
) {
|
|
24
25
|
/** 检测鼠标是否悬浮在表格体上 */
|
|
25
26
|
let isMouseOver = false;
|
|
27
|
+
watch(virtual_on, val => {
|
|
28
|
+
if (!val) {
|
|
29
|
+
removeListeners();
|
|
30
|
+
} else {
|
|
31
|
+
addEventListeners();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
onMounted(addEventListeners);
|
|
36
|
+
|
|
37
|
+
onBeforeUnmount(removeListeners);
|
|
26
38
|
|
|
27
|
-
|
|
39
|
+
function addEventListeners() {
|
|
28
40
|
window.addEventListener('keydown', handleKeydown);
|
|
29
41
|
targetElement.value?.addEventListener('mouseenter', handleMouseEnter);
|
|
30
42
|
targetElement.value?.addEventListener('mouseleave', handleMouseLeave);
|
|
31
43
|
targetElement.value?.addEventListener('mousedown', handleMouseDown);
|
|
32
|
-
}
|
|
44
|
+
}
|
|
33
45
|
|
|
34
|
-
|
|
46
|
+
function removeListeners() {
|
|
35
47
|
window.removeEventListener('keydown', handleKeydown);
|
|
36
48
|
targetElement.value?.removeEventListener('mouseenter', handleMouseEnter);
|
|
37
49
|
targetElement.value?.removeEventListener('mouseleave', handleMouseLeave);
|
|
38
50
|
targetElement.value?.removeEventListener('mousedown', handleMouseDown);
|
|
39
|
-
}
|
|
51
|
+
}
|
|
40
52
|
|
|
41
53
|
/** 键盘按下事件 */
|
|
42
54
|
function handleKeydown(e: KeyboardEvent) {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Ref, ShallowRef, computed, ref } from 'vue';
|
|
2
2
|
import { DEFAULT_TABLE_HEIGHT, DEFAULT_TABLE_WIDTH } from './const';
|
|
3
3
|
import { StkTableColumn } from './types';
|
|
4
|
-
import {
|
|
4
|
+
import { getCalculatedColWidth } from './utils';
|
|
5
5
|
|
|
6
6
|
type Option<DT extends Record<string, any>> = {
|
|
7
7
|
props: any;
|
|
8
|
-
|
|
8
|
+
tableContainerRef: Ref<HTMLElement | undefined>;
|
|
9
9
|
dataSourceCopy: ShallowRef<DT[]>;
|
|
10
|
-
tableHeaderLast:
|
|
11
|
-
tableHeaders:
|
|
10
|
+
tableHeaderLast: ShallowRef<StkTableColumn<DT>[]>;
|
|
11
|
+
tableHeaders: ShallowRef<StkTableColumn<DT>[][]>;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
/** 暂存纵向虚拟滚动的数据 */
|
|
@@ -52,7 +52,7 @@ const VUE2_SCROLL_TIMEOUT_MS = 200;
|
|
|
52
52
|
*/
|
|
53
53
|
export function useVirtualScroll<DT extends Record<string, any>>({
|
|
54
54
|
props,
|
|
55
|
-
|
|
55
|
+
tableContainerRef,
|
|
56
56
|
dataSourceCopy,
|
|
57
57
|
tableHeaderLast,
|
|
58
58
|
tableHeaders,
|
|
@@ -93,7 +93,10 @@ export function useVirtualScroll<DT extends Record<string, any>>({
|
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
const virtualX_on = computed(() => {
|
|
96
|
-
return
|
|
96
|
+
return (
|
|
97
|
+
props.virtualX &&
|
|
98
|
+
tableHeaderLast.value.reduce((sum, col) => (sum += getCalculatedColWidth(col)), 0) > virtualScrollX.value.containerWidth + 100
|
|
99
|
+
);
|
|
97
100
|
});
|
|
98
101
|
|
|
99
102
|
const virtualX_columnPart = computed(() => {
|
|
@@ -126,7 +129,7 @@ export function useVirtualScroll<DT extends Record<string, any>>({
|
|
|
126
129
|
for (let i = virtualScrollX.value.endIndex; i < tableHeaderLast.value.length; i++) {
|
|
127
130
|
const col = tableHeaderLast.value[i];
|
|
128
131
|
if (col.fixed !== 'right') {
|
|
129
|
-
width +=
|
|
132
|
+
width += getCalculatedColWidth(col);
|
|
130
133
|
}
|
|
131
134
|
}
|
|
132
135
|
return width;
|
|
@@ -138,7 +141,7 @@ export function useVirtualScroll<DT extends Record<string, any>>({
|
|
|
138
141
|
*/
|
|
139
142
|
function initVirtualScrollY(height?: number) {
|
|
140
143
|
if (!virtual_on.value) return;
|
|
141
|
-
const { offsetHeight, scrollTop } =
|
|
144
|
+
const { offsetHeight, scrollTop } = tableContainerRef.value || {};
|
|
142
145
|
const { rowHeight } = virtualScroll.value;
|
|
143
146
|
let containerHeight: number;
|
|
144
147
|
// FIXME: 可能多次获取offsetHeight 会导致浏览器频繁重排
|
|
@@ -160,7 +163,7 @@ export function useVirtualScroll<DT extends Record<string, any>>({
|
|
|
160
163
|
|
|
161
164
|
function initVirtualScrollX() {
|
|
162
165
|
if (!props.virtualX) return;
|
|
163
|
-
const { offsetWidth, scrollLeft } =
|
|
166
|
+
const { offsetWidth, scrollLeft } = tableContainerRef.value || {};
|
|
164
167
|
// scrollTo(null, 0);
|
|
165
168
|
virtualScrollX.value.containerWidth = offsetWidth || DEFAULT_TABLE_WIDTH;
|
|
166
169
|
updateVirtualScrollX(scrollLeft);
|
|
@@ -243,7 +246,7 @@ export function useVirtualScroll<DT extends Record<string, any>>({
|
|
|
243
246
|
for (let colIndex = 0; colIndex < headerLength; colIndex++) {
|
|
244
247
|
startIndex++;
|
|
245
248
|
const col = tableHeaderLast.value[colIndex];
|
|
246
|
-
const colWidth =
|
|
249
|
+
const colWidth = getCalculatedColWidth(col);
|
|
247
250
|
// fixed left 不进入计算列宽
|
|
248
251
|
if (col.fixed === 'left') {
|
|
249
252
|
leftColWidthSum += colWidth;
|
|
@@ -263,7 +266,7 @@ export function useVirtualScroll<DT extends Record<string, any>>({
|
|
|
263
266
|
let endIndex = headerLength;
|
|
264
267
|
for (let colIndex = startIndex + 1; colIndex < headerLength; colIndex++) {
|
|
265
268
|
const col = tableHeaderLast.value[colIndex];
|
|
266
|
-
colWidthSum +=
|
|
269
|
+
colWidthSum += getCalculatedColWidth(col);
|
|
267
270
|
// 列宽大于容器宽度则停止
|
|
268
271
|
if (colWidthSum >= containerWidth) {
|
|
269
272
|
endIndex = colIndex + 1; // 由于slice[start,end),要加1
|