fix: 接入用户与权限后端接口

This commit is contained in:
xy
2026-09-03 12:18:57 +08:00
parent e43a118dee
commit 48e5293509
19 changed files with 1982 additions and 375 deletions
+72 -3
View File
@@ -5,9 +5,12 @@ import type { ApiResponse } from '@/types/common'
import type {
BackendCollection,
CreateRoleRequest,
RolePermissionsResponseDto,
RolePermissionsUpdateRequest,
RoleRecord,
RoleQuery,
RoleResponseDto,
RoleSummaryResponseDto,
UpdateRoleRequest,
} from './types'
@@ -15,18 +18,35 @@ const useMockData = import.meta.env.VITE_USE_MOCK !== 'false'
const fallbackColors = ['#6b5bd2', '#0e6e8c', '#0f9d6c', '#d9821f', '#c65d5d']
function normalizeRole(dto: RoleResponseDto, index: number): RoleRecord {
function normalizeRole(dto: RoleResponseDto, index: number, userCount?: number | null): RoleRecord {
return {
id: dto.id,
code: dto.code,
name: dto.name,
description: dto.description ?? '—',
color: fallbackColors[index % fallbackColors.length],
userCount: dto.userCount ?? undefined,
userCount: userCount ?? undefined,
status: dto.status ?? 'ENABLED',
createdAt: dto.createdAt,
updatedAt: dto.updatedAt,
version: dto.version ?? undefined,
}
}
function normalizeRoleSummary(dto: RoleSummaryResponseDto, index: number): RoleRecord {
return normalizeRole(
{
id: dto.roleId,
code: dto.code,
name: dto.name,
description: dto.description,
status: dto.status,
},
index,
dto.userCount,
)
}
function toBackendQuery(query: RoleQuery) {
const params: Record<string, string | number> = {
page: Math.max(query.page, 1),
@@ -54,10 +74,59 @@ export function getRoles(query: RoleQuery = { page: 1, pageSize: 200 }): Promise
const records = Array.isArray(data)
? data
: (data.records ?? data.items ?? data.content ?? [])
return records.map(normalizeRole)
return records.map((role, index) => normalizeRole(role, index))
})
}
export function getRoleSummaries(campusId?: string): Promise<RoleRecord[]> {
if (useMockData) {
return Promise.resolve([])
}
return request
.get<ApiResponse<BackendCollection<RoleSummaryResponseDto>>>('/v1/roles/summary', {
params: campusId ? { campusId } : undefined,
})
.then(unwrapApiResponse)
.then((data) => {
const records = Array.isArray(data)
? data
: (data.records ?? data.items ?? data.content ?? [])
return records.map(normalizeRoleSummary)
})
}
export async function getRolePermissions(roleId: string): Promise<RolePermissionsResponseDto> {
if (useMockData) {
return {
roleId,
permissions: [],
version: 0,
updatedAt: '',
}
}
const response = await request.get<ApiResponse<RolePermissionsResponseDto>>(
`/v1/roles/${encodeURIComponent(roleId)}/permissions`,
)
return unwrapApiResponse(response)
}
export async function updateRolePermissions(
roleId: string,
payload: RolePermissionsUpdateRequest,
): Promise<RolePermissionsResponseDto> {
if (useMockData) {
throw new Error('Mock 模式不执行角色权限写操作')
}
const response = await request.put<ApiResponse<RolePermissionsResponseDto>>(
`/v1/roles/${encodeURIComponent(roleId)}/permissions`,
payload,
)
return unwrapApiResponse(response)
}
export function createRole(payload: CreateRoleRequest): Promise<RoleRecord> {
if (useMockData) {
return Promise.reject(new Error('Mock 模式不执行角色写操作'))
+118 -4
View File
@@ -124,6 +124,9 @@ export interface RoleRecord {
color?: string
userCount?: number
status: RoleStatus
createdAt?: string | null
updatedAt?: string | null
version?: number
}
export interface RoleQuery extends PageQuery {
@@ -138,6 +141,17 @@ export interface RoleResponseDto {
name: string
description?: string | null
status?: RoleStatus | null
createdAt?: string | null
updatedAt?: string | null
version?: number | null
}
export interface RoleSummaryResponseDto {
roleId: string
code: string
name: string
description?: string | null
status?: RoleStatus | null
userCount?: number | null
}
@@ -391,7 +405,7 @@ export interface ReportExportJobResponseDto {
downloadPath?: string | null
}
export type UserStatus = 'enabled' | 'disabled'
export type UserStatus = 'enabled' | 'disabled' | 'locked'
export interface UserQuery extends PageQuery {
keyword?: string
@@ -416,7 +430,10 @@ export interface UserRecord {
departmentId?: string | null
departmentName?: string
roleNames?: string[]
roleIds?: string[]
dataScope?: string
version?: number
passwordChangeRequired?: boolean
}
export type UserListResponse = PageResult<UserRecord>
@@ -429,12 +446,24 @@ export interface UserResponseDto {
phone?: string | null
email?: string | null
campusId?: string | null
campusName?: string | null
departmentId?: string | null
departmentName?: string | null
roles?: UserRoleSummaryDto[] | null
dataScope?: string | null
status?: string | null
status?: BackendUserStatus | null
lastLoginAt?: string | null
createdAt?: string | null
updatedAt?: string | null
version?: number | null
passwordChangeRequired?: boolean
}
export interface UserRoleSummaryDto {
id: string
code: string
name: string
status?: RoleStatus | null
}
export interface CreateUserRequest {
@@ -448,10 +477,11 @@ export interface CreateUserRequest {
departmentId?: string | null
status?: BackendUserStatus
dataScope?: BackendDataScope
roleIds?: string[]
}
export type BackendUserStatus = 'ENABLED' | 'DISABLED' | string
export type BackendDataScope = 'ALL' | 'CAMPUS' | 'DEPARTMENT' | 'READ_ONLY_ALL' | string
export type BackendUserStatus = 'ENABLED' | 'DISABLED' | 'LOCKED'
export type BackendDataScope = 'ALL' | 'CAMPUS' | 'DEPARTMENT' | 'READ_ONLY_ALL'
export interface UpdateUserRequest {
displayName: string
@@ -462,6 +492,90 @@ export interface UpdateUserRequest {
departmentId?: string | null
status: BackendUserStatus
dataScope: BackendDataScope
clearPhone?: boolean
clearEmail?: boolean
expectedVersion?: number
}
export interface UserManagementUpdateRequest {
displayName: string
employeeNo?: string
phone?: string
email?: string
clearPhone?: boolean
clearEmail?: boolean
campusId: string
departmentId?: string | null
status: BackendUserStatus
dataScope: BackendDataScope
roleIds: string[]
expectedVersion?: number
}
export interface UserRolesResponseDto {
userId: string
roles: UserRoleSummaryDto[]
version: number
updatedAt: string
}
export interface UserRolesUpdateRequest {
roleIds: string[]
expectedVersion?: number
}
export interface RolePermissionsResponseDto {
roleId: string
permissions: ApiPermissionResponseDto[]
version: number
updatedAt: string
}
export interface RolePermissionsUpdateRequest {
permissionIds: string[]
expectedVersion?: number
}
export interface PasswordResetRequest {
newPassword: string
forceChangeOnNextLogin?: boolean
}
export type UserImportRowStatus = 'VALID' | 'INVALID'
export interface UserImportPreviewRowDto {
rowNo: number
status: UserImportRowStatus
errors: string[]
username?: string | null
displayName?: string | null
}
export interface UserImportPreviewResponseDto {
importId: string
fileName: string
totalRows: number
validRows: number
invalidRows: number
rows: UserImportPreviewRowDto[]
expiresAt: string
}
export interface UserImportCommitRequest {
importId: string
}
export type UserImportJobStatus = 'PREVIEWED' | 'COMMITTED' | 'EXPIRED' | 'FAILED' | string
export interface UserImportJobResponseDto {
jobId: string
status: UserImportJobStatus
totalRows?: number | null
validRows?: number | null
invalidRows?: number | null
createdAt?: string | null
expiresAt?: string | null
committedAt?: string | null
}
export type PermissionSubjectType = 'user' | 'role'
+129 -2
View File
@@ -8,11 +8,18 @@ import type {
BackendCollection,
BackendPage,
CreateUserRequest,
PasswordResetRequest,
UpdateUserRequest,
UserImportCommitRequest,
UserImportJobResponseDto,
UserImportPreviewResponseDto,
UserManagementUpdateRequest,
UserListResponse,
UserQuery,
UserRecord,
UserResponseDto,
UserRolesResponseDto,
UserRolesUpdateRequest,
} from './types'
const useMockData = import.meta.env.VITE_USE_MOCK !== 'false'
@@ -70,24 +77,37 @@ function formatDateTime(value: string | null | undefined) {
}
function normalizeStatus(value: string | null | undefined): UserRecord['status'] {
if (value === 'LOCKED') {
return 'locked'
}
return value === 'DISABLED' || value === 'INACTIVE' ? 'disabled' : 'enabled'
}
function mapUser(dto: UserResponseDto): UserRecord {
const roles = dto.roles ?? []
const roleNames = roles.map((role) => role.name).filter(Boolean)
return {
id: dto.id,
name: dto.displayName,
account: dto.username,
department: dto.departmentId ? '未知科室' : '未指定科室',
role: '未返回角色',
department: dto.departmentName ?? (dto.departmentId ? '未知科室' : '未指定科室'),
role: roleNames.length ? roleNames.join('、') : '未分配角色',
status: normalizeStatus(dto.status),
lastLoginAt: formatDateTime(dto.lastLoginAt),
employeeNo: dto.employeeNo,
phone: dto.phone,
email: dto.email,
campusId: dto.campusId,
campusName: dto.campusName ?? undefined,
departmentId: dto.departmentId,
departmentName: dto.departmentName ?? undefined,
roleNames,
roleIds: roles.map((role) => role.id),
dataScope: dto.dataScope ?? '—',
version: dto.version ?? undefined,
passwordChangeRequired: dto.passwordChangeRequired,
}
}
@@ -203,6 +223,113 @@ export function updateUser(id: string, payload: UpdateUserRequest): Promise<User
.then(mapUser)
}
export function updateUserManagement(
id: string,
payload: UserManagementUpdateRequest,
): Promise<UserRecord> {
if (useMockData) {
return Promise.reject(new Error('Mock 模式不执行用户写操作'))
}
return request
.put<ApiResponse<UserResponseDto>>(`/v1/users/${encodeURIComponent(id)}/management`, payload)
.then(unwrapApiResponse)
.then(mapUser)
}
export async function getUserRoles(userId: string): Promise<UserRolesResponseDto> {
if (useMockData) {
return {
userId,
roles: [],
version: 0,
updatedAt: '',
}
}
const response = await request.get<ApiResponse<UserRolesResponseDto>>(
`/v1/users/${encodeURIComponent(userId)}/roles`,
)
return unwrapApiResponse(response)
}
export async function updateUserRoles(
userId: string,
payload: UserRolesUpdateRequest,
): Promise<UserRolesResponseDto> {
if (useMockData) {
return Promise.reject(new Error('Mock 模式不执行用户角色写操作'))
}
const response = await request.put<ApiResponse<UserRolesResponseDto>>(
`/v1/users/${encodeURIComponent(userId)}/roles`,
payload,
)
return unwrapApiResponse(response)
}
export async function resetUserPassword(
userId: string,
payload: PasswordResetRequest,
): Promise<void> {
if (useMockData) {
throw new Error('Mock 模式不执行密码重置')
}
const response = await request.post<ApiResponse<null>>(
`/v1/users/${encodeURIComponent(userId)}/password/reset`,
payload,
)
unwrapNullableApiResponse(response)
}
export async function previewUserImport(
file: File,
campusId?: string,
): Promise<UserImportPreviewResponseDto> {
if (useMockData) {
throw new Error('Mock 模式不执行用户导入')
}
const formData = new FormData()
formData.append('file', file)
if (campusId) {
formData.append('campusId', campusId)
}
const response = await request.post<ApiResponse<UserImportPreviewResponseDto>>(
'/v1/users/import/preview',
formData,
)
return unwrapApiResponse(response)
}
export async function commitUserImport(
payload: UserImportCommitRequest,
): Promise<UserImportJobResponseDto> {
if (useMockData) {
throw new Error('Mock 模式不执行用户导入')
}
const response = await request.post<ApiResponse<UserImportJobResponseDto>>(
'/v1/users/import/commit',
payload,
)
return unwrapApiResponse(response)
}
export async function getUserImportJob(jobId: string): Promise<UserImportJobResponseDto> {
if (useMockData) {
throw new Error('Mock 模式不执行用户导入')
}
const response = await request.get<ApiResponse<UserImportJobResponseDto>>(
`/v1/users/import/jobs/${encodeURIComponent(jobId)}`,
)
return unwrapApiResponse(response)
}
export function deleteUser(id: string): Promise<void> {
if (useMockData) {
return Promise.reject(new Error('Mock 模式不执行用户写操作'))