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

659 lines
18 KiB
Vue
Raw Normal View History

2025-11-07 09:56:20 +08:00
<template>
2026-01-12 18:07:21 +08:00
<view v-if="isMute">
<view class="forbidden_footer">
<view class="mute_tip" v-if="storeCurrentGroup.status === 3">全群禁言中</view>
<view class="mute_tip" v-else>您被禁言至{{date(storeCurrentMemberInGroup.muteEndTime)}}</view>
</view>
</view>
2026-01-15 22:50:35 +08:00
<view v-else-if="!isSingle && !storeCurrentMemberInGroup.userID">
<view class="forbidden_footer">
<view class="mute_tip">您不是群成员</view>
</view>
</view>
2026-01-12 18:07:21 +08:00
<view v-else>
2025-12-09 09:27:29 +08:00
<view class="chat_footer">
<!-- 语音信息 -->
2025-12-17 08:52:51 +08:00
<image class="action_btn" v-show="inputType == 'keyboard'" @click.prevent="swtichInputType('record')" mode="heightFix" src="@/static/images/chating_footer_audio.png" alt="" srcset="" />
<image class="action_btn" v-show="inputType == 'record'" @click.prevent="swtichInputType('keyboard')" mode="heightFix" src="@/static/images/chating_footer_audio_recording.png" alt="" srcset="" />
2025-12-09 09:27:29 +08:00
<view class="input_content">
2025-12-23 00:18:46 +08:00
<Recoder v-if="inputType == 'record'" @RecodEvent="onRecodEvent"></Recoder>
2025-12-09 09:27:29 +08:00
<SimpleEditor
2025-12-17 08:52:51 +08:00
v-if="inputType == 'keyboard'"
2025-12-09 09:27:29 +08:00
class="custom_editor"
ref="customEditor"
2025-12-23 00:18:46 +08:00
@onUserEvent="onEditorEvent"
:value="inputHtml"/>
2025-12-09 09:27:29 +08:00
</view>
2025-11-07 09:56:20 +08:00
2025-12-17 08:52:51 +08:00
<view class="footer_action_area" v-show="inputType == 'keyboard'">
<image class="action_btn" @click.prevent="updateActionBar(true)" src="@/static/images/chating_footer_emoji.png" alt="" srcset="" />
<image v-show="!hasContent" class="action_btn" @click.prevent="updateActionBar(false)" src="@/static/images/chating_footer_add.png" alt="" srcset="" />
2025-12-09 09:27:29 +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-17 08:52:51 +08:00
<chating-action-bar :isEmoji="isEmoji" @onUserEvent="onUserEvent" v-show="actionBarVisible" />
2025-12-09 09:27:29 +08:00
<u-action-sheet :safeAreaInsetBottom="true" round="12" :actions="actionSheetMenu" @select="selectClick"
:closeOnClickOverlay="true" :closeOnClickAction="true" :show="showActionSheet"
@close="showActionSheet = false">
</u-action-sheet>
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>
2026-01-12 18:07:21 +08:00
2025-11-07 09:56:20 +08:00
</template>
<script>
2025-11-25 05:36:02 +08:00
import {mapGetters,mapActions} from "vuex";
2025-12-11 22:33:31 +08:00
import {getPurePath,html2Text,getVideoCover,getVideoInfo} from "@/util/common";
2026-01-10 15:40:38 +08:00
import {offlinePushInfo,date} from "@/util/imCommon";
2025-11-25 05:36:02 +08:00
import {ChatingFooterActionTypes,UpdateMessageTypes,} from "@/constant";
2026-01-15 22:50:35 +08:00
import IMSDK, {IMMethods,MessageStatus,MessageType,SessionType} from "openim-uniapp-polyfill";
2025-12-23 00:18:46 +08:00
import CustomEditor from "./CustomEditor";
2025-12-09 09:27:29 +08:00
import SimpleEditor from "./SimpleEditor";
2025-12-08 18:10:51 +08:00
import ChatingActionBar from "./ChatingActionBar";
2025-12-17 08:52:51 +08:00
import Recoder from "./Recoder";
2026-02-09 07:29:02 +08:00
import {upload} from "@/api/login.js"
import IM from "@/util/im.js";
import permision from "@/js_sdk/wa-permission/permission.js"
2025-11-07 09:56:20 +08:00
const needClearTypes = [MessageType.TextMessage];
2025-11-25 05:36:02 +08:00
const rtcChoose = [
{
name: "视频通话",
2025-12-11 22:33:31 +08:00
type: 'video',
2025-11-07 09:56:20 +08:00
idx: 0,
},
{
2025-11-25 05:36:02 +08:00
name: "语言通话",
2025-12-11 22:33:31 +08:00
type: 'voice',
2025-11-07 09:56:20 +08:00
idx: 1,
},
];
export default {
components: {
2025-12-23 00:18:46 +08:00
CustomEditor,SimpleEditor,
2025-11-07 09:56:20 +08:00
ChatingActionBar,
2025-12-17 08:52:51 +08:00
Recoder,
2025-11-07 09:56:20 +08:00
},
props: {
footerOutsideFlag: Number,
},
data() {
return {
2025-12-02 03:05:52 +08:00
recording:false,
2025-12-17 08:52:51 +08:00
voiceIconText : "正在录音...",
inputType:"keyboard",
2025-12-02 03:05:52 +08:00
isEmoji:false,
2025-12-23 00:18:46 +08:00
inputHtml: '',
2025-11-07 09:56:20 +08:00
actionBarVisible: false,
isInputFocus: false,
actionSheetMenu: [],
showActionSheet: false,
2025-12-09 09:27:29 +08:00
isInsertingEmoji: false, // 标记是否正在插入表情
2025-12-11 22:33:31 +08:00
fileSelectedArray:[]
2025-11-07 09:56:20 +08:00
};
},
computed: {
...mapGetters([
"storeCurrentConversation",
"storeCurrentGroup",
"storeBlackList",
2026-01-10 15:40:38 +08:00
"storeCurrentUserID",
"storeCurrentMemberInGroup"
2025-11-07 09:56:20 +08:00
]),
2026-01-15 22:50:35 +08:00
isSingle() {
return (
this.storeCurrentConversation.conversationType === SessionType.Single
);
},
2025-11-07 09:56:20 +08:00
hasContent() {
return html2Text(this.inputHtml) !== "";
},
2026-01-12 18:07:21 +08:00
isAdminOrOwner(){
return this.storeCurrentMemberInGroup && this.storeCurrentMemberInGroup?.roleLevel>20;
},
isMute(){
2026-01-15 22:50:35 +08:00
if(this.isSingle){
return false;
}
2026-01-12 18:07:21 +08:00
if(this.storeCurrentGroup && this.storeCurrentGroup.status === 3 && !this.isAdminOrOwner){
return true;
}
if (this.storeCurrentMemberInGroup && this.storeCurrentMemberInGroup?.muteEndTime>0){
return true;
}
return false;
}
2025-11-07 09:56:20 +08:00
},
watch: {
footerOutsideFlag(newVal) {
this.onClickActionBarOutside();
},
},
mounted() {
2026-01-11 13:51:16 +08:00
//console.log(this.storeCurrentGroup)
//console.log(this.storeCurrentMemberInGroup)
2025-11-07 09:56:20 +08:00
this.setKeyboardListener();
},
beforeDestroy() {
this.disposeKeyboardListener();
},
methods: {
...mapActions("message", ["pushNewMessage", "updateOneMessage"]),
2026-01-10 15:40:38 +08:00
date,
2025-11-07 09:56:20 +08:00
async createTextMessage() {
let message = "";
const text = html2Text(this.inputHtml);
message = await IMSDK.asyncApi(
IMMethods.CreateTextMessage,
IMSDK.uuid(),
text
);
2026-01-12 18:07:21 +08:00
//console.log(message);
2025-11-07 09:56:20 +08:00
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)) {
2025-12-09 09:27:29 +08:00
this.$refs.customEditor.clear();
2025-11-07 09:56:20 +08:00
}
2026-02-09 07:29:02 +08:00
let method = IMMethods.SendMessage;
if([MessageType.PictureMessage,MessageType.VoiceMessage,MessageType.VideoMessage,MessageType.FileMessage].includes(message.contentType)){
method = IMMethods.SendMessageNotOss;
}
2025-11-07 09:56:20 +08:00
this.$emit("scrollToBottom");
2026-02-09 07:29:02 +08:00
IMSDK.asyncApi(method, 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}) => {
2026-01-12 18:07:21 +08:00
//console.log(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}) => {
2025-12-27 07:08:30 +08:00
console.log(errCode,errMsg,data);
2025-11-25 05:36:02 +08:00
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() {
2025-12-09 09:27:29 +08:00
// 如果正在插入表情,不隐藏表情栏
if (this.isInsertingEmoji) {
return;
}
2025-11-07 09:56:20 +08:00
if (this.actionBarVisible) {
this.actionBarVisible = false;
}
},
2025-12-02 03:05:52 +08:00
updateActionBar(isEmoji) {
2025-12-09 09:27:29 +08:00
if(this.actionBarVisible){
if(this.isEmoji!== !!isEmoji){
this.isEmoji = !!isEmoji;
}else{
this.actionBarVisible = false;
}
}else{
this.actionBarVisible = true;
this.isEmoji = !!isEmoji;
}
2025-11-07 09:56:20 +08:00
},
2025-12-17 08:52:51 +08:00
swtichInputType(type){
console.log(type);
this.inputType = type;
if(this.inputType == 'record'){
this.actionBarVisible = false;
this.isEmoji = false;
}
},
2025-12-23 00:18:46 +08:00
2025-12-17 08:52:51 +08:00
async sendLocationMessage(res){
console.log(res);
const _this = this;
const message = await IMSDK.asyncApi(
IMMethods.CreateLocationMessage,
IMSDK.uuid(),
{
latitude:res.lat,
longitude:res.lng,
description:res.address
}
);
_this.sendMessage(message,_this.storeCurrentConversation.userID,_this.storeCurrentConversation.groupID);
},
async sendVoiceMessage(audio){
const _this = this;
const message = await IMSDK.asyncApi(
IMMethods.CreateSoundMessageFromFullPath,
IMSDK.uuid(),
{
soundPath:getPurePath(audio.tempFilePath),
duration:audio.contentDuration
}
);
_this.sendMessage(message,_this.storeCurrentConversation.userID,_this.storeCurrentConversation.groupID);
},
2025-12-11 22:33:31 +08:00
// from comp
sendMediaMesage(paths) {
const _this = this;
paths.forEach(async (item) => {
2026-02-09 07:29:02 +08:00
console.log(item);
2025-12-11 22:33:31 +08:00
try {
let message = null;
if(item.search('.mp4')>0){
2026-02-09 07:29:02 +08:00
message = await IM.createVideoMessage(item);
2025-12-11 22:33:31 +08:00
}else{
2026-02-09 07:29:02 +08:00
message = await IM.createImageMessage(item);
2025-12-09 09:27:29 +08:00
}
2026-02-09 07:29:02 +08:00
console.log(message);
2025-12-11 22:33:31 +08:00
if(message){
_this.sendMessage(message,_this.storeCurrentConversation.userID,_this.storeCurrentConversation.groupID);
2025-12-02 03:05:52 +08:00
}
2025-12-11 22:33:31 +08:00
} catch (error) {
console.log(error);
2025-12-09 09:27:29 +08:00
}
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('根据相关政策,暂时禁用视频通话');
//发送视频通话
2025-11-07 09:56:20 +08:00
} else {
2025-11-25 05:36:02 +08:00
uni.$u.toast('根据相关政策,暂时禁用音频通话');
//发送音频通话
2025-11-07 09:56:20 +08:00
}
},
// keyboard
2025-11-25 05:36:02 +08:00
keyboardChangeHander({height}) {
2025-12-09 09:27:29 +08:00
//console.log(height);
// 如果正在插入表情,不隐藏表情栏
if (this.isInsertingEmoji) {
return;
}
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-23 00:18:46 +08:00
onEditorEvent(e){
const _this = this;
if(e.type=="atevent" && this.storeCurrentConversation.groupID){
uni.navigateTo({
url: `/pages/common/contactChoose/chooseGroupMember?groupID=${this.storeCurrentConversation.groupID}&checkedUserIDList=[]&hideUserIDList=[${this.storeCurrentUserID}]`,
events: {
onSelectedConfirm(userList) {
userList.forEach((user)=>{
_this.$refs.customEditor.insertMention(user.remark || user.nickname || user.showName,user.userID);
_this.$refs.customEditor.focus();
})
}
}
});
return ;
}
if(e.type=="ready"){
return ;
}
if(e.type=="focus"){
this.isInputFocus = true;
this.$emit("scrollToBottom");
return ;
}
if(e.type=="blur"){
this.isInputFocus = false;
return ;
}
if(e.type=="onChange"){
return ;
}
if(e.type=="input"){
// SimpleEditor 返回的是纯文本,直接使用
this.inputHtml = e.html || e.text || "";
return ;
}
console.log(e);
},
onRecodEvent(e){
2025-12-17 08:52:51 +08:00
const _this = this;
switch(e.type){
case "voiceIconTextChange":
_this.voiceIconText = e.text;
break;
case "sendVoiceMessage":
_this.sendVoiceMessage(e.audio);
break;
case "recordingStateChange":
_this.recording = e.state;
break;
default:
break;
2025-12-02 03:05:52 +08:00
}
},
2026-02-09 07:29:02 +08:00
pickMedia(){
const _this = this;
plus.gallery.pick(({files})=>{
console.log(files);
_this.sendMediaMesage(files);
}, (error )=>{
console.log(error);
}, {
animation:true,
confirmText:"确定",
//crop:null,
editable:true,
filename:"_doc/",
//filter:"none",//image,none,video
filter:"image",
maximum:9,
multiple:true,
permissionAlert:true,
//popover:{},
//selected:[""],
onmaxed(){
console.log("超出最大选择数");
},
});
},
2025-12-09 09:27:29 +08:00
onUserEvent(e){
2025-12-11 22:33:31 +08:00
const _this = this;
2025-12-09 09:27:29 +08:00
switch(e.type){
case "clearSendStr":
this.$refs.customEditor.clear();
break;
case "delSendStr":
this.$refs.customEditor.delete();
break;
2025-12-11 22:33:31 +08:00
case "insertEmoji":
//TODO 在光标处插入文字extra
//editorContext.insertImage(
// 标记正在插入表情(先设置标志,保护表情栏不被隐藏)
this.isInsertingEmoji = true;
// 确保表情栏显示(只在真正需要时才更新状态,避免不必要的响应式触发)
const wasVisible = this.actionBarVisible;
const wasEmoji = this.isEmoji;
if (!wasVisible || !wasEmoji) {
// 只有在需要时才更新状态,减少响应式触发
if (!wasVisible) {
this.actionBarVisible = true;
}
if (!wasEmoji) {
this.isEmoji = true;
}
}
// 直接插入文本,不等待 nextTick,减少延迟
this.$refs.customEditor.insertText(e.emoji,() =>{
2025-12-23 00:18:46 +08:00
//console.log("插入文字成功");
2025-12-11 22:33:31 +08:00
// 延迟重置标志,确保其他事件不会隐藏表情栏
setTimeout(() => {
this.isInsertingEmoji = false;
}, 300);
},(err) => {
2025-12-23 00:18:46 +08:00
//console.log("插入文字失败", err);
2025-12-11 22:33:31 +08:00
this.isInsertingEmoji = false;
});
break;
case "prepend_image_message":
if(e.source == "camera"){
var cmr = plus.camera.getCamera();
cmr.captureImage((src) =>{
_this.sendMediaMesage([plus.io.convertLocalFileSystemURL(src)]);
}, (err) =>{
console.log(err);
}, {
filename:"_doc/camera/"
});
return ;
}
if(e.source == "album"){
2026-02-09 07:29:02 +08:00
_this.pickMedia();
}
2025-12-11 22:33:31 +08:00
break;
case "prepend_call_message":
this.actionSheetMenu = [...rtcChoose];
this.showActionSheet = true;
break;
case "prepend_file_message":
uni.chooseFile({
count:9,
multiple: true,
success: (res) => {
console.log('选择文件成功', res);
//const filePaths = res.tempFiles.map(file => file.path);
//_this.sendMediaMesage(filePaths);
},
fail: (err) => {
console.log('选择文件失败', err);
}
});
break;
case "prepend_location_message":
2025-12-17 08:52:51 +08:00
uni.navigateTo({
url:"/pages/common/map",
events:{
onConfirm(res) {
_this.sendLocationMessage(res);
}
}
})
2025-12-11 22:33:31 +08:00
break;
default:
console.log(e);
break;
2025-12-09 09:27:29 +08:00
}
}
2025-11-07 09:56:20 +08:00
},
};
</script>
<style lang="scss" scoped>
.custom_editor {
2025-12-23 00:18:46 +08:00
min-height: 60rpx;
max-height: 240rpx;
2025-11-07 09:56:20 +08:00
}
2025-12-23 00:18:46 +08:00
2025-11-07 09:56:20 +08:00
.forbidden_footer {
width: 100%;
height: 112rpx;
color: #8e9ab0;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
background: #f0f2f6;
}
2025-12-23 00:18:46 +08:00
2025-11-07 09:56:20 +08:00
.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;
2025-12-23 00:18:46 +08:00
padding: 10rpx 20rpx;
2025-12-17 08:52:51 +08:00
gap: 20rpx;
2026-01-10 15:40:38 +08:00
.mute_tip{
}
2025-12-23 00:18:46 +08:00
2025-11-07 09:56:20 +08:00
.input_content {
flex: 1;
border-radius: 8rpx;
position: relative;
2025-12-23 00:18:46 +08:00
2025-11-07 09:56:20 +08:00
.record_btn {
// background-color: #3c9cff;
background: #fff;
color: black;
height: 30px;
font-size: 24rpx;
}
}
2025-12-23 00:18:46 +08:00
2025-12-17 08:52:51 +08:00
.action_btn{
2025-12-23 00:18:46 +08:00
width: 50rpx;
height: 50rpx;
margin-bottom: 6rpx;
2025-12-17 08:52:51 +08:00
}
2025-11-07 09:56:20 +08:00
.footer_action_area {
display: flex;
align-items: center;
2025-12-17 08:52:51 +08:00
gap: 20rpx;
2025-11-07 09:56:20 +08:00
}
2025-12-23 00:18:46 +08:00
2025-11-07 09:56:20 +08:00
.send_btn {
2025-12-23 00:18:46 +08:00
height: 50rpx;
line-height: 50rpx;
2025-12-02 03:05:52 +08:00
background-color: $uni-color-success;
2025-12-17 08:52:51 +08:00
padding: 0 8px;
2025-11-07 09:56:20 +08:00
border-radius: 4px;
color: #fff;
}
}
2025-12-17 08:52:51 +08:00
/* 语音动画 */
.voice_an {
width: 300rpx;
height: 300rpx;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -55%);
background-color: rgba(41, 41, 41, 0.7);
color: white;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
border-radius: 10rpx;
.text {
padding-top: 30rpx;
}
@keyframes runVoice {
0% {
height: 10%;
}
20% {
height: 50%;
}
50% {
height: 100%;
}
80% {
height: 50%;
}
100% {
height: 0%;
}
}
.wave {
width: 6rpx;
height: 100%;
margin-left: 10rpx;
border-radius: 50rpx;
background-color: #999;
vertical-align: middle;
display: inline-block;
}
.voice_an_icon {
width: 200rpx;
height: 100rpx;
line-height: 50rpx;
margin: 50rpx 0;
}
.voice_an_icon #one {
animation: runVoice 0.6s infinite 0.1s;
}
.voice_an_icon #two {
animation: runVoice 0.6s infinite 0.3s;
}
.voice_an_icon #three {
animation: runVoice 0.6s infinite 0.6s;
}
.voice_an_icon #four {
animation: runVoice 0.6s infinite 0.1s;
}
.voice_an_icon #five {
animation: runVoice 0.6s infinite 0.3s;
}
.voice_an_icon #six {
animation: runVoice 0.6s infinite 0.6s;
}
.voice_an_icon #seven {
animation: runVoice 0.6s infinite 0.1s;
}
}
2025-11-07 09:56:20 +08:00
</style>