Files
Scoreboard/web/src/admin/components/baseData/baseDataApi.ts
T

133 lines
5.5 KiB
TypeScript

import { ref } from 'vue'
import { ApiError, adminApi, mockMode, mockReason } from '../../api/client'
import type {
Athlete,
AthleteInput,
EntryImportResult,
Event,
EventCategory,
EventCategoryInput,
EventInput,
ScoringProfile,
ScoringProfileInput,
ScoreChannel,
ScoreChannelInput,
Sport,
SportInput,
Team,
TeamInput,
} from '@/shared'
import { mockBaseData } from './mockBaseData'
/**
* 基础数据接口入口。
*
* 复用 adminApi.client 的 REST 方法,但补上与管理端一致的降级策略:
* 只有「服务端未就绪」(网络/超时/解析失败/404)才落回示例数据,
* 业务错误(鉴权、校验、冲突)原样抛出,避免把真实报错伪装成示例数据。
*
* 之所以不直接用 adminApi 的封装:api/client.ts 不在本页允许修改的范围内,
* 且这里需要区分「基础数据接口没实现」与「整个后端没起来」两种降级。
*/
const FALLBACK_CODES: readonly string[] = ['NETWORK', 'TIMEOUT', 'PARSE_FAILED']
/**
* 本页接口的降级标记。
*
* 不能只看全局 mockMode:服务端可能整体正常、只有基础数据接口尚未实现,
* 此时 adminApi.bootstrap 会把 mockMode 重置为 false,页面却仍在用示例数据。
*/
export const baseDataFallback = ref(false)
function isFallback(error: unknown): boolean {
// 404 表示接口在当前服务端尚未实现,与整个后端没起来一样按「未就绪」处理。
return error instanceof ApiError && (FALLBACK_CODES.includes(error.code) || error.status === 404)
}
async function call<T>(real: () => Promise<T>, mock: () => T | Promise<T>): Promise<T> {
try {
const value = await real()
baseDataFallback.value = false
if (mockMode.value) {
mockMode.value = false
mockReason.value = null
}
return value
} catch (error) {
if (!isFallback(error)) throw error
baseDataFallback.value = true
mockMode.value = true
mockReason.value = error instanceof Error ? error.message : '无法连接服务端'
return await mock()
}
}
export const baseDataApi = {
teams: {
list: (): Promise<Team[]> => call(() => adminApi.client.teams.list(), () => mockBaseData.listTeams()),
create: (input: TeamInput): Promise<Team> =>
call(() => adminApi.client.teams.create(input), () => mockBaseData.createTeam(input)),
update: (id: number, input: TeamInput): Promise<Team> =>
call(() => adminApi.client.teams.update(id, input), () => mockBaseData.updateTeam(id, input)),
},
sports: {
list: (): Promise<Sport[]> => call(() => adminApi.client.sports.list(), () => mockBaseData.listSports()),
create: (input: SportInput): Promise<Sport> =>
call(() => adminApi.client.sports.create(input), () => mockBaseData.createSport(input)),
update: (id: number, input: SportInput): Promise<Sport> =>
call(() => adminApi.client.sports.update(id, input), () => mockBaseData.updateSport(id, input)),
remove: (id: number): Promise<void> =>
call(() => adminApi.client.sports.remove(id), () => mockBaseData.removeSport(id)),
},
events: {
list: (): Promise<Event[]> => call(() => adminApi.client.events.list(), () => mockBaseData.listEvents()),
get: (id: number): Promise<Event> => call(() => adminApi.client.events.get(id), () => mockBaseData.getEvent(id)),
create: (input: EventInput): Promise<Event> =>
call(() => adminApi.client.events.create(input), () => mockBaseData.createEvent(input)),
update: (id: number, input: EventInput): Promise<Event> =>
call(() => adminApi.client.events.update(id, input), () => mockBaseData.updateEvent(id, input)),
remove: (id: number): Promise<void> =>
call(() => adminApi.client.events.remove(id), () => mockBaseData.removeEvent(id)),
saveCategories: (eventId: number, inputs: EventCategoryInput[]): Promise<EventCategory[]> =>
call(
() => adminApi.client.events.saveCategories(eventId, inputs),
() => mockBaseData.saveCategories(eventId, inputs),
),
saveChannels: (eventId: number, inputs: ScoreChannelInput[]): Promise<ScoreChannel[]> =>
call(
() => adminApi.client.events.saveChannels(eventId, inputs),
() => mockBaseData.saveChannels(eventId, inputs),
),
},
profiles: {
list: (): Promise<ScoringProfile[]> =>
call(() => adminApi.client.scoringProfiles.list(), () => mockBaseData.listProfiles()),
create: (input: ScoringProfileInput): Promise<ScoringProfile> =>
call(() => adminApi.client.scoringProfiles.create(input), () => mockBaseData.createProfile(input)),
update: (id: number, input: ScoringProfileInput): Promise<ScoringProfile> =>
call(() => adminApi.client.scoringProfiles.update(id, input), () => mockBaseData.updateProfile(id, input)),
},
athletes: {
list: (): Promise<Athlete[]> => call(() => adminApi.client.athletes.list(), () => mockBaseData.listAthletes()),
create: (input: AthleteInput): Promise<Athlete> =>
call(() => adminApi.client.athletes.create(input), () => mockBaseData.createAthlete(input)),
update: (id: number, input: AthleteInput): Promise<Athlete> =>
call(() => adminApi.client.athletes.update(id, input), () => mockBaseData.updateAthlete(id, input)),
remove: (id: number): Promise<void> =>
call(() => adminApi.client.athletes.remove(id), () => mockBaseData.removeAthlete(id)),
},
entries: {
importFile: (file: File): Promise<EntryImportResult> =>
call(
() => adminApi.client.entries.importFile(file),
() => mockBaseData.importEntries(file),
),
},
}