mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-11-18 14:32:10 +08:00
refactor: move app initialization from AppMain to main.ts and improve router guards
This commit is contained in:
parent
423e79f64d
commit
1b28285b04
49
src/App.vue
49
src/App.vue
@ -1,18 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import AppMain from './AppMain.vue'
|
||||
import AppLoading from './components/common/AppLoading.vue'
|
||||
import { useAppStore } from '@/store'
|
||||
import { naiveI18nOptions } from '@/utils'
|
||||
import { darkTheme } from 'naive-ui'
|
||||
|
||||
// 使用 Suspense 处理异步组件加载
|
||||
const appStore = useAppStore()
|
||||
|
||||
const naiveLocale = computed(() => {
|
||||
return naiveI18nOptions[appStore.lang] ? naiveI18nOptions[appStore.lang] : naiveI18nOptions.enUS
|
||||
})
|
||||
|
||||
const propOverrides = {
|
||||
ProSearchForm: {
|
||||
labelPlacement: 'left',
|
||||
cols: 4,
|
||||
},
|
||||
ProModalForm: {
|
||||
labelWidth: 100,
|
||||
labelPlacement: 'left',
|
||||
preset: 'card',
|
||||
},
|
||||
ProDataTable: {
|
||||
paginateSinglePage: false,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Suspense>
|
||||
<!-- 异步组件 -->
|
||||
<AppMain />
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<template #fallback>
|
||||
<AppLoading />
|
||||
</template>
|
||||
</Suspense>
|
||||
<pro-config-provider
|
||||
:prop-overrides="propOverrides"
|
||||
abstract
|
||||
inline-theme-disabled
|
||||
:theme="appStore.colorMode === 'dark' ? darkTheme : null"
|
||||
:locale="naiveLocale.locale"
|
||||
:date-locale="naiveLocale.dateLocale"
|
||||
:theme-overrides="appStore.theme"
|
||||
>
|
||||
<naive-provider>
|
||||
<router-view />
|
||||
<Watermark :show-watermark="appStore.showWatermark" />
|
||||
</naive-provider>
|
||||
</pro-config-provider>
|
||||
</template>
|
||||
|
||||
@ -1,73 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { App } from 'vue'
|
||||
import { installRouter } from '@/router'
|
||||
import { installPinia } from '@/store'
|
||||
import { naiveI18nOptions } from '@/utils'
|
||||
import { darkTheme } from 'naive-ui'
|
||||
import { useAppStore } from './store'
|
||||
|
||||
// 创建异步初始化 Promise - 这会让组件变成异步组件
|
||||
const initializationPromise = (async () => {
|
||||
// 获取当前应用实例
|
||||
const app = getCurrentInstance()?.appContext.app
|
||||
if (!app) {
|
||||
throw new Error('Failed to get app instance')
|
||||
}
|
||||
|
||||
// 注册模块 Pinia
|
||||
await installPinia(app)
|
||||
|
||||
// 注册模块 Vue-router
|
||||
await installRouter(app)
|
||||
|
||||
// 注册模块 指令/静态资源
|
||||
const modules = import.meta.glob<{ install: (app: App) => void }>('./modules/*.ts', {
|
||||
eager: true,
|
||||
})
|
||||
|
||||
Object.values(modules).forEach(module => app.use(module))
|
||||
|
||||
return true
|
||||
})()
|
||||
|
||||
// 等待初始化完成 - 这使得整个 setup 函数变成异步的
|
||||
await initializationPromise
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const naiveLocale = computed(() => {
|
||||
return naiveI18nOptions[appStore.lang] ? naiveI18nOptions[appStore.lang] : naiveI18nOptions.enUS
|
||||
})
|
||||
|
||||
const propOverrides = {
|
||||
ProSearchForm: {
|
||||
labelPlacement: 'left',
|
||||
cols: 4,
|
||||
},
|
||||
ProModalForm: {
|
||||
labelWidth: 100,
|
||||
labelPlacement: 'left',
|
||||
preset: 'card',
|
||||
},
|
||||
ProDataTable: {
|
||||
paginateSinglePage: false,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<pro-config-provider
|
||||
:prop-overrides="propOverrides"
|
||||
abstract
|
||||
inline-theme-disabled
|
||||
:theme="appStore.colorMode === 'dark' ? darkTheme : null"
|
||||
:locale="naiveLocale.locale"
|
||||
:date-locale="naiveLocale.dateLocale"
|
||||
:theme-overrides="appStore.theme"
|
||||
>
|
||||
<naive-provider>
|
||||
<router-view />
|
||||
<Watermark :show-watermark="appStore.showWatermark" />
|
||||
</naive-provider>
|
||||
</pro-config-provider>
|
||||
</template>
|
||||
42
src/main.ts
42
src/main.ts
@ -1,5 +1,41 @@
|
||||
import type { App as AppType } from 'vue'
|
||||
import App from './App.vue'
|
||||
import AppLoading from './components/common/AppLoading.vue'
|
||||
import { installPinia } from '@/store'
|
||||
import { installRouter } from '@/router'
|
||||
|
||||
// 创建应用实例并挂载
|
||||
const app = createApp(App)
|
||||
app.mount('#app')
|
||||
async function bootstrap() {
|
||||
// 显示加载动画
|
||||
const loadingApp = createApp(AppLoading)
|
||||
loadingApp.mount('#app')
|
||||
|
||||
try {
|
||||
// 创建应用实例
|
||||
const app = createApp(App)
|
||||
|
||||
// 注册模块 Pinia
|
||||
await installPinia(app)
|
||||
|
||||
// 注册模块 Vue-router
|
||||
await installRouter(app)
|
||||
|
||||
// 注册模块 指令/静态资源
|
||||
const modules = import.meta.glob<{ install: (app: AppType) => void }>('./modules/*.ts', {
|
||||
eager: true,
|
||||
})
|
||||
|
||||
Object.values(modules).forEach(module => app.use(module))
|
||||
|
||||
// 卸载加载动画并挂载主应用
|
||||
loadingApp.unmount()
|
||||
app.mount('#app')
|
||||
}
|
||||
catch (error) {
|
||||
// 如果初始化失败,卸载加载动画并显示错误
|
||||
loadingApp.unmount()
|
||||
console.error('Application initialization failed:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
bootstrap().catch(console.error)
|
||||
|
||||
@ -1,92 +1,142 @@
|
||||
import type { Router } from 'vue-router'
|
||||
import type { NavigationGuardNext, RouteLocationNormalized, Router } from 'vue-router'
|
||||
import { useAppStore, useRouteStore, useTabStore } from '@/store'
|
||||
import { local } from '@/utils'
|
||||
|
||||
const title = import.meta.env.VITE_APP_NAME
|
||||
|
||||
// 路由上下文
|
||||
interface RouteContext {
|
||||
appStore: ReturnType<typeof useAppStore>
|
||||
routeStore: ReturnType<typeof useRouteStore>
|
||||
tabStore: ReturnType<typeof useTabStore>
|
||||
isLogin: boolean
|
||||
}
|
||||
|
||||
// 外链处理
|
||||
function handleExternalLink(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
|
||||
if (to.meta.isLink) {
|
||||
window.open(to.meta.linkPath as string)
|
||||
next(false)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 根路径重定向
|
||||
function handleRootPath(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext, context: RouteContext) {
|
||||
if (to.path === '/') {
|
||||
if (context.isLogin) {
|
||||
next({ path: import.meta.env.VITE_HOME_PATH, replace: true })
|
||||
}
|
||||
else {
|
||||
next({ path: '/login', replace: true })
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 登录页面访问处理
|
||||
function handleLoginPage(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext, context: RouteContext) {
|
||||
if (to.name === 'login' && context.isLogin) {
|
||||
next({ path: import.meta.env.VITE_HOME_PATH, replace: true })
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 认证检查
|
||||
function handleAuthentication(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext, context: RouteContext) {
|
||||
if (!context.isLogin && to.name !== 'login' && to.meta?.requiresAuth !== false) {
|
||||
const redirect = to.name === 'not-found' ? undefined : to.fullPath
|
||||
next({ path: '/login', query: { redirect } })
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 路由初始化
|
||||
async function handleRouteInitialization(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext, context: RouteContext) {
|
||||
if (context.isLogin && to.name !== 'login' && !context.routeStore.isInitAuthRoute) {
|
||||
try {
|
||||
await context.routeStore.initAuthRoute()
|
||||
|
||||
if (to.name === 'not-found') {
|
||||
next({
|
||||
path: to.fullPath,
|
||||
replace: true,
|
||||
query: to.query,
|
||||
hash: to.hash,
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Route initialization failed:', error)
|
||||
local.remove('accessToken')
|
||||
context.routeStore.resetRouteStore()
|
||||
const redirect = to.fullPath !== '/' && to.fullPath !== '/login' ? to.fullPath : undefined
|
||||
next({ path: '/login', query: redirect ? { redirect } : undefined, replace: true })
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 路由处理器列表
|
||||
const routeHandlers = [
|
||||
handleExternalLink,
|
||||
handleRootPath,
|
||||
handleLoginPage,
|
||||
handleAuthentication,
|
||||
handleRouteInitialization,
|
||||
]
|
||||
|
||||
// 处理路由
|
||||
async function processRoute(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext, context: RouteContext) {
|
||||
for (const handler of routeHandlers) {
|
||||
const handled = await handler(to, from, next, context)
|
||||
if (handled)
|
||||
return
|
||||
}
|
||||
|
||||
// 如果没有任何处理器处理该路由,则继续导航
|
||||
next()
|
||||
}
|
||||
|
||||
export function setupRouterGuard(router: Router) {
|
||||
const appStore = useAppStore()
|
||||
const routeStore = useRouteStore()
|
||||
const tabStore = useTabStore()
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
// 判断是否是外链,如果是直接打开网页并拦截跳转
|
||||
if (to.meta.isLink) {
|
||||
window.open(to.meta.linkPath)
|
||||
next(false) // 取消当前导航
|
||||
return
|
||||
}
|
||||
// 开始 loadingBar
|
||||
appStore.showProgress && window.$loadingBar?.start()
|
||||
|
||||
// 判断有无TOKEN,登录鉴权
|
||||
const isLogin = Boolean(local.get('accessToken'))
|
||||
|
||||
// 处理根路由重定向
|
||||
if (to.path === '/') {
|
||||
if (isLogin) {
|
||||
// 已登录,重定向到首页
|
||||
next({ path: import.meta.env.VITE_HOME_PATH, replace: true })
|
||||
}
|
||||
else {
|
||||
// 未登录,重定向到登录页
|
||||
next({ path: '/login', replace: true })
|
||||
}
|
||||
return
|
||||
// 创建路由上下文
|
||||
const context: RouteContext = {
|
||||
appStore,
|
||||
routeStore,
|
||||
tabStore,
|
||||
isLogin: Boolean(local.get('accessToken')),
|
||||
}
|
||||
|
||||
// 如果用户未登录,重定向到登录页
|
||||
if (!isLogin) {
|
||||
const redirect = to.name === 'not-found' ? undefined : to.fullPath
|
||||
next({ path: '/login', query: { redirect } })
|
||||
return
|
||||
}
|
||||
|
||||
// 如果用户已登录且访问login页面,重定向到首页
|
||||
if (to.name === 'login' && isLogin) {
|
||||
next({ path: '/' })
|
||||
return
|
||||
}
|
||||
|
||||
// 判断路由有无进行初始化
|
||||
if (!routeStore.isInitAuthRoute && to.name !== 'login') {
|
||||
try {
|
||||
await routeStore.initAuthRoute()
|
||||
// 动态路由加载完回到根路由
|
||||
if (to.name === 'not-found') {
|
||||
// 等待权限路由加载好了,回到之前的路由,否则404
|
||||
next({
|
||||
path: to.fullPath,
|
||||
replace: true,
|
||||
query: to.query,
|
||||
hash: to.hash,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// 如果路由初始化失败(比如 401 错误),重定向到登录页
|
||||
local.remove('accessToken')
|
||||
const redirect = to.fullPath !== '/' ? to.fullPath : undefined
|
||||
next({ path: '/login', query: redirect ? { redirect } : undefined })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
// 使用函数式处理路由
|
||||
await processRoute(to, from, next, context)
|
||||
})
|
||||
|
||||
router.beforeResolve((to) => {
|
||||
// 设置菜单高亮
|
||||
routeStore.setActiveMenu(to.meta.activePath ?? to.fullPath)
|
||||
// 添加tabs
|
||||
tabStore.addTab(to)
|
||||
// 设置高亮标签;
|
||||
// 设置高亮标签
|
||||
tabStore.setCurrentTab(to.fullPath as string)
|
||||
})
|
||||
|
||||
router.afterEach((to) => {
|
||||
// 修改网页标题
|
||||
document.title = `${to.meta.title} - ${title}`
|
||||
document.title = `${to.meta.title || 'Nova Admin'} - ${title}`
|
||||
// 结束 loadingBar
|
||||
appStore.showProgress && window.$loadingBar?.finish()
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user