Procházet zdrojové kódy

✨ feat(knowledge): add knowledge category management components and hooks

- add KnowledgeCategoryTreeSelect component for category selection
- add useCategoryData hook for category data management
- add useKnowledgeCategoriesQuery hook with React Query integration
- implement CRUD operations for knowledge categories
- add category tree building and path finding utilities

✨ refactor(knowledge): optimize knowledge category page

- replace direct API calls with React Query hooks
- use new KnowledgeCategoryTreeSelect component
- simplify state management with React Query cache
- add loading states and error handling
- improve pagination and search functionality
yourname před 7 měsíci
rodič
revize
3dadb0e15f

+ 185 - 0
src/client/admin/components/KnowledgeCategoryTreeSelect.tsx

@@ -0,0 +1,185 @@
+import React from 'react';
+import { TreeSelect } from 'antd';
+import type { DefaultOptionType } from 'antd/es/select';
+import { useAllKnowledgeCategories, buildCategoryTree } from '@/client/admin/hooks/useKnowledgeCategoriesQuery';
+import type { InferResponseType } from 'hono/client';
+import { silverUsersClient } from '@/client/api';
+
+// 定义类型
+type KnowledgeCategory = InferResponseType<typeof silverUsersClient['knowledge-categories']['$get'], 200>['data'][0];
+
+interface KnowledgeCategoryTreeSelectProps {
+  value?: number | null;
+  onChange?: (value: number | null) => void;
+  placeholder?: string;
+  allowClear?: boolean;
+  treeDefaultExpandAll?: boolean;
+  disabled?: boolean;
+  multiple?: boolean;
+  maxTagCount?: number;
+  style?: React.CSSProperties;
+  className?: string;
+  showRoot?: boolean;
+  rootLabel?: string;
+}
+
+interface TreeNode extends DefaultOptionType {
+  title: string;
+  value: number;
+  disabled?: boolean;
+  children?: TreeNode[];
+}
+
+const KnowledgeCategoryTreeSelect: React.FC<KnowledgeCategoryTreeSelectProps> = ({
+  value,
+  onChange,
+  placeholder = '请选择知识分类',
+  allowClear = true,
+  treeDefaultExpandAll = true,
+  disabled = false,
+  multiple = false,
+  maxTagCount,
+  style,
+  className,
+  showRoot = false,
+  rootLabel = '全部分类',
+}) => {
+  const { data: categories = [], isLoading } = useAllKnowledgeCategories(true);
+
+  // 构建树形数据
+  const buildTreeData = (): TreeNode[] => {
+    const tree = buildCategoryTree(categories, null)
+      .map(item => ({
+        title: item.name,
+        value: item.id,
+        key: item.id.toString(),
+        children: item.children?.map(child => ({
+          title: child.name,
+          value: child.id,
+          key: child.id.toString(),
+          children: child.children?.map(grandchild => ({
+            title: grandchild.name,
+            value: grandchild.id,
+            key: grandchild.id.toString(),
+            disabled: !grandchild.isActive,
+          })),
+          disabled: !child.isActive,
+        })),
+        disabled: !item.isActive,
+      }));
+
+    if (showRoot && !multiple) {
+      return [
+        {
+          title: rootLabel,
+          value: 0,
+          key: 'root',
+          children: tree
+        }
+      ];
+    }
+    
+    return tree;
+  };
+
+  const treeData = React.useMemo(() => buildTreeData(), [categories, showRoot, rootLabel, multiple]);
+
+  // 处理值变化
+  const handleChange = (newValue: any) => {
+    if (onChange) {
+      if (newValue === 0) {
+        onChange(null);
+      } else {
+        onChange(newValue);
+      }
+    }
+  };
+
+  return (
+    <TreeSelect
+      style={style}
+      className={className}
+      value={value}
+      onChange={handleChange}
+      treeData={treeData}
+      placeholder={placeholder}
+      loading={isLoading}
+      allowClear={allowClear}
+      treeDefaultExpandAll={treeDefaultExpandAll}
+      disabled={disabled}
+      multiple={multiple}
+      maxTagCount={maxTagCount}
+      treeLine={{ showLeafIcon: false }}
+      fieldNames={{ label: 'title', value: 'value', children: 'children' }}
+      showSearch
+      treeNodeFilterProp="title"
+    />
+  );
+};
+
+// 自定义Hook:用于获取和管理知识分类(保持向后兼容)
+export const useKnowledgeCategories = () => {
+  const { data: categories = [], isLoading, refetch } = useAllKnowledgeCategories();
+
+  // 构建分类树(平铺结构转树形)
+  const buildCategoryTree = React.useCallback((items: KnowledgeCategory[], parentId: number | null = null): KnowledgeCategory[] => {
+    return items
+      .filter(item => item.parentId === parentId)
+      .map(item => ({
+        ...item,
+        children: buildCategoryTree(items, item.id)
+      }));
+  }, []);
+
+  // 获取分类路径
+  const getCategoryPath = React.useCallback((categoryId: number, allCategories: KnowledgeCategory[]): string[] => {
+    const path: string[] = [];
+    const findPath = (id: number) => {
+      const category = allCategories.find(cat => cat.id === id);
+      if (category) {
+        if (category.parentId) {
+          findPath(category.parentId);
+        }
+        path.push(category.name);
+      }
+    };
+    
+    findPath(categoryId);
+    return path;
+  }, []);
+
+  // 获取分类名称
+  const getCategoryName = React.useCallback((categoryId: number | null | undefined): string => {
+    if (!categoryId) return '';
+    const category = categories.find(cat => cat.id === categoryId);
+    return category?.name || '';
+  }, [categories]);
+
+  // 获取所有子分类ID(包含自身)
+  const getAllChildrenIds = React.useCallback((categoryId: number): number[] => {
+    const ids: number[] = [categoryId];
+    const findChildren = (parentId: number) => {
+      categories.forEach(cat => {
+        if (cat.parentId === parentId) {
+          ids.push(cat.id);
+          findChildren(cat.id);
+        }
+      });
+    };
+    
+    findChildren(categoryId);
+    return ids;
+  }, [categories]);
+
+  return {
+    categories,
+    loading: isLoading,
+    fetchCategories: refetch,
+    buildCategoryTree,
+    getCategoryPath,
+    getCategoryName,
+    getAllChildrenIds,
+  };
+};
+
+export default KnowledgeCategoryTreeSelect;

