Vue

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

2019-09-20  本文已影响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中读取后台数据进行路由添加。
上一篇下一篇

猜你喜欢

热点阅读