UPDATE: 1、优化动态加载路由;2、修改模式切换,菜单没刷新等问题

This commit is contained in:
贾同学
2025-10-27 16:27:12 +08:00
parent 9376d702f3
commit 425b54bc44
17 changed files with 507 additions and 466 deletions

View File

@@ -61,9 +61,12 @@ const menuList = computed(() => authStore.showMenuListGet)
const showMenuFlag = computed(() => authStore.showMenuFlagGet) const showMenuFlag = computed(() => authStore.showMenuFlagGet)
const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string) const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string)
const handleClickMenu = (subItem: Menu.MenuOptions) => { const handleClickMenu = async (subItem: Menu.MenuOptions) => {
if (subItem.meta.isLink) return window.open(subItem.meta.isLink, '_blank') if (subItem.meta?.isLink) {
router.push(subItem.path) window.open(subItem.meta.isLink, '_blank')
return
}
await router.push(subItem.path)
} }
</script> </script>

View File

@@ -28,8 +28,14 @@
import { computed } from 'vue' import { computed } from 'vue'
import { useAuthStore } from '@/stores/modules/auth' import { useAuthStore } from '@/stores/modules/auth'
import { useModeStore } from '@/stores/modules/mode' // 引入模式 store 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 authStore = useAuthStore()
const modeStore = useModeStore() const modeStore = useModeStore()
const tabsStore = useTabsStore()
const title = computed(() => { const title = computed(() => {
return modeStore.currentMode === '' ? '选择模块' : modeStore.currentMode + '模块' return modeStore.currentMode === '' ? '选择模块' : modeStore.currentMode + '模块'
@@ -64,7 +70,9 @@ const handelOpen = async (item: string, key: string) => {
await authStore.setShowMenu() await authStore.setShowMenu()
modeStore.setCurrentMode(item) // 将模式code存入 store modeStore.setCurrentMode(item) // 将模式code存入 store
// 强制刷新页面 // 强制刷新页面
window.location.reload() await tabsStore.closeMultipleTab()
await initDynamicRouter()
await router.push({ path: '/home/index' })
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -1,12 +1,4 @@
<template> <template>
<!-- <div class="userInfo">-->
<!-- <div class="icon">-->
<!-- <Avatar/>-->
<!-- </div>-->
<!-- <div class="username">-->
<!-- {{ username }}-->
<!-- </div>-->
<!-- </div>-->
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<div class="userInfo"> <div class="userInfo">
<div class="icon"> <div class="icon">
@@ -80,7 +72,6 @@
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { LOGIN_URL } from '@/config' import { LOGIN_URL } from '@/config'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { logoutApi } from '@/api/user/login'
import { useUserStore } from '@/stores/modules/user' import { useUserStore } from '@/stores/modules/user'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import InfoDialog from './InfoDialog.vue' import InfoDialog from './InfoDialog.vue'
@@ -90,10 +81,9 @@ import VersionDialog from '@/views/system/versionRegister/index.vue'
import { Avatar, Sunny, Switch, Tools } from '@element-plus/icons-vue' import { Avatar, Sunny, Switch, Tools } from '@element-plus/icons-vue'
import { useAuthStore } from '@/stores/modules/auth' import { useAuthStore } from '@/stores/modules/auth'
import { useDictStore } from '@/stores/modules/dict' import { useDictStore } from '@/stores/modules/dict'
import { useAppSceneStore, useModeStore } from '@/stores/modules/mode' import { useAppSceneStore } from '@/stores/modules/mode'
import { useTheme } from '@/hooks/useTheme'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { updateScene } from '@/api/system/base/index' import { updateScene } from '@/api/system/base'
const userStore = useUserStore() const userStore = useUserStore()
const dictStore = useDictStore() const dictStore = useDictStore()
@@ -101,10 +91,6 @@ const username = computed(() => userStore.userInfo.name)
const router = useRouter() const router = useRouter()
const authStore = useAuthStore() const authStore = useAuthStore()
const modeStore = useModeStore()
const AppSceneStore = useAppSceneStore()
const { changePrimary } = useTheme()
// 初始化 i18n // 初始化 i18n
const { t } = useI18n() // 使用 t 方法替代 $t const { t } = useI18n() // 使用 t 方法替代 $t
@@ -116,21 +102,8 @@ const logout = () => {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(async () => { }).then(async () => {
// 1.执行退出登录接口
await logoutApi()
// 2.清除 Token
userStore.setAccessToken('')
userStore.setRefreshToken('')
userStore.setExp(0)
userStore.setUserInfo({ id: '', name: '' })
userStore.setIsRefreshToken(false)
dictStore.setDictData([])
modeStore.setCurrentMode('')
AppSceneStore.setCurrentMode('')
// 3.重定向到登陆页
ElMessage.success('退出登录成功!') ElMessage.success('退出登录成功!')
//重置菜单/导航栏权限 await userStore.logout()
await authStore.resetAuthStore()
await router.push(LOGIN_URL) await router.push(LOGIN_URL)
}) })
} }
@@ -157,8 +130,9 @@ const changeScene = async (value: string) => {
} }
//模式切换 //模式切换
const changeMode = () => { const changeMode = async () => {
authStore.changeModel() authStore.changeModel()
await router.push('/home/index')
} }
</script> </script>

View File

@@ -52,19 +52,10 @@ import { ElMessage, type FormItemRule } from 'element-plus'
import { updatePassWord } from '@/api/user/user' import { updatePassWord } from '@/api/user/user'
import { type User } from '@/api/user/interface/user' import { type User } from '@/api/user/interface/user'
import { useUserStore } from '@/stores/modules/user' import { useUserStore } from '@/stores/modules/user'
import { useAuthStore } from '@/stores/modules/auth'
import { useDictStore } from '@/stores/modules/dict'
import { logoutApi } from '@/api/user/login'
import { LOGIN_URL } from '@/config' import { LOGIN_URL } from '@/config'
import { useAppSceneStore, useModeStore } from '@/stores/modules/mode'
import { UserState } from '@/stores/interface'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const userStore = useUserStore() const userStore = useUserStore()
const dictStore = useDictStore()
const authStore = useAuthStore()
const modeStore = useModeStore()
const AppSceneStore = useAppSceneStore()
const router = useRouter() const router = useRouter()
// 定义弹出组件元信息 // 定义弹出组件元信息
const dialogFormRef = ref() const dialogFormRef = ref()
@@ -134,19 +125,7 @@ const save = () => {
await updatePassWord(formContent.value) await updatePassWord(formContent.value)
ElMessage.success('修改密码成功,请重新登录!') ElMessage.success('修改密码成功,请重新登录!')
setTimeout(async () => { setTimeout(async () => {
// 1.执行退出登录接口 await userStore.logout()
await logoutApi()
// 2.清除 Token
userStore.setAccessToken('')
userStore.setRefreshToken('')
userStore.setExp(0)
userStore.setUserInfo({ id: '', name: '' } as UserState['userInfo'])
userStore.setIsRefreshToken(false)
dictStore.setDictData([])
modeStore.setCurrentMode('')
AppSceneStore.setCurrentMode('')
//重置菜单/导航栏权限
await authStore.resetAuthStore()
await router.push(LOGIN_URL) await router.push(LOGIN_URL)
}, 2000) }, 2000)
} }

