Files
EmailBill/Web/src/views/MessageView.vue

355 lines
7.7 KiB
Vue
Raw Normal View History

<!-- eslint-disable vue/no-v-html -->
<template>
2025-12-30 17:02:30 +08:00
<div class="page-container-flex">
2026-01-16 11:15:44 +08:00
<van-pull-refresh
v-model="refreshing"
@refresh="onRefresh"
>
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
2026-01-16 11:15:44 +08:00
<van-cell-group
v-if="list.length"
inset
style="margin-top: 10px"
>
<van-swipe-cell
v-for="item in list"
:key="item.id"
>
<div
class="message-card"
@click="viewDetail(item)"
>
<div class="card-left">
2026-01-16 11:15:44 +08:00
<div
class="message-title"
:class="{ unread: !item.isRead }"
>
{{ item.title }}
</div>
2025-12-29 16:07:43 +08:00
<div class="message-content">
{{ item.content }}
</div>
<div class="message-info">
{{ item.createTime }}
</div>
</div>
<div class="card-right">
2026-01-16 11:15:44 +08:00
<van-tag
v-if="!item.isRead"
type="danger"
>
未读
</van-tag>
<van-icon
name="arrow"
size="16"
class="arrow-icon"
/>
</div>
</div>
<template #right>
2026-01-16 11:15:44 +08:00
<van-button
square
text="删除"
type="danger"
class="delete-button"
@click="handleDelete(item)"
/>
</template>
</van-swipe-cell>
</van-cell-group>
2026-01-16 11:15:44 +08:00
<van-empty
v-else-if="!loading"
description="暂无消息"
/>
</van-list>
</van-pull-refresh>
<!-- 详情弹出层 -->
2025-12-30 17:02:30 +08:00
<PopupContainer
v-model="detailVisible"
:title="currentMessage.title"
:subtitle="currentMessage.createTime"
height="75%"
>
<div
2026-01-16 11:15:44 +08:00
v-if="currentMessage.messageType === 2"
class="detail-content rich-html-content"
v-html="currentMessage.content"
2026-01-16 11:15:44 +08:00
/>
<div
v-else
class="detail-content"
>
2025-12-30 17:02:30 +08:00
{{ currentMessage.content }}
</div>
2026-01-16 11:15:44 +08:00
<template
v-if="currentMessage.url && currentMessage.messageType === 1"
#footer
>
<van-button
type="primary"
block
round
@click="handleUrlJump(currentMessage.url)"
>
查看详情
</van-button>
</template>
2025-12-30 17:02:30 +08:00
</PopupContainer>
2025-12-28 10:23:57 +08:00
</div>
</template>
<script setup>
2026-01-16 11:15:44 +08:00
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { showToast, showDialog } from 'vant'
import { getMessageList, markAsRead, deleteMessage, markAllAsRead } from '@/api/message'
import { useMessageStore } from '@/stores/message'
import PopupContainer from '@/components/PopupContainer.vue'
const messageStore = useMessageStore()
const router = useRouter()
const list = ref([])
const loading = ref(false)
const finished = ref(false)
const refreshing = ref(false)
const pageIndex = ref(1)
const pageSize = ref(20)
const detailVisible = ref(false)
const currentMessage = ref({})
const onLoad = async () => {
if (refreshing.value) {
2026-01-16 11:15:44 +08:00
list.value = []
pageIndex.value = 1
refreshing.value = false
}
try {
const res = await getMessageList({
pageIndex: pageIndex.value,
pageSize: pageSize.value
2026-01-16 11:15:44 +08:00
})
if (res.success) {
// 格式化时间
2026-01-16 11:15:44 +08:00
const data = res.data.map((item) => ({
...item,
createTime: new Date(item.createTime).toLocaleString()
2026-01-16 11:15:44 +08:00
}))
if (pageIndex.value === 1) {
2026-01-16 11:15:44 +08:00
list.value = data
} else {
2026-01-16 11:15:44 +08:00
list.value = [...list.value, ...data]
}
2026-01-16 11:15:44 +08:00
// 判断是否加载完成
if (list.value.length >= res.total || data.length < pageSize.value) {
2026-01-16 11:15:44 +08:00
finished.value = true
} else {
2026-01-16 11:15:44 +08:00
pageIndex.value++
}
} else {
2026-01-16 11:15:44 +08:00
showToast(res.message || '加载失败')
finished.value = true
}
} catch (error) {
2026-01-16 11:15:44 +08:00
console.error(error)
showToast('加载失败')
finished.value = true
} finally {
2026-01-16 11:15:44 +08:00
loading.value = false
}
2026-01-16 11:15:44 +08:00
}
const onRefresh = () => {
2026-01-16 11:15:44 +08:00
finished.value = false
loading.value = true
onLoad()
}
const viewDetail = async (item) => {
if (!item.isRead) {
try {
2026-01-16 11:15:44 +08:00
await markAsRead(item.id)
item.isRead = true
messageStore.updateUnreadCount()
} catch (error) {
2026-01-16 11:15:44 +08:00
console.error('标记已读失败', error)
}
}
2026-01-16 11:15:44 +08:00
currentMessage.value = item
detailVisible.value = true
}
const handleUrlJump = (targetUrl) => {
2026-01-16 11:15:44 +08:00
if (!targetUrl) {
return
}
if (targetUrl.startsWith('http')) {
2026-01-16 11:15:44 +08:00
window.open(targetUrl, '_blank')
} else if (targetUrl.startsWith('/')) {
2026-01-16 11:15:44 +08:00
router.push(targetUrl)
detailVisible.value = false
} else {
2026-01-16 11:15:44 +08:00
showToast('无效的URL')
}
2026-01-16 11:15:44 +08:00
}
const handleDelete = (item) => {
showDialog({
title: '提示',
message: '确定要删除这条消息吗?',
2026-01-16 11:15:44 +08:00
showCancelButton: true
}).then(async (action) => {
if (action === 'confirm') {
try {
2026-01-16 11:15:44 +08:00
const res = await deleteMessage(item.id)
if (res.success) {
2026-01-16 11:15:44 +08:00
showToast('删除成功')
const wasUnread = !item.isRead
list.value = list.value.filter((i) => i.id !== item.id)
if (wasUnread) {
2026-01-16 11:15:44 +08:00
messageStore.updateUnreadCount()
}
} else {
2026-01-16 11:15:44 +08:00
showToast(res.message || '删除失败')
}
} catch (error) {
2026-01-16 11:15:44 +08:00
console.error('删除消息失败', error)
showToast('删除失败')
}
}
2026-01-16 11:15:44 +08:00
})
}
const handleMarkAllRead = () => {
showDialog({
title: '提示',
message: '确定要将所有消息标记为已读吗?',
2026-01-16 11:15:44 +08:00
showCancelButton: true
}).then(async (action) => {
if (action === 'confirm') {
try {
2026-01-16 11:15:44 +08:00
const res = await markAllAsRead()
if (res.success) {
2026-01-16 11:15:44 +08:00
showToast('操作成功')
// 刷新列表
2026-01-16 11:15:44 +08:00
onRefresh()
// 更新未读计数
2026-01-16 11:15:44 +08:00
messageStore.updateUnreadCount()
} else {
2026-01-16 11:15:44 +08:00
showToast(res.message || '操作失败')
}
} catch (error) {
2026-01-16 11:15:44 +08:00
console.error('标记所有已读失败', error)
showToast('操作失败')
}
}
2026-01-16 11:15:44 +08:00
})
}
onMounted(() => {
// onLoad 会由 van-list 自动触发
2026-01-16 11:15:44 +08:00
})
defineExpose({
handleMarkAllRead
2026-01-16 11:15:44 +08:00
})
</script>
<style scoped>
.message-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
cursor: pointer;
}
.card-left {
flex: 1;
min-width: 0;
padding-right: 12px;
}
.card-right {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.arrow-icon {
color: var(--van-gray-5);
}
.message-title {
font-size: 15px;
margin-bottom: 6px;
color: var(--van-text-color);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
2026-01-16 11:15:44 +08:00
.message-content {
2025-12-29 16:07:43 +08:00
font-size: 14px;
color: var(--van-text-color-2);
margin-bottom: 6px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.message-title.unread {
font-weight: bold;
}
.message-info {
font-size: 12px;
color: var(--van-text-color-2);
}
.delete-button {
height: 100%;
}
.detail-time {
color: var(--van-text-color-2);
font-size: 14px;
}
.detail-content {
padding: 16px;
font-size: 14px;
line-height: 1.6;
color: var(--van-text-color);
}
.detail-content:not(.rich-html-content) {
white-space: pre-wrap;
}
2025-12-30 17:02:30 +08:00
:deep(.van-pull-refresh) {
flex: 1;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
/* 设置页面容器背景色 */
:deep(.van-nav-bar) {
background: transparent !important;
}
2026-01-16 11:15:44 +08:00
</style>