feat: add read and delete for each notification

This commit is contained in:
shixi 2025-11-11 23:41:52 +08:00
parent a4aa133db5
commit dbc5ea32ae
5 changed files with 79 additions and 3 deletions

View File

@ -23,6 +23,7 @@ import LoginForm from '#/views/_core/authentication/login.vue';
const notifications = ref<NotificationItem[]>([ const notifications = ref<NotificationItem[]>([
{ {
id: 1,
avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB', avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',
date: '3小时前', date: '3小时前',
isRead: true, isRead: true,
@ -30,6 +31,7 @@ const notifications = ref<NotificationItem[]>([
title: '收到了 14 份新周报', title: '收到了 14 份新周报',
}, },
{ {
id: 2,
avatar: 'https://avatar.vercel.sh/1', avatar: 'https://avatar.vercel.sh/1',
date: '刚刚', date: '刚刚',
isRead: false, isRead: false,
@ -37,6 +39,7 @@ const notifications = ref<NotificationItem[]>([
title: '朱偏右 回复了你', title: '朱偏右 回复了你',
}, },
{ {
id: 3,
avatar: 'https://avatar.vercel.sh/1', avatar: 'https://avatar.vercel.sh/1',
date: '2024-01-01', date: '2024-01-01',
isRead: false, isRead: false,
@ -44,6 +47,7 @@ const notifications = ref<NotificationItem[]>([
title: '曲丽丽 评论了你', title: '曲丽丽 评论了你',
}, },
{ {
id: 4,
avatar: 'https://avatar.vercel.sh/satori', avatar: 'https://avatar.vercel.sh/satori',
date: '1天前', date: '1天前',
isRead: false, isRead: false,
@ -102,6 +106,17 @@ function handleNoticeClear() {
notifications.value = []; notifications.value = [];
} }
function markRead(id: number | string) {
const item = notifications.value.find((item) => item.id === id);
if (item) {
item.isRead = true;
}
}
function remove(id: number | string) {
notifications.value = notifications.value.filter((item) => item.id !== id);
}
function handleMakeAll() { function handleMakeAll() {
notifications.value.forEach((item) => (item.isRead = true)); notifications.value.forEach((item) => (item.isRead = true));
} }
@ -144,6 +159,8 @@ watch(
:dot="showDot" :dot="showDot"
:notifications="notifications" :notifications="notifications"
@clear="handleNoticeClear" @clear="handleNoticeClear"
@read="(item) => item.id && markRead(item.id)"
@remove="(item) => item.id && remove(item.id)"
@make-all="handleMakeAll" @make-all="handleMakeAll"
/> />
</template> </template>

View File

@ -23,6 +23,7 @@ import LoginForm from '#/views/_core/authentication/login.vue';
const notifications = ref<NotificationItem[]>([ const notifications = ref<NotificationItem[]>([
{ {
id: 1,
avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB', avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',
date: '3小时前', date: '3小时前',
isRead: true, isRead: true,
@ -30,6 +31,7 @@ const notifications = ref<NotificationItem[]>([
title: '收到了 14 份新周报', title: '收到了 14 份新周报',
}, },
{ {
id: 2,
avatar: 'https://avatar.vercel.sh/1', avatar: 'https://avatar.vercel.sh/1',
date: '刚刚', date: '刚刚',
isRead: false, isRead: false,
@ -37,6 +39,7 @@ const notifications = ref<NotificationItem[]>([
title: '朱偏右 回复了你', title: '朱偏右 回复了你',
}, },
{ {
id: 3,
avatar: 'https://avatar.vercel.sh/1', avatar: 'https://avatar.vercel.sh/1',
date: '2024-01-01', date: '2024-01-01',
isRead: false, isRead: false,
@ -44,6 +47,7 @@ const notifications = ref<NotificationItem[]>([
title: '曲丽丽 评论了你', title: '曲丽丽 评论了你',
}, },
{ {
id: 4,
avatar: 'https://avatar.vercel.sh/satori', avatar: 'https://avatar.vercel.sh/satori',
date: '1天前', date: '1天前',
isRead: false, isRead: false,
@ -102,6 +106,17 @@ function handleNoticeClear() {
notifications.value = []; notifications.value = [];
} }
function markRead(id: number | string) {
const item = notifications.value.find((item) => item.id === id);
if (item) {
item.isRead = true;
}
}
function remove(id: number | string) {
notifications.value = notifications.value.filter((item) => item.id !== id);
}
function handleMakeAll() { function handleMakeAll() {
notifications.value.forEach((item) => (item.isRead = true)); notifications.value.forEach((item) => (item.isRead = true));
} }
@ -144,6 +159,8 @@ watch(
:dot="showDot" :dot="showDot"
:notifications="notifications" :notifications="notifications"
@clear="handleNoticeClear" @clear="handleNoticeClear"
@read="(item) => item.id && markRead(item.id)"
@remove="(item) => item.id && remove(item.id)"
@make-all="handleMakeAll" @make-all="handleMakeAll"
/> />
</template> </template>

View File

@ -23,6 +23,7 @@ import LoginForm from '#/views/_core/authentication/login.vue';
const notifications = ref<NotificationItem[]>([ const notifications = ref<NotificationItem[]>([
{ {
id: 1,
avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB', avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',
date: '3小时前', date: '3小时前',
isRead: true, isRead: true,
@ -30,6 +31,7 @@ const notifications = ref<NotificationItem[]>([
title: '收到了 14 份新周报', title: '收到了 14 份新周报',
}, },
{ {
id: 2,
avatar: 'https://avatar.vercel.sh/1', avatar: 'https://avatar.vercel.sh/1',
date: '刚刚', date: '刚刚',
isRead: false, isRead: false,
@ -37,6 +39,7 @@ const notifications = ref<NotificationItem[]>([
title: '朱偏右 回复了你', title: '朱偏右 回复了你',
}, },
{ {
id: 3,
avatar: 'https://avatar.vercel.sh/1', avatar: 'https://avatar.vercel.sh/1',
date: '2024-01-01', date: '2024-01-01',
isRead: false, isRead: false,
@ -44,6 +47,7 @@ const notifications = ref<NotificationItem[]>([
title: '曲丽丽 评论了你', title: '曲丽丽 评论了你',
}, },
{ {
id: 4,
avatar: 'https://avatar.vercel.sh/satori', avatar: 'https://avatar.vercel.sh/satori',
date: '1天前', date: '1天前',
isRead: false, isRead: false,
@ -102,6 +106,17 @@ function handleNoticeClear() {
notifications.value = []; notifications.value = [];
} }
function markRead(id: number | string) {
const item = notifications.value.find((item) => item.id === id);
if (item) {
item.isRead = true;
}
}
function remove(id: number | string) {
notifications.value = notifications.value.filter((item) => item.id !== id);
}
function handleMakeAll() { function handleMakeAll() {
notifications.value.forEach((item) => (item.isRead = true)); notifications.value.forEach((item) => (item.isRead = true));
} }
@ -145,6 +160,8 @@ watch(
:dot="showDot" :dot="showDot"
:notifications="notifications" :notifications="notifications"
@clear="handleNoticeClear" @clear="handleNoticeClear"
@read="(item) => item.id && markRead(item.id)"
@remove="(item) => item.id && remove(item.id)"
@make-all="handleMakeAll" @make-all="handleMakeAll"
/> />
</template> </template>

View File

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { NotificationItem } from './types'; import type { NotificationItem } from './types';
import { Bell, MailCheck } from '@vben/icons'; import { Bell, CircleCheckBig, CircleX, MailCheck } from '@vben/icons';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { import {
@ -35,6 +35,7 @@ const emit = defineEmits<{
clear: []; clear: [];
makeAll: []; makeAll: [];
read: [NotificationItem]; read: [NotificationItem];
remove: [NotificationItem];
viewAll: []; viewAll: [];
}>(); }>();
@ -91,7 +92,7 @@ function handleClick(item: NotificationItem) {
</div> </div>
<VbenScrollbar v-if="notifications.length > 0"> <VbenScrollbar v-if="notifications.length > 0">
<ul class="!flex max-h-[360px] w-full flex-col"> <ul class="!flex max-h-[360px] w-full flex-col">
<template v-for="item in notifications" :key="item.title"> <template v-for="item in notifications" :key="item.id ?? item.title">
<li <li
class="hover:bg-accent border-border relative flex w-full cursor-pointer items-start gap-5 border-t px-3 py-3" class="hover:bg-accent border-border relative flex w-full cursor-pointer items-start gap-5 border-t px-3 py-3"
@click="handleClick(item)" @click="handleClick(item)"
@ -107,7 +108,6 @@ function handleClick(item: NotificationItem) {
<img <img
:src="item.avatar" :src="item.avatar"
class="aspect-square h-full w-full object-cover" class="aspect-square h-full w-full object-cover"
role="img"
/> />
</span> </span>
<div class="flex flex-col gap-1 leading-none"> <div class="flex flex-col gap-1 leading-none">
@ -119,6 +119,30 @@ function handleClick(item: NotificationItem) {
{{ item.date }} {{ item.date }}
</p> </p>
</div> </div>
<div
class="absolute right-3 top-1/2 flex -translate-y-1/2 flex-col gap-2"
>
<VbenIconButton
v-if="!item.isRead"
size="xs"
variant="ghost"
class="h-6 px-2"
:tooltip="$t('common.confirm')"
@click.stop="emit('read', item)"
>
<CircleCheckBig class="size-4" />
</VbenIconButton>
<VbenIconButton
v-if="item.isRead"
size="xs"
variant="ghost"
class="text-destructive h-6 px-2"
:tooltip="$t('common.delete')"
@click.stop="emit('remove', item)"
>
<CircleX class="size-4" />
</VbenIconButton>
</div>
</li> </li>
</template> </template>
</ul> </ul>

View File

@ -1,4 +1,5 @@
interface NotificationItem { interface NotificationItem {
id: number | string;
avatar: string; avatar: string;
date: string; date: string;
isRead?: boolean; isRead?: boolean;