【开发】3.3-路由和菜单

本文详细介绍了一个应用程序中路由和菜单的基本结构、配置与管理方法。涵盖了路由配置、菜单生成、面包屑组件、从服务器请求菜单等功能,以及如何实现特定需求如菜单跳转、新增页面与布局、使用自定义图标和带参数的路由。

路由和菜单是组织起一个应用的关键骨架,pro 中的路由为了方便管理,使用了中心化的方式,在 config.ts 统一配置和管理。

1. 基本结构

在这一部分,脚手架通过结合一些配置文件、基本算法及工具函数,搭建好了路由和菜单的基本框架,主要涉及以下几个模块/功能:

  • 路由管理 通过约定的语法根据在 config.ts 中配置路由。
  • 菜单生成 根据路由配置来生成菜单。菜单项名称,嵌套路径与路由高度耦合。
  • 面包屑 组件 PageHeaderWrapper 中内置的面包屑,也可通过 RouteContext 提供的信息自定义生成。

下面简单介绍下各个模块的基本思路,如果你对实现过程不感兴趣,只想了解应该怎么实现相关需求,可以直接查看需求实例

1.1 路由

目前脚手架中所有的路由都通过 config.ts 来统一管理,在 umi 的配置中我们增加了一些参数,如 nameiconhideChildrenInMenuauthority,来辅助生成菜单。其中:

  • nameicon分别代表生成菜单项的文本和图标。

  • hideChildrenInMenu 用于隐藏不需要在菜单中展示的子路由。用法可以查看分步表单的配置。

  • hideInMenu 可以在菜单中不展示这个路由,包括子路由。

  • authority 用来配置这个路由的权限,如果配置了将会验证当前用户的权限,并决定是否展示。

你可能注意到配置中的 name 和菜单实际展示的不同,这是因为我们使用了全球化组件的原因,具体参见 i18n

1.2 菜单

菜单根据 config.ts 生成。

如果你的项目并不需要菜单,你可以在 src/layouts/BasicLayout.tsx 中设置 menuRender={false}

1.3 从服务器请求菜单

你可以在 src/layouts/BasicLayout.tsx 中修改 menuDataRender,并在代码中发起 http 请求,只需服务器返回下面格式的 json 即可。

const [menuData, setMenuData] = useState([]);

useEffect(() => {
  // 这里是一个演示用法
  // 真实项目中建议使用 dva dispatch 或者 umi-request
  fetch('/api/example.json')
    .then(response => response.json())
    .then(data => {
      setMenuData(data || []);
    });
}, []);

...

return (
  <ProLayout
    // ...
    menuDataRender={() => menuData}
    // ...
  />
);

menuData 数据格式如下,ts 定义在此:MenuDataItem.

[
  {
    "path": "/dashboard",
    "name": "dashboard",
    "icon": "dashboard",
    "children": [
      {
        "path": "/dashboard/analysis",
        "name": "analysis"
      },
      {
        "path": "/dashboard/monitor",
        "name": "monitor"
      },
      {
        "path": "/dashboard/workplace",
        "name": "workplace"
      }
    ]
  }
  // ....
]

注意 path 必须要在 config.ts 中定义。(约定式路由不需要,只需页面真实有效即可)注意 如果没有选用 typescript,config.js 中菜单的嵌套写法,需要把 “children” 换成 “routes”

1.4 面包屑

面包屑由 PageHeaderWrapper 实现,Layout 将 根据 MenuData 生成的 breadcrumb,并通过 PageHeaderWrapper 将其展现。 PageHeaderWrapper 封装至 Ant Design 的 PageHeader,api 完全相同。

2. 需求实例

上面对这部分的实现概要进行了介绍,接下来通过实际的案例来说明具体该怎么做。

2.1 菜单跳转到外部地址

你可以直接将完整 url 填入 path 中,框架会自动处理。

{
    path: 'https://pro.ant.design/docs/getting-started-cn',
    target: '_blank', // 点击新窗口打开
    name: "文档",
}

如果你需要自定义 menuItem 的点击逻辑,你可以通过 menuItemRender 的来实现。

2.2 新增页面

通过区块新增请阅读 Block

脚手架默认提供了两种布局模板:基础布局 - BasicLayout 以及 账户相关布局 - UserLayout

在这里插入图片描述

在这里插入图片描述

如果你的页面可以利用这两种布局,那么只需要在路由配置中增加一条即可:

