发布
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 28s
Docker Build & Deploy / Deploy to Production (push) Successful in 11s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 2s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 28s
Docker Build & Deploy / Deploy to Production (push) Successful in 11s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 2s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
This commit is contained in:
@@ -5,6 +5,15 @@
|
||||
title="预算管理"
|
||||
placeholder
|
||||
>
|
||||
<template #title>
|
||||
<div
|
||||
class="nav-date-picker"
|
||||
@click="showDatePicker = true"
|
||||
>
|
||||
<span>{{ currentYear }}年{{ currentMonth }}月</span>
|
||||
<van-icon name="arrow-down" />
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<van-icon
|
||||
v-if="
|
||||
@@ -26,14 +35,7 @@
|
||||
@click="showArchiveSummary()"
|
||||
/>
|
||||
<van-icon
|
||||
v-if="activeTab !== BudgetCategory.Savings"
|
||||
name="plus"
|
||||
size="20"
|
||||
title="添加预算"
|
||||
@click="budgetEditRef.open({ category: activeTab })"
|
||||
/>
|
||||
<van-icon
|
||||
v-else
|
||||
v-if="activeTab === BudgetCategory.Savings"
|
||||
name="setting-o"
|
||||
size="20"
|
||||
title="储蓄分类配置"
|
||||
@@ -52,174 +54,26 @@
|
||||
title="支出"
|
||||
:name="BudgetCategory.Expense"
|
||||
>
|
||||
<BudgetSummary
|
||||
v-if="activeTab !== BudgetCategory.Savings"
|
||||
v-model:date="selectedDate"
|
||||
:stats="overallStats"
|
||||
:title="activeTabTitle"
|
||||
:get-value-class="getValueClass"
|
||||
/>
|
||||
<van-pull-refresh
|
||||
v-model="isRefreshing"
|
||||
class="scroll-content"
|
||||
@refresh="onRefresh"
|
||||
>
|
||||
<div class="budget-list">
|
||||
<template v-if="expenseBudgets?.length > 0">
|
||||
<van-swipe-cell
|
||||
v-for="budget in expenseBudgets"
|
||||
:key="budget.id"
|
||||
>
|
||||
<BudgetCard
|
||||
:budget="budget"
|
||||
:progress-color="getProgressColor(budget)"
|
||||
:percent-class="{
|
||||
warning: budget.current / budget.limit > 0.8
|
||||
}"
|
||||
:period-label="getPeriodLabel(budget.type)"
|
||||
@click="
|
||||
budgetEditRef.open({
|
||||
data: budget,
|
||||
isEditFlag: true,
|
||||
category: budget.category
|
||||
})
|
||||
"
|
||||
>
|
||||
<template #amount-info>
|
||||
<div class="info-item">
|
||||
<div class="label">
|
||||
已支出
|
||||
</div>
|
||||
<div class="value expense">
|
||||
¥{{ formatMoney(budget.current) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="label">
|
||||
预算
|
||||
</div>
|
||||
<div class="value">
|
||||
¥{{ formatMoney(budget.limit) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="label">
|
||||
余额
|
||||
</div>
|
||||
<div
|
||||
class="value"
|
||||
:class="budget.limit - budget.current >= 0 ? 'income' : 'expense'"
|
||||
>
|
||||
¥{{ formatMoney(budget.limit - budget.current) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</BudgetCard>
|
||||
<template #right>
|
||||
<van-button
|
||||
square
|
||||
text="删除"
|
||||
type="danger"
|
||||
class="delete-button"
|
||||
@click="handleDelete(budget)"
|
||||
/>
|
||||
</template>
|
||||
</van-swipe-cell>
|
||||
</template>
|
||||
<van-empty
|
||||
v-else
|
||||
description="暂无支出预算"
|
||||
/>
|
||||
</div>
|
||||
<div style="height: calc(50px + env(safe-area-inset-bottom, 0px))" />
|
||||
</van-pull-refresh>
|
||||
<div class="scroll-content">
|
||||
<BudgetChartAnalysis
|
||||
:overall-stats="overallStats"
|
||||
:budgets="expenseBudgets"
|
||||
:active-tab="activeTab"
|
||||
/>
|
||||
</div>
|
||||
</van-tab>
|
||||
|
||||
<van-tab
|
||||
title="收入"
|
||||
:name="BudgetCategory.Income"
|
||||
>
|
||||
<BudgetSummary
|
||||
v-if="activeTab !== BudgetCategory.Savings"
|
||||
v-model:date="selectedDate"
|
||||
:stats="overallStats"
|
||||
:title="activeTabTitle"
|
||||
:get-value-class="getValueClass"
|
||||
/>
|
||||
<van-pull-refresh
|
||||
v-model="isRefreshing"
|
||||
class="scroll-content"
|
||||
@refresh="onRefresh"
|
||||
>
|
||||
<div class="budget-list">
|
||||
<template v-if="incomeBudgets?.length > 0">
|
||||
<van-swipe-cell
|
||||
v-for="budget in incomeBudgets"
|
||||
:key="budget.id"
|
||||
>
|
||||
<BudgetCard
|
||||
:budget="budget"
|
||||
:progress-color="getProgressColor(budget)"
|
||||
:percent-class="{
|
||||
income: budget.current / budget.limit >= 1
|
||||
}"
|
||||
:period-label="getPeriodLabel(budget.type)"
|
||||
@click="
|
||||
budgetEditRef.open({
|
||||
data: budget,
|
||||
isEditFlag: true,
|
||||
category: budget.category
|
||||
})
|
||||
"
|
||||
>
|
||||
<template #amount-info>
|
||||
<div class="info-item">
|
||||
<div class="label">
|
||||
已收入
|
||||
</div>
|
||||
<div class="value income">
|
||||
¥{{ formatMoney(budget.current) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="label">
|
||||
目标
|
||||
</div>
|
||||
<div class="value">
|
||||
¥{{ formatMoney(budget.limit) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="label">
|
||||
差额
|
||||
</div>
|
||||
<div
|
||||
class="value"
|
||||
:class="budget.current >= budget.limit ? 'income' : 'expense'"
|
||||
>
|
||||
¥{{ formatMoney(Math.abs(budget.limit - budget.current)) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</BudgetCard>
|
||||
<template #right>
|
||||
<van-button
|
||||
square
|
||||
text="删除"
|
||||
type="danger"
|
||||
class="delete-button"
|
||||
@click="handleDelete(budget)"
|
||||
/>
|
||||
</template>
|
||||
</van-swipe-cell>
|
||||
</template>
|
||||
<van-empty
|
||||
v-else
|
||||
description="暂无收入预算"
|
||||
/>
|
||||
</div>
|
||||
<div style="height: calc(50px + env(safe-area-inset-bottom, 0px))" />
|
||||
</van-pull-refresh>
|
||||
<div class="scroll-content">
|
||||
<BudgetChartAnalysis
|
||||
:overall-stats="overallStats"
|
||||
:budgets="incomeBudgets"
|
||||
:active-tab="activeTab"
|
||||
/>
|
||||
</div>
|
||||
</van-tab>
|
||||
|
||||
<van-tab
|
||||
@@ -305,6 +159,16 @@
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
|
||||
<!-- 悬浮编辑按钮 -->
|
||||
<van-floating-bubble
|
||||
v-if="activeTab !== BudgetCategory.Savings"
|
||||
v-model:offset="bubbleOffset"
|
||||
icon="edit"
|
||||
axis="xy"
|
||||
magnetic="x"
|
||||
@click="showListPopup = true"
|
||||
/>
|
||||
|
||||
<BudgetEditPopup
|
||||
ref="budgetEditRef"
|
||||
@success="fetchBudgetList"
|
||||
@@ -314,6 +178,247 @@
|
||||
@success="fetchBudgetList"
|
||||
/>
|
||||
|
||||
<!-- 预算明细列表弹窗 -->
|
||||
<PopupContainer
|
||||
v-model="showListPopup"
|
||||
:title="popupTitle"
|
||||
height="80%"
|
||||
>
|
||||
<template #header-actions>
|
||||
<van-icon
|
||||
v-if="activeTab !== BudgetCategory.Savings"
|
||||
name="plus"
|
||||
size="20"
|
||||
title="添加预算"
|
||||
@click="budgetEditRef.open({ category: activeTab })"
|
||||
/>
|
||||
<van-icon
|
||||
v-else
|
||||
name="setting-o"
|
||||
size="20"
|
||||
title="储蓄分类配置"
|
||||
@click="savingsConfigRef.open()"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<van-pull-refresh
|
||||
v-model="isRefreshing"
|
||||
style="min-height: 100%"
|
||||
@refresh="onRefresh"
|
||||
>
|
||||
<div class="budget-list">
|
||||
<!-- 支出列表 -->
|
||||
<template v-if="activeTab === BudgetCategory.Expense">
|
||||
<template v-if="expenseBudgets?.length > 0">
|
||||
<van-swipe-cell
|
||||
v-for="budget in expenseBudgets"
|
||||
:key="budget.id"
|
||||
>
|
||||
<BudgetCard
|
||||
:budget="budget"
|
||||
:progress-color="getProgressColor(budget)"
|
||||
:percent-class="{
|
||||
warning: budget.current / budget.limit > 0.8
|
||||
}"
|
||||
:period-label="getPeriodLabel(budget.type)"
|
||||
@click="
|
||||
budgetEditRef.open({
|
||||
data: budget,
|
||||
isEditFlag: true,
|
||||
category: budget.category
|
||||
})
|
||||
"
|
||||
>
|
||||
<template #amount-info>
|
||||
<div class="info-item">
|
||||
<div class="label">
|
||||
已支出
|
||||
</div>
|
||||
<div class="value expense">
|
||||
¥{{ formatMoney(budget.current) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="label">
|
||||
预算
|
||||
</div>
|
||||
<div class="value">
|
||||
¥{{ formatMoney(budget.limit) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="label">
|
||||
余额
|
||||
</div>
|
||||
<div
|
||||
class="value"
|
||||
:class="budget.limit - budget.current >= 0 ? 'income' : 'expense'"
|
||||
>
|
||||
¥{{ formatMoney(budget.limit - budget.current) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</BudgetCard>
|
||||
<template #right>
|
||||
<van-button
|
||||
square
|
||||
text="删除"
|
||||
type="danger"
|
||||
class="delete-button"
|
||||
@click="handleDelete(budget)"
|
||||
/>
|
||||
</template>
|
||||
</van-swipe-cell>
|
||||
</template>
|
||||
<van-empty
|
||||
v-else
|
||||
description="暂无支出预算"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 收入列表 -->
|
||||
<template v-if="activeTab === BudgetCategory.Income">
|
||||
<template v-if="incomeBudgets?.length > 0">
|
||||
<van-swipe-cell
|
||||
v-for="budget in incomeBudgets"
|
||||
:key="budget.id"
|
||||
>
|
||||
<BudgetCard
|
||||
:budget="budget"
|
||||
:progress-color="getProgressColor(budget)"
|
||||
:percent-class="{
|
||||
income: budget.current / budget.limit >= 1
|
||||
}"
|
||||
:period-label="getPeriodLabel(budget.type)"
|
||||
@click="
|
||||
budgetEditRef.open({
|
||||
data: budget,
|
||||
isEditFlag: true,
|
||||
category: budget.category
|
||||
})
|
||||
"
|
||||
>
|
||||
<template #amount-info>
|
||||
<div class="info-item">
|
||||
<div class="label">
|
||||
已收入
|
||||
</div>
|
||||
<div class="value income">
|
||||
¥{{ formatMoney(budget.current) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="label">
|
||||
目标
|
||||
</div>
|
||||
<div class="value">
|
||||
¥{{ formatMoney(budget.limit) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="label">
|
||||
差额
|
||||
</div>
|
||||
<div
|
||||
class="value"
|
||||
:class="budget.current >= budget.limit ? 'income' : 'expense'"
|
||||
>
|
||||
¥{{ formatMoney(Math.abs(budget.limit - budget.current)) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</BudgetCard>
|
||||
<template #right>
|
||||
<van-button
|
||||
square
|
||||
text="删除"
|
||||
type="danger"
|
||||
class="delete-button"
|
||||
@click="handleDelete(budget)"
|
||||
/>
|
||||
</template>
|
||||
</van-swipe-cell>
|
||||
</template>
|
||||
<van-empty
|
||||
v-else
|
||||
description="暂无收入预算"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 存款列表 -->
|
||||
<template v-if="activeTab === BudgetCategory.Savings">
|
||||
<template v-if="savingsBudgets?.length > 0">
|
||||
<BudgetCard
|
||||
v-for="budget in savingsBudgets"
|
||||
:key="budget.id"
|
||||
:budget="budget"
|
||||
:progress-color="getProgressColor(budget)"
|
||||
:percent-class="{ income: budget.current / budget.limit >= 1 }"
|
||||
:period-label="getPeriodLabel(budget.type)"
|
||||
style="margin: 0 12px 12px"
|
||||
>
|
||||
<template #amount-info>
|
||||
<div class="info-item">
|
||||
<div class="label">
|
||||
已存
|
||||
</div>
|
||||
<div class="value income">
|
||||
¥{{ formatMoney(budget.current) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="label">
|
||||
目标
|
||||
</div>
|
||||
<div class="value">
|
||||
¥{{ formatMoney(budget.limit) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="label">
|
||||
还差
|
||||
</div>
|
||||
<div class="value expense">
|
||||
¥{{ formatMoney(Math.max(0, budget.limit - budget.current)) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="card-footer-actions">
|
||||
<van-button
|
||||
size="small"
|
||||
icon="arrow-left"
|
||||
plain
|
||||
type="primary"
|
||||
@click.stop="handleSavingsNav(budget, -1)"
|
||||
/>
|
||||
<span class="current-date-label">
|
||||
{{ getSavingsDateLabel(budget) }}
|
||||
</span>
|
||||
<van-button
|
||||
size="small"
|
||||
icon="arrow"
|
||||
plain
|
||||
type="primary"
|
||||
icon-position="right"
|
||||
:disabled="disabledSavingsNextNav(budget)"
|
||||
@click.stop="handleSavingsNav(budget, 1)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</BudgetCard>
|
||||
</template>
|
||||
<van-empty
|
||||
v-else
|
||||
description="暂无存款计划"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<div style="height: calc(50px + env(safe-area-inset-bottom, 0px))" />
|
||||
</van-pull-refresh>
|
||||
</PopupContainer>
|
||||
|
||||
<PopupContainer
|
||||
v-model="showUncoveredDetails"
|
||||
title="未覆盖预算的分类"
|
||||
@@ -373,6 +478,23 @@
|
||||
/>
|
||||
</div>
|
||||
</PopupContainer>
|
||||
|
||||
<!-- 日期选择弹窗 -->
|
||||
<van-popup
|
||||
v-model:show="showDatePicker"
|
||||
position="bottom"
|
||||
round
|
||||
>
|
||||
<van-date-picker
|
||||
v-model="pickerDate"
|
||||
title="选择年月"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:columns-type="['year', 'month']"
|
||||
@confirm="onConfirmDate"
|
||||
@cancel="showDatePicker = false"
|
||||
/>
|
||||
</van-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -392,16 +514,36 @@ 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 BudgetChartAnalysis from '@/components/Budget/BudgetChartAnalysis.vue'
|
||||
import PopupContainer from '@/components/PopupContainer.vue'
|
||||
|
||||
const activeTab = ref(BudgetCategory.Expense)
|
||||
const selectedDate = ref(new Date())
|
||||
const showDatePicker = ref(false)
|
||||
const minDate = new Date(2020, 0, 1)
|
||||
const maxDate = new Date(2030, 11, 31)
|
||||
const pickerDate = ref([
|
||||
selectedDate.value.getFullYear().toString(),
|
||||
(selectedDate.value.getMonth() + 1).toString().padStart(2, '0')
|
||||
])
|
||||
|
||||
const currentYear = computed(() => selectedDate.value.getFullYear())
|
||||
const currentMonth = computed(() => selectedDate.value.getMonth() + 1)
|
||||
|
||||
const budgetEditRef = ref(null)
|
||||
const savingsConfigRef = ref(null)
|
||||
const isRefreshing = ref(false)
|
||||
const showUncoveredDetails = ref(false)
|
||||
const showListPopup = ref(false)
|
||||
const uncoveredCategories = ref([])
|
||||
|
||||
// 初始化悬浮按钮位置,避免遮挡底部导航栏
|
||||
// 默认位置:右下角,距离底部 100px (避开 Tabbar),距离右侧 24px
|
||||
const bubbleOffset = ref({
|
||||
x: window.innerWidth - 48 - 24,
|
||||
y: window.innerHeight - 48 - 100
|
||||
})
|
||||
|
||||
const showSummaryPopup = ref(false)
|
||||
const archiveSummary = ref('')
|
||||
|
||||
@@ -420,6 +562,19 @@ const activeTabTitle = computed(() => {
|
||||
return '达成'
|
||||
})
|
||||
|
||||
const popupTitle = computed(() => {
|
||||
switch (activeTab.value) {
|
||||
case BudgetCategory.Expense:
|
||||
return '支出预算明细'
|
||||
case BudgetCategory.Income:
|
||||
return '收入预算明细'
|
||||
case BudgetCategory.Savings:
|
||||
return '存款计划明细'
|
||||
default:
|
||||
return '预算明细'
|
||||
}
|
||||
})
|
||||
|
||||
const isArchive = computed(() => {
|
||||
const now = new Date()
|
||||
return (
|
||||
@@ -437,6 +592,12 @@ watch(selectedDate, async () => {
|
||||
await Promise.all([fetchBudgetList(), fetchCategoryStats(), fetchUncoveredCategories()])
|
||||
})
|
||||
|
||||
const onConfirmDate = ({ selectedValues }) => {
|
||||
const [year, month] = selectedValues
|
||||
selectedDate.value = new Date(parseInt(year), parseInt(month) - 1, 1)
|
||||
showDatePicker.value = false
|
||||
}
|
||||
|
||||
const getValueClass = (rate) => {
|
||||
const numRate = parseFloat(rate)
|
||||
if (numRate === 0) {
|
||||
@@ -844,4 +1005,13 @@ const disabledSavingsNextNav = (budget) => {
|
||||
:deep(.van-nav-bar) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.nav-date-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--van-text-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user