feat(api): 集成后端API,实现数据持久化和用户登录
- 新增token管理和用户信息状态,支持基于token的自动登录 - 实现登录或注册接口调用,自动根据URL参数完成身份验证 - 从后端加载交易记录、消息和待办提醒,替代本地缓存读写 - 对交易记录的增删改操作同步到数据库,确保数据一致性 - 对消息和提醒的增删改操作支持服务器同步,提升数据可靠性 - 优化消息发送逻辑,先保存用户消息,再处理AI回复并存库 - 添加待办提醒切换状态API调用,确保状态同步服务器 - 改进界面互动,支持文件与头像上传功能,更新管家形象 - 采用异步处理并增加错误捕获和用户提示,增强稳定性
This commit is contained in:
@@ -23,8 +23,8 @@ const EditTransactionModal: React.FC<EditTransactionModalProps> = ({ transaction
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-[60] flex items-end md:items-center justify-center bg-black/50 backdrop-blur-sm p-4">
|
<div className="fixed inset-0 z-[60] flex items-end md:items-center justify-center bg-black/50 backdrop-blur-sm p-4 pb-28 md:pb-4">
|
||||||
<div className="bg-white w-full max-w-md rounded-2xl p-6 shadow-2xl animate-float md:animate-none">
|
<div className="bg-white w-full max-w-md rounded-2xl p-6 shadow-2xl md:animate-none max-h-[calc(100vh-120px)] overflow-y-auto no-scrollbar">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3 className="text-lg font-bold text-gray-900">编辑账单</h3>
|
<h3 className="text-lg font-bold text-gray-900">编辑账单</h3>
|
||||||
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">
|
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import BillReminders, { getDaysRemaining } from './BillReminders';
|
|||||||
|
|
||||||
interface LedgerPageProps {
|
interface LedgerPageProps {
|
||||||
transactions: Transaction[];
|
transactions: Transaction[];
|
||||||
onUpdateTransaction: (t: Transaction) => void;
|
onUpdateTransaction: (t: Transaction) => Promise<void>;
|
||||||
onDeleteTransaction: (id: string) => void;
|
onDeleteTransaction: (id: string) => Promise<void>;
|
||||||
budgets: Record<string, number>;
|
budgets: Record<string, number>;
|
||||||
setBudgets: (val: Record<string, number>) => void;
|
setBudgets: (val: Record<string, number>) => void;
|
||||||
reminders: BillReminder[];
|
reminders: BillReminder[];
|
||||||
@@ -200,7 +200,24 @@ const LedgerPage: React.FC<LedgerPageProps> = ({
|
|||||||
{subTab === 'calendar' && <CalendarView transactions={transactions} reminders={reminders} viewDate={selectedMonth} />}
|
{subTab === 'calendar' && <CalendarView transactions={transactions} reminders={reminders} viewDate={selectedMonth} />}
|
||||||
{subTab === 'bills' && <BillReminders reminders={reminders.filter(r => r.frequency === 'monthly' || r.targetDate.startsWith(selectedMonth))} onAdd={onAddReminder} onUpdate={onUpdateReminder} onDelete={onDeleteReminder} onToggle={onToggleReminder} />}
|
{subTab === 'bills' && <BillReminders reminders={reminders.filter(r => r.frequency === 'monthly' || r.targetDate.startsWith(selectedMonth))} onAdd={onAddReminder} onUpdate={onUpdateReminder} onDelete={onDeleteReminder} onToggle={onToggleReminder} />}
|
||||||
</div>
|
</div>
|
||||||
{editingTransaction && <EditTransactionModal transaction={editingTransaction} onSave={(updated) => { onUpdateTransaction(updated); setEditingTransaction(null); }} onDelete={(id) => { onDeleteTransaction(id); setEditingTransaction(null); }} onClose={() => setEditingTransaction(null)} />}
|
{editingTransaction && (
|
||||||
|
<EditTransactionModal
|
||||||
|
transaction={editingTransaction}
|
||||||
|
onSave={async (updated) => {
|
||||||
|
try {
|
||||||
|
await onUpdateTransaction(updated);
|
||||||
|
setEditingTransaction(null);
|
||||||
|
} catch (e) {
|
||||||
|
// Error handled in App.tsx
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onDelete={async (id) => {
|
||||||
|
await onDeleteTransaction(id);
|
||||||
|
setEditingTransaction(null);
|
||||||
|
}}
|
||||||
|
onClose={() => setEditingTransaction(null)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ const VoiceInput: React.FC<VoiceInputProps> = ({ onTranscript, isProcessing, isF
|
|||||||
const recognitionRef = useRef<any>(null);
|
const recognitionRef = useRef<any>(null);
|
||||||
const timerRef = useRef<number | null>(null);
|
const timerRef = useRef<number | null>(null);
|
||||||
|
|
||||||
|
const isStartingRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
|
if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
|
||||||
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||||
@@ -22,30 +24,37 @@ const VoiceInput: React.FC<VoiceInputProps> = ({ onTranscript, isProcessing, isF
|
|||||||
recognitionRef.current.onresult = (event: any) => {
|
recognitionRef.current.onresult = (event: any) => {
|
||||||
const transcript = event.results[0][0].transcript;
|
const transcript = event.results[0][0].transcript;
|
||||||
if (transcript) onTranscript(transcript);
|
if (transcript) onTranscript(transcript);
|
||||||
setIsListening(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
recognitionRef.current.onerror = (event: any) => {
|
recognitionRef.current.onerror = (event: any) => {
|
||||||
|
if (event.error === 'network') {
|
||||||
|
alert("语音识别网络连接失败。请检查网络或尝试切换浏览器(推荐 Chrome/Edge)。");
|
||||||
|
}
|
||||||
console.error('Speech recognition error', event.error);
|
console.error('Speech recognition error', event.error);
|
||||||
setIsListening(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
recognitionRef.current.onend = () => {
|
recognitionRef.current.onend = () => {
|
||||||
setIsListening(false);
|
setIsListening(false);
|
||||||
|
isStartingRef.current = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [onTranscript]);
|
}, [onTranscript]);
|
||||||
|
|
||||||
const startListening = (e: React.MouseEvent | React.TouchEvent) => {
|
const startListening = (e: React.PointerEvent) => {
|
||||||
e.preventDefault();
|
if (e.cancelable) e.preventDefault();
|
||||||
if (isProcessing || isListening) return;
|
|
||||||
|
if (isProcessing || isListening || isStartingRef.current) return;
|
||||||
|
|
||||||
if (recognitionRef.current) {
|
if (recognitionRef.current) {
|
||||||
try {
|
try {
|
||||||
|
isStartingRef.current = true;
|
||||||
recognitionRef.current.start();
|
recognitionRef.current.start();
|
||||||
setIsListening(true);
|
setIsListening(true);
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
console.error("Failed to start recognition", e);
|
console.error("Failed to start recognition", err);
|
||||||
|
// 如果 start 失败,立即重置状态,否则会锁死
|
||||||
|
isStartingRef.current = false;
|
||||||
|
setIsListening(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alert("您的浏览器不支持语音识别,请使用 Chrome 或 Edge。");
|
alert("您的浏览器不支持语音识别,请使用 Chrome 或 Edge。");
|
||||||
@@ -53,20 +62,23 @@ const VoiceInput: React.FC<VoiceInputProps> = ({ onTranscript, isProcessing, isF
|
|||||||
};
|
};
|
||||||
|
|
||||||
const stopListening = () => {
|
const stopListening = () => {
|
||||||
|
// 仅在识别引擎正在运行时尝试停止
|
||||||
|
// 不要在这里重置 isListening 和 isStartingRef,等待 onend 回调
|
||||||
if (isListening) {
|
if (isListening) {
|
||||||
recognitionRef.current?.stop();
|
try {
|
||||||
setIsListening(false);
|
recognitionRef.current?.stop();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to stop recognition", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${isFullWidth ? 'w-full' : ''} relative select-none touch-none`}>
|
<div className={`${isFullWidth ? 'w-full' : ''} relative select-none touch-none`}>
|
||||||
<button
|
<button
|
||||||
onMouseDown={startListening}
|
onPointerDown={startListening}
|
||||||
onMouseUp={stopListening}
|
onPointerUp={stopListening}
|
||||||
onMouseLeave={stopListening}
|
onPointerLeave={stopListening}
|
||||||
onTouchStart={startListening}
|
|
||||||
onTouchEnd={stopListening}
|
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
className={`relative flex items-center justify-center transition-all duration-300 active:scale-95 shadow-lg ${
|
className={`relative flex items-center justify-center transition-all duration-300 active:scale-95 shadow-lg ${
|
||||||
isFullWidth
|
isFullWidth
|
||||||
|
|||||||
224
docs/数据结构.md
224
docs/数据结构.md
@@ -1,4 +1,5 @@
|
|||||||
# App_02_记账
|
# App_02_记账
|
||||||
|
|
||||||
- 应用信息
|
- 应用信息
|
||||||
- 应用名称
|
- 应用名称
|
||||||
- 记账
|
- 记账
|
||||||
@@ -9,83 +10,158 @@
|
|||||||
- 应用描述
|
- 应用描述
|
||||||
- 一个精明、毒舌但实际上很负责任的财务经理。说话喜欢带刺,吐槽用户乱花钱,但关键时刻会给出靠谱建议。
|
- 一个精明、毒舌但实际上很负责任的财务经理。说话喜欢带刺,吐槽用户乱花钱,但关键时刻会给出靠谱建议。
|
||||||
- 数据结构
|
- 数据结构
|
||||||
- Project 项目分类
|
- 枚举数据
|
||||||
- name 名称(name)
|
- TransactionType 交易类型
|
||||||
- desc 背景(context)
|
- EXPENSE = "expense"
|
||||||
- target 目标(goal)
|
- INCOME = "income"
|
||||||
- value 价值(value)
|
- ExpenseCategory 支出分类
|
||||||
|
- FOOD = "餐饮"
|
||||||
|
- SHOPPING = "购物"
|
||||||
|
- TRANSPORT = "交通"
|
||||||
|
- HOUSING = "住房"
|
||||||
|
- ENTERTAINMENT = "娱乐"
|
||||||
|
- MEDICAL = "医疗"
|
||||||
|
- SOCIAL = "人情"
|
||||||
|
- EDUCATION = "学习"
|
||||||
|
- FINANCE = "金融"
|
||||||
|
- CHILD = "孩子"
|
||||||
|
- PET = "宠物"
|
||||||
|
- CAR = "汽车"
|
||||||
|
- BEAUTY = "美容"
|
||||||
|
- BUSINESS = "经营"
|
||||||
|
- MISC = "杂项"
|
||||||
|
- IncomeCategory 收入分类
|
||||||
|
- SALARY = "工资"
|
||||||
|
- BONUS = "奖金"
|
||||||
|
- INVESTMENT = "理财"
|
||||||
|
- PARTTIME = "兼职"
|
||||||
|
- GIFT = "红包"
|
||||||
|
- OTHER = "其他收入"
|
||||||
|
- MessageRole 消息角色
|
||||||
|
- USER = "user"
|
||||||
|
- ASSISTANT = "assistant"
|
||||||
|
- ReminderFrequency 提醒频率
|
||||||
|
- MONTHLY = "monthly"
|
||||||
|
- ONCE = "once"
|
||||||
|
|
||||||
|
- Common 通用属性
|
||||||
|
- seq 排序
|
||||||
|
- isEnabled 是否启用
|
||||||
- creator 创建人
|
- creator 创建人
|
||||||
- Member
|
- created_at 创建时间
|
||||||
- Task 任务项目
|
- updated_at 更新时间
|
||||||
- title 标题(title)
|
- Transaction 交易记录
|
||||||
- project 分类(projectId)
|
- item 交易项目名称
|
||||||
- Project
|
- amount: float 交易金额
|
||||||
- desc 描述(notes)
|
- type: TransactionType 交易类型
|
||||||
- startDate 开始日期(date)
|
- category: ExpenseCategory | IncomeCategory 交易分类
|
||||||
- startTime 开始时间(startTime)
|
- date 交易日期 YYYY-MM-DD
|
||||||
- isCompleted 是否完成(isCompleted)
|
- timestamp: int 时间戳(毫秒)
|
||||||
- created_at 创建时间(createdAt)
|
- Message 聊天消息
|
||||||
- creator 创建人
|
- role: MessageRole 消息角色
|
||||||
- Member
|
- content 消息内容
|
||||||
|
- timestamp: int 时间戳(毫秒)
|
||||||
|
- transaction_id 关联的交易ID
|
||||||
|
- Budget 预算
|
||||||
|
- member_id 会员ID
|
||||||
|
- budgets: dict 预算字典 {名称: 金额}
|
||||||
|
- month 预算月份 YYYY-MM
|
||||||
|
- Reminder 账单提醒
|
||||||
|
- member_id 会员ID
|
||||||
|
- title 提醒标题
|
||||||
|
- amount: float 金额(可选)
|
||||||
|
- frequency: ReminderFrequency 频率
|
||||||
|
- target_date 目标日期: DD(每月) 或 YYYY-MM-DD(单次)
|
||||||
|
- is_completed: bool 完成状态
|
||||||
|
- AISettings AI助手设置
|
||||||
|
- member_id 会员ID
|
||||||
|
- name AI助手名称
|
||||||
|
- persona AI性格描述
|
||||||
|
- avatar 头像Base64编码
|
||||||
|
- UserPreference 用户偏好设置
|
||||||
|
- member_id 会员ID
|
||||||
|
- custom_preset_name 自定义预设名称
|
||||||
|
- custom_preset_description 自定义预设描述
|
||||||
|
- theme_index: int 主题索引
|
||||||
|
|
||||||
- API接口
|
- API接口
|
||||||
- auth 授权
|
- Auth 授权
|
||||||
- POST /mp/auth/get_token
|
- POST /api/wx/get_token
|
||||||
- 获取token
|
- 获取token
|
||||||
- GET /mp/auth/profile
|
- GET /api/wx/profile
|
||||||
- 获取个人信息
|
- 获取个人信息
|
||||||
- 项目
|
- transaction Bill: 交易记录
|
||||||
- GET /mp/todo/projects
|
- GET /api/bill/transactions
|
||||||
- 项目列表
|
- 获取分页交易记录
|
||||||
- GET /mp/todo/projects/{project_id}
|
- GET /api/bill/transactions/{id}
|
||||||
- 获取项目详细
|
- 获取交易记录详情
|
||||||
- POST /mp/todo/projects
|
- POST /api/bill/transactions
|
||||||
- 创建项目
|
- 创建交易记录
|
||||||
- PUT /mp/todo/projects/{project_id}
|
- PUT /api/bill/transactions/{id}
|
||||||
- 更新项目
|
- 更新交易记录
|
||||||
- DELETE /mp/todo/projects/{project_id}
|
- DELETE /api/bill/transactions/{id}
|
||||||
- 删除项目
|
- 删除交易记录
|
||||||
- 任务
|
- Message Bill: 聊天消息
|
||||||
- GET /mp/todo/tasks
|
- GET /api/bill/messages
|
||||||
- 任务列表
|
- 获取分页聊天消息
|
||||||
- GET /mp/todo/tasks/{task_id}
|
- GET /api/bill/messages/{id}
|
||||||
- 获取任务详细
|
- 获取聊天消息详情
|
||||||
- POST /mp/todo/tasks
|
- POST /api/bill/messages
|
||||||
- 创建任务
|
- 创建聊天消息
|
||||||
- PUT /mp/todo/tasks/{task_id}
|
- PUT /api/bill/messages/{id}
|
||||||
- 更新任务
|
- 更新聊天消息
|
||||||
- DELETE /mp/todo/tasks/{task_id}
|
- DELETE /api/bill/messages/{id}
|
||||||
- 删除项目
|
- 删除聊天消息
|
||||||
- PUT /mp/todo/tasks/{task_id}/completed
|
- Budget Bill: 预算
|
||||||
- 设置为已完成
|
- GET /api/bill/budgets
|
||||||
- PUT /mp/todo/tasks/{task_id}/uncompleted
|
- 获取分页预算
|
||||||
- 设置为未完成
|
- GET /api/bill/budgets/{id}
|
||||||
|
- 获取预算详情
|
||||||
|
- POST /api/bill/transactions
|
||||||
|
- 创建交易记录
|
||||||
|
- PUT /api/bill/transactions/{id}
|
||||||
|
- 更新交易记录
|
||||||
|
- DELETE /api/bill/transactions/{id}
|
||||||
|
- 删除交易记录
|
||||||
|
- BillReminder Bill: 账单提醒
|
||||||
|
- GET /api/bill/bill_reminders
|
||||||
|
- 获取分页账单提醒
|
||||||
|
- GET /api/bill/bill_reminders/{id}
|
||||||
|
- 获取账单提醒详情
|
||||||
|
- POST /api/bill/transactions
|
||||||
|
- 创建交易记录
|
||||||
|
- PUT /api/bill/transactions/{id}
|
||||||
|
- 更新交易记录
|
||||||
|
- DELETE /api/bill/transactions/{id}
|
||||||
|
- 删除交易记录
|
||||||
|
- AISettings AI助手设置
|
||||||
|
- GET /api/h5/ai_settings
|
||||||
|
- 获取AI助手设置
|
||||||
|
- PUT /api/h5/ai_settings
|
||||||
|
- 更新AI助手设置
|
||||||
|
- POST /api/h5/ai_settings/reset
|
||||||
|
- 重置AI助手设置
|
||||||
|
- UserPreference 用户偏好设置
|
||||||
|
- GET /api/h5/user_preference
|
||||||
|
- 获取用户偏好设置
|
||||||
|
- PUT /api/h5/user_preference
|
||||||
|
- 更新用户偏好设置
|
||||||
|
|
||||||
- 问题/需求
|
- 问题/需求
|
||||||
- 单用户转换为多用户模式
|
- 单用户转换为多用户模式
|
||||||
- 增加登录验证
|
- 增加登录验证
|
||||||
- 增加creator字段
|
- 增加creator字段
|
||||||
- 登录问题
|
- 登录问题
|
||||||
- 小程序跳转到H5, 需要检测登录状态
|
|
||||||
- 如果已经登录, 需要把登录信息传递到H5首页
|
|
||||||
- 如果未登录跳转至小程序登录页
|
|
||||||
- URL传递参数openid和mobile
|
- URL传递参数openid和mobile
|
||||||
- H5端首页先检测token状态
|
- H5端首页先检测token状态
|
||||||
- 如果存在并且未过期, 则携带token获取用户信息
|
- 如果存在并且未过期, 则携带token获取用户信息
|
||||||
- 如果不存在则通过openid和mobile获取token, 并获取用户信息
|
- 如果不存在则通过openid和mobile获取token, 并获取用户信息
|
||||||
- 新建项目问题
|
- 语音接口识别
|
||||||
- 目前是实时更新模式, 保存到本地缓存没问题, 但是要持久化到数据库就会报错
|
- 接口鲁棒性调试
|
||||||
- 增加保存按钮, 点击保存后提交接口, 并同步本地数据
|
- 识别消息对接创建交易记录接口
|
||||||
- 分别处理本地新建项目和提交接口新建项目的逻辑, 并处理错误提示, 例如项目名称重复
|
- 约定和调试大模型返回数据
|
||||||
- 任务过滤查询
|
|
||||||
- 日期等于今天的任务
|
|
||||||
- 日期小于今日的任务
|
|
||||||
- 查询所有任务匹配到日历中
|
|
||||||
- 今日任务和日历中任务的匹配调节
|
|
||||||
- 测试任务2没有显示在今日任务中
|
|
||||||
- 最可能原因是日期转换过程中的时区差异。当API返回的日期时间字符串 "2026-01-14T00:00:00" 在客户端进行时区转换时,可能会因为客户端的时区设置导致日期变成 2026-01-13 或其他日期,从而不满足 t.date === systemDate 的条件。
|
|
||||||
- 多个在增删改查操作中存在的时区转换问题
|
|
||||||
- 开发任务
|
- 开发任务
|
||||||
- 小程序跳转时增加登录判断和URL传参(openid和mobile)
|
|
||||||
- 1小时/人
|
|
||||||
- H5首页接收openid和mobile参数
|
- H5首页接收openid和mobile参数
|
||||||
- 0.5小时/人
|
- 0.5小时/人
|
||||||
- H5首页token检测并获取, 通过接口获取用户信息
|
- H5首页token检测并获取, 通过接口获取用户信息
|
||||||
@@ -94,11 +170,19 @@
|
|||||||
- 2小时/人
|
- 2小时/人
|
||||||
- 开发H5端获取用户信息接口
|
- 开发H5端获取用户信息接口
|
||||||
- 1.5小时/人
|
- 1.5小时/人
|
||||||
- 开发项目增删改查接口
|
- 开发交易记录增删改查接口
|
||||||
- 4小时/人
|
- 4小时/人
|
||||||
- 开发任务增删改查接口和已完成/未完成接口
|
- 开发聊天增删改查接口
|
||||||
- 5小时/人
|
- 6小时/人
|
||||||
- 开发任务按时间过滤列表接口
|
- 开发预算增删改查接口
|
||||||
- 2小时/人
|
- 4小时/人
|
||||||
- 调试H5页面交互, 调用项目和任务接口
|
- 开发账单提醒增删改查完成接口
|
||||||
- 6~10小时/人
|
- 6小时/人
|
||||||
|
- 开发AI助手设置增删改查接口
|
||||||
|
- 4小时/人
|
||||||
|
- 开发用户偏好设置增删改查接口
|
||||||
|
- 3小时/人
|
||||||
|
- 调试H5页面交互, 调用交易记录、聊天、预算、账单提醒、助手设置、用户偏好接口
|
||||||
|
- 8~12小时/人
|
||||||
|
- 切换线上配置,打包、发布到线上服务器
|
||||||
|
- 2小时/人
|
||||||
@@ -4,7 +4,8 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>崔经理记账</title>
|
<title>崔经理记账</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<!-- <script src="https://cdn.tailwindcss.com"></script> -->
|
||||||
|
<script src="https://mp.yizhezhe.com/static/tailwindcss-3.4.17.js"></script>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&display=swap" rel="stylesheet">
|
||||||
|
|||||||
678
services/api.ts
Normal file
678
services/api.ts
Normal file
@@ -0,0 +1,678 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user