feat: v1.2 数据可视化与统计增强

新增功能:
- uCharts 集成,支持环形图和折线图
- 统计页分类占比环形图
- 统计页每日趋势折线图
- 统计页增加最大单笔和对比上月指标
- 首页今日支出卡片

组件:
- ChartWrapper 通用图表组件(支持 pie/line/column)
- 支持 H5 和微信小程序双端

依赖:
- 新增 @qiun/ucharts 图表库
This commit is contained in:
2026-05-29 16:28:50 +08:00
parent c7f29aa7a7
commit 7f8b301f95
6 changed files with 2969 additions and 1249 deletions

View File

@@ -36,6 +36,20 @@
</view>
</view>
<view class="today-card" v-if="!loading">
<view class="today-left">
<text class="today-label">今日支出</text>
<text class="today-amount">{{ formatAmount(todayExpense) }}</text>
</view>
<view class="today-right">
<text class="today-count">{{ todayCount }}</text>
</view>
</view>
<view class="today-card" v-else>
<Skeleton width="200rpx" height="28rpx" variant="text" />
<Skeleton width="160rpx" height="48rpx" variant="rect" />
</view>
<view class="quick-actions">
<view class="quick-btn" @tap="goAdd('expense')">
<SvgIcon name="trend-down" :size="48" color="#FF6B6B" />
@@ -103,6 +117,8 @@ const budget = ref<any>(null)
const recentTx = ref<any[]>([])
const loading = ref(true)
const loadError = ref(false)
const todayExpense = ref(0)
const todayCount = ref(0)
const greeting = computed(() => {
const h = new Date().getHours()
@@ -117,10 +133,12 @@ async function loadData() {
try {
loading.value = true
loadError.value = false
const today = new Date().toISOString().slice(0, 10)
await Promise.all([
statsStore.fetchOverview(),
budgetStore.fetchBudget(),
txStore.fetchTransactions({ page: 1 })
txStore.fetchTransactions({ page: 1 }),
fetchTodayExpense(today)
])
overview.value = statsStore.overview
budget.value = budgetStore.budget
@@ -133,6 +151,21 @@ async function loadData() {
}
}
async function fetchTodayExpense(today: string) {
try {
const { request } = await import('@/utils/request')
const data = await request<any>({
url: '/transactions',
data: { page: 1, pageSize: 100, type: 'expense', startDate: today, endDate: today }
})
todayExpense.value = data.list.reduce((sum: number, t: any) => sum + t.amount, 0)
todayCount.value = data.list.length
} catch {
todayExpense.value = 0
todayCount.value = 0
}
}
onMounted(() => loadData())
// Refresh data when returning from other pages (e.g., after adding a transaction)
@@ -270,6 +303,45 @@ function goBills() {
&.expense { color: #FF6B6B; }
}
.today-card {
margin: 24rpx 40rpx 0;
padding: 28rpx 32rpx;
background: #FFFFFF;
border-radius: 28rpx;
border: 2rpx solid #F0E0D6;
box-shadow: 4rpx 4rpx 12rpx rgba(45, 27, 27, 0.08);
display: flex;
justify-content: space-between;
align-items: center;
}
.today-left {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.today-label {
font-size: 24rpx;
color: #8B7E7E;
}
.today-amount {
font-family: 'Fredoka', sans-serif;
font-size: 36rpx;
font-weight: 600;
color: #FF6B6B;
}
.today-right {
text-align: right;
}
.today-count {
font-size: 24rpx;
color: #8B7E7E;
}
.quick-actions {
display: flex;
gap: 24rpx;