topfans/frontend/pages/mailbox/components/Postcard.vue
2026-07-14 21:59:15 +08:00

116 lines
4.0 KiB
Vue

<template>
<view class="postcard">
<view class="postcard-header">
<text class="postcard-title">{{ notification.title }}</text>
<view class="postcard-meta">
<text v-if="notification.type" class="meta-type">[{{ typeLabel }}]</text>
<text class="meta-time">{{ formatTime(notification.created_at) }}</text>
</view>
</view>
<view class="postcard-content">
<text>{{ notification.content }}</text>
</view>
<view v-if="businessMeta.length" class="postcard-extra">
<view v-for="m in businessMeta" :key="m.k" class="meta-row">
<text class="meta-key">{{ m.k }}:</text>
<text class="meta-val">{{ m.v }}</text>
</view>
</view>
<view v-if="!notification.is_read" class="postcard-unread-tip">
<text>● 未读</text>
</view>
<view class="postcard-toolbar">
<view v-if="!notification.is_read" class="toolbar-btn toolbar-btn-read" @click="$emit('mark-read', notification)">
<text>✓ 标已读</text>
</view>
<view class="toolbar-btn toolbar-btn-delete" @click="$emit('delete', notification)">
<text>🗑 删除</text>
</view>
</view>
</view>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({ notification: { type: Object, required: true } })
defineEmits(['mark-read', 'delete'])
const typeMap = {
feedback_replied: '反馈回复',
report_resolved: '举报结果',
target_reported: '被举报',
activity: '活动通知',
}
const typeLabel = computed(() => typeMap[props.notification.type] || props.notification.type || '通知')
const businessMeta = computed(() => {
// data 是 JSON 字符串,反序列化展示业务字段
let data = {}
try { data = JSON.parse(props.notification.data || '{}') } catch (e) { data = {} }
const list = []
if (data.feedback_id) list.push({ k: '反馈 ID', v: data.feedback_id })
if (data.report_id) list.push({ k: '举报 ID', v: data.report_id })
if (data.resolved_action) list.push({ k: '处理动作', v: actionLabel(data.resolved_action) })
if (data.category_code) list.push({ k: '分类', v: data.category_code })
if (data.original_title) list.push({ k: '原标题', v: data.original_title })
if (data.activity_id) list.push({ k: '活动 ID', v: data.activity_id })
return list
})
function actionLabel(a) {
return { takedown: '已下架', ban: '已封禁', warn: '已警告', dismiss: '已驳回', restore: '已解除' }[a] || a
}
function formatTime(ms) {
if (!ms) return ''
const d = new Date(ms)
const pad = n => (n < 10 ? '0' + n : '' + n)
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
}
</script>
<style lang="scss" scoped>
.postcard {
background: #fff; border-radius: 16rpx; padding: 32rpx;
.postcard-header { border-bottom: 1rpx solid #f5f5f5; padding-bottom: 16rpx; margin-bottom: 16rpx; }
.postcard-title { font-size: 32rpx; font-weight: 600; display: block; margin-bottom: 12rpx; }
.postcard-meta {
display: flex; gap: 16rpx; font-size: 22rpx; color: #999;
.meta-type { color: #5C40FF; }
}
.postcard-content {
font-size: 28rpx; line-height: 1.7; color: #333; margin-bottom: 24rpx;
white-space: pre-wrap;
}
.postcard-extra {
.meta-row {
display: flex; font-size: 24rpx; color: #666;
padding: 8rpx 0;
.meta-key { color: #999; margin-right: 16rpx; min-width: 140rpx; }
}
}
.postcard-unread-tip {
margin-top: 24rpx; padding-top: 16rpx; border-top: 1rpx solid #f5f5f5;
color: #f5222d; font-size: 24rpx;
}
.postcard-toolbar {
margin-top: 24rpx;
padding-top: 16rpx;
border-top: 1rpx solid #f5f5f5;
display: flex;
gap: 16rpx;
background: #fafafa;
border-radius: 12rpx;
padding: 16rpx;
.toolbar-btn {
flex: 1;
text-align: center;
padding: 12rpx 0;
border-radius: 8rpx;
font-size: 26rpx;
&.toolbar-btn-read { background: #e8f4ff; color: #5C40FF; }
&.toolbar-btn-delete { background: #fff1f0; color: #f5222d; }
}
}
}
</style>