AI-accounting-soft-uniApp/pages/statistics/statistics.vue
2025-12-12 16:49:06 +08:00

247 lines
5.5 KiB
Vue

<template>
<view class="container">
<view class="tabs">
<view class="tab" :class="{ active: activeTab === 'daily' }" @click="switchTab('daily')">
按日
</view>
<view class="tab" :class="{ active: activeTab === 'weekly' }" @click="switchTab('weekly')">
按周
</view>
<view class="tab" :class="{ active: activeTab === 'monthly' }" @click="switchTab('monthly')">
按月
</view>
</view>
<view class="summary-card">
<view class="summary-item">
<text class="label">总收入</text>
<text class="amount income">¥{{ statistics.totalIncome?.toFixed(2) || '0.00' }}</text>
</view>
<view class="summary-item">
<text class="label">总支出</text>
<text class="amount expense">¥{{ statistics.totalExpense?.toFixed(2) || '0.00' }}</text>
</view>
<view class="summary-item">
<text class="label">余额</text>
<text class="amount balance">¥{{ getBalance.toFixed(2) }}</text>
</view>
</view>
<view class="category-stats">
<text class="section-title">分类统计</text>
<view v-if="statistics.categoryStatistics && statistics.categoryStatistics.length > 0">
<view v-for="item in statistics.categoryStatistics" :key="item.categoryId" class="category-item">
<text class="category-icon">{{ item.categoryIcon || '📦' }}</text>
<view class="category-info">
<text class="category-name">{{ item.categoryName }}</text>
<view class="progress-bar">
<view class="progress-fill" :style="{ width: getProgress(item) + '%' }"></view>
</view>
</view>
<text class="category-amount">¥{{ item.amount?.toFixed(2) || '0.00' }}</text>
</view>
</view>
<view v-else class="empty">
<text>暂无统计数据</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { getDailyStatistics, getWeeklyStatistics, getMonthlyStatistics } from '../../api/statistics'
import { formatDate, getWeekStart } from '../../utils/date'
const activeTab = ref('monthly')
const statistics = ref({
totalIncome: 0,
totalExpense: 0,
balance: 0,
categoryStatistics: [],
dailyStatistics: []
})
const switchTab = async (tab) => {
activeTab.value = tab
await loadStatistics()
}
const loadStatistics = async () => {
try {
const now = new Date()
let data
if (activeTab.value === 'daily') {
const startDate = formatDate(now)
const endDate = formatDate(now)
data = await getDailyStatistics(startDate, endDate)
} else if (activeTab.value === 'weekly') {
const weekStart = formatDate(getWeekStart(now))
data = await getWeeklyStatistics(weekStart)
} else {
data = await getMonthlyStatistics(now.getFullYear(), now.getMonth() + 1)
}
const income = data?.totalIncome || 0
const expense = data?.totalExpense || 0
statistics.value = {
totalIncome: income,
totalExpense: expense,
balance: income - expense,
categoryStatistics: data?.categoryStatistics || [],
dailyStatistics: data?.dailyStatistics || []
}
} catch (error) {
console.error('加载统计失败', error)
statistics.value = {
totalIncome: 0,
totalExpense: 0,
balance: 0,
categoryStatistics: [],
dailyStatistics: []
}
}
}
const getBalance = computed(() => {
return statistics.value.totalIncome - statistics.value.totalExpense
})
const getProgress = (item) => {
const total = statistics.value.totalExpense || 1
const amount = parseFloat(item.amount || 0)
return total > 0 ? (amount / total) * 100 : 0
}
onMounted(() => {
loadStatistics()
})
</script>
<style scoped>
.container {
min-height: calc(100vh - 100rpx);
background: #f5f5f5;
padding: 20rpx;
padding-bottom: calc(170rpx + env(safe-area-inset-bottom));
}
.tabs {
display: flex;
background: #fff;
border-radius: 12rpx;
margin-bottom: 20rpx;
padding: 10rpx;
}
.tab {
flex: 1;
text-align: center;
padding: 20rpx;
border-radius: 8rpx;
color: #666;
font-size: 28rpx;
}
.tab.active {
background: #667eea;
color: #fff;
}
.summary-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12rpx;
padding: 40rpx;
margin-bottom: 20rpx;
display: flex;
justify-content: space-around;
}
.summary-item {
text-align: center;
color: #fff;
}
.label {
display: block;
font-size: 24rpx;
opacity: 0.8;
margin-bottom: 10rpx;
}
.amount {
display: block;
font-size: 36rpx;
font-weight: bold;
}
.category-stats {
background: #fff;
border-radius: 12rpx;
padding: 30rpx;
}
.section-title {
display: block;
font-size: 32rpx;
font-weight: bold;
margin-bottom: 30rpx;
color: #333;
}
.category-item {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.category-item:last-child {
border-bottom: none;
}
.category-icon {
font-size: 48rpx;
margin-right: 20rpx;
}
.category-info {
flex: 1;
}
.category-name {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 10rpx;
}
.progress-bar {
width: 100%;
height: 8rpx;
background: #f0f0f0;
border-radius: 4rpx;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #667eea;
transition: width 0.3s;
}
.category-amount {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-left: 20rpx;
}
.empty {
text-align: center;
padding: 60rpx 0;
color: #999;
}
</style>