vue權限管理系統

vue權限系統

後臺管理系統通常都會有權限模塊,用來控制用戶能訪問哪些頁面和哪些數據接口。大多數管理系統的頁面都長這樣。 前端

image

左邊爲菜單,分爲兩級,右邊爲圖表顯示區域,有增刪改查的按鈕。vue

表的結構

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_auth_rule
-- ----------------------------
DROP TABLE IF EXISTS `t_auth_rule`;
CREATE TABLE `t_auth_rule` (
  `id_pk` bigint(20) NOT NULL AUTO_INCREMENT,
  `auth_id` varchar(128) NOT NULL COMMENT '權限Id',
  `pauth_id` varchar(128) DEFAULT NULL COMMENT '父級Id',
  `auth_name` varchar(255) NOT NULL COMMENT '權限名稱',
  `auth_icon` varchar(255) NOT NULL COMMENT '權限圖標',
  `auth_type` smallint(6) NOT NULL COMMENT '權限類型,BIT表示其屬性\r\n            0x00表示可顯示的菜單權限節點;\r\n            0x01表示普通節點',
  `auth_condition` text COMMENT '條件',
  `remark` varchar(255) DEFAULT NULL COMMENT '備註',
  `is_menu` smallint(255) DEFAULT '0' COMMENT '是否爲菜單,0表示非,1表示是',
  `weight` int(11) NOT NULL DEFAULT '0' COMMENT '權重',
  `rule` varchar(256) DEFAULT NULL COMMENT '規則路徑主要對應菜單或方法的路徑名稱',
  `cr_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
  `up_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
  PRIMARY KEY (`id_pk`),
  UNIQUE KEY `AK_auth_id` (`auth_id`)
) ENGINE=InnoDB AUTO_INCREMENT=264 DEFAULT CHARSET=utf8 COMMENT='權限規則表,記錄權限相關的信息,權限以父子關係存在,菜單是權限的一種。';

SET FOREIGN_KEY_CHECKS = 1;


SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_role_auth
-- ----------------------------
DROP TABLE IF EXISTS `t_role_auth`;
CREATE TABLE `t_role_auth` (
  `id_pk` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_id_fk` varchar(32) DEFAULT NULL COMMENT '角色id',
  `auth_id_fk` varchar(128) DEFAULT NULL COMMENT '權限id',
  `aa` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id_pk`)
) ENGINE=InnoDB AUTO_INCREMENT=77 DEFAULT CHARSET=utf8 COMMENT='角色與權限的關係表';

SET FOREIGN_KEY_CHECKS = 1;

稍微解釋一下表結構,t_auth_rule 表用來存儲對應的權限菜單,通常來講,菜單分爲一級和二級菜單,rule字段對應前端的路由規則;而按鈕爲第三級,rule對應的是接口url地址。
t_role_auth 表爲角色權限關聯表,一個角色擁有哪些權限是經過這張表查出來的。固然還有一個role表,還有一個帳號表,帳號表裏有一個role的外鍵。
這樣就是一個帳號 --> 角色 --> 權限的關係。vue-router

對於菜單的權限,經過路由表匹配

// 本地寫好路由列表(須要進行動態匹配的)
export const routerList: Array<RouteConfig> = [
  // 首頁
  {
    path: '/',
    name: '_home',
    redirect: '/home',
    component: Layout,
    meta: {
      hideInMenu: true,
      notCache: true
    },
    children: [
      {
        path: 'home',
        name: 'home',
        meta: {
          hideInMenu: true,
          title: '首頁',
          notCache: true,
          icon: 'md-home'
        },
        component: () => import('@/views/overview-operations/data-center/DataCenter.vue')
      }
    ]
  }
]

// 負責將後臺返回的 菜單列表 轉成vue-router所須要的 router list
export function toRouterComponent(menuList: Array<any>) {
  if (!menuList.length) return []

  let routerArr = [];
  for (let j = 0; j < menuList.length; j++) {
    let obj;
    let firstIndex = routerList.findIndex(i => i.path === menuList[j].url); // 一級菜單
    if (firstIndex !== -1) {
      let children: Array<RouteConfig> = []
      obj = {
        path: routerList[firstIndex].path,
        component: routerList[firstIndex].component,
        redirect: routerList[firstIndex].redirect,
        name: routerList[firstIndex].name,
        meta: routerList[firstIndex].meta,
        children
      };

      // 若是有子菜單
      if (menuList[j].children && menuList[j].children.length) {
        for (let k = 0; k < menuList[j].children.length; k++) {
          const _children = routerList[firstIndex].children!
          let secondIndex = _children.findIndex(i => {
            let fullpath = '';
            if (routerList[firstIndex].path === '/') {
              fullpath = `${routerList[firstIndex].path}${i.path}`;
            } else {
              fullpath = `${routerList[firstIndex].path}/${i.path}`;
            }
            return fullpath === menuList[j].children[k].url;
          });
          if (secondIndex !== -1) {
            obj.children.push(_children[secondIndex]);
          }
        }
      }
    }

    if (obj) {
      routerArr.push(obj);
    }
  }

  return routerArr;
}

