preface

Once our project dependencies are configured, the most important step is how to handle user permissions and assign the specified menus to users. First you need to know that our routing table will only a small part of the configuration, this part is not need any permission will be able to access, namely white list, such as login routing, routing, routing, and so on 500, 404 other we can from the interface to get inside, so that we can according to user’s permission to do some filter processing, In this way, different users can display different menus and routes.

Of course, there are other ways to do this, and I personally prefer this way for now. All right, let’s cut to the chase.

Configure the AXIos interceptor

Like configuring the AXIos interceptor, there are probably a thousand ways to write it for a thousand people, but the core logic is the same, just doing some setup, some request headers and some processing of the response data.

Create an instance

First we create an instance of axios, using the axios.create(config) method. BaseURL will automatically precede our url (unless the url is an absolute url), and timeout indicates the timeout that the request will be interrupted if there is no response after this time.

const service = axios.create({
  baseURL: defaultConfig.zhtApiUrl,
  timeout: timeout * 1000
})
Copy the code

Once the instance is created, you can configure the interceptor.

Request interceptor

In the request interceptor, we can do some processing on the request header. For example, we can add tokens to request interceptors like this:

service.interceptors.request.use(
  config= > {
    const TOKEN = getToken()
    if (TOKEN) {
      config.headers.Authorization = TOKEN
    }
    return config
  },
  error= > {
    Promise.reject(error)
  }
)
Copy the code

Response interceptor

In the response interceptor, the response data is mainly processed, such as the response status, response data, and some error handling.

service.interceptors.response.use(
  response= > {
    const { url: apiUrl } = response.config
    const { status, statusText, data } = response
    if (status === 200) {
      if(apiUrl! .includes('auth/social/token')) {
        // The interface to obtain the user token returns a different format from other interfaces.
        return {
          code: 0.msg: 'success',
          data
        }
      } else {
        return data
      }
    } else {
      / / an error
      message.error('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('Interface has no permission to access, please check the interface or parameters! status:${status} - statusText: ${statusText}. `)}else if (status === 500) {
        message.error('Please check the interface or server status! status:${status} - statusText: ${statusText}. `)
        router.push('/portal-error500')}else {
        message.error('Wrong! message:${error.message} - statusText: ${statusText}. `)}}else {
      message.error('No access! Error message:${error}`)}})Copy the code

Each person or different projects may have different configurations. Therefore, the configurations here are for reference only, and specific configurations need to be based on business requirements.

Cancel interceptor

If you want to cancel the interceptor at some point, you can do this:

const myInterceptor = axios.interceptors.request.use(function () {/ *... * /});
axios.interceptors.request.eject(myInterceptor);
Copy the code

Navigation guard

Vue-router provides navigation guards that are used to guard navigation by jumping or canceling. There are many ways to implement route navigation: global, proprietary to a single route, or component-level.

We can create a permissions. Ts file to write some global navigational guards.

Global front guard

You can register a global front-guard using router.beforeEach.

router.beforeEach((to, from) = > {
  // ...
  // Return false to cancel navigation
  return false
})
Copy the code

In permissions. Ts file, we can go to configuration, in front of the navigation jump, we need to determine whether the current user login, if he didn’t want to go to the login page, so we let him jump to the login page, if logged in, will determine whether he has obtained the permission, without obtaining permission, we will go to the interface, Otherwise, we go straight to the project’s home page (if you remember the redirect, redirect to that page, not the home page).

Our login interface only returns token-related information, so we also need to obtain the permission of the user according to the TOKEN, and other information.

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 {
        
          // Get the application
          await store.dispatch(`userModule/${userAction.GetApplictions}`)
          
          // Obtain the user transfer permission
          const roles = await store.dispatch(`userModule/${userAction.GetInfo}`)
          
          // Get the route configuration, which can be filtered by 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) {
      // Within the whitelist range
      next()
    } else {
      next('/login')}}})Copy the code

Global post-hook

We can also create global post-hooks, which are useful for analyzing, changing page titles, declaring pages, and many other things.

router.afterEach((to, from) = > {
  NProgress.done()
})
Copy the code

Status Management Store

Permissions module

Create a user module (store/modules/permissions/index. The ts), as used to store here, permissions, the center of the processing route.

State the type

First we define the state type in permissions, I only have one routes data in this, so we just need to define the routes type.

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>
}
Copy the code

Mutations and Actions constants

We then define our MutationTypes and ActionTypes, which is fine if we don’t, but the page may be loaded with magic strings. (The official recommendation is that we define some constants in this way, and then we only need to change one place.)

export enum MutationTypes {
  SetRoutes = "SET_ROUTES"
}

export enum ActionTypes {
  GenerateRoutes = "GENERATE_ROUTES"
}

Copy the code

Mutations and Actions method types

Once we’ve defined this, we also need to define the mutations and Actions method types.

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
}

Copy the code

implementation

With all the types defined, it’s time to implement the core logic in Permissions.

In actions, we can retrieve route data from the interface and filter it by roles. If we have access to routes, we can add them dynamically by router.addroute.

Since the route returned by the interface is not a real component,

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[]) {
    // Optionally return a route based on roles
    // console.log('permissions roles', roles)
    return new Promise<void> ((resolve, reject) = > {
      // asyncRoutesMap is static data that simulates calling data from a background interface
      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)
        })
      })

      After processing, you can return accessRoutes
      commit(MutationTypes.SetRoutes, asyncRoutesMap)

      resolve(asyncRoutesMap)
    })
  }
}
Copy the code

User modules

Create a user module (store/modules/user/index. The ts), used to store user information, the user’s permissions and application and the center of the menu.

State the type

The state of the user module contains the user name, permissions, profile picture, introduction, currentApp object (currentApp) and array collection of all apps.

exportinterface ApplicationType { id? : number appId? : number path? : string menuName? : string childMenu? :Array<ApplicationType>
  [propName:string]: any
}

export default interface UserModuleStateType {
  name: string
  roles: Array<string>
  currentApp: ApplicationType
  applications: Array<ApplicationType> avatar? : string introduction? : string }Copy the code

Mutations and Actions constants

export enum MutationTypes {
  SetRoles = "SET_ROLES",
  SetApplications = "SET_APPLICATIONS",
  SetCurrentApp = "SET_CURRENT_APP"
}

export enum ActionTypes {
  GetInfo = "GET_INFO",
  GetApplictions = "GET_APPLICATONS"
}
Copy the code

Mutations and Actions method types

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
}
Copy the code

implementation

Once you’ve defined all the types, it’s time to implement the concrete logic.

This includes setting the current user role SET_ROLSE (permissions), getting the application data set SET_APPLICATIONS, and setting the application object SET_CURRENT_APP that is currently connected to the central platform. The data is static data, simulating the data called from the background interface.

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() gets the interface
      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
Copy the code

At this point, our user module is configured.

conclusion

We use a lot of TypeScript syntax in Vuex, many of which are built-in TypeScript utility types, such as:

  • Pick<T, K>

    The ability to take a given property and its type from an existing object type and build a new object type.

  • Omit<T, K>

    Complementary to the “Pick<T, K>” utility type, it can strip a given attribute from an existing object type and then build a new object type.

There are a lot of useful tool types to discover and use, and there are a lot of problems with TypeScript that take time to develop, and the more you write, the better you get at it.

To communicate with

Whatever problem you’re having, or just want to make a friend and talk about technology (= =), can join our organization, with us ~

Enjoy this part of the content, join our QQ group, and share the technology with you

QQ group: 1032965518