campus-errand/miniapp/pages/message/system-msg.vue
2026-03-12 18:12:10 +08:00

252 lines
4.6 KiB
Vue

<template>
<view class="system-msg-page">
<!-- 系统消息列表 -->
<view
class="msg-item"
v-for="item in messages"
:key="item.id"
@click="goDetail(item)"
>
<view class="msg-content">
<view class="msg-header">
<text class="msg-title">{{ item.title }}</text>
<view v-if="!item.isRead" class="unread-dot"></view>
</view>
<text class="msg-preview">{{ item.contentPreview }}</text>
<text class="msg-time">{{ formatTime(item.createdAt) }}</text>
</view>
<image
v-if="item.thumbnailUrl"
class="msg-thumb"
:src="item.thumbnailUrl"
mode="aspectFill"
></image>
</view>
<!-- 空状态 -->
<view v-if="messages.length === 0 && !loading" class="empty">
<text class="empty-text">暂无系统消息</text>
</view>
<!-- 通知详情弹窗 -->
<view v-if="showDetail" class="detail-mask" @click="closeDetail">
<view class="detail-popup" @click.stop>
<view class="detail-header">
<text class="detail-title">{{ detailData.title }}</text>
<text class="detail-close" @click="closeDetail">✕</text>
</view>
<scroll-view scroll-y class="detail-body">
<image
v-if="detailData.thumbnailUrl"
class="detail-image"
:src="detailData.thumbnailUrl"
mode="widthFix"
></image>
<rich-text :nodes="detailData.content || ''"></rich-text>
</scroll-view>
<text class="detail-time">{{ formatTime(detailData.createdAt) }}</text>
</view>
</view>
</view>
</template>
<script>
import { getSystemMessages, getSystemMessageDetail } from '../../utils/api'
export default {
data() {
return {
messages: [],
loading: false,
showDetail: false,
detailData: {}
}
},
onLoad() {
this.loadMessages()
},
methods: {
/** 加载系统消息列表 */
async loadMessages() {
this.loading = true
try {
const res = await getSystemMessages()
this.messages = res || []
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
this.loading = false
}
},
/** 查看消息详情 */
async goDetail(item) {
try {
const res = await getSystemMessageDetail(item.id)
this.detailData = res
this.showDetail = true
// 标记为已读
item.isRead = true
} catch (e) {
uni.showToast({ title: '加载详情失败', icon: 'none' })
}
},
/** 关闭详情弹窗 */
closeDetail() {
this.showDetail = false
this.detailData = {}
},
/** 格式化时间 */
formatTime(dateStr) {
if (!dateStr) return ''
const d = new Date(dateStr)
const pad = (n) => String(n).padStart(2, '0')
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
}
}
}
</script>
<style scoped>
.system-msg-page {
padding: 20rpx 24rpx;
}
.msg-item {
display: flex;
background-color: #ffffff;
border-radius: 16rpx;
padding: 24rpx 30rpx;
margin-bottom: 16rpx;
}
.msg-content {
flex: 1;
overflow: hidden;
}
.msg-header {
display: flex;
align-items: center;
}
.msg-title {
font-size: 30rpx;
color: #333333;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.unread-dot {
width: 14rpx;
height: 14rpx;
background-color: #ff4d4f;
border-radius: 50%;
margin-left: 12rpx;
flex-shrink: 0;
}
.msg-preview {
font-size: 26rpx;
color: #999999;
margin-top: 10rpx;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
display: block;
}
.msg-time {
font-size: 22rpx;
color: #cccccc;
margin-top: 10rpx;
display: block;
}
.msg-thumb {
width: 120rpx;
height: 120rpx;
border-radius: 8rpx;
margin-left: 20rpx;
flex-shrink: 0;
}
.empty {
text-align: center;
padding: 120rpx 0;
}
.empty-text {
font-size: 28rpx;
color: #cccccc;
}
/* 详情弹窗 */
.detail-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.detail-popup {
width: 85%;
max-height: 80vh;
background-color: #ffffff;
border-radius: 20rpx;
padding: 40rpx;
display: flex;
flex-direction: column;
}
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.detail-title {
font-size: 34rpx;
color: #333333;
font-weight: bold;
flex: 1;
}
.detail-close {
font-size: 36rpx;
color: #999999;
padding: 10rpx;
}
.detail-body {
flex: 1;
max-height: 60vh;
}
.detail-image {
width: 100%;
margin-bottom: 20rpx;
border-radius: 8rpx;
}
.detail-time {
font-size: 22rpx;
color: #cccccc;
margin-top: 20rpx;
text-align: right;
display: block;
}
</style>