@nokecy/qc-ui 1.1.45 → 1.1.47

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.
@@ -10,6 +10,7 @@ var _antd = require("antd");
10
10
  var _react = _interopRequireWildcard(require("react"));
11
11
  var _agGridEnterprise = require("ag-grid-enterprise");
12
12
  var _theme = require("./theme");
13
+ var _useTypeInferenceRow = require("./hooks/useTypeInferenceRow");
13
14
  require("./index.less");
14
15
  var _queryForm = _interopRequireDefault(require("./queryForm"));
15
16
  var _toolBar = _interopRequireDefault(require("./toolBar"));
@@ -315,16 +316,8 @@ function AgGridPlus(props) {
315
316
  };
316
317
  }, []);
317
318
 
318
- // 【修复】缓存第一次数据用于类型推断,避免 rowData 更新时重新计算 colDefs
319
- // 原因: getTableColumns 只需要第一行数据来推断列的数据类型(数字列自动求和)
320
- // 类型推断应该只在初始化时进行一次,后续数据更新不应改变列定义
321
- var firstRowDataRef = (0, _react.useRef)(null);
322
- (0, _react.useEffect)(function () {
323
- // 只在首次有数据时保存
324
- if (!firstRowDataRef.current && rowData.length > 0) {
325
- firstRowDataRef.current = rowData[0];
326
- }
327
- }, [rowData]);
319
+ // 用于列类型推断的首行数据。为什么必须是 state 而不是 ref,见该 hook 的注释。@author nokecy
320
+ var typeInferenceRow = (0, _useTypeInferenceRow.useTypeInferenceRow)(rowData);
328
321
 
