vue + elementui 自定义后台权限(带按钮权限)

先看后台的主体样式 左侧菜单和右侧红框(PageTabs)
在这里插入图片描述

第一步:与后端同学讨论菜单的返回格式。可以随便定义,下面是项目中定义的

// type: 5页面权限   10是按钮权限。 visible:是否显示  orderNum:标识
const menu = {
  elements: [
    {
      id: 1529296954965106690,
      parentId: 0,
      resourceId: 0,
      name: '导航维护系统',
      path: '-',
      component: 'management',
      visible: true,
      type: 1,
      icon: 'el-icon-place',
      orderNum: 2,
      props: {},
      remark: '',
      resource: null,
      children: [
        {
          id: 1529296954965106693,
          parentId: 1529296954965106690,
          resourceId: 0,
          name: '产品线配置SDK',
          path: '/setting/productLine',
          component: 'InspectionManagement',
          visible: true,
          type: 5,
          icon: 'el-icon-document-add',
          orderNum: 1,
          props: {},
          remark: '',
          resource: null,
          children: null,
        },
        {
          id: 1529296954965106695,
          parentId: 1529296954965106690,
          resourceId: 0,
          name: '字典管理',
          path: '/setting/dictionaries',
          component: 'dictionaries',
          visible: true,
          type: 5,
          icon: 'el-icon-document-add',
          orderNum: 2,
          props: {},
          remark: '',
          resource: null,
          children: null,
        },
        {
          id: 1529296954965106691,
          parentId: 1529296954965106690,
          resourceId: 0,
          name: '请求记录SDK',
          path: '/setting/requestRecord',
          component: 'RequestRecord',
          visible: true,
          type: 5,
          icon: 'el-icon-document-add',
          orderNum: 3,
          props: {},
          remark: '',
          resource: null,
          children: null,
        },
      ],
    },
  ],
};

第二步就是在登录的时候添加能够显示的菜单项

   // 获取按钮权限
	getPermissionList(elements, permissionList) {
      elements.forEach((element) => {
        if (element.type === 10) {
          permissionList.push(element.path);
        }
        if (element.children) {
          this.getPermissionList(element.children, permissionList);
        }
      });
    },
    //设置权限, res 就是请求接口获取当前用户的权限数据
    afterLogin(res) {
      this.$store.dispatch('fetchData', {
        method: 'GET',
        router: '/account/menu/tree',
      }).then((res) => {
        console.warn(res, '返回数据');
        const { elements } = res.data;
        const naviItems = [];

        // 按钮权限列表
        const permissionList = [];
        this.getPermissionList(elements, permissionList);
        this.$store.commit('setBtnsPermissionList', permissionList);
			
        elements.forEach((element) => {
          const temp = {
            key: String(element.orderNum),
            title: element.name,
            icon: element.icon,
            hidden: !element.visible,
          };
          const tempEle = [];
          if (element.children) {
            element.children.forEach((item) => {
              tempEle.push({
              	//设置key这一步有点繁琐可以跟后端说直接返回
                key: `${element.orderNum}.${item.orderNum}`,
                title: item.name,
                path: item.path,
                hidden: !item.visible,
              });
            });
          }
          if (!element.children) {
            temp.path = element.path;
          } else {
            temp.children = tempEle;
          }
          naviItems.push(temp);
        });
        // 保存权限 vuex做了持久化 保存到了Storage
        this.$store.commit('setNaviItems', naviItems);
        this.$store.commit('setOpenPages', []); // 先清空打开页面列表
        
        // 第一个不是hidden菜单的索引
        let theFirstNoHideMenuIndex = null;
        for (let index = 0; index < naviItems.length; index += 1) {
          const item = naviItems[index];
          if (item.hidden === false) {
            theFirstNoHideMenuIndex = index;
            break;
          }
        }
        
        if (!naviItems[theFirstNoHideMenuIndex].children) {
          const firstNaviItem = naviItems[theFirstNoHideMenuIndex].path.split('/');
          const componentName = firstNaviItem[firstNaviItem.length - 1];
          const { path, title, key } = naviItems[theFirstNoHideMenuIndex];
          this.$store.dispatch('updateOpenPages', { //设置第一次进页面pgeTabs的默认值
            path, title, key, componentName,
          });
          this.$store.commit('setOpenKeys', [key]); //设置第一次进页面pgeTabs的默认值
          this.$store.commit('setSelectedKeys', [key]);//设置左侧菜单的选中项(高亮)
          // 需要设置一下导航默认打开页面
          if (!this.redirect) {
            this.$router.push(path);
          } else {
            this.$router.push(this.redirect);
          }
        } else if (naviItems[theFirstNoHideMenuIndex].children) { // 有子菜单的菜单
          const firstNaviItem = naviItems[theFirstNoHideMenuIndex].children[0].path.split('/');
          const componentName = firstNaviItem[firstNaviItem.length - 1];
          const { path, title, key } = naviItems[theFirstNoHideMenuIndex].children[0];
          this.$store.dispatch('updateOpenPages', {
            path, title, key, componentName,
          });
          this.$store.commit('setOpenKeys', [naviItems[theFirstNoHideMenuIndex].key]);
          this.$store.commit('setSelectedKeys', [key]);

          // 需要设置一下导航默认打开页面
          if (!this.redirect) {
            this.$router.push(path);
          } else {
            this.$router.push(this.redirect);
          }
        }
      });
    }

