feat: Refactor transaction handling and add new features
- Updated ReasonGroupList.vue to modify classify button behavior for adding new classifications. - Refactored TransactionDetail.vue to integrate PopupContainer and enhance transaction detail display. - Improved TransactionDetailDialog.vue with updated classify button functionality. - Simplified BalanceView.vue by removing manual entry button. - Enhanced PeriodicRecord.vue to update classify button interactions. - Removed unused add transaction dialog from TransactionsRecord.vue. - Added new API endpoints in TransactionRecordController for parsing transactions and handling offsets. - Introduced BillForm.vue and ManualBillAdd.vue for streamlined bill entry. - Implemented OneLineBillAdd.vue for intelligent transaction parsing. - Created GlobalAddBill.vue for a unified bill addition interface.
This commit is contained in:
@@ -3,14 +3,6 @@
|
||||
<!-- 顶部导航栏 -->
|
||||
<van-nav-bar title="交易记录" placeholder>
|
||||
<template #right>
|
||||
<van-button
|
||||
v-if="tabActive === 'balance'"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="transactionsRecordRef.openAddDialog()"
|
||||
>
|
||||
手动录账
|
||||
</van-button>
|
||||
<van-button
|
||||
v-if="tabActive === 'email'"
|
||||
size="small"
|
||||
|
||||
@@ -191,6 +191,14 @@
|
||||
|
||||
<!-- 分类按钮网格 -->
|
||||
<div class="classify-buttons">
|
||||
<van-button
|
||||
type="success"
|
||||
size="small"
|
||||
class="classify-btn"
|
||||
@click="showAddClassify = true"
|
||||
>
|
||||
+ 新增
|
||||
</van-button>
|
||||
<van-button
|
||||
v-for="item in classifyColumns"
|
||||
:key="item.id"
|
||||
@@ -201,14 +209,6 @@
|
||||
>
|
||||
{{ item.text }}
|
||||
</van-button>
|
||||
<van-button
|
||||
type="success"
|
||||
size="small"
|
||||
class="classify-btn"
|
||||
@click="showAddClassify = true"
|
||||
>
|
||||
+ 新增
|
||||
</van-button>
|
||||
<van-button
|
||||
v-if="form.classify"
|
||||
type="warning"
|
||||
|
||||
@@ -32,124 +32,9 @@
|
||||
@save="onDetailSave"
|
||||
/>
|
||||
|
||||
<!-- 新增交易记录弹出层 -->
|
||||
<PopupContainer
|
||||
v-model="addDialogVisible"
|
||||
title="手动录账单"
|
||||
height="85%"
|
||||
>
|
||||
<van-form @submit="onAddSubmit">
|
||||
<van-cell-group inset title="基本信息">
|
||||
<van-field
|
||||
v-model="addForm.occurredAt"
|
||||
is-link
|
||||
readonly
|
||||
name="occurredAt"
|
||||
label="交易时间"
|
||||
placeholder="请选择交易时间"
|
||||
@click="showDateTimePicker = true"
|
||||
:rules="[{ required: true, message: '请选择交易时间' }]"
|
||||
/>
|
||||
</van-cell-group>
|
||||
|
||||
<van-cell-group inset title="交易明细">
|
||||
<van-field
|
||||
v-model="addForm.reason"
|
||||
name="reason"
|
||||
label="交易摘要"
|
||||
placeholder="请输入交易摘要"
|
||||
type="textarea"
|
||||
rows="2"
|
||||
autosize
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
<van-field
|
||||
v-model="addForm.amount"
|
||||
name="amount"
|
||||
label="交易金额"
|
||||
placeholder="请输入交易金额"
|
||||
type="number"
|
||||
:rules="[{ required: true, message: '请输入交易金额' }]"
|
||||
/>
|
||||
<van-field
|
||||
v-model="addForm.typeText"
|
||||
is-link
|
||||
readonly
|
||||
name="type"
|
||||
label="交易类型"
|
||||
placeholder="请选择交易类型"
|
||||
@click="showAddTypePicker = true"
|
||||
:rules="[{ required: true, message: '请选择交易类型' }]"
|
||||
/>
|
||||
<van-field
|
||||
v-model="addForm.classify"
|
||||
is-link
|
||||
readonly
|
||||
name="classify"
|
||||
label="交易分类"
|
||||
placeholder="请选择或输入交易分类"
|
||||
@click="showAddClassifyPicker = true"
|
||||
/>
|
||||
</van-cell-group>
|
||||
|
||||
<div style="margin: 16px;">
|
||||
<van-button round block type="primary" native-type="submit" :loading="addSubmitting">
|
||||
确认添加
|
||||
</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
</PopupContainer>
|
||||
|
||||
<!-- 新增交易 - 日期时间选择器 -->
|
||||
<van-popup v-model:show="showDateTimePicker" position="bottom" round>
|
||||
<van-date-picker
|
||||
v-model="dateTimeValue"
|
||||
title="选择日期时间"
|
||||
:min-date="new Date(2020, 0, 1)"
|
||||
:max-date="new Date()"
|
||||
@confirm="onDateTimeConfirm"
|
||||
@cancel="showDateTimePicker = false"
|
||||
/>
|
||||
</van-popup>
|
||||
|
||||
<!-- 新增交易 - 交易类型选择器 -->
|
||||
<van-popup v-model:show="showAddTypePicker" position="bottom" round>
|
||||
<van-picker
|
||||
show-toolbar
|
||||
:columns="typeColumns"
|
||||
@confirm="onAddTypeConfirm"
|
||||
@cancel="showAddTypePicker = false"
|
||||
/>
|
||||
</van-popup>
|
||||
|
||||
<!-- 新增交易 - 交易分类选择器 -->
|
||||
<van-popup v-model:show="showAddClassifyPicker" position="bottom" round>
|
||||
<van-picker
|
||||
ref="addClassifyPickerRef"
|
||||
:columns="classifyColumns"
|
||||
@confirm="onAddClassifyConfirm"
|
||||
@cancel="showAddClassifyPicker = false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<div class="picker-toolbar">
|
||||
<van-button class="toolbar-cancel" size="small" @click="clearAddClassify">清空</van-button>
|
||||
<van-button class="toolbar-add" size="small" type="primary" @click="showAddClassify = true">新增</van-button>
|
||||
<van-button class="toolbar-confirm" size="small" type="primary" @click="confirmAddClassify">确认</van-button>
|
||||
</div>
|
||||
</template>
|
||||
</van-picker>
|
||||
</van-popup>
|
||||
|
||||
<!-- 新增分类对话框 -->
|
||||
<van-dialog
|
||||
v-model:show="showAddClassify"
|
||||
title="新增交易分类"
|
||||
show-cancel-button
|
||||
@confirm="addNewClassify"
|
||||
>
|
||||
<van-field v-model="newClassify" placeholder="请输入新的交易分类" />
|
||||
</van-dialog>
|
||||
|
||||
<!-- 底部浮动搜索框 -->
|
||||
<div class="floating-search">
|
||||
@@ -165,18 +50,14 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { showToast } from 'vant'
|
||||
import {
|
||||
getTransactionList,
|
||||
getTransactionDetail,
|
||||
createTransaction,
|
||||
deleteTransaction
|
||||
getTransactionDetail
|
||||
} from '@/api/transactionRecord'
|
||||
import { getCategoryList, createCategory } from '@/api/transactionCategory'
|
||||
import TransactionList from '@/components/TransactionList.vue'
|
||||
import TransactionDetail from '@/components/TransactionDetail.vue'
|
||||
import PopupContainer from '@/components/PopupContainer.vue'
|
||||
|
||||
const transactionList = ref([])
|
||||
const loading = ref(false)
|
||||
@@ -192,52 +73,7 @@ const currentTransaction = ref(null)
|
||||
const searchKeyword = ref('')
|
||||
let searchTimer = null
|
||||
|
||||
// 新增交易弹窗相关
|
||||
const addDialogVisible = ref(false)
|
||||
const addSubmitting = ref(false)
|
||||
const showDateTimePicker = ref(false)
|
||||
const dateTimeValue = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()])
|
||||
const showAddTypePicker = ref(false)
|
||||
const showAddClassifyPicker = ref(false)
|
||||
const addClassifyPickerRef = ref(null)
|
||||
const showAddClassify = ref(false)
|
||||
const newClassify = ref('')
|
||||
|
||||
// 交易类型
|
||||
const typeColumns = [
|
||||
{ text: '支出', value: 0 },
|
||||
{ text: '收入', value: 1 },
|
||||
{ text: '不计入收支', value: 2 }
|
||||
]
|
||||
|
||||
// 分类相关
|
||||
const classifyColumns = ref([])
|
||||
|
||||
// 新增表单
|
||||
const addForm = reactive({
|
||||
occurredAt: '',
|
||||
reason: '',
|
||||
amount: '',
|
||||
type: 0,
|
||||
typeText: '',
|
||||
classify: ''
|
||||
})
|
||||
|
||||
// 加载分类列表
|
||||
const loadClassifyList = async (type = null) => {
|
||||
try {
|
||||
const response = await getCategoryList(type)
|
||||
if (response.success) {
|
||||
classifyColumns.value = (response.data || []).map(item => ({
|
||||
text: item.name,
|
||||
value: item.name,
|
||||
id: item.id
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载分类列表出错:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
const loadData = async (isRefresh = false) => {
|
||||
@@ -345,149 +181,16 @@ const viewDetail = async (transaction) => {
|
||||
// 详情保存后的回调
|
||||
const onDetailSave = async () => {
|
||||
loadData(true)
|
||||
// 重新加载分类列表
|
||||
await loadClassifyList()
|
||||
}
|
||||
|
||||
// 删除功能由 TransactionList 组件内部处理,组件通过 :show-delete 启用
|
||||
|
||||
// 打开新增弹窗
|
||||
const openAddDialog = () => {
|
||||
// 重置表单
|
||||
addForm.occurredAt = ''
|
||||
addForm.reason = ''
|
||||
addForm.amount = ''
|
||||
addForm.type = 0
|
||||
addForm.typeText = ''
|
||||
addForm.classify = ''
|
||||
|
||||
// 设置默认日期时间为当前时间
|
||||
const now = new Date()
|
||||
dateTimeValue.value = [now.getFullYear(), now.getMonth() + 1, now.getDate()]
|
||||
addForm.occurredAt = formatDateForSubmit(now)
|
||||
|
||||
addDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 格式化日期用于提交
|
||||
const formatDateForSubmit = (date) => {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
// 日期时间选择确认
|
||||
const onDateTimeConfirm = ({ selectedValues }) => {
|
||||
const date = new Date(selectedValues[0], selectedValues[1] - 1, selectedValues[2])
|
||||
addForm.occurredAt = formatDateForSubmit(date)
|
||||
showDateTimePicker.value = false
|
||||
}
|
||||
|
||||
// 新增交易 - 交易类型选择确认
|
||||
const onAddTypeConfirm = ({ selectedValues, selectedOptions }) => {
|
||||
addForm.type = selectedValues[0]
|
||||
addForm.typeText = selectedOptions[0].text
|
||||
showAddTypePicker.value = false
|
||||
}
|
||||
|
||||
// 新增交易 - 交易分类选择确认
|
||||
const onAddClassifyConfirm = ({ selectedOptions }) => {
|
||||
if (selectedOptions && selectedOptions[0]) {
|
||||
addForm.classify = selectedOptions[0].text
|
||||
}
|
||||
showAddClassifyPicker.value = false
|
||||
}
|
||||
|
||||
// 新增交易 - 清空分类
|
||||
const clearAddClassify = () => {
|
||||
addForm.classify = ''
|
||||
showAddClassifyPicker.value = false
|
||||
showToast('已清空分类')
|
||||
}
|
||||
|
||||
// 新增交易 - 确认分类(从 picker 中获取选中值)
|
||||
const confirmAddClassify = () => {
|
||||
if (addClassifyPickerRef.value) {
|
||||
const selectedValues = addClassifyPickerRef.value.getSelectedOptions()
|
||||
if (selectedValues && selectedValues[0]) {
|
||||
addForm.classify = selectedValues[0].text
|
||||
}
|
||||
}
|
||||
showAddClassifyPicker.value = false
|
||||
}
|
||||
|
||||
// 新增分类
|
||||
const addNewClassify = async () => {
|
||||
if (!newClassify.value.trim()) {
|
||||
showToast('请输入分类名称')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await createCategory({
|
||||
name: newClassify.value.trim(),
|
||||
type: addForm.type
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
showToast('新增分类成功')
|
||||
newClassify.value = ''
|
||||
// 重新加载分类列表
|
||||
await loadClassifyList(addForm.type)
|
||||
// 设置为新增的分类
|
||||
addForm.classify = response.data.name
|
||||
} else {
|
||||
showToast(response.message || '新增分类失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('新增分类失败:', error)
|
||||
showToast('新增分类失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 提交新增交易
|
||||
const onAddSubmit = async () => {
|
||||
try {
|
||||
addSubmitting.value = true
|
||||
|
||||
const data = {
|
||||
occurredAt: addForm.occurredAt,
|
||||
reason: addForm.reason,
|
||||
amount: parseFloat(addForm.amount),
|
||||
type: addForm.type,
|
||||
classify: addForm.classify || null
|
||||
}
|
||||
|
||||
const response = await createTransaction(data)
|
||||
if (response.success) {
|
||||
showToast('添加成功')
|
||||
addDialogVisible.value = false
|
||||
loadData(true)
|
||||
try { window.dispatchEvent(new CustomEvent('transactions-changed', { detail: response.data })) } catch(e) {}
|
||||
// 重新加载分类列表
|
||||
await loadClassifyList()
|
||||
} else {
|
||||
showToast(response.message || '添加失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加出错:', error)
|
||||
showToast('添加失败: ' + (error.message || '未知错误'))
|
||||
} finally {
|
||||
addSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadClassifyList()
|
||||
// 不需要手动调用 loadData,van-list 会自动触发 onLoad
|
||||
})
|
||||
|
||||
// 监听全局删除事件,保持页面一致性
|
||||
const onGlobalTransactionDeleted = (e) => {
|
||||
const onGlobalTransactionDeleted = () => {
|
||||
// 如果在此页面,重新刷新当前列表以保持数据一致
|
||||
transactionList.value = []
|
||||
pageIndex.value = 1
|
||||
@@ -502,7 +205,7 @@ onBeforeUnmount(() => {
|
||||
})
|
||||
|
||||
// 外部新增/修改/批量更新时的刷新监听
|
||||
const onGlobalTransactionsChanged = (e) => {
|
||||
const onGlobalTransactionsChanged = () => {
|
||||
transactionList.value = []
|
||||
pageIndex.value = 1
|
||||
finished.value = false
|
||||
@@ -515,10 +218,7 @@ onBeforeUnmount(() => {
|
||||
window.removeEventListener && window.removeEventListener('transactions-changed', onGlobalTransactionsChanged)
|
||||
})
|
||||
|
||||
// 暴露给父级方法调用
|
||||
defineExpose({
|
||||
openAddDialog
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
@@ -548,21 +248,7 @@ defineExpose({
|
||||
border: none;
|
||||
}
|
||||
|
||||
.picker-toolbar {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding: 5px 10px;
|
||||
border-bottom: 1px solid #ebedf0;
|
||||
}
|
||||
|
||||
.toolbar-cancel {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.toolbar-confirm {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* 设置页面容器背景色 */
|
||||
:deep(.van-nav-bar) {
|
||||
|
||||
Reference in New Issue
Block a user