vue手把手带你创建聊天室(vue-native-websocket)

谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口
[1] 。

正文开始:
socket中使用了VUEX如果不打算使用状态管理器,可以忽略下文中的某些配置。

在vue中我们使用:vue-native-websocket
第一步安装:

npm install vue-native-websocket --save

然后在main.js中进行全局注册:

Vue.use(VueNativeSock,"后台地址*****",{// 启用Vuex集成store: store,// 数据发送/接收使用使用jsonformat: "json",connectManually: true,reconnection: true,// 尝试重连的次数reconnectionAttempts: 5,// 重连间隔时间reconnectionDelay: 3000,passToStoreHandler: function (eventName, event) {if (!eventName.startsWith('SOCKET_')) { return }let method = 'commit';let target = eventName.toUpperCase();let msg = event;if (this.format === 'json' && event.data) {msg = JSON.parse(event.data);if (msg.mutation) {target = [msg.namespace || '', msg.mutation].filter((e) => !!e).join('/');} else if (msg.action) {method = 'dispatch';target = [msg.namespace || '', msg.action].filter((e) => !!e).join('/');}}this.store[method](target, msg);this.store.state.socket.message = msg;}
});

第三部写html:
在一个.vue结尾的文件中写入一下代码:

<!--公用组件:消息内容展示,实现群聊和单聊业务-->
<template><div id="mainContent" ><div class="top-panel" ref="topPanel"><div class="title-panel"><!-- <p>当前在线人数: {{onlineUsers}}</p> --></div></div><!--消息显示--><div class="messages-panel" ref="messagesContainer"><div class="row-panel" v-for="(item,index) of senderMessageList" :key="index"><!--发送者消息样式--><div class="sender-panel" v-if="item.userID == userID"><!--昵称展示--><div class="user-name-panel sender"><p>{{item.username}}</p></div><!--消息--><div class="msg-body"><!--消息尾巴--><div class="tail-panel"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-zbds30duihuakuangyou" color="#dce7dc"></use></svg></div><!--消息内容--><p v-html="item.msgText" @click="viewLargerImage($event)" ref="comment"/></div><!--头像--><div class="avatar-panel"><img :src="item.avatarSrc" alt=""></div></div><!--对方消息样式--><div class="otherSide-panel" v-else><!--头像--><div class="avatar-panel"><img :src="item.avatarSrc" alt=""></div><!--昵称展示--><div class="user-name-panel sender"><p>{{item.username}}</p></div><!--消息--><div class="msg-body"><!--消息尾巴--><div class="tail-panel"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-zbds30duihuakuangzuo"></use></svg></div><!--消息内容--><p v-html="item.msgText" @click="viewLargerImage($event)" ref="comment" /></div></div></div></div><!--用户输入模块--><div class="user-input-panel" @click="getEditableDivFocus()"><div class="toolbar-panel"><div class="item-panel" v-for="(item ,index) of toolbarList" :key="index"><img class="emoticon" :src="require(`../assets/img/${item.src}`)"@mouseenter="toolbarSwitch('hover',$event,item.src,item.hover,item.down,item.name)"@mouseleave="toolbarSwitch('leave',$event,item.src,item.hover,item.down,item.name)"@mousedown="toolbarSwitch('down',$event,item.src,item.hover,item.down,item.name)"@mouseup="toolbarSwitch('up',$event,item.src,item.hover,item.down,item.name)" :alt="item.info"></div></div><div id="msgInputContainer" class="input-panel" ref="msgInputContainer" @keydown.enter.exact="sendMessage($event)"contenteditable="true" spellcheck="false"></div><div class="send-panel" ref="sendPanel" @click="mobileSend()"><p>发送</p></div><!--表情面板--><div class="emoticon-panel" :style="{display: emoticonShowStatus}" ref="emoticonPanel"><div class="row-panel"><div class="item-panel" v-for="(item,index) of this.emojiList" :key="index"><img :src="require(`../assets/images/emoji/${item.src}`)" :alt="item.info"@mouseover="emojiConversion($event,'over',item.src,item.hover,item.info)"@mouseleave="emojiConversion($event,'leave',item.src,item.hover,item.info)"@click="emojiConversion($event,'click',item.src,item.hover,item.info)"></div></div><div class="ico-panel"></div></div></div></div>
</template><script src="../assets/js/message-display.js"></script><style lang="scss" src="../assets/css/message-display.scss" scoped></style>

