實時聊天
- 知識點
- 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>