import type { FeedbackDraft, FeedbackRecord, HealthStatus, StudentDraft, StudentProfile, StudentProfileDefaults, Term } from './types' const DEFAULT_API_BASE_URL = 'http://127.0.0.1:8080' const API_BASE_URL_KEY = 'teaching-feedback-api-base-url-v1' export const DEVELOPMENT_USER_ID = '5a4da7f0-d70c-465f-bcab-124c504aa9f0' interface ApiErrorBody { error?: string } interface RawStudentProfile { id: string name: string grade: string subject: string academic_year: number term: Term guardian_title: string start_time: string end_time: string created_at: string updated_at: string } interface RawProfileDefaults { grade: string subject: string lesson_duration_minutes: number updated_at: string } interface RawFeedbackRecord { id: string profile_id: string | null feedback_date: string content: string created_at: string updated_at: string } interface RawHealthStatus { status: string database_configured: boolean } type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' export class ApiRequestError extends Error { constructor( message: string, readonly statusCode?: number ) { super(message) this.name = 'ApiRequestError' } } export function getApiBaseUrl(): string { return (wx.getStorageSync(API_BASE_URL_KEY) as string) || DEFAULT_API_BASE_URL } export function setApiBaseUrl(value: string): string { const normalized = value.trim().replace(/\/+$/, '') if (!/^https?:\/\/[^\s]+$/i.test(normalized)) { throw new ApiRequestError('服务地址必须以 http:// 或 https:// 开头') } wx.setStorageSync(API_BASE_URL_KEY, normalized) return normalized } export function getApiErrorMessage(error: unknown): string { if (!(error instanceof ApiRequestError)) return '请求失败,请稍后重试' if (error.statusCode === 401) return '开发身份未通过后端校验' if (error.statusCode === 404) return '请求的数据不存在或无权访问' if (error.statusCode === 503) return '后端尚未配置数据库连接' return error.message } function request(path: string, method: HttpMethod = 'GET', data?: object): Promise { return new Promise((resolve, reject) => { wx.request({ url: `${getApiBaseUrl()}${path}`, method, data, timeout: 10000, header: { 'Content-Type': 'application/json', 'X-User-Id': DEVELOPMENT_USER_ID }, success(response) { if (response.statusCode >= 200 && response.statusCode < 300) { resolve(response.data as T) return } const body = response.data as ApiErrorBody reject(new ApiRequestError(body?.error || `服务返回 HTTP ${response.statusCode}`, response.statusCode)) }, fail(error) { reject(new ApiRequestError(`无法连接后端:${error.errMsg}`)) } }) }) } function parseTimestamp(value: string): number { const timestamp = Date.parse(value) return Number.isNaN(timestamp) ? Date.now() : timestamp } function mapProfile(profile: RawStudentProfile): StudentProfile { return { id: profile.id, name: profile.name, grade: profile.grade, subject: profile.subject, academicYear: profile.academic_year, term: profile.term, guardianTitle: profile.guardian_title, startTime: profile.start_time, endTime: profile.end_time, createdAt: parseTimestamp(profile.created_at), updatedAt: parseTimestamp(profile.updated_at) } } function profileInput(profile: StudentDraft): object { return { name: profile.name, grade: profile.grade, subject: profile.subject, academic_year: profile.academicYear, term: profile.term, guardian_title: profile.guardianTitle, start_time: profile.startTime, end_time: profile.endTime } } function mapDefaults(defaults: RawProfileDefaults): StudentProfileDefaults { return { grade: defaults.grade, subject: defaults.subject, lessonDurationMinutes: defaults.lesson_duration_minutes } } function mapFeedbackRecord(record: RawFeedbackRecord): FeedbackRecord { return { id: record.id, profileId: record.profile_id, feedbackDate: record.feedback_date, content: record.content, createdAt: parseTimestamp(record.created_at), updatedAt: parseTimestamp(record.updated_at) } } export async function getHealth(): Promise { const result = await request('/health') return { status: result.status, databaseConfigured: result.database_configured } } export async function listProfiles(): Promise { return (await request('/api/v1/profiles')).map(mapProfile) } export async function createProfile(profile: StudentDraft): Promise { return mapProfile(await request('/api/v1/profiles', 'POST', profileInput(profile))) } export async function deleteProfile(profileId: string): Promise { await request(`/api/v1/profiles/${encodeURIComponent(profileId)}`, 'DELETE') } export async function getProfileDefaults(): Promise { return mapDefaults(await request('/api/v1/profile-defaults')) } export async function updateProfileDefaults( defaults: StudentProfileDefaults ): Promise { const result = await request('/api/v1/profile-defaults', 'PUT', { grade: defaults.grade, subject: defaults.subject, lesson_duration_minutes: defaults.lessonDurationMinutes }) return mapDefaults(result) } export async function listFeedbackRecords(profileId?: string): Promise { const query = profileId ? `?profile_id=${encodeURIComponent(profileId)}` : '' return (await request(`/api/v1/feedback-records${query}`)).map(mapFeedbackRecord) } export async function createFeedbackRecord(draft: FeedbackDraft): Promise { const result = await request('/api/v1/feedback-records', 'POST', { profile_id: draft.profileId, feedback_date: draft.feedbackDate, content: draft.content }) return mapFeedbackRecord(result) } export async function deleteFeedbackRecord(recordId: string): Promise { await request(`/api/v1/feedback-records/${encodeURIComponent(recordId)}`, 'DELETE') }