序文
プロジェクトの枠組み: 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()
}
}
})
この記事に関して質問や異なる見解がある場合は、コメント欄で指摘してください。
終わり