Add dynamic routing and jump after Vue login (super detailed)

foreword

Project framework: Vue3+TypeScript

There is such a requirement that the system only has the most basic routes by default, such as login, 404, etc. Other routes need to be added dynamically after login. The system does not have a fixed homepage. After login, it will jump to the first menu page of the dynamic menu.

analyze

This logic is simple at first glance, but in fact there are many small pits in it. The easiest pitfall to step on is that the dynamic 路由尚未渲染完成has already triggered the routing jump. At this time, it must be 404, because the route does not exist; another pitfall that is easy to step on is that the page will be 路由重复加载blank at this time, and it needs to be manually refreshed to display normally. .

The first thing that comes to mind is to use Promisethe function to solve it, but it doesn't work. addRouteis a macro task and resolveis a micro task, so Promisethe end of does not mean that the dynamic routing has been added.

Secondly, I thought of using asyncthe function to ensure that when the successful login result is obtained, the route has been added, but after some attempts, it still does not work. Because the operation of adding a route is not asynchronous and does not return Promisean object, the here awaitwill have no effect. (PS: Use afterwards Promise.allto solve this problem, the specific method will be mentioned later.)

Finally, I thought of a very stupid solution, polling. After experimenting, it is determined that it can be achieved, but as said at the beginning, this will appear very awkward low, although it finally solved the problem.

practice

The login operation is the same, so take it out and write it only once. I won’t introduce the form, let’s start with clicking the login form after the verification is passed.

Putting all the login codes on one page will look bloated, so I extracted the specific login operation logic. src/utilsCreate a file in the directory auth.ts.

auth.ts

import {
    
     useRouteListStore } from '@/store/router'
const routeListStore = useRouteListStore()

// 登录
export async function Login(data: {
    
     username: string; password: string; portal: string; corpCode: string }) {
    
    
  const {
    
     username, password, portal, corpCode } = data
  try {
    
    
    // 登录接口
    const res = await getLogin({
    
     username, password, portal, corpCode })
    // ...
    // 这里写保存用户信息及 token 的逻辑
    // ...
    
    // 添加路由操作,写在 pinia 中,后面会说
    await routeListStore.updateRouteList()
    return res
  } catch (err) {
    
    
    return err
  }
}

Next, write the specific logic for adding routes. src/storeCreate a file under the directory , router.tsand add the content as follows: (PS: The specific file path should be combined with the specific project content. The following path and menu format are only examples).

According to different processing methods, there are two options.

Solution 1: Use async function

src/store/router.ts

export const useRouteListStore = defineStore('routeList', {
    
    
  state: () => ({
    
    
    routeList: [],
    breadcrumb: [],
    getRouter: true // 是否需要重新加载路由
  }),
  actions: {
    
    
    // 更新菜单并追加路由
    async updateRouteList() {
    
    
      const modules = import.meta.glob('../views/**/*.vue')
      // 此为接口请求获取的菜单
      const list = await getMenus()
      list.forEach((e) => {
    
    
        e.route = e.path
        e.component = () => import('@/layout/index.vue')
        e.redirect = `${
      
      e.path}/${
      
      e.children[0].path}`
        e.children.forEach((item) => {
    
    
          item.route = `${
      
      e.path}/${
      
      item.path}`
          item.component = modules[`../views${
      
      item.component}.vue`]
        })
      })
      await addRouteList(list)
      this.getRouter = false
      this.routeList = list
      return true
    },
  }
})

Next, write the logic of dynamically adding routes, and use Promise.all to ensure that when the results are returned in Pinia, the dynamic routes have been loaded. In src/routerthe create index.tsfile, add the following:

src/router/index.ts

export function addRouteList(data: any[] = []) {
    
    
  return new Promise((resolve) => {
    
    
    const promises = []
    data.forEach((e) => promises.push(router.addRoute(e)))
    Promise.all(promises).then(() => resolve(true))
  })
}

After using the async function, the operation of the login page will become very simple.

login.vue

import {
    
     Login } from '@/utils/auth'

