diff --git a/frontend/.cursorrules b/frontend/.cursorrules index 0da2b2c8..0178c52b 100644 --- a/frontend/.cursorrules +++ b/frontend/.cursorrules @@ -38,10 +38,11 @@ src/ ├── utils/ # 工具函数 ├── hooks/ # 自定义 Hooks └── types/ # TS 类型定义 +│ └── pages.ts # 分页基础类 ModuleName/ # 模块结构 ├── components/ # 模块私有组件 -├── types/ # 类型定义 +├── type.ts # 类型定义 ├── service.ts # API 服务 └── index.tsx # 模块入口 ``` @@ -64,7 +65,7 @@ ModuleName/ # 模块结构 - 事件处理:`handle` 前缀 - 异步函数:动词开头(`fetchData`) -3. 基础类型: +3. 已定义了基础类型,所以直接继承即可,把Response query request 的定义都模块的types.ts文件中: ```typescript interface BaseResponse { id: number; diff --git a/frontend/src/pages/System/Role/components/PermissionModal.tsx b/frontend/src/pages/System/Role/components/PermissionModal.tsx index 94a5a68e..2bb1a23a 100644 --- a/frontend/src/pages/System/Role/components/PermissionModal.tsx +++ b/frontend/src/pages/System/Role/components/PermissionModal.tsx @@ -1,127 +1,153 @@ import React, { useEffect, useState } from 'react'; -import { Modal, Table, message, Spin, Tag } from 'antd'; -import type { Permission } from '../types'; -import { PermissionType, PermissionTypeText } from '../types'; -import { getRolePermissions, assignPermissions, getAllPermissions } from '../service'; +import { Modal, Tree, Spin } from 'antd'; +import type { DataNode } from 'antd/es/tree'; +import type { Key } from 'rc-tree/lib/interface'; +import { getPermissionTree } from '../service'; interface PermissionModalProps { - roleId: number; visible: boolean; + roleId: number; onCancel: () => void; - onSuccess: () => void; + onOk: (permissionIds: number[]) => void; + defaultCheckedKeys?: number[]; +} + +interface Permission { + id: number; + code: string; + name: string; + type: string; + sort: number; +} + +interface MenuItem { + id: number; + name: string; + path: string; + icon: string; + type: number; + permissionChildren: MenuItem[]; + permissions: Permission[]; } const PermissionModal: React.FC = ({ - roleId, visible, + roleId, onCancel, - onSuccess + onOk, + defaultCheckedKeys = [] }) => { - const [permissions, setPermissions] = useState([]); - const [selectedKeys, setSelectedKeys] = useState([]); const [loading, setLoading] = useState(false); + const [treeData, setTreeData] = useState([]); + const [checkedKeys, setCheckedKeys] = useState(defaultCheckedKeys); - // 获取角色已有权限和所有权限 - const fetchData = async () => { - try { - setLoading(true); - const [allPermissions, rolePermissions] = await Promise.all([ - getAllPermissions(), - getRolePermissions(roleId) - ]); - setPermissions(allPermissions); - setSelectedKeys(rolePermissions.map(p => p.id)); - } catch (error) { - message.error('获取权限数据失败'); - } finally { - setLoading(false); - } + // 将菜单和权限数据转换为Tree组件需要的格式 + const convertToTreeData = (menuList: MenuItem[]): { treeData: DataNode[], idMapping: Record } => { + const idMapping: Record = {}; + + const convertMenuToNode = (menu: MenuItem): DataNode => { + const node: DataNode = { + key: `menu-${menu.id}`, + title: menu.name, + children: [] + }; + + // 添加功能权限节点 + if (menu.permissions?.length > 0) { + const permissionNodes = menu.permissions.map(perm => { + const key = `permission-${perm.id}`; + idMapping[key] = perm.id; + return { + key, + title: perm.name, + isLeaf: true + }; + }); + node.children!.push(...permissionNodes); + } + + // 递归处理子菜单 + if (menu.permissionChildren?.length > 0) { + const childNodes = menu.permissionChildren.map(convertMenuToNode); + node.children!.push(...childNodes); + } + + return node; + }; + + const treeData = menuList.map(convertMenuToNode); + return { treeData, idMapping }; }; useEffect(() => { - if (visible) { - fetchData(); - } - }, [visible, roleId]); - - const handleOk = async () => { - try { + const loadPermissionTree = async () => { + if (!visible) return; + setLoading(true); - await assignPermissions(roleId, selectedKeys); - message.success('权限更新成功'); - onSuccess(); - } catch (error) { - message.error('权限更新失败'); - } finally { - setLoading(false); - } - }; - - const getPermissionTypeTag = (type: PermissionType) => { - const colorMap = { - [PermissionType.MENU]: 'blue', - [PermissionType.BUTTON]: 'green', - [PermissionType.API]: 'orange' + try { + const response = await getPermissionTree(); + const { treeData, idMapping } = convertToTreeData(response); + setTreeData(treeData); + + // 将默认选中的ID转换为Tree需要的key + const defaultKeys = defaultCheckedKeys.map(id => { + const key = Object.entries(idMapping).find(([_, value]) => value === id)?.[0]; + return key; + }).filter(Boolean); + + setCheckedKeys(defaultCheckedKeys); + } catch (error) { + console.error('获取权限树失败:', error); + } finally { + setLoading(false); + } }; - return ( - - {PermissionTypeText[type]} - - ); + + loadPermissionTree(); + }, [visible, defaultCheckedKeys]); + + const handleCheck = (checked: Key[] | { checked: Key[]; halfChecked: Key[] }) => { + const checkedKeys = Array.isArray(checked) ? checked : checked.checked; + const permissionIds: number[] = []; + + checkedKeys.forEach(key => { + const keyStr = key.toString(); + if (keyStr.startsWith('permission-')) { + const id = parseInt(keyStr.replace('permission-', '')); + if (!isNaN(id)) { + permissionIds.push(id); + } + } + }); + + setCheckedKeys(permissionIds); }; - const columns = [ - { - title: '权限编码', - dataIndex: 'code', - width: 150 - }, - { - title: '权限名称', - dataIndex: 'name', - width: 150 - }, - { - title: '权限类型', - dataIndex: 'type', - width: 120, - render: (type: PermissionType) => getPermissionTypeTag(type) - }, - { - title: '所属菜单', - dataIndex: 'menuName', - width: 150 - }, - { - title: '描述', - dataIndex: 'description', - ellipsis: true - } - ]; + const handleOk = () => { + onOk(checkedKeys); + }; + + // 将权限ID转换为Tree需要的key + const getTreeCheckedKeys = (): string[] => { + return checkedKeys.map(id => `permission-${id}`); + }; return ( - setSelectedKeys(keys as number[]) - }} - columns={columns} - dataSource={permissions} - rowKey="id" - pagination={false} - scroll={{ y: 400 }} - size="small" + diff --git a/frontend/src/pages/System/Role/index.tsx b/frontend/src/pages/System/Role/index.tsx index ef8daa6c..5367a1ae 100644 --- a/frontend/src/pages/System/Role/index.tsx +++ b/frontend/src/pages/System/Role/index.tsx @@ -1,10 +1,10 @@ import React, { useEffect, useState } from 'react'; -import { Card, Button, Table, Space, Modal, Form, Input, InputNumber, Switch, message, Tag } from 'antd'; +import { Card, Button, Table, Space, Modal, Form, Input, InputNumber, message, Tag } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, KeyOutlined, TagsOutlined } from '@ant-design/icons'; import type { RoleResponse, RoleTagResponse, RoleQuery } from './types'; -import { getRoleList, createRole, updateRole, deleteRole } from './service'; +import { getRoleList, createRole, updateRole, deleteRole, getRolePermissions, assignPermissions } from './service'; import type { TablePaginationConfig } from 'antd/es/table'; -import type { SorterResult } from 'antd/es/table/interface'; +import type {FilterValue, SorterResult} from 'antd/es/table/interface'; import PermissionModal from './components/PermissionModal'; import TagModal from './components/TagModal'; @@ -15,9 +15,10 @@ const RolePage: React.FC = () => { const [confirmLoading, setConfirmLoading] = useState(false); const [permissionModalVisible, setPermissionModalVisible] = useState(false); const [tagModalVisible, setTagModalVisible] = useState(false); - const [selectedRole, setSelectedRole] = useState(null); + const [selectedRole, setSelectedRole] = useState(); const [loading, setLoading] = useState(false); const [roles, setRoles] = useState([]); + const [defaultCheckedKeys, setDefaultCheckedKeys] = useState([]); const [pagination, setPagination] = useState({ current: 1, pageSize: 10, @@ -111,9 +112,54 @@ const RolePage: React.FC = () => { } }; - const handleAssignPermissions = (record: RoleResponse) => { - setSelectedRole(record); - setPermissionModalVisible(true); + // 处理表格变化 + const handleTableChange = ( + newPagination: TablePaginationConfig, + filters: Record, + sorter: SorterResult | SorterResult[] + ) => { + const { field, order } = Array.isArray(sorter) ? sorter[0] : sorter; + setSortField(field as string); + setSortOrder(order as 'ascend' | 'descend'); + + const params = { + current: newPagination.current, + pageSize: newPagination.pageSize, + sortField: field as string, + sortOrder: order === 'ascend' ? 'asc' : order === 'descend' ? 'desc' : undefined + }; + + fetchRoles({ + pageNum: params.current, + pageSize: params.pageSize, + sortField: params.sortField, + sortOrder: params.sortOrder + }); + }; + + // 处理分配权限 + const handleAssignPermissions = async (record: RoleResponse) => { + try { + const permissions = await getRolePermissions(record.id); + setSelectedRole(record); + setPermissionModalVisible(true); + setDefaultCheckedKeys(permissions); + } catch (error) { + message.error('获取角色权限失败'); + } + }; + + // 处理权限分配确认 + const handlePermissionAssign = async (permissionIds: number[]) => { + if (!selectedRole) return; + try { + await assignPermissions(selectedRole.id, permissionIds); + message.success('权限分配成功'); + setPermissionModalVisible(false); + fetchRoles(); + } catch (error) { + message.error('权限分配失败'); + } }; const handleAssignTags = (record: RoleResponse) => { @@ -121,22 +167,6 @@ const RolePage: React.FC = () => { setTagModalVisible(true); }; - const handleTableChange = ( - pagination: TablePaginationConfig, - _: any, - sorter: SorterResult - ) => { - const { field, order } = sorter; - setSortField(field as string); - setSortOrder(order as 'ascend' | 'descend'); - fetchRoles({ - pageNum: pagination.current, - pageSize: pagination.pageSize, - sortField: field as string, - sortOrder: order === 'ascend' ? 'asc' : order === 'descend' ? 'desc' : undefined - }); - }; - const columns = [ { title: '角色编码', @@ -285,13 +315,11 @@ const RolePage: React.FC = () => { {selectedRole && ( <> setPermissionModalVisible(false)} - onSuccess={() => { - setPermissionModalVisible(false); - fetchRoles(); - }} + onOk={handlePermissionAssign} + defaultCheckedKeys={defaultCheckedKeys} /> request.put(` export const createRoleTag = (data: RoleTagRequest) => request.post(ROLE_TAG_BASE_URL, data); // 获取角色的权限列表 -export const getRolePermissions = (roleId: number) => request.get(`${BASE_URL}/${roleId}/permissions`); +export const getRolePermissions = (roleId: number) => request.get(`${BASE_URL}/${roleId}/permissions`); // 分配权限 -export const assignPermissions = (roleId: number, permissionIds: number[]) => request.post(`${BASE_URL}/${roleId}/permissions`, permissionIds); +export const assignPermissions = (roleId: number, permissionIds: Number[]) => request.post(`${BASE_URL}/${roleId}/permissions`, permissionIds); // 获取所有权限列表 -export const getAllPermissions = () => request.get('/api/v1/permission/list'); \ No newline at end of file +export const getAllPermissions = () => request.get('/api/v1/permission/list'); + +// 获取权限树 +export const getPermissionTree = () => request.get('/api/v1/menu/permission-tree'); \ No newline at end of file diff --git a/frontend/src/pages/System/Role/types.ts b/frontend/src/pages/System/Role/types.ts index f1acbd2a..0ca850e9 100644 --- a/frontend/src/pages/System/Role/types.ts +++ b/frontend/src/pages/System/Role/types.ts @@ -1,4 +1,5 @@ import type { BaseResponse } from '@/types/base/response'; +import type { BaseQuery } from '@/types/base/query'; // 权限类型枚举 export enum PermissionType { @@ -15,14 +16,14 @@ export const PermissionTypeText = { }; // 角色查询参数 -export interface RoleQuery { +export interface RoleQuery extends BaseQuery { + code?: string; + name?: string; + enabled?: boolean; pageNum?: number; pageSize?: number; sortField?: string; sortOrder?: 'asc' | 'desc'; - code?: string; - name?: string; - enabled?: boolean; } // 角色请求参数 @@ -31,6 +32,7 @@ export interface RoleRequest { name: string; description?: string; sort?: number; + enabled?: boolean; } // 角色响应数据 @@ -46,16 +48,16 @@ export interface RoleResponse extends BaseResponse { // 角色标签请求参数 export interface RoleTagRequest { + code: string; name: string; - color?: string; - description?: string; + color: string; } // 角色标签响应数据 export interface RoleTagResponse extends BaseResponse { + code: string; name: string; color: string; - description?: string; } // 权限数据 @@ -63,10 +65,6 @@ export interface Permission { id: number; code: string; name: string; - type: PermissionType; - menuId: number; - menuName?: string; - enabled: boolean; + type: string; sort: number; - description?: string; } \ No newline at end of file diff --git a/frontend/src/store/userSlice.ts b/frontend/src/store/userSlice.ts index e477bbc1..3c65cae5 100644 --- a/frontend/src/store/userSlice.ts +++ b/frontend/src/store/userSlice.ts @@ -35,9 +35,7 @@ const userSlice = createSlice({ }, setMenus: (state, action: PayloadAction) => { state.menus = action.payload; - console.log(action.payload) - // console.log(JSON.stringify(action.payload)) - // localStorage.setItem('menus', action.payload); + localStorage.setItem('menus', JSON.stringify(action.payload)); }, logout: (state) => { state.token = null;