252 lines
4.7 KiB
Vue
252 lines
4.7 KiB
Vue
<template>
|
||
<view class="system-msg-page">
|
||
<!-- 系统消息列表(Requirements 15.1) -->
|
||
<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>
|
||
|
||
<!-- 通知详情弹窗(Requirements 15.2) -->
|
||
<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
|
||
}
|
||
},
|
||
|
||
/** 查看消息详情(Requirements 15.2) */
|
||
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>
|