Template
1
0
mirror of https://github.com/un-pany/v3-admin-vite.git synced 2025-04-22 03:49:19 +08:00

feat: 实现动态路由与动态菜单

This commit is contained in:
chen.s.g 2024-09-21 20:46:57 +08:00
parent 0f140a61b2
commit 1fd876de72
5 changed files with 141 additions and 7 deletions

View File

@ -0,0 +1,87 @@
/** 菜单类型 */
export enum MenuType {
Menu = "menu",
Page = "page",
Button = "button",
Link = "link"
}
/** 菜单元数据 */
export interface MenuMeta {
/** 菜单标题 */
title: string
/** 菜单图标 */
icon?: string
/** 菜单总是可见 */
alwaysShow?: boolean
/** 菜单是否可用 */
roles?: string[]
}
/** 菜单详情 */
export interface MenuItem {
/** 菜单名称 */
name: string
/** 菜单类型 */
type: MenuType
/** 菜单路径 */
path: string
/** 菜单元数据 */
meta: MenuMeta
/** 子菜单 */
children?: MenuItem[]
}
/**
*
* (Roles )
* Name
*/
const dynamicRoutes: MenuItem[] = [
{
path: "/permission",
name: "Permission",
type: MenuType.Menu,
meta: {
title: "权限",
icon: "lock",
roles: ["admin", "editor"], // 可以在根路由中设置角色
alwaysShow: true // 将始终显示根菜单
},
children: [
{
path: "page",
name: "PagePermission",
type: MenuType.Page,
meta: {
title: "页面级",
roles: ["admin"] // 或者在子导航中设置角色
}
},
{
path: "directive",
name: "DirectivePermission",
type: MenuType.Page,
meta: {
title: "按钮级" // 如果未设置角色,则表示:该页面不需要权限,但会继承根路由的角色
}
}
]
}
]
/** 模拟加载菜单接口 */
export function getMenuDataApi() {
return new Promise<typeof dynamicRoutes>((resolve, reject) => {
// 模拟接口响应时间 1s
setTimeout(() => {
// 模拟接口调用成功
if (Math.random() < 0.8) {
resolve(dynamicRoutes)
} else {
// 模拟接口调用出错
reject(new Error("接口发生错误"))
}
}, 1000)
})
}

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))
// 确保添加路由已完成

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,35 @@ const filterDynamicRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
return res
}
function transformMenuToRoute(menuItem: MenuItem, rootItem: boolean, parentPath: string = ""): RouteRecordRaw {
const childrenRoute: RouteRecordRaw[] = []
const vuePath = "/src/views" + parentPath + "/" + menuItem.path + ".vue"
const vuePage = rootItem ? 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], false, menuItem.path))
}
}
}
const routeItem: RouteRecordRaw = {
path: menuItem.path,
name: menuItem.name,
component: vuePage,
meta: {
title: menuItem.meta.title,
svgIcon: menuItem.meta?.icon,
roles: menuItem.meta?.roles
},
children: childrenRoute.length > 0 ? childrenRoute : undefined
}
return routeItem
}
export const usePermissionStore = defineStore("permission", () => {
/** 可访问的路由 */
const routes = ref<RouteRecordRaw[]>([])
@ -32,8 +64,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], true))
}
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,13 @@ 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"
export const useUserStore = defineStore("user", () => {
const token = ref<string>(getToken() || "")
const roles = ref<string[]>([])
const menus = reactive<MenuItem[]>([])
const username = ref<string>("")
const tagsViewStore = useTagsViewStore()
@ -30,6 +32,13 @@ 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 changeRoles = async (role: string) => {
const newToken = "token-" + role
@ -60,7 +69,7 @@ export const useUserStore = defineStore("user", () => {
}
}
return { token, roles, username, login, getInfo, changeRoles, logout, resetToken }
return { token, roles, menus, username, login, getInfo, getMenu, changeRoles, logout, resetToken }
})
/** 在 setup 外使用 */