feat: 联调
This commit is contained in:
@@ -0,0 +1,432 @@
|
||||
export interface UserListParams {
|
||||
token: string
|
||||
roleType: string
|
||||
page: number
|
||||
size?: number
|
||||
search?: string
|
||||
institution?: string
|
||||
status?: string
|
||||
gender?: string
|
||||
}
|
||||
|
||||
export interface UserListItem {
|
||||
id: string
|
||||
name: string
|
||||
account: string
|
||||
role: string
|
||||
roleType: string
|
||||
org: string
|
||||
lastLogin: string
|
||||
enabled: boolean
|
||||
phone: string
|
||||
realName: string
|
||||
gender: 0 | 1 | 2
|
||||
titleName: string
|
||||
major: string
|
||||
trainingStage: string
|
||||
status: 0 | 1
|
||||
institutionId: string
|
||||
}
|
||||
|
||||
export interface UserListResult {
|
||||
users: UserListItem[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface CreateUserPayload {
|
||||
phone: string
|
||||
real_name: string
|
||||
role_type: string
|
||||
institution?: number
|
||||
gender?: 0 | 1 | 2
|
||||
title_name?: string
|
||||
major?: string
|
||||
training_stage?: string
|
||||
status?: 0 | 1
|
||||
}
|
||||
|
||||
export interface UpdateUserPayload {
|
||||
real_name?: string
|
||||
phone?: string
|
||||
role_type?: string
|
||||
institution?: number
|
||||
gender?: 0 | 1 | 2
|
||||
title_name?: string
|
||||
major?: string
|
||||
training_stage?: string
|
||||
status?: 0 | 1
|
||||
}
|
||||
|
||||
export interface CreateUserParams {
|
||||
token: string
|
||||
payload: CreateUserPayload
|
||||
}
|
||||
|
||||
export interface UpdateUserParams {
|
||||
token: string
|
||||
id: string | number
|
||||
payload: UpdateUserPayload
|
||||
}
|
||||
|
||||
export interface DisableUserParams {
|
||||
token: string
|
||||
id: string | number
|
||||
}
|
||||
|
||||
export interface ResetUserPasswordParams {
|
||||
token: string
|
||||
id: string | number
|
||||
password?: string
|
||||
}
|
||||
|
||||
export interface ImportUsersParams {
|
||||
token: string
|
||||
file: File
|
||||
}
|
||||
|
||||
export interface ExportUsersParams extends UserListParams {}
|
||||
|
||||
const listKeys = ['results', 'list', 'rows', 'items', 'records', 'users']
|
||||
const totalKeys = ['count', 'total', 'total_count', 'totalCount']
|
||||
|
||||
function parseResponseText(text: string): unknown {
|
||||
if (!text) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function getMessageFromResponse(data: unknown): string {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return ''
|
||||
}
|
||||
|
||||
const record = data as Record<string, unknown>
|
||||
const message = record.message || record.msg || record.detail
|
||||
if (typeof message === 'string') {
|
||||
return message
|
||||
}
|
||||
|
||||
return getMessageFromResponse(record.data)
|
||||
}
|
||||
|
||||
function getString(record: Record<string, unknown>, keys: string[], fallback = '-'): string {
|
||||
for (const key of keys) {
|
||||
const value = record[key]
|
||||
if (typeof value === 'string' && value.trim()) {
|
||||
return value
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
||||
|
||||
function getBoolean(record: Record<string, unknown>, keys: string[], fallback = true): boolean {
|
||||
for (const key of keys) {
|
||||
const value = record[key]
|
||||
if (typeof value === 'boolean') {
|
||||
return value
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return value === 1
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return !['0', 'false', 'disabled', 'inactive', '禁用', '停用', '冻结'].includes(value.toLowerCase())
|
||||
}
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
||||
|
||||
function getGender(record: Record<string, unknown>): 0 | 1 | 2 {
|
||||
const value = record.gender
|
||||
if (value === 1 || value === '1') return 1
|
||||
if (value === 2 || value === '2') return 2
|
||||
return 0
|
||||
}
|
||||
|
||||
function getStatus(record: Record<string, unknown>): 0 | 1 {
|
||||
const value = record.status
|
||||
if (value === 0 || value === '0' || value === false || value === '禁用') {
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
function getTotal(record: Record<string, unknown>, fallback: number): number {
|
||||
for (const key of totalKeys) {
|
||||
const value = record[key]
|
||||
if (typeof value === 'number') {
|
||||
return value
|
||||
}
|
||||
if (typeof value === 'string' && Number.isFinite(Number(value))) {
|
||||
return Number(value)
|
||||
}
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
||||
|
||||
function findUserPayload(data: unknown): { items: unknown[]; total: number } {
|
||||
if (Array.isArray(data)) {
|
||||
return { items: data, total: data.length }
|
||||
}
|
||||
|
||||
if (!data || typeof data !== 'object') {
|
||||
return { items: [], total: 0 }
|
||||
}
|
||||
|
||||
const record = data as Record<string, unknown>
|
||||
|
||||
for (const key of listKeys) {
|
||||
const value = record[key]
|
||||
if (Array.isArray(value)) {
|
||||
return { items: value, total: getTotal(record, value.length) }
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(record.data)) {
|
||||
return { items: record.data, total: getTotal(record, record.data.length) }
|
||||
}
|
||||
|
||||
if (record.data && typeof record.data === 'object') {
|
||||
const nested = findUserPayload(record.data)
|
||||
return {
|
||||
items: nested.items,
|
||||
total: nested.total || getTotal(record, nested.items.length)
|
||||
}
|
||||
}
|
||||
|
||||
return { items: [], total: getTotal(record, 0) }
|
||||
}
|
||||
|
||||
function normalizeUser(item: unknown, index: number): UserListItem {
|
||||
const record = item && typeof item === 'object' ? (item as Record<string, unknown>) : {}
|
||||
const name = getString(record, ['name', 'real_name', 'realName', 'nickname', 'username'], `用户${index + 1}`)
|
||||
const roleType = getString(record, ['role_type', 'roleType', 'role'], '')
|
||||
const status = getStatus(record)
|
||||
|
||||
return {
|
||||
id: getString(record, ['id', 'uuid', 'user_id', 'userId'], `${index}`),
|
||||
name,
|
||||
account: getString(record, ['account', 'username', 'phone', 'mobile', 'email'], name),
|
||||
role: getString(record, ['role_name', 'roleName', 'role', 'role_type', 'roleType']),
|
||||
roleType,
|
||||
org: getString(record, ['org', 'organization', 'organization_name', 'organizationName', 'hospital', 'hospital_name', 'hospitalName']),
|
||||
lastLogin: getString(record, ['last_login', 'lastLogin', 'login_time', 'loginTime']),
|
||||
enabled: getBoolean(record, ['enabled', 'is_active', 'isActive', 'status']),
|
||||
phone: getString(record, ['phone', 'mobile', 'account', 'username'], ''),
|
||||
realName: getString(record, ['real_name', 'realName', 'name', 'nickname'], name),
|
||||
gender: getGender(record),
|
||||
titleName: getString(record, ['title_name', 'titleName']),
|
||||
major: getString(record, ['major']),
|
||||
trainingStage: getString(record, ['training_stage', 'trainingStage']),
|
||||
status,
|
||||
institutionId: getString(record, ['institution', 'institution_id', 'institutionId'])
|
||||
}
|
||||
}
|
||||
|
||||
function createAuthorization(token: string) {
|
||||
return /^Bearer\s+/i.test(token) ? token : `Bearer ${token}`
|
||||
}
|
||||
|
||||
function createUserQuery(params: Partial<UserListParams>, includePage = true) {
|
||||
const query = new URLSearchParams()
|
||||
if (params.roleType) {
|
||||
query.set('role_type', params.roleType)
|
||||
}
|
||||
if (params.search?.trim()) {
|
||||
query.set('search', params.search.trim())
|
||||
}
|
||||
if (params.institution?.trim()) {
|
||||
query.set('institution', params.institution.trim())
|
||||
}
|
||||
if (params.status) {
|
||||
query.set('status', params.status)
|
||||
}
|
||||
if (params.gender) {
|
||||
query.set('gender', params.gender)
|
||||
}
|
||||
if (includePage && params.page) {
|
||||
query.set('page', String(params.page))
|
||||
}
|
||||
if (includePage && params.size) {
|
||||
query.set('size', String(params.size))
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
async function parseMutationResponse(response: Response, fallbackMessage: string): Promise<unknown> {
|
||||
const text = await response.text()
|
||||
const data = parseResponseText(text)
|
||||
|
||||
if (!response.ok) {
|
||||
const message = getMessageFromResponse(data) || fallbackMessage
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
function getFilenameFromDisposition(disposition: string | null, fallback: string) {
|
||||
if (!disposition) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
const utf8Match = disposition.match(/filename\*=UTF-8''([^;]+)/i)
|
||||
if (utf8Match?.[1]) {
|
||||
return decodeURIComponent(utf8Match[1])
|
||||
}
|
||||
|
||||
const filenameMatch = disposition.match(/filename="?([^"]+)"?/i)
|
||||
return filenameMatch?.[1] || fallback
|
||||
}
|
||||
|
||||
async function downloadResponse(response: Response, fallbackFilename: string, fallbackMessage: string) {
|
||||
if (!response.ok) {
|
||||
const text = await response.text()
|
||||
const data = parseResponseText(text)
|
||||
const message = getMessageFromResponse(data) || fallbackMessage
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = getFilenameFromDisposition(response.headers.get('content-disposition'), fallbackFilename)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
link.remove()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
export async function fetchUsers(params: UserListParams): Promise<UserListResult> {
|
||||
const query = createUserQuery(params)
|
||||
const authorization = createAuthorization(params.token)
|
||||
|
||||
const response = await fetch(`/server/api/cms/users/?${query.toString()}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: authorization
|
||||
}
|
||||
})
|
||||
const text = await response.text()
|
||||
const data = parseResponseText(text)
|
||||
|
||||
if (!response.ok) {
|
||||
const message = getMessageFromResponse(data) || '获取用户列表失败'
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
const payload = findUserPayload(data)
|
||||
|
||||
return {
|
||||
users: payload.items.map(normalizeUser),
|
||||
total: payload.total
|
||||
}
|
||||
}
|
||||
|
||||
export async function createUser(params: CreateUserParams): Promise<unknown> {
|
||||
const response = await fetch('/server/api/cms/users/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: createAuthorization(params.token),
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(params.payload)
|
||||
})
|
||||
return parseMutationResponse(response, '新增用户失败')
|
||||
}
|
||||
|
||||
export async function updateUser(params: UpdateUserParams): Promise<unknown> {
|
||||
const response = await fetch(`/server/api/cms/users/${params.id}/update/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: createAuthorization(params.token),
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(params.payload)
|
||||
})
|
||||
|
||||
return parseMutationResponse(response, '编辑用户失败')
|
||||
}
|
||||
|
||||
export async function disableUser(params: DisableUserParams): Promise<unknown> {
|
||||
const response = await fetch(`/server/api/cms/users/${params.id}/disable/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: createAuthorization(params.token)
|
||||
}
|
||||
})
|
||||
|
||||
return parseMutationResponse(response, '停用用户失败')
|
||||
}
|
||||
|
||||
export async function resetUserPassword(params: ResetUserPasswordParams): Promise<unknown> {
|
||||
const payload = params.password ? { password: params.password } : {}
|
||||
const response = await fetch(`/server/api/cms/users/${params.id}/reset-password/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: createAuthorization(params.token),
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
|
||||
return parseMutationResponse(response, '重置密码失败')
|
||||
}
|
||||
|
||||
export async function importUsers(params: ImportUsersParams): Promise<unknown> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', params.file)
|
||||
|
||||
const response = await fetch('/server/api/cms/users/import/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: createAuthorization(params.token)
|
||||
},
|
||||
body: formData
|
||||
})
|
||||
return parseMutationResponse(response, '导入用户失败')
|
||||
}
|
||||
|
||||
export async function exportUsers(params: ExportUsersParams): Promise<void> {
|
||||
const query = createUserQuery(params, false)
|
||||
const url = `/server/api/cms/users/export/${query.toString() ? `?${query.toString()}` : ''}`
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: createAuthorization(params.token)
|
||||
}
|
||||
})
|
||||
|
||||
await downloadResponse(response, 'users.xlsx', '导出用户失败')
|
||||
}
|
||||
|
||||
export async function downloadUserImportTemplate(token: string): Promise<void> {
|
||||
const response = await fetch('/server/api/cms/users/import-template/', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: createAuthorization(token)
|
||||
}
|
||||
})
|
||||
|
||||
await downloadResponse(response, 'users-import-template.xlsx', '下载导入模板失败')
|
||||
}
|
||||
Reference in New Issue
Block a user