feat: 招商银行 PDF 账单导入、跨格式去重与导入性能优化

- 新增招商银行「交易流水」PDF 解析(PDFBox 坐标还原,支持多页与折行),CSV/PDF 去重标识统一,支持跨格式去重
- 账单导入改为批量插入,MyBatis 日志改 Slf4j,导入接口超时放宽至 120s
- 交易导出 Excel 增加样式、冻结表头与自动筛选,导出前二次确认
- 交易列表展示不计入统计金额,导入预览支持设置「计入统计」
This commit is contained in:
2026-09-19 14:30:09 +08:00
parent 9981e77bf1
commit 196afb682d
16 changed files with 1072 additions and 107 deletions
+35 -3
View File
@@ -76,6 +76,18 @@ const pageSummary = computed(() => {
}
return { income, expense, balance: income - expense }
})
// 当前页不计入统计的金额汇总(仅统计 includeInStats=false 的记录)
const pageExcludedSummary = computed(() => {
let income = 0
let expense = 0
for (const t of transactions.value) {
if (t.includeInStats) continue
const amount = Number(t.amount) || 0
if (t.type === 'INCOME') income += amount
else expense += amount
}
return { income, expense }
})
const loading = ref(false) // 列表加载状态
const showModal = ref(false) // 新增/编辑弹窗显示状态
const showImportModal = ref(false) // 账单导入弹窗显示状态
@@ -1555,8 +1567,23 @@ async function handleBatchSubmit() {
}
}
/** 导出交易记录为 Excel 文件 */
async function handleExport() {
/** 导出中状态,防止重复点击 */
const exporting = ref(false)
/** 导出交易记录:弹出二次确认,确认后执行导出 */
function handleExport() {
dialog.warning({
title: t('transaction.exportConfirmTitle'),
content: t('transaction.exportConfirm'),
positiveText: t('common.confirm'),
negativeText: t('common.cancel'),
onPositiveClick: () => doExport(),
})
}
/** 实际执行导出交易记录为 Excel 文件 */
async function doExport() {
exporting.value = true
try {
const res = await exportTransaction({
types: filterType.value.length > 0 ? filterType.value : undefined,
@@ -1584,6 +1611,8 @@ async function handleExport() {
message.success(t('transaction.exportSuccess'))
} catch (e: any) {
message.error(e.message || t('transaction.exportFailed'))
} finally {
exporting.value = false
}
}
@@ -1614,7 +1643,7 @@ onMounted(() => {
{{ t('transaction.batchDelete') }}
</NButton>
<NButton @click="showImportModal = true">{{ t('transaction.importBill') }}</NButton>
<NButton @click="handleExport">{{ t('transaction.exportExcel') }}</NButton>
<NButton :loading="exporting" @click="handleExport">{{ t('transaction.exportExcel') }}</NButton>
<NButton v-if="aiEnabled" @click="handleAiRecordOpen">
<template #icon>
<NIcon :component="SparklesOutline" />
@@ -1802,6 +1831,9 @@ onMounted(() => {
{{ t('transaction.pageExpense') }} <span style="color: #d03050;">{{ pageSummary.expense.toFixed(2) }}</span>
· {{ t('transaction.pageIncome') }} <span style="color: #18a058;">{{ pageSummary.income.toFixed(2) }}</span>
· {{ t('transaction.pageBalance') }} <span :style="{ color: pageSummary.balance < 0 ? '#d03050' : '#18a058' }">{{ pageSummary.balance.toFixed(2) }}</span>
<span style="margin-left: 8px;">
{{ t('transaction.pageExcluded', { expense: pageExcludedSummary.expense.toFixed(2), income: pageExcludedSummary.income.toFixed(2) }) }}
</span>
</span>
</NSpace>