Files
EmailBill/Web/src/views/EmailRecord.vue
SunCheng 32d5ed62d0
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 16s
Docker Build & Deploy / Deploy to Production (push) Successful in 6s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
fix
2026-02-20 14:57:19 +08:00

585 lines
15 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.
<!-- eslint-disable vue/no-v-html -->
<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
v-if="email.transactionCount > 0"
class="bill-count"
>
<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))" />
</van-pull-refresh>
<!-- 详情弹出层 -->
<PopupContainerV2
v-model:show="detailVisible"
:title="currentEmail ? currentEmail.Subject || currentEmail.subject || '(无主题)' : ''"
:height="'75%'"
>
<div v-if="currentEmail">
<!-- 操作按钮栏 -->
<div style="padding: 12px 16px; text-align: right; border-bottom: 1px solid var(--van-border-color)">
<van-button
size="small"
type="primary"
:loading="refreshingAnalysis"
@click="handleRefreshAnalysis"
>
重新分析
</van-button>
</div>
<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
v-if="(currentEmail.TransactionCount || currentEmail.transactionCount || 0) > 0"
title="已解析账单数"
:value="`${currentEmail.TransactionCount || currentEmail.transactionCount || 0}条`"
is-link
@click="viewTransactions"
/>
</van-cell-group>
<div class="email-content">
<h4 style="margin-left: 10px">
邮件内容
</h4>
<div
v-if="currentEmail.htmlBody"
class="content-body html-content"
v-html="currentEmail.htmlBody"
/>
<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: var(--van-gray-6)">
Debug: {{ Object.keys(currentEmail).join(', ') }}
</div>
</div>
</div>
</div>
</PopupContainerV2>
<!-- 账单列表弹出层 -->
<PopupContainerV2
v-model:show="transactionListVisible"
title="关联账单列表"
:height="'75%'"
>
<BillListComponent
data-source="custom"
:transactions="transactionList"
:loading="false"
:finished="true"
:show-delete="true"
:enable-filter="false"
@click="handleTransactionClick"
@delete="handleTransactionDelete"
/>
</PopupContainerV2>
<!-- 账单详情编辑弹出层 -->
<TransactionDetail
:show="transactionDetailVisible"
:transaction="currentTransaction"
@update:show="transactionDetailVisible = $event"
@save="handleTransactionSave"
/>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { showToast, showConfirmDialog } from 'vant'
import {
getEmailList,
getEmailDetail,
deleteEmail,
refreshTransactionRecords,
syncEmails,
getEmailTransactions
} from '@/api/emailRecord'
import { getTransactionDetail } from '@/api/transactionRecord'
import BillListComponent from '@/components/Bill/BillListComponent.vue'
import TransactionDetail from '@/components/TransactionDetail.vue'
import PopupContainerV2 from '@/components/PopupContainerV2.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)
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
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 onGlobalTransactionDeleted = (e) => {
console.log('收到全局交易删除事件:', e)
// 如果交易列表弹窗打开,尝试重新加载邮箱的交易列表
if (transactionListVisible.value && currentEmail.value) {
const emailId = currentEmail.value.id || currentEmail.value.Id
getEmailTransactions(emailId)
.then((response) => {
if (response.success) {
transactionList.value = response.data || []
}
})
.catch(() => {})
}
}
window.addEventListener &&
window.addEventListener('transaction-deleted', onGlobalTransactionDeleted)
onBeforeUnmount(() => {
window.removeEventListener &&
window.removeEventListener('transaction-deleted', onGlobalTransactionDeleted)
})
// 监听新增/修改/批量更新事件,刷新弹窗内交易或邮件列表
const onGlobalTransactionsChanged = (e) => {
console.log('收到全局交易变更事件:', e)
if (transactionListVisible.value && currentEmail.value) {
const emailId = currentEmail.value.id || currentEmail.value.Id
getEmailTransactions(emailId)
.then((response) => {
if (response.success) {
transactionList.value = response.data || []
}
})
.catch(() => {})
} else {
// 也刷新邮件列表以保持统计一致
loadData(true)
}
}
window.addEventListener &&
window.addEventListener('transactions-changed', onGlobalTransactionsChanged)
onBeforeUnmount(() => {
window.removeEventListener &&
window.removeEventListener('transactions-changed', onGlobalTransactionsChanged)
})
// 处理点击账单
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 handleTransactionDelete = (transactionId) => {
// 从当前的交易列表中移除该交易
transactionList.value = transactionList.value.filter((t) => t.id !== transactionId)
// 刷新邮件列表
loadData(true)
// 刷新当前邮件详情
if (currentEmail.value) {
const emailId = currentEmail.value.id
getEmailDetail(emailId).then((response) => {
if (response.success) {
currentEmail.value = response.data
}
})
}
try {
window.dispatchEvent(new CustomEvent('transaction-deleted', { detail: transactionId }))
} catch (e) {
console.error(e)
}
}
// 账单保存后刷新列表
const handleTransactionSave = async () => {
// 刷新账单列表
if (currentEmail.value) {
const emailId = currentEmail.value.id
const response = await getEmailTransactions(emailId)
if (response.success) {
transactionList.value = response.data || []
}
}
try {
window.dispatchEvent(
new CustomEvent('transactions-changed', {
detail: {
emailId: currentEmail.value?.id
}
})
)
} catch (e) {
console.error(e)
}
}
// 格式化日期
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: var(--van-text-color-2);
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: var(--van-background-2);
border: 1px solid var(--van-border-color);
}
} */
.delete-button {
height: 100%;
}
/* 设置页面容器背景色 */
:deep(.van-nav-bar) {
background: transparent !important;
}
</style>