美文网首页程序猿
VUE全家桶+element-ui实现一个后台管理系统

VUE全家桶+element-ui实现一个后台管理系统

作者: wangwenquan1234 | 来源:发表于2019-12-06 16:44 被阅读0次

初始化项目

开发环境

  • IDE webstorm2019
  • nodejs 8.15.1
  • npm 6.4.1
  • @vue/cli-3.11.1

package.json

{
  "name": "webapp",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "axios": "^0.19.0",
    "core-js": "^2.6.5",
    "element-ui": "^2.12.0",
    "vue": "^2.6.10",
    "vue-drag-verify": "^1.0.6",
    "vue-fragment": "^1.5.1",
    "vue-router": "^3.0.3",
    "vuex": "^3.0.1"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^3.11.0",
    "@vue/cli-plugin-eslint": "^3.11.0",
    "@vue/cli-service": "^3.11.0",
    "@vue/eslint-config-standard": "^4.0.0",
    "babel-eslint": "^10.0.1",
    "eslint": "^5.16.0",
    "eslint-plugin-vue": "^5.0.0",
    "less": "^3.0.4",
    "less-loader": "^5.0.0",
    "vue-template-compiler": "^2.6.10"
  }
}
项目创建1.png
项目创建2.png
创建项目3.png 项目创建完成.png
npm run serve.png

目录结构整理

i目录结构.png

我在cli基础上做了修改,目录说明如下

  • api目录封装axios http请求用的
  • common文件夹是css js img文件
  • component文件夹是通用的组件
  • router文件夹是路由
  • store文件夹是vuex
  • view是page页面
  • auth.js文件是做动态路由的文件
代码.png 页面效果.png

动态路由

和后端的约定

我和后端的约定是这样的,数据库中有一个表,假设叫menu,专门存放菜单的
接口返回数据机构如下

