feat: 初始化收支管理系统
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
<script setup lang="ts">
|
||||
// 统计面板组件:展示月度收支汇总、分类占比饼图、每日收支趋势柱状图
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { NCard, NGrid, NGridItem, NStatistic, NSpace, NSelect } from 'naive-ui'
|
||||
import VChart from 'vue-echarts'
|
||||
import { use } from 'echarts/core'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { LineChart, PieChart, BarChart } from 'echarts/charts'
|
||||
import { TitleComponent, TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'
|
||||
import { getMonthlyStatistics, getCategoryStatistics, getTrendStatistics } from '../api/statistics'
|
||||
|
||||
// 注册 ECharts 所需组件
|
||||
use([CanvasRenderer, LineChart, PieChart, BarChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent])
|
||||
|
||||
const now = new Date()
|
||||
const selectedYear = ref(now.getFullYear()) // 选中的年份
|
||||
const selectedMonth = ref(now.getMonth() + 1) // 选中的月份
|
||||
|
||||
// 月度汇总数据(总收入、总支出、结余、交易笔数)
|
||||
const monthly = ref({ totalIncome: 0, totalExpense: 0, balance: 0, transactionCount: 0 })
|
||||
const categoryData = ref<any[]>([]) // 分类统计数据
|
||||
const trendData = ref<any[]>([]) // 每日趋势数据
|
||||
|
||||
// 年份和月份下拉选项
|
||||
const yearOptions = Array.from({ length: 10 }, (_, i) => ({ label: `${2020 + i}年`, value: 2020 + i }))
|
||||
const monthOptions = Array.from({ length: 12 }, (_, i) => ({ label: `${i + 1}月`, value: i + 1 }))
|
||||
|
||||
const pieOption = ref({}) // 饼图配置
|
||||
const trendOption = ref({}) // 趋势图配置
|
||||
|
||||
/** 更新支出分类饼图配置 */
|
||||
function updatePieChart() {
|
||||
pieOption.value = {
|
||||
title: { text: '支出分类占比', left: 'center' },
|
||||
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'], // 环形图内外半径
|
||||
data: categoryData.value
|
||||
.filter((c: any) => c.type === 'EXPENSE') // 只展示支出类
|
||||
.map((c: any) => ({ name: c.categoryName, value: c.amount })),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/** 更新每日收支趋势柱状图配置 */
|
||||
function updateTrendChart() {
|
||||
trendOption.value = {
|
||||
title: { text: '每日收支趋势' },
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: { data: ['收入', '支出'], bottom: 0 },
|
||||
grid: { left: 60, right: 20, top: 40, bottom: 40 },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: trendData.value.map((d: any) => d.date.substring(5)), // 只显示 MM-DD
|
||||
},
|
||||
yAxis: { type: 'value' },
|
||||
series: [
|
||||
{ name: '收入', type: 'bar', data: trendData.value.map((d: any) => d.income), color: '#18a058' },
|
||||
{ name: '支出', type: 'bar', data: trendData.value.map((d: any) => d.expense), color: '#d03050' },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/** 并行请求所有统计数据并更新图表 */
|
||||
async function fetchData() {
|
||||
const params = { year: selectedYear.value, month: selectedMonth.value }
|
||||
try {
|
||||
const [monthlyRes, categoryRes, trendRes]: any[] = await Promise.all([
|
||||
getMonthlyStatistics(params),
|
||||
getCategoryStatistics(params),
|
||||
getTrendStatistics(params),
|
||||
])
|
||||
monthly.value = monthlyRes.data
|
||||
categoryData.value = categoryRes.data
|
||||
trendData.value = trendRes.data
|
||||
updatePieChart()
|
||||
updateTrendChart()
|
||||
} catch (e: any) {
|
||||
console.error('加载统计数据失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听年月变化,自动重新加载数据
|
||||
watch([selectedYear, selectedMonth], fetchData)
|
||||
onMounted(fetchData) // 组件挂载时加载数据
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- 顶部:标题和年月选择器 -->
|
||||
<NSpace justify="space-between" align="center" style="margin-bottom: 20px;">
|
||||
<h2 style="margin: 0;">统计面板</h2>
|
||||
<NSpace>
|
||||
<NSelect v-model:value="selectedYear" :options="yearOptions" style="width: 100px;" />
|
||||
<NSelect v-model:value="selectedMonth" :options="monthOptions" style="width: 80px;" />
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
|
||||
<!-- 统计卡片:总收入、总支出、结余、交易笔数 -->
|
||||
<NGrid :cols="4" :x-gap="16" :y-gap="16" style="margin-bottom: 20px;">
|
||||
<NGridItem>
|
||||
<NCard>
|
||||
<NStatistic label="总收入" :value="Number(monthly.totalIncome).toFixed(2)" style="color: #18a058;">
|
||||
<template #prefix>+</template>
|
||||
</NStatistic>
|
||||
</NCard>
|
||||
</NGridItem>
|
||||
<NGridItem>
|
||||
<NCard>
|
||||
<NStatistic label="总支出" :value="Number(monthly.totalExpense).toFixed(2)" style="color: #d03050;">
|
||||
<template #prefix>-</template>
|
||||
</NStatistic>
|
||||
</NCard>
|
||||
</NGridItem>
|
||||
<NGridItem>
|
||||
<NCard>
|
||||
<NStatistic label="结余" :value="Number(monthly.balance).toFixed(2)" />
|
||||
</NCard>
|
||||
</NGridItem>
|
||||
<NGridItem>
|
||||
<NCard>
|
||||
<NStatistic label="交易笔数" :value="monthly.transactionCount" />
|
||||
</NCard>
|
||||
</NGridItem>
|
||||
</NGrid>
|
||||
|
||||
<!-- 图表区域:饼图 + 趋势柱状图 -->
|
||||
<NGrid :cols="2" :x-gap="16" :y-gap="16">
|
||||
<NGridItem>
|
||||
<NCard>
|
||||
<VChart :option="pieOption" style="height: 350px;" autoresize />
|
||||
</NCard>
|
||||
</NGridItem>
|
||||
<NGridItem>
|
||||
<NCard>
|
||||
<VChart :option="trendOption" style="height: 350px;" autoresize />
|
||||
</NCard>
|
||||
</NGridItem>
|
||||
</NGrid>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user