+ 112 - 0
src/client/admin/hooks/useCategoryData.ts

@@ -0,0 +1,112 @@
+import { useState, useEffect, useCallback } from 'react';
+import { silverUsersClient } from '@/client/api';
+import type { InferResponseType } from 'hono/client';
+
+type KnowledgeCategory = InferResponseType<typeof silverUsersClient['knowledge-categories']['$get'], 200>['data'][0];
+
+interface UseCategoryDataReturn {
+  categories: KnowledgeCategory[];
+  loading: boolean;
+  fetchCategories: (showAll?: boolean) => Promise<KnowledgeCategory[]>;
+  buildCategoryTree: (items: KnowledgeCategory[], parentId?: number | null) => KnowledgeCategory[];
+  getCategoryPath: (categoryId: number, allCategories?: KnowledgeCategory[]) => string[];
+  getCategoryName: (categoryId: number | null | undefined) => string;
+  getAllChildrenIds: (categoryId: number) => number[];
+  refresh: () => void;
+}
+
+export const useCategoryData = (): UseCategoryDataReturn => {
+  const [categories, setCategories] = useState<KnowledgeCategory[]>([]);
+  const [loading, setLoading] = useState(false);
+
+  const fetchCategories = useCallback(async (showAll: boolean = false): Promise<KnowledgeCategory[]> => {
+    setLoading(true);
+    try {
+      const response = await silverUsersClient['knowledge-categories'].$get({
+        query: { 
+          page: 1, 
+          pageSize: 1000,
+          ...(showAll ? {} : { isActive: 1 })
+        }
+      });
+      const result = await response.json();
+      setCategories(result.data);
+      return result.data;
+    } catch (error) {
+      console.error('获取知识分类失败:', error);
+      return [];
+    } finally {
+      setLoading(false);
+    }
+  }, []);
+
+  const refresh = useCallback(() => {
+    fetchCategories();
+  }, [fetchCategories]);
+
+  // 构建分类树(平铺结构转树形)
+  const buildCategoryTree = (items: KnowledgeCategory[], parentId: number | null = null): KnowledgeCategory[] => {
+    return items
+      .filter(item => item.parentId === parentId)
+      .map(item => ({
+        ...item,
+        children: buildCategoryTree(items, item.id)
+      }));
+  };
+
+  // 获取分类路径
+  const getCategoryPath = (categoryId: number, allCategories: KnowledgeCategory[] = categories): string[] => {
+    const path: string[] = [];
+    const findPath = (id: number) => {
+      const category = allCategories.find(cat => cat.id === id);
+      if (category) {
+        if (category.parentId) {
+          findPath(category.parentId);
+        }
+        path.push(category.name);
+      }
+    };
+    
+    findPath(categoryId);
+    return path;
+  };
+
+  // 获取分类名称
+  const getCategoryName = (categoryId: number | null | undefined): string => {
+    if (!categoryId) return '';
+    const category = categories.find(cat => cat.id === categoryId);
+    return category?.name || '';
+  };
+
+  // 获取所有子分类ID(包含自身)
+  const getAllChildrenIds = (categoryId: number): number[] => {
+    const ids: number[] = [categoryId];
+    const findChildren = (parentId: number) => {
+      categories.forEach(cat => {
+        if (cat.parentId === parentId) {
+          ids.push(cat.id);
+          findChildren(cat.id);
+        }
+      });
+    };
+    
+    findChildren(categoryId);
+    return ids;
+  };
+
+  // 初始化数据
+  useEffect(() => {
+    fetchCategories();
+  }, [fetchCategories]);
+
+  return {
+    categories,
+    loading,
+    fetchCategories,
+    buildCategoryTree,
+    getCategoryPath,
+    getCategoryName,
+    getAllChildrenIds,
+    refresh,
+  };
+};

