You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1405 lines
42 KiB

<template name="im-input">
<view id="more-oprate">
<view class="im-container bg-gray" :style="[{paddingBottom:paddingB+'px',minHeight:InputBottom==0?'auto':InputBottom+50+ 'px'}]"><!-- bottom:footerHeight+'px', -->
<view class="im-footer bg-gray" v-if="contactid!==-2">
<view class="im-menus f-28" v-show="!isFocus || contact.is_group!=1" style="margin-bottom: 8rpx;" :class="[recShow ? 'cuIcon-keyboard' : 'cuIcon-sound']" hover-class="tap" @tap="showRec"></view>
<view class="im-menus f-24" v-show="isFocus && contact.is_group==1" style="margin-bottom: 13rpx;" @tap="modelName='userModel'">@</view>
<view class="im-msgarea" style="flex: 0.97;width: 400rpx;">
<!-- im-flex1 -->
<editor id="editor" style="width: 100%;line-break: anywhere;" class="solid-bottom bg-white im-input c-333" :adjust-position="false" maxlength="300" cursor-spacing="10"
@focus="InputFocus" @blur="InputBlur" @input="changeMsgText" @ready="onEditorReady" :read-only="readOnly" v-show="recShow==false"> </editor>
<view class="toolBox" v-show="recShow==true">
<view class="recorder" :class="{active:isUseRecorder}" @touchstart.prevent="startRecorder"
@touchend.prevent="endRecorder" @touchmove.prevent="moveRecorder" @touchcancel="cancelRecorder">
{{isUseRecorder ? '松开结束' : '按住说话'}}
</view>
</view>
<view class='im-flex im-space-between message-quote radius-6 im-align-items-center' v-if="quote">
<view class='text-overflow'>{{quote.content}}</view>
<view class="cuIcon-close" @tap="closeQuote"></view>
</view>
</view>
<mumu-recorder ref='recorderRef' @success='handlerSuccess' @error='handlerError' v-if="isH5"></mumu-recorder>
<view class="im-flex im-justify-content-start im-align-items-center" style="margin-bottom: 8rpx;" >
<view class="im-menus cuIcon-emoji f-28 ml-5" hover-class="tap" @tap="showAppBox(1)"></view>
<view class="im-menus cuIcon-roundadd f-28 mr-10" hover-class="tap" v-if="!inputMsg" @tap="showAppBox(2)"></view>
<view v-if="inputMsg" style="width: 140rpx;">
<button class="cu-btn bg-green shadow mr-10" @touchend.prevent="sendTextMsg">发送</button>
</view>
</view>
</view>
<view v-else class="im-footer bg-gray">
<view class="im-flex1 im-msgarea mr-10 ml-10">
<editor id="editor" class="solid-bottom bg-white im-input c-333" :adjust-position="false" maxlength="300" cursor-spacing="10"
@focus="InputFocus" @blur="InputBlur" @input="changeMsgText" @ready="onEditorReady" :read-only="readOnly" v-show="recShow==false"> </editor>
</view>
<view class="im-flex im-justify-content-start im-align-items-center mr-10" style="margin-bottom: 15rpx;" >
<button class="cu-btn bg-green shadow" @click="sendTextMsg1" :disabled="isAnswering">发送</button>
</view>
</view>
<!-- 表情窗口 -->
<view class="im-flex im-columns im-emoji-box" :style="[{height:boxHeight+'px'}]" v-if="appBox==1">
<scroll-view scroll-x class="bg-gray nav im-emoji-header" scroll-with-animation :scroll-left="scrollLeft">
<view class="cu-item" :class="index==TabCur?'text-green':''" v-for="(item,index) in emojiList" :key="index" @tap="tabSelect" :data-id="index" :data-name="item.name">
<view :class="[item.icon]" class="f-20"></view>
</view>
</scroll-view>
<scroll-view scroll-y class="bg-white im-emoji-body">
<view class="im-flex im-wrap im-justify-content-start im-align-items-center pd-10" :class="emojiName=='favors'?' cu-list grid col-5':''">
<view v-if="emojiName=='favors'" class="im-emoji-item">
<view class="upload-emoji" @tap="uploadEmoji"><text class="cuIcon-add c-999" style="vertical-align: sub;"></text></view>
</view>
<view v-for="(item,index) in currentEmojiList" class="im-emoji-item" :key="index">
<!-- <block v-for="(iteme,indexe) in imglist" :key="indexe" v-if="network_log == 'none'">
<view v-if="iteme.name==item.name">
<image :src="iteme.path" style="width:100rpx;height:100rpx" mode="aspectFit" :fade-show="false" lazy-load @tap="chooseDiyEmoji(item)" v-if="emojiName=='favors'"></image>
<image :src="iteme.path" style="width:44rpx;;height:44rpx" mode="aspectFit" lazy-load @tap="chooseEmoji(item)" v-else></image>
</view>
</block>
<block v-else> -->
<image :src="item.src" style="width:100rpx;height:100rpx" mode="aspectFit" :fade-show="false" lazy-load @tap="chooseDiyEmoji(item)" v-if="emojiName=='favors'"></image>
<image :src="item.src" style="width:44rpx;;height:44rpx" mode="aspectFit" lazy-load @tap="chooseEmoji(item)" v-else></image>
<!-- </block> -->
</view>
</view>
</scroll-view>
</view>
<!-- 工具栏窗口 -->
<view class="im-flex im-app-box im-flex im-justify-content-start im-wrap im-align-content-start pd-20" :style="[{height:boxHeight+'px'}]" v-if="appBox==2">
<view class="im-flex im-columns im-align-items-center mt-10 im-app-item" @tap="chooseImg">
<view class="bg-white cuIcon-album f-24 radius-10 im-app-item-icon"></view>
<view class="mt-5">照片</view>
</view>
<view class="im-flex im-columns im-align-items-center mt-10 im-app-item" @tap="chooseVideo">
<view class="bg-white cuIcon-video f-24 radius-10 im-app-item-icon"></view>
<view class="mt-5">视频</view>
</view>
<view class="im-flex im-columns im-align-items-center mt-10 im-app-item" @tap="chooseFile">
<view class="bg-white cuIcon-file f-24 radius-10 im-app-item-icon"></view>
<view class="mt-5">文件</view>
</view>
<view class="im-flex im-columns im-align-items-center mt-10 im-app-item" v-if='!contact.is_group && (isH5 || isApp) && parseInt(globalConfig.chatInfo.webrtc)' @tap="calling(0)">
<view class="bg-white cuIcon-dianhua f-24 radius-10 im-app-item-icon"></view>
<view class="mt-5">语音通话</view>
</view>
<view class="im-flex im-columns im-align-items-center mt-10 im-app-item" v-if='!contact.is_group && (isH5 || isApp) && parseInt(globalConfig.chatInfo.webrtc)' @tap="calling(1)">
<view class="bg-white cuIcon-record f-24 radius-10 im-app-item-icon"></view>
<view class="mt-5">视频通话</view>
</view>
<view class="im-flex im-columns im-align-items-center mt-10 im-app-item" @tap="chooseLocation">
<view class="bg-white cuIcon-location f-24 radius-10 im-app-item-icon"></view>
<view class="mt-5">位置</view>
</view>
<view class="im-flex im-columns im-align-items-center mt-10 im-app-item" @tap="modelName='contactModel'">
<view class="bg-white cuIcon-addressbook f-24 radius-10 im-app-item-icon"></view>
<view class="mt-5">名片</view>
</view>
</view>
</view>
<view class="voice-model c-white radius-10 im-flex im-columns im-align-items-center pd-10" v-show="isUseRecorder">
<view class="cuIcon-voicefill mt-15 mb-5 f-28" :class="[isCancel ? 'c-red' : 'voice-icon']"></view>
<view :class="[isCancel ? 'c-red' : '']">
{{isCancel ? '松开取消' : '正在录音'}}
</view>
</view>
<view class="cu-modal bottom-modal" :class="modelName=='userModel'?'show':''" @tap="closeModel()">
<view class="cu-dialog" v-if="modelName=='userModel'">
<view class="cu-bar bg-white">
<view class="action text-gray" @tap="closeModel()">取消</view>
<view class="f-16">选择提醒的人</view>
<view class="action text-green" @tap="at()">完成</view>
</view>
<view class="manage-content" style="height:500px" @tap.stop=''>
<scroll-view style="height:500px" scroll-y="true">
<user-select v-if="contact.is_group==1" :type="4" :contact_id="contact.id" ref="userSelect" @setData="setAtList"></user-select>
</scroll-view>
</view>
</view>
</view>
<view class="cu-modal bottom-modal" :class="modelName=='contactModel'?'show':''" @tap="closeModel()">
<view class="cu-dialog" v-if="modelName=='contactModel'">
<view class="cu-bar bg-white">
<view class="action text-gray" @tap="closeModel()">取消</view>
<view class="f-16">选择分享的联系人</view>
<view class="action text-green" @tap="sendContactCard()">完成</view>
</view>
<view class="manage-content" style="height:500px" @tap.stop=''>
<scroll-view style="height:500px" scroll-y="true">
<user-select :type="1" :multiple="false" :contact_id="contact.id" ref="contactSelect"></user-select>
</scroll-view>
</view>
</view>
</view>
</view>
</template>
<script>
import MumuRecorder from '@/uni_modules/mumu-recorder/components/mumu-recorder/mumu-recorder.vue'
import userSelect from '@/components/message/user-select.vue';
import Edit from '@/utils/edit.js'
import emoji from '@/utils/emoji.js'
import { useMsgStore } from '@/store/message';
import { useloginStore } from '@/store/login';
import pinia from '@/store/index'
// #ifdef APP-PLUS
import {getSavedImages3} from '@/utils/LocalFileSystemURL.js'
// #endif
const msgStore = useMsgStore(pinia);
const userStore = useloginStore(pinia);
export default {
name : "im-input",
components: { MumuRecorder,userSelect },
props : {
boxStatus:{type:Number, default:0},
contactid:{type:Number, default:0},
contact:{type:Object, default:{}},
isAnswering:{type:Boolean, default:false}
},
data() {
return {
editorCtx:null,
InputBottom : 0,
paddingB : 0,
footerHeight : 50,
boxHeight : 300,
uploading : false,
recShow : false,
inputMsg : "",
recorderManager : null,
recing : false,
recLength : 1,
recTimer : null,
tmpVoice : '',
isUseRecorder: false,
playItemIndex: -1,
currentAudio: '',
mainHeight:0,
isCancel:false,
isH5:false,
isApp:false,
appBox:0,
TabCur: 0,
emojiName:'',
scrollLeft: 0,
emojiList:[],
currentEmojiList:[],
isFocus:false,
globalConfig:userStore.globalConfig,
userInfo:userStore.userInfo,
readOnly:false,
edit: null,
modelName:false,
isAt:false,
quote:'',
boardHeight:0,
inputMsg1:"",
network_log:'',
imglist:[],
FileImg:uni.getStorageSync('FileImg')
}
},
watch:{
boxStatus(val){
this.appBox=0;
this.InputBottom=0;
},
appBox(val){
// 如果没有打开应用盒子,输入框的高度设置为0;
if(val==0 && !this.isFocus){
this.InputBottom=0;
}
},
InputBottom(val){
this.$emit('setPad',val);
},
inputMsg1(val){
const text1 = val.replace(/<(\/?)(?!img\b)[^>]+>/g, '');
const hasImg = /<img\b[^>]*>/i.test(text1);
if(hasImg){
const imgSrcList = [];
const imgRegex = /<img\b[^>]*src=["']?([^"'>]+)["']?[^>]*>/gi;
let imgMatch;
while ((imgMatch = imgRegex.exec(text1)) !== null) {
imgSrcList.push(imgMatch[1]);
}
imgSrcList.forEach(src => {
this.editorCtx.insertImage({
src: src,
// alt: item.title,
width: 18,
height: 18,
nowrap:true,
extClass:'emoji-image',
success: ()=>{
},
});
})
}else{
this.editorCtx.insertText({
text: text1
})
}
// console.log(text1,'123');
}
},
created : function(){
// #ifndef H5
// 监听键盘高度
// uni.onKeyboardHeightChange(res => {
// if(this.appBox==0 || res.height>0){
// this.boardHeight=res.height;
// }
// })
// #endif
uni.$on('updateEmoji',(res) => {
this.getEmojiList()
})
this.currentEmojiList=emoji[0]['children'];
this.getEmojiList();
// #ifdef APP-PLUS
// this.downloadFileImg()
// #endif
uni.getSystemInfo({
success: res => {
let windowHeight = res.windowHeight;
this.mainHeight=windowHeight;
}
});
// #ifdef H5
this.isH5=true;
// this.paddingB=15;
// this.footerHeight=65;
// #endif
// #ifdef APP-PLUS
this.isApp=true;
// #endif
// #ifndef H5
this.isH5=false;
this.recorderManager = uni.getRecorderManager();
this.recorderManager.onStop((res) => {
this.tmpVoice = res.tempFilePath;
this.recing = false;
if(this.recLengt<1){
// 检测录音是否大于1秒
return this.checkRecorder(this.recLength);
}else{
// 发送语音消息
this.sendVoiceMsg();
}
});
this.recorderManager.onError(() => {
uni.showToast({ title: '录音失败', icon: 'none' });
this.recing = false;
});
// #endif
// #ifdef MP
try {
var res = uni.getSystemInfoSync();
res.model = res.model.replace(' ', '');
res.model = res.model.toLowerCase();
var res1 = res.model.indexOf('iphonex');
if(res1 > 5){res1 = -1;}
var res2 = res.model.indexOf('iphone1');
if(res2 > 5){res2 = -1;}
if(res1 != -1 || res2 != -1){
let paddingB = uni.upx2px(50);
this.paddingB =paddingB;
this.footerHeight=55+paddingB;
}
} catch (e){return null;}
// #endif
// this.network_log = uni.getStorageSync('network_log')
// if(this.network_log == 'none'){
// this.getImagePath()
// }
},
methods:{
updateKeyboardHeightChange(res){
if(this.appBox==0 || res.height>0){
this.boardHeight=res.height;
}
},
msgItem(){
return {
id:this.$util.getUuid(),
sendTime:new Date().getTime(),
status: "going",
type: "text",
content: "",
is_read: 0,
is_group: 0,
file_id: 0,
file_cate: 0,
fileName: "",
fileSize: 0,
extends: null,
isImageInserted: false
}
},
// 设置@提醒的人
at(){
this.isAt=true;
let data=this.$refs.userSelect.selectUser;
this.closeModel();
this.edit.addLink({
prefix: '@',
data:data
})
setTimeout(()=>{
this.getFocus();
},100)
},
// 设置@提醒的人
sendContactCard(){
let data=this.$refs.contactSelect.selectUser;
if(data.length!=1){
return uni.showToast({
icon:'none',
title:'必须选择一位联系人'
})
}
this.closeModel();
let contact=data[0];
let message={
type:'contact',
status:'going',
content:'[个人名片] '+contact.displayName,
extends:{
avatar:contact.avatar,
displayName:contact.displayName,
id:contact.id
}
};
this.$emit('send',Object.assign(this.msgItem(), message),false);
},
// 关闭弹窗
closeModel(){
if(this.modelName=="userModel"){
this.$refs.userSelect.selectUser=[];
this.$refs.userSelect.changeUser=[];
}
this.modelName='';
},
setAtList(item){
const val = {user_id:item.user_id,realname:item.realname?item.realname:item.realname1}
this.isAt=true;
this.closeModel();
this.edit.addLink({
prefix: '@',
data: item.avatar?val:item
})
},
// 编辑器获得焦点
getFocus(){
this.editorCtx.format('fontFamily', 'inherit');
this.isFocus=true;
},
chooseDiyEmoji(item){
let message={
type:'emoji',
content:item.src,
file_id:item.file_id,
status:'going'
}
this.$emit('send',Object.assign(this.msgItem(), message),false);
},
// 选择表情
chooseEmoji(item){
// #ifdef H5
this.editorCtx.insertImage({
src: item.src,
alt: item.title,
width: 18,
height: 18,
nowrap:true,
extClass:'emoji-image',
success: ()=>{
},
complete: ()=> {
this.editorCtx.blur();
this.showAppBox(1)
},
});
// #endif
// #ifndef H5
this.readOnly= true
setTimeout(()=>{
this.editorCtx.insertImage({
src: item.src,
alt: item.title,
width: 18,
height: 18,
nowrap:true,
extClass:'emoji-image',
success: function() {
},
complete: ()=> {
this.readOnly= false
},
});
},10);
// #endif
},
tabSelect(e) {
this.TabCur = e.currentTarget.dataset.id;
this.emojiName = e.currentTarget.dataset.name;
this.scrollLeft = (e.currentTarget.dataset.id - 1) * 60;
this.currentEmojiList=this.emojiList[this.TabCur]['children'];
},
showAppBox(val){
if(this.appBox==val){
this.appBox=0;
this.InputBottom=0;
}else{
this.appBox=val;
this.InputBottom=this.boxHeight;
this.recShow = false;
}
},
// 展示录音界面
showRec : function(){
this.InputBottom=0;
this.appBox=0;
this.recShow == true ? this.recShow = false : this.recShow = true
},
// 发送录音
sendVoiceMsg: function(){
if (this.tmpVoice == '') {
uni.showToast({ title: "录制已取消", icon: "none" });
return;
}
let res={
localUrl:this.tmpVoice,
duration:this.recLength
}
this.handlerSuccess(res);
this.tmpVoice = '';
this.recLength = 0;
},
// 发送文本消息
sendTextMsg: function () {
if(this.appBox!=1){
this.isFocus=true;
}
this.editorCtx.getContents({
success:(e)=>{
let msg=e.html;
let msg1=e.text.replace(/\n/g, ' ').trim();
const contentText = /^\s*$/.test(msg.replace(/<[^>]+>/g, ""));
const hasImg = /<img\b/i.test(msg);
// console.log('消息发送',msg);
let result = [];
// #ifdef H5
const parser = new DOMParser();
const doc = parser.parseFromString(msg, 'text/html');
const images = doc.querySelectorAll('img');
images.forEach(img => {
if (!img.classList.contains('emoji-image')) {
result.push(img.src);
}
});
// #endif
// #ifdef APP
// 匹配所有 img 标签
const imgRegex1 = /<img[^>]+>/g;
// 匹配 class 属性中的 emoji-image
const emojiClassRegex = /class=["'][^"']*emoji-image[^"']*["']/i;
const images1 = msg.match(imgRegex1) || [];
images1.forEach(imgTag => {
if (!emojiClassRegex.test(imgTag)) {
// 提取 src 属性
const srcMatch = imgTag.match(/src=["']([^"']+)["']/);
if (srcMatch && srcMatch[1]) {
result.push(srcMatch[1]);
}
}
});
// #endif
// console.log('图片消息',result);
if (msg == '<p><br></p>'||(contentText && !hasImg)) {
uni.showToast({ title: "内容不能为空", icon: "none" });
this.inputMsg = '';
this.editorCtx.clear();
return false;
}
if(result.length>0){
let message={
type:'image',
status:'going'
};
result.forEach((res)=>{
message.imgid=1;
message.content=res;
// console.info(res);
this.$emit('send',Object.assign(this.msgItem(), message),res);
})
this.inputMsg = '';
this.editorCtx.clear();
}else{
// 获取@的所有人
this.edit.getLink().then((e)=>{
let message={
type:'text',
content:msg,
plain_text:msg1,
extends:this.quote
}
// 如果有qute就是引用消息
if(this.quote.msg_id){
message.pid=this.quote.msg_id;
message.extends=this.quote;
}
const userList = Array.from(new Set(e));
message.at=userList;
this.inputMsg = '';
this.closeQuote();
this.editorCtx.clear();
if(this.appBox!=1){
this.getFocus();
setTimeout(()=>{
this.isFocus=true;
},10)
}
this.$emit('send',Object.assign(this.msgItem(), message),'');
});
}
},
fail:(e)=>{
this.inputMsg = '';
this.editorCtx.clear();
this.editorCtx.format('fontFamily', 'inherit');
console.info('错误');
}
})
},
sendTextMsg1(){
this.editorCtx.getContents({
success:(e)=>{
let msg=e.html;
let msg1=e.text.replace(/\n/g, ' ').trim();
const contentText = /^\s*$/.test(msg.replace(/<[^>]+>/g, ""));
if (msg == '<p><br></p>'||contentText) {
uni.showToast({ title: "内容不能为空", icon: "none" });
this.inputMsg = '';
this.editorCtx.clear();
return false;
}
this.edit.getLink().then((e)=>{
let message={
type:'text',
content:msg,
plain_text:msg1,
extends:this.quote
}
// 如果有qute就是引用消息
if(this.quote.msg_id){
message.pid=this.quote.msg_id;
message.extends=this.quote;
}
const userList = Array.from(new Set(e));
message.at=userList;
this.inputMsg = '';
this.closeQuote();
this.editorCtx.clear();
if(this.appBox!=1){
this.getFocus();
setTimeout(()=>{
this.isFocus=true;
},10)
}
this.$emit('send',Object.assign(this.msgItem(), message),'');
this.opengpt(msg1)
});
}
})
},
opengpt(text){
const val = text;
this.$emit('sendChat',val);
},
uploadEmoji(){
uni.navigateTo({
url: '/pages/message/emoji'
})
return;
},
// 选择图片
chooseImg(){
let message={
type:'image',
status:'going'
};
uni.chooseImage({
count : 9,
sizeType: ['original', 'compressed'],
sourceType : ['album', 'camera'],
success : (res)=>{
const tempFiles = res.tempFiles;
tempFiles.forEach((item) => {
message.content=item.path;
message.fileName=item.name;
message.fileSize=item.size;
this.$emit('send',Object.assign(this.msgItem(), message),item.path);
})
}
});
},
// 选择视频
chooseVideo: function () {
let _this = this;
let message={
type:'video',
status:'going'
};
uni.showModal({
title:"提示",
content:"是否需要进行压缩视频",
confirmText:"是",
cancelText:"否",
success(e) {
uni.chooseVideo({
sourceType: ['camera', 'album'],
compressed:e.confirm,
success: (res) => {
// if(res.duration>60){
// return uni.showToast({
// title: '视频长度不能超过60秒'
// })
// }
const tempFilePaths = res.tempFilePath;
let fixMode=(res.width > res.height) ? 1 : 2;
let arr={
duration:Math.ceil(res.duration),
width:res.width,
height:res.height,
fixMode:fixMode,
poster:''
};
message.fileName=res.name;
message.fileSize=res.size;
message.extends=arr;
message.content=tempFilePaths;
message.confused = e.confirm;
_this.$emit('send',Object.assign(_this.msgItem(), message),tempFilePaths);
}
});
}
})
},
// 选择文件
// chooseFile:function(){
// let self=this;
// // #ifdef H5
// uni.chooseFile({
// count: 5, //默认100
// success: function (res) {
// self.appendFile(res);
// }
// });
// // #endif
// // #ifdef MP
// wx.chooseMessageFile({
// count: 5, //默认100
// success: function (res) {
// self.appendFile(res);
// }
// });
// // #endif
// // #ifdef APP-PLUS
// const lemonjkFileSelect = uni.requireNativePlugin('lemonjk-FileSelect');
// lemonjkFileSelect.showPicker({
// pathScope: "/Download", // 各属性配置见下方【showPicker可配置参数说明】
// mimeType: "*/*",
// utisType:"public.data",
// multi:'yes',
// }, result => {
// // 未授权文件读取权限,可以提示用户未打开读取文件权限(仅安卓)
// if(result.code==1001){
// uni.showModal({
// title:"需要文件访问权限",
// content:"您还未授权本应用读取文件。为保证您可以正常上传文件,请在权限设置页面打开文件访问权限(不同手机厂商表述可能略有差异)请根据自己手机品牌设置",
// confirmText:"去授权",
// cancelText:"算了",
// success(e) {
// if(e.confirm){
// // 跳转到应用设置页
// lemonjkFileSelect.gotoSetting();
// }
// }
// })
// }
// let type='file';
// let imageExts=['jpg','jpeg','png','bmp','gif'];
// let videoExts=['mp4','3gp','avi','m2v','mkv','mov'];
// result.files.forEach((item)=>{
// if(imageExts.includes(item.fileExtension)){
// type='image';
// }else if(videoExts.includes(item.fileExtension)){
// type='video';
// }else{
// type='file';
// }
// let filePath='file://'+item.filePath;
// const message={
// type:type,
// status:'going',
// fileName:item.FileName,
// fileSize:item.fileSize,
// content:filePath
// };
// this.$emit('send',Object.assign(this.msgItem(), message),filePath);
// })
// })
// // #endif
// },
chooseFile: function() {
let self = this;
// 允许的文件类型(按扩展名)
const allowedExtensions = ['pdf', 'doc', 'docx', 'txt', 'xls', 'xlsx', 'ppt', 'pptx', 'zip', 'rar'];
// #ifdef H5
uni.chooseFile({
count: 5,
accept: '.' + allowedExtensions.join(',.'), // 限制选择类型
success: function(res) {
const filteredFiles = res.tempFiles.filter(file => {
const ext = file.name.split('.').pop().toLowerCase();
return allowedExtensions.includes(ext);
});
if (filteredFiles.length > 0) {
self.appendFile({ tempFiles: filteredFiles });
} else {
uni.showToast({ title: '只能上传PDF/Word/Excel等文档', icon: 'none' });
}
}
});
// #endif
// #ifdef MP
wx.chooseMessageFile({
count: 5,
type: 'file', // 小程序专用参数:只选文档
success: function(res) {
const filteredFiles = res.tempFiles.filter(file => {
const ext = file.name.split('.').pop().toLowerCase();
return allowedExtensions.includes(ext);
});
if (filteredFiles.length > 0) {
self.appendFile({ tempFiles: filteredFiles });
} else {
uni.showToast({ title: '只能上传PDF/Word/Excel等文档', icon: 'none' });
}
}
});
// #endif
// #ifdef APP-PLUS
const lemonjkFileSelect = uni.requireNativePlugin('lemonjk-FileSelect');
lemonjkFileSelect.showPicker({
pathScope: "/Download",
mimeType: "application/*", // 限制MIME类型
utisType: "public.data",
multi: 'yes'
}, result => {
if (result.code == 1001) {
uni.showModal({
title: "需要文件访问权限",
content: "请授权文件访问权限",
confirmText: "去授权",
success(e) {
if (e.confirm) lemonjkFileSelect.gotoSetting();
}
});
return;
}
// 过滤文件类型
const validFiles = result.files.filter(item => {
const ext = item.fileExtension.toLowerCase();
return allowedExtensions.includes(ext);
});
if (validFiles.length === 0) {
uni.showToast({ title: '只能上传PDF/Word/Excel等文档', icon: 'none' });
return;
}
validFiles.forEach(item => {
const message = {
type: 'file', // 强制设为文件类型
status: 'going',
fileName: item.FileName,
fileSize: item.fileSize,
content: 'file://' + item.filePath
};
this.$emit('send', Object.assign(this.msgItem(), message), message.content);
});
});
// #endif
},
// 写入文件
appendFile(res){
const tempFiles=res.tempFiles;
tempFiles.forEach((item) => {
let path=item.path;
// #ifdef APP-PLUS
path='file://'+ item.path;
// #endif
let message={
type:'file',
status:'going',
fileName:item.name,
fileSize:item.size,
content:path
};
// #ifdef H5
let type=item.type;
if(type.indexOf("image/")!=-1){
message.type="image";
}
if(type.indexOf("video/")!=-1){
message.type="video";
}
// #endif
this.$emit('send',Object.assign(this.msgItem(), message),path);
})
},
// 选择定位
chooseLocation(){
uni.chooseLocation({
success: (res) => {
// console.log('123456:' + res);
// console.log('位置名称:' + res.name);
// console.log('详细地址:' + res.address);
// console.log('纬度:' + res.latitude);
// console.log('经度:' + res.longitude);
let message={
type:'location',
status:'going',
content:'[位置] '+res.name,
extends:res
};
this.$emit('send',Object.assign(this.msgItem(), message),false);
}
})
// uni.navigateTo({
// url:'/pages/mapselect/mapselect'
// })
},
calling(is_video){
// #ifdef MP
return uni.showToast({
title:'小程序暂不支持',
icon:'none'
})
// #endif
if(!parseInt(this.globalConfig.chatInfo.webrtc)){
return uni.showToast({
title:'未开启音视频通话',
icon:'none'
})
}
if(!parseInt(this.globalConfig.chatInfo.simpleChat)){
return uni.showToast({
title:'系统已关闭私聊',
icon:'none'
})
}
if(msgStore.webrtcLock){
return uni.showToast({
title:'其他终端正在通话中',
icon:'none'
})
}
let msg_id=this.$util.getUuid();
uni.navigateTo({
url: '/pages/message/call?msg_id='+msg_id+'&type='+is_video+'&status=1&id='+this.contact.id+'&name='+this.contact.displayName+'&avatar='+encodeURI(this.contact.avatar)
})
},
changeMsgText(e){
// 只有设置过@后才回去检查@时间
if(this.isAt){
this.edit.eventLink(e.detail);
if(this.edit.isSetContents){
setTimeout(()=>{
this.getFocus()
},200);
}
}
const html = String(e.detail.html || '');
// const urlMatch = html.match(/<p>(\s|<br\s*\/?>)*(<img[^>]*>(\s|<br\s*\/?>)*)*(https?:\/\/[^\s]+?)\s*<\/p>/i);
const urlMatch = html.match(/<p>(?:<span[^>]*>.*?<\/span>)*(\s|<br\s*\/?>)*(<img[^>]*>(\s|<br\s*\/?>)*)*(https?:\/\/[^\s]+?)\s*<\/p>/i);
const url = urlMatch ? urlMatch[4] : '';
// 检查是否是图片 URL
const isImageUrl = /\.(png|jpe?g|gif|webp|svg|bmp)(\?.*)?$/i.test(url);
// console.log(e);
if (urlMatch&&isImageUrl) {
this.img1(urlMatch[4]);
return
}
const txt=html.replace(/\n/g, '');
if((txt==''||txt=='<p><br></p>') && e.detail.html=='<p><br></p>'){
this.inputMsg='';
}else{
this.inputMsg=e.detail.html;
}
},
img1(val){
if (val) {
const _this = this
this.editorCtx.insertImage({
src: val,
width: 50,
height: 'auto',
nowrap: true
});
setTimeout(() => {
this.editorCtx.getContents({
success: (res) => {
const html = res.html;
// 转义URL中的特殊字符以确保正则匹配
const escapedUrl = val.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// 匹配<p>标签内的URL
let regex;
let newHtml;
// #ifdef APP
regex = new RegExp(
`(<p[^>]*>\\s*)` + // 分组1: <p>开始
`((?:<img[^>]*>\\s*)*)` + // 分组2: 前置图片
`${escapedUrl}` + // 要删除的URL
`((?:\\s*<img[^>]*>)*)` + // 分组3: 后置图片
`(\\s*</p>)`, // 分组4: </p>结束
'gi');
newHtml = html.replace(regex, '$1$2$3$4');
// #endif
// #ifdef H5
regex = new RegExp(`(<img[^>]*>(\\s|<br\\s*\/?>)*)${escapedUrl}(?=[^<]*</p>)`, 'gi');
newHtml = html.replace(regex, '$1');
// #endif
// console.log('1',escapedUrl);
// console.log('2',regex);
// console.log('3',newHtml);
// 更新编辑器内容
this.editorCtx.setContents({
html: newHtml,
success: () => {
// console.log(_this.editorCtx);
}
});
}
});
}, 0); // 使用setTimeout确保插入完成后再删除
}
},
onEditorReady() {
// #ifdef MP-BAIDU
this.editorCtx = requireDynamicLib('editorLib').createEditorContext('editor');
// #endif
// #ifdef APP-PLUS || MP-WEIXIN || H5
const query = uni.createSelectorQuery().in(this);
query.select('#editor').context((res) => {
this.edit = new Edit({context: res.context,maxCount: 300});
this.editorCtx = res.context
}).exec()
// #endif
},
InputFocus(e) {
this.isFocus=true;
if(this.appBox>0){
// 关闭表情盒子和更多盒子
this.appBox=0;
}
this.InputBottom = 0;
},
getInputTop(){
console.log('123456');
const query=this.$util.getQuery(this);
setTimeout(()=>{
query.select('#editor').boundingClientRect();
query.exec(data =>{
if(this.boardHeight>0){
//如果editord的top,还没屏幕减去键盘高度大,说明editor还在下面,给InputBottom增加一个键盘高度
if((this.mainHeight-this.boardHeight)>data[0].top){
this.InputBottom = 0;
}else{
this.InputBottom =this.boardHeight;
}
}
})
},10)
},
InputBlur(e) {
if(!this.appBox && !this.isFocus){
this.InputBottom = 0;
}
setTimeout(()=>{
this.isFocus=false;
},10)
},
// 开始录音
startRecorder() {
console.log('录音开始...')
// #ifdef H5
this.$refs.recorderRef.start()
// #endif
// #ifndef H5
this.recorderManager.start({duration:60000, format:'mp3' });
this.recLength = 0;
this.recTimer = setInterval(()=>{this.recLength++;}, 1000);
// #endif
this.isUseRecorder = true
},
// 结束录音
endRecorder() {
console.log('录音结束')
// #ifdef H5
this.$refs.recorderRef.stop()
// #endif
// #ifndef H5
this.recorderManager.stop();
clearInterval(this.recTimer);
// #endif
this.isUseRecorder = false
},
//中断录音
cancelRecorder(){
this.endRecorder();
this.isCancel=true;
},
// 移除录音按钮
moveRecorder(e){
let touch=e.touches[0];
if(touch.clientY<this.mainHeight-80){
this.isCancel=true;
}else{
this.isCancel=false;
}
},
handlerSuccess(res) {
this.checkRecorder(res.duration);
if(this.isCancel){
this.isCancel=false;
return console.log('录音已取消');
}
let message={
type:'voice',
content:res.localUrl,
fileName:this.$util.getUuid()+'.mp3',
extends:{
duration: res.duration
}
}
this.$emit('send',Object.assign(this.msgItem(), message));
},
// 检测录音是否合格
checkRecorder(duration){
if(duration<1 || isNaN(duration) || !duration){
this.recLength = 0;
this.tmpVoice = '';
this.recing = false;
this.isCancel=true;
return uni.showToast({
title: '录音时间太短',
icon: 'error'
})
}
},
handlerError(code) {
switch (code) {
case '201':
uni.showModal({
content: '麦克风权限被拒绝请刷新页面后授权麦克风权限'
})
break
default:
console.log('录音功能受限请知晓')
break
}
},
closeQuote(){
this.quote='';
},
// 引用消息
quoteMessage(quote){
this.quote=quote;
// 如果是群聊.需要@人员
if(this.contact.is_group==1 && quote.user_id!=this.userInfo.user_id){
this.setAtList({
user_id:quote.user_id,
realname:quote.realname,
})
}
},
getEmojiList(){
this.$api.emojiApi.emojiList({}).then((res)=>{
if(res.code==0){
emoji[1]['children']=res.data;
if(this.TabCur==1){
this.currentEmojiList=res.data;
}
}
this.emojiList=emoji;
})
},
// downloadFileImg(){
// if(this.FileImg==false){
// // console.log('123456');
// var self1 = this;
// this.currentEmojiList.forEach((res)=>{
// // console.log(res);
// uni.downloadFile({ url: res.src,success: (downloadResult) => {
// self1.saveToPermanentStorage(downloadResult.tempFilePath);
// }})
// });
// }
// },
// // App端持久化存储实现
// saveToPermanentStorage(tempPath) {
// return new Promise((resolve, reject) => {
// // 获取应用文档目录(持久化存储)
// plus.io.resolveLocalFileSystemURL(
// '_doc',
// (docDir) => {
// // 创建目标路径
// docDir.getDirectory(
// 'emoji',
// { create: true, exclusive: false },
// (entry) => {
// // 从临时路径获取文件名
// const fileName = this.getFileName(tempPath);
// uni.setStorageSync('FileImg',true);
// const fileName1 = this.getFileName(docDir.fullPath + 'emoji/' +fileName);
// // console.log(fileName);
// // console.log(fileName1);
// // 新增:检查文件是否存在
// entry.getFile(fileName1,{ create: false }, // 不创建新文件
// (fileEntry) => {
// // console.log('文件已存在,拒绝操作');
// // 文件已存在,拒绝操作
// reject(new Error('文件已存在: ' + fileName));
// },
// (error) => {
// // console.log(error);
// // 文件不存在(或发生其他错误),继续复制操作
// if (error.code === 14) { // 1表示文件不存在(不同平台错误码可能不同)
// this.copyFile(tempPath, entry, fileName, resolve, reject);
// } else {
// reject(error);
// }
// }
// );
// },
// (error) => {
// reject(error);
// }
// );
// },
// (error) => {
// reject(error);
// }
// );
// });
// },
// // 提取复制逻辑为独立方法
// copyFile(tempPath, targetDir, fileName, resolve, reject) {
// plus.io.resolveLocalFileSystemURL(
// tempPath,
// (tempEntry) => {
// tempEntry.copyTo(
// targetDir,
// fileName,
// (newEntry) => {
// resolve(newEntry.toLocalURL());
// },
// (error) => {
// reject(error);
// }
// );
// },
// (error) => {
// reject(error);
// }
// );
// },
// // 获取文件名工具方法
// getFileName(path) {
// const index = path.lastIndexOf('/');
// let fileName = path.substr(index + 1);
// fileName = fileName.replace(/\(\d+\)(?=\.[^./]+$)/, '');
// return fileName;
// },
// async getImagePath(){
// this.imglist = await getSavedImages3()
// this.imglist.map(item => {
// item.path = plus.io.convertLocalFileSystemURL(item.path)
// item.name = item.name.replace(/\.png$/i, '');
// });
// // console.info('读取地址',this.imglist);
// },
}
}
</script>
<style lang="scss" scoped>
.im-footer{
// position:fixed; left:0; bottom:0;
padding:0; width:100%; min-height:100rpx;display:flex; flex-wrap:nowrap; overflow:hidden; box-shadow:1px 1px 6px #999999; align-items:flex-end;z-index:101}
.im-footer .items{width:auto; line-height:88rpx; flex-shrink:0; font-size:28rpx; color:#2B2E3D;}
.im-menus{width:80rpx; height:80rpx; flex-shrink:0; line-height:80rpx; text-align:center;}
.im-input{padding:14rpx 14rpx; border-radius:10rpx;margin:0 8rpx !important;height:100%;min-height:44rpx;max-height: 300rpx;font-size: 28rpx;word-break: break-all;}
.im-msgarea{padding:12rpx 10rpx 12rpx 0;}
.im-record{width:100%; position:fixed; left:0; bottom:0; background:#FFFFFF; padding:30px 0; padding-bottom:100rpx; z-index:11; box-shadow:1px 1px 6px #999999;}
.im-record-close{width:100rpx; height:100rpx; position:absolute; top:0px; right:0px; z-index:100; text-align:center; line-height:100rpx; color:#888888; font-size:38rpx !important;}
.im-record-txt{text-align:center; font-size:26rpx; line-height:30px; padding-bottom:10px; color:#CCCCCC;}
.im-record-btn{width:60px; height:60px; margin:0 auto; border:5px solid #F1F2F3; border-radius:100%; background:#00B26A;}
.im-recording{background:#FF0000; animation:fade linear 2s infinite;}
@keyframes fade{from{opacity:0.1;} 50%{opacity:1;} to{opacity:0.1;}}
.im-record-txt text{color:#00B26A; padding:0 12px;}
.im-send-voice{margin-top:12px; font-size:28rpx; color:#00BA62; text-align:center;}
.im-send-voice text{margin:0 15px; color:#00BA62;}
.toolBox {
height:72rpx;
margin-bottom:3rpx;
.recorder{
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
padding: 14rpx;
border-radius: 10rpx;
font-size: 28rpx;
box-shadow: 2rpx 2rpx 6rpx rgba(0, 0, 0, 0.2);
position: relative;
margin: 0 6rpx !important;
height:100%;
&.active {
background-color: #67C23A;;
color:#fff;
}
}
}
.voice-model{
width:240rpx;
height:180rpx;
position: fixed;
top: 50%;
z-index: 2;
transform: translate(-50%, -50%);
left: 50%;
padding: 0 4rpx 0 6rpx;
background-color: #363636b3;
}
.im-emoji-box{
// position:fixed;
// bottom:0;
width:100%;
z-index:3;
background-color: #F1F2F3;
.im-emoji-header{
height:90rpx;
}
.im-emoji-body{
height:100%;padding-bottom: 100rpx;
.im-emoji-item{
padding:22rpx;
}
}
}
.im-app-box{
position:fixed;bottom:0;width:100%;background-color: #F1F2F3;z-index:3;
.im-app-item{
width:160rpx;height:160rpx;
.im-app-item-icon{
padding:20rpx 25rpx;
}
}
}
.message-quote{
padding:8rpx;
font-size:24rpx;
margin:16rpx -10rpx 0 10rpx;
background-color: #e3e3e3;
.text-overflow{
overflow: hidden !important;
text-overflow: ellipsis;
white-space: nowrap !important;
width:380rpx;
}
}
.voice-icon{
animation: twinkle 0.5s infinite alternate;
}
@keyframes twinkle {
0%{
opacity:0.9;
}
100%{
opacity:0.3;
}
}
.upload-emoji{
width:90rpx;
height:90rpx;
border:dashed 1px #999;
font-size:50rpx;
text-align: center;
border-radius: 6rpx;
}
.im-container{
width: 100%;
min-height: 3.125rem;
box-shadow: 1px 1px 6px #999;
z-index: 1001;
border-top: solid 1px #e6e6e6;
}
</style>
<style>
.im-input /deep/ .ql-editor{
max-height:300rpx;
line-height: 1.5;
font-size: 30rpx !important;
word-break: break-all;
word-wrap: break-word;
padding-left: 3px;
}
.im-input /deep/ .ql-editor p img{
vertical-align: text-top;
}
</style>