{
    "code": 200,
    "message": "SUCCESS",
    "data": [
        {
            "menuId": "39acd7d439674d9687a115adc86e6785",
            "parentId": "0",
            "menuName": "基本信息",
            "menuUrl": "basic-information",
            "menuIcon": "el-icon-setting",
            "menuSort": "9998",
            "createDate": null,
            "createPersonId": null,
            "updateDate": null,
            "updatePersonId": null,
            "logicDelete": "0",
            "subMenuList": [
                {
                    "menuId": "0b61404507d148c7a76695cf5da78c92",
                    "parentId": "39acd7d439674d9687a115adc86e6785",
                    "menuName": "厂区设置",
                    "menuUrl": "factory-setting",
                    "menuIcon": null,
                    "menuSort": "99989",
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                },
                {
                    "menuId": "d42f1103ae1f4a33bc51c5b6cce42669",
                    "parentId": "39acd7d439674d9687a115adc86e6785",
                    "menuName": "库区设置",
                    "menuUrl": "warehouse-setting",
                    "menuIcon": null,
                    "menuSort": "99988",
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                },
                {
                    "menuId": "f2adf1bb65c34da28b40f930e836542e",
                    "parentId": "39acd7d439674d9687a115adc86e6785",
                    "menuName": "道口设置",
                    "menuUrl": "crossing-setting",
                    "menuIcon": null,
                    "menuSort": "99987",
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                },
                {
                    "menuId": "8c8bcf4974db4d6ea31b5862935e3db6",
                    "parentId": "39acd7d439674d9687a115adc86e6785",
                    "menuName": "门岗设置",
                    "menuUrl": "gate-setting",
                    "menuIcon": null,
                    "menuSort": "99986",
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                },
                {
                    "menuId": "edac960fe25f45dfae18d9a3e96cae24",
                    "parentId": "39acd7d439674d9687a115adc86e6785",
                    "menuName": "承运商管理",
                    "menuUrl": "carrier-management",
                    "menuIcon": null,
                    "menuSort": "99985",
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                },
                {
                    "menuId": "5a4bc0a658bd46d29f3d29323abaa974",
                    "parentId": "39acd7d439674d9687a115adc86e6785",
                    "menuName": "车辆管理",
                    "menuUrl": "vehicle-management",
                    "menuIcon": null,
                    "menuSort": "99984",
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                },
                {
                    "menuId": "1746de6200ec47ee8f53fd214bb61dbc",
                    "parentId": "39acd7d439674d9687a115adc86e6785",
                    "menuName": "司机管理",
                    "menuUrl": "driver-management",
                    "menuIcon": null,
                    "menuSort": "99983",
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                },
                {
                    "menuId": "22d38716ef6941a8bc1ba41338f92438",
                    "parentId": "39acd7d439674d9687a115adc86e6785",
                    "menuName": "装车时长设置",
                    "menuUrl": "loading-time-setting",
                    "menuIcon": null,
                    "menuSort": "99982",
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                },
                {
                    "menuId": "cb8fd30191024a96b7d398f2a2ef2e6c",
                    "parentId": "39acd7d439674d9687a115adc86e6785",
                    "menuName": "锁定时长设置",
                    "menuUrl": "lock-time-setting",
                    "menuIcon": null,
                    "menuSort": "99981",
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                },
                {
                    "menuId": "c8a063132e4a4ad6af8f1fca1f6bcce4",
                    "parentId": "39acd7d439674d9687a115adc86e6785",
                    "menuName": "系统设置",
                    "menuUrl": "system-seeting",
                    "menuIcon": null,
                    "menuSort": "99980",
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                }
            ]
        },
        {
            "menuId": "ec6ec25da6ec4818a6d6b5fda36e914e",
            "parentId": "0",
            "menuName": "作业管理",
            "menuUrl": "job-management",
            "menuIcon": "el-icon-s-flag",
            "menuSort": "9997",
            "createDate": null,
            "createPersonId": null,
            "updateDate": null,
            "updatePersonId": null,
            "logicDelete": "0",
            "subMenuList": [
                {
                    "menuId": "00f412e738ed45bc88e8379d11b5e446",
                    "parentId": "ec6ec25da6ec4818a6d6b5fda36e914e",
                    "menuName": "预约信息列表",
                    "menuUrl": "appointment-information-list",
                    "menuIcon": null,
                    "menuSort": null,
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                },
                {
                    "menuId": "4e1a2bd2302543278c24b2434cfd7a76",
                    "parentId": "ec6ec25da6ec4818a6d6b5fda36e914e",
                    "menuName": "整车监控管理",
                    "menuUrl": "vehicle-monitoring",
                    "menuIcon": null,
                    "menuSort": null,
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                },
                {
                    "menuId": "c59e4e1f536648929274a97b80b55270",
                    "parentId": "ec6ec25da6ec4818a6d6b5fda36e914e",
                    "menuName": "门岗登记管理",
                    "menuUrl": "gate-registration",
                    "menuIcon": null,
                    "menuSort": null,
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                }
            ]
        },
        {
            "menuId": "9ddcea2cd7a6494f818ce19801a512ed",
            "parentId": "0",
            "menuName": "预约管理",
            "menuUrl": "ppointment-management",
            "menuIcon": "el-icon-s-promotion",
            "menuSort": "9996",
            "createDate": null,
            "createPersonId": null,
            "updateDate": null,
            "updatePersonId": null,
            "logicDelete": "0",
            "subMenuList": [
                {
                    "menuId": "b3efa8b104964cd1934e33ecab448b28",
                    "parentId": "9ddcea2cd7a6494f818ce19801a512ed",
                    "menuName": "新建预约",
                    "menuUrl": "new-appointment",
                    "menuIcon": null,
                    "menuSort": null,
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                }
            ]
        },
        {
            "menuId": "efc659d4a8544b089c05da0bd8a90077",
            "parentId": "0",
            "menuName": "统计分析",
            "menuUrl": "statistic-analysis",
            "menuIcon": "el-icon-s-data",
            "menuSort": "9995",
            "createDate": null,
            "createPersonId": null,
            "updateDate": null,
            "updatePersonId": null,
            "logicDelete": "0",
            "subMenuList": [
                {
                    "menuId": "346d07d74b644bfbb7c09638f485198a",
                    "parentId": "efc659d4a8544b089c05da0bd8a90077",
                    "menuName": "承运商迟到分析",
                    "menuUrl": "carrier-company-arrive-late",
                    "menuIcon": null,
                    "menuSort": null,
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                }
            ]
        },
        {
            "menuId": "b6b7a4d5e27c4b949a9f4850cfd6340d",
            "parentId": "0",
            "menuName": "权限管理",
            "menuUrl": "auth-management",
            "menuIcon": "el-icon-lock",
            "menuSort": "9994",
            "createDate": null,
            "createPersonId": null,
            "updateDate": null,
            "updatePersonId": null,
            "logicDelete": "0",
            "subMenuList": [
                {
                    "menuId": "3a51229dc739487a9c694794e82369e3",
                    "parentId": "b6b7a4d5e27c4b949a9f4850cfd6340d",
                    "menuName": "角色管理",
                    "menuUrl": "role-management",
                    "menuIcon": null,
                    "menuSort": null,
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                },
                {
                    "menuId": "67fb6c43069449158b1874090028222d",
                    "parentId": "b6b7a4d5e27c4b949a9f4850cfd6340d",
                    "menuName": "菜单管理",
                    "menuUrl": "menu-management",
                    "menuIcon": null,
                    "menuSort": null,
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                },
                {
                    "menuId": "a3e9c552227745e0a04af8d8f8b40146",
                    "parentId": "b6b7a4d5e27c4b949a9f4850cfd6340d",
                    "menuName": "用户管理",
                    "menuUrl": "user-management",
                    "menuIcon": null,
                    "menuSort": null,
                    "createDate": null,
                    "createPersonId": null,
                    "updateDate": null,
                    "updatePersonId": null,
                    "logicDelete": "0",
                    "subMenuList": []
                }
            ]
        }
    ]
}

