anon-ui/src/pages/mailbox/detail.vue
2026-05-03 19:36:05 +08:00

476 lines
10 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>
<view class="detail-page">
<view class="dream-bg dream-bg-one"></view>
<view class="dream-bg dream-bg-two"></view>
<view class="detail-header">
<view class="back-button" hover-class="button-hover" @tap="goBack"></view>
<view>
<text class="detail-title">短信详情</text>
<text class="detail-subtitle">查看这条匿名短信的发送进度</text>
</view>
</view>
<view class="status-card" :class="statusMeta.className">
<view class="status-top">
<view class="status-pill" :class="statusMeta.className">
<text class="status-dot"></text>
<text>{{ statusMeta.text }}</text>
</view>
<text class="order-text">#{{ currentMail.orderNumber }}</text>
</view>
<text class="status-title">{{ statusTitle }}</text>
<text class="status-desc">{{ statusDesc }}</text>
</view>
<view class="phone-preview">
<view class="preview-header">
<view class="avatar"></view>
<view>
<text class="preview-name">匿名短信</text>
<text class="preview-recipient">发给 {{ maskedRecipient }}</text>
</view>
</view>
<view class="imessage-area">
<view class="sms-bubble">
<text class="sms-content">{{ currentMail.content }}</text>
</view>
</view>
</view>
<view class="info-panel">
<view class="info-row">
<text class="info-label">联系人</text>
<text class="info-value">{{ maskedRecipient }}</text>
</view>
<view class="info-row">
<text class="info-label">提交时间</text>
<text class="info-value">{{ submitTime }}</text>
</view>
<view class="info-row">
<text class="info-label">发送时间</text>
<text class="info-value">{{ sendTime }}</text>
</view>
<view class="info-row">
<text class="info-label">费用</text>
<text class="info-value">¥{{ currentMail.price.toFixed(2) }}</text>
</view>
</view>
<view class="timeline-panel">
<view class="timeline-item done">
<view class="timeline-dot"></view>
<view class="timeline-content">
<text class="timeline-title">已提交</text>
<text class="timeline-time">{{ submitTime }}</text>
</view>
</view>
<view class="timeline-item" :class="{ done: currentMail.status === sentStatus, failed: currentMail.status === failedStatus }">
<view class="timeline-dot"></view>
<view class="timeline-content">
<text class="timeline-title">{{ currentMail.status === failedStatus ? "发送失败" : "等待发送" }}</text>
<text class="timeline-time">{{ sendTime }}</text>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import {
MAIL_STATUS_META,
MailStatus,
deserializeMail,
formatMailTime,
maskRecipient,
type Mail,
} from "@/config/mail";
const fallbackMail: Mail = {
id: 0,
orderNumber: "2503000000000000",
recipient: "13800138000",
content: "这是一条匿名短信记录,稍后会展示真实发送内容。",
sendTime: "2025-03-18 20:00:00",
submitTime: "2025-03-18 19:45:00",
status: MailStatus.PENDING,
price: 0,
isNew: false,
};
const mail = ref<Mail>(fallbackMail);
const sentStatus = MailStatus.SENT;
const failedStatus = MailStatus.FAILED;
onLoad((query) => {
const detail = deserializeMail(query?.mail as string | undefined);
if (detail) {
mail.value = detail;
}
});
const currentMail = computed(() => mail.value);
const statusMeta = computed(() => MAIL_STATUS_META[currentMail.value.status]);
const maskedRecipient = computed(() => maskRecipient(currentMail.value.recipient));
const submitTime = computed(() => formatMailTime(currentMail.value.submitTime));
const sendTime = computed(() => formatMailTime(currentMail.value.sendTime));
const statusTitle = computed(() => {
if (currentMail.value.status === MailStatus.SENT) return "短信已经送达";
if (currentMail.value.status === MailStatus.FAILED) return "发送没有成功";
return "短信正在等待发送";
});
const statusDesc = computed(() => {
if (currentMail.value.status === MailStatus.SENT) return "对方收到后,如果有回复会在信箱中展示。";
if (currentMail.value.status === MailStatus.FAILED) return "可以稍后重新提交,或检查联系人号码是否正确。";
return "系统会按预约时间发送,请留意后续状态变化。";
});
const goBack = () => {
uni.navigateBack({
delta: 1,
fail: () => {
uni.switchTab({
url: "/pages/mailbox/index",
});
},
});
};
</script>
<style scoped lang="scss">
.detail-page {
min-height: 100vh;
box-sizing: border-box;
padding: 28rpx 26rpx 48rpx;
background:
linear-gradient(180deg, rgba(255, 244, 250, 0.98) 0%, rgba(238, 248, 255, 0.96) 48%, #f7f8fb 100%),
linear-gradient(135deg, rgba(255, 204, 225, 0.22), rgba(199, 233, 255, 0.24));
position: relative;
overflow: hidden;
}
.dream-bg {
position: fixed;
border-radius: 50%;
pointer-events: none;
z-index: 0;
}
.dream-bg-one {
width: 360rpx;
height: 360rpx;
right: -160rpx;
top: 110rpx;
background: rgba(255, 188, 214, 0.24);
}
.dream-bg-two {
width: 320rpx;
height: 320rpx;
left: -140rpx;
top: 620rpx;
background: rgba(167, 218, 255, 0.22);
}
.detail-header,
.status-card,
.phone-preview,
.info-panel,
.timeline-panel {
position: relative;
z-index: 1;
}
.detail-header {
display: flex;
align-items: center;
gap: 18rpx;
margin-bottom: 24rpx;
}
.back-button {
width: 68rpx;
height: 68rpx;
line-height: 62rpx;
text-align: center;
border-radius: 50%;
color: #293247;
font-size: 54rpx;
background: rgba(255, 255, 255, 0.78);
border: 1rpx solid rgba(255, 255, 255, 0.9);
box-shadow: 0 10rpx 24rpx rgba(83, 96, 130, 0.07);
}
.button-hover {
opacity: 0.72;
}
.detail-title,
.detail-subtitle,
.status-title,
.status-desc,
.preview-name,
.preview-recipient,
.sms-content,
.info-label,
.info-value,
.timeline-title,
.timeline-time {
display: block;
}
.detail-title {
color: #20273a;
font-size: 42rpx;
font-weight: 700;
line-height: 1.2;
}
.detail-subtitle {
margin-top: 8rpx;
color: #687286;
font-size: 24rpx;
line-height: 1.4;
}
.status-card {
overflow: hidden;
padding: 26rpx;
border-radius: 28rpx;
background:
linear-gradient(145deg, rgba(255, 255, 255, 0.9) 0%, rgba(255, 248, 252, 0.82) 55%, rgba(240, 249, 255, 0.86) 100%),
rgba(255, 255, 255, 0.78);
border: 1rpx solid rgba(255, 255, 255, 0.88);
box-shadow: 0 18rpx 42rpx rgba(93, 107, 139, 0.09);
}
.status-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
margin-bottom: 26rpx;
}
.status-pill {
display: flex;
align-items: center;
height: 48rpx;
padding: 0 18rpx;
border-radius: 999rpx;
color: #fff;
font-size: 22rpx;
font-weight: 700;
}
.status-pill.status-pending {
background: linear-gradient(135deg, #ffb75e 0%, #f59e0b 100%);
}
.status-pill.status-sent {
background: linear-gradient(135deg, #7ee0a3 0%, #34b878 100%);
}
.status-pill.status-failed {
background: linear-gradient(135deg, #ff8daf 0%, #e85b7f 100%);
}
.status-dot {
width: 10rpx;
height: 10rpx;
margin-right: 10rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.9);
}
.order-text {
flex: 1;
color: #9aa3b2;
font-size: 22rpx;
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.status-title {
color: #263044;
font-size: 36rpx;
font-weight: 700;
line-height: 1.35;
}
.status-desc {
margin-top: 12rpx;
color: #687286;
font-size: 25rpx;
line-height: 1.55;
}
.phone-preview,
.info-panel,
.timeline-panel {
margin-top: 22rpx;
border-radius: 28rpx;
background: rgba(255, 255, 255, 0.78);
border: 1rpx solid rgba(255, 255, 255, 0.9);
box-shadow: 0 18rpx 42rpx rgba(93, 107, 139, 0.08);
}
.phone-preview {
overflow: hidden;
}
.preview-header {
display: flex;
align-items: center;
padding: 26rpx 28rpx 18rpx;
border-bottom: 1rpx solid rgba(230, 236, 246, 0.78);
}
.avatar {
width: 74rpx;
height: 74rpx;
line-height: 74rpx;
margin-right: 16rpx;
text-align: center;
border-radius: 50%;
color: #fff;
font-size: 28rpx;
font-weight: 700;
background: linear-gradient(135deg, #ff78a2 0%, #83c6ff 100%);
}
.preview-name {
color: #263044;
font-size: 30rpx;
font-weight: 700;
}
.preview-recipient {
margin-top: 6rpx;
color: #8c96a8;
font-size: 23rpx;
}
.imessage-area {
padding: 28rpx;
background: linear-gradient(180deg, rgba(247, 250, 255, 0.76), rgba(255, 248, 252, 0.72));
}
.sms-bubble {
max-width: 590rpx;
padding: 22rpx 24rpx;
border-radius: 28rpx 28rpx 28rpx 8rpx;
background: #fff;
border: 1rpx solid rgba(232, 238, 248, 0.88);
box-shadow: 0 10rpx 24rpx rgba(93, 107, 139, 0.06);
}
.sms-content {
color: #333b4f;
font-size: 29rpx;
line-height: 1.7;
}
.info-panel {
padding: 8rpx 26rpx;
}
.info-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24rpx;
min-height: 82rpx;
border-bottom: 1rpx solid rgba(230, 236, 246, 0.72);
}
.info-row:last-child {
border-bottom: 0;
}
.info-label {
color: #8c96a8;
font-size: 24rpx;
flex-shrink: 0;
}
.info-value {
color: #263044;
font-size: 25rpx;
font-weight: 600;
text-align: right;
}
.timeline-panel {
padding: 26rpx 28rpx;
}
.timeline-item {
position: relative;
display: flex;
padding-bottom: 30rpx;
}
.timeline-item:last-child {
padding-bottom: 0;
}
.timeline-item::after {
content: "";
position: absolute;
left: 11rpx;
top: 28rpx;
bottom: 0;
width: 2rpx;
background: rgba(200, 211, 226, 0.7);
}
.timeline-item:last-child::after {
display: none;
}
.timeline-dot {
width: 24rpx;
height: 24rpx;
margin-top: 4rpx;
margin-right: 20rpx;
border-radius: 50%;
background: #d5dde9;
flex-shrink: 0;
}
.timeline-item.done .timeline-dot {
background: linear-gradient(135deg, #7ee0a3, #34b878);
}
.timeline-item.failed .timeline-dot {
background: linear-gradient(135deg, #ff8daf, #e85b7f);
}
.timeline-content {
flex: 1;
}
.timeline-title {
color: #263044;
font-size: 27rpx;
font-weight: 700;
line-height: 1.35;
}
.timeline-time {
margin-top: 8rpx;
color: #8c96a8;
font-size: 23rpx;
}
</style>