更多ruoyi-nbcio功能请看演示系统
gitee源代码地址
前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio
演示地址:RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/
更多nbcio-boot功能请看演示系统
gitee源代码地址
后端代码: https://gitee.com/nbacheng/nbcio-boot
前端代码:https://gitee.com/nbacheng/nbcio-vue.git
在线演示(包括H5) : http://218.75.87.38:9888
1、在navbar.vue里增加消息组件
<!-- 消息 --> <el-tooltip :content="$t('navbar.message')" effect="dark" placement="bottom"> <header-notice id="message" class="right-menu-item hover-effect" /> </el-tooltip>
就是在右上角显示消息图标bell
2、HeadNotice.vue文件vue3版本修改如下:
<template> <div> <a-popover trigger="click" placement="bottomRight" :autoAdjustOverflow="true" :arrowPointAtCenter="true" overlayClassName="header-notice-wrapper" @visibleChange="handleHoverChange" :overlayStyle="{ width: '400px', top: '50px' }"> <template #content> <a-spin :spinning="loadding"> <a-tabs> <a-tab-pane :tab="msg1Title" key="1"> <a-list> <a-list-item :key="index" v-for="(record, index) in notice1"> <div style="margin-left: 5%;width: 50%"> <p><a @click="showNoticeList(record)">{{ record.titile }}</a></p> <p style="color: rgba(0,0,0,.45);margin-bottom: 0px">{{ record.createTime }} 发布</p> </div> <div style="text-align: right"> <a-tag @click="showNoticeList(record)" v-if="record.priority === 'L'" color="blue">一般消息</a-tag> <a-tag @click="showNoticeList(record)" v-if="record.priority === 'M'" color="orange">重要消息</a-tag> <a-tag @click="showNoticeList(record)" v-if="record.priority === 'H'" color="red">紧急消息</a-tag> </div> </a-list-item> <div style="margin-top: 5px;text-align: center"> <a-button @click="toMyNotice()" type="dashed" block>查看更多</a-button> </div> </a-list> </a-tab-pane> <a-tab-pane :tab="msg2Title" key="2"> <a-list> <a-list-item :key="index" v-for="(record, index) in notice2"> <div style="margin-left: 5%;width: 50%"> <p><a @click="showNoticeList(record)">{{ record.titile }}</a></p> <p style="color: rgba(0,0,0,.45);margin-bottom: 0px">{{ record.createTime }} 发布</p> </div> <div style="text-align: right"> <a-tag @click="showNoticeList(record)" v-if="record.priority === 'L'" color="blue">一般消息</a-tag> <a-tag @click="showNoticeList(record)" v-if="record.priority === 'M'" color="orange">重要消息</a-tag> <a-tag @click="showNoticeList(record)" v-if="record.priority === 'H'" color="red">紧急消息</a-tag> </div> </a-list-item> <div style="margin-top: 5px;text-align: center"> <a-button @click="toMyNotice()" type="dashed" block>查看更多</a-button> </div> </a-list> </a-tab-pane> <a-tab-pane :tab="msg3Title" key="3"> <a-list> <a-list-item :key="index" v-for="(record, index) in notice3"> <div style="margin-left: 5%;width: 50%"> <p><a @click="showNoticeList(record)">{{ record.titile }}</a></p> <p style="color: rgba(0,0,0,.45);margin-bottom: 0px">{{ record.createTime }} 发布</p> </div> <div style="text-align: right"> <a-tag @click="showNoticeList(record)" v-if="record.priority === 'L'" color="blue">一般消息</a-tag> <a-tag @click="showNoticeList(record)" v-if="record.priority === 'M'" color="orange">重要消息</a-tag> <a-tag @click="showNoticeList(record)" v-if="record.priority === 'H'" color="red">紧急消息</a-tag> </div> </a-list-item> <div style="margin-top: 5px;text-align: center"> <a-button @click="toMyNotice()" type="dashed" block>查看更多</a-button> </div> </a-list> </a-tab-pane> </a-tabs> </a-spin> </template> <span @click="fetchNotice" class="header-notice"> <a-badge :count="msgTotal"> <svg-icon icon-class="bell" style="width: 18px;height:18px;" /> </a-badge> </span> <show-notice ref="showNoticeRef" @ok="modalFormOk"></show-notice> <dynamic-notice ref="showDynamNoticeRef" :path="openPath" :formData="formData" /> </a-popover> </div> </template> <script setup lang="ts"> import ShowNotice from './ShowNotice.vue' import useUserStore from '@/store/modules/user'; import DynamicNotice from './DynamicNotice' import { ElNotification } from "element-plus"; import { listByUser, updateUserIdAndNotice } from "@/api/system/notice"; const router = useRouter(); const userStore = useUserStore(); const loadding = ref(false) const hovered = ref(false) const notice1 = ref<any>([]) const notice2 = ref<any>([]) const notice3 = ref<any>([]) const msg1Count = ref("0") const msg2Count = ref("0") const msg3Count = ref("0") const msg1Title = ref("通知(0)") const msg2Title = ref("") const msg3Title = ref("") const stopTimer = ref(false) let websock : any = null; // websocket 实例 const lockReconnect = ref(false) const formData = ref<any>({}) const openPath = ref('') const showDynamNoticeRef = ref(DynamicNotice) const showNoticeRef = ref(ShowNotice) const loadData = () => { try { // 获取系统消息 listByUser().then((res) => { console.log("listByUser res",res); if (res.code == 200) { notice1.value = res.data.anntMsgList; msg1Count.value = res.data.anntMsgTotal; msg1Title.value = "通知(" + res.data.anntMsgTotal + ")"; notice2.value = res.data.sysMsgList; msg2Count.value = res.data.sysMsgTotal; msg2Title.value = "系统消息(" + res.data.sysMsgTotal + ")"; notice3.value = res.data.todealMsgList; msg3Count.value = res.data.todealMsgTotal; msg3Title.value = "待办消息(" + res.data.todealMsgTotal + ")"; } }).catch(error => { console.log("系统消息通知异常", error); //这行打印permissionName is undefined stopTimer.value = true; console.log("清理timer"); }); } catch (err) { stopTimer.value = true; console.log("通知异常", err); } } const fetchNotice = () => { if (loadding.value) { loadding.value = false return } loadding.value = true setTimeout(() => { loadding.value = false }, 200) } const showNoticeList = (record: any) => { updateUserIdAndNotice({ noticeId: record.noticeId }).then((res) => { if (res.code == 200) { loadData(); } }); hovered.value = false; if (record.openType === 'component') { openPath.value = record.openPage; formData.value = { id: record.busId }; showDynamNoticeRef.value.detail(record.openPage); } else { console.log("showNoticeList showNoticeRef",showNoticeRef.value) showNoticeRef.value.detail(record); } } const toMyNotice = () => { router.push({ path: '/personal/mynotice' }); } const modalFormOk = () =>{ } const handleHoverChange = (visible: any) => { hovered.value = visible; } const initWebSocket = () => { // WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于https var userName = userStore.name; //var url = import.meta.env.VITE_APP_WS_API + "/websocket/" + userName + "?Authorization=Bearer " + getToken() + "&clientid=" + import.meta.env.VITE_APP_CLIENT_ID; var url = import.meta.env.VITE_APP_WS_API + "/websocket/" + userName; console.log("initWebSocket url=",url); websock = new WebSocket(url); websock.onopen = websocketOnopen; websock.onerror = websocketOnerror; websock.onmessage = websocketOnmessage; websock.onclose = websocketOnclose; } const websocketOnopen = () => { console.log("WebSocket连接成功"); } const websocketOnerror = (e) => { console.log("WebSocket连接发生错误"); reconnect(); } const websocketOnmessage = (e) => { console.log("-----接收消息-------", e); console.log("-----接收消息-------", e.data); var data = eval("(" + e.data + ")"); //解析对象 if (data.cmd == "topic") { //系统通知 loadData(); ElNotification({ //websocket消息通知弹出 title: 'websocket消息通知', message: data.msgTxt, type: 'success', duration: 1000 }) } else if (data.cmd == "user") { //用户消息 loadData(); ElNotification({ title: 'websocket消息通知', message: data.msgTxt, type: 'success', duration: 1000 }) } } const websocketOnclose = (e) => { console.log("connection closed (" + e + ")"); if (e) { console.log("connection closed (" + e.code + ")"); } reconnect(); } const websocketSend = (text) => { // 数据发送 try { websock.send(text); } catch (err) { console.log("send failed (" + err.code + ")"); } } const openNotification = (data) =>{ var text = data.msgTxt; const key = `open${Date.now()}`; ElNotification({ title: '消息提醒', message: text, type: 'success', duration: 1000 }) } const reconnect = () => { if (lockReconnect.value) return; lockReconnect.value = true; //没连接上会一直重连,设置延迟避免请求过多 setTimeout(function() { console.info("尝试重连..."); initWebSocket(); lockReconnect.value = false; }, 5000); } const showDetail = (key, data) => { ElNotification[key].close(); var id = data.msgId; getAction(url.queryById, { id: id }).then((res) => { if (res.success) { var record = res.result; showNotice(record); } }) } const msgTotal = computed(() => { return parseInt(msg1Count.value) + parseInt(msg2Count.value) + parseInt(msg3Count.value); }) onMounted(() => { loadData(); initWebSocket(); }) onUnmounted(() => { websocketOnclose(''); }) </script> <style lang="css"> .header-notice-wrapper { top: 50px !important; } </style> <style lang="less" scoped> .header-notice { display: inline-block; transition: all 0.3s; span { vertical-align: initial; } } </style>
3、ShowNotice.vue文件vue3版本修改如下:
<template> <n-modal :title="title" :width="modelStyle.width" :visible="visible" :bodyStyle ="bodyStyle" @cancel="handleCancel" > <template #footer> <a-button key="back" @click="handleCancel">关闭</a-button> <a-button v-if="recordDetail.openType==='url'" type="primary" @click="toHandle">去处理</a-button> </template> <a-card class="daily-article" :loading="loading"> <a-card-meta :title="recordDetail.noticeTitle" :description="'发布人:'+recordDetail.sender + ' 发布时间: ' + recordDetail.sendTime"> </a-card-meta> <a-divider /> <span v-html="recordDetail.noticeContent" class="article-content"></span> </a-card> </n-modal> </template> <script setup lang="ts" name="ShowNotice"> import NModal from './NModal' const router = useRouter(); const title = ref("通知消息") const recordDetail = ref<any>({}) const visible = ref(false) const loading = ref(false) const bodyStyle = ref({ padding: "0", height:(window.innerHeight*0.8)+"px", "overflow-y":"auto", }) const modelStyle = ref({ width: '60%', style: { top: '20px' }, fullScreen: false }) const detail = (record: any) => { visible.value = true; recordDetail.value = toRaw(record); } const handleCancel = () => { visible.value = false; } const toHandle = () => { if(recordDetail.value.openType==='url'){ visible.value = false; //链接跳转 router.push({path: recordDetail.value.openPage}) } } //暴露detail方法 defineExpose({ detail }); onMounted(() => { console.log("ShowNotice visible",visible) }) </script> <style lang="less" scoped> .announcementCustomModal{ .ant-modal-header { border: none; display: inline-block; position: absolute; z-index: 1; right: 56px; padding: 0; .ant-modal-title{ .custom-btn{ width: 56px; height: 56px; border: none; box-shadow: none; } } } .daily-article{ border-bottom: 0; } } .daily-article { .article-button { font-size: 1.2rem !important; } .ant-card-body { padding: 18px !important; } .ant-card-head { padding: 0 1rem; } .ant-card-meta { margin-bottom: 1rem; } .article-content { p { word-wrap: break-word; word-break: break-all; text-overflow: initial; white-space: normal; font-size: .9rem !important; margin-bottom: .8rem; } :deep(a) { color: #1890ff; } } } </style>