// 根據菜單權限,獲取路由數組
// 本地只保存後臺返回的菜單,在頁面刷新的時候從本地拿到菜單從新調用toRouterComponent生成 路由數組
export function getRouterList() {
  if (!storage.get('username')) {
    storage.set('menuTree', ''); // 清空菜單權限數據
  }
  let menuList = storage.get('menuTree') ? storage.get('menuTree') : [];

  const routerArr = toRouterComponent(menuList);
  return routerArr;
}

menuList,菜單數組(或對象), 由後臺返回; routerList爲前端定義的路由表;遍歷routerList,若是routerList的path在menuMap裏能找到的話,就表示該路由存在。最後生成一個過濾後的路由表,用vue提供的addRoutes方法動態添加到路由中,並把過濾後的路由表存到本地。api

在頁面刷新的時候,從本地獲取路由表,添加到路由表中,代碼以下,constRouterArr爲基礎路由表,好比登陸,404等 數組

注意這一步有個問題,因爲我寫的storage庫用了JSON.stringify,把路由表中的component(實際爲一個函數)丟失了,因此在從本地獲取路由的時候,還要從新生成一個新的路由表,從新把component加上去,即把上面的addrouters從新執行一遍緩存

對於按鈕的權限

if (res.data.auth_rule_map) {
    let obj = {}
    Object.keys(res.data.auth_rule_map).forEach(i => {
      // 將全部的按鈕放到一個obj裏 key 爲接口地址  
      if (res.data.auth_rule_map[i].is_menu === 0) {  // 若是是按鈕
        obj[res.data.auth_rule_map[i].rule] = 1
      }             
    })
    storage.set("btnList", obj);
    storage.set("menuTree", res.data.auth_rule_map);
}

auth_rule_map爲接口返回權限map,把按鈕的權限過濾出來存到本地
將map添加到每一個路由組件的data裏,(這裏有一個問題,怎麼判斷一個組件是不是路由組件),目前想到的是經過組件name來判斷,把全部的路由組件放到一個數組裏作判斷。 ide

在組件內部的按鈕上加上v-if,若是this.uri__裏的uri在uriMap裏存在就顯示。
也能夠經過方法來判斷,以下面的__isBtnShow,不只能夠控制按鈕的顯示隱藏,還能夠控制其樣式,好比顏色等,更加靈活,推薦使用方法來控制函數

uri = {
    ADD_MEMBER: '/api/add_member'
}

export default function install (Vue) {
  const uriMap = storage.get('btnList')
  //uriMap['/admin/api/auth_rule/update_auth_rule.action'] = 1
  Vue.mixin({
    created() {
      const arr = ['MemberManage', 'PayManage', '...']
      if (arr.indexOf(this.$options.name) !== -1) {
        this.dataUri__ = uriMap
        this.uri__ = uri  
      }
    },
    data() {
      return {
        dataUri__: {}
      }
    },
    methods: {
      __isBtnShow(uri) {
        return uriMap[uri] ? 'display: inline-block' : 'display: none'
      },
    }
  })
}

<Button v-if="dataUri__[uri__.ADD_MEMBER]">添加會員</Button>

// 經過方法來控制,更加靈活
<Button :style="__isBtnShow(uri__.ADD_MEMBER)">添加會員</Button>

登出的問題

**登出後要清空緩存,routerArr,btnList 等。
因爲以前登陸,調用addRouter把權限上個帳號的路由表加進去了,因此登出後要location.reload()一次,從新實例化路由表,去掉動態添加的路由,只保留基礎路由。
location.reload()體驗不是太好,可是vue-router沒有提供動態刪除路由的api,好比 deleteRouter。**this

同時登兩個帳號,致使刷新頁面的時候,前者頁面的本地緩存被覆蓋,權限菜單等數據發生變化,路由表也發生變化

能想到的解決方法是存一個loginIndex 來表示登陸帳號的個數,好比第一次登陸的時候存一個loginIndex=0, 後面存數據的時候都把這個參數帶上;後面登多個帳號的時候個loginIndex++,這樣localStorage的key就是一個動態的(這樣仍是不行)
最簡單的方法是存到localStorage裏,只有登出纔會清空緩存,只能登一個帳號。url

相關文章
相關標籤/搜索