From bba7c2b56959b57f1bbd516914f58db34e617607 Mon Sep 17 00:00:00 2001 From: Rylan Date: Thu, 5 Feb 2026 22:59:18 +0800 Subject: [PATCH 1/2] feat(Cascader): support same values across levels with `valueType='full'` --- js/tree-v1/tree-node-model.ts | 15 +++- js/tree-v1/tree-node.ts | 163 +++++++++++++++++++++++----------- js/tree-v1/tree-store.ts | 65 ++++++++------ js/tree-v1/types.ts | 4 +- 4 files changed, 166 insertions(+), 81 deletions(-) diff --git a/js/tree-v1/tree-node-model.ts b/js/tree-v1/tree-node-model.ts index 838e83aa7e..6652f9fd19 100644 --- a/js/tree-v1/tree-node-model.ts +++ b/js/tree-v1/tree-node-model.ts @@ -1,8 +1,15 @@ -import { isUndefined, isBoolean, pick, omit } from 'lodash-es'; -import { TreeNode } from './tree-node'; -import { OptionData } from '../common'; -import { TreeNodeValue, TypeTreeNodeModel, TypeTreeNodeData, TypeTreeItem, TreeNodeModelProps } from './types'; +import { isBoolean, isUndefined, omit, pick } from 'lodash-es'; import log from '../log/log'; +import { TreeNode } from './tree-node'; + +import type { OptionData } from '../common'; +import type { TreeNodeModelProps, TreeNodeValue, TypeTreeItem, TypeTreeNodeData, TypeTreeNodeModel } from './types'; + +export const PATH_SEPARATOR = '/'; + +export function pathToKey(path: TreeNodeValue[]) { + return path.join(PATH_SEPARATOR); +} // 获取节点需要暴露的属性 function getExposedProps(node: TreeNode): TreeNodeModelProps { diff --git a/js/tree-v1/tree-node.ts b/js/tree-v1/tree-node.ts index c91e45f626..9607a5e005 100644 --- a/js/tree-v1/tree-node.ts +++ b/js/tree-v1/tree-node.ts @@ -1,7 +1,8 @@ import { get, isBoolean, isFunction, isNil, isNull, isNumber, uniqueId } from 'lodash-es'; import log from '../log'; -import { createNodeModel, updateNodeModel } from './tree-node-model'; +import { createNodeModel, pathToKey, updateNodeModel } from './tree-node-model'; import { TreeStore } from './tree-store'; + import type { TreeNodeState, TreeNodeValue, @@ -175,12 +176,6 @@ export class TreeNode { // 设置 value // 没有 value 的时候,value 默认使用自动生成的 唯一 id this.value = isNil(get(data, propValue)) ? this[privateKey] : get(data, propValue); - const { nodeMap, privateMap } = tree; - if (nodeMap.get(this.value)) { - log.warn('Tree', `Dulplicate value: ${this.value}`); - } - nodeMap.set(this.value, this); - privateMap.set(this[privateKey], this); this.label = get(data, propLabel) || ''; @@ -199,6 +194,24 @@ export class TreeNode { this.parent = null; } + const { nodeMap, privateMap } = tree; + const nodeKey = this.getNodeMapKey(); + + if (config?.allowDuplicateValue) { + const siblings = this.parent ? this.parent.children : tree.children; + if (Array.isArray(siblings)) { + const hasDuplicate = siblings.some((sibling) => sibling instanceof TreeNode && sibling.value === this.value); + if (hasDuplicate) { + log.warn('Tree', `Duplicated value in the same level: ${this.value}`); + } + } + } else if (nodeMap.get(nodeKey)) { + log.warn('Tree', `Duplicated value: ${this.value}`); + } + + nodeMap.set(nodeKey, this); + privateMap.set(this[privateKey], this); + // 同步数据属性到节点属性 // 仅 syncableStatus 列举的属性被同步到 treeNode 实例属性 syncableProps.forEach((prop) => { @@ -224,7 +237,8 @@ export class TreeNode { if (this.isLeaf()) { // initExpanded 时,子节点没有完全加载,无法依赖 isLeaf 状态判断 this.expanded = false; - this.tree.expandedMap.delete(this.value); + const expandedKey = config.allowDuplicateValue ? this.getNodeMapKey() : this.value; + this.tree.expandedMap.delete(expandedKey); } // checked 状态依赖于子节点状态 @@ -245,16 +259,17 @@ export class TreeNode { */ private initChecked(): void { const { tree, value, parent } = this; - const { checkedMap } = tree; - const { checkStrictly } = tree.config; + const { checkedMap, config } = tree; + const { checkStrictly, allowDuplicateValue } = config; + const checkedKey = allowDuplicateValue ? this.getNodeMapKey() : value; if (this.checked) { - checkedMap.set(value, true); + checkedMap.set(checkedKey, true); } // 这里不可以使用 parent.isChecked 方法 // 因为当前节点创建时尚未插入父节点的 children 数组,可能父节点选中态仅受到之前子节点状态的影响 // 这会导致父节点状态计算错误,进而引发子节点变更了选中状态 if (!checkStrictly && parent?.checked) { - checkedMap.set(value, true); + checkedMap.set(checkedKey, true); } this.updateChecked(); } @@ -267,17 +282,18 @@ export class TreeNode { const { tree } = this; let { expanded } = this; const { config } = tree; + const expandedKey = config.allowDuplicateValue ? this.getNodeMapKey() : this.value; if (isNumber(config.expandLevel) && this.getLevel() < config.expandLevel) { - tree.expandedMap.set(this.value, true); + tree.expandedMap.set(expandedKey, true); expanded = true; } if (this.children === true && config.lazy) { expanded = false; } if (expanded) { - tree.expandedMap.set(this.value, true); + tree.expandedMap.set(expandedKey, true); } else { - tree.expandedMap.delete(this.value); + tree.expandedMap.delete(expandedKey); } this.expanded = expanded; } @@ -288,8 +304,10 @@ export class TreeNode { */ private initActived(): void { const { tree, actived } = this; + const { allowDuplicateValue } = tree.config; if (actived && this.isActivable()) { - tree.activedMap.set(this.value, true); + const activedKey = allowDuplicateValue ? this.getNodeMapKey() : this.value; + tree.activedMap.set(activedKey, true); } } @@ -330,7 +348,8 @@ export class TreeNode { // 如果之前是叶子节点,现在有了子节点,且 expandAll 为 true,则展开 if (wasLeaf && tree.config.expandAll && !this.isLeaf()) { - tree.expandedMap.set(this.value, true); + const expandedKey = tree.config.allowDuplicateValue ? this.getNodeMapKey() : this.value; + tree.expandedMap.set(expandedKey, true); this.expanded = true; } @@ -417,10 +436,12 @@ export class TreeNode { nodes.forEach((item) => { const node = item; node.tree = tree; - tree.nodeMap.set(node.value, node); + const nodeKey = node.getNodeMapKey(); + tree.nodeMap.set(nodeKey, node); tree.privateMap.set(node[privateKey], node); if (node.expanded) { - tree.expandedMap.set(node.value, true); + const expandedKey = tree.config.allowDuplicateValue ? nodeKey : node.value; + tree.expandedMap.set(expandedKey, true); } }); @@ -512,10 +533,12 @@ export class TreeNode { */ private clean(): void { const { tree, value } = this; - tree.activedMap.delete(value); - tree.checkedMap.delete(value); - tree.expandedMap.delete(value); - tree.nodeMap.delete(value); + const nodeKey = this.getNodeMapKey(); + const stateKey = tree.config.allowDuplicateValue ? nodeKey : value; + tree.activedMap.delete(stateKey); + tree.checkedMap.delete(stateKey); + tree.expandedMap.delete(stateKey); + tree.nodeMap.delete(nodeKey); tree.filterMap.delete(value); tree.privateMap.delete(this[privateKey]); } @@ -665,6 +688,19 @@ export class TreeNode { return nodes.reverse(); } + /** + * 获取节点在 nodeMap 中的 key + * - 当 `allowDuplicateValue` 为 true 时,使用路径作为 key + * - 否则使用节点的 value 作为 key + */ + public getNodeMapKey(): string { + const { allowDuplicateValue } = this.tree.config; + if (allowDuplicateValue) { + return pathToKey(this.getPath().map((node) => node.value)); + } + return this.value; + } + /** * 获取节点所在层级 * @return number 层级序号 @@ -708,8 +744,9 @@ export class TreeNode { const { allowFoldNodeOnFilter } = config; let visible = true; + const nodeKey = this.getNodeMapKey(); - if (!nodeMap.get(this.value)) { + if (!nodeMap.get(nodeKey)) { // 节点不在当前树上,所以不可见 return false; } @@ -836,22 +873,27 @@ export class TreeNode { */ public isActived(map?: Map): boolean { const { tree, value } = this; + const { allowDuplicateValue } = tree.config; const activedMap = map || tree.activedMap; - return !!(tree.nodeMap.get(value) && activedMap.get(value)); + const nodeKey = this.getNodeMapKey(); + const activedKey = allowDuplicateValue ? nodeKey : value; + return !!(tree.nodeMap.get(nodeKey) && activedMap.get(activedKey)); } /** - * 节点是否已展开 + * 节点是否被展开 * @param {Map} [map] 预设展开节点 map, 用于计算节点在预期环境中的展开状态 * @return boolean 是否已展开 */ public isExpanded(map?: Map): boolean { const { tree, value, vmIsLocked } = this; const { hasFilter, config } = tree; - const { allowFoldNodeOnFilter } = config; + const { allowFoldNodeOnFilter, allowDuplicateValue } = config; if (hasFilter && !allowFoldNodeOnFilter && vmIsLocked) return true; const expandedMap = map || tree.expandedMap; - return !!(tree.nodeMap.get(value) && expandedMap.get(value)); + const nodeKey = this.getNodeMapKey(); + const expandedKey = allowDuplicateValue ? nodeKey : value; + return !!(tree.nodeMap.get(nodeKey) && expandedMap.get(expandedKey)); } /** @@ -861,20 +903,22 @@ export class TreeNode { */ public isChecked(map?: TypeIdMap): boolean { const { children, tree, value } = this; - const { checkStrictly, valueMode } = tree.config; + const { checkStrictly, valueMode, allowDuplicateValue } = tree.config; + const nodeKey = this.getNodeMapKey(); // 节点不在当前树上,视为未选中 - if (!tree.nodeMap.get(value)) return false; + if (!tree.nodeMap.get(nodeKey)) return false; // 节点不可选,视为未选中 if (!this.isCheckable()) return false; const checkedMap = map || tree.checkedMap; + const checkedKey = allowDuplicateValue ? nodeKey : value; // 严格模式,则已经可以判定选中状态 if (checkStrictly) { - return !!checkedMap.get(value); + return !!checkedMap.get(checkedKey); } let checked = false; // 在 checkedMap 中,则根据 valueMode 的值进行判断 if ( - checkedMap.get(value) && + checkedMap.get(checkedKey) && // 如果 valueMode 为 all、parentFirst,则视为选中 (valueMode !== 'onlyLeaf' || // 如果 valueMode 为 onlyLeaf 并且当前节点是叶子节点,则视为选中 @@ -893,7 +937,10 @@ export class TreeNode { // 从父节点状态推断子节点状态 // 这里再调用 isChecked 会导致死循环 const parents = this.getParents(); - checked = parents.some((node) => checkedMap.get(node.value)); + checked = parents.some((node) => { + const parentKey = allowDuplicateValue ? node.getNodeMapKey() : node.value; + return checkedMap.get(parentKey); + }); } return checked; } @@ -905,8 +952,9 @@ export class TreeNode { public isIndeterminate(): boolean { const { children, tree, value } = this; const { checkStrictly } = tree.config; + const nodeKey = this.getNodeMapKey(); // 节点不在当前树上,视为未选中 - if (!tree.nodeMap.get(value)) return false; + if (!tree.nodeMap.get(nodeKey)) return false; // 节点不可选,视为未选中 if (!this.isCheckable()) return false; // 严格模式没有半选状态 @@ -1046,19 +1094,22 @@ export class TreeNode { // 折叠列表中,先移除同级节点 const siblings = node.getSiblings(); siblings.forEach((snode) => { - map.delete(snode.value); + const snodeKey = config?.allowDuplicateValue ? snode.getNodeMapKey() : snode.value; + map.delete(snodeKey); // 同级节点相关状态更新 snode.update(); snode.updateChildren(); }); } // 最后设置自己的折叠状态 - map.set(node.value, true); + const nodeKey = config?.allowDuplicateValue ? node.getNodeMapKey() : node.value; + map.set(nodeKey, true); node.update(); node.updateChildren(); }); } else { - map.delete(this.value); + const thisKey = config?.allowDuplicateValue ? this.getNodeMapKey() : this.value; + map.delete(thisKey); } if (options.directly) { @@ -1096,19 +1147,21 @@ export class TreeNode { if (!options.directly) { map = new Map(tree.activedMap); } + const { allowDuplicateValue } = config; + const activedKey = allowDuplicateValue ? this.getNodeMapKey() : this.value; if (this.isActivable()) { if (actived) { const prevKeys = Array.from(map.keys()); if (!config.activeMultiple) { map.clear(); } - prevKeys.forEach((value) => { - const node = tree.getNode(value); + prevKeys.forEach((key) => { + const node = allowDuplicateValue ? tree.nodeMap.get(key as string) : tree.getNode(key); node?.update(); }); - map.set(this.value, true); + map.set(activedKey, true); } else { - map.delete(this.value); + map.delete(activedKey); } } this.update(); @@ -1198,10 +1251,11 @@ export class TreeNode { } } + const checkedKey = config.allowDuplicateValue ? this.getNodeMapKey() : this.value; if (checked) { - map.set(this.value, true); + map.set(checkedKey, true); } else { - map.delete(this.value); + map.delete(checkedKey); } if (config.checkStrictly) { @@ -1218,7 +1272,8 @@ export class TreeNode { // 状态更新务必放到扩散动作之后 // 过早的状态更新会导致后续计算出错 if (options.directly) { - const relatedNodes = tree.getRelatedNodes([this.value], { + const nodeKey = config.allowDuplicateValue ? this.getNodeMapKey() : this.value; + const relatedNodes = tree.getRelatedNodes([nodeKey], { reverse: true, }); relatedNodes.forEach((node) => { @@ -1234,7 +1289,6 @@ export class TreeNode { public setIndeterminate(indeterminate: boolean, opts?: TypeSettingOptions) { const { tree } = this; - const config = tree.config || {}; const options: TypeSettingOptions = { // 为 true, 为 UI 操作,状态扩散受 disabled 影响 // 为 false, 为值操作, 状态扩散不受 disabled 影响 @@ -1268,6 +1322,8 @@ export class TreeNode { // 选中态向上游扩散 private spreadParentChecked(checked: boolean, map?: TypeIdMap, opts?: TypeSettingOptions) { + const { tree } = this; + const { allowDuplicateValue } = tree.config; const options: TypeSettingOptions = { isAction: true, directly: false, @@ -1279,7 +1335,8 @@ export class TreeNode { const { children } = this; if (Array.isArray(children) && children.length > 0) { // 有子节点,则选中态由子节点选中态集合来决定 - map.delete(this.value); + const checkedKey = allowDuplicateValue ? this.getNodeMapKey() : this.value; + map.delete(checkedKey); } const { parent } = this; @@ -1289,6 +1346,7 @@ export class TreeNode { // 选中态向下游扩散 private spreadChildrenChecked(checked: boolean, map?: TypeIdMap, opts?: TypeSettingOptions) { + const { allowDuplicateValue } = this.tree.config; const options: TypeSettingOptions = { isAction: true, directly: false, @@ -1304,14 +1362,16 @@ export class TreeNode { if (!Array.isArray(children)) return; if (children.length <= 0) return; // 有子节点,则选中态由子节点选中态集合来决定 - map.delete(this.value); + const thisCheckedKey = allowDuplicateValue ? this.getNodeMapKey() : this.value; + map.delete(thisCheckedKey); children.forEach((node) => { // 对于 UI 动作,向下扩散时,禁用状态会阻止状态切换 if (options.isAction && node.isDisabled()) return; + const nodeCheckedKey = allowDuplicateValue ? node.getNodeMapKey() : node.value; if (checked) { - map.set(node.value, true); + map.set(nodeCheckedKey, true); } else { - map.delete(node.value); + map.delete(nodeCheckedKey); } node.spreadChildrenChecked(checked, map, options); }); @@ -1358,11 +1418,12 @@ export class TreeNode { return; } - const { checkedMap } = tree; + const { checkedMap, config } = tree; this.checked = this.isChecked(); this.indeterminate = this.isIndeterminate(); if (this.checked) { - checkedMap.set(value, true); + const checkedKey = config.allowDuplicateValue ? this.getNodeMapKey() : value; + checkedMap.set(checkedKey, true); } tree.updated(this); } diff --git a/js/tree-v1/tree-store.ts b/js/tree-v1/tree-store.ts index 541b63429a..ec5d74d3cb 100644 --- a/js/tree-v1/tree-store.ts +++ b/js/tree-v1/tree-store.ts @@ -1,19 +1,21 @@ -import { isArray, isFunction, isNumber, isString, difference, camelCase, isPlainObject } from 'lodash-es'; +import { camelCase, difference, isArray, isFunction, isNumber, isPlainObject, isString } from 'lodash-es'; import mitt from 'mitt'; import { TreeNode } from './tree-node'; -import { +import { pathToKey } from './tree-node-model'; + +import type { TreeNodeValue, TypeIdMap, - TypeTimer, + TypeRelatedNodesOptions, TypeTargetNode, - TypeTreeNodeData, - TypeTreeItem, - TypeTreeStoreOptions, + TypeTimer, + TypeTreeEventState, TypeTreeFilter, TypeTreeFilterOptions, - TypeRelatedNodesOptions, - TypeTreeEventState, + TypeTreeItem, + TypeTreeNodeData, + TypeTreeStoreOptions, } from './types'; // 构建一个树的数据模型 @@ -184,15 +186,20 @@ export class TreeStore { /** * 获取指定节点对象 - * @param {string|number|TreeNode} item 获取节点对象的条件,可以是节点 value,也可以是节点本身 + * @param {string|number|TreeNode|Array} item 获取节点对象的条件,可以是节点 value,也可以是节点本身,也可以是路径数组 * @return TreeNode 节点对象,如果判断树中没有符合条件的节点,返回 null */ - public getNode(item: TypeTargetNode): TreeNode { + public getNode(item: TypeTargetNode | TreeNodeValue[]): TreeNode { let node = null; - if (isString(item) || isNumber(item)) { + const { allowDuplicateValue } = this.config; + if (isArray(item) && allowDuplicateValue) { + const pathKey = pathToKey(item); + node = this.nodeMap.get(pathKey); + } else if (isString(item) || isNumber(item)) { node = this.nodeMap.get(item); } else if (item instanceof TreeNode) { - node = this.nodeMap.get(item.value); + const nodeKey = item.getNodeMapKey(); + node = this.nodeMap.get(nodeKey); } if (!node) node = null; return node; @@ -591,14 +598,15 @@ export class TreeStore { * @return void */ public setActived(actived: TreeNodeValue[]): void { - const { activeMultiple } = this.config; + const { activeMultiple, allowDuplicateValue } = this.config; const list = actived.slice(0); if (!activeMultiple) { list.length = 1; } list.forEach((val) => { this.activedMap.set(val, true); - const node = this.getNode(val); + // 当 allowDuplicateValue 为 true 时,val 是路径 key,从 nodeMap 获取节点 + const node = allowDuplicateValue ? this.nodeMap.get(val as string) : this.getNode(val); if (node) { node.update(); } @@ -661,8 +669,9 @@ export class TreeStore { * @return void */ public setExpandedDirectly(list: TreeNodeValue[], expanded = true): void { + const { allowDuplicateValue } = this.config; list.forEach((val) => { - const node = this.getNode(val); + const node = allowDuplicateValue ? this.nodeMap.get(val as string) : this.getNode(val); if (!node?.isLeaf() && expanded) { this.expandedMap.set(val, true); } else { @@ -704,28 +713,30 @@ export class TreeStore { */ public getChecked(map?: TypeIdMap): TreeNodeValue[] { const { nodeMap, config } = this; - const { valueMode, checkStrictly } = config; + const { valueMode, checkStrictly, allowDuplicateValue } = config; const list: TreeNodeValue[] = []; const checkedMap = map || this.checkedMap; nodeMap.forEach((node) => { // 判断未选中,直接忽略 if (!node.isChecked(checkedMap)) return; + // 当 allowDuplicateValue 为 true 时,返回路径 key + const nodeKey = allowDuplicateValue ? node.getNodeMapKey() : node.value; if (valueMode === 'parentFirst' && !checkStrictly) { // valueMode 为 parentFirst // 仅取值父节点 if (!node.parent || !node.parent.isChecked(checkedMap)) { - list.push(node.value); + list.push(nodeKey); } } else if (valueMode === 'onlyLeaf' && !checkStrictly) { // valueMode 为 onlyLeaf // 仅取值叶子节点 if (node.isLeaf()) { - list.push(node.value); + list.push(nodeKey); } } else { // valueMode 为 all // 取值所有选中节点 - list.push(node.value); + list.push(nodeKey); } }); return list; @@ -758,18 +769,20 @@ export class TreeStore { * @return void */ public setChecked(list: TreeNodeValue[]): void { - const { checkStrictly, checkable } = this.config; + const { checkStrictly, checkable, allowDuplicateValue } = this.config; if (!checkable) return; list.forEach((val: TreeNodeValue) => { - const node = this.getNode(val); + const node = allowDuplicateValue ? this.nodeMap.get(val as string) : this.getNode(val); if (!node) return; + const checkedKey = allowDuplicateValue ? node.getNodeMapKey() : val; if (checkStrictly) { - this.checkedMap.set(val, true); + this.checkedMap.set(checkedKey, true); node.updateChecked(); } else { const childrenNodes = node.walk(); childrenNodes.forEach((childNode) => { - this.checkedMap.set(childNode.value, true); + const childCheckedKey = allowDuplicateValue ? childNode.getNodeMapKey() : childNode.value; + this.checkedMap.set(childCheckedKey, true); }); } }); @@ -839,6 +852,7 @@ export class TreeStore { * @return TreeNode[] 关联节点数组 */ public getRelatedNodes(list: TreeNodeValue[], options?: TypeRelatedNodesOptions): TreeNode[] { + const { allowDuplicateValue } = this.config; const conf = { // 默认倒序排列,从底层节点开始遍历 reverse: false, @@ -849,7 +863,7 @@ export class TreeStore { const map = new Map(); list.forEach((value) => { if (map.get(value)) return; - const node = this.getNode(value); + const node = allowDuplicateValue ? this.nodeMap.get(value as string) : this.getNode(value); if (node) { const parents = node.getParents().reverse(); const children = node.walk(); @@ -861,7 +875,8 @@ export class TreeStore { } // 用 map 实现节点去重 related.forEach((relatedNode) => { - map.set(relatedNode.value, relatedNode); + const relatedKey = allowDuplicateValue ? relatedNode.getNodeMapKey() : relatedNode.value; + map.set(relatedKey, relatedNode); }); } }); diff --git a/js/tree-v1/types.ts b/js/tree-v1/types.ts index 4529ddc691..9c74d941bc 100644 --- a/js/tree-v1/types.ts +++ b/js/tree-v1/types.ts @@ -1,5 +1,5 @@ -import { TreeNode } from './tree-node'; import { TreeOptionData } from '../common'; +import { TreeNode } from './tree-node'; // ------ 自动规范类型 start ------- @@ -270,4 +270,6 @@ export interface TypeTreeStoreOptions { onUpdate?: Function; // 是否允许在过滤时折叠节点 allowFoldNodeOnFilter?: Boolean; + // 是否允许不同层级有重复的 value 值 + allowDuplicateValue?: boolean; } From 4caf7b7549f45da935738baafbc4cad0a9e67357 Mon Sep 17 00:00:00 2001 From: Rylan Date: Thu, 5 Feb 2026 23:17:48 +0800 Subject: [PATCH 2/2] chore: simplify logic --- js/tree-v1/tree-node.ts | 94 +++++++++++++++------------------------- js/tree-v1/tree-store.ts | 29 +++++-------- 2 files changed, 46 insertions(+), 77 deletions(-) diff --git a/js/tree-v1/tree-node.ts b/js/tree-v1/tree-node.ts index 9607a5e005..a3b650c781 100644 --- a/js/tree-v1/tree-node.ts +++ b/js/tree-v1/tree-node.ts @@ -237,8 +237,7 @@ export class TreeNode { if (this.isLeaf()) { // initExpanded 时,子节点没有完全加载,无法依赖 isLeaf 状态判断 this.expanded = false; - const expandedKey = config.allowDuplicateValue ? this.getNodeMapKey() : this.value; - this.tree.expandedMap.delete(expandedKey); + this.tree.expandedMap.delete(this.getNodeMapKey()); } // checked 状态依赖于子节点状态 @@ -258,10 +257,10 @@ export class TreeNode { * @return void */ private initChecked(): void { - const { tree, value, parent } = this; + const { tree, parent } = this; const { checkedMap, config } = tree; - const { checkStrictly, allowDuplicateValue } = config; - const checkedKey = allowDuplicateValue ? this.getNodeMapKey() : value; + const { checkStrictly } = config; + const checkedKey = this.getNodeMapKey(); if (this.checked) { checkedMap.set(checkedKey, true); } @@ -282,7 +281,7 @@ export class TreeNode { const { tree } = this; let { expanded } = this; const { config } = tree; - const expandedKey = config.allowDuplicateValue ? this.getNodeMapKey() : this.value; + const expandedKey = this.getNodeMapKey(); if (isNumber(config.expandLevel) && this.getLevel() < config.expandLevel) { tree.expandedMap.set(expandedKey, true); expanded = true; @@ -304,10 +303,8 @@ export class TreeNode { */ private initActived(): void { const { tree, actived } = this; - const { allowDuplicateValue } = tree.config; if (actived && this.isActivable()) { - const activedKey = allowDuplicateValue ? this.getNodeMapKey() : this.value; - tree.activedMap.set(activedKey, true); + tree.activedMap.set(this.getNodeMapKey(), true); } } @@ -348,8 +345,7 @@ export class TreeNode { // 如果之前是叶子节点,现在有了子节点,且 expandAll 为 true,则展开 if (wasLeaf && tree.config.expandAll && !this.isLeaf()) { - const expandedKey = tree.config.allowDuplicateValue ? this.getNodeMapKey() : this.value; - tree.expandedMap.set(expandedKey, true); + tree.expandedMap.set(this.getNodeMapKey(), true); this.expanded = true; } @@ -440,8 +436,7 @@ export class TreeNode { tree.nodeMap.set(nodeKey, node); tree.privateMap.set(node[privateKey], node); if (node.expanded) { - const expandedKey = tree.config.allowDuplicateValue ? nodeKey : node.value; - tree.expandedMap.set(expandedKey, true); + tree.expandedMap.set(nodeKey, true); } }); @@ -534,10 +529,9 @@ export class TreeNode { private clean(): void { const { tree, value } = this; const nodeKey = this.getNodeMapKey(); - const stateKey = tree.config.allowDuplicateValue ? nodeKey : value; - tree.activedMap.delete(stateKey); - tree.checkedMap.delete(stateKey); - tree.expandedMap.delete(stateKey); + tree.activedMap.delete(nodeKey); + tree.checkedMap.delete(nodeKey); + tree.expandedMap.delete(nodeKey); tree.nodeMap.delete(nodeKey); tree.filterMap.delete(value); tree.privateMap.delete(this[privateKey]); @@ -872,12 +866,10 @@ export class TreeNode { * @return boolean 是否被激活 */ public isActived(map?: Map): boolean { - const { tree, value } = this; - const { allowDuplicateValue } = tree.config; + const { tree } = this; const activedMap = map || tree.activedMap; const nodeKey = this.getNodeMapKey(); - const activedKey = allowDuplicateValue ? nodeKey : value; - return !!(tree.nodeMap.get(nodeKey) && activedMap.get(activedKey)); + return !!(tree.nodeMap.get(nodeKey) && activedMap.get(nodeKey)); } /** @@ -886,14 +878,13 @@ export class TreeNode { * @return boolean 是否已展开 */ public isExpanded(map?: Map): boolean { - const { tree, value, vmIsLocked } = this; + const { tree, vmIsLocked } = this; const { hasFilter, config } = tree; - const { allowFoldNodeOnFilter, allowDuplicateValue } = config; + const { allowFoldNodeOnFilter } = config; if (hasFilter && !allowFoldNodeOnFilter && vmIsLocked) return true; const expandedMap = map || tree.expandedMap; const nodeKey = this.getNodeMapKey(); - const expandedKey = allowDuplicateValue ? nodeKey : value; - return !!(tree.nodeMap.get(nodeKey) && expandedMap.get(expandedKey)); + return !!(tree.nodeMap.get(nodeKey) && expandedMap.get(nodeKey)); } /** @@ -902,23 +893,22 @@ export class TreeNode { * @return boolean 是否被选中 */ public isChecked(map?: TypeIdMap): boolean { - const { children, tree, value } = this; - const { checkStrictly, valueMode, allowDuplicateValue } = tree.config; + const { children, tree } = this; + const { checkStrictly, valueMode } = tree.config; const nodeKey = this.getNodeMapKey(); // 节点不在当前树上,视为未选中 if (!tree.nodeMap.get(nodeKey)) return false; // 节点不可选,视为未选中 if (!this.isCheckable()) return false; const checkedMap = map || tree.checkedMap; - const checkedKey = allowDuplicateValue ? nodeKey : value; // 严格模式,则已经可以判定选中状态 if (checkStrictly) { - return !!checkedMap.get(checkedKey); + return !!checkedMap.get(nodeKey); } let checked = false; // 在 checkedMap 中,则根据 valueMode 的值进行判断 if ( - checkedMap.get(checkedKey) && + checkedMap.get(nodeKey) && // 如果 valueMode 为 all、parentFirst,则视为选中 (valueMode !== 'onlyLeaf' || // 如果 valueMode 为 onlyLeaf 并且当前节点是叶子节点,则视为选中 @@ -937,10 +927,7 @@ export class TreeNode { // 从父节点状态推断子节点状态 // 这里再调用 isChecked 会导致死循环 const parents = this.getParents(); - checked = parents.some((node) => { - const parentKey = allowDuplicateValue ? node.getNodeMapKey() : node.value; - return checkedMap.get(parentKey); - }); + checked = parents.some((node) => checkedMap.get(node.getNodeMapKey())); } return checked; } @@ -1094,22 +1081,19 @@ export class TreeNode { // 折叠列表中,先移除同级节点 const siblings = node.getSiblings(); siblings.forEach((snode) => { - const snodeKey = config?.allowDuplicateValue ? snode.getNodeMapKey() : snode.value; - map.delete(snodeKey); + map.delete(snode.getNodeMapKey()); // 同级节点相关状态更新 snode.update(); snode.updateChildren(); }); } // 最后设置自己的折叠状态 - const nodeKey = config?.allowDuplicateValue ? node.getNodeMapKey() : node.value; - map.set(nodeKey, true); + map.set(node.getNodeMapKey(), true); node.update(); node.updateChildren(); }); } else { - const thisKey = config?.allowDuplicateValue ? this.getNodeMapKey() : this.value; - map.delete(thisKey); + map.delete(this.getNodeMapKey()); } if (options.directly) { @@ -1147,8 +1131,7 @@ export class TreeNode { if (!options.directly) { map = new Map(tree.activedMap); } - const { allowDuplicateValue } = config; - const activedKey = allowDuplicateValue ? this.getNodeMapKey() : this.value; + const activedKey = this.getNodeMapKey(); if (this.isActivable()) { if (actived) { const prevKeys = Array.from(map.keys()); @@ -1156,7 +1139,7 @@ export class TreeNode { map.clear(); } prevKeys.forEach((key) => { - const node = allowDuplicateValue ? tree.nodeMap.get(key as string) : tree.getNode(key); + const node = tree.nodeMap.get(key as string); node?.update(); }); map.set(activedKey, true); @@ -1251,7 +1234,7 @@ export class TreeNode { } } - const checkedKey = config.allowDuplicateValue ? this.getNodeMapKey() : this.value; + const checkedKey = this.getNodeMapKey(); if (checked) { map.set(checkedKey, true); } else { @@ -1272,8 +1255,7 @@ export class TreeNode { // 状态更新务必放到扩散动作之后 // 过早的状态更新会导致后续计算出错 if (options.directly) { - const nodeKey = config.allowDuplicateValue ? this.getNodeMapKey() : this.value; - const relatedNodes = tree.getRelatedNodes([nodeKey], { + const relatedNodes = tree.getRelatedNodes([checkedKey], { reverse: true, }); relatedNodes.forEach((node) => { @@ -1323,7 +1305,6 @@ export class TreeNode { // 选中态向上游扩散 private spreadParentChecked(checked: boolean, map?: TypeIdMap, opts?: TypeSettingOptions) { const { tree } = this; - const { allowDuplicateValue } = tree.config; const options: TypeSettingOptions = { isAction: true, directly: false, @@ -1335,8 +1316,7 @@ export class TreeNode { const { children } = this; if (Array.isArray(children) && children.length > 0) { // 有子节点,则选中态由子节点选中态集合来决定 - const checkedKey = allowDuplicateValue ? this.getNodeMapKey() : this.value; - map.delete(checkedKey); + map.delete(this.getNodeMapKey()); } const { parent } = this; @@ -1346,7 +1326,6 @@ export class TreeNode { // 选中态向下游扩散 private spreadChildrenChecked(checked: boolean, map?: TypeIdMap, opts?: TypeSettingOptions) { - const { allowDuplicateValue } = this.tree.config; const options: TypeSettingOptions = { isAction: true, directly: false, @@ -1362,16 +1341,14 @@ export class TreeNode { if (!Array.isArray(children)) return; if (children.length <= 0) return; // 有子节点,则选中态由子节点选中态集合来决定 - const thisCheckedKey = allowDuplicateValue ? this.getNodeMapKey() : this.value; - map.delete(thisCheckedKey); + map.delete(this.getNodeMapKey()); children.forEach((node) => { // 对于 UI 动作,向下扩散时,禁用状态会阻止状态切换 if (options.isAction && node.isDisabled()) return; - const nodeCheckedKey = allowDuplicateValue ? node.getNodeMapKey() : node.value; if (checked) { - map.set(nodeCheckedKey, true); + map.set(node.getNodeMapKey(), true); } else { - map.delete(nodeCheckedKey); + map.delete(node.getNodeMapKey()); } node.spreadChildrenChecked(checked, map, options); }); @@ -1413,17 +1390,16 @@ export class TreeNode { * @return void */ public updateChecked(from?: string): void { - const { tree, value, isIndeterminateManual } = this; + const { tree, isIndeterminateManual } = this; if (isIndeterminateManual && ['refresh'].includes(from)) { return; } - const { checkedMap, config } = tree; + const { checkedMap } = tree; this.checked = this.isChecked(); this.indeterminate = this.isIndeterminate(); if (this.checked) { - const checkedKey = config.allowDuplicateValue ? this.getNodeMapKey() : value; - checkedMap.set(checkedKey, true); + checkedMap.set(this.getNodeMapKey(), true); } tree.updated(this); } diff --git a/js/tree-v1/tree-store.ts b/js/tree-v1/tree-store.ts index ec5d74d3cb..a427c24e18 100644 --- a/js/tree-v1/tree-store.ts +++ b/js/tree-v1/tree-store.ts @@ -598,15 +598,14 @@ export class TreeStore { * @return void */ public setActived(actived: TreeNodeValue[]): void { - const { activeMultiple, allowDuplicateValue } = this.config; + const { activeMultiple } = this.config; const list = actived.slice(0); if (!activeMultiple) { list.length = 1; } list.forEach((val) => { this.activedMap.set(val, true); - // 当 allowDuplicateValue 为 true 时,val 是路径 key,从 nodeMap 获取节点 - const node = allowDuplicateValue ? this.nodeMap.get(val as string) : this.getNode(val); + const node = this.nodeMap.get(val); if (node) { node.update(); } @@ -669,9 +668,8 @@ export class TreeStore { * @return void */ public setExpandedDirectly(list: TreeNodeValue[], expanded = true): void { - const { allowDuplicateValue } = this.config; list.forEach((val) => { - const node = allowDuplicateValue ? this.nodeMap.get(val as string) : this.getNode(val); + const node = this.nodeMap.get(val); if (!node?.isLeaf() && expanded) { this.expandedMap.set(val, true); } else { @@ -713,14 +711,13 @@ export class TreeStore { */ public getChecked(map?: TypeIdMap): TreeNodeValue[] { const { nodeMap, config } = this; - const { valueMode, checkStrictly, allowDuplicateValue } = config; + const { valueMode, checkStrictly } = config; const list: TreeNodeValue[] = []; const checkedMap = map || this.checkedMap; nodeMap.forEach((node) => { // 判断未选中,直接忽略 if (!node.isChecked(checkedMap)) return; - // 当 allowDuplicateValue 为 true 时,返回路径 key - const nodeKey = allowDuplicateValue ? node.getNodeMapKey() : node.value; + const nodeKey = node.getNodeMapKey(); if (valueMode === 'parentFirst' && !checkStrictly) { // valueMode 为 parentFirst // 仅取值父节点 @@ -769,20 +766,18 @@ export class TreeStore { * @return void */ public setChecked(list: TreeNodeValue[]): void { - const { checkStrictly, checkable, allowDuplicateValue } = this.config; + const { checkStrictly, checkable } = this.config; if (!checkable) return; list.forEach((val: TreeNodeValue) => { - const node = allowDuplicateValue ? this.nodeMap.get(val as string) : this.getNode(val); + const node = this.nodeMap.get(val); if (!node) return; - const checkedKey = allowDuplicateValue ? node.getNodeMapKey() : val; if (checkStrictly) { - this.checkedMap.set(checkedKey, true); + this.checkedMap.set(node.getNodeMapKey(), true); node.updateChecked(); } else { const childrenNodes = node.walk(); childrenNodes.forEach((childNode) => { - const childCheckedKey = allowDuplicateValue ? childNode.getNodeMapKey() : childNode.value; - this.checkedMap.set(childCheckedKey, true); + this.checkedMap.set(childNode.getNodeMapKey(), true); }); } }); @@ -852,7 +847,6 @@ export class TreeStore { * @return TreeNode[] 关联节点数组 */ public getRelatedNodes(list: TreeNodeValue[], options?: TypeRelatedNodesOptions): TreeNode[] { - const { allowDuplicateValue } = this.config; const conf = { // 默认倒序排列,从底层节点开始遍历 reverse: false, @@ -863,7 +857,7 @@ export class TreeStore { const map = new Map(); list.forEach((value) => { if (map.get(value)) return; - const node = allowDuplicateValue ? this.nodeMap.get(value as string) : this.getNode(value); + const node = this.nodeMap.get(value); if (node) { const parents = node.getParents().reverse(); const children = node.walk(); @@ -875,8 +869,7 @@ export class TreeStore { } // 用 map 实现节点去重 related.forEach((relatedNode) => { - const relatedKey = allowDuplicateValue ? relatedNode.getNodeMapKey() : relatedNode.value; - map.set(relatedKey, relatedNode); + map.set(relatedNode.getNodeMapKey(), relatedNode); }); } });