iview admin動態路由、權限控制個人見解

iview admin目前是沒有給我處理好權限這塊的邏輯。所以,權限這塊還是得我們自己去擼。(臉上笑嘻嘻、心裏mmp!)

  • 思路

    做權限,說到底就是爲了讓不同權限的用戶, 可以訪問不同的功能模塊。比如我是admin權限, 我就可以爲所欲爲了,你服不服??? 那我如果是隻有某個權限,可能我只能使用某一個或者某幾個功能(也就是前端頁面)。頁面內的具體功能也是一樣的道理,舉個栗子:我是admin權限,我可以對某個表格進行:增、刪、改、查、上傳等一系列操作。如果我不是,可能就收到權限的限制,只能進行查看和上傳,其他的功能是無權限進行操作的。好了,大概的思路就是這樣的,光BB誰不會啊;那具體看一下基於iview admin的權限是如何實現。

  • 具體實現邏輯

    我們從上面的思路具體分析一下,首先,不同用戶可以訪問不同的模塊(頁面),所以我們要做到不同身份的用戶進入系統的時候,就會根據這個用戶的身份顯示不同的頁面,顯示不同的菜單。我們的項目正常是路由(router.js)是在本地配置好的一個路由文件,所以,要想實現動態的路由,我們就必須讓router.js實現動態生成。我選用的方案是vue-router 2.2版本新增了一個router.addRoutes(routes)方法去實現。(可能還有一些“巨佬”是用的其他方案,'佩服三連')。那用了addRouter方法之後呢,實際上我們本地的router.js是只需要一些基本的路由了,其他都可以刪掉了。感覺有點囉嗦了,我先貼代碼壓壓驚吧。

import Main from '@/components/main'
/**
 * iview-admin中meta除了原生參數外可配置的參數:
 * meta: {
 *  title: { String|Number|Function }
 *         顯示在側邊欄、麪包屑和標籤欄的文字
 *         使用'{{ 多語言字段 }}'形式結合多語言使用,例子看多語言的路由配置;
 *         可以傳入一個回調函數,參數是當前路由對象,例子看動態路由和帶參路由
 *  hideInBread: (false) 設爲true後此級路由將不會出現在麪包屑中,示例看QQ羣路由配置
 *  hideInMenu: (false) 設爲true後在左側菜單不會顯示該頁面選項
 *  notCache: (false) 設爲true後頁面在切換標籤後不會緩存,如果需要緩存,無需設置這個字段,而且需要設置頁面組件name屬性和路由配置的name一致
 *  access: (null) 可訪問該頁面的權限數組,當前路由設置的權限會影響子路由
 *  icon: (-) 該頁面在左側菜單、麪包屑和標籤導航處顯示的圖標,如果是自定義圖標,需要在圖標名稱前加下劃線'_'
 *  beforeCloseName: (-) 設置該字段,則在關閉當前tab頁時會去'@/router/before-close.js'裏尋找該字段名對應的方法,作爲關閉前的鉤子函數
 * }
 */

var Router = [{
        path: '/',
        name: '_home',
        redirect: '/home',
        component: Main,
        meta: {
            icon: 'md-home',
            title: '首頁',
            hideInMenu: true
        },
        children: [{
            path: '/home',
            name: 'home',
            meta: {
                icon: 'md-home',
                title: '首頁'
            },
            component: () => import('@/view/home/home.vue')
        }]
    },
    {
        path: '/401',
        name: 'error_401',
        meta: {
            hideInMenu: true
        },
        component: () => import('@/view/error-page/401.vue')
    },
    {
        path: '/500',
        name: 'error_500',
        meta: {
            hideInMenu: true
        },
        component: () => import('@/view/error-page/500.vue')
    },
    {
        path: '*',
        name: 'error_404',
        meta: {
            hideInMenu: true
        },
        component: () => import('@/view/error-page/404.vue')
    }
]

export default Router

吶~ 我本地路由文件只留下了這些基本的路由,!!!那我其他的路由怎麼辦??--- 不慌,具體的業務路由代碼先不用管,交給addRouter這位兄dei和你們後端的兄dei就好了。你要做的就是拿到後端給你返回的list,然後用addRouter方法添加到router裏就好了。
接下來,你把你之前的路由文件原封不動的ctrl+c給後端的兄dei,就是你刪除掉的那些路由,這裏說明一下:(list的格式必須和router.js裏的格式一致,可以和後端兄弟商量一下了,讓他幫你把格式造好直接返給你。)
接下來進入關鍵的一步了,終於又可以貼代碼了!!!
在vuex的app.js我定義了一個獲取router文件的方法:


    getUserRouters({
            commit
        }) {
            const code = getParams(window.location.href).code
            return new Promise((resolve, reject) => {
                try {
                    callBack(code).then(res => {
                        let routers = backendMenusToRouters(res.data.resultData.route)
                        commit('setRouters', routers)
                        setToken(res.data.resultData.token)
                        localSave('dataMenuList',JSON.stringify(res.data.resultData.route))
                        resolve(res.data.resultData.route)
                    }).catch(err => {
                        reject(err)
                    })
                } catch (error) {
                    reject(error)
                }
            })
        }

ok,我爲什麼沒有用iview admin的登陸邏輯呢,是因爲我們這邊的登陸會走一個平臺的驗證,公司統一的,其實和iview admin的 差不多的。---- 上面的函數裏,我在本地存了一下路由的文件,後面會用到。