View File

@@ -1,98 +1,97 @@
<template> <template>
<template v-for="subItem in menuList" :key="subItem.path"> <template v-for="subItem in menuList" :key="subItem.path">
<el-sub-menu v-if="subItem.children?.length" :index="subItem.path"> <el-sub-menu v-if="subItem.children?.length" :index="subItem.path">
<template #title> <template #title>
<el-icon> <el-icon>
<component :is="subItem.meta.icon"></component> <component :is="subItem.meta.icon"></component>
</el-icon> </el-icon>
<span class="sle">{{ subItem.meta.title }}</span> <span class="sle">{{ subItem.meta.title }}</span>
</template> </template>
<SubMenu :menu-list="subItem.children" /> <SubMenu :menu-list="subItem.children" />
</el-sub-menu> </el-sub-menu>
<el-menu-item v-else :index="subItem.path" @click="handleClickMenu(subItem)"> <el-menu-item v-else :index="subItem.path" @click="handleClickMenu(subItem)">
<el-icon> <el-icon>
<component :is="subItem.meta.icon"></component> <component :is="subItem.meta.icon"></component>
</el-icon> </el-icon>
<template #title> <template #title>
<span class="sle">{{ subItem.meta.title }}</span> <span class="sle">{{ subItem.meta.title }}</span>
</template> </template>
</el-menu-item> </el-menu-item>
</template> </template>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeMount } from "vue"; import { useRouter } from 'vue-router'
import { useRouter } from "vue-router";
defineProps<{ menuList: Menu.MenuOptions[] }>();
const router = useRouter();
const handleClickMenu = (subItem: Menu.MenuOptions) => {
//console.log('1456----------------',subItem);
if (subItem.meta.isLink) return window.open(subItem.meta.isLink, "_blank");
router.push(subItem.path);
};
defineProps<{ menuList: Menu.MenuOptions[] }>()
const router = useRouter()
const handleClickMenu = async (subItem: Menu.MenuOptions) => {
if (subItem.meta?.isLink) {
window.open(subItem.meta.isLink, '_blank')
return
}
await router.push(subItem.path)
}
</script> </script>
<style lang="scss"> <style lang="scss">
.el-sub-menu .el-sub-menu__title:hover { .el-sub-menu .el-sub-menu__title:hover {
// color: var(--el-menu-hover-text-color) !important; // color: var(--el-menu-hover-text-color) !important;
// background-color: transparent !important; // background-color: transparent !important;
color: #fff !important;//一级导航文字选中颜色 color: #fff !important; //一级导航文字选中颜色
//background-color: #5274a5 !important; //一级导航选中背景色 //background-color: #5274a5 !important; //一级导航选中背景色
background-color: var(--el-color-primary-light-3) !important;
background-color: var(--el-color-primary-light-3) !important;
} }
.el-menu--collapse { .el-menu--collapse {
.is-active { .is-active {
.el-sub-menu__title { .el-sub-menu__title {
color: #ffffff !important; color: #ffffff !important;
// background-color: var(--el-color-primary) !important; // background-color: var(--el-color-primary) !important;
//background-color: #5274a5 !important; //background-color: #5274a5 !important;
background-color: var(--el-color-primary-light-3) !important; background-color: var(--el-color-primary-light-3) !important;
border-bottom: 0 !important; border-bottom: 0 !important;
}
} }
}
} }
.el-menu-item { .el-menu-item {
&:hover { &:hover {
color: var(--el-menu-hover-text-color); color: var(--el-menu-hover-text-color);
} }
&.is-active { &.is-active {
// color: var(--el-menu-active-color) !important; // color: var(--el-menu-active-color) !important;
// background-color: var(--el-menu-active-bg-color) !important; // background-color: var(--el-menu-active-bg-color) !important;
color: #fff !important;//一级导航文字选中颜色 color: #fff !important; //一级导航文字选中颜色
//background-color: #5274a5 !important; //一级导航选中背景色 //background-color: #5274a5 !important; //一级导航选中背景色
background-color: var(--el-color-primary-light-3) !important; background-color: var(--el-color-primary-light-3) !important;
&::before { &::before {
position: absolute; position: absolute;
top: 0; top: 0;
bottom: 0; bottom: 0;
width: 4px; width: 4px;
content: ""; content: '';
background-color: var(--el-color-primary); background-color: var(--el-color-primary);
}
} }
}
} }
.vertical, .vertical,
.classic, .classic,
.transverse { .transverse {
.el-menu-item { .el-menu-item {
&.is-active { &.is-active {
&::before { &::before {
left: 0; left: 0;
} }
}
} }
}
} }
.columns { .columns {
.el-menu-item { .el-menu-item {
&.is-active { &.is-active {
&::before { &::before {
right: 0; right: 0;
} }
}
} }
}
} }
</style> </style>

