From 0e480b51423a13470ccf20a059cb401f8033d55d Mon Sep 17 00:00:00 2001 From: Songsl <2431955898@qq.com> Date: Sat, 25 Apr 2026 17:33:33 +0800 Subject: [PATCH] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=8C=87=E4=BB=A4?= =?UTF-8?q?=E5=B0=81=E8=A3=85=EF=BC=8C=E5=88=B7=E6=96=B0=E7=A9=BA=E7=99=BD?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/directives/auth.ts | 66 ++++++++++++++++++++++++++++++++++++++ src/main.ts | 6 +++- src/router/handleRouter.ts | 18 +++++++++++ src/router/index.ts | 47 +++++++++++++++++---------- src/stores/modules/user.ts | 20 ++++-------- src/utils/storage/index.ts | 48 +++++++++++---------------- 6 files changed, 143 insertions(+), 62 deletions(-) create mode 100644 src/directives/auth.ts diff --git a/src/directives/auth.ts b/src/directives/auth.ts new file mode 100644 index 0000000..f860202 --- /dev/null +++ b/src/directives/auth.ts @@ -0,0 +1,66 @@ +import type { Directive, DirectiveBinding } from 'vue' +import {getStorage} from '@/utils/storage' + + +/** + * v-auth 指令 - 包含权限则显示 + * 用法:v-auth="'admin'" 或 v-auth="'user:read,user:write'" + */ +export const authDirective: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + checkPermission(el, binding.value, true) + }, + updated(el: HTMLElement, binding: DirectiveBinding) { + checkPermission(el, binding.value, true) + }, +} + +/** + * v-auth-not 指令 - 不包含权限则显示 + * 用法:v-auth-not="'admin'" 或 v-auth-not="'user:read'" + */ +export const authNotDirective: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + checkPermission(el, binding.value, false) + }, + updated(el: HTMLElement, binding: DirectiveBinding) { + checkPermission(el, binding.value, false) + }, +} + +/** + * 权限检查核心逻辑 + * @param el DOM 元素 + * @param value 指令绑定的权限字符串(单个或逗号分隔的多个) + * @param shouldContain true=包含时显示,false=不包含时显示 + */ +function checkPermission(el: HTMLElement, value: string | string[], shouldContain: boolean) { + if (!value) { + shouldContain ? el.remove() : (el.style.display = '') + return + } + + // 统一处理为字符串并分割 + const permissions = Array.isArray(value) ? value : value.split(',').map((p) => p.trim()) + + // 白名单权限配置(可从后端获取或配置文件读取) + const WHITE_LIST = getStorage('btnAuth'); + + // 检查是否有任一权限在白名单中 + const hasPermission = permissions.some((p) => WHITE_LIST.includes(p)) + + // 根据 shouldContain 决定显示/隐藏 + if (shouldContain) { + hasPermission ? (el.style.display = '') : el.remove() + } else { + hasPermission ? el.remove() : (el.style.display = '') + } +} + +/** + * 注册所有权限指令 + */ +export function setupAuthDirective(app: any) { + app.directive('auth', authDirective) + app.directive('auth-not', authNotDirective) +} diff --git a/src/main.ts b/src/main.ts index 294238a..78e0c95 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,9 +11,13 @@ import { createPinia } from 'pinia' import App from './App.vue' import router from './router' +import {setupAuthDirective} from './directives/auth' +import { setStorage } from '@/utils/storage' + +setStorage("one", "1") const app = createApp(App) - +setupAuthDirective(app) app.use(createPinia()) app.use(router) app.use(ElementPlus, { diff --git a/src/router/handleRouter.ts b/src/router/handleRouter.ts index dc77fc1..cf5189e 100644 --- a/src/router/handleRouter.ts +++ b/src/router/handleRouter.ts @@ -1,3 +1,5 @@ +import { getStorage } from '@/utils/storage' +import router from '@/router' const viewModules = import.meta.glob('@/views/**/index.vue') export const handleRouter = (list: any[]):any[] => { @@ -21,3 +23,19 @@ export const handleRouter = (list: any[]):any[] => { return routerList; }; + +export const setRouter = () => { + + const menuList = getStorage('menusInfo') + const token = getStorage('token') + if (token && menuList.length > 0) { + const routeList = handleRouter(menuList).filter( + (item) => item.path && item.path !== '/dashboard', + ) + routeList.forEach((route) => router.addRoute('Layout', route)) + } + + router.getRoutes().forEach((route) => { + console.log(`路径: ${route.path}, 名称: ${route.name || '无'}`) + }) +} diff --git a/src/router/index.ts b/src/router/index.ts index 7fac18b..e58f7ab 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,6 +1,8 @@ import { createRouter, createWebHistory } from 'vue-router' import { useUserStore } from '@/stores/modules/user' import Layout from '@/components/layout/Layout.vue' +import { setRouter, handleRouter } from './handleRouter' +import { getStorage, setStorage } from '@/utils/storage' const routes = [ // user路由 @@ -67,10 +69,10 @@ const routes = [ }, ], }, - { - path: '/:pathMatch(.*)*', - redirect: '/user/login', // 404 重定向到登录页,避免循环 - }, + // { + // path: '/:pathMatch(.*)*', + // redirect: '/user/login', // 404 重定向到登录页,避免循环 + // }, ] const router = createRouter({ @@ -78,27 +80,38 @@ const router = createRouter({ routes, }) -// 全局前置守卫 -router.beforeEach(async (to, from) => { +// 标记:动态路由是否已初始化完成 +let dynamicRoutesReady = false + +router.beforeEach(async (to, _from) => { const userStore = useUserStore() const isLoggedIn = !!userStore.token - console.log('全局前置守卫', isLoggedIn, to.meta.requiresAuth) - - // 1. 未登录用户访问需要登录的页面(包括首页)→ 重定向到登录页 + // 1. 未登录访问需要认证的页 → 登录页 if (!isLoggedIn && to.meta.requiresAuth) { - if (to.path === '/user/login') return true - console.log('全局前置守卫', isLoggedIn, to.meta.requiresAuth) return '/user/login' } - // 2. 已登录用户访问授权页 → 重定向到首页 - // if (isLoggedIn) { - // if (to.path === '/' && to.meta.isAuthPage) return true - // return '/' - // } + // 2. 已登录访问登录/注册页 → 首页 + if (isLoggedIn && to.meta.isAuthPage) { + return '/' + } - // 3. 其他情况正常放行 + // 3. ✅ 动态路由恢复(只执行一次) + if (isLoggedIn && !dynamicRoutesReady) { + const menuList = getStorage('menusInfo') + if (menuList?.length > 0) { + const routeList = handleRouter(menuList).filter( + (item) => item.path && item.path !== '/dashboard', + ) + routeList.forEach((route) => router.addRoute('Layout', route)) + dynamicRoutesReady = true + // 返回目标路由以触发重新匹配 + return { ...to, replace: true } + } + } + + // 4. 其他情况正常放行 return true }) diff --git a/src/stores/modules/user.ts b/src/stores/modules/user.ts index 44e0bb8..2879d53 100644 --- a/src/stores/modules/user.ts +++ b/src/stores/modules/user.ts @@ -3,7 +3,7 @@ import { loginApi, registerApi, logoutApi, forgotPasswordApi, changePasswordApi, import type { LoginParams, RegisterParams, ForgotPasswordParams, ChangePasswordParams, SendCodeParams } from '@/api/types/user'; import type { UserInfo, MenuItem, PermissionItem } from '@/stores/types/user'; import { formatMenuToElMenu, type ElMenuItem } from '@/utils/menuFormat'; -import { handleRouter } from '@/router/handleRouter.ts' +import { handleRouter, setRouter } from '@/router/handleRouter.ts' // 定义用户 Store export const useUserStore = defineStore('user', () => { @@ -31,25 +31,17 @@ const setUserInfo = (set: any) => { menusInfo.value = set.menus_info; permissionsInfo.value = set.permissions_info; + const btnAuth = (set.permissions_info || []).map((item: any) => item.code).filter((item: any) => item) + // 保存到本地存储 setStorage('token', set.user.token); setStorage('userInfo', set.user); setStorage('menusInfo', set.menus_info); setStorage('permissionsInfo', set.permissions_info); + setStorage('btnAuth', btnAuth) - - const userStore = useUserStore() - if (userStore.token && userStore.menusInfo.length > 0) { - const routeList = handleRouter(userStore.menusInfo).filter( - (item) => item.path && item.path !== '/dashboard', - ) - routeList.forEach((route) => router.addRoute('Layout', route)) - } - - router.getRoutes().forEach((route) => { - console.log(`路径: ${route.path}, 名称: ${route.name || '无'}`) - }) - console.log('用户信息保存成功', handleRouter(set.menus_info), router); + setRouter() + }; /** diff --git a/src/utils/storage/index.ts b/src/utils/storage/index.ts index f4bfe67..62929d6 100644 --- a/src/utils/storage/index.ts +++ b/src/utils/storage/index.ts @@ -14,7 +14,7 @@ const addPrefix = (key: string): string => `${PROJECT_PREFIX}${key}` /** * 获取对应的 Storage 实例 */ -const getStorageInstance = (type: StorageType = 'local') => { +const getStorageInstance = (type: StorageType = 'session') => { return type === 'local' ? localStorage : sessionStorage } @@ -33,11 +33,7 @@ const autoParse = (value: string | null) => { /** * 增/改:设置存储值(默认localStorage,自动加前缀) */ -export const setStorage = ( - key: string, - value: T, - type: StorageType = 'local' -): void => { +export const setStorage = (key: string, value: T, type: StorageType = 'session'): void => { const prefixedKey = addPrefix(key) const storage = getStorageInstance(type) let storedValue: string @@ -54,8 +50,8 @@ export const setStorage = ( */ export const getStorage = ( key: string, - type: StorageType = 'local', - defaultValue?: T + type: StorageType = 'session', + defaultValue?: T, ): T | undefined => { const prefixedKey = addPrefix(key) const storage = getStorageInstance(type) @@ -66,10 +62,7 @@ export const getStorage = ( /** * 删:删除指定键 */ -export const removeStorage = ( - key: string, - type: StorageType = 'local' -): void => { +export const removeStorage = (key: string, type: StorageType = 'session'): void => { const prefixedKey = addPrefix(key) getStorageInstance(type).removeItem(prefixedKey) } @@ -77,9 +70,9 @@ export const removeStorage = ( /** * 清空:只清空当前项目前缀的存储 */ -export const clearStorage = (type: StorageType = 'local'): void => { +export const clearStorage = (type: StorageType = 'session'): void => { const storage = getStorageInstance(type) - Object.keys(storage).forEach(key => { + Object.keys(storage).forEach((key) => { if (key.startsWith(PROJECT_PREFIX)) { storage.removeItem(key) } @@ -92,23 +85,18 @@ export const clearStorage = (type: StorageType = 'local'): void => { export const useStorage = ( key: string, defaultValue: T, - type: StorageType = 'local' + type: StorageType = 'session', ): Ref => { const prefixedKey = addPrefix(key) - return useVueUseStorage( - prefixedKey, - defaultValue, - getStorageInstance(type), - { - serializer: { - read: autoParse, - write: (v: unknown) => { - if (typeof v === 'object' && v !== null) { - return JSON.stringify(v) - } - return String(v) + return useVueUseStorage(prefixedKey, defaultValue, getStorageInstance(type), { + serializer: { + read: autoParse, + write: (v: unknown) => { + if (typeof v === 'object' && v !== null) { + return JSON.stringify(v) } - } - } - ) as Ref + return String(v) + }, + }, + }) as Ref }