diff --git a/frontend/src/layouts/LayoutTransverse/index.vue b/frontend/src/layouts/LayoutTransverse/index.vue index 447ac20..81e5af4 100644 --- a/frontend/src/layouts/LayoutTransverse/index.vue +++ b/frontend/src/layouts/LayoutTransverse/index.vue @@ -61,9 +61,12 @@ const menuList = computed(() => authStore.showMenuListGet) const showMenuFlag = computed(() => authStore.showMenuFlagGet) const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string) -const handleClickMenu = (subItem: Menu.MenuOptions) => { - if (subItem.meta.isLink) return window.open(subItem.meta.isLink, '_blank') - router.push(subItem.path) +const handleClickMenu = async (subItem: Menu.MenuOptions) => { + if (subItem.meta?.isLink) { + window.open(subItem.meta.isLink, '_blank') + return + } + await router.push(subItem.path) } diff --git a/frontend/src/layouts/components/Footer/index.vue b/frontend/src/layouts/components/Footer/index.vue index 0f4e5fd..67acae0 100644 --- a/frontend/src/layouts/components/Footer/index.vue +++ b/frontend/src/layouts/components/Footer/index.vue @@ -28,8 +28,14 @@ import { computed } from 'vue' import { useAuthStore } from '@/stores/modules/auth' import { useModeStore } from '@/stores/modules/mode' // 引入模式 store +import { useTabsStore } from '@/stores/modules/tabs' +import { useRouter } from 'vue-router' +import { initDynamicRouter } from '@/routers/modules/dynamicRouter' + +const router = useRouter() const authStore = useAuthStore() const modeStore = useModeStore() +const tabsStore = useTabsStore() const title = computed(() => { return modeStore.currentMode === '' ? '选择模块' : modeStore.currentMode + '模块' @@ -64,7 +70,9 @@ const handelOpen = async (item: string, key: string) => { await authStore.setShowMenu() modeStore.setCurrentMode(item) // 将模式code存入 store // 强制刷新页面 - window.location.reload() + await tabsStore.closeMultipleTab() + await initDynamicRouter() + await router.push({ path: '/home/index' }) } diff --git a/frontend/src/routers/index.ts b/frontend/src/routers/index.ts index e0968a3..0a625a9 100644 --- a/frontend/src/routers/index.ts +++ b/frontend/src/routers/index.ts @@ -6,6 +6,9 @@ import { initDynamicRouter } from '@/routers/modules/dynamicRouter' import { staticRouter } from '@/routers/modules/staticRouter' import NProgress from '@/config/nprogress' +// 白名单转换为 Set 提高性能 +const WHITE_LIST_SET = new Set(ROUTER_WHITE_LIST) + const mode = import.meta.env.VITE_ROUTER_MODE const routerMode = { @@ -30,11 +33,9 @@ const routerMode = { * @param meta.isKeepAlive ==> 当前路由是否缓存 * */ const router = createRouter({ - history: routerMode[mode](), + history: routerMode[mode]?.() || createWebHashHistory(), // 默认 fallback 到 hash 模式 routes: [...staticRouter], - // 不区分路由大小写,非严格模式下提供了更宽松的路径匹配 strict: false, - // 页面刷新时,滚动条位置还原 scrollBehavior: () => ({ left: 0, top: 0 }) }) @@ -44,38 +45,52 @@ const router = createRouter({ router.beforeEach(async (to, from, next) => { const userStore = useUserStore() const authStore = useAuthStore() + // 1.NProgress 开始 NProgress.start() + // 2.动态设置标题 const title = import.meta.env.VITE_GLOB_APP_TITLE document.title = to.meta.title ? `${to.meta.title} - ${title}` : title - // 3.判断是访问登陆页,有 Token 就在当前页面,没有 Token 重置路由到登陆页 + // 3.判断是访问登陆页,有 Token 就留在当前页,没有 Token 重置路由到登陆页 if (to.path.toLocaleLowerCase() === LOGIN_URL) { - if (userStore.accessToken) return next(from.fullPath) + if (userStore.accessToken) { + // 已登录则不再回到登录页,直接跳过 + return next('/') + } resetRouter() return next() } // 4.判断访问页面是否在路由白名单地址(静态路由)中,如果存在直接放行 - if (ROUTER_WHITE_LIST.includes(to.path)) return next() + if (WHITE_LIST_SET.has(to.path)) return next() // 5.判断是否有 Token,没有重定向到 login 页面 if (!userStore.accessToken) return next({ path: LOGIN_URL, replace: true }) // 6.如果没有菜单列表,就重新请求菜单列表并添加动态路由 if (!authStore.authMenuListGet.length) { - await initDynamicRouter() - return next({ ...to, replace: true }) + try { + await initDynamicRouter() + return next({ ...to, replace: true }) + } catch (error) { + console.error('动态路由加载失败:', error) + // 清除 token 并跳转登录页 + await userStore.logout() + return next({ path: LOGIN_URL, replace: true }) + } } // 7.存储 routerName 做按钮权限筛选 await authStore.setRouteName(to.name as string) + // 8. 当前页面是否有激活信息,没有就刷新 const activateInfo = authStore.activateInfo if (!Object.keys(activateInfo).length) { await authStore.setActivateInfo() } + // 9.正常访问页面 next() }) @@ -96,7 +111,7 @@ export const resetRouter = () => { * */ router.onError(error => { NProgress.done() - //console.warn('路由错误', error.message) + console.warn('路由错误', error.message) }) /** diff --git a/frontend/src/routers/modules/dynamicRouter.ts b/frontend/src/routers/modules/dynamicRouter.ts index cb43751..b2cf26d 100644 --- a/frontend/src/routers/modules/dynamicRouter.ts +++ b/frontend/src/routers/modules/dynamicRouter.ts @@ -1,60 +1,117 @@ -import router from "@/routers/index"; -import { LOGIN_URL } from "@/config"; -import { RouteRecordRaw } from "vue-router"; -import { ElNotification } from "element-plus"; -import { useUserStore } from "@/stores/modules/user"; -import { useAuthStore } from "@/stores/modules/auth"; +import router from '@/routers' +import { LOGIN_URL } from '@/config' +import { type RouteRecordRaw } from 'vue-router' +import { ElNotification } from 'element-plus' +import { useUserStore } from '@/stores/modules/user' +import { useAuthStore } from '@/stores/modules/auth' // 引入 views 文件夹下所有 vue 文件 -const modules = import.meta.glob("@/views/**/*.vue"); +const modules = import.meta.glob('@/views/**/*.vue') + +let isInitializing = false + +/** + * 清除已有的动态路由 + */ +const clearDynamicRoutes = () => { + const routes = router.getRoutes() + routes.forEach(route => { + if (route.name && route.name !== 'layout' && route.name !== 'login') { + router.removeRoute(route.name) + } + }) +} + +/** + * 根据 component 路径查找对应的模块 + * @param path 组件路径 + */ +const resolveComponentModule = async (path: string) => { + // 规范化路径,去除首尾斜杠 + let normalizedPath = path.trim() + if (!normalizedPath.startsWith('/')) { + normalizedPath = '/' + normalizedPath + } + if (normalizedPath.endsWith('.vue')) { + normalizedPath = normalizedPath.slice(0, -4) + } + + const fullPath = `/src/views${normalizedPath}.vue` + return modules[fullPath] +} /** * @description 初始化动态路由 */ export const initDynamicRouter = async () => { - const userStore = useUserStore(); - const authStore = useAuthStore(); + if (isInitializing) return Promise.reject(new Error('Dynamic router initialization in progress')) - try { - // 1.获取菜单列表 && 按钮权限列表 - await authStore.getAuthMenuList(); - await authStore.getAuthButtonList(); + isInitializing = true + const userStore = useUserStore() + const authStore = useAuthStore() - // 2.判断当前用户有没有菜单权限 - if (!authStore.authMenuListGet.length) { - ElNotification({ - title: "无权限访问", - message: "当前账号无任何菜单权限,请联系系统管理员!", - type: "warning", - duration: 3000 - }); - userStore.setAccessToken(""); - userStore.setRefreshToken(""); - userStore.setExp(0) - router.replace(LOGIN_URL); - return Promise.reject("No permission"); + try { + // 1. 获取菜单列表 && 按钮权限列表 + await authStore.getAuthMenuList() + await authStore.getAuthButtonList() + + // 2. 判断当前用户有没有菜单权限 + if (!authStore.authMenuListGet.length) { + ElNotification({ + title: '无权限访问', + message: '当前账号无任何菜单权限,请联系系统管理员!', + type: 'warning', + duration: 3000 + }) + userStore.setAccessToken('') + userStore.setRefreshToken('') + userStore.setExp(0) + await router.replace(LOGIN_URL) + return Promise.reject('No permission') + } + + // 3. 清理之前的动态路由 + clearDynamicRoutes() + + // 4. 添加动态路由 + for (const item of authStore.flatMenuListGet) { + // 删除 children 避免冗余嵌套 + if (item.children) delete item.children + + // 处理组件映射 + if (item.component && typeof item.component === 'string') { + const moduleLoader = await resolveComponentModule(item.component) + if (moduleLoader) { + item.component = moduleLoader + } else { + console.warn(`未能找到组件: ${item.component}`) + continue + } + } + + // 类型守卫:确保满足 RouteRecordRaw 接口要求 + if ( + typeof item.path === 'string' && + (typeof item.component === 'function' || typeof item.redirect === 'string') + ) { + const routeItem = item as unknown as RouteRecordRaw + if (item.meta.isFull) { + router.addRoute(routeItem) + } else { + router.addRoute('layout', routeItem) + } + } else { + console.warn('Invalid route item:', item) + } + } + } catch (error) { + // 当按钮 || 菜单请求出错时,重定向到登陆页 + userStore.setAccessToken('') + userStore.setRefreshToken('') + userStore.setExp(0) + await router.replace(LOGIN_URL) + return Promise.reject(error) + } finally { + isInitializing = false } - - // 3.添加动态路由 - authStore.flatMenuListGet.forEach(item => { - item.children && delete item.children; - - if (item.component && typeof item.component == "string") { - item.component = modules["/src/views" + item.component + ".vue"]; - } - - if (item.meta.isFull) { - router.addRoute(item as unknown as RouteRecordRaw); - } else { - router.addRoute("layout", item as unknown as RouteRecordRaw); - } - }); - } catch (error) { - // 当按钮 || 菜单请求出错时,重定向到登陆页 - userStore.setAccessToken(""); - userStore.setRefreshToken(""); - userStore.setExp(0) - router.replace(LOGIN_URL); - return Promise.reject(error); - } -}; +} diff --git a/frontend/src/stores/interface/index.ts b/frontend/src/stores/interface/index.ts index 64b82e9..b548d7f 100644 --- a/frontend/src/stores/interface/index.ts +++ b/frontend/src/stores/interface/index.ts @@ -1,65 +1,69 @@ -export type LayoutType = 'vertical' | 'classic' | 'transverse' | 'columns'; +import { type Activate } from '@/api/activate/interface' -export type AssemblySizeType = 'large' | 'default' | 'small'; +export type LayoutType = 'vertical' | 'classic' | 'transverse' | 'columns' -export type LanguageType = 'zh' | 'en' | null; +export type AssemblySizeType = 'large' | 'default' | 'small' + +export type LanguageType = 'zh' | 'en' | null /* GlobalState */ export interface GlobalState { - layout: LayoutType; - assemblySize: AssemblySizeType; - language: LanguageType; - maximize: boolean; - primary: string; - isDark: boolean; - isGrey: boolean; - isWeak: boolean; - asideInverted: boolean; - headerInverted: boolean; - isCollapse: boolean; - accordion: boolean; - breadcrumb: boolean; - breadcrumbIcon: boolean; - tabs: boolean; - tabsIcon: boolean; - footer: boolean; + layout: LayoutType + assemblySize: AssemblySizeType + language: LanguageType + maximize: boolean + primary: string + isDark: boolean + isGrey: boolean + isWeak: boolean + asideInverted: boolean + headerInverted: boolean + isCollapse: boolean + accordion: boolean + breadcrumb: boolean + breadcrumbIcon: boolean + tabs: boolean + tabsIcon: boolean + footer: boolean } /* UserState */ export interface UserState { - accessToken: string; - refreshToken: string; - isRefreshToken: boolean; - userInfo: { id: string, name: string,loginName:string }; + accessToken: string + refreshToken: string + isRefreshToken: boolean + exp: number + userInfo: { id: string; name: string; loginName: string } } /* tabsMenuProps */ export interface TabsMenuProps { - icon: string; - title: string; - path: string; - name: string; - close: boolean; - isKeepAlive: boolean; - unshift?: boolean; + icon: string + title: string + path: string + name: string + close: boolean + isKeepAlive: boolean + unshift?: boolean } /* TabsState */ export interface TabsState { - tabsMenuList: TabsMenuProps[]; + tabsMenuList: TabsMenuProps[] } /* AuthState */ export interface AuthState { - routeName: string; + routeName: string authButtonList: { - [key: string]: string[]; - }; - authMenuList: Menu.MenuOptions[]; - showMenuFlag: boolean; + [key: string]: string[] + } + authMenuList: Menu.MenuOptions[] + showMenuFlag: boolean + activateInfo: Activate.ActivationCodePlaintext } /* KeepAliveState */ export interface KeepAliveState { - keepAliveName: string[]; + keepAliveName: string[] } diff --git a/frontend/src/stores/modules/auth.ts b/frontend/src/stores/modules/auth.ts index e438136..5e99f51 100644 --- a/frontend/src/stores/modules/auth.ts +++ b/frontend/src/stores/modules/auth.ts @@ -1,15 +1,13 @@ import { defineStore } from 'pinia' -import { AuthState } from '@/stores/interface' +import { type AuthState } from '@/stores/interface' import { getAuthButtonListApi, getAuthMenuListApi } from '@/api/user/login' import { getAllBreadcrumbList, getFlatMenuList, getShowMenuList } from '@/utils' -import { useRouter } from 'vue-router' import { AUTH_STORE_KEY } from '@/stores/constant' import { useModeStore } from '@/stores/modules/mode' import { getLicense } from '@/api/activate' import type { Activate } from '@/api/activate/interface' -export const useAuthStore = defineStore({ - id: AUTH_STORE_KEY, +export const useAuthStore = defineStore(AUTH_STORE_KEY, { state: (): AuthState => ({ // 按钮权限列表 authButtonList: {}, @@ -19,8 +17,7 @@ export const useAuthStore = defineStore({ routeName: '', //登录不显示菜单栏和导航栏,点击进入测试的时候显示 showMenuFlag: JSON.parse(localStorage.getItem('showMenuFlag') as string), - router: useRouter(), - activateInfo: {} + activateInfo: {} as Activate.ActivationCodePlaintext }), getters: { // 按钮权限列表 @@ -72,10 +69,9 @@ export const useAuthStore = defineStore({ localStorage.setItem('showMenuFlag', 'true') }, //更改模式 - async changeModel() { + changeModel() { this.showMenuFlag = false localStorage.removeItem('showMenuFlag') - this.router.push({ path: '/home/index' }) }, async setActivateInfo() { const license_result = await getLicense() diff --git a/frontend/src/stores/modules/check.ts b/frontend/src/stores/modules/check.ts index 4f30b1e..8a7b721 100644 --- a/frontend/src/stores/modules/check.ts +++ b/frontend/src/stores/modules/check.ts @@ -1,32 +1,37 @@ -import {defineStore} from "pinia"; -import {CHECK_STORE_KEY} from "@/stores/constant"; -import type {CheckData} from "@/api/check/interface"; -import type {Plan} from '@/api/plan/interface' -import {useAppSceneStore} from "@/stores/modules/mode"; +import { defineStore } from 'pinia' +import { CHECK_STORE_KEY } from '@/stores/constant' +import type { CheckData } from '@/api/check/interface' +import type { Plan } from '@/api/plan/interface' +import { useAppSceneStore } from '@/stores/modules/mode' export const useCheckStore = defineStore(CHECK_STORE_KEY, { state: () => ({ devices: [] as CheckData.Device[], plan: {} as Plan.ResPlan, - selectTestItems: {preTest: true, timeTest: false, channelsTest: false, test: true} as CheckData.SelectTestItem, + selectTestItems: { + preTest: true, + timeTest: false, + channelsTest: false, + test: true + } as CheckData.SelectTestItem, checkType: 1, // 0:手动检测 1:自动检测 reCheckType: 1, // 0:不合格项复检 1:全部复检 showDetailType: 0, // 0:数据查询 1:误差体系跟换 2:正式检测 temperature: 0, humidity: 0, - chnNumList: [],//连线数据 - nodesConnectable: true,//设置是能可以连线 + chnNumList: [] as string[], //连线数据 + nodesConnectable: true //设置是能可以连线 }), getters: {}, actions: { addDevices(device: CheckData.Device[]) { - this.devices.push(...device); + this.devices.push(...device) }, setPlan(plan: Plan.ResPlan) { this.plan = plan }, clearDevices() { - this.devices = []; + this.devices = [] }, initSelectTestItems() { const appSceneStore = useAppSceneStore() @@ -56,12 +61,11 @@ export const useCheckStore = defineStore(CHECK_STORE_KEY, { setHumidity(humidity: number) { this.humidity = humidity }, - setChnNum(chnNumList: string[]) { + setChnNum(chnNumList: string[]) { this.chnNumList = chnNumList }, - setNodesConnectable(nodesConnectable: boolean) { + setNodesConnectable(nodesConnectable: boolean) { this.nodesConnectable = nodesConnectable - }, - + } } -}); \ No newline at end of file +}) \ No newline at end of file diff --git a/frontend/src/stores/modules/dict.ts b/frontend/src/stores/modules/dict.ts index e37e779..a622883 100644 --- a/frontend/src/stores/modules/dict.ts +++ b/frontend/src/stores/modules/dict.ts @@ -5,11 +5,9 @@ import { DICT_STORE_KEY } from '@/stores/constant' // 模拟数据 //import dictData from '@/api/system/dictData' - -export const useDictStore = defineStore({ - id: DICT_STORE_KEY, +export const useDictStore = defineStore(DICT_STORE_KEY, { state: () => ({ - dictData: [] as Dict[], + dictData: [] as Dict[] }), getters: {}, actions: { @@ -27,7 +25,7 @@ export const useDictStore = defineStore({ // 初始化获取全部字典数据并缓存 async initDictData(initData: Dict[]) { this.dictData = initData - }, + } }, - persist: piniaPersistConfig(DICT_STORE_KEY), + persist: piniaPersistConfig(DICT_STORE_KEY) }) diff --git a/frontend/src/stores/modules/global.ts b/frontend/src/stores/modules/global.ts index 63d7c07..c744548 100644 --- a/frontend/src/stores/modules/global.ts +++ b/frontend/src/stores/modules/global.ts @@ -1,55 +1,53 @@ -import { defineStore } from "pinia"; -import { type GlobalState } from "@/stores/interface"; -import { DEFAULT_PRIMARY} from "@/config"; -import piniaPersistConfig from "@/stores/helper/persist"; -import {GLOBAL_STORE_KEY} from "@/stores/constant"; +import { defineStore } from 'pinia' +import { type GlobalState } from '@/stores/interface' +import { DEFAULT_PRIMARY } from '@/config' +import piniaPersistConfig from '@/stores/helper/persist' +import { GLOBAL_STORE_KEY } from '@/stores/constant' -export const useGlobalStore = defineStore({ - id: GLOBAL_STORE_KEY, - // 修改默认值之后,需清除 localStorage 数据 - state: (): GlobalState => ({ - // 布局模式 (纵向:vertical | 经典:classic | 横向:transverse | 分栏:columns) - layout: "transverse", - // element 组件大小 - assemblySize: "default", - // 当前系统语言 - language: null, - // 当前页面是否全屏 - maximize: false, - // 主题颜色 - primary: DEFAULT_PRIMARY, - // 深色模式 - isDark: false, - // 灰色模式 - isGrey: false, - // 色弱模式 - isWeak: false, - // 侧边栏反转 - asideInverted: false, - // 头部反转 - headerInverted: false, - // 折叠菜单 - isCollapse: false, - // 菜单手风琴 - accordion: false, - // 面包屑导航 - breadcrumb: true, - // 面包屑导航图标 - breadcrumbIcon: true, - // 标签页 - tabs: true, - // 标签页图标 - tabsIcon: true, - // 页脚 - footer: false - - }), - getters: {}, - actions: { - // Set GlobalState - setGlobalState(...args: ObjToKeyValArray) { - this.$patch({ [args[0]]: args[1] }); - } - }, - persist: piniaPersistConfig(GLOBAL_STORE_KEY) -}); +export const useGlobalStore = defineStore(GLOBAL_STORE_KEY, { + // 修改默认值之后,需清除 localStorage 数据 + state: (): GlobalState => ({ + // 布局模式 (纵向:vertical | 经典:classic | 横向:transverse | 分栏:columns) + layout: 'transverse', + // element 组件大小 + assemblySize: 'default', + // 当前系统语言 + language: null, + // 当前页面是否全屏 + maximize: false, + // 主题颜色 + primary: DEFAULT_PRIMARY, + // 深色模式 + isDark: false, + // 灰色模式 + isGrey: false, + // 色弱模式 + isWeak: false, + // 侧边栏反转 + asideInverted: false, + // 头部反转 + headerInverted: false, + // 折叠菜单 + isCollapse: false, + // 菜单手风琴 + accordion: false, + // 面包屑导航 + breadcrumb: true, + // 面包屑导航图标 + breadcrumbIcon: true, + // 标签页 + tabs: true, + // 标签页图标 + tabsIcon: true, + // 页脚 + footer: false + }), + getters: {}, + actions: { + // Set GlobalState + setGlobalState(...args: ObjToKeyValArray) { + this.$patch({ [args[0]]: args[1] }) + } + }, + persist: piniaPersistConfig(GLOBAL_STORE_KEY) +}) diff --git a/frontend/src/stores/modules/keepAlive.ts b/frontend/src/stores/modules/keepAlive.ts index 52eb1e4..7b0223c 100644 --- a/frontend/src/stores/modules/keepAlive.ts +++ b/frontend/src/stores/modules/keepAlive.ts @@ -1,24 +1,23 @@ -import {defineStore} from "pinia"; -import {KeepAliveState} from "@/stores/interface"; -import {KEEP_ALIVE_STORE_KEY} from '@/stores/constant' +import { defineStore } from 'pinia' +import { type KeepAliveState } from '@/stores/interface' +import { KEEP_ALIVE_STORE_KEY } from '@/stores/constant' -export const useKeepAliveStore = defineStore({ - id: KEEP_ALIVE_STORE_KEY, - state: (): KeepAliveState => ({ - keepAliveName: [] - }), - actions: { - // Add KeepAliveName - async addKeepAliveName(name: string) { - !this.keepAliveName.includes(name) && this.keepAliveName.push(name); - }, - // Remove KeepAliveName - async removeKeepAliveName(name: string) { - this.keepAliveName = this.keepAliveName.filter(item => item !== name); - }, - // Set KeepAliveName - async setKeepAliveName(keepAliveName: string[] = []) { - this.keepAliveName = keepAliveName; +export const useKeepAliveStore = defineStore(KEEP_ALIVE_STORE_KEY, { + state: (): KeepAliveState => ({ + keepAliveName: [] + }), + actions: { + // Add KeepAliveName + async addKeepAliveName(name: string) { + !this.keepAliveName.includes(name) && this.keepAliveName.push(name) + }, + // Remove KeepAliveName + async removeKeepAliveName(name: string) { + this.keepAliveName = this.keepAliveName.filter(item => item !== name) + }, + // Set KeepAliveName + async setKeepAliveName(keepAliveName: string[] = []) { + this.keepAliveName = keepAliveName + } } - } -}); +}) diff --git a/frontend/src/stores/modules/mode.ts b/frontend/src/stores/modules/mode.ts index 99caac0..8c14004 100644 --- a/frontend/src/stores/modules/mode.ts +++ b/frontend/src/stores/modules/mode.ts @@ -1,29 +1,17 @@ // src/stores/modules/mode.ts -import { defineStore } from 'pinia'; - -// export const useModeStore = defineStore('mode', { -// state: () => ({ -// currentMode: '' as string, -// }), -// actions: { -// setCurrentMode(modeName: string) { -// this.currentMode = modeName; -// }, -// }, -// }); - +import { defineStore } from 'pinia' export const useModeStore = defineStore('mode', { state: () => ({ - currentMode: localStorage.getItem('currentMode') || '' as string, + currentMode: localStorage.getItem('currentMode') || ('' as string) }), actions: { - setCurrentMode(modeName: string) { - this.currentMode = modeName; - localStorage.setItem('currentMode', modeName); // 保存到 localStorage - }, - }, - }); + setCurrentMode(modeName: string) { + this.currentMode = modeName + localStorage.setItem('currentMode', modeName) // 保存到 localStorage + } + } +}) export const useAppSceneStore = defineStore('scene', { state: () => ({ diff --git a/frontend/src/stores/modules/tabs.ts b/frontend/src/stores/modules/tabs.ts index 120b622..d9de523 100644 --- a/frontend/src/stores/modules/tabs.ts +++ b/frontend/src/stores/modules/tabs.ts @@ -1,81 +1,77 @@ -import router from "@/routers"; -import { defineStore } from "pinia"; -import { getUrlWithParams } from "@/utils"; -import { useKeepAliveStore } from "./keepAlive"; -import { TabsState, TabsMenuProps } from "@/stores/interface"; -import piniaPersistConfig from "@/stores/helper/persist"; -import {TABS_STORE_KEY} from "@/stores/constant"; +import router from '@/routers' +import { defineStore } from 'pinia' +import { getUrlWithParams } from '@/utils' +import { useKeepAliveStore } from './keepAlive' +import type { TabsMenuProps, TabsState } from '@/stores/interface' +import { TABS_STORE_KEY } from '@/stores/constant' -const keepAliveStore = useKeepAliveStore(); - -export const useTabsStore = defineStore({ - id: TABS_STORE_KEY, - state: (): TabsState => ({ - tabsMenuList: [] - }), - actions: { - // Add Tabs - async addTabs(tabItem: TabsMenuProps) { - - if (this.tabsMenuList.every(item => item.path !== tabItem.path)) { - if (tabItem?.unshift) { - this.tabsMenuList.unshift(tabItem); - }else{ - this.tabsMenuList.push(tabItem); +const keepAliveStore = useKeepAliveStore() +export const useTabsStore = defineStore(TABS_STORE_KEY, { + state: (): TabsState => ({ + tabsMenuList: [] + }), + actions: { + // Add Tabs + async addTabs(tabItem: TabsMenuProps) { + if (this.tabsMenuList.every(item => item.path !== tabItem.path)) { + if (tabItem?.unshift) { + this.tabsMenuList.unshift(tabItem) + } else { + this.tabsMenuList.push(tabItem) + } + } + if (!keepAliveStore.keepAliveName.includes(tabItem.name) && tabItem.isKeepAlive) { + await keepAliveStore.addKeepAliveName(tabItem.name) + } + }, + // Remove Tabs + async removeTabs(tabPath: string, isCurrent: boolean = true) { + if (isCurrent) { + this.tabsMenuList.forEach((item, index) => { + if (item.path !== tabPath) return + const nextTab = this.tabsMenuList[index + 1] || this.tabsMenuList[index - 1] + if (!nextTab) return + router.push(nextTab.path) + }) + } + this.tabsMenuList = this.tabsMenuList.filter(item => item.path !== tabPath) + // remove keepalive + const tabItem = this.tabsMenuList.find(item => item.path === tabPath) + tabItem?.isKeepAlive && (await keepAliveStore.removeKeepAliveName(tabItem.name)) + }, + // Close Tabs On Side + async closeTabsOnSide(path: string, type: 'left' | 'right') { + const currentIndex = this.tabsMenuList.findIndex(item => item.path === path) + if (currentIndex !== -1) { + const range = type === 'left' ? [0, currentIndex] : [currentIndex + 1, this.tabsMenuList.length] + this.tabsMenuList = this.tabsMenuList.filter((item, index) => { + return index < range[0] || index >= range[1] || !item.close + }) + } + // set keepalive + const KeepAliveList = this.tabsMenuList.filter(item => item.isKeepAlive) + await keepAliveStore.setKeepAliveName(KeepAliveList.map(item => item.name)) + }, + // Close MultipleTab + async closeMultipleTab(tabsMenuValue?: string) { + this.tabsMenuList = this.tabsMenuList.filter(item => { + return item.path === tabsMenuValue || !item.close + }) + // set keepalive + const KeepAliveList = this.tabsMenuList.filter(item => item.isKeepAlive) + await keepAliveStore.setKeepAliveName(KeepAliveList.map(item => item.name)) + }, + // Set Tabs + async setTabs(tabsMenuList: TabsMenuProps[]) { + this.tabsMenuList = tabsMenuList + }, + // Set Tabs Title + async setTabsTitle(title: string) { + this.tabsMenuList.forEach(item => { + if (item.path == getUrlWithParams()) item.title = title + }) } - } - if (!keepAliveStore.keepAliveName.includes(tabItem.name) && tabItem.isKeepAlive) { - keepAliveStore.addKeepAliveName(tabItem.name); - } - }, - // Remove Tabs - async removeTabs(tabPath: string, isCurrent: boolean = true) { - if (isCurrent) { - this.tabsMenuList.forEach((item, index) => { - if (item.path !== tabPath) return; - const nextTab = this.tabsMenuList[index + 1] || this.tabsMenuList[index - 1]; - if (!nextTab) return; - router.push(nextTab.path); - }); - } - this.tabsMenuList = this.tabsMenuList.filter(item => item.path !== tabPath); - // remove keepalive - const tabItem = this.tabsMenuList.find(item => item.path === tabPath); - tabItem?.isKeepAlive && keepAliveStore.removeKeepAliveName(tabItem.name); - }, - // Close Tabs On Side - async closeTabsOnSide(path: string, type: "left" | "right") { - const currentIndex = this.tabsMenuList.findIndex(item => item.path === path); - if (currentIndex !== -1) { - const range = type === "left" ? [0, currentIndex] : [currentIndex + 1, this.tabsMenuList.length]; - this.tabsMenuList = this.tabsMenuList.filter((item, index) => { - return index < range[0] || index >= range[1] || !item.close; - }); - } - // set keepalive - const KeepAliveList = this.tabsMenuList.filter(item => item.isKeepAlive); - keepAliveStore.setKeepAliveName(KeepAliveList.map(item => item.name)); - }, - // Close MultipleTab - async closeMultipleTab(tabsMenuValue?: string) { - this.tabsMenuList = this.tabsMenuList.filter(item => { - return item.path === tabsMenuValue || !item.close; - }); - // set keepalive - const KeepAliveList = this.tabsMenuList.filter(item => item.isKeepAlive); - keepAliveStore.setKeepAliveName(KeepAliveList.map(item => item.name)); - }, - // Set Tabs - async setTabs(tabsMenuList: TabsMenuProps[]) { - this.tabsMenuList = tabsMenuList; - }, - // Set Tabs Title - async setTabsTitle(title: string) { - this.tabsMenuList.forEach(item => { - if (item.path == getUrlWithParams()) item.title = title; - }); } - } - // persist: piniaPersistConfig(TABS_STORE_KEY) -}); + // persist: piniaPersistConfig(TABS_STORE_KEY) +}) diff --git a/frontend/src/stores/modules/user.ts b/frontend/src/stores/modules/user.ts index d32efe0..b1d7b8a 100644 --- a/frontend/src/stores/modules/user.ts +++ b/frontend/src/stores/modules/user.ts @@ -1,36 +1,57 @@ -import {defineStore} from "pinia"; -import {UserState} from "@/stores/interface"; -import piniaPersistConfig from "@/stores/helper/persist"; -import {USER_STORE_KEY} from "@/stores/constant"; +import { defineStore } from 'pinia' +import { type UserState } from '@/stores/interface' +import piniaPersistConfig from '@/stores/helper/persist' +import { USER_STORE_KEY } from '@/stores/constant' +import { logoutApi } from '@/api/user/login' +import { useAuthStore } from '@/stores/modules/auth' +import { useAppSceneStore, useModeStore } from '@/stores/modules/mode' +import { useDictStore } from '@/stores/modules/dict' -export const useUserStore = defineStore({ - id: USER_STORE_KEY, - state: (): UserState => ({ - accessToken: "", - refreshToken: "", - isRefreshToken:false, - exp: Number(0), - userInfo: {id:"", name: "" ,loginName:""}, - }), - getters: {}, - actions: { - // Set Token - setAccessToken(accessToken: string) { - this.accessToken = accessToken; +export const useUserStore = defineStore(USER_STORE_KEY, { + state: (): UserState => ({ + accessToken: '', + refreshToken: '', + isRefreshToken: false, + exp: Number(0), + userInfo: { id: '', name: '', loginName: '' } + }), + getters: {}, + actions: { + // Set Token + setAccessToken(accessToken: string) { + this.accessToken = accessToken + }, + setRefreshToken(refreshToken: string) { + this.refreshToken = refreshToken + }, + setIsRefreshToken(isRefreshToken: boolean) { + this.isRefreshToken = isRefreshToken + }, + // Set setUserInfo + setUserInfo(userInfo: UserState['userInfo']) { + this.userInfo = userInfo + }, + setExp(exp: number) { + this.exp = exp + }, + async logout() { + const authStore = useAuthStore() + const modeStore = useModeStore() + const appSceneStore = useAppSceneStore() + const dictStore = useDictStore() + // 1.执行退出登录接口 + await logoutApi() + // 2.清除 Token + this.setAccessToken('') + this.setRefreshToken('') + this.setExp(0) + this.setUserInfo({ id: '', name: '', loginName: '' }) + this.setIsRefreshToken(false) + dictStore.setDictData([]) + modeStore.setCurrentMode('') + appSceneStore.setCurrentMode('') + await authStore.resetAuthStore() + } }, - setRefreshToken(refreshToken: string) { - this.refreshToken = refreshToken; - }, - setIsRefreshToken(isRefreshToken: boolean) { - this.isRefreshToken = isRefreshToken; - }, - // Set setUserInfo - setUserInfo(userInfo: UserState["userInfo"]) { - this.userInfo = userInfo; - }, - setExp(exp: number) { - this.exp = exp; - } - }, - persist: piniaPersistConfig(USER_STORE_KEY), -}); + persist: piniaPersistConfig(USER_STORE_KEY) +}) diff --git a/frontend/src/views/home/tabs/model.vue b/frontend/src/views/home/tabs/model.vue index 6b8dcf5..527f8a3 100644 --- a/frontend/src/views/home/tabs/model.vue +++ b/frontend/src/views/home/tabs/model.vue @@ -30,13 +30,15 @@ import { useAuthStore } from '@/stores/modules/auth' import { useAppSceneStore, useModeStore } from '@/stores/modules/mode' // 引入模式 store import { getCurrentScene } from '@/api/user/login' +import { initDynamicRouter } from '@/routers/modules/dynamicRouter' +import { useTabsStore } from '@/stores/modules/tabs' const authStore = useAuthStore() const modeStore = useModeStore() // 使用模式 store const AppSceneStore = useAppSceneStore() const activateInfo = authStore.activateInfo const isActivateOpen = import.meta.env.VITE_ACTIVATE_OPEN - +const tabsStore = useTabsStore() const modeList = [ { name: '模拟式模块', @@ -69,10 +71,10 @@ const handelOpen = async (item: any) => { const { data: scene } = await getCurrentScene() // 获取当前场景 AppSceneStore.setCurrentMode(scene + '') //0:省级平台,1:设备出厂,2:研发自测 await authStore.setShowMenu() - await authStore.getAuthMenuList() + await tabsStore.closeMultipleTab() + await initDynamicRouter() return } -const handleSelect = (key: string, keyPath: string[]) => {} onMounted(() => {})