@jetlinks-web/components 3.2.1 → 3.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.
@@ -1,3 +1,9 @@
1
+ function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) {
2
+ return lhs;
3
+ }
4
+ else {
5
+ return rhsFn();
6
+ } }
1
7
  function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) {
2
8
  const op = ops[i];
3
9
  const fn = ops[i + 1];
@@ -15,10 +21,15 @@ function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]
15
21
  }
16
22
  } return value; }
17
23
  /* Analyzed bindings: {} */
18
- import { defineComponent, h, shallowRef, } from 'vue';
24
+ import { defineComponent, h, shallowRef, markRaw } from 'vue';
19
25
  /**
20
- * CellRender - 优化的单元格渲染组件
21
- * 使用 shallowRef 和手动更新检查来避免不必要的重渲染
26
+ * CellRender - 高性能单元格渲染组件
27
+ *
28
+ * 优化点:
29
+ * 1. 使用 shallowRef 避免深度响应式
30
+ * 2. 缓存渲染结果,避免不必要的重渲染
31
+ * 3. 使用 markRaw 标记渲染结果,避免被 Vue 代理
32
+ * 4. 智能对比 record 的 key 变化
22
33
  */
23
34
  const __sfc_main__ = defineComponent({
24
35
  name: "CellRender",
@@ -35,43 +46,63 @@ const __sfc_main__ = defineComponent({
35
46
  index: {
36
47
  type: Number,
37
48
  required: true
49
+ },
50
+ // 可选:指定用于对比的 key 字段
51
+ rowKey: {
52
+ type: String,
53
+ default: 'id'
38
54
  }
39
55
  },
40
56
  setup(props) {
41
57
  // 使用 shallowRef 避免深度响应式
42
58
  const cachedResult = shallowRef(null);
43
- const lastValue = shallowRef(undefined);
44
- const lastRecordId = shallowRef(undefined);
45
- // 手动检查是否需要重新渲染
46
- const shouldUpdate = () => {
47
- // 检查 value 是否变化
48
- if (props.value !== lastValue.value) {
49
- return true;
50
- }
51
- // 检查 record 的关键属性是否变化(假设有 id 或 key)
52
- const currentRecordId = _optionalChain([(props.record), 'optionalAccess', _ => _.id]) || _optionalChain([(props.record), 'optionalAccess', _2 => _2.key]);
53
- if (currentRecordId !== lastRecordId.value) {
54
- return true;
59
+ // 缓存上次渲染的关键信息
60
+ let lastValue = Symbol('initial');
61
+ let lastRecordKey = Symbol('initial');
62
+ let lastIndex = -1;
63
+ // 获取 record 的唯一标识
64
+ const getRecordKey = (record) => {
65
+ return _nullishCoalesce(_nullishCoalesce(_optionalChain([record, 'optionalAccess', _ => _[props.rowKey]]), () => (_optionalChain([record, 'optionalAccess', _2 => _2.key]))), () => (_optionalChain([record, 'optionalAccess', _3 => _3.__dataIndex])));
66
+ };
67
+ // 检查是否需要重新渲染
68
+ const shouldRerender = () => {
69
+ const currentRecordKey = getRecordKey(props.record);
70
+ // 快速检查:index 和 key 都没变,value 也没变
71
+ if (lastIndex === props.index &&
72
+ lastRecordKey === currentRecordKey &&
73
+ lastValue === props.value) {
74
+ return false;
55
75
  }
56
- return false;
76
+ return true;
57
77
  };
58
- // 执行渲染函数
59
- const render = () => {
60
- if (shouldUpdate() || cachedResult.value === null) {
61
- lastValue.value = props.value;
62
- lastRecordId.value = _optionalChain([(props.record), 'optionalAccess', _3 => _3.id]) || _optionalChain([(props.record), 'optionalAccess', _4 => _4.key]);
63
- try {
64
- cachedResult.value = props.renderFn(props.value, props.record, props.index);
65
- }
66
- catch (error) {
78
+ // 执行渲染
79
+ const doRender = () => {
80
+ if (!shouldRerender() && cachedResult.value !== null) {
81
+ return cachedResult.value;
82
+ }
83
+ // 更新缓存的对比值
84
+ lastValue = props.value;
85
+ lastRecordKey = getRecordKey(props.record);
86
+ lastIndex = props.index;
87
+ try {
88
+ const result = props.renderFn(props.value, props.record, props.index);
89
+ // 使用 markRaw 避免渲染结果被代理
90
+ cachedResult.value = result != null ? markRaw({ node: result }) : null;
91
+ }
92
+ catch (error) {
93
+ if (import.meta.env.DEV) {
67
94
  console.error('[CellRender] Render error:', error);
68
- cachedResult.value = null;
69
95
  }
96
+ cachedResult.value = null;
70
97
  }
71
98
  return cachedResult.value;
72
99
  };
73
100
  return () => {
74
- return h('div', { class: 'cell-render-wrapper' }, [render()]);
101
+ const result = doRender();
102
+ return h('div', {
103
+ class: 'cell-render-wrapper',
104
+ style: { display: 'contents' } // 避免额外的 DOM 层级影响布局
105
+ }, _nullishCoalesce(_optionalChain([result, 'optionalAccess', _4 => _4.node]), () => (null)));
75
106
  };
76
107
  }
77
108
  });
@@ -454,21 +454,26 @@ const __sfc_main__ = _defineComponent({
454
454
  ]),
455
455
  _createElementVNode("div", _hoisted_2, [
456
456
  _createElementVNode("div", _hoisted_3, [
457
- _createVNode(_unref(VirtualTable), _mergeProps(props, {
457
+ _createVNode(_unref(VirtualTable), _mergeProps({
458
+ ref_key: "virtualTableRef",
459
+ ref: virtualTableRef
460
+ }, props, {
458
461
  "data-source": bodyDataSource.value,
459
462
  columns: newColumns.value,
460
463
  scroll: scroll.value,
461
464
  pagination: false,
462
465
  virtual: {
463
466
  itemHeight: props.cellHeight,
464
- overscan: 1,
467
+ overscan: 5,
465
468
  threshold: props.height / props.cellHeight
466
469
  }
467
470
  }), {
468
- bodyCell: _withCtx(({ column, record }) => [
471
+ bodyCell: _withCtx(({ column, record, index }) => [
469
472
  _renderSlot(_ctx.$slots, column.dataIndex, {
470
473
  column: column,
471
- record: record
474
+ record: record,
475
+ index: record.__dataIndex,
476
+ visibleIndex: index
472
477
  })
473
478
  ]),
474
479
  _: 3 /* FORWARDED */
@@ -17,16 +17,15 @@ function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]
17
17
  /* Analyzed bindings: {
18
18
  "onBeforeUnmount": "setup-const",
19
19
  "computed": "setup-const",
20
- "reactive": "setup-const",
20
+ "shallowReactive": "setup-const",
21
21
  "watch": "setup-const",
22
22
  "provide": "setup-const",
23
23
  "toRaw": "setup-const",
24
- "nextTick": "setup-const",
25
24
  "onMounted": "setup-const",
25
+ "shallowRef": "setup-const",
26
+ "ref": "setup-const",
26
27
  "get": "setup-maybe-ref",
27
28
  "isArray": "setup-maybe-ref",
28
- "set": "setup-maybe-ref",
29
- "cloneDeep": "setup-maybe-ref",
30
29
  "useProvideFormItemContext": "setup-maybe-ref",
31
30
  "Schema": "setup-maybe-ref",
32
31
  "useInjectError": "setup-maybe-ref",
@@ -39,27 +38,30 @@ function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]
39
38
  "prefixCls": "setup-ref",
40
39
  "wrapSSR": "setup-maybe-ref",
41
40
  "hashId": "setup-maybe-ref",
41
+ "visible": "setup-ref",
42
42
  "context": "setup-maybe-ref",
43
43
  "globalErrorMessage": "setup-maybe-ref",
44
44
  "hideTimer": "setup-let",
45
- "cachedFieldPath": "setup-let",
46
- "cachedName": "setup-let",
45
+ "cachedSchema": "setup-let",
46
+ "cachedRulesHash": "setup-let",
47
47
  "fieldPath": "setup-ref",
48
48
  "eventKey": "setup-ref",
49
- "fieldId": "setup-ref",
50
49
  "fieldName": "setup-ref",
51
50
  "rowIndex": "setup-ref",
52
51
  "fieldValue": "setup-ref",
53
- "errorMap": "setup-reactive-const",
52
+ "errorState": "setup-maybe-ref",
54
53
  "popContainer": "setup-const",
55
54
  "removeTimer": "setup-const",
56
55
  "showErrorTip": "setup-const",
57
56
  "hideErrorTip": "setup-const",
58
57
  "getFieldRules": "setup-const",
58
+ "getSchema": "setup-const",
59
59
  "validateField": "setup-const",
60
60
  "validateRules": "setup-const",
61
61
  "onFieldBlur": "setup-const",
62
62
  "onFieldChange": "setup-const",
63
+ "currentEventKey": "setup-ref",
64
+ "fieldId": "setup-ref",
63
65
  "isRegistered": "setup-let",
64
66
  "registerField": "setup-const",
65
67
  "name": "props",
@@ -67,11 +69,11 @@ function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]
67
69
  "rules": "props"
68
70
  } */
69
71
  import { defineComponent as _defineComponent } from 'vue';
70
- import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, unref as _unref, normalizeClass as _normalizeClass, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, createBlock as _createBlock, renderSlot as _renderSlot, Fragment as _Fragment } from "vue";
72
+ import { unref as _unref, toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, normalizeClass as _normalizeClass, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, renderSlot as _renderSlot, Fragment as _Fragment, createElementBlock as _createElementBlock } from "vue";
71
73
  const _hoisted_1 = { style: { "color": "#1d2129" } };
72
74
  const _hoisted_2 = ["id"];
73
- import { onBeforeUnmount, computed, reactive, watch, provide, toRaw, nextTick, onMounted } from "vue";
74
- import { get, isArray, } from 'lodash-es';
75
+ import { onBeforeUnmount, computed, shallowReactive, watch, provide, toRaw, onMounted, shallowRef, ref } from "vue";
76
+ import { get, isArray } from 'lodash-es';
75
77
  import { useProvideFormItemContext } from 'ant-design-vue/lib/form/FormItemContext';
76
78
  import Schema from 'async-validator';
77
79
  import { useInjectError, useInjectForm } from "./hooks";
@@ -93,34 +95,21 @@ const __sfc_main__ = _defineComponent({
93
95
  const emit = __emit;
94
96
  const prefixCls = computed(() => 'jetlinks-edit-table');
95
97
  const [wrapSSR, hashId] = genEditTableStyle(prefixCls);
98
+ const visible = ref(false);
96
99
  const context = useInjectForm();
97
100
  const globalErrorMessage = useInjectError();
98
101
  let hideTimer = null;
99
- // 缓存字段路径,避免频繁的数组操作
100
- let cachedFieldPath = null;
101
- let cachedName = undefined;
102
- // 计算字段路径
102
+ // 缓存 Schema 实例,避免重复创建
103
+ let cachedSchema = null;
104
+ let cachedRulesHash = null;
105
+ // 计算字段路径 - 简化逻辑
103
106
  const fieldPath = computed(() => {
104
107
  if (!props.name)
105
108
  return [];
106
- // 如果 name 没变,返回缓存
107
- if (cachedName === props.name && cachedFieldPath) {
108
- return cachedFieldPath;
109
- }
110
- cachedName = props.name;
111
- cachedFieldPath = isArray(props.name) ? props.name : [props.name];
112
- return cachedFieldPath;
109
+ return isArray(props.name) ? props.name : [props.name];
113
110
  });
114
- // 事件key,用于标识字段 - 使用简单字符串拼接
111
+ // 事件key - 使用 shallowRef 优化
115
112
  const eventKey = computed(() => fieldPath.value.join('-'));
116
- // 字段ID
117
- const fieldId = computed(() => {
118
- const path = fieldPath.value;
119
- if (path.length === 0)
120
- return '';
121
- const [index, ...extra] = path;
122
- return `${index}-${extra.join('.')}`;
123
- });
124
113
  // 字段名(最后一个路径部分)
125
114
  const fieldName = computed(() => {
126
115
  const path = fieldPath.value;
@@ -131,7 +120,7 @@ const __sfc_main__ = _defineComponent({
131
120
  const path = fieldPath.value;
132
121
  return path.length > 0 ? Number(path[0]) : 0;
133
122
  });
134
- // 字段值 - 优化:避免不必要的 get 调用
123
+ // 字段值 - 优化访问路径
135
124
  const fieldValue = computed(() => {
136
125
  if (!_optionalChain([context, 'access', _2 => _2.dataSource, 'optionalAccess', _3 => _3.value]) || !props.name)
137
126
  return undefined;
@@ -149,35 +138,33 @@ const __sfc_main__ = _defineComponent({
149
138
  // 深层路径:使用 lodash.get
150
139
  return get(dataSource, props.name);
151
140
  });
152
- // 错误状态
153
- const errorMap = reactive({
141
+ // 错误状态 - 使用 shallowReactive 减少响应式开销
142
+ const errorState = shallowReactive({
154
143
  message: '',
155
144
  visible: false
156
145
  });
157
146
  // 向下提供错误状态
158
- provide(TABLE_FORM_ITEM_ERROR, errorMap);
159
- const popContainer = (e) => {
160
- return e;
161
- };
147
+ provide(TABLE_FORM_ITEM_ERROR, errorState);
148
+ const popContainer = (e) => e;
162
149
  const removeTimer = () => {
163
150
  if (hideTimer) {
164
- window.clearTimeout(hideTimer);
151
+ clearTimeout(hideTimer);
165
152
  hideTimer = null;
166
153
  }
167
154
  };
168
155
  const showErrorTip = (msg) => {
169
156
  removeTimer();
170
- errorMap.message = msg;
171
- errorMap.visible = true;
157
+ errorState.message = msg;
158
+ errorState.visible = true;
172
159
  };
173
160
  const hideErrorTip = () => {
174
- errorMap.visible = false;
161
+ errorState.visible = false;
175
162
  removeTimer();
176
163
  hideTimer = setTimeout(() => {
177
- errorMap.message = '';
164
+ errorState.message = '';
178
165
  }, 300);
179
166
  };
180
- // 获取当前字段的校验规则
167
+ // 获取当前字段的校验规则 - 缓存规则
181
168
  const getFieldRules = () => {
182
169
  // 优先使用 props.rules
183
170
  if (props.rules && props.rules.length > 0) {
@@ -196,24 +183,41 @@ const __sfc_main__ = _defineComponent({
196
183
  }
197
184
  return [];
198
185
  };
186
+ // 获取或创建 Schema 实例
187
+ const getSchema = () => {
188
+ const rules = getFieldRules();
189
+ if (!rules || rules.length === 0) {
190
+ cachedSchema = null;
191
+ cachedRulesHash = null;
192
+ return null;
193
+ }
194
+ // 简单的规则哈希检查
195
+ const rulesHash = JSON.stringify(rules);
196
+ if (cachedRulesHash === rulesHash && cachedSchema) {
197
+ return cachedSchema;
198
+ }
199
+ // 创建新的 Schema
200
+ const descriptor = {
201
+ [fieldName.value]: rules
202
+ };
203
+ cachedSchema = new Schema(descriptor);
204
+ cachedRulesHash = rulesHash;
205
+ return cachedSchema;
206
+ };
199
207
  // 使用 async-validator 执行校验
200
208
  const validateField = () => {
201
209
  return new Promise((resolve, reject) => {
202
- const rules = getFieldRules();
203
- if (!rules || rules.length === 0) {
210
+ const schema = getSchema();
211
+ if (!schema) {
204
212
  hideErrorTip();
205
213
  _optionalChain([context, 'access', _6 => _6.removeFieldError, 'optionalCall', _7 => _7(eventKey.value)]);
206
214
  resolve();
207
215
  return;
208
216
  }
209
- const descriptor = {
210
- [fieldName.value]: rules
211
- };
212
- const validator = new Schema(descriptor);
213
217
  const data = {
214
218
  [fieldName.value]: toRaw(fieldValue.value)
215
219
  };
216
- validator.validate(data, { firstFields: true }, (errors) => {
220
+ schema.validate(data, { firstFields: true }, (errors) => {
217
221
  if (errors && errors.length > 0) {
218
222
  const errorMsg = errors[0].message || '校验失败';
219
223
  showErrorTip(errorMsg);
@@ -244,9 +248,9 @@ const __sfc_main__ = _defineComponent({
244
248
  }
245
249
  else {
246
250
  removeTimer();
247
- errorMap.message = _optionalChain([error, 'access', _18 => _18[0], 'optionalAccess', _19 => _19.message]) || errorMap.message;
248
- errorMap.visible = !!error.length;
249
- _optionalChain([context, 'access', _20 => _20.addFieldError, 'optionalCall', _21 => _21(eventKey.value, errorMap.message)]);
251
+ errorState.message = _optionalChain([error, 'access', _18 => _18[0], 'optionalAccess', _19 => _19.message]) || errorState.message;
252
+ errorState.visible = !!error.length;
253
+ _optionalChain([context, 'access', _20 => _20.addFieldError, 'optionalCall', _21 => _21(eventKey.value, errorState.message)]);
250
254
  }
251
255
  });
252
256
  return promise;
@@ -255,24 +259,36 @@ const __sfc_main__ = _defineComponent({
255
259
  return validateField();
256
260
  };
257
261
  const onFieldBlur = () => {
258
- // 改为失焦时校验,减少实时校验的性能开销
262
+ // 失焦时校验,减少实时校验的性能开销
259
263
  validateRules();
260
264
  };
261
265
  const onFieldChange = () => {
262
- // 只触发 change 事件,不进行校验
263
- // validateRules() // 移到 onBlur
264
266
  emit('change');
265
267
  };
266
- // 监听全局错误变化 - 精确监听单个字段,避免深度遍历
267
- watch(() => _optionalChain([globalErrorMessage, 'optionalAccess', _22 => _22.value, 'optionalAccess', _23 => _23[eventKey.value]]), (errorMsg) => {
268
+ // 监听全局错误变化 - 使用精确的 key 监听
269
+ const currentEventKey = shallowRef('');
270
+ watch(() => eventKey.value, (newKey) => {
271
+ currentEventKey.value = newKey;
272
+ }, { immediate: true });
273
+ watch(() => {
274
+ const key = currentEventKey.value;
275
+ return key ? _optionalChain([globalErrorMessage, 'optionalAccess', _22 => _22.value, 'optionalAccess', _23 => _23[key]]) : undefined;
276
+ }, (errorMsg) => {
268
277
  if (errorMsg) {
269
278
  showErrorTip(errorMsg);
270
279
  }
271
- else {
280
+ else if (errorState.visible) {
272
281
  hideErrorTip();
273
282
  }
274
283
  }, { immediate: true });
275
- // 注册到 ant-design-vue form context
284
+ // 注册到 ant-design-vue form context - 延迟计算
285
+ const fieldId = computed(() => {
286
+ const path = fieldPath.value;
287
+ if (path.length === 0)
288
+ return '';
289
+ const [index, ...extra] = path;
290
+ return `${index}-${extra.join('.')}`;
291
+ });
276
292
  useProvideFormItemContext({
277
293
  id: fieldId,
278
294
  onFieldChange,
@@ -280,11 +296,10 @@ const __sfc_main__ = _defineComponent({
280
296
  }, computed(() => {
281
297
  return get(_optionalChain([context, 'access', _24 => _24.dataSource, 'optionalAccess', _25 => _25.value]), props.name);
282
298
  }));
283
- // 注册字段到父组件 - 使用字段池复用,减少创建/销毁开销
299
+ // 注册字段到父组件 - 使用字段池复用
284
300
  let isRegistered = false;
285
301
  const registerField = () => {
286
302
  if (props.name && !isRegistered) {
287
- // 使用字段池获取或创建字段信息
288
303
  const fieldInfo = fieldPool.acquire(eventKey.value, {
289
304
  fieldName: fieldName.value,
290
305
  eventKey: eventKey.value,
@@ -296,20 +311,20 @@ const __sfc_main__ = _defineComponent({
296
311
  isRegistered = true;
297
312
  }
298
313
  };
299
- // 延迟注册,避免虚拟滚动时的同步阻塞
314
+ // 延迟注册,使用 queueMicrotask 替代 nextTick,性能更好
300
315
  onMounted(() => {
301
- nextTick(() => {
302
- registerField();
303
- });
316
+ queueMicrotask(registerField);
304
317
  });
305
318
  onBeforeUnmount(() => {
306
- hideErrorTip();
319
+ removeTimer();
307
320
  if (isRegistered) {
308
321
  _optionalChain([context, 'access', _28 => _28.removeField, 'optionalCall', _29 => _29(eventKey.value)]);
309
- // 释放字段到池中,而不是销毁
310
322
  fieldPool.release(eventKey.value);
311
323
  isRegistered = false;
312
324
  }
325
+ // 清理缓存
326
+ cachedSchema = null;
327
+ cachedRulesHash = null;
313
328
  });
314
329
  // 暴露方法
315
330
  __expose({
@@ -321,32 +336,31 @@ const __sfc_main__ = _defineComponent({
321
336
  return (_ctx, _cache) => {
322
337
  const _component_a_tooltip = _resolveComponent("a-tooltip");
323
338
  return (_openBlock(), _createElementBlock(_Fragment, null, [
324
- (errorMap.visible)
339
+ (_unref(errorState).visible)
325
340
  ? (_openBlock(), _createBlock(_component_a_tooltip, {
326
341
  key: 0,
327
342
  color: "#ffffff",
328
343
  "get-popup-container": popContainer,
329
344
  arrowPointAtCenter: true,
330
- open: ""
345
+ open: visible.value,
346
+ "onUpdate:open": _cache[0] || (_cache[0] = ($event) => ((visible).value = $event)),
347
+ onOpenChange: _cache[1] || (_cache[1] = (v) => visible.value = v)
331
348
  }, {
332
349
  title: _withCtx(() => [
333
- _createElementVNode("span", _hoisted_1, _toDisplayString(errorMap.message), 1 /* TEXT */)
350
+ _createElementVNode("span", _hoisted_1, _toDisplayString(_unref(errorState).message), 1 /* TEXT */)
334
351
  ]),
335
352
  default: _withCtx(() => [
336
- (errorMap.visible)
337
- ? (_openBlock(), _createElementBlock("div", {
338
- key: 0,
339
- class: _normalizeClass(['jetlinks-table-form-error-target', _unref(hashId), 'hashId'])
340
- }, null, 2 /* CLASS */))
341
- : _createCommentVNode("v-if", true)
353
+ _createElementVNode("div", {
354
+ class: _normalizeClass(['jetlinks-table-form-error-target', _unref(hashId), 'hashId'])
355
+ }, null, 2 /* CLASS */)
342
356
  ]),
343
357
  _: 1 /* STABLE */
344
- }))
358
+ }, 8 /* PROPS */, ["open"]))
345
359
  : _createCommentVNode("v-if", true),
346
360
  _createElementVNode("div", {
347
361
  id: eventKey.value,
348
362
  style: { "position": "relative" },
349
- class: _normalizeClass({ 'jetlinks-edit-table-form-has-error': errorMap.message, [_unref(hashId)]: true })
363
+ class: _normalizeClass({ 'jetlinks-edit-table-form-has-error': _unref(errorState).message, [_unref(hashId)]: true })
350
364
  }, [
351
365
  _renderSlot(_ctx.$slots, "default")
352
366
  ], 10 /* CLASS, PROPS */, _hoisted_2)