前言
整体流程大概说下,在登陆成功之后,需要后端把角色权限的路由返给我们,我们再根据角色权限把后端给的路由在本地存储,在router/index 的路由控制页面用beforeEach钩子函数中做动态路由的处理,把角色权限渲染到页面中。
一、login页面处理
登陆成功之后需要把拿到的路由固定json格式的数据通过localStorage.setItem进行本地存储,方便后续的使用,有两个地方用到了,我先说明一下,一个是beforeEach,一个是utils.menus用到了(这个文件会在后边提到,以及使用方式),还有就是最重要的后端返回的数据格式!!一定要按着这个格式来,非常重要,不然后续没法加载路由!!后端返回的数据格式如下:
children上边的对于工单管理,children下边部分对于列表和新增,title就是导航名称展示的名称和文件中的不一样,大家看明白就行,parentId,排序,index子导航排序,icon,导航图标,path和name就不用解释了把,component,这个是文件的路径,这里先提一下,这个是需要根据你的文件的目录自主设置,一会在用到的地方重点说明,meta元信息,(我这里放了三个,是让大家看清楚数据格式,实际操作首先后端需要拿到全部的路径,我们这边是根据后端返回的进行渲染的)
[
{
"parentId": "1",
"index": "1",
"title": "起爆器查看",
"type": "group",
"icon": "el-icon-location",//这个是加载导航栏的图标,
"children": [
{
"index": "1-1",
"title": "首页",
"path": "/home",
"name": "home",
"component": "home/index",
"menuType": 0,
"meta": {
"selectIndex": "1-1",
"check": true
}
},
{
"index": "1-2",
"title": "项目查看",
"path": "/projectview",
"name": "projectview",
"component": "projectview/index",
"menuType": 0,
"meta": {
"selectIndex": "1-2",
"check": true
}
},
{
"index": "1-3",
"title": "起爆器",
"path": "/detonator_view",
"name": "detonator_view",
"component": "detonator_view/index",
"menuType": 0,
"meta": {
"selectIndex": "1-3",
"check": true
}
}
]
},
{
"parentId": "2",
"index": "2",
"title": "信息管理",
"type": "group",
"icon": "el-icon-s-tools",
"children": [
{
"index": "2-1",
"title": "用户管理",
"path": "/user_manage",
"name": "user_manage",
"component": "user_manage/index",
"menuType": 0,
"meta": {
"selectIndex": "2-1",
"check": true
}
},
{
"index": "2-2",
"title": "公司管理",
"path": "/company_manage",
"name": "company_manage",
"component": "company_manage/index",
"menuType": 0,
"meta": {
"selectIndex": "2-2",
"check": true
}
}
]
},
{
"parentId": "3",
"index": "3",
"title": "系统管理",
"type": "group",
"icon": "el-icon-s-order",
"children": [
{
"index": "3-1",
"title": "字典管理",
"path": "/sys_info_manage",
"name": "sys_info_manage",
"component": "sys_info_manage/index",
"menuType": 0,
"meta": {
"selectIndex": "3-1",
"check": true
}
}
]
}
]
login页面完整代码
<template>
<div class="login-container">
<div class="login" @keyup.enter="denglu">
<div class="title">某某管理系统</div>
<div class="content">
<div class="input"><span>用户名</span><el-input v-model="input" @keyup.enter="denglu"></el-input></div>
<div class="input"><span>密码</span><el-input v-model="password" show-password @keyup.enter="denglu"></el-input>
</div>
<div class="but" @click="denglu">登录</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'HomeView',
data() {
return {
input: '',
password: ''
}
},
created(){
localStorage.setItem('ztcomtoken', '')//本地存储
},
methods: {
async denglu() {
//这里是我封装的网络请求,你们替换自己的就行
const res = await this.$axios("home/PostLogin", {
password: this.password,
phone: this.input
});
console.log("res", JSON.parse(JSON.stringify(res)));
if (res.data.code == 0) {
const menus = res.data.nav;
const menusJSON = JSON.stringify(menus);
console.log("menusJSON", JSON.parse(JSON.stringify(menusJSON)));
localStorage.setItem('menusJSON', menusJSON)
localStorage.setItem('reload', 0)
//这里就是存储josn数据的地方
localStorage.setItem('ztcomtoken', res.data.access_token)//本地存储
// window.sessionStorage.setItem('token', res.data.access_token);//本地存储
this.$message.success("登录成功!");
sessionStorage.setItem("login", true);
//登陆成功之后的跳转
this.$router.push({ name: "shouye" });
} else {
this.$message.error(res.data.msg);
}
}
}
}
</script>
<style lang="less" scoped>
.login-container {
background-color: #2d3a4b;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.login {
width: 80%;
max-width: 400px;
/* 最大宽度,自适应宽度 */
padding: 20px;
border: 1px solid #ebeef5;
border-radius: 5px;
}
.title {
color: white;
text-align: center;
font-size: 20px;
margin-bottom: 20px;
}
.content {
display: flex;
flex-direction: column;
}
.input {
color: white;
margin-bottom: 10px;
}
.but {
text-align: center;
background-color: #409EFF;
color: #fff;
padding: 10px;
border-radius: 5px;
cursor: pointer;
}
.but:hover {
background-color: #66b1ff;
}
</style>
二、router 的处理
import Vue from "vue";
import VueRouter from "vue-router";
import Layout from "@/views/layout/index.vue";
import addDynamicRoutes from '@/utils/addRoutes'
Vue.use(VueRouter);
// 解决同一页面跳转出现的错误,
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch((err) => err);
};
const originalReplace = VueRouter.prototype.replace;
VueRouter.prototype.replace = function replace(location) {
return originalReplace.call(this, location).catch((err) => err);
};
const routes = [
{
path: "/",
name: "Layout",
component: Layout,
// redirect: { name: "Login", check: true },
meta: { check: true },
children: [],
},
{
path: "/login",
name: "Login",
meta: { check: false },
component: () => import("@/views/login/index.vue"),
},
];
const router = new VueRouter({
mode: "hash",
base: process.env.BASE_URL,
routes,
});
router.beforeEach((to, from, next) => {
// console.log("-----beforeEach------");
// 检查本地存储中的 token
const token = localStorage.getItem('ztcomtoken');
// console.log("token",JSON.parse(JSON.stringify(token)));
const layoutRoute = router.options.routes.find((item) => item.name === 'Layout');
// 如果用户未登录且访问需要认证的路由,则重定向到登录页面
if (!token && to.meta.check) {
// console.log("-----Login---to------");
next({ name: 'Login' });
return;
}
// 如果用户已登录且Layout路由的子路由为空,则从localStorage获取menus并动态添加路由
if (token && layoutRoute && !layoutRoute.children.length) {
const menus = JSON.parse(localStorage.getItem('menusJSON') || '[]');
// console.log("menus", menus);
addDynamicRoutes(router, menus);
next({ ...to, replace: true });
// 动态路由加载完成后,重定向到 shouye
// next({ name: 'shouye', replace: true });
// 工单也加上了 是不是需要重新登陆一下把最新的数据拿过来,清一下缓存
}
// 无论是否添加了路由,都继续其它逻辑
next();
});
export default router;
Router页面用了layout页面和addDynamicRoutes文件。
layout页面是主页面,整个后台管理系统的框架页面,也就是根目录,addDynamicRoutes就是添加动态路由的方法,
三,addDynamicRoutes文件
export default function addDynamicRoutes(router, menus) {
// 获取所有注册的路由,包括根路由
const allRoutes = router.options.routes;
// 找到名为 'Layout' 的路由配置
const layoutRoute = allRoutes.find((route) => route.name == 'Layout');
if (!layoutRoute) {
console.error('Layout route not found.');
return;
}
menus.forEach((menu) => {
if (menu.children) {
menu.children.forEach((child) => {
// 构建路由配置对象
const routeConfig = {
path: child.path,
name: child.name,
//这里就是你的目录文件的路径,整个需要根据自己代码文件路径做调整,
component: () => import(`@/views/${child.component}.vue`), // 使用 ES6 动态导入语法
meta: child.meta,
};
// 添加新的路由配置到 Layout 路由的子路由中
router.addRoute(layoutRoute.name, routeConfig);
allRoutes.find((item) => item.name === 'Layout')?.children.push(routeConfig);
});
}
});
}
四,layout 页面
整体页面布局预览
<template>
<div class="layout">
<div class="nav">
<div class="left">
<div class="left-title">某某管理系统</div>
<div @click="logout"><i class="el-icon-switch-button"></i></div>
</div>
</div>
<div class="left-menu">
<el-menu unique-opened @select="selectAction" :default-active="$route.path" active-text-color="blue"
class="el-menu-vertical-demo" :default-openeds="defaultOpeneds">
<el-submenu v-for="item in menuItems" :key="item.index" :index="item.index">
<template slot="title">
<i :class="item.icon"></i>
<span v-if="item.title">{{ item.title }}</span>
</template>
<!-- 遍历当前 submenu 的 children 数组 -->
<el-menu-item v-for="child in item.children" :key="child.index" :index="child.index"
:class="{ select: selectIndex == child.index }">
<span slot="title" v-if="child.title">{{ child.title }}</span>
</el-menu-item>
</el-submenu>
</el-menu>
</div>
<div class="right-content">
<el-breadcrumb separator="/">
<!-- 动态显示当前选中的菜单项路径 -->
<el-breadcrumb-item to="###" v-if="selectIndex">{{
menuItems.find((item) => item.index == selectIndex.split('-')[0]).title
}}</el-breadcrumb-item>
<el-breadcrumb-item>{{ breadcrumbTitle }}</el-breadcrumb-item>
</el-breadcrumb>
<router-view />
</div>
</div>
</template>
<script>
//导入辅助函数获取selectIndex
import { mapState, mapMutations } from "vuex";
import menus from '@/utils/menus'
export default {
name: "LayoutView",
data() {
return {
menuItems: [...menus],
};
},
created() {
if (localStorage.getItem('reload') == 0) {
localStorage.setItem('reload', 1)
setTimeout(function () {
window.location.reload()
}, 500);
}
},
computed: {
...mapState("app", ["selectIndex"]),
// 面包屑切换子标题
breadcrumbTitle() {
const [parentIndex, childIndex] = this.selectIndex.split('-');
const parentItem = this.menuItems.find((item) => item.index == parentIndex);
const childItem = parentItem ? parentItem.children.find((child) => child.index.split('-')[1] == childIndex) : null;
return childItem ? childItem.title : 'shouYe';
},
defaultOpeneds() {
// 根据当前选中的 selectIndex 返回对应的菜单索引
const [parentIndex] = this.selectIndex.split('-');
return [parentIndex];
},
},
watch: {
$route: {
handler(newV) {
if (newV.meta.selectIndex) {
this.changeSelect(newV.meta.selectIndex);
}
},
immediate: true,
},
},
methods: {
...mapMutations('app', ['changeSelect']),
selectAction(index) {
// 分割 index 以获取 parentIndex 和 childIndex
const parts = index.split('-');
const parentIndex = parts[0];
// 找到对应的父菜单项
const parentItem = this.menuItems.find((item) => item.index == parentIndex);
if (parentItem && parentItem.children) {
// 找到对应的子菜单项
const childItem = parentItem.children.find((child) => child.index == index);
if (childItem) {
this.changeSelect(index); // 更新 Vuex 中的 selectIndex 状态
this.$router.push({ name: childItem.path.name });
}
}
},
// 退出登录
logout() {
this.$router.replace({ name: "Login" });
localStorage.removeItem("token");
},
// ...mapMutations("app", ["changeSelect"]),
},
};
</script>
<style lang="less" scoped>
.layout {
display: flex;
height: 100%;
justify-content: flex-start;
align-items: flex-start;
overflow: hidden;
.nav {
z-index: 100;
position: fixed;
left: 0;
top: 0;
right: 0;
height: 40px;
min-width: 1000px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
/* 添加阴影效果 */
.left {
width: 100%;
box-sizing: border-box;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
color: black;
.left-title {
color: black;
font-size: 18px;
font-weight: bold;
}
}
}
.left-menu {
width: 200px;
height: calc(100% - 40px);
margin-top: 43px !important;
overflow-y: auto;
overflow-x: hidden;
.el-menu-vertical-demo {
height: 100%;
width: 100%;
.el-menu-item {
font-size: 12px;
}
.select {
background-color: rgb(230, 247, 255) !important;
}
}
i {
margin-right: 10px;
font-size: 20px;
}
}
.right-content {
height: calc(100% - 40px);
width: calc(100% - 200px);
background-color: rgb(247, 248, 250);
padding: 15px !important;
margin-top: 40px;
box-sizing: border-box;
text-align: left;
}
}
.el-submenu {
text-align: left;
}
.select {
border-right: 4px solid #409eff;
/* 蓝色边框,你可以自定义颜色和宽度 */
color: blue;
}
.el-icon-switch-button {
// 鼠标移入变小手
cursor: pointer;
}
</style>
其中layout页面用到了vuex和menus文件,
五,vuex文件
文件目录:
store.index.js文件代码
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//从指定目录中读取所有的模块
const modules = require.context("./modules", true, /\.(js|ts)$/);
//获取所有的文件名称,并且将名称作为key和module进行匹配
const moduleList = modules.keys().reduce((target, filePath)=>{
//题目文件的名称
const fileName = filePath.replace(/\.\/(\w+)\.(js|ts)/, "$1");
//将文件名称和对应导出的模块进行绑定
target[fileName] = modules(filePath).default;
return target;
}, {});
export default new Vuex.Store({
modules: {
...moduleList
}
})
modules下的app.js文件
export default {
namespaced: true,
state: {
selectIndex: "1-1",
},
mutations: {
changeSelect(state, index) {
state.selectIndex = index;
},
},
};
六,menus.js文件
/**
* 左侧菜单栏
*/
// 从 localStorage 中获取的菜单数据
const rawMenus = localStorage.getItem('menusJSON');
let menus = rawMenus ? JSON.parse(rawMenus) : [];
// 转换菜单数据格式以匹配 layoutMenuItems 结构
function formatMenus(menus) {
return menus.map(menu => ({
index: menu.index,
title: menu.title,
icon: menu.icon,
children: menu.children ? menu.children.map(child => ({
index: child.index,
title: child.title,
path: { name: child.name },
})) : [],
}));
}
menus = formatMenus(menus);
export default menus;
以上就是本次全部内容,有收获记得点个赞a
标签:index,vue,const,name,title,element,menus,routes,路由 From: https://blog.csdn.net/qq_43557848/article/details/143281469