@openli1115/lowcode-edit-pro-table 1.0.88 → 1.0.90

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.
@@ -9,7 +9,38 @@ import { FORM_EMPTY_ARRAY } from "../../context/useComponentContext";
9
9
  var _window$React = window.React,
10
10
  createElement = _window$React.createElement,
11
11
  useCallback = _window$React.useCallback,
12
+ useEffect = _window$React.useEffect,
12
13
  useMemo = _window$React.useMemo;
14
+ function toStr(v) {
15
+ if (v == null) return '';
16
+ return String(v);
17
+ }
18
+ function getNodeValue(node) {
19
+ if (!node || typeof node !== 'object') return '';
20
+ // 兼容 value/id/key 等常见字段
21
+ if (node.value != null) return toStr(node.value);
22
+ if (node.id != null) return toStr(node.id);
23
+ if (node.key != null) return toStr(node.key);
24
+ return '';
25
+ }
26
+ function getNodeLabel(node, fallback) {
27
+ if (fallback === void 0) {
28
+ fallback = '';
29
+ }
30
+ if (!node || typeof node !== 'object') return fallback;
31
+ if (node.label != null) return toStr(node.label);
32
+ if (node.name != null) return toStr(node.name);
33
+ if (node.title != null) return toStr(node.title);
34
+ return fallback;
35
+ }
36
+ function getNodeChildren(node) {
37
+ if (!node || typeof node !== 'object') return [];
38
+ // 兼容 children / options / childs 等结构
39
+ if (Array.isArray(node.children)) return node.children;
40
+ if (Array.isArray(node.options)) return node.options;
41
+ if (Array.isArray(node.childs)) return node.childs;
42
+ return [];
43
+ }
13
44
 
