Files
cms/src/api/institutions.ts
T

386 lines
9.8 KiB
TypeScript
Raw Normal View History

2026-06-12 15:43:30 +08:00
export interface InstitutionListParams {
token: string
search?: string
type?: string
province?: string
city?: string
page?: number
}
export interface InstitutionListItem {
id: number
code: string
name: string
type: string
level: string
province: string
city: string
bannerUrl: string
createdAt: string
updatedAt: string
}
export interface InstitutionListResult {
institutions: InstitutionListItem[]
total: number
}
export interface InstitutionPayload {
code?: string
name?: string
type?: string
level?: string
province?: string
city?: string
2026-06-17 15:11:53 +08:00
banner_url?: string
2026-06-12 15:43:30 +08:00
}
export interface CreateInstitutionPayload extends InstitutionPayload {
code: string
name: string
}
export interface InstitutionMutationParams {
token: string
payload: InstitutionPayload
}
export interface UpdateInstitutionParams extends InstitutionMutationParams {
id: number
}
export interface DisableInstitutionParams {
token: string
id: number
}
2026-06-17 15:11:53 +08:00
export interface UploadInstitutionBannerParams {
token: string
id: number
file: File
}
export interface UploadInstitutionBannerResult {
message: string
bannerUrl: string
raw: unknown
}
2026-06-12 15:43:30 +08:00
export interface ImportInstitutionsParams {
token: string
file: File
}
export interface ExportInstitutionsParams extends InstitutionListParams {}
function createAuthorization(token: string) {
return /^Bearer\s+/i.test(token) ? token : `Bearer ${token}`
}
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>, key: string): string {
const value = record[key]
if (typeof value === 'string') {
return value
}
if (typeof value === 'number') {
return String(value)
}
return ''
}
2026-06-17 15:11:53 +08:00
function getBannerUrlFromResponse(data: unknown): string {
if (!data || typeof data !== 'object') {
return ''
}
const record = data as Record<string, unknown>
const bannerUrl = record.banner_url || record.bannerUrl
if (typeof bannerUrl === 'string') {
return bannerUrl
}
const nested = record.data || record.result || record.payload
if (nested && typeof nested === 'object') {
return getBannerUrlFromResponse(nested)
}
return ''
}
2026-06-12 15:43:30 +08:00
function normalizeInstitution(item: unknown): InstitutionListItem {
const record = item && typeof item === 'object' ? (item as Record<string, unknown>) : {}
const id = record.id
return {
id: typeof id === 'number' ? id : Number(id) || 0,
code: getString(record, 'code'),
name: getString(record, 'name'),
type: getString(record, 'type'),
level: getString(record, 'level'),
province: getString(record, 'province'),
city: getString(record, 'city'),
bannerUrl: getString(record, 'banner_url'),
createdAt: getString(record, 'created_at'),
updatedAt: getString(record, 'updated_at')
}
}
function getTotal(data: unknown, fallback: number): number {
if (!data || typeof data !== 'object') {
return fallback
}
const record = data as Record<string, unknown>
const total = record.count || record.total
if (typeof total === 'number') {
return total
}
if (typeof total === 'string' && Number.isFinite(Number(total))) {
return Number(total)
}
return fallback
}
function getResults(data: unknown): unknown[] {
if (Array.isArray(data)) {
return data
}
if (!data || typeof data !== 'object') {
return []
}
const record = data as Record<string, unknown>
if (Array.isArray(record.results)) {
return record.results
}
if (Array.isArray(record.data)) {
return record.data
}
if (record.data && typeof record.data === 'object') {
return getResults(record.data)
}
return []
}
function createInstitutionQuery(params: Partial<InstitutionListParams>) {
const query = new URLSearchParams()
if (params.search?.trim()) {
query.set('search', params.search.trim())
}
if (params.type) {
query.set('type', params.type)
}
if (params.province?.trim()) {
query.set('province', params.province.trim())
}
if (params.city?.trim()) {
query.set('city', params.city.trim())
}
if (params.page) {
query.set('page', String(params.page))
}
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 fetchInstitutions(params: InstitutionListParams): Promise<InstitutionListResult> {
const query = createInstitutionQuery(params)
const url = `/server/api/cms/institutions/${query.toString() ? `?${query.toString()}` : ''}`
const response = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: createAuthorization(params.token)
}
})
const text = await response.text()
const data = parseResponseText(text)
if (!response.ok) {
const message = getMessageFromResponse(data) || '获取医院列表失败'
throw new Error(message)
}
const results = getResults(data)
return {
institutions: results.map(normalizeInstitution),
total: getTotal(data, results.length)
}
}
export async function createInstitution(params: InstitutionMutationParams): Promise<unknown> {
const response = await fetch('/server/api/cms/institutions/', {
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 updateInstitution(params: UpdateInstitutionParams): Promise<unknown> {
const response = await fetch(`/server/api/cms/institutions/${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 disableInstitution(params: DisableInstitutionParams): Promise<unknown> {
const response = await fetch(`/server/api/cms/institutions/${params.id}/disable/`, {
method: 'POST',
headers: {
Accept: 'application/json',
Authorization: createAuthorization(params.token)
}
})
return parseMutationResponse(response, '停用机构失败')
}
2026-06-17 15:11:53 +08:00
export async function uploadInstitutionBanner(params: UploadInstitutionBannerParams): Promise<UploadInstitutionBannerResult> {
const formData = new FormData()
formData.append('file', params.file)
const response = await fetch(`/server/api/cms/institutions/${params.id}/banner/`, {
method: 'POST',
headers: {
Accept: 'application/json',
Authorization: createAuthorization(params.token)
},
body: formData
})
const data = await parseMutationResponse(response, '上传机构背景图失败')
return {
message: getMessageFromResponse(data),
bannerUrl: getBannerUrlFromResponse(data),
raw: data
}
}
2026-06-12 15:43:30 +08:00
export async function importInstitutions(params: ImportInstitutionsParams): Promise<unknown> {
const formData = new FormData()
formData.append('file', params.file)
const response = await fetch('/server/api/cms/institutions/import/', {
method: 'POST',
headers: {
Accept: 'application/json',
Authorization: createAuthorization(params.token)
},
body: formData
})
return parseMutationResponse(response, '导入机构失败')
}
export async function exportInstitutions(params: ExportInstitutionsParams): Promise<void> {
const query = createInstitutionQuery(params)
const url = `/server/api/cms/institutions/export/${query.toString() ? `?${query.toString()}` : ''}`
const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: createAuthorization(params.token)
}
})
await downloadResponse(response, 'institutions.xlsx', '导出机构失败')
}
export async function downloadInstitutionImportTemplate(token: string): Promise<void> {
const response = await fetch('/server/api/cms/institutions/import-template/', {
method: 'GET',
headers: {
Authorization: createAuthorization(token)
}
})
await downloadResponse(response, 'institutions-import-template.xlsx', '下载导入模板失败')
}