自定义指令封装,刷新空白问题
This commit is contained in:
parent
e8a596259a
commit
0e480b5142
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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 || '无'}`)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue