feat(app): 实现完整的财务管家记账聊天与统计功能

- 新增App主组件,实现聊天、账本、统计三大功能模块切换
- 支持语音及文本输入,自动解析记账信息并生成交易记录
- 交易记录支持编辑、删除、提醒等操作,数据本地持久化
- 管家性格预设和自定义设定,包含头像上传和语气调整
- 会话展示支持关联交易显示,样式区分收入与支出
- 账本页添加日期分组交易列表及饼图统计展示
- 消息列表实现渐变背景和交互动画,提升用户体验
- 支持图片上传并同步发送给AI进行解析记账
- 底部导航实现三标签切换并同步显示对应内容
- 全局事务编辑弹窗支持直接调用和删除操作
- 增加环境变量示例和.gitignore规则,完善工程配置
This commit is contained in:
2026-01-19 20:20:54 +08:00
commit c06e74bd01
24 changed files with 7081 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
import React, { useState, useEffect } from 'react';
import { Transaction, TransactionType, ExpenseCategory, IncomeCategory } from '../types';
interface EditTransactionModalProps {
transaction: Transaction;
onSave: (updated: Transaction) => void;
onDelete: (id: string) => void;
onClose: () => void;
hideDelete?: boolean;
}
const EditTransactionModal: React.FC<EditTransactionModalProps> = ({ transaction, onSave, onDelete, onClose, hideDelete = false }) => {
const [draft, setDraft] = useState<Transaction>(transaction);
useEffect(() => {
setDraft(transaction);
}, [transaction]);
const handleDelete = () => {
if (confirm('确定要删除这条记录吗?崔经理会盯着你的。')) {
onDelete(draft.id);
}
};
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="bg-white w-full max-w-md rounded-2xl p-6 shadow-2xl animate-float md:animate-none">
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-bold text-gray-900">编辑账单</h3>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>
</button>
</div>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">金额</label>
<input
type="number"
value={draft.amount}
onChange={(e) => setDraft({...draft, amount: parseFloat(e.target.value) || 0})}
className="w-full p-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none font-bold text-lg bg-white shadow-sm transition-all"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">内容</label>
<input
type="text"
value={draft.item}
onChange={(e) => setDraft({...draft, item: e.target.value})}
className="w-full p-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none bg-white shadow-sm transition-all"
/>
</div>
<div className="flex gap-4">
<div className="flex-1">
<label className="block text-sm font-medium text-gray-700 mb-1">类型</label>
<select
value={draft.type}
onChange={(e) => setDraft({
...draft,
type: e.target.value as TransactionType,
category: e.target.value === TransactionType.EXPENSE ? ExpenseCategory.MISC : IncomeCategory.OTHER
})}
className="w-full p-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-indigo-500 outline-none bg-white shadow-sm transition-all"
>
<option value={TransactionType.EXPENSE}>支出</option>
<option value={TransactionType.INCOME}>收入</option>
</select>
</div>
<div className="flex-1">
<label className="block text-sm font-medium text-gray-700 mb-1">分类</label>
<select
value={draft.category}
onChange={(e) => setDraft({...draft, category: e.target.value})}
className="w-full p-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-indigo-500 outline-none bg-white shadow-sm transition-all"
>
{draft.type === TransactionType.EXPENSE
? Object.values(ExpenseCategory).map(c => <option key={c} value={c}>{c}</option>)
: Object.values(IncomeCategory).map(c => <option key={c} value={c}>{c}</option>)
}
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">日期</label>
<input
type="date"
value={draft.date}
onChange={(e) => setDraft({...draft, date: e.target.value})}
className="w-full p-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-indigo-500 outline-none bg-white shadow-sm transition-all"
/>
</div>
<div className="pt-4 flex gap-3">
{!hideDelete && (
<button
onClick={handleDelete}
className="flex-1 py-3 px-4 bg-red-50 text-red-600 rounded-xl font-semibold hover:bg-red-100 transition-colors"
>
删除
</button>
)}
<button
onClick={() => onSave(draft)}
className={`py-3 px-4 bg-indigo-600 text-white rounded-xl font-semibold hover:bg-indigo-700 transition-all shadow-md shadow-indigo-200 ${hideDelete ? 'w-full' : 'flex-[2]'}`}
>
保存修改
</button>
</div>
</div>
</div>
</div>
);
};
export default EditTransactionModal;