Files
2026-03-11 09:49:47 +08:00

329 lines
8.3 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<!-- 消息输入容器-->
<div class="message-input-container" :style="{ paddingBottom: keyboardHeight > 0 ? keyboardHeight + 'px' : '' }">
<!-- 正常状态可以发送消息 -->
<view class="input-container" v-if="canSendMessage">
<!-- 消息输入框 -->
<input class="message-input" type="text" :value="inputRawValue" confirm-type="send" @confirm="sendInputMessage"
@input="updateRawValue($event.target.value)" :adjust-position="false"
:style="{ borderColor: inputRawValue ? '#006eff' : 'transparent' }" />
<!-- 更多功能按钮当输入框为空时显示 -->
<view class="icon-btn" v-if="!inputRawValue" @click="toggleMorePanel">
<image :src="MorePanelIcon"></image>
</view>
<!-- 发送按钮当输入框有内容时显示 -->
<view class="send-btn" v-if="inputRawValue" @click="sendInputMessage">
<text>发送</text>
</view>
</view>
<!-- 退出群聊状态显示提示信息 -->
<view class="input-container disabled-container" v-else>
<view class="disabled-message">
<text class="disabled-text">你已退出群聊无法发送消息</text>
</view>
</view>
<!-- 更多功能面板 -->
<view class="more-panel" v-if="showMorePanel" @touchmove.stop.prevent>
<view class="panel-content">
<ImagePicker @closePanel="closePanel" />
<PhotoPicker @closePanel="closePanel" />
<VideoCallPicker @closePanel="closePanel" @showUserPicker="handleShowUserPicker" />
<VoiceCallPicker @closePanel="closePanel" @showUserPicker="handleShowUserPicker" />
</view>
</view>
<!-- 用户选择器弹窗 -->
<view v-if="showUserPicker" class="user-picker-modal">
<view class="modal-mask" @click="closeUserPicker"></view>
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">选择通话用户</text>
<view class="close-btn" @click="closeUserPicker">×</view>
</view>
<UserPicker :maxCount="1" :dataSource="filteredGroupMembers" :enableDelete="false" :inModal="true"
@confirm="handleUserSelect" />
</view>
</view>
</div>
</template>
<script lang="ts">
export default {
options: {
virtualHost: true,
}
}
</script>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { useMessageInputState } from '../../states/MessageInputState';
import { useConversationListState } from '../../states/ConversationListState';
import { useGroupSettingState } from '../../states/GroupSettingState';
import VideoCallPicker from './AttachmentPicker/VideoCallPicker.vue';
import VoiceCallPicker from './AttachmentPicker/voiceCallPicker.vue';
import ImagePicker from './AttachmentPicker/CameraPicker.vue';
import PhotoPicker from './AttachmentPicker/PhotoPicker.vue';
import UserPicker from '../UserPicker/UserPicker.vue';
import MorePanelIcon from '../../assets/chat/more-panel.svg';
import { TUIBridge } from '../../TUIBridge';
import { EVENT } from '../../constants/event';
import { MessageContentType } from '../../constants/chat'
const { inputRawValue, updateRawValue, sendMessage } = useMessageInputState();
const { activeConversation } = useConversationListState();
const { allMembers, isInGroup, currentUserID, getGroupMemberList } = useGroupSettingState();
const showMorePanel = ref(false);
const showUserPicker = ref(false);
const currentCallType = ref(1);
const keyboardHeight = ref(0);
// 判断是否可以发送消息
const canSendMessage = computed(() => {
// 如果不是群聊,可以发送消息
if (activeConversation.value?.type !== 'GROUP') {
return true;
}
// 如果是群聊,检查是否在群里
return isInGroup.value !== false;
});
// 过滤掉自己的群成员列表
const filteredGroupMembers = computed(() => {
return (allMembers.value || []).filter(member => member.userID !== currentUserID.value);
});
const toggleMorePanel = () => {
showMorePanel.value = !showMorePanel.value;
};
const closePanel = () => {
showMorePanel.value = false;
};
const sendInputMessage = async () => {
if (!inputRawValue.value) return;
sendMessage({
type: MessageContentType.TEXT,
content: inputRawValue.value
});
inputRawValue.value = '';
};
const handleShowUserPicker = ({ type }) => {
currentCallType.value = type;
showUserPicker.value = true;
};
const handleUserSelect = (selectedUserIDs: string[]) => {
if (selectedUserIDs.length > 0) {
TUIBridge.notifyEvent({
eventName: EVENT.ON_CALLS,
params: {
userIDList: selectedUserIDs,
type: currentCallType.value,
// groupID: activeConversation.value.groupID,
},
});
}
closeUserPicker();
};
const closeUserPicker = () => {
showUserPicker.value = false;
};
onMounted(async () => {
if (activeConversation.value?.type === 'GROUP') {
await getGroupMemberList({ count: 100 });
}
// Listen for keyboard height changes
uni.onKeyboardHeightChange((res) => {
keyboardHeight.value = res.height;
if (res.height > 0) {
// Close more panel when keyboard pops up
showMorePanel.value = false;
uni.$emit('TUIChat:keyboardHeightChange', res.height);
}
});
})
onUnmounted(() => {
uni.offKeyboardHeightChange();
})
</script>
<style lang="scss" scoped>
.message-input-container {
position: relative;
/* iOS安全区域适配 - 稍微增加间距 */
padding-bottom: calc(constant(safe-area-inset-bottom) * 0.4);
padding-bottom: calc(env(safe-area-inset-bottom) * 0.4);
/* 添加背景色覆盖安全区域 */
background-color: #FFFFFF;
}
.input-container {
width: 100%;
height: 48px;
background-color: #FFFFFF;
border-top: 1px solid #e5e5e5;
display: flex;
align-items: center;
z-index: 100;
/* 移除额外间距,只保留外层容器的安全区域适配 */
padding-bottom: 0;
}
.icon-btn {
width: 28px;
height: 28px;
margin-right: 10px;
display: flex;
align-items: center;
justify-content: center;
image {
width: 100%;
height: 100%;
}
}
.message-input {
flex: 1;
height: 36px;
padding: 0 15px;
background-color: #F0F2F7;
border-radius: 4px;
font-size: 16px;
border: 1px solid transparent;
margin: 0 10px;
}
.disabled-container {
justify-content: center;
align-items: center;
background-color: #f8f9fa;
/* iOS安全区域适配 - 稍微增加间距 */
padding-bottom: calc(constant(safe-area-inset-bottom) * 0.55);
padding-bottom: calc(env(safe-area-inset-bottom) * 0.55);
}
.disabled-message {
padding: 12px 16px;
text-align: center;
}
.disabled-text {
font-size: 14px;
color: #999;
line-height: 1.4;
}
.send-btn {
min-width: 60px;
height: 36px;
padding: 0 12px;
display: flex;
justify-content: center;
align-items: center;
background-color: #006eff;
color: #fff;
border-radius: 4px;
font-size: 14px;
margin: 0 5px;
}
.more-panel {
width: 100%;
height: 125px;
background-color: #fff;
z-index: 99;
/* iOS安全区域适配 - 稍微增加间距 */
padding-bottom: calc(constant(safe-area-inset-bottom) * 0.55);
padding-bottom: calc(env(safe-area-inset-bottom) * 0.55);
}
.panel-content {
height: 100%;
padding: 10px 0;
display: flex;
flex-wrap: wrap;
border-top: 1px solid #eee;
justify-content: center;
padding: 10px;
}
.user-picker-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.modal-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
position: relative;
width: 95%;
max-width: 500px;
height: 90vh;
max-height: 800px;
background-color: #fff;
border-radius: 12px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #e5e5e5;
background-color: #fff;
}
.modal-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.close-btn {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #999;
cursor: pointer;
border-radius: 50%;
transition: background-color 0.2s;
}
.close-btn:hover {
background-color: #f5f5f5;
}
</style>