根据接口返回的数据动态创建路由

代码如下

// auth.js

import router from '@/router'
import store from '@/store'
import { post } from '@/api/http'
import { Message } from 'element-ui'

let flag = false

/**
 * 路由守卫
 * api /menu/getAllMenuInfo 查询所有菜单
 */
router.beforeEach((to, from, next) => {
  // 如果store中没有token,则证明当前用户没有登录过
  if (!store.state.token.length) {
    // 如果路由刚好在登录页面上,则需要next掉,不然会死循环
    if (to.path === '/login') {
      return next()
    }
    // 跳转到登录页
    return next('/login')
  }

  // 用户登录成功
  // 如果路由刚好在登录页上,则跳转到 / 路径上
  if (to.path === '/login') {
    return next('/')
  }

  // 创建动态路由
  // 不加这个判断页面会死循环
  if (!flag) {
    // flag变成true
    flag = true

    // 如果sessionStorage没有router字段
    if (!sessionStorage.getItem('router')) {
      // 向接口中拿menu list
      post('/menu/getAllMenuInfo', {}).then(res => {
        // 1.将接口数据转成vue-router数据格式
        const data = transformHttpDataToVueRouterData(res.data)

        // 2.将处理过的数据存到sessionStore中去
        sessionStorage.setItem('router', JSON.stringify(data))

        // 3.将数据派发store中
        store.dispatch('menuList', {
          menuList: data
        })

        // 4.创建动态路由
        routerGo(to, next)
      })
    } else {
      // 如果sessionStorage有router字段,则直接创建路由
      routerGo(to, next)
    }
  } else {
    next()
  }
})

/**
 * 添加路由
 * @param to
 * @param next
 */
function routerGo (to, next) {
  // 获取sessionStorage中的router字段,并序列化
  const routerData = JSON.parse(sessionStorage.getItem('router'))

  // 如果前线列表是空的,返回提示
  if (!routerData.length) {
    return Message.error('当前权限列表为空!')
  }

  // layout组件是主页布局文件,需要手动引入
  const asyncRouter = [
    {
      name: 'Layout',
      path: '/layout',
      component: () => import('@/component/layout/Layout.vue'),
      children: []
    }
  ]

  // 通过componentPath创建component组件
  asyncRouter[0].children = transformVueRouterDataToVueRouterComponent(routerData)

  // 在路由末尾添加404
  asyncRouter.push({
    path: '*',
    name: 'notFount',
    component: () => import('@/view/not-fount/NotFound.vue')
  })

  // 添加动态路由
  router.addRoutes(asyncRouter)

  next({ ...to, replace: true })
}

/**
 * 接口列表格式转换成满足vue-router的对应字段
 * @param data
 * @param array
 * @param str
 * @returns {Array}
 */
