主要更新: - 🎯 新增综合分析仪表板,包含关键指标卡片、预算对比、智能洞察等组件 - 📊 增强数据可视化能力,新增标签云分析、时间维度分析等图表 - 📱 优化移动端响应式设计,改进触控交互体验 - 🔧 新增多个API模块(base、budget、tag),完善数据管理 - 🗂️ 重构路由结构,新增贷款、快速添加、设置、统计等独立模块 - 🔄 优化数据导入导出功能,增强数据迁移能力 - 🐛 修复多个已知问题,提升系统稳定性 技术改进: - 使用IndexedDB提升本地存储性能 - 实现模拟API服务,支持离线开发 - 增加自动化测试脚本,确保功能稳定 - 优化打包配置,提升构建效率 文件变更: - 新增42个文件 - 修改55个文件 - 包含测试脚本、配置文件、组件和API模块 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
186 lines
3.7 KiB
Vue
186 lines
3.7 KiB
Vue
<script setup lang="ts">
|
|
import type { EChartsOption } from '#/components/charts/useChart';
|
|
import type { Transaction } from '#/types/finance';
|
|
|
|
import { computed, onMounted, ref, watch } from 'vue';
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
import { useChart } from '#/components/charts/useChart';
|
|
|
|
interface Props {
|
|
transactions: Transaction[];
|
|
year: number;
|
|
}
|
|
|
|
const props = defineProps<Props>();
|
|
|
|
const chartRef = ref<HTMLDivElement | null>(null);
|
|
const { setOptions } = useChart(chartRef);
|
|
|
|
const chartData = computed(() => {
|
|
const months = [
|
|
'1月',
|
|
'2月',
|
|
'3月',
|
|
'4月',
|
|
'5月',
|
|
'6月',
|
|
'7月',
|
|
'8月',
|
|
'9月',
|
|
'10月',
|
|
'11月',
|
|
'12月',
|
|
];
|
|
const incomeData = Array.from({ length: 12 }).fill(0);
|
|
const expenseData = Array.from({ length: 12 }).fill(0);
|
|
const netData = Array.from({ length: 12 }).fill(0);
|
|
|
|
// 统计每月数据
|
|
props.transactions.forEach((transaction) => {
|
|
const date = dayjs(transaction.date);
|
|
if (date.year() === props.year) {
|
|
const monthIndex = date.month(); // 0-11
|
|
|
|
if (transaction.type === 'income') {
|
|
incomeData[monthIndex] += transaction.amount;
|
|
} else {
|
|
expenseData[monthIndex] += transaction.amount;
|
|
}
|
|
}
|
|
});
|
|
|
|
// 计算净收入
|
|
for (let i = 0; i < 12; i++) {
|
|
netData[i] = incomeData[i] - expenseData[i];
|
|
}
|
|
|
|
return {
|
|
months,
|
|
income: incomeData,
|
|
expense: expenseData,
|
|
net: netData,
|
|
};
|
|
});
|
|
|
|
const chartOptions = computed<EChartsOption>(() => ({
|
|
title: {
|
|
text: `${props.year}年月度收支对比`,
|
|
left: 'center',
|
|
},
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
axisPointer: {
|
|
type: 'cross',
|
|
crossStyle: {
|
|
color: '#999',
|
|
},
|
|
},
|
|
formatter: (params: any) => {
|
|
let html = `<div style="font-weight: bold">${params[0].name}</div>`;
|
|
params.forEach((item: any) => {
|
|
const value = item.value.toFixed(2);
|
|
const prefix =
|
|
item.seriesName === '净收入' && item.value > 0 ? '+' : '';
|
|
html += `<div>${item.marker} ${item.seriesName}: ${prefix}¥${value}</div>`;
|
|
});
|
|
return html;
|
|
},
|
|
},
|
|
toolbox: {
|
|
feature: {
|
|
dataView: { show: true, readOnly: false },
|
|
magicType: { show: true, type: ['line', 'bar'] },
|
|
restore: { show: true },
|
|
saveAsImage: { show: true },
|
|
},
|
|
},
|
|
legend: {
|
|
data: ['收入', '支出', '净收入'],
|
|
top: 30,
|
|
},
|
|
grid: {
|
|
left: '3%',
|
|
right: '4%',
|
|
bottom: '3%',
|
|
containLabel: true,
|
|
},
|
|
xAxis: [
|
|
{
|
|
type: 'category',
|
|
data: chartData.value.months,
|
|
axisPointer: {
|
|
type: 'shadow',
|
|
},
|
|
},
|
|
],
|
|
yAxis: [
|
|
{
|
|
type: 'value',
|
|
name: '金额',
|
|
axisLabel: {
|
|
formatter: '¥{value}',
|
|
},
|
|
},
|
|
],
|
|
series: [
|
|
{
|
|
name: '收入',
|
|
type: 'bar',
|
|
data: chartData.value.income,
|
|
itemStyle: {
|
|
color: '#52c41a',
|
|
},
|
|
},
|
|
{
|
|
name: '支出',
|
|
type: 'bar',
|
|
data: chartData.value.expense,
|
|
itemStyle: {
|
|
color: '#ff4d4f',
|
|
},
|
|
},
|
|
{
|
|
name: '净收入',
|
|
type: 'line',
|
|
data: chartData.value.net,
|
|
itemStyle: {
|
|
color: '#1890ff',
|
|
},
|
|
lineStyle: {
|
|
width: 3,
|
|
},
|
|
symbol: 'circle',
|
|
symbolSize: 8,
|
|
},
|
|
],
|
|
}));
|
|
|
|
watch(chartOptions, (options) => {
|
|
setOptions(options);
|
|
});
|
|
|
|
onMounted(() => {
|
|
setOptions(chartOptions.value);
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="monthly-comparison-chart">
|
|
<div ref="chartRef" class="chart-container"></div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.monthly-comparison-chart {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.chart-container {
|
|
width: 100%;
|
|
height: 400px;
|
|
}
|
|
</style>
|