mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-11-19 23:42:31 +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">
|
<script setup lang="ts">
|
||||||
import AppMain from './AppMain.vue'
|
import { useAppStore } from '@/store'
|
||||||
import AppLoading from './components/common/AppLoading.vue'
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Suspense>
|
<pro-config-provider
|
||||||
<!-- 异步组件 -->
|
:prop-overrides="propOverrides"
|
||||||
<AppMain />
|
abstract
|
||||||
|
inline-theme-disabled
|
||||||
<!-- 加载状态 -->
|
:theme="appStore.colorMode === 'dark' ? darkTheme : null"
|
||||||
<template #fallback>
|
:locale="naiveLocale.locale"
|
||||||
<AppLoading />
|
:date-locale="naiveLocale.dateLocale"
|
||||||
</template>
|
:theme-overrides="appStore.theme"
|
||||||
</Suspense>
|
>
|
||||||
|
<naive-provider>
|
||||||
|
<router-view />
|
||||||
|
<Watermark :show-watermark="appStore.showWatermark" />
|
||||||
|
</naive-provider>
|
||||||
|
</pro-config-provider>
|
||||||
</template>
|
</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 App from './App.vue'
|
||||||
|
import AppLoading from './components/common/AppLoading.vue'
|
||||||
|
import { installPinia } from '@/store'
|
||||||
|
import { installRouter } from '@/router'
|
||||||
|
|
||||||
// 创建应用实例并挂载
|
async function bootstrap() {
|
||||||
const app = createApp(App)
|
// 显示加载动画
|
||||||
app.mount('#app')
|
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 { useAppStore, useRouteStore, useTabStore } from '@/store'
|
||||||
import { local } from '@/utils'
|
import { local } from '@/utils'
|
||||||
|
|
||||||
const title = import.meta.env.VITE_APP_NAME
|
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) {
|
export function setupRouterGuard(router: Router) {
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const routeStore = useRouteStore()
|
const routeStore = useRouteStore()
|
||||||
const tabStore = useTabStore()
|
const tabStore = useTabStore()
|
||||||
|
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
// 判断是否是外链,如果是直接打开网页并拦截跳转
|
|
||||||
if (to.meta.isLink) {
|
|
||||||
window.open(to.meta.linkPath)
|
|
||||||
next(false) // 取消当前导航
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 开始 loadingBar
|
// 开始 loadingBar
|
||||||
appStore.showProgress && window.$loadingBar?.start()
|
appStore.showProgress && window.$loadingBar?.start()
|
||||||
|
|
||||||
// 判断有无TOKEN,登录鉴权
|
// 创建路由上下文
|
||||||
const isLogin = Boolean(local.get('accessToken'))
|
const context: RouteContext = {
|
||||||
|
appStore,
|
||||||
// 处理根路由重定向
|
routeStore,
|
||||||
if (to.path === '/') {
|
tabStore,
|
||||||
if (isLogin) {
|
isLogin: Boolean(local.get('accessToken')),
|
||||||
// 已登录,重定向到首页
|
|
||||||
next({ path: import.meta.env.VITE_HOME_PATH, replace: true })
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// 未登录,重定向到登录页
|
|
||||||
next({ path: '/login', replace: true })
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果用户未登录,重定向到登录页
|
// 使用函数式处理路由
|
||||||
if (!isLogin) {
|
await processRoute(to, from, next, context)
|
||||||
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()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
router.beforeResolve((to) => {
|
router.beforeResolve((to) => {
|
||||||
// 设置菜单高亮
|
// 设置菜单高亮
|
||||||
routeStore.setActiveMenu(to.meta.activePath ?? to.fullPath)
|
routeStore.setActiveMenu(to.meta.activePath ?? to.fullPath)
|
||||||
// 添加tabs
|
// 添加tabs
|
||||||
tabStore.addTab(to)
|
tabStore.addTab(to)
|
||||||
// 设置高亮标签;
|
// 设置高亮标签
|
||||||
tabStore.setCurrentTab(to.fullPath as string)
|
tabStore.setCurrentTab(to.fullPath as string)
|
||||||
})
|
})
|
||||||
|
|
||||||
router.afterEach((to) => {
|
router.afterEach((to) => {
|
||||||
// 修改网页标题
|
// 修改网页标题
|
||||||
document.title = `${to.meta.title} - ${title}`
|
document.title = `${to.meta.title || 'Nova Admin'} - ${title}`
|
||||||
// 结束 loadingBar
|
// 结束 loadingBar
|
||||||
appStore.showProgress && window.$loadingBar?.finish()
|
appStore.showProgress && window.$loadingBar?.finish()
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user