添加预算视图统计信息,显示本周、本月和年度达成率,优化样式和布局
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 16s
Docker Build & Deploy / Deploy to Production (push) Successful in 9s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s

This commit is contained in:
孙诚
2026-01-06 21:23:04 +08:00
parent 343c754431
commit efdfe88155

View File

@@ -9,6 +9,31 @@
<div class="page-content"> <div class="page-content">
<van-tabs v-model:active="activeTab" sticky offset-top="46" type="card"> <van-tabs v-model:active="activeTab" sticky offset-top="46" type="card">
<van-tab title="支出" :name="BudgetCategory.Expense"> <van-tab title="支出" :name="BudgetCategory.Expense">
<div class="summary-card common-card">
<div class="summary-item">
<div class="label">本周{{ activeTabTitle }}</div>
<div class="value" :class="getValueClass(overallStats.week.rate)">
{{ overallStats.week.rate }}<span class="unit">%</span>
</div>
<div class="sub-label">{{ overallStats.week.count }}个预算</div>
</div>
<div class="divider"></div>
<div class="summary-item">
<div class="label">本月{{ activeTabTitle }}</div>
<div class="value" :class="getValueClass(overallStats.month.rate)">
{{ overallStats.month.rate }}<span class="unit">%</span>
</div>
<div class="sub-label">{{ overallStats.month.count }}个预算</div>
</div>
<div class="divider"></div>
<div class="summary-item">
<div class="label">年度{{ activeTabTitle }}</div>
<div class="value" :class="getValueClass(overallStats.year.rate)">
{{ overallStats.year.rate }}<span class="unit">%</span>
</div>
<div class="sub-label">{{ overallStats.year.count }}个预算</div>
</div>
</div>
<div class="budget-list"> <div class="budget-list">
<template v-if="expenseBudgets?.length > 0"> <template v-if="expenseBudgets?.length > 0">
<van-swipe-cell v-for="budget in expenseBudgets" :key="budget.id"> <van-swipe-cell v-for="budget in expenseBudgets" :key="budget.id">
@@ -20,8 +45,8 @@
<van-tag v-else type="success" size="small" plain>进行中</van-tag> <van-tag v-else type="success" size="small" plain>进行中</van-tag>
</div> </div>
<div class="header-actions"> <div class="header-actions">
<van-button icon="replay" size="mini" plain round @click="handleSync(budget)" :loading="budget.syncing" /> <van-button icon="replay" size="mini" plain @click="handleSync(budget)" :loading="budget.syncing" />
<van-button :icon="budget.isStopped ? 'play' : 'pause'" size="mini" plain round @click="handleToggleStop(budget)" /> <van-button :icon="budget.isStopped ? 'play' : 'pause'" size="mini" plain @click="handleToggleStop(budget)" />
</div> </div>
</div> </div>
@@ -78,6 +103,31 @@
</van-tab> </van-tab>
<van-tab title="收入" :name="BudgetCategory.Income"> <van-tab title="收入" :name="BudgetCategory.Income">
<div class="summary-card common-card">
<div class="summary-item">
<div class="label">本周{{ activeTabTitle }}</div>
<div class="value" :class="getValueClass(overallStats.week.rate)">
{{ overallStats.week.rate }}<span class="unit">%</span>
</div>
<div class="sub-label">{{ overallStats.week.count }}个预算</div>
</div>
<div class="divider"></div>
<div class="summary-item">
<div class="label">本月{{ activeTabTitle }}</div>
<div class="value" :class="getValueClass(overallStats.month.rate)">
{{ overallStats.month.rate }}<span class="unit">%</span>
</div>
<div class="sub-label">{{ overallStats.month.count }}个预算</div>
</div>
<div class="divider"></div>
<div class="summary-item">
<div class="label">年度{{ activeTabTitle }}</div>
<div class="value" :class="getValueClass(overallStats.year.rate)">
{{ overallStats.year.rate }}<span class="unit">%</span>
</div>
<div class="sub-label">{{ overallStats.year.count }}个预算</div>
</div>
</div>
<div class="budget-list"> <div class="budget-list">
<template v-if="incomeBudgets?.length > 0"> <template v-if="incomeBudgets?.length > 0">
<van-swipe-cell v-for="budget in incomeBudgets" :key="budget.id"> <van-swipe-cell v-for="budget in incomeBudgets" :key="budget.id">
@@ -147,6 +197,31 @@
</van-tab> </van-tab>
<van-tab title="存款" :name="BudgetCategory.Savings"> <van-tab title="存款" :name="BudgetCategory.Savings">
<div class="summary-card common-card">
<div class="summary-item">
<div class="label">本周{{ activeTabTitle }}</div>
<div class="value" :class="getValueClass(overallStats.week.rate)">
{{ overallStats.week.rate }}<span class="unit">%</span>
</div>
<div class="sub-label">{{ overallStats.week.count }}个预算</div>
</div>
<div class="divider"></div>
<div class="summary-item">
<div class="label">本月{{ activeTabTitle }}</div>
<div class="value" :class="getValueClass(overallStats.month.rate)">
{{ overallStats.month.rate }}<span class="unit">%</span>
</div>
<div class="sub-label">{{ overallStats.month.count }}个预算</div>
</div>
<div class="divider"></div>
<div class="summary-item">
<div class="label">年度{{ activeTabTitle }}</div>
<div class="value" :class="getValueClass(overallStats.year.rate)">
{{ overallStats.year.rate }}<span class="unit">%</span>
</div>
<div class="sub-label">{{ overallStats.year.count }}个预算</div>
</div>
</div>
<div class="budget-list"> <div class="budget-list">
<template v-if="savingsBudgets?.length > 0"> <template v-if="savingsBudgets?.length > 0">
<van-swipe-cell v-for="budget in savingsBudgets" :key="budget.id"> <van-swipe-cell v-for="budget in savingsBudgets" :key="budget.id">
@@ -321,6 +396,53 @@ const expenseBudgets = ref([])
const incomeBudgets = ref([]) const incomeBudgets = ref([])
const savingsBudgets = ref([]) const savingsBudgets = ref([])
const activeTabTitle = computed(() => {
if (activeTab.value === BudgetCategory.Expense) return '使用'
return '达成'
})
const overallStats = computed(() => {
const allBudgets = [...expenseBudgets.value, ...incomeBudgets.value, ...savingsBudgets.value]
const getStatsForType = (type) => {
const category = activeTab.value
const filtered = allBudgets.filter(b => b.type === type && b.category === category && !b.isStopped)
if (filtered.length === 0) return { rate: '0.0', current: 0, limit: 0, count: 0 }
const current = filtered.reduce((sum, b) => sum + (b.current || 0), 0)
const limit = filtered.reduce((sum, b) => sum + (b.limit || 0), 0)
const rate = limit > 0 ? (current / limit) * 100 : 0
return {
rate: rate.toFixed(1),
current,
limit,
count: filtered.length
}
}
return {
week: getStatsForType(BudgetPeriodType.Week),
month: getStatsForType(BudgetPeriodType.Month),
year: getStatsForType(BudgetPeriodType.Year)
}
})
const getValueClass = (rate) => {
const numRate = parseFloat(rate)
if (numRate === 0) return ''
if (activeTab.value === BudgetCategory.Expense) {
if (numRate >= 100) return 'expense'
if (numRate >= 80) return 'warning'
return 'income'
} else {
if (numRate >= 100) return 'income'
if (numRate >= 80) return 'warning'
return 'expense'
}
}
const form = reactive({ const form = reactive({
name: '', name: '',
type: BudgetPeriodType.Month, type: BudgetPeriodType.Month,
@@ -457,7 +579,6 @@ const handleDelete = (budget) => {
const res = await deleteBudget(budget.id) const res = await deleteBudget(budget.id)
if (res.success) { if (res.success) {
showToast('已删除') showToast('已删除')
delete refDateMap[budget.id]
fetchBudgetList() fetchBudgetList()
} }
} catch (err) { } catch (err) {
@@ -684,4 +805,70 @@ const onSubmit = async () => {
:deep(.van-tabs__nav--card) { :deep(.van-tabs__nav--card) {
margin: 0 12px; margin: 0 12px;
} }
.summary-card {
display: flex;
justify-content: space-around;
align-items: center;
text-align: center;
padding: 12px 16px;
margin-top: 12px;
margin-bottom: 4px;
}
.summary-item {
flex: 1;
}
.summary-item .label {
font-size: 12px;
color: #969799;
margin-bottom: 6px;
}
.summary-item .value {
font-size: 20px;
font-weight: bold;
margin-bottom: 2px;
color: #323233;
}
.summary-item .value.expense {
color: #ee0a24;
}
.summary-item .value.income {
color: #07c160;
}
.summary-item .value.warning {
color: #ff976a;
}
.summary-item .unit {
font-size: 11px;
margin-left: 1px;
font-weight: normal;
}
.summary-item .sub-label {
font-size: 11px;
color: #c8c9cc;
}
.divider {
width: 1px;
height: 24px;
background-color: #ebedf0;
margin: 0 4px;
}
@media (prefers-color-scheme: dark) {
.summary-item .value {
color: #f5f5f5;
}
.divider {
background-color: #2c2c2c;
}
}
</style> </style>