需求
前端页面用户登录成功时,接口返回用户菜单,菜单里面包含新增的路由信息,将菜单解析成新增路由,并将其添加到router里面,以及进行持久化操作
配置alias
vue项目在经过编译打包之后,后期无法使用import 导入组件,并且项目路径发生了改变,也无法简单使用require 动态导入组件,这里解决方法是配置alias ,同时用resolve 配合require 一起使用。
配置如下:
安装webpack (已安装则跳过),这里可以直接复制以下代码到package.json 文件中的"devDependencies" 属性中,重新install 即可。
"webpack": "^5.37.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.9.0",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.7.3",
"webpackbar": "^5.0.0-3
复制以下代码到vue.config.js 配置文件中,可以使用开启alia 功能,使用@ 符号替代src
const { defineConfig } = require('@vue/cli-service')
const path = require('path');
function resolve(dir){
return path.join(__dirname,dir)
}
module.exports = defineConfig({
transpileDependencies: true,
devServer:{
port:8080,
},
chainWebpack:(config)=>{
config.resolve.alias
.set('@',resolve('./src'))
.set('components',resolve('./src/components'))
.set('views',resolve('./src/views'))
.set('assets',resolve('./src/assets'))
},
publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
outputDir: 'dist',
assetsDir: 'static',
productionSourceMap: false,
lintOnSave: false
})
router配置
src/router/index.js 文件
import Router from 'vue-router'
import store from "../store/index";
const menuRoot={
path:'/home',
name:'home',
component:()=>import('@/views/home/Home'),
redirect:'/home/index',
meta:{
title: '首页',
}
}
const commonRouter=[
{
path: "/",
redirect: "/login"
},
{
path:'/login',
name:'login',
component:()=>import('@/views/login/Login.vue'),
meta:{
title:'登录'
}
},
{
path:'/404',
name:'404',
component:()=>import('@/views/error/404'),
meta:{
title:'404'
}
}
]
const Router_404={
path:'*',
redirect: '/404'
}
const menuRouter=function(menus,res,parentPath,parentName){
res=res||[];
menus&&menus.forEach(elem=>{
const path=!parentPath?elem.path:parentPath+'/'+elem.path;
const name=!parentName?elem.name:parentName+'-'+elem.name;
if(!elem.children){
let obj={...elem};
obj.path=path;
obj.name=name;
obj.component=resolve=>(require([`components/${elem.component}`],resolve));
res.push(obj);
}else{
menuRouter(elem.children,res,path,name);
}
})
return res;
}
const createRouter = () => {
return new Router({
mode:'history',
scrollBehavior: () => ({ y: 0 }),
routes: commonRouter
})
}
let router=createRouter();
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher
}
export async function createMenuRouter(menus){
resetRouter();
return new Promise((resolve)=>{
const menuRouterTemp={...menuRoot};
menuRouterTemp.children=menuRouter(menus);
router.options.routes.push(menuRouterTemp);
router.addRoute(menuRouterTemp);
router.addRoute(Router_404);
resolve();
})
}
router.beforeEach((to,from,next)=>{
if(to.path==='/login'||to.path==='/404') {
next();
}else{
if(store.getters.token){
let menuRouterIsExist=false,item=router.options.routes;
for(let index in item){
if(item[index].name==='home'){
menuRouterIsExist=true;
break;
}
}
if(!menuRouterIsExist){
createMenuRouter(store.getters.menus).then(()=>{
next({...to, replace:true})
}).catch(()=>{
next('/login');
})
}else{
next();
}
}else{
next('/login');
}
}
})
router.afterEach((to)=>{
if(!to.meta.title){
document.title = to.meta.title
}
})
export default router;
在main.js 中导入路由
...
import router from "./router";
...
new Vue({
render: h => h(App),
router,
store,
beforeCreate() {
Vue.prototype.$bus=this;
}
}).$mount('#app')
路由持久化
安装js-cookie
npm install --save js-cookie
将接口返回的路由信息存到Vuex 和cookie 中
src/store/moudles/user.js 文件:
import request from "../../http/request";
import Cookies from 'js-cookie'
import {createMenuRouter} from "@/router";
const state={
token:Cookies.get('Authorization')||null,
name:'',
account:'',
roles:[],
menus:Cookies.get('menus')?JSON.parse(Cookies.get('menus')):[]
}
const mutations={
setToken(state,token){
state.token=token;
},
setName(state,name){
state.name=name;
},
setAccount(state,account){
state.account=account
},
setRoles(state,roles){
state.roles=roles
},
setMenus(state,menus){
state.menus=menus;
}
}
const actions={
async login({commit},{account,password}){
return new Promise((resolve,reject)=>{
request.post('user/login',{
account,
password
}).then(res=>{
if(res.code===200){
Cookies.set('Authorization',res.data.token,{expires:1});
Cookies.set('menus',JSON.stringify(res.data.menus),{expires:1});
commit('setToken',res.data.token);
commit('setMenus',res.data.menus);
createMenuRouter(res.data.menus)
}
resolve({
code:res.code,
msg:res.msg
})
}).catch(error=>{
reject(error)
})
})
}
}
export default {
namespaced:true,
state,
mutations,
actions
}
src/store/getters.js 文件:
const getters={
token:state=>state.user.token,
name:state=>state.user.name,
account:state=>state.user.account,
roles:state=>state.user.roles,
menus:state=>state.user.menus
}
export default getters
src/store/index.js 文件:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import getters from "./getters";
import user from './moudules/user'
const modules= {
user
}
export default new Vuex.Store({
modules,
getters
})
登录模块
this.loginLoading=true;
this.$store.dispatch('user/login',this.loginForm).then(res=>{
if(res.code===200){
this.$message.success(res.msg);
setTimeout(()=>{
router.push('/home');
},200);
} else{
this.$message.error(res.msg);
}
this.loginLoading=false;
}).catch(()=>{
this.$message.error("登录失败");
this.loginLoading=false;
})
接口返回值
user/login 接口返回值如下 menus 数据里面只有叶子节点有component 属性,最后会将多级菜单压缩成一层路由,并将该层路由赋值给指定路由的children 属性 menus 数据还用于渲染管理页面的左侧菜单
{
code: 200,
msg: "登录成功",
data: {
token:'abcdefg',
menus: [
{
"path": "index",
"name": "indexxx",
"component": "Index",
"meta": {
"title": "首页",
"icon": "el-icon-location"
}
},
{
"path": "menu1",
"name": "menu1",
"meta": {
"title": "多级菜单1",
"icon": "el-icon-news"
},
"children": [
{
"path": "subMenu1",
"name": "subMenu1",
"component": "TestOne",
"meta": {
"name": "测试名1",
"title": "测试标题",
"icon": "el-icon-location"
}
},
{
"path": "subMenu2",
"name": "subMenu2",
"meta": {
"name": "测试名2",
"title": "测试标题2",
"icon": "el-icon-location"
},
"children": [
{
"path": "subMenu1",
"name": "subMenu1",
"component": "TestOne",
"meta": {
"name": "测试名1",
"title": "测试标题",
"icon": "el-icon-location"
}
},
{
"path": "subMenu2",
"name": "subMenu2",
"component": "TestTwo",
"meta": {
"name": "测试名2",
"title": "测试标题2",
"icon": "el-icon-location"
}
}
]
}
]
},
{
"path": "index12",
"name": "index12",
"component": "Index",
"meta": {
"title": "首页2",
"icon": "el-icon-location"
}
},
{
"path": "index122",
"name": "index122",
"component": "Index",
"meta": {
"title": "首页2",
"icon": "el-icon-location"
}
},
{
"path": "index123",
"name": "index123",
"component": "Index",
"meta": {
"title": "首页2",
"icon": "el-icon-location"
}
},
{
"path": "index124",
"name": "index124",
"component": "Index",
"meta": {
"title": "首页2",
"icon": "el-icon-location"
}
},
{
"path": "index125",
"name": "index125",
"component": "Index",
"meta": {
"title": "首页2",
"icon": "el-icon-location"
}
},
{
"path": "index126",
"name": "index126",
"component": "Index",
"meta": {
"title": "首页2",
"icon": "el-icon-location"
}
},
{
"path": "index127",
"name": "index127",
"component": "Index",
"meta": {
"title": "首页2",
"icon": "el-icon-location"
}
},
{
"path": "index128",
"name": "index128",
"component": "Index",
"meta": {
"title": "首页2",
"icon": "el-icon-location"
}
},
{
"path": "index129",
"name": "index129",
"component": "Index",
"meta": {
"title": "首页2",
"icon": "el-icon-location"
}
},
{
"path": "index1211",
"name": "index1211",
"component": "Index",
"meta": {
"title": "首页2",
"icon": "el-icon-location"
}
},
{
"path": "index1212",
"name": "index1212",
"component": "Index",
"meta": {
"title": "首页2",
"icon": "el-icon-location"
}
},
{
"path": "index1213",
"name": "index1213",
"component": "Index",
"meta": {
"title": "首页2",
"icon": "el-icon-location"
}
}
]
}
}
菜单组件
<template>
<div class="menu-wrapper">
<template v-for="item in menu">
<!-- 最后一级菜单 -->
<el-menu-item
v-if="!item.children"
:key="item.path"
:index="parent ? parent + '/' + item.path : item.path"
>
<i :class="item.meta.icon" class="iconStyle"></i>
<span slot="title">{{ item.meta.title.trim() }}</span>
</el-menu-item>
<!-- 此菜单下还有子菜单 -->
<el-submenu
v-if="item.children"
:key="item.path"
:index="parent ? parent + '/' + item.path : item.path">
<template slot="title">
<i :class="item.meta.icon" class="iconStyle"></i>
<span slot="title">{{ item.meta.title.trim() }}</span>
</template>
<!-- 递归 -->
<sidebar-item
:menu="item.children"
:parent="parent ? parent + '/' + item.path : item.path"/>
</el-submenu>
</template>
</div>
</template>
<script>
export default {
name: "SidebarItem",
props: ["menu", "parent"]
};
</script>
<style scoped>
.iconStyle{
font-size: 20px;
padding-right: 10px;
color: rgb(191, 203, 217);
}
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 100%;
}
/*隐藏文字*/
.el-menu--collapse .el-submenu__title span{
display: none;
}
/*隐藏 > */
.el-menu--collapse .el-submenu__title .el-submenu__icon-arrow{
display: none;
}
:deep(.el-menu-item:hover),:deep(.el-submenu__title:hover){
background-color: #1f2d3d !important;
}
</style>
<el-menu :collapse="asideIsCollapse"
collapse-transition="collapse-transition"
text-color="rgb(191, 203, 217)"
active-text-color="rgb(64, 158, 255)"
router
background-color="rgb(48, 65, 86)">
<SidebarItem :menu="this.$store.getters.menus" parent="/home"></SidebarItem>
</el-menu>
效果演示
该项目存在一定的保密性,所以这里只能放出动态路由代码,这里给出效果演示 如图所示:login界面有三个根路由,这个404路由基本上用不到,也可以登录成功时再加上去 如图所示:这是登录成功之后的页面
|