Files
EmailBill/Web/src/views/EmailRecord.vue
孙诚 545796aba9
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Failing after 5s
Docker Build & Deploy / Deploy to Production (push) Has been skipped
优化邮件解析正则表达式,增强交易信息提取能力;调整界面样式,增加删除功能
2025-12-31 11:49:25 +08:00

457 lines
12 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 && !(emailList && emailList.length)" vertical style="padding: 50px 0">
加载中...
</van-loading>
<!-- 邮件列表 -->
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<van-cell-group v-if="emailList && emailList.length" inset style="margin-top: 10px">
<van-swipe-cell
v-for="email in emailList"
:key="email.id"
>
<van-cell
:title="email.subject"
:label="`来自: ${email.from}\n收件: ${email.toName || email.to || '未知'}`"
is-link
@click="viewDetail(email)"
>
<template #value>
<div class="email-info">
<div class="email-date">{{ formatDate(email.receivedDate) }}</div>
<div class="bill-count" v-if="email.transactionCount > 0">
<span style="font-size: 12px;">已解析{{ email.transactionCount }}条账单</span>
</div>
</div>
</template>
</van-cell>
<template #right>
<van-button
square
type="danger"
text="删除"
class="delete-button"
@click="handleDelete(email)"
/>
</template>
</van-swipe-cell>
</van-cell-group>
<van-empty
v-if="!loading && !(emailList && emailList.length)"
description="暂无邮件记录"
/>
</van-list>
<!-- 底部安全距离 -->
<div style="height: calc(50px + env(safe-area-inset-bottom, 0px))"></div>
</van-pull-refresh>
<!-- 详情弹出层 -->
<van-popup
v-model:show="detailVisible"
position="bottom"
:style="{ height: '80%' }"
round
closeable
>
<div class="popup-container" v-if="currentEmail">
<div class="popup-header-fixed">
<h3>{{ currentEmail.Subject || currentEmail.subject || '(无主题)' }}</h3>
</div>
<div class="popup-scroll-content">
<van-cell-group inset style="margin-top: 12px;">
<van-cell title="发件人" :value="currentEmail.From || currentEmail.from || '未知'" />
<van-cell title="接收时间" :value="formatDate(currentEmail.ReceivedDate || currentEmail.receivedDate)" />
<van-cell title="记录时间" :value="formatDate(currentEmail.CreateTime || currentEmail.createTime)" />
<van-cell
title="已解析账单数"
:value="`${currentEmail.TransactionCount || currentEmail.transactionCount || 0}条`"
is-link
@click="viewTransactions"
v-if="(currentEmail.TransactionCount || currentEmail.transactionCount || 0) > 0"
/>
</van-cell-group>
<div class="email-content">
<h4 style="margin-left: 10px;">邮件内容</h4>
<div
v-if="currentEmail.htmlBody"
v-html="currentEmail.htmlBody"
class="content-body html-content"
></div>
<div
v-else-if="currentEmail.body"
class="content-body"
>
{{ currentEmail.body }}
</div>
<div v-else class="content-body empty-content">
暂无邮件内容
<div style="font-size: 12px; margin-top: 8px; color: #999;">
Debug: {{ Object.keys(currentEmail).join(', ') }}
</div>
</div>
</div>
<div style="margin: 16px;">
<van-button
round
block
type="primary"
:loading="refreshingAnalysis"
@click="handleRefreshAnalysis"
>
重新分析
</van-button>
</div>
</div>
</div>
</van-popup>
<!-- 账单列表弹出层 -->
<PopupContainer
v-model="transactionListVisible"
title="关联账单列表"
height="70%"
>
<TransactionList
:transactions="transactionList"
:loading="false"
:finished="true"
:show-delete="true"
@click="handleTransactionClick"
/>
</PopupContainer>
<!-- 账单详情编辑弹出层 -->
<TransactionDetail
:show="transactionDetailVisible"
:transaction="currentTransaction"
@update:show="transactionDetailVisible = $event"
@save="handleTransactionSave"
/>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { showToast, showConfirmDialog } from 'vant'
import { getEmailList, getEmailDetail, deleteEmail, refreshTransactionRecords, syncEmails, getEmailTransactions } from '@/api/emailRecord'
import { getTransactionDetail } from '@/api/transactionRecord'
import TransactionList from '@/components/TransactionList.vue'
import TransactionDetail from '@/components/TransactionDetail.vue'
import PopupContainer from '@/components/PopupContainer.vue'
const emailList = 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 currentEmail = ref(null)
const refreshingAnalysis = ref(false)
const syncing = ref(false)
const transactionListVisible = ref(false)
const transactionList = ref([])
const transactionDetailVisible = ref(false)
const currentTransaction = ref(null)
// 加载数据
const loadData = async (isRefresh = false) => {
if (loading.value) return // 防止重复加载
if (isRefresh) {
pageIndex.value = 1
emailList.value = []
finished.value = false
}
loading.value = true
try {
const params = {
pageIndex: pageIndex.value,
pageSize: pageSize
}
const response = await getEmailList(params)
if (response.success) {
const newList = response.data || []
total.value = response.total || 0
if (isRefresh) {
emailList.value = newList
} else {
emailList.value = [...(emailList.value || []), ...newList]
}
// 判断是否还有更多数据返回数据少于pageSize条或为空说明没有更多了
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 = () => {
loadData(true)
}
// 加载更多
const onLoad = () => {
if (!finished.value && !loading.value) {
loadData()
}
}
// 查看详情
const viewDetail = async (email) => {
try {
const response = await getEmailDetail(email.id)
console.log('详情 API 返回:', response)
if (response.success) {
currentEmail.value = response.data
console.log('currentEmail:', currentEmail.value)
detailVisible.value = true
} else {
showToast(response.message || '获取详情失败')
}
} catch (error) {
console.error('获取详情出错:', error)
showToast('获取详情失败')
}
}
// 删除
const handleDelete = async (email) => {
try {
await showConfirmDialog({
title: '提示',
message: '确定要删除这封邮件吗?',
})
const response = await deleteEmail(email.id)
if (response.success) {
showToast('删除成功')
loadData(true)
} else {
showToast(response.message || '删除失败')
}
} catch (error) {
if (error !== 'cancel') {
console.error('删除出错:', error)
showToast('删除失败')
}
}
}
// 重新分析
const handleRefreshAnalysis = async () => {
if (!currentEmail.value) return
try {
await showConfirmDialog({
title: '提示',
message: '确定要重新分析该邮件并刷新交易记录吗?',
})
refreshingAnalysis.value = true
const response = await refreshTransactionRecords(currentEmail.value.id || currentEmail.value.Id)
if (response.success) {
showToast('重新分析成功')
detailVisible.value = false
} else {
showToast(response.message || '重新分析失败')
}
} catch (error) {
if (error !== 'cancel') {
console.error('重新分析出错:', error)
showToast('重新分析失败: ' + (error.message || '未知错误'))
}
} finally {
refreshingAnalysis.value = false
}
}
// 立即同步
const handleSync = async () => {
try {
syncing.value = true
const response = await syncEmails()
if (response.success) {
showToast(response.message || '同步成功')
// 同步成功后刷新列表
loadData(true)
} else {
showToast(response.message || '同步失败')
}
} catch (error) {
console.error('同步出错:', error)
showToast('同步失败: ' + (error.message || '未知错误'))
} finally {
syncing.value = false
}
}
// 查看关联的账单列表
const viewTransactions = async () => {
if (!currentEmail.value) return
try {
const emailId = currentEmail.value.id || currentEmail.value.Id
const response = await getEmailTransactions(emailId)
if (response.success) {
transactionList.value = response.data || []
transactionListVisible.value = true
} else {
showToast(response.message || '获取账单列表失败')
}
} catch (error) {
console.error('获取账单列表出错:', error)
showToast('获取账单列表失败')
}
}
// 处理点击账单
const handleTransactionClick = async (transaction) => {
try {
const response = await getTransactionDetail(transaction.id)
if (response.success) {
currentTransaction.value = response.data
transactionDetailVisible.value = true
} else {
showToast(response.message || '获取账单详情失败')
}
} catch (error) {
console.error('获取账单详情出错:', error)
showToast('获取账单详情失败')
}
}
// 账单保存后刷新列表
const handleTransactionSave = async () => {
// 刷新账单列表
if (currentEmail.value) {
const emailId = currentEmail.value.id || currentEmail.value.Id
const response = await getEmailTransactions(emailId)
if (response.success) {
transactionList.value = response.data || []
}
}
}
// 格式化日期
const formatDate = (dateString) => {
if (!dateString) return ''
const date = new Date(dateString)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
}
onMounted(() => {
loadData(true)
})
// 暴露给父级方法调用
defineExpose({
handleSync
})
</script>
<style scoped>
:deep(.van-pull-refresh) {
flex: 1;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.email-info {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
}
.bill-count {
margin-bottom: 2px;
}
.email-date {
font-size: 12px;
color: #969799;
padding-right: 10px;
}
.email-content {
margin-top: 16px;
}
.email-content h4 {
margin: 0 0 12px 0;
font-size: 16px;
font-weight: bold;
}
.content-body {
padding: 12px;
border-radius: 8px;
white-space: pre-wrap;
word-break: break-word;
font-size: 14px;
line-height: 1.6;
max-height: 300px;
overflow-y: auto;
margin: 0 20px;
}
@media (prefers-color-scheme: dark) {
.content-body {
background-color: #2c2c2c;
border: 1px solid #3a3a3a;
}
}
.delete-button {
height: 100%;
}
/* 设置页面容器背景色 */
:deep(.van-nav-bar) {
background: transparent !important;
}
</style>