+ 245 - 0
src/client/admin/hooks/useKnowledgeCategoriesQuery.ts

@@ -0,0 +1,245 @@
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { message } from 'antd';
+import { silverUsersClient } from '@/client/api';
+import type { InferResponseType, InferRequestType } from 'hono/client';
+
+// 定义类型
+type KnowledgeCategory = InferResponseType<typeof silverUsersClient['knowledge-categories']['$get'], 200>['data'][0];
+type CreateCategoryRequest = InferRequestType<typeof silverUsersClient['knowledge-categories']['$post']>['json'];
+type UpdateCategoryRequest = InferRequestType<typeof silverUsersClient['knowledge-categories'][':id']['$put']>['json'];
+
+// 错误响应类型
+interface ErrorResponse {
+  code: number;
+  message: string;
+  errors?: Record<string, string[]>;
+}
+
+// Query keys
+export const KNOWLEDGE_CATEGORIES_KEYS = {
+  all: ['knowledge-categories'] as const,
+  lists: () => [...KNOWLEDGE_CATEGORIES_KEYS.all, 'list'] as const,
+  list: (params: Record<string, any>) => [...KNOWLEDGE_CATEGORIES_KEYS.lists(), params] as const,
+  details: () => [...KNOWLEDGE_CATEGORIES_KEYS.all, 'detail'] as const,
+  detail: (id: number) => [...KNOWLEDGE_CATEGORIES_KEYS.details(), id] as const,
+  tree: () => [...KNOWLEDGE_CATEGORIES_KEYS.all, 'tree'] as const,
+};
+
+// 获取知识分类列表
+export const useKnowledgeCategories = (params?: {
+  page?: number;
+  pageSize?: number;
+  keyword?: string;
+  isActive?: number;
+}) => {
+  return useQuery({
+    queryKey: KNOWLEDGE_CATEGORIES_KEYS.list(params || {}),
+    queryFn: async () => {
+      const response = await silverUsersClient['knowledge-categories'].$get({
+        query: {
+          page: params?.page || 1,
+          pageSize: params?.pageSize || 10,
+          ...(params?.keyword && { keyword: params.keyword }),
+          ...(params?.isActive !== undefined && { isActive: params.isActive }),
+        },
+      });
+      
+      if (!response.ok) {
+        throw new Error('获取知识分类失败');
+      }
+      
+      return response.json();
+    },
+    staleTime: 5 * 60 * 1000, // 5分钟
+    gcTime: 10 * 60 * 1000, // 10分钟
+  });
+};
+
+// 获取所有知识分类(用于树形选择)
+export const useAllKnowledgeCategories = (showAll: boolean = false) => {
+  return useQuery({
+    queryKey: KNOWLEDGE_CATEGORIES_KEYS.list({ showAll }),
+    queryFn: async () => {
+      const response = await silverUsersClient['knowledge-categories'].$get({
+        query: { 
+          page: 1, 
+          pageSize: 1000,
+          ...(showAll ? {} : { isActive: 1 })
+        }
+      });
+      
+      if (!response.ok) {
+        throw new Error('获取知识分类失败');
+      }
+      
+      const result = await response.json();
+      return result.data;
+    },
+    staleTime: 5 * 60 * 1000,
+    gcTime: 10 * 60 * 1000,
+  });
+};
+
+// 创建知识分类
+export const useCreateKnowledgeCategory = () => {
+  const queryClient = useQueryClient();
+  
+  return useMutation({
+    mutationFn: async (data: CreateCategoryRequest) => {
+      const response = await silverUsersClient['knowledge-categories'].$post({
+        json: data,
+      });
+      
+      if (!response.ok) {
+        const error = await response.json();
+        throw new Error(error.message || '创建知识分类失败');
+      }
+      
+      return response.json();
+    },
+    onSuccess: () => {
+      message.success('创建成功');
+      // 刷新相关缓存
+      queryClient.invalidateQueries({ queryKey: KNOWLEDGE_CATEGORIES_KEYS.lists() });
+      queryClient.invalidateQueries({ queryKey: KNOWLEDGE_CATEGORIES_KEYS.tree() });
+    },
+    onError: (error) => {
+      const errorMessage = error instanceof Error ? error.message : '创建失败';
+      message.error(errorMessage);
+    },
+  });
+};
+
+// 更新知识分类
+export const useUpdateKnowledgeCategory = () => {
+  const queryClient = useQueryClient();
+  
+  return useMutation({
+    mutationFn: async ({ id, data }: { id: number; data: UpdateCategoryRequest }) => {
+      const response = await silverUsersClient['knowledge-categories'][':id']['$put']({
+        param: { id: id.toString() },
+        json: data,
+      });
+      
+      if (!response.ok) {
+        const error = await response.json();
+        throw new Error(error.message || '更新知识分类失败');
+      }
+      
+      return response.json();
+    },
+    onSuccess: () => {
+      message.success('更新成功');
+      // 刷新相关缓存
+      queryClient.invalidateQueries({ queryKey: KNOWLEDGE_CATEGORIES_KEYS.lists() });
+      queryClient.invalidateQueries({ queryKey: KNOWLEDGE_CATEGORIES_KEYS.tree() });
+    },
+    onError: (error) => {
+      const errorMessage = error instanceof Error ? error.message : '更新失败';
+      message.error(errorMessage);
+    },
+  });
+};
+
+// 删除知识分类
+export const useDeleteKnowledgeCategory = () => {
+  const queryClient = useQueryClient();
+  
+  return useMutation({
+    mutationFn: async (id: number) => {
+      const response = await silverUsersClient['knowledge-categories'][':id']['$delete']({
+        param: { id: id.toString() },
+      });
+      
+      if (!response.ok) {
+        const error = await response.json();
+        throw new Error(error.message || '删除知识分类失败');
+      }
+      
+      return response.json();
+    },
+    onSuccess: () => {
+      message.success('删除成功');
+      // 刷新相关缓存
+      queryClient.invalidateQueries({ queryKey: KNOWLEDGE_CATEGORIES_KEYS.lists() });
+      queryClient.invalidateQueries({ queryKey: KNOWLEDGE_CATEGORIES_KEYS.tree() });
+    },
+    onError: (error) => {
+      const errorMessage = error instanceof Error ? error.message : '删除失败';
+      message.error(errorMessage);
+    },
+  });
+};
+
+// 切换知识分类状态
+export const useToggleKnowledgeCategoryStatus = () => {
+  const queryClient = useQueryClient();
+  
+  return useMutation({
+    mutationFn: async ({ id, isActive }: { id: number; isActive: boolean }) => {
+      const response = await silverUsersClient['knowledge-categories'][':id']['$put']({
+        param: { id: id.toString() },
+        json: { isActive: isActive ? 1 : 0 } as UpdateCategoryRequest,
+      });
+      
+      if (!response.ok) {
+        const error = await response.json();
+        throw new Error(error.message || '更新状态失败');
+      }
+      
+      return response.json();
+    },
+    onSuccess: () => {
+      // 刷新相关缓存
+      queryClient.invalidateQueries({ queryKey: KNOWLEDGE_CATEGORIES_KEYS.lists() });
+      queryClient.invalidateQueries({ queryKey: KNOWLEDGE_CATEGORIES_KEYS.tree() });
+    },
+    onError: (error) => {
+      const errorMessage = error instanceof Error ? error.message : '更新状态失败';
+      message.error(errorMessage);
+    },
+  });
+};
+
+// 工具函数:构建分类树
+export const buildCategoryTree = (items: KnowledgeCategory[], parentId: number | null = null): KnowledgeCategory[] => {
+  return items
+    .filter(item => item.parentId === parentId)
+    .map(item => ({
+      ...item,
+      children: buildCategoryTree(items, item.id)
+    }));
+};
+
+// 工具函数:获取分类路径
+export const getCategoryPath = (categoryId: number, allCategories: KnowledgeCategory[]): string[] => {
+  const path: string[] = [];
+  const findPath = (id: number) => {
+    const category = allCategories.find(cat => cat.id === id);
+    if (category) {
+      if (category.parentId) {
+        findPath(category.parentId);
+      }
+      path.push(category.name);
+    }
+  };
+  
+  findPath(categoryId);
+  return path;
+};
+
+// 工具函数:获取所有子分类ID
+export const getAllChildrenIds = (categoryId: number, categories: KnowledgeCategory[]): number[] => {
+  const ids: number[] = [categoryId];
+  const findChildren = (parentId: number) => {
+    categories.forEach(cat => {
+      if (cat.parentId === parentId) {
+        ids.push(cat.id);
+        findChildren(cat.id);
+      }
+    });
+  };
+  
+  findChildren(categoryId);
+  return ids;
+};

