133 lines
5.5 KiB
TypeScript
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),
|
|
),
|
|
},
|
|
}
|