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( data: BackendPage | T[], fallbackPage: number, fallbackPageSize: number, ): PageResult { 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 = { 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 { if (!useMockData) { return request .get>>('/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 { if (useMockData) { const record = mockRecords.find((item) => item.id === id) return record ? { ...record } : null } try { const response = await request.get>( `/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 { if (useMockData) { return Promise.reject(new Error('Mock 模式不执行用户写操作')) } return request .post>('/v1/users', payload) .then(unwrapApiResponse) .then(mapUser) } export function updateUser(id: string, payload: UpdateUserRequest): Promise { if (useMockData) { return Promise.reject(new Error('Mock 模式不执行用户写操作')) } return request .put>(`/v1/users/${encodeURIComponent(id)}`, payload) .then(unwrapApiResponse) .then(mapUser) } export async function getUserRoles(userId: string): Promise { if (useMockData) { return [] } const response = await request.get>( `/v1/users/${encodeURIComponent(userId)}/roles`, ) return unwrapApiResponse(response) } export async function updateUserRoles( userId: string, payload: UserRolesUpdateRequest, ): Promise { if (useMockData) { return Promise.reject(new Error('Mock 模式不执行用户角色写操作')) } const response = await request.put>( `/v1/users/${encodeURIComponent(userId)}/roles`, payload, ) unwrapNullableApiResponse(response) } export async function resetUserPassword( userId: string, payload: PasswordResetRequest, ): Promise { if (useMockData) { throw new Error('Mock 模式不执行密码重置') } const response = await request.post>( `/v1/users/${encodeURIComponent(userId)}/password/reset`, payload, ) unwrapNullableApiResponse(response) } export async function previewUserImport( file: File, campusId?: string, ): Promise { 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>( '/v1/users/import/preview', formData, ) return unwrapApiResponse(response) } export async function commitUserImport( payload: UserImportCommitRequest, ): Promise { if (useMockData) { throw new Error('Mock 模式不执行用户导入') } const response = await request.post>( '/v1/users/import/commit', payload, ) return unwrapApiResponse(response) } export async function getUserImportJob(jobId: string): Promise { if (useMockData) { throw new Error('Mock 模式不执行用户导入') } const response = await request.get>( `/v1/users/import/jobs/${encodeURIComponent(jobId)}`, ) return unwrapApiResponse(response) } export function deleteUser(id: string): Promise { if (useMockData) { return Promise.reject(new Error('Mock 模式不执行用户写操作')) } return request .delete>(`/v1/users/${encodeURIComponent(id)}`) .then((response) => { unwrapNullableApiResponse(response) }) }