vue-element-admin登陸邏輯,以及動態添加路由,顯示側邊欄

這段時間在研究element-admin,感受這個庫有許多值得學習的地方,我學習這個庫的方法是,先看它的路由,順着路由,摸清它的邏輯,有點像順藤摸瓜。css

這個庫分的模塊很是清晰,適合多人合做開發項目,可是若是一我的去用這個庫開發後臺,步驟顯的有點繁瑣,特別是調用後端接口,以前一個方法就調用到了,可是用這個庫,須要前後分幾步調用。html

好比說調用一個登錄接口:點擊登錄按鈕----》調用store中的方法----》調用api中對應登錄的方法---》request.js中封裝的axios方法vue

4步!!!!,讓我看來確實是有點繁瑣,這個問題到後面解決,經過本身封裝的axios方法,直接調用後臺接口,目前不知道會不會遇到其它問題。好了,接下來進入正題!!!ios

 

接下來先介紹一下,element-admin的登陸邏輯git

一、先看登陸方法裏寫什麼:es6

handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true;
          //調用user模塊紅的login
          console.log("點擊登錄按鈕")
          this.$store.dispatch('user/login', this.loginForm).then(() => {
            console.log("登陸成功");
            this.$router.push({ path: this.redirect || '/' });
            this.loading = false;
          }).catch(() => {
            this.loading = false;
          })
        } else {
          console.log('error submit!!');
          return false;
        }
      })
    }

經過上面紅色代碼,能夠看出,點擊過登陸按鈕後,調用了$store裏的一個方法,名叫logingithub

二、下面來看看$store裏的這個login方法:vue-router

import { login, logout, getInfo,self} from '@/api/user'


