diff --git a/src/store/auth.ts b/src/store/auth.ts
index 6951c4c..01b37cc 100644
--- a/src/store/auth.ts
+++ b/src/store/auth.ts
@@ -1,5 +1,5 @@
import { router } from '@/router'
-import { fetchLogin, fetchLoginUserInfo } from '@/service'
+import { fetchLogin, fetchLoginUserInfo } from '@/service/api/auth'
import { local } from '@/utils'
import { useRouteStore } from './router'
import { useTabStore } from './tab'
diff --git a/src/store/router/helper.ts b/src/store/router/helper.ts
index 326a780..fd84cae 100644
--- a/src/store/router/helper.ts
+++ b/src/store/router/helper.ts
@@ -4,6 +4,7 @@ import { h } from 'vue'
import { usePermission } from '@/hooks'
import Layout from '@/layouts/index.vue'
import { arrayToTree, renderIcon } from '@/utils'
+import { safeAsyncComponent } from '@/utils/component-guard'
import { clone, min, omit, pick } from 'radash'
import { RouterLink } from 'vue-router'
@@ -78,21 +79,52 @@ export function createRoutes(routeData: (AppRoute.BackendRoute | AppRoute.RowRou
componentPath = `${componentPath}.vue`
}
const fullPath = `/src/views${componentPath}`
- item.component = modules[fullPath]
+ const originalComponent = modules[fullPath]
// 如果组件未找到,输出调试信息并提供默认组件
- if (!item.component) {
+ if (!originalComponent) {
console.warn(`组件未找到: ${fullPath}`)
console.warn('可用组件路径:', Object.keys(modules).slice(0, 10)) // 只显示前10个避免日志过长
// 为找不到组件的页面提供一个默认的空页面组件
- item.component = () => h('div', { class: 'p-4' }, [
- h('div', { class: 'text-center text-gray-500' }, [
- h('h3', '页面开发中'),
- h('p', `组件路径: ${fullPath}`),
- h('p', '请联系开发人员创建对应的页面组件'),
- ]),
- ])
+ item.component = safeAsyncComponent(
+ () => Promise.resolve({
+ template: `
+
+
+
页面开发中
+
组件路径: ${fullPath}
+
请联系开发人员创建对应的页面组件
+
+
+ `,
+ }),
+ {
+ 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') {
diff --git a/src/store/router/index.ts b/src/store/router/index.ts
index 5158964..faf1068 100644
--- a/src/store/router/index.ts
+++ b/src/store/router/index.ts
@@ -1,7 +1,7 @@
import type { MenuOption } from 'naive-ui'
import { router } from '@/router'
import { staticRoutes } from '@/router/routes.static'
-import { fetchUserRoutes } from '@/service'
+import { fetchUserRoutes } from '@/service/api/system/menu'
import { $t } from '@/utils'
import { coiMsgError } from '@/utils/coi'
import { createMenus, createRoutes, generateCacheRoutes } from './helper'
@@ -31,8 +31,20 @@ export const useRouteStore = defineStore('route-store', {
this.$reset()
},
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
setActiveMenu(key: string) {
diff --git a/src/store/tab.ts b/src/store/tab.ts
index 218965e..c553f6e 100644
--- a/src/store/tab.ts
+++ b/src/store/tab.ts
@@ -1,5 +1,5 @@
import type { RouteLocationNormalized } from 'vue-router'
-import { router } from '@/router'
+import { navigationGuard } from '@/router'
interface TabState {
pinTabs: RouteLocationNormalized[]
@@ -34,29 +34,34 @@ export const useTabStore = defineStore('tab-store', {
this.tabs.push(route)
},
async closeTab(fullPath: string) {
- const tabsLength = this.tabs.length
- // 如果动态标签大于一个,才会标签跳转
- if (this.tabs.length > 1) {
- // 获取关闭的标签索引
- const index = this.getTabIndex(fullPath)
- const isLast = index + 1 === tabsLength
- // 如果是关闭的当前页面,路由跳转到原先标签的后一个标签
- if (this.currentTabPath === fullPath && !isLast) {
- // 跳转到后一个标签
- router.push(this.tabs[index + 1].fullPath)
- }
- else if (this.currentTabPath === fullPath && isLast) {
- // 已经是最后一个了,就跳转前一个
- router.push(this.tabs[index - 1].fullPath)
+ try {
+ const tabsLength = this.tabs.length
+ // 如果动态标签大于一个,才会标签跳转
+ if (this.tabs.length > 1) {
+ // 获取关闭的标签索引
+ const index = this.getTabIndex(fullPath)
+ const isLast = index + 1 === tabsLength
+ // 如果是关闭的当前页面,路由跳转到原先标签的后一个标签
+ if (this.currentTabPath === fullPath && !isLast) {
+ // 跳转到后一个标签
+ await navigationGuard.safePush(this.tabs[index + 1].fullPath)
+ }
+ else if (this.currentTabPath === fullPath && isLast) {
+ // 已经是最后一个了,就跳转前一个
+ 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) {
@@ -75,9 +80,14 @@ export const useTabStore = defineStore('tab-store', {
this.tabs.length = 0
this.pinTabs.length = 0
},
- closeAllTabs() {
- this.tabs.length = 0
- router.push('/')
+ async closeAllTabs() {
+ try {
+ this.tabs.length = 0
+ await navigationGuard.safePush('/')
+ }
+ catch (error) {
+ console.error('关闭所有标签页时发生错误:', error)
+ }
},
hasExistTab(fullPath: string) {