1283 lines
34 KiB
Vue
1283 lines
34 KiB
Vue
<template>
|
||
<view
|
||
:class="[
|
||
'IMmsgContent-container',
|
||
{ isIos: 'IMmsgContent-ios-container' },
|
||
{ 'disable-scroll': showVoiceMask },
|
||
]"
|
||
>
|
||
<u-navbar
|
||
:title="ImItemInfo.username"
|
||
:autoBack="true"
|
||
:placeholder="true"
|
||
></u-navbar>
|
||
<view class="tui-chat-content" id="tui-chat-content">
|
||
<!-- <tui-loadmore
|
||
v-if="loadding"
|
||
:index="3"
|
||
type="primary"
|
||
text=" "
|
||
></tui-loadmore> -->
|
||
<view>
|
||
<!-- <view class="tui-label">对方已通过您的好友请求</view> -->
|
||
<view v-for="(item, index) of msgList" :key="index">
|
||
<view class="tui-chat-center">
|
||
{{ formatTime(item.message_time) }}
|
||
</view>
|
||
<view class="tui-chat-right" v-if="item.message_kind == 1">
|
||
<view
|
||
v-if="item.msg.type == 'text' || item.msg.type == 'user'"
|
||
class="tui-chatbox tui-chatbox-right"
|
||
>
|
||
<u-parse
|
||
:content="item.msg.content.text"
|
||
class="parse-content"
|
||
></u-parse>
|
||
</view>
|
||
<view
|
||
v-if="item.msg.type == 'img'"
|
||
class="tui-chatbox tui-chatbox-right img"
|
||
>
|
||
<image
|
||
:src="item.msg.content.url"
|
||
:style="{
|
||
width: item.msg.content.w,
|
||
height: item.msg.content.h,
|
||
}"
|
||
@click="showImgPreview(item)"
|
||
class=""
|
||
></image>
|
||
</view>
|
||
<view
|
||
class="tui-flex-center tui-flex-end"
|
||
v-if="item.msg.type == 'voice'"
|
||
>
|
||
<view
|
||
class="tui-chatbox tui-chatbox-right tui-chat-flex tui-ml tui-flex-reverse"
|
||
@click="playVoice(item)"
|
||
>
|
||
<image
|
||
:src="
|
||
isPlayVoice && item.msg.id == playMsgid
|
||
? '/static/images/chat/voice.gif'
|
||
: '/static/images/chat/voice.png'
|
||
"
|
||
class="tui-chat-voice tui-rotate tui-ml"
|
||
></image>
|
||
<view :style="{ width: calculateVoiceWidth(item.mp3Time) }">
|
||
{{ item.mp3Time }}"
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<image
|
||
:src="item.msg.userinfo.face"
|
||
class="tui-user-pic tui-left"
|
||
></image>
|
||
</view>
|
||
<view class="tui-chat-left" v-if="item.message_kind == 2">
|
||
<image
|
||
:src="item.msg.userinfo.face"
|
||
class="tui-user-pic tui-right"
|
||
></image>
|
||
<view class="tui-flex-center" v-if="item.msg.type == 'voice'">
|
||
<view
|
||
class="tui-chatbox tui-chatbox-left tui-chat-flex tui-mr"
|
||
@click="playVoice(item)"
|
||
>
|
||
<image
|
||
:src="
|
||
isPlayVoice && item.msg.id == playMsgid
|
||
? '/static/images/chat/voice.gif'
|
||
: '/static/images/chat/voice.png'
|
||
"
|
||
class="tui-chat-voice tui-mr"
|
||
></image>
|
||
<view :style="{ width: calculateVoiceWidth(item.mp3Time) }">
|
||
{{ item.mp3Time }}"
|
||
</view>
|
||
</view>
|
||
<!-- <tui-badge :dot="true" type="danger"></tui-badge> -->
|
||
</view>
|
||
<view
|
||
v-if="item.msg.type == 'text'"
|
||
class="tui-chatbox tui-chatbox-left"
|
||
>
|
||
<u-parse :content="item.msg.content.text"></u-parse>
|
||
</view>
|
||
<view
|
||
v-if="item.msg.type == 'img'"
|
||
class="tui-chatbox tui-chatbox-right"
|
||
>
|
||
<!-- <image
|
||
:src="formatImg(item)"
|
||
@click="showImgPreview(item)"
|
||
class="tui-user-pic tui-left img"
|
||
></image> -->
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<t-chat-bar
|
||
@sendMsg="chatBarSendMsg"
|
||
paddingBottom
|
||
@uploadPictures="uploadPictures"
|
||
@photograph="photograph"
|
||
@uploadVideo="selectVideo"
|
||
@showVoicePopup="showVoicePopup"
|
||
@voiceEnd="voiceEnd"
|
||
:isHideKeyBoard="isHideKeyBoard"
|
||
></t-chat-bar>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
GetImConfig,
|
||
GetImMsgList,
|
||
GetSendMsgAdd,
|
||
SetMsgRead,
|
||
} from "../../api/im";
|
||
import { UploadFilePromise } from "../../api/upload";
|
||
import { toDayTime } from "../../utils/date";
|
||
import tChatBar from "@/components/t-chat-bar/t-chat-bar";
|
||
import tuiBadge from "@/components/tui-badge/tui-badge.vue";
|
||
import emoji from "../../static/im/emojiData.js";
|
||
import { mapState } from "vuex";
|
||
export default {
|
||
components: {
|
||
tChatBar,
|
||
tuiBadge,
|
||
},
|
||
data() {
|
||
return {
|
||
loadding: false,
|
||
show: true,
|
||
bottom: 0,
|
||
myUserId: 2,
|
||
chatList: [
|
||
{
|
||
type: 1, //1-文字 2-图片 3-语音,4-时间 5-提醒 ...
|
||
userId: 1, //用户标识 不一定是userid
|
||
text: "",
|
||
src: "",
|
||
read: false,
|
||
success: false,
|
||
},
|
||
],
|
||
ImItemInfo: {
|
||
friend_id: 0,
|
||
},
|
||
msgList: [],
|
||
msgImgList: [],
|
||
pcEmoji: [],
|
||
pcEmojiObj: {},
|
||
//播放语音相关参数
|
||
AUDIO: uni.createInnerAudioContext(),
|
||
// 录音
|
||
RECORDER: uni.getRecorderManager(),
|
||
playMsgid: "",
|
||
isPlayVoice: false,
|
||
// socket: {},
|
||
chattype: "user",
|
||
isHideKeyBoard: false,
|
||
emojiPath: "https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/",
|
||
isIos: false,
|
||
showVoiceMask: false,
|
||
};
|
||
},
|
||
onLoad: function (options) {
|
||
if (options.item) {
|
||
this.ImItemInfo = JSON.parse(options.item);
|
||
console.log(this.ImItemInfo);
|
||
}
|
||
this.faceList = emoji.imgArr[1].emojiList;
|
||
},
|
||
onShow() {
|
||
// this.socketServer();
|
||
this.getPcEmoji();
|
||
// this.getImConfig();
|
||
this.getImMsgList();
|
||
|
||
// #ifdef APP-PLUS
|
||
const platform = uni.getSystemInfoSync().platform;
|
||
if (platform == "ios") {
|
||
this.isIos = true;
|
||
}
|
||
// #endif
|
||
},
|
||
watch: {
|
||
getMsg: function (val) {
|
||
let that = this;
|
||
let cacheid = 0;
|
||
console.log("获取到数据");
|
||
console.log(val);
|
||
|
||
var nowDate = new Date();
|
||
let lastid = val.message_id;
|
||
let tempmy = this.userInfo.im;
|
||
|
||
let msg_type = val.msg_type;
|
||
var nowDate = new Date();
|
||
|
||
let msg = {
|
||
type: "user",
|
||
sendmethod: this.chattype,
|
||
avatar: val.avatar,
|
||
needload: false,
|
||
fromid: this.ImItemInfo.id,
|
||
toid: tempmy.puid,
|
||
message_kind: 2,
|
||
msg: {
|
||
id: lastid,
|
||
type: msg_type,
|
||
userinfo: {
|
||
uid: this.ImItemInfo.id,
|
||
username: this.ImItemInfo.username,
|
||
face: val.avatar,
|
||
},
|
||
content: {
|
||
text: val.content,
|
||
},
|
||
},
|
||
mp3Time: msg_type == "voice" ? val.length : 0,
|
||
message_time: nowDate,
|
||
};
|
||
// 用户消息
|
||
switch (msg_type) {
|
||
case "text":
|
||
msg.msg.content.text = val.content;
|
||
break;
|
||
case "voice":
|
||
msg.msg.content.url = val.content;
|
||
break;
|
||
case "img":
|
||
msg.msg.content.url = val.content;
|
||
break;
|
||
case "video":
|
||
msg.msg.content.url = val.content;
|
||
break;
|
||
case "redEnvelope":
|
||
msg.msg.content.blessing = val.content;
|
||
break;
|
||
case "rtc":
|
||
msg.msg.content.text = val.content;
|
||
break;
|
||
}
|
||
if (that.chattype == "user" && val.type == "friend") {
|
||
if (that.ImItemInfo.id == val.id) {
|
||
console.log("screened");
|
||
console.log(msg);
|
||
that.screened(msg);
|
||
|
||
// //发起视频聊天
|
||
// if (msg_type == "rtc") {
|
||
// // #ifdef MP-WEIXIN
|
||
// this.$.gotopage("/rtc/room/1v1wx?to_user_id=" + that.options.uid);
|
||
// // #endif
|
||
|
||
// // #ifdef APP-PLUS
|
||
// this.$.gotopage("/rtc/room/1v1?to_user_id=" + that.options.uid);
|
||
// // #endif
|
||
// }
|
||
}
|
||
cacheid = msg.fromid;
|
||
} else if (that.chattype == "group" && val.type == "group") {
|
||
if (that.ImItemInfo.id == val.toid) {
|
||
that.screened(msg);
|
||
}
|
||
cacheid = msg.toid;
|
||
///群聊
|
||
} else {
|
||
///不让在当前页面显示内容
|
||
}
|
||
|
||
if (that.ImItemInfo.id == val.id) {
|
||
this.setMsgRead(lastid);
|
||
//设为已读
|
||
}
|
||
|
||
this.$nextTick(() => {
|
||
const query = uni.createSelectorQuery().in(this);
|
||
query.select(".IMmsgContent-container").boundingClientRect();
|
||
query.selectAll(".IMmsgContent-container view").boundingClientRect();
|
||
query.exec((res) => {
|
||
const scrollViewHeight = res[0].height;
|
||
const scrollContentHeight = res[1].reduce(
|
||
(total, item) => total + item.height,
|
||
0
|
||
);
|
||
// const scrollTop = scrollContentHeight - scrollViewHeight;
|
||
|
||
uni.pageScrollTo({
|
||
scrollTop: scrollViewHeight,
|
||
duration: 0,
|
||
});
|
||
});
|
||
});
|
||
|
||
// this.$apiconfig.cacheMessage(val, val.sendmethod, cacheid, tempmy);
|
||
this.$store.commit("user/REMOVE_IM_KEY", this.ImItemInfo.id.toString());
|
||
// //新消息数归零
|
||
// this.$store.commit("resetWeidu", {
|
||
// type: this.chattype,
|
||
// val: this.chat_to_puid,
|
||
// });
|
||
},
|
||
},
|
||
computed: {
|
||
...mapState("user", ["uid", "userInfo", "socket", "getMsg"]),
|
||
},
|
||
methods: {
|
||
async setMsgRead(lastid) {
|
||
await SetMsgRead({ message_id: lastid });
|
||
},
|
||
voiceEnd(voiceMP3Info) {
|
||
console.log('"voiceEnd"', voiceMP3Info);
|
||
|
||
if (voiceMP3Info) {
|
||
uni.uploadFile({
|
||
url: "https://mall.gpxscs.cn/mobile/shop/oss/upload", //仅为示例,非真实的接口地址
|
||
filePath: voiceMP3Info.voice,
|
||
header: {
|
||
merchcode: "cb0ac353f02a73a7c45885a862fe4de1",
|
||
},
|
||
name: "upfile",
|
||
formData: {
|
||
user: "test",
|
||
},
|
||
success: (uploadFileRes) => {
|
||
console.log(voiceMP3Info);
|
||
|
||
let tmpres = JSON.parse(uploadFileRes.data);
|
||
|
||
let msg = {
|
||
length: voiceMP3Info.length,
|
||
url: tmpres.data.url,
|
||
mp3Time: voiceMP3Info.length,
|
||
};
|
||
|
||
// let min = parseInt(this.recordLength / 60);
|
||
// let sec = this.recordLength % 60;
|
||
// min = min < 10 ? "0" + min : min;
|
||
// sec = sec < 10 ? "0" + sec : sec;
|
||
// msg.length = min + ":" + sec;
|
||
///上传录音到服务器
|
||
this.sendMsg(msg, "voice");
|
||
},
|
||
});
|
||
}
|
||
this.showVoiceMask = false;
|
||
console.log("结束录音");
|
||
},
|
||
showVoicePopup() {
|
||
this.AUDIO.stop();
|
||
this.showVoiceMask = true;
|
||
},
|
||
uploadPictures() {
|
||
this.isHideKeyBoard = true;
|
||
this.selectPhotoOrTakeShot("album");
|
||
setTimeout(() => {
|
||
this.isHideKeyBoard = false;
|
||
}, 100);
|
||
},
|
||
photograph() {
|
||
this.isHideKeyBoard = true;
|
||
this.selectPhotoOrTakeShot("camera");
|
||
setTimeout(() => {
|
||
this.isHideKeyBoard = false;
|
||
}, 100);
|
||
},
|
||
selectVideo() {
|
||
this.isHideKeyBoard = true;
|
||
this.upLoadVideo();
|
||
setTimeout(() => {
|
||
this.isHideKeyBoard = false;
|
||
}, 100);
|
||
},
|
||
upLoadVideo() {
|
||
uni.chooseVideo({
|
||
count: 1,
|
||
sourceType: ["camera", "album"],
|
||
success: async (res) => {
|
||
uni.showLoading({
|
||
title: "视频上传中..",
|
||
mask: true,
|
||
});
|
||
|
||
let result = await UploadFilePromise(res.tempFilePath);
|
||
if (result && result.status == 200) {
|
||
let msg = {
|
||
url: res.tempFilePath,
|
||
w: result.data.width,
|
||
h: result.data.height,
|
||
};
|
||
|
||
msg.url = result.data.url;
|
||
this.sendMsg(msg, "video");
|
||
}
|
||
|
||
uni.hideLoading();
|
||
},
|
||
});
|
||
},
|
||
selectPhotoOrTakeShot(type) {
|
||
let that = this;
|
||
uni.chooseImage({
|
||
sourceType: [type],
|
||
sizeType: ["original", "compressed"], //可以指定是原图还是压缩图,默认二者都有
|
||
success: (res) => {
|
||
for (let i = 0; i < res.tempFilePaths.length; i++) {
|
||
uni.getImageInfo({
|
||
src: res.tempFilePaths[i],
|
||
success: async (image) => {
|
||
var msg = {
|
||
url: res.tempFilePaths[i],
|
||
w: image.width,
|
||
h: image.height,
|
||
};
|
||
|
||
console.log(res);
|
||
|
||
let result = await UploadFilePromise(res.tempFilePaths[i], {
|
||
user: "test",
|
||
});
|
||
if (result && result.status == 200) {
|
||
msg.url = result.data.url;
|
||
this.sendMsg(msg, "img");
|
||
}
|
||
},
|
||
});
|
||
}
|
||
},
|
||
});
|
||
},
|
||
// 添加文字消息到列表
|
||
addTextMsg(msg) {
|
||
this.msgList.push(msg);
|
||
},
|
||
// 添加语音消息到列表
|
||
addVoiceMsg(msg) {
|
||
this.msgList.push(msg);
|
||
},
|
||
// 添加图片消息到列表
|
||
addImgMsg(msg) {
|
||
msg.msg.content = this.setPicSize(msg.msg.content);
|
||
this.msgImgList.push(msg.msg.content.url);
|
||
this.msgList.push(msg);
|
||
},
|
||
addRedEnvelopeMsg(msg) {
|
||
this.msgList.push(msg);
|
||
},
|
||
// 添加系统文字消息到列表
|
||
addSystemTextMsg(msg) {
|
||
this.msgList.push(msg);
|
||
},
|
||
// 添加系统红包消息到列表
|
||
addSystemRedEnvelopeMsg(msg) {
|
||
this.msgList.push(msg);
|
||
},
|
||
screened(msg) {
|
||
if (msg.type == "system") {
|
||
// 系统消息
|
||
switch (msg.msg.type) {
|
||
case "text":
|
||
this.addSystemTextMsg(msg);
|
||
break;
|
||
case "redEnvelope":
|
||
this.addSystemRedEnvelopeMsg(msg);
|
||
break;
|
||
}
|
||
} else if (msg.type == "user" || msg.type == "friend") {
|
||
// 用户消息
|
||
switch (msg.msg.type) {
|
||
case "text":
|
||
this.addTextMsg(msg);
|
||
break;
|
||
case "voice":
|
||
this.addVoiceMsg(msg);
|
||
break;
|
||
case "img":
|
||
this.addImgMsg(msg);
|
||
break;
|
||
case "video":
|
||
this.addTextMsg(msg);
|
||
break;
|
||
case "redEnvelope":
|
||
this.addRedEnvelopeMsg(msg);
|
||
break;
|
||
}
|
||
// console.log('用户消息');
|
||
//非自己的消息震动
|
||
// if (msg.msg.userinfo.uid != this.myuid) {
|
||
// // console.log('振动');
|
||
// uni.vibrateLong();
|
||
// }
|
||
}
|
||
},
|
||
// socketServer() {
|
||
// console.log(this.userInfo.im.node_site_url);
|
||
|
||
// this.socket = new this.$Socket({
|
||
// url: this.userInfo.im.node_site_url,
|
||
// maxInterValCount: 5,
|
||
// interValTime: 2000,
|
||
// onClose: (res) => {
|
||
// console.log("socket关闭");
|
||
// },
|
||
// onOpen: (res) => {
|
||
// console.log("socket连接成功");
|
||
// },
|
||
// onMsg: (res) => {
|
||
// console.log("onMsg", res);
|
||
// },
|
||
// });
|
||
|
||
// this.socket.connectserver(this.userInfo.im);
|
||
// },
|
||
processTimeString(timeStr) {
|
||
const parts = timeStr.split(":");
|
||
|
||
// If there's no colon or format is invalid, return as-is
|
||
if (parts.length !== 2) return timeStr;
|
||
|
||
const minutes = parseInt(parts[0], 10);
|
||
const seconds = parseInt(parts[1], 10);
|
||
|
||
// Case 1: "00:60" or "01:00" → return "60"
|
||
if (
|
||
(minutes === 0 && seconds === 60) ||
|
||
(minutes === 1 && seconds === 0)
|
||
) {
|
||
return "60";
|
||
}
|
||
|
||
// Case 2: "00:XX" where XX < 60 → return "XX"
|
||
if (minutes === 0 && seconds < 60) {
|
||
return parts[1]; // Return the original seconds part to preserve leading zero
|
||
}
|
||
|
||
// For all other cases, return the original string
|
||
return timeStr;
|
||
},
|
||
playVoice(item) {
|
||
this.playMsgid = item.msg.id;
|
||
|
||
if (this.isPlayVoice == true) {
|
||
this.AUDIO.stop();
|
||
this.isPlayVoice = false;
|
||
return;
|
||
}
|
||
|
||
if (item.message_length) {
|
||
this.AUDIO.src = item.message_content;
|
||
} else {
|
||
this.AUDIO.src = item.msg.content.url;
|
||
}
|
||
this.$nextTick(function () {
|
||
this.isPlayVoice = true;
|
||
this.AUDIO.play();
|
||
});
|
||
|
||
this.$nextTick(() => {
|
||
this.AUDIO.onEnded((res) => {
|
||
this.isPlayVoice = false;
|
||
});
|
||
});
|
||
},
|
||
im_decode(content) {
|
||
let that = this;
|
||
|
||
//支持的html标签
|
||
var html = function (end) {
|
||
return new RegExp(
|
||
"\\n*\\[" +
|
||
(end || "") +
|
||
"(code|pre|div|span|p|table|thead|th|tbody|tr|td|ul|li|ol|li|dl|dt|dd|h2|h3|h4|h5)([\\s\\S]*?)\\]\\n*",
|
||
"g"
|
||
);
|
||
};
|
||
content = (content || "")
|
||
.replace(/&(?!#?[a-zA-Z0-9]+;)/g, "&")
|
||
//.replace(/</g, '<').replace(/>/g, '>').replace(/'/g, ''').replace(/"/g, '"') //XSS
|
||
.replace(/@(\S+)(\s+?|$)/g, '@<a href="javascript:;">$1</a>$2') //转义@
|
||
|
||
.replace(/face\[([^\s\[\]]+?)\]/g, function (face) {
|
||
//转义表情
|
||
|
||
var alt = face.replace(/^face/g, "");
|
||
return (
|
||
'<img alt="' +
|
||
alt +
|
||
'" title="' +
|
||
alt +
|
||
'" src="' +
|
||
that.pcEmojiObj[alt] +
|
||
'">'
|
||
);
|
||
})
|
||
.replace(/img\[([^\s]+?)\]/g, function (img) {
|
||
//转义图片
|
||
//return '<img class="layui-layim-photos" style="width: 100%;height: 100%;" src="' + img.replace(/(^img\[)|(\]$)/g, '') + '">';
|
||
|
||
return (
|
||
'<div><img class="chatimg single-img" src="' +
|
||
img.replace(/(^img\[)|(\]$)/g, "") +
|
||
'" /><span style="min-width: 240px;"> </span></div>'
|
||
);
|
||
})
|
||
.replace(/file\([\s\S]+?\)\[[\s\S]*?\]/g, function (str) {
|
||
//转义文件
|
||
var href = (str.match(/file\(([\s\S]+?)\)\[/) || [])[1];
|
||
var text = (str.match(/\)\[([\s\S]*?)\]/) || [])[1];
|
||
if (!href) return str;
|
||
return $.__("不支持该格式:file");
|
||
// #ifdef H5
|
||
return (
|
||
'<a class="layui-layim-file" href="' +
|
||
href +
|
||
'" download target="_blank"><i class="iconfont zc zc-xiangqing im-file"></i><cite>' +
|
||
$.sprintf($.__("不支持该格式,请复制网址到浏览器打开 %s"), href) +
|
||
"</cite></a>"
|
||
);
|
||
// #endif
|
||
|
||
// #ifndef H5
|
||
return (
|
||
'<a class="layui-layim-file" href="' +
|
||
href +
|
||
'" download target="_blank"><i class="iconfont zc zc-xiangqing im-file"></i><cite>' +
|
||
$.sprintf($.__("不支持该格式,请复制网址到浏览器打开 %s"), href) +
|
||
"</cite></a>"
|
||
);
|
||
// #endif
|
||
})
|
||
.replace(/audio\[([^\s]+?)\]/g, function (audio) {
|
||
//转义音频
|
||
return $.__("不支持该格式:audio");
|
||
return (
|
||
'<div class="layui-unselect layui-layim-audio" layim-event="playAudio" data-src="' +
|
||
audio.replace(/(^audio\[)|(\]$)/g, "") +
|
||
'"><i class="layui-icon"></i><p>音频消息</p></div>'
|
||
);
|
||
})
|
||
.replace(/video\[([^\s]+?)\]/g, function (video) {
|
||
//转义音频
|
||
return $.__("不支持该格式:video");
|
||
return (
|
||
'<div class="layui-unselect layui-layim-video" layim-event="playVideo" data-src="' +
|
||
video.replace(/(^video\[)|(\]$)/g, "") +
|
||
'"><i class="layui-icon"></i></div>'
|
||
);
|
||
})
|
||
.replace(/\[pre([\s\S]*)\]([\s\S]*)\[\/pre\]/g, function (video) {
|
||
//转义音频
|
||
return $.__("不支持该格式:code");
|
||
return (
|
||
'<div class="layui-unselect layui-layim-video" layim-event="playVideo" data-src="' +
|
||
video.replace(/(^video\[)|(\]$)/g, "") +
|
||
'"><i class="layui-icon"></i></div>'
|
||
);
|
||
})
|
||
|
||
.replace(/a\([\s\S]+?\)\[[\s\S]*?\]/g, function (str) {
|
||
//转义链接
|
||
var href = (str.match(/a\(([\s\S]+?)\)\[/) || [])[1];
|
||
var text = (str.match(/\)\[([\s\S]*?)\]/) || [])[1];
|
||
if (!href) return str;
|
||
return (
|
||
'<a href="' + href + '" target="_blank">' + (text || href) + "</a>"
|
||
);
|
||
});
|
||
//.replace(html(), '\<$1 $2\>').replace(html('/'), '\</$1\>') //转移HTML代码
|
||
//.replace(/\n/g, '<br>') //转义换行
|
||
|
||
return content;
|
||
},
|
||
replaceEmoji(str) {
|
||
// 正则表达式匹配内容
|
||
let replacedStr = str.replace(/\[([^(\]|\[)]*)\]/g, (item, index) => {
|
||
// console.log("item: " + item);
|
||
for (let i = 0; i < this.faceList.length; i++) {
|
||
let row = this.faceList[i];
|
||
for (let j = 0; j < row.length; j++) {
|
||
let EM = row[j];
|
||
if (EM.alt == item) {
|
||
//在线表情路径,图文混排必须使用网络路径,请上传一份表情到你的服务器后再替换此路径
|
||
//比如你上传服务器后,你的100.gif路径为https://www.xxx.com/emoji/100.gif 则替换onlinePath填写为https://www.xxx.com/emoji/
|
||
// let onlinePath = 'https://s2.ax1x.com/2019/04/12/'
|
||
// let imgstr = '<image src="'+onlinePath+this.onlineEmoji[EM.url]+'">';
|
||
|
||
let onlinePath = this.emojiPath;
|
||
let imgstr =
|
||
'<img style="width:24px;height:24px;" src="' +
|
||
onlinePath +
|
||
EM.url +
|
||
'">';
|
||
// console.log("imgstr: " + imgstr);
|
||
return imgstr;
|
||
}
|
||
}
|
||
}
|
||
});
|
||
return replacedStr;
|
||
},
|
||
chatBarSendMsg(content) {
|
||
content = this.replaceEmoji(content);
|
||
let msg = {
|
||
text: content,
|
||
};
|
||
|
||
this.sendMsg(msg, "text");
|
||
},
|
||
sendMsgText() {
|
||
this.sendMsg(msg, "text");
|
||
if (!this.textMsg) {
|
||
return;
|
||
}
|
||
|
||
let content = this.replaceEmoji(this.textMsg);
|
||
let msg = {
|
||
text: content,
|
||
};
|
||
},
|
||
async sendMsg(content, type, needload = false) {
|
||
console.info("--------------");
|
||
console.info(content);
|
||
console.info(content.text);
|
||
console.info(type);
|
||
console.info(needload);
|
||
|
||
let message_id = 1001;
|
||
var mine = {
|
||
username: this.userInfo.user_nickname,
|
||
avatar: this.userInfo.user_avatar,
|
||
id: this.userInfo.im.puid,
|
||
user_id: this.userInfo.user_id,
|
||
content: content.text || content.url,
|
||
length: type == "voice" ? content.length : 0,
|
||
w: type == "img" ? content.w : 0,
|
||
h: type == "img" ? content.h : 0,
|
||
item_id: typeof content.item_id != "undefined" ? content.item_id : 0,
|
||
type: type,
|
||
mine: true,
|
||
};
|
||
|
||
console.log(mine.avatar);
|
||
|
||
var to = {
|
||
id: this.ImItemInfo.id,
|
||
friend_id: this.ImItemInfo.user_id,
|
||
user_id: this.ImItemInfo.user_id,
|
||
name: this.ImItemInfo.user_nickname,
|
||
avatar: this.ImItemInfo.user_avatar,
|
||
type: this.chattype,
|
||
};
|
||
|
||
let maxLength = 50;
|
||
if (mine.content.replace(/\s/g, "") !== "") {
|
||
if (mine.content.length > maxLength) {
|
||
//return layer.msg('内容最长不能超过' + maxLength + '个字符')
|
||
}
|
||
}
|
||
|
||
var chat_data = {
|
||
mine: mine,
|
||
to: to,
|
||
};
|
||
|
||
let params = {
|
||
user_other_id: this.ImItemInfo.friend_id,
|
||
message_content: mine.content,
|
||
item_id: typeof content.item_id != "undefined" ? content.item_id : 0,
|
||
length: mine.length,
|
||
w: mine.w,
|
||
h: mine.h,
|
||
type: type,
|
||
};
|
||
|
||
let res = await GetSendMsgAdd(params);
|
||
if (res && res.status == 200) {
|
||
chat_data.mine.message_id = res.data.message_other_id;
|
||
|
||
this.socket.nsend(chat_data);
|
||
let lastid = res.data.message_other_id;
|
||
let tempmy = this.userInfo.im;
|
||
///TOID现在是模拟的
|
||
var nowDate = new Date();
|
||
let msg = {
|
||
type: "user",
|
||
sendmethod: this.chattype,
|
||
needload: needload,
|
||
fromid: tempmy.puid,
|
||
toid: this.ImItemInfo.user_id,
|
||
message_kind: 1,
|
||
msg: {
|
||
id: lastid,
|
||
type: type,
|
||
userinfo: {
|
||
uid: tempmy.puid,
|
||
username: this.userInfo.user_nickname,
|
||
face: this.userInfo.user_avatar,
|
||
},
|
||
content: content,
|
||
},
|
||
mp3Time: type == "voice" ? content.length : 0,
|
||
message_time: nowDate,
|
||
};
|
||
this.screened(msg);
|
||
this.$nextTick(() => {
|
||
const query = uni.createSelectorQuery().in(this);
|
||
query.select(".IMmsgContent-container").boundingClientRect();
|
||
query.selectAll(".IMmsgContent-container view").boundingClientRect();
|
||
query.exec((res) => {
|
||
const scrollViewHeight = res[0].height;
|
||
const scrollContentHeight = res[1].reduce(
|
||
(total, item) => total + item.height,
|
||
0
|
||
);
|
||
// const scrollTop = scrollContentHeight - scrollViewHeight;
|
||
|
||
uni.pageScrollTo({
|
||
scrollTop: scrollViewHeight,
|
||
duration: 0,
|
||
});
|
||
});
|
||
});
|
||
}
|
||
},
|
||
calculateVoiceWidth(seconds) {
|
||
// 微信语音最大宽度限制(根据实际情况调整)
|
||
const MAX_WIDTH = 400; // rpx
|
||
const MIN_WIDTH = 100; // rpx
|
||
const MAX_DURATION = 60; // 微信语音最大60秒
|
||
|
||
// 确保秒数在合理范围内
|
||
seconds = Math.min(Math.max(seconds, 1), MAX_DURATION);
|
||
|
||
// 计算宽度(线性增长)
|
||
const width =
|
||
MIN_WIDTH + (seconds / MAX_DURATION) * (MAX_WIDTH - MIN_WIDTH);
|
||
|
||
// 返回rpx单位的值,取整
|
||
return Math.round(width) + "rpx";
|
||
},
|
||
getPcEmoji() {
|
||
this.pcEmoji = [
|
||
"[微笑]",
|
||
"[嘻嘻]",
|
||
"[哈哈]",
|
||
"[可爱]",
|
||
"[可怜]",
|
||
"[挖鼻]",
|
||
"[吃惊]",
|
||
"[害羞]",
|
||
"[挤眼]",
|
||
"[闭嘴]",
|
||
"[鄙视]",
|
||
"[爱你]",
|
||
"[泪]",
|
||
"[偷笑]",
|
||
"[亲亲]",
|
||
"[生病]",
|
||
"[太开心]",
|
||
"[白眼]",
|
||
"[右哼哼]",
|
||
"[左哼哼]",
|
||
"[嘘]",
|
||
"[衰]",
|
||
"[委屈]",
|
||
"[吐]",
|
||
"[哈欠]",
|
||
"[抱抱]",
|
||
"[怒]",
|
||
"[疑问]",
|
||
"[馋嘴]",
|
||
"[拜拜]",
|
||
"[思考]",
|
||
"[汗]",
|
||
"[困]",
|
||
"[睡]",
|
||
"[钱]",
|
||
"[失望]",
|
||
"[酷]",
|
||
"[色]",
|
||
"[哼]",
|
||
"[鼓掌]",
|
||
"[晕]",
|
||
"[悲伤]",
|
||
"[抓狂]",
|
||
"[黑线]",
|
||
"[阴险]",
|
||
"[怒骂]",
|
||
"[互粉]",
|
||
"[心]",
|
||
"[伤心]",
|
||
"[猪头]",
|
||
"[熊猫]",
|
||
"[兔子]",
|
||
"[ok]",
|
||
"[耶]",
|
||
"[good]",
|
||
"[NO]",
|
||
"[赞]",
|
||
"[来]",
|
||
"[弱]",
|
||
"[草泥马]",
|
||
"[神马]",
|
||
"[囧]",
|
||
"[浮云]",
|
||
"[给力]",
|
||
"[围观]",
|
||
"[威武]",
|
||
"[奥特曼]",
|
||
"[礼物]",
|
||
"[钟]",
|
||
"[话筒]",
|
||
"[蜡烛]",
|
||
"[蛋糕]",
|
||
];
|
||
|
||
let obj = {};
|
||
|
||
this.pcEmoji.forEach((item, index) => {
|
||
obj[item] =
|
||
"https://media-mall-prod-1259811287.cos.ap-guangzhou.myqcloud.com/static/xcxfile/appicon/im/face/" +
|
||
index +
|
||
".gif";
|
||
});
|
||
|
||
this.pcEmojiObj = obj;
|
||
},
|
||
async getImMsgList() {
|
||
let params = {
|
||
typ: "json",
|
||
user_other_id: this.ImItemInfo.friend_id,
|
||
type: "friend",
|
||
page: 1,
|
||
};
|
||
let res = await GetImMsgList(params);
|
||
if (res && res.status == 200) {
|
||
this.msgList = res.data.items.reverse();
|
||
|
||
this.msgList.forEach((item) => {
|
||
item.msg.content.text = this.im_decode(item.msg.content.text);
|
||
if (item.msg.type == "voice") {
|
||
item.mp3Time = this.processTimeString(item.message_length);
|
||
}
|
||
if (item.msg.type == "img") {
|
||
item.msg.content = this.setPicSize(item.msg.content);
|
||
this.msgImgList.push(item.msg.content.url);
|
||
}
|
||
});
|
||
}
|
||
|
||
this.$nextTick(() => {
|
||
setTimeout(() => {
|
||
// 增加延迟确保渲染完成
|
||
const query = uni.createSelectorQuery().in(this);
|
||
query.select(".IMmsgContent-container").boundingClientRect();
|
||
query.selectAll(".IMmsgContent-container view").boundingClientRect();
|
||
query.exec((res) => {
|
||
if (!res[0] || !res[1]) return; // 容错处理
|
||
|
||
const scrollViewHeight = res[0].height; // 容器可视高度
|
||
const scrollContentHeight = res[1].reduce(
|
||
(total, item) => total + item.height + 10, // 加上间距
|
||
0
|
||
);
|
||
|
||
// 关键修正:滚动到内容底部
|
||
const targetScrollTop = Math.max(
|
||
0,
|
||
scrollContentHeight - scrollViewHeight
|
||
);
|
||
|
||
uni.pageScrollTo({
|
||
scrollTop: scrollViewHeight,
|
||
duration: 0, // 适当动画时长
|
||
success: () => console.log("滚动到底部成功"),
|
||
fail: (err) => console.error("滚动失败:", err),
|
||
});
|
||
});
|
||
}, 100); // 安卓/iOS可能需要更长延迟
|
||
});
|
||
},
|
||
formatTime(time) {
|
||
return toDayTime(time);
|
||
},
|
||
//处理图片尺寸,如果不处理宽高,新进入页面加载图片时候会闪
|
||
setPicSize(content) {
|
||
// 让图片最长边等于设置的最大长度,短边等比例缩小,图片控件真实改变,区别于aspectFit方式。
|
||
let maxW = uni.upx2px(250); //350是定义消息图片最大宽度
|
||
let maxH = uni.upx2px(250); //350是定义消息图片最大高度
|
||
if (content.w > maxW || content.h > maxH) {
|
||
let scale = content.w / content.h;
|
||
content.w = scale > 1 ? maxW : maxH * scale;
|
||
content.h = scale > 1 ? maxW / scale : maxH;
|
||
}
|
||
return content;
|
||
},
|
||
showImgPreview(item) {
|
||
uni.previewImage({
|
||
indicator: "none",
|
||
current: item.msg.content.url,
|
||
urls: this.msgImgList,
|
||
});
|
||
},
|
||
},
|
||
onPageScroll(e) {
|
||
if (e.scrollTop == 0 && !this.loadding) {
|
||
this.loadding = true;
|
||
setTimeout(() => {
|
||
this.show = true;
|
||
this.loadding = false;
|
||
}, 1000);
|
||
}
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.IMmsgContent-container {
|
||
padding-left: 20rpx;
|
||
padding-right: 20rpx;
|
||
padding-bottom: 146rpx;
|
||
box-sizing: border-box;
|
||
background-color: #fafafa;
|
||
font-size: 1rem;
|
||
font-family: -apple-system-font, Helvetica Neue, Helvetica, sans-serif;
|
||
min-height: 100vh;
|
||
|
||
&.disable-scroll {
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.u-navbar {
|
||
height: 88rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.tui-chat-content {
|
||
width: 100%;
|
||
|
||
/* 阻止默认触摸行为 */
|
||
pointer-events: auto;
|
||
/* 阻止滚动传递 */
|
||
overscroll-behavior: contain;
|
||
|
||
/* 当显示语音面板时添加的类 */
|
||
}
|
||
|
||
.penetrate-touch {
|
||
pointer-events: none;
|
||
}
|
||
|
||
.tui-chatbox {
|
||
max-width: 66%;
|
||
border-radius: 10rpx;
|
||
position: relative;
|
||
padding: 12rpx 22rpx;
|
||
font-size: 32rpx;
|
||
color: #333;
|
||
word-break: break-all;
|
||
word-wrap: break-word;
|
||
}
|
||
|
||
.tui-chatbox::before {
|
||
content: "";
|
||
position: absolute;
|
||
width: 0;
|
||
height: 0;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
border: 16rpx solid;
|
||
}
|
||
|
||
.tui-chatbox-left {
|
||
background: #fff;
|
||
// border: 1rpx solid #fff;
|
||
display: inline-block;
|
||
}
|
||
|
||
.tui-chatbox-left::before {
|
||
right: 100%;
|
||
border-color: transparent #fff transparent transparent;
|
||
}
|
||
|
||
.tui-chatbox-right {
|
||
background: #ffecf2;
|
||
// border: 1rpx solid #fe411b;
|
||
}
|
||
|
||
.tui-chatbox-right::before {
|
||
left: 100%;
|
||
border-color: transparent transparent transparent #ffecf2;
|
||
}
|
||
|
||
/*chatbox*/
|
||
|
||
.tui-chatbox ::v-deep {
|
||
.chat-bubble-box {
|
||
display: flex;
|
||
align-items: center;
|
||
.chatimg {
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
margin-right: 20rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.tui-chat-left,
|
||
.tui-chat-right {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
padding-top: 36rpx;
|
||
}
|
||
|
||
.tui-user-pic {
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
flex-shrink: 0;
|
||
border-radius: 50%;
|
||
display: block;
|
||
}
|
||
|
||
.tui-left {
|
||
margin-left: 26rpx;
|
||
}
|
||
|
||
.tui-right {
|
||
margin-right: 26rpx;
|
||
}
|
||
|
||
.tui-chat-right {
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.tui-chat-center {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 28rpx;
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
padding-top: 36rpx;
|
||
}
|
||
|
||
.tui-label {
|
||
display: inline-block;
|
||
background: rgba(0, 0, 0, 0.4);
|
||
color: #fff;
|
||
font-size: 24rpx;
|
||
line-height: 24rpx;
|
||
margin-top: 36rpx;
|
||
padding: 12rpx 16rpx;
|
||
text-align: center;
|
||
border-radius: 8rpx;
|
||
margin-left: 50%;
|
||
transform: translateX(-50%);
|
||
}
|
||
|
||
.tui-img-chatbox {
|
||
position: relative;
|
||
}
|
||
|
||
.tui-img-chatbox::after {
|
||
content: "";
|
||
position: absolute;
|
||
height: 200%;
|
||
width: 200%;
|
||
border: 1rpx solid #eaeef1;
|
||
transform-origin: 0 0;
|
||
-webkit-transform-origin: 0 0;
|
||
-webkit-transform: scale(0.5);
|
||
transform: scale(0.5);
|
||
left: 0;
|
||
top: 0;
|
||
border-radius: 20rpx;
|
||
}
|
||
|
||
.tui-chat-img {
|
||
max-width: 200rpx;
|
||
/* min-height: 80rpx; */
|
||
display: block;
|
||
border-radius: 10rpx;
|
||
}
|
||
|
||
.tui-chat-flex {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.tui-flex-center {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.tui-chat-voice {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
display: block;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.tui-rotate {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
.tui-chat-fail {
|
||
width: 50rpx;
|
||
height: 50rpx;
|
||
display: block;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.tui-mr {
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.tui-ml {
|
||
margin-left: 16rpx;
|
||
}
|
||
|
||
.tui-flex-end {
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.tui-flex-reverse {
|
||
flex-direction: row-reverse;
|
||
}
|
||
|
||
._root ::v-deep {
|
||
.chat-bubble-box {
|
||
display: flex;
|
||
align-items: center;
|
||
.chatimg {
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
margin-right: 20rpx;
|
||
}
|
||
}
|
||
.single-img {
|
||
width: 300rpx;
|
||
height: 300rpx;
|
||
}
|
||
}
|
||
|
||
.img {
|
||
image {
|
||
flex: 1;
|
||
max-width: 350upx;
|
||
max-height: 350upx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.IMmsgContent-ios-container {
|
||
padding-bottom: 220rpx;
|
||
}
|
||
</style>
|