@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.
@@ -1,12 +1,12 @@
1
- import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
2
1
  import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
3
- import { defineComponent, computed, ref, watch, onMounted, onBeforeUnmount, nextTick, toRef, defineExpose, createVNode as _createVNode } from 'vue';
2
+ import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
3
+ import { defineComponent, computed, ref, watch, onMounted, onBeforeUnmount, nextTick, toRef, shallowRef, markRaw, createVNode as _createVNode } from 'vue';
4
4
  import { Table } from 'ant-design-vue';
5
5
  import { tableProps } from "ant-design-vue/es/table";
6
6
  import { useVirtualScroll } from './useVirtualScroll';
7
7
  import { useTreeData } from './useTreeData';
8
- // 展开图标组件
9
- var ExpandIcon = defineComponent({
8
+ // 展开图标组件 - 使用 markRaw 避免响应式包装
9
+ var ExpandIcon = markRaw(defineComponent({
10
10
  name: 'VirtualExpandIcon',
11
11
  props: {
12
12
  expanded: Boolean,
@@ -25,7 +25,6 @@ var ExpandIcon = defineComponent({
25
25
  };
26
26
  return function () {
27
27
  if (!props.hasChildren) {
28
- // 占位符,保持对齐
29
28
  return _createVNode("span", {
30
29
  "class": "".concat(props.prefixCls, "-row-expand-icon ").concat(props.prefixCls, "-row-expand-icon-spaced")
31
30
  }, null);
@@ -38,11 +37,57 @@ var ExpandIcon = defineComponent({
38
37
  }, null);
39
38
  };
40
39
  }
41
- });
40
+ }));
41
+ // Proxy 缓存池
42
+ var proxyCache = new WeakMap();
43
+ function getOrCreateProxy(record, node) {
44
+ var proxy = proxyCache.get(record);
45
+ if (proxy && proxy.__virtual_tree_node__ === node) {
46
+ return proxy;
47
+ }
48
+ proxy = new Proxy(record, {
49
+ get: function get(target, prop) {
50
+ if (prop === '__virtual_tree_node__') {
51
+ return node;
52
+ }
53
+ return Reflect.get(target, prop);
54
+ },
55
+ set: function set(target, prop, value) {
56
+ if (prop !== '__virtual_tree_node__') {
57
+ return Reflect.set(target, prop, value);
58
+ }
59
+ return true;
60
+ },
61
+ has: function has(target, prop) {
62
+ if (prop === '__virtual_tree_node__') {
63
+ return true;
64
+ }
65
+ return Reflect.has(target, prop);
66
+ },
67
+ ownKeys: function ownKeys(target) {
68
+ return [].concat(_toConsumableArray(Reflect.ownKeys(target)), ['__virtual_tree_node__']);
69
+ },
70
+ getOwnPropertyDescriptor: function getOwnPropertyDescriptor(target, prop) {
71
+ if (prop === '__virtual_tree_node__') {
72
+ return {
73
+ configurable: true,
74
+ enumerable: false,
75
+ value: node
76
+ };
77
+ }
78
+ return Reflect.getOwnPropertyDescriptor(target, prop);
79
+ }
80
+ });
81
+ proxyCache.set(record, proxy);
82
+ return proxy;
83
+ }
42
84
  /**
43
85
  * 虚拟滚动 Table 组件
44
- * 完全兼容 a-table 的所有功能,同时支持大数据虚拟滚动
45
- * 支持树形数据的虚拟滚动
86
+ * 性能优化:
87
+ * 1. 数据切片使用 computed 缓存,避免重复切片
88
+ * 2. 使用 shallowRef 减少响应式开销
89
+ * 3. 使用 passive 滚动事件监听
90
+ * 4. rowKey 确保组件正确复用
46
91
  */
47
92
  export default defineComponent({
48
93
  name: 'JVirtualTable',
@@ -60,9 +105,8 @@ export default defineComponent({
60
105
  slots = _ref.slots,
61
106
  expose = _ref.expose,
62
107
  emit = _ref.emit;
63
- // Table ref
64
108
  var tableRef = ref();
65
- var scrollContainerRef = ref();
109
+ var scrollContainerRef = shallowRef();
66
110
  // 解析虚拟滚动配置
67
111
  var virtualConfig = computed(function () {
68
112
  if (!props.virtual) return null;
@@ -79,21 +123,18 @@ export default defineComponent({
79
123
  threshold: props.virtual.threshold || 100
80
124
  };
81
125
  });
82
- // 获取容器高度
126
+ // 容器高度
83
127
  var containerHeight = computed(function () {
84
128
  var _props$scroll;
85
129
  if (!((_props$scroll = props.scroll) !== null && _props$scroll !== void 0 && _props$scroll.y)) return 0;
86
130
  var y = props.scroll.y;
87
131
  if (typeof y === 'number') return y;
88
- // 解析 '500px' 这样的字符串
89
132
  var num = parseInt(String(y), 10);
90
133
  return isNaN(num) ? 0 : num;
91
134
  });
92
- // 获取子节点字段名
93
135
  var childrenColumnName = computed(function () {
94
136
  return props.childrenColumnName || 'children';
95
137
  });
96
- // 获取 rowKey
97
138
  var rowKeyProp = computed(function () {
98
139
  return props.rowKey || 'key';
99
140
  });
@@ -105,7 +146,7 @@ export default defineComponent({
105
146
  return children && Array.isArray(children) && children.length > 0;
106
147
  });
107
148
  });
108
- // 使用树形数据 hook
149
+ // 树形数据处理
109
150
  var treeDataResult = useTreeData({
110
151
  data: toRef(props, 'dataSource'),
111
152
  childrenColumnName: childrenColumnName.value,
@@ -114,32 +155,29 @@ export default defineComponent({
114
155
  expandedRowKeys: props.expandedRowKeys ? toRef(props, 'expandedRowKeys') : undefined,
115
156
  defaultExpandAllRows: props.defaultExpandAllRows,
116
157
  onExpandedRowsChange: function onExpandedRowsChange(keys) {
117
- emit('expandedRowsChange', keys);
158
+ return emit('expandedRowsChange', keys);
118
159
  }
119
160
  });
120
- // 处理展开/折叠
121
161
  var handleExpand = function handleExpand(node) {
122
162
  treeDataResult.toggle(node.key);
123
163
  emit('expand', !node.expanded, node.record);
124
164
  };
125
- // 判断是否启用虚拟滚动
126
- var shouldEnableVirtual = computed(function () {
127
- var _props$scroll2, _props$dataSource;
128
- if (!virtualConfig.value) return false;
129
- if (!((_props$scroll2 = props.scroll) !== null && _props$scroll2 !== void 0 && _props$scroll2.y)) return false;
130
- // 对于树形数据,使用扁平化后的数据长度
131
- var dataLength = isTreeData.value ? treeDataResult.flattenedData.value.length : ((_props$dataSource = props.dataSource) === null || _props$dataSource === void 0 ? void 0 : _props$dataSource.length) || 0;
132
- return dataLength >= virtualConfig.value.threshold;
133
- });
134
- // 虚拟滚动的数据数量(树形数据使用扁平化后的长度)
165
+ // 数据数量
135
166
  var itemCount = computed(function () {
136
- var _props$dataSource2;
167
+ var _props$dataSource;
137
168
  if (isTreeData.value) {
138
169
  return treeDataResult.flattenedData.value.length;
139
170
  }
140
- return ((_props$dataSource2 = props.dataSource) === null || _props$dataSource2 === void 0 ? void 0 : _props$dataSource2.length) || 0;
171
+ return ((_props$dataSource = props.dataSource) === null || _props$dataSource === void 0 ? void 0 : _props$dataSource.length) || 0;
141
172
  });
142
- // 初始化虚拟滚动
173
+ // 判断是否启用虚拟滚动
174
+ var shouldEnableVirtual = computed(function () {
175
+ var _props$scroll2;
176
+ if (!virtualConfig.value) return false;
177
+ if (!((_props$scroll2 = props.scroll) !== null && _props$scroll2 !== void 0 && _props$scroll2.y)) return false;
178
+ return itemCount.value >= virtualConfig.value.threshold;
179
+ });
180
+ // 虚拟滚动 hook
143
181
  var virtualScroll = useVirtualScroll({
144
182
  itemCount: itemCount,
145
183
  containerHeight: containerHeight,
@@ -147,178 +185,86 @@ export default defineComponent({
147
185
  overscan: ((_virtualConfig$value2 = virtualConfig.value) === null || _virtualConfig$value2 === void 0 ? void 0 : _virtualConfig$value2.overscan) || 5,
148
186
  threshold: ((_virtualConfig$value3 = virtualConfig.value) === null || _virtualConfig$value3 === void 0 ? void 0 : _virtualConfig$value3.threshold) || 100
149
187
  });
150
- // 查找并监听滚动容器
151
- var findScrollContainer = function findScrollContainer() {
152
- if (!tableRef.value) return null;
153
- // 查找 .ant-table-body 元素(这是实际的滚动容器)
154
- var tableElement = tableRef.value.$el || tableRef.value;
155
- var scrollBody = tableElement.querySelector('.ant-table-body');
156
- return scrollBody;
157
- };
158
- // 设置滚动监听
159
- var setupScrollListener = function setupScrollListener() {
160
- nextTick(function () {
161
- var container = findScrollContainer();
162
- if (container) {
163
- scrollContainerRef.value = container;
164
- container.addEventListener('scroll', virtualScroll.handleScroll);
165
- }
166
- });
167
- };
168
- // 移除滚动监听
169
- var removeScrollListener = function removeScrollListener() {
170
- if (scrollContainerRef.value) {
171
- scrollContainerRef.value.removeEventListener('scroll', virtualScroll.handleScroll);
172
- scrollContainerRef.value = undefined;
173
- }
174
- };
175
- // 监听虚拟滚动启用状态变化
176
- watch(shouldEnableVirtual, function (enabled) {
177
- if (enabled) {
178
- setupScrollListener();
179
- } else {
180
- removeScrollListener();
188
+ // ⭐ 关键优化:使用 computed 缓存切片数据,避免每次渲染都创建新数组
189
+ var virtualDataSource = computed(function () {
190
+ if (!shouldEnableVirtual.value) {
191
+ return props.dataSource || [];
181
192
  }
182
- });
183
- // 生命周期
184
- onMounted(function () {
185
- if (shouldEnableVirtual.value) {
186
- setupScrollListener();
193
+ var start = virtualScroll.startIndex.value;
194
+ var end = virtualScroll.endIndex.value;
195
+ if (isTreeData.value) {
196
+ var flatData = treeDataResult.flattenedData.value;
197
+ var visibleNodes = flatData.slice(start, end);
198
+ return visibleNodes.map(function (node) {
199
+ return getOrCreateProxy(node.record, node);
200
+ });
187
201
  }
202
+ return (props.dataSource || []).slice(start, end);
188
203
  });
189
- onBeforeUnmount(function () {
190
- removeScrollListener();
191
- });
192
- // 暴露方法
193
- defineExpose({
194
- scrollTo: virtualScroll.scrollTo,
195
- scrollToIndex: virtualScroll.scrollToIndex,
196
- // 树形数据相关方法
197
- expand: treeDataResult.expand,
198
- collapse: treeDataResult.collapse,
199
- toggle: treeDataResult.toggle,
200
- expandAll: treeDataResult.expandAll,
201
- collapseAll: treeDataResult.collapseAll,
202
- expandTo: treeDataResult.expandTo,
203
- isExpanded: treeDataResult.isExpanded
204
- });
205
- // 渲染函数
206
- return function () {
204
+ // 缓存 columns 处理
205
+ var processedColumns = computed(function () {
207
206
  var _props$indentSize;
208
- // 不使用虚拟滚动的情况
209
- if (!shouldEnableVirtual.value) {
210
- return _createVNode(Table, _objectSpread(_objectSpread({
211
- "ref": tableRef
212
- }, attrs), props), slots);
207
+ if (!isTreeData.value || !shouldEnableVirtual.value) {
208
+ return props.columns;
213
209
  }
214
- // 使用虚拟滚动
215
- var startIndex = virtualScroll.startIndex,
216
- endIndex = virtualScroll.endIndex,
217
- offsetY = virtualScroll.offsetY,
218
- totalHeight = virtualScroll.totalHeight;
219
- // 准备虚拟滚动数据
220
- var virtualDataSource;
221
- var needsCustomIndent = false;
210
+ var columns = props.columns;
211
+ if (!columns || columns.length === 0) return columns;
222
212
  var indentSize = (_props$indentSize = props.indentSize) !== null && _props$indentSize !== void 0 ? _props$indentSize : 15;
223
- if (isTreeData.value) {
224
- // 树形数据:使用扁平化后的数据
225
- var flatData = treeDataResult.flattenedData.value;
226
- var visibleNodes = flatData.slice(startIndex.value, endIndex.value);
227
- // 使用 Proxy 代理原始 record,确保修改能同步到原始数据
228
- virtualDataSource = visibleNodes.map(function (node) {
229
- return new Proxy(node.record, {
230
- get: function get(target, prop) {
231
- if (prop === '__virtual_tree_node__') {
232
- return node;
213
+ return columns.map(function (col, colIndex) {
214
+ if (colIndex === 0) {
215
+ var originalRender = col.customRender;
216
+ return _objectSpread(_objectSpread({}, col), {}, {
217
+ customRender: function customRender(renderProps) {
218
+ var record = renderProps.record,
219
+ text = renderProps.text;
220
+ var treeNode = record.__virtual_tree_node__;
221
+ if (!treeNode) {
222
+ if (originalRender) return originalRender(renderProps);
223
+ return text;
233
224
  }
234
- return Reflect.get(target, prop);
235
- },
236
- set: function set(target, prop, value) {
237
- if (prop !== '__virtual_tree_node__') {
238
- return Reflect.set(target, prop, value);
239
- }
240
- return true;
241
- },
242
- has: function has(target, prop) {
243
- if (prop === '__virtual_tree_node__') {
244
- return true;
245
- }
246
- return Reflect.has(target, prop);
247
- },
248
- ownKeys: function ownKeys(target) {
249
- return [].concat(_toConsumableArray(Reflect.ownKeys(target)), ['__virtual_tree_node__']);
250
- },
251
- getOwnPropertyDescriptor: function getOwnPropertyDescriptor(target, prop) {
252
- if (prop === '__virtual_tree_node__') {
253
- return {
254
- configurable: true,
255
- enumerable: false,
256
- value: node
257
- };
258
- }
259
- return Reflect.getOwnPropertyDescriptor(target, prop);
225
+ var level = treeNode.level,
226
+ hasChildren = treeNode.hasChildren,
227
+ expanded = treeNode.expanded;
228
+ var indentStyle = {
229
+ paddingLeft: "".concat(level * indentSize, "px"),
230
+ display: 'inline-flex',
231
+ alignItems: 'center'
232
+ };
233
+ var content = originalRender ? originalRender(renderProps) : text;
234
+ return _createVNode("span", {
235
+ "style": indentStyle
236
+ }, [_createVNode(ExpandIcon, {
237
+ "expanded": expanded,
238
+ "hasChildren": hasChildren,
239
+ "onExpand": function onExpand() {
240
+ return handleExpand(treeNode);
241
+ }
242
+ }, null), _createVNode("span", null, [content])]);
260
243
  }
261
244
  });
262
- });
263
- needsCustomIndent = true;
264
- } else {
265
- // 普通数据:直接切片
266
- virtualDataSource = (props.dataSource || []).slice(startIndex.value, endIndex.value);
267
- }
268
- // 处理列,添加树形展开功能
269
- var processedColumns = props.columns;
270
- if (needsCustomIndent && processedColumns && processedColumns.length > 0) {
271
- // 克隆 columns,修改第一列以添加展开按钮和缩进
272
- processedColumns = processedColumns.map(function (col, colIndex) {
273
- if (colIndex === 0) {
274
- var originalRender = col.customRender;
275
- return _objectSpread(_objectSpread({}, col), {}, {
276
- customRender: function customRender(renderProps) {
277
- var record = renderProps.record,
278
- text = renderProps.text,
279
- index = renderProps.index;
280
- var treeNode = record.__virtual_tree_node__;
281
- if (!treeNode) {
282
- // 非树形数据行,使用原始渲染
283
- if (originalRender) {
284
- return originalRender(renderProps);
285
- }
286
- return text;
287
- }
288
- var level = treeNode.level,
289
- hasChildren = treeNode.hasChildren,
290
- expanded = treeNode.expanded;
291
- var indentStyle = {
292
- paddingLeft: "".concat(level * indentSize, "px"),
293
- display: 'inline-flex',
294
- alignItems: 'center'
295
- };
296
- var content = originalRender ? originalRender(renderProps) : text;
297
- return _createVNode("span", {
298
- "style": indentStyle
299
- }, [_createVNode(ExpandIcon, {
300
- "expanded": expanded,
301
- "hasChildren": hasChildren,
302
- "onExpand": function onExpand() {
303
- return handleExpand(treeNode);
304
- }
305
- }, null), _createVNode("span", null, [content])]);
306
- }
307
- });
308
- }
309
- return col;
310
- });
245
+ }
246
+ return col;
247
+ });
248
+ });
249
+ // 缓存 components
250
+ var virtualComponents = computed(function () {
251
+ if (!shouldEnableVirtual.value) {
252
+ return props.components;
311
253
  }
312
- // 自定义 body 组件
313
- var customComponents = _objectSpread(_objectSpread({}, props.components || {}), {}, {
254
+ var startIndex = virtualScroll.startIndex,
255
+ endIndex = virtualScroll.endIndex,
256
+ offsetY = virtualScroll.offsetY,
257
+ totalHeight = virtualScroll.totalHeight,
258
+ getItemHeight = virtualScroll.getItemHeight;
259
+ var columns = processedColumns.value;
260
+ return _objectSpread(_objectSpread({}, props.components || {}), {}, {
314
261
  body: {
315
262
  wrapper: function wrapper(bodyProps, _ref2) {
316
- var _processedColumns, _bodySlots$default, _processedColumns2;
263
+ var _bodySlots$default;
317
264
  var bodySlots = _ref2.slots;
318
- // 计算底部占位高度
319
265
  var accumulatedHeight = offsetY.value;
320
266
  for (var i = startIndex.value; i < endIndex.value; i++) {
321
- accumulatedHeight += virtualScroll.getItemHeight(i);
267
+ accumulatedHeight += getItemHeight(i);
322
268
  }
323
269
  var bottomHeight = totalHeight.value - accumulatedHeight;
324
270
  return _createVNode("tbody", _objectSpread(_objectSpread({}, bodyProps), {}, {
@@ -334,7 +280,7 @@ export default defineComponent({
334
280
  border: 0,
335
281
  height: "".concat(offsetY.value, "px")
336
282
  },
337
- "colSpan": ((_processedColumns = processedColumns) === null || _processedColumns === void 0 ? void 0 : _processedColumns.length) || 1
283
+ "colSpan": (columns === null || columns === void 0 ? void 0 : columns.length) || 1
338
284
  }, null)]), (_bodySlots$default = bodySlots.default) === null || _bodySlots$default === void 0 ? void 0 : _bodySlots$default.call(bodySlots), bottomHeight > 0 && _createVNode("tr", {
339
285
  "aria-hidden": "true",
340
286
  "style": {
@@ -346,22 +292,85 @@ export default defineComponent({
346
292
  border: 0,
347
293
  height: "".concat(bottomHeight, "px")
348
294
  },
349
- "colSpan": ((_processedColumns2 = processedColumns) === null || _processedColumns2 === void 0 ? void 0 : _processedColumns2.length) || 1
295
+ "colSpan": (columns === null || columns === void 0 ? void 0 : columns.length) || 1
350
296
  }, null)])]);
351
297
  }
352
298
  }
353
299
  });
354
- // 移除树形数据的 children 配置,因为我们自己处理
355
- var tableProps = _objectSpread(_objectSpread({}, props), {}, {
356
- dataSource: virtualDataSource,
357
- columns: processedColumns,
358
- components: customComponents
359
- }, isTreeData.value ? {
300
+ });
301
+ // 滚动监听
302
+ var findScrollContainer = function findScrollContainer() {
303
+ if (!tableRef.value) return null;
304
+ var tableElement = tableRef.value.$el || tableRef.value;
305
+ return tableElement.querySelector('.ant-table-body');
306
+ };
307
+ var setupScrollListener = function setupScrollListener() {
308
+ nextTick(function () {
309
+ var container = findScrollContainer();
310
+ if (container) {
311
+ scrollContainerRef.value = container;
312
+ container.addEventListener('scroll', virtualScroll.handleScroll, {
313
+ passive: true
314
+ });
315
+ }
316
+ });
317
+ };
318
+ var removeScrollListener = function removeScrollListener() {
319
+ if (scrollContainerRef.value) {
320
+ scrollContainerRef.value.removeEventListener('scroll', virtualScroll.handleScroll);
321
+ scrollContainerRef.value = undefined;
322
+ }
323
+ };
324
+ watch(shouldEnableVirtual, function (enabled) {
325
+ if (enabled) setupScrollListener();else removeScrollListener();
326
+ });
327
+ onMounted(function () {
328
+ if (shouldEnableVirtual.value) setupScrollListener();
329
+ });
330
+ onBeforeUnmount(function () {
331
+ removeScrollListener();
332
+ });
333
+ // 滚动到指定索引(同时更新虚拟状态和真实 DOM)
334
+ var scrollToIndex = function scrollToIndex(index) {
335
+ virtualScroll.scrollToIndex(index);
336
+ // 同步滚动真实 DOM
337
+ nextTick(function () {
338
+ if (scrollContainerRef.value) {
339
+ scrollContainerRef.value.scrollTop = virtualScroll.scrollTop.value;
340
+ }
341
+ });
342
+ };
343
+ // 滚动到指定位置
344
+ var scrollTo = function scrollTo(offset) {
345
+ virtualScroll.scrollTo(offset);
346
+ nextTick(function () {
347
+ if (scrollContainerRef.value) {
348
+ scrollContainerRef.value.scrollTop = virtualScroll.scrollTop.value;
349
+ }
350
+ });
351
+ };
352
+ expose({
353
+ scrollTo: scrollTo,
354
+ scrollToIndex: scrollToIndex,
355
+ expand: treeDataResult.expand,
356
+ collapse: treeDataResult.collapse,
357
+ toggle: treeDataResult.toggle,
358
+ expandAll: treeDataResult.expandAll,
359
+ collapseAll: treeDataResult.collapseAll,
360
+ expandTo: treeDataResult.expandTo,
361
+ isExpanded: treeDataResult.isExpanded
362
+ });
363
+ return function () {
364
+ var tablePropsObj = _objectSpread(_objectSpread({}, props), {}, {
365
+ dataSource: virtualDataSource.value,
366
+ columns: processedColumns.value,
367
+ components: virtualComponents.value
368
+ }, isTreeData.value && shouldEnableVirtual.value ? {
360
369
  childrenColumnName: '__disabled__'
361
370
  } : {});
362
371
  return _createVNode(Table, _objectSpread(_objectSpread({
363
372
  "ref": tableRef
364
- }, attrs), tableProps), slots);
373
+ }, attrs), tablePropsObj), slots);
365
374
  };
366
375
  }
367
376
  });