Files
im/pages/conversation/chating/components/ChatingFooter/index.vue
T

552 lines
15 KiB
Vue
Raw Normal View History

2025-11-07 09:56:20 +08:00
<template>
<view>
<view>
<view class="chat_footer">
2025-12-02 03:05:52 +08:00
<!-- 语音信息 -->
2025-12-05 16:10:52 +08:00
<image v-if="1==2" v-show="!isAudio" @click.prevent="isAudio=!isAudio" src="@/static/images/chating_footer_audio.png" alt="" srcset="" />
<image v-if="1==2" v-show="isAudio" @click.prevent="isAudio=!isAudio" src="@/static/images/chating_footer_audio_recording.png" alt="" srcset="" />
2025-11-07 09:56:20 +08:00
<view class="input_content">
2025-12-02 03:05:52 +08:00
<!-- #ifdef APP-PLUS -->
<view v-if="isAudio" class="voice_title" @touchstart.stop.prevent="startVoice"
@touchmove.stop.prevent="moveVoice" @touchend.stop="endVoice"
@touchcancel.stop="cancelVoice" :style="{ background: recording ? '#c7c6c6' : '#FFFFFF' }">
<text>{{ voiceTitle }}</text>
</view>
<!-- #endif -->
<CustomEditor v-if="!isAudio" class="custom_editor" ref="customEditor" @ready="editorReady" @focus="editorFocus"
2025-11-07 09:56:20 +08:00
@blur="editorBlur" @input="editorInput" />
</view>
2025-12-02 03:05:52 +08:00
<view class="footer_action_area" v-show="!isAudio">
2025-12-05 16:10:52 +08:00
<image class="emoji_action" @click.prevent="updateActionBar(true)" src="@/static/images/chating_footer_emoji.png" alt="" srcset="" />
<image v-show="!hasContent" @click.prevent="updateActionBar(false)" src="@/static/images/chating_footer_add.png" alt="" srcset="" />
2025-12-02 03:05:52 +08:00
<button class="send_btn" type="primary" v-show="hasContent" @touchend.prevent="sendTextMessage">发送</button>
2025-11-07 09:56:20 +08:00
</view>
</view>
2025-12-05 16:10:52 +08:00
<chating-action-bar :isEmoji="isEmoji" @sendMessage="sendMessage($event,storeCurrentConversation.userID,storeCurrentConversation.groupID)" @prepareMediaMessage="prepareMediaMessage"
2025-11-07 09:56:20 +08:00
v-show="actionBarVisible" />
<u-action-sheet :safeAreaInsetBottom="true" round="12" :actions="actionSheetMenu" @select="selectClick"
:closeOnClickOverlay="true" :closeOnClickAction="true" :show="showActionSheet"
@close="showActionSheet = false">
</u-action-sheet>
</view>
2025-12-02 03:05:52 +08:00
<!-- 录音动画 -->
<!-- #ifdef APP-PLUS -->
<view class="voice_an" v-if="recording">
<view class="voice_an_icon">
<view id="one" class="wave"></view>
<view id="two" class="wave"></view>
<view id="three" class="wave"></view>
<view id="four" class="wave"></view>
<view id="five" class="wave"></view>
<view id="six" class="wave"></view>
<view id="seven" class="wave"></view>
</view>
<view class="text">
<text>{{voiceIconText}}</text>
</view>
</view>
<!-- #endif -->
2025-11-07 09:56:20 +08:00
</view>
</template>
<script>
2025-11-25 05:36:02 +08:00
import {mapGetters,mapActions} from "vuex";
import {getPurePath,html2Text} from "@/util/common";
import {offlinePushInfo} from "@/util/imCommon";
import {ChatingFooterActionTypes,UpdateMessageTypes,} from "@/constant";
import IMSDK, {IMMethods,MessageStatus,MessageType,} from "openim-uniapp-polyfill";
2025-11-07 09:56:20 +08:00
import UParse from "@/components/gaoyia-parse/parse.vue";
2025-12-08 18:10:51 +08:00
import CustomEditor from "./CustomEditor";
import ChatingActionBar from "./ChatingActionBar";
2025-11-07 09:56:20 +08:00
const needClearTypes = [MessageType.TextMessage];
2025-11-25 05:36:02 +08:00
const rtcChoose = [
{
name: "视频通话",
type: ChatingFooterActionTypes.Video,
2025-11-07 09:56:20 +08:00
idx: 0,
},
{
2025-11-25 05:36:02 +08:00
name: "语言通话",
type: ChatingFooterActionTypes.Voice,
2025-11-07 09:56:20 +08:00
idx: 1,
},
];
export default {
components: {
CustomEditor,
ChatingActionBar,
UParse,
},
props: {
footerOutsideFlag: Number,
},
data() {
return {
2025-12-02 03:05:52 +08:00
recording:false,
sendMsgTimmer: null, //发送时间定时器
sendDuring: 0, //发送时间计数器 1分钟以内的信息不显示时间
sendTimeBetween: 60, //发送信息显示的间隔,60秒以内信息不显示发送时间
isEmoji:false,
isAudio:false,
2025-11-07 09:56:20 +08:00
customEditorCtx: null,
inputHtml: "",
actionBarVisible: false,
isInputFocus: false,
actionSheetMenu: [],
showActionSheet: false,
};
},
computed: {
...mapGetters([
"storeCurrentConversation",
"storeCurrentGroup",
"storeBlackList",
]),
hasContent() {
return html2Text(this.inputHtml) !== "";
},
},
watch: {
footerOutsideFlag(newVal) {
this.onClickActionBarOutside();
},
},
mounted() {
this.setKeyboardListener();
},
beforeDestroy() {
this.disposeKeyboardListener();
},
methods: {
...mapActions("message", ["pushNewMessage", "updateOneMessage"]),
async createTextMessage() {
let message = "";
const text = html2Text(this.inputHtml);
message = await IMSDK.asyncApi(
IMMethods.CreateTextMessage,
IMSDK.uuid(),
text
);
console.log(message);
return message;
},
async sendTextMessage() {
if (!this.hasContent) return;
const message = await this.createTextMessage();
2025-12-05 16:10:52 +08:00
this.sendMessage(message,this.storeCurrentConversation.userID,this.storeCurrentConversation.groupID);
2025-11-07 09:56:20 +08:00
},
2025-12-05 16:10:52 +08:00
sendMessage(message,user_id,group_id) {
2025-11-07 09:56:20 +08:00
this.pushNewMessage(message);
if (needClearTypes.includes(message.contentType)) {
this.customEditorCtx.clear();
}
this.$emit("scrollToBottom");
IMSDK.asyncApi(IMMethods.SendMessage, IMSDK.uuid(), {
2025-12-05 16:10:52 +08:00
recvID: user_id,
groupID: group_id,
2025-11-07 09:56:20 +08:00
message,
offlinePushInfo,
})
2025-11-25 05:36:02 +08:00
.then(({data}) => {
2025-11-07 09:56:20 +08:00
this.updateOneMessage({
message: data,
isSuccess: true,
});
})
2025-11-25 05:36:02 +08:00
.catch(({data,errCode,errMsg}) => {
uni.$u.toast(errMsg);
2025-11-07 09:56:20 +08:00
this.updateOneMessage({
message: data,
type: UpdateMessageTypes.KeyWords,
2025-11-25 05:36:02 +08:00
keyWords: [
{
2025-11-07 09:56:20 +08:00
key: "status",
value: MessageStatus.Failed,
},
{
key: "errCode",
value: errCode,
},
],
});
});
},
2025-12-02 03:05:52 +08:00
recordAudioMsg(){
if (uni.getSystemInfoSync().platform == "android") {
permission.requestAndroid("android.permission.RECORD_AUDIO"); //Android请求录音权限
} else {
permission.requestIOS("record"); //ios请求录音权限
}
},
2025-11-07 09:56:20 +08:00
// action
onClickActionBarOutside() {
if (this.actionBarVisible) {
this.actionBarVisible = false;
}
},
2025-12-02 03:05:52 +08:00
updateActionBar(isEmoji) {
2025-11-07 09:56:20 +08:00
this.actionBarVisible = !this.actionBarVisible;
2025-12-02 03:05:52 +08:00
console.log(this.isEmoji);
this.isEmoji = !!isEmoji;
console.log(this.isEmoji);
2025-11-07 09:56:20 +08:00
},
editorReady(e) {
this.customEditorCtx = e.context;
this.customEditorCtx.clear();
},
editorFocus() {
this.isInputFocus = true;
this.$emit("scrollToBottom");
},
editorBlur() {
this.isInputFocus = false;
},
editorInput(e) {
this.inputHtml = e.detail.html;
},
2025-12-02 03:05:52 +08:00
prepareMediaMessage(type,extra) {
2025-11-25 05:36:02 +08:00
console.log(type)
if (type === ChatingFooterActionTypes.Video) {
this.actionSheetMenu = [...rtcChoose];
this.showActionSheet = true;
}
2025-11-07 09:56:20 +08:00
if (type === ChatingFooterActionTypes.Album) {
2025-11-25 05:36:02 +08:00
this.chooseOrShotImage(["album"]).then((paths) =>
this.batchCreateImageMesage(paths)
);
}
if (type === ChatingFooterActionTypes.Camera) {
this.chooseOrShotImage(["camera"]).then((paths) =>
this.batchCreateImageMesage(paths)
);
}
if (type === ChatingFooterActionTypes.Location) {
uni.chooseLocation({
complete(res) {
console.log(res);
},
fail(res) {
console.log(res);
}
//latitude:1,
//longitude:1,
})
2025-11-07 09:56:20 +08:00
}
2025-12-02 03:05:52 +08:00
if (type === "emoji") {
//TODO 在光标处插入文字extra
this.customEditorCtx.insertText({
text: extra,
success: () => {
console.log("插入文字成功");
},
fail: (err) => {
console.log("插入文字失败", err);
}
});
}
2025-11-07 09:56:20 +08:00
},
// from comp
batchCreateImageMesage(paths) {
2025-11-25 05:36:02 +08:00
/*
createAdvancedTextMessage
createTextAtMessage
createLocationMessage
createTextMessage
createCustomMessage
createQuoteMessage
createAdvancedQuoteMessage
createCardMessage
createImageMessage
createImageMessage
createImageMessageByURL
createSoundMessage
createSoundMessageFromFullPath
createSoundMessageByURL
createVideoMessage
createVideoMessageFromFullPath
createVideoMessageByURL
createFileMessage
createFileMessageFromFullPath
createFileMessageByURL
createMergerMessage
createFaceMessage
createForwardMessage
*/
2025-11-07 09:56:20 +08:00
paths.forEach(async (path) => {
const message = await IMSDK.asyncApi(
IMMethods.CreateImageMessageFromFullPath,
IMSDK.uuid(),
getPurePath(path)
);
2025-12-05 16:10:52 +08:00
this.sendMessage(message,this.storeCurrentConversation.userID,this.storeCurrentConversation.groupID);
2025-11-07 09:56:20 +08:00
});
},
2025-11-25 05:36:02 +08:00
selectClick({idx}) {
2025-11-07 09:56:20 +08:00
if (idx === 0) {
2025-11-25 05:36:02 +08:00
uni.$u.toast('根据相关政策,暂时禁用视频通话');
//发送视频通话
// this.chooseOrShotImage(["album"]).then((paths) =>
// this.batchCreateImageMesage(paths)
// );
2025-11-07 09:56:20 +08:00
} else {
2025-11-25 05:36:02 +08:00
uni.$u.toast('根据相关政策,暂时禁用音频通话');
//发送音频通话
// this.chooseOrShotImage(["camera"]).then((paths) =>
// this.batchCreateImageMesage(paths)
// );
2025-11-07 09:56:20 +08:00
}
},
chooseOrShotImage(sourceType) {
return new Promise((resolve, reject) => {
uni.chooseImage({
count: 9,
sizeType: ["compressed"],
sourceType,
2025-11-25 05:36:02 +08:00
success: function({tempFilePaths}) {
2025-11-07 09:56:20 +08:00
resolve(tempFilePaths);
},
fail: function(err) {
console.log(err);
reject(err);
},
});
});
},
// keyboard
2025-11-25 05:36:02 +08:00
keyboardChangeHander({height}) {
2025-11-07 09:56:20 +08:00
if (height > 0) {
if (this.actionBarVisible) {
this.actionBarVisible = false;
}
}
},
setKeyboardListener() {
uni.onKeyboardHeightChange(this.keyboardChangeHander);
},
disposeKeyboardListener() {
uni.offKeyboardHeightChange(this.keyboardChangeHander);
},
2025-12-02 03:05:52 +08:00
/*----------------------------------------------------H5不支持)录音相关 start-------------------------------------- */
//准备开始录音
startVoice(e) {
if (!this.Audio.paused) {
//如果音频正在播放 先暂停。
this.stopAudio(this.AudioExam)
}
this.recording = true;
this.isStopVoice = false;
this.voiceCanSend = true;
this.voiceIconText = "正在录音..."
this.PointY = e.touches[0].clientY;
this.Recorder.start({
format: 'mp3'
});
},
//录音已经开始
beginVoice() {
let that = this;
if (that.isStopVoice) {
that.Recorder.stop();
return;
}
that.voiceTitle = '松开 结束'
that.voiceInterval = setInterval(() => {
console.log("that.voiceTime", that.voiceTime);
if (that.voiceTime > 49) {
that.voiceIconText = "录音结束倒计时[" + (60 - that.voiceTime) + "]s";
};
if (that.voiceTime == 60) {
clearInterval(that.voiceInterval);
that.endVoice();
}
that.voiceTime++;
}, 1000)
},
//move 正在录音中
moveVoice(e) {
const PointY = e.touches[0].clientY;
const slideY = this.PointY - PointY;
if (slideY > uni.upx2px(120)) {
this.voiceCanSend = false;
this.voiceIconText = '松开手指 取消发送 '
} else if (slideY > uni.upx2px(60)) {
this.voiceCanSend = true;
this.voiceIconText = '手指上滑 取消发送 '
} else {
this.voiceIconText = '正在录音... '
}
},
//结束录音
endVoice() {
this.isStopVoice = true; //加锁 确保已经结束录音并不会录制
this.Recorder.stop();
this.voiceTitle = '按住 说话'
},
//录音被打断
cancelVoice(e) {
console.log("路由被打断", e);
this.voiceTime = 0;
this.voiceTitle = '按住 说话';
this.voiceCanSend = false;
this.Recorder.stop();
},
//处理录音文件
handleRecorder({tempFilePath,duration }) {
if (this.voiceTime < 1) {
this.voiceIconText = "说话时间过短";
setTimeout(() => {
this.recording = false;
}, 500)
return;
}
let contentDuration = this.voiceTime;
this.voiceTime = 0;
this.recording = false;
clearInterval(this.voiceInterval);
console.log("录音文件", tempFilePath);
console.log("是否发送语音信息", this.voiceCanSend);
let voiceFile = {
tempFilePath: tempFilePath,
contentDuration: Math.ceil(contentDuration),
anmitionPlay: false,
};
if (this.voiceCanSend) {
console.log("=====上传语音文件,并发送语音信息====");
let audioType = this.messageApi.CONTENT_TYPE.AUDIO_CONTENT_TYPE;
this.uploadFile(voiceFile, audioType);
return;
} else {
console.log("=====已经取消发送语音信息====")
return;
}
},
//控制播放还是暂停音频文件
handleAudio(item) {
this.AudioExam = item;
this.Audio.paused ? this.playAudio(item) : this.stopAudio(item);
},
//播放音频
playAudio(item) {
let target = item.content.fileSaveTarget;
let src = item.content.fullPath;
if (target == "local") {
src = this.$u.api.multipartAddress.getFileByPath + src;
}
this.Audio.src = src;
this.Audio.hasBeenSentId = item.id;
this.Audio.play();
let currentAudioMsg = this.messageList.find(it => it.id == item.id);
currentAudioMsg.content.anmitionPlay = true;
},
//停止音频
stopAudio(item) {
let currentAudioMsg = this.messageList.find(it => it.id == item.id);
currentAudioMsg.content.anmitionPlay = false;
this.Audio.src = '';
this.Audio.stop();
},
//关闭动画
closeAnmition() {
const hasBeenSentId = this.Audio.hasBeenSentId;
let item = this.messageList.find(it => it.id == hasBeenSentId);
item.content.anmitionPlay = false;
},
/*-------------------------------------录音相关方法块 end---------------------------------------------------*/
2025-11-07 09:56:20 +08:00
},
};
</script>
<style lang="scss" scoped>
.custom_editor {
img {
vertical-align: sub;
}
}
.forbidden_footer {
width: 100%;
height: 112rpx;
color: #8e9ab0;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
background: #f0f2f6;
}
.chat_footer {
display: flex;
2025-12-02 03:05:52 +08:00
align-items: flex-end;
2025-11-07 09:56:20 +08:00
// background-color: #e9f4ff;
2025-12-02 03:05:52 +08:00
background: #f6f6f6;
2025-11-07 09:56:20 +08:00
// height: 50px;
max-height: 120px;
padding: 24rpx 20rpx;
.input_content {
flex: 1;
min-height: 30px;
max-height: 120px;
margin: 0 24rpx;
border-radius: 8rpx;
position: relative;
.record_btn {
// background-color: #3c9cff;
background: #fff;
color: black;
height: 30px;
font-size: 24rpx;
}
}
.quote_message {
@include vCenterBox();
justify-content: space-between;
margin-top: 12rpx;
padding: 8rpx;
// padding-top: 20rpx;
border-radius: 6rpx;
background-color: #fff;
color: #666;
.content {
::v-deep uni-view {
@include ellipsisWithLine(2);
}
}
}
.footer_action_area {
display: flex;
align-items: center;
.emoji_action {
margin-right: 24rpx;
}
2025-12-08 18:10:51 +08:00
2025-11-07 09:56:20 +08:00
image {
width: 26px;
height: 26px;
}
}
.send_btn {
height: 30px;
line-height: 30px;
2025-12-02 03:05:52 +08:00
background-color: $uni-color-success;
2025-12-08 18:10:51 +08:00
padding: 0 8px;
2025-11-07 09:56:20 +08:00
border-radius: 4px;
color: #fff;
}
}
</style>