每天进步一点点----vue 动态路由demo

1,225 阅读4分钟

------------------------------------------------------------------------------------------------

不搬砖你养我啊

------------------------------------------------------------------------------------------------

流程图




相信大部分同学在做路由权限处理的时候都是从 @花裤衩 大佬的手摸手系列借鉴来的, 我在做这个模块的时候也是从大佬仓库copy过来改改逻辑,因为仓库项目代码太多和每个公司实现权限鉴别的方式不一致, 所以写个demo记录一下。


关于权限控制方式

手摸手,带你用vue撸后台 系列一(基础篇) 实现权限控制的方式是完全通过前端来控制, 后端只需要返回登录者的 role (角色身份) , 然后通过role去动态生成可以访问的路由。 但有的公司是登录之后后端直接返回可访问路由表由前端去匹配。

具体怎么控制还得看公司的业务需求, 本demo通过后端直接返回路由表进行匹配


demo实现过程

demo越简单越好


mock服务

在package.json文件配置mock脚本 

(因为node不能直接运行ts代码, 引入ts-node-dev插件)

 "scripts": { "mock": "cd mock && ts-node-dev mock-server.ts", }

使用express起一个本地服务用来获取可访问路由表(mock =>mock-server.ts)

import express from 'express'
import Mock from 'mockjs'
import bodyParser from 'body-parser'
const app = express()app.use(bodyParser.json())
const pageRouter = require('./page-router')

// 设置跨域
app.all('*', function (req, res, next) {  
    res.header('Access-Control-Allow-Origin', '*')  
    res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS')  
    // 此处根据前端请求携带的请求头进行配置  
    res.header('Access-Control-Allow-Headers','Content-Type,Access-Token,X-Access-Token'  )  
    next()
})
    // ******************* 获取路由表 *************************
app.get('/router', (req, res) => {  
    res.json(Mock.mock(pageRouter.getPageRouter(req)))
})
app.listen('3000', () => {  console.log('mock服务器启动ing中... port: 3000')})


mock接口返回数据(mock => page-router.ts)

根据登录用户生成该用户可访问的路由表(admin,operator: 可看作是用户名)

getPageRouter: (data: any) => {  
    const resTemp: IResTemp = {    
        status: {        
            code: 0,      
            message: 'success',     
            time: Random.now('day', 'yyyy-MM-dd HH:mm:ss')   
        },     
        data: {      
            userType: '',     
            dataList: []   
        }  
    }    
    if (data.query.type === 'admin') {
        resTemp.data.userType = 'admin'
        resTemp.data.dataList = adminRouter
    } else if (data.query.type === 'operator') {  
        resTemp.data.userType = 'operator'    
        resTemp.data.dataList = operatorRouter   
    } else {
        resTemp.status.code = 403      
        resTemp.status.message = '无法登录,请联系管理员'      
        resTemp.data.dataList = []    
    }    
        return resTemp  
    }
}




tip:

mock数据时如果改动mock文件里面的代码需要重启服务, 可以使用 “nodemon” 来实现类似热更新的功能


路由文件

在做动态路由匹配之前需要预先定义好静态路由(constantRoutes)和动态路由(asyncRoutes)

静态路由指不需要权限即可访问,例如登录页,404页面等。动态路由从后端获取,需要注意的是路由为 “*”的这个特殊路由需要放在动态路由的最后

参考vue官方文档: 动态路由匹配    


在router.ts文件暴露一个路由初始化的函数 resetRouter  

退出登录或登录信息过期时清除动态路由信息

const createRouter = () =>  
    new Router({   
        mode: 'history', 
        scrollBehavior: () => ({ x: 0, y: 0 }),   
        routes: constantRoutes  
    })

const router: any = createRouter()

export function resetRouter() { 
    const newRouter: any = createRouter()  
    router.matcher = newRouter.matcher // reset router
}


动态路由匹配

src => permission.ts

import router from './router'
import { Route } from 'vue-router'
import { UserModule } from '@/store/modules/userInfo'
import { asyncRoutes, constantRoutes } from '@/router'

