美文网首页Vue
VUE--ElementUI--动态菜单+路由(五)

VUE--ElementUI--动态菜单+路由(五)

作者: 无剑_君 | 来源:发表于2019-09-20 16:02 被阅读0次

    一、动态菜单

    (一). 前端代码

    1. 登录页
    <template>
      <div class="container">
        <el-form ref="loginForm" :rules="loginRules" :model="form" label-width="80px">
          <el-row>
            <el-col :span="3">
              <div class="grid-content bg-purple-dark">&nbsp;</div>
            </el-col>
            <el-col :span="9">
              <el-image style="width: 100%; height: 100%" :src="url"></el-image>
            </el-col>
            <el-col :span="1">
              <div class="grid-content bg-purple-dark">&nbsp;</div>
            </el-col>
            <el-col :span="8">
              <h3>新星电商后台管理系统</h3>
              <el-form-item label="用户" prop="username">
                <el-input v-model="form.username" placeholder="输入用户"></el-input>
              </el-form-item>
              <el-form-item label="密码">
                <el-input type="password" v-model="form.password" placeholder="输入密码"></el-input>
              </el-form-item>
              <el-button type="primary" @click="login('loginForm')">登录</el-button>
            </el-col>
            <el-col :span="5">
              <div class="grid-content bg-purple-dark">&nbsp;</div>
            </el-col>
          </el-row>
        </el-form>
      </div>
    </template>
    <script>
    //
    import { login } from "@/api/login.js";
    import { getRouter } from "@/api/menu.js";
    import { setToken } from "@/utils/store.js";
    
    export default {
      data() {
        return {
          url: "/images/logoLeft.png",
          form: { username: "admin", password: "123456" },
          // 表单验证
          loginRules: {
            username: [
              { required: true, message: "请输入用户名称", trigger: "blur" }
            ]
          }
        };
      },
      methods: {
        login(loginForm) {
          this.$refs[loginForm].validate(valid => {
            if (valid) {
              login(this.form).then(response => {
                if (response.data.code == "0000") {
                  // 令牌存储
                  setToken(response.data.data);
                  this.$router.push("/layout");
                } else {
                  alert(response.data.message);
                }
              });
            } else {
              console.log("error submit!!");
            }
          });
        }
      }
    };
    </script>
    <style lang="scss" scoped>
    .container {
      margin-top: 160px;
    }
    </style>
    
    1. 侧边栏
    <template>
      <div>
        <h3>首页</h3>
        <el-menu :default-openeds="['2']" :collapse="isCollapse">
          <!-- 目录 -->
          <el-submenu :index="item.key" v-for="item in menuData" :key="item.id">
            <template slot="title">
              <i :class="item.icon"></i>
              <span slot="title">{{item.title}}</span>
            </template>
            <el-menu-item-group>
              <!-- 子菜单 -->
              <el-menu-item
                v-for="childItem in item.children"
                :key="childItem.id"
                :index="item.key"
                @click="navRouter(childItem.path)"
              >{{childItem.title}}</el-menu-item>
            </el-menu-item-group>
          </el-submenu>
        </el-menu>
      </div>
    </template>
    <script>
    import { getMenu } from "@/api/menu.js";
    export default {
      data() {
        return {
          menuData: []
        };
      },
      props: {
        isCollapse: {
          type: Boolean
        }
      },
      created() {
        this.initMenu();
      },
      methods: {
        navRouter(url) {
          this.$router.push("/layout/" + url);
        },
        initMenu() {
          getMenu().then(res => {
            this.menuData = res.data.data;
          });
        }
      }
    };
    </script>
    
    1. 主页
    <template>
      <el-container>
        <el-aside :width="asWidth">
          <Aside :isCollapse="isCollapse" />
        </el-aside>
        <el-container>
          <el-header>
            <el-row>
              <el-col :span="8" class="menu">
                <el-button type="primary" class="el-icon-s-fold" @click="menu"></el-button>
              </el-col>
              <el-col :span="8" :offset="8" class="menu logout">
                <el-button type="info" @click="logout">退出登录</el-button>
              </el-col>
            </el-row>
          </el-header>
          <el-main>
            <router-view />
          </el-main>
        </el-container>
      </el-container>
    </template>
    <script>
    // 导入组件
    import Aside from "@/views/layout/Aside.vue";
    import { deleteKey } from "@/utils/store.js";
    import { log } from 'util';
    
    export default {
      name: "layout",
      components: {
        Aside
      },
      data() {
        return { isCollapse: true, asWidth: "100" };
      },
      methods: {
        menu() {
          this.isCollapse = !this.isCollapse;
        },
        logout() {
            // 移除token
            deleteKey("token");
            this.$router.push("/");
        }
      }
    };
    </script>
    <style lang="scss" scoped>
    .menu {
      margin-top: 10px;
      text-align: left;
    }
    .menu.logout {
      text-align: right;
    }
    </style>
    
    1. 令牌存储store.js
    
     var store=window.localStorage;
    
    export function setToken(val){
        store.token=val;
    }
    
    export function deleteKey(key){
        store.removeItem(key)
    }
    
    export function getToken(){
        return store.token;
    }
    
    
    1. 路由处理router.js
    import dynamicRouter from "@/router/dynamicRouter.js";
    import { getToken } from '@/utils/store.js';
    import Vue from 'vue';
    import Router from 'vue-router';
    import { getRouter } from "@/api/menu.js";
    
    Vue.use(Router)
    /**
     * 重写路由的push方法
     */
    const routerPush = Router.prototype.push
    Router.prototype.push = function push(location) {
      return routerPush.call(this, location).catch(error=> error)
    }
    
    const router = new Router({
      mode: 'history',
      base: process.env.BASE_URL,
      routes: [
        {
          path: '/',
          name: 'login',
          component: () => import('@/views/Login.vue')
        }
      ]
    });
    /**
     * 避免循环执行
     */
    let newRoutes = null;
    router.beforeEach((to, from, next) => {
      // 判断是否是登录界面
      if (to.path != '/') {
        // 从获取store中Token
        if (getToken()) {
          // 刷新动态路由丢失
          if (!newRoutes) {
            // 调用API获取动态路由
            getRouter().then(res => {
              res.data.data.forEach(element => {
                // 重要:赋值给变量
                let value = element.component;
                element.component = function component(resolve) {
                  require(["@/views" + value], resolve);
                };
              });
              dynamicRouter.routes[0].children = res.data.data;
              newRoutes = dynamicRouter.routes;
              // 添加路由数据
              router.addRoutes(newRoutes);
              next({ ...to, replace: true });
            });
          } else {
            next()
          }
        }else{
          next("/");
        }
      } else {
        // 如果是登录界面放行
        next();
      }
    });
    
    export default router;
    
    1. 动态路由定义dynamicRouter.js
    /**
     * 动态路由定义
     */
    let dynamicRouter = {
        routes: [
            {
                path: "/layout",
                name: "layout",
                component: () => import("@/views/layout/Layout.vue"),
                children: []
            }
        ]
    }
    
    export default dynamicRouter;
    
    
    1. 登录API
    import request from "@/utils/request.js";
    /**
     * 登录方法
     * @param {} data 
     */
    export function login(data){
        return request({
            url:"/admin-service/auth/login",
            method:"post",
            data:data
        });
    }
    
    1. 菜单路由API
    import request from "@/utils/request.js";
    
    /**
     * 获取菜单数据
     */
    export function getMenu(){
        return request({
            url:"/admin-service/menu/admin",
            method:"get"
        });
    }
    /**
     * 获取路由数据
     */
    export function getRouter(){
        return request({
            url:"/admin-service/menu/router/admin",
            method:"get"
        });
    }
    
    

    (二). 后台代码

    1. 数据表结构与数据
      在此只列表菜单表,其它表,用户、角色、权限未列。
    CREATE TABLE `system_menu`  (
      `id` bigint(20) NOT NULL,
      `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由名',
      `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路径',
      `menu_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '显示名称',
      `parent_id` int(11) NULL DEFAULT NULL COMMENT '父级ID',
      `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标',
      `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件地址',
      `deleted` int(11) NULL DEFAULT NULL COMMENT '逻辑删除(1 已删除 0未删除)',
      `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
      `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
    
    -- ----------------------------
    -- Records of system_menu
    -- ----------------------------
    INSERT INTO `system_menu` VALUES (1, 'userManage', '', '会员管理', 0, 'el-icon-s-custom', NULL, 0, '2019-09-11 14:33:17', NULL);
    INSERT INTO `system_menu` VALUES (2, 'user', 'user', '会员列表', 1, 'el-icon-s-custom', '/user/index.vue', 0, '2019-09-11 14:35:26', NULL);
    INSERT INTO `system_menu` VALUES (3, 'role', 'role', '角色管理', 0, 'el-icon-s-custom', '/role/index.vue', 0, '2019-09-12 10:49:40', NULL);
    INSERT INTO `system_menu` VALUES (4, 'goods', '', '商品管理', 0, 'el-icon-shopping-cart-full', NULL, 0, '2019-09-19 10:28:36', NULL);
    INSERT INTO `system_menu` VALUES (5, 'category', 'category', '分类管理', 4, NULL, '/category/index.vue', 0, '2019-09-19 10:29:58', NULL);
    INSERT INTO `system_menu` VALUES (6, 'goods', 'goods', '商品管理', 4, NULL, '/goods/index.vue', 0, '2019-09-19 10:30:52', NULL);
    
    
    1. 菜单VO类代码
    @Data
    public class MenuVO {
        private Long id;
        private String title;
        private String icon;
        private String key;
        private String path;
        private Set<MenuVO> children;
    }
    
    
    1. 路由VO类代码
    @Data
    public class RouterVO {
        /**
         * 名称
         */
        private String name;
        /**
         * 路径
         */
        private String path;
        /**
         * 组件
         */
        private String component;
    }
    
    
    1. 菜单路由业务类代码
        @Override
        public List<MenuVO> getMenusByUsername(String username) {
            // 1.根据用户名查找角色
            List<RoleVO> roles = roleMapper.getRolesByUsername(username);
            List<MenuVO> menuVOList=new ArrayList<>();
            for (RoleVO roleVO : roles) {
                List<Menu> lists = menuMapper.getMenusByRoleId(roleVO.getId());
                for (Menu menu:lists) {
                    if(null!=menu&&null!=menu.getParentId()&&menu.getParentId()==0) {
                        MenuVO menuVO = new MenuVO();
                        menuVO.setId(menu.getId());
                        menuVO.setKey(menu.getId().toString());
                        menuVO.setTitle(menu.getMenuName());
                        menuVO.setIcon(menu.getIcon());
                        menuVO.setPath(menu.getPath());
                        menuVO.setChildren(this.getSubMenu(lists,menuVO));
                        // 添加一级菜单数据
                        menuVOList.add(menuVO);
                    }
                }
            }
            return menuVOList;
        }
    
        /**
         * 获取子菜单
         * @param lists
         * @param menuVO
         * @return
         */
        private Set<MenuVO> getSubMenu( List<Menu> lists,  MenuVO menuVO ){
            Set<MenuVO> menuVOList=new HashSet<>();
            for (Menu menu0:lists) {
                if(menuVO.getId()==menu0.getParentId()){
                    MenuVO menuVO0 = new MenuVO();
                    menuVO0.setId(menu0.getId());
                    menuVO0.setKey(menu0.getId().toString());
                    menuVO0.setPath(menu0.getPath());
                    menuVO0.setIcon(menu0.getIcon());
                    menuVO0.setTitle(menu0.getMenuName());
                    menuVOList.add(menuVO0);
                    // 添加到集合
                    Set<MenuVO> menuVOSet=this.getSubMenu(lists,menuVO0);
                    menuVO0.setChildren(menuVOSet);
                }
            }
            return menuVOList;
        }
    }
        /**
         * 获取路由
         * @param username
         * @return
         */
        @Override
        public List<RouterVO> getRouter(String username) {
            // 1. 根据用户名查找角色
            List<RoleVO> roles = roleMapper.getRolesByUsername(username);
            List<RouterVO> routerVOList=new ArrayList<>();
            for (RoleVO roleVO : roles) {
                // 2. 通过角获取菜单
                List<Menu> lists = menuMapper.getMenusByRoleId(roleVO.getId());
                for (Menu menu:lists) {
                    // 目录为空
                    if(StringUtils.isNotEmpty(menu.getPath())) {
                        RouterVO routerVO = new RouterVO();
                        routerVO.setName(menu.getName());
                        routerVO.setComponent(menu.getComponent());
                        routerVO.setPath(menu.getPath());
                        // 添加一级菜单数据
                        routerVOList.add(routerVO);
                    }
                }
            }
            return routerVOList;
        }
    }
    
    
    1. 获取菜单与路由控制器代码
        @GetMapping("{username}")
        public Result<List<MenuVO>> getMenus(@PathVariable("username") String username) {
            return Result.ok(adminService.getMenusByUsername(username));
        }
    
        @GetMapping("router/{username}")
        public Result<List<RouterVO>> getRouter(@PathVariable("username") String username) {
            return Result.ok(menuService.getRouter(username));
        }
    

    二、常见问题

    1. 动态路中添加之坑
      vue lazy recursive ^./.*$
      解决:
      数据赋值给局部变量后添加。
     let value = element.component;
                element.component = function component(resolve) {
                  require(["@/views" + value], resolve);
                };
    
    1. 刷新页面空白
      解决:
      在beforeEach中读取后台数据进行路由添加。

    相关文章

      网友评论

        本文标题:VUE--ElementUI--动态菜单+路由(五)

        本文链接:https://www.haomeiwen.com/subject/rwnluctx.html