function transformHttpDataToVueRouterData (data, array = [], str = '/') {
  data.forEach((item, index) => {
    array.push({
      menuId: item.menuId,
      label: item.menuName,
      path: item.menuUrl,
      name: toCamel(item.menuUrl),
      icon: item.menuIcon,
      // 这个字段是用来做浏览器地址链接有用的
      componentPath: `${str}${item.menuUrl}`,
      children: []
    })
    if (item.subMenuList && item.subMenuList.length) {
      array[index].redirect = {
        name: toCamel(item.subMenuList[0].menuUrl)
      }
      transformHttpDataToVueRouterData(item.subMenuList, array[index].children, `${array[index].componentPath}/`)
    } else {
      array[index].redirect = null
    }
  })
  return array
}

/**
 * 将component字段转成component组件
 * @param root
 * @returns {*}
 */
function transformVueRouterDataToVueRouterComponent (root) {
  root.forEach((item) => {
    let path = item.componentPath
    path = path + '/' + toCamel(path.substring(path.lastIndexOf('/') + 1))

    // 因为webpack引入import机制的问题。全部转成变量不能解析
    item.component = () => import(`./view${path}.vue`)
    if (item.children && item.children.length) {
      transformVueRouterDataToVueRouterComponent(item.children)
    }
  })

  return root
}

/**
 * 中划线命名转大驼峰命名
 * @param str
 * @returns {*}
 */
function toCamel (str) {
  str = str.replace(/(\w)/, (match, $1) => `${$1.toUpperCase()}`)
  while (str.match(/\w-\w/)) {
    str = str.replace(/(\w)(-)(\w)/, (match, $1, $2, $3) => `${$1}${$3.toUpperCase()}`)
  }
  return str
}

创建之后来看一下路由

  1. 静态路由
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const constantRouter = [
  {
    path: '/',
    redirect: 'layout'
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/view/login/Login.vue')
  },
]

const router = new VueRouter({
  routes: constantRouter
})

export default router
  1. 动态路由


    动态路由.png

最后看一下代码截图


效果图.png

Layout组件布局

效果图如下


Layout.png

Menu菜单实现

我把接口的数据转换成vue-router支持的数据格式后存到的store中数据格式如下

