import type { Annotation, AnnotationConfigResponse, AnnotationLabel, AnnotationListResponse, DevicePoint, FaultsResponse, PeriodDetail, QueryOptionsResponse, SitePoint, SitePointsResponse, TimePoint, TimePointsResponse, TspluseRulerResponse, WaveWindowResponse, } from './types' const apiBase = import.meta.env.VITE_API_BASE ?? '' const TOKEN_KEY = 'auth_token' export function getToken(): string | null { return localStorage.getItem(TOKEN_KEY) } export function setToken(token: string | null) { if (token) localStorage.setItem(TOKEN_KEY, token) else localStorage.removeItem(TOKEN_KEY) } async function request(path: string, init?: RequestInit): Promise { const token = getToken() const response = await fetch(`${apiBase}${path}`, { ...init, headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}), ...(init?.headers ?? {}), }, }) if (response.status === 401 && path !== '/api/login') { setToken(null) window.dispatchEvent(new CustomEvent('auth-expired')) } if (!response.ok) { const body = await response.json().catch(() => null) throw new Error(body?.detail ?? `请求失败(${response.status})`) } return response.json() as Promise } export async function login(username: string, password: string) { const result = await request<{ token: string; username: string }>('/api/login', { method: 'POST', body: JSON.stringify({ username, password }), }) setToken(result.token) return result } export async function logout() { try { await request<{ ok: boolean }>('/api/logout', { method: 'POST' }) } finally { setToken(null) } } export function fetchQueryOptions() { return request('/api/query-options') } export function fetchSitePoints(devicePart: string) { return request(`/api/site-points?device_part=${encodeURIComponent(devicePart)}`) } export function fetchTimePoints(params: { devicePart: string devicePoints: DevicePoint[] minTime?: string maxTime?: string includeStopped?: boolean minStatus?: number | null statusFilter?: string[] sitePoints?: string[] }) { const search = new URLSearchParams({ device_part: params.devicePart }) params.devicePoints.forEach((value) => search.append('device_points', value)) if (params.minTime) search.set('min_time', params.minTime) if (params.maxTime) search.set('max_time', params.maxTime) if (params.includeStopped) search.set('include_stopped', 'true') if (params.minStatus) search.set('min_status', String(params.minStatus)) ;(params.statusFilter ?? []).forEach((value) => search.append('status_filter', value)) ;(params.sitePoints ?? []).forEach((value) => search.append('site_points', value)) return request(`/api/time-points?${search.toString()}`) } export function fetchTspluseRuler() { return request('/api/tspluse-ruler') } export function fetchFaults(params: { devicePart: string; minTime?: string; maxTime?: string }) { const search = new URLSearchParams({ device_part: params.devicePart }) if (params.minTime) search.set('min_time', params.minTime) if (params.maxTime) search.set('max_time', params.maxTime) return request(`/api/faults?${search.toString()}`) } export function fetchWaveWindow(params: { devicePart: string devicePoints: DevicePoint[] points: TimePoint[] maxPoints: number noSampling: boolean firstCycleOnly?: boolean sitePoints?: string[] }) { return request('/api/wave-window', { method: 'POST', body: JSON.stringify(params), }) } export function fetchPeriodDetail(waveFileId: number, periodNo: number) { return request(`/api/wave-files/${waveFileId}/periods/${periodNo}`) } export function fetchAnnotationConfig() { return request('/api/annotation-config') } export function fetchAnnotations(waveFileIds: number[]) { if (!waveFileIds.length) return Promise.resolve({ source: 'demo' as const, annotations: [] as Annotation[], notice: null }) const search = new URLSearchParams() waveFileIds.forEach((id) => search.append('wave_file_ids', String(id))) return request(`/api/annotations?${search.toString()}`) } export function fetchAnnotationsByIds(waveFileIds: number[]) { if (!waveFileIds.length) return Promise.resolve({ source: 'demo' as const, annotations: [] as Annotation[], notice: null }) return request('/api/annotations/query', { method: 'POST', body: JSON.stringify({ wave_file_ids: waveFileIds }), }) } export function createAnnotation(payload: { wave_file_id: number label: AnnotationLabel period_start: number period_end: number sample_index_start: number sample_index_end: number }) { return request('/api/annotations', { method: 'POST', body: JSON.stringify(payload), }) } export function deleteAnnotation(id: number) { return request<{ deleted: number; source: string; notice: string | null }>(`/api/annotations/${id}`, { method: 'DELETE', }) } export type PretrainRecordInput = { id: number point_name: string measurement_type: string rpm: number sample_time: string file_name: string } export function fetchPretrainStatus(ids: number[]) { if (!ids.length) return Promise.resolve({ recorded: [] as number[] }) const search = new URLSearchParams() ids.forEach((id) => search.append('ids', String(id))) return request<{ recorded: number[] }>(`/api/pretrain-records?${search.toString()}`) } export function recordPretrain(row: PretrainRecordInput) { return request<{ recorded: boolean; already: boolean }>('/api/pretrain-records', { method: 'POST', body: JSON.stringify(row), }) } export function deletePretrain(id: number) { return request<{ deleted: boolean }>(`/api/pretrain-records/${id}`, { method: 'DELETE', }) }