Detailed explanation of permission control based on vue, vue-router, vuex and addRoutes

Based on vuex, vue-router, vuex permission control tutorial, see the complete code address https://github.com/linrunzheng/vue-permission-control

Next, let us simulate the process of an ordinary user opening the website, and walk through the whole process step by step.

First, start by opening the local service localhost:8080. We know that we will enter the login page after opening, so what is the basis for the judgment.
The first is the token.
Users who are not logged in will not be able to obtain tokens, and after login, we will save the tokens to local or sessionStorage. Therefore, it is possible to know whether to log in according to whether there are tokens currently available.

In order to access the token and facilitate our operation, it can be implemented with vuex

/* state.js */
export default {
    get UserToken() {
        return localStorage.getItem('token')
    },
    set UserToken(value) {
        localStorage.setItem('token', value)
    }
}
/* mutation.js */
export default {
    LOGIN_IN(state, token) {
        state.UserToken = token
    },
    LOGIN_OUT(state) {
        state.UserToken = ''
    }
}

judgment of interception

  1. Entering a page that requires permission without a token: redirect to the login page
  2. Since our route is dynamically mounted, including ' ' and 404, when the route is not matched, it is also redirected to login
router.beforeEach((to, from, next) => {
    if (!store.state.UserToken) {
        if (
            to.matched.length > 0 &&
            !to.matched.some(record => record.meta.requiresAuth)
        ) {
            next()
        } else {
            next({ path: '/login' })
        }
    } 
})

Well, at this time, the user opens localhost:8080, and the default match is the '' path. At this time, we have not mounted the route and have no token, so we came to login.

After entering the username and password, there is a token, trigger *commit('LOGIN_IN')* through the store to set the token.

But there is still no route. At the beginning, only the login route is available.

/* 初始路由 */
export default new Router({
    routes: [
        {
            path: '/login',
            component: Login
        }
    ]
})

/* 准备动态添加的路由 */
export const DynamicRoutes = [
    {
        path: '',
        component: Layout,
        name: 'container',
        redirect: 'home',
        meta: {
            requiresAuth: true,
            name: '首页'
        },
        children: [
            {
                path: 'home',
                component: Home,
                name: 'home',
                meta: {
                    name: '首页'
                }
            }
        ]
    },
    {
        path: '/403',
        component: Forbidden
    },
    {
        path: '*',
        component: NotFound
    }
]

We need to go to the background to obtain permissions based on the token of the current user.

Since there is a lot of logic in permissions, a permission module is added to vuex to handle permissions.

In order to judge that there is an existing route list, it is necessary to store a state state permissionList in the permission module of vuex to judge. If the permissionList is not null, there is already a route. If it does not exist, we need to work.

router.beforeEach((to, from, next) => {
    if (!store.state.UserToken) {
        ...
    } else {
        /* 现在有token了 */
        if (!store.state.permission.permissionList) {
            /* 如果没有permissionList,真正的工作开始了 */
            store.dispatch('permission/FETCH_PERMISSION').then(() => {
                next({ path: to.path })
            })
        } else {
            if (to.path !== '/login') {
                next()
            } else {
                next(from.fullPath)
            }
        }
    }
})

Let's see what store.dispatch('permission/FETCH_PERMISSION') does

actions: {
    async FETCH_PERMISSION({ commit, state }) {
       /*  获取后台给的权限数组 */
        let permissionList = await fetchPermission()

        /*  根据后台权限跟我们定义好的权限对比,筛选出对应的路由并加入到path=''的children */
        let routes = recursionRouter(permissionList, dynamicRouter)
        let MainContainer = DynamicRoutes.find(v => v.path === '')
        let children = MainContainer.children
        children.push(...routes)

        /* 生成左侧导航菜单 */
        commit('SET_MENU', children)

        setDefaultRoute([MainContainer])

        /*  初始路由 */
        let initialRoutes = router.options.routes

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

        /* 完整的路由表 */
        commit('SET_PERMISSION', [...initialRoutes, ...DynamicRoutes])
    }
}

First of all, await fetchPermission() gets the permission array given by the background, the format is roughly as follows

{
    "code": 0,
    "message": "获取权限成功",
    "data": [
        {
            "name": "订单管理",
            "children": [
                {
                    "name": "订单列表"
                },
                {
                    "name": "生产管理",
                    "children": [
                        {
                            "name": "生产列表"
                        }                     
                    ]
                },
                {
                    "name": "退货管理"
                }
            ]
        }
    ]
}

Secondly, according to the route array we wrote, compare and filter to get the route we want

/* 这里是我们写好的需要权限判断的路由 */
const dynamicRoutes = [
    {
        path: '/order',
        component: Order,
        name: 'order-manage',
        meta: {
            name: '订单管理'
        },
        children: [
            {
                path: 'list',
                name: 'order-list',
                component: OrderList,
                meta: {
                    name: '订单列表'
                }
            },
            {
                path: 'product',
                name: 'product-manage',
                component: ProductManage,
                meta: {
                    name: '生产管理'
                },
                children: [
                    {
                        path: 'list',
                        name: 'product-list',
                        component: ProductionList,
                        meta: {
                            name: '生产列表'
                        }
                    },
                    {
                        path: 'review',
                        name: 'review-manage',
                        component: ReviewManage,
                        meta: {
                            name: '审核管理'
                        }
                    }
                ]
            },
            {
                path: 'returnGoods',
                name: 'return-goods',
                component: ReturnGoods,
                meta: {
                    name: '退货管理'
                }
            }
        ]
    }
]

