更新
This commit is contained in:
@@ -46,6 +46,12 @@
|
||||
<!-- 表情消息 -->
|
||||
<text v-if="msg.type === 'face'" class="message-emoji">{{ msg.emoji }}</text>
|
||||
|
||||
<!-- 语音消息 -->
|
||||
<view v-if="msg.type === 'audio'" class="message-audio" @click="playAudio(msg)">
|
||||
<text class="audio-icon">{{ playingAudioId === msg.audioId ? '⏸' : '▶' }}</text>
|
||||
<text class="audio-duration">{{ msg.duration || 0 }}秒</text>
|
||||
</view>
|
||||
|
||||
<!-- 消息时间 -->
|
||||
<view class="message-meta">
|
||||
<text class="message-time">{{ formatTime(msg.time) }}</text>
|
||||
@@ -73,22 +79,27 @@
|
||||
<uni-icons type="tool " size="30" color="#999"></uni-icons>
|
||||
<text class="tool-label">表情</text>
|
||||
</view> -->
|
||||
<view class="tool-item tool-item-left" @click="toggleInputMode">
|
||||
<uni-icons :type="inputMode === 'voice' ? 'compose' : 'mic'" size="38" color="#576b95"></uni-icons>
|
||||
<text class="tool-label" v-if="inputMode === 'voice'">文字</text>
|
||||
<text class="tool-label" v-else>语音</text>
|
||||
</view>
|
||||
<view class="tool-item" @click="chooseImage">
|
||||
<uni-icons type="image " size="30" color="#999"></uni-icons>
|
||||
<uni-icons type="image " size="40" color="#576b95"></uni-icons>
|
||||
<text class="tool-label">图片</text>
|
||||
</view>
|
||||
<view class="tool-item" @click="chooseFile">
|
||||
<uni-icons type="paperclip " size="30" color="#999"></uni-icons>
|
||||
<uni-icons type="paperclip " size="40" color="#576b95"></uni-icons>
|
||||
<text class="tool-label">文件</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 输入框 -->
|
||||
<view class="input-wrapper">
|
||||
<!-- 输入区域:文字模式 -->
|
||||
<view v-if="inputMode === 'text'" class="input-wrapper">
|
||||
<input
|
||||
class="message-input"
|
||||
v-model="inputText"
|
||||
placeholder="输入消息..."
|
||||
placeholder="请输入消息内容..."
|
||||
:adjust-position="false"
|
||||
:hold-keyboard="true"
|
||||
@focus="onInputFocus"
|
||||
@@ -99,6 +110,37 @@
|
||||
<text class="send-text">发送</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 语音模式:按住说话 -->
|
||||
<view
|
||||
v-else
|
||||
class="voice-input-wrapper"
|
||||
:class="{ 'voice-input-recording': isRecording }"
|
||||
@touchstart="onVoiceTouchStart"
|
||||
@touchmove="onVoiceTouchMove"
|
||||
@touchend="onVoiceTouchEnd"
|
||||
@touchcancel="onVoiceTouchCancel"
|
||||
>
|
||||
<text class="voice-input-text">{{ isRecording ? (willCancel ? '松开 取消' : '松开 发送') : '按住 说话' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 录音浮层 - 微信风格 -->
|
||||
<view v-if="isRecording" class="recording-overlay">
|
||||
<view class="recording-popup" :class="{ 'recording-cancel': willCancel }">
|
||||
<view class="recording-icon">
|
||||
<view v-if="!willCancel" class="recording-waves">
|
||||
<view class="wave"></view>
|
||||
<view class="wave"></view>
|
||||
<view class="wave"></view>
|
||||
<view class="wave"></view>
|
||||
<view class="wave"></view>
|
||||
</view>
|
||||
<text v-else class="cancel-icon">✕</text>
|
||||
</view>
|
||||
<text class="recording-hint">{{ willCancel ? '松开手指,取消发送' : '上滑取消' }}</text>
|
||||
<text class="recording-duration">{{ recordingDuration }}"</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表情选择器 -->
|
||||
@@ -137,7 +179,15 @@
|
||||
const showEmoji = ref(false);
|
||||
const keyboardHeight = ref(0);
|
||||
const userAvatar = ref(''); // 当前用户头像
|
||||
const inputMode = ref('text'); // 'text' | 'voice'
|
||||
const isRecording = ref(false);
|
||||
const recordingDuration = ref(0);
|
||||
const willCancel = ref(false); // 上滑取消
|
||||
const playingAudioId = ref('');
|
||||
let innerAudioContext = null;
|
||||
let chat = null;
|
||||
let recorderManager = null;
|
||||
let recordingTimer = null;
|
||||
let conversationID = '';
|
||||
let targetUserID = '';
|
||||
let currentUserID = ''; // 当前登录用户ID
|
||||
@@ -158,6 +208,174 @@
|
||||
'👏', '🙌', '👐', '🤲', '🤝', '🙏', '💪', '❤️'
|
||||
];
|
||||
|
||||
// 切换输入模式(文字/语音)
|
||||
function toggleInputMode() {
|
||||
inputMode.value = inputMode.value === 'text' ? 'voice' : 'text';
|
||||
}
|
||||
|
||||
// 初始化录音管理器
|
||||
function initRecorderManager() {
|
||||
// #ifdef MP-WEIXIN
|
||||
recorderManager = uni.getRecorderManager();
|
||||
recorderManager.onStop((res) => {
|
||||
if (willCancel.value) {
|
||||
willCancel.value = false;
|
||||
} else if (res.tempFilePath && res.duration >= 1000) {
|
||||
sendAudioMessage(res);
|
||||
} else if (res.duration < 1000) {
|
||||
uni.showToast({ title: '录音时间太短', icon: 'none' });
|
||||
}
|
||||
isRecording.value = false;
|
||||
recordingDuration.value = 0;
|
||||
if (recordingTimer) clearInterval(recordingTimer);
|
||||
});
|
||||
recorderManager.onError((err) => {
|
||||
console.error('录音错误:', err);
|
||||
isRecording.value = false;
|
||||
recordingDuration.value = 0;
|
||||
if (recordingTimer) clearInterval(recordingTimer);
|
||||
uni.showToast({ title: '录音失败', icon: 'none' });
|
||||
});
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 按住说话 - 开始
|
||||
function onVoiceTouchStart(e) {
|
||||
// #ifdef MP-WEIXIN
|
||||
e.preventDefault();
|
||||
willCancel.value = false;
|
||||
touchStartY = e.touches[0]?.clientY || 0;
|
||||
if (!recorderManager) initRecorderManager();
|
||||
startRecord();
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
uni.showToast({ title: '当前平台暂不支持语音', icon: 'none' });
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 松开 - 发送或取消
|
||||
function onVoiceTouchEnd(e) {
|
||||
// #ifdef MP-WEIXIN
|
||||
e.preventDefault();
|
||||
if (isRecording.value && recorderManager) {
|
||||
recorderManager.stop();
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 触摸取消(如滑出区域)
|
||||
function onVoiceTouchCancel(e) {
|
||||
onVoiceTouchEnd(e);
|
||||
}
|
||||
|
||||
let touchStartY = 0;
|
||||
// 监听上滑取消
|
||||
function onVoiceTouchMove(e) {
|
||||
if (!isRecording.value) return;
|
||||
const touch = e.touches[0];
|
||||
if (touch && touchStartY - touch.clientY > 80) {
|
||||
willCancel.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function startRecord() {
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.authorize({
|
||||
scope: 'scope.record',
|
||||
success: () => {
|
||||
isRecording.value = true;
|
||||
recordingDuration.value = 0;
|
||||
recorderManager.start({
|
||||
duration: 60000,
|
||||
sampleRate: 44100,
|
||||
numberOfChannels: 1,
|
||||
encodeBitRate: 192000,
|
||||
format: 'aac'
|
||||
});
|
||||
recordingTimer = setInterval(() => {
|
||||
recordingDuration.value++;
|
||||
}, 1000);
|
||||
},
|
||||
fail: () => {
|
||||
uni.showModal({
|
||||
title: '需要录音权限',
|
||||
content: '请在设置中开启录音权限以发送语音消息',
|
||||
confirmText: '去设置',
|
||||
success: (res) => {
|
||||
if (res.confirm) uni.openSetting();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
}
|
||||
|
||||
async function sendAudioMessage(recordRes) {
|
||||
try {
|
||||
uni.showLoading({ title: '发送中...' });
|
||||
const durationSec = Math.max(1, Math.round(recordRes.duration / 1000));
|
||||
const message = chat.createAudioMessage({
|
||||
to: targetUserID,
|
||||
conversationType: TencentCloudChat.TYPES.CONV_C2C,
|
||||
payload: { file: recordRes },
|
||||
onProgress: () => {}
|
||||
});
|
||||
const sendResult = await chat.sendMessage(message);
|
||||
const payload = sendResult?.data?.message?.payload || {};
|
||||
const audioUrl = payload.url || payload.file?.url || recordRes.tempFilePath;
|
||||
messages.value.push({
|
||||
type: 'audio',
|
||||
audioUrl,
|
||||
duration: durationSec,
|
||||
audioId: sendResult?.data?.message?.ID || 'local-' + Date.now(),
|
||||
time: Date.now(),
|
||||
flow: 'out',
|
||||
avatar: userAvatar.value
|
||||
});
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '发送成功', icon: 'success' });
|
||||
scrollToBottom();
|
||||
setTimeout(() => scrollToBottom(), 150);
|
||||
} catch (error) {
|
||||
console.error('发送语音失败:', error);
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '发送失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
|
||||
function playAudio(msg) {
|
||||
if (!msg.audioUrl) {
|
||||
uni.showToast({ title: '无法播放', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (playingAudioId.value === msg.audioId) {
|
||||
if (innerAudioContext) {
|
||||
innerAudioContext.stop();
|
||||
innerAudioContext.destroy();
|
||||
innerAudioContext = null;
|
||||
}
|
||||
playingAudioId.value = '';
|
||||
return;
|
||||
}
|
||||
if (innerAudioContext) {
|
||||
innerAudioContext.stop();
|
||||
innerAudioContext.destroy();
|
||||
}
|
||||
innerAudioContext = uni.createInnerAudioContext();
|
||||
innerAudioContext.src = msg.audioUrl;
|
||||
innerAudioContext.play();
|
||||
playingAudioId.value = msg.audioId;
|
||||
innerAudioContext.onEnded(() => {
|
||||
playingAudioId.value = '';
|
||||
innerAudioContext = null;
|
||||
});
|
||||
innerAudioContext.onError(() => {
|
||||
playingAudioId.value = '';
|
||||
innerAudioContext = null;
|
||||
uni.showToast({ title: '播放失败', icon: 'none' });
|
||||
});
|
||||
}
|
||||
|
||||
onLoad(async (options) => {
|
||||
try {
|
||||
const app = getApp();
|
||||
@@ -236,6 +454,13 @@
|
||||
onUnload(() => {
|
||||
if (chat) chat.off(TencentCloudChat.EVENT.MESSAGE_RECEIVED, onMessageReceived);
|
||||
if (scrollTimer) clearTimeout(scrollTimer);
|
||||
if (innerAudioContext) {
|
||||
innerAudioContext.stop();
|
||||
innerAudioContext.destroy();
|
||||
}
|
||||
if (isRecording.value && recorderManager) {
|
||||
recorderManager.stop();
|
||||
}
|
||||
});
|
||||
|
||||
async function loadHistoryMessages() {
|
||||
@@ -335,8 +560,11 @@
|
||||
return null; // 解析失败的消息不显示
|
||||
}
|
||||
|
||||
case TencentCloudChat.TYPES.MSG_AUDIO:
|
||||
return { ...baseMsg, type: 'text', text: '[语音消息]' };
|
||||
case TencentCloudChat.TYPES.MSG_AUDIO: {
|
||||
const audioUrl = msg.payload?.url || msg.payload?.file?.url || '';
|
||||
const audioDuration = msg.payload?.second ?? (msg.payload?.duration ? Math.round(msg.payload.duration / 1000) : 0);
|
||||
return { ...baseMsg, type: 'audio', audioUrl, duration: Math.round(audioDuration), audioId: msg.ID || msg.id || '' };
|
||||
}
|
||||
|
||||
case TencentCloudChat.TYPES.MSG_VIDEO:
|
||||
return { ...baseMsg, type: 'text', text: '[视频消息]' };
|
||||
@@ -625,37 +853,36 @@
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 容器 */
|
||||
/* 适老化设计:50-80岁 - 大字号、高对比、大触控区 */
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: #f7f8fa;
|
||||
background: #e8eaed;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.chat-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
padding: 0 20rpx;
|
||||
height: 100rpx;
|
||||
padding: 0 28rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #e7e7e7;
|
||||
border-bottom: 4rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 48rpx;
|
||||
color: #576b95;
|
||||
font-size: 52rpx;
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -667,51 +894,50 @@
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 34rpx;
|
||||
font-size: 40rpx;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.header-status {
|
||||
font-size: 22rpx;
|
||||
color: #888;
|
||||
margin-top: 2rpx;
|
||||
font-size: 28rpx;
|
||||
color: #555;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 88rpx;
|
||||
width: 100rpx;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.video-call-btn {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.video-icon {
|
||||
font-size: 44rpx;
|
||||
color: #576b95;
|
||||
font-size: 52rpx;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
/* 消息列表 */
|
||||
.message-list {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.message-container {
|
||||
padding: 20rpx 24rpx;
|
||||
padding-bottom: 280rpx;
|
||||
padding: 28rpx 32rpx;
|
||||
padding-bottom: 320rpx;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.message-wrapper {
|
||||
display: flex;
|
||||
margin-bottom: 24rpx;
|
||||
margin-bottom: 32rpx;
|
||||
animation: fadeIn 0.25s ease;
|
||||
}
|
||||
|
||||
@@ -728,45 +954,45 @@
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
/* 头像 */
|
||||
/* 头像 - 更大 */
|
||||
.avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 8rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
font-weight: 500;
|
||||
background: #e8e8e8;
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
.avatar-in {
|
||||
background: #e8e8e8;
|
||||
margin-right: 16rpx;
|
||||
background: #e0e0e0;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.avatar-out {
|
||||
background: #95ec69;
|
||||
margin-left: 16rpx;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
font-size: 28rpx;
|
||||
font-size: 36rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.avatar-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
/* 消息气泡 */
|
||||
/* 消息气泡 - 更大内边距 */
|
||||
.message-bubble {
|
||||
max-width: 500rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
border-radius: 8rpx;
|
||||
max-width: 540rpx;
|
||||
padding: 28rpx 32rpx;
|
||||
border-radius: 16rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -778,113 +1004,110 @@
|
||||
background: #95ec69;
|
||||
}
|
||||
|
||||
/* 文本消息 */
|
||||
/* 文本消息 - 大字号 */
|
||||
.message-text {
|
||||
font-size: 30rpx;
|
||||
line-height: 1.5;
|
||||
color: #000;
|
||||
font-size: 38rpx;
|
||||
line-height: 1.6;
|
||||
color: #1a1a1a;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.bubble-out .message-text {
|
||||
color: #000;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
/* 图片消息 */
|
||||
.message-image {
|
||||
max-width: 100%;
|
||||
border-radius: 8rpx;
|
||||
border-radius: 12rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 文件消息 */
|
||||
/* 文件消息 - 大触控区 */
|
||||
.message-file {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding: 12rpx;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
border-radius: 8rpx;
|
||||
gap: 24rpx;
|
||||
padding: 20rpx;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.bubble-out .message-file {
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.file-icon-wrapper {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
background: #576b95;
|
||||
border-radius: 8rpx;
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
background: #1890ff;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bubble-out .file-icon-wrapper {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
font-size: 40rpx;
|
||||
font-size: 48rpx;
|
||||
}
|
||||
|
||||
.file-details {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-size: 28rpx;
|
||||
color: #000;
|
||||
font-weight: 400;
|
||||
font-size: 34rpx;
|
||||
color: #1a1a1a;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bubble-out .file-name {
|
||||
color: #000;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.file-size {
|
||||
font-size: 22rpx;
|
||||
color: #888;
|
||||
font-size: 28rpx;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.bubble-out .file-size {
|
||||
color: #666;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.download-icon {
|
||||
font-size: 28rpx;
|
||||
color: #576b95;
|
||||
font-size: 36rpx;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.bubble-out .download-icon {
|
||||
color: #000;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
/* 表情消息 */
|
||||
.message-emoji {
|
||||
font-size: 96rpx;
|
||||
font-size: 112rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 消息元信息 */
|
||||
.message-meta {
|
||||
margin-top: 8rpx;
|
||||
margin-top: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-size: 20rpx;
|
||||
color: #b2b2b2;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.bubble-out .message-time {
|
||||
color: #888;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
@@ -894,92 +1117,220 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 600rpx;
|
||||
gap: 16rpx;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 100rpx;
|
||||
opacity: 0.25;
|
||||
font-size: 120rpx;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #b2b2b2;
|
||||
font-weight: 400;
|
||||
font-size: 38rpx;
|
||||
color: #555;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
font-size: 24rpx;
|
||||
color: #d0d0d0;
|
||||
font-size: 32rpx;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
/* 底部占位 */
|
||||
.bottom-placeholder {
|
||||
height: 40rpx;
|
||||
height: 56rpx;
|
||||
}
|
||||
|
||||
/* 输入区域 */
|
||||
/* 输入区域 - 大触控区 */
|
||||
.input-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #f7f8fa;
|
||||
border-top: 1rpx solid #e7e7e7;
|
||||
padding: 16rpx 20rpx;
|
||||
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
|
||||
background: #ededed;
|
||||
border-top: 1rpx solid #d9d9d9;
|
||||
padding: 20rpx 24rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
z-index: 100;
|
||||
transition: padding-bottom 0.3s ease;
|
||||
}
|
||||
|
||||
/* 工具栏 */
|
||||
.toolbar {
|
||||
display: flex;
|
||||
gap: 32rpx;
|
||||
margin-bottom: 16rpx;
|
||||
padding: 0 8rpx;
|
||||
gap: 48rpx;
|
||||
margin-bottom: 24rpx;
|
||||
padding: 0 12rpx;
|
||||
}
|
||||
|
||||
.tool-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
gap: 12rpx;
|
||||
min-height: 100rpx;
|
||||
}
|
||||
|
||||
.tool-item-left {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.tool-icon {
|
||||
font-size: 48rpx;
|
||||
font-size: 56rpx;
|
||||
}
|
||||
|
||||
.tool-label {
|
||||
font-size: 20rpx;
|
||||
color: #888;
|
||||
font-size: 30rpx;
|
||||
color: #555;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 语音输入 - 微信风格 */
|
||||
.voice-input-wrapper {
|
||||
height: 96rpx;
|
||||
background: #f7f7f7;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 56rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.voice-input-recording {
|
||||
background: #e8e8e8;
|
||||
border-color: #c0c0c0;
|
||||
}
|
||||
|
||||
.voice-input-text {
|
||||
font-size: 34rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 录音浮层 - 微信风格 */
|
||||
.recording-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.recording-popup {
|
||||
width: 320rpx;
|
||||
padding: 48rpx;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.recording-popup.recording-cancel {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.recording-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.recording-waves {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 8rpx;
|
||||
height: 80rpx;
|
||||
}
|
||||
|
||||
.recording-waves .wave {
|
||||
width: 12rpx;
|
||||
height: 24rpx;
|
||||
background: #07c160;
|
||||
border-radius: 6rpx;
|
||||
animation: waveAnim 0.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.recording-waves .wave:nth-child(1) { animation-delay: 0s; }
|
||||
.recording-waves .wave:nth-child(2) { animation-delay: 0.1s; }
|
||||
.recording-waves .wave:nth-child(3) { animation-delay: 0.2s; }
|
||||
.recording-waves .wave:nth-child(4) { animation-delay: 0.3s; }
|
||||
.recording-waves .wave:nth-child(5) { animation-delay: 0.4s; }
|
||||
|
||||
@keyframes waveAnim {
|
||||
0%, 100% { height: 24rpx; }
|
||||
50% { height: 60rpx; }
|
||||
}
|
||||
|
||||
.cancel-icon {
|
||||
font-size: 80rpx;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.recording-hint {
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.recording-popup .recording-duration {
|
||||
font-size: 32rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.message-audio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding: 20rpx 28rpx;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 12rpx;
|
||||
min-width: 120rpx;
|
||||
}
|
||||
|
||||
.bubble-out .message-audio {
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.audio-icon {
|
||||
font-size: 36rpx;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.audio-duration {
|
||||
font-size: 34rpx;
|
||||
color: #1a1a1a;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 输入框 */
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 45rpx;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 56rpx;
|
||||
}
|
||||
|
||||
.message-input {
|
||||
flex: 1;
|
||||
height: 72rpx;
|
||||
padding: 0 24rpx;
|
||||
height: 96rpx;
|
||||
padding: 0 32rpx;
|
||||
background: #fff;
|
||||
border-radius: 8rpx;
|
||||
border: 1rpx solid #e7e7e7;
|
||||
font-size: 28rpx;
|
||||
color: #000;
|
||||
border-radius: 16rpx;
|
||||
border: 4rpx solid #e0e0e0;
|
||||
font-size: 36rpx;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.send-button {
|
||||
width: 112rpx;
|
||||
height: 72rpx;
|
||||
background: #ededed;
|
||||
border-radius: 8rpx;
|
||||
width: 140rpx;
|
||||
height: 96rpx;
|
||||
background: #e0e0e0;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -991,16 +1342,15 @@
|
||||
}
|
||||
|
||||
.send-text {
|
||||
font-size: 28rpx;
|
||||
font-size: 36rpx;
|
||||
color: #888;
|
||||
font-weight: 400;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.send-button-active .send-text {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 表情选择器 */
|
||||
.emoji-panel {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -1017,8 +1367,8 @@
|
||||
.emoji-content {
|
||||
width: 100%;
|
||||
max-height: 65vh;
|
||||
background: #f7f8fa;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
background: #e8eaed;
|
||||
border-radius: 28rpx 28rpx 0 0;
|
||||
animation: slideUp 0.25s ease;
|
||||
}
|
||||
|
||||
@@ -1031,20 +1381,20 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 28rpx 28rpx 20rpx;
|
||||
border-bottom: 1rpx solid #e7e7e7;
|
||||
padding: 36rpx 36rpx 28rpx;
|
||||
border-bottom: 4rpx solid #e0e0e0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.emoji-title {
|
||||
font-size: 30rpx;
|
||||
font-size: 38rpx;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.emoji-close {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
background: #f0f0f0;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
@@ -1053,27 +1403,27 @@
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
font-size: 28rpx;
|
||||
color: #888;
|
||||
font-size: 36rpx;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.emoji-scroll {
|
||||
max-height: 55vh;
|
||||
padding: 20rpx;
|
||||
background: #f7f8fa;
|
||||
padding: 28rpx;
|
||||
background: #e8eaed;
|
||||
}
|
||||
|
||||
.emoji-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.emoji-cell {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -1086,6 +1436,6 @@
|
||||
}
|
||||
|
||||
.emoji-icon {
|
||||
font-size: 52rpx;
|
||||
font-size: 60rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user