329
322
  /**
330
323
  * 【P0 + P1 优化】使用派生状态应用方案(单向数据流)
@@ -625,7 +618,7 @@ function AgGridPlus(props) {
625
618
  // 计算所有列定义(包括隐藏的列)
626
619
  // 【修复】移除 rowData 依赖,使用缓存的首行数据进行类型推断
627
620
  var allColDefs = (0, _react.useMemo)(function () {
628
- var dataForTypeInference = firstRowDataRef.current || (rowData.length > 0 ? rowData[0] : {});
621
+ var dataForTypeInference = typeInferenceRow || (rowData.length > 0 ? rowData[0] : {});
629
622
  var cols = getTableColumns([dataForTypeInference]);
630
623
  allColDefsRef.current = cols; // 同步更新 ref
631
624
 
@@ -645,7 +638,7 @@ function AgGridPlus(props) {
645
638
  });
646
639
  }
647
640
  return cols;
648
- }, [_react.default.Children.count(children), props.columnDefs, extensibleColumns, gridKey, entityType, hostEntityExtensions]);
641
+ }, [_react.default.Children.count(children), props.columnDefs, extensibleColumns, gridKey, entityType, hostEntityExtensions, typeInferenceRow]);
649
642
 
650
643
  // 稳定化 gridParams 和 sortColumn 的引用(深比较,内容不变即复用旧引用)
651
644
  var stableGridParams = useDeepCompareStable(gridParams);
@@ -1052,7 +1045,7 @@ function AgGridPlus(props) {
1052
1045
  // 【默认逻辑】没有方案配置时,使用原有的列定义生成逻辑
1053
1046
  // 使用缓存的首行数据进行类型推断,而不是当前的 rowData
1054
1047
  // 这样 rowData 更新时不会触发 colDefs 重新计算
1055
- var dataForTypeInference = firstRowDataRef.current || (rowData.length > 0 ? rowData[0] : {});
1048
+ var dataForTypeInference = typeInferenceRow || (rowData.length > 0 ? rowData[0] : {});
1056
1049
  var columnDefs = getTableColumns([dataForTypeInference]);
1057
1050
 
1058
1051
  // 递归应用 columnCellClass 配置
@@ -1090,7 +1083,9 @@ function AgGridPlus(props) {
1090
1083
  // 【关键】优先依赖派生状态
1091
1084
  derivedColumnDefs, _react.default.Children.count(children), props.columnDefs, extensibleColumns, columnCellClass,
1092
1085
  // ref.current 变更不触发重渲染,作依赖无效;改依赖真正的数据源 allColDefs(memo 值) @author nokecy
1093
- allColDefs]);
1086
+ allColDefs,
1087
+ // 首批数据到达时重算一次,否则数值列的右对齐永远推断不出来 @author nokecy
1088
+ typeInferenceRow]);
1094
1089
 
1095
1090
  // 构建查询表单项(带 order 属性)
1096
1091
  var queryFormItems = (0, _react.useMemo)(function () {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 挑出用于列类型推断的那一行数据。
3
+ *
4
+ * 列的数据类型(数值列自动求和并右对齐)是靠首行数据的字段类型推断出来的,
5
+ * 而这个推断**只该做一次**:之后的数据更新不应该改变列定义,否则翻页、筛选都会重排列。
6
+ *
7
+ * 必须返回 state 而不是 ref。这里踩过一次:
8
+ * 原本用 `useRef` 缓存首行,而消费它的 `useMemo` 依赖数组里又没有 rowData,
9
+ * 于是异步加载的表格首次渲染时 rowData 为空 → 推断不出类型 → 数据到达后写进 ref
10
+ * 却不触发重渲染 → 列定义永久停留在「无类型」。表现是数值列该右对齐却一直左对齐,
11
+ * 不报错、不崩溃,只能靠盯着某一列发现。
12
+ *
13
+ * 用 state 就同时满足两件事:首批数据到达时触发一次重算,之后不再变。
14
+ *
15
+ * @author nokecy
16
+ */
17
+ export declare function useTypeInferenceRow(rowData: any[] | undefined | null): any;
18
+ export default useTypeInferenceRow;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ exports.useTypeInferenceRow = useTypeInferenceRow;
8
+ var _react = require("react");
9
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
10
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
11
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
12
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
13
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
14
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
15
+ /**
16
+ * 挑出用于列类型推断的那一行数据。
17
+ *
18
+ * 列的数据类型(数值列自动求和并右对齐)是靠首行数据的字段类型推断出来的,
19
+ * 而这个推断**只该做一次**:之后的数据更新不应该改变列定义,否则翻页、筛选都会重排列。
20
+ *
21
+ * 必须返回 state 而不是 ref。这里踩过一次:
22
+ * 原本用 `useRef` 缓存首行,而消费它的 `useMemo` 依赖数组里又没有 rowData,
23
+ * 于是异步加载的表格首次渲染时 rowData 为空 → 推断不出类型 → 数据到达后写进 ref
24
+ * 却不触发重渲染 → 列定义永久停留在「无类型」。表现是数值列该右对齐却一直左对齐,
25
+ * 不报错、不崩溃,只能靠盯着某一列发现。
26
+ *
27
+ * 用 state 就同时满足两件事:首批数据到达时触发一次重算,之后不再变。
28
+ *
29
+ * @author nokecy
30
+ */
31
+ function useTypeInferenceRow(rowData) {
32
+ var _useState = (0, _react.useState)(null),
33
+ _useState2 = _slicedToArray(_useState, 2),
34
+ row = _useState2[0],
35
+ setRow = _useState2[1];
36
+ (0, _react.useEffect)(function () {
37
+ if (!row && rowData && rowData.length > 0) {
38
+ setRow(rowData[0]);
39
+ }
40
+ }, [rowData, row]);
41
+ return row;
42
+ }
43
+ var _default = exports.default = useTypeInferenceRow;
@@ -24,7 +24,9 @@ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" ==
24
24
  /** 四支文案,按支覆盖。不传的支走默认。 */
25
25
 