14
45
  /**
15
46
  * 按路径在树形 dataSource 上逐级匹配,取出每一层的 label(2 级、3 级…任意多级,长度由 pathValues 决定)。
@@ -21,39 +52,92 @@ function resolvePathLabels(dataSource, pathValues) {
21
52
  var _loop = function _loop() {
22
53
  var pv = _step.value;
23
54
  var node = level.find(function (n) {
24
- return String(n === null || n === void 0 ? void 0 : n.value) === String(pv);
55
+ return getNodeValue(n) === toStr(pv);
25
56
  });
26
57
  if (!node) return 1; // break
27
- labels.push(node.label != null ? String(node.label) : String(pv));
28
- var next = node.children;
29
- level = Array.isArray(next) ? next : [];
58
+ labels.push(getNodeLabel(node, toStr(pv)));
59
+ level = getNodeChildren(node);
30
60
  };
31
61
  for (var _iterator = _createForOfIteratorHelperLoose(pathValues), _step; !(_step = _iterator()).done;) {
32
62
  if (_loop()) break;
33
63
  }
34
64
  return labels;
35
65
  }
36
- function normalizeCascaderPath(v) {
66
+ function isValidPathInTree(dataSource, pathValues) {
67
+ if (!Array.isArray(dataSource) || pathValues.length === 0) return false;
68
+ var level = dataSource;
69
+ var _loop2 = function _loop2() {
70
+ var pv = _step2.value;
71
+ var node = level.find(function (n) {
72
+ return getNodeValue(n) === toStr(pv);
73
+ });
74
+ if (!node) return {
75
+ v: false
76
+ };
77
+ level = getNodeChildren(node);
78
+ },
79
+ _ret;
80
+ for (var _iterator2 = _createForOfIteratorHelperLoose(pathValues), _step2; !(_step2 = _iterator2()).done;) {
81
+ _ret = _loop2();
82
+ if (_ret) return _ret.v;
83
+ }
84
+ return true;
85
+ }
86
+
87
+ /** 回填仅有叶子 id(如 city=2977)时,递归反查完整路径 [provinceId, cityId] */
88
+ function findPathByNodeValue(dataSource, targetValue, parentPath) {
89
+ if (parentPath === void 0) {
90
+ parentPath = [];
91
+ }
92
+ if (!Array.isArray(dataSource) || targetValue === '') return [];
93
+ for (var _iterator3 = _createForOfIteratorHelperLoose(dataSource), _step3; !(_step3 = _iterator3()).done;) {
94
+ var node = _step3.value;
95
+ var curValue = getNodeValue(node);
96
+ if (!curValue) continue;
97
+ var nextPath = parentPath.concat(curValue);
98
+ if (curValue === toStr(targetValue)) {
99
+ return nextPath;
100
+ }
101
+ var hit = findPathByNodeValue(getNodeChildren(node), targetValue, nextPath);
102
+ if (hit.length > 0) return hit;
103
+ }
104
+ return [];
105
+ }
106
+ function normalizeCascaderPath(v, dataSource) {
107
+ var tryExpandByLeaf = function tryExpandByLeaf(leaf, fallback) {
108
+ if (fallback === void 0) {
109
+ fallback = [];
110
+ }
111
+ var fullPath = findPathByNodeValue(dataSource, leaf);
112
+ return fullPath.length > 0 ? fullPath : fallback;
113
+ };
37
114
  if (Array.isArray(v)) {
38
- return v.filter(function (x) {
115
+ var path = v.filter(function (x) {
39
116
  return x != null && x !== '';
40
117
  }).map(function (x) {
41
- return String(x);
118
+ return toStr(x);
42
119
  });
120
+ if (path.length === 0) return [];
121
+ if (isValidPathInTree(dataSource, path)) return path;
122
+ return tryExpandByLeaf(path[path.length - 1], path);
43
123
  }
44
124
  if (typeof v === 'number' && !Number.isNaN(v)) {
45
- /** 表单里有时是数字 id(2977),Fusion dataSource string 化,需对齐 */
46
- return [String(v)];
125
+ /** 表单里有时是数字 id(2977),需要根据 dataSource 找完整路径 */
126
+ return tryExpandByLeaf(toStr(v), [toStr(v)]);
47
127
  }
48
128
  if (typeof v === 'string' && v.trim()) {
49
129
  try {
50
130
  var parsed = JSON.parse(v);
51
- return Array.isArray(parsed) ? parsed.map(function (x) {
52
- return String(x);
53
- }) : [];
131
+ if (!Array.isArray(parsed)) return [];
132
+ var _path = parsed.map(function (x) {
133
+ return toStr(x);
134
+ });
135
+ if (_path.length === 0) return [];
136
+ if (isValidPathInTree(dataSource, _path)) return _path;
137
+ return tryExpandByLeaf(_path[_path.length - 1], _path);
54
138
  } catch (_unused) {
55
- /** 历史或单值存成非 JSON 字符串时,当作单选叶子 value */
56
- return [v];
139
+ /** 历史或接口单值回填时,当作叶子值反查完整路径 */
140
+ return tryExpandByLeaf(v, [v]);
57
141
  }
58
142
  }
59
143
  return [];
@@ -92,6 +176,7 @@ var ProCascaderSelect = function ProCascaderSelect(props) {
92
176
  screen_inner_id = props.screen_inner_id,
93
177
  range_inner_table = props.range_inner_table,
94
178
  dataSource = props.dataSource,
179
+ options = props.options,
95
180
  disableEdit = props.disableEdit,
96
181
  readOnlyProp = props.readOnly,
97
182
  disabledProp = props.disabled,
@@ -99,6 +184,7 @@ var ProCascaderSelect = function ProCascaderSelect(props) {
99
184
  schemaOnChange = props.onChange,
100
185
  defaultValue = props.defaultValue,
101
186
  multiple = props.multiple;
187
+ var isMultiple = multiple === true || multiple === 'true';
102
188
  var _resolveProScreenBind = resolveProScreenBinding({
103
189
  screen_structure: screen_structure,
104
190
  screen_structure_field: screen_structure_field,
@@ -111,34 +197,99 @@ var ProCascaderSelect = function ProCascaderSelect(props) {
111
197
  var _useProBoundValue = useProBoundValue(fieldPath, unboundInit, FORM_EMPTY_ARRAY),
112
198
  value = _useProBoundValue.value,
113
199
  commit = _useProBoundValue.commit;
200
+ // 优先 dataSource,兼容部分 schema 透传为 options
201
+ var cascaderOptions = useMemo(function () {
202
+ if (Array.isArray(dataSource)) return dataSource;
203
+ if (Array.isArray(options)) return options;
204
+ return [];
205
+ }, [dataSource, options]);
114
206
  var handleChange = function handleChange(newValue, _data, extra) {
115
207
  var next = cascaderChangeToPath(newValue, extra);
208
+ console.log('[ProCascaderSelect] onChange', {
209
+ fieldPath: fieldPath,
210
+ rawValue: newValue,
211
+ selectedPath: extra === null || extra === void 0 ? void 0 : extra.selectedPath,
212
+ normalizedPath: next
213
+ });
116
214
  commit(next);
117
215
  schemaOnChange === null || schemaOnChange === void 0 ? void 0 : schemaOnChange(next, structureName, structureField);
118
216
  };
119
217
 
120
- /** 表单/接口:完整路径 string[],如 ['2974','2975'] */
121
- var displayValue = normalizeCascaderPath(value);
218
+ /** 表单/接口:优先完整路径;若仅有叶子 id(如 2977)则由 dataSource 反查完整路径 */
219
+ var displayValue = useMemo(function () {
220
+ return normalizeCascaderPath(value, cascaderOptions);
221
+ }, [value, cascaderOptions]);
222
+ var incomingLeafId = useMemo(function () {
223
+ if (Array.isArray(value)) {
224
+ var cleaned = value.filter(function (x) {
225
+ return x != null && x !== '';
226
+ });
227
+ return cleaned.length > 0 ? toStr(cleaned[cleaned.length - 1]) : '';
228
+ }
229
+ if (typeof value === 'number' && !Number.isNaN(value)) return toStr(value);
230
+ if (typeof value === 'string' && value.trim()) {
231
+ try {
232
+ var parsed = JSON.parse(value);
233
+ if (Array.isArray(parsed) && parsed.length > 0) {
234
+ return toStr(parsed[parsed.length - 1]);
235
+ }
236
+ } catch (_unused2) {
237
+ return value;
238
+ }
239
+ return value;
240
+ }
241
+ return '';
242
+ }, [value]);
243
+ var packedPathFromLeaf = useMemo(function () {
244
+ if (!incomingLeafId) return [];
245
+ return findPathByNodeValue(cascaderOptions, incomingLeafId);
246
+ }, [cascaderOptions, incomingLeafId]);
122
247
 
123
248
  /**
124
- * Fusion 单选:官方约定受控 value 为 string[] | number[],且实现里只对数组取 value[0] 再查节点。
125
- * 若传入完整路径 [父,子],会把「父」当成选中项,文案只有一级。
126
- * 组件展示应传「叶子」单元素数组 [leaf],内部 getLabelPath(叶子节点) 会生成多级展示。
127
- * 多选仍传 Fusion 所需的多个 value(与表单存值一致)。
249
+ * 回显兼容:
250
+ * - 多选:沿用路径数组
251
+ * - 单选:传叶子值(字符串);部分 Fusion 版本单选不接受数组 value,传数组会不显示
128
252
  */
129
253
  var valueForFusion = useMemo(function () {
130
254
  var path = displayValue;
131
- if (multiple) {
255
+ if (isMultiple) {
132
256
  return path;
133
257
  }
134
- if (path.length === 0) return [];
135
- return [path[path.length - 1]];
136
- }, [displayValue, multiple]);
258
+ if (path.length === 0) return '';
259
+ return path[path.length - 1];
260
+ }, [displayValue, isMultiple]);
261
+ useEffect(function () {
262
+ var leaf = displayValue.length > 0 ? displayValue[displayValue.length - 1] : '';
263
+ var resolvedPathFromLeaf = leaf ? findPathByNodeValue(cascaderOptions, toStr(leaf)) : [];
264
+ console.log('[ProCascaderSelect] echo-id-and-path', {
265
+ fieldPath: fieldPath,
266
+ incomingLeafId: incomingLeafId,
267
+ packedPathFromLeaf: packedPathFromLeaf
268
+ });
269
+ console.log('[ProCascaderSelect] render-debug', {
270
+ fieldPath: fieldPath,
271
+ structureName: structureName,
272
+ structureField: structureField,
273
+ rawBoundValue: value,
274
+ normalizedDisplayPath: displayValue,
275
+ fusionValue: valueForFusion,
276
+ isMultiple: isMultiple,
277
+ optionsCount: cascaderOptions.length,
278
+ resolvedPathFromLeaf: resolvedPathFromLeaf,
279
+ optionsPreview: cascaderOptions.slice(0, 3).map(function (n) {
280
+ return {
281
+ value: getNodeValue(n),
282
+ label: getNodeLabel(n),
283
+ childrenCount: getNodeChildren(n).length
284
+ };
285
+ })
286
+ });
287
+ }, [fieldPath, structureName, structureField, value, displayValue, valueForFusion, isMultiple, cascaderOptions]);
137
288
 
138
289
  /** dataSource 未就绪或异步时,仍用自定义文案兜底 */
139
290
  var schemaDisplayRender = props.displayRender;
140
291
  var pathDisplayRender = useCallback(function (labels, data) {
141
- var fullLabels = resolvePathLabels(dataSource, normalizeCascaderPath(value));
292
+ var fullLabels = resolvePathLabels(cascaderOptions, displayValue);
142
293
  if (fullLabels.length > 0) {
143
294
  return fullLabels.join(' / ');
144
295
  }
@@ -146,13 +297,13 @@ var ProCascaderSelect = function ProCascaderSelect(props) {
146
297
  return schemaDisplayRender(labels, data);
147
298
  }
148
299
  return (labels && labels.length ? labels.join(' / ') : '') || ((data === null || data === void 0 ? void 0 : data.label) != null ? String(data.label) : '');
149
- }, [dataSource, value, schemaDisplayRender]);
300
+ }, [cascaderOptions, displayValue, schemaDisplayRender]);
150
301
  return /*#__PURE__*/React.createElement("span", {
151
302
  className: "field-wrapper " + (disableEdit ? 'disable-edit' : 'enable-edit')
152
303
  }, /*#__PURE__*/React.createElement(_CascaderSelect, _extends({}, props, {
153
304
  readOnly: disableEdit ? true : readOnlyProp,
154
305
  disabled: disabledProp,
155
- dataSource: dataSource,
306
+ dataSource: cascaderOptions,
156
307
  value: valueForFusion,
157
308
  onChange: handleChange,
158
309
  displayRender: pathDisplayRender
@@ -102,6 +102,13 @@ var ConditionTable = function ConditionTable(_ref) {
102
102
  onChange: function onChange(v) {
103
103
  return update(row.id, 'option', v);
104
104
  }
105
+ // 关键:Modal 被挂到 window.top.document.body,但 Select 下拉默认使用当前 iframe 的 document.body,
106
+ // 会导致下拉被渲染到 iframe 内部并被 Modal 蒙层遮挡(显示在弹窗下方)。
107
+ // 将下拉容器固定到触发节点的父元素,保证下拉与 Select 同处 Modal 的 DOM 子树中,避免跨文档渲染被盖住。
108
+ ,
109
+ getPopupContainer: function getPopupContainer(triggerNode) {
110
+ return triggerNode && triggerNode.parentElement || document.body;
111
+ }
105
112
  }), /*#__PURE__*/React.createElement(_Input, {
106
113
  size: "small",
107
114
  style: {
@@ -374,15 +381,18 @@ var ProSearch = function ProSearch(_ref2) {
374
381
  });
375
382
  onSearch && onSearch(data);
376
383
  };
377
- var renderInput = function renderInput(field, key) {
384
+ var renderInput = function renderInput(field, key, displayCond) {
378
385
  var fv = fieldValues[field.id] || {
379
386
  low: '',
380
387
  high: '',
381
388
  conditions: []
382
389
  };
383
- var val = fv[key];
384
390
  var hasConditions = fv.conditions.length > 0;
385
391
  var disabled = hasConditions;
392
+ // 4.2 / 4.2a / 4.2b / 4.5 / 4.6:有多值条件时,字段行展示"首行代表"的 low/high 值
393
+ // - 4.2a BT(介于):起始值=low,到值=high 分别填入两个输入框
394
+ // - 非 BT 情况:到值留空,仅展示 low
395
+ var val = displayCond ? key === 'low' ? displayCond.low : displayCond.option === 'BT' ? displayCond.high : '' : fv[key];
386
396
  var _onChange = function onChange(v) {
387
397
  return updateVal(field.id, key, v);
388
398
  };
@@ -422,53 +432,59 @@ var ProSearch = function ProSearch(_ref2) {
422
432
  });
423
433
  };
424
434
 
425
- // Render summary of multi-value conditions on the input area
426
- var renderCondSummary = function renderCondSummary(fv) {
427
- var inc = fv.conditions.filter(function (c) {
428
- return c.sign === 'I' && (c.low || c.option);
435
+ /**
436
+ * 4.2 / 4.2a / 4.2b / 4.5 / 4.6:在起始值输入框前渲染选项比较符徽标。
437
+ * 规则:
438
+ * - 选择 tab(sign='I') 首行存在(含 4.6 两个 tab 都有值时):
439
+ * · 4.2 option 为空或 EQ → 不显示符号
440
+ * · 4.2b 其它 option → 显示「绿底」符号(GE=≥、LT=<、BT=~、CO=∋ 等)
441
+ * - 仅排除 tab(sign='E') 有值:
442
+ * · 4.5.1 任意 option → 显示「红底」符号
443
+ * · 4.5.2 option 为空或 EQ → 仍显示「红底」的「=」
444
+ */
445
+ var renderSignBadge = function renderSignBadge(fv) {
446
+ var firstInclude = fv.conditions.find(function (c) {
447
+ return c.sign === 'I';
429
448
  });
430
- var exc = fv.conditions.filter(function (c) {
431
- return c.sign === 'E' && (c.low || c.option);
449
+ var firstExclude = fv.conditions.find(function (c) {
450
+ return c.sign === 'E';
432
451
  });
433
- if (!inc.length && !exc.length) return null;
434
- return /*#__PURE__*/React.createElement("div", {
435
- style: {
436
- fontSize: 11,
437
- color: '#666',
438
- marginTop: 2,
439
- overflow: 'hidden',
440
- textOverflow: 'ellipsis',
441
- whiteSpace: 'nowrap'
442
- }
443
- }, inc.map(function (c, i) {
452
+ var badgeStyle = {
453
+ color: '#fff',
454
+ padding: '0 4px',
455
+ borderRadius: 2,
456
+ fontSize: 12,
457
+ lineHeight: '18px',
458
+ fontWeight: 'bold',
459
+ flexShrink: 0,
460
+ minWidth: 18,
461
+ textAlign: 'center',
462
+ display: 'inline-block'
463
+ };
464
+ // 4.6:两个 tab 都有值时,按选择 tab 首行规则显示(Include 优先)
465
+ if (firstInclude) {
466
+ var opt = firstInclude.option;
467
+ // 4.2:首行 option 为空或 EQ 不显示符号
468
+ if (!opt || opt === 'EQ') return null;
469
+ var sym = OPTION_SYMBOL[opt];
470
+ if (!sym) return null;
444
471
  return /*#__PURE__*/React.createElement("span", {
445
- key: i,
446
- style: {
447
- marginRight: 4
448
- }
449
- }, c.option && OPTION_SYMBOL[c.option] && /*#__PURE__*/React.createElement("span", {
450
- style: {
451
- background: '#1890ff',
452
- color: '#fff',
453
- padding: '0 2px',
454
- borderRadius: 2
455
- }
456
- }, OPTION_SYMBOL[c.option]), c.low, c.option === 'BT' && c.high ? "~" + c.high : '');
457
- }), exc.map(function (c, i) {
472
+ style: _extends({}, badgeStyle, {
473
+ background: '#52c41a'
474
+ })
475
+ }, sym);
476
+ }
477
+ if (firstExclude) {
478
+ var _opt = firstExclude.option;
479
+ // 4.5.2:空 / EQ 也要显示红底 "=" ;其它按 OPTION_SYMBOL 显示
480
+ var _sym = _opt && OPTION_SYMBOL[_opt] || '=';
458
481
  return /*#__PURE__*/React.createElement("span", {
459
- key: i,
460
- style: {
461
- marginRight: 4
462
- }
463
- }, c.option && OPTION_SYMBOL[c.option] && /*#__PURE__*/React.createElement("span", {
464
- style: {
465
- background: '#ff4d4f',
466
- color: '#fff',
467
- padding: '0 2px',
468
- borderRadius: 2
469
- }
470
- }, OPTION_SYMBOL[c.option]), c.low);
471
- }));
482
+ style: _extends({}, badgeStyle, {
483
+ background: '#ff4d4f'
484
+ })
485
+ }, _sym);
486
+ }
487
+ return null;
472
488
  };
473
489
  var headerSize = {
474
490
  fontWeight: headerBold ? 'bold' : 'normal',
@@ -538,7 +554,16 @@ var ProSearch = function ProSearch(_ref2) {
538
554
  high: '',
539
555
  conditions: []
540
556
  };
541
- var hasMulti = fv.conditions.length > 0;
557
+ // 4.3:多值按钮高亮条件 = 条件行数达到两行或以上
558
+ var hasMulti = fv.conditions.length >= 2;
559
+ // 4.6:"首行代表" —— 选择 tab 优先;仅排除 tab 有值时取排除 tab 首行
560
+ var firstInclude = fv.conditions.find(function (c) {
561
+ return c.sign === 'I';
562
+ });
563
+ var firstExclude = fv.conditions.find(function (c) {
564
+ return c.sign === 'E';
565
+ });
566
+ var displayCond = firstInclude || firstExclude || null;
542
567
  return /*#__PURE__*/React.createElement("div", {
543
568
  key: field.id,
544
569
  style: {
@@ -557,9 +582,17 @@ var ProSearch = function ProSearch(_ref2) {
557
582
  }
558
583
  }, field.fieldDesc), /*#__PURE__*/React.createElement("div", {
559
584
  style: {
560
- flex: 3
585
+ flex: 3,
586
+ display: 'flex',
587
+ alignItems: 'center',
588
+ gap: 4
589
+ }
590
+ }, renderSignBadge(fv), /*#__PURE__*/React.createElement("div", {
591
+ style: {
592
+ flex: 1,
593
+ minWidth: 0
561
594
  }
562
- }, renderInput(field, 'low'), renderCondSummary(fv)), showHighValue && /*#__PURE__*/React.createElement("div", {
595
+ }, renderInput(field, 'low', displayCond))), showHighValue && /*#__PURE__*/React.createElement("div", {
563
596
  style: {
564
597
  width: 24,
565
598
  textAlign: 'center',
@@ -570,7 +603,7 @@ var ProSearch = function ProSearch(_ref2) {
570
603
  style: {
571
604
  flex: 3
572
605
  }
573
- }, renderInput(field, 'high')), multiValue && /*#__PURE__*/React.createElement("div", {
606
+ }, renderInput(field, 'high', displayCond)), multiValue && /*#__PURE__*/React.createElement("div", {
574
607
  style: {
575
608
  width: 32,
576
609
  textAlign: 'center'
@@ -4,9 +4,5 @@ export declare function getFormContext(): any;
4
4
  export declare const FormProvider: React.FC<{
5
5
  initialValues?: Record<string, any>;
6
6
  onChange?: (values: Record<string, any>) => void;
7
- /** 由 preview 传入:最后一次 onChange(newStructures) 的引用;若与 initialValues 全等则本次为 store 回灌,勿再 merge(根治 #185) */
8
- lastOutboundStructuresRef?: {
9
- current: any;
10
- };
11
7
  children: React.ReactNode;
12
8
  }>;
@@ -33,47 +33,26 @@ export var FormProvider = function FormProvider(_ref) {
33
33
  var _ref$initialValues = _ref.initialValues,
34
34
  initialValues = _ref$initialValues === void 0 ? {} : _ref$initialValues,
35
35
  onChange = _ref.onChange,
36
- lastOutboundStructuresRef = _ref.lastOutboundStructuresRef,
37
36
  children = _ref.children;
38
37
  var FormContext = getFormContext();
39
38
 
40
39
  // 使用 ref 缓存初始值
41
40
  var initialValuesRef = useRef(initialValues);
42
- var _useState = useState(initialValues),
41
+
42
+ // 与外部 screenStructures 断开引用,避免逻辑流原地修改 window 对象时污染表单内部 state
43
+ var _useState = useState(function () {
44
+ return cloneDeep(initialValues);
45
+ }),
43
46
  values = _useState[0],
44
47
  setValues = _useState[1];
45
48
 
46
- /** 每次渲染同步,供 initialValues effect 判断「store 是否只是回灌了当前表单已有数据」 */
47
- var valuesRef = useRef(values);
48
- valuesRef.current = values;
49
-
50
49
  /**
51
- * store 每次 dispatch 都会给新的 screenStructures 引用;previewStore 直接把 onChange 的 payload 设为 state(同引用)。
52
- * initialValues === 上次 handleFormChange 推出去的对象,说明只是「表单store → props」回灌,禁止再 merge,否则大表 + Fusion #185。
53
- * 逻辑流 / 接口改数会换对象引用,不会与 lastOutbound 全等。
54
- * 另:setFieldValue 会在微任务里 onChange,回灌前 lastOutbound 已同步为深拷贝对象;若 store 又克隆导致引用不一致,需用深比较兜底。
50
+ * 不能只用 !isEqual:store 对同引用 clone 回灌时,initialValues 是新引用,但与 initialValuesRef 旧对象深比较相等,
51
+ * 会误判「无变化」而不 merge;若表单 values 已是用户 cloneDeep 的副本,不会随原地修改更新逻辑流不回填。
52
+ * 用「引用是否变化」决定是否同步;再用 merged 深比较避免无谓 setState(减轻 Fusion #185)。
55
53
  */
56
54
  useEffect(function () {
57
- if (lastOutboundStructuresRef && initialValues === lastOutboundStructuresRef.current) {
58
- initialValuesRef.current = initialValues;
59
- if (typeof console !== 'undefined' && console.debug) {
60
- console.debug('[FormProvider] skip merge (store ref === last outbound from form)');
61
- }
62
- return;
63
- }
64
- if (lastOutboundStructuresRef !== null && lastOutboundStructuresRef !== void 0 && lastOutboundStructuresRef.current && isEqual(initialValues, lastOutboundStructuresRef.current)) {
65
- initialValuesRef.current = initialValues;
66
- if (typeof console !== 'undefined' && console.debug) {
67
- console.debug('[FormProvider] skip merge (deep-equal to last outbound / store echo)');
68
- }
69
- return;
70
- }
71
- if (isEqual(initialValues, valuesRef.current)) {
72
- initialValuesRef.current = initialValues;
73
- return;
74
- }
75
- if (isEqual(initialValues, initialValuesRef.current)) {
76
- initialValuesRef.current = initialValues;
55
+ if (initialValues === initialValuesRef.current) {
77
56
  return;
78
57
  }
79
58
  console.log('[FormProvider] 🔄 useEffect triggered - initialValues:', initialValues);
@@ -87,7 +66,7 @@ export var FormProvider = function FormProvider(_ref) {
87
66
  });
88
67
  }, [initialValues]);
89
68
 
90
- // 优化 setFieldValue(lastOutbound 必须在 onChange 微任务之前写入,供 skip-merge 与 Fusion didUpdate 对齐)
69
+ // 优化 setFieldValue
91
70
  var setFieldValue = useCallback(function (path, value) {
92
71
  setValues(function (prev) {
93
72
  // 使用浅比较检查值是否实际变化
@@ -97,25 +76,19 @@ export var FormProvider = function FormProvider(_ref) {
97
76
  var newValues = cloneDeep(prev);
98
77
  set(newValues, path, value);
99
78
  console.log("[FormProvider] \uD83D\uDCDD Set field '" + path + "' to:", value);
100
- if (lastOutboundStructuresRef) {
101
- lastOutboundStructuresRef.current = newValues;
102
- }
79
+
80
+ // 触发 onChange 回调,把新值通知给外部
103
81
  if (onChange) {
82
+ // 对外回传使用副本,避免 preview/window 与 FormProvider 共用同一对象引用
83
+ var outboundValues = cloneDeep(newValues);
84
+ // 使用微任务避免阻塞渲染
104
85
  Promise.resolve().then(function () {
105
- var rd = typeof window !== 'undefined' ? window.ReactDOM : undefined;
106
- var run = function run() {
107
- return onChange(newValues);
108
- };
109
- if (rd && typeof rd.unstable_batchedUpdates === 'function') {
110
- rd.unstable_batchedUpdates(run);
111
- } else {
112
- run();
113
- }
86
+ onChange(outboundValues);
114
87
  });
115
88
  }
116
89
  return newValues;
117
90
  });
118
- }, [onChange, lastOutboundStructuresRef]);
91
+ }, [onChange]);
119
92
 
120
93
  // 使用 useMemo 缓存 context 值
121
94
  var contextValue = useMemo(function () {