Vue 项目中使用WebSocket 消息推送
一、功能需求
1.这是我在后台管理项目中使用到的,主要的作用是搞一个消息提醒的功能。
2.主要有右上角的提示和有下角的消息弹框。
3.主要实现的功能是如果用户有未读的消息,那么首次登录就弹框,如果用户关闭了页面,那么再次刷新页面的时候,也不再弹框,意思就是一个账户没有退出之前,也没有实时消息推送的时候,只弹一次框。
4.如果用户点击了未读消息,那么就会将此条消息置位历史(已读)。
页面展示:
二、页面代码
备注:我的是后台管理系统(用的是vue-element-admin),第一次写websocket,所以我写在了src->layout->AppMain.vue文件下面:
<template>
<section class="app-main">
<Message-remind :message-list="messageList" />
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<router-view :key="key" />
</keep-alive>
</transition>
</section>
</template>
<script>
import MessageRemind from '@/components/MessageRemind/index.vue'
import { getToken, getSid } from "@/utils/auth"; // get token from cookie
export default {
name: 'AppMain',
components: {
MessageRemind
},
watch: {
'$store.state.user': {
handler: function (newValue, oldValue) {
// 如果没有token,则表明退出了登录
if (!newValue.token) {
this.closeWebSocket();
}
},
immediate: true,
deep: true
}
},
data() {
return {
// socket参数
socket: null,
timeout: 60 * 1000, // 45秒一次心跳
timeoutObj: null, // 心跳心跳倒计时
serverTimeoutObj: null, // 心跳倒计时
timeoutnum: null, // 断开 重连倒计时
lockReconnect: false, // 防止
websocket: null,
messageList: {}
};
},
created() {
const hasToken = getToken();
const sid = getSid();
if (hasToken) {
this.initWebSocket(hasToken, sid)
}
},
computed: {
cachedViews() {
return this.$store.state.tagsView.cachedViews
},
key() {
return this.$route.path
}
},
mounted() {
// console.log(this.$store.state.tagsView.cachedViews)
},
methods: {
initWebSocket(token, sid) {
// WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于https
this.websocket = new WebSocket(process.env.VUE_APP_WEB_SOCKET_BASE_API + '?uiticket=' + token + '&sid=' + sid);
this.websocket.onopen = this.websocketonopen;
this.websocket.onerror = this.websocketonerror;
this.websocket.onmessage = this.setOnmessageMessage;
this.websocket.onclose = this.websocketclose;
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
// window.onbeforeunload = that.onbeforeunload
},
start() {
//清除延时器
this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
this.timeoutObj = setTimeout(() => {
if (this.websocket && this.websocket.readyState == 1) {
this.websocket.send('{"messageType": 99}');//发送消息,服务端返回信息,即表示连接良好,可以在socket的onmessage事件重置心跳机制函数
} else {
this.reconnect();
}
//定义一个延时器等待服务器响应,若超时,则关闭连接,重新请求server建立socket连接
this.serverTimeoutObj = setTimeout(() => {
this.websocket.close();
}, this.timeout)
}, this.timeout)
},
reset() { // 重置心跳
// 清除时间
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
// 重启心跳
this.start();
},
// 重新连接
reconnect() {
if (this.lockReconnect) return
this.lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
this.timeoutnum && clearTimeout(this.timeoutnum);
this.timeoutnum = setTimeout(() => {
this.initWebSocket();
this.lockReconnect = false;
}, 5000)
},
async setOnmessageMessage(event) {
this.messageList = JSON.parse(event.data)
if (this.messageList.data.messageType === 999) {
this.websocket.send('{"messageType": 99}');
}
this.$store.dispatch('user/steMessageMenu', this.messageList)
this.reset();
// 自定义全局监听事件
window.dispatchEvent(new CustomEvent('onmessageWS', {
detail: {
data: event.data
}
}))
//发现消息进入 开始处理前端触发逻辑
// if (event.data === 'success' || event.data === 'heartBath') return
},
websocketonopen(e) {
// console.log('onopen', {e});
//开启心跳
this.start();
console.log("WebSocket连接成功!!!" + new Date() + "----" + this.websocket.readyState);
},
websocketonerror(e) {
// console.log('websocketonerror', {e});
console.log("WebSocket连接发生错误" + e);
},
websocketclose(e) {
this.websocket.close();
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
console.log("WebSocket连接关闭");
},
websocketsend(messsage) {
this.websocket.send(messsage)
},
closeWebSocket() { // 关闭websocket
this.websocket.close()
},
},
}
</script>
<style lang="scss" scoped>
@import "~@/styles/global-height.scss";
.app-main {
/* 50= navbar 50 */
// min-height: calc(100vh - #{$navbar+'px'});
width: 100%;
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
flex: 1;
}
.fixed-header+.app-main {
padding-top: #{$navbar+'px'};
}
.hasTagsView {
.app-main {
// min-height: calc(100vh - #{$appMain+'px'});
}
.fixed-header+.app-main {
padding-top: 90px;
}
}
.copy {
text-align: center;
height: 30px;
line-height: 30px;
font-size: 13px;
color: #666;
background: #fff;
width: 100%;
box-shadow: 0 0 10px #dfe4ed;
}
</style>
<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
.fixed-header {
padding-right: 15px;
}
}
</style>