第三步看下store的数据 (actions.js,mutations.js,states.js,index.js) 下面按顺序展示

onst JSONbig = require('json-bigint')({ storeAsString: true });
export default {
	//右侧点击close图标的时候 关闭页面 PageTabs组件
  deletePageByPath({ commit, state }, params) {
    const { openPages } = state;
    const { path } = params;
    const newOpenPages = [];
    openPages.forEach((item) => {
      if (item.path !== path) {
        newOpenPages.push(item);
      }
    });
    commit('setOpenPages', newOpenPages);
  },
  // 更新 pageTabs列表
  updateOpenPages({ commit, state }, params) {
    const { openPages } = state;
    console.log(openPages);
    const openPagesString = [];
    openPages.forEach((item) => { openPagesString.push(JSON.stringify(item)); });
    if (!openPagesString.includes(JSON.stringify(params))) {
      openPages.push(params);
      commit('setOpenPages', openPages);
    }
  },
  updateNaviData({ commit }, data) { // 更新控制选中的菜单项
    const { key } = data;
    if (key) {
      commit('setSelectedKeys', [key]);
    }
  },

	//下面是本项目封装的axios请求(不用在意)
  // eslint-disable-next-line
  fetchData ({ commit }, {
    method = 'GET', page = {}, router, condition = {},
  }) {
    const { _vm } = this;
    const option = { ...condition };
    let paramPage = '';
    if (JSON.stringify(page) !== '{}') {
      option.page = { current: page.current, size: (typeof page.size === 'undefined' ? global.config.pageSize : page.size) };
      paramPage = `${JSON.stringify(condition) !== '{}' ? '&' : ''}pageNum=${page.pageNum}&pageSize=${page.pageSize}`;
    }
    if (typeof router === 'string') {
      switch (method) {
        default:
        case 'GET':
          // eslint-disable-next-line no-case-declarations
          let params = '';
          Object.keys(condition).forEach((item, key) => {
            params += `${key === 0 ? '' : '&'}${item}=${condition[item]}`;
          });
          return new Promise((resolve, reject) => {
            // _vm.$axios.get(`${global.config.domain}${router}${(params || paramPage) ? '?' : ''}${params}${paramPage}`).then((res) => { if (typeof res !== 'undefined') { resolve(res); } }).catch((e) => { reject(e); });
            _vm.$axios.get(`${global.config.domain}${router}${(params || paramPage) ? '?' : ''}${params}${paramPage}`, {
              transformResponse: [(data) => {
                let newData = '';
                if (typeof data === 'string') {
                  try {
                    newData = JSONbig.parse(data);
                  } catch (e) { /* Ignore */ }
                }
                // // console.log(newData);
                return newData;
              }],
            }).then((res) => { if (typeof res !== 'undefined') { resolve(res); } }).catch((e) => { reject(e); });
          });
        case 'POST':
          // 解决精度丢失的bug --- 默认把bigint 转换成字符串
          return new Promise((resolve, reject) => {
            _vm.$axios.post(`${global.config.domain}${router}`, option, {
              transformResponse: [(data) => {
                if (typeof data === 'string') {
                  try {
                    data = JSONbig.parse(data);
                  } catch (e) { /* Ignore */ }
                }
                return data;
              }],
            }).then((res) => { if (typeof res !== 'undefined') { resolve(res); } }).catch((e) => { reject(e); });
          });
      }
    }
  },
};
export default {
  setToken(state, data) {
    state.token = data;
  },
  setUserInfo(state, data) {
    state.userInfo = data;
  },
  setNaviItems(state, data) {//保存左侧菜单项
    state.naviItems = data;
  },
  setSelectedKeys(state, data) {//保存当前选中高亮的菜单
    state.selectedKeys = data;
  },
  setOpenKeys(state, data) { //展开项有子菜的时候 在能够展开多个菜单项的时候
    state.openKeys = data;
  },
  setOpenPages(state, data) {// 打开的页面列表 pageTabs组件使用
    state.openPages = data;
  },
  setRolesRoutes(state, data) {
    state.rolesRoutes = data;
  },
  setBtnsPermissionList(state, list) {//按钮权限
    state.btnsPermissionList = list;
  }
};
export default {
  pageList: [],
  token: '',
  userInfo: {},
  authStatus: false,
  //
  naviItems: [],
  selectedKeys: [],
  openKeys: [],
  openPages: [],
//
  rolesRoutes: [],
};

