fix
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 39s
Docker Build & Deploy / Deploy to Production (push) Successful in 12s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 39s
Docker Build & Deploy / Deploy to Production (push) Successful in 12s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
This commit is contained in:
@@ -73,6 +73,11 @@ public record BudgetArchiveContent
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool NoLimit { get; set; } = false;
|
public bool NoLimit { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 硬性消费
|
||||||
|
/// </summary>
|
||||||
|
public bool IsMandatoryExpense { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 描述说明
|
/// 描述说明
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ public class BudgetRecord : BaseEntity
|
|||||||
/// 不记额预算(选中后该预算没有预算金额,发生的收入或支出直接在存款中加减)
|
/// 不记额预算(选中后该预算没有预算金额,发生的收入或支出直接在存款中加减)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool NoLimit { get; set; } = false;
|
public bool NoLimit { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 硬性消费(固定消费,如房租、水电等。当是当前年月且为硬性消费时,会根据经过的天数累加Current)
|
||||||
|
/// </summary>
|
||||||
|
public bool IsMandatoryExpense { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum BudgetPeriodType
|
public enum BudgetPeriodType
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ public class BudgetService(
|
|||||||
Category = c.Category,
|
Category = c.Category,
|
||||||
SelectedCategories = c.SelectedCategories,
|
SelectedCategories = c.SelectedCategories,
|
||||||
NoLimit = c.NoLimit,
|
NoLimit = c.NoLimit,
|
||||||
|
IsMandatoryExpense = c.IsMandatoryExpense,
|
||||||
Description = c.Description,
|
Description = c.Description,
|
||||||
PeriodStart = periodRange.start,
|
PeriodStart = periodRange.start,
|
||||||
PeriodEnd = periodRange.end,
|
PeriodEnd = periodRange.end,
|
||||||
@@ -206,7 +207,8 @@ public class BudgetService(
|
|||||||
Limit = budget.Limit,
|
Limit = budget.Limit,
|
||||||
Category = budget.Category,
|
Category = budget.Category,
|
||||||
SelectedCategories = selectedCategories,
|
SelectedCategories = selectedCategories,
|
||||||
StartDate = new DateTime(referenceDate.Year, referenceDate.Month, 1)
|
StartDate = new DateTime(referenceDate.Year, referenceDate.Month, 1),
|
||||||
|
IsMandatoryExpense = budget.IsMandatoryExpense
|
||||||
}, referenceDate);
|
}, referenceDate);
|
||||||
if (budget.Type == statType)
|
if (budget.Type == statType)
|
||||||
{
|
{
|
||||||
@@ -254,6 +256,7 @@ public class BudgetService(
|
|||||||
Category = b.Category,
|
Category = b.Category,
|
||||||
SelectedCategories = b.SelectedCategories,
|
SelectedCategories = b.SelectedCategories,
|
||||||
NoLimit = b.NoLimit,
|
NoLimit = b.NoLimit,
|
||||||
|
IsMandatoryExpense = b.IsMandatoryExpense,
|
||||||
Description = b.Description
|
Description = b.Description
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
@@ -437,7 +440,40 @@ public class BudgetService(
|
|||||||
var referenceDate = now ?? DateTime.Now;
|
var referenceDate = now ?? DateTime.Now;
|
||||||
var (startDate, endDate) = GetPeriodRange(budget.StartDate, budget.Type, referenceDate);
|
var (startDate, endDate) = GetPeriodRange(budget.StartDate, budget.Type, referenceDate);
|
||||||
|
|
||||||
return await budgetRepository.GetCurrentAmountAsync(budget, startDate, endDate);
|
var actualAmount = await budgetRepository.GetCurrentAmountAsync(budget, startDate, endDate);
|
||||||
|
|
||||||
|
// 如果是硬性消费,且是当前年当前月,则根据经过的天数累加
|
||||||
|
if (actualAmount == 0
|
||||||
|
&& budget.IsMandatoryExpense
|
||||||
|
&& referenceDate.Year == startDate.Year
|
||||||
|
&& referenceDate.Month == startDate.Month)
|
||||||
|
{
|
||||||
|
if (budget.Type == BudgetPeriodType.Month)
|
||||||
|
{
|
||||||
|
// 计算本月的天数
|
||||||
|
var daysInMonth = DateTime.DaysInMonth(referenceDate.Year, referenceDate.Month);
|
||||||
|
// 计算当前已经过的天数(包括今天)
|
||||||
|
var daysElapsed = referenceDate.Day;
|
||||||
|
// 根据预算金额和经过天数计算应累加的金额
|
||||||
|
var mandatoryAccumulation = budget.Limit * daysElapsed / daysInMonth;
|
||||||
|
// 返回实际消费和硬性消费累加中的较大值
|
||||||
|
return mandatoryAccumulation;
|
||||||
|
}
|
||||||
|
else if (budget.Type == BudgetPeriodType.Year)
|
||||||
|
{
|
||||||
|
// 计算本年的天数(考虑闰年)
|
||||||
|
var daysInYear = DateTime.IsLeapYear(referenceDate.Year) ? 366 : 365;
|
||||||
|
// 计算当前已经过的天数(包括今天)
|
||||||
|
var daysElapsed = referenceDate.DayOfYear;
|
||||||
|
// 根据预算金额和经过天数计算应累加的金额
|
||||||
|
var mandatoryAccumulation = budget.Limit * daysElapsed / daysInYear;
|
||||||
|
// 返回实际消费和硬性消费累加中的较大值
|
||||||
|
return mandatoryAccumulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return actualAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static (DateTime start, DateTime end) GetPeriodRange(DateTime startDate, BudgetPeriodType type, DateTime referenceDate)
|
internal static (DateTime start, DateTime end) GetPeriodRange(DateTime startDate, BudgetPeriodType type, DateTime referenceDate)
|
||||||
@@ -793,6 +829,7 @@ public record BudgetResult
|
|||||||
public DateTime? PeriodStart { get; set; }
|
public DateTime? PeriodStart { get; set; }
|
||||||
public DateTime? PeriodEnd { get; set; }
|
public DateTime? PeriodEnd { get; set; }
|
||||||
public bool NoLimit { get; set; } = false;
|
public bool NoLimit { get; set; } = false;
|
||||||
|
public bool IsMandatoryExpense { get; set; } = false;
|
||||||
public string Description { get; set; } = string.Empty;
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
public static BudgetResult FromEntity(
|
public static BudgetResult FromEntity(
|
||||||
@@ -825,6 +862,7 @@ public record BudgetResult
|
|||||||
PeriodStart = start,
|
PeriodStart = start,
|
||||||
PeriodEnd = end,
|
PeriodEnd = end,
|
||||||
NoLimit = entity.NoLimit,
|
NoLimit = entity.NoLimit,
|
||||||
|
IsMandatoryExpense = entity.IsMandatoryExpense,
|
||||||
Description = description
|
Description = description
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
class="status-tag"
|
class="status-tag"
|
||||||
>
|
>
|
||||||
{{ budget.type === BudgetPeriodType.Year ? '年度' : '月度' }}
|
{{ budget.type === BudgetPeriodType.Year ? '年度' : '月度' }}
|
||||||
|
<span v-if="budget.isMandatoryExpense" class="mandatory-mark">📌</span>
|
||||||
</van-tag>
|
</van-tag>
|
||||||
</slot>
|
</slot>
|
||||||
<h3 class="card-title">
|
<h3 class="card-title">
|
||||||
@@ -58,6 +59,7 @@
|
|||||||
class="status-tag"
|
class="status-tag"
|
||||||
>
|
>
|
||||||
{{ budget.type === BudgetPeriodType.Year ? '年度' : '月度' }}
|
{{ budget.type === BudgetPeriodType.Year ? '年度' : '月度' }}
|
||||||
|
<span v-if="budget.isMandatoryExpense" class="mandatory-mark">📌</span>
|
||||||
</van-tag>
|
</van-tag>
|
||||||
</slot>
|
</slot>
|
||||||
<h3 class="card-title" style="max-width: 120px">
|
<h3 class="card-title" style="max-width: 120px">
|
||||||
@@ -672,6 +674,12 @@ const timePercentage = computed(() => {
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mandatory-mark {
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
/* @media (prefers-color-scheme: dark) {
|
/* @media (prefers-color-scheme: dark) {
|
||||||
.budget-description {
|
.budget-description {
|
||||||
background-color: var(--van-background-2);
|
background-color: var(--van-background-2);
|
||||||
|
|||||||
@@ -74,7 +74,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="activeTab === BudgetCategory.Expense"
|
|
||||||
ref="burndownChartRef"
|
ref="burndownChartRef"
|
||||||
class="chart-body burndown-chart"
|
class="chart-body burndown-chart"
|
||||||
/>
|
/>
|
||||||
@@ -115,7 +114,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="activeTab === BudgetCategory.Expense"
|
|
||||||
ref="yearBurndownChartRef"
|
ref="yearBurndownChartRef"
|
||||||
class="chart-body burndown-chart"
|
class="chart-body burndown-chart"
|
||||||
/>
|
/>
|
||||||
@@ -315,40 +313,21 @@ const updateCharts = () => {
|
|||||||
updateBarChart()
|
updateBarChart()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 仅支出时更新燃尽图
|
// 更新燃尽图/积累图
|
||||||
if (isExpense) {
|
if (!burndownChart && burndownChartRef.value) {
|
||||||
if (!burndownChart && burndownChartRef.value) {
|
burndownChart = echarts.init(burndownChartRef.value)
|
||||||
burndownChart = echarts.init(burndownChartRef.value)
|
|
||||||
}
|
|
||||||
updateBurndownChart()
|
|
||||||
|
|
||||||
if (!yearBurndownChart && yearBurndownChartRef.value) {
|
|
||||||
yearBurndownChart = echarts.init(yearBurndownChartRef.value)
|
|
||||||
}
|
|
||||||
updateYearBurndownChart()
|
|
||||||
|
|
||||||
if (!varianceChart && varianceChartRef.value) {
|
|
||||||
varianceChart = echarts.init(varianceChartRef.value)
|
|
||||||
}
|
|
||||||
updateVarianceChart()
|
|
||||||
} else {
|
|
||||||
// 非支出时销毁燃尽图实例
|
|
||||||
if (burndownChart) {
|
|
||||||
burndownChart.dispose()
|
|
||||||
burndownChart = null
|
|
||||||
}
|
|
||||||
if (yearBurndownChart) {
|
|
||||||
yearBurndownChart.dispose()
|
|
||||||
yearBurndownChart = null
|
|
||||||
}
|
|
||||||
// 收入/存款也可能需要偏差图,但目前逻辑主要针对支出
|
|
||||||
// 如果用户想看收入的偏差,也可以保留。我们之前的逻辑已经处理了收入的情况。
|
|
||||||
// 所以这里不应该销毁 varianceChart,而是应该更新它。
|
|
||||||
if (!varianceChart && varianceChartRef.value) {
|
|
||||||
varianceChart = echarts.init(varianceChartRef.value)
|
|
||||||
}
|
|
||||||
updateVarianceChart()
|
|
||||||
}
|
}
|
||||||
|
updateBurndownChart()
|
||||||
|
|
||||||
|
if (!yearBurndownChart && yearBurndownChartRef.value) {
|
||||||
|
yearBurndownChart = echarts.init(yearBurndownChartRef.value)
|
||||||
|
}
|
||||||
|
updateYearBurndownChart()
|
||||||
|
|
||||||
|
if (!varianceChart && varianceChartRef.value) {
|
||||||
|
varianceChart = echarts.init(varianceChartRef.value)
|
||||||
|
}
|
||||||
|
updateVarianceChart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,8 +496,9 @@ const updateBurndownChart = () => {
|
|||||||
const month = today.getMonth()
|
const month = today.getMonth()
|
||||||
const daysInMonth = new Date(year, month + 1, 0).getDate()
|
const daysInMonth = new Date(year, month + 1, 0).getDate()
|
||||||
const currentDay = today.getDate()
|
const currentDay = today.getDate()
|
||||||
|
const isExpense = props.activeTab === BudgetCategory.Expense
|
||||||
|
|
||||||
// 生成日期和理想燃尽线
|
// 生成日期和理想燃尽线/积累线
|
||||||
const dates = []
|
const dates = []
|
||||||
const idealBurndown = []
|
const idealBurndown = []
|
||||||
const actualBurndown = []
|
const actualBurndown = []
|
||||||
@@ -528,16 +508,33 @@ const updateBurndownChart = () => {
|
|||||||
|
|
||||||
for (let i = 1; i <= daysInMonth; i++) {
|
for (let i = 1; i <= daysInMonth; i++) {
|
||||||
dates.push(`${i}日`)
|
dates.push(`${i}日`)
|
||||||
// 理想燃尽:每天均匀消耗
|
|
||||||
const idealRemaining = Math.max(0, totalBudget * (1 - i / daysInMonth))
|
if (isExpense) {
|
||||||
idealBurndown.push(Math.round(idealRemaining))
|
// 支出:燃尽图(向下走)
|
||||||
|
// 理想燃尽:每天均匀消耗
|
||||||
|
const idealRemaining = Math.max(0, totalBudget * (1 - i / daysInMonth))
|
||||||
|
idealBurndown.push(Math.round(idealRemaining))
|
||||||
|
|
||||||
// 实际燃尽:根据当前日期显示
|
// 实际燃尽:根据当前日期显示
|
||||||
if (i <= currentDay && totalBudget > 0) {
|
if (i <= currentDay && totalBudget > 0) {
|
||||||
const actualRemaining = Math.max(0, totalBudget - (currentExpense * i / currentDay))
|
const actualRemaining = Math.max(0, totalBudget - (currentExpense * i / currentDay))
|
||||||
actualBurndown.push(Math.round(actualRemaining))
|
actualBurndown.push(Math.round(actualRemaining))
|
||||||
|
} else {
|
||||||
|
actualBurndown.push(null)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
actualBurndown.push(null)
|
// 收入:积累图(向上走)
|
||||||
|
// 理想积累:每天均匀积累
|
||||||
|
const idealAccumulated = Math.min(totalBudget, totalBudget * (i / daysInMonth))
|
||||||
|
idealBurndown.push(Math.round(idealAccumulated))
|
||||||
|
|
||||||
|
// 实际积累:根据当前日期显示
|
||||||
|
if (i <= currentDay && totalBudget > 0) {
|
||||||
|
const actualAccumulated = Math.min(totalBudget, currentExpense * i / currentDay)
|
||||||
|
actualBurndown.push(Math.round(actualAccumulated))
|
||||||
|
} else {
|
||||||
|
actualBurndown.push(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,6 +543,9 @@ const updateBurndownChart = () => {
|
|||||||
const splitLineColor = getCssVar('--chart-split')
|
const splitLineColor = getCssVar('--chart-split')
|
||||||
const axisLabelColor = getCssVar('--chart-text-muted')
|
const axisLabelColor = getCssVar('--chart-text-muted')
|
||||||
|
|
||||||
|
const idealSeriesName = isExpense ? '理想燃尽' : '理想积累'
|
||||||
|
const actualSeriesName = isExpense ? '实际燃尽' : '实际积累'
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
grid: {
|
grid: {
|
||||||
left: '3%',
|
left: '3%',
|
||||||
@@ -599,7 +599,7 @@ const updateBurndownChart = () => {
|
|||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: '理想燃尽',
|
name: idealSeriesName,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: idealBurndown,
|
data: idealBurndown,
|
||||||
smooth: false,
|
smooth: false,
|
||||||
@@ -614,7 +614,7 @@ const updateBurndownChart = () => {
|
|||||||
z: 1
|
z: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '实际燃尽',
|
name: actualSeriesName,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: actualBurndown,
|
data: actualBurndown,
|
||||||
smooth: false,
|
smooth: false,
|
||||||
@@ -642,8 +642,9 @@ const updateYearBurndownChart = () => {
|
|||||||
const currentMonth = today.getMonth()
|
const currentMonth = today.getMonth()
|
||||||
const currentDay = today.getDate()
|
const currentDay = today.getDate()
|
||||||
const daysInCurrentMonth = new Date(year, currentMonth + 1, 0).getDate()
|
const daysInCurrentMonth = new Date(year, currentMonth + 1, 0).getDate()
|
||||||
|
const isExpense = props.activeTab === BudgetCategory.Expense
|
||||||
|
|
||||||
// 生成月份和理想燃尽线
|
// 生成月份和理想燃尽线/积累线
|
||||||
const months = []
|
const months = []
|
||||||
const idealBurndown = []
|
const idealBurndown = []
|
||||||
const actualBurndown = []
|
const actualBurndown = []
|
||||||
@@ -678,16 +679,32 @@ const updateYearBurndownChart = () => {
|
|||||||
const daysInYearTotal = new Date(year, 12, 0).getDate() === 29 ? 366 : 365
|
const daysInYearTotal = new Date(year, 12, 0).getDate() === 29 ? 366 : 365
|
||||||
const yearProgress = i === 11 ? 1 : daysPassedInYear / daysInYearTotal
|
const yearProgress = i === 11 ? 1 : daysPassedInYear / daysInYearTotal
|
||||||
|
|
||||||
// 理想燃尽:每月均匀消耗
|
if (isExpense) {
|
||||||
const idealRemaining = Math.max(0, totalBudget * (1 - (i + 1) / 12))
|
// 支出:燃尽图(向下走)
|
||||||
idealBurndown.push(Math.round(idealRemaining))
|
// 理想燃尽:每月均匀消耗
|
||||||
|
const idealRemaining = Math.max(0, totalBudget * (1 - (i + 1) / 12))
|
||||||
|
idealBurndown.push(Math.round(idealRemaining))
|
||||||
|
|
||||||
// 实际燃尽:根据当前日期显示
|
// 实际燃尽:根据当前日期显示
|
||||||
if ((i < currentMonth || (i === currentMonth && currentDay > 0)) && totalBudget > 0) {
|
if ((i < currentMonth || (i === currentMonth && currentDay > 0)) && totalBudget > 0) {
|
||||||
const actualRemaining = Math.max(0, totalBudget - (currentExpense * yearProgress))
|
const actualRemaining = Math.max(0, totalBudget - (currentExpense * yearProgress))
|
||||||
actualBurndown.push(Math.round(actualRemaining))
|
actualBurndown.push(Math.round(actualRemaining))
|
||||||
|
} else {
|
||||||
|
actualBurndown.push(null)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
actualBurndown.push(null)
|
// 收入:积累图(向上走)
|
||||||
|
// 理想积累:每月均匀积累
|
||||||
|
const idealAccumulated = Math.min(totalBudget, totalBudget * ((i + 1) / 12))
|
||||||
|
idealBurndown.push(Math.round(idealAccumulated))
|
||||||
|
|
||||||
|
// 实际积累:根据当前日期显示
|
||||||
|
if ((i < currentMonth || (i === currentMonth && currentDay > 0)) && totalBudget > 0) {
|
||||||
|
const actualAccumulated = Math.min(totalBudget, currentExpense * yearProgress)
|
||||||
|
actualBurndown.push(Math.round(actualAccumulated))
|
||||||
|
} else {
|
||||||
|
actualBurndown.push(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -696,6 +713,9 @@ const updateYearBurndownChart = () => {
|
|||||||
const splitLineColor = getCssVar('--chart-split')
|
const splitLineColor = getCssVar('--chart-split')
|
||||||
const axisLabelColor = getCssVar('--chart-text-muted')
|
const axisLabelColor = getCssVar('--chart-text-muted')
|
||||||
|
|
||||||
|
const idealSeriesName = isExpense ? '理想燃尽' : '理想积累'
|
||||||
|
const actualSeriesName = isExpense ? '实际燃尽' : '实际积累'
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
grid: {
|
grid: {
|
||||||
left: '3%',
|
left: '3%',
|
||||||
@@ -747,7 +767,7 @@ const updateYearBurndownChart = () => {
|
|||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: '理想燃尽',
|
name: idealSeriesName,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: idealBurndown,
|
data: idealBurndown,
|
||||||
smooth: false,
|
smooth: false,
|
||||||
@@ -762,7 +782,7 @@ const updateYearBurndownChart = () => {
|
|||||||
z: 1
|
z: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '实际燃尽',
|
name: actualSeriesName,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: actualBurndown,
|
data: actualBurndown,
|
||||||
smooth: false,
|
smooth: false,
|
||||||
@@ -1037,13 +1057,13 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
updateBarChart()
|
updateBarChart()
|
||||||
|
|
||||||
// 仅支出时初始化燃尽图
|
// 初始化燃尽图/积累图
|
||||||
if (isExpense && burndownChartRef.value) {
|
if (burndownChartRef.value) {
|
||||||
burndownChart = echarts.init(burndownChartRef.value)
|
burndownChart = echarts.init(burndownChartRef.value)
|
||||||
updateBurndownChart()
|
updateBurndownChart()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isExpense && yearBurndownChartRef.value) {
|
if (yearBurndownChartRef.value) {
|
||||||
yearBurndownChart = echarts.init(yearBurndownChartRef.value)
|
yearBurndownChart = echarts.init(yearBurndownChartRef.value)
|
||||||
updateYearBurndownChart()
|
updateYearBurndownChart()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,20 +21,46 @@
|
|||||||
<!-- 新增:不记额预算复选框 -->
|
<!-- 新增:不记额预算复选框 -->
|
||||||
<van-field label="不记额预算">
|
<van-field label="不记额预算">
|
||||||
<template #input>
|
<template #input>
|
||||||
<van-checkbox v-model="form.noLimit" @update:model-value="onNoLimitChange">
|
<van-checkbox
|
||||||
不记额预算(仅限年度)
|
v-model="form.noLimit"
|
||||||
|
@update:model-value="onNoLimitChange"
|
||||||
|
>
|
||||||
|
不记额预算
|
||||||
</van-checkbox>
|
</van-checkbox>
|
||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
<van-field name="type" label="统计周期">
|
<!-- 新增:硬性消费复选框 -->
|
||||||
|
<van-field label="硬性消费">
|
||||||
|
<template #input>
|
||||||
|
<div class="mandatory-wrapper">
|
||||||
|
<van-checkbox
|
||||||
|
v-model="form.isMandatoryExpense"
|
||||||
|
:disabled="form.noLimit"
|
||||||
|
>
|
||||||
|
硬性消费
|
||||||
|
<span class="mandatory-tip">
|
||||||
|
当前周期 月/年 按天数自动累加
|
||||||
|
</span>
|
||||||
|
</van-checkbox>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
<van-field
|
||||||
|
name="type"
|
||||||
|
label="统计周期"
|
||||||
|
>
|
||||||
<template #input>
|
<template #input>
|
||||||
<van-radio-group
|
<van-radio-group
|
||||||
v-model="form.type"
|
v-model="form.type"
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
:disabled="isEdit || form.noLimit"
|
:disabled="isEdit || form.noLimit"
|
||||||
>
|
>
|
||||||
<van-radio :name="BudgetPeriodType.Month"> 月 </van-radio>
|
<van-radio :name="BudgetPeriodType.Month">
|
||||||
<van-radio :name="BudgetPeriodType.Year"> 年 </van-radio>
|
月
|
||||||
|
</van-radio>
|
||||||
|
<van-radio :name="BudgetPeriodType.Year">
|
||||||
|
年
|
||||||
|
</van-radio>
|
||||||
</van-radio-group>
|
</van-radio-group>
|
||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
@@ -60,7 +86,10 @@
|
|||||||
>
|
>
|
||||||
可多选分类
|
可多选分类
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="selected-categories">
|
<div
|
||||||
|
v-else
|
||||||
|
class="selected-categories"
|
||||||
|
>
|
||||||
<span class="ellipsis-text">
|
<span class="ellipsis-text">
|
||||||
{{ form.selectedCategories.join('、') }}
|
{{ form.selectedCategories.join('、') }}
|
||||||
</span>
|
</span>
|
||||||
@@ -78,7 +107,14 @@
|
|||||||
</van-form>
|
</van-form>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<van-button block round type="primary" @click="onSubmit"> 保存预算 </van-button>
|
<van-button
|
||||||
|
block
|
||||||
|
round
|
||||||
|
type="primary"
|
||||||
|
@click="onSubmit"
|
||||||
|
>
|
||||||
|
保存预算
|
||||||
|
</van-button>
|
||||||
</template>
|
</template>
|
||||||
</PopupContainer>
|
</PopupContainer>
|
||||||
</template>
|
</template>
|
||||||
@@ -103,7 +139,8 @@ const form = reactive({
|
|||||||
category: BudgetCategory.Expense,
|
category: BudgetCategory.Expense,
|
||||||
limit: '',
|
limit: '',
|
||||||
selectedCategories: [],
|
selectedCategories: [],
|
||||||
noLimit: false // 新增字段
|
noLimit: false, // 新增字段
|
||||||
|
isMandatoryExpense: false // 新增:硬性消费
|
||||||
})
|
})
|
||||||
|
|
||||||
const open = ({ data, isEditFlag, category }) => {
|
const open = ({ data, isEditFlag, category }) => {
|
||||||
@@ -121,7 +158,8 @@ const open = ({ data, isEditFlag, category }) => {
|
|||||||
category: category,
|
category: category,
|
||||||
limit: data.limit,
|
limit: data.limit,
|
||||||
selectedCategories: data.selectedCategories ? [...data.selectedCategories] : [],
|
selectedCategories: data.selectedCategories ? [...data.selectedCategories] : [],
|
||||||
noLimit: data.noLimit || false // 新增
|
noLimit: data.noLimit || false, // 新增
|
||||||
|
isMandatoryExpense: data.isMandatoryExpense || false // 新增:硬性消费
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Object.assign(form, {
|
Object.assign(form, {
|
||||||
@@ -131,7 +169,8 @@ const open = ({ data, isEditFlag, category }) => {
|
|||||||
category: category,
|
category: category,
|
||||||
limit: '',
|
limit: '',
|
||||||
selectedCategories: [],
|
selectedCategories: [],
|
||||||
noLimit: false // 新增
|
noLimit: false, // 新增
|
||||||
|
isMandatoryExpense: false // 新增:硬性消费
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
visible.value = true
|
visible.value = true
|
||||||
@@ -155,7 +194,8 @@ const onSubmit = async () => {
|
|||||||
...form,
|
...form,
|
||||||
limit: form.noLimit ? 0 : parseFloat(form.limit), // 不记额时金额为0
|
limit: form.noLimit ? 0 : parseFloat(form.limit), // 不记额时金额为0
|
||||||
selectedCategories: form.selectedCategories,
|
selectedCategories: form.selectedCategories,
|
||||||
noLimit: form.noLimit // 新增
|
noLimit: form.noLimit, // 新增
|
||||||
|
isMandatoryExpense: form.isMandatoryExpense // 新增:硬性消费
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = form.id ? await updateBudget(data) : await createBudget(data)
|
const res = form.id ? await updateBudget(data) : await createBudget(data)
|
||||||
@@ -187,6 +227,8 @@ const onNoLimitChange = (value) => {
|
|||||||
if (value) {
|
if (value) {
|
||||||
// 选中不记额时,自动设为年度预算
|
// 选中不记额时,自动设为年度预算
|
||||||
form.type = BudgetPeriodType.Year
|
form.type = BudgetPeriodType.Year
|
||||||
|
// 选中不记额时,清除硬性消费选择
|
||||||
|
form.isMandatoryExpense = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -218,4 +260,16 @@ const onNoLimitChange = (value) => {
|
|||||||
color: var(--van-text-color-2);
|
color: var(--van-text-color-2);
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mandatory-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mandatory-tip {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--van-text-color-3);
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -139,7 +139,8 @@ public class BudgetController(
|
|||||||
Category = dto.Category,
|
Category = dto.Category,
|
||||||
SelectedCategories = dto.SelectedCategories != null ? string.Join(",", dto.SelectedCategories) : string.Empty,
|
SelectedCategories = dto.SelectedCategories != null ? string.Join(",", dto.SelectedCategories) : string.Empty,
|
||||||
StartDate = dto.StartDate ?? DateTime.Now,
|
StartDate = dto.StartDate ?? DateTime.Now,
|
||||||
NoLimit = dto.NoLimit
|
NoLimit = dto.NoLimit,
|
||||||
|
IsMandatoryExpense = dto.IsMandatoryExpense
|
||||||
};
|
};
|
||||||
|
|
||||||
var varidationError = await ValidateBudgetSelectedCategoriesAsync(budget);
|
var varidationError = await ValidateBudgetSelectedCategoriesAsync(budget);
|
||||||
@@ -182,6 +183,7 @@ public class BudgetController(
|
|||||||
budget.Category = dto.Category;
|
budget.Category = dto.Category;
|
||||||
budget.SelectedCategories = dto.SelectedCategories != null ? string.Join(",", dto.SelectedCategories) : string.Empty;
|
budget.SelectedCategories = dto.SelectedCategories != null ? string.Join(",", dto.SelectedCategories) : string.Empty;
|
||||||
budget.NoLimit = dto.NoLimit;
|
budget.NoLimit = dto.NoLimit;
|
||||||
|
budget.IsMandatoryExpense = dto.IsMandatoryExpense;
|
||||||
if (dto.StartDate.HasValue)
|
if (dto.StartDate.HasValue)
|
||||||
{
|
{
|
||||||
budget.StartDate = dto.StartDate.Value;
|
budget.StartDate = dto.StartDate.Value;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ public class CreateBudgetDto
|
|||||||
public string[] SelectedCategories { get; set; } = Array.Empty<string>();
|
public string[] SelectedCategories { get; set; } = Array.Empty<string>();
|
||||||
public DateTime? StartDate { get; set; }
|
public DateTime? StartDate { get; set; }
|
||||||
public bool NoLimit { get; set; } = false;
|
public bool NoLimit { get; set; } = false;
|
||||||
|
public bool IsMandatoryExpense { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UpdateBudgetDto : CreateBudgetDto
|
public class UpdateBudgetDto : CreateBudgetDto
|
||||||
|
|||||||
Reference in New Issue
Block a user