19 lines
3.7 KiB
Vue
19 lines
3.7 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted, ref } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { ArrowLeft, Bell, RefreshCcw } from '@lucide/vue'
|
|
import { ApiError } from '@/api/http'
|
|
import { useNotificationsStore } from '../public'
|
|
import { useAuthStore } from '@/modules/auth/public'
|
|
import { getMyNotification, markMyNotificationRead } from '../api/notifications'
|
|
import type { NotificationDetail } from '../contracts'
|
|
import { notificationCategoryLabel, notificationSentAt } from '../presentation'
|
|
const route = useRoute(); const router = useRouter(); const auth = useAuthStore(); const notifications = useNotificationsStore(); const detail = ref<NotificationDetail | null>(null); const loading = ref(false); const errorMessage = ref(''); const readErrorMessage = ref('')
|
|
const canMarkRead = computed(() => auth.hasPermission('merchant:notification:read'))
|
|
async function markReadIfAllowed(){if(!detail.value||detail.value.status!=='UNREAD'||!canMarkRead.value)return;try{await markMyNotificationRead(detail.value.id);detail.value={...detail.value,status:'READ'};await notifications.refreshUnreadCount(true)}catch(error){readErrorMessage.value=error instanceof ApiError?error.message:'消息已加载,但自动标记已读失败'}}
|
|
async function load(){loading.value=true;errorMessage.value='';readErrorMessage.value='';try{detail.value=await getMyNotification(String(route.params.notificationId));await markReadIfAllowed()}catch(error){detail.value=null;errorMessage.value=error instanceof ApiError?error.message:'消息加载失败,请稍后重试'}finally{loading.value=false}}
|
|
onMounted(()=>{void load()})
|
|
</script>
|
|
<template><article v-loading="loading" class="notification-detail"><header class="notification-detail-header"><button type="button" class="notification-detail-back" @click="router.push({ name: 'notifications-list' })"><ArrowLeft :size="16" />返回列表</button><div><strong>{{ detail?.title || '消息详情' }}</strong><span>{{ notificationSentAt(detail?.sentAt) }} · {{ notificationCategoryLabel(detail?.type) }}</span></div><Bell :size="22" /></header><div v-if="errorMessage" class="state-panel state-panel--error" role="alert"><p>{{ errorMessage }}</p><el-button @click="load()"><RefreshCcw :size="15" />重试</el-button></div><section v-else class="notification-content"><el-alert v-if="readErrorMessage" type="warning" :closable="false" :title="readErrorMessage" show-icon /><p>{{ detail?.content || '暂无消息内容' }}</p></section></article></template>
|
|
<style scoped>.notification-detail{overflow:hidden;min-height:240px;border:1px solid var(--client-border);border-radius:var(--client-radius);background:#fff}.notification-detail-header{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:14px;min-height:64px;padding:10px 16px;border-bottom:1px solid var(--client-border)}.notification-detail-back{display:inline-flex;align-items:center;gap:5px;padding:5px 0;border:0;background:transparent;color:var(--el-color-primary);cursor:pointer}.notification-detail-header>div{display:flex;min-width:0;flex-direction:column;gap:3px}.notification-detail-header strong{overflow:hidden;color:#1f2d45;font-size:18px;text-overflow:ellipsis;white-space:nowrap}.notification-detail-header span{color:var(--client-muted);font-size:12px}.notification-content{padding:16px}.notification-content p{margin:0;white-space:pre-wrap;color:var(--client-text);font-size:13px;line-height:1.75}.state-panel{display:flex;min-height:180px;align-items:center;justify-content:center;flex-direction:column;gap:12px}.state-panel--error{color:#b42318;background:#fff8f7}@media(max-width:720px){.notification-detail-header{grid-template-columns:1fr auto}.notification-detail-back{grid-column:1/-1}.notification-content{padding:14px}}</style>
|