動的ルーティングを追加し、Vue ログイン後にジャンプする (超詳細)

序文

プロジェクトの枠組み: Vue3+TypeScript

システムには、ログイン、404 などの最も基本的なルートのみがデフォルトで存在するという要件があります。他のルートはログイン後に動的に追加する必要があります。システムには固定のホームページはなく、ログイン後はダイナミックメニューの最初のメニューページにジャンプします。

分析する

このロジックは一見単純ですが、実際には小さな落とし穴がたくさんあります。最も踏み込みやすい落とし穴は、ダイナミックが路由尚未渲染完成すでに配線ジャンプをトリガーしていることです。この時点では、ルートが存在しないため 404 でなければなりません。踏み込みやすいもう 1 つの落とし穴は、この時点でページが空白になることです路由重复加载。正常に表示するには手動で更新する必要があります。

最初に思いつくのは、Promise関数を使用してそれを解決することですが、それは機能しません。addRouteはマクロタスクであり、resolveマイクロタスクなので、Promise終了してもダイナミックルーティングが追加されたわけではありません。

async次に、ログイン成功時にルートが追加されたことを確認する関数を使おうと考えたのですが、何度か試してみてもうまくいきませんPromiseルートを追加する操作は非同期ではなく、オブジェクトを返さないため、here はawait効果がありません。(追記:Promise.allこの問題を解決するには、後で使用します。具体的な方法は後で説明します。)

最後に、私は投票という非常に愚かな解決策を考えました。low実験の結果、それが達成できると判断されましたが、最初に述べたように、最終的に問題は解決しましたが、これは非常にぎこちないように見えます。

練習する

ログイン操作は同じなので一度取り出して書き込んでください。フォームの紹介は省略しますので、認証が通過したらログインフォームをクリックするところから始めましょう。

ログインコードをすべて1ページに載せると肥大化してしまうので、具体的なログイン操作ロジックを抽出しました。ディレクトリsrc/utilsにファイルを作成します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
  }
}

次に、ルートを追加するための具体的なロジックを作成します。src/storeディレクトリの下にファイルを作成しrouter.ts次のようにコンテンツを追加します: (PS: 特定のファイル パスは特定のプロジェクト コンテンツと組み合わせる必要があります。次のパスとメニュー形式は単なる例です)。

さまざまな処理方法に応じて、2 つのオプションがあります。

解決策 1: 非同期関数を使用する

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
    },
  }
})

次に、ルートを動的に追加するロジックを作成し、Promise.all を使用して、結果が Pinia に返されたときに動的ルートがロードされていることを確認します。src/router作成ファイルindex.ts次の内容を追加します。

src/ルーター/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))
  })
}

async 機能を使用すると、ログイン ページの操作が非常に簡単になります。

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)
    })
  })
}

解決策 2: ポーリングを使用する

ポーリング スキームは、ログイン後に結果が取得された時点でルートがロードされていることを確認する必要がないため、関数を使用するよりもasyncはるかに簡単です。具体的な実装コードは以下のとおりです。

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/ルーター/index.ts

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

ポーリングの利点はロジックがシンプルなことですが、唯一面倒な点は、ログイン後にタイマーを追加して、ルートがロードされているかどうかを定期的に取得することです。タイマーを追加する理由は、メニューの取得が非同期リクエストであり、プログラムが非常に高速に実行されるため、ルーティング ジャンプ コマンドの実行時にメニューが確実にロードされるようにする必要があるためです。

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
})

補充する

上記のコードは、最初のログイン後にシステムがページに正常にジャンプできることのみを保証できます。現在のアカウントからログアウトし、再度ログインするか、ログインするアカウントを変更すると、ルーティングの読み込みが繰り返されるという問題が発生します。これも記事の冒頭で踏みやすい落とし穴です。この落とし穴を解決するのは難しいことではなく、気づけば簡単に解決できます。

解決策は、プレルート ガードを追加し、現在のルートをリロードする必要があるかどうかを判断するフィールドを Pinia に追加することです。具体的なコードは次のとおりです。

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()
    }
  }
})

この記事に関して質問や異なる見解がある場合は、コメント欄で指摘してください。

終わり

おすすめ

転載: blog.csdn.net/m0_53808238/article/details/131792975