update 消息盒子支持按类型分组切换展示

This commit is contained in:
疯狂的狮子Li
2026-03-26 17:34:51 +08:00
parent 5be8fcf571
commit 1daa291bb6
4 changed files with 101 additions and 52 deletions
+59 -48
View File
@@ -1,12 +1,17 @@
<template> <template>
<div v-loading="state.loading" class="layout-navbars-breadcrumb-user-news"> <div v-loading="state.loading" class="layout-navbars-breadcrumb-user-news">
<div class="head-box"> <div class="head-box">
<div class="head-box-title">通知公告</div> <div class="head-box-title">消息盒子</div>
<div class="head-box-btn" @click="readAll">全部已读</div> <div class="head-box-btn" @click="readAll">全部已读</div>
</div> </div>
<el-tabs v-model="activeTab" class="message-tabs" stretch>
<el-tab-pane :label="`系统 ${tabCount.system}`" name="system"></el-tab-pane>
<el-tab-pane :label="`通知 ${tabCount.notice}`" name="notice"></el-tab-pane>
<el-tab-pane :label="`工作 ${tabCount.workflow}`" name="workflow"></el-tab-pane>
</el-tabs>
<div v-loading="state.loading" class="content-box"> <div v-loading="state.loading" class="content-box">
<template v-if="newsList.length > 0"> <template v-if="currentNewsList.length > 0">
<div v-for="(v, k) in newsList" :key="k" class="content-box-item" @click="onNewsClick(k)"> <div v-for="(v, k) in currentNewsList" :key="k" class="content-box-item" @click="onNewsClick(k)">
<div class="item-conten"> <div class="item-conten">
<div class="content-box-title">{{ v.title || '消息' }}</div> <div class="content-box-title">{{ v.title || '消息' }}</div>
<div>{{ v.message }}</div> <div>{{ v.message }}</div>
@@ -18,15 +23,15 @@
<span v-else class="el-tag el-tag--danger el-tag--mini read">未读</span> <span v-else class="el-tag el-tag--danger el-tag--mini read">未读</span>
</div> </div>
</template> </template>
<el-empty v-else :description="'消息为空'"></el-empty> <el-empty v-else :description="emptyDescription"></el-empty>
</div> </div>
<div v-if="newsList.length > 0" class="foot-box" @click="onGoToGiteeClick">前往gitee</div>
</div> </div>
</template> </template>
<script setup lang="ts" name="layoutBreadcrumbUserNews"> <script setup lang="ts" name="layoutBreadcrumbUserNews">
import { useNoticeStore } from '@/store/modules/notice'; import { useNoticeStore } from '@/store/modules/notice';
import router from '@/router'; import router from '@/router';
import { NOTICE_GROUP } from '@/utils/push-message';
const noticeStore = useNoticeStore(); const noticeStore = useNoticeStore();
const { readAll } = useNoticeStore(); const { readAll } = useNoticeStore();
@@ -35,24 +40,35 @@ const { readAll } = useNoticeStore();
const state = reactive({ const state = reactive({
loading: false loading: false
}); });
const newsList = ref([]) as any; const activeTab = ref<string>(NOTICE_GROUP.SYSTEM);
const newsList = computed(() => noticeStore.state.notices);
/** const tabCount = computed(() => ({
* 初始化数据 system: newsList.value.filter((item: any) => (item.category || NOTICE_GROUP.SYSTEM) === NOTICE_GROUP.SYSTEM).length,
* @returns notice: newsList.value.filter((item: any) => item.category === NOTICE_GROUP.NOTICE).length,
*/ workflow: newsList.value.filter((item: any) => item.category === NOTICE_GROUP.WORKFLOW).length
const getTableData = async () => { }));
state.loading = true;
newsList.value = noticeStore.state.notices; const currentNewsList = computed(() => {
state.loading = false; return newsList.value.filter((item: any) => {
}; return (item.category || NOTICE_GROUP.SYSTEM) === activeTab.value;
});
});
const emptyDescription = computed(() => {
if (activeTab.value === NOTICE_GROUP.NOTICE) {
return '暂无通知公告消息';
}
if (activeTab.value === NOTICE_GROUP.WORKFLOW) {
return '暂无工作流消息';
}
return '暂无系统消息';
});
//点击消息,写入已读 //点击消息,写入已读
const onNewsClick = async (item: any) => { const onNewsClick = async (item: any) => {
newsList.value[item].read = true; currentNewsList.value[item].read = true;
//并且写入pinia const current = currentNewsList.value[item];
noticeStore.state.notices = newsList.value;
const current = newsList.value[item];
if (current?.path) { if (current?.path) {
await router.push({ await router.push({
path: current.path, path: current.path,
@@ -60,17 +76,6 @@ const onNewsClick = async (item: any) => {
}); });
} }
}; };
// 前往通知中心点击
const onGoToGiteeClick = () => {
window.open('https://gitee.com/dromara/RuoYi-Vue-Plus/tree/5.X/');
};
onMounted(() => {
nextTick(() => {
getTableData();
});
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -107,11 +112,34 @@ onMounted(() => {
} }
} }
.message-tabs {
padding-top: 6px;
:deep(.el-tabs__header) {
margin-bottom: 0;
}
:deep(.el-tabs__nav-wrap::after) {
background: rgba(148, 163, 184, 0.14);
}
:deep(.el-tabs__item) {
font-size: 12px;
height: 34px;
color: var(--app-text-muted);
}
:deep(.el-tabs__item.is-active) {
color: var(--app-accent-strong);
font-weight: 600;
}
}
.content-box { .content-box {
height: 300px; height: 300px;
overflow: auto; overflow: auto;
font-size: 13px; font-size: 13px;
padding: 8px 0; padding: 8px 0 0;
.content-box-item { .content-box-item {
display: flex; display: flex;
@@ -167,23 +195,6 @@ onMounted(() => {
} }
} }
.foot-box {
height: 40px;
color: var(--app-accent-strong);
font-size: 12px;
font-weight: 600;
cursor: pointer;
opacity: 0.8;
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid rgba(148, 163, 184, 0.14);
margin-top: 4px;
&:hover {
opacity: 1;
}
}
:deep(.el-empty__description p) { :deep(.el-empty__description p) {
font-size: 13px; font-size: 13px;
} }
+1
View File
@@ -3,6 +3,7 @@ import { reactive } from 'vue';
export interface NoticeItem { export interface NoticeItem {
title?: string; title?: string;
category?: string;
type?: string; type?: string;
source?: string; source?: string;
read: boolean; read: boolean;
+35
View File
@@ -5,6 +5,20 @@ export const PUSH_MESSAGE_TYPE = {
CUSTOM: 'custom' CUSTOM: 'custom'
} as const; } as const;
export const PUSH_MESSAGE_SOURCE = {
BACKEND: 'backend',
NOTICE: 'notice',
WORKFLOW: 'workflow',
LLM: 'llm',
CLIENT: 'client'
} as const;
export const NOTICE_GROUP = {
SYSTEM: 'system',
NOTICE: 'notice',
WORKFLOW: 'workflow'
} as const;
export interface PushMessagePayload { export interface PushMessagePayload {
type?: string; type?: string;
source?: string; source?: string;
@@ -45,3 +59,24 @@ export const parsePushMessage = (raw: string): PushMessagePayload => {
export const shouldAppendNotice = (payload: PushMessagePayload) => { export const shouldAppendNotice = (payload: PushMessagePayload) => {
return MESSAGE_CENTER_TYPES.has(payload.type ?? PUSH_MESSAGE_TYPE.MESSAGE); return MESSAGE_CENTER_TYPES.has(payload.type ?? PUSH_MESSAGE_TYPE.MESSAGE);
}; };
export const resolveNoticeGroup = (payload: PushMessagePayload) => {
if (payload.type === PUSH_MESSAGE_TYPE.NOTICE || payload.source === PUSH_MESSAGE_SOURCE.NOTICE) {
return NOTICE_GROUP.NOTICE;
}
if (payload.source === PUSH_MESSAGE_SOURCE.WORKFLOW) {
return NOTICE_GROUP.WORKFLOW;
}
return NOTICE_GROUP.SYSTEM;
};
export const resolveNoticeTitle = (payload: PushMessagePayload) => {
const group = resolveNoticeGroup(payload);
if (group === NOTICE_GROUP.NOTICE) {
return '通知公告消息';
}
if (group === NOTICE_GROUP.WORKFLOW) {
return '工作流消息';
}
return '系统消息';
};
+5 -3
View File
@@ -1,7 +1,7 @@
import { getToken } from '@/utils/auth'; import { getToken } from '@/utils/auth';
import { ElNotification } from 'element-plus'; import { ElNotification } from 'element-plus';
import { useNoticeStore } from '@/store/modules/notice'; import { useNoticeStore } from '@/store/modules/notice';
import { parsePushMessage, PUSH_MESSAGE_TYPE, shouldAppendNotice } from '@/utils/push-message'; import { parsePushMessage, resolveNoticeGroup, resolveNoticeTitle, shouldAppendNotice } from '@/utils/push-message';
let closePushConnection: (() => void) | undefined; let closePushConnection: (() => void) | undefined;
@@ -10,8 +10,10 @@ const appendNotice = (raw: string) => {
if (!shouldAppendNotice(payload)) { if (!shouldAppendNotice(payload)) {
return; return;
} }
const title = resolveNoticeTitle(payload);
useNoticeStore().addNotice({ useNoticeStore().addNotice({
title: payload.type === PUSH_MESSAGE_TYPE.NOTICE ? '通知公告' : '系统消息', title,
category: resolveNoticeGroup(payload),
type: payload.type, type: payload.type,
source: payload.source, source: payload.source,
message: payload.message ?? '', message: payload.message ?? '',
@@ -23,7 +25,7 @@ const appendNotice = (raw: string) => {
time: new Date(payload.timestamp ?? Date.now()).toLocaleString() time: new Date(payload.timestamp ?? Date.now()).toLocaleString()
}); });
ElNotification({ ElNotification({
title: payload.type === PUSH_MESSAGE_TYPE.NOTICE ? '通知公告' : '消息', title,
message: payload.message ?? '', message: payload.message ?? '',
type: 'success', type: 'success',
duration: 3000 duration: 3000