Explorar el Código

✨ feat(admin): 添加部门树选择组件

- 实现部门树的懒加载功能,优化大数据量下的性能
- 支持显示根节点选项,可自定义根节点标签
- 添加缓存机制,减少重复请求
- 支持禁用、清除、自定义样式等多种属性配置
- 适配部门层级结构,提供直观的部门选择体验
yourname hace 8 meses
padre
commit
84e92624b1
Se han modificado 1 ficheros con 158 adiciones y 0 borrados
  1. 158 0
      src/client/admin/components/LazyDepartmentTreeSelect.tsx

+ 158 - 0
src/client/admin/components/LazyDepartmentTreeSelect.tsx

@@ -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;