From ff13d39bd5b72dc295b3ac87b8b68ae83eff30c2 Mon Sep 17 00:00:00 2001 From: smail Date: Sun, 20 Sep 2026 06:56:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=82=AE=E4=BB=B6=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E5=A4=B1=E8=B4=A5=E5=B1=95=E7=A4=BA=E5=B9=B6=E9=98=B2?= =?UTF-8?q?=E6=AD=A2=E6=97=A7=E8=AF=B7=E6=B1=82=E8=A6=86=E7=9B=96=E6=96=B0?= =?UTF-8?q?=E9=82=AE=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/EmailDetail/EmailDetail.css | 34 ++++++ frontend/src/views/EmailDetail/EmailDetail.js | 89 +++++++++++---- .../src/views/EmailDetail/EmailDetail.vue | 23 +++- test/frontend/email-detail.test.mjs | 106 ++++++++++++++++++ 4 files changed, 226 insertions(+), 26 deletions(-) create mode 100644 test/frontend/email-detail.test.mjs diff --git a/frontend/src/views/EmailDetail/EmailDetail.css b/frontend/src/views/EmailDetail/EmailDetail.css index fcdeb64..409303e 100644 --- a/frontend/src/views/EmailDetail/EmailDetail.css +++ b/frontend/src/views/EmailDetail/EmailDetail.css @@ -47,6 +47,40 @@ padding: 28px 30px; } +.mail-detail-state { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + padding: 32px 20px; + text-align: center; + color: var(--pm-text-secondary); +} + +.mail-detail-state h1 { + margin: 0; + font-size: 22px; + color: var(--pm-text-primary); +} + +.mail-detail-state p { + margin: 0; +} + +.mail-detail-state-actions { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 12px; + margin-top: 12px; +} + +.mail-detail-state-actions .el-button + .el-button { + margin-left: 0; +} + .mail-subject { font-size: 36px; font-weight: 600; diff --git a/frontend/src/views/EmailDetail/EmailDetail.js b/frontend/src/views/EmailDetail/EmailDetail.js index 123dbc8..f8ebffd 100644 --- a/frontend/src/views/EmailDetail/EmailDetail.js +++ b/frontend/src/views/EmailDetail/EmailDetail.js @@ -1,6 +1,4 @@ -import './EmailDetail.js' - -import {ref} from 'vue' +import {onBeforeUnmount, ref, watch} from 'vue' import {useRoute, useRouter} from 'vue-router' @@ -30,27 +28,69 @@ export function usePage() { const ccs = ref([]) const showCC = ref(false) - - http.post("/api/v1/email/detail", {id: parseInt(route.params.id)}).then(res => { - detailData.value = res.data || {} - detailData.value.attachments = res.data.attachments || []; - - if (res.data.to && res.data.to !== "") { - try { - tos.value = JSON.parse(res.data.to) - } catch (error) { - tos.value = [] - } + + const loading = ref(false) + const loadError = ref('') + let requestSequence = 0 + + const parseAddresses = (value) => { + try { + const addresses = JSON.parse(value || '[]') + return Array.isArray(addresses) ? addresses : [] + } catch { + return [] } - if (res.data.cc && res.data.cc !== "") { - try { - ccs.value = JSON.parse(res.data.cc) - } catch (error) { - ccs.value = [] - } + } + + const loadDetail = async () => { + const sequence = ++requestSequence + detailData.value = {attachments: []} + tos.value = [] + ccs.value = [] + showCC.value = false + loadError.value = '' + loading.value = true + + const rawId = String(route.params.id || '') + const id = Number(rawId) + if (!/^\d+$/.test(rawId) || !Number.isSafeInteger(id) || id <= 0) { + loadError.value = '邮件地址无效,请返回邮件列表' + loading.value = false + return } - showCC.value = ccs.value && ccs.value.length > 0 + + try { + const res = await http.post('/api/v1/email/detail', {id}) + // 切换邮件或离开页面后,旧请求不得覆盖当前状态。 + if (sequence !== requestSequence) return + if (res?.errorNo !== 0) { + loadError.value = res?.errorMsg || '读取邮件失败,请重试' + return + } + const data = res.data + if (!data || typeof data !== 'object' || Array.isArray(data) || Number(data.id) !== id) { + loadError.value = '邮件不可用,请返回邮件列表' + return + } + detailData.value = {...data, attachments: Array.isArray(data.attachments) ? data.attachments : []} + tos.value = parseAddresses(data.to) + ccs.value = parseAddresses(data.cc) + showCC.value = ccs.value.length > 0 + } catch { + if (sequence === requestSequence) { + loadError.value = '网络连接失败,请稍后重试' + } + } finally { + if (sequence === requestSequence) loading.value = false + } + } + + watch(() => route.params.id, loadDetail, {immediate: true}) + onBeforeUnmount(() => { + requestSequence++ }) + + const backToList = () => router.push({name: 'list'}) const getInitial = (name) => { if (!name) return '?'; @@ -67,7 +107,8 @@ export function usePage() { } const handleDelete = () => { - const id = detailData.value.id || parseInt(route.params.id); + if (loading.value || loadError.value) return; + const id = detailData.value.id; if (!id) return; let tag = groupStore.tag; @@ -119,6 +160,10 @@ export function usePage() { tos, ccs, showCC, + loading, + loadError, + loadDetail, + backToList, getInitial, formatDetailDate, handleDelete diff --git a/frontend/src/views/EmailDetail/EmailDetail.vue b/frontend/src/views/EmailDetail/EmailDetail.vue index b87ef9c..c628fc3 100644 --- a/frontend/src/views/EmailDetail/EmailDetail.vue +++ b/frontend/src/views/EmailDetail/EmailDetail.vue @@ -1,17 +1,28 @@