Skip to content

Commit 5199bc3

Browse files
committed
feat(Cascader): support same values across levels with valueType='full'
1 parent 9332330 commit 5199bc3

File tree

4 files changed

+123
-71
lines changed

4 files changed

+123
-71
lines changed

packages/components/cascader/core/effect.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { isNumber, isFunction, cloneDeep } from 'lodash-es';
2-
import { TreeNode, CascaderContextType, TdCascaderProps, TreeNodeValue, TreeNodeModel } from '../interface';
1+
import { cloneDeep, isFunction, isNumber } from 'lodash-es';
2+
import { pathToKey } from '@tdesign/common-js/tree-v1/tree-node-model';
33
import { getFullPathLabel, getTreeValue } from './helper';
44

5+
import type { CascaderContextType, TdCascaderProps, TreeNode, TreeNodeModel, TreeNodeValue } from '../interface';
6+
57
/**
68
* 点击item的副作用
79
* @param propsTrigger
@@ -211,31 +213,46 @@ export const treeNodesEffect = (
211213
* @param treeStore
212214
* @param treeValue
213215
* @param expend
216+
* @param valueType
214217
*/
215218
export const treeStoreExpendEffect = (
216219
treeStore: CascaderContextType['treeStore'],
217220
value: CascaderContextType['value'],
218221
expend: TreeNodeValue[],
222+
valueType?: TdCascaderProps['valueType'],
219223
) => {
220224
const treeValue = getTreeValue(value);
221225

222226
if (!treeStore) return;
227+
228+
const allowDuplicateValue = treeStore.config?.allowDuplicateValue;
229+
223230
// init expanded, 无expend状态时设置
224231
if (Array.isArray(treeValue) && expend.length === 0) {
225232
const expandedMap = new Map();
226-
const [val] = treeValue;
227-
if (val) {
228-
expandedMap.set(val, true);
229-
const node = treeStore.getNode(val);
230-
if (!node) {
231-
treeStore.refreshNodes();
232-
return;
233+
234+
// 对于 valueType='full',value 本身就是完整路径,用它来查找节点
235+
let node = null;
236+
if (valueType === 'full' && Array.isArray(value) && value.length > 0) {
237+
node = treeStore.getNode(value as TreeNodeValue[]);
238+
} else {
239+
const [val] = treeValue;
240+
if (val) {
241+
expandedMap.set(val, true);
242+
node = treeStore.getNode(val);
233243
}
244+
}
245+
246+
if (node) {
234247
node.getParents().forEach((tn: TreeNode) => {
235-
expandedMap.set(tn.value, true);
248+
const expandedKey = allowDuplicateValue ? pathToKey(tn.getPath().map((n) => n.value)) : tn.value;
249+
expandedMap.set(expandedKey, true);
236250
});
237251
const expandedArr = Array.from(expandedMap.keys());
238252
treeStore.replaceExpanded(expandedArr);
253+
} else if (treeValue.length > 0) {
254+
treeStore.refreshNodes();
255+
return;
239256
} else {
240257
treeStore.resetExpanded();
241258
}

packages/components/cascader/core/helper.ts

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { PATH_SEPARATOR } from '@tdesign/common-js/tree-v1/tree-node-model';
12
import { isEmpty } from 'lodash-es';
2-
import {
3-
TreeNode,
3+
4+
import type {
45
CascaderContextType,
5-
TdCascaderProps,
66
CascaderValue,
7+
TdCascaderProps,
8+
TreeNode,
79
TreeNodeValue,
810
TreeOptionData,
911
} from '../interface';
@@ -15,20 +17,34 @@ import {
1517
* @returns
1618
*/
1719
export function getSingleContent(cascaderContext: CascaderContextType): string {
18-
const { value, multiple, treeStore, showAllLevels } = cascaderContext;
19-
const BooleanIsFalseExceptZero = 0;
20-
if (multiple || (!value && value !== BooleanIsFalseExceptZero)) return '';
21-
22-
if (Array.isArray(value)) return '';
23-
const node = treeStore && treeStore.getNodes(value as TreeNodeValue | TreeNode);
24-
if (!(node && node.length)) {
25-
return value as string;
20+
const { value, multiple, treeStore, showAllLevels, valueType } = cascaderContext;
21+
22+
const isEmpty = (!value && value !== 0) || (Array.isArray(value) && value.length === 0);
23+
const isInvalidFullPathValue = valueType === 'full' && !Array.isArray(value);
24+
if (multiple || isEmpty || isInvalidFullPathValue) return '';
25+
26+
const formatContent = (path: TreeNode[]) => {
27+
if (!path?.length) return '';
28+
return showAllLevels ? path.map((node) => node.label).join(` ${PATH_SEPARATOR} `) : path[path.length - 1].label;
29+
};
30+
31+
const getDefaultDisplay = (val: any) => (Array.isArray(val) ? val.join(` ${PATH_SEPARATOR} `) : String(val));
32+
33+
// valueType = 'full'
34+
if (valueType === 'full' && Array.isArray(value) && value.length > 0) {
35+
const node = treeStore?.getNode(value as string[]);
36+
const path = node?.getPath();
37+
return path?.length ? formatContent(path) : getDefaultDisplay(value);
2638
}
27-
const path = node && node[0].getPath();
28-
if (path && path.length) {
29-
return showAllLevels ? path.map((node: TreeNode) => node.label).join(' / ') : path[path.length - 1].label;
39+
40+
// valueType = 'single'
41+
const nodes = treeStore?.getNodes(value as TreeNodeValue | TreeNode);
42+
if (!nodes?.length) {
43+
return getDefaultDisplay(value);
3044
}
31-
return value as string;
45+
46+
const path = nodes[0].getPath();
47+
return path?.length ? formatContent(path) : getDefaultDisplay(value);
3248
}
3349

3450
/**
@@ -38,18 +54,23 @@ export function getSingleContent(cascaderContext: CascaderContextType): string {
3854
* @returns
3955
*/
4056
export function getMultipleContent(cascaderContext: CascaderContextType) {
41-
const { value, multiple, treeStore, showAllLevels } = cascaderContext;
57+
const { value, multiple, treeStore, showAllLevels, valueType } = cascaderContext;
4258

4359
if (!multiple) return [];
4460
if (multiple && !Array.isArray(value)) return [];
4561

46-
const node = treeStore && treeStore.getNodes(value as TreeNodeValue | TreeNode);
47-
if (!node) return [];
48-
4962
return (value as TreeNodeValue[])
5063
.map((item: TreeNodeValue) => {
51-
const node = treeStore.getNodes(item);
52-
return showAllLevels ? getFullPathLabel(node?.[0]) : node?.[0]?.label;
64+
let node: TreeNode | undefined;
65+
// valueType='full' 时,item 是路径数组,使用 getNode 获取节点
66+
if (valueType === 'full' && Array.isArray(item)) {
67+
node = treeStore?.getNode(item as string[]);
68+
} else {
69+
const nodes = treeStore?.getNodes(item);
70+
node = nodes?.[0];
71+
}
72+
if (!node) return undefined;
73+
return showAllLevels ? getFullPathLabel(node) : node?.label;
5374
})
5475
.filter((item) => !!item);
5576
}
@@ -107,20 +128,16 @@ export const getTreeValue = (value: CascaderContextType['value']) => {
107128
};
108129

109130
/**
110-
* 按数据类型计算通用数值
111-
* @param value
112-
* @param showAllLevels
113-
* @param multiple
114-
* @returns
131+
* 获取用于展开的节点值
115132
*/
116-
export const getCascaderValue = (value: CascaderValue, valueType: TdCascaderProps['valueType'], multiple: boolean) => {
117-
if (valueType === 'single') {
118-
return value;
119-
}
120-
if (multiple) {
121-
return (value as Array<CascaderValue>).map((item: TreeNodeValue[]) => item[item.length - 1]);
133+
export const getExpandValue = (value: CascaderContextType['value'], valueType?: TdCascaderProps['valueType']) => {
134+
if (valueType === 'full' && Array.isArray(value) && value.length > 0) {
135+
// valueType='full' 时,value 是完整路径数组
136+
// 返回最后一个值用于展开
137+
return value[value.length - 1] as TreeNodeValue;
122138
}
123-
return value[(value as Array<CascaderValue>).length - 1];
139+
const treeValue = getTreeValue(value);
140+
return treeValue[0];
124141
};
125142

126143
/**

packages/components/cascader/hooks.tsx

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { useEffect, useMemo, useRef, useState } from 'react';
22
import { isArray, isEqual, isFunction } from 'lodash-es';
33

4+
import { pathToKey } from '@tdesign/common-js/tree-v1/tree-node-model';
45
import TreeStore from '@tdesign/common-js/tree-v1/tree-store';
56
import type { TypeTreeNodeData } from '@tdesign/common-js/tree-v1/types';
67

78
import useControlled from '../hooks/useControlled';
89
import useDefaultProps from '../hooks/useDefaultProps';
910
import { treeNodesEffect, treeStoreExpendEffect } from './core/effect';
10-
import { getCascaderValue, getTreeValue, isEmptyValues, isValueInvalid } from './core/helper';
11+
import { getTreeValue, isEmptyValues, isValueInvalid } from './core/helper';
1112
import { cascaderDefaultProps } from './defaultProps';
1213

1314
import type { TreeOptionData } from '../common';
@@ -122,6 +123,7 @@ export const useCascaderContext = (originalProps: TdCascaderProps) => {
122123
...keys,
123124
children: typeof keys.children === 'string' ? keys.children : 'children',
124125
},
126+
allowDuplicateValue: props.valueType === 'full',
125127
onLoad: () => {
126128
setTimeout(() => {
127129
store.refreshNodes();
@@ -135,7 +137,7 @@ export const useCascaderContext = (originalProps: TdCascaderProps) => {
135137
} else {
136138
treeStore.reload(options);
137139
treeStore.refreshNodes();
138-
treeStoreExpendEffect(treeStore, scopeVal, []);
140+
treeStoreExpendEffect(treeStore, scopeVal, [], props.valueType);
139141
treeNodesEffect(inputVal, treeStore, setTreeNodes, props.filter, isParentFilterable);
140142
}
141143
};
@@ -161,14 +163,14 @@ export const useCascaderContext = (originalProps: TdCascaderProps) => {
161163

162164
// value 校验逻辑
163165
useEffect(() => {
164-
const { setValue, multiple, valueType = 'single' } = cascaderContext;
166+
const { setValue, multiple } = cascaderContext;
165167

166168
if (isValueInvalid(innerValue, cascaderContext)) {
167169
setValue(multiple ? [] : '', 'invalid-value');
168170
}
169171

170172
if (!isEmptyValues(innerValue)) {
171-
setScopeVal(getCascaderValue(innerValue, valueType, multiple));
173+
setScopeVal(innerValue);
172174
} else {
173175
setScopeVal(multiple ? [] : '');
174176
}
@@ -181,8 +183,8 @@ export const useCascaderContext = (originalProps: TdCascaderProps) => {
181183

182184
useEffect(() => {
183185
if (!treeStore) return;
184-
treeStoreExpendEffect(treeStore, scopeVal, expend);
185-
}, [treeStore, scopeVal, expend]);
186+
treeStoreExpendEffect(treeStore, scopeVal, expend, props.valueType);
187+
}, [treeStore, scopeVal, expend, props.valueType]);
186188

187189
useEffect(() => {
188190
if (!treeStore) return;
@@ -191,8 +193,27 @@ export const useCascaderContext = (originalProps: TdCascaderProps) => {
191193

192194
useEffect(() => {
193195
if (!treeStore) return;
194-
treeStore.replaceChecked(getTreeValue(scopeVal));
195-
}, [options, scopeVal, treeStore, multiple]);
196+
197+
const { valueType } = props;
198+
199+
const getCheckedKeys = (): TreeNodeValue[] => {
200+
if (valueType !== 'full') {
201+
return getTreeValue(scopeVal);
202+
}
203+
204+
const isValidPath = (path: unknown): path is TreeNodeValue[] => Array.isArray(path) && path.length > 0;
205+
206+
// 多选模式
207+
if (multiple && Array.isArray(scopeVal)) {
208+
return (scopeVal as TreeNodeValue[][]).filter(isValidPath).map(pathToKey);
209+
}
210+
211+
// 单选模式
212+
return isValidPath(scopeVal) ? [pathToKey(scopeVal)] : [];
213+
};
214+
215+
treeStore.replaceChecked(getCheckedKeys());
216+
}, [options, scopeVal, treeStore, multiple, props]);
196217

197218
useEffect(() => {
198219
if (!innerPopupVisible && isFilterable) {
@@ -212,32 +233,29 @@ export const useCascaderContext = (originalProps: TdCascaderProps) => {
212233
multiple: TdCascaderProps['multiple'],
213234
) => {
214235
const { treeStore } = cascaderContext;
236+
if (!treeStore) return [];
237+
215238
const optionsData: TreeOptionData[] = [];
216239

217-
if (!treeStore) return optionsData;
240+
const pushNodeData = (node?: TreeNode) => {
241+
if (node?.data) optionsData.push(node.data);
242+
};
218243

219244
if (valueType === 'full') {
220-
if (multiple) {
221-
// 未来需支持全路径拼接搜索
222-
arrValue.forEach((value) => {
223-
if (isArray(value) && value.length) {
224-
const nodeValue = value[value.length - 1];
225-
const [node] = treeStore.getNodes(nodeValue) || [];
226-
node?.data && optionsData.push(node.data);
227-
}
228-
});
229-
} else if (isArray(arrValue) && arrValue.length) {
230-
const nodeValue = arrValue[arrValue.length - 1];
231-
const [node] = treeStore.getNodes(nodeValue) || [];
232-
node?.data && optionsData.push(node.data);
233-
}
234-
} else if (valueType === 'single') {
235-
arrValue.forEach((value) => {
236-
const [node] = treeStore.getNodes(value) || [];
237-
node?.data && optionsData.push(node.data);
245+
const values = multiple ? arrValue : [arrValue];
246+
values.forEach((v) => {
247+
if (isArray(v) && v.length) {
248+
pushNodeData(treeStore.getNode(v as TreeNodeValue[]));
249+
}
238250
});
251+
return optionsData;
239252
}
240253

254+
arrValue.forEach((v) => {
255+
const node = treeStore.getNodes(v)?.[0];
256+
pushNodeData(node);
257+
});
258+
241259
return optionsData;
242260
};
243261

0 commit comments

Comments
 (0)