From 15bcb072f991806372c82f42f2dbbb88e491fe4e Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sun, 2 Aug 2026 19:09:23 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BA=86=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E8=A7=92=E8=89=B2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/auth.ts | 20 +++++++++++++--- src/layouts/DefaultLayout.vue | 44 ++++++++++++++++++++++++++++------- src/stores/user.ts | 19 +++++++++------ 3 files changed, 64 insertions(+), 19 deletions(-) diff --git a/src/api/auth.ts b/src/api/auth.ts index 69aadbe..15f059c 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -36,9 +36,23 @@ export const refreshToken = (): Promise<{ AccessToken: string }> => { return post<{ AccessToken: string }>('/Auth/RefreshToken') } -/** 获取当前用户角色列表 */ -export const getUserRoles = (): Promise => { - return get('/Auth/GetUserRoles') +/** 角色项(后端 GetUserRoles 返回的元素结构) */ +export interface RoleItem { + RoleUUID?: string + RoleName: string + RoleStatus?: string | number + AssignScope?: string + RoleDesc?: string + CompanyName?: string + CompanyShort?: string + CompanyUUID?: string + /** 是否为当前激活角色 */ + active?: boolean +} + +/** 获取当前用户角色列表(解包后端 data.roles 两层结构) */ +export const getUserRoles = (): Promise => { + return get<{ roles: RoleItem[] }>('/Auth/GetUserRoles').then((res) => res.roles || []) } /** 切换当前角色 */ diff --git a/src/layouts/DefaultLayout.vue b/src/layouts/DefaultLayout.vue index e9b7f72..358bae3 100644 --- a/src/layouts/DefaultLayout.vue +++ b/src/layouts/DefaultLayout.vue @@ -14,7 +14,7 @@ import { ElMessage } from 'element-plus' import { useAppStore } from '@/stores/app' import { useUserStore } from '@/stores/user' import { useTabsStore } from '@/stores/tabs' -import { getAvatarUrl } from '@/api/auth' +import { getAvatarUrl, type RoleItem } from '@/api/auth' import TabBar from '@/components/TabBar.vue' import RouteProgress from '@/components/RouteProgress.vue' import PageMask from '@/components/PageMask.vue' @@ -55,14 +55,14 @@ onMounted(() => { } }) -/** 切换身份/角色 */ -async function handleSwitchRole(role: string): Promise { - if (role === userStore.currentRole || roleSwitching.value) return +/** 切换身份/角色(role 为 RoleItem 对象) */ +async function handleSwitchRole(role: RoleItem): Promise { + if (role.RoleName === userStore.currentRole || roleSwitching.value) return roleSwitching.value = true try { await userStore.switchRole(role) tabsStore.reset() - ElMessage.success(`已切换到:${role}`) + ElMessage.success(`已切换到:${role.RoleName}`) router.push('/dashboard') } catch { ElMessage.error('身份切换失败') @@ -223,14 +223,14 @@ const isFaIcon = (icon: string | undefined | null): boolean => {
- + - {{ role }} + {{ role.RoleName }}
@@ -439,6 +439,9 @@ const isFaIcon = (icon: string | undefined | null): boolean => { } :deep(.el-menu) { + // min-width: 0 覆盖默认 min-width: 240px,允许跟随容器收缩到 64px + min-width: 0; + width: 100%; border-right: none; padding: 8px 0; // 宽度跟随侧边栏平滑收缩 @@ -460,6 +463,11 @@ const isFaIcon = (icon: string | undefined | null): boolean => { transition: opacity 0.25s ease; } + // 一级菜单(有二级菜单的):覆盖 Element Plus 默认 min-width: 200px,允许跟随容器收缩 + :deep(.el-sub-menu) { + min-width: 0; + } + // 二级菜单容器:折叠时高度平滑收起,展开侧边栏后恢复 // min-width: 0 允许其跟随容器收缩(否则默认 min-width: 200px 会撑住 sub-menu 宽度) :deep(.el-menu--inline) { @@ -473,6 +481,24 @@ const isFaIcon = (icon: string | undefined | null): boolean => { // 注意:icon 保持与展开态完全相同的位置(padding/margin 不变),仅宽度收缩 + 文字淡出 &.collapsed { width: 64px; + // 一级菜单(sub-menu)宽度锁定为容器宽 + :deep(.el-sub-menu) { + width: 100%; + min-width: 0; + } + // sub-menu 标题:padding-left 与 el-menu-item 对齐(icon 原地不动), + // padding-right 收紧(默认 44px 为箭头预留,会撑破 64px) + :deep(.el-sub-menu__title) { + width: 100%; + min-width: 0; + padding-left: 20px; + padding-right: 20px; + } + // 二级菜单容器宽度锁定为容器宽 + :deep(.el-menu--inline) { + width: 100%; + min-width: 0; + } // 隐藏左侧指示条 :deep(.el-menu-item), :deep(.el-sub-menu__title) { diff --git a/src/stores/user.ts b/src/stores/user.ts index 94f5f36..dacd3c3 100644 --- a/src/stores/user.ts +++ b/src/stores/user.ts @@ -1,14 +1,14 @@ import { defineStore } from 'pinia' import { ref, computed } from 'vue' import type { UserInfo, MenuItem } from '@/types/api' -import { getCurrentUser, getUserRoles, switchRole as apiSwitchRole, login as apiLogin, logout as apiLogout, type LoginParams } from '@/api/auth' +import { getCurrentUser, getUserRoles, switchRole as apiSwitchRole, login as apiLogin, logout as apiLogout, type LoginParams, type RoleItem } from '@/api/auth' import { getMenuList } from '@/api/menu' /** 用户状态管理:当前登录用户、角色、菜单、权限 */ export const useUserStore = defineStore('user', () => { // === 状态 === const userInfo = ref(null) - const roles = ref([]) + const roles = ref([]) const menus = ref([]) const token = ref('') const loading = ref(false) @@ -23,9 +23,10 @@ export const useUserStore = defineStore('user', () => { try { const raw = localStorage.getItem(CACHE_KEY) if (!raw) return - const cached = JSON.parse(raw) as { userInfo?: UserInfo | null; menus?: MenuItem[] | null } + const cached = JSON.parse(raw) as { userInfo?: UserInfo | null; menus?: MenuItem[] | null; roles?: RoleItem[] | null } if (cached.userInfo) userInfo.value = cached.userInfo if (cached.menus) menus.value = cached.menus + if (cached.roles?.length) roles.value = cached.roles } catch { // 缓存损坏:清除避免反复解析失败 localStorage.removeItem(CACHE_KEY) @@ -37,7 +38,7 @@ export const useUserStore = defineStore('user', () => { try { localStorage.setItem( CACHE_KEY, - JSON.stringify({ userInfo: userInfo.value, menus: menus.value }), + JSON.stringify({ userInfo: userInfo.value, menus: menus.value, roles: roles.value }), ) } catch { // 写入失败(隐私模式/配额满):忽略,不影响功能 @@ -76,6 +77,7 @@ export const useUserStore = defineStore('user', () => { await apiLogin(params) await fetchUserInfo() await fetchMenus() + await fetchRoles() } finally { loading.value = false } @@ -125,15 +127,18 @@ export const useUserStore = defineStore('user', () => { } /** 获取角色列表 */ - async function fetchRoles(): Promise { + async function fetchRoles(): Promise { const list = await getUserRoles() roles.value = list + saveToCache() return list } /** 切换当前角色(身份切换) */ - async function switchRole(role: string): Promise { - await apiSwitchRole(role) + async function switchRole(role: RoleItem | string): Promise { + // 传对象时取 RoleName(后端 SwitchRole 期望角色名) + const roleName = typeof role === 'string' ? role : role.RoleName + await apiSwitchRole(roleName) // 刷新用户信息和菜单(不同角色可能有不同菜单权限) await fetchUserInfo() await fetchMenus()