likes
comments
collection
share

动态配置路由和侧边栏

作者站长头像
站长
· 阅读数 3

需求背景: 后台管理需要给用户进行权限划分,用户登录后台管理后接口根据权限返回不同的侧边栏菜单和对应的页面及功能权限数据,然后前端根据数据进行动态配置侧边栏展示和对应的路由

使用技术:

vuex 实现store管理应用数据

配置store

import { createStore } from 'vuex'
import menu from './modules/menu'
import user from './modules/user'
import permission from './modules/permission'
import getters from './getters'
import operateMap from './modules/operateMap'

const store = createStore({
    modules: {
        menu,
        user,
        permission,
        operateMap,
    },
    getters,
})

export default store

menu: 用来请求菜单列表数据,并保存数据

permission: 将根据权限返回的菜单列表数据处理为侧边栏可用的数据,和路由数据,针对的是页面

operateMap: 将根据权限返回的菜单列表数据处理为功能权限数组数据,以供具体页面的功能模块限制,针对的是具体功能

router配置

在说到具体的模块对应的store之前,先来看一下router中的配置

1. 静态路由表
export const constantRoutes = [
    {
        path: '/',
        name: '$404',
        component: () => import('@/views/default/index.vue'),
    },
    {
        path: '/:pathMatch(.*)*',
        redirect: '/',
    },
    {
        path: '/bigclass/login',
        name: '$login',
        component: () => import('@/views/login.vue'),
    },
    ]

静态路由是用来存放一些不因权限配置而更改的页面路由,比如登录,默认404页面,以及需求中的其他页面等

2. 生成路由
const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    scrollBehavior(to, from, savedPosition) {
        if (savedPosition) {
            return savedPosition
        } else {
            return { x: 0, y: 0 }
        }
    },
    routes: constantRoutes,
})

先根据静态路由生成router

3. 动态请求菜单列表
// 判断该路由是否需要登录权限
    if (islogin) {
        // 判断当前的token是否存在 ; 登录存入的token
        if (store.getters.routes.length === 0) {
            store
                .dispatch('menu/getMenuList')
                .then(menuList => {
                    store.dispatch('operateMap/configOperateMap', menuList)
                    store.dispatch('permission/generateRoutes', { menuList }).then(addRoutes => {
                        addRoutes.forEach(item => {
                            // 针对特殊的路由特殊处理
                            if (hideMainPaddingWhiteList.indexOf(item.path) !== -1) {
                                item.meta = { ...item.meta, hideMainPadding: true }
                            }
                            router.addRoute(item)
                        })
                        store.dispatch('permission/generateSliderNavs', { menuList }).then(sliderNavs => {
                            if (sliderNavs.length > 0) {
                                next({ path: addRoutes[0].path, replace: true })
                            } else {
                                next({ path: '/', replace: true })
                            }
                        })
                    })
                })
                .catch(error => {
                    error && message.error(error)
                })
        } else {
            next()
        }
    } else {
        next({
            path: '/bigclass/login',
        })
    }

路由的配置到这里基本完成了,这里需要判断是否登录,已登录再判断是否需要请求菜单列表,然后是对菜单数据处理为相应的动态路由表,侧边栏数据以及功能权限数据,在配置完侧边栏之后,需要动态地定位到路由表的第一条数据

菜单数据的store

const getDefaultState = () => {
    return {
        menuList: [],
    }
}
const state = getDefaultState()

const mutations = {
    SET_MENULIST: (state, menuList) => {
        state.menuList = menuList
    },
    RESET_STATE: state => {
        Object.assign(state, getDefaultState())
    },
}

const actions = {
    // 获取菜单列表
    getMenuList({ commit }) {
        return new Promise((resolve, reject) => {
            Inter.getMenuList()
                .then(response => {
                    if (requestResult.success(response)) {
                        if (response.data.list) {
                            commit('SET_MENULIST', response.data.list)
                            resolve(response.data.list)
                        }
                    } else {
                        reject()
                    }
                })
                .catch(error => {
                    reject(error)
                })
        })
    },
    resetState({ commit }) {
        return new Promise(resolve => {
            commit('RESET_STATE')
            resolve()
        })
    },
}
export default {
    namespaced: true,
    state,
    mutations,
    actions,
}

主要是请求菜单数据,并保存下来

permission

