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

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 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, {

View File

@ -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 || '无'}`)
})
}

View File

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

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 { 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()
};
/**

View File

@ -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 = <T = unknown>(
key: string,
value: T,
type: StorageType = 'local'
): void => {
export const setStorage = <T = unknown>(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 = <T = unknown>(
*/
export const getStorage = <T = unknown>(
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 = <T = unknown>(
/**
*
*/
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 = <T = unknown>(
key: string,
defaultValue: T,
type: StorageType = 'local'
type: StorageType = 'session',
): Ref<T> => {
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<T>
return String(v)
},
},
}) as Ref<T>
}