更新记录
1.0.0(2024-11-20) 下载此版本
基本功能实现
平台兼容性
Vue2 | Vue3 |
---|---|
√ | × |
App | 快应用 | 微信小程序 | 支付宝小程序 | 百度小程序 | 字节小程序 | QQ小程序 |
---|---|---|---|---|---|---|
HBuilderX 3.1.0 app-vue | × | √ | × | × | × | × |
钉钉小程序 | 快手小程序 | 飞书小程序 | 京东小程序 |
---|---|---|---|
× | × | × | × |
H5-Safari | Android Browser | 微信浏览器(Android) | QQ浏览器(Android) | Chrome | IE | Edge | Firefox | PC-Safari |
---|---|---|---|---|---|---|---|---|
√ | √ | √ | √ | √ | √ | √ | √ | √ |
hx-wechat 介绍
即时通讯聊天应用,基于Vue 2和uView-UI,通过WebSocket提供实时通讯。支持发送图片、语音、视频和文件。具备引用、删除、复制(下载文件)、撤回和重新编辑等功能,让沟通更便捷。
使用
1. 创建 store :chat.js
export const state = {
conversitionList: [], //聊天记录 聊天框的聊天记录
socket: null, // websoket
zdColse: false // 是否是主动关闭websocket
}
export const getters = {
}
export const mutations = {
// 通用方法:更新数据方法
setPropName(state, res) {
state[res.propName] = res.value;
}
}
export const actions = {
}
① main.js 引入
import store from '@/store';
Vue.prototype.$store = store;
2.引入uview
① main.js 引入
import uView from '@/uni_modules/uview-ui'
import store from '@/store';
Vue.use(uView)
②uni.scss 引入css @import '@/uni_modules/uview-ui/theme.scss';
3.组件方法
props传参
props属性 | 说明 | 类型 | 默认值 |
---|---|---|---|
pageTitle | 是否展示顶部标题 | Boolean | true |
sender | 发送人信息 | Object | {} |
reciver | 接受人信息 | Object | {} |
conversitionList | 获取接口的聊天记录 | Array | [] |
handleId | 正在操作某聊天记录的操作栏 id | String | '' |
sender发送人
事件名称 | 说明 | 类型 | 可选值 | 默认值 | 注意 |
---|---|---|---|---|---|
avatar | 发送人图片 | String | —— | '' | |
id | 发送人id | String | —— | '' | |
name | 发送人名称 | String | —— | '' |
reciver接受人
事件名称 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
professorIcon | 接受人人图片 | String | —— | '' |
professorId | 接受人id | String | —— | '' |
professorName | 接受人名称 | String | —— | '' |
conversitionList数据 里的项
事件名称 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
id | 聊天记录id | String | —— | '' |
createTime | 聊天记录创建时间 | String | —— | '' |
sendId | 发送者id | String | —— | '' |
sendType | 发送人类型 | String | —— | 'customer' |
avatar | 发送者头像 | String | —— | '' |
length | 语音的时长 | String | —— | '' |
receiveId | 接受者id | String | —— | ‘' |
receiveType | 接受者类型 | String | —— | "professor" |
content | 发送的内容【文本,表情包,图片,视频,语音,文件】 | String | —— | '' |
fileName | 图片/视频/语音/文件文件名 | String | —— | '' |
fileSize | 图片/视频/语音/文件文件大小 | Number | —— | 0 |
type | 内容类型 | String | mage 、video 、audio 、 text | 'text' |
operate | 操作类型 | Number | 发送消息 0 、 撤回 1 、 引用 2 、删除 3 、系统消息 4、 结束消息 5 | 0 |
quoteId | 引用消息内容的id | String | —— | '' |
quoteContent | 引用消息内容 | String | —— | '' |
quoteSendId | 引用消息发送人id | String | —— | '' |
quoteSendName | 引用消息发送人名称 | String | —— | '' |
quoteType | 引用消息内推类型 | String | mage 、video 、audio 、 text | '' |
id | 删除、撤回的时候该数据id | String | —— | '' |
hx-wechat组件调业务组件实现传参,调接口
事件名称 | 说明 | 类型 | 回调参数 |
---|---|---|---|
@uploadFile | 上传图片/音频/视频/文件 接口 | Fn | conversition,fileObj,urls |
@editMessageLog | 修改咨询信息历史记录(引用,撤回,删除等) 接口 | Fn | conversition |
@senderData | 发送信息 接口 | Fn | conversition |
conversition数据
事件名称 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
sendId | 发送者id | String | —— | '' |
sendType | 发送人类型 | String | —— | 'customer' |
avatar | 发送者头像 | String | —— | '' |
length | 语音的时长 | String | —— | '' |
receiveId | 接受者id | String | —— | ‘' |
receiveType | 接受者类型 | String | —— | "professor" |
content | 发送的内容【文本,表情包,图片,视频,语音,文件】 | String | —— | '' |
fileName | 图片/视频/语音/文件文件名 | String | —— | '' |
fileSize | 图片/视频/语音/文件文件大小 | Number | —— | 0 |
type | 内容类型 | String | mage 、video 、audio 、 text | 'text' |
operate | 操作类型 | Number | 发送消息 0 、 撤回 1 、 引用 2 、删除 3 、系统消息 4、 结束消息 5 | 0 |
quoteId | 引用消息内容的id | String | —— | '' |
quoteContent | 引用消息内容 | String | —— | '' |
quoteType | 引用消息内推类型 | String | mage 、video 、audio 、 text | '' |
quoteSendId | 引用消息发送人id | String | —— | '' |
quoteSendName | 引用消息发送人名称 | String | —— | '' |
id | 删除、撤回的时候该数据id | String | —— | '' |
uploadFile 方法的参数urls
字段名 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
urls | 图片/视频/语音/文件地址url | String | —— | '' |
uploadFile 方法的参数fileObj
字段名 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
deviceType | 设备 | String | mobile 移动端、 pcWchat 电脑版微信,开发中工具 | "mobile" |
type | 信息类型 | String | 图片,视频,文件 | '图片' |
业务组件调hx-wechat组件事件
事件名称 | 说明 | 参数 | 执行 | 注意 |
---|---|---|---|---|
quoteDel | 调完发送信息接口要删除引用id | —— | this.$refs.hxwechat.quoteDel() | |
sendInfo | 调完上传图片/视频/语音/文件后要把数据传到hx-wechat组件里 去发送信息 | 2个参数,第一个是uploadFileData ,第二个是类型type [ successUploadFile 上传文件成功 、errorUploadFile 上次文件失败] | .sendInfo(uploadFileData, 'errorUploadFile'); | 这个方法是在@uploadFile 这个方法里调用,因 上传图片/视频/语音/文件后要把数据传到hx-wechat组件里 去发送信息 |
uploadFileData数据
事件名称 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
item | @uploadFile 传递过来的数据 item | —— | {} | |
content | 发送的内容【文本,表情包,图片,视频,语音,文件】 | String | —— | '' |
fileName | 图片/视频/语音/文件文件名 | String | —— | '' |
fileSize | 图片/视频/语音/文件文件大小 | Number | —— | 0 |
4. 案例
<template>
<hx-wechat ref="hxwechat" :reciver="reciver" :sender="sender" @uploadFile="uploadFileFn" @senderData="senderDataFn"
:handleId="handleId" @editMessageLog="editMessageLogFn"></hx-wechat>
</template>
<script>
import * as Api from '@/api/meet.js'
export default {
data() {
return {
/*** websocket 连接模块**/
SOCKETAPI: "ws://192.168.1.119:18080/****/oK4rA67BPm3Bx-mB_4htXQ4daU1I",
// WebSocket 连接次数
socketCount: 0,
// 最大重连次数
maxSocketCount: 5,
// 重连定时器
socketTime: null,
/***接受人-专家**/
reciver: {
professorIcon: "https://b0.bdstatic.com/ugc/plwcNl7cCzbLNwBlvLGcuAa0ea704a48784b19a64e448c1870ccfb.jpg",
professorId: "27",
professorName: "吴一",
},
/***发送人**/
sender: {
id: "oK4rA67****I",
avatar: "https://img2.baidu.com/it/u=4006946835,4011191580&fm=253&fmt=auto&app=138&f=JPEG?w=682&h=560",
name: "婉约",
},
/*** 聊天记录**/
handleId: 'false', //正在操作某聊天记录的操作栏 id
}
},
onShow() {
this.initSocket();
},
computed: {},
methods: {
// 链接 websocket
async initSocket() {
let app = this;
//1.判断是否有websocet对象,有的话清空
await app.closeSocket();
//2.创建websocket
let socket = uni.connectSocket({
url: app.SOCKETAPI,
success(res) {
console.log('websocket 创建成功', res);
},
})
//1.1 把socket 数据存在store里
app.$store.commit('setPropName', {
propName: 'socket',
value: socket
});
//3。监听WebSocket连接打开事件
app.$store.state.socket.onOpen((res) => {
console.info("监听WebSocket连接打开事件", res)
clearInterval(app.socketTime)
});
//4.监听WebSocket接受到服务器的消息事件
app.$store.state.socket.onMessage((res) => {
console.log('监听WebSocket接受到服务器的消息事件', res)
app.getMessageLogListFn();
});
// 5.监听连接关闭close
app.$store.state.socket.onClose((e) => {
console.log('WebSocket连接关闭', app.$store.state.zdColse);
app.$store.commit('setPropName', {
propName: 'socket',
value: null
});
if (!app.$store.state.zdColse) {
clearInterval(app.socketTime)
app.reSocket()
}
})
//5.连接失败
this.$store.state.socket.onError((e) => {
console.log('WebSocket连接打开失败,请检查!2222!!', e);
clearInterval(app.socketTime)
app.closeSocket('error')
})
},
//重新连接
reSocket() {
let app = this;
app.socketCount++
console.log("开始断线重连!!!!!!!!!!!");
if (app.socketCount > app.maxSocketCount) {
console.log('WebSocket 重连次数已达上限,开始每隔30秒重连一次');
uni.showModal({
content: '请检查网络是否异常,请重新进入',
showCancel: false,
success: function(res) {
if (res.confirm) {
//退到首页,退到想象的页面
app.$navTo('pages/index/index')
}
}
});
return false
}
if (app.socketTime) {
clearInterval(app.socketTime)
}
app.socketTime = setTimeout(() => {
app.initSocket()
}, 3000)
},
//关闭socket
closeSocket(type) {
let app = this;
console.log('关闭socket:', type, this.$store.state.socket)
//1. 判断是否有websocet对象,有的话清空
if (this.$store.state.socket) {
console.log('执行:', this.$store.state.socket.close)
//2.关闭websocet
this.$store.state.socket.close({
success: () => {
console.log('WebSocket关闭成功');
},
fail: (err) => {
console.error('WebSocket关闭失败', err);
//2.1 关闭失败的话,则去首页
if (err.errMsg && err.errMsg.indexOf('fail:close wcwss return fail') != -1) {
if (type == 'error') {
uni.showModal({
content: '请检查网络是否异常,请重新进入',
showCancel: false,
success: function(res) {
if (res.confirm) {
app.$navTo('pages/index/index')
}
}
});
}
return false
}
}
});
}
},
//清楚Socket
clearSocket() {
const app = this
clearInterval(app.socketTime)
this.$store.commit('setPropName', {
propName: 'zdColse',
value: true
});
app.closeSocket()
},
//获取咨询信息历史记录
async getMessageLogListFn() {
let message = await Api.getMessageLogList({
professorId: this.reciver.professorId,
customerId: this.sender.openid
})
if (message.code == 200) {
let arr = message.data.records || []
this.isHandleId = null
// 把数据更新到store里
this.$store.commit('setPropName', {
propName: 'conversitionList',
value: arr
});
} else {
// 把数据更新到store里
this.$store.commit('setPropName', {
propName: 'conversitionList',
value: []
});
}
},
//发送数据
senderDataFn(conversition) {
// 发送数据接口
Api.sendMessage(conversition).then(res => {
this.handleId = null
this.$refs.hxwechat.quoteDel()
})
},
//修改咨询信息历史记录(引用,撤回,删除等)
editMessageLogFn(conversition) {
let app = this;
Api.editMessageLog(conversition).then(res => {
if (conversition.operate == 3) {
app.getMessageLogListFn();
}
this.handleId = null
})
},
//上传图片,视频,文件 接口
uploadFileFn({
item,
fileObj,
urls
}) {
const app = this
return new Promise((resolve, reject) => {
//1.上传图片 到服务器
uni.uploadFile({
url: `http://192.168.1.119:18080/****/attachUpload`, //需要传图片的后台接口
filePath: urls, // 上传图片 通过uni-app的uni-file-picker组件 函数返回
name: 'file', //文件名字
header: {
openId: this.sender.openId,
appletSessionId: this.reciver.professorId,
},
success: res => {
let result = JSON.parse(res.data);
console.log('上传文件接口返回:', result.code)
//1.1 上传接口失败后
if (result.code != 200) {
console.log('1111')
setTimeout(() => {
uni.showToast({
title: `发送${fileObj.type}失败`,
duration: 3500,
icon: 'none'
});
app.$refs.hxwechat.sendInfo(null, 'errorUploadFile');
}, 100)
return false
}
//1.2 请求成功后 把数据发送 聊天的接口
let conversition = JSON.parse(JSON
.stringify(item))
conversition.content = result.data.url
conversition.fileSize = result.data.fileSize
conversition.fileName = conversition.fileName ? conversition
.fileName :
result.data.fileName
//2. 发送去聊天
app.$refs.hxwechat.sendInfo(conversition, 'successUploadFile');
resolve(result)
},
fail: e => {
console.log(e)
}
});
});
}
}
}
</script>
<style>
</style>
问题
- 一旦聊天视频多了,就会出现同一页面存在多个video时,video无法正常播放一直在加载转圈的问题 视频呈现黑色 不建议同个页面使用多个video组件,建议不超过3个vide如果要实现video列表功能,请进行优化(点击images时让video播放,所以这边最好是上传视频的时候,可以截取第一帧当封面,可以参考下面的代码,不过这个第一帧当封面这个需要后端的配合,所以我插件源码代码注释,用的是video,大家自行解决 下面简单的说明
<view v-for="(item, index) in getConversitionList" :key="item.id">
<view v-if="item.type === 'video'" class="mineSendVideo contentStyle" @click="handlePlayClick(item, index)">
<video v-if="playingIndex === index" :src="item.content" controls></video>
<image v-else src="https://img0.baidu.com/it/u=868358166,284066207&fm=253&fmt=auto&app=138&f=JPEG?w=285&h=300" @click="handlePlayClick(item, index)"></image>
</view>
</view>
getConversitionList: [
{ id: 1, type: 'video', content: "http://192.168.1.119:18080/attach/2024/11/19/LEMHtb2yyh0nbd09781d997ae492f8d0e0860cbd2f53_20241119095153000073953.mp4" },
{ id: 1, type: 'video', content: "http://192.168.1.119:18080/attach/2024/11/20/hjA3YD2L49sobd09781d997ae492f8d0e0860cbd2f53_20241120091858000082940.mp4" },
{ id: 2, type: 'video', content: "http://192.168.1.119:18080/attach/2024/11/19/视频_20241119100315000077705.mp4" }
],
playingIndex: -1 // 用于记录当前播放的视频索引
methods: {
handlePlayClick(item, index) {
// 如果当前点击的视频已经在播放,则停止播放
if (this.playingIndex === index) {
this.playingIndex = -1;
} else {
// 否则,播放当前点击的视频
this.playingIndex = index;
}
}
}
- 1