init
This commit is contained in:
139
src/components/charts/SpendingTrendChart.tsx
Normal file
139
src/components/charts/SpendingTrendChart.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import type { Transaction } from '../../types';
|
||||
import { formatCurrency } from '../../utils/format';
|
||||
|
||||
interface SpendingTrendChartProps {
|
||||
transactions: Transaction[];
|
||||
days?: number;
|
||||
}
|
||||
|
||||
export const SpendingTrendChart: React.FC<SpendingTrendChartProps> = ({
|
||||
transactions,
|
||||
days = 7
|
||||
}) => {
|
||||
// Process data for the chart
|
||||
const chartData = useMemo(() => {
|
||||
const today = new Date();
|
||||
const data: { date: string; amount: number; fullDate: string }[] = [];
|
||||
|
||||
for (let i = days - 1; i >= 0; i--) {
|
||||
const d = new Date(today);
|
||||
d.setDate(d.getDate() - i);
|
||||
const dateStr = d.toISOString().split('T')[0];
|
||||
const displayDate = `${d.getMonth() + 1}/${d.getDate()}`;
|
||||
|
||||
const dailyTotal = transactions
|
||||
.filter(t => t.type === 'expense' && t.transactionDate.startsWith(dateStr))
|
||||
.reduce((sum, t) => sum + Math.abs(t.amount), 0);
|
||||
|
||||
data.push({
|
||||
date: displayDate,
|
||||
amount: dailyTotal,
|
||||
fullDate: dateStr
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}, [transactions, days]);
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||
borderColor: '#e2e8f0',
|
||||
textStyle: {
|
||||
color: '#1e293b'
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
const item = params[0];
|
||||
return `${item.name}<br/>支出: ${formatCurrency(item.value, 'CNY')}`;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
top: '10%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: chartData.map(d => d.date),
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#94a3b8'
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#f1f5f9',
|
||||
type: 'dashed'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#94a3b8',
|
||||
formatter: (value: number) => {
|
||||
if (value >= 1000) return `${(value / 1000).toFixed(1)}k`;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: chartData.map(d => d.amount),
|
||||
type: 'bar', // Changed to bar for cleaner daily comparison, or line for trend
|
||||
smooth: true,
|
||||
name: '支出',
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#f59e0b' }, // Amber 500
|
||||
{ offset: 1, color: '#d97706' } // Amber 600
|
||||
]
|
||||
},
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
},
|
||||
showBackground: true,
|
||||
backgroundStyle: {
|
||||
color: 'rgba(241, 245, 249, 0.5)'
|
||||
},
|
||||
animationDuration: 1000,
|
||||
animationEasing: 'cubicOut'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="glass-card" style={{ padding: '1.5rem', marginTop: 'var(--spacing-xl)' }}>
|
||||
<h3 style={{
|
||||
margin: '0 0 1rem 0',
|
||||
fontSize: '1.25rem',
|
||||
color: 'var(--color-text)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem'
|
||||
}}>
|
||||
<span style={{
|
||||
width: '4px',
|
||||
height: '20px',
|
||||
background: 'var(--color-primary)',
|
||||
borderRadius: '2px',
|
||||
display: 'inline-block'
|
||||
}} />
|
||||
近7日支出趋势
|
||||
</h3>
|
||||
<ReactECharts option={option} style={{ height: '300px' }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user