// app
  {
    path: '/',
    component: '../layouts/BasicLayout',
    routes: [
      // dashboard
      { path: '/', redirect: '/dashboard/analysis' },
      { path :'/dashboard/test',component:"./Dashboard/Test"},
    ...
},

加好后,会默认生成相关的路由及导航。

2.3 新增布局

在脚手架中我们通过嵌套路由来实现布局模板。config.ts 是一个数组,其中第一级数据就是我们的布局,如果你需要新增布局可以再直接增加一个新的一级数据。

module.exports = [
   // user
   {
    path: '/user',
    component: '../layouts/UserLayout',
    routes:[...]
   },
   // app
   {
    path: '/',
    component: '../layouts/BasicLayout',
    routes:[...]
   },
   // new
   {
    path: '/new',
    component: '../layouts/new_page',
    routes:[...]
   },
]

2.4 在菜单中使用自定义图标

由于 umi 的限制,在 config.ts 是不能直接使用 React 组件的,Pro 中暂时支持使用 ant.design 本身的 icon type,和传入一个 img 的 url。只需要直接在 icon 属性上配置即可,如果是个 url,Pro 会自动处理为一个 img 标签。

如果你想使用 iconfont 的图标,你可以使用 ant.design 的自定义图标.

2.5 带参数的路由

脚手架默认支持带参数的路由,但是在菜单中显示带参数的路由并不是个好主意,我们并不会自动的帮你注入一个参数,你可能需要在代码中自行处理。

{
  path: '/dashboard/:page',
  hideInMenu:true,
  name: 'analysis',
  component: './Dashboard/Analysis',
},

你可以通过以下代码来跳转到这个路由:

import router from 'umi/router';

router.push('/dashboard/anyParams');

//or

import Link from 'umi/link';

<Link to="/dashboard/anyParams">go</Link>;

在路由组件中,可以通过this.props.match.params 来获得路由参数。更多详细内容请参见:umi#路由

在 Vue3 项目中实现基于角色的菜单控制动态路由配置,通常需要结合路由管理、状态管理以及权限判断机制来完成。以下是实现这一功能的核心方法步骤: ### 3.1 动态路由配置 Vue3 中使用 `vue-router` 实现动态路由,主要通过 `router.addRoute()` 方法实现路由的动态添加。通常在用户登录后,根据其角色权限从接口获取对应的路由信息,然后将这些路由动态添加到 `router` 中。 - 路由信息可以由后端返回,也可以在前端预定义,具体取决于项目架构。 - 路由模块通常定义在 `router/index.ts` 中,其中包含基础路由(如登录页)异步加载的动态路由模块 [^1]。 示例代码如下: ```ts // router/index.ts import { createRouter, createWebHistory, RouteRecordRaw } from &#39;vue-router&#39; const routes: Array<RouteRecordRaw> = [ { path: &#39;/login&#39;, name: &#39;Login&#39;, component: () => import(&#39;../views/Login.vue&#39;) }, // 动态路由将在此后通过 addRoute 添加 ] const router = createRouter({ history: createWebHistory(), routes }) export default router ``` ### 3.2 权限控制与角色映射 为了实现基于角色的菜单控制,通常需要将角色与路由进行映射。前端可以通过一个 `permission` 模块,在用户登录后根据角色信息从接口获取对应的菜单数据(例如 `/api/menu`),并将其转换为可添加的路由对象 。 - 菜单数据结构通常包括路径、组件名、权限标识等字段。 - 使用 `store/permission` 模块来管理权限状态,例如当前用户的角色、可访问的菜单列表等 [^1]。 ### 3.3 菜单渲染与权限指令 在左侧菜单栏中渲染动态菜单项时,通常会结合 Vuex(或 Pinia)中的权限状态进行过滤显示。可以通过全局指令来实现按钮级别的权限控制。 - 全局指令如 `v-permission` 可用于控制某个按钮或组件是否显示。 - 指令逻辑中判断当前用户是否具备所需权限,例如: ```ts // directives/permission.ts export default { mounted(el, binding) { const { value } = binding const permissions = [&#39;admin&#39;, &#39;editor&#39;] // 模拟当前用户权限 if (value && !permissions.includes(value)) { el.parentNode.removeChild(el) } } } ``` - 在组件中使用方式如下: ```vue <template> <el-button v-permission="&#39;admin&#39;">仅管理员可见</el-button> </template> ``` ### 3.4 路由守卫与权限验证 在 `router.beforeEach()` 中进行权限验证,判断用户是否具有访问目标路由的权限: ```ts // router/permission.ts import router from &#39;./index&#39; import store from &#39;@/store&#39; router.beforeEach((to, from, next) => { const token = store.getters.token if (token) { if (to.path === &#39;/login&#39;) { next({ path: &#39;/&#39; }) } else { const hasRoutes = store.getters.addedRoutes if (hasRoutes) { next() } else { store.dispatch(&#39;generateRoutes&#39;).then(routes => { routes.forEach(route => { router.addRoute(route) }) next({ ...to, replace: true }) }) } } } else { if (to.meta.requiresAuth) { next(&#39;/login&#39;) } else { next() } } }) ``` ### 3.5 Mock 接口模拟数据 在开发阶段,可以使用 Mock.js 或者本地模拟接口来模拟 `/api/menu` 接口返回的菜单数据,以便测试动态路由菜单渲染功能 [^1]。 示例数据结构如下: ```json [ { "name": "Dashboard", "path": "/dashboard", "component": "Layout", "children": [ { "name": "Home", "path": "home", "component": "views/Dashboard/Home.vue" } ] } ] ``` ### 3.6 样式与全局组件 在 `main.ts` 中引入 Element Plus(或 Element UI 的 Vue3 兼容版本),并注册全局组件、过滤器、指令等,确保权限控制组件可以全局使用 [^2]。 ```ts import { createApp } from &#39;vue&#39; import App from &#39;./App.vue&#39; import router from &#39;./router&#39; import store from &#39;./store&#39; import ElementPlus from &#39;element-plus&#39; import &#39;element-plus/dist/index.css&#39; import * as directives from &#39;./directives&#39; const app = createApp(App) Object.keys(directives).forEach(key => { app.directive(key, directives[key]) }) app.use(router).use(store).use(ElementPlus).mount(&#39;#app&#39;) ``` ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值