自定义指令封装,刷新空白问题
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 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, {
|
||||||
|
|
|
||||||
|
|
@ -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 || '无'}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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,23 +85,18 @@ 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,
|
serializer: {
|
||||||
defaultValue,
|
read: autoParse,
|
||||||
getStorageInstance(type),
|
write: (v: unknown) => {
|
||||||
{
|
if (typeof v === 'object' && v !== null) {
|
||||||
serializer: {
|
return JSON.stringify(v)
|
||||||
read: autoParse,
|
|
||||||
write: (v: unknown) => {
|
|
||||||
if (typeof v === 'object' && v !== null) {
|
|
||||||
return JSON.stringify(v)
|
|
||||||
}
|
|
||||||
return String(v)
|
|
||||||
}
|
}
|
||||||
}
|
return String(v)
|
||||||
}
|
},
|
||||||
) as Ref<T>
|
},
|
||||||
|
}) as Ref<T>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue