feat(store): 优化状态管理和路由处理

* 增强路由状态管理
  - 改进动态路由加载和混合路由模式支持
  - 优化路由重置机制,保护基础路由
  - 完善路由helper工具函数
* 提升标签页管理
  - 集成安全导航机制,使用navigationGuard
  - 增强标签页关闭和跳转的错误处理
* 改进认证状态管理
  - 优化登录流程和用户信息处理
  - 更好的登录重定向逻辑

提升应用稳定性和用户体验
This commit is contained in:
Leo 2025-07-06 02:35:47 +08:00
parent 5e13342f7b
commit 4f27534f22
4 changed files with 92 additions and 38 deletions

View File

@ -1,5 +1,5 @@
import { router } from '@/router' import { router } from '@/router'
import { fetchLogin, fetchLoginUserInfo } from '@/service' import { fetchLogin, fetchLoginUserInfo } from '@/service/api/auth'
import { local } from '@/utils' import { local } from '@/utils'
import { useRouteStore } from './router' import { useRouteStore } from './router'
import { useTabStore } from './tab' import { useTabStore } from './tab'

View File

@ -4,6 +4,7 @@ import { h } from 'vue'
import { usePermission } from '@/hooks' import { usePermission } from '@/hooks'
import Layout from '@/layouts/index.vue' import Layout from '@/layouts/index.vue'
import { arrayToTree, renderIcon } from '@/utils' import { arrayToTree, renderIcon } from '@/utils'
import { safeAsyncComponent } from '@/utils/component-guard'
import { clone, min, omit, pick } from 'radash' import { clone, min, omit, pick } from 'radash'
import { RouterLink } from 'vue-router' import { RouterLink } from 'vue-router'
@ -78,21 +79,52 @@ export function createRoutes(routeData: (AppRoute.BackendRoute | AppRoute.RowRou
componentPath = `${componentPath}.vue` componentPath = `${componentPath}.vue`
} }
const fullPath = `/src/views${componentPath}` const fullPath = `/src/views${componentPath}`
item.component = modules[fullPath] const originalComponent = modules[fullPath]
// 如果组件未找到,输出调试信息并提供默认组件 // 如果组件未找到,输出调试信息并提供默认组件
if (!item.component) { if (!originalComponent) {
console.warn(`组件未找到: ${fullPath}`) console.warn(`组件未找到: ${fullPath}`)
console.warn('可用组件路径:', Object.keys(modules).slice(0, 10)) // 只显示前10个避免日志过长 console.warn('可用组件路径:', Object.keys(modules).slice(0, 10)) // 只显示前10个避免日志过长
// 为找不到组件的页面提供一个默认的空页面组件 // 为找不到组件的页面提供一个默认的空页面组件
item.component = () => h('div', { class: 'p-4' }, [ item.component = safeAsyncComponent(
h('div', { class: 'text-center text-gray-500' }, [ () => Promise.resolve({
h('h3', '页面开发中'), template: `
h('p', `组件路径: ${fullPath}`), <div class="p-4">
h('p', '请联系开发人员创建对应的页面组件'), <div class="text-center text-gray-500">
]), <h3></h3>
]) <p>组件路径: ${fullPath}</p>
<p></p>
</div>
</div>
`,
}),
{
delay: 0,
timeout: 5000,
},
)
}
else {
// 使用安全的异步组件加载器包装原有组件
item.component = safeAsyncComponent(
originalComponent as any,
{
delay: 100,
timeout: 10000,
onError: (error, retry, fail, attempts) => {
console.error(`组件加载失败: ${fullPath}`, error)
if (attempts <= 2) {
console.warn(`重试加载组件: ${fullPath} (第${attempts}次)`)
retry()
}
else {
console.error(`组件加载最终失败: ${fullPath}`)
fail()
}
},
},
)
} }
} }
else if (item.meta.menuType === '1') { else if (item.meta.menuType === '1') {

View File

@ -1,7 +1,7 @@
import type { MenuOption } from 'naive-ui' import type { MenuOption } from 'naive-ui'
import { router } from '@/router' import { router } from '@/router'
import { staticRoutes } from '@/router/routes.static' import { staticRoutes } from '@/router/routes.static'
import { fetchUserRoutes } from '@/service' import { fetchUserRoutes } from '@/service/api/system/menu'
import { $t } from '@/utils' import { $t } from '@/utils'
import { coiMsgError } from '@/utils/coi' import { coiMsgError } from '@/utils/coi'
import { createMenus, createRoutes, generateCacheRoutes } from './helper' import { createMenus, createRoutes, generateCacheRoutes } from './helper'
@ -31,8 +31,20 @@ export const useRouteStore = defineStore('route-store', {
this.$reset() this.$reset()
}, },
resetRoutes() { resetRoutes() {
if (router.hasRoute('appRoot')) // 获取所有路由名称
router.removeRoute('appRoot') const allRouteNames = router.getRoutes().map(route => route.name).filter(Boolean)
// 保护固定路由,不删除这些基础路由
const protectedRoutes = ['root', 'login', '403', '404', '500', 'notFound']
// 删除除了保护路由之外的所有路由
allRouteNames.forEach((name) => {
if (name && !protectedRoutes.includes(name as string)) {
if (router.hasRoute(name)) {
router.removeRoute(name)
}
}
})
}, },
// set the currently highlighted menu key // set the currently highlighted menu key
setActiveMenu(key: string) { setActiveMenu(key: string) {

View File

@ -1,5 +1,5 @@
import type { RouteLocationNormalized } from 'vue-router' import type { RouteLocationNormalized } from 'vue-router'
import { router } from '@/router' import { navigationGuard } from '@/router'
interface TabState { interface TabState {
pinTabs: RouteLocationNormalized[] pinTabs: RouteLocationNormalized[]
@ -34,29 +34,34 @@ export const useTabStore = defineStore('tab-store', {
this.tabs.push(route) this.tabs.push(route)
}, },
async closeTab(fullPath: string) { async closeTab(fullPath: string) {
const tabsLength = this.tabs.length try {
// 如果动态标签大于一个,才会标签跳转 const tabsLength = this.tabs.length
if (this.tabs.length > 1) { // 如果动态标签大于一个,才会标签跳转
// 获取关闭的标签索引 if (this.tabs.length > 1) {
const index = this.getTabIndex(fullPath) // 获取关闭的标签索引
const isLast = index + 1 === tabsLength const index = this.getTabIndex(fullPath)
// 如果是关闭的当前页面,路由跳转到原先标签的后一个标签 const isLast = index + 1 === tabsLength
if (this.currentTabPath === fullPath && !isLast) { // 如果是关闭的当前页面,路由跳转到原先标签的后一个标签
// 跳转到后一个标签 if (this.currentTabPath === fullPath && !isLast) {
router.push(this.tabs[index + 1].fullPath) // 跳转到后一个标签
} await navigationGuard.safePush(this.tabs[index + 1].fullPath)
else if (this.currentTabPath === fullPath && isLast) { }
// 已经是最后一个了,就跳转前一个 else if (this.currentTabPath === fullPath && isLast) {
router.push(this.tabs[index - 1].fullPath) // 已经是最后一个了,就跳转前一个
await navigationGuard.safePush(this.tabs[index - 1].fullPath)
}
} }
// 删除标签
this.tabs = this.tabs.filter((item) => {
return item.fullPath !== fullPath
})
// 删除后如果清空了,就跳转到默认首页
if (tabsLength - 1 === 0)
await navigationGuard.safePush('/')
}
catch (error) {
console.error('关闭标签页时发生错误:', error)
} }
// 删除标签
this.tabs = this.tabs.filter((item) => {
return item.fullPath !== fullPath
})
// 删除后如果清空了,就跳转到默认首页
if (tabsLength - 1 === 0)
router.push('/')
}, },
closeOtherTabs(fullPath: string) { closeOtherTabs(fullPath: string) {
@ -75,9 +80,14 @@ export const useTabStore = defineStore('tab-store', {
this.tabs.length = 0 this.tabs.length = 0
this.pinTabs.length = 0 this.pinTabs.length = 0
}, },
closeAllTabs() { async closeAllTabs() {
this.tabs.length = 0 try {
router.push('/') this.tabs.length = 0
await navigationGuard.safePush('/')
}
catch (error) {
console.error('关闭所有标签页时发生错误:', error)
}
}, },
hasExistTab(fullPath: string) { hasExistTab(fullPath: string) {