export default dynamicRoutes

For comparison, I wrote a recursive function, using name and meta.name to compare, according to this function, we can get the result we want

/**
 *
 * @param  {Array} userRouter 后台返回的用户权限json
 * @param  {Array} allRouter  前端配置好的所有动态路由的集合
 * @return {Array} realRoutes 过滤后的路由
 */

export function recursionRouter(userRouter = [], allRouter = []) {
    var realRoutes = []
    allRouter.forEach((v, i) => {
        userRouter.forEach((item, index) => {
            if (item.name === v.meta.name) {
                if (item.children && item.children.length > 0) {
                    v.children = recursionRouter(item.children, v.children)
                }
                realRoutes.push(v)
            }
        })
    })
    return realRoutes
}

After getting the filtered array, add it to the children whose path is ''

{
        path: '',
        component: Layout,
        name: 'container',
        redirect: 'home',
        meta: {
            requiresAuth: true,
            name: '首页'
        },
        children: [
            {
                path: 'home',
                component: Home,
                name: 'home',
                meta: {
                    name: '首页'
                }
            },
            <!-- 将上面得到的东西加入到这里 -->
            ...
        ]
    }

At this time, the children whose path is '' is the navigation menu on our left, which is stored in the sidebarMenu of the state for use. After adding to children, DynamicRoutes can be added to the route at this time.

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


 /*  初始路由 */
let initialRoutes = router.options.routes
/* 合并起来,就是完整的路由了 */
commit('SET_PERMISSION', [...initialRoutes, ...DynamicRoutes])
路由添加完了,也就是action操作完毕了,即可在action.then里面调用 next({ path: to.path })进去路由,这里要注意, next里面要传参数即要进入的页面的路由信息,因为next传参数后,当前要进入的路由会被废止,转而进入参数对应的路由,虽然是同一个路由,这么做主要是为了确保addRoutes生效了。

After entering the route, we need to start generating the left menu. We have saved it in the sidebarMenu before. Now we just need to recursively generate the menu. Although the element navigation menu is used, for recursive routing, we need to encapsulate it ourselves. The core place here is the name of the component. Where there are children in the component, it uses itself again to traverse the routing of the entire tree structure.

<template>
    <div class="menu-container">
        <template v-for="v in menuList">
            <el-submenu :index="v.name" v-if="v.children&&v.children.length>0" :key="v.name">
                <template slot="title">
                    <i class="iconfont icon-home"></i>
                    <span>{{v.meta.name}}</span>
                </template>
                <el-menu-item-group>
                    <my-nav :menuList="v.children"></my-nav>
                </el-menu-item-group>
            </el-submenu>
            <el-menu-item :key="v.name" :index="v.name" @click="gotoRoute(v.name)" v-else>
                <i class="iconfont icon-home"></i>
                <span slot="title">{{v.meta.name}}</span>
            </el-menu-item>
        </template>
    </div>
</template>

<script>
export default {
    name: 'my-nav',
    props: {
        menuList: {
            type: Array,
            default: function() {
                return []
            }
        }
    },
    methods: {
        gotoRoute(name) {
            this.$router.push({ name })
        }
    }
}
</script>

After refreshing the page, according to our router.beforeEach judgment, if there is a token but no permissionList, we will re-trigger the action to get the route, so there is no need to worry. But the navigation menu active effect will disappear. However, we have set the key of el-menu-item as the name of the route, so we only need to assign the name of the current route to el-menu default-active in afterEach after refreshing. Similarly, get all the matched routes in the afterEach stage to achieve breadcrumb navigation.

if (!store.state.permission.permissionList) {
    store.dispatch('permission/FETCH_PERMISSION').then(() => {
        next({ path: to.path })
    })
} 



...
router.afterEach((to, from, next) => {
    var routerList = to.matched
    store.commit('setCrumbList', routerList)
    store.commit('permission/SET_CURRENT_MENU', to.name)
})

After logging out, you need to refresh the page, because we added it through addRoutes, the router does not have the deleteRoutes api, so clearing the token, clearing the permissionList and other information, refreshing the page is the safest.

最后还有一点,每次请求得带上token, 可以对axios封装一下来处理
var instance = axios.create({
    timeout: 30000,
    baseURL
})

// 添加请求拦截器
instance.interceptors.request.use(
    function(config) {
        // 请求头添加token
        if (store.state.UserToken) {
            config.headers.Authorization = store.state.UserToken
        }
        return config
    },
    function(error) {
        return Promise.reject(error)
    }
)

/* axios请求二次封装 */
instance.get = function(url, data, options) {
    return new Promise((resolve, reject) => {
        axios
            .get(url, data, options)
            .then(
                res => {
                    var response = res.data
                    if (response.code === 0) {
                        resolve(response.data)
                    } else {
                        Message.warning(response.message)
                        /* reject(response.message) */
                    }
                },
                error => {
                    if (error.response.status === 401) {
                        Message.warning({
                            message: '登陆超时,请重新登录'
                        })
                        store.commit('LOGIN_OUT')
                        window.location.reload()
                    } else {
                        Message.error({
                            message: '系统异常'
                        })
                    }
                    reject(error)
                }
            )
            .catch(e => {
                console.log(e)
            })
    })
}

export default instance

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325144734&siteId=291194637