菜单权限
This commit is contained in:
@@ -20,11 +20,11 @@ const Header = ({ collapsed, setCollapsed }) => {
|
||||
};
|
||||
|
||||
const userMenuItems = [
|
||||
{
|
||||
key: "profile",
|
||||
icon: <UserOutlined />,
|
||||
label: "个人信息",
|
||||
},
|
||||
// {
|
||||
// key: "profile",
|
||||
// icon: <UserOutlined />,
|
||||
// label: "个人信息",
|
||||
// },
|
||||
{
|
||||
key: "logout",
|
||||
icon: <LogoutOutlined />,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useMemo } from "react";
|
||||
import React, { useMemo,useEffect } from "react";
|
||||
import { Layout, Menu } from "antd";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { getMenuItems } from "@/utils/menuUtils";
|
||||
import { getMenuItems } from "@/utils/menuUtils";
|
||||
import { Logo } from "@/components/Layout/Logo";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
|
||||
@@ -14,12 +14,13 @@ const Sidebar = ({ collapsed }) => {
|
||||
const { isDarkMode } = useTheme();
|
||||
const { user } = useAuth();
|
||||
|
||||
const menuItems = useMemo(() => {
|
||||
if (!user?.id) return [];
|
||||
const { adminRole: role } = user;
|
||||
return getMenuItems(role);
|
||||
const menuClient = useMemo(() => {
|
||||
if (!user?.id||user.menukeys?.length===0) return [];
|
||||
return getMenuItems(user?.menukeys || []);
|
||||
}, [user]);
|
||||
|
||||
useEffect(()=>{
|
||||
console.log(menuClient,'menuClient');
|
||||
},[menuClient])
|
||||
const defaultOpenKeys = useMemo(() => {
|
||||
const pathSegments = location.pathname.split("/").filter(Boolean);
|
||||
return pathSegments.reduce((acc, _, index) => {
|
||||
@@ -29,7 +30,6 @@ const Sidebar = ({ collapsed }) => {
|
||||
}, []);
|
||||
}, [location.pathname]);
|
||||
|
||||
// Handle menu item click
|
||||
const handleMenuClick = ({ key }) => {
|
||||
navigate(key);
|
||||
};
|
||||
@@ -41,8 +41,8 @@ const Sidebar = ({ collapsed }) => {
|
||||
collapsed={collapsed}
|
||||
theme={isDarkMode ? "dark" : "light"}
|
||||
width={256}
|
||||
collapsedWidth={80} // 添加这个属性
|
||||
className={`app-sidebar ${collapsed ? "collapsed" : ""}`} // 添加collapsed类名
|
||||
collapsedWidth={80}
|
||||
className={`app-sidebar ${collapsed ? "collapsed" : ""}`}
|
||||
>
|
||||
<Logo collapsed={collapsed} isDarkMode={isDarkMode} />
|
||||
<Menu
|
||||
@@ -50,7 +50,7 @@ const Sidebar = ({ collapsed }) => {
|
||||
mode="inline"
|
||||
selectedKeys={[location.pathname]}
|
||||
defaultOpenKeys={defaultOpenKeys}
|
||||
items={menuItems}
|
||||
items={menuClient}
|
||||
onClick={handleMenuClick}
|
||||
/>
|
||||
</Sider>
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
import React from "react";
|
||||
import { Navigate, useLocation } from "react-router-dom";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { Spin } from "antd";
|
||||
|
||||
const PUBLIC_PATHS = ['login', '404'];
|
||||
export const ProtectedRoute = ({ children }) => {
|
||||
const { user, loading } = useAuth();
|
||||
const { user } = useAuth();
|
||||
const location = useLocation();
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
const currentPath = location.pathname.replace(/^\//, '');
|
||||
if (PUBLIC_PATHS.includes(currentPath) || currentPath === '*') {
|
||||
return children;
|
||||
}
|
||||
if (user?.id) {
|
||||
const hasPermission = user.menukeys?.some(key => {
|
||||
return currentPath === key || currentPath.startsWith(`${key}/`);
|
||||
});
|
||||
|
||||
if (!hasPermission ) {
|
||||
return <Navigate to="/404" replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
if (!user?.id) {
|
||||
return (
|
||||
<Navigate to={`/login?redirectTo=${location.pathname || ""}`} replace />
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
// 如果用户未登录,重定向到登录页
|
||||
return <Navigate to="/login" state={{ from: location }} replace />;
|
||||
};
|
||||
|
||||
@@ -40,7 +40,8 @@ export const AuthProvider = ({ children }) => {
|
||||
error,
|
||||
} = await supabase.auth.getSession();
|
||||
const role = await checkInTeam(session?.user ?? null);
|
||||
setUser({ ...session?.user, adminRole: role });
|
||||
const menukey = await fetchMenuList(role);
|
||||
setUser({ ...session?.user, adminRole: role, menukeys: menukey });
|
||||
} catch (error) {
|
||||
console.error("Error getting session:", error);
|
||||
} finally {
|
||||
@@ -55,7 +56,8 @@ export const AuthProvider = ({ children }) => {
|
||||
if (user?.id && !user?.adminRole) {
|
||||
(async () => {
|
||||
const role = await checkInTeam(user);
|
||||
setUser({ ...user, adminRole: role });
|
||||
const menukey = await fetchMenuList(role);
|
||||
setUser({ ...user, adminRole: role, menukeys: menukey });
|
||||
})();
|
||||
}
|
||||
}, [user]);
|
||||
@@ -179,7 +181,26 @@ export const AuthProvider = ({ children }) => {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchMenuList = async (role) => {
|
||||
console.log(role,'role');
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('resources')
|
||||
.select('*')
|
||||
.eq('type', 'menuKey')
|
||||
.eq('attributes->>roleName', role) // 添加这行来筛选 OWNER 角色
|
||||
.single();
|
||||
if (error) throw error;
|
||||
if(data?.attributes?.menuKeys){
|
||||
return data.attributes.menuKeys;
|
||||
}else{
|
||||
return [];
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('获取菜单失败:' + error.message);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
// 登出
|
||||
const logout = async () => {
|
||||
try {
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { Form, Input, Button, Card, message } from 'antd';
|
||||
|
||||
const Settings = () => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const onFinish = (values) => {
|
||||
console.log('Success:', values);
|
||||
message.success('Settings updated successfully');
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title="Settings">
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={onFinish}
|
||||
initialValues={{
|
||||
email: 'john@example.com',
|
||||
notifications: true,
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
label="Email"
|
||||
name="email"
|
||||
rules={[{ required: true, type: 'email' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Save Changes
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
283
src/pages/resource/menu/index.jsx
Normal file
283
src/pages/resource/menu/index.jsx
Normal file
@@ -0,0 +1,283 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Table,
|
||||
Button,
|
||||
Modal,
|
||||
Form,
|
||||
Select,
|
||||
Input,
|
||||
message,
|
||||
Card,
|
||||
Typography,
|
||||
Space,
|
||||
TreeSelect
|
||||
} from 'antd';
|
||||
import { getAllRouteOptions, allRoutes } from '@/routes/routes';
|
||||
import { supabase } from '@/config/supabase';
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
|
||||
const { Title } = Typography;
|
||||
const TYPE = 'menuKey';
|
||||
|
||||
export default function MenuManagement() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [menuList, setMenuList] = useState([]);
|
||||
const [form] = Form.useForm();
|
||||
const [roles, setRoles] = useState([]);
|
||||
|
||||
// 获取所有路由选项
|
||||
const routeOptions = getAllRouteOptions();
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '角色名称',
|
||||
dataIndex: ['attributes', 'roleName'],
|
||||
key: 'roleName',
|
||||
render: (roleName) => {
|
||||
const role = roles.find(r => r.name === roleName);
|
||||
return role ? role.name : roleName;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '可访问菜单',
|
||||
dataIndex: ['attributes', 'menuKeys'],
|
||||
key: 'menuKeys',
|
||||
render: (menuKeys) => menuKeys?.map(key => {
|
||||
const route = routeOptions.find(r => r.value === key);
|
||||
return route ? route.label : key;
|
||||
}).join(', '),
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: ['attributes', 'description'],
|
||||
key: 'description',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button
|
||||
type="link"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleEdit(record)}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => handleDelete(record.id)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
// 获取菜单数据
|
||||
const fetchMenuList = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { data, error } = await supabase
|
||||
.from('resources')
|
||||
.select('*')
|
||||
.eq('type', TYPE)
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) throw error;
|
||||
setMenuList(data);
|
||||
} catch (error) {
|
||||
message.error('获取数据失败:' + error.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取角色列表
|
||||
const fetchRoles = async () => {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('roles')
|
||||
.select('*')
|
||||
.order('name');
|
||||
|
||||
if (error) throw error;
|
||||
setRoles(data || []);
|
||||
} catch (error) {
|
||||
message.error('获取角色数据失败');
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理表单提交
|
||||
const handleSubmit = async (values) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const menuData = {
|
||||
type: TYPE,
|
||||
attributes: {
|
||||
roleName: values.roleName,
|
||||
menuKeys: values.menuKeys,
|
||||
description: values.description
|
||||
}
|
||||
};
|
||||
|
||||
let result;
|
||||
if (form.getFieldValue('id')) {
|
||||
result = await supabase
|
||||
.from('resources')
|
||||
.update(menuData)
|
||||
.eq('id', form.getFieldValue('id'))
|
||||
.select();
|
||||
} else {
|
||||
result = await supabase
|
||||
.from('resources')
|
||||
.insert([menuData])
|
||||
.select();
|
||||
}
|
||||
|
||||
if (result.error) throw result.error;
|
||||
|
||||
message.success('保存成功');
|
||||
setIsModalVisible(false);
|
||||
fetchMenuList();
|
||||
} catch (error) {
|
||||
message.error('保存失败:' + error.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 编辑菜单
|
||||
const handleEdit = (record) => {
|
||||
form.setFieldsValue({
|
||||
id: record.id,
|
||||
roleName: record.attributes.roleName,
|
||||
menuKeys: record.attributes.menuKeys,
|
||||
description: record.attributes.description
|
||||
});
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
// 删除菜单
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const {data, error } = await supabase
|
||||
.from('resources')
|
||||
.delete()
|
||||
.eq('id', id);
|
||||
if (error) throw error;
|
||||
if(data.length>0){
|
||||
message.success('删除成功');
|
||||
fetchMenuList();
|
||||
}else{
|
||||
message.error('删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('删除失败:' + error.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 将路由配置转换为 TreeSelect 所需的格式
|
||||
const getTreeData = (routes) => {
|
||||
return routes.map(route => ({
|
||||
title: route.name,
|
||||
value: route.key,
|
||||
children: route.children ? getTreeData(route.children) : undefined,
|
||||
}));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchMenuList();
|
||||
fetchRoles();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="bg-gradient-to-b from-gray-50 to-white min-h-screen p-2">
|
||||
<Card className="shadow-lg rounded-lg">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title level={4} className="mb-0">菜单权限管理</Title>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
form.resetFields();
|
||||
setIsModalVisible(true);
|
||||
}}
|
||||
>
|
||||
新增角色菜单
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={menuList}
|
||||
loading={loading}
|
||||
rowKey="id"
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title={form.getFieldValue('id') ? '编辑角色菜单' : '新增角色菜单'}
|
||||
open={isModalVisible}
|
||||
onOk={() => form.submit()}
|
||||
onCancel={() => setIsModalVisible(false)}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleSubmit}
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item name="id" hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="roleName"
|
||||
label="角色名称"
|
||||
rules={[{ required: true, message: '请选择角色' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择角色"
|
||||
options={roles.map(role => ({
|
||||
label: role.name,
|
||||
value: role.name
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="menuKeys"
|
||||
label="可访问菜单"
|
||||
rules={[{ required: true, message: '请选择可访问菜单' }]}
|
||||
>
|
||||
<TreeSelect
|
||||
treeData={getTreeData(allRoutes)}
|
||||
treeCheckable
|
||||
showCheckedStrategy={TreeSelect.SHOW_ALL}
|
||||
checkStrictly={true}
|
||||
placeholder="请选择可访问菜单"
|
||||
style={{ width: '100%' }}
|
||||
maxTagCount={3}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="description"
|
||||
label="描述"
|
||||
>
|
||||
<Input.TextArea placeholder="请输入描述信息" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,12 +4,8 @@ import { PlusOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
||||
import { supabase } from '@/config/supabase';
|
||||
|
||||
const RoleHeader = ({ onAdd, onSearch }) => (
|
||||
<div className="flex justify-between mb-4">
|
||||
<Input.Search
|
||||
placeholder="搜索权限"
|
||||
onSearch={onSearch}
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
<div className="flex justify-end mb-4">
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
@@ -43,8 +39,8 @@ export default function PermissionManagement() {
|
||||
const [resourceModalVisible, setResourceModalVisible] = useState(false);
|
||||
const [roleForm] = Form.useForm();
|
||||
const [resourceForm] = Form.useForm();
|
||||
const [editingResource, setEditingResource] = useState(null);
|
||||
|
||||
// 获取所有权限数据(包括关联数据)
|
||||
const fetchPermissions = async (params = {}) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
@@ -56,15 +52,6 @@ export default function PermissionManagement() {
|
||||
resources:resource_id(*)
|
||||
`, { count: 'exact' });
|
||||
|
||||
// 添加搜索条件
|
||||
if (params.search) {
|
||||
query = query.or(`
|
||||
roles.name.ilike.%${params.search}%,
|
||||
resources.resource_name.ilike.%${params.search}%,
|
||||
resources.description.ilike.%${params.search}%
|
||||
`);
|
||||
}
|
||||
|
||||
// 添加排序
|
||||
if (params.field && params.order) {
|
||||
const ascending = params.order === 'ascend';
|
||||
@@ -240,6 +227,47 @@ export default function PermissionManagement() {
|
||||
}
|
||||
};
|
||||
|
||||
// 添加删除资源的函数
|
||||
const handleDeleteResource = async (resourceId) => {
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('permission_resources')
|
||||
.delete()
|
||||
.eq('id', resourceId);
|
||||
|
||||
if (error) throw error;
|
||||
message.success('删除资源成功');
|
||||
fetchResources();
|
||||
} catch (error) {
|
||||
message.error('删除资源失败');
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 添加更新资源的函数
|
||||
const handleUpdateResource = async (values) => {
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('permission_resources')
|
||||
.update({
|
||||
resource_name: values.resource_name,
|
||||
resource_type: values.resource_type,
|
||||
attributes_type: values.attributes_type,
|
||||
description: values.description
|
||||
})
|
||||
.eq('id', editingResource.id);
|
||||
|
||||
if (error) throw error;
|
||||
message.success('更新资源成功');
|
||||
setResourceModalVisible(false);
|
||||
resourceForm.resetFields();
|
||||
fetchResources();
|
||||
} catch (error) {
|
||||
message.error('更新资源失败');
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化加载
|
||||
useEffect(() => {
|
||||
fetchPermissions();
|
||||
@@ -274,7 +302,6 @@ export default function PermissionManagement() {
|
||||
title: '角色',
|
||||
dataIndex: ['roles', 'name'],
|
||||
key: 'role_name',
|
||||
sorter: true,
|
||||
render: (name) => (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
|
||||
{name}
|
||||
@@ -285,10 +312,9 @@ export default function PermissionManagement() {
|
||||
title: '资源',
|
||||
dataIndex: ['resources', 'resource_name'],
|
||||
key: 'resource_name',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '资源类型',
|
||||
title: '控制颗粒度',
|
||||
dataIndex: ['resources', 'resource_type'],
|
||||
key: 'resource_type',
|
||||
render: (type) => (
|
||||
@@ -306,10 +332,8 @@ export default function PermissionManagement() {
|
||||
key: 'attributes_type',
|
||||
render: (attributes_type) => (
|
||||
<span>
|
||||
{!attributes_type ? '无' : attributes_type}
|
||||
|
||||
{!attributes_type ? '-' : attributes_type}
|
||||
</span>
|
||||
|
||||
)
|
||||
},
|
||||
{
|
||||
@@ -333,12 +357,12 @@ export default function PermissionManagement() {
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: ['resources', 'description'],
|
||||
key: 'description',
|
||||
ellipsis: true,
|
||||
},
|
||||
// {
|
||||
// title: '描述',
|
||||
// dataIndex: ['resources', 'description'],
|
||||
// key: 'description',
|
||||
// ellipsis: true,
|
||||
// },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'operation',
|
||||
@@ -365,82 +389,129 @@ export default function PermissionManagement() {
|
||||
];
|
||||
|
||||
// Modal 表单内容
|
||||
const renderFormItems = () => (
|
||||
<>
|
||||
<Form.Item
|
||||
name="role_id"
|
||||
label="角色"
|
||||
rules={[{ required: true, message: '请选择角色' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择角色"
|
||||
dropdownRender={(menu) => (
|
||||
<>
|
||||
{menu}
|
||||
<Divider style={{ margin: '8px 0' }} />
|
||||
<Button
|
||||
type="text"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setRoleModalVisible(true)}
|
||||
style={{ paddingLeft: 8 }}
|
||||
>
|
||||
添加角色
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
{roles.map(role => (
|
||||
<Select.Option key={role.id} value={role.id}>
|
||||
{role.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
const renderFormItems = () => {
|
||||
const [roleSelectOpen, setRoleSelectOpen] = useState(false);
|
||||
const [resourceSelectOpen, setResourceSelectOpen] = useState(false);
|
||||
|
||||
<Form.Item
|
||||
name="resource_id"
|
||||
label="资源"
|
||||
rules={[{ required: true, message: '请选择资源' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择资源"
|
||||
dropdownRender={(menu) => (
|
||||
<>
|
||||
{menu}
|
||||
<Divider style={{ margin: '8px 0' }} />
|
||||
<Button
|
||||
type="text"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setResourceModalVisible(true)}
|
||||
style={{ paddingLeft: 8 }}
|
||||
>
|
||||
添加资源
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="role_id"
|
||||
label="角色"
|
||||
rules={[{ required: true, message: '请选择角色' }]}
|
||||
>
|
||||
{resources.map(resource => (
|
||||
<Select.Option key={resource.id} value={resource.id}>
|
||||
{`${resource.resource_name} ${resource.attributes_type?`(${resource.attributes_type})`:''}`}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="请选择角色"
|
||||
open={roleSelectOpen}
|
||||
onDropdownVisibleChange={setRoleSelectOpen}
|
||||
dropdownRender={(menu) => (
|
||||
<>
|
||||
{menu}
|
||||
<Divider style={{ margin: '8px 0' }} />
|
||||
<Button
|
||||
type="text"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
setRoleSelectOpen(false); // 关闭下拉框
|
||||
setRoleModalVisible(true);
|
||||
}}
|
||||
style={{ paddingLeft: 8 }}
|
||||
>
|
||||
添加角色
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
{roles.map(role => (
|
||||
<Select.Option key={role.id} value={role.id}>
|
||||
{role.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="action"
|
||||
label="操作类型"
|
||||
rules={[{ required: true, message: '请选择操作类型' }]}
|
||||
>
|
||||
<Select placeholder="请选择操作类型">
|
||||
<Select.Option value="create">创建</Select.Option>
|
||||
<Select.Option value="read">读取</Select.Option>
|
||||
<Select.Option value="update">更新</Select.Option>
|
||||
<Select.Option value="delete">删除</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
<Form.Item
|
||||
name="resource_id"
|
||||
label="资源"
|
||||
rules={[{ required: true, message: '请选择资源' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择资源"
|
||||
open={resourceSelectOpen}
|
||||
onDropdownVisibleChange={setResourceSelectOpen}
|
||||
dropdownRender={(menu) => (
|
||||
<>
|
||||
{menu}
|
||||
<Divider style={{ margin: '8px 0' }} />
|
||||
<div style={{ padding: '8px', display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
setResourceSelectOpen(false);
|
||||
setEditingResource(null); // 清空编辑状态
|
||||
resourceForm.resetFields(); // 清空表单
|
||||
setResourceModalVisible(true);
|
||||
}}
|
||||
>
|
||||
添加资源
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
{resources.map(resource => (
|
||||
<Select.Option key={resource.id} value={resource.id}>
|
||||
<div className="flex justify-between items-center">
|
||||
<span>{`${resource.resource_name} ${resource.attributes_type?`(${resource.attributes_type})`:''}`}</span>
|
||||
<div className="flex space-x-2" onClick={e => e.stopPropagation()}>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => {
|
||||
setResourceSelectOpen(false);
|
||||
setEditingResource(resource);
|
||||
resourceForm.setFieldsValue(resource);
|
||||
setResourceModalVisible(true);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这个资源吗?',
|
||||
onOk: () => handleDeleteResource(resource.id),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="action"
|
||||
label="操作类型"
|
||||
rules={[{ required: true, message: '请选择操作类型' }]}
|
||||
>
|
||||
<Select placeholder="请选择操作类型" allowClear>
|
||||
<Select.Option value="create">创建</Select.Option>
|
||||
<Select.Option value="read">读取</Select.Option>
|
||||
<Select.Option value="update">更新</Select.Option>
|
||||
<Select.Option value="delete">删除</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<App>
|
||||
@@ -535,18 +606,26 @@ export default function PermissionManagement() {
|
||||
|
||||
{/* 添加资源 Modal */}
|
||||
<Modal
|
||||
title="添加资源"
|
||||
title={editingResource ? "编辑资源" : "添加资源"}
|
||||
open={resourceModalVisible}
|
||||
onCancel={() => {
|
||||
setResourceModalVisible(false);
|
||||
setEditingResource(null);
|
||||
resourceForm.resetFields();
|
||||
}}
|
||||
onOk={() => resourceForm.submit()}
|
||||
zIndex={1100}
|
||||
>
|
||||
<Form
|
||||
form={resourceForm}
|
||||
layout="vertical"
|
||||
onFinish={handleAddResource}
|
||||
onFinish={(values) => {
|
||||
if (editingResource) {
|
||||
handleUpdateResource(values);
|
||||
} else {
|
||||
handleAddResource(values);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
name="resource_name"
|
||||
|
||||
@@ -2,10 +2,9 @@ import React, { Suspense } from 'react';
|
||||
import { Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { Spin } from 'antd';
|
||||
import MainLayout from '@/components/Layout/MainLayout';
|
||||
import { generateRoutes } from '@/routes/routes';
|
||||
import { allRoutes } from '@/routes/routes';
|
||||
import Login from '@/pages/auth/Login';
|
||||
import NotFound from '@/pages/notFound';
|
||||
import Dashboard from '@/pages/Dashboard';
|
||||
import { ProtectedRoute } from '@/components/auth/ProtectedRoute';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
|
||||
@@ -14,24 +13,16 @@ const LoadingComponent = () => (
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
// 这里做premeision 路由限制
|
||||
const renderRoutes = (routes) => {
|
||||
return routes
|
||||
// .filter(route => !route.hidden)
|
||||
.map(route => {
|
||||
const Component = route.component;
|
||||
if (route.children) {
|
||||
return (
|
||||
<Route key={route.path} path={route.path} element={
|
||||
<Suspense fallback={<LoadingComponent />}>
|
||||
<Component />
|
||||
</Suspense>
|
||||
}>
|
||||
{renderRoutes(route.children)}
|
||||
</Route>
|
||||
);
|
||||
}
|
||||
|
||||
// 渲染路由
|
||||
const renderRoutes = (routes) => {
|
||||
if (!Array.isArray(routes)) {
|
||||
console.warn('routes is not an array:', routes);
|
||||
return [];
|
||||
}
|
||||
return routes.map(route => {
|
||||
const Component = route.component;
|
||||
if (route.children) {
|
||||
return (
|
||||
<Route
|
||||
key={route.path}
|
||||
@@ -41,21 +32,47 @@ const renderRoutes = (routes) => {
|
||||
<Component />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
>
|
||||
{renderRoutes(route.children)}
|
||||
</Route>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Route
|
||||
key={route.path}
|
||||
path={route.path}
|
||||
element={
|
||||
<Suspense fallback={<LoadingComponent />}>
|
||||
<Component />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const AppRoutes = () => {
|
||||
const { user } = useAuth();
|
||||
const { adminRole: role } = user;
|
||||
const { user, loading } = useAuth();
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-screen">
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route
|
||||
path="/login"
|
||||
element={
|
||||
user?.id ? <Navigate to="/company/serviceTemplate" replace /> : <Login />
|
||||
user?.id && user.menukeys?.includes('company/serviceTemplate') ? (
|
||||
<Navigate to="/company/serviceTemplate" replace />
|
||||
) : (
|
||||
<Login />
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -67,11 +84,15 @@ const AppRoutes = () => {
|
||||
</ProtectedRoute>
|
||||
}
|
||||
>
|
||||
<Route
|
||||
{/* 默认重定向到服务管理页面 */}
|
||||
<Route
|
||||
index
|
||||
element={<Navigate to="/company/serviceTemplate" replace />}
|
||||
/>
|
||||
{renderRoutes(generateRoutes(role))}
|
||||
|
||||
{/* 渲染所有路由 */}
|
||||
{renderRoutes(allRoutes)}
|
||||
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
@@ -1,210 +1,180 @@
|
||||
import { lazy } from "react";
|
||||
|
||||
// Resource Management routes
|
||||
const resourceRoutes = [
|
||||
// 所有可用的路由配置
|
||||
export const allRoutes = [
|
||||
{
|
||||
path: "team",
|
||||
component: lazy(() => import("@/pages/resource/team")),
|
||||
name: "团队管理",
|
||||
icon: "team",
|
||||
roles: ["OWNER"],
|
||||
path: "dashboard",
|
||||
component: lazy(() => import("@/pages/Dashboard")),
|
||||
name: "仪表盘",
|
||||
icon: "dashboard",
|
||||
key: "dashboard",
|
||||
},
|
||||
{
|
||||
path: "role",
|
||||
component: lazy(() => import("@/pages/resource/role")),
|
||||
name: "角色管理",
|
||||
icon: "setting",
|
||||
roles: ["OWNER"],
|
||||
},
|
||||
{
|
||||
path: "bucket",
|
||||
component: lazy(() => import("@/pages/resource/bucket")),
|
||||
name: "对象存储",
|
||||
icon: "shop",
|
||||
roles: ["OWNER"],
|
||||
},
|
||||
|
||||
{
|
||||
path: "task/edit/:id?",
|
||||
component: lazy(() => import("@/pages/resource/resourceTask/edit")),
|
||||
hidden: true,
|
||||
name: "新增/编辑任务",
|
||||
roles: ["OWNER"],
|
||||
},
|
||||
];
|
||||
|
||||
// Company routes
|
||||
const companyRoutes = [
|
||||
{
|
||||
path: "quotation",
|
||||
component: lazy(() => import("@/pages/company/quotation")),
|
||||
name: "报价单",
|
||||
icon: "file",
|
||||
roles: ["ADMIN", "OWNER"],
|
||||
},
|
||||
|
||||
{
|
||||
path: "quotaInfo/:id?", // 添加可选的 id 参数
|
||||
hidden: true,
|
||||
component: lazy(() => import("@/pages/company/quotation/detail")),
|
||||
name: "报价单详情",
|
||||
icon: "file",
|
||||
roles: ["ADMIN", "OWNER"],
|
||||
},
|
||||
{
|
||||
path: "project",
|
||||
component: lazy(() => import("@/pages/company/project")),
|
||||
name: "专案管理",
|
||||
path: "resource",
|
||||
component: lazy(() => import("@/pages/resource")),
|
||||
name: "资源管理",
|
||||
icon: "appstore",
|
||||
roles: ["ADMIN", "OWNER"],
|
||||
},
|
||||
{
|
||||
path: "task",
|
||||
component: lazy(() => import("@/pages/company/task")),
|
||||
name: "任务管理",
|
||||
icon: "appstore",
|
||||
roles: ["OWNER"],
|
||||
key: "resource",
|
||||
children: [
|
||||
{
|
||||
path: "team",
|
||||
component: lazy(() => import("@/pages/resource/team")),
|
||||
name: "团队管理",
|
||||
icon: "team",
|
||||
key: "resource/team",
|
||||
},
|
||||
{
|
||||
path: "role",
|
||||
component: lazy(() => import("@/pages/resource/role")),
|
||||
name: "角色管理",
|
||||
icon: "setting",
|
||||
key: "resource/role",
|
||||
},
|
||||
{
|
||||
path: "menu",
|
||||
component: lazy(() => import("@/pages/resource/menu")),
|
||||
name: "菜单管理",
|
||||
icon: "menu",
|
||||
key: "resource/menu",
|
||||
},
|
||||
{
|
||||
path: "bucket",
|
||||
component: lazy(() => import("@/pages/resource/bucket")),
|
||||
name: "对象存储",
|
||||
icon: "shop",
|
||||
key: "resource/bucket",
|
||||
},
|
||||
{
|
||||
path: "task/edit/:id?",
|
||||
component: lazy(() => import("@/pages/resource/resourceTask/edit")),
|
||||
name: "新增/编辑任务",
|
||||
hidden: true,
|
||||
key: "resource/task/edit",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "taskInfo/:id?",
|
||||
hidden:true,
|
||||
component: lazy(() => import("@/pages/company/task/detail")),
|
||||
name: "任务管理详情",
|
||||
icon: "appstore",
|
||||
roles: ["OWNER"],
|
||||
},
|
||||
{
|
||||
path: "serviceTemplate",
|
||||
component: lazy(() => import("@/pages/company/service")),
|
||||
name: "服务管理",
|
||||
icon: "container",
|
||||
roles: ["ADMIN", "OWNER"],
|
||||
},
|
||||
{
|
||||
path: "templateItemManage",
|
||||
component: lazy(() => import("@/pages/company/service/itemsManange")),
|
||||
name: "资源类型",
|
||||
icon: "container",
|
||||
roles: ["ADMIN", "OWNER"],
|
||||
},
|
||||
{
|
||||
path: "serviceTemplateInfo/:id?",
|
||||
hidden: true,
|
||||
component: lazy(() => import("@/pages/company/service/detail")),
|
||||
name: "服务模版详情",
|
||||
icon: "container",
|
||||
roles: ["ADMIN", "OWNER"],
|
||||
},
|
||||
{
|
||||
path: "quotaInfo/preview/:id?", // 添加可选的 id 参数
|
||||
hidden: true,
|
||||
component: lazy(() => import("@/pages/company/quotation/view")),
|
||||
name: "报价单预览",
|
||||
icon: "file",
|
||||
roles: ["ADMIN", "OWNER"],
|
||||
},
|
||||
{
|
||||
path: "customer",
|
||||
component: lazy(() => import("@/pages/company/customer")),
|
||||
name: "客户管理",
|
||||
icon: "user",
|
||||
roles: ["ADMIN", "OWNER"],
|
||||
},
|
||||
{
|
||||
path: "customerInfo/:id?",
|
||||
hidden: true,
|
||||
component: lazy(() => import("@/pages/company/customer/detail")),
|
||||
name: "客户详情",
|
||||
icon: "user",
|
||||
roles: ["ADMIN", "OWNER"],
|
||||
},
|
||||
{
|
||||
path: "supplier",
|
||||
component: lazy(() => import("@/pages/company/supplier")),
|
||||
name: "供应商管理",
|
||||
icon: "branches",
|
||||
roles: ["ADMIN", "OWNER"],
|
||||
},
|
||||
{
|
||||
path: "supplierInfo/:id?",
|
||||
hidden: true,
|
||||
component: lazy(() => import("@/pages/company/supplier/detail")),
|
||||
name: "供应商详情",
|
||||
icon: "branches",
|
||||
roles: ["ADMIN", "OWNER"],
|
||||
},
|
||||
|
||||
{
|
||||
path: "projectInfo/:id?",
|
||||
hidden: true,
|
||||
component: lazy(() => import("@/pages/company/project/detail")),
|
||||
name: "专案管理详情",
|
||||
icon: "appstore",
|
||||
roles: ["ADMIN", "OWNER"],
|
||||
},
|
||||
{
|
||||
path: "projectView/:id?",
|
||||
hidden: true,
|
||||
component: lazy(() => import("@/pages/company/project/info")),
|
||||
name: "专案详情",
|
||||
icon: "appstore",
|
||||
roles: ["ADMIN", "OWNER"],
|
||||
path: "company",
|
||||
component: lazy(() => import("@/pages/company")),
|
||||
name: "公司管理",
|
||||
icon: "bank",
|
||||
key: "company",
|
||||
children: [
|
||||
{
|
||||
path: "quotation",
|
||||
component: lazy(() => import("@/pages/company/quotation")),
|
||||
name: "报价单",
|
||||
icon: "file",
|
||||
key: "company/quotation",
|
||||
},
|
||||
{
|
||||
path: "quotaInfo/:id?",
|
||||
component: lazy(() => import("@/pages/company/quotation/detail")),
|
||||
name: "报价单详情",
|
||||
icon: "file",
|
||||
hidden: true,
|
||||
key: "company/quotaInfo",
|
||||
},
|
||||
{
|
||||
path: "project",
|
||||
component: lazy(() => import("@/pages/company/project")),
|
||||
name: "专案管理",
|
||||
icon: "appstore",
|
||||
key: "company/project",
|
||||
},
|
||||
{
|
||||
path: "task",
|
||||
component: lazy(() => import("@/pages/company/task")),
|
||||
name: "任务管理",
|
||||
icon: "appstore",
|
||||
key: "company/task",
|
||||
},
|
||||
{
|
||||
path: "taskInfo/:id?",
|
||||
component: lazy(() => import("@/pages/company/task/detail")),
|
||||
name: "任务管理详情",
|
||||
icon: "appstore",
|
||||
hidden: true,
|
||||
key: "company/taskInfo",
|
||||
},
|
||||
{
|
||||
path: "serviceTemplate",
|
||||
component: lazy(() => import("@/pages/company/service")),
|
||||
name: "服务管理",
|
||||
icon: "appstore",
|
||||
key: "company/serviceTemplate",
|
||||
},
|
||||
{
|
||||
path: "supplier",
|
||||
component: lazy(() => import("@/pages/company/supplier")),
|
||||
name: "供应商管理",
|
||||
icon: "branches",
|
||||
key: "company/supplier",
|
||||
},
|
||||
{
|
||||
path: "supplierInfo/:id?",
|
||||
component: lazy(() => import("@/pages/company/supplier/detail")),
|
||||
name: "供应商详情",
|
||||
icon: "branches",
|
||||
hidden: true,
|
||||
key: "company/supplierInfo",
|
||||
},
|
||||
{
|
||||
path: "projectInfo/:id?",
|
||||
component: lazy(() => import("@/pages/company/project/detail")),
|
||||
name: "专案管理详情",
|
||||
icon: "appstore",
|
||||
hidden: true,
|
||||
key: "company/projectInfo",
|
||||
},
|
||||
{
|
||||
path: "projectView/:id?",
|
||||
component: lazy(() => import("@/pages/company/project/info")),
|
||||
name: "专案详情",
|
||||
icon: "appstore",
|
||||
hidden: true,
|
||||
key: "company/projectView",
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const marketingRoutes = [];
|
||||
export const getRouteByKey = (key) => {
|
||||
const keys = key.split('/').filter(Boolean);
|
||||
let current = allRoutes;
|
||||
let result = null;
|
||||
|
||||
// const roleRoutes = [
|
||||
// {
|
||||
// path: "role",
|
||||
// component: lazy(() => import("@/pages/role")),
|
||||
// name: "角色管理",
|
||||
// icon: "setting",
|
||||
// roles: ["ADMIN", "OWNER"],
|
||||
// },
|
||||
// ];
|
||||
keys.forEach(k => {
|
||||
const found = current.find(r => r.path.split('/')[0] === k);
|
||||
if (found) {
|
||||
result = found;
|
||||
current = found.children || [];
|
||||
}
|
||||
});
|
||||
|
||||
export const generateRoutes = (role) => {
|
||||
return [
|
||||
{
|
||||
path: "dashboard",
|
||||
component: lazy(() => import("@/pages/Dashboard")),
|
||||
name: "仪表盘",
|
||||
icon: "dashboard",
|
||||
roles: ["ADMIN", "OWNER", "MEMBER"],
|
||||
},
|
||||
{
|
||||
path: "resource",
|
||||
component: lazy(() => import("@/pages/resource")),
|
||||
name: "资源管理",
|
||||
icon: "appstore",
|
||||
children: resourceRoutes.filter((route) => route.roles.includes(role)),
|
||||
roles: ["OWNER"],
|
||||
},
|
||||
{
|
||||
path: "company",
|
||||
component: lazy(() => import("@/pages/company")),
|
||||
name: "公司管理",
|
||||
icon: "bank",
|
||||
children: companyRoutes.filter((route) => route.roles.includes(role)),
|
||||
roles: ["ADMIN", "OWNER"],
|
||||
},
|
||||
// {
|
||||
// path: "marketing",
|
||||
// component: lazy(() => import("@/pages/marketing")),
|
||||
// name: "行销中心",
|
||||
// icon: "shopping",
|
||||
// children: marketingRoutes.filter((route) => route.roles.includes(role)),
|
||||
// roles: ["ADMIN", "OWNER"],
|
||||
// },
|
||||
|
||||
// {
|
||||
// path: "role",
|
||||
// component: lazy(() => import("@/pages/role")),
|
||||
// name: "权限管理",
|
||||
// icon: "setting",
|
||||
// children: roleRoutes.filter((route) => route.roles.includes(role)),
|
||||
// roles: ["ADMIN", "OWNER"],
|
||||
// },
|
||||
].filter((route) => route.roles.includes(role));
|
||||
return result;
|
||||
};
|
||||
|
||||
export const flattenRoutes = (routes, parentPath = '') => {
|
||||
return routes.reduce((acc, route) => {
|
||||
const path = parentPath ? `${parentPath}/${route.path}` : route.path;
|
||||
acc.push({ ...route, path });
|
||||
|
||||
if (route.children) {
|
||||
acc.push(...flattenRoutes(route.children, path));
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
||||
|
||||
// 获取所有可选的路由选项(用于菜单管理)
|
||||
export const getAllRouteOptions = () => {
|
||||
return flattenRoutes(allRoutes)
|
||||
.filter(route => !route.hidden)
|
||||
.map(route => ({
|
||||
label: route.name,
|
||||
value: route.key,
|
||||
isLeaf: !route.children
|
||||
}));
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { generateRoutes } from "@/routes/routes";
|
||||
import * as AntIcons from "@ant-design/icons";
|
||||
import { ColorIcon } from "@/components/Layout/ColorIcon";
|
||||
import { allRoutes } from "@/routes/routes";
|
||||
|
||||
const getAntIcon = (iconName) => {
|
||||
const iconKey = `${iconName.charAt(0).toUpperCase()}${iconName.slice(
|
||||
@@ -10,25 +10,69 @@ const getAntIcon = (iconName) => {
|
||||
return AntIcons[iconKey] ? React.createElement(AntIcons[iconKey]) : null;
|
||||
};
|
||||
|
||||
const generateMenuItems = (routes, parentPath = "") => {
|
||||
const generateMenuItems = (routes, menuKeys = [], parentPath = "") => {
|
||||
return routes
|
||||
.filter((route) => !route.hidden)
|
||||
.filter((route) => {
|
||||
if (!menuKeys.length) return !route.hidden;
|
||||
|
||||
const isRouteAllowed = menuKeys.includes(route.key);
|
||||
|
||||
// 如果有子路由,只要子路由中有被授权的,父路由就应该显示
|
||||
if (route.children) {
|
||||
const hasAllowedChildren = route.children.some(child =>
|
||||
menuKeys.includes(child.key) ||
|
||||
(child.children && child.children.some(grandChild => menuKeys.includes(grandChild.key)))
|
||||
);
|
||||
return hasAllowedChildren || isRouteAllowed;
|
||||
}
|
||||
|
||||
return isRouteAllowed;
|
||||
})
|
||||
.map((route) => {
|
||||
const fullPath = `${parentPath}/${route.path}`.replace(/\/+/g, "/");
|
||||
const icon = route.icon && <ColorIcon icon={getAntIcon(route.icon)} />;
|
||||
|
||||
const menuItem = {
|
||||
key: fullPath,
|
||||
key: route.key, // 使用 key 而不是 fullPath
|
||||
icon,
|
||||
label: route.name,
|
||||
};
|
||||
|
||||
if (route.children) {
|
||||
menuItem.children = generateMenuItems(route.children, fullPath);
|
||||
const filteredChildren = generateMenuItems(route.children, menuKeys, fullPath);
|
||||
if (filteredChildren.length > 0) {
|
||||
menuItem.children = filteredChildren;
|
||||
}
|
||||
}
|
||||
|
||||
return menuItem;
|
||||
});
|
||||
};
|
||||
|
||||
export const getMenuItems = (role) => generateMenuItems(generateRoutes(role));
|
||||
|
||||
export const getMenuItems = (menuKeys = []) => generateMenuItems(allRoutes, menuKeys);
|
||||
export const filterRoutesByMenuKeys = (routes, menuKeys) => {
|
||||
if (!Array.isArray(routes) || !Array.isArray(menuKeys)) {
|
||||
return [];
|
||||
}
|
||||
return routes.reduce((acc, route) => {
|
||||
// 检查当前路由的 key 是否在 menuKeys 中
|
||||
const isRouteAllowed = menuKeys.includes(route.key);
|
||||
|
||||
// 递归处理子路由
|
||||
let filteredChildren = [];
|
||||
if (route.children) {
|
||||
filteredChildren = filterRoutesByMenuKeys(route.children, menuKeys);
|
||||
}
|
||||
|
||||
// 如果当前路由被允许或者有被允许的子路由,则保留该路由
|
||||
if (isRouteAllowed || filteredChildren.length > 0) {
|
||||
acc.push({
|
||||
...route,
|
||||
children: filteredChildren.length > 0 ? filteredChildren : undefined
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
||||
export { generateMenuItems, getAntIcon };
|
||||
|
||||
Reference in New Issue
Block a user