fix(daily): fix multiselect

This commit is contained in:
zhangheng
2026-05-11 18:28:26 +08:00
parent 332d6ab5f1
commit a1565dc18e
8 changed files with 1090 additions and 86 deletions

View File

@@ -0,0 +1,225 @@
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<string, TreeValueMeta> = {}
): Record<string, TreeValueMeta> => {
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<string, TreeValueMeta>,
targetValues: string[]
): TreeNodeData[] =>
targetValues.reduce<TreeNodeData[]>((acc, value) => {
const targetNode = valueMap[value]?.node
if (targetNode) {
acc.push(targetNode)
}
return acc
}, [])
export const getLabelsByValues = (
valueMap: Record<string, TreeValueMeta>,
targetValues: string[]
): string[] =>
targetValues.reduce<string[]>((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<string, any> = {}
): Record<string, any> => {
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,
},
]
})
}