Files
xh-medical-sign-web/clinical-web/src/api/management/users.ts
T

335 lines
9.0 KiB
TypeScript

import axios from 'axios'
import { unwrapApiResponse, unwrapNullableApiResponse } from '@/utils/api-response'
import { mockFlags } from '@/utils/mock-flags'
import { request } from '@/utils/request'
import type { ApiResponse, PageResult } from '@/types/common'
import type {
BackendCollection,
BackendPage,
CreateUserRequest,
PasswordResetRequest,
UpdateUserRequest,
UserImportCommitRequest,
UserImportJobResponseDto,
UserImportPreviewResponseDto,
UserListResponse,
UserQuery,
UserRecord,
UserResponseDto,
UserRoleSummaryDto,
UserRolesUpdateRequest,
} from './types'
const useMockData = mockFlags.users
export const isUserMockEnabled = useMockData
const mockRecords: UserRecord[] = [
{
id: 'user-001',
name: '张文静',
account: 'zhangwenjing',
department: '医务管理',
role: '系统管理员',
status: 'enabled',
lastLoginAt: '2026-08-27 08:12',
},
{
id: 'user-002',
name: '王医生',
account: 'wangdoctor',
department: '消化内科',
role: '医生',
status: 'enabled',
lastLoginAt: '2026-08-27 08:35',
},
{
id: 'user-003',
name: '李护士',
account: 'linurse',
department: '儿科急诊',
role: '护士',
status: 'disabled',
lastLoginAt: '2026-08-20 14:26',
},
]
function formatDateTime(value: string | null | undefined) {
if (!value) {
return '—'
}
const date = new Date(value)
if (Number.isNaN(date.getTime())) {
return value
.replace('T', ' ')
.replace(/\.\d+Z$/, '')
.replace(/Z$/, '')
}
const pad = (part: number) => String(part).padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(
date.getHours(),
)}:${pad(date.getMinutes())}`
}
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,
employeeId: dto.employeeId,
name: dto.displayName,
account: dto.username,
department: dto.departmentName ?? (dto.departmentId ? '未知科室' : '未指定科室'),
role: roleNames.length ? roleNames.join('、') : '未分配角色',
status: normalizeStatus(dto.status),
lastLoginAt: formatDateTime(dto.lastLoginAt),
employeeNo: dto.employeeNo,
position: dto.position,
professionalTitle: dto.professionalTitle,
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),
roleDataScopes: roles.map((role) => role.dataScope ?? 'SELF'),
version: dto.version ?? undefined,
passwordChangeRequired: dto.passwordChangeRequired,
}
}
function normalizePage<T>(
data: BackendPage<T> | T[],
fallbackPage: number,
fallbackPageSize: number,
): PageResult<T> {
const records = Array.isArray(data) ? data : (data.records ?? data.items ?? data.content ?? [])
return {
records,
total: Array.isArray(data) ? records.length : (data.total ?? records.length),
page: Array.isArray(data) ? fallbackPage : (data.page ?? fallbackPage),
pageSize: Array.isArray(data) ? fallbackPageSize : (data.size ?? fallbackPageSize),
}
}
function toBackendQuery(query: UserQuery) {
const params: Record<string, string | number> = {
page: Math.max(query.page, 1),
size: Math.min(Math.max(query.pageSize, 1), 200),
}
if (query.keyword?.trim()) {
params.keyword = query.keyword.trim()
}
if (query.campusId) {
params.campusId = query.campusId
}
if (query.departmentId) {
params.departmentId = query.departmentId
}
return params
}
export function getUsers(query: UserQuery): Promise<UserListResponse> {
if (!useMockData) {
return request
.get<ApiResponse<BackendCollection<UserResponseDto>>>('/v1/users', {
params: toBackendQuery(query),
})
.then((response) => {
const page = normalizePage(unwrapApiResponse(response), query.page, query.pageSize)
return {
...page,
records: page.records.map(mapUser),
}
})
}
const keyword = query.keyword?.trim().toLowerCase()
const filteredRecords = mockRecords.filter((record) => {
const matchesKeyword =
!keyword || [record.name, record.account].join(' ').toLowerCase().includes(keyword)
const matchesDepartment = !query.department || record.department === query.department
const matchesDepartmentId = !query.departmentId || record.departmentId === query.departmentId
const matchesStatus = !query.status || query.status === 'all' || record.status === query.status
const matchesCampus = !query.campusId || record.campusId === query.campusId
return (
matchesKeyword && matchesDepartment && matchesDepartmentId && matchesStatus && matchesCampus
)
})
const page = Math.max(query.page, 1)
const pageSize = Math.max(query.pageSize, 1)
const start = (page - 1) * pageSize
return Promise.resolve({
records: filteredRecords.slice(start, start + pageSize),
total: filteredRecords.length,
page,
pageSize,
})
}
export async function getUserDetail(id: string): Promise<UserRecord | null> {
if (useMockData) {
const record = mockRecords.find((item) => item.id === id)
return record ? { ...record } : null
}
try {
const response = await request.get<ApiResponse<UserResponseDto>>(
`/v1/users/${encodeURIComponent(id)}`,
)
return mapUser(unwrapApiResponse(response))
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 404) {
return null
}
throw error
}
}
export function createUser(payload: CreateUserRequest): Promise<UserRecord> {
if (useMockData) {
return Promise.reject(new Error('Mock 模式不执行用户写操作'))
}
return request
.post<ApiResponse<UserResponseDto>>('/v1/users', payload)
.then(unwrapApiResponse)
.then(mapUser)
}
export function updateUser(id: string, payload: UpdateUserRequest): Promise<UserRecord> {
if (useMockData) {
return Promise.reject(new Error('Mock 模式不执行用户写操作'))
}
return request
.put<ApiResponse<UserResponseDto>>(`/v1/users/${encodeURIComponent(id)}`, payload)
.then(unwrapApiResponse)
.then(mapUser)
}
export async function getUserRoles(userId: string): Promise<UserRoleSummaryDto[]> {
if (useMockData) {
return []
}
const response = await request.get<ApiResponse<UserRoleSummaryDto[]>>(
`/v1/users/${encodeURIComponent(userId)}/roles`,
)
return unwrapApiResponse(response)
}
export async function updateUserRoles(
userId: string,
payload: UserRolesUpdateRequest,
): Promise<void> {
if (useMockData) {
return Promise.reject(new Error('Mock 模式不执行用户角色写操作'))
}
const response = await request.put<ApiResponse<null>>(
`/v1/users/${encodeURIComponent(userId)}/roles`,
payload,
)
unwrapNullableApiResponse(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 模式不执行用户写操作'))
}
return request
.delete<ApiResponse<null>>(`/v1/users/${encodeURIComponent(id)}`)
.then((response) => {
unwrapNullableApiResponse(response)
})
}