Просмотр исходного кода

✨ feat(admin): 实现角色权限配置功能

- 新增PermissionConfigModal组件,用于角色权限配置
- 重构Roles页面,将权限配置逻辑迁移到独立组件
- 优化权限数据加载和处理逻辑,提升用户体验

♻️ refactor(admin): 代码结构优化

- 移除Roles页面中重复的权限相关逻辑
- 调整权限数据获取方式,使用React Query进行状态管理
- 优化组件拆分,提高代码复用性和可维护性
yourname 8 месяцев назад
Родитель
Сommit
6b00f80e7f
2 измененных файлов с 270 добавлено и 170 удалено
  1. 223 0
      src/client/admin/components/PermissionConfigModal.tsx
  2. 47 170
      src/client/admin/pages/Roles.tsx

+ 223 - 0
src/client/admin/components/PermissionConfigModal.tsx

@@ -0,0 +1,223 @@
+import React, { useEffect, useState, useCallback } from 'react';
+import { Modal, Tree, Spin, message } from 'antd';
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { permissionClient, rolePermissionClient } from '@/client/api';
+import type { InferResponseType } from 'hono/client';
+
+type PermissionListResponse = InferResponseType<typeof permissionClient.$get, 200>;
+type PermissionItem = PermissionListResponse['data'][0];
+
+interface PermissionConfigModalProps {
+  visible: boolean;
+  roleId: number | null;
+  roleName?: string;
+  onClose: () => void;
+  onSuccess?: () => void;
+}
+
+interface TreeNode {
+  title: string;
+  key: string;
+  id?: number;
+  children?: TreeNode[];
+  isLeaf?: boolean;
+  selectable?: boolean;
+  disabled?: boolean;
+}
+
+const PermissionConfigModal: React.FC<PermissionConfigModalProps> = ({
+  visible,
+  roleId,
+  roleName,
+  onClose,
+  onSuccess,
+}) => {
+  const queryClient = useQueryClient();
+  const [selectedPermissionIds, setSelectedPermissionIds] = useState<number[]>([]);
+  const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
+  const [treeData, setTreeData] = useState<TreeNode[]>([]);
+  const [autoExpandParent, setAutoExpandParent] = useState(true);
+
+  // 获取所有权限(分组加载)
+  const { data: permissions = [], isLoading: permissionsLoading } = useQuery({
+    queryKey: ['permissions'],
+    queryFn: async () => {
+      const response = await permissionClient.$get({ 
+        query: { page: 1, pageSize: 1000 } 
+      });
+      if (!response.ok) throw new Error('获取权限列表失败');
+      const data = await response.json();
+      return data.data;
+    },
+    enabled: visible,
+  });
+
+  // 获取角色权限
+  const { data: rolePermissions = [], isLoading: rolePermissionsLoading } = useQuery({
+    queryKey: ['role-permissions', roleId],
+    queryFn: async () => {
+      if (!roleId) return [];
+      const response = await rolePermissionClient.$get({
+        query: { filters: JSON.stringify({ roleId }) }
+      });
+      if (!response.ok) throw new Error('获取角色权限失败');
+      const data = await response.json();
+      return data.data.map((item: any) => item.permissionId);
+    },
+    enabled: visible && !!roleId,
+  });
+
+  // 更新角色权限
+  const updateRolePermissionsMutation = useMutation({
+    mutationFn: async ({ roleId, permissionIds }: { roleId: number; permissionIds: number[] }) => {
+      const permissions = permissionIds.map(permissionId => ({
+        permissionId,
+        dataScopeType: 'COMPANY' as const,
+        customDepartments: [] as number[]
+      }));
+
+      const response = await rolePermissionClient.batch.$post({
+        json: { roleId, permissions }
+      });
+      if (!response.ok) throw new Error('更新角色权限失败');
+      return response.json();
+    },
+    onSuccess: () => {
+      message.success('角色权限更新成功');
+      onSuccess?.();
+    },
+    onError: (error: Error) => {
+      message.error(error.message);
+    },
+  });
+
+  // 构建权限树数据(按模块分组)
+  const buildPermissionTree = useCallback((permissions: PermissionItem[]): TreeNode[] => {
+    const grouped: Record<string, PermissionItem[]> = {};
+    
+    // 按模块分组
+    permissions.forEach(permission => {
+      const module = permission.module || '其他';
+      if (!grouped[module]) {
+        grouped[module] = [];
+      }
+      grouped[module].push(permission);
+    });
+
+    // 构建树结构
+    return Object.entries(grouped).map(([module, items]) => ({
+      title: module,
+      key: `module_${module}`,
+      id: undefined,
+      isLeaf: false,
+      selectable: false,
+      disabled: false,
+      children: items.map(item => ({
+        title: `${item.name} (${item.code})`,
+        key: `permission_${item.id}`,
+        id: item.id,
+        isLeaf: true,
+        selectable: true,
+        disabled: false,
+      })),
+    }));
+  }, []);
+
+  // 处理权限树构建
+  useEffect(() => {
+    if (visible && permissions.length > 0) {
+      const tree = buildPermissionTree(permissions);
+      setTreeData(tree);
+    }
+  }, [visible, permissions, buildPermissionTree]);
+
+  // 处理选中状态
+  useEffect(() => {
+    if (visible && rolePermissions.length > 0) {
+      setSelectedPermissionIds(rolePermissions);
+      
+      // 自动展开包含已选权限的模块
+      const expandedModuleKeys: string[] = [];
+      const tree = buildPermissionTree(permissions);
+      
+      tree.forEach(moduleNode => {
+        moduleNode.children?.forEach(permissionNode => {
+          if (rolePermissions.includes(permissionNode.id!)) {
+            expandedModuleKeys.push(moduleNode.key);
+          }
+        });
+      });
+      setExpandedKeys(expandedModuleKeys);
+    } else if (!visible) {
+      setSelectedPermissionIds([]);
+      setExpandedKeys([]);
+    }
+  }, [visible, rolePermissions, permissions, buildPermissionTree]);
+
+  const handleSavePermissions = () => {
+    if (!roleId) return;
+    
+    updateRolePermissionsMutation.mutate(
+      { roleId, permissionIds: selectedPermissionIds },
+      {
+        onSuccess: () => {
+          onClose();
+        }
+      }
+    );
+  };
+
+  const onCheck = (checkedKeysValue: any) => {
+    const checked = Array.isArray(checkedKeysValue) ? checkedKeysValue : checkedKeysValue.checked;
+    const permissionKeys = checked.filter((key: string) => key.startsWith('permission_'));
+    setSelectedPermissionIds(permissionKeys.map((key: string) => 
+      parseInt(key.replace('permission_', ''))
+    ));
+  };
+
+  const handleExpand = (keys: string[]) => {
+    setExpandedKeys(keys);
+    setAutoExpandParent(false);
+  };
+
+  const isLoading = permissionsLoading || rolePermissionsLoading;
+
+  return (
+    <Modal
+      title={`配置权限 - ${roleName}`}
+      open={visible}
+      onCancel={onClose}
+      onOk={handleSavePermissions}
+      okButtonProps={{ 
+        loading: updateRolePermissionsMutation.isPending,
+        disabled: !roleId || isLoading
+      }}
+      width={800}
+      destroyOnClose
+      okText="保存"
+      cancelText="取消"
+    >
+      <Spin spinning={isLoading}>
+        <Tree
+          checkable
+          treeData={treeData}
+          checkedKeys={selectedPermissionIds.map(id => `permission_${id}`)}
+          onCheck={onCheck}
+          onExpand={handleExpand}
+          expandedKeys={expandedKeys}
+          autoExpandParent={autoExpandParent}
+          height={400}
+          titleRender={(node) => {
+            if (node.key.startsWith('module_')) {
+              return <strong style={{ color: '#1890ff' }}>{node.title}</strong>;
+            }
+            return <span>{node.title}</span>;
+          }}
+          checkStrictly={false}
+        />
+      </Spin>
+    </Modal>
+  );
+};
+
+export default PermissionConfigModal;

