sparkles: 引入菜单权限控制
All checks were successful
Release / lint (push) Successful in 27s
Release / Release (push) Successful in 1m21s

This commit is contained in:
my_ong 2024-12-18 12:43:51 +08:00
parent 566da2e791
commit ee0525e62b
6 changed files with 166 additions and 101 deletions

100
src/api/auth/api.d.ts vendored
View File

@ -1,71 +1,71 @@
declare namespace auth {
/**
* AuthUser
*/
export interface AuthUser {
/** anonymous */
anonymous?: boolean;
/**
* AuthUser
*/
export interface AuthUser {
/** anonymous */
anonymous?: boolean
/** 用户头像 */
avatar?: string;
/** 用户头像 */
avatar?: string
/** 邮箱 */
email?: string;
/** 邮箱 */
email?: string
/** extInfo */
extInfo?: auth.NutMap;
/** extInfo */
extInfo?: auth.NutMap
/** 姓名 */
fullName: string;
/** 姓名 */
fullName: string
/** 电话 */
mobile?: string;
/** 电话 */
mobile?: string
/** 密码 */
password: string;
/** 密码 */
password: string
/** 权限列表 */
permissions: Array<string>;
/** 权限列表 */
permissions: Array<string>
/** jwt refreshToken */
refreshToken: string;
/** jwt refreshToken */
refreshToken: string
/** 角色列表 */
roles: Array<string>;
/** 角色列表 */
roles: Array<string>
/** 性别 */
sex?: 'MALE' | 'FEMALE';
/** 性别 */
sex?: 'MALE' | 'FEMALE'
/** sexInfo */
sexInfo?: auth.Codebook;
/** sexInfo */
sexInfo?: auth.Codebook
/** jwt Token */
token: string;
/** jwt Token */
token: string
/** 用户名 */
userName: string;
}
/** 用户名 */
userName: string
}
/**
*
*/
export interface LoginDTO {
/** 验证码 */
captcha?: string;
/**
*
*/
export interface LoginDTO {
/** 验证码 */
captcha?: string
/** 手机号 */
mobile?: string;
/** 手机号 */
mobile?: string
/** 密码 */
password?: string;
/** 密码 */
password?: string
/** 登录类型 */
type: 'ACCOUNT' | 'WECHAT';
/** 登录类型 */
type: 'ACCOUNT' | 'WECHAT'
/** 用户名 */
userName?: string;
/** 用户名 */
userName?: string
/** uuid */
uuid?: string;
}
/** uuid */
uuid?: string
}
}

View File

