From 34550d051757f177b9f921be24284fda9d75fe41 Mon Sep 17 00:00:00 2001 From: liamzi Date: Tue, 14 Jan 2025 18:52:03 +0800 Subject: [PATCH] =?UTF-8?q?=E8=8F=9C=E5=8D=95=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Layout/Header.jsx | 10 +- src/components/Layout/Sidebar.jsx | 22 +- src/components/auth/ProtectedRoute.jsx | 33 ++- src/contexts/AuthContext.jsx | 27 +- src/pages/Settings.jsx | 40 --- src/pages/resource/menu/index.jsx | 283 +++++++++++++++++++ src/pages/resource/role/index.jsx | 285 ++++++++++++------- src/routes/AppRoutes.jsx | 79 ++++-- src/routes/routes.js | 362 ++++++++++++------------- src/utils/menuUtils.jsx | 58 +++- 10 files changed, 790 insertions(+), 409 deletions(-) delete mode 100644 src/pages/Settings.jsx create mode 100644 src/pages/resource/menu/index.jsx diff --git a/src/components/Layout/Header.jsx b/src/components/Layout/Header.jsx index aaecc5e..9e38413 100644 --- a/src/components/Layout/Header.jsx +++ b/src/components/Layout/Header.jsx @@ -20,11 +20,11 @@ const Header = ({ collapsed, setCollapsed }) => { }; const userMenuItems = [ - { - key: "profile", - icon: , - label: "个人信息", - }, + // { + // key: "profile", + // icon: , + // label: "个人信息", + // }, { key: "logout", icon: , diff --git a/src/components/Layout/Sidebar.jsx b/src/components/Layout/Sidebar.jsx index 2d7df68..807cfa8 100644 --- a/src/components/Layout/Sidebar.jsx +++ b/src/components/Layout/Sidebar.jsx @@ -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" : ""}`} > { mode="inline" selectedKeys={[location.pathname]} defaultOpenKeys={defaultOpenKeys} - items={menuItems} + items={menuClient} onClick={handleMenuClick} /> diff --git a/src/components/auth/ProtectedRoute.jsx b/src/components/auth/ProtectedRoute.jsx index e6976fb..ae2c601 100644 --- a/src/components/auth/ProtectedRoute.jsx +++ b/src/components/auth/ProtectedRoute.jsx @@ -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 ( -
- -
- ); + 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 ; + } + + return children; } - if (!user?.id) { - return ( - - ); - } - - return children; + // 如果用户未登录,重定向到登录页 + return ; }; diff --git a/src/contexts/AuthContext.jsx b/src/contexts/AuthContext.jsx index 8c84f5b..3589d9c 100644 --- a/src/contexts/AuthContext.jsx +++ b/src/contexts/AuthContext.jsx @@ -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 { diff --git a/src/pages/Settings.jsx b/src/pages/Settings.jsx deleted file mode 100644 index fceb56e..0000000 --- a/src/pages/Settings.jsx +++ /dev/null @@ -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 ( - -
- - - - - - - -
-
- ); -}; - -export default Settings; \ No newline at end of file diff --git a/src/pages/resource/menu/index.jsx b/src/pages/resource/menu/index.jsx new file mode 100644 index 0000000..f0b8bd7 --- /dev/null +++ b/src/pages/resource/menu/index.jsx @@ -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) => ( + + + + + ), + }, + ]; + + // 获取菜单数据 + 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 ( +
+ +
+ 菜单权限管理 + +
+ + + + form.submit()} + onCancel={() => setIsModalVisible(false)} + confirmLoading={loading} + > +
+ + + ( - <> - {menu} - - - - )} - > - {roles.map(role => ( - - {role.name} - - ))} - - + const renderFormItems = () => { + const [roleSelectOpen, setRoleSelectOpen] = useState(false); + const [resourceSelectOpen, setResourceSelectOpen] = useState(false); - - - + + - - - - - ); + + + + + + + + + ); + }; return ( @@ -535,18 +606,26 @@ export default function PermissionManagement() { {/* 添加资源 Modal */} { setResourceModalVisible(false); + setEditingResource(null); resourceForm.resetFields(); }} onOk={() => resourceForm.submit()} + zIndex={1100} > { + if (editingResource) { + handleUpdateResource(values); + } else { + handleAddResource(values); + } + }} > ( ); -// 这里做premeision 路由限制 -const renderRoutes = (routes) => { - return routes - // .filter(route => !route.hidden) - .map(route => { - const Component = route.component; - if (route.children) { - return ( - }> - - - }> - {renderRoutes(route.children)} - - ); - } +// 渲染路由 +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 ( - }> } - /> + > + {renderRoutes(route.children)} + ); - }); + } + + return ( + }> + + + } + /> + ); + }); }; const AppRoutes = () => { - const { user } = useAuth(); - const { adminRole: role } = user; + const { user, loading } = useAuth(); + + if (loading) { + return ( +
+ +
+ ); + } return ( : + user?.id && user.menukeys?.includes('company/serviceTemplate') ? ( + + ) : ( + + ) } /> @@ -67,11 +84,15 @@ const AppRoutes = () => { } > - } /> - {renderRoutes(generateRoutes(role))} + + {/* 渲染所有路由 */} + {renderRoutes(allRoutes)} + } /> diff --git a/src/routes/routes.js b/src/routes/routes.js index 5a02bb2..23cb9c8 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -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 + })); +}; \ No newline at end of file diff --git a/src/utils/menuUtils.jsx b/src/utils/menuUtils.jsx index f59f487..ca0ee54 100644 --- a/src/utils/menuUtils.jsx +++ b/src/utils/menuUtils.jsx @@ -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 && ; 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 };