feat: 添加获取未被预算覆盖的分类统计信息接口;更新前端以展示未覆盖分类的详细信息
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 22s
Docker Build & Deploy / Deploy to Production (push) Successful in 7s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s

This commit is contained in:
2026-01-11 12:33:12 +08:00
parent e3ea64fb05
commit d2b2158b30
4 changed files with 181 additions and 8 deletions

View File

@@ -72,7 +72,18 @@ export function getCategoryStats(category, referenceDate) {
params: { category, referenceDate }
})
}
/**
* 获取未被预算覆盖的分类统计信息
* @param {number} category 预算分类
* @param {string} referenceDate 参考日期
*/
export function getUncoveredCategories(category, referenceDate) {
return request({
url: '/Budget/GetUncoveredCategories',
method: 'get',
params: { category, referenceDate }
})
}
/**
* 归档预算
* @param {number} year 年份

View File

@@ -2,7 +2,15 @@
<div class="page-container-flex">
<van-nav-bar title="预算管理" placeholder>
<template #right>
<van-icon
<van-icon
v-if="activeTab !== BudgetCategory.Savings && uncoveredCategories.length > 0"
name="warning-o"
size="20"
color="#ee0a24"
style="margin-right: 12px"
@click="showUncoveredDetails = true"
/>
<van-icon
v-if="activeTab !== BudgetCategory.Savings"
name="plus"
size="20"
@@ -167,23 +175,53 @@
ref="savingsConfigRef"
@success="fetchBudgetList"
/>
<PopupContainer
v-model="showUncoveredDetails"
title="未覆盖预算的分类"
:subtitle="`本月共 <b style='color:var(--van-primary-color)'>${uncoveredCategories.length}</b> 个分类未设置预算`"
height="60%"
>
<div class="uncovered-list">
<div v-for="item in uncoveredCategories" :key="item.category" class="uncovered-item">
<div class="item-left">
<div class="category-name">{{ item.category }}</div>
<div class="transaction-count">{{ item.transactionCount }} 笔记录</div>
</div>
<div class="item-right">
<div class="item-amount" :class="activeTab === BudgetCategory.Expense ? 'expense' : 'income'">
¥{{ formatMoney(item.totalAmount) }}
</div>
</div>
</div>
</div>
<template #footer>
<van-button block round type="primary" @click="showUncoveredDetails = false">
我知道了
</van-button>
</template>
</PopupContainer>
</div>
</template>
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { showToast, showConfirmDialog } from 'vant'
import { getBudgetList, deleteBudget, getBudgetStatistics, getCategoryStats } from '@/api/budget'
import { getBudgetList, deleteBudget, getBudgetStatistics, getCategoryStats, getUncoveredCategories } from '@/api/budget'
import { BudgetPeriodType, BudgetCategory } from '@/constants/enums'
import BudgetCard from '@/components/Budget/BudgetCard.vue'
import BudgetSummary from '@/components/Budget/BudgetSummary.vue'
import BudgetEditPopup from '@/components/Budget/BudgetEditPopup.vue'
import SavingsConfigPopup from '@/components/Budget/SavingsConfigPopup.vue'
import PopupContainer from '@/components/PopupContainer.vue'
const activeTab = ref(BudgetCategory.Expense)
const budgetEditRef = ref(null)
const savingsConfigRef = ref(null)
const isRefreshing = ref(false)
const showUncoveredDetails = ref(false)
const uncoveredCategories = ref([])
const expenseBudgets = ref([])
const incomeBudgets = ref([])
@@ -199,7 +237,7 @@ const activeTabTitle = computed(() => {
})
watch(activeTab, async () => {
await fetchCategoryStats()
await Promise.all([fetchCategoryStats(), fetchUncoveredCategories()])
})
const getValueClass = (rate) => {
@@ -232,7 +270,7 @@ const fetchBudgetList = async () => {
const onRefresh = async () => {
try {
await Promise.all([fetchBudgetList(), fetchCategoryStats()])
await Promise.all([fetchBudgetList(), fetchCategoryStats(), fetchUncoveredCategories()])
} catch (err) {
console.error('刷新失败', err)
} finally {
@@ -266,10 +304,28 @@ const fetchCategoryStats = async () => {
}
}
const fetchUncoveredCategories = async () => {
if (activeTab.value === BudgetCategory.Savings) {
uncoveredCategories.value = []
return
}
try {
const res = await getUncoveredCategories(activeTab.value)
if (res.success) {
uncoveredCategories.value = res.data || []
}
} catch (err) {
console.error('获取未覆盖分类失败', err)
}
}
onMounted(async () => {
try {
await fetchBudgetList()
await fetchCategoryStats()
await Promise.all([
fetchBudgetList(),
fetchCategoryStats(),
fetchUncoveredCategories()
])
} catch (err) {
console.error('获取初始化数据失败', err)
}
@@ -387,4 +443,46 @@ const handleDelete = (budget) => {
:deep(.van-tabs__nav--card) {
margin: 0 12px;
}
.uncovered-list {
padding: 12px 16px;
}
.uncovered-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
background-color: var(--van-background-2, #ffffff);
border-radius: 12px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
.item-left {
display: flex;
flex-direction: column;
}
.category-name {
font-size: 16px;
font-weight: 500;
color: var(--van-text-color, #323233);
margin-bottom: 4px;
}
.transaction-count {
font-size: 12px;
color: var(--van-text-color-2, #969799);
}
.item-right {
text-align: right;
}
.item-amount {
font-size: 18px;
font-weight: 600;
font-family: DIN Alternate, system-ui;
}
</style>