[
    {
        "menuId": "39acd7d439674d9687a115adc86e6785",
        "label": "基本信息",
        "path": "basic-information",
        "name": "BasicInformation",
        "icon": "el-icon-setting",
        "componentPath": "/basic-information",
        "children": [
            {
                "menuId": "0b61404507d148c7a76695cf5da78c92",
                "label": "厂区设置",
                "path": "factory-setting",
                "name": "FactorySetting",
                "icon": null,
                "componentPath": "/basic-information/factory-setting",
                "children": [],
                "redirect": null
            },
            {
                "menuId": "d42f1103ae1f4a33bc51c5b6cce42669",
                "label": "库区设置",
                "path": "warehouse-setting",
                "name": "WarehouseSetting",
                "icon": null,
                "componentPath": "/basic-information/warehouse-setting",
                "children": [],
                "redirect": null
            },
            {
                "menuId": "f2adf1bb65c34da28b40f930e836542e",
                "label": "道口设置",
                "path": "crossing-setting",
                "name": "CrossingSetting",
                "icon": null,
                "componentPath": "/basic-information/crossing-setting",
                "children": [],
                "redirect": null
            },
            {
                "menuId": "8c8bcf4974db4d6ea31b5862935e3db6",
                "label": "门岗设置",
                "path": "gate-setting",
                "name": "GateSetting",
                "icon": null,
                "componentPath": "/basic-information/gate-setting",
                "children": [],
                "redirect": null
            },
            {
                "menuId": "edac960fe25f45dfae18d9a3e96cae24",
                "label": "承运商管理",
                "path": "carrier-management",
                "name": "CarrierManagement",
                "icon": null,
                "componentPath": "/basic-information/carrier-management",
                "children": [],
                "redirect": null
            },
            {
                "menuId": "5a4bc0a658bd46d29f3d29323abaa974",
                "label": "车辆管理",
                "path": "vehicle-management",
                "name": "VehicleManagement",
                "icon": null,
                "componentPath": "/basic-information/vehicle-management",
                "children": [],
                "redirect": null
            },
            {
                "menuId": "1746de6200ec47ee8f53fd214bb61dbc",
                "label": "司机管理",
                "path": "driver-management",
                "name": "DriverManagement",
                "icon": null,
                "componentPath": "/basic-information/driver-management",
                "children": [],
                "redirect": null
            },
            {
                "menuId": "22d38716ef6941a8bc1ba41338f92438",
                "label": "装车时长设置",
                "path": "loading-time-setting",
                "name": "LoadingTimeSetting",
                "icon": null,
                "componentPath": "/basic-information/loading-time-setting",
                "children": [],
                "redirect": null
            },
            {
                "menuId": "cb8fd30191024a96b7d398f2a2ef2e6c",
                "label": "锁定时长设置",
                "path": "lock-time-setting",
                "name": "LockTimeSetting",
                "icon": null,
                "componentPath": "/basic-information/lock-time-setting",
                "children": [],
                "redirect": null
            },
            {
                "menuId": "c8a063132e4a4ad6af8f1fca1f6bcce4",
                "label": "系统设置",
                "path": "system-seeting",
                "name": "SystemSeeting",
                "icon": null,
                "componentPath": "/basic-information/system-seeting",
                "children": [],
                "redirect": null
            }
        ],
        "redirect": {
            "name": "FactorySetting"
        }
    },
    {
        "menuId": "ec6ec25da6ec4818a6d6b5fda36e914e",
        "label": "作业管理",
        "path": "job-management",
        "name": "JobManagement",
        "icon": "el-icon-s-flag",
        "componentPath": "/job-management",
        "children": [
            {
                "menuId": "00f412e738ed45bc88e8379d11b5e446",
                "label": "预约信息列表",
                "path": "appointment-information-list",
                "name": "AppointmentInformationList",
                "icon": null,
                "componentPath": "/job-management/appointment-information-list",
                "children": [],
                "redirect": null
            },
            {
                "menuId": "4e1a2bd2302543278c24b2434cfd7a76",
                "label": "整车监控管理",
                "path": "vehicle-monitoring",
                "name": "VehicleMonitoring",
                "icon": null,
                "componentPath": "/job-management/vehicle-monitoring",
                "children": [],
                "redirect": null
            },
            {
                "menuId": "c59e4e1f536648929274a97b80b55270",
                "label": "门岗登记管理",
                "path": "gate-registration",
                "name": "GateRegistration",
                "icon": null,
                "componentPath": "/job-management/gate-registration",
                "children": [],
                "redirect": null
            }
        ],
        "redirect": {
            "name": "AppointmentInformationList"
        }
    },
    {
        "menuId": "9ddcea2cd7a6494f818ce19801a512ed",
        "label": "预约管理",
        "path": "ppointment-management",
        "name": "PpointmentManagement",
        "icon": "el-icon-s-promotion",
        "componentPath": "/ppointment-management",
        "children": [
            {
                "menuId": "b3efa8b104964cd1934e33ecab448b28",
                "label": "新建预约",
                "path": "new-appointment",
                "name": "NewAppointment",
                "icon": null,
                "componentPath": "/ppointment-management/new-appointment",
                "children": [],
                "redirect": null
            }
        ],
        "redirect": {
            "name": "NewAppointment"
        }
    },
    {
        "menuId": "efc659d4a8544b089c05da0bd8a90077",
        "label": "统计分析",
        "path": "statistic-analysis",
        "name": "StatisticAnalysis",
        "icon": "el-icon-s-data",
        "componentPath": "/statistic-analysis",
        "children": [
            {
                "menuId": "346d07d74b644bfbb7c09638f485198a",
                "label": "承运商迟到分析",
                "path": "carrier-company-arrive-late",
                "name": "CarrierCompanyArriveLate",
                "icon": null,
                "componentPath": "/statistic-analysis/carrier-company-arrive-late",
                "children": [],
                "redirect": null
            }
        ],
        "redirect": {
            "name": "CarrierCompanyArriveLate"
        }
    },
    {
        "menuId": "b6b7a4d5e27c4b949a9f4850cfd6340d",
        "label": "权限管理",
        "path": "auth-management",
        "name": "AuthManagement",
        "icon": "el-icon-lock",
        "componentPath": "/auth-management",
        "children": [
            {
                "menuId": "3a51229dc739487a9c694794e82369e3",
                "label": "角色管理",
                "path": "role-management",
                "name": "RoleManagement",
                "icon": null,
                "componentPath": "/auth-management/role-management",
                "children": [],
                "redirect": null
            },
            {
                "menuId": "67fb6c43069449158b1874090028222d",
                "label": "菜单管理",
                "path": "menu-management",
                "name": "MenuManagement",
                "icon": null,
                "componentPath": "/auth-management/menu-management",
                "children": [],
                "redirect": null
            },
            {
                "menuId": "a3e9c552227745e0a04af8d8f8b40146",
                "label": "用户管理",
                "path": "user-management",
                "name": "UserManagement",
                "icon": null,
                "componentPath": "/auth-management/user-management",
                "children": [],
                "redirect": null
            }
        ],
        "redirect": {
            "name": "RoleManagement"
        }
    }
]

