自定义指令封装,刷新空白问题

This commit is contained in:
Songsl 2026-04-25 17:33:33 +08:00
parent e8a596259a
commit 0e480b5142
6 changed files with 143 additions and 62 deletions

66
src/directives/auth.ts Normal file
View File

@ -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)
}

View File

@ -11,9 +11,13 @@ import { createPinia } from 'pinia'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import {setupAuthDirective} from './directives/auth'
import { setStorage } from '@/utils/storage'
setStorage("one", "1")
const app = createApp(App) const app = createApp(App)
setupAuthDirective(app)
app.use(createPinia()) app.use(createPinia())
app.use(router) app.use(router)
app.use(ElementPlus, { app.use(ElementPlus, {

View File

@ -1,3 +1,5 @@
import { getStorage } from '@/utils/storage'
import router from '@/router'
const viewModules = import.meta.glob('@/views/**/index.vue') const viewModules = import.meta.glob('@/views/**/index.vue')
export const handleRouter = (list: any[]):any[] => { export const handleRouter = (list: any[]):any[] => {
@ -21,3 +23,19 @@ export const handleRouter = (list: any[]):any[] => {
return routerList; 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 || '无'}`)
})
}

View File

@ -1,6 +1,8 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/stores/modules/user' import { useUserStore } from '@/stores/modules/user'
import Layout from '@/components/layout/Layout.vue' import Layout from '@/components/layout/Layout.vue'
import { setRouter, handleRouter } from './handleRouter'
import { getStorage, setStorage } from '@/utils/storage'
const routes = [ const routes = [
// user路由 // user路由
@ -67,10 +69,10 @@ const routes = [
}, },
], ],
}, },
{ // {
path: '/:pathMatch(.*)*', // path: '/:pathMatch(.*)*',
redirect: '/user/login', // 404 重定向到登录页,避免循环 // redirect: '/user/login', // 404 重定向到登录页,避免循环
}, // },
] ]
const router = createRouter({ const router = createRouter({
@ -78,27 +80,38 @@ const router = createRouter({
routes, routes,
}) })
// 全局前置守卫 // 标记:动态路由是否已初始化完成
router.beforeEach(async (to, from) => { let dynamicRoutesReady = false
router.beforeEach(async (to, _from) => {
const userStore = useUserStore() const userStore = useUserStore()
const isLoggedIn = !!userStore.token const isLoggedIn = !!userStore.token
console.log('全局前置守卫', isLoggedIn, to.meta.requiresAuth) // 1. 未登录访问需要认证的页 → 登录页
// 1. 未登录用户访问需要登录的页面(包括首页)→ 重定向到登录页
if (!isLoggedIn && to.meta.requiresAuth) { if (!isLoggedIn && to.meta.requiresAuth) {
if (to.path === '/user/login') return true
console.log('全局前置守卫', isLoggedIn, to.meta.requiresAuth)
return '/user/login' return '/user/login'
} }
// 2. 已登录用户访问授权页 → 重定向到首页 // 2. 已登录访问登录/注册页 → 首页
// if (isLoggedIn) { if (isLoggedIn && to.meta.isAuthPage) {
// if (to.path === '/' && to.meta.isAuthPage) return true return '/'
// 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 return true
}) })

View File

@ -3,7 +3,7 @@ import { loginApi, registerApi, logoutApi, forgotPasswordApi, changePasswordApi,
import type { LoginParams, RegisterParams, ForgotPasswordParams, ChangePasswordParams, SendCodeParams } from '@/api/types/user'; import type { LoginParams, RegisterParams, ForgotPasswordParams, ChangePasswordParams, SendCodeParams } from '@/api/types/user';
import type { UserInfo, MenuItem, PermissionItem } from '@/stores/types/user'; import type { UserInfo, MenuItem, PermissionItem } from '@/stores/types/user';
import { formatMenuToElMenu, type ElMenuItem } from '@/utils/menuFormat'; import { formatMenuToElMenu, type ElMenuItem } from '@/utils/menuFormat';
import { handleRouter } from '@/router/handleRouter.ts' import { handleRouter, setRouter } from '@/router/handleRouter.ts'
// 定义用户 Store // 定义用户 Store
export const useUserStore = defineStore('user', () => { export const useUserStore = defineStore('user', () => {
@ -31,25 +31,17 @@ const setUserInfo = (set: any) => {
menusInfo.value = set.menus_info; menusInfo.value = set.menus_info;
permissionsInfo.value = set.permissions_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('token', set.user.token);
setStorage('userInfo', set.user); setStorage('userInfo', set.user);
setStorage('menusInfo', set.menus_info); setStorage('menusInfo', set.menus_info);
setStorage('permissionsInfo', set.permissions_info); setStorage('permissionsInfo', set.permissions_info);
setStorage('btnAuth', btnAuth)
setRouter()
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);
}; };
/** /**

View File

@ -14,7 +14,7 @@ const addPrefix = (key: string): string => `${PROJECT_PREFIX}${key}`
/** /**
* Storage * Storage
*/ */
const getStorageInstance = (type: StorageType = 'local') => { const getStorageInstance = (type: StorageType = 'session') => {
return type === 'local' ? localStorage : sessionStorage return type === 'local' ? localStorage : sessionStorage
} }
@ -33,11 +33,7 @@ const autoParse = (value: string | null) => {
/** /**
* /localStorage * /localStorage
*/ */
export const setStorage = <T = unknown>( export const setStorage = <T = unknown>(key: string, value: T, type: StorageType = 'session'): void => {
key: string,
value: T,
type: StorageType = 'local'
): void => {
const prefixedKey = addPrefix(key) const prefixedKey = addPrefix(key)
const storage = getStorageInstance(type) const storage = getStorageInstance(type)
let storedValue: string let storedValue: string
@ -54,8 +50,8 @@ export const setStorage = <T = unknown>(
*/ */
export const getStorage = <T = unknown>( export const getStorage = <T = unknown>(
key: string, key: string,
type: StorageType = 'local', type: StorageType = 'session',
defaultValue?: T defaultValue?: T,
): T | undefined => { ): T | undefined => {
const prefixedKey = addPrefix(key) const prefixedKey = addPrefix(key)
const storage = getStorageInstance(type) const storage = getStorageInstance(type)
@ -66,10 +62,7 @@ export const getStorage = <T = unknown>(
/** /**
* *
*/ */
export const removeStorage = ( export const removeStorage = (key: string, type: StorageType = 'session'): void => {
key: string,
type: StorageType = 'local'
): void => {
const prefixedKey = addPrefix(key) const prefixedKey = addPrefix(key)
getStorageInstance(type).removeItem(prefixedKey) 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) const storage = getStorageInstance(type)
Object.keys(storage).forEach(key => { Object.keys(storage).forEach((key) => {
if (key.startsWith(PROJECT_PREFIX)) { if (key.startsWith(PROJECT_PREFIX)) {
storage.removeItem(key) storage.removeItem(key)
} }
@ -92,14 +85,10 @@ export const clearStorage = (type: StorageType = 'local'): void => {
export const useStorage = <T = unknown>( export const useStorage = <T = unknown>(
key: string, key: string,
defaultValue: T, defaultValue: T,
type: StorageType = 'local' type: StorageType = 'session',
): Ref<T> => { ): Ref<T> => {
const prefixedKey = addPrefix(key) const prefixedKey = addPrefix(key)
return useVueUseStorage( return useVueUseStorage(prefixedKey, defaultValue, getStorageInstance(type), {
prefixedKey,
defaultValue,
getStorageInstance(type),
{
serializer: { serializer: {
read: autoParse, read: autoParse,
write: (v: unknown) => { write: (v: unknown) => {
@ -107,8 +96,7 @@ export const useStorage = <T = unknown>(
return JSON.stringify(v) return JSON.stringify(v)
} }
return String(v) return String(v)
} },
} },
} }) as Ref<T>
) as Ref<T>
} }