@ -2,6 +2,7 @@ import { RouteRecordRaw } from 'vue-router'
import XEUtils from 'xe-utils'
import i18n from '@/locales'
import { routes } from '@/router'
import { useUserStore } from '@/stores/user'
const { t } = i18n.global
export interface MenuNode {
key: string
@ -73,8 +74,8 @@ export function menuTree() {
export function hasPermission(key?: string) {
console.log(key)
// return useUserStore().hasPermission(key);
return true
return useUserStore().hasPermission(key) // 过滤菜单是否显示
// return true
}
export function permissions() {
@ -91,6 +92,7 @@ export function fliteredMenus() {
return !item.hidden
})
.filter((item) => {
if (item.name === 'dashboard') return true // 首页显示
return hasPermission(item.keyPath)
})
}

View File

@ -1,5 +1,6 @@
{
"index": "首页",
"dashboard": "仪表盘",
"acl": {
"name": "系统管理",
"users": "用户管理",

View File

@ -4,6 +4,12 @@ import AdminLayout from '@/layout/admin-layout.vue'
import BlankLayout from '@/layout/blank-layout.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/admin/dashboard',
meta: { title: 'menus.dashboard', icon: 'icon-dashboard', flat: true },
name: 'Dashboard',
component: () => import('../views/admin/dashboard-page.vue'),
},
{
path: '/admin/acl', // 系统管理
name: 'ACL',
@ -99,48 +105,93 @@ export const routes: Array<RouteRecordRaw> = [
},
]
const getNewRouter = () => {
// TODO 用户store判断是否登录,登录就返回过滤的,没有登录就返回全量
return createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
name: 'Index',
redirect: '/login',
},
{
path: '/login',
name: 'Login',
redirect: '/login/user',
component: UserLayout,
children: [
{
path: 'user',
name: 'LoginPage',
component: () => import('../views/login/user-login.vue'),
},
],
},
{
path: '/admin',
name: 'Admin',
redirect: '/admin/dashboard',
component: AdminLayout,
meta: { title: 'menus.index', icon: 'icon-home' },
children: routes,
},
{
path: '/:catchAll(.*)',
redirect: '/message',
},
{
path: '/message',
name: 'Message',
component: () => import('@/views/message/message-page.vue'),
},
],
})
export default createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
name: 'Index',
redirect: '/login',
},
{
path: '/login',
name: 'Login',
redirect: '/login/user',
component: UserLayout,
children: [
{
path: 'user',
name: 'LoginPage',
component: () => import('../views/login/user-login.vue'),
},
],
},
{
path: '/admin',
name: 'Admin',
redirect: '/admin/dashboard',
component: AdminLayout,
meta: { title: 'menus.index', icon: 'icon-home' },
children: routes,
},
{
path: '/:catchAll(.*)',
redirect: '/message',
},
{
path: '/message',
name: 'Message',
component: () => import('@/views/message/message-page.vue'),
},
],
})
export const getP = (permissions: Array<string>): RouteRecordRaw => {
const newRoutes: RouteRecordRaw[] = filterRoutesByPermission(routes, permissions)
return {
path: '/admin',
name: 'Admin',
redirect: '/admin/dashboard',
component: AdminLayout,
meta: { title: 'menus.index', icon: 'icon-home' },
children: newRoutes,
}
}
export default getNewRouter()
// 过滤路由,最多两层
const filterRoutesByPermission = (routes: RouteRecordRaw[], permissions: string[]): RouteRecordRaw[] => {
return routes
.map((route) => {
// 确保 route.name 是字符串
const parentName = typeof route.name === 'string' ? route.name : undefined
if ('Dashboard' === parentName) return route // 首页不做权限过滤
const hasParentPermission = parentName && permissions.includes(parentName)
// 如果有子路由,过滤子路由
if (Array.isArray(route.children) && route.children.length > 0) {
route.children = route.children.filter((child) => {
// 确保 child.name 是字符串
const childName = typeof child.name === 'string' ? child.name : undefined
// 拼接父级和子级的权限字符串
const fullName = parentName && childName ? `${parentName}.${childName}` : childName
// 确保 fullName 是字符串并且存在于 permissions 中
return typeof fullName === 'string' && permissions.includes(fullName)
})
// 如果子路由中有任何一个有权限,或者父级路由本身有权限,则保留该父级路由
if (route.children.length > 0 || hasParentPermission) {
return route
}
} else if (hasParentPermission) {
// 如果没有子路由,直接检查父级路由的权限
return route
}
// 如果不符合任何条件,返回 undefined表示该路由将被过滤掉
return undefined
})
.filter((route): route is RouteRecordRaw => route !== undefined) // 过滤掉 undefined 的项并确保类型正确
}

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import router from '@/router'
import router, { getP } from '@/router'
export const useUserStore = defineStore('user', {
state: (): auth.AuthUser => ({
@ -31,10 +31,13 @@ export const useUserStore = defineStore('user', {
},
userLogin(user: auth.AuthUser) {
Object.assign(this, user)
// 过了路由的地址
// router.addRoute
router.push({ path: '/admin/acl/users' }) // 登录后跳转到用户列表页面
// 根据权限动态添加地址
if (!this.isAdmin) {
const path = getP(this.permissions)
router.addRoute(path)
// router.replace(router.currentRoute.value.fullPath)
}
router.push({ path: '/admin/dashboard' })
},
logout() {
this.mobile = ''
@ -47,7 +50,11 @@ export const useUserStore = defineStore('user', {
this.sex = 'FEMALE'
this.token = ''
this.refreshToken = ''
router.push({ path: '/login/user' }) // 退出登录后跳转到登录页面,未实现,要清理缓存?
router.push({ path: '/login/user' })
},
hasPermission(permission?: string) {
if (this.isAdmin) return true
return permission !== undefined && this.permissions.includes(permission)
},
},
persist: {

View File

@ -0,0 +1,4 @@
<template>
<p>这是首页欢迎使用库管系统</p>
</template>
<script setup lang="ts"></script>