feat(app): 实现完整的财务管家记账聊天与统计功能
- 新增App主组件,实现聊天、账本、统计三大功能模块切换 - 支持语音及文本输入,自动解析记账信息并生成交易记录 - 交易记录支持编辑、删除、提醒等操作,数据本地持久化 - 管家性格预设和自定义设定,包含头像上传和语气调整 - 会话展示支持关联交易显示,样式区分收入与支出 - 账本页添加日期分组交易列表及饼图统计展示 - 消息列表实现渐变背景和交互动画,提升用户体验 - 支持图片上传并同步发送给AI进行解析记账 - 底部导航实现三标签切换并同步显示对应内容 - 全局事务编辑弹窗支持直接调用和删除操作 - 增加环境变量示例和.gitignore规则,完善工程配置
This commit is contained in:
114
components/Dashboard.tsx
Normal file
114
components/Dashboard.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user