新增不记额收支
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 34s
Docker Build & Deploy / Deploy to Production (push) Successful in 8s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 0s
Docker Build & Deploy / WeChat Notification (push) Successful in 3s

This commit is contained in:
孙诚
2026-01-15 10:53:05 +08:00
parent 12cf1b6323
commit 65f7316c82
9 changed files with 456 additions and 123 deletions

View File

@@ -1,24 +1,26 @@
<template>
<div class="common-card budget-card" @click="toggleExpand">
<!-- eslint-disable vue/no-v-html -->
<template>
<!-- 普通预算卡片 -->
<div v-if="!budget.noLimit" class="common-card budget-card" @click="toggleExpand">
<div class="budget-content-wrapper">
<!-- 折叠状态 -->
<div v-if="!isExpanded" class="budget-collapsed">
<div class="collapsed-header">
<div class="budget-info">
<slot name="tag">
<van-tag
:type="budget.type === BudgetPeriodType.Year ? 'warning' : 'primary'"
plain
class="status-tag"
>
{{ budget.type === BudgetPeriodType.Year ? '年度' : '月度' }}
</van-tag>
</slot>
<h3 class="card-title">{{ budget.name }}</h3>
<span v-if="budget.selectedCategories?.length" class="card-subtitle">
({{ budget.selectedCategories.join('') }})
</span>
</div>
<div class="budget-info">
<slot name="tag">
<van-tag
:type="budget.type === BudgetPeriodType.Year ? 'warning' : 'primary'"
plain
class="status-tag"
>
{{ budget.type === BudgetPeriodType.Year ? '年度' : '月度' }}
</van-tag>
</slot>
<h3 class="card-title">{{ budget.name }}</h3>
<span v-if="budget.selectedCategories?.length" class="card-subtitle">
({{ budget.selectedCategories.join('') }})
</span>
</div>
<van-icon name="arrow-down" class="expand-icon" />
</div>
@@ -44,95 +46,233 @@
<!-- 展开状态 -->
<div v-else class="budget-inner-card">
<div class="card-header" style="margin-bottom: 0;">
<div class="budget-info">
<slot name="tag">
<van-tag
:type="budget.type === BudgetPeriodType.Year ? 'warning' : 'primary'"
plain
class="status-tag"
>
{{ budget.type === BudgetPeriodType.Year ? '年度' : '月度' }}
</van-tag>
</slot>
<h3 class="card-title" style="max-width: 120px;">{{ budget.name }}</h3>
</div>
<div class="header-actions">
<slot name="actions">
<div class="budget-info">
<slot name="tag">
<van-tag
:type="budget.type === BudgetPeriodType.Year ? 'warning' : 'primary'"
plain
class="status-tag"
>
{{ budget.type === BudgetPeriodType.Year ? '年度' : '月度' }}
</van-tag>
</slot>
<h3 class="card-title" style="max-width: 120px;">{{ budget.name }}</h3>
</div>
<div class="header-actions">
<slot name="actions">
<van-button
v-if="budget.description"
:icon="showDescription ? 'info' : 'info-o'"
size="small"
:type="showDescription ? 'primary' : 'default'"
plain
@click.stop="showDescription = !showDescription"
/>
<van-button
icon="orders-o"
size="small"
plain
title="查询关联账单"
@click.stop="handleQueryBills"
/>
<template v-if="budget.category !== 2">
<van-button
v-if="budget.description"
:icon="showDescription ? 'info' : 'info-o'"
size="small"
:type="showDescription ? 'primary' : 'default'"
plain
@click.stop="showDescription = !showDescription"
/>
<van-button
icon="orders-o"
icon="edit"
size="small"
plain
title="查询关联账单"
@click.stop="handleQueryBills"
@click.stop="$emit('click', budget)"
/>
<template v-if="budget.category !== 2">
<van-button
icon="edit"
size="small"
plain
@click.stop="$emit('click', budget)"
/>
</template>
</slot>
</div>
</div>
<div class="budget-body">
<div v-if="budget.selectedCategories?.length" class="category-tags">
<van-tag
v-for="cat in budget.selectedCategories"
:key="cat"
size="mini"
class="category-tag"
plain
round
>
{{ cat }}
</van-tag>
</div>
<div class="amount-info">
<slot name="amount-info"></slot>
</div>
<div class="progress-section">
<slot name="progress-info">
<span class="period-type">{{ periodLabel }}{{ budget.category === 0 ? '使用' : '达成' }}</span>
<van-progress
:percentage="Math.min(percentage, 100)"
stroke-width="8"
:color="progressColor"
:show-pivot="false"
/>
<span class="percent" :class="percentClass">{{ percentage }}%</span>
</slot>
</div>
<div class="progress-section time-progress">
<span class="period-type">时间进度</span>
<van-progress
:percentage="timePercentage"
stroke-width="4"
color="var(--van-gray-6)"
:show-pivot="false"
/>
<span class="percent">{{ timePercentage }}%</span>
</div>
<van-collapse-transition>
<div v-if="budget.description && showDescription" class="budget-description">
<div class="description-content rich-html-content" v-html="budget.description"></div>
</div>
</van-collapse-transition>
</template>
</slot>
</div>
</div>
<div class="budget-body">
<div v-if="budget.selectedCategories?.length" class="category-tags">
<van-tag
v-for="cat in budget.selectedCategories"
:key="cat"
size="mini"
class="category-tag"
plain
round
>
{{ cat }}
</van-tag>
</div>
<div class="amount-info">
<slot name="amount-info"></slot>
</div>
<div class="progress-section">
<slot name="progress-info">
<span class="period-type">{{ periodLabel }}{{ budget.category === 0 ? '使用' : '达成' }}</span>
<van-progress
:percentage="Math.min(percentage, 100)"
stroke-width="8"
:color="progressColor"
:show-pivot="false"
/>
<span class="percent" :class="percentClass">{{ percentage }}%</span>
</slot>
</div>
<div class="progress-section time-progress">
<span class="period-type">时间进度</span>
<van-progress
:percentage="timePercentage"
stroke-width="4"
color="var(--van-gray-6)"
:show-pivot="false"
/>
<span class="percent">{{ timePercentage }}%</span>
</div>
<van-collapse-transition>
<div v-if="budget.description && showDescription" class="budget-description">
<div class="description-content rich-html-content" v-html="budget.description"></div>
</div>
</van-collapse-transition>
</div>
</div>
</div>
<!-- 关联账单列表弹窗 -->
<PopupContainer
v-model="showBillListModal"
title="关联账单列表"
height="75%"
>
<TransactionList
:transactions="billList"
:loading="billLoading"
:finished="true"
:show-delete="false"
:show-checkbox="false"
@click="handleBillClick"
@delete="handleBillDelete"
/>
</PopupContainer>
</div>
<!-- 不记额预算卡片 -->
<div v-else class="common-card budget-card no-limit-card" @click="toggleExpand">
<div class="budget-content-wrapper">
<!-- 折叠状态 -->
<div v-if="!isExpanded" class="budget-collapsed">
<div class="collapsed-header">
<div class="budget-info">
<slot name="tag">
<van-tag
type="success"
plain
class="status-tag"
>
{{ budget.type === BudgetPeriodType.Year ? '年度' : '月度' }}
</van-tag>
</slot>
<h3 class="card-title">{{ budget.name }}</h3>
<span v-if="budget.selectedCategories?.length" class="card-subtitle">
({{ budget.selectedCategories.join('') }})
</span>
</div>
<van-icon name="arrow-down" class="expand-icon" />
</div>
<div class="collapsed-footer no-limit-footer">
<div class="collapsed-item">
<span class="compact-label">实际</span>
<span class="compact-value">
<slot name="collapsed-amount">
{{ budget.current !== undefined
? `¥${budget.current?.toFixed(0) || 0}`
: '--' }}
</slot>
</span>
</div>
</div>
</div>
<!-- 展开状态 -->
<div v-else class="budget-inner-card">
<div class="card-header" style="margin-bottom: 0;">
<div class="budget-info">
<slot name="tag">
<van-tag
type="success"
plain
class="status-tag"
>
{{ budget.type === BudgetPeriodType.Year ? '年度' : '月度' }}
</van-tag>
</slot>
<h3 class="card-title" style="max-width: 120px;">{{ budget.name }}</h3>
</div>
<div class="header-actions">
<slot name="actions">
<van-button
v-if="budget.description"
:icon="showDescription ? 'info' : 'info-o'"
size="small"
:type="showDescription ? 'primary' : 'default'"
plain
@click.stop="showDescription = !showDescription"
/>
<van-button
icon="orders-o"
size="small"
plain
title="查询关联账单"
@click.stop="handleQueryBills"
/>
<template v-if="budget.category !== 2">
<van-button
icon="edit"
size="small"
plain
@click.stop="$emit('click', budget)"
/>
</template>
</slot>
</div>
</div>
<div class="budget-body">
<div v-if="budget.selectedCategories?.length" class="category-tags">
<van-tag
v-for="cat in budget.selectedCategories"
:key="cat"
size="mini"
class="category-tag"
plain
round
>
{{ cat }}
</van-tag>
</div>
<div class="no-limit-amount-info">
<div class="amount-item">
<span>
<span class="label">实际</span>
<span class="value" style="margin-left: 12px;">¥{{ budget.current?.toFixed(0) || 0 }}</span>
</span>
</div>
</div>
<div class="no-limit-notice">
<span>
<van-icon name="info-o" style="margin-right: 4px;" />
不记额预算 - 直接计入存款明细
</span>
</div>
<van-collapse-transition>
<div v-if="budget.description && showDescription" class="budget-description">
<div class="description-content rich-html-content" v-html="budget.description"></div>
</div>
</van-collapse-transition>
</div>
</div>
</div>
<!-- 关联账单列表弹窗 -->
@@ -261,6 +401,14 @@ const timePercentage = computed(() => {
cursor: pointer;
}
.no-limit-card {
border-left: 3px solid var(--van-success-color);
}
.collapsed-footer.no-limit-footer {
justify-content: flex-start;
}
.budget-content-wrapper {
position: relative;
width: 100%;
@@ -481,6 +629,39 @@ const timePercentage = computed(() => {
font-size: 11px;
}
.no-limit-notice {
text-align: center;
font-size: 12px;
color: var(--van-text-color-2);
background-color: var(--van-light-gray);
border-radius: 4px;
margin-top: 8px;
}
.no-limit-amount-info {
display: flex;
justify-content: center;
margin: 0px 0;
}
.amount-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.amount-item .label {
font-size: 12px;
color: var(--van-text-color-2);
}
.amount-item .value {
font-size: 20px;
font-weight: 600;
color: var(--van-success-color);
}
.budget-description {
margin-top: 8px;
background-color: var(--van-background);