成功拿到路由文件之後,我們就可以再main.js裏讓addRouter兄dei登場了。代碼:


    const token = getToken()
    const queryCode = getParams(window.location.href).code
    if (!token && !queryCode) {
        //調登陸邏輯
    } else if (!token && queryCode) {
       //調用app.js裏的getUserRouters方法
        store.dispatch('getUserRouters').then(res => {
            new Vue({
                el: '#app',
                router,
                i18n,
                store,
                render: h => h(App),
                mounted() {
                    const routers = backendMenusToRouters(res)
                    router.addRoutes(routers)
                },
            })
        }) 
    } else {
        //如果是登陸過的,後者是正常刷新操作,只要從localStorage裏拿數據就好了
        router.addRoutes(backendMenusToRouters(JSON.parse(localRead('dataMenuList'))))
        new Vue({
            el: '#app',
            router,
            i18n,
            store,
            render: h => h(App)
        })
    }

上面的backendMenusToRouters函數我貼出來,這個函數就是爲了處理路由文件的,路由掛載component是一個函數,所以需要特殊處理。


    /**
 * @description 將後端菜單樹轉換爲路由樹
 * @param {Array} menus
 * @returns {Array}
 */
export const backendMenusToRouters = (menus) => {
    let routers = []
    forEach(menus, (menu) => {
        // 將後端數據轉換成路由數據
        let route = backendMenuToRoute(menu)
        // 如果後端數據有下級,則遞歸處理下級
        if (menu.children && menu.children.length !== 0) {
            route.children = backendMenusToRouters(menu.children)
        }
        routers.push(route)
    })
    return routers
}

/**
 * @description 將後端菜單轉換爲路由
 * @param {Object} menu
 * @returns {Object}
 */
const backendMenuToRoute = (menu) => {
    // 具體內容根據自己的數據結構來定,這裏需要注意的一點是
    // 原先routers寫法是component: () => import('@/view/error-page/404.vue')
    // 經過json數據轉換,這裏會丟失,所以需要按照上面提過的做轉換,下面只寫了核心點,其他自行處理
    let route = Object.assign({}, menu)
    route.component = resolve => require([`@/${menu.component}`], resolve)
    return route
}

關於處理component需要配置個依賴:
npm install babel-plugin-syntax-dynamic-import

.babelrc 增加
{
"plugins": ["syntax-dynamic-import"]
}

這裏加一句,vuex裏app.js的menuList方法需做稍稍的小改動:


    
     menuList: (state, getters, rootState) => getMenuByRouter(state.routers, rootState.user.access)

main.vue的menuList賦值:


    menuList( ) {
            return this.$store.getters.menuList
        },

到這裏,你的動態路由可以說已經完成了。

那又有同學問了,那還有一些頁面是子頁面, 不需要在菜單裏顯示怎麼處理???別慌,我喝口水慢慢跟你說。
我們可以看一下路由的配置,在meta的對象裏,有個hideInMenu屬性,妥了,那我們搞起來吧。其實iview admin已經幫我寫好了這塊的邏輯處理,代碼在util.js裏:


    /**
 * @param {Array} list 通過路由列表得到菜單列表
 * @returns {Array}
 */
export const getMenuByRouter = (list, access) => {
    let res = []
    forEach(list, item => {
        if (!item.meta || (item.meta && !item.meta.hideInMenu)) {
            let obj = {
                icon: (item.meta && item.meta.icon) || '',
                name: item.name,
                meta: item.meta
            }
            if ((hasChild(item) || (item.meta && item.meta.showAlways))) {
                obj.children = getMenuByRouter(item.children, access)
            }
            if (item.meta && item.meta.href) obj.href = item.meta.href
            // if (showThisMenuEle(item, access)) res.push(obj)
            res.push(obj)
        }
    })
    return res
}

-,- ok,大功告成! 慢着... 那如果我想對菜單進行增刪改操作怎麼辦??
好辦!你就前端寫頁面吧,這裏我推薦一個很成熟的方案,我目前項目就是參考他的做的。傳送門:xBoot管理系統

個人認爲他們的菜單管理做的真的很棒!!我又不要臉的借鑑了他們的功能權限,進行了我們項目的功能權限管理的設計。

  • 具體實現邏輯

首先,每個角色的具體功能權限是在meta的access裏攜帶進來的,access是一個數組,['del','add'....]我和後端定義好了每個字段代表什麼功能權限。比如:'del'代表刪除按鈕、'upload'代表上傳功能等等... 當我們進入不同的頁面的時候,根據access的功能列表給用戶設置不同的功能權限。這裏是借鑑了網上的實現邏輯,自定義一個指令,然後每個按鈕根據功能綁定不同的字段,做是否remove動作。好了,又開始bb了,貼代碼吧,在libs定義一個hasPermission.js文件:


    const hasPermission = {
    install (Vue, options) {
        Vue.directive('hasPermission', {
            inserted (el, binding, vnode) {
                let permissionList = vnode.context.$route.meta.access;
                if (!permissionList.includes(binding.value)) {
                    el.remove()
                }
            }
        });
    }
};
export default hasPermission;

在main.js裏全局定義:


    import hasPermission from '@/libs/hasPermission.js'
    Vue.use(hasPermission)

頁面中具體按鈕的使用:


    <Icon type="md-cloud-download" v-hasPermission="'output'" @click="exportTaskExcel" />

注意:綁定的字段必須是字符串格式。
具體頁面上的按鈕權限的分配在前端頁面是怎麼控制的,完全可以去xBoot裏借鑑。
我也不知道我寫的大家看不看得懂,如果看不懂,再多看一遍,再看不懂歡迎留言或者加我QQ互相學習:602353272。
最後,再BB一句,有巨佬有更好的方案歡迎賜教!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章