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

View File

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

View File

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

View File

@ -4,6 +4,12 @@ import AdminLayout from '@/layout/admin-layout.vue'
import BlankLayout from '@/layout/blank-layout.vue' import BlankLayout from '@/layout/blank-layout.vue'
export const routes: Array<RouteRecordRaw> = [ 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', // 系统管理 path: '/admin/acl', // 系统管理
name: 'ACL', name: 'ACL',
@ -99,48 +105,93 @@ export const routes: Array<RouteRecordRaw> = [
}, },
] ]
const getNewRouter = () => { export default createRouter({
// TODO 用户store判断是否登录,登录就返回过滤的,没有登录就返回全量 history: createWebHashHistory(),
return createRouter({ routes: [
history: createWebHashHistory(), {
routes: [ path: '/',
{ name: 'Index',
path: '/', redirect: '/login',
name: 'Index', },
redirect: '/login', {
}, path: '/login',
{ name: 'Login',
path: '/login', redirect: '/login/user',
name: 'Login', component: UserLayout,
redirect: '/login/user', children: [
component: UserLayout, {
children: [ path: 'user',
{ name: 'LoginPage',
path: 'user', component: () => import('../views/login/user-login.vue'),
name: 'LoginPage', },
component: () => import('../views/login/user-login.vue'), ],
}, },
], {
}, path: '/admin',
{ name: 'Admin',
path: '/admin', redirect: '/admin/dashboard',
name: 'Admin', component: AdminLayout,
redirect: '/admin/dashboard', meta: { title: 'menus.index', icon: 'icon-home' },
component: AdminLayout, children: routes,
meta: { title: 'menus.index', icon: 'icon-home' }, },
children: routes, {
}, path: '/:catchAll(.*)',
{ redirect: '/message',
path: '/:catchAll(.*)', },
redirect: '/message', {
}, path: '/message',
{ name: 'Message',
path: '/message', component: () => import('@/views/message/message-page.vue'),
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 { defineStore } from 'pinia'
import router from '@/router' import router, { getP } from '@/router'
export const useUserStore = defineStore('user', { export const useUserStore = defineStore('user', {
state: (): auth.AuthUser => ({ state: (): auth.AuthUser => ({
@ -31,10 +31,13 @@ export const useUserStore = defineStore('user', {
}, },
userLogin(user: auth.AuthUser) { userLogin(user: auth.AuthUser) {
Object.assign(this, user) Object.assign(this, user)
// 过了路由的地址 // 根据权限动态添加地址
// router.addRoute if (!this.isAdmin) {
const path = getP(this.permissions)
router.push({ path: '/admin/acl/users' }) // 登录后跳转到用户列表页面 router.addRoute(path)
// router.replace(router.currentRoute.value.fullPath)
}
router.push({ path: '/admin/dashboard' })
}, },
logout() { logout() {
this.mobile = '' this.mobile = ''
@ -47,7 +50,11 @@ export const useUserStore = defineStore('user', {
this.sex = 'FEMALE' this.sex = 'FEMALE'
this.token = '' this.token = ''
this.refreshToken = '' 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: { persist: {

View File

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