import React, { useState, useEffect, useRef, useCallback } from 'react'; // 组织路径信息(懒加载接口返回) export interface OrganizationPath { tenant_name: string; dep_name: string; dep_short_name: string; ou_name: string; } // 导入类型 export interface UserInfo { id: number; username: string; nick_name: string; area: string; ou_id: string; ou_name: string; is_leader: boolean; status: number; // 以下字段在搜索接口中有值,在懒加载接口中为 null tenant_name: string | null; dep_name: string | null; dep_short_name: string | null; email?: string; phone_number?: string; // 懒加载接口返回的组织路径信息 organization_path?: OrganizationPath | null; } export interface TreeNodeItem { label: string; value: string; isUser: boolean; hasChildren: boolean; userInfo?: UserInfo; children?: TreeNodeItem[]; // 懒加载状态 isLoading?: boolean; isLoaded?: boolean; } type MultiCascaderProps = { options: TreeNodeItem[]; // 已选择的用户ID列表(用于判断按钮状态) selectedUsers?: string[]; placeholder?: string; maxHeight?: number; searchable?: boolean; searchPlaceholder?: string; // 懒加载回调 onLoadChildren?: (node: TreeNodeItem) => Promise; // 搜索用户回调 onSearchUsers?: (keyword: string) => Promise; // 树数据变化回调(懒加载后同步更新父组件的树数据) onTreeDataChange?: (treeData: TreeNodeItem[]) => void; // 用户从树中添加时的回调 onAddUser?: (userNode: TreeNodeItem) => void; // 用户从搜索结果添加时的回调 onSearchUserAdded?: (userNode: TreeNodeItem) => void; }; const MultiCascader: React.FC = ({ options, selectedUsers = [], placeholder = '请选择', maxHeight = 300, searchable = false, searchPlaceholder = '搜索...', onLoadChildren, onSearchUsers, onTreeDataChange, onAddUser, onSearchUserAdded }) => { const [visible, setVisible] = useState(false); const [expandedKeys, setExpandedKeys] = useState>(new Set()); const [searchKeyword, setSearchKeyword] = useState(''); const [searchResults, setSearchResults] = useState([]); const [searchLoading, setSearchLoading] = useState(false); const [localOptions, setLocalOptions] = useState(options); const containerRef = useRef(null); const searchInputRef = useRef(null); const loadingNodesRef = useRef>(new Set()); const prevOptionsRef = useRef(options); // 同步外部 options 变化 useEffect(() => { setLocalOptions(options); }, [options]); // 同步树数据变化到父组件(懒加载后触发) useEffect(() => { // 只有当 localOptions 真正变化时才调用回调,避免无限循环 if (onTreeDataChange && localOptions !== prevOptionsRef.current) { prevOptionsRef.current = localOptions; onTreeDataChange(localOptions); } }, [localOptions, onTreeDataChange]); // 点击外部关闭 useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (containerRef.current && !containerRef.current.contains(event.target as Node)) { setVisible(false); setSearchKeyword(''); setSearchResults([]); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); // 下拉框打开时聚焦搜索框 useEffect(() => { if (visible && searchable && searchInputRef.current) { setTimeout(() => searchInputRef.current?.focus(), 100); } if (!visible) { setSearchKeyword(''); setSearchResults([]); } }, [visible, searchable]); // 搜索用户 const handleSearch = useCallback(async (keyword: string) => { setSearchKeyword(keyword); if (!keyword.trim() || !onSearchUsers) { setSearchResults([]); return; } setSearchLoading(true); try { const results = await onSearchUsers(keyword); setSearchResults(results); } catch (error) { console.error('搜索用户失败:', error); setSearchResults([]); } finally { setSearchLoading(false); } }, [onSearchUsers]); // 防抖搜索 useEffect(() => { const timer = setTimeout(() => { if (searchKeyword && searchable && onSearchUsers) { handleSearch(searchKeyword); } }, 300); return () => clearTimeout(timer); }, [searchKeyword, searchable, onSearchUsers, handleSearch]); // 懒加载子节点 const handleExpand = async (node: TreeNodeItem) => { // 如果已经加载过(有子组织或用户),则直接展开/折叠,不调用API if (node.isLoaded) { setExpandedKeys(prev => { const newSet = new Set(prev); if (newSet.has(node.value)) { newSet.delete(node.value); // 折叠 } else { newSet.add(node.value); // 展开 } return newSet; }); return; } // 正在加载中,直接展开已加载的部分 if (loadingNodesRef.current.has(node.value)) { setExpandedKeys(prev => { const newSet = new Set(prev); newSet.add(node.value); return newSet; }); return; } // 标记为加载中 loadingNodesRef.current.add(node.value); setLocalOptions(prev => updateNodeLoading(prev, node.value, true)); try { if (onLoadChildren) { const children = await onLoadChildren(node); // 更新本地选项 setLocalOptions(prev => updateNodeChildren(prev, node.value, children, true)); } // 展开节点 setExpandedKeys(prev => { const newSet = new Set(prev); newSet.add(node.value); return newSet; }); } catch (error) { console.error('加载子节点失败:', error); } finally { loadingNodesRef.current.delete(node.value); setLocalOptions(prev => updateNodeLoading(prev, node.value, false)); } }; // 更新节点加载状态 const updateNodeLoading = (nodes: TreeNodeItem[], value: string, isLoading: boolean): TreeNodeItem[] => { return nodes.map(node => { if (node.value === value) { return { ...node, isLoading }; } if (node.children) { return { ...node, children: updateNodeLoading(node.children, value, isLoading) }; } return node; }); }; // 更新节点的子节点 const updateNodeChildren = ( nodes: TreeNodeItem[], value: string, children: TreeNodeItem[], isLoaded: boolean ): TreeNodeItem[] => { return nodes.map(node => { if (node.value === value) { return { ...node, children, isLoaded, hasChildren: children.length > 0 }; } if (node.children) { return { ...node, children: updateNodeChildren(node.children, value, children, isLoaded) }; } return node; }); }; // 检查用户是否已添加 const isUserAdded = (value: string): boolean => { return selectedUsers.includes(value); }; // 从树中添加用户 const handleAddUser = (userNode: TreeNodeItem) => { if (isUserAdded(userNode.value)) { return; // 已添加,不处理 } onAddUser?.(userNode); }; // 从搜索结果添加用户 const handleAddSearchUser = (userNode: TreeNodeItem) => { if (isUserAdded(userNode.value)) { return; // 已添加,不处理 } onSearchUserAdded?.(userNode); }; // 渲染组织/用户节点 const renderNode = (node: TreeNodeItem, level: number = 0, parentKey: string = ''): React.ReactNode => { const hasChildren = node.hasChildren; const isExpanded = expandedKeys.has(node.value); const isLoading = node.isLoading; const isLoaded = node.isLoaded; const isAdded = isUserAdded(node.value); const nodeKey = parentKey ? `${parentKey}-${node.value}` : node.value; return (
0 ? "ml-4" : ""}> {/* 组织节点:点击整行可展开/收缩 */} {hasChildren ? (
handleExpand(node)} > {/* 展开/折叠图标 */} {isLoading ? ( ) : isExpanded ? ( ) : ( )} {/* 节点标签 */} {node.label}
) : ( /* 用户节点 */
{/* 用户节点:显示添加按钮 */} {node.label} {node.isUser && ( )}
)} {/* 渲染子节点 - 只有当有实际子节点时才渲染 */} {node.children && node.children.length > 0 && isExpanded && (
{node.children.map(child => renderNode(child, level + 1, nodeKey))}
)}
); }; // 渲染搜索结果 const renderSearchResults = () => { if (searchLoading) { return (
搜索中...
); } if (searchResults.length === 0) { return (
无匹配结果
); } return (
找到 {searchResults.length} 个用户
{searchResults.map(user => (
{user.userInfo?.nick_name || user.label}
{/* 优先使用 organization_path 中的值(懒加载接口),为空则使用顶层字段(搜索接口) */} {user.userInfo?.organization_path?.tenant_name || user.userInfo?.tenant_name} · {user.userInfo?.ou_name}
))}
); }; // 显示文本 const getDisplayText = () => { if (selectedUsers.length === 0) return placeholder; return `已选择 ${selectedUsers.length} 位成员`; }; return (
{/* 触发器 */}
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setVisible(!visible); } }} onClick={() => setVisible(!visible)} > {getDisplayText()}
{/* 下拉面板 */} {visible && (
{/* 搜索框 */} {searchable && (
handleSearch(e.target.value)} onClick={(e) => e.stopPropagation()} /> {searchKeyword && ( )}
)} {/* 内容区域 */}
{searchKeyword && searchable ? ( renderSearchResults() ) : localOptions.length > 0 ? ( localOptions.map(option => renderNode(option)) ) : (
暂无数据
)}
)}
); }; export default MultiCascader;