diff --git a/src/App.vue b/src/App.vue index b3c5f63..dcb3877 100644 --- a/src/App.vue +++ b/src/App.vue @@ -17,6 +17,7 @@ import { Local, Session } from '/@/utils/storage'; import mittBus from '/@/utils/mitt'; import { needRoleSelection, isRoleDialogTriggered, setRoleDialogTriggered } from '/@/utils/roleSelect'; import setIntroduction from '/@/utils/setIconfont'; +import { listAllRole } from '/@/api/admin/role'; // 引入组件 const LockScreen = defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue')); @@ -58,6 +59,34 @@ onBeforeMount(() => { }); // 角色选择弹框是否已在本轮打开过(防止事件被触发两次) let roleDialogOpenedThisSession = false + +/** 校验缓存中的 roleId 是否仍在 listAllRole 结果中;若不存在则清除 roleId/roleCode/roleName 并返回 true(需要弹框) */ +async function validateCachedRoleId(): Promise { + const cachedRoleId = Local.get('roleId'); + if (cachedRoleId == null || cachedRoleId === '') return false; + try { + const res = await listAllRole(); + const data = res?.data; + const allRoles: any[] = Array.isArray(data) + ? data + : data && typeof data === 'object' + ? (Object.values(data) as any[]).flat() + : []; + const exists = allRoles.some( + (r: any) => r && String(r.roleId) === String(cachedRoleId) + ); + if (!exists) { + Local.remove('roleId'); + Local.remove('roleCode'); + Local.remove('roleName'); + return true; + } + return false; + } catch { + return false; + } +} + onMounted(() => { // 唯一入口:只通过事件打开,且只打开一次;延迟打开以等待异步组件挂载 mittBus.on('openRoleSelectDialog', () => { @@ -67,7 +96,7 @@ onMounted(() => { changeRoleFirRef.value?.open() }, 300) }) - nextTick(() => { + nextTick(async () => { // 监听布局配置弹窗点击打开 mittBus.on('openSettingsDrawer', () => { settingsRef.value.openDrawer(); @@ -81,10 +110,16 @@ onMounted(() => { if (Session.get('isTagsViewCurrenFull')) { stores.setCurrenFullscreen(Session.get('isTagsViewCurrenFull')); } - // 与请求拦截器共用同一逻辑:先设标志再 emit,由监听器统一打开(监听器内会延迟 300ms 以等待异步组件挂载) - if (Session.getToken() && needRoleSelection() && !isRoleDialogTriggered()) { - setRoleDialogTriggered(true) - mittBus.emit('openRoleSelectDialog') + // 有 token 时:先校验缓存 roleId 是否仍有效,无效则清缓存并弹框选角色 + if (Session.getToken()) { + const needOpenByInvalidRole = await validateCachedRoleId(); + if (needOpenByInvalidRole && !isRoleDialogTriggered()) { + setRoleDialogTriggered(true); + mittBus.emit('openRoleSelectDialog'); + } else if (!needOpenByInvalidRole && needRoleSelection() && !isRoleDialogTriggered()) { + setRoleDialogTriggered(true); + mittBus.emit('openRoleSelectDialog'); + } } }) }); diff --git a/src/layout/navBars/tagsView/tagsView.vue b/src/layout/navBars/tagsView/tagsView.vue index 1c712a5..d94749a 100644 --- a/src/layout/navBars/tagsView/tagsView.vue +++ b/src/layout/navBars/tagsView/tagsView.vue @@ -114,6 +114,34 @@ const isActive = (v: RouteItem) => { } } }; +/** 标题为「轮播图」的 tag 不展示(切换角色后常误出现,直接过滤) */ +const isCarouselTag = (v: RouteItem) => other.setTagsViewNameI18n(v) === '轮播图'; +/** 仅首页作为固定 tag,其它 isAffix(如后端菜单的轮播图)不再默认占一条 tag */ +const isAffixTagAllowed = (v: RouteItem) => v.path === '/home' || v.path === '/home/index'; +/** 后端菜单 sortOrder/sort_order/weight 为 1 的项常被误当“默认”加载为 tag,排除(首页除外) */ +const isSortOrderOneExcluded = (v: RouteItem) => { + const meta = v.meta as any; + const order = + meta?.sortOrder ?? meta?.sort_order ?? meta?.weight ?? (v as any).sort_order ?? (v as any).weight; + return Number(order) === 1 && !isAffixTagAllowed(v); +}; +/** 递归扁平化路由(含 children),用于校验 path 是否在当前角色菜单中 */ +const flattenRoutes = (routes: RouteItem[]): RouteItem[] => { + let list: RouteItem[] = []; + (routes || []).forEach((r: RouteItem) => { + list.push(r); + if ((r as any).children?.length) list.push(...flattenRoutes((r as any).children)); + }); + return list; +}; +/** 当前 tag 的 path 是否存在于当前角色路由列表中(避免恢复出“不存在的 tag”) */ +const pathInCurrentRoutes = (tag: RouteItem): boolean => { + const flat = flattenRoutes(state.tagsViewRoutesList); + return flat.some( + (r: RouteItem) => + r.path === tag.path || (r.meta?.isDynamic && (r.meta as any).isDynamicPath === tag.path) + ); +}; // 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录 const addBrowserSetSession = (tagsViewList: Array) => { Session.set('tagsViewList', tagsViewList); @@ -128,11 +156,20 @@ const getTagsViewRoutes = async () => { }; // pinia 中获取路由信息:如果是设置了固定的(isAffix),进行初始化显示 const initTagsView = async () => { - if (Session.get('tagsViewList') && getThemeConfig.value.isCacheTagsView) { - state.tagsViewList = await Session.get('tagsViewList'); + const cached = Session.get('tagsViewList'); + const hasValidCache = cached && getThemeConfig.value.isCacheTagsView && Array.isArray(cached); + // 仅当当前角色已有路由列表时,才从缓存恢复;恢复时只保留当前路由中存在的 path,避免切换角色后出现“不存在的 tag” + if (hasValidCache && state.tagsViewRoutesList.length > 0) { + state.tagsViewList = cached.filter( + (v: RouteItem) => + !isCarouselTag(v) && + !isSortOrderOneExcluded(v) && + pathInCurrentRoutes(v) && + (v.meta?.isAffix ? isAffixTagAllowed(v) : true) + ); } else { await state.tagsViewRoutesList.map((v: RouteItem) => { - if (v.meta?.isAffix && !v.meta.isHide) { + if (v.meta?.isAffix && !v.meta.isHide && !isCarouselTag(v) && isAffixTagAllowed(v) && !isSortOrderOneExcluded(v)) { v.url = setTagsViewHighlight(v); state.tagsViewList.push({ ...v }); storesKeepALiveNames.addCachedView(v); @@ -158,6 +195,8 @@ const solveAddTagsView = async (path: string, to?: RouteToFrom) => { // 防止:Avoid app logic that relies on enumerating keys on a component instance. The keys will be empty in production mode to avoid performance overhead. let findItem = state.tagsViewRoutesList.find((v: RouteItem) => v.path === isDynamicPath); if (!findItem) return false; + if (isCarouselTag(findItem)) return false; + if (isSortOrderOneExcluded(findItem)) return false; if (findItem.meta.isAffix) return false; if (findItem.meta.isLink && !findItem.meta.isIframe) return false; to?.meta?.isDynamic ? (findItem.params = to.params) : (findItem.query = to?.query); @@ -211,6 +250,8 @@ const addTagsView = (path: string, to?: RouteToFrom) => { item = state.tagsViewRoutesList.find((v: RouteItem) => v.path === path); } if (!item) return false; + if (isCarouselTag(item)) return false; + if (isSortOrderOneExcluded(item)) return false; if (item?.meta?.isLink && !item.meta.isIframe) return false; if (to?.meta?.isDynamic) item.params = to?.params ? to?.params : route.params; else item.query = to?.query ? to?.query : route.query; @@ -279,7 +320,7 @@ const closeOtherTagsView = (path: string) => { if (Session.get('tagsViewList')) { state.tagsViewList = []; Session.get('tagsViewList').map((v: RouteItem) => { - if (v.meta?.isAffix && !v.meta.isHide) { + if (v.meta?.isAffix && !v.meta?.isHide && !isCarouselTag(v) && pathInCurrentRoutes(v) && isAffixTagAllowed(v) && !isSortOrderOneExcluded(v)) { v.url = setTagsViewHighlight(v); storesKeepALiveNames.delOthersCachedViews(v); state.tagsViewList.push({ ...v }); @@ -295,7 +336,7 @@ const closeAllTagsView = () => { storesKeepALiveNames.delAllCachedViews(); state.tagsViewList = []; Session.get('tagsViewList').map((v: RouteItem) => { - if (v.meta?.isAffix && !v.meta.isHide) { + if (v.meta?.isAffix && !v.meta?.isHide && !isCarouselTag(v) && pathInCurrentRoutes(v) && isAffixTagAllowed(v) && !isSortOrderOneExcluded(v)) { v.url = setTagsViewHighlight(v); state.tagsViewList.push({ ...v }); router.push({ path: state.tagsViewList[state.tagsViewList.length - 1].path }); @@ -547,7 +588,7 @@ onBeforeMount(() => { router.push('/home/index'); state.tagsViewList = []; state.tagsViewRoutesList.map((v: RouteItem) => { - if (v.meta?.isAffix && !v.meta.isHide) { + if (v.meta?.isAffix && !v.meta?.isHide && !isCarouselTag(v) && isAffixTagAllowed(v) && !isSortOrderOneExcluded(v)) { v.url = setTagsViewHighlight(v); state.tagsViewList.push({ ...v }); } diff --git a/src/stores/userInfo.ts b/src/stores/userInfo.ts index 9c40d33..8306fef 100644 --- a/src/stores/userInfo.ts +++ b/src/stores/userInfo.ts @@ -46,7 +46,6 @@ export const useUserInfo = defineStore('userInfo', { return new Promise((resolve, reject) => { login(data) .then((res) => { - debugger this.setTokenCache(res.access_token, res.refresh_token); Local.remove('roleCode'); Local.remove('roleName'); diff --git a/src/views/admin/system/role/change-role.vue b/src/views/admin/system/role/change-role.vue index 6dcb686..7e96678 100644 --- a/src/views/admin/system/role/change-role.vue +++ b/src/views/admin/system/role/change-role.vue @@ -42,7 +42,7 @@