Files
EmailBill/Web/src/views/TransactionsRecord.vue
孙诚 e00b5cffb7 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.
2026-01-01 14:43:43 +08:00

257 lines
6.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="page-container-flex">
<!-- 下拉刷新区域 -->
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<!-- 加载提示 -->
<van-loading v-if="loading && !(transactionList && transactionList.length)" vertical style="padding: 50px 0">
加载中...
</van-loading>
<!-- 交易记录列表 -->
<TransactionList
:transactions="transactionList"
:loading="loading"
:finished="finished"
:show-delete="true"
@load="onLoad"
@click="viewDetail"
@delete="(id) => {
// 从当前的交易列表中移除该交易
transactionList.value = transactionList.value.filter(t => t.id !== id)
}"
/>
<!-- 底部安全距离 -->
<div style="height: calc(50px + env(safe-area-inset-bottom, 0px))"></div>
</van-pull-refresh>
<!-- 详情/编辑弹出层 -->
<TransactionDetail
v-model:show="detailVisible"
:transaction="currentTransaction"
@save="onDetailSave"
/>
<!-- 底部浮动搜索框 -->
<div class="floating-search">
<van-search
v-model="searchKeyword"
placeholder="搜索交易摘要、来源、卡号、分类"
@update:model-value="onSearchChange"
@clear="onSearchClear"
shape="round"
/>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { showToast } from 'vant'
import {
getTransactionList,
getTransactionDetail
} from '@/api/transactionRecord'
import TransactionList from '@/components/TransactionList.vue'
import TransactionDetail from '@/components/TransactionDetail.vue'
const transactionList = ref([])
const loading = ref(false)
const refreshing = ref(false)
const finished = ref(false)
const pageIndex = ref(1)
const pageSize = 20
const total = ref(0)
const detailVisible = ref(false)
const currentTransaction = ref(null)
// 搜索相关
const searchKeyword = ref('')
let searchTimer = null
// 加载数据
const loadData = async (isRefresh = false) => {
if (isRefresh) {
pageIndex.value = 1
transactionList.value = []
finished.value = false
}
loading.value = true
try {
const params = {
pageIndex: pageIndex.value,
pageSize: pageSize
}
// 添加搜索关键词
if (searchKeyword.value) {
params.searchKeyword = searchKeyword.value
}
const response = await getTransactionList(params)
if (response.success) {
const newList = response.data || []
total.value = response.total || 0
if (isRefresh) {
transactionList.value = newList
} else {
transactionList.value = [...(transactionList.value || []), ...newList]
}
if (newList.length === 0 || newList.length < pageSize) {
finished.value = true
} else {
finished.value = false
pageIndex.value++
}
} else {
showToast(response.message || '加载数据失败')
finished.value = true
}
} catch (error) {
console.error('加载数据出错:', error)
showToast('加载数据出错: ' + (error.message || '未知错误'))
finished.value = true
} finally {
loading.value = false
refreshing.value = false
}
}
// 下拉刷新
const onRefresh = () => {
finished.value = false
transactionList.value = []
loadData(true)
}
// 搜索相关方法
const onSearchChange = () => {
// 防抖处理用户停止输入500ms后自动搜索
if (searchTimer) {
clearTimeout(searchTimer)
}
searchTimer = setTimeout(() => {
onSearch()
}, 500)
}
const onSearch = () => {
// 重置分页状态并刷新数据
transactionList.value = []
finished.value = false
loadData(true)
}
const onSearchClear = () => {
searchKeyword.value = ''
onSearch()
}
// 加载更多
const onLoad = () => {
loadData(false)
}
// 查看详情
const viewDetail = async (transaction) => {
try {
const response = await getTransactionDetail(transaction.id)
if (response.success) {
currentTransaction.value = response.data
detailVisible.value = true
} else {
showToast(response.message || '获取详情失败')
}
} catch (error) {
console.error('获取详情出错:', error)
showToast('获取详情失败')
}
}
// 详情保存后的回调
const onDetailSave = async () => {
loadData(true)
}
// 删除功能由 TransactionList 组件内部处理,组件通过 :show-delete 启用
onMounted(async () => {
// 不需要手动调用 loadDatavan-list 会自动触发 onLoad
})
// 监听全局删除事件,保持页面一致性
const onGlobalTransactionDeleted = () => {
// 如果在此页面,重新刷新当前列表以保持数据一致
transactionList.value = []
pageIndex.value = 1
finished.value = false
loadData(true)
}
window.addEventListener && window.addEventListener('transaction-deleted', onGlobalTransactionDeleted)
onBeforeUnmount(() => {
window.removeEventListener && window.removeEventListener('transaction-deleted', onGlobalTransactionDeleted)
})
// 外部新增/修改/批量更新时的刷新监听
const onGlobalTransactionsChanged = () => {
transactionList.value = []
pageIndex.value = 1
finished.value = false
loadData(true)
}
window.addEventListener && window.addEventListener('transactions-changed', onGlobalTransactionsChanged)
onBeforeUnmount(() => {
window.removeEventListener && window.removeEventListener('transactions-changed', onGlobalTransactionsChanged)
})
</script>
<style scoped>
:deep(.van-pull-refresh) {
flex: 1;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.floating-search {
position: fixed;
bottom: 90px;
left: 0;
right: 0;
z-index: 999;
padding: 8px 16px;
background: transparent;
pointer-events: none;
}
.floating-search :deep(.van-search) {
pointer-events: auto;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
border-radius: 20px;
border: none;
}
/* 设置页面容器背景色 */
:deep(.van-nav-bar) {
background: transparent !important;
}
</style>