View File

@@ -6,6 +6,9 @@ import { initDynamicRouter } from '@/routers/modules/dynamicRouter'
import { staticRouter } from '@/routers/modules/staticRouter' import { staticRouter } from '@/routers/modules/staticRouter'
import NProgress from '@/config/nprogress' import NProgress from '@/config/nprogress'
// 白名单转换为 Set 提高性能
const WHITE_LIST_SET = new Set(ROUTER_WHITE_LIST)
const mode = import.meta.env.VITE_ROUTER_MODE const mode = import.meta.env.VITE_ROUTER_MODE
const routerMode = { const routerMode = {
@@ -30,11 +33,9 @@ const routerMode = {
* @param meta.isKeepAlive ==> 当前路由是否缓存 * @param meta.isKeepAlive ==> 当前路由是否缓存
* */ * */
const router = createRouter({ const router = createRouter({
history: routerMode[mode](), history: routerMode[mode]?.() || createWebHashHistory(), // 默认 fallback 到 hash 模式
routes: [...staticRouter], routes: [...staticRouter],
// 不区分路由大小写,非严格模式下提供了更宽松的路径匹配
strict: false, strict: false,
// 页面刷新时,滚动条位置还原
scrollBehavior: () => ({ left: 0, top: 0 }) scrollBehavior: () => ({ left: 0, top: 0 })
}) })
@@ -44,38 +45,52 @@ const router = createRouter({
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
const userStore = useUserStore() const userStore = useUserStore()
const authStore = useAuthStore() const authStore = useAuthStore()
// 1.NProgress 开始 // 1.NProgress 开始
NProgress.start() NProgress.start()
// 2.动态设置标题 // 2.动态设置标题
const title = import.meta.env.VITE_GLOB_APP_TITLE const title = import.meta.env.VITE_GLOB_APP_TITLE
document.title = to.meta.title ? `${to.meta.title} - ${title}` : title document.title = to.meta.title ? `${to.meta.title} - ${title}` : title
// 3.判断是访问登陆页,有 Token 就在当前页,没有 Token 重置路由到登陆页 // 3.判断是访问登陆页,有 Token 就在当前页,没有 Token 重置路由到登陆页
if (to.path.toLocaleLowerCase() === LOGIN_URL) { if (to.path.toLocaleLowerCase() === LOGIN_URL) {
if (userStore.accessToken) return next(from.fullPath) if (userStore.accessToken) {
// 已登录则不再回到登录页,直接跳过
return next('/')
}
resetRouter() resetRouter()
return next() return next()
} }
// 4.判断访问页面是否在路由白名单地址(静态路由)中,如果存在直接放行 // 4.判断访问页面是否在路由白名单地址(静态路由)中,如果存在直接放行
if (ROUTER_WHITE_LIST.includes(to.path)) return next() if (WHITE_LIST_SET.has(to.path)) return next()
// 5.判断是否有 Token没有重定向到 login 页面 // 5.判断是否有 Token没有重定向到 login 页面
if (!userStore.accessToken) return next({ path: LOGIN_URL, replace: true }) if (!userStore.accessToken) return next({ path: LOGIN_URL, replace: true })
// 6.如果没有菜单列表,就重新请求菜单列表并添加动态路由 // 6.如果没有菜单列表,就重新请求菜单列表并添加动态路由
if (!authStore.authMenuListGet.length) { if (!authStore.authMenuListGet.length) {
await initDynamicRouter() try {
return next({ ...to, replace: true }) 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 做按钮权限筛选 // 7.存储 routerName 做按钮权限筛选
await authStore.setRouteName(to.name as string) await authStore.setRouteName(to.name as string)
// 8. 当前页面是否有激活信息,没有就刷新 // 8. 当前页面是否有激活信息,没有就刷新
const activateInfo = authStore.activateInfo const activateInfo = authStore.activateInfo
if (!Object.keys(activateInfo).length) { if (!Object.keys(activateInfo).length) {
await authStore.setActivateInfo() await authStore.setActivateInfo()
} }
// 9.正常访问页面 // 9.正常访问页面
next() next()
}) })
@@ -96,7 +111,7 @@ export const resetRouter = () => {
* */ * */
router.onError(error => { router.onError(error => {
NProgress.done() NProgress.done()
//console.warn('路由错误', error.message) console.warn('路由错误', error.message)
}) })
/** /**

View File

@@ -1,60 +1,117 @@
import router from "@/routers/index"; import router from '@/routers'
import { LOGIN_URL } from "@/config"; import { LOGIN_URL } from '@/config'
import { RouteRecordRaw } from "vue-router"; import { type RouteRecordRaw } from 'vue-router'
import { ElNotification } from "element-plus"; import { ElNotification } from 'element-plus'
import { useUserStore } from "@/stores/modules/user"; import { useUserStore } from '@/stores/modules/user'
import { useAuthStore } from "@/stores/modules/auth"; import { useAuthStore } from '@/stores/modules/auth'
// 引入 views 文件夹下所有 vue 文件 // 引入 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 初始化动态路由 * @description 初始化动态路由
*/ */
export const initDynamicRouter = async () => { export const initDynamicRouter = async () => {
const userStore = useUserStore(); if (isInitializing) return Promise.reject(new Error('Dynamic router initialization in progress'))
const authStore = useAuthStore();
try { isInitializing = true
// 1.获取菜单列表 && 按钮权限列表 const userStore = useUserStore()
await authStore.getAuthMenuList(); const authStore = useAuthStore()
await authStore.getAuthButtonList();
// 2.判断当前用户有没有菜单权限 try {
if (!authStore.authMenuListGet.length) { // 1. 获取菜单列表 && 按钮权限列表
ElNotification({ await authStore.getAuthMenuList()
title: "无权限访问", await authStore.getAuthButtonList()
message: "当前账号无任何菜单权限,请联系系统管理员!",
type: "warning", // 2. 判断当前用户有没有菜单权限
duration: 3000 if (!authStore.authMenuListGet.length) {
}); ElNotification({
userStore.setAccessToken(""); title: '无权限访问',
userStore.setRefreshToken(""); message: '当前账号无任何菜单权限,请联系系统管理员!',
userStore.setExp(0) type: 'warning',
router.replace(LOGIN_URL); duration: 3000
return Promise.reject("No permission"); })
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);
}
};

View File

@@ -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 */ /* GlobalState */
export interface GlobalState { export interface GlobalState {
layout: LayoutType; layout: LayoutType
assemblySize: AssemblySizeType; assemblySize: AssemblySizeType
language: LanguageType; language: LanguageType
maximize: boolean; maximize: boolean
primary: string; primary: string
isDark: boolean; isDark: boolean
isGrey: boolean; isGrey: boolean
isWeak: boolean; isWeak: boolean
asideInverted: boolean; asideInverted: boolean
headerInverted: boolean; headerInverted: boolean
isCollapse: boolean; isCollapse: boolean
accordion: boolean; accordion: boolean
breadcrumb: boolean; breadcrumb: boolean
breadcrumbIcon: boolean; breadcrumbIcon: boolean
tabs: boolean; tabs: boolean
tabsIcon: boolean; tabsIcon: boolean
footer: boolean; footer: boolean
} }
/* UserState */ /* UserState */
export interface UserState { export interface UserState {
accessToken: string; accessToken: string
refreshToken: string; refreshToken: string
isRefreshToken: boolean; isRefreshToken: boolean
userInfo: { id: string, name: string,loginName:string }; exp: number
userInfo: { id: string; name: string; loginName: string }
} }
/* tabsMenuProps */ /* tabsMenuProps */
export interface TabsMenuProps { export interface TabsMenuProps {
icon: string; icon: string
title: string; title: string
path: string; path: string
name: string; name: string
close: boolean; close: boolean
isKeepAlive: boolean; isKeepAlive: boolean
unshift?: boolean; unshift?: boolean
} }
/* TabsState */ /* TabsState */
export interface TabsState { export interface TabsState {
tabsMenuList: TabsMenuProps[]; tabsMenuList: TabsMenuProps[]
} }
/* AuthState */ /* AuthState */
export interface AuthState { export interface AuthState {
routeName: string; routeName: string
authButtonList: { authButtonList: {
[key: string]: string[]; [key: string]: string[]
}; }
authMenuList: Menu.MenuOptions[]; authMenuList: Menu.MenuOptions[]
showMenuFlag: boolean; showMenuFlag: boolean
activateInfo: Activate.ActivationCodePlaintext
} }
/* KeepAliveState */ /* KeepAliveState */
export interface KeepAliveState { export interface KeepAliveState {
keepAliveName: string[]; keepAliveName: string[]
} }

View File

@@ -1,15 +1,13 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { AuthState } from '@/stores/interface' import { type AuthState } from '@/stores/interface'
import { getAuthButtonListApi, getAuthMenuListApi } from '@/api/user/login' import { getAuthButtonListApi, getAuthMenuListApi } from '@/api/user/login'
import { getAllBreadcrumbList, getFlatMenuList, getShowMenuList } from '@/utils' import { getAllBreadcrumbList, getFlatMenuList, getShowMenuList } from '@/utils'
import { useRouter } from 'vue-router'
import { AUTH_STORE_KEY } from '@/stores/constant' import { AUTH_STORE_KEY } from '@/stores/constant'
import { useModeStore } from '@/stores/modules/mode' import { useModeStore } from '@/stores/modules/mode'
import { getLicense } from '@/api/activate' import { getLicense } from '@/api/activate'
import type { Activate } from '@/api/activate/interface' import type { Activate } from '@/api/activate/interface'
export const useAuthStore = defineStore({ export const useAuthStore = defineStore(AUTH_STORE_KEY, {
id: AUTH_STORE_KEY,
state: (): AuthState => ({ state: (): AuthState => ({
// 按钮权限列表 // 按钮权限列表
authButtonList: {}, authButtonList: {},
@@ -19,8 +17,7 @@ export const useAuthStore = defineStore({
routeName: '', routeName: '',
//登录不显示菜单栏和导航栏,点击进入测试的时候显示 //登录不显示菜单栏和导航栏,点击进入测试的时候显示
showMenuFlag: JSON.parse(localStorage.getItem('showMenuFlag') as string), showMenuFlag: JSON.parse(localStorage.getItem('showMenuFlag') as string),
router: useRouter(), activateInfo: {} as Activate.ActivationCodePlaintext
activateInfo: {}
}), }),
getters: { getters: {
// 按钮权限列表 // 按钮权限列表
@@ -72,10 +69,9 @@ export const useAuthStore = defineStore({
localStorage.setItem('showMenuFlag', 'true') localStorage.setItem('showMenuFlag', 'true')
}, },
//更改模式 //更改模式
async changeModel() { changeModel() {
this.showMenuFlag = false this.showMenuFlag = false
localStorage.removeItem('showMenuFlag') localStorage.removeItem('showMenuFlag')
this.router.push({ path: '/home/index' })
}, },
async setActivateInfo() { async setActivateInfo() {
const license_result = await getLicense() const license_result = await getLicense()

View File

@@ -1,32 +1,37 @@
import {defineStore} from "pinia"; import { defineStore } from 'pinia'
import {CHECK_STORE_KEY} from "@/stores/constant"; import { CHECK_STORE_KEY } from '@/stores/constant'
import type {CheckData} from "@/api/check/interface"; import type { CheckData } from '@/api/check/interface'
import type {Plan} from '@/api/plan/interface' import type { Plan } from '@/api/plan/interface'
import {useAppSceneStore} from "@/stores/modules/mode"; import { useAppSceneStore } from '@/stores/modules/mode'
export const useCheckStore = defineStore(CHECK_STORE_KEY, { export const useCheckStore = defineStore(CHECK_STORE_KEY, {
state: () => ({ state: () => ({
devices: [] as CheckData.Device[], devices: [] as CheckData.Device[],
plan: {} as Plan.ResPlan, 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:自动检测 checkType: 1, // 0:手动检测 1:自动检测
reCheckType: 1, // 0:不合格项复检 1:全部复检 reCheckType: 1, // 0:不合格项复检 1:全部复检
showDetailType: 0, // 0:数据查询 1:误差体系跟换 2正式检测 showDetailType: 0, // 0:数据查询 1:误差体系跟换 2正式检测
temperature: 0, temperature: 0,
humidity: 0, humidity: 0,
chnNumList: [],//连线数据 chnNumList: [] as string[], //连线数据
nodesConnectable: true,//设置是能可以连线 nodesConnectable: true //设置是能可以连线
}), }),
getters: {}, getters: {},
actions: { actions: {
addDevices(device: CheckData.Device[]) { addDevices(device: CheckData.Device[]) {
this.devices.push(...device); this.devices.push(...device)
}, },
setPlan(plan: Plan.ResPlan) { setPlan(plan: Plan.ResPlan) {
this.plan = plan this.plan = plan
}, },
clearDevices() { clearDevices() {
this.devices = []; this.devices = []
}, },
initSelectTestItems() { initSelectTestItems() {
const appSceneStore = useAppSceneStore() const appSceneStore = useAppSceneStore()
@@ -56,12 +61,11 @@ export const useCheckStore = defineStore(CHECK_STORE_KEY, {
setHumidity(humidity: number) { setHumidity(humidity: number) {
this.humidity = humidity this.humidity = humidity
}, },
setChnNum(chnNumList: string[]) { setChnNum(chnNumList: string[]) {
this.chnNumList = chnNumList this.chnNumList = chnNumList
}, },
setNodesConnectable(nodesConnectable: boolean) { setNodesConnectable(nodesConnectable: boolean) {
this.nodesConnectable = nodesConnectable this.nodesConnectable = nodesConnectable
}, }
} }
}); })

View File

@@ -5,11 +5,9 @@ import { DICT_STORE_KEY } from '@/stores/constant'
// 模拟数据 // 模拟数据
//import dictData from '@/api/system/dictData' //import dictData from '@/api/system/dictData'
export const useDictStore = defineStore(DICT_STORE_KEY, {
export const useDictStore = defineStore({
id: DICT_STORE_KEY,
state: () => ({ state: () => ({
dictData: [] as Dict[], dictData: [] as Dict[]
}), }),
getters: {}, getters: {},
actions: { actions: {
@@ -27,7 +25,7 @@ export const useDictStore = defineStore({
// 初始化获取全部字典数据并缓存 // 初始化获取全部字典数据并缓存
async initDictData(initData: Dict[]) { async initDictData(initData: Dict[]) {
this.dictData = initData this.dictData = initData
}, }
}, },
persist: piniaPersistConfig(DICT_STORE_KEY), persist: piniaPersistConfig(DICT_STORE_KEY)
}) })

View File

@@ -1,55 +1,53 @@
import { defineStore } from "pinia"; import { defineStore } from 'pinia'
import { type GlobalState } from "@/stores/interface"; import { type GlobalState } from '@/stores/interface'
import { DEFAULT_PRIMARY} from "@/config"; import { DEFAULT_PRIMARY } from '@/config'
import piniaPersistConfig from "@/stores/helper/persist"; import piniaPersistConfig from '@/stores/helper/persist'
import {GLOBAL_STORE_KEY} from "@/stores/constant"; import { GLOBAL_STORE_KEY } from '@/stores/constant'
export const useGlobalStore = defineStore({ export const useGlobalStore = defineStore(GLOBAL_STORE_KEY, {
id: GLOBAL_STORE_KEY, // 修改默认值之后,需清除 localStorage 数据
// 修改默认值之后,需清除 localStorage 数据 state: (): GlobalState => ({
state: (): GlobalState => ({ // 布局模式 (纵向vertical | 经典classic | 横向transverse | 分栏columns)
// 布局模式 (纵向vertical | 经典classic | 横向transverse | 分栏columns) layout: 'transverse',
layout: "transverse", // element 组件大小
// element 组件大小 assemblySize: 'default',
assemblySize: "default", // 当前系统语言
// 当前系统语言 language: null,
language: null, // 当前页面是否全屏
// 当前页面是否全屏 maximize: false,
maximize: false, // 主题颜色
// 主题颜色 primary: DEFAULT_PRIMARY,
primary: DEFAULT_PRIMARY, // 深色模式
// 深色模式 isDark: false,
isDark: false, // 灰色模式
// 灰色模式 isGrey: false,
isGrey: false, // 色弱模式
// 色弱模式 isWeak: false,
isWeak: false, // 侧边栏反转
// 侧边栏反转 asideInverted: false,
asideInverted: false, // 头部反转
// 头部反转 headerInverted: false,
headerInverted: false, // 折叠菜单
// 折叠菜单 isCollapse: false,
isCollapse: false, // 菜单手风琴
// 菜单手风琴 accordion: false,
accordion: false, // 面包屑导航
// 面包屑导航 breadcrumb: true,
breadcrumb: true, // 面包屑导航图标
// 面包屑导航图标 breadcrumbIcon: true,
breadcrumbIcon: true, // 标签页
// 标签页 tabs: true,
tabs: true, // 标签页图标
// 标签页图标 tabsIcon: true,
tabsIcon: true, // 页脚
// 页脚 footer: false
footer: false }),
getters: {},
}), actions: {
getters: {}, // Set GlobalState
actions: { setGlobalState(...args: ObjToKeyValArray<GlobalState>) {
// Set GlobalState this.$patch({ [args[0]]: args[1] })
setGlobalState(...args: ObjToKeyValArray<GlobalState>) { }
this.$patch({ [args[0]]: args[1] }); },
} persist: piniaPersistConfig(GLOBAL_STORE_KEY)
}, })
persist: piniaPersistConfig(GLOBAL_STORE_KEY)
});

View File

@@ -1,24 +1,23 @@
import {defineStore} from "pinia"; import { defineStore } from 'pinia'
import {KeepAliveState} from "@/stores/interface"; import { type KeepAliveState } from '@/stores/interface'
import {KEEP_ALIVE_STORE_KEY} from '@/stores/constant' import { KEEP_ALIVE_STORE_KEY } from '@/stores/constant'
export const useKeepAliveStore = defineStore({ export const useKeepAliveStore = defineStore(KEEP_ALIVE_STORE_KEY, {
id: KEEP_ALIVE_STORE_KEY, state: (): KeepAliveState => ({
state: (): KeepAliveState => ({ keepAliveName: []
keepAliveName: [] }),
}), actions: {
actions: { // Add KeepAliveName
// Add KeepAliveName async addKeepAliveName(name: string) {
async addKeepAliveName(name: string) { !this.keepAliveName.includes(name) && this.keepAliveName.push(name)
!this.keepAliveName.includes(name) && this.keepAliveName.push(name); },
}, // Remove KeepAliveName
// Remove KeepAliveName async removeKeepAliveName(name: string) {
async removeKeepAliveName(name: string) { this.keepAliveName = this.keepAliveName.filter(item => item !== name)
this.keepAliveName = this.keepAliveName.filter(item => item !== name); },
}, // Set KeepAliveName
// Set KeepAliveName async setKeepAliveName(keepAliveName: string[] = []) {
async setKeepAliveName(keepAliveName: string[] = []) { this.keepAliveName = keepAliveName
this.keepAliveName = keepAliveName; }
} }
} })
});

View File

@@ -1,29 +1,17 @@
// src/stores/modules/mode.ts // src/stores/modules/mode.ts
import { defineStore } from 'pinia'; import { defineStore } from 'pinia'
// export const useModeStore = defineStore('mode', {
// state: () => ({
// currentMode: '' as string,
// }),
// actions: {
// setCurrentMode(modeName: string) {
// this.currentMode = modeName;
// },
// },
// });
export const useModeStore = defineStore('mode', { export const useModeStore = defineStore('mode', {
state: () => ({ state: () => ({
currentMode: localStorage.getItem('currentMode') || '' as string, currentMode: localStorage.getItem('currentMode') || ('' as string)
}), }),
actions: { actions: {
setCurrentMode(modeName: string) { setCurrentMode(modeName: string) {
this.currentMode = modeName; this.currentMode = modeName
localStorage.setItem('currentMode', modeName); // 保存到 localStorage localStorage.setItem('currentMode', modeName) // 保存到 localStorage
}, }
}, }
}); })
export const useAppSceneStore = defineStore('scene', { export const useAppSceneStore = defineStore('scene', {
state: () => ({ state: () => ({

View File

@@ -1,81 +1,77 @@
import router from "@/routers"; import router from '@/routers'
import { defineStore } from "pinia"; import { defineStore } from 'pinia'
import { getUrlWithParams } from "@/utils"; import { getUrlWithParams } from '@/utils'
import { useKeepAliveStore } from "./keepAlive"; import { useKeepAliveStore } from './keepAlive'
import { TabsState, TabsMenuProps } from "@/stores/interface"; import type { TabsMenuProps, TabsState } from '@/stores/interface'
import piniaPersistConfig from "@/stores/helper/persist"; import { TABS_STORE_KEY } from '@/stores/constant'
import {TABS_STORE_KEY} from "@/stores/constant";
const keepAliveStore = useKeepAliveStore(); 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);
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) })
});

View File

@@ -1,36 +1,57 @@
import {defineStore} from "pinia"; import { defineStore } from 'pinia'
import {UserState} from "@/stores/interface"; import { type UserState } from '@/stores/interface'
import piniaPersistConfig from "@/stores/helper/persist"; import piniaPersistConfig from '@/stores/helper/persist'
import {USER_STORE_KEY} from "@/stores/constant"; 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({ export const useUserStore = defineStore(USER_STORE_KEY, {
id: USER_STORE_KEY, state: (): UserState => ({
state: (): UserState => ({ accessToken: '',
accessToken: "", refreshToken: '',
refreshToken: "", isRefreshToken: false,
isRefreshToken:false, exp: Number(0),
exp: Number(0), userInfo: { id: '', name: '', loginName: '' }
userInfo: {id:"", name: "" ,loginName:""}, }),
}), getters: {},
getters: {}, actions: {
actions: { // Set Token
// Set Token setAccessToken(accessToken: string) {
setAccessToken(accessToken: string) { this.accessToken = accessToken
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) { persist: piniaPersistConfig(USER_STORE_KEY)
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),
});

View File

@@ -30,13 +30,15 @@
import { useAuthStore } from '@/stores/modules/auth' import { useAuthStore } from '@/stores/modules/auth'
import { useAppSceneStore, useModeStore } from '@/stores/modules/mode' // 引入模式 store import { useAppSceneStore, useModeStore } from '@/stores/modules/mode' // 引入模式 store
import { getCurrentScene } from '@/api/user/login' import { getCurrentScene } from '@/api/user/login'
import { initDynamicRouter } from '@/routers/modules/dynamicRouter'
import { useTabsStore } from '@/stores/modules/tabs'
const authStore = useAuthStore() const authStore = useAuthStore()
const modeStore = useModeStore() // 使用模式 store const modeStore = useModeStore() // 使用模式 store
const AppSceneStore = useAppSceneStore() const AppSceneStore = useAppSceneStore()
const activateInfo = authStore.activateInfo const activateInfo = authStore.activateInfo
const isActivateOpen = import.meta.env.VITE_ACTIVATE_OPEN const isActivateOpen = import.meta.env.VITE_ACTIVATE_OPEN
const tabsStore = useTabsStore()
const modeList = [ const modeList = [
{ {
name: '模拟式模块', name: '模拟式模块',
@@ -69,10 +71,10 @@ const handelOpen = async (item: any) => {
const { data: scene } = await getCurrentScene() // 获取当前场景 const { data: scene } = await getCurrentScene() // 获取当前场景
AppSceneStore.setCurrentMode(scene + '') //0省级平台1设备出厂2研发自测 AppSceneStore.setCurrentMode(scene + '') //0省级平台1设备出厂2研发自测
await authStore.setShowMenu() await authStore.setShowMenu()
await authStore.getAuthMenuList() await tabsStore.closeMultipleTab()
await initDynamicRouter()
return return
} }
const handleSelect = (key: string, keyPath: string[]) => {}
onMounted(() => {}) onMounted(() => {})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>