feat(auth): 补齐退出登录交互并改为真实退出

- 侧栏底部用户区改为按原型 #userMenu 展开的用户菜单
  (个人信息 / 修改密码 / 退出登录),支持点击浮层外部与 Esc 收起
- 退出登录增加二次确认;确认后注销服务端会话并整页跳转回登录页,
  确保 token / tokenExpiresAt / userInfo(含 permissions)与内存态一并清空
- clearSession() 重置跨账号内存态(院区列表与选中项),
  否则 SPA 内退出后换账号,新账号会看到上一个账号的院区
- 新增只读的个人信息弹窗(后端暂无「修改本人资料」接口,不提供编辑入口)
- 顺带修正 stores/login.ts 中被 prettier 判为不合规的缩进
This commit is contained in:
yelan
2026-10-06 16:30:57 +08:00
parent eb4693cf65
commit 439af9caaf
6 changed files with 510 additions and 110 deletions
+31 -27
View File
@@ -17,6 +17,7 @@ import type {
PasswordChangeRequest,
} from '@/types/auth'
import { clearAuthStorage, readStoredAuthSession, saveAuthSession } from '@/utils/auth-storage'
import { useAppStore } from '@/stores/app'
export const useLoginStore = defineStore('login', () => {
const storedSession = readStoredAuthSession()
@@ -48,36 +49,36 @@ export const useLoginStore = defineStore('login', () => {
)
}
/**
* 已下线菜单:后端菜单资源里仍然下发、但前端已经不再提供页面的菜单。
*
* 「系统管理 → 报表分析」(`/report/index`)已整体下线,后端菜单资源暂时无法同步下架,
* 所以在前端统一屏蔽:侧边栏不展示,路由守卫也会把它当成无权限菜单重定向走,
* 避免出现点进去只有「前端暂未接入组件」占位页的死链。
* 后端下架该菜单资源后,这个集合可以删掉。
*/
const RETIRED_MENU_PATHS = new Set(['/report/index'])
/**
* 已下线菜单:后端菜单资源里仍然下发、但前端已经不再提供页面的菜单。
*
* 「系统管理 → 报表分析」(`/report/index`)已整体下线,后端菜单资源暂时无法同步下架,
* 所以在前端统一屏蔽:侧边栏不展示,路由守卫也会把它当成无权限菜单重定向走,
* 避免出现点进去只有「前端暂未接入组件」占位页的死链。
* 后端下架该菜单资源后,这个集合可以删掉。
*/
const RETIRED_MENU_PATHS = new Set(['/report/index'])
function isRetiredMenu(menu: PermissionResourceNode) {
return RETIRED_MENU_PATHS.has(menu.routePath?.replace(/\/$/, '') ?? '')
}
function isRetiredMenu(menu: PermissionResourceNode) {
return RETIRED_MENU_PATHS.has(menu.routePath?.replace(/\/$/, '') ?? '')
}
const accessibleMenus = computed(() =>
(authorization.value?.navigation ?? [])
.filter((directory) => directory.resourceType === 'DIR' && directory.status === 'ENABLED')
.flatMap((directory) => directory.children ?? [])
.filter((menu) => !isRetiredMenu(menu)),
)
const accessibleMenus = computed(() =>
(authorization.value?.navigation ?? [])
.filter((directory) => directory.resourceType === 'DIR' && directory.status === 'ENABLED')
.flatMap((directory) => directory.children ?? [])
.filter((menu) => !isRetiredMenu(menu)),
)
/** 侧边栏用的导航树:结构与后端一致,只剔除已下线菜单(空目录由使用方过滤) */
const visibleNavigation = computed(() =>
(authorization.value?.navigation ?? [])
.filter((directory) => directory.resourceType === 'DIR' && directory.status === 'ENABLED')
.map((directory) => ({
...directory,
children: (directory.children ?? []).filter((menu) => !isRetiredMenu(menu)),
})),
)
/** 侧边栏用的导航树:结构与后端一致,只剔除已下线菜单(空目录由使用方过滤) */
const visibleNavigation = computed(() =>
(authorization.value?.navigation ?? [])
.filter((directory) => directory.resourceType === 'DIR' && directory.status === 'ENABLED')
.map((directory) => ({
...directory,
children: (directory.children ?? []).filter((menu) => !isRetiredMenu(menu)),
})),
)
function menuForPath(path: string): PermissionResourceNode | null {
const normalizedPath = path.replace(/\/$/, '') || '/'
@@ -130,6 +131,9 @@ const visibleNavigation = computed(() =>
authorizationSyncedAt = 0
currentUserReady.value = false
currentUserRequest = null
// 会话结束后清掉跨账号的内存态(当前是院区列表与选中项),
// 否则 SPA 内退出登录后换账号登录,新账号会看到上一个账号的院区。
useAppStore().reset()
}
async function login(payload: LoginRequest) {