Files
emall-web/src/views/Sent.vue
2025-10-18 16:18:47 +08:00

581 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.

<template>
<div class="sent-container">
<!-- 顶部导航 -->
<div class="header glass-card">
<van-icon name="arrow-left" size="24" @click="goBack" />
<h2>发件箱</h2>
<van-icon name="sort" size="24" @click="showSort = true" />
</div>
<!-- 邮件列表 -->
<div class="mail-list">
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="loadMore"
>
<div
v-for="mail in sortedMails"
:key="mail.mailId"
class="mail-item glass-card"
@click="openMail(mail)"
>
<div class="mail-icon">
<div class="time-capsule-small" :class="{'delivered': mail.status === 'DELIVERED'}"></div>
</div>
<div class="mail-content">
<div class="mail-header">
<h3 class="mail-title">{{ mail.title || '无标题' }}</h3>
<span class="mail-date">{{ formatDate(mail.sendTime) }}</span>
</div>
<p class="mail-preview">{{ (mail.content || '').substring(0, 50) }}...</p>
<div class="mail-footer">
<span class="mail-recipient">收件人: {{ mail.recipient?.username || '未知' }}</span>
<van-tag :type="getStatusType(mail.status)" size="small">
{{ getStatusText(mail.status) }}
</van-tag>
</div>
<div class="mail-progress">
<div class="progress-info">
<span>投递进度</span>
<span>{{ getProgressText(mail) }}</span>
</div>
<van-progress
:percentage="getProgressPercentage(mail)"
stroke-width="4"
color="#00D4FF"
track-color="rgba(255, 255, 255, 0.1)"
/>
</div>
</div>
<div class="mail-actions">
<van-icon name="eye" size="18" @click.stop="previewMail(mail)" />
<van-icon name="edit" size="18" @click.stop="editMail(mail)" v-if="mail.status === 'DRAFT'" />
<van-icon name="close" size="18" @click.stop="cancelMail(mail)" v-if="mail.status === 'PENDING'" />
</div>
</div>
</van-list>
<div v-if="mails && mails.length === 0 && !loading" class="empty-state">
<van-empty description="暂无已发送的邮件" />
</div>
</div>
<!-- 底部导航 -->
<van-tabbar v-model="active" class="custom-tabbar">
<van-tabbar-item icon="home-o" to="/home">时光胶囊</van-tabbar-item>
<van-tabbar-item icon="envelop-o" to="/inbox">收件箱</van-tabbar-item>
<van-tabbar-item icon="send-o" to="/sent">发件箱</van-tabbar-item>
<van-tabbar-item icon="user-o" to="/profile">个人中心</van-tabbar-item>
</van-tabbar>
<!-- 排序弹窗 -->
<van-popup v-model:show="showSort" position="bottom" :style="{ height: '30%' }">
<div class="sort-popup">
<h3>排序方式</h3>
<van-radio-group v-model="sortType">
<van-cell-group>
<van-cell title="按发送时间" clickable @click="sortType = 'sendDate'">
<template #right-icon>
<van-radio name="sendDate" />
</template>
</van-cell>
<van-cell title="按投递时间" clickable @click="sortType = 'deliveryDate'">
<template #right-icon>
<van-radio name="deliveryDate" />
</template>
</van-cell>
<van-cell title="按状态" clickable @click="sortType = 'status'">
<template #right-icon>
<van-radio name="status" />
</template>
</van-cell>
</van-cell-group>
</van-radio-group>
<div class="sort-actions">
<van-button block type="primary" @click="applySort">确定</van-button>
</div>
</div>
</van-popup>
<!-- 预览弹窗 -->
<van-popup v-model:show="showPreview" position="bottom" :style="{ height: '70%' }">
<div class="preview-popup">
<div class="preview-header">
<h3>{{ previewMailData.title }}</h3>
<van-icon name="cross" @click="showPreview = false" />
</div>
<div class="preview-content">
<div class="preview-info">
<p><strong>收件人:</strong> {{ previewMailData.recipient?.username || '未知' }}</p>
<p><strong>发送时间:</strong> {{ formatDate(previewMailData.sendTime) }}</p>
<p v-if="previewMailData.deliveryTime"><strong>投递时间:</strong> {{ formatDate(previewMailData.deliveryTime) }}</p>
<p><strong>状态:</strong> {{ getStatusText(previewMailData.status) }}</p>
</div>
<div class="preview-text">
{{ previewMailData.content }}
</div>
</div>
</div>
</van-popup>
</div>
</template>
<script>
import { ref, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import { showFailToast, showSuccessToast, Dialog } from 'vant'
import { mailActions, mailState } from '../store'
export default {
name: 'Sent',
setup() {
const router = useRouter()
const active = ref(2)
const sortType = ref('sendDate')
const showSort = ref(false)
const showPreview = ref(false)
const previewMailData = ref(null)
// 使用直接导入的状态和操作
const mails = computed(() => mailState.sentList)
const loading = computed(() => mailState.loading)
// 邮件数据
const finished = ref(false)
const page = ref(1)
const pageSize = ref(10)
// 排序后的邮件
const sortedMails = computed(() => {
if (!mails.value || !Array.isArray(mails.value)) return []
const sorted = [...mails.value]
switch (sortType.value) {
case 'sendDate':
return sorted.sort((a, b) => new Date(b.sendTime) - new Date(a.sendTime))
case 'deliveryDate':
return sorted.sort((a, b) => {
if (!a.deliveryTime) return 1
if (!b.deliveryTime) return -1
return new Date(a.deliveryTime) - new Date(b.deliveryTime)
})
case 'status':
const statusOrder = { DRAFT: 0, PENDING: 1, DELIVERING: 2, DELIVERED: 3 }
return sorted.sort((a, b) => statusOrder[a.status] - statusOrder[b.status])
default:
return sorted
}
})
// 获取已发送邮件列表
const fetchMails = async (reset = false) => {
if (loading.value || finished.value) return
try {
if (reset) {
page.value = 1
finished.value = false
}
const response = await mailActions.getMails({
type: 'SENT',
page: page.value,
size: pageSize.value
})
// 响应拦截器已经处理了success判断这里直接处理数据
if (response && response.data) {
// 判断是否加载完成
if (!response.data.list || response.data.list.length < pageSize.value) {
finished.value = true
} else {
page.value += 1
}
} else {
showFailToast('获取邮件列表失败')
}
} catch (error) {
console.error('获取邮件列表失败:', error)
showFailToast('获取邮件列表失败')
}
}
// 加载更多
const loadMore = () => {
fetchMails()
}
// 返回上一页
const goBack = () => {
router.back()
}
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr) return '未设置'
const date = new Date(dateStr)
const now = new Date()
const diffTime = date - now
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
if (diffDays < 0) {
// 过去的时间
const pastDays = Math.abs(diffDays)
if (pastDays === 0) return '今天'
if (pastDays === 1) return '昨天'
if (pastDays < 7) return `${pastDays}天前`
if (pastDays < 30) return `${Math.floor(pastDays / 7)}周前`
if (pastDays < 365) return `${Math.floor(pastDays / 30)}个月前`
return `${Math.floor(pastDays / 365)}年前`
} else {
// 未来的时间
if (diffDays === 0) return '今天'
if (diffDays === 1) return '明天'
if (diffDays < 7) return `${diffDays}天后`
if (diffDays < 30) return `${Math.floor(diffDays / 7)}周后`
if (diffDays < 365) return `${Math.floor(diffDays / 30)}个月后`
return `${Math.floor(diffDays / 365)}年后`
}
}
// 获取状态类型
const getStatusType = (status) => {
switch (status) {
case 'DRAFT': return 'warning'
case 'PENDING': return 'primary'
case 'DELIVERING': return 'primary'
case 'DELIVERED': return 'success'
default: return 'default'
}
}
// 获取状态文本
const getStatusText = (status) => {
switch (status) {
case 'DRAFT': return '草稿'
case 'PENDING': return '待投递'
case 'DELIVERING': return '投递中'
case 'DELIVERED': return '已送达'
default: return '未知'
}
}
// 获取进度文本
const getProgressText = (mail) => {
if (mail.status === 'DRAFT') return '未设置投递时间'
if (mail.status === 'DELIVERED') return '已完成'
const now = new Date()
const sendTime = new Date(mail.sendTime)
const deliveryTime = new Date(mail.deliveryTime)
const total = deliveryTime - sendTime
const elapsed = now - sendTime
const percentage = Math.min(100, Math.max(0, (elapsed / total) * 100))
return `${Math.round(percentage)}%`
}
// 获取进度百分比
const getProgressPercentage = (mail) => {
if (mail.status === 'DRAFT') return 0
if (mail.status === 'DELIVERED') return 100
const now = new Date()
const sendTime = new Date(mail.sendTime)
const deliveryTime = new Date(mail.deliveryTime)
const total = deliveryTime - sendTime
const elapsed = now - sendTime
return Math.min(100, Math.max(0, (elapsed / total) * 100))
}
// 打开邮件
const openMail = (mail) => {
router.push(`/capsule/${mail.mailId}`)
}
// 预览邮件
const previewMail = (mail) => {
previewMailData.value = mail
showPreview.value = true
}
// 编辑邮件
const editMail = (mail) => {
router.push(`/compose?edit=${mail.mailId}`)
}
// 取消邮件
const cancelMail = (mail) => {
Dialog.confirm({
title: '确认撤销',
message: '确定要撤销这封邮件的发送吗?撤销后将无法恢复。',
})
.then(async () => {
try {
const response = await mailActions.revokeMail(mail.mailId)
if (response.code === 200) {
showSuccessToast('邮件已撤销')
// 重新加载邮件列表
fetchMails(true)
} else {
showFailToast(response.message || '撤销失败')
}
} catch (error) {
console.error('撤销邮件失败:', error)
showFailToast('撤销失败')
}
})
.catch(() => {
// 取消操作
})
}
// 应用排序
const applySort = () => {
showSort.value = false
showFailToast('排序已应用')
}
onMounted(() => {
fetchMails(true)
})
return {
active,
sortType,
showSort,
showPreview,
previewMailData,
sortedMails,
loading,
finished,
goBack,
formatDate,
getStatusType,
getStatusText,
getProgressText,
getProgressPercentage,
openMail,
previewMail,
editMail,
cancelMail,
applySort,
loadMore
}
}
}
</script>
<style scoped>
.sent-container {
height: 100vh;
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
}
.mail-list {
flex: 1;
overflow-y: auto;
padding: 0 15px 15px;
}
.mail-item {
display: flex;
padding: 15px;
margin-bottom: 15px;
cursor: pointer;
transition: all 0.3s ease;
}
.mail-item:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(0, 212, 255, 0.2);
}
.mail-icon {
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
margin-right: 15px;
}
.time-capsule-small {
width: 30px;
height: 45px;
background: var(--gradient-color);
border-radius: 15px;
position: relative;
box-shadow: 0 5px 15px rgba(0, 212, 255, 0.3);
}
.time-capsule-small::before {
content: '';
position: absolute;
top: 8px;
left: 50%;
transform: translateX(-50%);
width: 20px;
height: 20px;
background: radial-gradient(circle, var(--accent-color), transparent);
border-radius: 50%;
opacity: 0.7;
}
.time-capsule-small.delivered::before {
background: radial-gradient(circle, #4CAF50, transparent);
}
.mail-content {
flex: 1;
}
.mail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.mail-title {
margin: 0;
font-size: 16px;
font-weight: bold;
}
.mail-date {
font-size: 12px;
color: var(--text-secondary);
}
.mail-preview {
margin: 0 0 10px;
font-size: 14px;
color: var(--text-secondary);
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.mail-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.mail-recipient {
font-size: 12px;
color: var(--text-secondary);
}
.mail-progress {
margin-top: 10px;
}
.progress-info {
display: flex;
justify-content: space-between;
font-size: 12px;
margin-bottom: 5px;
}
.progress-info span:first-child {
color: var(--text-secondary);
}
.progress-info span:last-child {
color: var(--accent-color);
}
.mail-actions {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
margin-left: 10px;
}
.mail-actions .van-icon {
color: var(--text-secondary);
cursor: pointer;
transition: color 0.3s ease;
}
.mail-actions .van-icon:hover {
color: var(--accent-color);
}
.empty-state {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
}
.sort-popup {
padding: 20px;
height: 100%;
}
.sort-popup h3 {
margin: 0 0 15px;
font-size: 18px;
}
.sort-actions {
margin-top: 20px;
}
.preview-popup {
padding: 20px;
height: 100%;
overflow-y: auto;
}
.preview-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.preview-header h3 {
margin: 0;
font-size: 18px;
}
.preview-content {
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
padding: 15px;
}
.preview-info {
margin-bottom: 15px;
}
.preview-info p {
margin: 5px 0;
font-size: 14px;
}
.preview-body {
border-top: 1px solid rgba(255, 255, 255, 0.1);
padding-top: 15px;
}
.preview-body p {
margin: 0;
line-height: 1.6;
}
</style>