feat(message): 优化消息通知功能

- 修改标题为"IOT设备管理系统"
- 优化消息通知组件,增加未读消息数量显示- 添加消息详情查看功能
- 更新消息列表展示,增加操作按钮
- 调整消息接口路径
This commit is contained in:
Kven 2025-03-26 21:19:40 +08:00
parent 69a0144ebd
commit 13360113ef
8 changed files with 144 additions and 65 deletions

View File

@ -8,7 +8,7 @@
href="https://unpkg.byted-static.com/latest/byted/arco-config/assets/favicon.ico" href="https://unpkg.byted-static.com/latest/byted/arco-config/assets/favicon.ico"
/> />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>基于MQTT的IOT设备管理系统</title> <title>IOT设备管理系统</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -1,39 +0,0 @@
import axios from 'axios';
export interface MessageRecord {
id: number;
type: string;
title: string;
subTitle: string;
avatar?: string;
content: string;
time: string;
status: 0 | 1;
messageType?: number;
}
export type MessageListType = MessageRecord[];
export function queryMessageList() {
return axios.post<MessageListType>('/api/message/list');
}
interface MessageStatus {
ids: string[];
}
// 批量设置消息已读
export function setMessageStatus(data: MessageStatus) {
return axios.post<MessageListType>('/api/message/read', data);
}
export interface ChatRecord {
id: number;
username: string;
content: string;
time: string;
isCollect: boolean;
}
export function queryChatList() {
return axios.post<ChatRecord[]>('/api/chat/list');
}

View File

@ -68,7 +68,7 @@ export function queryMessagesList(data: MessagesRecord) {
} }
// 未读消息数量 // 未读消息数量
export function queryMessagesCount() { export function queryMessagesCount() {
return axios.get('/api/rest/notice/count-unread'); return axios.get('/api/rest/notice/countUnread');
} }
// 已读消息数量 // 已读消息数量

View File

