import { TreeNodeData } from "@mantine/core" const TREE_PATH_SEPARATOR = " / " /** * 判断一个节点本身是否命中 * 父节点看 label(group_name),叶子看 label(member_name) */ const nodeMatch = (node: TreeNodeData, search: string): boolean => { if (!search) return true // 空搜索默认全过 const text = String(node.label).toLowerCase() return text.includes(search.toLowerCase()) } /* ------------------------------------------------- * 1. DFS 自上而下,匹配即保留,否则剪掉整枝 * ------------------------------------------------- */ export const filterTreeData_dfs = ( treeData: TreeNodeData[], searchName: string ): TreeNodeData[] => { if (!searchName.trim()) return treeData // 空搜索直接返回原树 const loop = (list: TreeNodeData[]): TreeNodeData[] => { const result: TreeNodeData[] = [] for (const n of list) { // 1. 只要节点本身命中,就整枝保留(children 不用再判断) if (nodeMatch(n, searchName)) { result.push({ ...n }) // 浅拷贝一份,避免污染原树 continue } // 2. 本身没命中,再看 children 有没有命中 if (n.children) { const newChildren = loop(n.children) if (newChildren.length) { result.push({ ...n, children: newChildren }) } } } return result } return loop(treeData) } // 辅助:根据 value 查找 label export interface FindResult { path: string // 完整 label 路径,用 / 分隔 node: TreeNodeData | null } export interface TreeValueMeta { path: string node: TreeNodeData } export const buildTreeValueMap = ( nodes: TreeNodeData[], pathArr: string[] = [], acc: Record = {} ): Record => { for (const node of nodes) { const currentPath = [...pathArr, String(node.label)] acc[node.value] = { path: currentPath.join(TREE_PATH_SEPARATOR), node, } if (node.children?.length) { buildTreeValueMap(node.children, currentPath, acc) } } return acc } export const findLabel = ( nodes: TreeNodeData[], targetValue: string, pathArr: string[] = [] // 内部用,调用端无需传入 ): FindResult => { for (const node of nodes) { const curPath = [...pathArr, node.label as string] // 复制一份,避免污染上层 if (node.value === targetValue) { return { path: curPath.join(TREE_PATH_SEPARATOR), node } } if (node.children) { const found = findLabel(node.children, targetValue, curPath) if (found.node) return found } } return { path: "", node: null } } export const findNodesByValues = ( valueMap: Record, targetValues: string[] ): TreeNodeData[] => targetValues.reduce((acc, value) => { const targetNode = valueMap[value]?.node if (targetNode) { acc.push(targetNode) } return acc }, []) export const getLabelsByValues = ( valueMap: Record, targetValues: string[] ): string[] => targetValues.reduce((acc, value) => { const label = valueMap[value]?.path if (label) { acc.push(label) } return acc }, []) const findNodeByValue = ( nodes: TreeNodeData[], targetValue: string ): TreeNodeData | null => { for (const node of nodes) { if (node.value === targetValue) { return node } if (node.children?.length) { const found = findNodeByValue(node.children, targetValue) if (found) { return found } } } return null } const collectLeafValues = ( nodes: TreeNodeData[], acc: string[] = [] ): string[] => { for (const node of nodes) { if (node.children?.length) { collectLeafValues(node.children, acc) } else { acc.push(node.value) } } return acc } export const getDescendantLeafValues = ( nodes: TreeNodeData[], targetValue: string ): string[] => { const targetNode = findNodeByValue(nodes, targetValue) if (!targetNode) { return [] } if (!targetNode.children?.length) { return [targetNode.value] } return collectLeafValues(targetNode.children) } export const getStateFromDataByName = ( data: TreeNodeData[], record: Record = {} ): Record => { let result = data?.reduce((acc, item) => { let child_acc = {} if (item.children && item.children?.length) { acc[item.value] = true child_acc = getStateFromDataByName(item.children, record) } return { ...acc, ...child_acc } }, record) return result } export interface UserTreeData { title?: string value?: string | number children?: UserTreeData[] disabled?: boolean } export function formatUserTreeOptions(nodes: UserTreeData[]): TreeNodeData[] { return nodes.flatMap((node) => { const children = formatUserTreeOptions(node.children ?? []) const title = String(node.title ?? "").trim() const rawValue = node.value const shouldSkipCurrentNode = node.disabled || rawValue === undefined || rawValue === null || !title || (typeof rawValue === "string" && rawValue.includes("-") && children.length === 0) if (shouldSkipCurrentNode) { return children } return [ { label: title, value: String(rawValue), children: children.length > 0 ? children : undefined, }, ] }) }