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

114
components/Dashboard.tsx Normal file
View File

@@ -0,0 +1,114 @@
import React, { useMemo } from 'react';
import { Transaction, TransactionType, ExpenseCategory } from '../types';
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend, BarChart, Bar, XAxis, YAxis, CartesianGrid } from 'recharts';
interface DashboardProps {
expenses: Transaction[]; // Kept prop name as expenses for compatibility but type is Transaction
}
const COLORS = [
'#6366f1', // Indigo
'#ec4899', // Pink
'#10b981', // Emerald
'#f59e0b', // Amber
'#8b5cf6', // Violet
'#ef4444', // Red
'#3b82f6', // Blue
'#14b8a6', // Teal
'#f97316', // Orange
'#84cc16', // Lime
'#06b6d4', // Cyan
'#d946ef', // Fuchsia
'#64748b', // Slate
'#a855f7', // Purple
'#fbbf24' // Yellow
];
const Dashboard: React.FC<DashboardProps> = ({ expenses }) => {
const today = new Date().toISOString().split('T')[0];
// Filter only expenses for the dashboard visuals
const expenseTransactions = useMemo(() => expenses.filter(e => e.type === TransactionType.EXPENSE), [expenses]);
const todaysExpenses = useMemo(() => {
return expenseTransactions.filter(e => e.date === today);
}, [expenseTransactions, today]);
const totalToday = useMemo(() => {
return todaysExpenses.reduce((sum, e) => sum + e.amount, 0);
}, [todaysExpenses]);
const categoryData = useMemo(() => {
const map = new Map<string, number>();
todaysExpenses.forEach(e => {
map.set(e.category, (map.get(e.category) || 0) + e.amount);
});
return Array.from(map.entries()).map(([name, value], index) => ({
name,
value,
fill: COLORS[index % COLORS.length]
}));
}, [todaysExpenses]);
const weeklyData = useMemo(() => {
const data: { date: string; total: number }[] = [];
for (let i = 6; i >= 0; i--) {
const d = new Date();
d.setDate(d.getDate() - i);
const dateStr = d.toISOString().split('T')[0];
const dailySum = expenseTransactions
.filter(e => e.date === dateStr)
.reduce((sum, e) => sum + e.amount, 0);
data.push({ date: dateStr.slice(5), total: dailySum });
}
return data;
}, [expenseTransactions]);
return (
<div className="flex flex-col h-full bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div className="p-6 bg-gradient-to-r from-indigo-500 to-purple-600 text-white">
<h2 className="text-xl font-bold mb-1">今日消费统计</h2>
<div className="flex items-end gap-2">
<span className="text-4xl font-extrabold tracking-tight">¥{totalToday.toFixed(2)}</span>
<span className="text-indigo-100 mb-1 text-sm">{today}</span>
</div>
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-6 no-scrollbar">
<div className="bg-gray-50 p-4 rounded-xl">
<h3 className="text-gray-700 font-semibold mb-4 text-sm uppercase tracking-wider">今日分类占比</h3>
{categoryData.length > 0 ? (
<div className="h-64 w-full">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={categoryData}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={80}
paddingAngle={5}
dataKey="value"
>
{categoryData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.fill} />
))}
</Pie>
<Tooltip formatter={(value: number) => `¥${value.toFixed(2)}`} />
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
) : (
<div className="h-32 flex items-center justify-center text-gray-400 text-sm">
今天还没有消费哦,小王表现不错!
</div>
)}
</div>
</div>
</div>
);
};
export default Dashboard;