feat: 添加收支趋势折线图组件,展示收入、支出和结余随时间变化的趋势。
This commit is contained in:
@@ -38,18 +38,232 @@ function TrendLineChart({ data, title = '收支趋势', loading = false }: Trend
|
||||
trigger: 'axis',
|
||||
confine: true, // Keep tooltip inside chart area
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.8)',
|
||||
// ... (keep existing properties) ...
|
||||
borderColor: '#e2e8f0',
|
||||
borderWidth: 1,
|
||||
textStyle: {
|
||||
color: '#1e293b',
|
||||
},
|
||||
// ...
|
||||
return(
|
||||
<div className = "trend-line-chart" >
|
||||
extraCssText: 'backdrop-filter: blur(8px); border-radius: 8px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985',
|
||||
},
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
let result = `<div style="font-weight: 600; margin-bottom: 8px;">${params[0].axisValue}</div>`;
|
||||
|
||||
let income = 0;
|
||||
let expense = 0;
|
||||
|
||||
params.forEach((param: any) => {
|
||||
const color = param.color;
|
||||
const value = param.value;
|
||||
if (param.seriesName === '收入') income = value;
|
||||
if (param.seriesName === '支出') expense = value;
|
||||
|
||||
result += `<div style="display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 4px;">
|
||||
<span style="display: flex; align-items: center; gap: 6px;">
|
||||
<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background-color: ${color};"></span>
|
||||
<span style="color: #64748b; font-size: 12px;">${param.seriesName}</span>
|
||||
</span>
|
||||
<span style="font-weight: 600; font-family: 'Inter', sans-serif;">¥${value.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
})}</span>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
const net = income - expense;
|
||||
const isSurplus = net >= 0;
|
||||
const netColor = isSurplus ? '#10b981' : '#ef4444';
|
||||
const netLabel = isSurplus ? '结余' : '赤字';
|
||||
|
||||
result += `<div style="margin-top: 8px; padding-top: 8px; border-top: 1px dashed #e2e8f0; display: flex; align-items: center; justify-content: space-between; gap: 16px;">
|
||||
<span style="color: #64748b; font-size: 12px;">本日${netLabel}</span>
|
||||
<span style="font-weight: 700; color: ${netColor}; font-family: 'Inter', sans-serif;">
|
||||
${isSurplus ? '+' : ''}¥${net.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||
</span>
|
||||
</div>`;
|
||||
|
||||
return result;
|
||||
},
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'slider',
|
||||
show: true,
|
||||
xAxisIndex: [0],
|
||||
start: 0,
|
||||
end: 100,
|
||||
bottom: 0,
|
||||
borderColor: 'transparent',
|
||||
fillerColor: 'rgba(99, 102, 241, 0.1)',
|
||||
handleStyle: {
|
||||
color: '#6366f1',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'inside',
|
||||
xAxisIndex: [0],
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
legend: {
|
||||
data: ['收入', '支出', '结余'],
|
||||
top: 40,
|
||||
left: 'center',
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
top: 80,
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: data.map((item) => formatDate(item.date)),
|
||||
axisLabel: {
|
||||
rotate: 45,
|
||||
fontSize: 11,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: (value: number) => {
|
||||
if (value >= 10000) {
|
||||
return `${(value / 10000).toFixed(1)}万`;
|
||||
}
|
||||
return value.toFixed(0);
|
||||
},
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '收入',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: data.map((item) => item.income),
|
||||
itemStyle: {
|
||||
color: '#10b981',
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(16, 185, 129, 0.3)' },
|
||||
{ offset: 1, color: 'rgba(16, 185, 129, 0.05)' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '支出',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: data.map((item) => item.expense),
|
||||
itemStyle: {
|
||||
color: '#ef4444',
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(239, 68, 68, 0.3)' },
|
||||
{ offset: 1, color: 'rgba(239, 68, 68, 0.05)' },
|
||||
],
|
||||
},
|
||||
},
|
||||
markPoint: {
|
||||
data: [
|
||||
{ type: 'max', name: '最大支出' },
|
||||
],
|
||||
symbol: 'pin',
|
||||
symbolSize: 45,
|
||||
itemStyle: {
|
||||
color: '#ef4444'
|
||||
},
|
||||
label: {
|
||||
color: '#fff',
|
||||
fontSize: 10,
|
||||
formatter: 'Max'
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '结余',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
data: data.map((item) => item.balance),
|
||||
itemStyle: {
|
||||
color: '#3b82f6',
|
||||
},
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
type: 'dashed',
|
||||
},
|
||||
markPoint: {
|
||||
data: [
|
||||
{ type: 'max', name: 'Max' },
|
||||
{ type: 'min', name: 'Min' },
|
||||
],
|
||||
symbol: 'pin',
|
||||
symbolSize: 40,
|
||||
label: {
|
||||
color: '#fff',
|
||||
fontSize: 10,
|
||||
formatter: '{c}'
|
||||
}
|
||||
},
|
||||
markLine: {
|
||||
data: [{ type: 'average', name: 'Avg' }],
|
||||
precision: 0,
|
||||
label: {
|
||||
formatter: '均值: {c}'
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="trend-line-chart">
|
||||
<div className="chart-loading">加载中...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
return (
|
||||
<div className="trend-line-chart">
|
||||
<div className="chart-empty">暂无数据</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="trend-line-chart">
|
||||
<ReactECharts
|
||||
option={option}
|
||||
style={{ height: '100%', width: '100%', minHeight: '300px' }}
|
||||
notMerge={true}
|
||||
lazyUpdate={true}
|
||||
/>
|
||||
</div >
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user