392 lines
8.6 KiB
Vue
392 lines
8.6 KiB
Vue
<template>
|
||
<view class="message-detail-page">
|
||
<!-- 顶部导航栏 -->
|
||
<view class="nav-header" :style="navStyle">
|
||
<view class="nav-left">
|
||
<view class="back-btn" @tap="goBack">
|
||
<text class="back-icon">‹</text>
|
||
</view>
|
||
<text class="nav-title">消息详情</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 内容区域 -->
|
||
<scroll-view
|
||
scroll-y
|
||
class="detail-content"
|
||
:style="contentStyle"
|
||
>
|
||
<!-- 消息卡片 -->
|
||
<view class="message-card">
|
||
<!-- 消息图标和类型 -->
|
||
<view class="message-header">
|
||
<view :class="['message-icon', message.type]">
|
||
<image :src="getMessageIcon(message.type)" mode="aspectFit" class="icon-img" />
|
||
</view>
|
||
<view class="header-info">
|
||
<view :class="['type-tag', message.type]">
|
||
<text>{{ getTypeLabel(message.type) }}</text>
|
||
</view>
|
||
<view v-if="!message.read" class="read-status">
|
||
<view class="status-dot"></view>
|
||
<text>未读</text>
|
||
</view>
|
||
<view v-else class="read-status read">
|
||
<view class="status-dot"></view>
|
||
<text>已读</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 消息标题 -->
|
||
<view class="message-title-wrap">
|
||
<text class="message-title">{{ message.title }}</text>
|
||
</view>
|
||
|
||
<!-- 消息内容 -->
|
||
<view class="message-body">
|
||
<text class="message-content">{{ message.content }}</text>
|
||
</view>
|
||
|
||
<!-- 消息时间 -->
|
||
<view class="message-footer">
|
||
<text class="message-time">{{ formatTime(message.timestamp) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部占位 -->
|
||
<view class="bottom-placeholder"></view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted } from 'vue'
|
||
import { onLoad } from '@dcloudio/uni-app'
|
||
import { markMessageAsRead } from '@/api/main.js'
|
||
|
||
const message = ref({
|
||
id: '',
|
||
title: '',
|
||
content: '',
|
||
type: 'system',
|
||
read: false,
|
||
timestamp: Date.now()
|
||
})
|
||
|
||
const statusBarHeight = ref(20)
|
||
const navBarHeight = ref(44)
|
||
|
||
const navStyle = computed(() => ({
|
||
paddingTop: `${statusBarHeight.value}px`,
|
||
height: `${statusBarHeight.value + navBarHeight.value}px`
|
||
}))
|
||
|
||
const contentStyle = computed(() => ({
|
||
paddingTop: `${statusBarHeight.value + navBarHeight.value + 58}px`,
|
||
height: `calc(100vh - ${statusBarHeight.value + navBarHeight.value}px)`
|
||
}))
|
||
|
||
onLoad((options) => {
|
||
if (options?.message) {
|
||
try {
|
||
message.value = JSON.parse(decodeURIComponent(options.message))
|
||
|
||
// 如果是未读状态,自动标记为已读
|
||
if (!message.value.read) {
|
||
autoMarkAsRead()
|
||
}
|
||
} catch (e) {
|
||
console.error('解析消息数据失败:', e)
|
||
}
|
||
}
|
||
})
|
||
|
||
onMounted(() => {
|
||
const sysInfo = uni.getSystemInfoSync()
|
||
statusBarHeight.value = sysInfo.statusBarHeight || 20
|
||
navBarHeight.value = 10
|
||
})
|
||
|
||
const getMessageIcon = (type) => {
|
||
const icons = {
|
||
course: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/icons/course.png',
|
||
system: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/icons/plan.png',
|
||
activity: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/icons/data.png',
|
||
points: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/icons/message.png'
|
||
}
|
||
return icons[type] || icons.system
|
||
}
|
||
|
||
const getTypeLabel = (type) => {
|
||
const labels = {
|
||
course: '课程通知',
|
||
system: '系统消息',
|
||
activity: '活动推送',
|
||
points: '积分动态'
|
||
}
|
||
return labels[type] || '通知'
|
||
}
|
||
|
||
const formatTime = (timestamp) => {
|
||
if (!timestamp) return ''
|
||
|
||
const num = typeof timestamp === 'number' ? timestamp : Number(timestamp)
|
||
const time = num.toString().length === 10 ? num * 1000 : num
|
||
|
||
const date = new Date(time)
|
||
const now = new Date()
|
||
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
const hours = String(date.getHours()).padStart(2, '0')
|
||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||
|
||
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
||
const msgDate = `${year}-${month}-${day}`
|
||
|
||
if (msgDate === today) {
|
||
return `今天 ${hours}:${minutes}`
|
||
}
|
||
|
||
return `${year}年${month}月${day}日 ${hours}:${minutes}`
|
||
}
|
||
|
||
const goBack = () => {
|
||
uni.navigateBack()
|
||
}
|
||
|
||
const autoMarkAsRead = async () => {
|
||
if (message.value.read) return
|
||
|
||
try {
|
||
await markMessageAsRead(message.value.id)
|
||
message.value.read = true
|
||
uni.$emit('messageRead', { id: message.value.id })
|
||
} catch (e) {
|
||
console.error('自动标记已读失败:', e)
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.message-detail-page {
|
||
min-height: 100vh;
|
||
background: #F5F7FA;
|
||
}
|
||
|
||
.nav-header {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 100;
|
||
background: rgba(255, 255, 255, 0.95);
|
||
backdrop-filter: blur(12px);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding-left: 24rpx;
|
||
padding-right: 24rpx;
|
||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||
|
||
.nav-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.back-btn {
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(0, 0, 0, 0.04);
|
||
border-radius: 50%;
|
||
transition: all 0.2s ease;
|
||
|
||
&:active {
|
||
transform: scale(0.9);
|
||
background: rgba(0, 0, 0, 0.08);
|
||
}
|
||
|
||
.back-icon {
|
||
font-size: 48rpx;
|
||
color: #333;
|
||
line-height: 1;
|
||
font-weight: 300;
|
||
}
|
||
}
|
||
|
||
.nav-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
}
|
||
|
||
.detail-content {
|
||
padding-top: 8rpx;
|
||
}
|
||
|
||
.message-card {
|
||
margin: 0 24rpx;
|
||
padding: 32rpx;
|
||
background: white;
|
||
border-radius: 20rpx;
|
||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
.message-header {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 20rpx;
|
||
margin-bottom: 28rpx;
|
||
|
||
.message-icon {
|
||
width: 96rpx;
|
||
height: 96rpx;
|
||
border-radius: 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
|
||
|
||
&.course {
|
||
background: linear-gradient(135deg, #4A90D9 0%, #2E6BB5 100%);
|
||
}
|
||
|
||
&.system {
|
||
background: linear-gradient(135deg, #52C41A 0%, #389E0D 100%);
|
||
}
|
||
|
||
&.activity {
|
||
background: linear-gradient(135deg, #FF6B35 0%, #E85D2C 100%);
|
||
}
|
||
|
||
&.points {
|
||
background: linear-gradient(135deg, #667EEA 0%, #5A4BD1 100%);
|
||
}
|
||
|
||
.icon-img {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
}
|
||
}
|
||
|
||
.header-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12rpx;
|
||
|
||
.type-tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 6rpx 16rpx;
|
||
border-radius: 20rpx;
|
||
font-size: 22rpx;
|
||
font-weight: 500;
|
||
|
||
&.course {
|
||
background: rgba(74, 144, 217, 0.1);
|
||
color: #4A90D9;
|
||
}
|
||
|
||
&.system {
|
||
background: rgba(82, 196, 26, 0.1);
|
||
color: #52C41A;
|
||
}
|
||
|
||
&.activity {
|
||
background: rgba(255, 107, 53, 0.1);
|
||
color: #FF6B35;
|
||
}
|
||
|
||
&.points {
|
||
background: rgba(102, 126, 234, 0.1);
|
||
color: #667EEA;
|
||
}
|
||
}
|
||
|
||
.read-status {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
|
||
.status-dot {
|
||
width: 12rpx;
|
||
height: 12rpx;
|
||
border-radius: 50%;
|
||
background: linear-gradient(135deg, #FF4757, #FF2D55);
|
||
animation: pulse 2s infinite;
|
||
}
|
||
|
||
text {
|
||
font-size: 22rpx;
|
||
color: #FF4757;
|
||
font-weight: 500;
|
||
}
|
||
|
||
&.read {
|
||
.status-dot {
|
||
background: #52C41A;
|
||
animation: none;
|
||
}
|
||
|
||
text {
|
||
color: #52C41A;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: 0.5; }
|
||
}
|
||
|
||
.message-title-wrap {
|
||
margin-bottom: 24rpx;
|
||
padding-bottom: 24rpx;
|
||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
|
||
|
||
.message-title {
|
||
font-size: 34rpx;
|
||
font-weight: 600;
|
||
color: #1A1A1A;
|
||
line-height: 1.4;
|
||
}
|
||
}
|
||
|
||
.message-body {
|
||
margin-bottom: 24rpx;
|
||
|
||
.message-content {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
line-height: 1.8;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
}
|
||
}
|
||
|
||
.message-footer {
|
||
padding-top: 20rpx;
|
||
border-top: 1rpx solid rgba(0, 0, 0, 0.06);
|
||
|
||
.message-time {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
|
||
.bottom-placeholder {
|
||
height: 80rpx;
|
||
}
|
||
</style>
|