前言
配置完项目依赖项后,接下来就是最重要的一步,即如何处理权限并将命名菜单分配给用户。首先要知道的是,我们的路由表中只有一小部分是无需权限即可访问的,即白名单,比如登录路由
、404路由
、500路由
等,我们可以拿到剩下的界面,这样我们就可以根据用户的权限做一些过滤,实现对不同的用户显示不同的菜单和路线。
当然还有其他方法可以完成这些过程,目前我个人更喜欢它。好了,废话不多说,我们开始自慰吧。
配置axios拦截器
就像定义axios
拦截器一样,一千个人估计有一千种写法,但核心逻辑不变。无非就是设置请求头和处理响应数据。
创建实例
首先,使用axios.create(config)
方法创建一个axios实例。 baseURL
会自动添加到我们的 url
前面(除非 URL 是绝对的 URL
)。 timeout
代表超时。如果请求超过这个时间并且没有响应,则请求被中止。
const service = axios.create({
baseURL: defaultConfig.zhtApiUrl,
timeout: timeout * 1000
})
一旦创建了实例,就可以定义拦截器了。
找绑匪
在请求拦截器中我们可以处理请求头。比如我们可以向拦截器添加一个请求TOKEN
,我们可以这样做:
service.interceptors.request.use(
config => {
const TOKEN = getToken()
if (TOKEN) {
config.headers.Authorization = TOKEN
}
return config
},
error => {
Promise.reject(error)
}
)
响应拦截器
service.interceptors.request.use(
config => {
const TOKEN = getToken()
if (TOKEN) {
config.headers.Authorization = TOKEN
}
return config
},
error => {
Promise.reject(error)
}
)
在响应拦截器中,主要处理响应信息,比如响应状态、响应信息以及一些错误处理。
service.interceptors.response.use(
response => {
const { url: apiUrl } = response.config
const { status, statusText, data } = response
if (status === 200) {
if (apiUrl!.includes('auth/social/token')) {
// 获取 user token 的接口,返回格式跟其它接口有区别,
return {
code: 0,
msg: 'success',
data
}
} else {
return data
}
} else {
// 报错
message.error(`出错了哦 status: ${status} - statusText: ${statusText}`)
return Promise.reject({
code: 1,
msg: statusText
})
}
},
error => {
// { response, message, request, config } = error
if (error.response) {
const { status, statusText } = error.response
if (status === 401) {
message.error(`接口没有权限访问,请检查接口或者参数! status: ${status} - statusText: ${statusText}.`)
} else if (status === 500) {
message.error(`请检查接口或服务器状态! status: ${status} - statusText: ${statusText}.`)
router.push('/portal-error500')
} else {
message.error(`出错啦! message: ${error.message} - statusText: ${statusText}.`)
}
} else {
message.error(`禁止访问! 错误信息: ${error}`)
}
}
)
每个人或不同的项目可能有不同的配置。因此,这里的配置仅供参考。具体配置必须根据业务需求而定。
解锁
如果您想随时取消劫持,可以这样做:
const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);
导航卫士
vue-router
附带的导航保护主要用于跳跃或倒车时的保护导航。有很多方法可以在其中构建路线导航:全局、专门针对单个路线或在组件级别。
我们可以创建一个permissions.ts
文件来编写全局导航守卫。
全局正面防护
您可以使用router.beforeEach
注册全局封面。
router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return false
})
在 permissions.ts
文件中,我们可以这样指定。在导航跳转之前,我们需要判断当前用户是否登录。如果他想去的不是登录页面,我们就让他跳转到登录页面。 ,如果登录,我们评估他是否已获得许可。如果他没有得到许可,我们就会调整用户界面,否则我们会直接进入项目的主页(如果你还记得跳转页面,那么redirect
就去那个页面,而不是主页)。
我们的登录界面只返回与TOKEN
相关的信息,所以我们需要根据token
获取该用户的权限等信息。
router.beforeEach(async (to, from, next) => {
const token = getToken()
const isWhite = whiteList.findIndex(w => w === to.path)
NProgress.start()
if (token) {
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if (hasRoles) {
next()
} else {
try {
// 获取应用
await store.dispatch(`userModule/${userAction.GetApplictions}`)
// 获取用过户信息权限
const roles = await store.dispatch(`userModule/${userAction.GetInfo}`)
// 获取路由配置,可以根据 roles 来过滤
const accessRoutes = await store.dispatch(`permissionsModule/${permissionAction.GenerateRoutes}`, roles)
next({ path: '/', replace: true })
} catch (error) {
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
} else {
if (to.path === '/login') {
next()
NProgress.done()
} else if (isWhite > -1) {
// 在白名单范围之内
next()
} else {
next('/login')
}
}
})
全球邮件挂钩
我们还可以创建全局后挂钩,这对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情很有用。
router.afterEach((to, from) => {
NProgress.done()
})
农场管理店
授权模块
创建一个用户模块 (store/modules/permissions/index.ts
),作为路由存储和权限处理的中心。
状态类型
首先我们用字母permissions
定义state
类型。我这里只有一个routes
,所以我只需要定义这个routes
类型。
export type ChildRouteType= Array<RouteType>
export interface RouteType {
name: string,
path: string,
component: string,
redirect?: string,
children?: ChildRouteType
}
export default interface PermissionsStateTypes {
routes: Array<RouteType>
}
突变和作用常数
然后定义我们的 MutationTypes
和 ActionTypes
类型。当然,不指定也可以,但是页面可以导出满魔法集。 (官方也建议我们这样定义一些常量,以后如果改的话只需要改一处即可)
export enum MutationTypes {
SetRoutes = "SET_ROUTES"
}
export enum ActionTypes {
GenerateRoutes = "GENERATE_ROUTES"
}
突变类型和功能
定义完这些之后,我们还需要定义方法类型mutations
和actions
。
export type Mutations<T = PermissionsStateTypes> = {
[MutationTypes.SetRoutes](state: T, routes: RouteType[]): void
}
type ActionArgs = Omit<ActionContext<PermissionsStateTypes, RootState>, 'commit'> & {
commit<k extends keyof Mutations>(
key: k,
payload: Parameters<Mutations[k]>[1]
): ReturnType<Mutations[k]>
}
export type Actions = {
[ActionTypes.GenerateRoutes]({ commit }: ActionArgs, roles: string[]): void
}
实现
一旦定义了所有类型,就可以实现permissions
的核心逻辑。
在actions
中我们将路由信息获取到用户界面,然后通过我们设置的roles
进行过滤。如果路线有权限,我们可以使用按钮router.addRoute
动态添加它。
因为接口返回的路由不是真正的组件,
const state: PermissionsStateTypes = {
routes: []
}
const mutations: MutationTree<PermissionsStateTypes> & Mutations = {
[MutationTypes.SetRoutes](state: PermissionsStateTypes, routes: RouteType[]) {
state.routes = routes
}
}
const handleParseChildRoutes = (childs: ChildRouteType, prePath: string): ChildRouteType => {
if (childs) {
// @ts-ignore
return childs.map((c: RouteType) => {
return {
name: c.name,
path: c.path,
component: c.component === 'RouterView' ? RouterView : () => import(`@/views${prePath}/index.vue`)
}
})
} else {
return []
}
}
const actions: ActionTree<PermissionsStateTypes, RootState> & Actions = {
[ActionTypes.GenerateRoutes]({ commit }, roles: string[]) {
// 可以根据 roles 来选择性返回 route
// console.log('permissions roles', roles)
return new Promise<void>((resolve, reject) => {
// 这里的 asyncRoutesMap 是静态的数据,模拟从后台接口调用数据
asyncRoutesMap.forEach((route: RouteType) => {
router.addRoute({
name: route.name,
path: route.path,
redirect: route.redirect ? route.redirect : undefined,
component: () => import(`@/${route.component.toLowerCase()}/index.vue`),
children: handleParseChildRoutes((route.children as ChildRouteType), route.path)
})
})
// 经过处理之后,可以返回 accessRoutes
commit(MutationTypes.SetRoutes, asyncRoutesMap)
resolve(asyncRoutesMap)
})
}
}
用户模块
创建用户模块 (store/modules/user/index.ts
) 来存储用户信息、访问权限以及应用程序和菜单中心。
状态类型
用户模块state
包含用户名、权限、头像、简介、中台当前应用对象(currentApp)以及所有应用的批量集合。
突变和作用常数
export enum MutationTypes {
SetRoles = "SET_ROLES",
SetApplications = "SET_APPLICATIONS",
SetCurrentApp = "SET_CURRENT_APP"
}
export enum ActionTypes {
GetInfo = "GET_INFO",
GetApplictions = "GET_APPLICATONS"
}
突变和操作的方法类型
type ActionArgs = Omit<ActionContext<UserModuleStateType, RootState>, 'commit'> & {
commit<k extends keyof Mutations>(
key: k,
payload: Parameters<Mutations[k]>[1]
): ReturnType<Mutations[k]>
}
export type Mutations<T = UserModuleStateType> = {
[MutationTypes.SetRoles](state: T, roles: Array<string>): void
[MutationTypes.SetApplications](state: T, apps: Array<ApplicationType>): void
[MutationTypes.SetCurrentApp](state: T, app: ApplicationType): void
}
export type Actions = {
[ActionTypes.GetInfo]({ commit }: ActionArgs): void
[ActionTypes.GetApplictions]({ commit }: ActionArgs): void
}
实现
定义完所有类型后,需要实现一些逻辑。
包括设置当前用户角色SET_ROLSE
(即访问权限)、获取应用数据集合SET_APPLICATIONS
以及设置当前中心连接的应用对象SET_CURRENT_APP
。数据都是静态数据,模拟后端接口调用的数据。
const state: UserModuleStateType = {
name: '',
avatar: '',
introduction: '',
roles: [],
applications: [],
currentApp: {}
}
const mutations: MutationTree<UserModuleStateType> & Mutations = {
[MutationTypes.SET_ROLES](state: UserModuleStateType, roles: Array<string>) {
state.roles = roles
},
[MutationTypes.SET_APPLICATIONS](state: UserModuleStateType, apps: Array<ApplicationType>) {
state.applications = apps
},
[MutationTypes.SET_CURRENT_APP](state: UserModuleStateType, app: ApplicationType) {
state.currentApp = app
}
}
const actions: ActionTree<UserModuleStateType, RootState> & Actions = {
[ActionTypes.GetInfo]({ commit }) {
return new Promise((resolve, reject) => {
// getInfo() 获取接口
const roles = ['admin', 'editor']
commit(MutationTypes.SET_ROLES, roles)
resolve(roles)
})
},
[ActionTypes.GetApplictions]({ commit }) {
return new Promise<void>((resolve, reject) => {
commit(MutationTypes.SET_APPLICATIONS, applications)
commit(MutationTypes.SET_CURRENT_APP, applications[0])
resolve()
})
}
}
const UserModule: Module<UserModuleStateType, RootState> = {
namespaced: true,
state,
mutations,
actions
}
export default UserModule
至此,我们的用户模块就配置完成了。
总结
文中vuex
我们使用了很多TypeScript
语法,其中很多是TypeScript
工具类型,例如:
-
Pick<T, K>
您可以从现有对象类型中选择特定属性及其类型,然后构建新的对象类型。
-
Omit<T, K>
补充了工具类型“Pick”。它可以从现有对象类型中删除特定属性,然后构造新的对象类型。
我们有很多实用的工具等待我们去发现和使用。在使用TypeScript
应用程序时,您可能会遇到很多问题。这需要时间去打磨。写得越多越好。熟练。
一起交流吧
无论你面临什么问题或者想交朋友一起讨论技术(=.=),都可以加入我们的组织,和我们一起工作~
如果您喜欢这部分内容,请加入我们的QQ群,和大家一起交流技术~
QQ群:1032965518
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。