递归组件

直接上代码


代码所在项目位置.png
<!-- Layout -->
<el-menu
  :collapse="isCollapse"
  background-color="#20222A"
  text-color="rgba(255,255,255,0.9)"
  active-text-color="#409EFF"
  :default-active="this.$route.path"
  router
  mode="vertical">
  <Menus :menus="menuList" :collapse="isCollapse"></Menus>
</el-menu>

<!-- Menus.vue -->
<template>
  <div>
    <template v-for="menu in menus">
      <el-submenu v-if="menu.children && menu.children.length > 0"
                  :index="'/layout'+menu.componentPath"
                  :key="menu.children.menuId">
        <template slot="title">
          <i :class="menu.icon"></i>
          <span v-show="!collapse" slot="title">{{menu.label}}</span>
        </template>
        <Menus :menus="menu.children"></Menus>
      </el-submenu>

      <template v-else>
        <el-menu-item :index="'/layout'+menu.componentPath" :key="menu.menuId">
          <i :class="menu.icon"></i>
          <span slot="title">{{menu.label}}</span>
        </el-menu-item>
      </template>
    </template>
  </div>
</template>

<script>
  export default {
    name: 'Menus',
    props: {
      menus: {
        type: Array,
        required: true
      },
      collapse: {
        type: Boolean,
        default: false
      }
    }
  }
</script>

<style>
  .el-scrollbar__view {
    height: 100%;
  }

  .el-menu-item.is-active {
    background-color: rgba(26, 27, 34) !important;
  }

  .scrollbar-wrapper {
    overflow-x: hidden !important;
  }

  .sidebar-container .el-scrollbar__wrap {
    overflow-y: scroll !important;
  }

  .sidebar-container .el-icon-arrow-right {
    display: none;
  }
</style>

来看一下效果图


菜单实现.png 菜单实现.png

问题点说明

这里有一个bug需要说明一下,在菜单收起的状态下,文字是隐藏的。原因是递归组件上多了一个div导致dom渲染的时候多无用的div,

image.png
而这个div还不能被删除。大家可以阅读这篇文章,讲的很清晰了
【Vue+Element】菜单导航折叠后文字不隐藏
最开始我也是使用的这种方案处理的这个bug。然而在测试的时候,我发现这个东西在IE11下会报错。所以我用了另外一方法。如下图
image.png

权限菜单分配

什么是权限菜单

一个后台管理系统,往往有多个角色,每个角色的操作的菜单也不尽相同。如下图


运营商管理员.png
门岗.png
超级管理员.png

如果路由都是静态被创建出来的,用户知道知道路由地址,就可以操作原本没有权限的菜单,那样是非常不安全的。这也是为什么要做动态路由的原因。

实现流程

  1. api接口 /menu/getAllMenuInfo 查询所有菜单(返回值是一个tree,前文已有这个字段了)
  2. api接口 /menu/getMenuByUserId 根据用户ID查询该用户拥有权限的菜单
let menuKeys = JSON.parse(res.data.menuId).menuCheckedKeys
{
    "code": 200,
    "message": "SUCCESS",
    "data": {
        "roleId": "5e10028557c3d93af3997a3e2815bb87",
        "menuId": "{\"menuCheckedKeys\":[\"39acd7d439674d9687a115adc86e6785\",\"edac960fe25f45dfae18d9a3e96cae24\",\"5a4bc0a658bd46d29f3d29323abaa974\",\"1746de6200ec47ee8f53fd214bb61dbc\",\"9ddcea2cd7a6494f818ce19801a512ed\",\"b3efa8b104964cd1934e33ecab448b28\"],\"checkedKeys\":[\"edac960fe25f45dfae18d9a3e96cae24\",\"5a4bc0a658bd46d29f3d29323abaa974\",\"1746de6200ec47ee8f53fd214bb61dbc\",\"b3efa8b104964cd1934e33ecab448b28\"]}",
        "createDate": "2019-11-27 08:57:33",
        "createPersonId": "b1e282ce10c4a8a8ff844704554ba524",
        "updateDate": "2019-11-27 08:57:33",
        "updatePersonId": null
    }
}