// 动态路由匹配(根据业务逻辑而定)
const formatAsyncRoutes = (  apiRoutes: ApiRoute[],  asyncRoutes: LocalRoute[]) => {  
    const res: LocalRoute[] = []  
    apiRoutes.map(item1 => {    
        asyncRoutes.map(item2 => {      
            if (item1.path === item2.path) {        
                const temp = item2        
                if (item1.children && item2.children) {         
                    temp.children = formatAsyncRoutes(item1.children, item2.children)   
                }        
                res.push(temp)      
            }    
        })  
    })  
    return res
}

const setAsyncRoutes = (apiRoutes: ApiRoute[], asyncRoutes: LocalRoute[]) => {  
    const accessRoutes = formatAsyncRoutes(apiRoutes, asyncRoutes)  
    accessRoutes.push({ path: '*', redirect: '/404', meta: { hidden: true } })  
    router.addRoutes(accessRoutes)  
    // 手动添加动态路由(侧边栏使用)  
    router.options.routes = constantRoutes.concat(<any[]>accessRoutes)
}

// 在路由上挂载添加动态路由的方法
router.setAsyncRoutes = setAsyncRoutes

router.beforeEach(async (to: Route, from: Route, next: any) => {  
    if (to.path === '/login') {    
        next() 
    } else {    
        const hasRoute = UserModule.userInfo.userRoutes.length    
        if (hasRoute) {      
            next()    
        } else {      
            // 防止页面刷新数据丢失      
            const userInfo = localStorage.getItem('userInfo')      
            if (userInfo) {        
                const formatUserInfo: LoginUserInfo = {          
                userType: JSON.parse(userInfo).userType,          
                userRoutes: JSON.parse(userInfo).dataList        
            }        
            UserModule.SET_USER_INFO(formatUserInfo)        
            setAsyncRoutes(<ApiRoute[]>UserModule.userInfo.userRoutes, asyncRoutes)        
            next({ ...to, replace: true })      
        } else {        
            next('/login')      
        }    
    }  
}})

因为通常情况下后端返回的数据结构跟前端路由表结构有所出入,所以很有必要定义一个函数来格式化路由

formatAsyncRoutes:    根据后端返回可访问的路由表和本地所存有的所有动态路由表进行比对,筛选出真正的动态路由表文件

通配路由必须挂在动态路由的结尾

accessRoutes.push({ path: '*', redirect: '/404', meta: { hidden: true } })


(不建议这样做)

在demo中使用 router.options来生成侧边栏,所以需要手动添加动态路由信息

router.options.routes = constantRoutes.concat(<any[]>accessRoutes)


在路由实例上面挂载一个设置动态路由的方法共全局使用(登录时)

router.setAsyncRoutes = setAsyncRoutes

router.beforeEach: 第一次进入页面的时候会执行一次动态路由, 具体逻辑可参照上面流程图


登录

src => store =>modules => userInfo.ts

登录之后调用在permisson.ts挂在载Router上的 setAsyncRoutes 方法进行

import router, { resetRouter, asyncRoutes } from '@/router'import { getPageRouter } from '@/api/page-router/page-router.ts'

@Action  
Login (userType: string) {    
    return new Promise(resolve => {      
        getPageRouter({ type: userType }).then(res => {        
            localStorage.setItem('userInfo', JSON.stringify(res.data))        
            this.SET_USER_INFO({          
                userType: res.data.userType,          
                userRoutes: res.data.dataList        
            })        
            router.setAsyncRoutes(<ApiRoute[]>this.userInfo.userRoutes, asyncRoutes)        
            resolve()      
        })    
    })  
}


退出登录

src => store =>modules => userInfo.ts

import router, { resetRouter, asyncRoutes } from '@/router'
@Mutation  REMOVE_USER_INFO () {    
    localStorage.removeItem('userInfo')    
    this.userInfo = {      
        userType: '',      
        userRoutes: []    
    }    
    router.push('/login')    
    resetRouter()  
}


侧边栏

src => layout => components => Sidebar => SidebarItem.vue

通过递归这个组件生成,没啥好说的  - - 



关于其他

demo中只做了页面级的权限控制, 还有按钮级别的权限控制, 有兴趣的同学可以自己去尝试(可以根据 vue的指令配合后端接口来做)


最后放上demo的地址

demo地址

github.com/86driver/ts…