首页 > 其他分享 >vue后台element,routes实现动态路由控制权限管理

vue后台element,routes实现动态路由控制权限管理

时间:2024-10-28 11:47:55浏览次数:9  
标签:index vue const name title element menus routes 路由

前言

整体流程大概说下,在登陆成功之后,需要后端把角色权限的路由返给我们,我们再根据角色权限把后端给的路由在本地存储,在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

相关文章

  • 异常:找不到模块“@/views/HouseDetail.vue”或其相应的类型声明。ts(2307)
    原因:在配置Vue项目路由,特别是使用TS时,可能会遇到模块声明错误。为了解决‘找不到模块’的ts(2307)错误,可以在src目录下创建vite-env.d.ts文件,然后引入特定代码来声明*.vue文件为Vue组件,允许通过import导入。这样通常能解决无法识别模块的问题。解决:在src目录下创建vite-env.d.ts......
  • (开题报告)django+vue基于vue的飞特购物平台的设计与实现论文+源码
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、选题背景关于购物平台的设计与实现的研究,现有研究主要以大型综合购物平台或特定类型商品的购物平台为主。专门针对飞特购物平台这种特定需求和......
  • 如何利用vue和php做前后端分离开发
    使用Vue.js配合PHP进行前后端分离开发要着重关注几点关键性的事项:1、API设计原则、2、Vue.js在前端的搭建与实现、3、PHP后端的构建与优化、4、前后端数据交互格式和通信机制、5、安全性措施、6、性能调优等方面。在这些要点中,API设计原则是确保前后端能够顺畅协作的基础,应该优先......
  • VSCode中添加vue文件模板
    1、文件–>首选项—>用户代码片段2、在弹出的搜索框中输入`vue`,并点击 3、输入以下内容:"Printtoconsole":{"prefix":"vue","body":["<template>","<div></div>"......
  • vue3 中 defineExpose 作用
    在Vue3中,`defineExpose`是一个在`<scriptsetup>`语法中使用的函数,它的主要作用是用于显式地暴露组件内部的属性和方法,以便在父组件中可以通过`ref`获取并访问这些属性和方法。一、作用1.控制组件的对外暴露内容在默认情况下,使用`<scriptsetup>`的组件会自动暴露其内部......
  • JAVA开源项目 基于Vue和SpringBoot甘肃非物质文化网站
    本文项目编号T042,文末自助获取源码\color{red}{T042,文末自助获取源码}......
  • JAVA开源项目 基于Vue和SpringBoot网上购物商城
    本文项目编号T041,文末自助获取源码\color{red}{T041,文末自助获取源码}......
  • 基于Springboot+Vue的候鸟监测数据管理系统 (含源码数据库)
    1.开发环境开发系统:Windows10/11架构模式:MVC/前后端分离JDK版本:JavaJDK1.8开发工具:IDEA数据库版本:mysql5.7或8.0数据库可视化工具:navicat服务器:SpringBoot自带apachetomcat主要技术:Java,Springboot,mybatis,mysql,vue2.视频演示地址3.功能这个系......
  • 基于Springboot+Vue的企业绩效考核管理系统 (含源码数据库)
    1.开发环境开发系统:Windows10/11架构模式:MVC/前后端分离JDK版本:JavaJDK1.8开发工具:IDEA数据库版本:mysql5.7或8.0数据库可视化工具:navicat服务器:SpringBoot自带apachetomcat主要技术:Java,Springboot,mybatis,mysql,vue2.视频演示地址3.功能该系统......
  • 【Vue3】第二篇
    Vue3学习第二篇01.事件处理02.事件传参03.事件修饰符04.数组变化侦测05.计算属性06.class绑定07.style绑定08.侦听器09.表单输入绑定10.模板引用01.事件处理在vue当中的事件处理和html、css中的不一样,它单独做了处理。注意:用法中只是用点击事件来举......