修复权限:隔离教师工作台与学员中心菜单自服务可见性
This commit is contained in:
1 parent
2b1fcdb260
commit
ecfd38be17
15 files changed
+929
-17
No files matched your search
@@ -4,6 +4,7 @@ import { getRouters } from '@/api/menu'
|
||||
import Layout from '@/layout/index'
|
||||
import ParentView from '@/components/ParentView'
|
||||
import InnerLink from '@/layout/components/InnerLink'
|
||||
import { isSelfServiceRouteAllowed } from '@/utils/selfServiceRoute'
|
||||
|
||||
const permission = {
|
||||
state: {
|
||||
@@ -30,16 +31,15 @@ const permission = {
|
||||
},
|
||||
actions: {
|
||||
// 生成路由
|
||||
GenerateRoutes({ commit }) {
|
||||
GenerateRoutes({ commit, rootGetters }) {
|
||||
return new Promise(resolve => {
|
||||
// 向后端请求路由数据
|
||||
getRouters().then(res => {
|
||||
//console.log("getRouters_res==============");
|
||||
//console.log(res);
|
||||
const roles = (rootGetters && rootGetters.roles) || []
|
||||
const sdata = JSON.parse(JSON.stringify(res.data))
|
||||
const rdata = JSON.parse(JSON.stringify(res.data))
|
||||
const sidebarRoutes = filterAsyncRouter(sdata)
|
||||
const rewriteRoutes = filterAsyncRouter(rdata, false, true)
|
||||
const sidebarRoutes = filterAsyncRouter(sdata, false, false, roles)
|
||||
const rewriteRoutes = filterAsyncRouter(rdata, false, true, roles)
|
||||
const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
|
||||
rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
|
||||
router.addRoutes(asyncRoutes);
|
||||
@@ -55,8 +55,11 @@ const permission = {
|
||||
}
|
||||
|
||||
// 遍历后台传来的路由字符串,转换为组件对象
|
||||
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
|
||||
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false, roles = []) {
|
||||
return asyncRouterMap.filter(route => {
|
||||
if (!isSelfServiceRouteAllowed(route, roles)) {
|
||||
return false
|
||||
}
|
||||
if (type && route.children) {
|
||||
route.children = filterChildren(route.children)
|
||||
}
|
||||
@@ -73,7 +76,11 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
|
||||
}
|
||||
}
|
||||
if (route.children != null && route.children && route.children.length) {
|
||||
route.children = filterAsyncRouter(route.children, route, type)
|
||||
route.children = filterAsyncRouter(route.children, route, type, roles)
|
||||
// 如果作为wrapper的父路由在过滤后子路由为空,则一并移除
|
||||
if (route.children.length === 0) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
delete route['children']
|
||||
delete route['redirect']
|
||||
|
||||
@@ -65,12 +65,15 @@ service.interceptors.request.use(config => {
|
||||
Promise.reject(error)
|
||||
})
|
||||
|
||||
let lastForbiddenNotificationTime = 0
|
||||
const FORBIDDEN_DEBOUNCE_MS = 2500 // 2.5秒内对403权限拒绝只提醒一次
|
||||
|
||||
// 响应拦截器
|
||||
service.interceptors.response.use(res => {
|
||||
// 未设置状态码则默认成功状态
|
||||
const code = res.data.code || 200
|
||||
// 获取错误信息
|
||||
const msg = errorCode[code] || res.data.msg || errorCode['default']
|
||||
// 获取错误信息,优先使用服务端返回的明确业务说明
|
||||
const msg = res.data.msg || errorCode[code] || errorCode['default']
|
||||
// 二进制数据则直接返回
|
||||
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
|
||||
return res.data
|
||||
@@ -88,6 +91,18 @@ service.interceptors.response.use(res => {
|
||||
})
|
||||
}
|
||||
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
|
||||
} else if (code === 403) {
|
||||
const forbiddenMsg = res.data.msg || errorCode['403'] || '当前操作没有权限'
|
||||
const now = Date.now()
|
||||
if (now - lastForbiddenNotificationTime > FORBIDDEN_DEBOUNCE_MS) {
|
||||
lastForbiddenNotificationTime = now
|
||||
Notification.error({
|
||||
title: '访问受限',
|
||||
message: forbiddenMsg,
|
||||
duration: 3500
|
||||
})
|
||||
}
|
||||
return Promise.reject(new Error(forbiddenMsg))
|
||||
} else if (code === 500) {
|
||||
Message({ message: msg, type: 'error' })
|
||||
return Promise.reject(new Error(msg))
|
||||
@@ -104,6 +119,19 @@ service.interceptors.response.use(res => {
|
||||
error => {
|
||||
console.log('err' + error)
|
||||
let { message } = error
|
||||
if (error.response && (error.response.status === 403 || (error.response.data && error.response.data.code === 403))) {
|
||||
const forbiddenMsg = (error.response.data && error.response.data.msg) || '当前操作没有权限'
|
||||
const now = Date.now()
|
||||
if (now - lastForbiddenNotificationTime > FORBIDDEN_DEBOUNCE_MS) {
|
||||
lastForbiddenNotificationTime = now
|
||||
Notification.error({
|
||||
title: '访问受限',
|
||||
message: forbiddenMsg,
|
||||
duration: 3500
|
||||
})
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
if (message === 'Network Error') {
|
||||
message = '后端接口连接异常'
|
||||
} else if (message.includes('timeout')) {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 角色专属自服务路由与菜单权限过滤工具
|
||||
*
|
||||
* 业务规则:
|
||||
* 1. 教师工作台 (train/me/teacher/index):仅对具备 train_teacher 角色的用户可见。
|
||||
* 2. 学员中心 (train/me/student/index):仅对具备 train_student 角色的用户可见。
|
||||
* 3. 即使拥有超级管理员 (admin) 或培训管理员 (train_admin) 角色,无真实身份/角色时自服务菜单一律隐藏。
|
||||
* 4. 普通管理与业务菜单(如人员管理下的教师信息、学员信息等)不受本规则过滤。
|
||||
*/
|
||||
|
||||
export function isSelfServiceRouteAllowed(route, roles = []) {
|
||||
if (!route) return false
|
||||
const comp = route.component
|
||||
// 教师工作台专属自服务组件
|
||||
if (comp === 'train/me/teacher/index') {
|
||||
return Array.isArray(roles) && roles.includes('train_teacher')
|
||||
}
|
||||
// 学员中心专属自服务组件
|
||||
if (comp === 'train/me/student/index') {
|
||||
return Array.isArray(roles) && roles.includes('train_student')
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度过滤路由树中的自服务专属路由
|
||||
*/
|
||||
export function filterSelfServiceRoutes(routes, roles = []) {
|
||||
if (!Array.isArray(routes)) return []
|
||||
return routes.reduce((acc, route) => {
|
||||
if (!isSelfServiceRouteAllowed(route, roles)) {
|
||||
return acc
|
||||
}
|
||||
const item = { ...route }
|
||||
if (item.children && Array.isArray(item.children)) {
|
||||
item.children = filterSelfServiceRoutes(item.children, roles)
|
||||
// 如果作为父容器wrapper在子路由被完全过滤后变为空,则移除该父容器
|
||||
if (item.children.length === 0 && (item.component === 'Layout' || !item.component || item.path === '/')) {
|
||||
return acc
|
||||
}
|
||||
}
|
||||
acc.push(item)
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
@@ -374,12 +374,6 @@ export default {
|
||||
methods: {
|
||||
initStudentData() {
|
||||
this.loadProfile()
|
||||
this.loadEnrollments()
|
||||
this.loadTimetable()
|
||||
this.loadReport()
|
||||
this.loadAttendance()
|
||||
this.loadDuration()
|
||||
this.loadPortrait()
|
||||
},
|
||||
loadProfile() {
|
||||
this.profileLoading = true
|
||||
@@ -388,6 +382,13 @@ export default {
|
||||
if (res.code === 200) {
|
||||
this.studentProfile = res.data
|
||||
this.identityErrorMsg = ''
|
||||
// 学员身份校验通过后,再并发加载学业与选课各Tab数据
|
||||
this.loadEnrollments()
|
||||
this.loadTimetable()
|
||||
this.loadReport()
|
||||
this.loadAttendance()
|
||||
this.loadDuration()
|
||||
this.loadPortrait()
|
||||
} else if (res.code === 403) {
|
||||
this.identityErrorMsg = res.msg || '当前登录账号未绑定任何学员档案,请联系管理员核对分配。'
|
||||
}
|
||||
|
||||
@@ -309,8 +309,6 @@ export default {
|
||||
methods: {
|
||||
initTeacherData() {
|
||||
this.loadProfile()
|
||||
this.loadMyPlans()
|
||||
this.loadMyTimetable()
|
||||
},
|
||||
loadProfile() {
|
||||
this.profileLoading = true
|
||||
@@ -319,6 +317,9 @@ export default {
|
||||
if (res.code === 200) {
|
||||
this.teacherProfile = res.data
|
||||
this.identityErrorMsg = ''
|
||||
// 教师身份校验通过后,再加载任教班级与课表日程
|
||||
this.loadMyPlans()
|
||||
this.loadMyTimetable()
|
||||
} else if (res.code === 403) {
|
||||
this.identityErrorMsg = res.msg || '当前登录账号未绑定任何教师档案,请联系管理员分配绑定。'
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user