404页面修改
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
zpc 2026-04-20 10:20:49 +08:00
parent d6db9996be
commit 12c7c59595
2 changed files with 174 additions and 3 deletions

View File

@ -107,6 +107,26 @@ router.beforeEach(async (to, _from, next) => {
}
// 生成动态路由
const accessRoutes = await permissionStore.generateRoutes()
// 如果用户没有任何菜单权限,提示并退出登录
if (!accessRoutes || accessRoutes.length === 0) {
console.warn('当前用户没有任何菜单权限')
dynamicRoutesLoaded = false
const { ElMessageBox } = await import('element-plus')
const roles = userStore.userInfo?.roles?.join('、') || '无'
await ElMessageBox.alert(
`当前账号未分配菜单权限,无法进入系统。<br/><br/>` +
`<b>账号:</b>${userStore.userInfo?.username}<br/>` +
`<b>角色:</b>${roles}<br/><br/>` +
`请联系超级管理员,在「系统管理 - 角色管理」中为对应角色分配菜单,或在「管理员管理」中为该账号分配专属菜单。`,
'无菜单权限',
{ dangerouslyUseHTMLString: true, confirmButtonText: '返回登录', type: 'warning' }
).catch(() => {})
userStore.resetState()
next(`/login?redirect=${to.path}`)
return
}
// 添加动态路由
accessRoutes.forEach(route => {
router.addRoute(route)

View File

@ -3,19 +3,137 @@
<div class="error-content">
<h1>404</h1>
<p>抱歉您访问的页面不存在</p>
<el-button type="primary" @click="goHome">返回首页</el-button>
<!-- 权限提示信息 -->
<div v-if="permissionInfo.show" class="permission-info">
<el-alert type="warning" :closable="false" show-icon>
<template #title>
<span>当前账号缺少访问该页面的菜单权限</span>
</template>
<template #default>
<div class="info-detail">
<p><span class="label">访问路径</span>{{ permissionInfo.path }}</p>
<p><span class="label">所需菜单</span>{{ permissionInfo.menuName || '未知' }}</p>
<p v-if="permissionInfo.permission"><span class="label">权限标识</span>{{ permissionInfo.permission }}</p>
<p><span class="label">当前账号</span>{{ permissionInfo.username }}</p>
<p><span class="label">当前角色</span>{{ permissionInfo.roles }}</p>
<p class="tip">请联系超级管理员角色管理中为对应角色分配菜单或在管理员管理中分配专属菜单</p>
</div>
</template>
</el-alert>
</div>
<div class="error-actions">
<el-button type="primary" @click="goHome">返回首页</el-button>
<el-button @click="handleLogout">退出登录</el-button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { reactive, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useUserStore } from '@/store/modules/user'
import { usePermissionStore } from '@/store/modules/permission'
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
const permissionStore = usePermissionStore()
/**
* 已知路径与菜单名称的映射表
*/
const pathMenuMap: Record<string, { name: string; permission?: string }> = {
'/dashboard': { name: '数据统计' },
'/system': { name: '系统管理' },
'/system/admin': { name: '系统管理 > 管理员管理', permission: 'system:admin:view' },
'/system/role': { name: '系统管理 > 角色管理', permission: 'system:role:view' },
'/system/menu': { name: '系统管理 > 菜单管理', permission: 'system:menu:view' },
'/system/dept': { name: '系统管理 > 部门管理', permission: 'system:dept:view' },
'/system/dict': { name: '系统管理 > 字典管理', permission: 'system:dict:view' },
'/system/log': { name: '系统管理 > 操作日志', permission: 'system:log:view' },
'/system/config': { name: '系统管理 > 系统配置', permission: 'system:config:view' },
'/user': { name: '用户管理' },
'/user/list': { name: '用户管理 > 用户列表', permission: 'user:view' },
'/assessment': { name: '测评管理' },
'/assessment/type': { name: '测评管理 > 测评类型', permission: 'assessment:view' },
'/assessment/question': { name: '测评管理 > 题库管理', permission: 'question:view' },
'/assessment/category': { name: '测评管理 > 报告分类', permission: 'category:view' },
'/assessment/conclusion': { name: '测评管理 > 报告结论', permission: 'conclusion:view' },
'/assessment/scoreOption': { name: '测评管理 > 评分标准', permission: 'assessment:view' },
'/assessment/record': { name: '测评管理 > 测评记录', permission: 'assessmentRecord:view' },
'/assessment/report-page-config': { name: '测评管理 > 报告页面配置', permission: 'assessment:view' },
'/order': { name: '订单管理' },
'/order/list': { name: '订单管理 > 订单列表', permission: 'order:view' },
'/planner': { name: '规划师管理' },
'/planner/list': { name: '规划师管理 > 规划师列表', permission: 'planner:view' },
'/planner/booking': { name: '规划师管理 > 预约记录', permission: 'booking:view' },
'/distribution': { name: '分销管理' },
'/distribution/invite-code': { name: '分销管理 > 邀请码管理', permission: 'inviteCode:view' },
'/distribution/commission': { name: '分销管理 > 佣金记录', permission: 'commission:view' },
'/distribution/withdrawal': { name: '分销管理 > 提现管理', permission: 'withdrawal:view' },
'/content': { name: '内容管理' },
'/content/banner': { name: '内容管理 > 轮播图管理', permission: 'banner:view' },
'/content/promotion': { name: '内容管理 > 宣传图管理', permission: 'promotion:view' },
'/content/business-page': { name: '内容管理 > 业务介绍页', permission: 'businessPage:view' },
'/content/navigation': { name: '内容管理 > 首页导航管理', permission: 'content:view' },
}
const permissionInfo = reactive({
show: false,
path: '',
menuName: '',
permission: '',
username: '',
roles: ''
})
/**
* 尝试从后端菜单数据中查找路径对应的菜单名称
*/
function findMenuNameFromStore(path: string): string | null {
const menus = permissionStore.menus
if (!menus || menus.length === 0) return null
function search(items: any[]): string | null {
for (const item of items) {
if (item.path === path) return item.name
if (item.children?.length) {
const found = search(item.children)
if (found) return found
}
}
return null
}
return search(menus)
}
onMounted(() => {
// 访 query.redirect referrer
const originalPath = (route.query.redirect as string) || route.redirectedFrom?.path || ''
if (originalPath && originalPath !== '/404' && userStore.userInfo) {
const mapped = pathMenuMap[originalPath]
const storeMenuName = findMenuNameFromStore(originalPath)
permissionInfo.show = true
permissionInfo.path = originalPath
permissionInfo.menuName = storeMenuName || mapped?.name || originalPath
permissionInfo.permission = mapped?.permission || ''
permissionInfo.username = userStore.userInfo.username || ''
permissionInfo.roles = userStore.userInfo.roles?.join('、') || '无'
}
})
const goHome = () => {
router.push('/')
}
const handleLogout = async () => {
await userStore.logout()
}
</script>
<style scoped>
@ -29,6 +147,9 @@ const goHome = () => {
.error-content {
text-align: center;
max-width: 560px;
width: 100%;
padding: 0 20px;
}
.error-content h1 {
@ -40,6 +161,36 @@ const goHome = () => {
.error-content p {
font-size: 18px;
color: #606266;
margin: 20px 0 30px;
margin: 20px 0 20px;
}
.permission-info {
margin-bottom: 24px;
text-align: left;
}
.info-detail p {
margin: 6px 0;
font-size: 13px;
color: #606266;
line-height: 1.6;
}
.info-detail .label {
color: #909399;
display: inline-block;
min-width: 80px;
}
.info-detail .tip {
margin-top: 12px;
color: #e6a23c;
font-size: 12px;
}
.error-actions {
display: flex;
gap: 12px;
justify-content: center;
}
</style>