refactor(projects): 消息提示增加等级区分
This commit is contained in:
@@ -1,18 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { useDebounceFn, useInfiniteScroll } from '@vueuse/core';
|
||||
import { NOTIFY_MESSAGE_LEVEL_DICT_CODE } from '@/constants/dict';
|
||||
import {
|
||||
fetchGetMyNotifyMessagePage,
|
||||
fetchGetUnreadNotifyCount,
|
||||
fetchUpdateAllNotifyMessageRead,
|
||||
fetchUpdateNotifyMessageRead
|
||||
} from '@/service/api';
|
||||
import { formatRelativeTime } from '@/utils/datetime';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { formatDateTime, formatRelativeTime } from '@/utils/datetime';
|
||||
|
||||
defineOptions({ name: 'NotificationBell' });
|
||||
|
||||
const dictStore = useDictStore();
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
const UNREAD_COUNT_POLL_INTERVAL = 30 * 1000;
|
||||
const UNREAD_COUNT_POLL_INTERVAL = 15 * 1000;
|
||||
|
||||
type TabKey = 'unread' | 'read';
|
||||
|
||||
@@ -43,10 +47,18 @@ const drawerOpen = ref(false);
|
||||
const activeTab = ref<TabKey>('unread');
|
||||
const searchKeyword = ref('');
|
||||
|
||||
const detailVisible = ref(false);
|
||||
const detailMessage = ref<Api.NotifyMessage.NotifyMessage | null>(null);
|
||||
|
||||
function keywordParam() {
|
||||
return searchKeyword.value.trim() || undefined;
|
||||
}
|
||||
|
||||
/** 列表圆点颜色:跟随消息等级(与等级徽标同一字典色源);取不到时回 undefined,由 CSS 兜底 */
|
||||
function levelDotColor(level: number) {
|
||||
return dictStore.getDictItem(NOTIFY_MESSAGE_LEVEL_DICT_CODE, level)?.colorType ?? undefined;
|
||||
}
|
||||
|
||||
async function refreshUnreadCount() {
|
||||
const { data, error } = await fetchGetUnreadNotifyCount();
|
||||
if (!error && typeof data === 'number') {
|
||||
@@ -180,6 +192,16 @@ async function markRead(item: Api.NotifyMessage.NotifyMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
function openDetail(row: Api.NotifyMessage.NotifyMessage) {
|
||||
// 弹框持有该行引用,正文不随未读列表移除而消失
|
||||
detailMessage.value = row;
|
||||
detailVisible.value = true;
|
||||
// 未读消息「打开即已读」:后台静默标记,避免"看一半就跑到已读"
|
||||
if (!row.readStatus) {
|
||||
markRead(row);
|
||||
}
|
||||
}
|
||||
|
||||
async function markAllRead() {
|
||||
const { error } = await fetchUpdateAllNotifyMessageRead();
|
||||
if (error) return;
|
||||
@@ -193,6 +215,8 @@ async function markAllRead() {
|
||||
let pollTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
// 等级徽标颜色/文案走字典:若未在登录缓存内则按编码补拉一次(已缓存时不发请求)
|
||||
dictStore.ensureDictData(NOTIFY_MESSAGE_LEVEL_DICT_CODE);
|
||||
refreshUnreadCount();
|
||||
pollTimer = setInterval(() => {
|
||||
if (document.hidden) return;
|
||||
@@ -253,12 +277,15 @@ onBeforeUnmount(() => {
|
||||
v-for="row in listStates.unread.items"
|
||||
:key="row.id"
|
||||
class="notification-bell__row is-unread"
|
||||
@click="markRead(row)"
|
||||
@click="openDetail(row)"
|
||||
>
|
||||
<span class="notification-bell__row-dot" />
|
||||
<span class="notification-bell__row-dot" :style="{ backgroundColor: levelDotColor(row.level) }" />
|
||||
<div class="notification-bell__row-body">
|
||||
<div class="notification-bell__row-title">{{ row.templateContent }}</div>
|
||||
<div class="notification-bell__row-time">{{ formatRelativeTime(row.createTime) }}</div>
|
||||
<div class="notification-bell__row-meta">
|
||||
<DictTag :dict-code="NOTIFY_MESSAGE_LEVEL_DICT_CODE" :value="row.level" size="small" round />
|
||||
<span class="notification-bell__row-time">{{ formatRelativeTime(row.createTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -273,18 +300,23 @@ onBeforeUnmount(() => {
|
||||
|
||||
<ElTabPane name="read">
|
||||
<template #label>
|
||||
<span class="notification-bell__tab-label">
|
||||
已读
|
||||
<span class="notification-bell__tab-count">{{ listStates.read.total }}</span>
|
||||
</span>
|
||||
<span class="notification-bell__tab-label">已读</span>
|
||||
</template>
|
||||
<ElScrollbar ref="readScrollbar" class="notification-bell__scroll">
|
||||
<ul v-if="listStates.read.items.length > 0" class="notification-bell__list">
|
||||
<li v-for="row in listStates.read.items" :key="row.id" class="notification-bell__row">
|
||||
<span class="notification-bell__row-dot" />
|
||||
<li
|
||||
v-for="row in listStates.read.items"
|
||||
:key="row.id"
|
||||
class="notification-bell__row"
|
||||
@click="openDetail(row)"
|
||||
>
|
||||
<span class="notification-bell__row-dot" :style="{ backgroundColor: levelDotColor(row.level) }" />
|
||||
<div class="notification-bell__row-body">
|
||||
<div class="notification-bell__row-title">{{ row.templateContent }}</div>
|
||||
<div class="notification-bell__row-time">{{ formatRelativeTime(row.createTime) }}</div>
|
||||
<div class="notification-bell__row-meta">
|
||||
<DictTag :dict-code="NOTIFY_MESSAGE_LEVEL_DICT_CODE" :value="row.level" size="small" round />
|
||||
<span class="notification-bell__row-time">{{ formatRelativeTime(row.createTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -303,6 +335,28 @@ onBeforeUnmount(() => {
|
||||
<ElButton @click="closeDrawer">关闭</ElButton>
|
||||
</template>
|
||||
</ElDrawer>
|
||||
|
||||
<ElDialog v-model="detailVisible" width="520px" align-center class="notification-bell__detail">
|
||||
<template #header>
|
||||
<div class="notification-bell__detail-head">
|
||||
<span class="notification-bell__detail-sender">{{ detailMessage?.templateNickname || '系统通知' }}</span>
|
||||
<DictTag
|
||||
v-if="detailMessage"
|
||||
:dict-code="NOTIFY_MESSAGE_LEVEL_DICT_CODE"
|
||||
:value="detailMessage.level"
|
||||
size="small"
|
||||
round
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="detailMessage" class="notification-bell__detail-body">
|
||||
<div class="notification-bell__detail-content">{{ detailMessage.templateContent }}</div>
|
||||
<div class="notification-bell__detail-time">收到于 {{ formatDateTime(detailMessage.createTime) }}</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<ElButton @click="detailVisible = false">关闭</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -484,18 +538,15 @@ onBeforeUnmount(() => {
|
||||
gap: 10px;
|
||||
padding: 12px 4px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 120ms ease;
|
||||
}
|
||||
|
||||
.notification-bell__row + .notification-bell__row {
|
||||
border-top: 1px dashed var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.notification-bell__row.is-unread {
|
||||
cursor: pointer;
|
||||
transition: background-color 120ms ease;
|
||||
}
|
||||
|
||||
.notification-bell__row.is-unread:hover {
|
||||
.notification-bell__row:hover {
|
||||
background-color: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
@@ -527,8 +578,14 @@ onBeforeUnmount(() => {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.notification-bell__row-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.notification-bell__row-time {
|
||||
margin-top: 4px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -547,4 +604,41 @@ onBeforeUnmount(() => {
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.notification-bell__detail-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.notification-bell__detail-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding-right: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.notification-bell__detail-sender {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.notification-bell__detail-content {
|
||||
color: var(--el-text-color-regular);
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.notification-bell__detail-time {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user