效果图
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'
import vuescroll from "vuescroll"
import "vuescroll/dist/vuescroll.css"
Vue.config.productionTip = false
Vue.use(Antd)
Vue.use(vuescroll)
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
package.json
{
"name": "cms-pc",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"electron:serve": "vue-cli-service electron:serve",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps"
},
"main": "background.js",
"dependencies": {
"ant-design-vue": "^1.7.8",
"axios": "^0.24.0",
"core-js": "^3.8.3",
"vue": "^2.6.14",
"vue-router": "^3.5.1",
"vuex": "^3.6.2",
"electron": "^17.1.2",
"vuescroll": "^4.17.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"vue-template-compiler": "^2.6.14",
"less": "^3.0.4",
"less-loader": "^5.0.0",
"vue-cli-plugin-electron-builder": "^1.3.5"
}
}
vue组件
<template>
<div class="index">
<a-row :gutter="[10,10]">
<a-col :span="24">
<h2 v-text="'You are talking to: '+message2UserId"/>
<div class="messagesWrapper">
<vue-scroll ref="chatMessagesScroller" :ops="ops" style="width:100%;">
<div class="chatMessages">
<a-row :gutter="[10,10]">
<a-col v-for="(message,index) in chatMessages" :key="index" :span="24">
<div :class="['chatMessageItem',message.fromId === loginUserId ? 'send' : 'receive']">
<div class="messageAuthor">
<a-avatar :size="45" icon="user"/>
div>
<div class="messageInfo">
<div class="messageTime">2022-03-28div>
<div :class="['chat-bubble',message.fromId === loginUserId ?
'chat-bubble-right chat-bubble-success':'chat-bubble-left chat-bubble-primary']"
:id="'chatMessage_'+index">
<span v-text="message.messageInfo"/>
div>
div>
div>
a-col>
a-row>
div>
vue-scroll>
div>
<a-textarea v-model="messageInfo" placeholder="input message" rows="4"/>
<a-button type="primary" v-on:click="sendMessage" icon="enter" style="float: right;margin-top:10px">senda-button>
a-col>
<a-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<span>
当前状态:<span :style="isLogined ? 'color:green;':'color: red;'" v-text="isLogined ? '已登录': '未登录'"/>
span>
<a-input-search v-model="loginUserId" placeholder="your id" @search="imLogin">
<a-button type="primary" slot="enterButton">
login
a-button>
a-input-search>
a-col>
<a-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<label>chat to user:label>
<a-input v-model="message2UserId" placeholder="chat target user id" style="width:100%"/>
a-col>
a-row>
div>
template>
<script>
export default {
name: "index",
data() {
return {
loginUserId: '',
socket: undefined,
message2UserId: '',
messageInfo: '',
chatMessages: [],
isLogined: false,
ops: {
vuescroll: {},
scrollPanel: {
scrollingX: false
},
rail: {
keepShow: true
},
bar: {
hoverStyle: true,
onlyShowBarOnScroll: false,
background: "#929292",
opacity: 0.5,
'overflow-x': "hidden"
}
},
isSending: false,
}
},
methods: {
initSocketConnect() {
let that = this;
if (window.WebSocket || window.MozWebSocket) {
that.socket = new WebSocket("ws://localhost:7777/ws");
that.socket.onmessage = function (event) {
let messagePacket = JSON.parse(event.data);
let msgCommand = messagePacket.command;
if (msgCommand !== 'HEARTBEAT_RESPONSE') {
that.processMessage(messagePacket);
}
};
that.socket.onopen = function (event) {
console.log("websocket connected");
that.startHeartBeat();
};
that.socket.onclose = function (event) {
console.log("websocket disConnected");
};
} else {
console.log("your browser does not support websocket");
}
},
startHeartBeat() {
let that = this;
setInterval(function () {
let packet = {};
packet.command = "3";
that.socket.send(JSON.stringify(packet));
}, 5000);
},
imLogin() {
let that = this;
if (that.loginUserId) {
let loginPacket = {};
loginPacket.dataInfo = {
userId: that.loginUserId,
password: "123456",
clientType: "web",
clientVersion: "1",
};
loginPacket.command = "1";
that.socket.send(JSON.stringify(loginPacket));
that.isLogined = true;
} else {
that.$notification['info']({
message: 'please input your loginId',
});
}
},
processMessage(messagePacket) {
let that = this;
let msgCommand = messagePacket.command;
switch (msgCommand) {
case "SYS_NOTIFY_REQUEST":
that.processSysNotifyPacket(messagePacket);
break;
case "P2P_MESSAGE_REQUEST":
that.processP2pMessagePacket(messagePacket);
break;
default:
console.log("msg type to do...");
}
},
processSysNotifyPacket(notifyPacket) {
let that = this;
let notifyType = notifyPacket.notifyType;
switch (notifyType) {
case "CLIENT_ONLINE":
that.processUserOnline(notifyPacket.jsonNotifyInfo);
break;
case "CLIENT_OFFLINE":
that.processUserOffline(notifyPacket.jsonNotifyInfo);
break;
default:
console.log("process sys notify...");
}
},
processUserOnline(onlineInfo) {
let that = this;
let userObj = JSON.parse(onlineInfo);
let onlineUser = userObj.onlineUser;
if (that.loginUserId !== onlineUser) {
if (window.Notification) {
Notification.requestPermission(function (status) {
var n = new Notification('上线通知', {body: '用户:' + userObj.onlineUser + "上线了!"});
});
}
}
},
processUserOffline(offlineInfo) {
let that = this;
let userObj = JSON.parse(offlineInfo);
let userId = userObj.offlineUser;
if (that.loginUserId !== onlineUser) {
if (window.Notification) {
Notification.requestPermission(function (status) {
var n = new Notification('下线通知', {body: '用户:' + userId + "下线了!"});
});
}
}
},
sendMessage() {
let that = this;
if (!window.WebSocket && !window.MozWebSocket) {
alert("your browser does not support websocket");
return;
}
if (that.socket.readyState === WebSocket.OPEN) {
if (that.message2UserId) {
if (that.isLogined) {
if (that.messageInfo) {
if (that.message2UserId !== that.loginUserId) {
that.isSending = true;
let p2pMessagePacket = {};
p2pMessagePacket.dataInfo = {
fromId: that.loginUserId,
toId: that.message2UserId,
messageInfo: that.messageInfo,
};
p2pMessagePacket.command = "5";
that.chatMessages.push(p2pMessagePacket.dataInfo);
that.socket.send(JSON.stringify(p2pMessagePacket));
that.messageInfo = '';
that.scrollMessages2Bottom();
that.isSending = false;
} else {
that.$notification['info']({
message: 'you can not chat with yourself',
});
}
} else {
that.$notification['info']({
message: 'chat message can not be empty!',
});
}
} else {
that.$notification['info']({
message: 'please login before chat to someone!',
});
}
} else {
that.$notification['info']({
message: 'please input userId which you wanna to chat',
});
}
} else {
console.log("WebSocket connect failed");
}
},
processP2pMessagePacket(messagePacket) {
let that = this;
that.chatMessages.push(messagePacket);
that.scrollMessages2Bottom();
},
scrollMessages2Bottom() {
let that = this;
let targetEleId = "#chatMessage_" + (that.chatMessages.length - 1);
console.log("targetEle:" + targetEleId);
setTimeout(function () {
that.$refs.chatMessagesScroller.scrollIntoView(targetEleId, 100);
}, 100);
}
},
mounted() {
let that = this;
that.initSocketConnect();
}
}
script>
<style scoped>
.index {
width: 60%;
margin: 0 auto;
height: 100%;
margin-top: 60px;
}
.messagesWrapper {
height: 60vh;
background-color: rgba(255,255,255,.6);
border-radius: 4px;
margin-bottom: 20px;
}
.chatMessages{
padding: 20px;
}
.chatMessageItem{
width: 100%;
display: flex;
align-items: flex-start;
}
.chatMessageItem .messageInfo{
width: 0;
flex: 1;
}
.chatMessageItem.send{
flex-direction: row-reverse;
justify-content: flex-end;
}
.chatMessageItem.send .messageAuthor{
margin-left: 10px;
}
.chatMessageItem.send .messageInfo{
text-align: right;
}
.chatMessageItem.receive{
flex-direction: row;
justify-content: flex-start;
}
.chatMessageItem.receive .messageAuthor{
margin-right: 10px;
}
.chatMessageItem.receive .messageInfo{
text-align: left;
}
.chat-bubble {
color: #333;
box-shadow: 3px 5px 15px rgba(0, 0, 0, .2);
padding: 5px 10px;
width: auto;
max-width: 50%;
text-align: left;
display: inline-block !important;
position: relative;
word-break: break-all;
background-color: #ffffff;
transition: all .2s;
cursor: pointer;
margin-bottom: 10px;
}
.chat-bubble:hover {
transform: scale(1.03);
}
.chat-bubble-left {
float: left;
border-radius: 0 5px 5px 5px;
}
.chat-bubble-left:before {
content: '';
width: 6px;
height: 6px;
left: -6px;
top: 0;
position: absolute;
border-left: 3px solid transparent;
border-bottom: 3px solid transparent;
border-top: 3px solid #ffffff;
border-right: 3px solid #ffffff;
}
.chat-bubble-right {
float: right;
border-radius: 5px 0 5px 5px;
}
.chat-bubble-right:after {
content: '';
width: 6px;
height: 6px;
right: -6px;
top: 0;
position: absolute;
border-left: 3px solid #ffffff;
border-bottom: 3px solid transparent;
border-top: 3px solid #ffffff;
border-right: 3px solid transparent;
}
.chat-bubble-left.chat-bubble-primary {
background: linear-gradient(90deg, #2b92e4, #30a1dc) !important;
color: #ffffff !important;
}
.chat-bubble-left.chat-bubble-primary:before {
border-right: 3px solid #2b92e4 !important;
border-top: 3px solid #2b92e4 !important;
}
.chat-bubble-right.chat-bubble-primary {
background: linear-gradient(90deg, #30a1dc, #2b92e4) !important;
color: #ffffff !important;
}
.chat-bubble-right.chat-bubble-primary:after {
border-left: 3px solid #2b92e4 !important;
border-top: 3px solid #2b92e4 !important;
}
.chat-bubble-left.chat-bubble-success {
background: linear-gradient(90deg, #4caf50, #66b869) !important;
color: #ffffff !important;
}
.chat-bubble-left.chat-bubble-success:before {
border-right: 3px solid #4caf50 !important;
border-top: 3px solid #4caf50 !important;
}
.chat-bubble-right.chat-bubble-success {
background: linear-gradient(90deg, #66b869, #4caf50) !important;
color: #ffffff !important;
}
.chat-bubble-right.chat-bubble-success:after {
border-left: 3px solid #4caf50 !important;
border-top: 3px solid #4caf50 !important;
}
style>