You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
apply-assistant-v3/root/NEUIKit/pages/Chat/message/message-input.vue

1243 lines
34 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div style="" class="pbInput" :style="writeStyle">
<div class="input-root">
<!-- 以下这个 div 用于确保 vue2 ref 会更新 -->
<div style="display: none">
<div>{{ teamMute ? "禁言" : "不禁言" }}</div>
<div>{{ isTeamMute ? "禁言" : "不禁言" }}</div>
</div>
<div class="msg-input-wrapper">
<div v-if="isReplyMsg" class="reply-message-wrapper">
<div class="reply-message-close" @tap="removeReplyMsg">
<Icon color="#929299" :iconStyle="{ fontWeight: '200' }" :size="13" type="icon-guanbi" />
</div>
<div class="reply-line"></div>
<div class="reply-title">{{ t("replyText") }}</div>
<div class="reply-to">
<Appellation :account="replyMsg && replyMsg.senderId" :team-id="props.conversationType === V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM ? to : ''" color="#929299" :fontSize="13"> </Appellation>
</div>
<div class="reply-to-colon">:</div>
<div v-if="replyMsg && replyMsg.messageClientId === 'noFind'" class="reply-noFind">
{{ t("replyNotFindText") }}
</div>
<div class="reply-message" v-else>
<message-one-line v-if="replyMsg && replyMsg.messageType === V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_TEXT" :text="replyMsg && replyMsg.text"></message-one-line>
<div v-else>
{{ "[" + REPLY_MSG_TYPE_MAP[replyMsg && replyMsg.messageType] + "]" }}
</div>
</div>
</div>
<!-- 输入框上按钮组 -->
<!-- <div class="msg-button-group">
<div @tap="handleAudioVisible" v-if="!isWeb" class="msg-input-button">
<Icon v-if="audioPanelVisible" :size="20" type="audio-btn-selected" key="audio-btn-selected" />
<Icon v-else :size="20" type="icon-audio" key="icon-audio" />
</div>
<div class="msg-input-button">
<Icon @tap="handleEmojiVisible" :size="20" type="icon-biaoqing" />
</div>
<div class="msg-input-button">
<div>
<Icon @tap="handleSendImageMsg" :size="20" type="icon-tupian" />
</div>
</div>
<div class="msg-input-button">
<Icon @tap="handleSendMoreVisible" type="send-more" :size="20" />
</div>
<div class="msg-input-button">
<Icon @tap="handleSetting" type="icon-shezhi" :size="20" />
</div>
</div> -->
<div class="g_flex_c">
<div @tap="handleAudioVisible" v-if="!isWeb && false" class="msg-input-button g_p_5" style="padding-right: 7px; padding-left: 10px">
<Icon v-if="audioPanelVisible" :size="28" type="audio-btn-selected" key="audio-btn-selected" />
<Icon v-else :size="28" type="icon-audio" key="icon-audio" />
</div>
<!-- v-if="inputVisible" ↓↓↓↓↓↓↓ -->
<div class="msg-input g_flex_1">
<!-- 当从表情面板切换到文字输入时直接唤起键盘会导致input框滚动消失故此处需要用EmojiInput兼容下保证先隐藏表情面板再弹出键盘 -->
<div v-show="showEmojiInput" @click="onClickEmojiInput" class="input-emoji">
<div v-if="inputText" class="input-text">{{ inputText }}</div>
<div v-else class="input-placeholder" style="padding-left: 0">
{{ isTeamMute ? t("teamMutePlaceholder") : t("chatInputPlaceHolder") }}
</div>
</div>
<!-- {{ssff}}键盘高度{{keyHeight}} 输入框距离底部距离{{writeStyle}}屏幕高度{{screenHeight}}顶部聊天区域高度{{msgKeyHeight}} -->
<input v-show="!showEmojiInput" :focus="isFocus" class="msg-input-input g_flex_1" :maxlength="-1" :placeholder="isTeamMute ? t('teamMutePlaceholder') : t('chatInputPlaceHolder')" v-model="inputText" type="text" :disabled="isTeamMute" :confirm-hold="true" cursor-spacing="20" :adjust-position="pushUp" confirm-type="send" @keyboardheightchange="keyboardheightchange" @focus="handleInputFocus" @confirm="handleSendTextMsg" @blur="handleInputBlur" @input="handleInput" id="msg-input" />
</div>
<div class="msg-input-button" @tap="(event) => handleSendJob()">
<div class="iconfont icon-5gongdanguanli g_c_6 g_fs_24"></div>
</div>
<!-- <div class="send-more-panel-item-wrapper">
<div class="send-more-panel-item" @tap="(event) => handleSendJob()">
<div class="iconfont icon-5gongdanguanli g_c_6 g_fs_24 g_fw_600"></div>
</div>
</div> -->
<!-- <div class="msg-input-button" v-if="false">
<Icon @tap="handleEmojiVisible" class="g_p_6" style="padding-right: 5px" :size="28" type="icon-biaoqing" />
</div> -->
<div class="msg-input-button" v-if="true">
<Icon @tap="handleSendMoreVisible" class="g_p_6" style="padding-left: 5px; padding-right: 8px" type="send-more" :size="28" />
</div>
</div>
<!-- 表情面板 -->
<div v-if="emojiVisible" class="msg-emoji-panel" @click.stop="() => {}">
<Face @emojiClick="handleEmoji" @emojiDelete="handleEmojiDelete" @emojiSend="handleSendTextMsg" />
</div>
<!-- 发送语音消息面板 -->
<div v-if="audioPanelVisible" class="msg-audio-panel" @click.stop="() => {}">
<VoicePanel @handleSendAudioMsg="handleSendAudioMsg"></VoicePanel>
</div>
<!-- 发送更多面板 -->
<div v-if="sendMoreVisible" class="send-more-panel" @click.stop="() => {}">
<div class="send-more-panel-item-wrapper">
<div class="send-more-panel-item" @tap="(event) => handleSendImageMsg()">
<Icon type="icon-tupian" :size="30"></Icon>
</div>
<div class="icon-text">{{ t("imgText") }}</div>
</div>
<div class="send-more-panel-item-wrapper">
<div class="send-more-panel-item" @tap="(event) => handleSendVideoMsg('camera', event)">
<Icon type="icon-paishe" :size="30"></Icon>
</div>
<div class="icon-text">{{ t("shootText") }}</div>
</div>
<!-- <div class="send-more-panel-item-wrapper">
<div class="send-more-panel-item" @tap="(event) => handleSendVideoMsg('album', event)">
<Icon type="icon-shipin2" :size="30"></Icon>
</div>
<div class="icon-text">{{ t("albumText") }}</div>
</div> -->
<!-- <div class="send-more-panel-item-wrapper" v-if="isApp && props.conversationType !== V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM">
<div class="send-more-panel-item" @tap="handleCall(1)">
<Icon type="icon-audio-call" :size="30"></Icon>
</div>
<div class="icon-text">{{ t("voiceCallText") }}</div>
</div>
<div class="send-more-panel-item-wrapper" v-if="isApp && props.conversationType !== V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM">
<div class="send-more-panel-item" @tap="() => handleCall(2)">
<Icon type="icon-video-call" :size="30"></Icon>
</div>
<div class="icon-text">{{ t("videoCallText") }}</div>
</div> -->
<div v-if="isWeb" class="send-more-panel-item-wrapper" @tap="handleSendFileMsg">
<div class="send-more-panel-item">
<Icon type="icon-file" :size="30"></Icon>
</div>
<div class="icon-text">{{ t("fileText") }}</div>
</div>
<!-- <div class="send-more-panel-item-wrapper" v-if="corpUserFlag">
<div class="send-more-panel-item" @tap="(event) => handleSendCustomMsg('album', event)">
<Icon type="icon-touxiang5" :size="30"></Icon>
</div>
<div class="icon-text">{{ t("customText") }}</div>
</div> -->
<div class="send-more-panel-item-wrapper" v-if="corpUserFlag">
<div class="send-more-panel-item" @tap="(event) => handleSendJob()">
<div class="iconfont icon-fasong1 g_c_6 g_fs_24 g_fw_600"></div>
</div>
<div class="icon-text">{{ "发送职位" }}</div>
</div>
<!-- <div class="send-more-panel-item-wrapper">
<div class="send-more-panel-item" @tap="(event) => handleSetting()">
<Icon type="icon-shezhi" :size="30"></Icon>
</div>
<div class="icon-text">设置</div>
</div> -->
<!-- <div class="msg-input-button">
<Icon @tap="handleSetting" type="icon-shezhi" :size="20" />
</div> -->
</div>
</div>
<!-- @消息相关 popup -->
<UniPopup ref="popupRef" background-color="#ffffff" type="bottom" mask-background-color="rgba(0,0,0,0.4)" @change="onPopupChange">
<MentionMemberList :team-id="to"></MentionMemberList>
</UniPopup>
</div>
</div>
</template>
<script lang="ts" setup>
import Face from "./face.vue";
import VoicePanel from "./voice-panel.vue";
import Icon from "../../../components/Icon.vue";
import { ref, getCurrentInstance, computed, onUnmounted, onMounted, defineProps, withDefaults, defineEmits, watch } from "../../../utils/transformVue";
import { ALLOW_AT, events, REPLY_MSG_TYPE_MAP } from "../../../utils/constants";
import { emojiMap } from "../../../utils/emoji";
import { t } from "../../../utils/i18n";
import { handleNoPermission } from "../../../utils/permission";
import { customNavigateTo } from "../../../utils/customNavigate";
import MessageOneLine from "../../../components/MessageOneLine.vue";
import { isAndroidApp, stopAllAudio, isIosWeb, isWeb, isWxApp, startCall, isApp, isIosApp } from "../../../utils";
// @ts-ignore
import UniPopup from "../../../components/uni-components/uni-popup/components/uni-popup/uni-popup.vue";
// @ts-ignore
import MentionMemberList from "./mention-member-list.vue";
import { autorun } from "mobx";
import Appellation from "../../../components/Appellation.vue";
import { AT_ALL_ACCOUNT } from "../../../utils/constants";
import { deepClone } from "../../../utils";
import { V2NIMTeam, V2NIMTeamChatBannedMode, V2NIMTeamMember } from "nim-web-sdk-ng/dist/v2/NIM_UNIAPP_SDK/V2NIMTeamService";
import { V2NIMMessageForUI, YxServerExt, YxAitMsg } from "@xkit-yx/im-store-v2/dist/types/types";
import { V2NIMConst } from "nim-web-sdk-ng/dist/v2/NIM_UNIAPP_SDK";
export type MentionedMember = { accountId: string; appellation: string };
const corpUserFlag = ref(uni.getStorageSync("apply-userinfo").corpUserFlag);
const props = withDefaults(
defineProps<{
conversationType: V2NIMConst.V2NIMConversationType;
to: string;
replyMsgsMap?: {
[key: string]: V2NIMMessageForUI;
};
jobListShow: Boolean;
}>(),
{}
);
const emits = defineEmits(["jobListShow"]);
const conversationId = props.conversationType === V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P ? uni.$UIKitNIM.V2NIMConversationIdUtil.p2pConversationId(props.to) : uni.$UIKitNIM.V2NIMConversationIdUtil.teamConversationId(props.to);
const inputText = ref("");
const extVisible = ref(false);
// 音频面板flag
const audioPanelVisible = ref(false);
// 发送更多面板flag
const sendMoreVisible = ref(false);
// 表情面板flag
const emojiVisible = ref(false);
// input框flag
const inputVisible = computed(() => {
if (audioPanelVisible.value || sendMoreVisible.value) {
return false;
} else {
return true;
}
});
// 发起呼叫type: 1 音频呼叫2 视频呼叫
const handleCall = (type: number) => {
const myAccount = uni.$UIKitStore.userStore.myUserInfo.accountId;
const remoteShowName = uni.$UIKitStore.uiStore.getAppellation({
account: props.to,
});
if (myAccount) {
startCall({
remoteUserAccid: props.to,
currentUserAccid: myAccount,
type: type,
remoteShowName: remoteShowName,
});
} else {
uni.showToast({
title: t("callFailedText"),
icon: "none",
});
}
};
// 用于解决表情面板和键盘冲突,导致输入框滚动消失问题
const showEmojiInput = ref(false);
// 回复消息相关
const isReplyMsg = ref(false);
const isFocus = ref(false);
const replyMsg = ref<V2NIMMessageForUI>();
// @消息相关
const ctx = getCurrentInstance();
const popupRef = ref(null);
const selectedAtMembers = ref<MentionedMember[]>([]);
// 群相关
const team = ref<V2NIMTeam>();
const teamMembers = ref<V2NIMTeamMember[]>([]);
const teamMute = ref<V2NIMTeamChatBannedMode>(V2NIMConst.V2NIMTeamChatBannedMode.V2NIM_TEAM_CHAT_BANNED_MODE_UNBAN);
const isGroupOwner = ref(false);
const isGroupManager = ref(false);
// 是否允许@所有人
const allowAtAll = computed(() => {
let ext: YxServerExt = {};
try {
ext = JSON.parse((team.value || {}).serverExtension || "{}");
} catch (error) {
//
}
if (ext[ALLOW_AT] === "manager") {
return isGroupOwner.value || isGroupManager.value;
}
return true;
});
// 是否是群禁言
// const isTeamMute = computed(() => {
// // console.log(
// 'isGroupOwner, isGroupManager',
// isGroupOwner.value,
// isGroupManager.value
// )
// if (!teamMute.value) {
// return false
// }
// // 群主或者群管理员在群禁言时,可以发送消息
// if (isGroupOwner.value || isGroupManager.value) {
// return false
// }
// return true
// })
const isTeamMute = ref(true);
const updateTeamMute = () => {
if (teamMute.value === V2NIMConst.V2NIMTeamChatBannedMode.V2NIM_TEAM_CHAT_BANNED_MODE_UNBAN) {
isTeamMute.value = false;
return;
}
// 群主或者群管理员在群禁言时,可以发送消息
if (isGroupOwner.value || isGroupManager.value) {
isTeamMute.value = false;
return;
}
isTeamMute.value = true;
return;
};
const onPopupChange = (e: any) => {
uni.$emit(events.HANDLE_MOVE_THROUGH, e.value);
};
// 点击@群成员
const handleMentionItemClick = (member: MentionedMember) => {
//@ts-ignore
ctx.refs.popupRef.close();
uni.$emit(events.HANDLE_MOVE_THROUGH, false);
const nickInTeam = member.appellation;
selectedAtMembers.value = [...selectedAtMembers.value.filter((item) => item.accountId !== member.accountId), member];
const newInputText = inputText.value + nickInTeam + " ";
// 更新input框的内容
inputText.value = newInputText;
};
const closePopup = () => {
//@ts-ignore
ctx.refs.popupRef.close();
};
// 点击表情输入框,隐藏表情面板,显示键盘
const onClickEmojiInput = () => {
showEmojiInput.value = false;
extVisible.value = false;
emojiVisible.value = false;
extVisible.value = false;
audioPanelVisible.value = false;
sendMoreVisible.value = false;
// #ifdef MP-WEIXIN
if (uni.getSystemInfoSync().osName == "ios") {
uni.$emit(events.KeyboardEvent, 0);
}
// #endif
if (isIosWeb) {
isFocus.value = true;
} else {
setTimeout(() => {
isFocus.value = true;
}, 500);
}
};
const writeStyle = ref("");
const isIOS = ref(false);
writeStyle.value = "bottom:0";
// // #ifdef MP-WEIXIN
// const pushUp = ref(false)
// // #endif
// // #ifdef APP-PLUS
// const pushUp = ref(true)
// // #endif
const pushUp = ref(false);
const keyHeight = ref(350);
const msgKeyHeight = ref("100%");
const ssff = ref(uni.getWindowInfo().safeArea.top);
const handleInputFocus = (e) => {
emojiVisible.value = false;
extVisible.value = false;
audioPanelVisible.value = false;
sendMoreVisible.value = false;
const systemInfo = uni.getSystemInfoSync();
const availableHeight = systemInfo.windowHeight - e.detail.height - ssff.value;
setTimeout(()=>{
console.log("获取焦点获取焦点获取焦点获取焦点获取焦点获取焦点获取焦点获取焦点获取焦点获取焦点获取焦点获取焦点获取焦点获取焦点");
uni.$emit("msgKeyHeight", availableHeight); // 传递实际可用高度
writeStyle.value = `bottom: calc(${e.detail.height}px - env(safe-area-inset-bottom))`;
uni.$emit(events.ON_SCROLL_BOTTOM);
},80)
// if (e.detail.height === 0) {
// writeStyle.value = "bottom: 0";
// uni.$emit("msgKeyHeight", "100%"); // 键盘收起时恢复全屏高度
// } else {
// // 隐藏其他面板
// emojiVisible.value = false;
// extVisible.value = false;
// audioPanelVisible.value = false;
// sendMoreVisible.value = false;
// const systemInfo = uni.getSystemInfoSync();
// const availableHeight = systemInfo.windowHeight - e.detail.height - ssff.value;
// uni.$emit("msgKeyHeight", availableHeight); // 传递实际可用高度
// writeStyle.value = `bottom: calc(${e.detail.height}px - env(safe-area-inset-bottom))`;
// // let safeBottom = 0;
// }
}
// 在message-input.vue的keyboardheightchange事件中修改
// 在message-input.vue的keyboardheightchange事件中修改
const keyboardheightchange = (e) => {
keyHeight.value = e.detail.height;
uni.$emit(events.KeyboardEvent, e.detail.height);
uni.$emit("KeyboardHeight", e.detail.height);
if (e.detail.height === 0) {
writeStyle.value = "bottom: 0";
uni.$emit("msgKeyHeight", "100%"); // 键盘收起时恢复全屏高度
} else {
// 隐藏其他面板
console.log("触发输入框触发输入框触发输入框触发输入框触发输入框触发输入框触发输入框触发输入框");
emojiVisible.value = false;
extVisible.value = false;
audioPanelVisible.value = false;
sendMoreVisible.value = false;
const systemInfo = uni.getSystemInfoSync();
const availableHeight = systemInfo.windowHeight - e.detail.height - ssff.value;
uni.$emit("msgKeyHeight", availableHeight); // 传递实际可用高度
writeStyle.value = `bottom: calc(${e.detail.height}px - env(safe-area-inset-bottom))`;
}
setTimeout(()=>{
uni.$emit(events.ON_SCROLL_BOTTOM);
},80)
};
// const handleInputBlur = () => {
// isFocus.value = false;
// uni.$emit("msgKeyHeight", "100%");
// };
// 滚动到底部
const scrollBottom = () => {
if (isAndroidApp || isWxApp || isIosApp) {
setTimeout(() => {
uni.$emit(events.ON_SCROLL_BOTTOM);
}, 80);
} else {
uni.$emit(events.ON_SCROLL_BOTTOM);
}
};
// 输入框输入事件
const handleInput = (event: any) => {
const text = event?.detail?.value;
const isAit = text.endsWith("@") || text.endsWith("@\n");
if (props.conversationType == V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM) {
if (isAit) {
// 当前输入的是@
uni.hideKeyboard();
// @ts-ignore
ctx.refs.popupRef.open("bottom");
isFocus.value = false;
uni.$emit(events.HANDLE_MOVE_THROUGH, true);
}
}
};
// 发送文本消息
const handleSendTextMsg = () => {
if (inputText.value.trim() === "") return;
const ext = onAtMembersExtHandler();
const textMsg = uni.$UIKitNIM.V2NIMMessageCreator.createTextMessage(inputText.value);
uni.$UIKitStore.msgStore
.sendMessageActive({
msg: textMsg,
conversationId,
serverExtension: selectedAtMembers.value.length && (ext as any),
sendBefore: () => {
scrollBottom();
},
})
.catch(() => {
uni.showToast({
icon: "error",
title: t("sendMsgFailedText"),
});
})
.finally(() => {
scrollBottom();
});
inputText.value = "";
isReplyMsg.value = false;
selectedAtMembers.value = [];
uni.$emit(events.KeyboardEvent, 0); //点击发送消息list会多一段paddingBottom 需要键盘高度设置为0
};
// 发送文件消息
const handleSendFileMsg = () => {
uni.chooseFile({
count: 1,
type: "all",
success: (res) => {
// @ts-ignore
const filePath = res?.tempFilePaths?.[0];
// @ts-ignore
const fileName = res?.tempFiles?.[0]?.name;
if (filePath && fileName) {
const fileMsg = uni.$UIKitNIM.V2NIMMessageCreator.createFileMessage(filePath, fileName);
uni.$UIKitStore.msgStore.sendMessageActive({
msg: fileMsg,
conversationId,
sendBefore: () => {
scrollBottom();
},
});
}
},
fail: () => {
uni.showToast({
title: t("sendFileFailedText"),
icon: "none",
});
},
});
};
const handleSendJob = () => {
console.log("props.jobListShow", props.jobListShow);
emits("jobListShow", true);
};
const handleSendCustomMsg = () => {
const customMsg = uni.$UIKitNIM.V2NIMMessageCreator.createCustomMessage(
"这是PGQ的自定义消息",
JSON.stringify({
type: "0",
content: "测试自定义消息",
msgurl: "https://nim-nosdn.netease.im/MTAxMTAwMg==/bmltYV8yNTQ5NjkyNDA0XzE3MzA5ODYyNTU3NzdfZjM1ODJhMjQtMTM0Yi00NTk4LWIwNWEtODA0ZGEyYjMxN2Y5?imageView&createTime=1730986256503",
})
);
uni.$UIKitStore.msgStore.sendMessageActive({
msg: customMsg,
conversationId,
sendBefore: () => {
scrollBottom();
},
});
};
// 移除回复消息
const removeReplyMsg = () => {
uni.$UIKitStore.msgStore.removeReplyMsgActive(replyMsg?.value?.conversationId as string);
isReplyMsg.value = false;
};
// 显示表情面板
const handleEmojiVisible = () => {
showEmojiInput.value = true;
extVisible.value = true;
audioPanelVisible.value = false;
sendMoreVisible.value = false;
setTimeout(() => {
emojiVisible.value = !emojiVisible.value;
if (emojiVisible.value) {
uni.$emit(events.KeyboardEvent, 230);
} else {
uni.$emit(events.KeyboardEvent, 0);
}
uni.$emit(events.ON_SCROLL_BOTTOM);
}, 80);
};
// watch(sendMoreVisible, (val) => {
// if (val) {
// uni.$emit("msgKeyHeight", uni.getSystemInfoSync().windowHeight - 270); // 传递实际可用高度
// } else {
// uni.$emit("msgKeyHeight", "100%"); // 传递实际可用高度
// }
// });
// 显示发送更多"+"面板
const handleSendMoreVisible = () => {
if (isTeamMute.value) return;
audioPanelVisible.value = false;
emojiVisible.value = false;
sendMoreVisible.value = !sendMoreVisible.value;
setTimeout(()=>{
if (sendMoreVisible.value) {
uni.$emit(events.KeyboardEvent, 230);
uni.$emit("msgKeyHeight", uni.getSystemInfoSync().windowHeight - 260); // 传递实际可用高度
} else {
uni.$emit(events.KeyboardEvent, 0);
uni.$emit("msgKeyHeight", '100%'); // 传递实际可用高度
}
uni.$emit(events.ON_SCROLL_BOTTOM);
},80)
// }, 300)
};
// 删除表情
const handleEmojiDelete = () => {
let target = "";
const isEmojiEnd = Object.keys(emojiMap).reduce((prev, cur) => {
const isEnd = inputText.value.endsWith(cur);
if (isEnd) {
target = cur;
}
return prev || isEnd;
}, false);
if (isEmojiEnd && target) {
inputText.value = inputText.value.replace(target, "");
} else {
inputText.value = inputText.value.slice(0, -1);
}
};
// 显示语音面板
const handleAudioVisible = () => {
if (isTeamMute.value) return;
audioPanelVisible.value = !audioPanelVisible.value;
emojiVisible.value = false;
if (audioPanelVisible.value) {
uni.$emit(events.KeyboardEvent, 230);
} else {
uni.$emit(events.KeyboardEvent, 0);
}
uni.$emit(events.ON_SCROLL_BOTTOM);
};
// 发送图片消息
const handleSendImageMsg = () => {
if (isTeamMute.value) return;
stopAllAudio();
uni.chooseImage({
count: 1,
sizeType: ["compressed"],
success: (res) => {
const imgMsg = uni.$UIKitNIM.V2NIMMessageCreator.createImageMessage(res.tempFilePaths[0]);
uni.$UIKitStore.msgStore
.sendMessageActive({
msg: imgMsg,
conversationId,
progress: () => true,
sendBefore: () => {
scrollBottom();
},
})
.then(() => {
scrollBottom();
})
.catch(() => {
scrollBottom();
uni.showToast({
icon: "error",
title: t("sendImageFailedText"),
});
});
},
//没有开启权限时,提示开启权限
complete: handleNoPermission,
});
};
// 发送视频消息(使用相机或者从相册选择)
const handleSendVideoMsg = (type: string, event: any) => {
if (isTeamMute.value) return;
stopAllAudio();
// 这里做一层拦截的原因是微信小程序在input聚焦的时候点击+号按钮会触发此函数执行阻止冒泡也无法解决该问题疑为uniapp编译问题
if (isWxApp && event?.type == "blur") {
return;
}
uni.chooseVideo({
sourceType: [type],
compressed: true,
maxDuration: 60,
success: (res) => {
const videoMsg = uni.$UIKitNIM.V2NIMMessageCreator.createVideoMessage(res.tempFilePath);
uni.$UIKitStore.msgStore
.sendMessageActive({
msg: videoMsg,
conversationId,
progress: () => true,
sendBefore: () => {
scrollBottom();
},
})
.then(() => {
scrollBottom();
})
.catch(() => {
scrollBottom();
uni.showToast({
icon: "error",
title: t("sendVideoFailedText"),
});
});
},
//没有开启权限时,提示开启权限
complete: handleNoPermission,
});
};
// 发送语音消息
const handleSendAudioMsg = (filePath: string, duration: number) => {
const audioMsg = uni.$UIKitNIM.V2NIMMessageCreator.createAudioMessage(filePath);
uni.$UIKitStore.msgStore
.sendMessageActive({
msg: audioMsg,
conversationId,
progress: () => true,
sendBefore: (msg) => {
scrollBottom();
uni.$UIKitStore.msgStore.addMsg(msg.conversationId, [
{
...msg,
attachment: {
duration: duration,
},
},
]);
},
})
.then(() => {
scrollBottom();
})
.catch(() => {
uni.showToast({
icon: "error",
title: t("sendAudioFailedText"),
});
scrollBottom();
});
};
// 跳转设置页
const handleSetting = () => {
if (props.conversationType === V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P) {
customNavigateTo({
url: `/pages/Chat/message/p2p-set?id=${props.to}`,
});
} else if (props.conversationType === V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM) {
customNavigateTo({
url: `/pages/Group/group-set/index?id=${props.to}`,
});
}
};
let uninstallTeamWatch = () => {};
const msgHeight = ref(0);
const screenHeight = ref(0);
screenHeight.value = uni.getSystemInfoSync().windowHeight;
onMounted(() => {
uninstallTeamWatch = autorun(() => {
if (props.conversationType === V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM) {
const _team: V2NIMTeam = deepClone(uni.$UIKitStore.teamStore.teams.get(props.to));
teamMembers.value = deepClone(uni.$UIKitStore.teamMemberStore.getTeamMember(props.to));
const myUser = uni.$UIKitStore.userStore.myUserInfo;
isGroupOwner.value = _team?.ownerAccountId == myUser.accountId;
isGroupManager.value = teamMembers.value.filter((item) => item.memberRole === V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_MANAGER).some((member) => member.accountId === (myUser ? myUser.accountId : ""));
team.value = _team;
if (_team) {
teamMute.value = _team.chatBannedMode;
}
updateTeamMute();
}
});
// 撤回后,重新编辑消息
uni.$on(events.ON_REEDIT_MSG, (msg: V2NIMMessageForUI) => {
const _replyMsg = props.replyMsgsMap?.[msg.messageClientId];
// 如果重新编辑的是回复消息,则需要将回复消息展示在输入框上方
if (_replyMsg?.messageClientId) {
_replyMsg && uni.$UIKitStore.msgStore.replyMsgActive(_replyMsg);
replyMsg.value = _replyMsg;
isReplyMsg.value = true;
}
// 如果重新编辑的是@消息,则需要将被@的成员重新加入selectedAtMembers
if (msg.serverExtension) {
const extObj = JSON.parse(msg.serverExtension);
const yxAitMsg = extObj.yxAitMsg;
if (yxAitMsg) {
const _mentionedMembers: MentionedMember[] = [];
Object.keys(yxAitMsg).forEach((key) => {
if (key == AT_ALL_ACCOUNT) {
_mentionedMembers.push({
accountId: key,
appellation: "所有人",
});
} else {
_mentionedMembers.push({
accountId: key,
appellation: uni.$UIKitStore.uiStore.getAppellation({
account: key,
teamId: props.to,
ignoreAlias: true,
}),
});
}
});
selectedAtMembers.value = _mentionedMembers;
}
}
inputText.value = msg?.oldText || "";
isFocus.value = true;
});
uni.$on(events.REPLY_MSG, (msg: V2NIMMessageForUI) => {
isReplyMsg.value = true;
isFocus.value = true;
replyMsg.value = msg;
});
uni.$on(events.AIT_TEAM_MEMBER, (member: MentionedMember) => {
selectedAtMembers.value = [...selectedAtMembers.value.filter((item) => item.accountId !== member.accountId), member];
const newInputText = inputText.value + "@" + member.appellation + " ";
// 更新input框的内容
inputText.value = newInputText;
});
// 关闭表情、语音、发送更多面板
uni.$on(events.CLOSE_PANEL, () => {
emojiVisible.value = false;
extVisible.value = false;
audioPanelVisible.value = false;
sendMoreVisible.value = false;
// #ifdef MP-WEIXIN
if (uni.getSystemInfoSync().osName == "ios") {
uni.$emit(events.KeyboardEvent, 0);
}
// #endif
});
// @消息 @群成员
uni.$on(events.HANDLE_AIT_MEMBER, (member: MentionedMember) => {
handleMentionItemClick(member);
});
// 关闭@群成员面板
uni.$on(events.CLOSE_AIT_POPUP, () => {
closePopup();
});
// 表情点击
uni.$on(events.EMOJI_CLICK, (emoji) => {
handleEmoji(emoji);
});
// 表情删除
uni.$on(events.EMOJI_DELETE, () => {
handleEmojiDelete();
});
// 表情发送
uni.$on(events.EMOJI_SEND, () => {
emojiVisible.value = false;
extVisible.value = false;
handleSendTextMsg();
});
// #ifdef MP-WEIXIN
if (uni.getSystemInfoSync().osName == "ios") {
isIOS.value = true;
}
// #endif
if (uni.onKeyboardHeightChange) {
uni.onKeyboardHeightChange((res) => {
const isAndroidWxapp = uni.getSystemInfoSync().platform == "android" && isWxApp;
// 此处是为了点击安卓键盘上的收起按钮时,表情面板需要隐藏
if ((res.height === 0 && isAndroidApp) || (res.height === 0 && isAndroidWxapp)) {
writeStyle.value = "bottom: 0";
emojiVisible.value = false;
extVisible.value = false;
uni.$emit(events.KeyboardEvent, 0);
}
});
}
uni.$on("msgHeight", (res) => {
// console.log('inputinputinputinputinputinputinputinput')
// console.log(res)
msgHeight.value = res;
});
uni.$on("screenHeight", (res) => {
// console.log(res)
if (res) {
screenHeight.value = res;
} else {
screenHeight.value = uni.getSystemInfoSync().windowHeight;
}
});
isTeamMute.value = false;
});
// // 点击表情
// const handleEmoji = (emoji: { key: string; type: string }) => {
// inputText.value += emoji.key;
// };
// const handleEmoji = (emoji: Emoji) => {
const handleEmoji = (emoji: { key: string; type: string; emoji: string; text: string }) => {
if (emoji.type === "emoji") {
inputText.value += emoji.emoji;
} else if (emoji.type === "text") {
inputText.value += emoji.text;
}
};
const onAtMembersExtHandler = () => {
let ext: YxServerExt;
if (selectedAtMembers.value.length) {
selectedAtMembers.value
.filter((member) => {
if (!allowAtAll.value && member.accountId === AT_ALL_ACCOUNT) {
return false;
}
return true;
})
.forEach((member) => {
const substr = `@${member.appellation}`;
const positions: number[] = [];
let pos = inputText.value?.indexOf(substr);
while (pos !== -1) {
positions.push(pos);
pos = inputText.value?.indexOf(substr, pos + 1);
}
if (positions.length) {
if (!ext) {
ext = {
yxAitMsg: {
[member.accountId]: {
text: substr,
segments: [],
},
},
};
} else {
(ext.yxAitMsg as YxAitMsg)[member.accountId] = {
text: substr,
segments: [],
};
}
positions.forEach((position) => {
const start = position;
(ext?.yxAitMsg as YxAitMsg)[member.accountId].segments.push({
start,
end: start + substr.length,
broken: false,
});
});
}
});
}
// @ts-ignore
return ext;
};
onUnmounted(() => {
uni.$off(events.REPLY_MSG);
uni.$off(events.ON_REEDIT_MSG);
uni.$off(events.REPLY_MSG);
uni.$off(events.AIT_TEAM_MEMBER);
// 关闭表情面板
uni.$off(events.CLOSE_PANEL);
// @消息 @群成员
uni.$off(events.HANDLE_AIT_MEMBER);
// 关闭@群成员面板
uni.$off(events.CLOSE_AIT_POPUP);
// 表情点击
uni.$off(events.EMOJI_CLICK);
// 表情删除
uni.$off(events.EMOJI_DELETE);
// 表情发送
uni.$off(events.EMOJI_SEND);
removeReplyMsg();
uninstallTeamWatch();
});
</script>
<style scoped lang="scss">
@import "../../styles/common.scss";
.input-root {
width: 100%;
display: flex;
flex-direction: column;
height: auto;
max-height: 300px;
}
.input-root-h5 {
height: auto;
position: relative;
order: 1;
}
.msg-input-wrapper {
padding-bottom: calc(env(safe-area-inset-bottom) + 10px);
width: 100%;
height: 100%;
background-color: #eff1f3;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
z-index: 999;
}
.msg-input {
overflow-x: hidden;
padding: 7px;
background-color: #eff1f3;
&-input {
background-color: #fff;
height: 40px;
font-size: 16px;
padding: 0 12px;
border-radius: 6px;
margin-bottom: 5px;
&::placeholder {
padding: 0 12px;
}
}
}
.msg-button-group {
padding: 12px 20px 2px 20px;
display: flex;
flex-direction: row;
align-items: center;
}
.msg-input-button {
// flex: 1;
// &:not(:last-child) {
// margin-right: 60px;
// }
&.msg-input-loading {
animation: loadingCircle 1s infinite linear;
z-index: 1;
width: 20px;
height: 20px;
margin-top: 4px;
.loading {
width: 100%;
height: 100%;
}
}
}
.msg-ext {
overflow-y: auto;
width: 100%;
height: 300px;
background-color: #eff1f3;
z-index: 1;
}
.msg-emoji-panel {
overflow-y: auto;
width: 100%;
height: 246px;
background-color: #eff1f3;
z-index: 1;
}
.msg-audio-panel {
overflow-y: hidden;
width: 100%;
height: 300px;
background-color: #eff1f3;
z-index: 1;
}
.send-more-panel {
padding: 15px;
overflow-y: hidden;
width: 100%;
height: 300px;
background-color: #eff1f3;
z-index: 1;
flex-wrap: wrap;
box-sizing: border-box;
}
.send-more-panel-item-wrapper {
display: flex;
flex-direction: column;
align-items: center;
display: inline-block;
margin-bottom: 10px;
width: 25%;
.send-more-panel-item {
background-color: #fff;
border-radius: 8px;
width: 60px;
height: 60px;
display: flex;
align-items: center;
margin: 0 15px;
justify-content: center;
}
.icon-text {
font-size: 12px;
color: #747475;
margin-top: 8px;
text-align: center;
}
}
.reply-message-wrapper {
display: flex;
font-size: 13px;
background-color: #eff1f2;
height: 25px;
padding-top: 6px;
align-items: center;
color: #929299;
.reply-noFind {
width: fit-content;
}
.reply-to-colon {
flex-basis: 3px;
margin-right: 2px;
}
.reply-message-close {
flex-basis: 14px;
margin-left: 10px;
display: flex;
align-items: center;
}
.reply-message {
flex: 1;
display: flex;
align-items: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
message-one-line {
flex: 1;
font-size: 13px;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.reply-title {
flex-basis: 30px;
white-space: nowrap;
margin-right: 5px;
}
.reply-to {
max-width: 120px;
flex: 0 0 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
}
}
.input-emoji {
background-color: #fff;
height: 40px;
line-height: 40px;
font-size: 16px;
padding: 0 12px;
border-radius: 6px;
}
.input-text {
white-space: nowrap;
}
.input-placeholder {
background-color: #fff;
height: 40px;
line-height: 40px;
font-size: 16px;
padding: 0 12px;
border-radius: 6px;
color: gray;
}
.file-picker-wrapper {
position: absolute;
width: 60px;
height: 60px;
z-index: 1;
.files-button {
width: 60px;
height: 60px;
}
}
.pbInput {
height: "auto";
position: fixed;
left: 0;
width: 100vw;
}
// 临时处理方案, 全局样式在这里不生效
.g_flex_c {
display: flex;
display: -webkit-flex;
justify-content: center;
align-items: center;
}
.g_p_5 {
padding: 5px;
}
.g_p_6 {
padding: 6px;
}
.g_flex_1 {
flex: 1;
}
</style>