1. Requisitos funcionales
1. Esto es lo que utilicé en el proyecto de gestión en segundo plano. La función principal es proporcionar una función de recordatorio de mensajes.
2. Hay principalmente mensajes en la esquina superior derecha y un cuadro de mensaje en la esquina inferior.
3. La función principal es que si el usuario tiene mensajes no leídos, aparecerá un cuadro emergente cuando inicie sesión por primera vez. Si el usuario cierra la página, el cuadro emergente ya no aparecerá cuando la página se actualiza nuevamente. Esto significa que una cuenta no se cerrará antes. Cuando no hay envío de mensajes en tiempo real, el cuadro solo aparecerá una vez.
4. Si el usuario hace clic en un mensaje no leído, el mensaje se establecerá en el historial (leído).
Visualización de página:
2. Código de página
Nota: El mío es un sistema de gestión en segundo plano (usando vue-element-admin), y es la primera vez que escribo websocket, así que lo escribí en el archivo 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>