菜单权限
This commit is contained in:
@@ -20,11 +20,11 @@ const Header = ({ collapsed, setCollapsed }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const userMenuItems = [
|
const userMenuItems = [
|
||||||
{
|
// {
|
||||||
key: "profile",
|
// key: "profile",
|
||||||
icon: <UserOutlined />,
|
// icon: <UserOutlined />,
|
||||||
label: "个人信息",
|
// label: "个人信息",
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
key: "logout",
|
key: "logout",
|
||||||
icon: <LogoutOutlined />,
|
icon: <LogoutOutlined />,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo } from "react";
|
import React, { useMemo,useEffect } from "react";
|
||||||
import { Layout, Menu } from "antd";
|
import { Layout, Menu } from "antd";
|
||||||
import { useNavigate, useLocation } from "react-router-dom";
|
import { useNavigate, useLocation } from "react-router-dom";
|
||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
@@ -14,12 +14,13 @@ const Sidebar = ({ collapsed }) => {
|
|||||||
const { isDarkMode } = useTheme();
|
const { isDarkMode } = useTheme();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
const menuItems = useMemo(() => {
|
const menuClient = useMemo(() => {
|
||||||
if (!user?.id) return [];
|
if (!user?.id||user.menukeys?.length===0) return [];
|
||||||
const { adminRole: role } = user;
|
return getMenuItems(user?.menukeys || []);
|
||||||
return getMenuItems(role);
|
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
useEffect(()=>{
|
||||||
|
console.log(menuClient,'menuClient');
|
||||||
|
},[menuClient])
|
||||||
const defaultOpenKeys = useMemo(() => {
|
const defaultOpenKeys = useMemo(() => {
|
||||||
const pathSegments = location.pathname.split("/").filter(Boolean);
|
const pathSegments = location.pathname.split("/").filter(Boolean);
|
||||||
return pathSegments.reduce((acc, _, index) => {
|
return pathSegments.reduce((acc, _, index) => {
|
||||||
@@ -29,7 +30,6 @@ const Sidebar = ({ collapsed }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
// Handle menu item click
|
|
||||||
const handleMenuClick = ({ key }) => {
|
const handleMenuClick = ({ key }) => {
|
||||||
navigate(key);
|
navigate(key);
|
||||||
};
|
};
|
||||||
@@ -41,8 +41,8 @@ const Sidebar = ({ collapsed }) => {
|
|||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
theme={isDarkMode ? "dark" : "light"}
|
theme={isDarkMode ? "dark" : "light"}
|
||||||
width={256}
|
width={256}
|
||||||
collapsedWidth={80} // 添加这个属性
|
collapsedWidth={80}
|
||||||
className={`app-sidebar ${collapsed ? "collapsed" : ""}`} // 添加collapsed类名
|
className={`app-sidebar ${collapsed ? "collapsed" : ""}`}
|
||||||
>
|
>
|
||||||
<Logo collapsed={collapsed} isDarkMode={isDarkMode} />
|
<Logo collapsed={collapsed} isDarkMode={isDarkMode} />
|
||||||
<Menu
|
<Menu
|
||||||
@@ -50,7 +50,7 @@ const Sidebar = ({ collapsed }) => {
|
|||||||
mode="inline"
|
mode="inline"
|
||||||
selectedKeys={[location.pathname]}
|
selectedKeys={[location.pathname]}
|
||||||
defaultOpenKeys={defaultOpenKeys}
|
defaultOpenKeys={defaultOpenKeys}
|
||||||
items={menuItems}
|
items={menuClient}
|
||||||
onClick={handleMenuClick}
|
onClick={handleMenuClick}
|
||||||
/>
|
/>
|
||||||
</Sider>
|
</Sider>
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Navigate, useLocation } from "react-router-dom";
|
import { Navigate, useLocation } from "react-router-dom";
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
import { useAuth } from "@/contexts/AuthContext";
|
||||||
import { Spin } from "antd";
|
|
||||||
|
|
||||||
|
const PUBLIC_PATHS = ['login', '404'];
|
||||||
export const ProtectedRoute = ({ children }) => {
|
export const ProtectedRoute = ({ children }) => {
|
||||||
const { user, loading } = useAuth();
|
const { user } = useAuth();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
if (loading) {
|
const currentPath = location.pathname.replace(/^\//, '');
|
||||||
return (
|
if (PUBLIC_PATHS.includes(currentPath) || currentPath === '*') {
|
||||||
<div className="min-h-screen flex items-center justify-center">
|
return children;
|
||||||
<Spin size="large" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
if (user?.id) {
|
||||||
|
const hasPermission = user.menukeys?.some(key => {
|
||||||
|
return currentPath === key || currentPath.startsWith(`${key}/`);
|
||||||
|
});
|
||||||
|
|
||||||
if (!user?.id) {
|
if (!hasPermission ) {
|
||||||
return (
|
return <Navigate to="/404" replace />;
|
||||||
<Navigate to={`/login?redirectTo=${location.pathname || ""}`} replace />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return children;
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果用户未登录,重定向到登录页
|
||||||
|
return <Navigate to="/login" state={{ from: location }} replace />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export const AuthProvider = ({ children }) => {
|
|||||||
error,
|
error,
|
||||||
} = await supabase.auth.getSession();
|
} = await supabase.auth.getSession();
|
||||||
const role = await checkInTeam(session?.user ?? null);
|
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) {
|
} catch (error) {
|
||||||
console.error("Error getting session:", error);
|
console.error("Error getting session:", error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -55,7 +56,8 @@ export const AuthProvider = ({ children }) => {
|
|||||||
if (user?.id && !user?.adminRole) {
|
if (user?.id && !user?.adminRole) {
|
||||||
(async () => {
|
(async () => {
|
||||||
const role = await checkInTeam(user);
|
const role = await checkInTeam(user);
|
||||||
setUser({ ...user, adminRole: role });
|
const menukey = await fetchMenuList(role);
|
||||||
|
setUser({ ...user, adminRole: role, menukeys: menukey });
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
}, [user]);
|
}, [user]);
|
||||||
@@ -179,7 +181,26 @@ export const AuthProvider = ({ children }) => {
|
|||||||
setLoading(false);
|
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 () => {
|
const logout = async () => {
|
||||||
try {
|
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';
|
import { supabase } from '@/config/supabase';
|
||||||
|
|
||||||
const RoleHeader = ({ onAdd, onSearch }) => (
|
const RoleHeader = ({ onAdd, onSearch }) => (
|
||||||
<div className="flex justify-between mb-4">
|
<div className="flex justify-end mb-4">
|
||||||
<Input.Search
|
|
||||||
placeholder="搜索权限"
|
|
||||||
onSearch={onSearch}
|
|
||||||
style={{ width: 300 }}
|
|
||||||
/>
|
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
@@ -43,8 +39,8 @@ export default function PermissionManagement() {
|
|||||||
const [resourceModalVisible, setResourceModalVisible] = useState(false);
|
const [resourceModalVisible, setResourceModalVisible] = useState(false);
|
||||||
const [roleForm] = Form.useForm();
|
const [roleForm] = Form.useForm();
|
||||||
const [resourceForm] = Form.useForm();
|
const [resourceForm] = Form.useForm();
|
||||||
|
const [editingResource, setEditingResource] = useState(null);
|
||||||
|
|
||||||
// 获取所有权限数据(包括关联数据)
|
|
||||||
const fetchPermissions = async (params = {}) => {
|
const fetchPermissions = async (params = {}) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -56,15 +52,6 @@ export default function PermissionManagement() {
|
|||||||
resources:resource_id(*)
|
resources:resource_id(*)
|
||||||
`, { count: 'exact' });
|
`, { 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) {
|
if (params.field && params.order) {
|
||||||
const ascending = params.order === 'ascend';
|
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(() => {
|
useEffect(() => {
|
||||||
fetchPermissions();
|
fetchPermissions();
|
||||||
@@ -274,7 +302,6 @@ export default function PermissionManagement() {
|
|||||||
title: '角色',
|
title: '角色',
|
||||||
dataIndex: ['roles', 'name'],
|
dataIndex: ['roles', 'name'],
|
||||||
key: 'role_name',
|
key: 'role_name',
|
||||||
sorter: true,
|
|
||||||
render: (name) => (
|
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">
|
<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}
|
{name}
|
||||||
@@ -285,10 +312,9 @@ export default function PermissionManagement() {
|
|||||||
title: '资源',
|
title: '资源',
|
||||||
dataIndex: ['resources', 'resource_name'],
|
dataIndex: ['resources', 'resource_name'],
|
||||||
key: 'resource_name',
|
key: 'resource_name',
|
||||||
sorter: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '资源类型',
|
title: '控制颗粒度',
|
||||||
dataIndex: ['resources', 'resource_type'],
|
dataIndex: ['resources', 'resource_type'],
|
||||||
key: 'resource_type',
|
key: 'resource_type',
|
||||||
render: (type) => (
|
render: (type) => (
|
||||||
@@ -306,10 +332,8 @@ export default function PermissionManagement() {
|
|||||||
key: 'attributes_type',
|
key: 'attributes_type',
|
||||||
render: (attributes_type) => (
|
render: (attributes_type) => (
|
||||||
<span>
|
<span>
|
||||||
{!attributes_type ? '无' : attributes_type}
|
{!attributes_type ? '-' : attributes_type}
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -333,12 +357,12 @@ export default function PermissionManagement() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
title: '描述',
|
// title: '描述',
|
||||||
dataIndex: ['resources', 'description'],
|
// dataIndex: ['resources', 'description'],
|
||||||
key: 'description',
|
// key: 'description',
|
||||||
ellipsis: true,
|
// ellipsis: true,
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'operation',
|
key: 'operation',
|
||||||
@@ -365,7 +389,11 @@ export default function PermissionManagement() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Modal 表单内容
|
// Modal 表单内容
|
||||||
const renderFormItems = () => (
|
const renderFormItems = () => {
|
||||||
|
const [roleSelectOpen, setRoleSelectOpen] = useState(false);
|
||||||
|
const [resourceSelectOpen, setResourceSelectOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="role_id"
|
name="role_id"
|
||||||
@@ -373,7 +401,10 @@ export default function PermissionManagement() {
|
|||||||
rules={[{ required: true, message: '请选择角色' }]}
|
rules={[{ required: true, message: '请选择角色' }]}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
|
allowClear
|
||||||
placeholder="请选择角色"
|
placeholder="请选择角色"
|
||||||
|
open={roleSelectOpen}
|
||||||
|
onDropdownVisibleChange={setRoleSelectOpen}
|
||||||
dropdownRender={(menu) => (
|
dropdownRender={(menu) => (
|
||||||
<>
|
<>
|
||||||
{menu}
|
{menu}
|
||||||
@@ -381,7 +412,10 @@ export default function PermissionManagement() {
|
|||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
onClick={() => setRoleModalVisible(true)}
|
onClick={() => {
|
||||||
|
setRoleSelectOpen(false); // 关闭下拉框
|
||||||
|
setRoleModalVisible(true);
|
||||||
|
}}
|
||||||
style={{ paddingLeft: 8 }}
|
style={{ paddingLeft: 8 }}
|
||||||
>
|
>
|
||||||
添加角色
|
添加角色
|
||||||
@@ -404,24 +438,60 @@ export default function PermissionManagement() {
|
|||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
placeholder="请选择资源"
|
placeholder="请选择资源"
|
||||||
|
open={resourceSelectOpen}
|
||||||
|
onDropdownVisibleChange={setResourceSelectOpen}
|
||||||
dropdownRender={(menu) => (
|
dropdownRender={(menu) => (
|
||||||
<>
|
<>
|
||||||
{menu}
|
{menu}
|
||||||
<Divider style={{ margin: '8px 0' }} />
|
<Divider style={{ margin: '8px 0' }} />
|
||||||
|
<div style={{ padding: '8px', display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
onClick={() => setResourceModalVisible(true)}
|
onClick={() => {
|
||||||
style={{ paddingLeft: 8 }}
|
setResourceSelectOpen(false);
|
||||||
|
setEditingResource(null); // 清空编辑状态
|
||||||
|
resourceForm.resetFields(); // 清空表单
|
||||||
|
setResourceModalVisible(true);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
添加资源
|
添加资源
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{resources.map(resource => (
|
{resources.map(resource => (
|
||||||
<Select.Option key={resource.id} value={resource.id}>
|
<Select.Option key={resource.id} value={resource.id}>
|
||||||
{`${resource.resource_name} ${resource.attributes_type?`(${resource.attributes_type})`:''}`}
|
<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.Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
@@ -432,7 +502,7 @@ export default function PermissionManagement() {
|
|||||||
label="操作类型"
|
label="操作类型"
|
||||||
rules={[{ required: true, message: '请选择操作类型' }]}
|
rules={[{ required: true, message: '请选择操作类型' }]}
|
||||||
>
|
>
|
||||||
<Select placeholder="请选择操作类型">
|
<Select placeholder="请选择操作类型" allowClear>
|
||||||
<Select.Option value="create">创建</Select.Option>
|
<Select.Option value="create">创建</Select.Option>
|
||||||
<Select.Option value="read">读取</Select.Option>
|
<Select.Option value="read">读取</Select.Option>
|
||||||
<Select.Option value="update">更新</Select.Option>
|
<Select.Option value="update">更新</Select.Option>
|
||||||
@@ -441,6 +511,7 @@ export default function PermissionManagement() {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<App>
|
<App>
|
||||||
@@ -535,18 +606,26 @@ export default function PermissionManagement() {
|
|||||||
|
|
||||||
{/* 添加资源 Modal */}
|
{/* 添加资源 Modal */}
|
||||||
<Modal
|
<Modal
|
||||||
title="添加资源"
|
title={editingResource ? "编辑资源" : "添加资源"}
|
||||||
open={resourceModalVisible}
|
open={resourceModalVisible}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setResourceModalVisible(false);
|
setResourceModalVisible(false);
|
||||||
|
setEditingResource(null);
|
||||||
resourceForm.resetFields();
|
resourceForm.resetFields();
|
||||||
}}
|
}}
|
||||||
onOk={() => resourceForm.submit()}
|
onOk={() => resourceForm.submit()}
|
||||||
|
zIndex={1100}
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
form={resourceForm}
|
form={resourceForm}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
onFinish={handleAddResource}
|
onFinish={(values) => {
|
||||||
|
if (editingResource) {
|
||||||
|
handleUpdateResource(values);
|
||||||
|
} else {
|
||||||
|
handleAddResource(values);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="resource_name"
|
name="resource_name"
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import React, { Suspense } from 'react';
|
|||||||
import { Routes, Route, Navigate } from 'react-router-dom';
|
import { Routes, Route, Navigate } from 'react-router-dom';
|
||||||
import { Spin } from 'antd';
|
import { Spin } from 'antd';
|
||||||
import MainLayout from '@/components/Layout/MainLayout';
|
import MainLayout from '@/components/Layout/MainLayout';
|
||||||
import { generateRoutes } from '@/routes/routes';
|
import { allRoutes } from '@/routes/routes';
|
||||||
import Login from '@/pages/auth/Login';
|
import Login from '@/pages/auth/Login';
|
||||||
import NotFound from '@/pages/notFound';
|
import NotFound from '@/pages/notFound';
|
||||||
import Dashboard from '@/pages/Dashboard';
|
|
||||||
import { ProtectedRoute } from '@/components/auth/ProtectedRoute';
|
import { ProtectedRoute } from '@/components/auth/ProtectedRoute';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
|
||||||
@@ -14,19 +13,26 @@ const LoadingComponent = () => (
|
|||||||
<Spin size="large" />
|
<Spin size="large" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
// 这里做premeision 路由限制
|
|
||||||
|
// 渲染路由
|
||||||
const renderRoutes = (routes) => {
|
const renderRoutes = (routes) => {
|
||||||
return routes
|
if (!Array.isArray(routes)) {
|
||||||
// .filter(route => !route.hidden)
|
console.warn('routes is not an array:', routes);
|
||||||
.map(route => {
|
return [];
|
||||||
|
}
|
||||||
|
return routes.map(route => {
|
||||||
const Component = route.component;
|
const Component = route.component;
|
||||||
if (route.children) {
|
if (route.children) {
|
||||||
return (
|
return (
|
||||||
<Route key={route.path} path={route.path} element={
|
<Route
|
||||||
|
key={route.path}
|
||||||
|
path={route.path}
|
||||||
|
element={
|
||||||
<Suspense fallback={<LoadingComponent />}>
|
<Suspense fallback={<LoadingComponent />}>
|
||||||
<Component />
|
<Component />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}>
|
}
|
||||||
|
>
|
||||||
{renderRoutes(route.children)}
|
{renderRoutes(route.children)}
|
||||||
</Route>
|
</Route>
|
||||||
);
|
);
|
||||||
@@ -47,15 +53,26 @@ const renderRoutes = (routes) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AppRoutes = () => {
|
const AppRoutes = () => {
|
||||||
const { user } = useAuth();
|
const { user, loading } = useAuth();
|
||||||
const { adminRole: role } = user;
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center min-h-screen">
|
||||||
|
<Spin size="large" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path="/login"
|
path="/login"
|
||||||
element={
|
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>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{/* 默认重定向到服务管理页面 */}
|
||||||
<Route
|
<Route
|
||||||
index
|
index
|
||||||
element={<Navigate to="/company/serviceTemplate" replace />}
|
element={<Navigate to="/company/serviceTemplate" replace />}
|
||||||
/>
|
/>
|
||||||
{renderRoutes(generateRoutes(role))}
|
|
||||||
|
{/* 渲染所有路由 */}
|
||||||
|
{renderRoutes(allRoutes)}
|
||||||
|
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@@ -1,210 +1,180 @@
|
|||||||
import { lazy } from "react";
|
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: "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: "专案管理",
|
|
||||||
icon: "appstore",
|
|
||||||
roles: ["ADMIN", "OWNER"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "task",
|
|
||||||
component: lazy(() => import("@/pages/company/task")),
|
|
||||||
name: "任务管理",
|
|
||||||
icon: "appstore",
|
|
||||||
roles: ["OWNER"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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"],
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const marketingRoutes = [];
|
|
||||||
|
|
||||||
// const roleRoutes = [
|
|
||||||
// {
|
|
||||||
// path: "role",
|
|
||||||
// component: lazy(() => import("@/pages/role")),
|
|
||||||
// name: "角色管理",
|
|
||||||
// icon: "setting",
|
|
||||||
// roles: ["ADMIN", "OWNER"],
|
|
||||||
// },
|
|
||||||
// ];
|
|
||||||
|
|
||||||
export const generateRoutes = (role) => {
|
|
||||||
return [
|
|
||||||
{
|
{
|
||||||
path: "dashboard",
|
path: "dashboard",
|
||||||
component: lazy(() => import("@/pages/Dashboard")),
|
component: lazy(() => import("@/pages/Dashboard")),
|
||||||
name: "仪表盘",
|
name: "仪表盘",
|
||||||
icon: "dashboard",
|
icon: "dashboard",
|
||||||
roles: ["ADMIN", "OWNER", "MEMBER"],
|
key: "dashboard",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "resource",
|
path: "resource",
|
||||||
component: lazy(() => import("@/pages/resource")),
|
component: lazy(() => import("@/pages/resource")),
|
||||||
name: "资源管理",
|
name: "资源管理",
|
||||||
icon: "appstore",
|
icon: "appstore",
|
||||||
children: resourceRoutes.filter((route) => route.roles.includes(role)),
|
key: "resource",
|
||||||
roles: ["OWNER"],
|
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: "company",
|
path: "company",
|
||||||
component: lazy(() => import("@/pages/company")),
|
component: lazy(() => import("@/pages/company")),
|
||||||
name: "公司管理",
|
name: "公司管理",
|
||||||
icon: "bank",
|
icon: "bank",
|
||||||
children: companyRoutes.filter((route) => route.roles.includes(role)),
|
key: "company",
|
||||||
roles: ["ADMIN", "OWNER"],
|
children: [
|
||||||
|
{
|
||||||
|
path: "quotation",
|
||||||
|
component: lazy(() => import("@/pages/company/quotation")),
|
||||||
|
name: "报价单",
|
||||||
|
icon: "file",
|
||||||
|
key: "company/quotation",
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// path: "marketing",
|
path: "quotaInfo/:id?",
|
||||||
// component: lazy(() => import("@/pages/marketing")),
|
component: lazy(() => import("@/pages/company/quotation/detail")),
|
||||||
// name: "行销中心",
|
name: "报价单详情",
|
||||||
// icon: "shopping",
|
icon: "file",
|
||||||
// children: marketingRoutes.filter((route) => route.roles.includes(role)),
|
hidden: true,
|
||||||
// roles: ["ADMIN", "OWNER"],
|
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",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
// {
|
export const getRouteByKey = (key) => {
|
||||||
// path: "role",
|
const keys = key.split('/').filter(Boolean);
|
||||||
// component: lazy(() => import("@/pages/role")),
|
let current = allRoutes;
|
||||||
// name: "权限管理",
|
let result = null;
|
||||||
// icon: "setting",
|
|
||||||
// children: roleRoutes.filter((route) => route.roles.includes(role)),
|
keys.forEach(k => {
|
||||||
// roles: ["ADMIN", "OWNER"],
|
const found = current.find(r => r.path.split('/')[0] === k);
|
||||||
// },
|
if (found) {
|
||||||
].filter((route) => route.roles.includes(role));
|
result = found;
|
||||||
|
current = found.children || [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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 React from "react";
|
||||||
import { generateRoutes } from "@/routes/routes";
|
|
||||||
import * as AntIcons from "@ant-design/icons";
|
import * as AntIcons from "@ant-design/icons";
|
||||||
import { ColorIcon } from "@/components/Layout/ColorIcon";
|
import { ColorIcon } from "@/components/Layout/ColorIcon";
|
||||||
|
import { allRoutes } from "@/routes/routes";
|
||||||
|
|
||||||
const getAntIcon = (iconName) => {
|
const getAntIcon = (iconName) => {
|
||||||
const iconKey = `${iconName.charAt(0).toUpperCase()}${iconName.slice(
|
const iconKey = `${iconName.charAt(0).toUpperCase()}${iconName.slice(
|
||||||
@@ -10,25 +10,69 @@ const getAntIcon = (iconName) => {
|
|||||||
return AntIcons[iconKey] ? React.createElement(AntIcons[iconKey]) : null;
|
return AntIcons[iconKey] ? React.createElement(AntIcons[iconKey]) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateMenuItems = (routes, parentPath = "") => {
|
const generateMenuItems = (routes, menuKeys = [], parentPath = "") => {
|
||||||
return routes
|
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) => {
|
.map((route) => {
|
||||||
const fullPath = `${parentPath}/${route.path}`.replace(/\/+/g, "/");
|
const fullPath = `${parentPath}/${route.path}`.replace(/\/+/g, "/");
|
||||||
const icon = route.icon && <ColorIcon icon={getAntIcon(route.icon)} />;
|
const icon = route.icon && <ColorIcon icon={getAntIcon(route.icon)} />;
|
||||||
|
|
||||||
const menuItem = {
|
const menuItem = {
|
||||||
key: fullPath,
|
key: route.key, // 使用 key 而不是 fullPath
|
||||||
icon,
|
icon,
|
||||||
label: route.name,
|
label: route.name,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (route.children) {
|
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;
|
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