+ 65 - 92
src/client/admin/pages/KnowledgeCategories.tsx

@@ -1,63 +1,51 @@
-import React, { useState, useEffect } from 'react';
-import { Card, Table, Button, Modal, Form, Input, Select, message, Space, Switch, Popconfirm, TreeSelect } from 'antd';
+import React, { useState } from 'react';
+import { Card, Table, Button, Modal, Form, Input, message, Space, Switch, Popconfirm } from 'antd';
 import { PlusOutlined, EditOutlined, DeleteOutlined, FolderOutlined } from '@ant-design/icons';
 import type { ColumnsType } from 'antd/es/table';
-import type { InferResponseType, InferRequestType } from 'hono/client';
+import type { InferResponseType } from 'hono/client';
 import { silverUsersClient } from '@/client/api';
+import KnowledgeCategoryTreeSelect from '@/client/admin/components/KnowledgeCategoryTreeSelect';
+import {
+  useKnowledgeCategories,
+  useCreateKnowledgeCategory,
+  useUpdateKnowledgeCategory,
+  useDeleteKnowledgeCategory,
+  useToggleKnowledgeCategoryStatus,
+} from '@/client/admin/hooks/useKnowledgeCategoriesQuery';
 
 const { TextArea } = Input;
 
 type KnowledgeCategory = InferResponseType<typeof silverUsersClient['knowledge-categories']['$get'], 200>['data'][0];