style文件:

#mainContent {width: 100%;height: 100%;.top-panel {width: 100%;height: 30px;display: flex;align-items: center;border-bottom: 1px solid #cecece;.title-panel {width: 70%;height: 25px;display: flex;align-items: center;.equipmentType {width: 18px;height: 18px;margin-left: 5px;img {width: 100%;height: 100%;}}}/*操作栏样式:单聊*/.operate-panel {width: 29%;height: 25px;.ico-panel {width: 100%;height: 100%;display: flex;justify-content: flex-end;align-items: center;.item-panel {width: 20px;height: 20px;img {width: 100%;height: 100%;}}}}/*操作栏样式:群聊*/// .operate-group-panel{// }}::-webkit-scrollbar {width: 2px;/*滚动条宽度*/height: 6px;/*滚动条高度*/}.messages-panel {width: 100%;min-height: 400px !important;overflow-y: auto;max-height: 800px;overflow-x: hidden;padding-top: 5px;padding-bottom: 15px;.row-panel {width: 100%;min-height: 50px;/*对方消息样式*/.otherSide-panel {width: 96%;min-height: 50px;display: flex;margin-bottom: 15px;position: relative;.avatar-panel {width: 30px;min-width: 30px;height: 30px;border-radius: 50%;overflow: hidden;img {width: 100%;height: 100%;}}.user-name-panel {width: 240px;height: 20px;position: absolute;left: 42px;top: 2px;display: flex;justify-content: flex-start;align-items: center;p {color: #9da9c6;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;}}.msg-body {max-width: 95%;min-height: 40px;background: #f4f3f3;border-radius: 5px;display: flex;align-items: center;padding: 10px;box-sizing: border-box;margin-top: 28px;margin-left: 16px;position: relative;/*消息尾巴*/.tail-panel {width: 20px;height: 100%;position: absolute;left: -10px;svg {margin-top: 8px;color: #f3f3f3;}}p {font-size: 12px;/*自动换行*/word-wrap: break-word;overflow: hidden;cursor: pointer;img {width: 100%;height: 100%;}}}}/*发送者消息样式*/.sender-panel {width: 96%;min-height: 50px;float: right;margin-right: 12px;display: flex;justify-content: flex-end;margin-bottom: 15px;position: relative;.avatar-panel {width: 30px;min-width: 30px;height: 30px;border-radius: 50%;overflow: hidden;img {width: 100%;height: 100%;}}.user-name-panel {width: 240px;height: 20px;position: absolute;right: 42px;top: 2px;overflow: hidden;text-overflow: ellipsis;display: flex;justify-content: flex-end;align-items: center;p {color: #9da9c6;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;}}.msg-body {max-width: 95%;min-height: 40px;background: #d8e8dc;border-radius: 5px;display: flex;align-items: center;padding: 10px;box-sizing: border-box;margin-top: 28px;margin-right: 16px;position: relative;/*消息尾巴*/.tail-panel {width: 20px;height: 100%;position: absolute;right: -18px;svg {margin-top: 8px;color: #f3f3f3;}}p {font-size: 12px;line-height: 23px;// 强制换行word-break: break-all;display: flex;align-items: center;cursor: pointer;img {width: 100%;height: 100%;display: block;}}}}}}.send-panel {width: 70px;height: 30px;background-image: linear-gradient(-90deg, #29bdd9 0%, #276ace 100%);text-align: center;color: white;border-radius: 5px;line-height: 30px;cursor: pointer;float: right;margin-right: 5px;}.user-input-panel {width: 100%;height: 160px;position: relative;border-top: 1px solid #cecece;.toolbar-panel {width: 100%;height: 40px;display: flex;align-items: center;.item-panel {width: 24px;height: 24px;margin-right: 20px;display: flex;justify-content: center;align-items: center;img {width: 100%;height: 100%;}}}.input-panel {width: 100%;min-height: 30px;max-height: 120px;overflow-y: auto;outline: none;display: flex;align-items: center;flex-flow: row wrap;// 强制换行word-break: break-all;}/*表情面板*/.emoticon-panel {width: 290px;height: 250px;border-radius: 5px;background: white;border: solid 1px #dfe0e0;padding: 20px;box-sizing: border-box;position: absolute;top: -260px;// left: -194px;display: flex;justify-content: flex-start;z-index: 9999999;.row-panel {width: 100%;height: 30px;display: flex;align-items: center;flex-flow: row wrap;.item-panel {width: 25px;height: 25px;margin-right: 6px;margin-bottom: 7px;position: relative;// 取消12的倍数的元素的右外边距// &:nth-child(12n){//   margin-right: 0;// }img {width: 100%;height: 100%;&:hover {width: 26px;height: 26px;}}}}.ico-panel {width: 0;height: 0;border-right: 6px solid transparent;border-left: 6px solid transparent;border-top: 6px solid #dfe0e0;position: absolute;bottom: -6px;img {width: 100%;height: 100%;}}}}
}

