更多ruoyi-nbcio功能请看演示系统
gitee源代码地址
前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio
演示地址:RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/
更多nbcio-boot功能请看演示系统
gitee源代码地址
后端代码: https://gitee.com/nbacheng/nbcio-boot
前端代码:https://gitee.com/nbacheng/nbcio-vue.git
在线演示(包括H5) : http://122.227.135.243:9888
1、在navbar.vue里增加消息组件
javascript
<!-- 消息 -->
<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版本修改如下:
javascript
<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版本修改如下:
javascript
<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>