Files
EmailBill/Web/src/components/Bill/BillForm.vue

290 lines
6.2 KiB
Vue
Raw Normal View History

<template>
<div class="bill-form">
<van-form @submit="handleSubmit">
<van-cell-group inset>
<!-- 日期时间 -->
<van-field label="时间">
<template #input>
<div style="display: flex; gap: 16px">
2026-01-16 11:15:44 +08:00
<div @click="showDatePicker = true">
{{ form.date }}
</div>
<div @click="showTimePicker = true">
{{ form.time }}
</div>
</div>
</template>
</van-field>
<!-- 金额 -->
<van-field
v-model="form.amount"
name="amount"
label="金额"
type="number"
placeholder="0.00"
:rules="[{ required: true, message: '请输入金额' }]"
/>
<!-- 备注 -->
<van-field
v-model="form.note"
name="note"
label="摘要"
placeholder="摘要信息"
rows="2"
autosize
type="textarea"
/>
<!-- 交易类型 -->
2026-01-27 15:29:25 +08:00
<van-field
name="type"
label="类型"
>
<template #input>
2026-01-27 15:29:25 +08:00
<van-radio-group
v-model="form.type"
direction="horizontal"
@change="handleTypeChange"
>
<van-radio :name="0">
支出
</van-radio>
<van-radio :name="1">
收入
</van-radio>
<van-radio :name="2">
不计
</van-radio>
</van-radio-group>
</template>
</van-field>
<!-- 分类 -->
2026-01-27 15:29:25 +08:00
<van-field
name="category"
label="分类"
>
<template #input>
2026-01-27 15:29:25 +08:00
<span
v-if="!categoryName"
style="color: var(--van-text-color-3)"
>请选择分类</span>
<span v-else>{{ categoryName }}</span>
</template>
</van-field>
2026-01-16 11:15:44 +08:00
2026-01-08 14:41:50 +08:00
<!-- 分类选择组件 -->
2026-01-27 15:29:25 +08:00
<ClassifySelector
v-model="categoryName"
:type="form.type"
/>
</van-cell-group>
<div class="actions">
2026-01-27 15:29:25 +08:00
<van-button
round
block
type="primary"
native-type="submit"
:loading="loading"
>
{{ submitText }}
</van-button>
2026-01-16 11:15:44 +08:00
<slot name="actions" />
</div>
</van-form>
<!-- 日期选择弹窗 -->
2026-01-27 15:29:25 +08:00
<van-popup
v-model:show="showDatePicker"
position="bottom"
round
teleport="body"
>
<van-date-picker
v-model="currentDate"
title="选择日期"
@confirm="onConfirmDate"
@cancel="showDatePicker = false"
/>
</van-popup>
<!-- 时间选择弹窗 -->
2026-01-27 15:29:25 +08:00
<van-popup
v-model:show="showTimePicker"
position="bottom"
round
teleport="body"
>
<van-time-picker
v-model="currentTime"
title="选择时间"
@confirm="onConfirmTime"
@cancel="showTimePicker = false"
/>
</van-popup>
</div>
</template>
<script setup>
import { ref, onMounted, watch, nextTick } from 'vue'
import { showToast } from 'vant'
import dayjs from 'dayjs'
import ClassifySelector from '@/components/Common/ClassifySelector.vue'
const props = defineProps({
initialData: {
type: Object,
default: () => ({})
},
loading: {
type: Boolean,
default: false
},
submitText: {
type: String,
default: '保存'
}
})
const emit = defineEmits(['submit'])
// 表单数据
const form = ref({
type: 0, // 0: 支出, 1: 收入, 2: 不计
amount: '',
date: dayjs().format('YYYY-MM-DD'),
time: dayjs().format('HH:mm'),
note: ''
})
const categoryName = ref('')
const isSyncing = ref(false)
// 弹窗控制
const showDatePicker = ref(false)
const showTimePicker = ref(false)
2026-01-08 14:41:50 +08:00
// 日期时间临时变量 (Vant DatePicker 需要数组 or 特定格式)
const currentDate = ref(dayjs().format('YYYY-MM-DD').split('-'))
const currentTime = ref(dayjs().format('HH:mm').split(':'))
// 初始化数据
const initForm = async () => {
if (props.initialData) {
isSyncing.value = true
const { occurredAt, amount, reason, type, classify } = props.initialData
2026-01-16 11:15:44 +08:00
if (occurredAt) {
const dt = dayjs(occurredAt)
form.value.date = dt.format('YYYY-MM-DD')
form.value.time = dt.format('HH:mm')
currentDate.value = form.value.date.split('-')
currentTime.value = form.value.time.split(':')
}
2026-01-16 11:15:44 +08:00
if (amount !== undefined) {
form.value.amount = amount
}
if (reason !== undefined) {
form.value.note = reason
}
if (type !== undefined) {
form.value.type = type
}
2026-01-08 14:41:50 +08:00
// 如果有传入分类名称,尝试设置
if (classify) {
2026-01-08 14:41:50 +08:00
categoryName.value = classify
}
nextTick(() => {
isSyncing.value = false
})
}
}
onMounted(() => {
initForm()
})
// 监听 initialData 变化 (例如重新解析后)
2026-01-16 11:15:44 +08:00
watch(
() => props.initialData,
() => {
initForm()
},
{ deep: true }
)
const handleTypeChange = (newType) => {
if (!isSyncing.value) {
categoryName.value = ''
}
}
const onConfirmDate = ({ selectedValues }) => {
form.value.date = selectedValues.join('-')
showDatePicker.value = false
}
const onConfirmTime = ({ selectedValues }) => {
form.value.time = selectedValues.join(':')
showTimePicker.value = false
}
const handleSubmit = () => {
if (!form.value.amount) {
showToast('请输入金额')
return
}
if (!categoryName.value) {
showToast('请选择分类')
return
}
const fullDateTime = `${form.value.date}T${form.value.time}:00`
2026-01-16 11:15:44 +08:00
const payload = {
occurredAt: fullDateTime,
classify: categoryName.value,
amount: parseFloat(form.value.amount),
reason: form.value.note || '',
type: form.value.type
}
2026-01-16 11:15:44 +08:00
emit('submit', payload)
}
// 暴露重置方法给父组件
const reset = () => {
form.value.amount = ''
form.value.note = ''
// 保留日期和类型
}
defineExpose({ reset })
</script>
<style scoped>
.bill-form {
padding-top: 10px;
}
.actions {
margin: 20px 16px;
}
.classify-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 12px 16px;
}
.classify-btn {
flex: 0 0 auto;
min-width: 70px;
border-radius: 16px;
}
</style>