26
26
  var DEFAULT_EMPTY_TEXTS = exports.DEFAULT_EMPTY_TEXTS = {
27
- error: '数据加载失败,请点上方「重试」',
27
+ // 不写方位:重试按钮就渲染在这句话正下方(见 shared/NoRowsOverlay)。
28
+ // 原文写的是「请点上方「重试」」,方位指错了。@author nokecy
29
+ error: '数据加载失败,可点下方按钮重试',
28
30
  noPermission: '没有查看该数据的权限,请联系系统管理员',
29
31
  noResult: '没有符合当前筛选条件的数据,试试放宽或清空筛选条件',
30
32
  noData: '暂无数据'
@@ -141,6 +141,34 @@ var AgField = function AgField(_ref) {
141
141
  return children;
142
142
  };
143
143
 
144
+ /**
145
+ * 快路径能否按这个字段名取到值。
146
+ *
147
+ * Formily 的字段名是「路径」不是「键」,支持点号嵌套(`item.qty`)、数组下标(`a[0]`)、
148
+ * 解构(`{value:code,label:name}`)等语法。快路径(只读文本/懒挂载)不挂 Formily 树,
149
+ * 取值得自己解析。这里只认「点号分段」这一种简单形态,其余一律判不合格 ->
150
+ * 调用方回退 AgField 全树(由 Formily 自己解析,正确优先于速度)。@author nokecy
151
+ */
152
+ var isSimpleFieldPath = function isSimpleFieldPath(name) {
153
+ return !!name && /^[^.{}[\]*]+(\.[^.{}[\]*]+)*$/.test(name);
154
+ };
155
+
156
+ /**
157
+ * 按 Formily 字段名从行数据取值。
158
+ *
159
+ * 曾经一律按扁平键取 record['item.qty'],而数据在 record.item.qty ->
160
+ * 嵌套字段名恒 undefined,单元格默认空白,点击挂上全树(按路径解析)才显出真值。
161
+ *
162
+ * 在 Observer 里逐层读可观察对象,依赖链完整,程序化改值(setValuesIn)照样刷新。@author nokecy
163
+ */
164
+ var readByFieldPath = function readByFieldPath(record, path) {
165
+ if (record == null) return undefined;
166
+ if (path.indexOf('.') < 0) return record[path];
167
+ return path.split('.').reduce(function (acc, key) {
168
+ return acc == null ? undefined : acc[key];
169
+ }, record);
170
+ };
171
+
144
172
  /**
145
173
  * 懒挂载单元格渲染器(可编辑列)。
146
174
  *
@@ -187,7 +215,7 @@ var LazyAgField = exports.LazyAgField = function LazyAgField(params) {
187
215
  // 免去调用方加 forceAgField 逃生阀。字段名缺失才回退 params.value。@author nokecy
188
216
  var dataKey = (_fieldSchema$name = fieldSchema === null || fieldSchema === void 0 ? void 0 : fieldSchema.name) !== null && _fieldSchema$name !== void 0 ? _fieldSchema$name : colDef === null || colDef === void 0 ? void 0 : colDef.field;
189
217
  return /*#__PURE__*/_react.default.createElement(_react2.Observer, null, function () {
190
- var liveValue = dataKey != null && record ? record[dataKey] : value;
218
+ var liveValue = dataKey != null && record ? readByFieldPath(record, dataKey) : value;
191
219
  var text = formatter ? formatter(liveValue, {
192
220
  columnProps: columnProps,
193
221
  fieldSchema: fieldSchema
@@ -224,7 +252,7 @@ var LazyReadOnlyText = exports.LazyReadOnlyText = function LazyReadOnlyText(para
224
252
  fieldSchema = params.fieldSchema;
225
253
  var dataKey = (_fieldSchema$name2 = fieldSchema === null || fieldSchema === void 0 ? void 0 : fieldSchema.name) !== null && _fieldSchema$name2 !== void 0 ? _fieldSchema$name2 : colDef === null || colDef === void 0 ? void 0 : colDef.field;
226
254
  return /*#__PURE__*/_react.default.createElement(_react2.Observer, null, function () {
227
- var liveValue = dataKey != null && record ? record[dataKey] : value;
255
+ var liveValue = dataKey != null && record ? readByFieldPath(record, dataKey) : value;
228
256
  var text = formatter ? formatter(liveValue, {
229
257
  columnProps: columnProps,
230
258
  fieldSchema: fieldSchema
@@ -317,7 +345,7 @@ var buildArrayTableColumns = exports.buildArrayTableColumns = function buildArra
317
345
  // ① 只读纯文本(永不可编辑):只读列 + 有格式化器 -> valueFormatter,不挂 Formily 树。
318
346
  // ② 可编辑懒挂载:可编辑列 + 有格式化器 + 无 reactions + 未强制 eager -> LazyAgField,
319
347
  // 存量格先出廉价文本、点击/导航才挂全树(挂了不卸)。
320
- // ③ 兜底全树:其余(无格式化器/带 reactions/forceAgField/只读但无格式化器) -> AgField。
348
+ // ③ 兜底全树:其余(无格式化器/带 reactions/forceAgField/复杂字段名/只读但无格式化器) -> AgField。
321
349
  var dataField = findDataFieldSchema(schema, name);
322
350
  var component = dataField === null || dataField === void 0 ? void 0 : dataField['x-component'];
323
351
  var formatter = (0, _readOnlyFormatters.getReadOnlyFormatter)(component);
@@ -335,12 +363,14 @@ var buildArrayTableColumns = exports.buildArrayTableColumns = function buildArra
335
363
  // 故按内层字段名补 valueGetter(仅在与 name 不一致时,保持 name===字段名 的现状)。@author nokecy
336
364
  var dataFieldName = dataField === null || dataField === void 0 ? void 0 : dataField.name;
337
365
  var valueGetter = dataFieldName && dataFieldName !== name ? function (params) {
338
- var _params$data;
339
- return params === null || params === void 0 || (_params$data = params.data) === null || _params$data === void 0 ? void 0 : _params$data[dataFieldName];
366
+ return readByFieldPath(params === null || params === void 0 ? void 0 : params.data, dataFieldName);
340
367
  } : undefined;
368
+ // 字段名是路径:`item.qty` 的值在 record.item.qty。快路径自己解析,只认点号分段;
369
+ // 解构(`{value:a,label:b}`)/数组下标等复杂语法解析不了,回退全树交给 Formily。@author nokecy
370
+ var canFastPath = !dataFieldName || isSimpleFieldPath(dataFieldName);
341
371
 
342
372
  // ① 只读纯文本
343
- if (readOnly && formatter) {
373
+ if (readOnly && formatter && canFastPath) {
344
374
  // 列级 readPretty 在「可编辑表」里是展示列(值可能被 setValuesIn 自动带出),静态 valueFormatter
345
375
  // 读 AG-Grid 缓存值不刷新 → 反应式只读渲染。整表 readPretty(明细/详情页,数据 setValues 整体加载、
346
376
  // 行引用整体替换、AG-Grid 自然刷新)仍走更廉价的静态 valueFormatter 快路径。@author nokecy
@@ -371,7 +401,7 @@ var buildArrayTableColumns = exports.buildArrayTableColumns = function buildArra
371
401
  }
372
402
 
373
403
  // ② 可编辑懒挂载(排除单击直控的 Switch/Checkbox:须 eager 出真控件)
374
- if (!readOnly && formatter && !hasReactions && !forceEager && !isInlineToggle) {
404
+ if (!readOnly && formatter && !hasReactions && !forceEager && !isInlineToggle && canFastPath) {
375
405
  return buf.concat(_objectSpread(_objectSpread(_objectSpread({}, base), valueGetter ? {
376
406
  valueGetter: valueGetter
377
407
  } : {}), {}, {
@@ -29,6 +29,7 @@ ModuleRegistry.registerModules([AllEnterpriseModule]);
29
29
 
30
30
  // 导入 AG-Grid v33 主题
31
31
  import { agGridTheme } from "./theme";
32
+ import { useTypeInferenceRow } from "./hooks/useTypeInferenceRow";
32
33
  import "./index.less";
33
34
  import FormRender from "./queryForm";
34
35
  import ToolBar from "./toolBar";
@@ -307,16 +308,8 @@ function AgGridPlus(props) {
307
308
  };
308
309
  }, []);
309
310
 
310
- // 【修复】缓存第一次数据用于类型推断,避免 rowData 更新时重新计算 colDefs
311
- // 原因: getTableColumns 只需要第一行数据来推断列的数据类型(数字列自动求和)
312
- // 类型推断应该只在初始化时进行一次,后续数据更新不应改变列定义
313
- var firstRowDataRef = useRef(null);
314
- useEffect(function () {
315
- // 只在首次有数据时保存
316
- if (!firstRowDataRef.current && rowData.length > 0) {
317
- firstRowDataRef.current = rowData[0];
318
- }
319
- }, [rowData]);
311
+ // 用于列类型推断的首行数据。为什么必须是 state 而不是 ref,见该 hook 的注释。@author nokecy
312
+ var typeInferenceRow = useTypeInferenceRow(rowData);
320
313
 
321
314
  /**
322
315
  * 【P0 + P1 优化】使用派生状态应用方案(单向数据流)
@@ -617,7 +610,7 @@ function AgGridPlus(props) {
617
610
  // 计算所有列定义(包括隐藏的列)
618
611
  // 【修复】移除 rowData 依赖,使用缓存的首行数据进行类型推断
619
612
  var allColDefs = useMemo(function () {
620
- var dataForTypeInference = firstRowDataRef.current || (rowData.length > 0 ? rowData[0] : {});
613
+ var dataForTypeInference = typeInferenceRow || (rowData.length > 0 ? rowData[0] : {});
621
614
  var cols = getTableColumns([dataForTypeInference]);
622
615
  allColDefsRef.current = cols; // 同步更新 ref
623
616
 
@@ -637,7 +630,7 @@ function AgGridPlus(props) {
637
630
  });
638
631
  }
639
632
  return cols;
640
- }, [React.Children.count(children), props.columnDefs, extensibleColumns, gridKey, entityType, hostEntityExtensions]);
633
+ }, [React.Children.count(children), props.columnDefs, extensibleColumns, gridKey, entityType, hostEntityExtensions, typeInferenceRow]);
641
634
 
642
635
  // 稳定化 gridParams 和 sortColumn 的引用(深比较,内容不变即复用旧引用)
643
636
  var stableGridParams = useDeepCompareStable(gridParams);
@@ -1044,7 +1037,7 @@ function AgGridPlus(props) {
1044
1037
  // 【默认逻辑】没有方案配置时,使用原有的列定义生成逻辑
1045
1038
  // 使用缓存的首行数据进行类型推断,而不是当前的 rowData
1046
1039
  // 这样 rowData 更新时不会触发 colDefs 重新计算
1047
- var dataForTypeInference = firstRowDataRef.current || (rowData.length > 0 ? rowData[0] : {});
1040
+ var dataForTypeInference = typeInferenceRow || (rowData.length > 0 ? rowData[0] : {});
1048
1041
  var columnDefs = getTableColumns([dataForTypeInference]);
1049
1042
 
1050
1043
  // 递归应用 columnCellClass 配置
@@ -1082,7 +1075,9 @@ function AgGridPlus(props) {
1082
1075
  // 【关键】优先依赖派生状态
1083
1076
  derivedColumnDefs, React.Children.count(children), props.columnDefs, extensibleColumns, columnCellClass,
1084
1077
  // ref.current 变更不触发重渲染,作依赖无效;改依赖真正的数据源 allColDefs(memo 值) @author nokecy
1085
- allColDefs]);
1078
+ allColDefs,
1079
+ // 首批数据到达时重算一次,否则数值列的右对齐永远推断不出来 @author nokecy
1080
+ typeInferenceRow]);
1086
1081
 
1087
1082
  // 构建查询表单项(带 order 属性)
1088
1083
  var queryFormItems = useMemo(function () {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 挑出用于列类型推断的那一行数据。
3
+ *
4
+ * 列的数据类型(数值列自动求和并右对齐)是靠首行数据的字段类型推断出来的,
5
+ * 而这个推断**只该做一次**:之后的数据更新不应该改变列定义,否则翻页、筛选都会重排列。
6
+ *
7
+ * 必须返回 state 而不是 ref。这里踩过一次:
8
+ * 原本用 `useRef` 缓存首行,而消费它的 `useMemo` 依赖数组里又没有 rowData,
9
+ * 于是异步加载的表格首次渲染时 rowData 为空 → 推断不出类型 → 数据到达后写进 ref
10
+ * 却不触发重渲染 → 列定义永久停留在「无类型」。表现是数值列该右对齐却一直左对齐,
11
+ * 不报错、不崩溃,只能靠盯着某一列发现。
12
+ *
13
+ * 用 state 就同时满足两件事:首批数据到达时触发一次重算,之后不再变。
14
+ *
15
+ * @author nokecy
16
+ */
17
+ export declare function useTypeInferenceRow(rowData: any[] | undefined | null): any;
18
+ export default useTypeInferenceRow;
@@ -0,0 +1,37 @@
1
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
2
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
4
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
5
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
6
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
7
+ import { useEffect, useState } from 'react';
8
+
9
+ /**
10
+ * 挑出用于列类型推断的那一行数据。
11
+ *
12
+ * 列的数据类型(数值列自动求和并右对齐)是靠首行数据的字段类型推断出来的,
13
+ * 而这个推断**只该做一次**:之后的数据更新不应该改变列定义,否则翻页、筛选都会重排列。
14
+ *
15
+ * 必须返回 state 而不是 ref。这里踩过一次:
16
+ * 原本用 `useRef` 缓存首行,而消费它的 `useMemo` 依赖数组里又没有 rowData,
17
+ * 于是异步加载的表格首次渲染时 rowData 为空 → 推断不出类型 → 数据到达后写进 ref
18
+ * 却不触发重渲染 → 列定义永久停留在「无类型」。表现是数值列该右对齐却一直左对齐,
19
+ * 不报错、不崩溃,只能靠盯着某一列发现。
20
+ *
21
+ * 用 state 就同时满足两件事:首批数据到达时触发一次重算,之后不再变。
22
+ *
23
+ * @author nokecy
24
+ */
25
+ export function useTypeInferenceRow(rowData) {
26
+ var _useState = useState(null),
27
+ _useState2 = _slicedToArray(_useState, 2),
28
+ row = _useState2[0],
29
+ setRow = _useState2[1];
30
+ useEffect(function () {
31
+ if (!row && rowData && rowData.length > 0) {
32
+ setRow(rowData[0]);
33
+ }
34
+ }, [rowData, row]);
35
+ return row;
36
+ }
37
+ export default useTypeInferenceRow;
@@ -15,7 +15,9 @@ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" ==
15
15
  /** 四支文案,按支覆盖。不传的支走默认。 */
16
16
 
17
17
  export var DEFAULT_EMPTY_TEXTS = {
18
- error: '数据加载失败,请点上方「重试」',
18
+ // 不写方位:重试按钮就渲染在这句话正下方(见 shared/NoRowsOverlay)。
19
+ // 原文写的是「请点上方「重试」」,方位指错了。@author nokecy
20
+ error: '数据加载失败,可点下方按钮重试',
19
21
  noPermission: '没有查看该数据的权限,请联系系统管理员',
20
22
  noResult: '没有符合当前筛选条件的数据,试试放宽或清空筛选条件',
21
23
  noData: '暂无数据'
@@ -134,6 +134,34 @@ var AgField = function AgField(_ref) {
134
134
  return children;
135
135
  };
136
136
 
137
+ /**
138
+ * 快路径能否按这个字段名取到值。
139
+ *
140
+ * Formily 的字段名是「路径」不是「键」,支持点号嵌套(`item.qty`)、数组下标(`a[0]`)、
141
+ * 解构(`{value:code,label:name}`)等语法。快路径(只读文本/懒挂载)不挂 Formily 树,
142
+ * 取值得自己解析。这里只认「点号分段」这一种简单形态,其余一律判不合格 ->
143
+ * 调用方回退 AgField 全树(由 Formily 自己解析,正确优先于速度)。@author nokecy
144
+ */
145
+ var isSimpleFieldPath = function isSimpleFieldPath(name) {
146
+ return !!name && /^[^.{}[\]*]+(\.[^.{}[\]*]+)*$/.test(name);
147
+ };
148
+
149
+ /**
150
+ * 按 Formily 字段名从行数据取值。
151
+ *
152
+ * 曾经一律按扁平键取 record['item.qty'],而数据在 record.item.qty ->
153
+ * 嵌套字段名恒 undefined,单元格默认空白,点击挂上全树(按路径解析)才显出真值。
154
+ *
155
+ * 在 Observer 里逐层读可观察对象,依赖链完整,程序化改值(setValuesIn)照样刷新。@author nokecy
156
+ */
157
+ var readByFieldPath = function readByFieldPath(record, path) {
158
+ if (record == null) return undefined;
159
+ if (path.indexOf('.') < 0) return record[path];
160
+ return path.split('.').reduce(function (acc, key) {
161
+ return acc == null ? undefined : acc[key];
162
+ }, record);
163
+ };
164
+
137
165
  /**
138
166
  * 懒挂载单元格渲染器(可编辑列)。
139
167
  *
@@ -180,7 +208,7 @@ var LazyAgField = function LazyAgField(params) {
180
208
  // 免去调用方加 forceAgField 逃生阀。字段名缺失才回退 params.value。@author nokecy
181
209
  var dataKey = (_fieldSchema$name = fieldSchema === null || fieldSchema === void 0 ? void 0 : fieldSchema.name) !== null && _fieldSchema$name !== void 0 ? _fieldSchema$name : colDef === null || colDef === void 0 ? void 0 : colDef.field;
182
210
  return /*#__PURE__*/React.createElement(Observer, null, function () {
183
- var liveValue = dataKey != null && record ? record[dataKey] : value;
211
+ var liveValue = dataKey != null && record ? readByFieldPath(record, dataKey) : value;
184
212
  var text = formatter ? formatter(liveValue, {
185
213
  columnProps: columnProps,
186
214
  fieldSchema: fieldSchema
@@ -217,7 +245,7 @@ var LazyReadOnlyText = function LazyReadOnlyText(params) {
217
245
  fieldSchema = params.fieldSchema;
218
246
  var dataKey = (_fieldSchema$name2 = fieldSchema === null || fieldSchema === void 0 ? void 0 : fieldSchema.name) !== null && _fieldSchema$name2 !== void 0 ? _fieldSchema$name2 : colDef === null || colDef === void 0 ? void 0 : colDef.field;
219
247
  return /*#__PURE__*/React.createElement(Observer, null, function () {
220
- var liveValue = dataKey != null && record ? record[dataKey] : value;
248
+ var liveValue = dataKey != null && record ? readByFieldPath(record, dataKey) : value;
221
249
  var text = formatter ? formatter(liveValue, {
222
250
  columnProps: columnProps,
223
251
  fieldSchema: fieldSchema
@@ -310,7 +338,7 @@ var buildArrayTableColumns = function buildArrayTableColumns(dataSource, field,
310
338
  // ① 只读纯文本(永不可编辑):只读列 + 有格式化器 -> valueFormatter,不挂 Formily 树。
311
339
  // ② 可编辑懒挂载:可编辑列 + 有格式化器 + 无 reactions + 未强制 eager -> LazyAgField,
312
340
  // 存量格先出廉价文本、点击/导航才挂全树(挂了不卸)。
313
- // ③ 兜底全树:其余(无格式化器/带 reactions/forceAgField/只读但无格式化器) -> AgField。
341
+ // ③ 兜底全树:其余(无格式化器/带 reactions/forceAgField/复杂字段名/只读但无格式化器) -> AgField。
314
342
  var dataField = findDataFieldSchema(schema, name);
315
343
  var component = dataField === null || dataField === void 0 ? void 0 : dataField['x-component'];
316
344
  var formatter = getReadOnlyFormatter(component);
@@ -328,12 +356,14 @@ var buildArrayTableColumns = function buildArrayTableColumns(dataSource, field,
328
356
  // 故按内层字段名补 valueGetter(仅在与 name 不一致时,保持 name===字段名 的现状)。@author nokecy
329
357
  var dataFieldName = dataField === null || dataField === void 0 ? void 0 : dataField.name;
330
358
  var valueGetter = dataFieldName && dataFieldName !== name ? function (params) {
331
- var _params$data;
332
- return params === null || params === void 0 || (_params$data = params.data) === null || _params$data === void 0 ? void 0 : _params$data[dataFieldName];
359
+ return readByFieldPath(params === null || params === void 0 ? void 0 : params.data, dataFieldName);
333
360
  } : undefined;
361
+ // 字段名是路径:`item.qty` 的值在 record.item.qty。快路径自己解析,只认点号分段;
362
+ // 解构(`{value:a,label:b}`)/数组下标等复杂语法解析不了,回退全树交给 Formily。@author nokecy
363
+ var canFastPath = !dataFieldName || isSimpleFieldPath(dataFieldName);
334
364
 
335
365
  // ① 只读纯文本
336
- if (readOnly && formatter) {
366
+ if (readOnly && formatter && canFastPath) {
337
367
  // 列级 readPretty 在「可编辑表」里是展示列(值可能被 setValuesIn 自动带出),静态 valueFormatter
338
368
  // 读 AG-Grid 缓存值不刷新 → 反应式只读渲染。整表 readPretty(明细/详情页,数据 setValues 整体加载、
339
369
  // 行引用整体替换、AG-Grid 自然刷新)仍走更廉价的静态 valueFormatter 快路径。@author nokecy
@@ -364,7 +394,7 @@ var buildArrayTableColumns = function buildArrayTableColumns(dataSource, field,
364
394
  }
365
395
 
366
396
  // ② 可编辑懒挂载(排除单击直控的 Switch/Checkbox:须 eager 出真控件)
367
- if (!readOnly && formatter && !hasReactions && !forceEager && !isInlineToggle) {
397
+ if (!readOnly && formatter && !hasReactions && !forceEager && !isInlineToggle && canFastPath) {
368
398
  return buf.concat(_objectSpread(_objectSpread(_objectSpread({}, base), valueGetter ? {
369
399
  valueGetter: valueGetter
370
400
  } : {}), {}, {