@zat-design/sisyphus-react 4.5.8-beta.6 → 4.5.8

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.
@@ -12,7 +12,7 @@ import classNames from 'classnames';
12
12
  import { compatStartTransition } from "../../../utils";
13
13
  import valueTypeMap from "../../../ProForm/utils/valueType";
14
14
  import transformMap from "../../utils/transform";
15
- import { getNamePath, difference, getDisabled, resolveChangedFields, syncGroupCombinedValues } from "../../utils/tools";
15
+ import { getNamePath, difference, getDisabled, isRecordShallowEqual, resolveChangedFields, syncGroupCombinedValues } from "../../utils/tools";
16
16
  import * as componentMap from "../../../ProForm/components";
17
17
  import { useProConfig } from "../../../ProConfigProvider";
18
18
  import Container from "../../../ProForm/components/Container";
@@ -104,6 +104,11 @@ const RenderField = ({
104
104
  const namePath = useMemo(() => getNamePath(name, virtualKey), [namePathKey, name, virtualKey]);
105
105
  const rowNamePath = useMemo(() => [...namePath, index], [namePath, index]);
106
106
  const rowData = form.getFieldValue(rowNamePath) || record || {};
107
+
108
+ // Form.Item shouldUpdate 可能在 RenderField 外层未重渲染时多次执行 render props。
109
+ // 缓存本次外层渲染期间的最新行计算结果,避免同一行版本重复调用动态配置函数。
110
+ const shouldUpdateStateCacheRef = useRef(undefined);
111
+ shouldUpdateStateCacheRef.current = undefined;
107
112
  let currentValue = dataIndex ? rowData?.[dataIndex] : null;
108
113
 
109
114
  // 构建新的参数格式(与 ProForm 保持一致)
@@ -663,45 +668,48 @@ const RenderField = ({
663
668
  namePath: rowNamePath
664
669
  };
665
670
  const latestRowParams = [latestRowData, latestReactiveParams];
671
+ const cachedState = shouldUpdateStateCacheRef.current;
672
+ if (cachedState && isRecordShallowEqual(cachedState.rowSnapshot, latestRowData)) {
673
+ return cachedState.result;
674
+ }
666
675
 
667
- // 关键修改:shouldUpdate 模式下,跳过缓存,直接重新计算所有响应式属性
668
- let latestFieldProps = fieldProps || {};
669
- if (_isFunction(fieldProps)) {
676
+ // 初次 render props 会与外层 RenderField 使用同一行数据,直接复用 hook 已计算结果。
677
+ const canReuseCurrentState = isRecordShallowEqual(latestRowData, dynamicProps.rowSnapshot);
678
+ let latestFieldProps = canReuseCurrentState ? lastFieldProps : fieldProps || {};
679
+ if (!canReuseCurrentState && _isFunction(fieldProps)) {
670
680
  latestFieldProps = fieldProps(latestRowData, latestReactiveParams);
671
681
  }
672
-
673
- // 重新计算 required(用于可能的后续逻辑)
674
- let latestRequired = required;
675
- if (_isFunction(required)) {
682
+ let latestRequired = canReuseCurrentState ? lastRequired : required;
683
+ if (!canReuseCurrentState && _isFunction(required)) {
676
684
  latestRequired = required(latestRowData, latestReactiveParams);
677
685
  }
678
-
679
- // 重新计算 rules(用于可能的后续逻辑)
680
- let latestRules = rules;
681
- if (_isFunction(rules)) {
686
+ let latestRules = canReuseCurrentState ? lastRules : rules;
687
+ if (!canReuseCurrentState && _isFunction(rules)) {
682
688
  latestRules = rules(latestRowData, latestReactiveParams);
683
689
  }
684
-
685
- // 重新计算 desensitization
686
- let latestDesensitization = desensitization || [];
687
- if (_isFunction(desensitization)) {
690
+ let latestDesensitization = canReuseCurrentState ? lastDesensitization : desensitization || [];
691
+ if (!canReuseCurrentState && _isFunction(desensitization)) {
688
692
  latestDesensitization = desensitization(latestRowData, latestReactiveParams);
689
693
  }
690
-
691
- // 重新计算 component
692
- let latestComponent = component || editRender;
693
- if (_isFunction(latestComponent)) {
694
+ let latestComponent = canReuseCurrentState ? lastComponent : component || editRender;
695
+ if (!canReuseCurrentState && _isFunction(latestComponent)) {
694
696
  latestComponent = latestComponent(latestRowData, latestReactiveParams);
695
697
  }
696
-
697
- // 重新计算 isEditable(使用原始的 column.isEditable,而不是处理后的 isEditable)
698
- let latestIsEditable = column.isEditable ?? true;
699
- if (_isFunction(column.isEditable)) {
698
+ let latestIsEditable = canReuseCurrentState ? isEditable : column.isEditable ?? true;
699
+ if (!canReuseCurrentState && _isFunction(column.isEditable)) {
700
700
  latestIsEditable = column.isEditable(latestRowData, latestReactiveParams);
701
701
  }
702
-
703
- // 重新计算 disabled
704
- const latestDisabled = getDisabled({
702
+ let latestValueType = canReuseCurrentState ? lastValueType : valueType;
703
+ if (!canReuseCurrentState && _isFunction(valueType)) {
704
+ const latestCurrentValue = dataIndex ? latestRowData?.[dataIndex] : null;
705
+ latestValueType = valueType(latestCurrentValue, latestRowData, {
706
+ index,
707
+ form,
708
+ namePath: rowNamePath,
709
+ name: column?.name
710
+ });
711
+ }
712
+ const latestDisabled = canReuseCurrentState ? lastDisabled : getDisabled({
705
713
  globalControl: otherProps?.globalControl,
706
714
  formDisabled: otherProps?.formDisabled,
707
715
  column,
@@ -721,13 +729,8 @@ const RenderField = ({
721
729
  onChange: handleChange,
722
730
  onBlur: handleBlur
723
731
  };
724
-
725
- // ⭐ 关键修改:在 shouldUpdate 模式下,需要重新设置 TargetComponent
726
- // 因为 component 函数可能返回不同的结果
727
732
  let latestTargetComponent = TargetComponent;
728
-
729
- // 首先检查是否有内置type
730
- if (!latestComponent && typeof type === 'string') {
733
+ if (!canReuseCurrentState && !latestComponent && typeof type === 'string') {
731
734
  latestTargetComponent = componentMap[type] ?? /*#__PURE__*/_jsx(_Fragment, {});
732
735
  }
733
736
 
@@ -741,7 +744,7 @@ const RenderField = ({
741
744
  params: latestRowParams,
742
745
  rowDisabled: rowDisabled || 'empty'
743
746
  });
744
- if (latestIsEditable && isEditing) {
747
+ if (!canReuseCurrentState && latestIsEditable && isEditing) {
745
748
  if ( /*#__PURE__*/React.isValidElement(latestComponent)) {
746
749
  latestTargetComponent = latestComponent;
747
750
  } else if (latestComponent) {
@@ -750,7 +753,7 @@ const RenderField = ({
750
753
  }
751
754
 
752
755
  // 查看模式
753
- if (latestIsView) {
756
+ if (!canReuseCurrentState && latestIsView) {
754
757
  if (typeof viewRender === 'function') {
755
758
  const latestCurrentValue = dataIndex ? latestRowData?.[dataIndex] : value;
756
759
  const View = viewRender(latestCurrentValue, latestRowData || {}, options);
@@ -769,7 +772,8 @@ const RenderField = ({
769
772
  ...latestComponentProps,
770
773
  otherProps: {
771
774
  ...latestComponentProps.otherProps,
772
- isView: latestIsView
775
+ isView: latestIsView,
776
+ valueType: latestValueType
773
777
  }
774
778
  };
775
779
 
@@ -788,7 +792,7 @@ const RenderField = ({
788
792
  };
789
793
  }
790
794
  }
791
- return {
795
+ const result = {
792
796
  finalComponentProps: latestComponentProps,
793
797
  targetComponent: latestTargetComponent,
794
798
  latestIsView,
@@ -796,6 +800,13 @@ const RenderField = ({
796
800
  effectiveRules: latestRules,
797
801
  effectiveFieldProps: latestFieldProps
798
802
  };
803
+ shouldUpdateStateCacheRef.current = {
804
+ rowSnapshot: {
805
+ ...latestRowData
806
+ },
807
+ result
808
+ };
809
+ return result;
799
810
  };
800
811
 
801
812
  // 根据 latestIsView 和最新 required/rules 构建正确的 Form.Item props
@@ -17,8 +17,24 @@ import Empty from "../assets/empty.png";
17
17
  import locale, { formatMessage } from "../locale";
18
18
  import { BaseTable, DraggableTable } from "./components/RcTable";
19
19
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
20
+ const EMPTY_VALUE = [];
21
+ const EMPTY_ACTION_PROPS = [];
22
+ const EMPTY_TOOLBAR_PROPS = [];
23
+
24
+ /** 保留浅层语义相同对象的引用,避免 rest props 每次分配新对象导致整表重算。 */
25
+ const useShallowStableObject = value => {
26
+ const valueRef = useRef(value);
27
+ const previous = valueRef.current;
28
+ const previousKeys = Object.keys(previous);
29
+ const nextKeys = Object.keys(value);
30
+ const changed = previousKeys.length !== nextKeys.length || nextKeys.some(key => !Object.is(previous[key], value[key]));
31
+ if (changed) {
32
+ valueRef.current = value;
33
+ }
34
+ return valueRef.current;
35
+ };
20
36
  const ProEditTable = ({
21
- value = [],
37
+ value = EMPTY_VALUE,
22
38
  onChange,
23
39
  onDrag,
24
40
  onDragEnd,
@@ -32,8 +48,8 @@ const ProEditTable = ({
32
48
  insertType = 'after',
33
49
  emptyBtnText = locale.ProEditTable.clickAdd,
34
50
  actionWidth,
35
- actionProps = [],
36
- toolbarProps = [],
51
+ actionProps = EMPTY_ACTION_PROPS,
52
+ toolbarProps = EMPTY_TOOLBAR_PROPS,
37
53
  toolbarSticky,
38
54
  rowSelection,
39
55
  onlyOneLineMsg = locale.ProEditTable.onlyOneLineMsg,
@@ -53,13 +69,14 @@ const ProEditTable = ({
53
69
  diffConfig,
54
70
  ...resetProps
55
71
  }, ref) => {
72
+ const stableResetProps = useShallowStableObject(resetProps);
56
73
  // 上下文form
57
74
  const contentForm = Form.useFormInstance();
58
75
  const formFieldProps = ProForm.useFieldProps() || {};
59
76
  const affixRef = useRef(null);
60
- const form = contentForm || formFieldProps?.form || resetProps?.form;
61
- let name = formFieldProps?.name || resetProps?.name || resetProps?.id?.split?.('_');
62
- const shouldUpdateDebounce = formFieldProps?.shouldUpdateDebounce ?? resetProps?.shouldUpdateDebounce ?? 200;
77
+ const form = contentForm || formFieldProps?.form || stableResetProps?.form;
78
+ let name = formFieldProps?.name || stableResetProps?.name || stableResetProps?.id?.split?.('_');
79
+ const shouldUpdateDebounce = formFieldProps?.shouldUpdateDebounce ?? stableResetProps?.shouldUpdateDebounce ?? 200;
63
80
  const {
64
81
  isView = false,
65
82
  viewEmpty = '-',
@@ -71,6 +88,9 @@ const ProEditTable = ({
71
88
  name = [...namePath, ...name.slice(1)];
72
89
  disabled = formFieldProps?.disabled || disabled; // formFieldProps?.disabled可能是函数??
73
90
  }
91
+ const nameSignature = Array.isArray(name) ? name.join('\u0001') : String(name);
92
+ // ProForm 上下文可能每次渲染都生成新的 namePath 数组,按路径语义稳定引用。
93
+ name = useMemo(() => name, [nameSignature]);
74
94
  const tableRef = useRef(null);
75
95
  // 虚拟表格错误存储:脱离 antd Field 生命周期持久化真实报错文案,
76
96
  // 解决滚动卸载后报错丢失(报错常驻)。按表格实例隔离。
@@ -95,6 +115,15 @@ const ProEditTable = ({
95
115
  const requiredAlign = useMemo(() => {
96
116
  return themeConfig?.data?.zauiFormAlign ?? configRequiredAlign ?? 'left';
97
117
  }, [themeConfig?.data?.zauiFormAlign, configRequiredAlign]);
118
+ const renderOtherProps = useMemo(() => ({
119
+ globalControl: stableResetProps?.otherProps?.globalControl,
120
+ formDisabled: stableResetProps?.otherProps?.formDisabled,
121
+ desensitizationKey: stableResetProps?.otherProps?.desensitizationKey
122
+ }), [stableResetProps?.otherProps?.desensitizationKey, stableResetProps?.otherProps?.formDisabled, stableResetProps?.otherProps?.globalControl]);
123
+ const mergedDiffConfig = useMemo(() => ({
124
+ ...stableResetProps?.otherProps?.diffConfig,
125
+ ...diffConfig
126
+ }), [diffConfig, stableResetProps?.otherProps?.diffConfig]);
98
127
  const [state, setState] = useSetState({
99
128
  forceUpdate: {},
100
129
  // 表格内部强制刷新开关
@@ -127,7 +156,7 @@ const ProEditTable = ({
127
156
  pageSize: paginationConfig?.pageSize ?? page.pageSize
128
157
  }), [page.pageNum, page.pageSize, paginationConfig?.current, paginationConfig?.pageSize]);
129
158
  const mergedSelectedRowKeys = rowSelection?.selectedRowKeys ?? selectedRowKeys;
130
- const virtualRowName = getNamePath(_isArray(name) ? name : [name], virtualKey);
159
+ const virtualRowName = useMemo(() => getNamePath(_isArray(name) ? name : [name], virtualKey), [name, virtualKey]);
131
160
 
132
161
  // 样式处理
133
162
  const _className = classnames({
@@ -150,7 +179,7 @@ const ProEditTable = ({
150
179
  const originalValues = useMemo(() => {
151
180
  let nextOriginalValues;
152
181
  // 原始值也需要设置对应的rowkey,后续比对通过rowkey对比
153
- const originalArr = _get(resetProps?.originalValues, name);
182
+ const originalArr = _get(stableResetProps?.originalValues, name);
154
183
  const isOriginalAllHasKey = originalArr ? originalArr?.every?.(item => item.rowKey) : true;
155
184
  const isNeedCoverOriginal = originalArr?.some?.(item => item.rowKey !== getRowKey(item));
156
185
  // 初始化默认生成row-key
@@ -164,14 +193,14 @@ const ProEditTable = ({
164
193
  _set(nextOriginalValues, name, originalArr);
165
194
  }
166
195
  return nextOriginalValues;
167
- }, [resetProps?.originalValues]);
196
+ }, [getRowKey, name, stableResetProps?.originalValues]);
168
197
  const getIsNew = useCallback(record => {
169
198
  const originalArr = _get(originalValues, name);
170
199
  if (!originalArr?.length) {
171
200
  return false;
172
201
  }
173
202
  return !originalArr?.find?.(item => getRowKey(item) === getRowKey(record));
174
- }, [originalValues, name]);
203
+ }, [getRowKey, originalValues, name]);
175
204
 
176
205
  // 分页变更
177
206
  const handlePageChange = useCallback(async (current, pageSize) => {
@@ -230,27 +259,27 @@ const ProEditTable = ({
230
259
  prefixCls,
231
260
  rowDisabled,
232
261
  actionDirection,
233
- diffConfig: {
234
- ...resetProps?.otherProps?.diffConfig,
235
- ...diffConfig
236
- },
262
+ diffConfig: mergedDiffConfig,
237
263
  shouldUpdateDebounce,
238
264
  getIsNew,
239
265
  handlePageChange,
240
- ...resetProps
266
+ validateTrigger: stableResetProps?.validateTrigger,
267
+ validateMessages: stableResetProps?.validateMessages,
268
+ virtual: stableResetProps?.virtual,
269
+ otherProps: renderOtherProps
241
270
  }), [actionDirection, actionProps, actionWidth, columns, deletePoConfirmMsg, diffConfig, editingKeys, emptyBtnText, form,
242
271
  // forceUpdate 不应该作为依赖项,因为它是触发刷新的信号,而非用于比较的数据
243
- getIsNew, handlePageChange, insertType, isView, max, mode, mulDeletePoConfirmMsg, name, namePath, onlyOneLineMsg, originalValues, prefixCls, requiredAlign, resetProps, rowDisabled, mergedSelectedRowKeys, selectedRows, shouldUpdateDebounce, tableRef, toolbarProps, value?.length, viewEmpty, virtualKey, resolvedPage]);
272
+ getIsNew, handlePageChange, insertType, isView, max, mergedDiffConfig, mode, mulDeletePoConfirmMsg, name, namePath, onlyOneLineMsg, originalValues, prefixCls, requiredAlign, renderOtherProps, rowDisabled, mergedSelectedRowKeys, selectedRows, shouldUpdateDebounce, stableResetProps?.validateMessages, stableResetProps?.validateTrigger, stableResetProps?.virtual, tableRef, toolbarProps, value?.length, viewEmpty, virtualKey, resolvedPage]);
244
273
 
245
274
  // 编辑行设置下样式
246
- const _rowClassName = record => {
275
+ const _rowClassName = useCallback(record => {
247
276
  const isEdit = !virtualKey || editingKeys.includes(record.rowKey);
248
- const className = isEdit ? 'is-editing' : '';
277
+ const rowClassName = isEdit ? 'is-editing' : '';
249
278
  if (getIsNew(record)) {
250
- return `is-new-row ${className}`;
279
+ return `is-new-row ${rowClassName}`;
251
280
  }
252
- return className;
253
- };
281
+ return rowClassName;
282
+ }, [editingKeys, getIsNew, virtualKey]);
254
283
 
255
284
  // 判断是否禁止添加、批量删除
256
285
  const isForbiddenBtn = useCallback(type => {
@@ -274,7 +303,7 @@ const ProEditTable = ({
274
303
  const isHideCheckBox = !rowSelection && isForbiddenBtn('mulDelete');
275
304
 
276
305
  // 复选框
277
- const _rowSelection = {
306
+ const _rowSelection = useMemo(() => ({
278
307
  fixed: true,
279
308
  columnWidth: 48,
280
309
  getCheckboxProps: record => {
@@ -296,7 +325,7 @@ const ProEditTable = ({
296
325
  });
297
326
  rowSelection?.onChange?.(nextSelectedRowKeys, nextSelectedRows, info);
298
327
  }
299
- };
328
+ }), [isHideCheckBox, mergedSelectedRowKeys, rowDisabled, rowSelection, setState]);
300
329
 
301
330
  // 空列表状态
302
331
  const emptyDom = useMemo(() => () => /*#__PURE__*/_jsxs("div", {
@@ -321,7 +350,7 @@ const ProEditTable = ({
321
350
  * 渲染表格选择器
322
351
  * @returns rowSelection
323
352
  */
324
- const renderRowSelection = () => {
353
+ const renderRowSelection = useCallback(() => {
325
354
  if (disabled || isView) {
326
355
  return null;
327
356
  }
@@ -332,10 +361,10 @@ const ProEditTable = ({
332
361
  return _rowSelection;
333
362
  }
334
363
  return null;
335
- };
364
+ }, [_rowSelection, disabled, draggable, isHideCheckBox, isView]);
336
365
  const _columns = useMemo(() => {
337
366
  return transformColumns(columns, config, cellCachesRef.current);
338
- }, [disabled, forceUpdate, columns, page, actionProps, editingKeys, config]);
367
+ }, [columns, config, forceUpdate]);
339
368
  const initDataSource = () => {
340
369
  // 检查每一项是否有 rowKey 或通过 rowKey 字段获取的 key
341
370
  const isAllHasKey = value?.every?.(item => item.rowKey || typeof rowKey === 'string' && item[rowKey]);
@@ -359,7 +388,7 @@ const ProEditTable = ({
359
388
  useEffect(() => {
360
389
  initDataSource();
361
390
  }, [value, originalValues]);
362
- useImperativeHandle(resetProps?.ref || ref, () => {
391
+ useImperativeHandle(stableResetProps?.ref || ref, () => {
363
392
  return {
364
393
  getInternalState: () => state,
365
394
  setInternalState: setState
@@ -377,11 +406,11 @@ const ProEditTable = ({
377
406
  }
378
407
  }, [toolbarSticky]);
379
408
  const _toolbarSticky = _isBoolean(toolbarSticky) ? {} : toolbarSticky;
380
- const _summary = typeof summary === 'object' ? data => /*#__PURE__*/_jsx(Summary, {
409
+ const _summary = useMemo(() => typeof summary === 'object' ? data => /*#__PURE__*/_jsx(Summary, {
381
410
  data: data,
382
411
  summary: summary,
383
412
  rowSelection: renderRowSelection()
384
- }) : summary;
413
+ }) : summary, [renderRowSelection, summary]);
385
414
  const TableComponent = draggable ? DraggableTable : BaseTable;
386
415
 
387
416
  // 给每个 record 附加编辑状态标识,让 shouldCellUpdate 能检测到状态变化
@@ -435,17 +464,18 @@ const ProEditTable = ({
435
464
  }) => rest);
436
465
  onChange?.(cleanValue);
437
466
  }, [onChange]);
467
+ const draggableProps = useMemo(() => ({
468
+ onChange: draggable ? handleDragChange : onChange,
469
+ onDrag,
470
+ onDragEnd,
471
+ draggable
472
+ }), [draggable, handleDragChange, onChange, onDrag, onDragEnd]);
438
473
  return /*#__PURE__*/_jsxs(_Fragment, {
439
474
  children: [/*#__PURE__*/_jsx(ConfigProvider, {
440
475
  renderEmpty: value?.length ? undefined : emptyDom,
441
476
  children: /*#__PURE__*/_jsx(TableComponent, {
442
477
  ...resetProps,
443
- draggableProps: {
444
- onChange: draggable ? handleDragChange : onChange,
445
- onDrag,
446
- onDragEnd,
447
- draggable
448
- },
478
+ draggableProps: draggableProps,
449
479
  tableProps: tableProps
450
480
  })
451
481
  }), !isView && value?.length ? toolbarSticky ? /*#__PURE__*/_jsx(Affix, {
@@ -485,7 +515,7 @@ const ProEditTable = ({
485
515
  virtualKey: virtualKey,
486
516
  editingKeys: editingKeys,
487
517
  onlyOneLineMsg: onlyOneLineMsg,
488
- virtual: resetProps?.virtual,
518
+ virtual: stableResetProps?.virtual,
489
519
  columns: columns,
490
520
  config: config,
491
521
  errorStore: errorStoreRef.current
@@ -11,6 +11,8 @@ interface Result {
11
11
  desensitization: any;
12
12
  valueType: any;
13
13
  component: any;
14
+ /** 当前缓存结果对应的行浅快照,供 RenderField 判断是否可安全复用。 */
15
+ rowSnapshot: Record<string, any> | undefined;
14
16
  }
15
17
  declare const useShouldUpdateForTable: (props: UseShouldUpdateForTableProps) => Result;
16
18
  export default useShouldUpdateForTable;
@@ -3,6 +3,7 @@ import _isEqualWith from "lodash/isEqualWith";
3
3
  import _isFunction from "lodash/isFunction";
4
4
  import { useRef, useState, useEffect } from 'react';
5
5
  import { customEqualForFunction } from "../../utils";
6
+ import { isRecordShallowEqual } from "./tools";
6
7
  const useShouldUpdateForTable = props => {
7
8
  const {
8
9
  rowParams,
@@ -18,6 +19,7 @@ const useShouldUpdateForTable = props => {
18
19
  const desensitizationRef = useRef(undefined);
19
20
  const valueTypeRef = useRef(undefined);
20
21
  const componentRef = useRef(undefined);
22
+ const resolvedRowSnapshotRef = useRef(undefined);
21
23
  const [, reRender] = useState({});
22
24
  const debouncedUpdateRef = useRef(null);
23
25
  const pendingParamsRef = useRef(null);
@@ -107,6 +109,9 @@ const useShouldUpdateForTable = props => {
107
109
  } else {
108
110
  componentRef.current = componentOrEditRender;
109
111
  }
112
+ resolvedRowSnapshotRef.current = values && typeof values === 'object' ? {
113
+ ...values
114
+ } : values;
110
115
  if (hasChange) {
111
116
  reRender({});
112
117
  }
@@ -136,12 +141,15 @@ const useShouldUpdateForTable = props => {
136
141
  const fieldPropsFactoryChanged = prevFieldPropsFactoryRef.current !== currentFieldPropsFactory;
137
142
  prevFieldPropsFactoryRef.current = currentFieldPropsFactory;
138
143
 
139
- // 行数据引用 + 行索引均不变时跳过深比较(虚拟滚动常见路径)
144
+ // 上一次保存的是浅快照,可同时覆盖引用稳定和 onFieldChange 原地修改两种路径。
140
145
  const prevRowParams = prevRowParamsRef.current;
141
- const rowDataUnchanged = isInitializedRef.current && prevRowParams?.[0] === rowParams[0] && prevRowParams?.[1]?.index === rowParams[1]?.index;
146
+ const rowDataUnchanged = isInitializedRef.current && isRecordShallowEqual(prevRowParams?.[0], rowParams[0]) && prevRowParams?.[1]?.index === rowParams[1]?.index;
142
147
  const shouldProcess = !isInitializedRef.current || fieldPropsFactoryChanged || !rowDataUnchanged && !_isEqualWith(prevRowParams, rowParams, customEqualForFunction);
143
148
  if (shouldProcess) {
144
- prevRowParamsRef.current = rowParams;
149
+ const currentRowData = rowParams[0];
150
+ prevRowParamsRef.current = [currentRowData && typeof currentRowData === 'object' ? {
151
+ ...currentRowData
152
+ } : currentRowData, rowParams[1]];
145
153
  isInitializedRef.current = true;
146
154
  if (fieldPropsFactoryChanged) {
147
155
  // 外部依赖变化会生成新的 fieldProps 函数,需要立即替换旧闭包缓存
@@ -162,7 +170,8 @@ const useShouldUpdateForTable = props => {
162
170
  fieldProps: fieldPropsRef.current ?? column.fieldProps,
163
171
  desensitization: desensitizationRef.current ?? column.desensitization,
164
172
  valueType: valueTypeRef.current ?? column.valueType,
165
- component: componentRef.current ?? (column.component || column.editRender)
173
+ component: componentRef.current ?? (column.component || column.editRender),
174
+ rowSnapshot: resolvedRowSnapshotRef.current
166
175
  };
167
176
  };
168
177
  export default useShouldUpdateForTable;
@@ -51,6 +51,7 @@ export function TabsHeader({
51
51
  const tabKeys = (tabsItems ?? []).map(item => item.key);
52
52
  return /*#__PURE__*/_jsx("div", {
53
53
  className: "pro-layout-tabs-header",
54
+ "data-pro-layout-tabs-measure": "true",
54
55
  children: /*#__PURE__*/_jsx(Tabs, {
55
56
  activeKey: activeKey,
56
57
  onChange: onTabChange,
@@ -17,6 +17,7 @@ import headerBg from "../assets/header_bg.png";
17
17
  // 全局上下文
18
18
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
19
  export const LayoutContext = /*#__PURE__*/createContext(undefined);
20
+ const TABS_HEADER_MEASURE_SELECTOR = '[data-pro-layout-tabs-measure="true"]';
20
21
  const ProLayout = props => {
21
22
  const {
22
23
  children,
@@ -94,26 +95,45 @@ const ProLayout = props => {
94
95
  useLayoutEffect(() => {
95
96
  const noticeEl = notice && !isIframePure ? noticeElRef.current : null;
96
97
  const visibleTabsBarEl = isTabsLayout && hasTabs && !isIframePure ? tabsBarEl : null;
98
+ const visibleTabsHeaderEl = visibleTabsBarEl?.querySelector(TABS_HEADER_MEASURE_SELECTOR);
97
99
  let active = true;
100
+ let animationFrameId;
98
101
  const measureStickyElements = () => {
99
102
  if (!active) return;
100
103
  const nextHeights = {
101
104
  notice: noticeEl?.getBoundingClientRect().height ?? 0,
102
- tabsBar: visibleTabsBarEl?.getBoundingClientRect().height ?? 0
105
+ tabsBar: visibleTabsHeaderEl?.getBoundingClientRect().height ?? 0
103
106
  };
104
107
  setStickyHeights(currentHeights => currentHeights.notice === nextHeights.notice && currentHeights.tabsBar === nextHeights.tabsBar ? currentHeights : nextHeights);
105
108
  };
106
109
  measureStickyElements();
107
- const observedElements = [noticeEl, visibleTabsBarEl].filter(element => element !== null);
110
+ if (typeof window.requestAnimationFrame === 'function') {
111
+ animationFrameId = window.requestAnimationFrame(measureStickyElements);
112
+ }
113
+ const handleTabsTransitionEnd = event => {
114
+ if (event.target === visibleTabsBarEl && event.propertyName === 'grid-template-rows') {
115
+ measureStickyElements();
116
+ }
117
+ };
118
+ visibleTabsBarEl?.addEventListener('transitionend', handleTabsTransitionEnd);
119
+ const observedElements = [noticeEl, visibleTabsHeaderEl].filter(element => element !== null && element !== undefined);
108
120
  if (observedElements.length === 0 || typeof ResizeObserver === 'undefined') {
109
121
  return () => {
110
122
  active = false;
123
+ if (animationFrameId !== undefined) {
124
+ window.cancelAnimationFrame(animationFrameId);
125
+ }
126
+ visibleTabsBarEl?.removeEventListener('transitionend', handleTabsTransitionEnd);
111
127
  };
112
128
  }
113
129
  const resizeObserver = new ResizeObserver(measureStickyElements);
114
130
  observedElements.forEach(element => resizeObserver.observe(element));
115
131
  return () => {
116
132
  active = false;
133
+ if (animationFrameId !== undefined) {
134
+ window.cancelAnimationFrame(animationFrameId);
135
+ }
136
+ visibleTabsBarEl?.removeEventListener('transitionend', handleTabsTransitionEnd);
117
137
  resizeObserver.disconnect();
118
138
  };
119
139
  }, [hasTabs, isIframePure, isTabsLayout, notice, tabsBarEl]);
@@ -388,11 +388,10 @@
388
388
 
389
389
  .pro-layout-tabs-content {
390
390
  > .tab-pane {
391
- // ProStep 以 ProHeader 开头时,去掉 tabs 内容区的顶部/右侧外层留白。
392
- // ProStep 保持默认 right: 0,内容与收起态步骤条保留 16px 间距;左侧缩进不变。
391
+ // ProStep 以 ProHeader 开头时,仅去掉 tabs 内容区的顶部留白。
392
+ // 保留右侧内边距,让 ProStep wrapper 继续为贴右步骤条预留 16px 间距。
393
393
  > .pro-step-wrapper:has(> .pro-header:first-child) {
394
394
  margin-top: calc(-1px - var(--zaui-space-size-md, 16px));
395
- margin-right: calc(-1px - var(--zaui-space-size-md, 16px));
396
395
  }
397
396
 
398
397
  > .pro-header.pro-header-no-describe:first-child {
@@ -1,6 +1,5 @@
1
+ import { EditOutlined } from '@ant-design/icons';
1
2
  import React from 'react';
2
- import ProIcon from "../../../ProIcon";
3
- import editSvg from "../../../assets/edit.svg";
4
3
  import styles from "./index.less";
5
4
  import { jsx as _jsx } from "react/jsx-runtime";
6
5
  const EditIcon = ({
@@ -9,10 +8,7 @@ const EditIcon = ({
9
8
  return /*#__PURE__*/_jsx("span", {
10
9
  className: styles['editable-cell-edit-icon'],
11
10
  onClick: onClick,
12
- children: /*#__PURE__*/_jsx(ProIcon, {
13
- src: editSvg,
14
- size: "20px"
15
- })
11
+ children: /*#__PURE__*/_jsx(EditOutlined, {})
16
12
  });
17
13
  };
18
14
  export default EditIcon;
@@ -14,10 +14,6 @@
14
14
  color: rgba(0, 0, 0, 0.65);
15
15
  flex-shrink: 0;
16
16
 
17
- svg path {
18
- fill: currentColor !important;
19
- }
20
-
21
17
  &:hover {
22
18
  color: var(--zaui-brand, #006aff);
23
19
  }
@@ -26,4 +22,3 @@
26
22
  td:hover .editable-cell-edit-icon {
27
23
  visibility: visible;
28
24
  }
29
-