-type CreateCategoryRequest = InferRequestType<typeof silverUsersClient['knowledge-categories']['$post']>['json'];
-type UpdateCategoryRequest = InferRequestType<typeof silverUsersClient['knowledge-categories'][':id']['$put']>['json'];
 
 const KnowledgeCategories: React.FC = () => {
-  const [data, setData] = useState<KnowledgeCategory[]>([]);
-  const [loading, setLoading] = useState(false);
   const [modalVisible, setModalVisible] = useState(false);
   const [modalType, setModalType] = useState<'create' | 'edit'>('create');
   const [currentRecord, setCurrentRecord] = useState<KnowledgeCategory | null>(null);
   const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });
   const [form] = Form.useForm();
   const [searchText, setSearchText] = useState('');
-  const [categories, setCategories] = useState<KnowledgeCategory[]>([]);
 
-  const fetchData = async (page = 1, pageSize = 10) => {
-    setLoading(true);
-    try {
-      const response = await silverUsersClient['knowledge-categories'].$get({
-        query: { page, pageSize, keyword: searchText || undefined }
-      });
-      const result = await response.json();
-      setData(result.data);
-      setPagination({
-        current: page,
-        pageSize,
-        total: result.pagination.total
-      });
-    } catch (error) {
-      message.error('获取数据失败');
-    } finally {
-      setLoading(false);
-    }
-  };
+  // React Query hooks
+  const { data: categoriesData, isLoading, refetch } = useKnowledgeCategories({
+    page: pagination.current,
+    pageSize: pagination.pageSize,
+    keyword: searchText,
+  });
 