@ -69,12 +69,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PropType } from 'vue'; import { PropType } from 'vue';
import { MessageRecord, MessageListType } from '@/api/message';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
const props = defineProps({ const props = defineProps({
renderList: { renderList: {
type: Array as PropType<MessageListType>, type: Array as PropType<any>,
required: true, required: true,
}, },
unreadCount: { unreadCount: {
@ -87,7 +86,7 @@
// emit('itemClick', [...props.renderList]); // emit('itemClick', [...props.renderList]);
// }; // };
const onItemClick = (item: MessageRecord) => { const onItemClick = (item: any) => {
if (!item.status) { if (!item.status) {
emit('itemClick', [item]); emit('itemClick', [item]);
} }

View File

@ -43,7 +43,7 @@
<li> <li>
<a-tooltip content="消息通知"> <a-tooltip content="消息通知">
<div class="message-box-trigger"> <div class="message-box-trigger">
<a-badge :count="9" dot> <a-badge v-if="messageCount > 0" :count="9" dot>
<a-button <a-button
class="nav-btn" class="nav-btn"
type="outline" type="outline"
@ -53,6 +53,15 @@
<icon-notification /> <icon-notification />
</a-button> </a-button>
</a-badge> </a-badge>
<a-button
v-else
class="nav-btn"
type="outline"
:shape="'circle'"
@click="setPopoverVisible"
>
<icon-notification />
</a-button>
</div> </div>
</a-tooltip> </a-tooltip>
<a-popover <a-popover
@ -204,7 +213,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref, inject } from 'vue'; import { computed, ref, inject, onMounted } from 'vue';
import { useDark, useToggle, useFullscreen } from '@vueuse/core'; import { useDark, useToggle, useFullscreen } from '@vueuse/core';
import { useAppStore, useUserStore } from '@/store'; import { useAppStore, useUserStore } from '@/store';
import { LOCALE_OPTIONS } from '@/locale'; import { LOCALE_OPTIONS } from '@/locale';
@ -215,6 +224,7 @@
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { clearToken } from '@/utils/auth'; import { clearToken } from '@/utils/auth';
import messageBox from '@/components/message-box/index.vue'; import messageBox from '@/components/message-box/index.vue';
import { queryMessagesCount } from '@/api/messages';
const router = useRouter(); const router = useRouter();
const appStore = useAppStore(); const appStore = useAppStore();
@ -222,6 +232,7 @@
const { changeLocale, currentLocale } = useLocale(); const { changeLocale, currentLocale } = useLocale();
const { isFullscreen, toggle: toggleFullScreen } = useFullscreen(); const { isFullscreen, toggle: toggleFullScreen } = useFullscreen();
const messageCount = ref(0);
const locales = [...LOCALE_OPTIONS]; const locales = [...LOCALE_OPTIONS];
const avatar = computed(() => { const avatar = computed(() => {
return userStore.avatar ? userStore.avatar : userIcon; return userStore.avatar ? userStore.avatar : userIcon;
@ -275,12 +286,22 @@
triggerBtn.value.dispatchEvent(event); triggerBtn.value.dispatchEvent(event);
}; };
const fetchMessageCount = async () => {
const response = await queryMessagesCount();
messageCount.value = response.data;
};
// //
const handleSwitchRole = (roleId: number) => { const handleSwitchRole = (roleId: number) => {
userStore.switchRole(roleId); userStore.switchRole(roleId);
}; };
const toggleDrawerMenu = inject('toggleDrawerMenu') as () => void; const toggleDrawerMenu = inject('toggleDrawerMenu') as () => void;
//
onMounted(() => {
fetchMessageCount();
});
</script> </script>
<style scoped lang="less"> <style scoped lang="less">

View File

@ -152,8 +152,13 @@
<template #createTime="{ record }"> <template #createTime="{ record }">
{{ dayjs(record.createTime).format('YYYY-MM-DD HH:mm') }} {{ dayjs(record.createTime).format('YYYY-MM-DD HH:mm') }}
</template> </template>
<template #isRead="{ record }"> <template #operation="{ record }">
{{ record.isRead == true ? '已读' : '未读' }} <a-button type="outline" size="small" @click="handleViewDetail(record)">
<template #icon>
<icon-eye />
</template>
详情
</a-button>
</template> </template>
</a-table> </a-table>
<a-pagination <a-pagination
@ -167,6 +172,31 @@
@change="onPageChange" @change="onPageChange"
/> />
</a-card> </a-card>
<template>
<a-modal
width="900px"
:visible="visible"
:footer="false"
@cancel="handleCancel"
>
<template #title>消息详情</template>
<a-descriptions :column="1">
<a-descriptions-item label="标题">
{{ selectedRecord.title }}
</a-descriptions-item>
<a-descriptions-item label="时间">
{{ dayjs(selectedRecord.createTime).format('YYYY-MM-DD HH:mm') }}
</a-descriptions-item>
<a-descriptions-item label="备注">
<div v-html="selectedRecord.remark"></div>
</a-descriptions-item>
<a-descriptions-item label="内容">
<div v-html="selectedRecord.content"></div>
</a-descriptions-item>
</a-descriptions>
</a-modal>
</template>
</div> </div>
</template> </template>
@ -193,6 +223,13 @@
const renderData = ref<MessageRecord[]>([]); const renderData = ref<MessageRecord[]>([]);
const formModel = ref(generateFormModel()); const formModel = ref(generateFormModel());
const selectedIds = ref<number[]>([]); const selectedIds = ref<number[]>([]);
const visible = ref(false);
const selectedRecord = ref({
title: '',
createTime: '',
remark: '',
content: '',
});
const { const {
cloneColumns, cloneColumns,
showColumns, showColumns,
@ -209,10 +246,6 @@
const { pagination, setPagination } = usePagination(); const { pagination, setPagination } = usePagination();
const columns = computed<TableColumnData[]>(() => [ const columns = computed<TableColumnData[]>(() => [
// {
// title: '',
// dataIndex: 'userId',
// },
{ {
title: '标题', title: '标题',
dataIndex: 'title', dataIndex: 'title',
@ -231,6 +264,11 @@
dataIndex: 'remark', dataIndex: 'remark',
slotName: 'remark', slotName: 'remark',
}, },
{
title: '操作',
dataIndex: 'operation',
slotName: 'operation',
},
]); ]);
// //
const fetchData = async (params = { size: 10, current: 1 }) => { const fetchData = async (params = { size: 10, current: 1 }) => {
@ -274,6 +312,15 @@
search(); search();
}; };
const handleViewDetail = (record: any) => {
selectedRecord.value = record;
visible.value = true;
};
const handleCancel = () => {
visible.value = false;
};
// //
const reset = () => { const reset = () => {
formModel.value = generateFormModel(); formModel.value = generateFormModel();

View File

@ -103,8 +103,8 @@
style="margin-bottom: 40px" style="margin-bottom: 40px"
@page-change="onPageChange" @page-change="onPageChange"
> >
<template #operationTime="{ record }"> <template #makeTime="{ record }">
{{ dayjs(record.operationTime).format('YYYY-MM-DD HH:mm') }} {{ dayjs(record.makeTime).format('YYYY-MM-DD HH:mm') }}
</template> </template>
</a-table> </a-table>
<a-pagination <a-pagination
@ -158,8 +158,8 @@
}, },
{ {
title: '操作时间', title: '操作时间',
dataIndex: 'operationTime', dataIndex: 'makeTime',
slotName: 'operationTime', slotName: 'makeTime',
sortable: { sortable: {
sortDirections: ['ascend', 'descend'], sortDirections: ['ascend', 'descend'],
}, },

View File

@ -76,12 +76,23 @@
@change="handleSortChange" @change="handleSortChange"
@page-change="onPageChange" @page-change="onPageChange"
> >
<template #publishTime="{ record }"> <template #createTime="{ record }">
{{ dayjs(record.publishTime).format('YYYY-MM-DD HH:mm') }} {{ dayjs(record.createTime).format('YYYY-MM-DD HH:mm') }}
</template> </template>
<template #isRead="{ record }"> <template #isRead="{ record }">
{{ record.isRead == true ? '已读' : '未读' }} {{ record.isRead == true ? '已读' : '未读' }}
</template> </template>
<template #operation="{ record }">
<a-button
type="outline"
size="small"
status="success"
@click="handleViewDetail(record)"
style="padding: 7px; margin-right: 10px"
>
<template #icon><icon-list /></template>详情
</a-button>
</template>
<!-- <template #operations="{ record }">--> <!-- <template #operations="{ record }">-->
<!-- <a-button--> <!-- <a-button-->
<!-- type="outline"--> <!-- type="outline"-->
@ -106,6 +117,29 @@
@change="onPageChange" @change="onPageChange"
/> />
</a-card> </a-card>
<a-modal
width="900px"
:visible="visible"
:footer="false"
@cancel="handleCancel"
>
<template #title>消息详情</template>
<a-descriptions :column="1">
<a-descriptions-item label="标题">
{{ selectedRecord.title }}
</a-descriptions-item>
<a-descriptions-item label="时间">
{{ dayjs(selectedRecord.createTime).format('YYYY-MM-DD HH:mm') }}
</a-descriptions-item>
<a-descriptions-item label="备注">
<div v-html="selectedRecord.remark"></div>
</a-descriptions-item>
<a-descriptions-item label="内容">
<div v-html="selectedRecord.content"></div>
</a-descriptions-item>
</a-descriptions>
</a-modal>
</div> </div>
</template> </template>
@ -153,8 +187,8 @@
}, },
{ {
title: '时间', title: '时间',
dataIndex: 'publishTime', dataIndex: 'createTime',
slotName: 'publishTime', slotName: 'createTime',
sortable: { sortable: {
sortDirections: ['ascend', 'descend'], sortDirections: ['ascend', 'descend'],
}, },
@ -164,11 +198,11 @@
dataIndex: 'isRead', dataIndex: 'isRead',
slotName: 'isRead', slotName: 'isRead',
}, },
// { {
// title: '', title: '操作',
// dataIndex: 'operations', dataIndex: 'operation',
// slotName: 'operations', slotName: 'operation',
// }, },
]); ]);
const statusOptions = computed<SelectOptionData[]>(() => [ const statusOptions = computed<SelectOptionData[]>(() => [
{ {
@ -181,6 +215,14 @@
}, },
]); ]);
const visible = ref(false);
const selectedRecord = ref({
title: '',
createTime: '',
remark: '',
content: '',
});
// //
const fetchData = async ( const fetchData = async (
params: BulletinsRecord = { size: 10, current: 1 } params: BulletinsRecord = { size: 10, current: 1 }
@ -234,6 +276,15 @@
search(); search();
}; };
const handleViewDetail = (record: any) => {
selectedRecord.value = record;
visible.value = true;
};
const handleCancel = () => {
visible.value = false;
};
search(); search();
// //