Files
his/healthlink-his-ui/src/permission.js
华佗 babf62083a fix: 切换账户后路由权限校验 — 防止通过旧标签/URL访问无权限页面
根因: router.beforeEach 在角色加载后 return true,不检查目标路由
是否在当前用户已注册的路由列表中。导致切换账户后,通过旧标签
或直接输入 URL 可访问前一个用户的页面。

修复: 在 return true 前增加 router.resolve() 检查,若目标路由
未注册(matched.length === 0)则拦截并提示无权访问。

数据库验证: 护士角色(role_id=201)确实没有住院医生工作站
(menu_id=288)的 sys_role_menu 权限,后端 getRouters 返回
正确。问题纯粹在前端路由守卫。
2026-06-09 16:55:17 +08:00

123 lines
3.8 KiB
JavaScript
Executable File

import router from './router'
import { ElMessage } from 'element-plus'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isHttp, isPathMatch } from '@/utils/validate'
import { isRelogin } from '@/utils/request'
import useUserStore from '@/store/modules/user'
import useLockStore from '@/store/modules/lock'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
import useNoticeStore from '@/store/modules/notice'
import useTagsViewStore from '@/store/modules/tagsView'
// 全局变量,用于控制公告弹窗只显示一次
let hasShownNoticePopup = false
NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/register']
const isWhiteList = (path) => {
return whiteList.some(pattern => isPathMatch(pattern, path))
}
router.beforeEach(async (to, from) => {
NProgress.start()
if (getToken()) {
to.meta.title && useSettingsStore().setTitle(to.meta.title)
const isLock = useLockStore().isLock
if (to.path === '/login') {
NProgress.done()
return { path: '/' }
}
if (isWhiteList(to.path)) {
return true
}
if (isLock && to.path !== '/lock') {
NProgress.done()
return { path: '/lock' }
}
if (!isLock && to.path === '/lock') {
NProgress.done()
return { path: '/' }
}
if (useUserStore().roles.length === 0) {
isRelogin.show = true
try {
await useUserStore().getInfo()
isRelogin.show = false
const accessRoutes = await usePermissionStore().generateRoutes()
accessRoutes.forEach(route => {
if (!isHttp(route.path)) {
if (!router.hasRoute(route.name)) {
try {
router.addRoute(route)
} catch (e) {
// 路由名重复时跳过,不影响其他路由加载
console.warn('路由添加跳过(名称重复):', route.name, e.message)
}
}
}
})
useNoticeStore().startPolling()
useTagsViewStore().loadPersistedViews()
return { ...to, replace: true }
} catch (err) {
console.error('路由加载失败:', err)
await useUserStore().logOut()
ElMessage.error(err.message || '登录已过期')
return { path: '/login' }
}
}
// 铁律: 路由权限校验 — 目标路由必须在已注册的路由中存在
// 防止切换账户后,通过旧标签或直接输入 URL 访问无权限页面
const resolved = router.resolve(to)
if (resolved.matched.length === 0 || resolved.name === 'NotFound') {
// 路由不存在(未注册),拒绝导航
ElMessage.warning('无权访问该页面')
return { path: '/' }
}
return true
} else {
if (isWhiteList(to.path)) {
return true
}
NProgress.done()
return `/login?redirect=${to.fullPath}`
}
})
router.afterEach(() => {
NProgress.done()
// 登录成功后显示公告弹窗(仅限非登录页面且未显示过)
const token = getToken()
const isLoginPage = router.currentRoute.value.path === '/login'
if (token && !isLoginPage && !hasShownNoticePopup) {
setTimeout(() => {
showNoticePopupGlobally()
hasShownNoticePopup = true
}, 1500)
}
})
// 全局函数:显示公告弹窗
function showNoticePopupGlobally() {
try {
const layouts = document.querySelectorAll('.app-wrapper')
for (const layout of layouts) {
const noticePopupRef = layout.__vue_app__?.config.globalProperties.$refs?.noticePopupRef
if (noticePopupRef && noticePopupRef.showNotice) {
noticePopupRef.showNotice()
return
}
}
window.dispatchEvent(new CustomEvent('show-notice-popup'))
} catch (error) {
console.error('显示公告弹窗失败:', error)
}
}