重点说明一下menuId这个字段,值是一个字符串,前端拿到之后要序列化转成对象。

  • res.menuId.menuCheckedKeys这个值就是当前user所拥有的菜单
  • res.menuId.checkedKeys这个值是用来反选的,因为element-ui tree这个控件的问题,强制加上去。
  • 这里需要说明一下。这块本来可以设计的更优雅。当时项目太赶,一直没来得及该,后期我会优化的
    代码片段.png
    对应代码片段54行.png
  1. 将allMenu这个tree转成一维数组
  2. 通过menuKeys跟这个一位数组比对
  3. 将比对结果转成tree
    代码实现如下
import router from '@/router'
import store from '@/store'
import { post, formDataPost } from '@/api/http'
import { Message } from 'element-ui'

/**
 * 路由守卫
 * api /menu/getAllMenuInfo 查询所有菜单
 */
router.beforeEach((to, from, next) => {
  // 如果store中没有token,则证明当前用户没有登录过
  if (!store.state.token.length) {
    // 如果路由刚好在登录页面上,则需要next掉,不然会死循环
    if (to.path === '/login') {
      next()
    } else {
      // 跳转到登录页
      next('/login')
    }
  } else {
    // 如果路由刚好在登录页上,则跳转到 / 路径上
    if (to.path === '/login') {
      return next('/')
    } else {
      // 创建动态路由
      // 不加这个判断页面会死循环
      if (!store.state.authorFlag) {
        // flag变成true
        store.dispatch('authorFlag', {
          flag: true
        })

        // 如果sessionStorage没有router字段
        if (!sessionStorage.getItem('router')) {
          // 1.api接口 /menu/getAllMenuInfo 查询所有菜单
          post('/menu/getAllMenuInfo', {}).then(res => {
            // 获取所有菜单
            const treeMenuList = res.data

            // 校验菜单
            if (!treeMenuList.length) {
              return Message.error('菜单为空!')
            }

            // 2 api接口 /roleMenu/getRoleMenuByUserId 根据用户ID查询该用户有权限的菜单
            formDataPost('/roleMenu/getRoleMenuByUserId', {
              userId: store.state.userId
            }).then(res => {
              // 获取权限菜单id
              const menuKeys = JSON.parse(res.data.menuId).menuCheckedKeys

              // 3.将menuList转为一维数组
              const arrayMenuList = treeToArray(treeMenuList)

              // 4.通过主键id进行过滤
              const filterArrayMenuList = filterArrayByMenuId(arrayMenuList, menuKeys)

              // 5.将过滤好的一位数组转成tree
              const filterTreeMenuList = arrayToTree(filterArrayMenuList, '0')

              // 6.将处理好的tree转成vue-router数据格式
              const resultData = transformHttpDataToVueRouterData(filterTreeMenuList)

              // 7.将数据存到sessionStorage中
              sessionStorage.setItem('router', JSON.stringify(resultData))

              // 8.派发到store中
              store.dispatch('menuList', {
                menuList: resultData
              })

              // 9.创建动态路由
              routerGo(to, next)

              router.replace({ name: resultData[0].name })
            })
          })
        } else {
          // 如果sessionStorage有router字段,则直接创建路由
          routerGo(to, next)
        }
      } else {
        next()
      }
    }
  }
})

/**
 * 添加路由
 * @param to
 * @param next
 */
function routerGo (to, next) {
  // 获取sessionStorage中的router字段,并序列化
  const routerData = JSON.parse(sessionStorage.getItem('router'))

  // layout组件是主页布局文件,需要手动引入
  const asyncRouter = [
    {
      name: 'Layout',
      path: '/layout',
      component: () => import('@/component/layout/Layout.vue'),
      children: []
    }
  ]

  // 通过componentPath创建component组件
  asyncRouter[0].children = transformVueRouterDataToVueRouterComponent(routerData)

  // 在路由末尾添加404
  asyncRouter.push({
    path: '*',
    name: 'notFount',
    component: () => import('@/view/not-fount/NotFound.vue')
  })

  // 添加动态路由
  router.addRoutes(asyncRouter)

  next({ ...to, replace: true })
}