1. 侧边栏数据处理
const configSliderNavs = menuList => {
    const sliderNavs = generateSliderNavs(menuList)
    return sliderNavs
}
// 获取侧边栏配置数据
const generateSliderNavs = menuList => {
    return (
        menuList &&
        menuList.map(({ name, icon, code, submenuList }) => {
            return {
                name,
                iconComp: icon,
                route: toReplaceName(code),
                child:
                    submenuList &&
                    submenuList.map(({ name, code }) => {
                        return {
                            name,
                            route: toReplaceName(code),
                        }
                    }),
            }
        })
    )
}
2. 路由数据处理
// 获取动态添加的router数据
const configRoutes = menuList => {
    const routers = []
    if (menuList) {
        const modules = import.meta.glob('@/views/**/index.vue')
        menuList.forEach(firtItem => {
            const parent = toReplaceName(firtItem.code)
            if (firtItem.submenuList) {
                firtItem.submenuList.forEach(secondItem => {
                    // 二级页面
                    const routeName = toReplaceName(secondItem.code)
                    const routePath = toReplacePath(secondItem.code)
                    let secondParam = ''
                    if (secondItem.params && secondItem.params.length > 0) {
                        secondItem.params.forEach(item => {
                            secondParam = secondParam + `:${item}`
                        })
                        secondParam = `/${secondParam}`
                    }
                    const title = secondItem.name
                    routers.push({
                        path: routePath + secondParam,
                        name: routeName,
                        meta: {
                            highLightRoute: routeName,
                            parent,
                            title,
                            showHeader: true,
                        },
                        component: modules[`/src/views${routePath}/index.vue`],
                    })
                    if (secondItem.submenuList) {
                        //三级页面
                        secondItem.submenuList.forEach(thirdItem => {
                            const thirdRouteName = toReplaceName(thirdItem.code)
                            const thirdRoutePath = toReplacePath(thirdItem.code)
                            let thirdParam = ''
                            if (thirdItem.params && thirdItem.params.length > 0) {
                                thirdItem.params.forEach(item => {
                                    thirdParam = thirdParam + `:${item}`
                                })
                                thirdParam = `/${thirdParam}`
                            }
                            const thirdTitle = thirdItem.name
                            routers.push({
                                path: thirdRoutePath + thirdParam,
                                name: thirdRouteName,
                                meta: {
                                    highLightRoute: routeName,
                                    parent,
                                    title: thirdTitle,
                                    showHeader: thirdItem.showHeader,
                                    showBreadcrumb: thirdItem.showBreadcrumb,
                                    breadRoutes: [{ path: routePath, breadcrumbName: title }],
                                },
                                component: modules[`/src/views${thirdRoutePath}/index.vue`],
                            })
                        })
                    }
                    if (secondItem.buttonList) {
                        // 另一个三级数据
                        secondItem.buttonList.forEach(buttonItem => {
                            if (buttonItem.routes) {
                                buttonItem.routes.forEach(buttonRoute => {
                                    const buttonRouteName = toReplaceName(buttonRoute.code)
                                    const buttonRoutePath = toReplacePath(buttonRoute.code)
                                    let buttonParam = ''
                                    if (buttonRoute.params && buttonRoute.params.length > 0) {
                                        buttonRoute.params.forEach(item => {
                                            buttonParam = buttonParam + `:${item}`
                                        })
                                        buttonParam = `/${buttonParam}`
                                    }
                                    const buttonTitle = buttonRoute.name
                                    routers.push({
                                        path: buttonRoutePath + buttonParam,
                                        name: buttonRouteName,
                                        meta: {
                                            highLightRoute: routeName,
                                            parent,
                                            title: buttonTitle,
                                            showHeader: buttonRoute.showHeader,
                                            showBreadcrumb: buttonRoute.showBreadcrumb,
                                            breadRoutes: [{ path: routePath, breadcrumbName: title }],
                                        },
                                        component: modules[`/src/views${buttonRoutePath}/index.vue`],
                                    })
                                })
                            }
                        })
                    }
                })
            }
        })
    }
    return routers
}
state, mutations,actions配置

const getDefaultState = () => {
    return {
        routes: [],
        addRoutes: [],
        sliderNavs: [],
    }
}
const state = getDefaultState()

const mutations = {
    SET_ROUTES: (state, routes) => {
        state.addRoutes = routes
        state.routes = constantRoutes.concat(routes)
    },
    SET_SLIDERNAVS: (state, sliderNavs) => {
        state.sliderNavs = sliderNavs
    },
    RESET_STATE: state => {
        Object.assign(state, getDefaultState())
    },
}

const actions = {
    generateRoutes({ commit }, { menuList }) {
        return new Promise(resolve => {
            // 这里是拼凑出来新的路由
            const accessedRoutes = configRoutes(menuList)
            commit('SET_ROUTES', accessedRoutes)
            resolve(accessedRoutes)
        })
    },
    generateSliderNavs({ commit }, { menuList }) {
        return new Promise(resolve => {
            const sliderNavs = configSliderNavs(menuList)
            commit('SET_SLIDERNAVS', sliderNavs)
            resolve(sliderNavs)
        })
    },
    resetState({ commit }) {
        return new Promise(resolve => {
            commit('RESET_STATE')
            resolve()
        })
    },
}

功能权限对应的某一个模块的配置

import { defineStore } from 'pinia'
import { useStore } from 'vuex'

export const useAccountStore = defineStore('crm-account', () => {
    const store = useStore()
    // 是否显示新建/编辑
    const showCreate = computed(() => {
        return store.getters.operateMap.indexOf('$permission_account_operate') !== -1
    })
    // 是否显示批量导入
    const showBatchImport = computed(() => {
        return store.getters.operateMap.indexOf('$permission_account_operate_batch_import') !== -1
    })
    // 是否显示冻结/恢复/离职
    const showStatus = computed(() => {
        return store.getters.operateMap.indexOf('$permission_account_operate_status') !== -1
    })
    // 是否显示复制密码
    const showCopyPassword = computed(() => {
        return store.getters.operateMap.indexOf('$permission_account_operate_password_copy') !== -1
    })
    // 是否显示重置密码
    const showResetPassword = computed(() => {
        return store.getters.operateMap.indexOf('$permission_account_operate_password_reset') !== -1
    })
    return {
        showCreate,
        showBatchImport,
        showStatus,
        showCopyPassword,
        showResetPassword,
    }
})

对于功能权限这块,笔者采用的是pinia来管理,也可根据自己的项目结构用vuex