@jetlinks-web/components 3.2.1 → 3.2.3
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/es/EditTable/CellRender.js +58 -27
- package/es/EditTable/EditTable.js +9 -4
- package/es/EditTable/FormItem.js +94 -80
- package/es/Search/hooks/index.js +1 -0
- package/es/VirtualTable/Table.js +205 -196
- package/es/VirtualTable/useVirtualScroll.js +112 -55
- package/lib/EditTable/CellRender.js +58 -27
- package/lib/EditTable/EditTable.js +9 -4
- package/lib/EditTable/FormItem.js +94 -80
- package/lib/Search/hooks/index.js +1 -0
- package/lib/VirtualTable/Table.js +204 -195
- package/lib/VirtualTable/useVirtualScroll.js +111 -54
- package/package.json +3 -3
|
@@ -6,7 +6,12 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.useVirtualScroll = useVirtualScroll;
|
|
7
7
|
var _vue = require("vue");
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* 高性能虚拟滚动 Hook
|
|
10
|
+
* 优化点:
|
|
11
|
+
* 1. 使用累积高度缓存,避免重复计算
|
|
12
|
+
* 2. 使用二分查找快速定位索引
|
|
13
|
+
* 3. 使用 shallowRef 减少响应式开销
|
|
14
|
+
* 4. 滚动节流减少计算频率
|
|
10
15
|
*/
|
|
11
16
|
function useVirtualScroll(options) {
|
|
12
17
|
var itemCount = options.itemCount,
|
|
@@ -17,6 +22,9 @@ function useVirtualScroll(options) {
|
|
|
17
22
|
overscan = _options$overscan === void 0 ? 5 : _options$overscan,
|
|
18
23
|
_options$threshold = options.threshold,
|
|
19
24
|
threshold = _options$threshold === void 0 ? 100 : _options$threshold;
|
|
25
|
+
// 固定行高模式(性能更高)
|
|
26
|
+
var isFixedHeight = typeof itemHeight === 'number';
|
|
27
|
+
var fixedItemHeight = isFixedHeight ? itemHeight : 54;
|
|
20
28
|
// 容器高度(支持响应式)
|
|
21
29
|
var containerHeight = (0, _vue.computed)(function () {
|
|
22
30
|
if ((0, _vue.isRef)(containerHeightOption)) {
|
|
@@ -24,23 +32,30 @@ function useVirtualScroll(options) {
|
|
|
24
32
|
}
|
|
25
33
|
return containerHeightOption;
|
|
26
34
|
});
|
|
27
|
-
// 滚动位置
|
|
28
|
-
var scrollTop = (0, _vue.
|
|
29
|
-
//
|
|
30
|
-
var itemHeights = (0, _vue.
|
|
35
|
+
// 滚动位置 - 使用 shallowRef 减少响应式开销
|
|
36
|
+
var scrollTop = (0, _vue.shallowRef)(0);
|
|
37
|
+
// 动态行高缓存(仅在非固定高度模式使用)
|
|
38
|
+
var itemHeights = (0, _vue.shallowRef)(new Map());
|
|
39
|
+
// 累积高度缓存 - 用于二分查找
|
|
40
|
+
var accumulatedHeights = (0, _vue.shallowRef)([]);
|
|
41
|
+
var heightsCacheValid = false;
|
|
31
42
|
/**
|
|
32
43
|
* 获取指定索引的行高
|
|
33
44
|
*/
|
|
34
45
|
var getItemHeight = function getItemHeight(index) {
|
|
46
|
+
if (isFixedHeight) {
|
|
47
|
+
return fixedItemHeight;
|
|
48
|
+
}
|
|
35
49
|
// 优先使用缓存的高度
|
|
36
|
-
|
|
37
|
-
|
|
50
|
+
var cached = itemHeights.value.get(index);
|
|
51
|
+
if (cached !== undefined) {
|
|
52
|
+
return cached;
|
|
38
53
|
}
|
|
39
|
-
//
|
|
54
|
+
// 使用配置的高度函数
|
|
40
55
|
if (typeof itemHeight === 'function') {
|
|
41
56
|
return itemHeight(index);
|
|
42
57
|
}
|
|
43
|
-
return
|
|
58
|
+
return fixedItemHeight;
|
|
44
59
|
};
|
|
45
60
|
/**
|
|
46
61
|
* 设置行高(用于动态测量)
|
|
@@ -48,65 +63,101 @@ function useVirtualScroll(options) {
|
|
|
48
63
|
var setItemHeight = function setItemHeight(index, height) {
|
|
49
64
|
if (height > 0 && itemHeights.value.get(index) !== height) {
|
|
50
65
|
itemHeights.value.set(index, height);
|
|
66
|
+
heightsCacheValid = false; // 使缓存失效
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* 重建累积高度缓存
|
|
71
|
+
*/
|
|
72
|
+
var rebuildAccumulatedHeights = function rebuildAccumulatedHeights() {
|
|
73
|
+
if (heightsCacheValid) return;
|
|
74
|
+
var count = itemCount.value;
|
|
75
|
+
var heights = new Array(count + 1);
|
|
76
|
+
heights[0] = 0;
|
|
77
|
+
if (isFixedHeight) {
|
|
78
|
+
// 固定高度模式,直接计算
|
|
79
|
+
for (var i = 0; i < count; i++) {
|
|
80
|
+
heights[i + 1] = heights[i] + fixedItemHeight;
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
// 动态高度模式
|
|
84
|
+
for (var _i = 0; _i < count; _i++) {
|
|
85
|
+
heights[_i + 1] = heights[_i] + getItemHeight(_i);
|
|
86
|
+
}
|
|
51
87
|
}
|
|
88
|
+
accumulatedHeights.value = heights;
|
|
89
|
+
heightsCacheValid = true;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* 二分查找:找到第一个累积高度大于 target 的索引
|
|
93
|
+
*/
|
|
94
|
+
var binarySearchIndex = function binarySearchIndex(target) {
|
|
95
|
+
rebuildAccumulatedHeights();
|
|
96
|
+
var heights = accumulatedHeights.value;
|
|
97
|
+
if (heights.length === 0) return 0;
|
|
98
|
+
var left = 0;
|
|
99
|
+
var right = heights.length - 1;
|
|
100
|
+
while (left < right) {
|
|
101
|
+
var mid = Math.floor((left + right) / 2);
|
|
102
|
+
if (heights[mid] <= target) {
|
|
103
|
+
left = mid + 1;
|
|
104
|
+
} else {
|
|
105
|
+
right = mid;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return Math.max(0, left - 1);
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* 获取累积高度
|
|
112
|
+
*/
|
|
113
|
+
var getAccumulatedHeight = function getAccumulatedHeight(index) {
|
|
114
|
+
rebuildAccumulatedHeights();
|
|
115
|
+
var heights = accumulatedHeights.value;
|
|
116
|
+
if (index < 0) return 0;
|
|
117
|
+
if (index >= heights.length) return heights[heights.length - 1] || 0;
|
|
118
|
+
return heights[index];
|
|
52
119
|
};
|
|
53
120
|
/**
|
|
54
121
|
* 计算总高度
|
|
55
122
|
*/
|
|
56
123
|
var totalHeight = (0, _vue.computed)(function () {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
total += getItemHeight(i);
|
|
124
|
+
if (isFixedHeight) {
|
|
125
|
+
return itemCount.value * fixedItemHeight;
|
|
60
126
|
}
|
|
61
|
-
|
|
127
|
+
rebuildAccumulatedHeights();
|
|
128
|
+
var heights = accumulatedHeights.value;
|
|
129
|
+
return heights.length > 0 ? heights[heights.length - 1] : 0;
|
|
62
130
|
});
|
|
63
131
|
/**
|
|
64
|
-
* 计算可视范围的起始索引
|
|
132
|
+
* 计算可视范围的起始索引 - 使用二分查找 O(log n)
|
|
65
133
|
*/
|
|
66
134
|
var startIndex = (0, _vue.computed)(function () {
|
|
67
|
-
|
|
135
|
+
var count = itemCount.value;
|
|
136
|
+
if (count < threshold) {
|
|
68
137
|
return 0;
|
|
69
138
|
}
|
|
70
|
-
var
|
|
71
|
-
|
|
72
|
-
var height = getItemHeight(i);
|
|
73
|
-
if (accumulated + height > scrollTop.value) {
|
|
74
|
-
return Math.max(0, i - overscan);
|
|
75
|
-
}
|
|
76
|
-
accumulated += height;
|
|
77
|
-
}
|
|
78
|
-
return 0;
|
|
139
|
+
var index = binarySearchIndex(scrollTop.value);
|
|
140
|
+
return Math.max(0, index - overscan);
|
|
79
141
|
});
|
|
80
142
|
/**
|
|
81
143
|
* 计算可视范围的结束索引
|
|
82
144
|
*/
|
|
83
145
|
var endIndex = (0, _vue.computed)(function () {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
var start = startIndex.value;
|
|
88
|
-
var accumulated = 0;
|
|
89
|
-
// 从起始索引开始累加
|
|
90
|
-
for (var i = 0; i < start; i++) {
|
|
91
|
-
accumulated += getItemHeight(i);
|
|
92
|
-
}
|
|
93
|
-
for (var _i = start; _i < itemCount.value; _i++) {
|
|
94
|
-
accumulated += getItemHeight(_i);
|
|
95
|
-
if (accumulated > scrollTop.value + containerHeight.value) {
|
|
96
|
-
return Math.min(itemCount.value, _i + 1 + overscan);
|
|
97
|
-
}
|
|
146
|
+
var count = itemCount.value;
|
|
147
|
+
if (count < threshold) {
|
|
148
|
+
return count;
|
|
98
149
|
}
|
|
99
|
-
|
|
150
|
+
var targetHeight = scrollTop.value + containerHeight.value;
|
|
151
|
+
var index = binarySearchIndex(targetHeight);
|
|
152
|
+
return Math.min(count, index + 1 + overscan);
|
|
100
153
|
});
|
|
101
154
|
/**
|
|
102
155
|
* 顶部偏移量
|
|
103
156
|
*/
|
|
104
157
|
var offsetY = (0, _vue.computed)(function () {
|
|
105
|
-
var
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
return offset;
|
|
158
|
+
var start = startIndex.value;
|
|
159
|
+
if (start === 0) return 0;
|
|
160
|
+
return getAccumulatedHeight(start);
|
|
110
161
|
});
|
|
111
162
|
/**
|
|
112
163
|
* 可见行数
|
|
@@ -114,20 +165,29 @@ function useVirtualScroll(options) {
|
|
|
114
165
|
var visibleCount = (0, _vue.computed)(function () {
|
|
115
166
|
return endIndex.value - startIndex.value;
|
|
116
167
|
});
|
|
168
|
+
// 滚动节流
|
|
169
|
+
var scrollRAF = null;
|
|
117
170
|
/**
|
|
118
|
-
*
|
|
171
|
+
* 处理滚动事件(带节流)
|
|
119
172
|
*/
|
|
120
173
|
var handleScroll = function handleScroll(e) {
|
|
121
174
|
var target = e.target;
|
|
122
|
-
if (target)
|
|
123
|
-
|
|
175
|
+
if (!target) return;
|
|
176
|
+
// 使用 RAF 节流
|
|
177
|
+
if (scrollRAF !== null) {
|
|
178
|
+
cancelAnimationFrame(scrollRAF);
|
|
124
179
|
}
|
|
180
|
+
scrollRAF = requestAnimationFrame(function () {
|
|
181
|
+
scrollTop.value = target.scrollTop;
|
|
182
|
+
scrollRAF = null;
|
|
183
|
+
});
|
|
125
184
|
};
|
|
126
185
|
/**
|
|
127
186
|
* 滚动到指定位置
|
|
128
187
|
*/
|
|
129
188
|
var scrollTo = function scrollTo(offset) {
|
|
130
|
-
|
|
189
|
+
var maxScroll = Math.max(0, totalHeight.value - containerHeight.value);
|
|
190
|
+
scrollTop.value = Math.max(0, Math.min(offset, maxScroll));
|
|
131
191
|
};
|
|
132
192
|
/**
|
|
133
193
|
* 滚动到指定索引
|
|
@@ -136,14 +196,11 @@ function useVirtualScroll(options) {
|
|
|
136
196
|
if (index < 0 || index >= itemCount.value) {
|
|
137
197
|
return;
|
|
138
198
|
}
|
|
139
|
-
|
|
140
|
-
for (var i = 0; i < index; i++) {
|
|
141
|
-
offset += getItemHeight(i);
|
|
142
|
-
}
|
|
143
|
-
scrollTo(offset);
|
|
199
|
+
scrollTo(getAccumulatedHeight(index));
|
|
144
200
|
};
|
|
145
|
-
//
|
|
201
|
+
// 数据变化时使缓存失效并重置滚动位置
|
|
146
202
|
(0, _vue.watch)(itemCount, function () {
|
|
203
|
+
heightsCacheValid = false;
|
|
147
204
|
var maxScroll = Math.max(0, totalHeight.value - containerHeight.value);
|
|
148
205
|
if (scrollTop.value > maxScroll) {
|
|
149
206
|
scrollTop.value = maxScroll;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jetlinks-web/components",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "es/index.js",
|
|
@@ -155,8 +155,8 @@
|
|
|
155
155
|
"webpack-merge": "^5.0.0",
|
|
156
156
|
"webpackbar": "^5.0.2",
|
|
157
157
|
"@jetlinks-web/constants": "^1.0.9",
|
|
158
|
-
"@jetlinks-web/
|
|
159
|
-
"@jetlinks-web/
|
|
158
|
+
"@jetlinks-web/hooks": "^1.1.1",
|
|
159
|
+
"@jetlinks-web/utils": "^1.2.12"
|
|
160
160
|
},
|
|
161
161
|
"publishConfig": {
|
|
162
162
|
"registry": "https://registry.npmjs.org/",
|