const actions = { // user login login({ commit }, userInfo) { const { username, password } = userInfo; return new Promise((resolve, reject) => { console.log("vuex中的請求") login({ username: username.trim(), password: password }).then(response => { console.log('vuex中') console.log(response); const { data } = response; commit('SET_TOKEN', data.token);//存在vueX中 setToken(data.token);//存在cookie中 resolve(); }).catch(error => { console.log(error); reject(error); }) }) },

咿,怎麼兩個login,熟悉vuex的話應該知道,第一個login是$store中的方法,第二個login方法是,api裏的login方法,用來調用接口的vuex

三、再來看看api中的login方法:element-ui

import request from '@/utils/request'

export function login(data) {
  return request({
    url: '/user/login',
    method: 'post',
    data
  })
}

上面是api中的login方法,它調用了request.js,request.js是封裝了axios,是用來請求後臺接口的,若是這個接口請求成功了,就回到了第一步中的.then()方法中

代碼是,路由跳轉到首頁,進入正式頁面!!!

重點來了!!!

之因此是稱之爲權限,也就是必須知足必定的條件纔可以訪問到正常頁面,那麼若是不知足呢?若是我沒有token,讓不讓我進正常頁面呢??

確定是不讓的,那我沒有token,該去哪?答案是還待在登陸頁,哪都去不了,那麼這些處理應該在哪寫呢?答案是,permission.js模塊

這個js在main.js引入,其實就是個路由攔截器:來看看它的代碼:

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar  一個進度條的插件
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'

NProgress.configure({ showSpinner: false }) // NProgress Configuration  是否有轉圈效果

const whiteList = ['/login'] // 沒有重定向白名單

router.beforeEach(async(to, from, next) => {
  // 開始進度條
  NProgress.start()

  // 設置頁面標題
  document.title = getPageTitle(to.meta.title)

  // 肯定用戶是否已登陸
  const hasToken = getToken()

  if (hasToken) {
    if (to.path === '/login') {
      // 若是已登陸,則重定向到主頁
      next({ path: '/' })
      NProgress.done()
    } else {
      const hasGetUserInfo = store.getters.name;
      console.log(hasGetUserInfo);
      if (hasGetUserInfo) {
        console.log("有用戶信息");
        next();
      } else {
        console.log("無用戶信息")
        try {
          // 得到用戶信息
          await store.dispatch('user/getInfo');
          //實際是請求用戶信息後返回,這裏是模擬數據,直接從store中取
          const roles=store.getters.roles; store.dispatch('permission/GenerateRoutes', { roles }).then(() => { // 生成可訪問的路由表
            router.addRoutes(store.getters.addRouters); // 動態添加可訪問路由表
            router.options.routes=store.getters.routers; next({ ...to, replace: true });// hack方法 確保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
 }) // next()
        } catch (error) {
          // 刪除token,進入登陸頁面從新登陸
          await store.dispatch('user/resetToken');
          Message.error(error || 'Has Error');
          next(`/login?redirect=${to.path}`);
          NProgress.done();
        }
      }
    }
  } else {
    /* has no token*/
    if (whiteList.indexOf(to.path) !== -1) {
      // 在免費登陸白名單,直接去
      next()
    } else {
      // 沒有訪問權限的其餘頁面被重定向到登陸頁面。
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  // 完成進度條
  NProgress.done()
})

一看代碼好多,不過不要怕,我來分析一下它的狀況,不就是點if else嘛

從上面代碼看,每次路由跳轉,都要從cookie中取token,

那麼能夠分兩種狀況,有token和無token

有token:再看看是否是去登陸頁的,登陸頁確定不能攔截的,若是是登陸頁就直接放行。若是不是登陸頁,就要看看本地有沒有用戶信息,看看cookie中有沒有用戶信息(不必定是token,也多是localstorage)。若是有用戶信息,放行。若是沒有用戶信息,就調用接口去獲取登陸信息,而後後面還有一點代碼,涉及到了動態添加路由(這裏先說到這,後面具體說動態添加權限路由的事)。獲取到用戶信息後放行。若是在獲取用戶信息的過程當中報錯,則回到登陸頁

無token:先看看用戶要進入的頁面是否是在白名單內,通常登陸、註冊、忘記密碼都是在白名單內的,這些頁面,在無token的狀況下也是直接放行。若是不在白名單內,滾回登陸頁。

以上就是element-admin的登陸邏輯了。不知道可否幫助到你,可是寫下來,讓本身的思路更清晰,也是不錯的。

 

 

下面來講一下,element-admin的動態權限路由,顯示側邊欄是什麼邏輯

首先要了解一下,側邊欄是如何渲染出來的,看看layout/components/slibar/index.vue有這樣一段代碼:

<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />

計算屬性中有這樣一段代碼:

  routes() {
      return this.$router.options.routes
    },

這個routes,是路由的元信息!!!是一個數組

看到這就應該明白,側邊欄是如何渲染出來的,

再來看看router.js裏的代碼:

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

/* Layout */
import Layout from '@/layout'

/**
 * 注意: 子菜單隻在路由子菜單時出現。長度> = 1
 * 參考網址: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
 *
 * hidden: true                   若是設置爲true,項目將不會顯示在側欄中(默認爲false)
 * alwaysShow: true               若是設置爲true,將始終顯示根菜單
 *                                若是不設置alwaysShow, 當項目有多個子路由時,它將成爲嵌套模式,不然不顯示根菜單
 * redirect: noRedirect           若是設置noRedirect,則不會在麪包屑中重定向
 * name:'router-name'             the name is used by <keep-alive> (must set!!!)
 * meta : {
    roles: ['admin','editor']    控制頁面角色(能夠設置多個角色)
    title: 'title'               名稱顯示在側邊欄和麪包屑(推薦集)
    icon: 'svg-name'             圖標顯示在側欄中
    breadcrumb: false            若是設置爲false,則該項將隱藏在breadcrumb中(默認爲true)
    activeMenu: '/example/list'  若是設置路徑,側欄將突出顯示您設置的路徑
  }
 */

/**
 * constantRoutes
 * 沒有權限要求的基本頁
 * 全部角色均可以訪問
 * 不須要動態判斷權限的路由
 */
export const constantRoutes = [

  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },

  {
    path: '/',
    component: Layout,
    redirect: '/self',
    children: [{
      path: 'self',
      name: 'Self',
      component: () => import('@/views/self/index'),
      meta: { title: '首頁', icon: 'dashboard' }
    }]
  },

  {
    path: '/example',
    component: Layout,
    redirect: '/example/table',
    name: 'Example',
    meta: { title: 'Example', icon: 'example' },
    children: [
      {
        path: 'table',
        name: 'Table',
        component: () => import('@/views/table/index'),
        meta: { title: 'Table', icon: 'table'}
      },
      {
        path: 'tree',
        name: 'Tree',
        component: () => import('@/views/tree/index'),
        meta: { title: 'Tree', icon: 'tree',breadcrumb: true},
        hidden: false,//在側邊欄上顯示  true爲不顯示  當父路由的字路由爲1個時,不顯示父路由,直接顯示子路由
        alwaysShow:false,//默認是false   設置爲true時會忽略設置的權限 一致顯示在跟路由上
      }
    ]
  },
  
  {
    path: '/form',
    component: Layout,
    children: [
      {
        path: 'index',
        name: 'Form',
        component: () => import('@/views/form/index'),
        meta: { title: 'Form', icon: 'form' }
      }
    ]
  },

  {
    path: '/nested',
    component: Layout,
    redirect: '/nested/menu1',
    name: 'Nested',
    meta: {
      title: 'Nested',
      icon: 'nested'
    },
    children: [
      {
        path: 'menu1',
        component: () => import('@/views/nested/menu1/index'), // Parent router-view
        name: 'Menu1',
        meta: { title: 'Menu1' },
        children: [
          {
            path: 'menu1-1',
            component: () => import('@/views/nested/menu1/menu1-1'),
            name: 'Menu1-1',
            meta: { title: 'Menu1-1' }
          },
          {
            path: 'menu1-2',
            component: () => import('@/views/nested/menu1/menu1-2'),
            name: 'Menu1-2',
            meta: { title: 'Menu1-2' },
            children: [
              {
                path: 'menu1-2-1',
                component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
                name: 'Menu1-2-1',
                meta: { title: 'Menu1-2-1' }
              },
              {
                path: 'menu1-2-2',
                component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
                name: 'Menu1-2-2',
                meta: { title: 'Menu1-2-2' }
              }
            ]
          },
          {
            path: 'menu1-3',
            component: () => import('@/views/nested/menu1/menu1-3'),
            name: 'Menu1-3',
            meta: { title: 'Menu1-3' }
          }
        ]
      },
      
    ]
  },
  {
    path: 'external-link',
    component: Layout,
    children: [
      {
        path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
        meta: { title: 'External Link', icon: 'link' }
      }
    ]
  },
  {
    path: '/self',
    component: Layout,
    children: [
      {
        path: 'index',
        name: 'self',
        component: () => import('@/views/self'),
        meta: { title: 'self', icon: 'form' }
      }
    ]
  },

  // 404頁面必須放在最後!!
  // { path: '*', redirect: '/404', hidden: true }
]

// 建立路由
const createRouter = () => new Router({
  // mode: 'history', // 須要服務支持
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

var router = createRouter()

// 重置路由
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}


//異步掛載的路由
//動態須要根據權限加載的路由表 
export const asyncRouterMap = [
  {
    path: '/permission',
    component: Layout,
    name: 'permission',
    redirect: '/permission/index222',
    meta: {title:'permission', role: ['admin','super_editor'] }, //頁面須要的權限
    children: [
      { 
        path: 'index222',
        component: () => import('@/views/self'),
        name: 'index222',
        meta: {title:'權限測試1',role: ['admin','super_editor'] }  //頁面須要的權限
      },
      { 
        path: 'index333',
        component: () => import('@/views/self'),
        name: 'index333',
        meta: {title:'權限測試2',role: ['admin','super_editor'] }  //頁面須要的權限
      }
    ]
  },
  { path: '*', redirect: '/404', hidden: true }
];



export default router

注意以上代碼中紅色的代碼,這個routes中分兩塊路由配置,一塊是固定的,無權限的路由配置,也就是不論是管理員身份,仍是超級管理員身份,都會顯示的路由配置。

第二塊是,帶權限的路由配置,根據用戶權限來顯示側邊欄。注意,帶權限的配置裏的meta中都有role項,表明是權限

 

首先,咱們在獲取用戶信息時,會獲得這個用戶有哪些權限,是一個數組,假如是這樣的

commit('SET_ROLES',['admin','super_editor']);//自定義權限

這個用戶的權限有這些(admin、super_editor),而後再根據用戶權限來篩選出符合的動態添加的路由,

何時篩選呢?

這就用到登陸時的攔截器了,上面遇到過,就在哪執行,來看看那裏都是寫了一些什麼代碼:

拿到這看看:

          // 得到用戶信息
          await store.dispatch('user/getInfo');
          //實際是請求用戶信息後返回,這裏是模擬數據,直接從store中取
          const roles=store.getters.roles;
          store.dispatch('permission/GenerateRoutes', { roles }).then(() => { // 生成可訪問的路由表
            router.addRoutes(store.getters.addRouters); // 動態添加可訪問路由表
            router.options.routes=store.getters.routers;
            next({ ...to, replace: true });// hack方法 確保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
          })

routes其實就是上面的兩個權限組成的數組,而後傳入了GenerateRoutes方法內,(注意es6語法,看不懂的去了解一下es6),再看看GenerateRoutes中的代碼:

import { asyncRouterMap, constantRoutes } from '@/router';

function hasPermission(roles, route) {
    if (route.meta && route.meta.role) {
        return roles.some(role => route.meta.role.indexOf(role) >= 0)
    } else {
        return true
    }
}
  
const permission = {
    namespaced: true,
    state: {
        routers: constantRoutes,
        addRouters: []
    },
    mutations: {
      SET_ROUTERS: (state, routers) => {
        state.addRouters = routers;
        state.routers = constantRoutes.concat(routers);
      }
    },
    actions: {
        GenerateRoutes({ commit }, data) {//roles是用戶所帶的權限
            return new Promise(resolve => { const { roles } = data; const accessedRouters = asyncRouterMap.filter(v => { // if (roles.indexOf('admin') >= 0) {
                    // return true;
                    // };
                    if (hasPermission(roles, v)) { if (v.children && v.children.length > 0) { v.children = v.children.filter(child => { if (hasPermission(roles, child)) { return child } return false; }); return v } else { return v } } return false; }); commit('SET_ROUTERS', accessedRouters); resolve(); }) }
    }
};
  
export default permission;
GenerateRoutes方法辦了一件事,就是把動態路由配置裏符合用戶權限的配置篩選出來,組成一個數組,而後,和固定路由合併到了一塊兒,存到了vuex中,

而後調用了這兩句代碼:

          router.addRoutes(store.getters.addRouters); // 動態添加可訪問路由表
            router.options.routes=store.getters.routers;
router.addRoutes()方法是,動態添加路由配置,參數是符合路由配置的數組,
而後將路由元信息,變成合並後的路由元信息,由於渲染側邊欄須要用到,
這兩句代碼,順序不能變,缺一不可,缺了addRoutes,點擊側邊欄的動態路由會無反應,缺了options,側邊欄顯示不出來動態添加的路由!!!!

以上就是element-admin的動態路由了,睡覺。。。。

今天正式開始用這個模板開發了,想說一下,這個模板有許多模塊,我用不到,好比說mock文件,當咱們不用它自己的api去請求接口時,徹底能夠將它刪除,我是今天把它原有的登陸邏輯去掉了,登陸時,就不會有mock的事了。
因而,我一個一個文件去註釋,對運行不影響的都刪除了,test文件一下刪除了,還有些不知道是幹什麼的js也去了,這樣項目看着對我來講比較輕巧,也比較熟悉。另外,哪些utils。還有store裏不用的方法均可以去掉。
當我把登陸改了以後,能去掉一大堆東西好很差。ok,有時間整理一個輕巧的模板,掛到碼雲上

更新一個bug,目前頁面上的路由攔截器的邏輯,並非我如今用的邏輯,不過相差不遠,項目上線後,發現刷新動態加載的路由頁面,頁面白屏,調試一下,發現是路由攔截器在重複跳轉頁面,這問題很奇怪,
本地沒有問題,而後琢磨了半天,既然是重複跳轉,它有一個特徵就是:to.path==from.path;那麼能夠利用這一特質,去攔截這種狀況,
if(to.path==from.path){
    next();
    return;
}

 

監聽瀏覽器前進後退事件

window.addEventListener("popstate", function () {
  backStatus=true;
  return;
})
相關文章
相關標籤/搜索