@zat-design/sisyphus-react 4.5.2 → 4.5.4
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.
- package/es/ProEditTable/components/RenderField/index.js +20 -15
- package/es/ProEditTable/components/RenderField/tools.js +5 -13
- package/es/ProEditTable/utils/index.js +2 -3
- package/es/ProEditTable/utils/tools.js +21 -3
- package/es/ProEditTable/utils/useShouldUpdateForTable.js +4 -3
- package/es/ProForm/components/combination/ProModalSelect/index.js +3 -3
- package/es/ProForm/components/combination/ProModalSelect/utils/index.js +24 -0
- package/es/ProTree/components/ProTree.js +3 -1
- package/es/ProUpload/index.js +2 -1
- package/es/utils/index.js +5 -0
- package/package.json +2 -2
- package/es/ProForm/components/combination/ProModalSelect/utils/mergeSelectedRows.js +0 -26
|
@@ -97,24 +97,29 @@ const RenderField = ({
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
// 单行正在编辑时,临时生成一套formItem用来存储中间值,点击取消时候重置回上一次状态
|
|
100
|
-
const
|
|
101
|
-
|
|
100
|
+
const namePathKey = useMemo(() => {
|
|
101
|
+
const path = getNamePath(name, virtualKey);
|
|
102
|
+
return Array.isArray(path) ? path.join('.') : String(path);
|
|
103
|
+
}, [name, virtualKey]);
|
|
104
|
+
const namePath = useMemo(() => getNamePath(name, virtualKey), [namePathKey, name, virtualKey]);
|
|
105
|
+
const rowNamePath = useMemo(() => [...namePath, index], [namePath, index]);
|
|
106
|
+
const rowData = form.getFieldValue(rowNamePath) || record || {};
|
|
102
107
|
let currentValue = dataIndex ? rowData?.[dataIndex] : null;
|
|
103
108
|
|
|
104
109
|
// 构建新的参数格式(与 ProForm 保持一致)
|
|
105
110
|
const reactiveParams = useMemo(() => ({
|
|
106
111
|
form,
|
|
107
112
|
index,
|
|
108
|
-
namePath:
|
|
109
|
-
}), [form, index,
|
|
113
|
+
namePath: rowNamePath
|
|
114
|
+
}), [form, index, rowNamePath]);
|
|
110
115
|
|
|
111
116
|
// 保留 options 用于 viewRender 和 formatArgs(兼容旧格式)
|
|
112
117
|
const options = useMemo(() => ({
|
|
113
118
|
index,
|
|
114
119
|
form,
|
|
115
120
|
name: column?.name,
|
|
116
|
-
namePath:
|
|
117
|
-
}), [index, column?.name,
|
|
121
|
+
namePath: rowNamePath
|
|
122
|
+
}), [index, column?.name, rowNamePath, form]);
|
|
118
123
|
|
|
119
124
|
// 行参数 - 使用useMemo优化(新格式:values, { form, index, namePath })
|
|
120
125
|
const rowParams = useMemo(() => [rowData, reactiveParams], [rowData, reactiveParams]);
|
|
@@ -141,7 +146,7 @@ const RenderField = ({
|
|
|
141
146
|
lastValueType = dynamicProps.valueType ?? valueType(currentValue, rowData, {
|
|
142
147
|
index,
|
|
143
148
|
form,
|
|
144
|
-
namePath:
|
|
149
|
+
namePath: rowNamePath,
|
|
145
150
|
name: column?.name
|
|
146
151
|
});
|
|
147
152
|
}
|
|
@@ -253,8 +258,8 @@ const RenderField = ({
|
|
|
253
258
|
if (hasFunctionDependency) {
|
|
254
259
|
// 使用 shouldUpdate 监听同一行的数据变化;shouldUpdate 与 dependencies 互斥,始终优先 shouldUpdate
|
|
255
260
|
lastFormItemProps.shouldUpdate = (prevValues, currentValues) => {
|
|
256
|
-
const prevRow = _get(prevValues,
|
|
257
|
-
const currentRow = _get(currentValues,
|
|
261
|
+
const prevRow = _get(prevValues, rowNamePath);
|
|
262
|
+
const currentRow = _get(currentValues, rowNamePath);
|
|
258
263
|
// 如果行数据发生变化,则重新渲染
|
|
259
264
|
return !_isEqual(prevRow, currentRow);
|
|
260
265
|
};
|
|
@@ -408,7 +413,7 @@ const RenderField = ({
|
|
|
408
413
|
errorStore.clearCell(record.rowKey, String(column?.dataIndex));
|
|
409
414
|
}
|
|
410
415
|
let callArgs = [...innerArgs];
|
|
411
|
-
const rowPath =
|
|
416
|
+
const rowPath = rowNamePath;
|
|
412
417
|
if (!onFieldChange && !onChange) {
|
|
413
418
|
if (dependencies?.length) {
|
|
414
419
|
const validateFieldKeys = dependencies?.map?.(key => [...rowPath, key]);
|
|
@@ -531,7 +536,7 @@ const RenderField = ({
|
|
|
531
536
|
return;
|
|
532
537
|
}
|
|
533
538
|
let callArgs = formatArgs(...args);
|
|
534
|
-
const rowPath =
|
|
539
|
+
const rowPath = rowNamePath;
|
|
535
540
|
const row = form.getFieldValue(rowPath, true);
|
|
536
541
|
const orgRow = _cloneDeep(row);
|
|
537
542
|
callArgs[1] = row;
|
|
@@ -564,7 +569,7 @@ const RenderField = ({
|
|
|
564
569
|
name: cellName,
|
|
565
570
|
...lastFieldProps,
|
|
566
571
|
...TargetComponent?.props,
|
|
567
|
-
namePath:
|
|
572
|
+
namePath: rowNamePath,
|
|
568
573
|
disabled: lastDisabled,
|
|
569
574
|
onChange: handleChange,
|
|
570
575
|
onBlur: handleBlur,
|
|
@@ -574,7 +579,7 @@ const RenderField = ({
|
|
|
574
579
|
otherProps: {
|
|
575
580
|
form,
|
|
576
581
|
names,
|
|
577
|
-
namePath:
|
|
582
|
+
namePath: rowNamePath,
|
|
578
583
|
name: originalName,
|
|
579
584
|
listName: cellName,
|
|
580
585
|
// 用于下拉框去重消费,保持和formlist一致
|
|
@@ -643,11 +648,11 @@ const RenderField = ({
|
|
|
643
648
|
// shouldUpdate 模式下重新获取最新行数据并重算所有响应式属性,返回最新渲染状态(不修改外层闭包)
|
|
644
649
|
const recalcShouldUpdateState = () => {
|
|
645
650
|
// 重新获取最新的行数据(shouldUpdate 触发时,通过 getFieldValue 拿到最新值)
|
|
646
|
-
const latestRowData = form.getFieldValue(
|
|
651
|
+
const latestRowData = form.getFieldValue(rowNamePath) || record || {};
|
|
647
652
|
const latestReactiveParams = {
|
|
648
653
|
form,
|
|
649
654
|
index,
|
|
650
|
-
namePath:
|
|
655
|
+
namePath: rowNamePath
|
|
651
656
|
};
|
|
652
657
|
const latestRowParams = [latestRowData, latestReactiveParams];
|
|
653
658
|
|
|
@@ -3,7 +3,7 @@ import _isEqualWith from "lodash/isEqualWith";
|
|
|
3
3
|
import _isEqual from "lodash/isEqual";
|
|
4
4
|
import _get from "lodash/get";
|
|
5
5
|
import { customEqualForFunction } from "../../../utils";
|
|
6
|
-
import { getNamePath } from "../../utils/tools";
|
|
6
|
+
import { getNamePath, isRecordShallowEqual } from "../../utils/tools";
|
|
7
7
|
/** 列配置中不应透传到 Form.Item 或 DOM 的字段(与 ProForm Render 保持一致) */
|
|
8
8
|
export const OMIT_FORM_ITEM_AND_DOM_KEYS = ['format', 'toISOString', 'toCSTString', 'switchValue', 'precision', 'clearNotShow', 'dependNames', 'shouldCellUpdate' // 表格内部性能优化属性,不应传递给 Form.Item
|
|
9
9
|
];
|
|
@@ -121,8 +121,8 @@ export const arePropsEqual = (prevProps, nextProps) => {
|
|
|
121
121
|
// 优化:不直接调用函数,而是比较输入参数(record 和 reactiveParams)
|
|
122
122
|
// 如果输入参数相同,fieldProps 的返回值应该相同(纯函数假设)
|
|
123
123
|
if (_isFunction(prevColumn?.fieldProps) && _isFunction(nextColumn?.fieldProps)) {
|
|
124
|
-
// 比较 record
|
|
125
|
-
if (!
|
|
124
|
+
// 比较 record 数据是否变化(浅比较,虚拟滚动性能优化)
|
|
125
|
+
if (!isRecordShallowEqual(prevRecord, nextRecord)) {
|
|
126
126
|
return false;
|
|
127
127
|
}
|
|
128
128
|
// reactiveParams 中的 form、index、namePath 已经在外层比较过了,无需重复比较
|
|
@@ -144,7 +144,7 @@ export const arePropsEqual = (prevProps, nextProps) => {
|
|
|
144
144
|
// 函数引用变化说明闭包可能捕获了新的外部状态(如异步加载的 list),必须重渲染
|
|
145
145
|
if (prevFunc !== nextFunc) return false;
|
|
146
146
|
// 同引用函数,比较输入参数(纯函数假设:输入相同则输出相同)
|
|
147
|
-
return
|
|
147
|
+
return isRecordShallowEqual(prevValues, nextValues);
|
|
148
148
|
}
|
|
149
149
|
return true; // 如果不是函数或只有一个是函数,认为相等
|
|
150
150
|
};
|
|
@@ -187,15 +187,7 @@ export const arePropsEqual = (prevProps, nextProps) => {
|
|
|
187
187
|
// 特殊处理:当使用自定义 component 函数时,比较整个 record 对象
|
|
188
188
|
// 因为自定义组件可能依赖 record 中的其他字段(不只是当前列的 dataIndex)
|
|
189
189
|
if (_isFunction(prevColumn?.component) || _isFunction(nextColumn?.component)) {
|
|
190
|
-
|
|
191
|
-
const prevKeys = Object.keys(prevRecord || {});
|
|
192
|
-
const nextKeys = Object.keys(nextRecord || {});
|
|
193
|
-
if (prevKeys.length !== nextKeys.length) {
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// 使用 some 方法代替 for 循环
|
|
198
|
-
if (prevKeys.some(key => prevRecord?.[key] !== nextRecord?.[key])) {
|
|
190
|
+
if (!isRecordShallowEqual(prevRecord, nextRecord)) {
|
|
199
191
|
return false;
|
|
200
192
|
}
|
|
201
193
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import _isFunction from "lodash/isFunction";
|
|
2
|
-
import _isEqual from "lodash/isEqual";
|
|
3
2
|
import _isBoolean from "lodash/isBoolean";
|
|
4
3
|
import _isArray from "lodash/isArray";
|
|
5
4
|
import _cloneDeepWith from "lodash/cloneDeepWith";
|
|
@@ -9,7 +8,7 @@ import classnames from 'classnames';
|
|
|
9
8
|
import { tools } from '@zat-design/utils';
|
|
10
9
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
|
11
10
|
import { actions, defaultBtnNameMap, defaultSingleActionKeys, defaultMultipleActionKeys, defaultEditingActionKeys } from "./config";
|
|
12
|
-
import { customValidate, getNamePath, splitNames, handleScrollToError, cloneDeepFilterNode } from "./tools";
|
|
11
|
+
import { customValidate, getNamePath, splitNames, handleScrollToError, cloneDeepFilterNode, isRecordShallowEqual } from "./tools";
|
|
13
12
|
import { filterInternalFields } from "../../ProForm/utils";
|
|
14
13
|
import ProTooltip from "../../ProTooltip";
|
|
15
14
|
import { RenderField, ActionButton } from "../components";
|
|
@@ -407,7 +406,7 @@ export const transformColumns = (columns = [], config, caches) => {
|
|
|
407
406
|
if (record === prevRecord) {
|
|
408
407
|
return true;
|
|
409
408
|
}
|
|
410
|
-
return !
|
|
409
|
+
return !isRecordShallowEqual(record, prevRecord);
|
|
411
410
|
}
|
|
412
411
|
const key = item.dataIndex || item.key;
|
|
413
412
|
if (!key) return true;
|
|
@@ -11,13 +11,12 @@ import _cloneDeep from "lodash/cloneDeep";
|
|
|
11
11
|
/* eslint-disable no-restricted-syntax */
|
|
12
12
|
import React from 'react';
|
|
13
13
|
import { tools, validate } from '@zat-design/utils';
|
|
14
|
+
import { generateId } from "../../utils";
|
|
14
15
|
/**
|
|
15
16
|
* 生成随机字符串
|
|
16
17
|
* @returns 随机字符串
|
|
17
18
|
*/
|
|
18
|
-
export const getRandom = () =>
|
|
19
|
-
return crypto.randomUUID().replace(/-/g, '').slice(0, 6);
|
|
20
|
-
};
|
|
19
|
+
export const getRandom = () => generateId();
|
|
21
20
|
|
|
22
21
|
/**
|
|
23
22
|
* 构建 AntD Table 的 rowKey 函数,支持字符串/函数两种形式,
|
|
@@ -36,6 +35,25 @@ export const buildTableRowKey = rowKey => {
|
|
|
36
35
|
};
|
|
37
36
|
};
|
|
38
37
|
|
|
38
|
+
/**
|
|
39
|
+
* 表格行数据的浅比较:仅比较第一层字段引用/值,避免虚拟滚动时整行深比较开销。
|
|
40
|
+
* 行对象在本组件场景通常为扁平标量字段;同引用时由调用方另行处理。
|
|
41
|
+
*/
|
|
42
|
+
export const isRecordShallowEqual = (a, b) => {
|
|
43
|
+
if (a === b) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (!a || !b) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const aKeys = Object.keys(a);
|
|
50
|
+
const bKeys = Object.keys(b);
|
|
51
|
+
if (aKeys.length !== bKeys.length) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return aKeys.every(key => a[key] === b[key]);
|
|
55
|
+
};
|
|
56
|
+
|
|
39
57
|
/**
|
|
40
58
|
* 深层次对比两个对象且取出来差异值
|
|
41
59
|
* @param object 比较对象
|
|
@@ -132,9 +132,10 @@ const useShouldUpdateForTable = props => {
|
|
|
132
132
|
// 使用 ref 存储上一次的 rowParams,避免引用变化导致的重复调用
|
|
133
133
|
const prevRowParamsRef = useRef(null);
|
|
134
134
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
const
|
|
135
|
+
// 行数据引用 + 行索引均不变时跳过深比较(虚拟滚动常见路径)
|
|
136
|
+
const prevRowParams = prevRowParamsRef.current;
|
|
137
|
+
const rowDataUnchanged = isInitializedRef.current && prevRowParams?.[0] === rowParams[0] && prevRowParams?.[1]?.index === rowParams[1]?.index;
|
|
138
|
+
const shouldProcess = !isInitializedRef.current || !rowDataUnchanged && !_isEqualWith(prevRowParams, rowParams, customEqualForFunction);
|
|
138
139
|
if (shouldProcess) {
|
|
139
140
|
prevRowParamsRef.current = rowParams;
|
|
140
141
|
isInitializedRef.current = true;
|
|
@@ -15,8 +15,8 @@ import Container from "../../Container";
|
|
|
15
15
|
import viewSvg from "../../../../assets/view.svg";
|
|
16
16
|
import useRequestList from "./hooks/useRequestList";
|
|
17
17
|
import locale from "../../../../locale";
|
|
18
|
-
import { hideTooltipIfOpen } from "./utils";
|
|
19
|
-
import {
|
|
18
|
+
import { hideTooltipIfOpen, mergeSelectedRows } from "./utils";
|
|
19
|
+
import { generateId } from "../../../../utils";
|
|
20
20
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
21
21
|
const ProModalSelect = (props, ref) => {
|
|
22
22
|
const {
|
|
@@ -81,7 +81,7 @@ const ProModalSelect = (props, ref) => {
|
|
|
81
81
|
const isMultiple = type === 'checkbox';
|
|
82
82
|
|
|
83
83
|
// 生成一个唯一id,用于满足tooltip功能
|
|
84
|
-
const uuid =
|
|
84
|
+
const uuid = generateId();
|
|
85
85
|
|
|
86
86
|
// 全局配置fieldNames指定 value与label的key
|
|
87
87
|
const {
|
|
@@ -22,4 +22,28 @@ export const hideTooltipIfOpen = (dom, open) => {
|
|
|
22
22
|
if (open && grandParent) {
|
|
23
23
|
grandParent.classList.add('pro-modal-select-parent-open');
|
|
24
24
|
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 合并跨页/筛选场景下的多选行数据
|
|
29
|
+
*
|
|
30
|
+
* antd Table 开启 preserveSelectedRowKeys 后,onChange 回传的 rowKeys 是全量(含不在当前页的 key),
|
|
31
|
+
* 但 rows 中不在当前 dataSource 的行对象会是 undefined。此函数用历史已选行(prevRows)补全这些缺失的行对象,
|
|
32
|
+
* 保证返回的 rows 与 rowKeys 全量对齐,从而避免翻页/精准筛选后丢失已选数据。
|
|
33
|
+
*
|
|
34
|
+
* @param prevRows 历史已选行(累积缓存)
|
|
35
|
+
* @param nextRowKeys antd 回传的全量选中 key
|
|
36
|
+
* @param nextRows antd 回传的行对象(不在当前页的为 undefined)
|
|
37
|
+
* @param getRowKey 取行 key 的方法
|
|
38
|
+
* @returns 与 nextRowKeys 对齐的全量行对象(无法找到行对象的 key 会被过滤)
|
|
39
|
+
*/
|
|
40
|
+
export const mergeSelectedRows = (prevRows, nextRowKeys, nextRows, getRowKey) => {
|
|
41
|
+
const rowMap = new Map();
|
|
42
|
+
prevRows?.forEach(row => {
|
|
43
|
+
if (row) rowMap.set(getRowKey(row), row);
|
|
44
|
+
});
|
|
45
|
+
nextRows?.forEach(row => {
|
|
46
|
+
if (row) rowMap.set(getRowKey(row), row);
|
|
47
|
+
});
|
|
48
|
+
return nextRowKeys.map(key => rowMap.get(key)).filter(Boolean);
|
|
25
49
|
};
|
|
@@ -111,7 +111,9 @@ const ProTree = props => {
|
|
|
111
111
|
...useRequest?.options
|
|
112
112
|
});
|
|
113
113
|
useDeepCompareEffect(() => {
|
|
114
|
-
|
|
114
|
+
// 显式传入数组(含空数组 [])时都同步到内部 state,
|
|
115
|
+
// 否则删除最后一个节点后 dataSource 变为 [] 时树数据不会被清空。
|
|
116
|
+
if (Array.isArray(dataSource)) {
|
|
115
117
|
const allKey = getAllKeys(dataSource, fieldNames);
|
|
116
118
|
setState({
|
|
117
119
|
treeData: dataSource,
|
package/es/ProUpload/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { message } from 'antd';
|
|
|
5
5
|
import { PointerSensor, useSensor } from '@dnd-kit/core';
|
|
6
6
|
import { arrayMove } from '@dnd-kit/sortable';
|
|
7
7
|
import { useProConfig } from "../ProConfigProvider";
|
|
8
|
+
import { generateId } from "../utils";
|
|
8
9
|
import DragRender from "./components/DragRender";
|
|
9
10
|
import ButtonRender from "./components/ButtonRender";
|
|
10
11
|
import ImageRender from "./components/ImageRender";
|
|
@@ -271,7 +272,7 @@ const ProUpload = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
271
272
|
}
|
|
272
273
|
});
|
|
273
274
|
if (!file.uid) {
|
|
274
|
-
file.uid =
|
|
275
|
+
file.uid = generateId();
|
|
275
276
|
}
|
|
276
277
|
if (!file.status) {
|
|
277
278
|
file.status = 'done';
|
package/es/utils/index.js
CHANGED
|
@@ -54,6 +54,11 @@ export const isEmpty = (value, emptyValue) => {
|
|
|
54
54
|
return false;
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* 生成用于 UI 元素的随机短 ID(非安全用途)
|
|
59
|
+
*/
|
|
60
|
+
export const generateId = () => Math.random().toString(36).slice(-6);
|
|
61
|
+
|
|
57
62
|
/**
|
|
58
63
|
* 判断一个元素是否触发了省略号(文本溢出)
|
|
59
64
|
* 如果元素设置为单行(white-space: nowrap)则比较 scrollWidth 与 clientWidth,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zat-design/sisyphus-react",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"files": [
|
|
6
6
|
"es",
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
"@typescript-eslint/parser": "^7.18.0",
|
|
141
141
|
"@umijs/fabric": "^2.8.1",
|
|
142
142
|
"@umijs/test": "^3.0.5",
|
|
143
|
-
"@zat-design/sisyphus-login": "4.0.
|
|
143
|
+
"@zat-design/sisyphus-login": "4.0.2",
|
|
144
144
|
"ahooks": "3.9.5",
|
|
145
145
|
"babel-plugin-import": "^1.13.8",
|
|
146
146
|
"babel-plugin-lodash": "^3.3.4",
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 合并跨页/筛选场景下的多选行数据
|
|
3
|
-
*
|
|
4
|
-
* antd Table 开启 preserveSelectedRowKeys 后,onChange 回传的 rowKeys 是全量(含不在当前页的 key),
|
|
5
|
-
* 但 rows 中不在当前 dataSource 的行对象会是 undefined。此函数用历史已选行(prevRows)补全这些缺失的行对象,
|
|
6
|
-
* 保证返回的 rows 与 rowKeys 全量对齐,从而避免翻页/精准筛选后丢失已选数据。
|
|
7
|
-
*
|
|
8
|
-
* @param prevRows 历史已选行(累积缓存)
|
|
9
|
-
* @param nextRowKeys antd 回传的全量选中 key
|
|
10
|
-
* @param nextRows antd 回传的行对象(不在当前页的为 undefined)
|
|
11
|
-
* @param getRowKey 取行 key 的方法
|
|
12
|
-
* @returns 与 nextRowKeys 对齐的全量行对象(无法找到行对象的 key 会被过滤)
|
|
13
|
-
*/
|
|
14
|
-
export const mergeSelectedRows = (prevRows, nextRowKeys, nextRows, getRowKey) => {
|
|
15
|
-
// 用 key -> row 建立查找表:当前页最新行覆盖历史行
|
|
16
|
-
const rowMap = new Map();
|
|
17
|
-
prevRows?.forEach(row => {
|
|
18
|
-
if (row) rowMap.set(getRowKey(row), row);
|
|
19
|
-
});
|
|
20
|
-
nextRows?.forEach(row => {
|
|
21
|
-
if (row) rowMap.set(getRowKey(row), row);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
// 按 nextRowKeys 顺序取行对象,取不到的(无缓存)过滤掉
|
|
25
|
-
return nextRowKeys.map(key => rowMap.get(key)).filter(Boolean);
|
|
26
|
-
};
|