新增不记额收支
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
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:
@@ -58,6 +58,11 @@ public record BudgetArchiveContent
|
||||
/// </summary>
|
||||
public string[] SelectedCategories { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 不记额预算
|
||||
/// </summary>
|
||||
public bool NoLimit { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 描述说明
|
||||
/// </summary>
|
||||
|
||||
@@ -34,6 +34,11 @@ public class BudgetRecord : BaseEntity
|
||||
/// 开始日期
|
||||
/// </summary>
|
||||
public DateTime StartDate { get; set; } = DateTime.Now;
|
||||
|
||||
/// <summary>
|
||||
/// 不记额预算(选中后该预算没有预算金额,发生的收入或支出直接在存款中加减)
|
||||
/// </summary>
|
||||
public bool NoLimit { get; set; } = false;
|
||||
}
|
||||
|
||||
public enum BudgetPeriodType
|
||||
|
||||
@@ -54,6 +54,7 @@ public class BudgetService(
|
||||
Current = c.Actual,
|
||||
Category = c.Category,
|
||||
SelectedCategories = c.SelectedCategories,
|
||||
NoLimit = c.NoLimit,
|
||||
Description = c.Description,
|
||||
PeriodStart = periodRange.start,
|
||||
PeriodEnd = periodRange.end,
|
||||
@@ -172,9 +173,9 @@ public class BudgetService(
|
||||
Count = 0
|
||||
};
|
||||
|
||||
// 获取当前分类下所有预算
|
||||
// 获取当前分类下所有预算,排除不记额预算
|
||||
var relevant = budgets
|
||||
.Where(b => b.Category == category)
|
||||
.Where(b => b.Category == category && !b.NoLimit)
|
||||
.ToList();
|
||||
|
||||
if (relevant.Count == 0)
|
||||
@@ -249,6 +250,7 @@ public class BudgetService(
|
||||
Actual = b.Current,
|
||||
Category = b.Category,
|
||||
SelectedCategories = b.SelectedCategories,
|
||||
NoLimit = b.NoLimit,
|
||||
Description = b.Description
|
||||
}).ToArray();
|
||||
|
||||
@@ -471,9 +473,13 @@ public class BudgetService(
|
||||
|
||||
decimal incomeLimitAtPeriod = 0;
|
||||
decimal expenseLimitAtPeriod = 0;
|
||||
decimal noLimitIncomeAtPeriod = 0; // 新增:不记额收入汇总
|
||||
decimal noLimitExpenseAtPeriod = 0; // 新增:不记额支出汇总
|
||||
|
||||
var incomeItems = new List<(string Name, decimal Limit, decimal Factor, decimal Total)>();
|
||||
var expenseItems = new List<(string Name, decimal Limit, decimal Factor, decimal Total)>();
|
||||
var noLimitIncomeItems = new List<(string Name, decimal Amount)>(); // 新增
|
||||
var noLimitExpenseItems = new List<(string Name, decimal Amount)>(); // 新增
|
||||
|
||||
foreach (var b in allBudgets)
|
||||
{
|
||||
@@ -507,16 +513,36 @@ public class BudgetService(
|
||||
|
||||
if (factor <= 0) continue;
|
||||
|
||||
var subtotal = b.Limit * factor;
|
||||
if (b.Category == BudgetCategory.Income)
|
||||
// 新增:处理不记额预算
|
||||
if (b.NoLimit)
|
||||
{
|
||||
incomeLimitAtPeriod += subtotal;
|
||||
incomeItems.Add((b.Name, b.Limit, factor, subtotal));
|
||||
// 不记额预算:计算实际发生的金额
|
||||
var actualAmount = await CalculateCurrentAmountAsync(b, date);
|
||||
if (b.Category == BudgetCategory.Income)
|
||||
{
|
||||
noLimitIncomeAtPeriod += actualAmount;
|
||||
noLimitIncomeItems.Add((b.Name, actualAmount));
|
||||
}
|
||||
else if (b.Category == BudgetCategory.Expense)
|
||||
{
|
||||
noLimitExpenseAtPeriod += actualAmount;
|
||||
noLimitExpenseItems.Add((b.Name, actualAmount));
|
||||
}
|
||||
}
|
||||
else if (b.Category == BudgetCategory.Expense)
|
||||
else
|
||||
{
|
||||
expenseLimitAtPeriod += subtotal;
|
||||
expenseItems.Add((b.Name, b.Limit, factor, subtotal));
|
||||
// 普通预算:按限额计算
|
||||
var subtotal = b.Limit * factor;
|
||||
if (b.Category == BudgetCategory.Income)
|
||||
{
|
||||
incomeLimitAtPeriod += subtotal;
|
||||
incomeItems.Add((b.Name, b.Limit, factor, subtotal));
|
||||
}
|
||||
else if (b.Category == BudgetCategory.Expense)
|
||||
{
|
||||
expenseLimitAtPeriod += subtotal;
|
||||
expenseItems.Add((b.Name, b.Limit, factor, subtotal));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,6 +578,34 @@ public class BudgetService(
|
||||
}
|
||||
description.Append($"<p>收入合计: <span class='income-value'><strong>{incomeLimitAtPeriod:N0}</strong></span></p>");
|
||||
|
||||
// 新增:显示不记额收入明细
|
||||
description.Append("<h3>不记额收入明细</h3>");
|
||||
if (noLimitIncomeItems.Count == 0) description.Append("<p>无不记额收入</p>");
|
||||
else
|
||||
{
|
||||
description.Append("""
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>预算名称</th>
|
||||
<th>实际发生</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
""");
|
||||
foreach (var item in noLimitIncomeItems)
|
||||
{
|
||||
description.Append($"""
|
||||
<tr>
|
||||
<td>{item.Name}</td>
|
||||
<td><span class='income-value'>{item.Amount:N0}</span></td>
|
||||
</tr>
|
||||
""");
|
||||
}
|
||||
description.Append("</tbody></table>");
|
||||
}
|
||||
description.Append($"<p>不记额收入合计: <span class='income-value'><strong>{noLimitIncomeAtPeriod:N0}</strong></span></p>");
|
||||
|
||||
description.Append("<h3>预算支出明细</h3>");
|
||||
if (expenseItems.Count == 0) description.Append("<p>无支出预算</p>");
|
||||
else
|
||||
@@ -583,14 +637,46 @@ public class BudgetService(
|
||||
}
|
||||
description.Append($"<p>支出合计: <span class='expense-value'><strong>{expenseLimitAtPeriod:N0}</strong></span></p>");
|
||||
|
||||
// 新增:显示不记额支出明细
|
||||
description.Append("<h3>不记额支出明细</h3>");
|
||||
if (noLimitExpenseItems.Count == 0) description.Append("<p>无不记额支出</p>");
|
||||
else
|
||||
{
|
||||
description.Append("""
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>预算名称</th>
|
||||
<th>实际发生</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
""");
|
||||
foreach (var item in noLimitExpenseItems)
|
||||
{
|
||||
description.Append($"""
|
||||
<tr>
|
||||
<td>{item.Name}</td>
|
||||
<td><span class='expense-value'>{item.Amount:N0}</span></td>
|
||||
</tr>
|
||||
""");
|
||||
}
|
||||
description.Append("</tbody></table>");
|
||||
}
|
||||
description.Append($"<p>不记额支出合计: <span class='expense-value'><strong>{noLimitExpenseAtPeriod:N0}</strong></span></p>");
|
||||
|
||||
description.Append("<h3>存款计划结论</h3>");
|
||||
description.Append($"<p>计划存款 = 收入 <span class='income-value'>{incomeLimitAtPeriod:N0}</span> - 支出 <span class='expense-value'>{expenseLimitAtPeriod:N0}</span></p>");
|
||||
description.Append($"<p>最终目标:<span class='highlight'><strong>{incomeLimitAtPeriod - expenseLimitAtPeriod:N0}</strong></span></p>");
|
||||
// 修改计算公式:包含不记额收入和支出
|
||||
var totalIncome = incomeLimitAtPeriod + noLimitIncomeAtPeriod;
|
||||
var totalExpense = expenseLimitAtPeriod + noLimitExpenseAtPeriod;
|
||||
description.Append($"<p>计划收入 = 预算 <span class='income-value'>{incomeLimitAtPeriod:N0}</span> + 不记额 <span class='income-value'>{noLimitIncomeAtPeriod:N0}</span> = <span class='income-value'><strong>{totalIncome:N0}</strong></span></p>");
|
||||
description.Append($"<p>计划支出 = 预算 <span class='expense-value'>{expenseLimitAtPeriod:N0}</span> + 不记额 <span class='expense-value'>{noLimitExpenseAtPeriod:N0}</span> = <span class='expense-value'><strong>{totalExpense:N0}</strong></span></p>");
|
||||
description.Append($"<p>最终目标:<span class='highlight'><strong>{totalIncome - totalExpense:N0}</strong></span></p>");
|
||||
|
||||
var virtualBudget = await BuildVirtualSavingsBudgetRecordAsync(
|
||||
periodType == BudgetPeriodType.Year ? -1 : -2,
|
||||
date,
|
||||
incomeLimitAtPeriod - expenseLimitAtPeriod);
|
||||
totalIncome - totalExpense); // 修改:使用总金额
|
||||
|
||||
// 计算实际发生的 收入 - 支出
|
||||
var current = await CalculateCurrentAmountAsync(new BudgetRecord
|
||||
@@ -638,7 +724,7 @@ public record BudgetResult
|
||||
public string Period { get; set; } = string.Empty;
|
||||
public DateTime? PeriodStart { get; set; }
|
||||
public DateTime? PeriodEnd { get; set; }
|
||||
|
||||
public bool NoLimit { get; set; } = false;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public static BudgetResult FromEntity(
|
||||
@@ -670,6 +756,7 @@ public record BudgetResult
|
||||
},
|
||||
PeriodStart = start,
|
||||
PeriodEnd = end,
|
||||
NoLimit = entity.NoLimit,
|
||||
Description = description
|
||||
};
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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">
|
||||
<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">
|
||||
<div class="budget-info">
|
||||
<slot name="tag">
|
||||
<van-tag
|
||||
v-for="cat in budget.selectedCategories"
|
||||
:key="cat"
|
||||
size="mini"
|
||||
class="category-tag"
|
||||
:type="budget.type === BudgetPeriodType.Year ? 'warning' : 'primary'"
|
||||
plain
|
||||
round
|
||||
class="status-tag"
|
||||
>
|
||||
{{ cat }}
|
||||
{{ budget.type === BudgetPeriodType.Year ? '年度' : '月度' }}
|
||||
</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"
|
||||
</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"
|
||||
/>
|
||||
<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>
|
||||
<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="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);
|
||||
|
||||
@@ -14,19 +14,27 @@
|
||||
placeholder="例如:每月餐饮、年度奖金"
|
||||
:rules="[{ required: true, message: '请填写预算名称' }]"
|
||||
/>
|
||||
<!-- 新增:不记额预算复选框 -->
|
||||
<van-field label="不记额预算">
|
||||
<template #input>
|
||||
<van-checkbox v-model="form.noLimit" @update:model-value="onNoLimitChange">不记额预算(仅限年度)</van-checkbox>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field name="type" label="统计周期">
|
||||
<template #input>
|
||||
<van-radio-group
|
||||
v-model="form.type"
|
||||
direction="horizontal"
|
||||
:disabled="isEdit"
|
||||
:disabled="isEdit || form.noLimit"
|
||||
>
|
||||
<van-radio :name="BudgetPeriodType.Month">月</van-radio>
|
||||
<van-radio :name="BudgetPeriodType.Year">年</van-radio>
|
||||
</van-radio-group>
|
||||
</template>
|
||||
</van-field>
|
||||
<!-- 仅当未选中"不记额预算"时显示预算金额 -->
|
||||
<van-field
|
||||
v-if="!form.noLimit"
|
||||
v-model="form.limit"
|
||||
type="number"
|
||||
name="limit"
|
||||
@@ -83,7 +91,8 @@ const form = reactive({
|
||||
type: BudgetPeriodType.Month,
|
||||
category: BudgetCategory.Expense,
|
||||
limit: '',
|
||||
selectedCategories: []
|
||||
selectedCategories: [],
|
||||
noLimit: false // 新增字段
|
||||
})
|
||||
|
||||
const open = ({
|
||||
@@ -104,7 +113,8 @@ const open = ({
|
||||
type: data.type,
|
||||
category: category,
|
||||
limit: data.limit,
|
||||
selectedCategories: data.selectedCategories ? [...data.selectedCategories] : []
|
||||
selectedCategories: data.selectedCategories ? [...data.selectedCategories] : [],
|
||||
noLimit: data.noLimit || false // 新增
|
||||
})
|
||||
} else {
|
||||
Object.assign(form, {
|
||||
@@ -113,7 +123,8 @@ const open = ({
|
||||
type: BudgetPeriodType.Month,
|
||||
category: category,
|
||||
limit: '',
|
||||
selectedCategories: []
|
||||
selectedCategories: [],
|
||||
noLimit: false // 新增
|
||||
})
|
||||
}
|
||||
visible.value = true
|
||||
@@ -131,8 +142,9 @@ const onSubmit = async () => {
|
||||
try {
|
||||
const data = {
|
||||
...form,
|
||||
limit: parseFloat(form.limit),
|
||||
selectedCategories: form.selectedCategories
|
||||
limit: form.noLimit ? 0 : parseFloat(form.limit), // 不记额时金额为0
|
||||
selectedCategories: form.selectedCategories,
|
||||
noLimit: form.noLimit // 新增
|
||||
}
|
||||
|
||||
const res = form.id ? await updateBudget(data) : await createBudget(data)
|
||||
@@ -159,6 +171,13 @@ const getCategoryName = (category) => {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const onNoLimitChange = (value) => {
|
||||
if (value) {
|
||||
// 选中不记额时,自动设为年度预算
|
||||
form.type = BudgetPeriodType.Year
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -465,6 +465,28 @@ const handleSaveSummary = async () => {
|
||||
isSavingSummary.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (budget) => {
|
||||
try {
|
||||
await showConfirmDialog({
|
||||
title: '删除预算',
|
||||
message: `确定要删除预算 "${budget.name}" 吗?`
|
||||
})
|
||||
|
||||
const res = await deleteBudget(budget.id)
|
||||
if (res.success) {
|
||||
showToast('删除成功')
|
||||
await fetchBudgetList()
|
||||
} else {
|
||||
showToast(res.message || '删除失败')
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.message !== 'cancel') {
|
||||
console.error('删除预算失败', err)
|
||||
showToast('删除预算失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -128,14 +128,18 @@ public class BudgetController(
|
||||
{
|
||||
try
|
||||
{
|
||||
// 不记额预算的金额强制设为0
|
||||
var limit = dto.NoLimit ? 0 : dto.Limit;
|
||||
|
||||
var budget = new BudgetRecord
|
||||
{
|
||||
Name = dto.Name,
|
||||
Type = dto.Type,
|
||||
Limit = dto.Limit,
|
||||
Limit = limit,
|
||||
Category = dto.Category,
|
||||
SelectedCategories = dto.SelectedCategories != null ? string.Join(",", dto.SelectedCategories) : string.Empty,
|
||||
StartDate = dto.StartDate ?? DateTime.Now
|
||||
StartDate = dto.StartDate ?? DateTime.Now,
|
||||
NoLimit = dto.NoLimit
|
||||
};
|
||||
|
||||
var varidationError = await ValidateBudgetSelectedCategoriesAsync(budget);
|
||||
@@ -169,11 +173,15 @@ public class BudgetController(
|
||||
var budget = await budgetRepository.GetByIdAsync(dto.Id);
|
||||
if (budget == null) return "预算不存在".Fail();
|
||||
|
||||
// 不记额预算的金额强制设为0
|
||||
var limit = dto.NoLimit ? 0 : dto.Limit;
|
||||
|
||||
budget.Name = dto.Name;
|
||||
budget.Type = dto.Type;
|
||||
budget.Limit = dto.Limit;
|
||||
budget.Limit = limit;
|
||||
budget.Category = dto.Category;
|
||||
budget.SelectedCategories = dto.SelectedCategories != null ? string.Join(",", dto.SelectedCategories) : string.Empty;
|
||||
budget.NoLimit = dto.NoLimit;
|
||||
if (dto.StartDate.HasValue)
|
||||
{
|
||||
budget.StartDate = dto.StartDate.Value;
|
||||
@@ -197,6 +205,12 @@ public class BudgetController(
|
||||
|
||||
private async Task<string> ValidateBudgetSelectedCategoriesAsync(BudgetRecord record)
|
||||
{
|
||||
// 验证不记额预算必须是年度预算
|
||||
if (record.NoLimit && record.Type != BudgetPeriodType.Year)
|
||||
{
|
||||
return "不记额预算只能设置为年度预算。";
|
||||
}
|
||||
|
||||
var allBudgets = await budgetRepository.GetAllAsync();
|
||||
|
||||
var recordSelectedCategories = record.SelectedCategories.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
@@ -8,6 +8,7 @@ public class CreateBudgetDto
|
||||
public BudgetCategory Category { get; set; }
|
||||
public string[] SelectedCategories { get; set; } = Array.Empty<string>();
|
||||
public DateTime? StartDate { get; set; }
|
||||
public bool NoLimit { get; set; } = false;
|
||||
}
|
||||
|
||||
public class UpdateBudgetDto : CreateBudgetDto
|
||||
|
||||
Reference in New Issue
Block a user