Files
app_02_bill/services/api.ts
karlcarlo 194de6ec06 feat(api): 集成后端API,实现数据持久化和用户登录
- 新增token管理和用户信息状态,支持基于token的自动登录
- 实现登录或注册接口调用,自动根据URL参数完成身份验证
- 从后端加载交易记录、消息和待办提醒,替代本地缓存读写
- 对交易记录的增删改操作同步到数据库,确保数据一致性
- 对消息和提醒的增删改操作支持服务器同步,提升数据可靠性
- 优化消息发送逻辑,先保存用户消息,再处理AI回复并存库
- 添加待办提醒切换状态API调用,确保状态同步服务器
- 改进界面互动,支持文件与头像上传功能,更新管家形象
- 采用异步处理并增加错误捕获和用户提示,增强稳定性
2026-01-22 18:25:42 +08:00

679 lines
14 KiB
TypeScript

import { Transaction, Message, BillReminder } from '../types';
// API基础配置
const getApiUrl = (): string => {
return process.env.API_URL || 'http://localhost:8000';
};
const getAuthToken = (): string | null => {
return localStorage.getItem('auth_token');
};
// 统一响应格式
interface ApiResponse<T = any> {
code: number;
msg?: string;
message?: string;
result?: T;
}
// 分页响应格式
interface PaginatedResponse<T> {
items: T[];
skip: number;
total: number;
}
// 统计数据响应格式
interface TransactionStats {
month: string;
total_income: number;
total_expense: number;
balance: number;
expense_by_category: Record<string, number>;
income_by_category: Record<string, number>;
daily_expense: Array<{ date: string; amount: number }>;
}
// 预算响应格式
interface Budget {
id: string;
user_id?: string;
member_id?: string;
budgets: Record<string, number>;
month: string;
created_at?: string;
updated_at?: string;
}
// 预算执行情况
interface BudgetExecution {
month: string;
budgets: Record<string, {
budget: number;
spent: number;
remaining: number;
percentage: number;
}>;
total_budget: number;
total_spent: number;
total_remaining: number;
is_over_budget: boolean;
}
// AI设置
interface AISettings {
id?: string;
user_id?: string;
member_id?: string;
name: string;
persona: string;
avatar?: string;
created_at?: string;
updated_at?: string;
}
// 通用请求函数
async function request<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const token = getAuthToken();
const apiUrl = getApiUrl();
const url = `${apiUrl}${endpoint}`;
console.log(`请求地址: ${url}`);
const defaultHeaders: HeadersInit = {
'Content-Type': 'application/json',
};
if (token) {
defaultHeaders['Authorization'] = `Bearer ${token}`;
}
const config: RequestInit = {
...options,
headers: {
...defaultHeaders,
...options.headers,
},
};
try {
const response = await fetch(url, config);
const data: ApiResponse<T> = await response.json();
if (!response.ok || (data.code && data.code !== 200)) {
const errorMsg = data.msg || data.message || `HTTP error! status: ${response.status}`;
throw new Error(errorMsg);
}
return data.result as T;
} catch (error) {
console.error('API请求错误:', error);
throw error;
}
}
// ==================== 用户认证接口 ====================
/**
* 登录或注册
*/
export async function loginOrRegister(openid: string, mobile: string): Promise<{
token: string;
user: any;
}> {
const apiUrl = getApiUrl();
console.log(`请求地址: ${apiUrl}/wx/loginOrRegister`);
const response = await fetch(`${apiUrl}/wx/loginOrRegister`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ openid, mobile }),
});
const data = await response.json();
if (data.code === 200 && data.result?.token) {
// 保存token到localStorage
localStorage.setItem('auth_token', data.result.token);
}
return data.result;
}
/**
* 获取用户信息
*/
export async function getUserProfile(): Promise<any> {
return request<any>('/wx/profile', {
method: 'GET',
});
}
/**
* 退出登录
*/
export function logout(): void {
localStorage.removeItem('auth_token');
}
// ==================== 交易记录接口 ====================
/**
* 创建交易记录
*/
export async function createTransaction(data: {
item: string;
amount: number;
type: 'expense' | 'income';
category: string;
date: string;
timestamp: number;
}): Promise<Transaction> {
return request<Transaction>('/bill/transactions/', {
method: 'POST',
body: JSON.stringify(data),
});
}
/**
* 查询交易记录列表(分页)
*/
export async function getTransactions(params?: {
skip?: number;
limit?: number;
type?: 'expense' | 'income';
category?: string;
start_date?: string;
end_date?: string;
month?: string;
}): Promise<PaginatedResponse<Transaction>> {
const queryString = new URLSearchParams(
Object.entries(params || {}).reduce((acc, [key, value]) => {
if (value !== undefined) {
acc[key] = String(value);
}
return acc;
}, {} as Record<string, string>)
).toString();
return request<PaginatedResponse<Transaction>>(
`/bill/transactions/${queryString ? `?${queryString}` : ''}`
);
}
/**
* 获取单个交易记录
*/
export async function getTransaction(id: string): Promise<Transaction> {
return request<Transaction>(`/bill/transactions/${id}`);
}
/**
* 更新交易记录
*/
export async function updateTransaction(
id: string,
data: Partial<{
item: string;
amount: number;
type: 'expense' | 'income';
category: string;
date: string;
timestamp: number;
}>
): Promise<Transaction> {
return request<Transaction>(`/bill/transactions/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
/**
* 删除交易记录
*/
export async function deleteTransaction(id: string): Promise<void> {
return request<void>(`/bill/transactions/${id}`, {
method: 'DELETE',
});
}
/**
* 批量删除交易记录
*/
export async function batchDeleteTransactions(
transactionIds: string[]
): Promise<{ deleted_count: number }> {
return request<{ deleted_count: number }>('/bill/transactions/batch', {
method: 'DELETE',
body: JSON.stringify({ transaction_ids: transactionIds }),
});
}
/**
* 获取交易统计数据
*/
export async function getTransactionStats(
month: string
): Promise<TransactionStats> {
return request<TransactionStats>(`/bill/transactions/stats/summary?month=${month}`);
}
// ==================== 聊天消息接口 ====================
/**
* 创建聊天消息
*/
export async function createMessage(data: {
role: 'user' | 'assistant';
content: string;
timestamp: number;
transaction_id?: string;
}): Promise<Message> {
return request<Message>('/bill/messages/', {
method: 'POST',
body: JSON.stringify(data),
});
}
/**
* 查询聊天消息列表
*/
export async function getMessages(params?: {
skip?: number;
limit?: number;
role?: 'user' | 'assistant';
start_timestamp?: number;
end_timestamp?: number;
}): Promise<PaginatedResponse<Message>> {
const queryString = new URLSearchParams(
Object.entries(params || {}).reduce((acc, [key, value]) => {
if (value !== undefined) {
acc[key] = String(value);
}
return acc;
}, {} as Record<string, string>)
).toString();
return request<PaginatedResponse<Message>>(
`/bill/messages/${queryString ? `?${queryString}` : ''}`
);
}
/**
* 获取单个消息
*/
export async function getMessage(id: string): Promise<Message> {
return request<Message>(`/bill/messages/${id}`);
}
/**
* 删除消息
*/
export async function deleteMessage(id: string): Promise<void> {
return request<void>(`/bill/messages/${id}`, {
method: 'DELETE',
});
}
/**
* 清空聊天历史
*/
export async function clearMessages(): Promise<{ deleted_count: number }> {
return request<{ deleted_count: number }>('/bill/messages/clear', {
method: 'DELETE',
});
}
// ==================== 预算管理接口 ====================
/**
* 获取月度预算
*/
export async function getMonthlyBudget(month: string): Promise<Budget> {
return request<Budget>(`/bill/budgets/${month}`);
}
/**
* 设置/更新月度预算
*/
export async function updateMonthlyBudget(
month: string,
budgets: Record<string, number>
): Promise<Budget> {
return request<Budget>(`/bill/budgets/${month}`, {
method: 'PUT',
body: JSON.stringify({ budgets }),
});
}
/**
* 获取预算执行情况
*/
export async function getBudgetExecution(
month: string
): Promise<BudgetExecution> {
return request<BudgetExecution>(`/bill/budgets/${month}/execution`);
}
// ==================== 账单提醒接口 ====================
/**
* 创建账单提醒
*/
export async function createReminder(data: {
title: string;
amount?: number;
frequency: 'monthly' | 'once';
target_date: string;
}): Promise<BillReminder> {
return request<BillReminder>('/bill/reminders/', {
method: 'POST',
body: JSON.stringify(data),
});
}
/**
* 查询账单提醒列表
*/
export async function getReminders(params?: {
skip?: number;
limit?: number;
frequency?: 'monthly' | 'once';
is_completed?: boolean;
month?: string;
}): Promise<PaginatedResponse<BillReminder>> {
const queryString = new URLSearchParams(
Object.entries(params || {}).reduce((acc, [key, value]) => {
if (value !== undefined) {
acc[key] = String(value);
}
return acc;
}, {} as Record<string, string>)
).toString();
return request<PaginatedResponse<BillReminder>>(
`/bill/reminders/${queryString ? `?${queryString}` : ''}`
);
}
/**
* 获取单个提醒
*/
export async function getReminder(id: string): Promise<BillReminder> {
return request<BillReminder>(`/bill/reminders/${id}`);
}
/**
* 更新账单提醒
*/
export async function updateReminder(
id: string,
data: Partial<{
title: string;
amount?: number;
frequency: 'monthly' | 'once';
target_date: string;
}>
): Promise<BillReminder> {
return request<BillReminder>(`/bill/reminders/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
/**
* 切换提醒完成状态
*/
export async function toggleReminder(
id: string
): Promise<{ id: string; is_completed: boolean }> {
return request<{ id: string; is_completed: boolean }>(
`/bill/reminders/${id}/toggle`,
{
method: 'PATCH',
}
);
}
/**
* 删除账单提醒
*/
export async function deleteReminder(id: string): Promise<void> {
return request<void>(`/bill/reminders/${id}`, {
method: 'DELETE',
});
}
/**
* 获取即将到期的提醒
*/
export async function getUpcomingReminders(
days: number = 7
): Promise<PaginatedResponse<BillReminder>> {
return request<PaginatedResponse<BillReminder>>(
`/bill/reminders/upcoming?days=${days}`
);
}
// ==================== AI设置接口 ====================
/**
* 获取AI设置
*/
export async function getAISettings(): Promise<AISettings> {
return request<AISettings>('/h5/ai-settings');
}
/**
* 更新AI设置
*/
export async function updateAISettings(data: {
name?: string;
persona?: string;
avatar?: string;
}): Promise<AISettings> {
return request<AISettings>('/h5/ai-settings', {
method: 'PUT',
body: JSON.stringify(data),
});
}
/**
* 重置AI设置为默认
*/
export async function resetAISettings(): Promise<AISettings> {
return request<AISettings>('/h5/ai-settings/reset', {
method: 'POST',
});
}
// ==================== 用户偏好接口 ====================
interface UserPreferences {
id?: string;
user_id?: string;
custom_preset_name?: string;
custom_preset_description?: string;
theme_index?: number;
created_at?: string;
updated_at?: string;
}
/**
* 获取用户偏好
*/
export async function getUserPreferences(): Promise<UserPreferences> {
return request<UserPreferences>('/h5/preferences');
}
/**
* 更新用户偏好
*/
export async function updateUserPreferences(
data: Partial<UserPreferences>
): Promise<UserPreferences> {
return request<UserPreferences>('/h5/preferences', {
method: 'PUT',
body: JSON.stringify(data),
});
}
// ==================== 数据同步接口 ====================
interface SyncData {
transactions?: Transaction[];
messages?: Message[];
budgets?: Record<string, number>;
reminders?: BillReminder[];
ai_settings?: AISettings;
preferences?: UserPreferences;
}
/**
* 全量数据同步(上传)
*/
export async function uploadSyncData(data: SyncData): Promise<{
synced_at: string;
summary: {
transactions_count: number;
messages_count: number;
reminders_count: number;
};
}> {
return request('/bill/sync/upload', {
method: 'POST',
body: JSON.stringify(data),
});
}
/**
* 全量数据同步(下载)
*/
export async function downloadSyncData(): Promise<SyncData> {
return request<SyncData>('/bill/sync/download');
}
/**
* 增量数据同步
*/
export async function incrementalSync(data: {
last_sync_at: string;
changes: SyncData;
}): Promise<{
synced_at: string;
server_changes: SyncData;
}> {
return request('/bill/sync/incremental', {
method: 'POST',
body: JSON.stringify(data),
});
}
// ==================== AI处理接口 ====================
interface AIProcessResult {
transaction: {
item: string;
amount: number;
type: 'expense' | 'income';
category: string;
} | null;
reply: string;
created_transaction_id?: string;
created_message_ids?: string[];
}
/**
* AI处理用户消息
*/
export async function processAIMessage(data: {
message: string;
image_base64?: string;
mime_type?: string;
}): Promise<AIProcessResult> {
return request<AIProcessResult>('/ai/process', {
method: 'POST',
body: JSON.stringify(data),
});
}
/**
* AI识别账单图片
*/
export async function recognizeReceipt(
imageFile: File
): Promise<AIProcessResult> {
const formData = new FormData();
formData.append('image', imageFile);
// 注意: FormData需要特殊处理,不设置Content-Type让浏览器自动设置
return request<AIProcessResult>('/ai/recognize-receipt', {
method: 'POST',
body: formData,
headers: {}, // 清空默认headers,让fetch自动处理multipart/form-data
});
}
// ==================== 导出所有API ====================
export default {
// 用户认证
loginOrRegister,
getUserProfile,
logout,
// 交易记录
createTransaction,
getTransactions,
getTransaction,
updateTransaction,
deleteTransaction,
batchDeleteTransactions,
getTransactionStats,
// 聊天消息
createMessage,
getMessages,
getMessage,
deleteMessage,
clearMessages,
// 预算管理
getMonthlyBudget,
updateMonthlyBudget,
getBudgetExecution,
// 账单提醒
createReminder,
getReminders,
getReminder,
updateReminder,
toggleReminder,
deleteReminder,
getUpcomingReminders,
// AI设置
getAISettings,
updateAISettings,
resetAISettings,
// 用户偏好
getUserPreferences,
updateUserPreferences,
// 数据同步
uploadSyncData,
downloadSyncData,
incrementalSync,
// AI处理
processAIMessage,
recognizeReceipt,
};