JS:

import emoji from '../json/emoji';
import toolbar from '../json/toolbar';
import lodash from 'lodash';
import base from "./base";
import VueCookies from "vue-cookies";export default {name: "message-display",data() {return {danmu: null,id: '1',roomid: '',images: [],userID: '',worker:null,ws:null,isHide:true,messagesContainerTimer: "",onlineUsers: this.$store.state.onlineUsers,createDisSrc: require("../img/titlebar_function_createDis_normal@2x.png"),resourceObj: {createDisNormal: require("../img/titlebar_function_createDis_normal@2x.png"),createDisHover: require("../img/titlebar_function_createDis_hover@2x.png"),createDisClick: require("../img/titlebar_function_createDis_normal_p@2x.png"),phoneNormal: require("../img/phone_normal_ap@2x.png"),groupMsgImg: require("../img/group-msg-img.png"),avatarImg: require("../img/avatar.jpg"),msgImgTest: require("../img/msg-img-test.gif"),msgImgTestB: require("../img/msg-img-testB.gif"),},// 消息内容messageContent: "",InputContent: "",emoticonShowStatus: "none",emojiList: emoji,toolbarList: toolbar,senderMessageList: [],audioCtx: null,// 声音频率arrFrequency: [196.00, 220.00, 246.94, 261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25, 587.33, 659.25, 698.46, 783.99, 880.00, 987.77, 1046.50],testWorker: null}},mounted: function () {let that = thislet uid = this.$cookies.get('u_id')let course_id = this.$route.query.idlet s_id = this.$cookies.get('s_id')let tokens = this.$cookies.get('PHPSESSID')this.$store.commit("changeCourse", {uid: uid,s_id: s_id,name: course_id});function work() {onmessage = ({ data: { jobId, message ,classID} }) => {postMessage({ jobId, result: message});};}const makeWorker = f => {let hide = that.isHidelet pendingJobs = {};let worker = new Worker(URL.createObjectURL(new Blob([`(${f.toString()})()`])) //Blob = Binary Large Object的缩写,直译为二进制大对象);worker.onmessage = ({ data: { result, jobId,classID } }) => {let  ws = new WebSocket('wss://apps.beiqujy.com/count' +`?room_id=${result[0][0]}&uid=${result[0][1]}&type=3&from=2&class_id=${result[1]}`)ws.onopen = function () {let login_data = '{"type":3,"from":2,"client_name":"' + '' + '","room_id":"' + result[0][0]+ '","class_id":"' + result[1] + '","uid":"' + result[0][1] + '"}';console.log(login_data)ws.send('首次发送消息'+login_data);};ws.onmessage = function (evt) {var received_msg = evt.data;};ws.onclose = function () {if(that.isHide){reconnect('wss://apps.beiqujy.com/count' +`?room_id=${result[0][0]}&uid=${result[0][1]}&type=3&from=2&class_id=${result[1]}`)}};function reconnect(url) {var connectsclearTimeout(connects);connects = setTimeout(function () {     //没连接上会一直重连,设置延迟避免请求过多var ws = new WebSocket(url);ws.onopen = function () {let login_data = '{"type":3,"from":2,"client_name":"' + '' + '","room_id":"' + result[0][0]+ '","class_id":"' + result[1] + '","uid":"' + result[0][1] + '"}';console.log('重连后发送的消息'+login_data)ws.send(login_data);};ws.onmessage = function (evt) {var received_msgs = evt.data;};ws.onclose = function () {if(that.isHide){reconnect('wss://apps.beiqujy.com/count' +`?room_id=${result[0][0]}&uid=${result[0][1]}&type=3&from=2&class_id=${result[1]}`)}};}, 5000);}// 调用resolve,改变Promise状态pendingJobs[jobId](result);// 删掉,防止key冲突delete pendingJobs[jobId];};return (...message) =>new Promise(resolve => {const jobId = String(Math.random());pendingJobs[jobId] = resolve;worker.postMessage({ jobId, message });});};this.testWorker = makeWorker(work);    this.$nextTick(() => {this.testWorker([VueCookies.get("roomid"), this.$store.state.uid],this.$route.query.id).then(message => {});});//链接socketthis.$connect(process.env.VUE_APP_SOCKET + `?course_id=${course_id}&uid=${uid}&type='students'&from=2&listen_id=123`);this.userID = this.$store.state.uidvar i = 0// webAudioAPI兼容性处理window.AudioContext = window.AudioContext || window.webkitAudioContext;// 设置列容器高度this.$refs.messagesContainer.style.height = this.getThisWindowHeight() - 350 + "px";// 全局点击事件,点击表情框以外的地方,隐藏当前表情框document.addEventListener('click', (e) => {let thisClassName = e.target.className;if (thisClassName !== "emoticon-panel" && thisClassName !== "emoticon") {this.emoticonShowStatus = "none";}});//从本地存储中获取数据渲染页面this.renderPage("", "", 1);// 监听消息接收this.$options.sockets.onmessage = (res) => {const data = JSON.parse(res.data);console.log(data)// this.messageChange()this.$store.commit("changeMessage", {msg: data.data.message});if (data.code == 201 || data.code == 202  ) {return} else {// this.$store.state.onlineUsers = data.onlineUsers;// 更新在线人数// this.onlineUsers = data.onlineUsers;// 获取服务端推送的消息const msgObj = {msg: data.data.msg,avatarSrc: data.data.user_img,userID: data.data.uid,username: data.data.user_nicename};if (lodash.isEmpty(localStorage.getItem("msgArray"))) {this.renderPage(JSON.parse(localStorage.getItem("msgArray")), msgObj, 0);} else {this.renderPage(JSON.parse(localStorage.getItem("msgArray")), msgObj, 0);}}};},beforeDestroy() {// 页面销毁时,断开连接localStorage.setItem("msgArray", '[]')this.isHide = falsethis.closeWorker()this.$disconnect();},methods: {closeWorker() {// console.log(this.testWorker)// this.worker.terminate()this.testWorker=null},createDisEventFun: function (status) {if (status === "hover") {this.createDisSrc = this.resourceObj.createDisHover} else if (status === "leave") {this.createDisSrc = this.resourceObj.createDisNormal} else {this.createDisSrc = this.resourceObj.createDisClick}},getThisWindowHeight: () => window.innerHeight,getThisWindowWidth: () => window.innerWidth,sendMessage: function (event) {if (event.keyCode === 13) {// 阻止编辑框默认生成div事件event.preventDefault();let msgText = "";// 获取输入框下的所有子元素let allNodes = event.target.childNodes;for (let item of allNodes) {// 判断当前元素是否为img元素if (item.nodeName === "IMG") {msgText += `/${item.alt}/`;} else {// 获取text节点的值if (item.nodeValue !== null) {msgText += item.nodeValue;}}}// 消息发送: 发送文字,为空则不发送if (msgText.trim().length > 0) {var obj = {uid: parseInt( this.$cookies.get('u_id')),message: msgText,type:'students',from:2,event:1,listen_id:123,course_id: parseInt( this.$route.query.id)}this.$socket.sendObj(obj)event.target.innerHTML = "";}}},mobileSend: function () {// 模拟触发回车事件this.fireKeyEvent(this.$refs.msgInputContainer, 'keydown', 13);},//  渲染页面renderPage: function (msgArray, msgObj, status) {if (status === 1) {// 页面第一次加载,如果本地存储中有数据则渲染至页面let msgArray = [];if (localStorage.getItem("msgArray") !== null) {msgArray = JSON.parse(localStorage.getItem("msgArray"));for (let i = 0; i < msgArray.length; i++) {const thisSenderMessageObj = {"msgText": msgArray[i].msg,"msgId": i,"avatarSrc": msgArray[i].avatarSrc,"userID": msgArray[i].userID,"username": msgArray[i].username};// 更新消息内容this.messageContent = msgArray[i].msg;// 向父组件传值this.$emit('updateLastMessage', this.messageContent);// 解析并渲染this.messageParsing(thisSenderMessageObj);}}} else {// 判断本地存储中是否有数据if (localStorage.getItem("msgArray") === null) {// 新增记录msgArray.push(msgObj);// 更新消息内容this.messageContent = msgObj.msg;// 向父组件传值this.$emit('updateLastMessage', this.messageContent);localStorage.setItem("msgArray", JSON.stringify(msgArray));for (let i = 0; i < msgArray.length; i++) {const thisSenderMessageObj = {"msgText": msgArray[i].msg,"msgId": i,"avatarSrc": msgArray[i].avatarSrc,"userID": msgArray[i].userID,"username": msgArray[i].username};// 解析并渲染this.messageParsing(thisSenderMessageObj);}} else {// 更新记录msgArray = JSON.parse(localStorage.getItem("msgArray"));msgArray.push(msgObj);localStorage.setItem("msgArray", JSON.stringify(msgArray));// 更新消息内容this.messageContent = msgObj.msg;// 向父组件传值this.$emit('updateLastMessage', this.messageContent);const thisSenderMessageObj = {"msgText": msgObj.msg,"msgId": Date.now(),"avatarSrc": msgObj.avatarSrc,"userID": msgObj.userID,"username": msgObj.username};// 解析并渲染this.messageParsing(thisSenderMessageObj);}}},// 模拟触发事件fireKeyEvent: function (el, evtType, keyCode) {let doc = el.ownerDocument,win = doc.defaultView || doc.parentWindow,evtObj;if (doc.createEvent) {if (win.KeyEvent) {evtObj = doc.createEvent('KeyEvents');evtObj.initKeyEvent(evtType, true, true, win, false, false, false, false, keyCode, 0);} else {evtObj = doc.createEvent('UIEvents');Object.defineProperty(evtObj, 'keyCode', {get: function () {return this.keyCodeVal;}});Object.defineProperty(evtObj, 'which', {get: function () {return this.keyCodeVal;}});evtObj.initUIEvent(evtType, true, true, win, 1);evtObj.keyCodeVal = keyCode;if (evtObj.keyCode !== keyCode) {console.log("keyCode " + evtObj.keyCode + " 和 (" + evtObj.which + ") 不匹配");}}el.dispatchEvent(evtObj);} else if (doc.createEventObject) {evtObj = doc.createEventObject();evtObj.keyCode = keyCode;el.fireEvent('on' + evtType, evtObj);}},// 消息解析messageParsing: function (msgObj) {console.log(msgObj)// 解析接口返回的数据进行渲染let separateReg = /(\/[^/]+\/)/g;let msgText = msgObj.msgText;let finalMsgText = "";if(msgText && msgText != undefined){// 将符合条件的字符串放到数组里const resultArray = msgText.match(separateReg);if (resultArray !== null) {for (let item of resultArray) {// 删除字符串中的/符号item = item.replace(/\//g, "");// 判断是否为图片: 后缀为.jpegif (this.isImg(item)) {const imgSrc = `${base.lkBaseURL}/uploads/chatImg/${item}`;// 获取图片宽高let imgInfo = {"imgWidth": this.getQueryVariable(imgSrc, "width"),"imgHeight": this.getQueryVariable(imgSrc, "height")};let thisImgWidth = 0;let thisImgHeight = 0;if (imgInfo.imgWidth < 400) {thisImgWidth = imgInfo.imgWidth;thisImgHeight = imgInfo.imgHeight;} else {// 缩放四倍thisImgWidth = imgInfo.imgWidth / 4;thisImgHeight = imgInfo.imgHeight / 4;}// 找到item中?位置,在?之前添加\\进行转义,解决正则无法匹配特殊字符问题const charIndex = item.indexOf("?");// 生成正则表达式条件,添加\\用于对?的转义const regularItem = this.insertStr(item, charIndex, "\\");// 解析为img标签const imgTag = `<img width="${thisImgWidth}" height="${thisImgHeight}" src="${imgSrc}" alt="聊天图片">`;// 替换匹配的字符串为img标签:全局替换msgText = msgText.replace(new RegExp(`/${regularItem}/`, 'g'), imgTag);}// 表情渲染: 遍历表情配置文件for (let emojiItem of this.emojiList) {// 判断捕获到的字符串与配置文件中的字符串是否相同if (emojiItem.info === item) {const imgSrc = require(`../img/emoji/${emojiItem.hover}`);const imgTag = `<img src="${imgSrc}" width="28" height="28" alt="${item}">`;// 替换匹配的字符串为img标签:全局替换msgText = msgText.replace(new RegExp(`/${item}/`, 'g'), imgTag);}}}finalMsgText = msgText;} else {finalMsgText = msgText;}}msgObj.msgText = finalMsgText;// 渲染页面this.senderMessageList.push(msgObj);let hash = {}this.senderMessageList = this.senderMessageList.reduce((prev, array) => {if (array.username == undefined) {hash[array.username] ? '' : hash[array.username] = true && prev.push(array)} else {prev.push(array)}return prev}, [])// 修改滚动条位置this.$nextTick(function () {this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;});},// 显示表情toolbarSwitch: function (status, event, path, hoverPath, downPath, toolItemName) {if (status === "hover" || status === "up") {event.target.src = require(`../img/${hoverPath}`);} else if (status === "leave") {event.target.src = require(`../img/${path}`);} else {// 可编辑div获取焦点this.getEditableDivFocus();event.target.src = require(`../img/${downPath}`);// 表情框显示条件if (toolItemName === "emoticon") {if (this.emoticonShowStatus === "flex") {this.emoticonShowStatus = "none";} else {this.emoticonShowStatus = "flex";}} else {this.emoticonShowStatus = "none";}}},// 判断一个对象是否为函数类型isFunction: function (obj) {return typeof obj === "function" && typeof obj.nodeType !== "number";},// 表情框鼠标悬浮显示动态表情emojiConversion: function (event, status, path, hoverPath, info) {if (status === "over") {event.target.src = require(`../img/emoji/${hoverPath}`);} else if (status === "click") {// 表情输入const imgSrc = require(`../img/emoji/${hoverPath}`);const imgTag = `<img src="${imgSrc}" width="28" height="28" alt="${info}">`;document.execCommand("insertHTML", false, imgTag);} else {event.target.src = require(`../img/emoji/${path}`);}},// base64转fileconvertBase64UrlToImgFile: function (urlData, fileName, fileType) {// 转换为bytelet bytes = window.atob(urlData);// 处理异常,将ascii码小于0的转换为大于0let ab = new ArrayBuffer(bytes.length);let ia = new Int8Array(ab);for (let i = 0; i < bytes.length; i++) {ia[i] = bytes.charCodeAt(i);}// 转换成文件,添加文件的type,name,lastModifiedDate属性let blob = new Blob([ab], { type: fileType });blob.lastModifiedDate = new Date();blob.name = fileName;return blob;},// 判断是否为图片isImg: function (str) {return str.indexOf(".jpeg") !== -1;},viewLargerImage: function (event) {const imgSrc = event.target.src;if (typeof imgSrc !== "undefined") {// 清空图片数组this.images = [];this.images.push(imgSrc);this.show();}},// 获取url参数getQueryVariable: function (url, variable) {// 对url进行截取url = url.substring(url.indexOf("?"), url.length);var query = url.substring(1);var vars = query.split("&");for (var i = 0; i < vars.length; i++) {var pair = vars[i].split("=");if (pair[0] == variable) { return pair[1]; }}return false;},// 字符串指定位置添加字符insertStr: function (source, start, newStr) {return source.slice(0, start) + newStr + source.slice(start);},// 可编辑div获取焦点getEditableDivFocus: function () {// 开头获取焦点this.$refs.msgInputContainer.focus();},// 图片查看插件show() {const viewer = this.$el.querySelector('.images').$viewerviewer.show()}},
}

至于表情包和一些聊天小组件,有需要的童鞋可以留言。

vue手把手带你创建聊天室(vue-native-websocket)相关推荐

  1. 视频教程-在Vue中使用GraphQL实现聊天室-Vue

    在Vue中使用GraphQL实现聊天室 某大厂资深前端工程师,多个大型web项目经验,JS全栈工程师,擅长前端工程化建设.hybrid技术方向. 大碗 ¥39.00 立即订阅 扫码下载「CSDN程序员 ...

  2. 撸一个聊天室(vue+koa2+websokect+mongodb)

    撸一个聊天室(vue+koa2+websokect+mongodb) 本篇博客主要介绍聊天室项目,作者学习vue和node时间较短,若有什么错误或建议,欢迎指出,谢谢~ 贴上源码链接 -> 源码 ...

  3. nuxt-chat聊天室|vue仿微信/探探界面nuxt+vue+vuex|朋友圈

    Nuxt-Chatroom 基于Nuxt.js+Vue.js仿微信|探探App界面聊天社交 运用nuxt.js+vue.js+vuex+vpopup+vant+webpack等技术架构开发的社交聊天室 ...

  4. 安装uve-cli,并使用vue ui可视化界面创建第一个vue项目

    安装vue-cli,并使用vue ui可视化界面创建第一个vue项目 下载vue-cli ,因为国内下载镜像比较慢,使用阿里的镜像仓库 npm install -g cnpm --registry=h ...

  5. 自己封装的环信接口,包括授权注册、创建聊天室、添加成员、发送信息等

    使用tp5开发,主要使用的是授权注册,在七牛直播间进行使用 地址 代码篇 <?php namespace lib; class Easemob{private $client_id = '';p ...

  6. Vue手把手带你入门(一) nodejs安装配置以及vue-cli脚手架创建第一个vue项目(超级详细)

    开发Vue程序的第一步就是PC安装配置node.js 1官网下载安装Node(我的安装路径是D:\nodejs\) 点我安装(安装在除C盘外任意一个磁盘中) 2 检查node和npm是否安装完成 下载 ...

  7. 基于 vue.js 的仿QQ聊天室

    简介 这是一款基于 vue.js 开发的聊天室组件库,在提供基础封装的同时,最大程度的增加扩展性. 下面是效果演示图: MChat组件效果图: IChat组件效果图: 如何安装 使用 npm 安装 n ...

  8. vue仿QQ聊天室|vue聊天实例,直播聊天室

    图片压缩 百亿站点 基于vue2.0+vue-cli+vuex+vue-router+webpack+es6+wcPop等技术开发的仿微信聊天界面|仿微信聊天室vue-chatRoom,实现了微信聊天 ...

  9. Vue+WebSocket-实现多人聊天室

    在前端中 WebSocket 是H5新增的对象 主要作用有:实时通讯  长连接  双向传输  后端主动推送数据 websocket实例的主要事件 前端: 直接new 一个实例 const ws = n ...

最新文章

  1. ipa解包打包工具_7步!教你轻松搞定ios重签ipa包
  2. rockemq 发送延迟消息_RockeMQ通过代码监控消费者状态
  3. 用php写京东抢购,关于抢京东券高并发的问题?
  4. 『数据中心』降低PUE值4种方法
  5. ML之FE:数据处理—特征工程之数据集划分成训练集、验证集、测试集三部分简介、代码实现、案例应用之详细攻略
  6. Oracle数据库的测试用户Scott的密码为什么是Tiger?
  7. Python学习(二)语言基础
  8. 腾讯技术研究类和数据分析第一次笔试(2021.8.22)——Python
  9. BAT面试进阶:最全Memcached面试30题含答案
  10. Java基础篇:如何使用 break 退出循环
  11. 微信开发值得推荐的开源项目
  12. HTML5标准制定完成,浏览器大战能消停吗?
  13. PHP僵尸网络,byob--建立自己的僵尸网络
  14. linux build文件,从源代码到可执行文件——编译全过程解析
  15. 法学专业能从事计算机工作吗,未来20年,这5个专业都是“香饽饽”,毕业生工作好找前途大好!...
  16. 【开源】小桥流水秒赞 3.8版本全解密无后门
  17. Qt跨平台框架在金融领域必然性
  18. C语言·XDOJ练习·股票计算
  19. dayjs 取本周时间段
  20. private、public、protected

热门文章

  1. 最近准备进行做GPS静态
  2. 河南中牟:依托大数据构建就医新格局
  3. Java基于springboot+vue的图书馆网上图书借阅系统 nodejs前后端分离
  4. Jmeter badboy脚本开发技术
  5. Android Texture 相关
  6. 【年鉴分享】2001—2022年中国县域统计年鉴(Pdf/Excel版本/无需转发)
  7. 数学建模论文写作方法之一(符号说明)
  8. 计算机毕业设计springboot教务管理系统mh0ch源码+系统+程序+lw文档+部署
  9. 校园考勤的好帮手:智能蓝牙定位手环
  10. 30分钟(零成本)快速搭建markdown个人github博客