2026-01-07 17:33:50 +08:00
|
|
|
|
<template>
|
2026-01-16 11:15:44 +08:00
|
|
|
|
<PopupContainer
|
|
|
|
|
|
v-model="visible"
|
|
|
|
|
|
:title="
|
|
|
|
|
|
isEdit
|
|
|
|
|
|
? `编辑${getCategoryName(form.category)}预算`
|
|
|
|
|
|
: `新增${getCategoryName(form.category)}预算`
|
|
|
|
|
|
"
|
2026-01-07 20:31:12 +08:00
|
|
|
|
height="75%"
|
2026-01-07 17:33:50 +08:00
|
|
|
|
>
|
|
|
|
|
|
<div class="add-budget-form">
|
|
|
|
|
|
<van-form>
|
|
|
|
|
|
<van-cell-group inset>
|
|
|
|
|
|
<van-field
|
|
|
|
|
|
v-model="form.name"
|
|
|
|
|
|
name="name"
|
|
|
|
|
|
label="预算名称"
|
|
|
|
|
|
placeholder="例如:每月餐饮、年度奖金"
|
|
|
|
|
|
:rules="[{ required: true, message: '请填写预算名称' }]"
|
|
|
|
|
|
/>
|
2026-01-15 10:53:05 +08:00
|
|
|
|
<!-- 新增:不记额预算复选框 -->
|
|
|
|
|
|
<van-field label="不记额预算">
|
|
|
|
|
|
<template #input>
|
2026-01-16 23:18:04 +08:00
|
|
|
|
<van-checkbox
|
|
|
|
|
|
v-model="form.noLimit"
|
|
|
|
|
|
@update:model-value="onNoLimitChange"
|
|
|
|
|
|
>
|
|
|
|
|
|
不记额预算
|
2026-01-16 11:15:44 +08:00
|
|
|
|
</van-checkbox>
|
2026-01-15 10:53:05 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</van-field>
|
2026-01-16 23:18:04 +08:00
|
|
|
|
<!-- 新增:硬性消费复选框 -->
|
2026-01-17 15:03:19 +08:00
|
|
|
|
<van-field :label="form.category === BudgetCategory.Expense ? '硬性消费' : '硬性收入'">
|
2026-01-16 23:18:04 +08:00
|
|
|
|
<template #input>
|
|
|
|
|
|
<div class="mandatory-wrapper">
|
|
|
|
|
|
<van-checkbox
|
|
|
|
|
|
v-model="form.isMandatoryExpense"
|
|
|
|
|
|
:disabled="form.noLimit"
|
|
|
|
|
|
>
|
2026-01-17 15:03:19 +08:00
|
|
|
|
{{ form.category === BudgetCategory.Expense ? '硬性消费' : '硬性收入' }}
|
2026-02-15 10:10:28 +08:00
|
|
|
|
<span class="mandatory-tip"> 当前周期 月/年 按天数自动累加(无记录时) </span>
|
2026-01-16 23:18:04 +08:00
|
|
|
|
</van-checkbox>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</van-field>
|
|
|
|
|
|
<van-field
|
|
|
|
|
|
name="type"
|
|
|
|
|
|
label="统计周期"
|
|
|
|
|
|
>
|
2026-01-07 17:33:50 +08:00
|
|
|
|
<template #input>
|
2026-01-16 11:15:44 +08:00
|
|
|
|
<van-radio-group
|
|
|
|
|
|
v-model="form.type"
|
2026-01-08 20:56:02 +08:00
|
|
|
|
direction="horizontal"
|
2026-01-15 10:53:05 +08:00
|
|
|
|
:disabled="isEdit || form.noLimit"
|
2026-01-08 20:56:02 +08:00
|
|
|
|
>
|
2026-01-16 23:18:04 +08:00
|
|
|
|
<van-radio :name="BudgetPeriodType.Month">
|
|
|
|
|
|
月
|
|
|
|
|
|
</van-radio>
|
|
|
|
|
|
<van-radio :name="BudgetPeriodType.Year">
|
|
|
|
|
|
年
|
|
|
|
|
|
</van-radio>
|
2026-01-07 17:33:50 +08:00
|
|
|
|
</van-radio-group>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</van-field>
|
2026-01-15 10:53:05 +08:00
|
|
|
|
<!-- 仅当未选中"不记额预算"时显示预算金额 -->
|
2026-01-07 17:33:50 +08:00
|
|
|
|
<van-field
|
2026-01-15 10:53:05 +08:00
|
|
|
|
v-if="!form.noLimit"
|
2026-01-07 17:33:50 +08:00
|
|
|
|
v-model="form.limit"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
name="limit"
|
|
|
|
|
|
label="预算金额"
|
|
|
|
|
|
placeholder="0.00"
|
|
|
|
|
|
:rules="[{ required: true, message: '请填写预算金额' }]"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #extra>
|
|
|
|
|
|
<span>元</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</van-field>
|
|
|
|
|
|
<van-field label="相关分类">
|
|
|
|
|
|
<template #input>
|
2026-01-16 11:15:44 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-if="form.selectedCategories.length === 0"
|
|
|
|
|
|
style="color: var(--van-text-color-3)"
|
|
|
|
|
|
>
|
|
|
|
|
|
可多选分类
|
|
|
|
|
|
</div>
|
2026-01-16 23:18:04 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-else
|
|
|
|
|
|
class="selected-categories"
|
|
|
|
|
|
>
|
2026-01-07 17:33:50 +08:00
|
|
|
|
<span class="ellipsis-text">
|
|
|
|
|
|
{{ form.selectedCategories.join('、') }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</van-field>
|
2026-01-08 14:41:50 +08:00
|
|
|
|
<ClassifySelector
|
|
|
|
|
|
v-model="form.selectedCategories"
|
|
|
|
|
|
:type="budgetType"
|
|
|
|
|
|
multiple
|
|
|
|
|
|
:show-add="false"
|
|
|
|
|
|
:show-clear="false"
|
|
|
|
|
|
/>
|
2026-01-07 17:33:50 +08:00
|
|
|
|
</van-cell-group>
|
|
|
|
|
|
</van-form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<template #footer>
|
2026-01-16 23:18:04 +08:00
|
|
|
|
<van-button
|
|
|
|
|
|
block
|
|
|
|
|
|
round
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
@click="onSubmit"
|
|
|
|
|
|
>
|
|
|
|
|
|
保存预算
|
|
|
|
|
|
</van-button>
|
2026-01-07 17:33:50 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</PopupContainer>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2026-01-08 14:41:50 +08:00
|
|
|
|
import { ref, reactive, computed } from 'vue'
|
2026-01-07 17:33:50 +08:00
|
|
|
|
import { showToast } from 'vant'
|
|
|
|
|
|
import { createBudget, updateBudget } from '@/api/budget'
|
|
|
|
|
|
import { BudgetPeriodType, BudgetCategory } from '@/constants/enums'
|
|
|
|
|
|
import PopupContainer from '@/components/PopupContainer.vue'
|
2026-01-08 14:41:50 +08:00
|
|
|
|
import ClassifySelector from '@/components/ClassifySelector.vue'
|
2026-01-07 17:33:50 +08:00
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits(['success'])
|
|
|
|
|
|
|
|
|
|
|
|
const visible = ref(false)
|
2026-01-07 19:19:53 +08:00
|
|
|
|
const isEdit = ref(false)
|
2026-01-07 17:33:50 +08:00
|
|
|
|
|
|
|
|
|
|
const form = reactive({
|
|
|
|
|
|
id: undefined,
|
|
|
|
|
|
name: '',
|
|
|
|
|
|
type: BudgetPeriodType.Month,
|
|
|
|
|
|
category: BudgetCategory.Expense,
|
|
|
|
|
|
limit: '',
|
2026-01-15 10:53:05 +08:00
|
|
|
|
selectedCategories: [],
|
2026-01-16 23:18:04 +08:00
|
|
|
|
noLimit: false, // 新增字段
|
|
|
|
|
|
isMandatoryExpense: false // 新增:硬性消费
|
2026-01-07 17:33:50 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2026-01-16 11:15:44 +08:00
|
|
|
|
const open = ({ data, isEditFlag, category }) => {
|
|
|
|
|
|
if (category === undefined) {
|
2026-01-07 19:19:53 +08:00
|
|
|
|
showToast('缺少必要参数:category')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isEdit.value = isEditFlag
|
2026-01-07 17:33:50 +08:00
|
|
|
|
if (data) {
|
|
|
|
|
|
Object.assign(form, {
|
|
|
|
|
|
id: data.id,
|
|
|
|
|
|
name: data.name,
|
|
|
|
|
|
type: data.type,
|
2026-01-07 19:19:53 +08:00
|
|
|
|
category: category,
|
2026-01-07 17:33:50 +08:00
|
|
|
|
limit: data.limit,
|
2026-01-15 10:53:05 +08:00
|
|
|
|
selectedCategories: data.selectedCategories ? [...data.selectedCategories] : [],
|
2026-01-16 23:18:04 +08:00
|
|
|
|
noLimit: data.noLimit || false, // 新增
|
|
|
|
|
|
isMandatoryExpense: data.isMandatoryExpense || false // 新增:硬性消费
|
2026-01-07 17:33:50 +08:00
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Object.assign(form, {
|
|
|
|
|
|
id: undefined,
|
|
|
|
|
|
name: '',
|
|
|
|
|
|
type: BudgetPeriodType.Month,
|
2026-01-07 19:19:53 +08:00
|
|
|
|
category: category,
|
2026-01-07 17:33:50 +08:00
|
|
|
|
limit: '',
|
2026-01-15 10:53:05 +08:00
|
|
|
|
selectedCategories: [],
|
2026-01-16 23:18:04 +08:00
|
|
|
|
noLimit: false, // 新增
|
|
|
|
|
|
isMandatoryExpense: false // 新增:硬性消费
|
2026-01-07 17:33:50 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
visible.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
defineExpose({
|
|
|
|
|
|
open
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-01-08 14:41:50 +08:00
|
|
|
|
const budgetType = computed(() => {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
return form.category === BudgetCategory.Expense
|
|
|
|
|
|
? 0
|
|
|
|
|
|
: form.category === BudgetCategory.Income
|
|
|
|
|
|
? 1
|
|
|
|
|
|
: 2
|
2026-01-07 17:33:50 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const onSubmit = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = {
|
|
|
|
|
|
...form,
|
2026-01-16 11:15:44 +08:00
|
|
|
|
limit: form.noLimit ? 0 : parseFloat(form.limit), // 不记额时金额为0
|
2026-01-15 10:53:05 +08:00
|
|
|
|
selectedCategories: form.selectedCategories,
|
2026-01-16 23:18:04 +08:00
|
|
|
|
noLimit: form.noLimit, // 新增
|
|
|
|
|
|
isMandatoryExpense: form.isMandatoryExpense // 新增:硬性消费
|
2026-01-07 17:33:50 +08:00
|
|
|
|
}
|
2026-01-16 11:15:44 +08:00
|
|
|
|
|
2026-01-07 17:33:50 +08:00
|
|
|
|
const res = form.id ? await updateBudget(data) : await createBudget(data)
|
|
|
|
|
|
if (res.success) {
|
|
|
|
|
|
showToast('保存成功')
|
|
|
|
|
|
visible.value = false
|
|
|
|
|
|
emit('success')
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
2026-01-07 19:19:53 +08:00
|
|
|
|
showToast(err.message || '保存失败')
|
2026-01-07 17:33:50 +08:00
|
|
|
|
console.error('保存预算失败', err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-07 19:19:53 +08:00
|
|
|
|
const getCategoryName = (category) => {
|
2026-01-16 11:15:44 +08:00
|
|
|
|
switch (category) {
|
2026-01-07 19:19:53 +08:00
|
|
|
|
case BudgetCategory.Expense:
|
|
|
|
|
|
return '支出'
|
|
|
|
|
|
case BudgetCategory.Income:
|
|
|
|
|
|
return '收入'
|
|
|
|
|
|
case BudgetCategory.Savings:
|
|
|
|
|
|
return '存款'
|
|
|
|
|
|
default:
|
|
|
|
|
|
return ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-15 10:53:05 +08:00
|
|
|
|
|
|
|
|
|
|
const onNoLimitChange = (value) => {
|
|
|
|
|
|
if (value) {
|
|
|
|
|
|
// 选中不记额时,自动设为年度预算
|
|
|
|
|
|
form.type = BudgetPeriodType.Year
|
2026-01-16 23:18:04 +08:00
|
|
|
|
// 选中不记额时,清除硬性消费选择
|
|
|
|
|
|
form.isMandatoryExpense = false
|
2026-01-15 10:53:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-07 17:33:50 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.add-budget-form {
|
|
|
|
|
|
padding: 20px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.selected-categories {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 4px 0;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ellipsis-text {
|
|
|
|
|
|
font-size: 14px;
|
2026-01-13 17:00:44 +08:00
|
|
|
|
color: var(--van-text-color);
|
2026-01-07 17:33:50 +08:00
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.no-data {
|
|
|
|
|
|
font-size: 13px;
|
2026-01-13 17:00:44 +08:00
|
|
|
|
color: var(--van-text-color-2);
|
2026-01-08 14:41:50 +08:00
|
|
|
|
padding: 8px 16px;
|
2026-01-07 17:33:50 +08:00
|
|
|
|
}
|
2026-01-16 23:18:04 +08:00
|
|
|
|
|
|
|
|
|
|
.mandatory-wrapper {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mandatory-tip {
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
color: var(--van-text-color-3);
|
|
|
|
|
|
margin-left: 6px;
|
|
|
|
|
|
}
|
2026-01-07 17:33:50 +08:00
|
|
|
|
</style>
|