导言
由于很久没碰前端了,碰到路由都不太会了。趁着后端对接来记录一下,就当复习。不过由于个人能力有限,这篇会偏向整个过程的实现逻辑,其中有很多具体的方法不会给来,有兴趣的可以去看一下源码~
目的:
搞清楚若依路由侧边栏菜单生成及展示的逻辑
侧边栏菜单sidebar-item应用的数据格式,后端数据所必需的字段和格式
前端对后端传来数据的处理
逻辑:
1.整个菜单应用逻辑
1.前端接受后端对应的格式数据。
2.将其根据Element-ui的<el-menu>所需的格式及用户角色对应改造成适合的列表存vuex里。
3.在相应的布局页面(...\src\layout\components\Sidebar\index.vue)从vuex取出数据应用。
4.在侧边栏展示和vue路由管理的对应跳转可以实现点击菜单跳转到对应页面
整体过程
1.前端请求与接收
1.路由守卫调用GenerateRoutes
在...\src\permission.js,有token且不在登录页。
store.dispatch('GenerateRoutes').then(accessRoutes => {
// 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
调的是...src\store\modules\permission.js的,名字一样。
这里调完之后会把处理好的路由塞进路由表里
2.GenerateRoutes:
// 生成路由
GenerateRoutes({ commit }) {
// 返回一个新的 Promise
return new Promise(resolve => {
// 向后端请求路由数据
getRouters().then(res => {
console.log("后端返回的数据:",res);
...
});
});
}
主要是承担请求后端,改造数据和存数据到vuex的功能,代码2.数据改造有
3.请求方法:
// 获取路由
export const getRouters = () => {
return request({
headers: {
isToken: true
},
url: 'api/Menus/GetUserMenus',
method: 'get'
})
}
2.数据改造及存储
在...\src\store\modules\permission.js
// 生成路由
GenerateRoutes({ commit }) {
// 返回一个新的 Promise
return new Promise(resolve => {
// 向后端请求路由数据
getRouters().then(res => {
console.log("后端返回的数据:",res);
// 深拷贝后端返回的路由数据
const sdata = JSON.parse(JSON.stringify(res.data));
const rdata = JSON.parse(JSON.stringify(res.data));
// 过滤出适合侧边栏的路由
const sidebarRoutes = filterAsyncRouter(sdata);
// 过滤出重写的路由
const rewriteRoutes = filterAsyncRouter(rdata, false, true);
// 过滤出动态路由
const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
// 添加一个重定向路由,处理未匹配的路径
rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true });
// 将动态路由添加到 Vue Router
router.addRoutes(asyncRoutes);
// 提交 mutations 更新路由状态
commit('SET_ROUTES', rewriteRoutes);
commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes));
commit('SET_DEFAULT_ROUTES', sidebarRoutes);
commit('SET_TOPBAR_ROUTES', sidebarRoutes);
// 解析 Promise,返回重写的路由
resolve(rewriteRoutes);
});
});
}
其中的三类路由:
sidebarRoutes:这些路由用于生成侧边栏菜单。它们通常是与用户可视导航相关的路由,
因此侧边栏会根据用户的角色或权限显示不同的菜单结构。
rewriteRoutes:这些路由包含重写逻辑,主要用于处理应用中的实际导航和访问控制。
可以理解为对原始路由的一个“过滤”或“优化”,确保在需要时能够准确地导航或限制用户的访问。
asyncRoutes:这些路由仅在特定条件下加载。它们通常与权限控制相关,因此需要在运行时动态地添加到
Vue Router。通过这种方式,可以在不重新加载页面的情况下,根据用户角色或权限控制对不同路由的访问。
3.侧边栏菜单数据应用:
在...\src\layout\components\Sidebar\index.vue侧边栏组件
数据来源sidebarRouters是拿GenerateRoutes存进vuex的侧边栏路由菜单
//...\src\store\modules\permission.js
SET_DEFAULT_ROUTES: (state, routes) => {
state.defaultRoutes = constantRoutes.concat(routes)
},
//...\src\layout\components\Sidebar\index.vue
...mapGetters(["sidebarRouters", "sidebar"]),
这个组件定义了侧边栏的样式等,主要还是sidebar-item组件
<template>
<div :class="{'has-logo':showLogo}" :style="{ backgroundColor: settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar :class="settings.sideTheme" wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground"
:text-color="settings.sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor"
:unique-opened="true"
:active-text-color="settings.theme"
:collapse-transition="false"
mode="vertical"
>
<!-- 遍历 sidebarRouters 数组,生成菜单项 -->
<!-- 使用路由路径和索引生成唯一的 key -->
<!-- 将当前路由项传递给 sidebar-item 组件 -->
<!-- 将当前路由的路径传递给 sidebar-item 组件 -->
<sidebar-item
v-for="(route, index) in sidebarRouters"
:key="route.path + index"
:item="route"
:base-path="route.path"
/>
</el-menu>
</el-scrollbar>
</div>
</template>
菜单组件sidebar-item介绍及其所需要的东西
介绍:
sidebar-item是若依自定义的一个组件,在...\src\layout\components\Sidebar\SidebarItem.vue
用于展示单个菜单项(侧边栏)。
根据...\src\layout\components\Sidebar\index.vue传过来的item,isNest,basePath等来确定菜单的展示样式(是否展示,是否下拉...)
源码学习:
template,用hidden控制是否展示,之后分两部分
1.传来的菜单只有一个子菜单/没有子菜单:
<!-- 判断是否只有一个显示的子项且满足其他条件 -->
<!-- && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && 要么 onlyOneChild 没有子项,要么它的所有子项都不可见。 -->
<template
v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
<!-- 如果存在子项的 meta,使用 app-link 组件进行导航 -->
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
<!-- 渲染一个菜单项,index 为子项的路径 -->
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
<!-- 显示子项的图标和标题,如果没有图标,则使用父项的图标 -->
<item :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" :title="onlyOneChild.meta.title" />
</el-menu-item>
</app-link>
</template>
这段的处理就相当于把没有子菜单的直接展示,把只有一个子菜单的直接显示子菜单
如下图,系统管理只有菜单管理一个子菜单,所以直接展示了菜单管理:
hasOneShowingChild方法分三种情况:
// 当存在且仅有一个可见的子项时:
// 如果 showingChildren.length === 1,则返回 true,表示有一个可显示的子项。
// 当没有可见子项时:
// 如果 showingChildren.length === 0,则会设置 this.onlyOneChild 为一个新的对象,且返回 true。这表示在没有显示的子项的情况下,父项仍然需要被显示。
// 如果有多个可见子项时:
// 如果有两个或更多可见子项,则返回 false,表示没有唯一的可显示子项。
hasOneShowingChild(children = [], parent) {
if (!children) {
children = [];
}
const showingChildren = children.filter(item => {
if (item.hidden) {
return false
} else {
// Temp set(will be used if only has one showing child)
this.onlyOneChild = item
return true
}
})
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
this.onlyOneChild = { ...parent, path: '', noShowingChildren: true }
return true
}
return false
},
2.第二部分:
绑定完当前菜单后嵌套调sidebar-item,循环绑该菜单项的子菜单
<!-- 如果不满足上述条件,则渲染一个子菜单 -->
<!-- :index 用于设置 <el-submenu> 组件的唯一标识符。这是一个字符串或路径,表示菜单项的唯一键,通常用于导航或路由。 -->
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
<template slot="title">
<!-- 渲染子菜单的标题,如果存在 meta,则显示图标和标题 -->
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
</template>
<!-- :basepath 是一个自定义的 prop,用于传递子菜单项的基础路径。
<sidebar-item> 组件中的 basepath 主要用于生成子项的相对路径,或者作为子项路径的起点。 -->
<!-- 循环调用绑子菜单 -->
<sidebar-item v-for="child in item.children" :key="child.path" :is-nest="true" :item="child"
:basepath="resolvePath(child.path)" class="nest-menu" />-
</el-submenu>
3.resolvePath方法
//用于解析并生成完整的导航路径
resolvePath(routePath, routeQuery) {
console.log("routePath",routePath);
console.log("分割符");
if (isExternal(routePath)) {//是外链
return routePath
}
//如果 basePath 是外部链接,则返回 basePath。在这种情况下,
//routePath 被忽略,最终导航会指向 basePath,这是因为 basePath 已经是一个完整的外部链接。
if (isExternal(this.basePath)) {
return this.basePath
}
// 如果提供了 routeQuery,则解析查询字符串,并生成包含 path 和 query 的对象:
if (routeQuery) {
let query = JSON.parse(routeQuery);
//返回的对象包含 path 和 query 字段,以便在导航时附加查询参数,如字典数据
return { path: path.resolve(this.basePath, routePath), query: query }
}
//如果 routeQuery 为空,且路径都为内部链接,则使用 path.resolve(this.basePath, routePath) 将 routePath 解析为基于 basePath 的完整路径。
return path.resolve(this.basePath, routePath)
}
4.有关alwaysShow
在路由表有相关介绍,我这没用这个
/**
* Note: 路由配置项
*
* hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
* alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
* // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
* // 若你想不管路由下面的 children 声明的个数都显示你的根路由
* // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
...
*/
所需要的东西:
大概长这样:
侧边栏菜单和路由表的关系
-
数据来源:
- 侧边栏菜单的构建通常基于路由表。路由表定义了所有可用的路由及其对应的组件、权限等信息。
- 当从后端获取路由数据时,侧边栏菜单会根据这些数据生成相应的菜单项。
-
动态生成:
- 当用户的权限发生变化(如角色变更)时,可能需要重新生成可访问的路由和侧边栏菜单。
- 例如,
GenerateRoutes
方法从后端获取路由数据后,通过权限过滤生成accessRoutes
,同时生成相应的侧边栏菜单。
-
结构一致性:
- 侧边栏菜单和路由表通常遵循相同的数据结构。侧边栏菜单的每个菜单项通常会对应路由表中的一条路由,这样在导航时可以确保用户点击菜单后,能够正确匹配到相应的组件。
-
管理与更新:
- 在
router.addRoutes(accessRoutes)
中,动态添加可访问的路由表。这意味着应用程序的路由结构是可变的,可以根据用户的角色和权限动态更新。 - 同时,更新路由表后,侧边栏菜单也会根据新的路由数据进行重新渲染,以反映最新的导航结构。
- 在
-
导航功能:
- 用户通过侧边栏菜单进行导航,实际背后的路由表则决定了哪些组件被加载和渲染。因此,侧边栏菜单的可用性和功能直接依赖于路由表的配置。
侧边栏菜单是用户与应用程序交互的界面,而路由表则是管理应用程序导航和组件渲染的核心结构。两者通过相同的数据模型和动态更新机制,保持一致性和功能性。
总结:
搞了很多天,主要还是基础和思路,很多时间都不知道自己在干嘛,其中的很多细节都还没搞懂,不过写了这篇,也大概能捋一下思路。