feat: 接入权限接口

This commit is contained in:
xy
2026-09-24 08:31:21 +08:00
parent 93fb5fd2c4
commit e5b5412322
24 changed files with 2398 additions and 1738 deletions
+90 -20
View File
@@ -3,6 +3,7 @@ import { defineStore } from 'pinia'
import {
changePassword as changePasswordRequest,
getCurrentAuthorization,
getCurrentUser,
login as loginRequest,
logout as logoutRequest,
@@ -11,33 +12,27 @@ import type {
AuthenticatedUser,
LoginRequest,
LoginResponse,
CurrentAuthorization,
PermissionResourceNode,
PasswordChangeRequest,
} from '@/types/auth'
import { clearAuthStorage, readStoredAuthSession, saveAuthSession } from '@/utils/auth-storage'
const dataScopeLabels: Record<AuthenticatedUser['dataScope'], string> = {
ALL: '全院',
CAMPUS: '院区',
DEPARTMENT: '科室',
READ_ONLY_ALL: '全院只读',
}
function getUserContext(user: AuthenticatedUser | null) {
return user ? `${dataScopeLabels[user.dataScope]}数据范围` : '医签通用户'
}
export const useLoginStore = defineStore('login', () => {
const storedSession = readStoredAuthSession()
const token = ref(storedSession?.token ?? '')
const expiresAt = ref(storedSession?.expiresAt ?? '')
const user = ref<AuthenticatedUser | null>(storedSession?.user ?? null)
const authorization = ref<CurrentAuthorization | null>(null)
const currentUserReady = ref(false)
let currentUserRequest: Promise<AuthenticatedUser> | null = null
let authorizationSyncedAt = 0
const isLoggedIn = computed(() => Boolean(token.value))
const isReadOnly = computed(() => user.value?.dataScope === 'READ_ONLY_ALL')
// Data scope belongs to roles now; it no longer determines whether a user can write.
const isReadOnly = computed(() => false)
const userName = computed(() => user.value?.displayName || user.value?.username || '医护用户')
const department = computed(() => getUserContext(user.value))
const department = computed(() => user.value?.departmentName || '医签通用户')
function hasPermission(required: string | string[]) {
const requiredPermissions = Array.isArray(required) ? required : [required]
@@ -46,12 +41,57 @@ export const useLoginStore = defineStore('login', () => {
return true
}
const permissions = user.value?.permissions ?? []
return requiredPermissions.every(
(permission) => permissions.includes(permission) || permissions.includes('*'),
const permissions = authorization.value?.permissions ?? user.value?.permissions ?? []
const legacyAliases: Record<string, string> = {
'sign:task:query': 'sign:task:view',
'sign:template:query': 'doc:template:view',
'sign:template:create': 'doc:template:create',
'sign:template:update': 'doc:template:edit',
'sign:template:publish': 'doc:template:publish',
'report:signing:query': 'report:sign:view',
'report:signing:export': 'report:sign:export',
'system:user:query': 'system:org:view',
'system:user:create': 'system:org:emp',
'system:user:update': 'system:org:emp',
'system:user:disable': 'system:org:emp',
'system:user:password:reset': 'system:org:pwd',
'system:role:permission:query': 'system:role-permission:view',
'system:role:permission:update': 'system:role-permission:edit',
'audit:log:query': 'audit:log:view',
}
return requiredPermissions.every((permission) => {
const alias = legacyAliases[permission]
return (
permissions.includes(permission) ||
(alias !== undefined && permissions.includes(alias)) ||
permissions.includes('*')
)
})
}
const accessibleMenus = computed(() =>
(authorization.value?.navigation ?? [])
.filter((directory) => directory.resourceType === 'DIR' && directory.status === 'ENABLED')
.flatMap((directory) => directory.children ?? []),
)
function menuForPath(path: string): PermissionResourceNode | null {
const normalizedPath = path.replace(/\/$/, '') || '/'
return (
accessibleMenus.value.find((menu) => menu.routePath?.replace(/\/$/, '') === normalizedPath) ??
null
)
}
const firstAuthorizedPath = computed(
() =>
accessibleMenus.value.find(
(menu) =>
menu.visible && menu.status === 'ENABLED' && menu.code && hasPermission(menu.code),
)?.routePath ?? null,
)
function hasWritePermission(required: string | string[]) {
return !isReadOnly.value && hasPermission(required)
}
@@ -61,6 +101,8 @@ export const useLoginStore = defineStore('login', () => {
token.value = session.token
expiresAt.value = session.expiresAt
user.value = session.user
authorization.value = null
authorizationSyncedAt = 0
currentUserReady.value = false
}
@@ -81,6 +123,8 @@ export const useLoginStore = defineStore('login', () => {
token.value = ''
expiresAt.value = ''
user.value = null
authorization.value = null
authorizationSyncedAt = 0
currentUserReady.value = false
currentUserRequest = null
}
@@ -92,9 +136,25 @@ export const useLoginStore = defineStore('login', () => {
}
async function refreshCurrentUser() {
const currentUser = await getCurrentUser()
setCurrentUser(currentUser)
return currentUser
const [currentUser, currentAuthorization] = await Promise.all([
getCurrentUser(),
getCurrentAuthorization(),
])
const effectiveUser = { ...currentUser, permissions: currentAuthorization.permissions }
authorization.value = currentAuthorization
authorizationSyncedAt = Date.now()
setCurrentUser(effectiveUser)
return effectiveUser
}
async function refreshAuthorization() {
const currentAuthorization = await getCurrentAuthorization()
authorization.value = currentAuthorization
authorizationSyncedAt = Date.now()
if (user.value) {
setCurrentUser({ ...user.value, permissions: currentAuthorization.permissions })
}
return currentAuthorization
}
async function ensureCurrentUser() {
@@ -102,7 +162,12 @@ export const useLoginStore = defineStore('login', () => {
return null
}
if (currentUserReady.value && user.value) {
if (
currentUserReady.value &&
user.value &&
authorization.value &&
Date.now() - authorizationSyncedAt < 10_000
) {
return user.value
}
@@ -142,6 +207,8 @@ export const useLoginStore = defineStore('login', () => {
token,
expiresAt,
user,
authorization,
accessibleMenus,
isLoggedIn,
isReadOnly,
userName,
@@ -149,6 +216,9 @@ export const useLoginStore = defineStore('login', () => {
login,
refreshCurrentUser,
ensureCurrentUser,
refreshAuthorization,
menuForPath,
firstAuthorizedPath,
hasPermission,
hasWritePermission,
changePassword,