Files
Toolbox/apps/app_order_box/kits/motion_reminder/api.ts
T
yangxiangyuan bbc7ca6523 feat(personal_collect,motion_reminder): 新增回收站与云端TTS语音功能
- 新增小程序个人收藏回收站页面,支持查看已删除收藏、恢复与彻底删除操作
- 修复个人收藏文件上传时的中文名乱码问题,新增原文件名透传参数
- 接入阿里云百炼TTS服务,实现运动提醒模块的云端语音合成与本地缓存,支持系统TTS与云端MP3双降级播放
- 新增语音引擎探测与异常预警UI,覆盖web端与移动端
- 优化web端订单盒页面布局,修复卡片操作按钮换行问题
- 新增调试探针脚本、ESLint配置与相关lint脚本
- 完善后端接口与路由,更新项目依赖与配置文件
2026-09-17 16:26:27 +08:00

112 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import axios from 'axios';
import { ApiEnvelope, MotionCard, MotionSession, TTSPrepareResult } from './types';
export const API_BASE_URL = process.env.EXPO_PUBLIC_API_BASE_URL || '/api/app_order_box';
// 注意:MOTION_BASE 是相对于 axios baseURL 的路径,不要包含 API_BASE_URL 前缀
// 否则 axios 会双重拼接导致 404(如 /api/app_order_box/api/app_order_box/kits/...)
const MOTION_BASE = '/kits/motion_reminder';
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 15000,
headers: {
'Content-Type': 'application/json',
},
});
const formatError = (error: any): string => {
if (error?.response) {
return `服务器响应 ${error.response.status}: ${error.response.statusText}\n${JSON.stringify(error.response.data)}`;
}
if (error?.request) {
return `服务器无响应: 无法连接到 ${API_BASE_URL}`;
}
if (error?.message) {
return `请求错误: ${error.message}`;
}
return '未知错误';
};
export const motionReminderApi = {
async listCards(): Promise<MotionCard[]> {
try {
const response = await api.get<ApiEnvelope<MotionCard[]>>(`${MOTION_BASE}/cards`);
return response.data.data || [];
} catch (error) {
throw new Error(formatError(error));
}
},
async saveCard(card: Partial<MotionCard>): Promise<MotionCard> {
try {
const response = card.id
? await api.put<ApiEnvelope<MotionCard>>(`${MOTION_BASE}/cards`, card)
: await api.post<ApiEnvelope<MotionCard>>(`${MOTION_BASE}/cards`, card);
if (!response.data.ok || !response.data.data) {
throw new Error(response.data.error || '保存失败');
}
return response.data.data;
} catch (error) {
throw new Error(formatError(error));
}
},
async deleteCard(id: string): Promise<void> {
try {
await api.delete(`${MOTION_BASE}/cards`, { params: { id } });
} catch (error) {
throw new Error(formatError(error));
}
},
async listHistory(): Promise<MotionSession[]> {
try {
const response = await api.get<ApiEnvelope<MotionSession[]>>(`${MOTION_BASE}/history`);
return response.data.data || [];
} catch (error) {
throw new Error(formatError(error));
}
},
async startSession(card: MotionCard): Promise<MotionSession> {
try {
const response = await api.post<ApiEnvelope<MotionSession>>(`${MOTION_BASE}/sessions/start`, { card });
if (!response.data.ok || !response.data.data) {
throw new Error(response.data.error || '启动失败');
}
return response.data.data;
} catch (error) {
throw new Error(formatError(error));
}
},
async syncSession(payload: Partial<MotionSession> & { id: string }): Promise<void> {
try {
await api.post(`${MOTION_BASE}/sessions/sync`, payload);
} catch (error) {
throw new Error(formatError(error));
}
},
async finishSession(payload: Partial<MotionSession> & { id: string; status: 'completed' | 'stopped' }): Promise<void> {
try {
await api.post(`${MOTION_BASE}/sessions/finish`, payload);
} catch (error) {
throw new Error(formatError(error));
}
},
/** 批量预合成整卡「最小播报单元」的云端 mp3,返回文本→URL 映射(服务端 md5 内容寻址,重复文本不会重复合成) */
async prepareTTS(card: MotionCard): Promise<TTSPrepareResult> {
try {
const response = await api.post<ApiEnvelope<TTSPrepareResult>>(`${MOTION_BASE}/tts/prepare`, { card });
if (!response.data.ok || !response.data.data) {
throw new Error(response.data.error || '语音预合成失败');
}
return response.data.data;
} catch (error) {
throw new Error(formatError(error));
}
},
};