|
|
@@ -0,0 +1,158 @@
|
|
|
+import React, { useState, useEffect } from 'react';
|
|
|
+import { TreeSelect, Spin } from 'antd';
|
|
|
+import { departmentsClient } from '@/client/api';
|
|
|
+import { useQuery } from '@tanstack/react-query';
|
|
|
+import debug from 'debug';
|
|
|
+
|
|
|
+const logger = debug('frontend:admin:department-tree-select');
|
|
|
+
|
|
|
+interface LazyDepartmentTreeSelectProps {
|
|
|
+ value?: number;
|
|
|
+ onChange?: (value: number | undefined) => void;
|
|
|
+ placeholder?: string;
|
|
|
+ allowClear?: boolean;
|
|
|
+ disabled?: boolean;
|
|
|
+ showRoot?: boolean;
|
|
|
+ rootLabel?: string;
|
|
|
+ style?: React.CSSProperties;
|
|
|
+ className?: string;
|
|
|
+}
|
|
|
+
|
|
|
+interface DepartmentNode {
|
|
|
+ title: string;
|
|
|
+ value: number;
|
|
|
+ key: string;
|
|
|
+ children?: DepartmentNode[];
|
|
|
+ isLeaf?: boolean;
|
|
|
+}
|
|
|
+
|
|
|
+const LazyDepartmentTreeSelect: React.FC<LazyDepartmentTreeSelectProps> = ({
|
|
|
+ value,
|
|
|
+ onChange,
|
|
|
+ placeholder = '请选择部门',
|
|
|
+ allowClear = true,
|
|
|
+ disabled = false,
|
|
|
+ showRoot = false,
|
|
|
+ rootLabel = '全部',
|
|
|
+ style,
|
|
|
+ className,
|
|
|
+}) => {
|
|
|
+ const [treeData, setTreeData] = useState<DepartmentNode[]>([]);
|
|
|
+ const [loadedKeys, setLoadedKeys] = useState<string[]>([]);
|
|
|
+
|
|
|
+ // 查询部门数据
|
|
|
+ const { data: departmentsData, isLoading } = useQuery({
|
|
|
+ queryKey: ['departments-tree'],
|
|
|
+ queryFn: async () => {
|
|
|
+ const response = await departmentsClient.$get({ query: { page: 1, pageSize: 100 } });
|
|
|
+ if (response.status !== 200) throw new Error('获取部门列表失败');
|
|
|
+ return response.json();
|
|
|
+ },
|
|
|
+ staleTime: 5 * 60 * 1000, // 5分钟缓存
|
|
|
+ });
|
|
|
+
|
|
|
+ // 构建部门树
|
|
|
+ const buildDepartmentTree = (departments: any[], parentId?: number): DepartmentNode[] => {
|
|
|
+ return departments
|
|
|
+ .filter(dept => dept.parentId === parentId)
|
|
|
+ .map(dept => ({
|
|
|
+ title: dept.name,
|
|
|
+ value: dept.id,
|
|
|
+ key: dept.id.toString(),
|
|
|
+ children: buildDepartmentTree(departments, dept.id),
|
|
|
+ isLeaf: !departments.some(d => d.parentId === dept.id),
|
|
|
+ }));
|
|
|
+ };
|
|
|
+
|
|
|
+ // 初始化树数据
|
|
|
+ useEffect(() => {
|
|
|
+ if (departmentsData?.data) {
|
|
|
+ const departments = departmentsData.data;
|
|
|
+ const tree = buildDepartmentTree(departments);
|
|
|
+
|
|
|
+ // 如果需要显示根节点
|
|
|
+ if (showRoot) {
|
|
|
+ setTreeData([
|
|
|
+ {
|
|
|
+ title: rootLabel,
|
|
|
+ value: 0,
|
|
|
+ key: '0',
|
|
|
+ children: tree,
|
|
|
+ isLeaf: tree.length === 0,
|
|
|
+ },
|
|
|
+ ]);
|
|
|
+ } else {
|
|
|
+ setTreeData(tree);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, [departmentsData, showRoot, rootLabel]);
|
|
|
+
|
|
|
+ // 处理懒加载
|
|
|
+ const handleLoadData = async (node: any) => {
|
|
|
+ try {
|
|
|
+ const parentId = parseInt(node.key);
|
|
|
+ const response = await departmentsClient.$get({
|
|
|
+ query: { page: 1, pageSize: 100 }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ const children = buildDepartmentTree(data.data, parentId);
|
|
|
+
|
|
|
+ // 更新树数据
|
|
|
+ setTreeData(prevTreeData => updateTreeData(prevTreeData, node.key, children));
|
|
|
+ setLoadedKeys(prev => [...prev, node.key]);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ logger('加载部门数据失败:', error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 更新树数据
|
|
|
+ const updateTreeData = (list: DepartmentNode[], key: string, children: DepartmentNode[]): DepartmentNode[] => {
|
|
|
+ return list.map(node => {
|
|
|
+ if (node.key === key) {
|
|
|
+ return {
|
|
|
+ ...node,
|
|
|
+ children,
|
|
|
+ isLeaf: children.length === 0,
|
|
|
+ };
|
|
|
+ }
|
|
|
+ if (node.children) {
|
|
|
+ return {
|
|
|
+ ...node,
|
|
|
+ children: updateTreeData(node.children, key, children),
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return node;
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理值变化
|
|
|
+ const handleChange = (newValue: number | undefined) => {
|
|
|
+ if (onChange) {
|
|
|
+ onChange(newValue === 0 ? undefined : newValue);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <TreeSelect
|
|
|
+ showSearch
|
|
|
+ style={style}
|
|
|
+ className={className}
|
|
|
+ value={value}
|
|
|
+ dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
|
|
+ placeholder={placeholder}
|
|
|
+ allowClear={allowClear}
|
|
|
+ treeDefaultExpandAll
|
|
|
+ treeData={treeData}
|
|
|
+ disabled={disabled || isLoading}
|
|
|
+ onChange={handleChange}
|
|
|
+ loading={isLoading}
|
|
|
+ notFoundContent={isLoading ? <Spin size="small" /> : '暂无数据'}
|
|
|
+ treeNodeFilterProp="title"
|
|
|
+ />
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default LazyDepartmentTreeSelect;
|