import React, { useState, useMemo, useRef } from 'react'; import './Tree.css'; /** * Aurora Admin Tree(React) * 对齐 组件2.txt Tree 规范:节点高32px、缩进16px/级、箭头12px、选中 #F0F5FF/#2F54EB、复选框级联 * nodes: [{ key, title, children, disabled }] * 受控:selectedKeys / checkedKeys(均为数组),通过 onSelect / onCheck 回调更新 */ function findNode(key, nodes) { for (const n of nodes) { if (n.key === key) return n; if (n.children) { const f = findNode(key, n.children); if (f) return f; } } return null; } function descendantKeys(node) { let out = []; (node.children || []).forEach((c) => { out.push(c.key); out = out.concat(descendantKeys(c)); }); return out; } function allChildrenChecked(node, set) { if (!node.children || !node.children.length) return set.has(node.key); return node.children.every((c) => allChildrenChecked(c, set)); } export default function Tree({ data = [], checkable = false, defaultExpandedKeys = [], selectedKeys = [], checkedKeys = [], onSelect, onCheck }) { const [expanded, setExpanded] = useState(() => new Set(defaultExpandedKeys)); const [selected, setSelected] = useState(() => new Set(selectedKeys)); const [checked, setChecked] = useState(() => new Set(checkedKeys)); const visibleList = useMemo(() => { const out = []; const walk = (nodes, depth) => { nodes.forEach((n) => { out.push({ node: n, depth }); if (n.children && n.children.length && expanded.has(n.key)) walk(n.children, depth + 1); }); }; walk(data, 0); return out; }, [data, expanded]); const hasChildren = (n) => !!(n.children && n.children.length); const isIndeterminate = (key) => { const node = findNode(key, data); if (!node || !hasChildren(node)) return false; const desc = descendantKeys(node); return desc.some((d) => checked.has(d)) && !allChildrenChecked(node, checked); }; const toggleExpand = (key) => setExpanded((prev) => { const n = new Set(prev); n.has(key) ? n.delete(key) : n.add(key); return n; }); const onRowClick = (node) => { setSelected(new Set([node.key])); onSelect && onSelect(node.key); }; const toggleCheck = (key) => { const node = findNode(key, data); const willCheck = !checked.has(key); const keys = [key, ...descendantKeys(node)]; const next = new Set(checked); keys.forEach((k) => (willCheck ? next.add(k) : next.delete(k))); const propagate = (nodes) => { nodes.forEach((n) => { if (hasChildren(n)) { if (allChildrenChecked(n, next)) next.add(n.key); else next.delete(n.key); propagate(n.children); } }); }; propagate(data); setChecked(next); onCheck && onCheck([...next]); }; return (