const onSubmit = () => {
    
    
  validate().then(() => {
    
    
    Login(formState).then(() => {
    
    
      router.push(routerStore.routeList[0].path)
    }).catch(err => {
    
    
      message.error(err.message)
    })
  })
}

Solution 2: use polling

asyncThe polling scheme is much simpler than using the function, because it does not need to ensure that the route is loaded at the moment the result is obtained after login. The specific implementation code is as follows:

src/store/router.ts

export const useRouteListStore = defineStore('routeList', {
    
    
  state: () => ({
    
    
    routeList: [],
    breadcrumb: [],
    getRouter: true
  }),
  actions: {
    
    
    // 更新菜单并追加路由
    updateRouteList() {
    
    
      listMenus().then((res) => {
    
    
        const list = res.data
        if (list === null) {
    
    
          this.getRouter = false
          router.push('/404')
          return
        }
        list.forEach((e) => {
    
    
          e.route = e.path
          e.component = () => import('@/layout/index.vue')
          e.children.forEach((item) => {
    
    
            item.route = `${
      
      e.path}/${
      
      item.path}`
            item.component = modules[`../views${
      
      item.component}.vue`]
          })
        })
        addRouteList(list)
        this.getRouter = false
        this.routeList = list
      })
  }
})

src/router/index.ts

export function addRouteList(data: any[] = []) {
    
    
  data.forEach((e) => {
    
    
    router.addRoute(e)
  })
}

The advantage of polling is that the logic is simple. The only troublesome point is to add a timer after login to periodically obtain whether the route is loaded. The reason why the timer is added is that obtaining the menu is an asynchronous request, and the program is executed very quickly, so it is necessary to ensure that the menu is loaded when the routing jump command is executed.

login.vue

import {
    
     ref, onBeforeUnmount } from 'vue'
import {
    
     useRouter } from 'vue-router'
import {
    
     useRouteListStore } from '@/store/router'
const routerStore = useRouteListStore()
import {
    
     Login } from '@/utils/auth'
const router = useRouter()

// 每0.5s判断一次菜单是否加载完成,最多判断30次,超过则说明网络环境极差
const timer = ref(null)
const onSubmit = () => {
    
    
  validate().then(() => {
    
    
    Login(formState).then(() => {
    
    
    let i = 0
      timer.value = setInterval(() => {
    
    
        if (routerStore.routeList[0].path) {
    
    
          router.push(routerStore.routeList[0].path)
        }
        i++
        if (i > 30) {
    
    
          clearInterval(timer.value)
          timer.value = null
          i = null
          message.error('当前网络环境较差!')
          spinning.value = false
        }
      }, 500)
    })
  })
}

// 不要忘记清除定时器
onBeforeUnmount(() => {
    
    
  clearInterval(timer.value)
  timer.value = null
})

Replenish

The above code can only ensure that the system can jump to the page normally after the first login. If you log out of the current account, log in again or change the account to log in, there will be a problem of repeated routing loading, which is another pit that is easy to step on at the beginning of the article. It is not difficult to solve this pit, as long as you notice it, it can be solved easily.

The solution is to add a pre-route guard, and add a field to Pinia to determine whether the current route needs to be reloaded. The specific code is as follows:

import Cookies from 'js-cookie'
import {
    
     useRouteListStore } from '@/store/router'

// 前置守卫
router.beforeEach(async (to, from, next) => {
    
    
  const token = Cookies.get('token')
  if (!token) {
    
    
    next({
    
     path: '/login' })
  } else {
    
    
    const routerStore = useRouteListStore()
    routerStore.addBreadcrumb(to)
    
    // 判断菜单是否存在且是否需要重新加载
    if (routerStore.routeList.length === 0 && routerStore.getRouter) {
    
    
      await routerStore.updateRouteList()
      next({
    
     path: to.path, query: to.query })
    } else {
    
    
      next()
    }
  }
})

If you have any questions or different views on this article, please point it out in the comment area.

END

Guess you like

Origin blog.csdn.net/m0_53808238/article/details/131792975