实时聊天 Vue + Vuex + sockjs-client + stompjs进行websocket连接
vue
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
项目地址:https://gitcode.com/gh_mirrors/vu/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: '', // websocket
sendContent: "",//发送的数据
userPhone: "",//给对方发送消息对方的userPhone
chatType: "",//聊天的方式 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.websocketUrl
axios.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() {
debugger
Toast.clear();
let datas = JSON.parse(this.sendContent);
if (datas.code == 500) {
Dialog.alert({
message: datas.message,
}).then(() => {
// on close
this.$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 close
this.$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)
debugger
this.seeMsg();
if (!this.isLoading) {
this.scrollBottom();
}
}
}
},
},
封装的发送消息的公共方法
sendFun(messageType, messageValue, quoteValue) {
// console.log(messageValue)
Toast.loading({
duration: 0, // 持续展示 toast
message: "发送中...",
forbidClick: true,
});
let date = new Date();
let createTime = date.getTime();
let prm=JSON.stringify({
chatRoomId: this.chatRoomId, //群聊id
messageType: 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, //用户id
memberId: 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">
<div
class="welcome"
v-html="welcome"
v-if="welcome && items.messageType == '4'"
></div>
<div v-if="items.messageType != '4'">
<div
class="chatMine"
v-if="items.userPhone == myInfo.userPhone"
>
<div
class="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
>
<img
src="~@/assets/img/redGirl.png"
alt=""
v-else-if="items.memberId == 9999999"
class="imgs"
/>
<img
src="~@/assets/img/girlHeadImg.png"
alt=""
v-else-if="items.sex == 'F'"
class="imgs"
/>
<img
src="~@/assets/img/boyHeadImg.png"
alt=""
class="imgs"
v-else
/>
</div>
<div
class="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'">
<div
class="chatText"
@touchstart="touchstart(items)"
@touchend="touchend"
>
{{ items.messageValue }}
</div>
<div
class="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'">
<div
class="chatImage"
@click="imgPreview(items.messageValue)"
>
<img :src="items.messageValue" alt="" />
</div>
<div
class="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>
<div
class="chatmask"
@click="videoPlay(items.messageValue, items.poster)"
>
<img src="~@/assets/bofang.png" alt="" />
</div>
</div>
<div
class="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.quoteValue && items.messageType != '12'"
>
<div
class="quoteValue"
v-if="items.quoteValue.messageType == '1'"
>
@{{
items.quoteValue.fullName +
items.quoteValue.messageValue
}}
</div>
<div
class="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>
<div
class="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>
<div
class="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>
<div
class="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
>
<img
src="~@/assets/img/girlHeadImg.png"
alt=""
v-else-if="items.sex == 'F'"
class="imgs"
/>
<img
src="~@/assets/img/boyHeadImg.png"
alt=""
class="imgs"
v-else
/>
<img
src="~@/assets/img/woman.png"
alt=""
class="sex"
v-if="items.sex == 'F'"
/>
<img
src="~@/assets/img/man.png"
alt=""
class="sex"
v-if="items.sex == 'M'"
/>
</div>
<div
class="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'">
<div
class="chatText"
@touchstart="touchstart(items)"
@touchend="touchend"
>
{{ items.messageValue }}
</div>
<div
class="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'">
<div
class="chatImage"
@click="imgPreview(items.messageValue)"
>
<img :src="items.messageValue" alt="" />
</div>
<div
class="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>
<div
class="chatmask"
@click="videoPlay(items.messageValue, items.poster)"
>
<img src="~@/assets/bofang.png" alt="" />
</div>
</div>
<div
class="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.quoteValue && items.messageType != '12'"
>
<div
class="quoteValue"
v-if="items.quoteValue.messageType == '1'"
>
@{{
items.quoteValue.fullName +
items.quoteValue.messageValue
}}
</div>
<div
class="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>
<div
class="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>
<div
class="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-field
class="sendInf"
v-model="message"
:border="false"
style="border: 0px;"
@keyup.enter.native="sendMsg"
/>
<div class="quoteMessage" v-if="quoteText">
<p class="quoteText">{{ quoteText }}</p>
<img
src="~@/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">
<span
v-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: "", //群聊id
pageNo: 1, //页数
pageSize: 50, //每页数据数量
oldMesId: "", //最后一条消息id
}, //群聊查询历史数据参数
chatRoomId: "", //群聊id
allPage: 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() {
debugger
Toast.clear();
let datas = JSON.parse(this.sendContent);
if (datas.code == 500) {
Dialog.alert({
message: datas.message,
}).then(() => {
// on close
this.$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 close
this.$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)
debugger
this.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.websocketUrl
axios.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.data
if (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) {
// ie
const range = textbox.createTextRange();
range.collapse(true);
range.moveStart("character", startIndex); // 起始光标
range.moveEnd("character", stopIndex - startIndex); // 结束光标
range.select(); // 不兼容苹果
} else {
// firefox/chrome
textbox.setSelectionRange(startIndex, stopIndex);
textbox.focus();
}
},
// 上传图片
async afterRead(fileList) {
Toast.loading({
duration: 0, // 持续展示 toast
message: "发送中...",
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, // 持续展示 toast
message: "发送中...",
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, // 持续展示 toast
message: "发送中...",
forbidClick: true,
});
let date = new Date();
let createTime = date.getTime();
let prm=JSON.stringify({
chatRoomId: this.chatRoomId, //群聊id
messageType: 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, //用户id
memberId: 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>
GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5
* chore: fix link broken
Signed-off-by: snoppy <michaleli@foxmail.com>
* Update packages/template-compiler/README.md [skip ci]
---------
Signed-off-by: snoppy <michaleli@foxmail.com>
Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891
Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
更多推荐
已为社区贡献1条内容
所有评论(0)