+ 47 - 170
src/client/admin/pages/Roles.tsx

@@ -1,8 +1,9 @@
 import React, { useState, useEffect } from 'react';
 import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
-import { Card, Table, Button, Modal, Form, Input, Space, Tag, message, Tree, Spin } from 'antd';
+import { Card, Table, Button, Modal, Form, Input, Space, Tag, message } from 'antd';
 import { EditOutlined, DeleteOutlined, PlusOutlined, KeyOutlined } from '@ant-design/icons';
-import { roleClient, permissionClient, rolePermissionClient } from '@/client/api';
+import { roleClient, rolePermissionClient } from '@/client/api';
+import PermissionConfigModal from '@/client/admin/components/PermissionConfigModal';
 import type { InferResponseType, InferRequestType } from 'hono/client';
 import dayjs from 'dayjs';
 
@@ -13,9 +14,6 @@ type RoleItem = RoleListResponse['data'][0];
 type CreateRoleRequest = InferRequestType<typeof roleClient.$post>['json'];
 type UpdateRoleRequest = InferRequestType<typeof roleClient[':id']['$put']>['json'];
 
-type PermissionListResponse = InferResponseType<typeof permissionClient.$get, 200>;
-type PermissionItem = PermissionListResponse['data'][0];
-
 // React Query hooks
 const useRoles = (params?: { page?: number; pageSize?: number; keyword?: string }) => {
   return useQuery({
@@ -34,34 +32,6 @@ const useRoles = (params?: { page?: number; pageSize?: number; keyword?: string
   });
 };
 
-const usePermissions = () => {
-  return useQuery({
-    queryKey: ['permissions'],
-    queryFn: async () => {
-      const response = await permissionClient.$get({ query: {} });
-      if (!response.ok) throw new Error('获取权限列表失败');
-      const data = await response.json();
-      return data.data;
-    },
-  });
-};
-
-const useRolePermissions = (roleId: number | null) => {
-  return useQuery({
-    queryKey: ['role-permissions', roleId],
-    queryFn: async () => {
-      if (!roleId) return [];
-      const response = await rolePermissionClient.$get({
-        query: { filters: JSON.stringify({ roleId }) }
-      });
-      if (!response.ok) throw new Error('获取角色权限失败');
-      const data = await response.json();
-      return data.data.map((item: any) => item.permissionId);
-    },
-    enabled: !!roleId,
-  });
-};
-
 const useCreateRole = () => {
   const queryClient = useQueryClient();
   return useMutation({
@@ -123,57 +93,6 @@ const useDeleteRole = () => {
   });
 };
 
-const useUpdateRolePermissions = () => {
-  const queryClient = useQueryClient();
-  return useMutation({
-    mutationFn: async ({ roleId, permissionIds }: { roleId: number; permissionIds: number[] }) => {
-      const permissions = permissionIds.map(permissionId => ({
-        permissionId,
-        dataScopeType: 'COMPANY' as const,
-        customDepartments: [] as number[]
-      }));
-
-      const response = await rolePermissionClient.batch.$post({
-        json: { roleId, permissions }
-      });
-      if (!response.ok) throw new Error('更新角色权限失败');
-      return response.json();
-    },
-    onSuccess: () => {
-      message.success('角色权限更新成功');
-    },
-    onError: (error: Error) => {
-      message.error(error.message);
-    },
-  });
-};
-
-const Roles: React.FC = () => {
-const queryClient = useQueryClient();
-const [modalVisible, setModalVisible] = useState(false);
-const [permissionModalVisible, setPermissionModalVisible] = useState(false);
-const [editingRole, setEditingRole] = useState<RoleItem | null>(null);
-const [selectedRole, setSelectedRole] = useState<RoleItem | null>(null);
-const [form] = Form.useForm();
-const [selectedPermissionIds, setSelectedPermissionIds] = useState<number[]>([]);
-const [searchKeyword, setSearchKeyword] = useState('');
-const [pagination, setPagination] = useState({
-  current: 1,
-  pageSize: 10,
-  total: 0
-});
-
-// React Query hooks
-const { data: rolesData, isLoading: rolesLoading } = useRoles({
-  page: pagination.current,
-  pageSize: pagination.pageSize,
-  keyword: searchKeyword
-});
-const roles = rolesData?.data || [];
-const { data: permissions = [], isLoading: permissionsLoading } = usePermissions();
-const { data: rolePermissions = [], isLoading: rolePermissionsLoading } = useRolePermissions(selectedRole?.id ?? null);
-
-// 单独查询每个角色的权限数量
 const useRolePermissionCount = (roleId: number) => {
   return useQuery({
     queryKey: ['role-permission-count', roleId],
@@ -189,23 +108,44 @@ const useRolePermissionCount = (roleId: number) => {
   });
 };
 
-// 更新分页信息
-React.useEffect(() => {
-  if (rolesData?.pagination) {
-    setPagination(prev => ({
-      ...prev,
-      current: rolesData.pagination.current,
-      pageSize: rolesData.pagination.pageSize,
-      total: rolesData.pagination.total
-    }));
-  }
-}, [rolesData?.pagination]);
+const Roles: React.FC = () => {
+  const queryClient = useQueryClient();
+  const [modalVisible, setModalVisible] = useState(false);
+  const [permissionModalVisible, setPermissionModalVisible] = useState(false);
+  const [editingRole, setEditingRole] = useState<RoleItem | null>(null);
+  const [selectedRole, setSelectedRole] = useState<RoleItem | null>(null);
+  const [form] = Form.useForm();
+  const [searchKeyword, setSearchKeyword] = useState('');
+  const [pagination, setPagination] = useState({
+    current: 1,
+    pageSize: 10,
+    total: 0
+  });
+
+  // React Query hooks
+  const { data: rolesData, isLoading: rolesLoading } = useRoles({
+    page: pagination.current,
+    pageSize: pagination.pageSize,
+    keyword: searchKeyword
+  });
+  const roles = rolesData?.data || [];
+
+  // 更新分页信息
+  useEffect(() => {
+    if (rolesData?.pagination) {
+      setPagination(prev => ({
+        ...prev,
+        current: rolesData.pagination.current,
+        pageSize: rolesData.pagination.pageSize,
+        total: rolesData.pagination.total
+      }));
+    }
+  }, [rolesData?.pagination]);
 
   // Mutations
   const createRoleMutation = useCreateRole();
   const updateRoleMutation = useUpdateRole();
   const deleteRoleMutation = useDeleteRole();
-  const updateRolePermissionsMutation = useUpdateRolePermissions();
 
   // 打开编辑模态框
   const handleEdit = (record: RoleItem) => {
@@ -227,7 +167,6 @@ React.useEffect(() => {
   // 打开权限配置模态框
   const handlePermissionConfig = (role: RoleItem) => {
     setSelectedRole(role);
-    setSelectedPermissionIds(rolePermissions);
     setPermissionModalVisible(true);
   };
 
@@ -273,50 +212,6 @@ React.useEffect(() => {
     setPagination(prev => ({ ...prev, current: 1 }));
   };
 
-  // 保存角色权限
-  const handleSavePermissions = () => {
-    if (!selectedRole) return;
-    
-    updateRolePermissionsMutation.mutate(
-      { roleId: selectedRole.id, permissionIds: selectedPermissionIds },
-      {
-        onSuccess: () => {
-          setPermissionModalVisible(false);
-          queryClient.invalidateQueries({ queryKey: ['roles'] });
-        }
-      }
-    );
-  };
-
-  // 权限数据按模块分组
-  const permissionTreeData = React.useMemo(() => {
-    const grouped: Record<string, PermissionItem[]> = {};
-    permissions.forEach(permission => {
-      const module = permission.module || '其他';
-      if (!grouped[module]) {
-        grouped[module] = [];
-      }
-      grouped[module].push(permission);
-    });
-
-    return Object.entries(grouped).map(([module, items]) => ({
-      title: module,
-      key: module,
-      checkable: false,
-      children: items.map(item => ({
-        title: `${item.name} (${item.code})`,
-        key: item.id.toString(),
-      })),
-    }));
-  }, [permissions]);
-
-  // 在权限模态框打开时更新选中状态
-  useEffect(() => {
-    if (permissionModalVisible && rolePermissions.length > 0) {
-      setSelectedPermissionIds(rolePermissions);
-    }
-  }, [permissionModalVisible, rolePermissions]);
-
   const columns = [
     {
       title: '角色名称',
@@ -401,7 +296,7 @@ React.useEffect(() => {
 
   return (
   <div className="p-6">
-    <Spin spinning={rolesLoading || permissionsLoading}>
+    <Spin spinning={rolesLoading}>
       <Card
         title="角色管理"
         extra={
@@ -493,34 +388,16 @@ React.useEffect(() => {
       </Modal>
 
       {/* 权限配置模态框 */}
-      <Modal
-        title={`配置权限 - ${selectedRole?.name}`}
-        open={permissionModalVisible}
-        onCancel={() => setPermissionModalVisible(false)}
-        onOk={handleSavePermissions}
-        okButtonProps={{ loading: updateRolePermissionsMutation.isPending }}
-        width={800}
-        destroyOnClose
-      >
-        <Spin spinning={rolePermissionsLoading}>
-          <Tree
-            checkable
-            treeData={permissionTreeData}
-            checkedKeys={selectedPermissionIds.map(String)}
-            onCheck={(checkedKeys) => {
-              const checked = checkedKeys as string[];
-              setSelectedPermissionIds(checked.map(Number));
-            }}
-            height={400}
-            titleRender={(node) => {
-              if (node.children) {
-                return <strong>{node.title}</strong>;
-              }
-              return <span>{node.title}</span>;
-            }}
-          />
-        </Spin>
-      </Modal>
+      <PermissionConfigModal
+        visible={permissionModalVisible}
+        roleId={selectedRole?.id || null}
+        roleName={selectedRole?.name}
+        onClose={() => setPermissionModalVisible(false)}
+        onSuccess={() => {
+          message.success('权限配置成功');
+          queryClient.invalidateQueries({ queryKey: ['roles'] });
+        }}
+      />
     </div>
   );
 };