/** * 部门树形下拉选择器组件 * 用于在表单中选择部门,支持树形结构展示 */ 'use client' import { useState, useRef, useEffect } from 'react' interface Department { id: string name: string parentId?: string | null code?: string | null children?: Department[] } interface DepartmentTreeSelectProps { departments: Department[] value: string onChange: (value: string) => void placeholder?: string className?: string allowEmpty?: boolean emptyLabel?: string } export default function DepartmentTreeSelect({ departments, value, onChange, placeholder = '请选择部门', className = '', allowEmpty = true, emptyLabel = '未分配', }: DepartmentTreeSelectProps) { const [isOpen, setIsOpen] = useState(false) const [expandedIds, setExpandedIds] = useState>(new Set()) const wrapperRef = useRef(null) // 点击外部关闭下拉框 useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (wrapperRef.current && !wrapperRef.current.contains(event.target as Node)) { setIsOpen(false) } } if (isOpen) { document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) } }, [isOpen]) // 构建部门树 const buildTree = (depts: Department[]): Department[] => { const deptMap = new Map() const rootDepts: Department[] = [] // 创建映射 depts.forEach(dept => { deptMap.set(dept.id, { ...dept, children: [] }) }) // 构建树 depts.forEach(dept => { const node = deptMap.get(dept.id)! if (dept.parentId && deptMap.has(dept.parentId)) { const parent = deptMap.get(dept.parentId)! if (!parent.children) parent.children = [] parent.children.push(node) } else { rootDepts.push(node) } }) return rootDepts } const treeData = buildTree(departments) // 获取选中的部门名称 const getSelectedName = (): string => { if (!value) return emptyLabel const dept = departments.find(d => d.id === value || d.name === value) return dept ? dept.name : (value || emptyLabel) } // 获取当前值的部门ID(用于匹配) const getValueId = (): string => { if (!value) return '' const dept = departments.find(d => d.id === value || d.name === value) return dept ? dept.id : value } // 切换展开/收起 const toggleExpand = (deptId: string, e: React.MouseEvent) => { e.stopPropagation() setExpandedIds(prev => { const newSet = new Set(prev) if (newSet.has(deptId)) { newSet.delete(deptId) } else { newSet.add(deptId) } return newSet }) } // 选择部门 const handleSelect = (deptId: string, deptName: string) => { onChange(deptId) setIsOpen(false) } // 递归渲染部门树 const renderDepartmentTree = (depts: Department[], level: number = 0): React.ReactNode => { const currentValueId = getValueId() return depts.map(dept => { const hasChildren = dept.children && dept.children.length > 0 const isExpanded = expandedIds.has(dept.id) const isSelected = currentValueId === dept.id return (
handleSelect(dept.id, dept.name)} > {/* 展开/折叠按钮 */} {hasChildren ? (
toggleExpand(dept.id, e)} >
) : (
)} {/* 部门图标 */} {/* 部门名称 */} {dept.name} {/* 选中标记 */} {isSelected && ( )}
{/* 子部门 */} {hasChildren && isExpanded && (
{renderDepartmentTree(dept.children!, level + 1)}
)}
) }) } return (
{/* 输入框 */} {/* 下拉列表 */} {isOpen && (
{allowEmpty && (
handleSelect('', '')} >
{emptyLabel} {!value && ( )}
)} {treeData.length > 0 ? ( renderDepartmentTree(treeData) ) : (
暂无部门
)}
)}
) }