Files
gym-manage/gym-manage-uniapp/pages/message/detail.vue
T

392 lines
8.6 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>
<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>