Files
quyun-v2/frontend/portal/src/views/user/NotificationsView.vue

175 lines
7.0 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>
<div class="bg-white rounded-xl shadow-sm border border-slate-100 min-h-[600px]">
<!-- Header & Tabs -->
<div class="px-6 pt-6 border-b border-slate-100 flex items-center justify-between">
<div class="flex items-center gap-8">
<button
v-for="tab in tabs"
:key="tab.value"
@click="currentTab = tab.value"
class="pb-4 text-sm font-medium transition-colors border-b-2 cursor-pointer focus:outline-none relative"
:class="currentTab === tab.value ? 'text-primary-600 border-primary-600' : 'text-slate-500 border-transparent hover:text-slate-700'"
>
{{ tab.label }}
<span v-if="tab.count > 0" class="absolute -top-1 -right-3 min-w-[1.25rem] h-5 px-1 bg-red-500 text-white text-xs rounded-full flex items-center justify-center scale-75">{{ tab.count }}</span>
</button>
</div>
<button class="mb-4 text-sm text-slate-500 hover:text-primary-600 cursor-pointer flex items-center gap-1">
<i class="pi pi-check-circle"></i> 全部已读
</button>
</div>
<!-- Notification List -->
<div class="p-0">
<div v-if="filteredNotifications.length > 0">
<div
v-for="item in filteredNotifications"
:key="item.id"
@click="handleNotificationClick(item)"
class="flex items-start gap-4 p-5 border-b border-slate-50 hover:bg-slate-50 transition-colors cursor-pointer group"
:class="{ 'bg-blue-50/30': !item.read }"
>
<!-- Icon -->
<div class="w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0" :class="getIconStyle(item.type).bg">
<i class="pi" :class="[getIconStyle(item.type).icon, getIconStyle(item.type).color]"></i>
</div>
<!-- Content -->
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between mb-1">
<h3 class="font-bold text-slate-900 text-sm group-hover:text-primary-600 transition-colors flex items-center gap-2">
<span v-if="!item.read" class="w-2 h-2 bg-blue-600 rounded-full inline-block"></span>
{{ item.title }}
</h3>
<span class="text-xs text-slate-400 whitespace-nowrap">{{ item.time }}</span>
</div>
<p class="text-sm text-slate-600 line-clamp-2">{{ item.content }}</p>
</div>
</div>
</div>
<!-- Empty State -->
<div v-else class="text-center py-20">
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-slate-50 mb-4">
<i class="pi pi-bell-slash text-2xl text-slate-300"></i>
</div>
<p class="text-slate-500 text-lg">暂无消息通知</p>
</div>
</div>
<!-- System Message Modal -->
<Dialog v-model:visible="dialogVisible" modal :header="selectedNotification?.title" :style="{ width: '50rem' }" :breakpoints="{ '960px': '75vw', '641px': '90vw' }">
<div class="p-4">
<div class="text-slate-500 text-sm mb-4">{{ selectedNotification?.time }}</div>
<div class="text-slate-700 leading-relaxed whitespace-pre-wrap">{{ selectedNotification?.content }}</div>
<!-- Mock rich content / image -->
<div v-if="selectedNotification?.id === 1" class="mt-4 p-4 bg-slate-50 rounded text-sm text-slate-500">
(此处为富文本内容展示区可能包含图片链接等)
</div>
</div>
<template #footer>
<Button label="关闭" icon="pi pi-check" @click="dialogVisible = false" autofocus />
</template>
</Dialog>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useRouter } from 'vue-router';
import Dialog from 'primevue/dialog';
import Button from 'primevue/button';
const router = useRouter();
const currentTab = ref('all');
const dialogVisible = ref(false);
const selectedNotification = ref(null);
const tabs = [
{ label: '全部', value: 'all', count: 3 },
{ label: '系统通知', value: 'system', count: 1 },
{ label: '订单通知', value: 'order', count: 1 },
{ label: '审核通知', value: 'audit', count: 0 },
{ label: '互动消息', value: 'interaction', count: 1 }
];
const notifications = ref([
{
id: 1,
type: 'system',
title: '平台服务协议更新通知',
content: '为了更好地保障您的权益,我们更新了《用户服务协议》和《隐私政策》,主要变更涉及账户安全与数据保护。\n\n具体变更内容如下\n1. 明确了数据采集范围...\n2. 优化了账号注销流程...',
time: '10分钟前',
read: false,
link: null
},
{
id: 2,
type: 'order',
title: '订单支付成功',
content: '您购买的《霸王别姬》全本实录珍藏版已支付成功订单号82934712请前往已购内容查看。',
time: '2小时前',
read: false,
link: '/me/orders/82934712'
},
{
id: 3,
type: 'interaction',
title: '收到新的评论回复',
content: '梅派传人小林 回复了您的评论:“感谢您的支持,这版录音确实非常珍贵...”。',
time: '昨天 14:30',
read: false,
link: '/contents/1'
},
{
id: 4,
type: 'audit',
title: '内容审核通过',
content: '恭喜!您发布的文章《京剧脸谱赏析》已通过审核并发布上线。',
time: '3天前',
read: true,
link: '/creator/contents'
},
{
id: 5,
type: 'system',
title: '春节期间服务调整公告',
content: '春节期间2月9日-2月17日提现申请处理时效将有所延迟敬请谅解。',
time: '5天前',
read: true,
link: null
}
]);
const filteredNotifications = computed(() => {
if (currentTab.value === 'all') return notifications.value;
return notifications.value.filter(n => n.type === currentTab.value);
});
const getIconStyle = (type) => {
switch(type) {
case 'system': return { bg: 'bg-blue-50', color: 'text-blue-600', icon: 'pi-megaphone' };
case 'order': return { bg: 'bg-green-50', color: 'text-green-600', icon: 'pi-shopping-bag' };
case 'audit': return { bg: 'bg-orange-50', color: 'text-orange-600', icon: 'pi-file-edit' };
case 'interaction': return { bg: 'bg-purple-50', color: 'text-purple-600', icon: 'pi-comments' };
default: return { bg: 'bg-slate-100', color: 'text-slate-500', icon: 'pi-bell' };
}
};
const handleNotificationClick = (item) => {
// 1. Mark as read
item.read = true;
// 2. Handle System type separately
if (item.type === 'system') {
selectedNotification.value = item;
dialogVisible.value = true;
return;
}
// 3. Navigate if link exists
if (item.link) {
router.push(item.link);
}
};
</script>