index.js


import Vue from 'vue';
import Vuex from 'vuex';
import state from '@/store/states';
// import crypto from '@/assets/js/crypto';
import getters from '@/store/getters';
import mutations from '@/store/mutations';
import actions from '@/store/actions';
import createPersistedState from 'vuex-persistedstate';

Vue.use(Vuex);
export default new Vuex.Store({
  state,
  mutations,
  getters,
  actions,
  plugins: [createPersistedState({
    key: window.atob('GKHealth'),
    reducer: (allStates) => ({
      token: allStates.token,
      userInfo: allStates.userInfo,
      naviItems: allStates.naviItems,
      selectedKeys: allStates.selectedKeys,
      openKeys: allStates.openKeys,
      openPages: allStates.openPages,
      btnsPermissionList: allStates.btnsPermissionList,
    }),
    storage: {
      getItem: (key) => window.sessionStorage.getItem(key),
      setItem: (key, value) => window.sessionStorage.setItem(key, value),
      removeItem: (key) => window.sessionStorage.removeItem(key),
    },
  })],
});

Navi.vue 左侧菜单组件

<template>
  <div class="menu-wrapper">
    <el-menu
      ref="navi"
      :default-active="defaultActive"
      class="el-menu-vertical"
      background-color="#202637"
      text-color="#fff"
      active-text-color="#6882bd"
    >
      <div v-for="item in this.naviItems.filter((x) => !x.hidden)" :key="item.key + item.path">
        <el-menu-item
          v-if="!item.children"
          :key="item.key + item.path"
          :index="String(item.key)"
          @click="onMenuSelect(item.path, item.title, item.key)"
        >
          <i :class="item.icon"></i>
          <span slot="title">{{ item.title }}</span>
        </el-menu-item>
        <template v-else>
          <el-submenu :index="String(item.key + item.path)">
            <template slot="title">
              <i :class="item.icon"></i>
              <span>{{ item.title }}</span>
            </template>
            <el-menu-item
              v-for="itemIn in item.children.filter((x) => !x.hidden)"
              :key="itemIn.key"
              :index="itemIn.key"
              @click="onMenuSelect(itemIn.path, itemIn.title, itemIn.key)"
              >{{ itemIn.title }}</el-menu-item
            >
          </el-submenu>
        </template>
      </div>
    </el-menu>
  </div>
</template>

<script>
import { mapState } from 'vuex';

export default {
  data() {
    return {
      defaultActive: '',
      defaultOpen: [],
      list: [],
    };
  },
  computed: {
  	//naviItems权限列表  selectedKeys当前选择的菜单 高亮
    ...mapState(['naviItems', 'selectedKeys']),
    openPages: {
    	// openPages 打开的页面 右侧菜单要是使用
      set(data) {
        this.$store.commit('setOpenPages', data);
      },
      get() {
        return this.$store.state.openPages;
      },
    },
  },
  watch: {
    $route(newVal) {
      const componentInfo = this.list.find((item) => item.path && item.path === newVal.path);
      if (JSON.stringify(componentInfo) !== '{}' && !componentInfo.hidden) {
        const { path, title, key } = componentInfo;
        this.updateKeyTitle(path, title, key);
      }
    },
  },
  mounted() {
    this.findComponent(this.naviItems);
    this.defaultActive = this.selectedKeys.join('');
  },
  methods: {
    onMenuSelect(path, label) {
      if (this.$route.path !== path) {
        this.$router.push(path);
      }
    },
    updateKeyTitle(path, title, key) {
      this.defaultActive = key;
     // this.$store.commit('setSelectedKeys', [key]);	
      const componentName = path.split('/')[2];//获取在项目中 页面的名字
      console.log('添加tab');
      this.$store.dispatch('updateOpenPages', {//更新高亮选中菜单
        path,
        title,
        key,
        componentName,
      });
    },
    findComponent(list) {
      list.forEach((item) => {
        this.list.push(item);
        if (item.children) {
          this.findComponent(item.children);
        }
      });
    },
  },
};
</script>

<style lang="stylus" scoped>
.menu-wrapper
   width: 220px
  .el-menu-vertical
    height: calc(100vh - 50px)
</style>

PageTabs.vue 右侧组件

