实时聊天
- 知识点
- WebSocket介绍
- SockJS
- STOMP
- 开发环境
- 功能实现
- 安装应用
- 在vuex中创建
- vue中的引入、监听、实例化与收发、订阅消息
- 引入组件
- 实例化与订阅
- 计算属性
- 监听收到消息
- 封装的发送消息的公共方法
- 发送消息
- 完整的代码
知识点
WebSocket介绍
WebSocket 是一种在 Web 应用中实现实时通信的方法,它可以在客户端和服务器端之间建立长连接,实现实时消息传递。
SockJS
SockJS 是一个 JavaScript 库,用于在浏览器和 Web 服务器之间建立实时通信连接。它提供了一个 WebSocket 的备选方案,并兼容多种浏览器和 Web 服务器。SockJS 会自动检测浏览器是否支持 WebSocket,如果不支持,则会自动降级为其他协议(如 long polling、iframe、JSONP 等)
STOMP
STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议,它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。
简单地说,stomp是一个用于client之间进行异步消息传输的简单文本协议
开发环境
"sockjs-client": "^1.5.1",
"stompjs": "^2.3.3",
"vue": "^2.6.10",
"vuex": "^3.6.2"
// 本地Node版本号
node v14.7.0
功能实现
安装应用
npm install sockjs-client --save
npm install stompjs --save
在vuex中创建
const call = {state: {websocketUrl: 'https://app.lzzgh.org.cn/lz72hourApi',stompClient: '', // websocketsendContent: "",//发送的数据userPhone: "",//给对方发送消息对方的userPhonechatType: "",//聊天的方式 1:单聊 2:群聊activitySign:false,//是否参加活动groupContent:"",//群聊发送数据groupCPContent:"", // cp小屋的clears:false,//清空消息},getters: {getWebsocketUrl: state => {return state.websocketUrl},getWebsocket: state => {return state.stompClient},getSendContent: state => {return state.sendContent},getGroupContent: state => {return state.groupContent},getGroupCPContent: state => {return state.groupCPContent},getUserPhone: state => {return state.userPhone},getChatType: state => {return state.chatType},getActivitySign:state =>{return state.activitySign},getClears:state =>{return state.clears}},mutations: {setMyWebsocket(state, stompClient) {state.stompClient = stompClient},setMySendContent(state, sendContent) {state.sendContent = sendContent},setMyGroupContent(state, groupContent) {state.groupContent = groupContent},setMyGroupCPContent(state, groupCPContent) {state.groupCPContent = groupCPContent},setMyUserPhone(state, userPhone) {state.userPhone = userPhone},setMyChatType(state, chatType) {state.chatType = chatType},setMyActivitySign(state){state.activitySign = !state.activitySign},setMyClears(state){state.clears = !state.clears}},actions: {setWebsocket({ commit }, stompClient) {commit('setMyWebsocket', { stompClient })},setSendContent({ commit }, sendContent) {commit('setMySendContent', { sendContent })},setGroupContent({ commit }, groupContent) {commit('setMyGroupContent', { groupContent })},setGroupCPContent({ commit }, groupCPContent) {commit('setMyGroupCPContent', { groupCPContent })},setUserPhone({ commit }, userPhone) {commit('setMyUserPhone', { userPhone })},setChatType({ commit }, chatType) {commit('setMyChatType', { chatType })}}
}
export default call
vue中的引入、监听、实例化与收发、订阅消息
引入组件
import { mapGetters } from "vuex";
import Video from "@/components/video"; //播放
import { imgPreview } from "@/utils/comperssImage.js"; //压缩图片
import { judgeTime } from "@/utils/base.js"; //时间显示
import { Picker } from "emoji-mart-vue"; //引入表情组件
import SockJS from 'sockjs-client';
import Stomp from 'stompjs';
实例化与订阅
init(){ axios.defaults.baseURL=this.websocketUrlaxios.defaults.headers={ "X-Access-Token": Vue.ls.get(ACCESS_TOKEN)}if (typeof WebSocket === "undefined") {alert("您的浏览器不支持socket");} else {console.log(this.websocketUrl)// 实例化let socket = new SockJS(this.websocketUrl + "/jeecg-boot/websocket",undefined, { timeout: 10000 });this.stompClient = Stomp.over(socket);this.$store.commit("setMyWebsocket", this.stompClient);let that = this;console.log(Vue.ls.get(ACCESS_TOKEN))// 订阅this.stompClient.connect({ "X-Access-Token": Vue.ls.get(ACCESS_TOKEN) }, function (frame) {that.isConnent = true;that.stompClient.subscribe("/topic/" + that.chatRoomId,function (response) {let data = response.body;that.$store.commit("setMyGroupCPContent", data);console.log("小屋" + response.body);});})}},
计算属性
computed: {...mapGetters({userInfo: "getUserInfo", //获取用户信息websocketUrl: "getWebsocketUrl",// stompClient: "getWebsocket", //获取websocket// sendContent: "getSendContent", //获取实时聊天信息sendContent: "getGroupCPContent", //获取实时聊天信息}),},
监听收到消息
watch: {sendContent() {debuggerToast.clear();let datas = JSON.parse(this.sendContent);if (datas.code == 500) {Dialog.alert({message: datas.message,}).then(() => {// on closethis.$router.go(-1);});return;}this.message = "";this.quoteText = "";this.quoteValue = null;this.smileShow = false;let mesItem = datas.result;mesItem.isOprate = false;if (mesItem.messageType == "6") {mesItem.poster = this.getImgView(mesItem.messageValue);}if (mesItem.quoteValue) {mesItem.quoteValue = JSON.parse(mesItem.quoteValue);}if (mesItem.delFlag == "1") {if (mesItem.memberId == this.myInfo.id &&mesItem.chatRoomId == this.chatRoomId) {Dialog.alert({message: "您已被踢出群聊,暂时不能参与会话!",confirmButtonText: "返回到首页",}).then(() => {// on closethis.$router.replace("/index");});}} else {let time = mesItem.createTime;if (mesItem.chatRoomId == this.chatRoomId) {if (this.talkArr.length > 0 &&new Date(time.replace(/\-/g, "/")).getTime() -new Date(this.talkArr[this.talkArr.length - 1].createTime.replace(/\-/g,"/")).getTime() <=5 * 60 * 1000) {this.talkArr[this.talkArr.length - 1].mesList.push(mesItem);} else {this.talkArr.push({createTime: time,time: judgeTime(time),mesList: [mesItem],});}console.log(this.talkArr)debuggerthis.seeMsg();if (!this.isLoading) {this.scrollBottom();}}}},},
封装的发送消息的公共方法
sendFun(messageType, messageValue, quoteValue) {// console.log(messageValue)Toast.loading({duration: 0, // 持续展示 toastmessage: "发送中...",forbidClick: true,});let date = new Date();let createTime = date.getTime();let prm=JSON.stringify({chatRoomId: this.chatRoomId, //群聊idmessageType: messageType, //1文字,5图片,6视频,7求手机号、8求微信号、9发手机号、10发微信号messageValue: messageValue, //发送信息// userPhone: this.myInfo.userPhone, //用户手机号// fullName: this.myInfo.fullName, //用户名// userIcon: this.myInfo.userIcon, //用户头像// memberId: this.myInfo.id, //用户idmemberId: sessionStorage.getItem('clientId'), //用户id// createTime: createTime, //创建时间// sex: this.myInfo.sex, //性别quoteValue: JSON.stringify(quoteValue), //引用的内容})console.log(prm)this.stompClient.send("/app/chatRoom",{},prm);},
发送消息
//发送消息sendMsg() {const that = this;if (this.message) {this.sendFun("1", this.message, this.quoteValue);// that.message = "";// that.quoteText = "";// that.quoteValue = null;} else {Toast("内容不能为空!");}},
完整的代码
聊天的完整代码,包括websocket的应用,(实例化,存vuex,订阅,收发消息),表情,上传图片,视频,历史记录,时间显示,长链接的建立与销毁等
<template><div class="groupChatInf"><!-- <NavBar :title="title"></NavBar> --><div class="chatInf"><div class="chatBox" id="chat"><van-pull-refresh v-model="isLoading" @refresh="onRefresh"><div class="chatOne" v-for="(item, i) in talkArr" :key="i"><div class="date">{{ item.time }}</div><div v-for="(items, is) in item.mesList" :key="is"><divclass="welcome"v-html="welcome"v-if="welcome && items.messageType == '4'"></div><div v-if="items.messageType != '4'"><divclass="chatMine"v-if="items.userPhone == myInfo.userPhone"><divclass="chatImg"v-if="items.messageType != '12' && items.messageType != '15'"><van-image:src="items.userIcon"fit="cover"v-if="items.userIcon"><template v-slot:loading><van-loading type="spinner" size="20" /></template><template v-slot:error>加载失败</template></van-image><imgsrc="~@/assets/img/redGirl.png"alt=""v-else-if="items.memberId == 9999999"class="imgs"/><imgsrc="~@/assets/img/girlHeadImg.png"alt=""v-else-if="items.sex == 'F'"class="imgs"/><imgsrc="~@/assets/img/boyHeadImg.png"alt=""class="imgs"v-else/></div><divclass="chatName"v-if="items.messageType != '12' && items.messageType != '15'">我<i v-if="items.isAdmin == '1'"><img src="~@/assets/img/manage2.png"/></i><i v-if="items.isAdmin == '2'"><img src="~@/assets/img/manage1.png"/></i></div><div class="chatvalueDiv" v-if="items.messageType == '1'"><divclass="chatText"@touchstart="touchstart(items)"@touchend="touchend">{{ items.messageValue }}</div><divclass="chatOprateMask"v-if="items.isOprate"@click="closeMask(items)"></div><div class="chatOprate" v-if="items.isOprate"><span @click="quoteChat(items)">引用</span><span @click="delChat(items)">删除</span><span @click="copyChat(items)">复制</span><span @click="recallChat(items)">撤回</span></div></div><div class="chatvalueDiv" v-if="items.messageType == '5'"><divclass="chatImage"@click="imgPreview(items.messageValue)"><img :src="items.messageValue" alt="" /></div><divclass="chatOprateMask"v-if="items.isOprate"@click="closeMask(items)"></div><div class="chatOprate" v-if="items.isOprate"><span @click="quoteChat(items)">引用</span><span @click="delChat(items)">删除</span><span @click="copyChat(items)">复制</span><span @click="recallChat(items)">撤回</span></div></div><div class="chatvalueDiv" v-if="items.messageType == '6'"><div class="chatVideo"><video:src="items.messageValue":poster="items.poster"></video><divclass="chatmask"@click="videoPlay(items.messageValue, items.poster)"><img src="~@/assets/bofang.png" alt="" /></div></div><divclass="chatOprateMask"v-if="items.isOprate"@click="closeMask(items)"></div><div class="chatOprate" v-if="items.isOprate"><span @click="quoteChat(items)">引用</span><span @click="delChat(items)">删除</span><span @click="copyChat(items)">复制</span><span @click="recallChat(items)">撤回</span></div></div><divclass="chatvalueDiv"v-if="items.quoteValue && items.messageType != '12'"><divclass="quoteValue"v-if="items.quoteValue.messageType == '1'">@{{items.quoteValue.fullName +items.quoteValue.messageValue}}</div><divclass="quoteImg"v-else-if="items.quoteValue.messageType == '5'"><p>@{{ items.quoteValue.fullName }}</p><img:src="items.quoteValue.messageValue"alt=""class="qimgs"@click="imgPreview(items.quoteValue.messageValue)"/></div><divclass="quoteImg"v-else-if="items.quoteValue.messageType == '6'"><p>@{{ items.quoteValue.fullName }}</p><div class="quoteVideo"><video:src="items.quoteValue.messageValue":poster="items.quoteValue.poster"></video><divclass="chatmask"@click="videoPlay(items.quoteValue.messageValue,items.quoteValue.poster)"><img src="~@/assets/bofang.png" alt="" /></div></div></div></div><div class="chatRecall" v-if="items.messageType == '12'">消息已撤回 <a @click="reEdit(items)">重新编辑</a></div></div><div class="chatOther" v-else><divclass="chatImg"@click="toPerson(items.memberId)"v-if="items.messageType != '12' && items.messageType != '15'"><van-image:src="items.userIcon"fit="cover"v-if="items.userIcon"><template v-slot:loading><van-loading type="spinner" size="20" /></template><template v-slot:error>加载失败</template></van-image><imgsrc="~@/assets/img/girlHeadImg.png"alt=""v-else-if="items.sex == 'F'"class="imgs"/><imgsrc="~@/assets/img/boyHeadImg.png"alt=""class="imgs"v-else/><imgsrc="~@/assets/img/woman.png"alt=""class="sex"v-if="items.sex == 'F'"/><imgsrc="~@/assets/img/man.png"alt=""class="sex"v-if="items.sex == 'M'"/></div><divclass="chatName"v-if="items.messageType != '12' && items.messageType != '15'">{{ items.fullName}}<i v-if="items.isAdmin == '1'"><img src="~@/assets/img/manage2.png"/></i><i v-if="items.isAdmin == '2'"><img src="~@/assets/img/manage1.png"/></i></div><div class="chatvalueDiv" v-if="items.messageType == '1'"><divclass="chatText"@touchstart="touchstart(items)"@touchend="touchend">{{ items.messageValue }}</div><divclass="chatOprateMask"v-if="items.isOprate"@click="closeMask(items)"></div><div class="chatOprate" v-if="items.isOprate"><span @click="quoteChat(items)">引用</span><span @click="delChat(items)">删除</span><span @click="copyChat(items)">复制</span></div></div><div class="chatvalueDiv" v-if="items.messageType == '5'"><divclass="chatImage"@click="imgPreview(items.messageValue)"><img :src="items.messageValue" alt="" /></div><divclass="chatOprateMask"v-if="items.isOprate"@click="closeMask(items)"></div><div class="chatOprate" v-if="items.isOprate"><span @click="quoteChat(items)">引用</span><span @click="delChat(items)">删除</span><span @click="copyChat(items)">复制</span></div></div><div class="chatvalueDiv" v-if="items.messageType == '6'"><div class="chatVideo"><video:src="items.messageValue":poster="items.poster"></video><divclass="chatmask"@click="videoPlay(items.messageValue, items.poster)"><img src="~@/assets/bofang.png" alt="" /></div></div><divclass="chatOprateMask"v-if="items.isOprate"@click="closeMask(items)"></div><div class="chatOprate" v-if="items.isOprate"><span @click="quoteChat(items)">引用</span><span @click="delChat(items)">删除</span><span @click="copyChat(items)">复制</span></div></div><divclass="chatvalueDiv"v-if="items.quoteValue && items.messageType != '12'"><divclass="quoteValue"v-if="items.quoteValue.messageType == '1'">@{{items.quoteValue.fullName +items.quoteValue.messageValue}}</div><divclass="quoteImg"v-else-if="items.quoteValue.messageType == '5'"><p>@{{ items.quoteValue.fullName }}</p><img:src="items.quoteValue.messageValue"alt=""class="qimgs"@click="imgPreview(items.quoteValue.messageValue)"/></div><divclass="quoteImg"v-else-if="items.quoteValue.messageType == '6'"><p>@{{ items.quoteValue.fullName }}</p><div class="quoteVideo"><video:src="items.quoteValue.messageValue":poster="items.quoteValue.poster"></video><divclass="chatmask"@click="videoPlay(items.quoteValue.messageValue,items.quoteValue.poster)"><img src="~@/assets/bofang.png" alt="" /></div></div></div></div><div class="chatRecall" v-if="items.messageType == '12'">{{ items.fullName }}撤回了一条消息</div><div class="chatRecall" v-if="items.messageType == '15'"><a>{{ items.createBy }}</a>加入了群聊</div></div></div></div></div></van-pull-refresh></div></div><div class="sendBox"><div class="sendBoxTop"><form><!-- @click="toMember" --><span class="sendMember" ><img src="~@/assets/img/member.png" alt="" /></span><van-fieldclass="sendInf"v-model="message":border="false"style="border: 0px;"@keyup.enter.native="sendMsg"/><div class="quoteMessage" v-if="quoteText"><p class="quoteText">{{ quoteText }}</p><imgsrc="~@/assets/img/mclose.png"alt=""class="quoteClose"@click="quoteClose"/></div><span class="sendExpression" @click="smile"><img src="~@/assets/img/smile.png" alt="" /></span><span class="sendBtn" @click="sendMsg"><img src="~@/assets/img/sendBtn.png" alt=""/></span></form></div><div class="emojiBox" @select="addEmoji" v-if="smileShow"><div class="emojiDiv"><spanv-for="(item, index) in emojiList":key="'e-' + index"@click="chooseEmoji(item)">{{ item.emoji }}</span></div></div><!-- <picker:include="['people', 'Smileys']":showSearch="false":showPreview="false":showCategories="false"@select="addEmoji"v-if="smileShow"/> --><div class="sendBoxBtm" v-if="!smileShow"><div class="sendOprate"><img src="~@/assets/img/micon1.png" alt="" /><van-uploader:after-read="afterRead"upload-icon="plus":before-read="beforeRead"accept="image/png,image/jpg,image/jpeg,video/mp4,video/mov":max-count="1"/></div><div class="sendOprate"><img src="~@/assets/img/micon2.png" alt="" /><!--是苹果手机直接调用摄像头--><van-uploader:after-read="afterReadVideo"upload-icon="plus":before-read="beforeReadVideo"accept="image/png,image/jpg,image/jpeg,video/mp4,video/mov":max-count="1"capture="camera"v-if="isIos"/><!--不是苹果手机先调起相册--><van-uploader:after-read="afterReadVideo"upload-icon="plus":before-read="beforeReadVideo"accept="image/png,image/jpg,image/jpeg,video/mp4,video/mov":max-count="1"v-if="!isIos"/></div><div class="sendOprate"><img src="~@/assets/img/nowx.png" alt="" /></div><div class="sendOprate"><img src="~@/assets/img/nophone.png" alt="" /></div></div></div><Video:videoPath="videoPath"v-if="videoShow"@videoFun="videoFun":posterPath="posterPath"></Video></div>
</template><script>
import {Image as vanImage,Toast,PullRefresh,Field,Loading,Dialog,ImagePreview,Uploader,
} from "vant";
import { mapGetters } from "vuex";
import Video from "@/components/video"; //播放
import { imgPreview } from "@/utils/comperssImage.js"; //压缩图片
import { judgeTime } from "@/utils/base.js"; //时间显示
import { Picker } from "emoji-mart-vue"; //引入表情组件
import SockJS from 'sockjs-client';
import Stomp from 'stompjs';
import Vue from 'vue'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import axios from 'axios'export default {name: "GroupChat",components: {[vanImage.name]: vanImage,[Field.name]: Field,[PullRefresh.name]: PullRefresh,[Loading.name]: Loading,[Uploader.name]: Uploader,Video,Picker,},props:{records:{type: Object,default: ()=>({})}},data() {return {stompClient:"",message: "", //发送内容talkArr: [], //聊天信息title: "聊天", //标题isToday: false, //是否是今天isLoading: false, //是否在加载中params: {chatRoomId: "", //群聊idpageNo: 1, //页数pageSize: 50, //每页数据数量oldMesId: "", //最后一条消息id}, //群聊查询历史数据参数chatRoomId: "", //群聊idallPage: 0, //历史消息总页数myInfo: {}, //个人信息welcome: "", //欢迎信息isFirst: true, //是否首次进入quoteText: "", //引用的文字videoShow: false, //播放器插件是否出现videoPath: "", //视频路径posterPath: "", //默认图路径touch: null, //长按方法定时器reMessage: "", //要重新编辑的信息fileList: [], //上传图片视频返回值videoList: [], //拍摄返回值isIos: false, //是否是苹果手机quoteValue: null, //引用的详细信息isClick: true, //操作按钮是否可以点击smileShow: false, //imgUrl:'',};},computed: {...mapGetters({userInfo: "getUserInfo", //获取用户信息websocketUrl: "getWebsocketUrl",// stompClient: "getWebsocket", //获取websocket// sendContent: "getSendContent", //获取实时聊天信息sendContent: "getGroupCPContent", //获取实时聊天信息}),},watch: {sendContent() {debuggerToast.clear();let datas = JSON.parse(this.sendContent);if (datas.code == 500) {Dialog.alert({message: datas.message,}).then(() => {// on closethis.$router.go(-1);});return;}this.message = "";this.quoteText = "";this.quoteValue = null;this.smileShow = false;let mesItem = datas.result;mesItem.isOprate = false;if (mesItem.messageType == "6") {mesItem.poster = this.getImgView(mesItem.messageValue);}if (mesItem.quoteValue) {mesItem.quoteValue = JSON.parse(mesItem.quoteValue);}if (mesItem.delFlag == "1") {if (mesItem.memberId == this.myInfo.id &&mesItem.chatRoomId == this.chatRoomId) {Dialog.alert({message: "您已被踢出群聊,暂时不能参与会话!",confirmButtonText: "返回到首页",}).then(() => {// on closethis.$router.replace("/index");});}} else {let time = mesItem.createTime;if (mesItem.chatRoomId == this.chatRoomId) {if (this.talkArr.length > 0 &&new Date(time.replace(/\-/g, "/")).getTime() -new Date(this.talkArr[this.talkArr.length - 1].createTime.replace(/\-/g,"/")).getTime() <=5 * 60 * 1000) {this.talkArr[this.talkArr.length - 1].mesList.push(mesItem);} else {this.talkArr.push({createTime: time,time: judgeTime(time),mesList: [mesItem],});}console.log(this.talkArr)debuggerthis.seeMsg();if (!this.isLoading) {this.scrollBottom();}}}},},created() {this.imgUrl=this.websocketUrl+'/jeecg-boot/sys/common/static'const that = this;const UA = navigator.userAgent;const isIpad = /(iPad).*OS\s([\d_]+)/.test(UA);const isIpod = /(iPod)(.*OS\s([\d_]+))?/.test(UA);const isIphone = !isIpad && /(iPhone\sOS)\s([\d_]+)/.test(UA);this.isIos = isIpad || isIpod || isIphone;if (this.$route.query.activityName) {this.title = this.$route.query.activityName;}this.type = this.$route.query.type;if (this.$route.query.activityId) {this.chatRoomId = this.$route.query.activityId;this.params.chatRoomId = this.$route.query.activityId;}console.log(this.records)this.chatRoomId = this.records.chatId;this.params.chatRoomId = this.records.chatId;this.init()// this.getpersonfo();//查看未读消息this.seeMsg();//获取历史消息this.getHistory();this.getEmoji();},// mounted() {},destroyed() {// 取消订阅this.stompClient.unsubscribe('/topic/'+this.chatRoomId)// 销毁连接this.stompClient.disconnect()}, methods: {// 初始化init(){axios.defaults.baseURL=this.websocketUrlaxios.defaults.headers={ "X-Access-Token": Vue.ls.get(ACCESS_TOKEN)}if (typeof WebSocket === "undefined") {alert("您的浏览器不支持socket");} else {console.log(this.websocketUrl)let socket = new SockJS(this.websocketUrl + "/jeecg-boot/websocket",undefined, { timeout: 10000 });this.stompClient = Stomp.over(socket);this.$store.commit("setMyWebsocket", this.stompClient);let that = this;console.log(Vue.ls.get(ACCESS_TOKEN))this.stompClient.connect({ "X-Access-Token": Vue.ls.get(ACCESS_TOKEN) }, function (frame) {that.isConnent = true;that.stompClient.subscribe("/topic/" + that.chatRoomId,function (response) {let data = response.body;that.$store.commit("setMyGroupCPContent", data);console.log("小屋" + response.body);});})}},//获取表情包getEmoji() {axios.get("/jeecg-boot/api/emoji/emojiList").then((res) => {if (res.code == 200) {this.emojiList = res.result;}}).catch(() => {});},//获取个人资料getpersonfo() {axios.get("/jeecg-boot/api/member/memberDetails").then((res) => {if (res.code == 200) {this.myInfo = res.result;}}).catch(() => {});},//发送消息sendMsg() {const that = this;if (this.message) {this.sendFun("1", this.message, this.quoteValue);// that.message = "";// that.quoteText = "";// that.quoteValue = null;} else {Toast("内容不能为空!");}},//查看未读消息seeMsg() {axios.get("/jeecg-boot/api/releaseLog/seePcReleaseLog", {params:{type: 2,chatRoomId: this.chatRoomId,}}).then((res) => {}).catch(() => {});},//刷新历史消息onRefresh() {this.getHistory();// if (this.params.pageNo <= this.allPage) {// this.getHistory();// } else {// this.isLoading = false;// }},//查看历史消息getHistory() {const that = this;axios.get("/jeecg-boot/api/memberMessage/historyCpMsgList", {params:this.params} ).then((res) => {// console.log(res);res=res.dataif (res.result.records.length == 0) {if (!this.isFirst) {Toast("没有更多数据了");}}res.result.records.forEach((item) => {let mesItem = item;let time = mesItem.createTime;if (mesItem.messageType == "6") {mesItem.poster = that.getImgView(mesItem.messageValue);}mesItem.isOprate = false;if (mesItem.quoteValue) {mesItem.quoteValue = JSON.parse(mesItem.quoteValue);}if (mesItem.chatRoomId == this.chatRoomId) {if (mesItem.messageType == "4") {this.welcome = mesItem.messageValue.replace(/\r\n/g, "<br>");this.welcome = this.welcome.replace(/\n/g, "<br>");}if (this.talkArr.length > 0) {let time1 = new Date(time.replace(/\-/g, "/")).getTime();let time2 = new Date(that.talkArr[0].createTime.replace(/\-/g, "/")).getTime();let diff = 0;if (parseInt(time1) >= parseInt(time2)) {diff = parseInt(time1) - parseInt(time2);} else {diff = parseInt(time2) - parseInt(time1);}if (diff <= 5 * 60 * 1000) {that.talkArr[0].mesList.unshift(mesItem);} else {that.talkArr.unshift({createTime: time,time: judgeTime(time),mesList: [mesItem],});}} else {that.talkArr.unshift({createTime: time,time: judgeTime(time),mesList: [mesItem],});}}});// Toast("刷新成功");this.isLoading = false;this.allPage = res.result.pages;this.params.oldMesId =res.result.records[res.result.records.length - 1].id;if (this.isFirst) {that.isFirst = false;that.scrollBottom();}}).catch(() => {});},//去个人主页toPerson(id) {if (id) {this.$router.push({ path: "/person", query: { id: id } });}},//去聊天室信息页面toMember() {this.$router.push({path: "/groupMember",query: { activityId: this.chatRoomId },});},scrollBottom() {this.$nextTick((res) => {let div = document.getElementById("chat");div.scrollTop = div.scrollHeight;});},//选择表情smile() {this.smileShow = !this.smileShow;},//查看大图imgPreview(src) {// ImagePreview([src]);},//视频播放videoPlay(path, poster) {this.videoPath = path;this.posterPath = poster;this.videoShow = true;},//videoFun(visible) {this.videoShow = visible;this.posterPath = "";this.videoPath = "";},/* 视频取第一秒截图 */getImgView(text) {return text + "?x-oss-process=video/snapshot,t_1000";},// 在屏幕上时触发touchstart(items) {clearTimeout(this.touch); //再次清空定时器,防止重复注册定时器this.touch = setTimeout(() => {items.isOprate = true;this.$forceUpdate();}, 800);},//离开屏幕时触发touchend() {clearTimeout(this.touch); //再次清空定时器,防止重复注册定时器},//点击任意地方关闭操作closeMask(items) {items.isOprate = false;this.$forceUpdate();},//重新编辑reEdit() {this.message = this.reMessage;},//引用quoteChat(items) {if (items.messageType == "1") {this.quoteText = "@" + items.fullName + " " + items.messageValue;} else if (items.messageType == "5") {this.quoteText = "@" + items.fullName + " [图片]";} else if (items.messageType == "6") {this.quoteText = "@" + items.fullName + " [视频]";}this.quoteValue = items;items.isOprate = false;this.$forceUpdate();},//清楚引用quoteClose() {this.quoteText = "";this.quoteValue = null;},//删除delChat(items) {if (this.isClick) {this.isClick = false;this.$fetch("/jeecg-boot/api/memberMessage/delMessageLog", {messageLogId: items.id,}).then((res) => {if (res.code == 200) {Toast("消息删除成功");items.isOprate = false;items.delFlag = "1";this.$forceUpdate();} else {Toast(res.message);}this.isClick = true;}).catch(() => {this.isClick = true;});}},//复制copyChat(items) {this.copeText(items);},//撤回recallChat(items) {if (this.isClick) {this.isClick = false;this.$fetch("/jeecg-boot/api/memberMessage/delMessageLog", {messageLogId: items.id,messageType: 12,}).then((res) => {if (res.code == 200) {Toast("消息撤回成功");items.isOprate = false;items.messageType = "12";this.$forceUpdate();} else {Toast(res.message);}this.isClick = true;}).catch(() => {this.isClick = true;});}},//复制消息copeText(items) {let that = this;// 数字没有 .length 不能执行selectText 需要转化成字符串const textString = items.messageValue.toString();let input = document.querySelector("#copy-input");if (!input) {input = document.createElement("input");input.id = "copy-input";input.readOnly = "readOnly"; // 防止ios聚焦触发键盘事件input.style.position = "absolute";input.style.left = "-1000px";input.style.zIndex = "-1000";document.body.appendChild(input);}input.value = textString;// ios必须先选中文字且不支持 input.select();this.selectText(input, 0, textString.length);if (document.execCommand("copy")) {document.execCommand("copy");console.log("复制成功");Toast("复制成功!");items.isOprate = false;that.$forceUpdate();} else {console.log("不兼容");}input.blur();},// input自带的select()方法在苹果端无法进行选择,所以需要自己去写一个类似的方法// 选择文本。createTextRange(setSelectionRange)是input方法selectText(textbox, startIndex, stopIndex) {if (textbox.createTextRange) {// ieconst range = textbox.createTextRange();range.collapse(true);range.moveStart("character", startIndex); // 起始光标range.moveEnd("character", stopIndex - startIndex); // 结束光标range.select(); // 不兼容苹果} else {// firefox/chrometextbox.setSelectionRange(startIndex, stopIndex);textbox.focus();}},// 上传图片async afterRead(fileList) {Toast.loading({duration: 0, // 持续展示 toastmessage: "发送中...",forbidClick: true,});let that = this;let formData = new FormData(); //构造一个 FormData,把后台需要发送的参数添加if (fileList.file.type.indexOf("video") == 0) {formData.append("file", fileList.file); //接口需要传的参数formData.append("biz", fileList.file.name.split(".")[0]);await axios.post("/jeecg-boot/sys/common/uploadCp", formData).then((res) => {Toast.clear();if (res.data.code == 0) {let imgsrc = res.data.message;that.sendFun("6", imgsrc);} else {Toast("发送失败");}});} else {imgPreview(fileList.file, async (files) => {// console.log(files)formData.append("file", files); //接口需要传的参数formData.append("biz", files.name.split(".")[0]);await axios.post("/jeecg-boot/sys/common/uploadCp", formData).then((res) => {Toast.clear();if (res.data.code == 0) {let imgsrc = this.imgUrl+res.data.message;that.sendFun("5", imgsrc);} else {Toast("发送失败");}});});}},beforeRead(fileList) {if (fileList.type.indexOf("image") != 0 &&fileList.type.indexOf("video") != 0) {Toast("只能上传图片(jpg,jpeg,png)、视频(mp4,mov)");return false;}let fileListLength = fileList.length ? fileList.length : 1;if (fileListLength + this.fileList.length > 1) {Toast("最多同时上传一张图片或视频");return false;}return true;},// 拍摄async afterReadVideo(fileList) {Toast.loading({duration: 0, // 持续展示 toastmessage: "发送中...",forbidClick: true,});let that = this;let formData = new FormData(); //构造一个 FormData,把后台需要发送的参数添加if (fileList.file.type.indexOf("video") == 0) {formData.append("file", fileList.file); //接口需要传的参数formData.append("biz", fileList.file.name.split(".")[0]);await axios.post("/jeecg-boot/sys/common/uploadCp", formData).then((res) => {Toast.clear();if (res.data.code == 0) {let imgsrc = res.data.message;that.sendFun("6", imgsrc);} else {Toast("发送失败");}});} else {imgPreview(fileList.file, async (files) => {// console.log(files)formData.append("file", files); //接口需要传的参数formData.append("biz", files.name.split(".")[0]);await axios.post("/jeecg-boot/sys/common/uploadCp", formData).then((res) => {Toast.clear();if (res.data.code == 0) {let imgsrc =this.imgUrl+ res.data.message;that.sendFun("5", imgsrc);} else {Toast("发送失败");}});});}},beforeReadVideo(fileList) {if (fileList.type.indexOf("image") != 0 &&fileList.type.indexOf("video") != 0) {Toast("只能上传图片(jpg,jpeg,png)、视频(mp4,mov)");return false;}let fileListLength = fileList.length ? fileList.length : 1;if (fileListLength + this.fileList.length > 1) {Toast("最多同时上传一张图片或视频");return false;}return true;},//发送方法sendFun(messageType, messageValue, quoteValue) {// console.log(messageValue)Toast.loading({duration: 0, // 持续展示 toastmessage: "发送中...",forbidClick: true,});let date = new Date();let createTime = date.getTime();let prm=JSON.stringify({chatRoomId: this.chatRoomId, //群聊idmessageType: messageType, //1文字,5图片,6视频,7求手机号、8求微信号、9发手机号、10发微信号messageValue: messageValue, //发送信息// userPhone: this.myInfo.userPhone, //用户手机号// fullName: this.myInfo.fullName, //用户名// userIcon: this.myInfo.userIcon, //用户头像// memberId: this.myInfo.id, //用户idmemberId: sessionStorage.getItem('clientId'), //用户id// createTime: createTime, //创建时间// sex: this.myInfo.sex, //性别quoteValue: JSON.stringify(quoteValue), //引用的内容})console.log(prm)this.stompClient.send("/app/chatRoom",{},prm);},//添加表情addEmoji(e) {this.message += e.native;},//选择表情chooseEmoji(item) {this.message += item.emoji;},},beforeDestroy() {Toast.clear();},updated() {},
};
</script><style lang="scss" scoped>
.groupChatInf {position: relative;.chatBox {// position: relative;height: calc(100vh - 320px);overflow-y: auto;}/deep/.van-pull-refresh {// min-height: 300px;min-height: calc(100vh - 320px);.van-pull-refresh__track {padding: 0px 10px;}}// ::v-deep .van-pull-refresh {// min-height: 300px;// }.welcome {text-align: center;font-size: 14px;color: #666;padding: 6px 10px;}.chatInf {width: 100%;padding: 0px 0px 70px 0px;.chatOne {.date {text-align: center;line-height: 30px;font-size: 12px;color: #999;}.chatMine,.chatOther {position: relative;margin-bottom: 10px;padding: 0px 50px;min-height: 40px;position: relative;.chatImg {width: 38px;height: 38px;position: absolute;top: 0px;.imgs {width: 100%;height: 100%;border-radius: 50%;overflow: hidden;}.van-image {width: 100%;height: 100%;border-radius: 50%;overflow: hidden;}.sex {width: 15px;position: absolute;right: -4px;bottom: 0px;}}.chatText {padding: 8px 13px;background: #d8d8d8;font-size: 14px;color: #333;line-height: 1.7;border-radius: 6px;position: relative;}.chatName {font-size: 12px;color: #666;line-height: 20px;img {width: 11px;position: relative;top: -1px;margin-right: 3px;}}.chatImage,.chatVideo {width: 124px;height: 124px;overflow: hidden;}.chatImage {img {width: 100%;height: 100%;object-fit: cover;}}.chatVideo {position: relative;video {width: 100%;height: 100%;}.chatmask {width: 100%;height: 100%;background: rgba(0, 0, 0, 0.5);position: absolute;top: 0;left: 0;img {width: 40px;height: 40px;position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);}}}.chatvalueDiv {position: relative;}.chatvalueDiv:after {content: "";display: block;clear: both;}.quoteValue {padding: 5px 10px;font-size: 10px;color: #8e8e93;border-radius: 4px;background: #eeeeef;margin-top: 5px;text-overflow: -o-ellipsis-lastline;overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: 2;line-clamp: 2;-webkit-box-orient: vertical;}.chatOprate {width: auto;height: 28px;background: #fff;position: absolute;bottom: -34px;z-index: 120;border-radius: 4px;display: flex;span {font-size: 13px;color: #333;line-height: 28px;width: 40px;text-align: center;margin: 0 10px;}}.chatOprateMask {width: 100%;height: 100%;position: fixed;top: 0;left: 0;background: rgba(255, 255, 255, 0);z-index: 111;}.quoteImg {padding: 5px 10px;border-radius: 4px;background: #eeeeef;margin-top: 5px;p {font-size: 10px;color: #8e8e93;}.qimgs {width: 90px;height: 60px;object-fit: cover;margin-left: 5px;}.quoteVideo {width: 90px;height: 60px;position: relative;margin-left: 5px;video {width: 100%;height: 100%;}.chatmask {width: 100%;height: 100%;background: rgba(0, 0, 0, 0.5);position: absolute;top: 0;left: 0;img {width: 30px;height: 30px;position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);}}}}}.chatMine {.chatImg {right: 0px;}.chatText {float: right;background: #ff4e7b;border: 1px #ff4e7b solid;color: #fff;}.chatName {text-align: right;}.quoteValue {float: right;}.chatImage,.chatVideo {border-radius: 6px 0 6px 6px;float: right;}.chatOprate {right: 0;}.chatOprate:after {content: "";position: absolute;width: 0px;height: 0px;line-height: 0px; /*为了防止ie下出现题型*/border-bottom: 5px solid #fff;border-left: 5px solid transparent;border-right: 5px solid transparent;right: 10px;top: -5px;}.quoteImg {float: right;p {float: left;}.qimgs,.quoteVideo {float: right;}}}.chatOther {.chatImg {left: 0px;}.chatText {float: left;background: #fff;// border: 1px #eee solid;}.chatImage,.chatVideo {border-radius: 0 6px 6px 6px;float: left;}.quoteValue {float: left;}.chatOprate {left: 0;}.chatOprate:after {content: "";position: absolute;width: 0px;height: 0px;line-height: 0px; /*为了防止ie下出现题型*/border-bottom: 5px solid #fff;border-left: 5px solid transparent;border-right: 5px solid transparent;left: 10px;top: -5px;}.quoteImg {float: left;p {float: left;}.qimgs,.quoteVideo {float: right;}}}.chatMine:after,.chatOther:after {content: "";display: block;clear: both;}}}.sendBox {// width: 400px;width: 100%;background: #fff;position: absolute;z-index: 99;left: 0px;margin: 0 auto;bottom: -20px;border-top: 1px #eee solid;.sendBoxTop {min-height: 50px;width: 100%;padding: 6px 60px 6px 60px;.sendMember {width: 40px;height: 40px;position: absolute;top: 5px;left: 10px;img {width: 24px;height: 24px;position: absolute;top: 8px;left: 8px;}}.sendInf {width: 100%;height: 40px;background: #f5f5f5;border-radius: 20px;padding-left: 10px;}.sendBtn {width: 60px;height: 50px;padding: 5px 10px;position: absolute;top: 0px;right: 0px;img {width: 40px;}}}.sendBoxBtm {width: 100%;height: 42px;display: flex;.sendOprate {flex: 1;height: 100%;align-items: center;justify-content: center;position: relative;img {padding: 5px;width: 34px;height: 34px;display: block;margin: 4px auto;}::v-deep .van-uploader {width: 34px;height: 34px;position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);.van-uploader__upload {width: 34px;height: 34px;margin: 0;opacity: 0;}}}}.sendExpression {width: 30px;height: 30px;position: absolute;top: 12px;right: 65px;padding: 3px;img {width: 24px;}}.quoteMessage {display: inline-block;background: #f4f4f4;border-radius: 6px;padding: 5px 30px 5px 8px;position: relative;margin-top: 6px;.quoteText {font-size: 10px;color: #8e8e93;line-height: 1.5;text-overflow: -o-ellipsis-lastline;overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: 2;line-clamp: 2;-webkit-box-orient: vertical;}.quoteClose {width: 24px;height: 24px;padding: 5px;position: absolute;right: 5px;top: 50%;transform: translateY(-50%);}}}.chatRecall {text-align: center;font-size: 12px;color: #8e8e93;padding: 6px 0px;line-height: 20px;a {color: #ff0021;}}.rejectCol {color: #ee6262;}::v-deep .emoji-mart {width: 100% !important;height: 160px;border: none;.emoji-mart-category-label {display: none;}}.emojiBox {width: 100%;height: 160px;overflow: auto;.emojiDiv {display: flex;flex-wrap: wrap;padding: 0 10px;span {padding: 3px;width:32px;height:32px;text-align:center;line-height:32px;}}}
}
::v-deep .van-field__control{margin-top: 8px;border: 0px;width: calc(100% - 40px);background-color: transparent;
}
::v-deep .van-field__control:focus{outline: none !important;
}
</style>