From a0fbe28595e8db5e2edefa834a88fa929eaad27a Mon Sep 17 00:00:00 2001 From: "chen.home" <1147347984@qq.com> Date: Sun, 21 Aug 2022 20:00:29 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9tab=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改tab逻辑,fixed刷新跳转bug,add静态路由 --- .env | 2 + package.json | 1 + src/layouts/components/sider/Menu.vue | 1 + src/router/guard/permission.ts | 9 +++- src/router/index.ts | 21 +------- src/router/modules/dashboard.ts | 35 +++++++++++++ src/router/modules/index.ts | 3 ++ src/router/routes/index.ts | 71 +++++++++++++++++---------- src/store/index.ts | 2 + src/store/modules/auth.ts | 12 +++-- src/store/modules/route.ts | 17 ++++++- src/store/modules/tab.ts | 11 ++++- src/types/env.d.ts | 2 + 13 files changed, 132 insertions(+), 55 deletions(-) create mode 100644 src/router/modules/dashboard.ts create mode 100644 src/router/modules/index.ts diff --git a/.env b/.env index 8dff965..006aa0b 100644 --- a/.env +++ b/.env @@ -4,5 +4,7 @@ VITE_BASE_URL=/ VITE_APP_TITLE = Ench Admin # 路由模式 VITE_HASH_ROUTE = Y +# 权限路由模式: static | dynamic +VITE_AUTH_ROUTE_MODE=dynamic # 存储前缀 VITE_STORAGE_PREFIX = "" diff --git a/package.json b/package.json index b218b96..bbcf4d7 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "dependencies": { "axios": "^0.27.2", "pinia": "^2.0.17", + "pinia-plugin-persist": "^1.0.0", "vue": "^3.2.37", "vue-router": "^4.1.3" }, diff --git a/src/layouts/components/sider/Menu.vue b/src/layouts/components/sider/Menu.vue index b000506..eb9efe9 100644 --- a/src/layouts/components/sider/Menu.vue +++ b/src/layouts/components/sider/Menu.vue @@ -4,6 +4,7 @@ :collapsed-width="64" :collapsed-icon-size="24" :indent="20" + accordion :options="routesStore.menus" :value="routesStore.activeMenu" @update:value="handleClickMenu" diff --git a/src/router/guard/permission.ts b/src/router/guard/permission.ts index a26a92c..7f2f013 100644 --- a/src/router/guard/permission.ts +++ b/src/router/guard/permission.ts @@ -28,8 +28,13 @@ export async function createPermissionGuard( // 有登录但是没有路由,初始化路由、侧边菜单等 await routeStore.initAuthRoute(); // 动态路由加载完回到根路由 - next({ name: 'appRoot' }); - return false; + if (to.name === 'not-found') { + // 动态路由没有加载导致被not-found-page路由捕获,等待权限路由加载好了,回到之前的路由 + // 若路由是从根路由重定向过来的,重新回到根路由 + const path = to.redirectedFrom?.fullPath; + next({ path, replace: true, query: to.query, hash: to.hash }); + return false; + } } // 权限路由已经加载,仍然未找到,重定向到not-found // if (to.name === 'not-found-page') { diff --git a/src/router/index.ts b/src/router/index.ts index 73d9ac2..f9447f7 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,26 +1,7 @@ import type { App } from 'vue'; import { createRouter, createWebHistory, createWebHashHistory, RouteRecordRaw } from 'vue-router'; import { setupRouterGuard } from './guard'; -import { BasicLayout } from '@/layouts/index'; -import { constantRoutes } from './routes'; - -const routes: RouteRecordRaw[] = [ - { - path: '/', - name: 'root', - redirect: 'appRoot', - component: BasicLayout, - children: [...constantRoutes], - }, - { - path: '/login', - name: 'login', - component: () => import('@/views/login/index.vue'), // 注意这里要带上 文件后缀.vue - meta: { - title: '登录', - }, - }, -]; +import { routes } from './routes'; const { VITE_HASH_ROUTE = 'N', VITE_BASE_URL } = import.meta.env; export const router = createRouter({ diff --git a/src/router/modules/dashboard.ts b/src/router/modules/dashboard.ts new file mode 100644 index 0000000..98976b5 --- /dev/null +++ b/src/router/modules/dashboard.ts @@ -0,0 +1,35 @@ +import { RouteRecordRaw } from 'vue-router'; +import { BasicLayout } from '@/layouts/index'; +export const dashboard: RouteRecordRaw = { + path: '/dashboard', + name: 'dashboard', + redirect: '/dashboard/workbench', + component: BasicLayout, + meta: { + title: '分析页', + requiresAuth: true, + icon: 'icon-park-outline:analysis', + }, + children: [ + { + name: 'dashboard_workbench', + path: '/dashboard/workbench', + component: () => import('@/views/dashboard/workbench/index.vue'), + meta: { + title: '工作台', + requiresAuth: true, + icon: 'icon-park-outline:alarm', + }, + }, + { + name: 'dashboard_monitor', + path: '/dashboard/monitor', + component: () => import('@/views/dashboard/monitor/index.vue'), + meta: { + title: '监控页', + requiresAuth: true, + icon: 'icon-park-outline:anchor', + }, + }, + ], +}; diff --git a/src/router/modules/index.ts b/src/router/modules/index.ts new file mode 100644 index 0000000..9c46a8e --- /dev/null +++ b/src/router/modules/index.ts @@ -0,0 +1,3 @@ +import { dashboard } from './dashboard'; + +export const staticRoutes = [dashboard]; diff --git a/src/router/routes/index.ts b/src/router/routes/index.ts index ca44484..edbbdfb 100644 --- a/src/router/routes/index.ts +++ b/src/router/routes/index.ts @@ -1,34 +1,53 @@ +import { RouteRecordRaw } from 'vue-router'; +import { BasicLayout } from '@/layouts/index'; + /* 页面中的一些固定路由,错误页等 */ -export const constantRoutes = [ +export const routes: RouteRecordRaw[] = [ { - path: '/no-found', - name: 'not-found', - component: () => import('@/views/error/not-found/index.vue'), - meta: { - title: '找不到页面', - icon: 'icon-park-outline:ghost', - }, + path: '/', + name: 'root', + redirect: 'appRoot', + component: BasicLayout, + children: [ + { + path: '/no-found', + name: 'not-found', + component: () => import('@/views/error/not-found/index.vue'), + meta: { + title: '找不到页面', + icon: 'icon-park-outline:ghost', + }, + }, + { + path: '/no-permission', + name: 'no-permission', + component: () => import('@/views/error/not-permission/index.vue'), + meta: { + title: '用户无权限', + icon: 'icon-park-outline:error', + }, + }, + { + path: '/service-error', + name: 'service-error', + component: () => import('@/views/error/service-error/index.vue'), + meta: { + title: '服务器错误', + icon: 'icon-park-outline:close-wifi', + }, + }, + { + path: '/:pathMatch(.*)*', + redirect: '/no-found', + }, + ], }, { - path: '/no-permission', - name: 'no-permission', - component: () => import('@/views/error/not-permission/index.vue'), + path: '/login', + name: 'login', + component: () => import('@/views/login/index.vue'), // 注意这里要带上 文件后缀.vue meta: { - title: '用户无权限', - icon: 'icon-park-outline:error', + title: '登录', }, }, - { - path: '/service-error', - name: 'service-error', - component: () => import('@/views/error/service-error/index.vue'), - meta: { - title: '服务器错误', - icon: 'icon-park-outline:close-wifi', - }, - }, - { - path: '/:pathMatch(.*)*', - redirect: '/no-found', - }, ]; diff --git a/src/store/index.ts b/src/store/index.ts index 3d337d1..9f73a9f 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,8 +1,10 @@ import { createPinia } from 'pinia'; +import piniaPluginPersist from 'pinia-plugin-persist'; import type { App } from 'vue'; export function setupStore(app: App) { const store = createPinia(); + store.use(piniaPluginPersist); app.use(store); } export * from './modules'; diff --git a/src/store/modules/auth.ts b/src/store/modules/auth.ts index a38439b..8fd0954 100644 --- a/src/store/modules/auth.ts +++ b/src/store/modules/auth.ts @@ -41,7 +41,7 @@ export const useAuthStore = defineStore('auth-store', { this.loginLoading = true; const { data } = await fetchLogin({ userName, password }); // 处理登录信息 - await this.handleAfterLogin(data); // TODO 避免any + await this.handleAfterLogin(data); this.loginLoading = false; }, @@ -50,9 +50,6 @@ export const useAuthStore = defineStore('auth-store', { async handleAfterLogin(data: Auth.loginToken) { // 将token和userInfo保存下来 const catchSuccess = await this.catchUserInfo(data); - // 初始化侧边菜单 - // const { initAuthRoute } = useRouteStore(); - // await initAuthRoute(); // 登录写入信息成功 if (catchSuccess) { @@ -68,9 +65,14 @@ export const useAuthStore = defineStore('auth-store', { }); return; } - // 如果不成功则重置存储 this.resetAuthStore(); + // 登录失败提示 + window.$notification?.error({ + title: '登录失败!', + content: `验证失败,请检查账号密码`, + duration: 3000, + }); }, /* 缓存用户信息 */ diff --git a/src/store/modules/route.ts b/src/store/modules/route.ts index cf4a818..282c6d1 100644 --- a/src/store/modules/route.ts +++ b/src/store/modules/route.ts @@ -4,12 +4,14 @@ import { MenuOption } from 'naive-ui'; import { createDynamicRoutes } from '@/router/guard/dynamic'; import { router } from '@/router'; import { fetchUserRoutes } from '@/service'; +import { staticRoutes } from '@/router/modules'; interface RoutesStatus { isInitAuthRoute: boolean; menus: any; userRoutes: AppRoute.Route[]; activeMenu: string | null; + authRouteMode: ImportMetaEnv['VITE_AUTH_ROUTE_MODE']; } export const useRouteStore = defineStore('route-store', { state: (): RoutesStatus => { @@ -18,6 +20,7 @@ export const useRouteStore = defineStore('route-store', { isInitAuthRoute: false, menus: [], activeMenu: null, + authRouteMode: import.meta.env.VITE_AUTH_ROUTE_MODE, }; }, actions: { @@ -83,6 +86,14 @@ export const useRouteStore = defineStore('route-store', { // 插入路由表 router.addRoute(appRoutes); }, + /* 初始化静态路由 */ + async initStaticRoute() { + /* 将静态路由转换为侧边菜单 */ + staticRoutes.forEach((route) => { + // 插入路由表 + router.addRoute(route); + }); + }, //* 将返回的路由表渲染成侧边栏 */ transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]): MenuOption[] { return userRoutes @@ -111,7 +122,11 @@ export const useRouteStore = defineStore('route-store', { }, async initAuthRoute() { this.isInitAuthRoute = false; - await this.initDynamicRoute(); + if (this.authRouteMode === 'dynamic') { + await this.initDynamicRoute(); + } else { + await this.initStaticRoute(); + } this.isInitAuthRoute = true; }, }, diff --git a/src/store/modules/tab.ts b/src/store/modules/tab.ts index 1169e8d..d40f432 100644 --- a/src/store/modules/tab.ts +++ b/src/store/modules/tab.ts @@ -9,6 +9,7 @@ interface TabState { path: string; }[]; tabs: RouteLocationNormalized[]; + tabWhiteList: string[]; currentTab: string; } export const useTabStore = defineStore('tab-store', { @@ -22,6 +23,7 @@ export const useTabStore = defineStore('tab-store', { }, ], tabs: [], + tabWhiteList: ['not-found', 'no-permission', 'service-error', 'login'], currentTab: 'dashboard_workbench', }; }, @@ -42,6 +44,10 @@ export const useTabStore = defineStore('tab-store', { if (this.hasExistTab(route.name as string)) { return; } + // 如果在白名单内则不添加,错误页等 + if (this.tabWhiteList.includes(route.name as string)) { + return; + } this.tabs.push(route); }, closeTab(name: string) { @@ -56,7 +62,7 @@ export const useTabStore = defineStore('tab-store', { if (this.currentTab === name && !isLast) { // 跳转到后一个标签 routerPush(this.tabs[index + 1].path); - } else { + } else if (this.currentTab === name && isLast) { // 已经是最后一个了,就跳转前一个 routerPush(this.tabs[index - 1].path); } @@ -85,4 +91,7 @@ export const useTabStore = defineStore('tab-store', { }); }, }, + persist: { + enabled: true, + }, }); diff --git a/src/types/env.d.ts b/src/types/env.d.ts index c621816..6188e00 100644 --- a/src/types/env.d.ts +++ b/src/types/env.d.ts @@ -32,6 +32,8 @@ interface ImportMetaEnv { readonly VITE_COMPRESS_TYPE?: 'gzip' | 'brotliCompress' | 'deflate' | 'deflateRaw'; /** hash路由模式 */ readonly VITE_HASH_ROUTE?: 'Y' | 'N'; + /** 路由加载模式 */ + readonly VITE_AUTH_ROUTE_MODE?: 'static' | 'dynamic'; /** 本地存储前缀 */ readonly VITE_STORAGE_PREFIX?: string; /** 后端服务的环境类型 */