/**
 * 接口列表格式转换成满足vue-router的对应字段
 * @param data
 * @param array
 * @param str
 * @returns {Array}
 */
function transformHttpDataToVueRouterData (data, array = [], str = '/') {
  data.forEach((item, index) => {
    array.push({
      menuId: item.menuId,
      label: item.menuName,
      path: item.menuUrl,
      name: toCamel(item.menuUrl),
      icon: item.menuIcon,
      // 这个字段是用来做浏览器地址链接有用的
      componentPath: `${str}${item.menuUrl}`,
      children: []
    })
    if (item.subMenuList && item.subMenuList.length) {
      array[index].redirect = {
        name: toCamel(item.subMenuList[0].menuUrl)
      }
      transformHttpDataToVueRouterData(item.subMenuList, array[index].children, `${array[index].componentPath}/`)
    } else {
      array[index].redirect = null
    }
  })
  return array
}

/**
 * 将component字段转成component组件
 * @param root
 * @returns {*}
 */
function transformVueRouterDataToVueRouterComponent (root) {
  root.forEach((item) => {
    let path = item.componentPath
    path = path + '/' + toCamel(path.substring(path.lastIndexOf('/') + 1))

    // 因为webpack引入import机制的问题。全部转成变量不能解析
    item.component = () => import(`./view${path}.vue`)
    if (item.children && item.children.length) {
      transformVueRouterDataToVueRouterComponent(item.children)
    }
  })

  return root
}

/**
 * 中划线命名转大驼峰命名
 * @param str
 * @returns {*}
 */
function toCamel (str) {
  str = str.replace(/(\w)/, (match, $1) => `${$1.toUpperCase()}`)
  while (str.match(/\w-\w/)) {
    str = str.replace(/(\w)(-)(\w)/, (match, $1, $2, $3) => `${$1}${$3.toUpperCase()}`)
  }
  return str
}

/**
 * 树转数组
 * @param data
 */
function treeToArray (data) {
  const result = []
  data.forEach(item => {
    function loop (data) {
      result.push({
        menuId: data.menuId,
        parentId: data.parentId,
        menuName: data.menuName,
        menuIcon: data.menuIcon,
        menuUrl: data.menuUrl,
        menuSort: data.menuSort,
        createDate: data.createDate,
        createPersonId: data.createPersonId,
        updateDate: data.updateDate,
        updatePersonId: data.updatePersonId,
        logicDelete: data.logicDelete
      })
      let child = data.subMenuList
      if (child) {
        for (let i = 0; i < child.length; i++) {
          loop(child[i])
        }
      }
    }

    loop(item)
  })
  return result
}

/**
 * 数组转树
 * @param arrayList
 * @param parentId
 */
function arrayToTree (arrayList, parentId) {
  let len = arrayList.length

  function loop (parentId) {
    let res = []
    for (let i = 0; i < len; i++) {
      let item = arrayList[i]
      if (item.parentId === parentId) {
        item.subMenuList = loop(item.menuId)
        res.push(item)
      }
    }
    return res
  }

  return loop(parentId)
}

/**
 * 过滤数组
 * @param list
 * @param keys
 */
function filterArrayByMenuId (list, keys) {
  let arr = []
  list.forEach(item => {
    let id = item.menuId

    keys.forEach(cItem => {
      if (id === cItem) {
        arr.push(item)
        return false
      }
    })
  })

  return arr
}

问题点说明

先用超级管理员登录,然后退出登录。这个时候router已经被创建了一次
如果再用另外一个账号A登录,这个时候会提示rouer被创建过了


组件被重复创建.png

而且原来超级管理员的所属的菜单依然存在,这样是不安全的。而我的决绝方案如下


image.png
只要路由是在登录的页面,我就强制的刷新以此页面,由于是单页面应用。这个时候会 重新执行main.js中的代码,路由也就重新被刷新了。如果有更好的方案,请一定告知!谢谢!!

项目部分截图

image.png image.png image.png

相关文章

网友评论

    本文标题:VUE全家桶+element-ui实现一个后台管理系统

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