-  useEffect(() => {
-    fetchData();
-    loadCategories();
-  }, []);
+  const createMutation = useCreateKnowledgeCategory();
+  const updateMutation = useUpdateKnowledgeCategory();
+  const deleteMutation = useDeleteKnowledgeCategory();
+  const toggleStatusMutation = useToggleKnowledgeCategoryStatus();
 
-  const loadCategories = async () => {
-    try {
-      const response = await silverUsersClient['knowledge-categories'].$get({
-        query: { page: 1, pageSize: 100 }
-      });
-      const result = await response.json();
-      setCategories(result.data);
-    } catch (error) {
-      message.error('加载分类失败');
+  // 更新分页信息
+  React.useEffect(() => {
+    if (categoriesData?.pagination) {
+      setPagination(prev => ({
+        ...prev,
+        total: categoriesData.pagination.total,
+      }));
     }
-  };
+  }, [categoriesData]);
 
   const handleCreate = () => {
     setModalType('create');
@@ -77,15 +65,7 @@ const KnowledgeCategories: React.FC = () => {
   };
 
   const handleDelete = async (id: number) => {
-    try {
-      await silverUsersClient['knowledge-categories'][':id']['$delete']({
-        param: { id: id.toString() }
-      });
-      message.success('删除成功');
-      fetchData(pagination.current, pagination.pageSize);
-    } catch (error) {
-      message.error('删除失败');
-    }
+    deleteMutation.mutate(id);
   };
 
   const handleSubmit = async () => {
@@ -93,57 +73,51 @@ const KnowledgeCategories: React.FC = () => {
       const values = await form.validateFields();
       
       if (modalType === 'create') {
-        await silverUsersClient['knowledge-categories'].$post({
-          json: values as CreateCategoryRequest
-        });
-        message.success('创建成功');
-      } else {
-        await silverUsersClient['knowledge-categories'][':id']['$put']({
-          param: { id: currentRecord!.id.toString() },
-          json: values as UpdateCategoryRequest
-        });
-        message.success('更新成功');
+        createMutation.mutate(values);
+      } else if (currentRecord) {
+        updateMutation.mutate({ id: currentRecord.id, data: values });
       }
       
       setModalVisible(false);
-      fetchData(pagination.current, pagination.pageSize);
-      loadCategories(); // 重新加载分类树
     } catch (error) {
-      message.error(modalType === 'create' ? '创建失败' : '更新失败');
+      console.error('表单验证失败:', error);
     }
   };
 
-  const handleTableChange = (pagination: any) => {
-    fetchData(pagination.current, pagination.pageSize);
+  const handleTableChange = (newPagination: any) => {
+    setPagination(prev => ({
+      ...prev,
+      current: newPagination.current,
+      pageSize: newPagination.pageSize,
+    }));
   };
 
   const handleSearch = () => {
-    fetchData(1, pagination.pageSize);
+    setPagination(prev => ({ ...prev, current: 1 }));
+    refetch();
   };
 
-  const handleStatusChange = async (id: number, isActive: boolean) => {
-    try {
-      await silverUsersClient['knowledge-categories'][':id']['$put']({
-        param: { id: id.toString() },
-        json: { isActive: isActive ? 1 : 0 } as UpdateCategoryRequest
-      });
-      message.success('状态更新成功');
-      fetchData(pagination.current, pagination.pageSize);
-    } catch (error) {
-      message.error('状态更新失败');
-    }
+  const handleStatusChange = (id: number, isActive: boolean) => {
+    toggleStatusMutation.mutate({ id, isActive });
   };
 
-  // 构建分类树结构
-  const buildCategoryTree = (categories: KnowledgeCategory[], parentId: number | null = null): any[] => {
-    return categories
-      .filter(cat => cat.parentId === parentId)
-      .map(cat => ({
-        title: cat.name,
-        value: cat.id,
-        children: buildCategoryTree(categories, cat.id)
-      }));
-  };
+  // 监听mutation成功,刷新数据
+  React.useEffect(() => {
+    if (
+      createMutation.isSuccess ||
+      updateMutation.isSuccess ||
+      deleteMutation.isSuccess ||
+      toggleStatusMutation.isSuccess
+    ) {
+      refetch();
+    }
+  }, [
+    createMutation.isSuccess,
+    updateMutation.isSuccess,
+    deleteMutation.isSuccess,
+    toggleStatusMutation.isSuccess,
+    refetch,
+  ]);
 
   const columns: ColumnsType<KnowledgeCategory> = [
     {
@@ -267,9 +241,9 @@ const KnowledgeCategories: React.FC = () => {
         
         <Table
           columns={columns}
-          dataSource={data}
+          dataSource={categoriesData?.data || []}
           rowKey="id"
-          loading={loading}
+          loading={isLoading}
           pagination={{
             ...pagination,
             showSizeChanger: true,
@@ -306,11 +280,10 @@ const KnowledgeCategories: React.FC = () => {
             label="父分类"
             name="parentId"
           >
-            <TreeSelect
+            <KnowledgeCategoryTreeSelect
               placeholder="请选择父分类(不选则为顶级分类)"
-              treeData={buildCategoryTree(categories)}
               allowClear
-              treeDefaultExpandAll
+              showRoot={false}
             />
           </Form.Item>