From 41b982c7b569388c1acdbb65665c6eeb33b022bd Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 3 Aug 2026 00:41:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E6=9E=A2=E6=9C=BA?= =?UTF-8?q?=E5=8D=8F=E5=BE=8B=E7=9B=B8=E5=85=B3=E9=A1=B5=E9=9D=A2=E8=A2=AB?= =?UTF-8?q?=E8=AF=AF=E5=88=A0=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy.ps1 | 2 +- src/router/index.ts | 91 ++++++++++++++++++++++++--------------------- src/stores/user.ts | 2 +- 3 files changed, 51 insertions(+), 44 deletions(-) diff --git a/deploy.ps1 b/deploy.ps1 index 3b06205..4bfcbab 100644 --- a/deploy.ps1 +++ b/deploy.ps1 @@ -1,4 +1,4 @@ -# GERP 前端部署脚本:全量编译并同步产物到生产目录(TempLate/GERP) +# GERP 前端部署脚本:全量编译并同步产物到生产目录(TempLate/GERP) # # 用途: # 1. 清理旧构建产物(dist 与 TempLate/GERP/assets),保证无 hash 残留 diff --git a/src/router/index.ts b/src/router/index.ts index c9d975d..15e2c55 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,5 +1,6 @@ import { nextTick } from 'vue' import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router' +import { ElMessage } from 'element-plus' import { useUserStore } from '@/stores/user' import { useProgressStore } from '@/stores/progress' import { useTabsStore } from '@/stores/tabs' @@ -228,37 +229,37 @@ export const staticRoutes: RouteRecordRaw[] = [ component: () => import('@/views/system/DbTable.vue'), meta: { title: '表结构管理', requiresAuth: true }, }, - // === 枢机协律(CreateE):旧页面 iframe 直载(LegacyPage) === - { - path: 'private/CreateE/shop', - name: 'PrivateCreateEShop', - component: () => import('@/views/LegacyPage.vue'), - meta: { title: '商店', requiresAuth: true, menuGuarded: true, legacySrc: '/pages/private/CreateE/shop.html' }, - }, - { - path: 'private/CreateE/Recharge', - name: 'PrivateCreateERecharge', - component: () => import('@/views/LegacyPage.vue'), - meta: { title: '储值充值', requiresAuth: true, menuGuarded: true, legacySrc: '/pages/private/CreateE/Recharge.html' }, - }, - { - path: 'private/CreateE/wallet', - name: 'PrivateCreateEWallet', - component: () => import('@/views/LegacyPage.vue'), - meta: { title: '钱包', requiresAuth: true, menuGuarded: true, legacySrc: '/pages/private/CreateE/wallet.html' }, - }, - { - path: 'private/CreateE/enterprise', - name: 'PrivateCreateEEnterprise', - component: () => import('@/views/LegacyPage.vue'), - meta: { title: '企业管理', requiresAuth: true, legacySrc: '/pages/private/CreateE/enterprise.html' }, - }, - { - path: 'private/CreateE/StoreValue', - name: 'PrivateCreateEStoreValue', - component: () => import('@/views/LegacyPage.vue'), - meta: { title: '储值查看', requiresAuth: true, menuGuarded: true, legacySrc: '/pages/private/CreateE/StoreValue.html' }, - }, + // === 枢机协律(CreateE):旧页面 iframe 直载(LegacyPage) === + { + path: 'private/CreateE/shop', + name: 'PrivateCreateEShop', + component: () => import('@/views/LegacyPage.vue'), + meta: { title: '商店', requiresAuth: true, menuGuarded: true, legacySrc: '/pages/private/CreateE/shop.html' }, + }, + { + path: 'private/CreateE/Recharge', + name: 'PrivateCreateERecharge', + component: () => import('@/views/LegacyPage.vue'), + meta: { title: '储值充值', requiresAuth: true, menuGuarded: true, legacySrc: '/pages/private/CreateE/Recharge.html' }, + }, + { + path: 'private/CreateE/wallet', + name: 'PrivateCreateEWallet', + component: () => import('@/views/LegacyPage.vue'), + meta: { title: '钱包', requiresAuth: true, menuGuarded: true, legacySrc: '/pages/private/CreateE/wallet.html' }, + }, + { + path: 'private/CreateE/enterprise', + name: 'PrivateCreateEEnterprise', + component: () => import('@/views/LegacyPage.vue'), + meta: { title: '企业管理', requiresAuth: true, legacySrc: '/pages/private/CreateE/enterprise.html' }, + }, + { + path: 'private/CreateE/StoreValue', + name: 'PrivateCreateEStoreValue', + component: () => import('@/views/LegacyPage.vue'), + meta: { title: '储值查看', requiresAuth: true, menuGuarded: true, legacySrc: '/pages/private/CreateE/StoreValue.html' }, + }, // === 阶段 8:SKU(移动端 C 端,待决策是否拆分独立工程) === { path: 'sku', @@ -298,6 +299,23 @@ let justOpenedNewTab = false router.beforeEach(async (to, _from, next) => { const tabsStore = useTabsStore() justOpenedNewTab = false + + // === 菜单守卫(先于 addTab,避免受限页 tab 误入标签栏)=== + // menuGuarded 路由(后端菜单驱动的业务页)必须出现在当前用户可见菜单树中。 + // 用途:URL 直达私有页(如 #/private/CreateE/wallet)时,若菜单树不含该路径 + // (当前角色公司无权),直接跳回 dashboard,阻止访问公司私有业务。 + // 注意:不能 next({name:'NotFound'})——catch-all 保留原路径会再次触发守卫, + // 造成导航无限循环 + tab 反复增删(栈溢出)。直接 next('/dashboard') 使 + // URL/tabs/路由三方一致,Dashboard 只挂载一次,无循环。 + // menus 未加载(首次进入/未登录)时放行,交由下方 restoreSession 处理登录与菜单。 + const userStore = useUserStore() + if (to.meta.requiresAuth === true && to.meta.menuGuarded && userStore.menus.length > 0 && !userStore.hasMenuPath(to.path)) { + useProgressStore().done() + ElMessage.warning('无权访问该页面') + next('/dashboard') + return + } + // 立即添加 tab:与 loading 同步出现,不等登录检查或组件加载 // addTab 返回 true=新 tab(首次打开,需 loading),false=已存在(已缓存,瞬时切换) if (to.meta.requiresAuth === true && to.meta.title) { @@ -316,8 +334,6 @@ router.beforeEach(async (to, _from, next) => { useProgressStore().done() } - const userStore = useUserStore() - // 登录页:若已处于登录状态(内存态或 Cookie 会话可恢复),跳转面板 if (to.name === 'Login') { if (userStore.isLoggedIn) { @@ -353,15 +369,6 @@ router.beforeEach(async (to, _from, next) => { } } - // 菜单守卫:menuGuarded 路由(后端菜单驱动的业务页)必须出现在当前用户可见菜单树中 - // 用途:URL 直达私有页(如 #/private/CreateE/wallet)时,若菜单树不含该路径(当前角色 - // 公司无权),则拦截到 404,避免绕过菜单过滤直接访问公司私有业务 - if (to.meta.menuGuarded && !userStore.hasMenuPath(to.path)) { - tabsStore.removeTab(to.path) - next({ name: 'NotFound', replace: true }) - return - } - next() }) diff --git a/src/stores/user.ts b/src/stores/user.ts index e02c258..0c28485 100644 --- a/src/stores/user.ts +++ b/src/stores/user.ts @@ -221,4 +221,4 @@ export const useUserStore = defineStore('user', () => { restoreSession, reset, } -}) +})