mirror of
https://github.com/un-pany/v3-admin-vite.git
synced 2025-04-22 03:49:19 +08:00
Merge 2ed53fb4c45bad529f7ab46e955966a09220f91c into ade5d806c74d04758fcd646e9eb7457b4a09cd02
This commit is contained in:
commit
9c0ac4ea94
108
src/api/hook-demo/use-dynamic-route.ts
Normal file
108
src/api/hook-demo/use-dynamic-route.ts
Normal file
@ -0,0 +1,108 @@
|
||||
/** 菜单类型 */
|
||||
export enum MenuType {
|
||||
Menu = "menu",
|
||||
Page = "page",
|
||||
Button = "button",
|
||||
Link = "link"
|
||||
}
|
||||
|
||||
/** 菜单元数据 */
|
||||
export interface MenuMeta {
|
||||
/** 菜单标题 */
|
||||
title: string
|
||||
/** 菜单图标 */
|
||||
icon?: string
|
||||
/** 隐藏菜单 */
|
||||
hidden?: boolean
|
||||
/** 菜单总是可见 */
|
||||
alwaysShow?: boolean
|
||||
/** 菜单是否可用 */
|
||||
roles?: string[]
|
||||
/** 其它参数 */
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/** 菜单详情 */
|
||||
export interface MenuItem {
|
||||
/** 菜单名称 */
|
||||
name: string
|
||||
/** 菜单类型 */
|
||||
type: MenuType
|
||||
/** 菜单路径 */
|
||||
path: string
|
||||
/** 重定向页面 */
|
||||
redirect?: string
|
||||
/** 组件页面 */
|
||||
component?: string
|
||||
/** 菜单元数据 */
|
||||
meta: MenuMeta
|
||||
/** 子菜单 */
|
||||
children?: MenuItem[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态路由
|
||||
* 用来放置有权限 (Roles 属性) 的路由
|
||||
* 必须带有 Name 属性
|
||||
*/
|
||||
const dynamicRoutes: MenuItem[] = [
|
||||
{
|
||||
path: "/permission",
|
||||
redirect: "/permission/page",
|
||||
name: "Permission",
|
||||
type: MenuType.Menu,
|
||||
meta: {
|
||||
title: "权限",
|
||||
icon: "lock",
|
||||
roles: ["admin", "editor"], // 可以在根路由中设置角色
|
||||
alwaysShow: true // 将始终显示根菜单
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "page",
|
||||
component: "/views/permission/page.vue",
|
||||
name: "PagePermission",
|
||||
type: MenuType.Page,
|
||||
meta: {
|
||||
title: "页面级",
|
||||
roles: ["admin"] // 或者在子导航中设置角色
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "directive",
|
||||
component: "/views/permission/directive.vue",
|
||||
name: "DirectivePermission",
|
||||
type: MenuType.Page,
|
||||
meta: {
|
||||
title: "按钮级" // 如果未设置角色,则表示:该页面不需要权限,但会继承根路由的角色
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "button",
|
||||
name: "ButtonPermission",
|
||||
type: MenuType.Button,
|
||||
meta: {
|
||||
title: "按钮权限"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
/** 模拟加载菜单接口 */
|
||||
export function getMenuDataApi() {
|
||||
return new Promise<typeof dynamicRoutes>((resolve, reject) => {
|
||||
// 模拟接口响应时间 1s
|
||||
setTimeout(() => {
|
||||
// 模拟接口调用成功
|
||||
if (Math.random() < 1) {
|
||||
resolve(dynamicRoutes)
|
||||
} else {
|
||||
// 模拟接口调用出错
|
||||
reject(new Error("接口发生错误"))
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
}
|
@ -5,9 +5,19 @@ import { useUserStoreHook } from "@/store/modules/user"
|
||||
export const permission: Directive = {
|
||||
mounted(el, binding) {
|
||||
const { value: permissionRoles } = binding
|
||||
const { roles } = useUserStoreHook()
|
||||
const { permission } = useUserStoreHook()
|
||||
|
||||
if (Array.isArray(permissionRoles) && permissionRoles.length > 0) {
|
||||
const hasPermission = roles.some((role) => permissionRoles.includes(role))
|
||||
let hasPermission = false
|
||||
permissionRoles.forEach((item) => {
|
||||
const res = (item as string).split(":")
|
||||
if (permission.has(res[0])) {
|
||||
if (permission.get(res[0])?.includes(res[1])) {
|
||||
hasPermission = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// hasPermission || (el.style.display = "none") // 隐藏
|
||||
hasPermission || el.parentNode?.removeChild(el) // 销毁
|
||||
} else {
|
||||
|
@ -2,7 +2,7 @@ import { type RouteRecordRaw, createRouter } from "vue-router"
|
||||
import { history, flatMultiLevelRoutes } from "./helper"
|
||||
import routeSettings from "@/config/route"
|
||||
|
||||
const Layouts = () => import("@/layouts/index.vue")
|
||||
export const Layouts = () => import("@/layouts/index.vue")
|
||||
|
||||
/**
|
||||
* 常驻路由
|
||||
|
@ -38,10 +38,11 @@ router.beforeEach(async (to, _from, next) => {
|
||||
// 否则要重新获取权限角色
|
||||
try {
|
||||
await userStore.getInfo()
|
||||
await userStore.getMenu()
|
||||
// 注意:角色必须是一个数组! 例如: ["admin"] 或 ["developer", "editor"]
|
||||
const roles = userStore.roles
|
||||
// 生成可访问的 Routes
|
||||
routeSettings.dynamic ? permissionStore.setRoutes(roles) : permissionStore.setAllRoutes()
|
||||
routeSettings.dynamic ? permissionStore.setRoutes(roles, userStore.menus) : permissionStore.setAllRoutes()
|
||||
// 将 "有访问权限的动态路由" 添加到 Router 中
|
||||
permissionStore.addRoutes.forEach((route) => router.addRoute(route))
|
||||
// 设置 replace: true, 因此导航将不会留下历史记录
|
||||
|
@ -2,9 +2,12 @@ import { ref } from "vue"
|
||||
import store from "@/store"
|
||||
import { defineStore } from "pinia"
|
||||
import { type RouteRecordRaw } from "vue-router"
|
||||
import { constantRoutes, dynamicRoutes } from "@/router"
|
||||
import { constantRoutes, dynamicRoutes, Layouts } from "@/router"
|
||||
import { flatMultiLevelRoutes } from "@/router/helper"
|
||||
import routeSettings from "@/config/route"
|
||||
import { MenuItem, MenuType } from "@/api/hook-demo/use-dynamic-route"
|
||||
|
||||
const modules = import.meta.glob(["@/views/*.vue", "@/views/**/*.vue"])
|
||||
|
||||
const hasPermission = (roles: string[], route: RouteRecordRaw) => {
|
||||
const routeRoles = route.meta?.roles
|
||||
@ -25,6 +28,38 @@ const filterDynamicRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
|
||||
return res
|
||||
}
|
||||
|
||||
function transformMenuToRoute(menuItem: MenuItem): RouteRecordRaw {
|
||||
const childrenRoute: RouteRecordRaw[] = []
|
||||
const vuePath = "/src" + (menuItem.component ?? "")
|
||||
const vuePage = menuItem.type == MenuType.Menu ? Layouts : modules[vuePath]
|
||||
|
||||
// 如果有 children,则需要递归添加 children 到 route
|
||||
if (menuItem.children && menuItem.children.length > 0) {
|
||||
for (let i = 0; i < menuItem.children.length; i++) {
|
||||
if (menuItem.children[i].type == MenuType.Page) {
|
||||
childrenRoute.push(transformMenuToRoute(menuItem.children[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const routeItem: RouteRecordRaw = {
|
||||
path: menuItem.path,
|
||||
name: menuItem.name,
|
||||
component: vuePage,
|
||||
meta: {
|
||||
svgIcon: menuItem.meta?.icon,
|
||||
...menuItem.meta
|
||||
},
|
||||
children: childrenRoute.length > 0 ? childrenRoute : undefined
|
||||
}
|
||||
|
||||
if (menuItem.redirect) {
|
||||
routeItem.redirect = menuItem.redirect
|
||||
}
|
||||
|
||||
return routeItem
|
||||
}
|
||||
|
||||
export const usePermissionStore = defineStore("permission", () => {
|
||||
/** 可访问的路由 */
|
||||
const routes = ref<RouteRecordRaw[]>([])
|
||||
@ -32,8 +67,13 @@ export const usePermissionStore = defineStore("permission", () => {
|
||||
const addRoutes = ref<RouteRecordRaw[]>([])
|
||||
|
||||
/** 根据角色生成可访问的 Routes(可访问的路由 = 常驻路由 + 有访问权限的动态路由) */
|
||||
const setRoutes = (roles: string[]) => {
|
||||
const accessedRoutes = filterDynamicRoutes(dynamicRoutes, roles)
|
||||
const setRoutes = (roles: string[], menus: MenuItem[]) => {
|
||||
const menuRoute: RouteRecordRaw[] = []
|
||||
for (let i = 0; i < menus.length; i++) {
|
||||
menuRoute.push(transformMenuToRoute(menus[i]))
|
||||
}
|
||||
|
||||
const accessedRoutes = filterDynamicRoutes(menuRoute, roles)
|
||||
_set(accessedRoutes)
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ref } from "vue"
|
||||
import { ref, reactive } from "vue"
|
||||
import store from "@/store"
|
||||
import { defineStore } from "pinia"
|
||||
import { useTagsViewStore } from "./tags-view"
|
||||
@ -7,11 +7,53 @@ import { getToken, removeToken, setToken } from "@/utils/cache/cookies"
|
||||
import { resetRouter } from "@/router"
|
||||
import { loginApi, getUserInfoApi } from "@/api/login"
|
||||
import { type LoginRequestData } from "@/api/login/types/login"
|
||||
import { MenuItem, getMenuDataApi } from "@/api/hook-demo/use-dynamic-route"
|
||||
import routeSettings from "@/config/route"
|
||||
|
||||
/**
|
||||
* 从菜单生成权限资源
|
||||
*
|
||||
* @param menus 授予用户的菜单列表
|
||||
* @returns Map<string, string[]> 权限资源
|
||||
*/
|
||||
function buildMenuPermission(menus: MenuItem[]) {
|
||||
const ret = new Map<string, string[]>()
|
||||
|
||||
menus.forEach((item) => {
|
||||
if (item.type === "menu" && item.children && item.children.length > 0) {
|
||||
const tmp = buildMenuPermission(item.children)
|
||||
if (tmp.size > 0) {
|
||||
tmp.forEach((value, key) => {
|
||||
if (ret.has(key)) {
|
||||
ret.set(key, [...new Set([...(ret.get(key) as string[]), ...value])])
|
||||
} else {
|
||||
ret.set(key, value)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if (item.type === "page" && item.children && item.children.length > 0) {
|
||||
const res: string[] = []
|
||||
|
||||
item.children?.forEach((child) => {
|
||||
if (child.name != "") {
|
||||
res.push(child.name)
|
||||
}
|
||||
})
|
||||
|
||||
if (res.length > 0) {
|
||||
ret.set(item.name, res)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore("user", () => {
|
||||
const token = ref<string>(getToken() || "")
|
||||
const roles = ref<string[]>([])
|
||||
const menus = reactive<MenuItem[]>([])
|
||||
const permission = reactive<Map<string, string[]>>(new Map())
|
||||
const username = ref<string>("")
|
||||
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
@ -30,6 +72,18 @@ export const useUserStore = defineStore("user", () => {
|
||||
// 验证返回的 roles 是否为一个非空数组,否则塞入一个没有任何作用的默认角色,防止路由守卫逻辑进入无限循环
|
||||
roles.value = data.roles?.length > 0 ? data.roles : routeSettings.defaultRoles
|
||||
}
|
||||
/** 获取菜单 */
|
||||
const getMenu = async () => {
|
||||
const data = await getMenuDataApi()
|
||||
if (data && data.length > 0) {
|
||||
menus.push(...data)
|
||||
|
||||
const permissionMap = buildMenuPermission(data)
|
||||
permissionMap.forEach((value, key) => {
|
||||
permission.set(key, value)
|
||||
})
|
||||
}
|
||||
}
|
||||
/** 模拟角色变化 */
|
||||
const changeRoles = async (role: string) => {
|
||||
const newToken = "token-" + role
|
||||
@ -60,7 +114,7 @@ export const useUserStore = defineStore("user", () => {
|
||||
}
|
||||
}
|
||||
|
||||
return { token, roles, username, login, getInfo, changeRoles, logout, resetToken }
|
||||
return { token, roles, menus, permission, username, login, getInfo, getMenu, changeRoles, logout, resetToken }
|
||||
})
|
||||
|
||||
/** 在 setup 外使用 */
|
||||
|
@ -3,8 +3,18 @@ import { useUserStoreHook } from "@/store/modules/user"
|
||||
/** 全局权限判断函数,和权限指令 v-permission 功能类似 */
|
||||
export const checkPermission = (permissionRoles: string[]): boolean => {
|
||||
if (Array.isArray(permissionRoles) && permissionRoles.length > 0) {
|
||||
const { roles } = useUserStoreHook()
|
||||
return roles.some((role) => permissionRoles.includes(role))
|
||||
const { permission } = useUserStoreHook()
|
||||
let hasPermission = false
|
||||
permissionRoles.forEach((item) => {
|
||||
const res = (item as string).split(":")
|
||||
if (permission.has(res[0])) {
|
||||
if (permission.get(res[0])?.includes(res[1])) {
|
||||
hasPermission = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return hasPermission
|
||||
} else {
|
||||
console.error("need roles! Like checkPermission(['admin','editor'])")
|
||||
return false
|
||||
|
@ -8,6 +8,11 @@ import SwitchRoles from "./components/SwitchRoles.vue"
|
||||
<SwitchRoles />
|
||||
<!-- v-permission 示例 -->
|
||||
<div class="margin-top-30">
|
||||
<div>
|
||||
权限指令说明:权限指令内容由页面名与下级资源名组成<br />如:DirectivePermission:ButtonPermission 指令中的
|
||||
DirectivePermission 为菜单配置中页面的 name 属性, ButtonPermission 为页面 children 中的下级资源 name 属性值<br />
|
||||
详情请参考 src/api/hook-demo/use-dynamic-route.ts 文件中的菜单配置
|
||||
</div>
|
||||
<div>
|
||||
<el-tag v-permission="['admin']" type="success" size="large" effect="plain">
|
||||
这里采用了 v-permission="['admin']" 所以只有 admin 可以看见这句话
|
||||
@ -23,6 +28,12 @@ import SwitchRoles from "./components/SwitchRoles.vue"
|
||||
这里采用了 v-permission="['admin', 'editor']" 所以 admin 和 editor 都可以看见这句话
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="margin-top-15">
|
||||
<el-tag v-permission="['DirectivePermission:ButtonPermission']" type="success" size="large" effect="plain">
|
||||
这里采用了 v-permission="['DirectivePermission:ButtonPermission']" 所以只有
|
||||
DirectivePermission:ButtonPermission 权限才可以看见这句话
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<!-- checkPermission 示例 -->
|
||||
<div class="margin-top-30">
|
||||
@ -40,6 +51,13 @@ import SwitchRoles from "./components/SwitchRoles.vue"
|
||||
<el-tab-pane v-if="checkPermission(['admin', 'editor'])" label="admin 和 editor">
|
||||
这里采用了 <el-tag>v-if="checkPermission(['admin', 'editor'])"</el-tag> 所以 admin 和 editor 都可以看见这句话
|
||||
</el-tab-pane>
|
||||
<el-tab-pane
|
||||
v-if="checkPermission(['DirectivePermission:ButtonPermission'])"
|
||||
label="DirectivePermission:ButtonPermission 按钮权限测试"
|
||||
>
|
||||
这里采用了 <el-tag>v-if="checkPermission(['DirectivePermission:ButtonPermission'])"</el-tag> 所以只有
|
||||
DirectivePermission:ButtonPermission 权限才可以看见这句话
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user