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