Template
1
0
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:
狂风代码 2024-11-14 16:56:43 +08:00 committed by GitHub
commit 9c0ac4ea94
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 252 additions and 11 deletions

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

View File

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

View File

@ -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")
/**
*

View File

@ -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, 因此导航将不会留下历史记录

View File

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

View File

@ -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 外使用 */

View File

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

View File

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