<template>
  <div class="page">
    <span
      @click="handleChangePath(page)"
      :class="{ ml10: index > 0, active: currentPath === page.path, cp: currentPath !== page.path }"
      v-for="(page, index) in openPages"
      :key="index"
    >
      {{ page.title }}
      <i
        v-if="openPages.length > 1"
        @click.stop="handleClosePath(page, index)"
        class="el-icon-error cp"
      ></i>
    </span>
  </div>
</template>

<script>
export default {
  data() {
    return {
      currentPath: '',
    };
  },
  computed: {
    openPages: {
      set(data) {
        this.$store.commit('setOpenPages', data);
      },
      get() {
        return this.$store.state.openPages;
      },
    },
  },
  mounted() {
    this.currentPath = this.$route.path;
  },
  watch: {
    $route(value) {
      this.currentPath = value.path;
    },
  },
  methods: {
    handleClosePath(page) {
      this.openPages.forEach((item, x) => {
        if (item.path === page.path) {
        	//这里有重新设置了openPages  可以看下上面store actions.js
          this.$store.dispatch('deletePageByPath', { path: page.path });
          if (this.$route.path === item.path) { // 关闭当前页
            const localPage = this.openPages;
            const localPageTempFlag = typeof this.openPages[x - 1] !== 'undefined';
            this.$router.push(localPageTempFlag ? localPage[x - 1].path : localPage[x].path);
            const keyTemp = localPageTempFlag ? localPage[x - 1].key : localPage[x].key;
            this.$store.dispatch('updateNaviData', { key: keyTemp });//更新左侧高亮导航
          }
        }
      });
    },
    handleChangePath(page) {
      if (this.$route.path !== page.path) {
        this.$router.push(page.path);
        this.$store.dispatch('updateNaviData', { key: page.key });
      }
    },
  },
};
</script>

<style lang="stylus" scoped>
@import "../assets/css/common.stylus"
.page
  height:40px;
  background: #F0F6FF
  width 100%
  disflex(row, flex-start, flex-end)
  padding 0 10px
  box-sizing: border-box
  span
    display: inline-block
    font-size: 13px
    color #666
    height: 28px
    line-height: 28px
    background: #f0f6ff;
    border-radius: 4px 4px 0px 0px;
    border: 1px solid #e6e8eb;
    border-bottom: none
    position relative
    z-index 100
    padding 0px 22px 0 10px
    overflow: hidden
    text-overflow: ellipsis;
    white-space: nowrap
    &.active
      transform: translateY(1px)
      background: #f9f9f9;
      border-color: #d4dce9
    i
      color #999
      position: absolute
      right: 5px
      top 7px
</style>

下面重要的一步设置路由 新建permission.js 在main.js里面引入

import router from '@/router';
import store from '@/store';

const whiteList = ['/', '/LoginRedirect'];

router.beforeEach(async (to, from, next) => {

  const hasToken = store.state.token;//已经登录过的用户
  if (hasToken) {
    if (to.path === '/' || to.path === '/LoginRedirect') {
      next();
    } else {
      const hasRolesRoutes = store.state.rolesRoutes && store.state.rolesRoutes.length > 0;
      // 已经有了权限菜单 则继续页面跳转
      if (hasRolesRoutes) {
        next();
      } else {//没有权限的设置权限。
        try {
        //   根据登录接口返回的树生成路由信息嵌入Layout.vue中
          const childrenRoutes = [];
          const { naviItems } = store.state;
          
          //这一步很多余是根据文件名字添加路由的可以自己优化(这里在router.js 新增页面不新增数据 都是在这一步添加)。所有当给后端路由表的时候要注意)。
          const formatRoutes = (items) => {
            items.forEach((item) => {
              if (item.path) {
                const name = item.path.split('/')[2];
                childrenRoutes.push({
                  path: item.path,
                  name,
                  component: (resolve) => require([`@/views${item.path}.vue`], resolve),
                });
              }
              if (item.children) { formatRoutes(item.children); }
            });
          };
          formatRoutes(naviItems);
     
          const asyncRoutes = [{
            path: '/',
            name: 'Layout',
            component: () => import('@/components/Layout.vue'),
            children: childrenRoutes,
          }, {
            path: '*',
            component: () => import('@/views/404.vue'),
          }];
          store.commit('setRolesRoutes', asyncRoutes);
          router.addRoutes(asyncRoutes);//添加路由
          next({ ...to, replace: true });
        } catch (error) {
          store.commit('setToken', '');
          next('/');
        }
      }
    }
  } else if (whiteList.indexOf(to.path) !== -1) {
    next();
  } else {
    store.commit('setToken', '');
    if (to.path === '/') {
      next('/');
    } else {
      next('/');
    }
  }
});

版权声明:本文为qq_38743783原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。