<template>
	<view 
		class="chat-container" 
		@touchend.capture="onRecordingTouchEnd" 
		@touchcancel.capture="onRecordingTouchCancel"
	>
		<!-- 顶部导航栏 -->
		<!-- 消息列表 -->
		<scroll-view 
			class="message-list" 
			scroll-y 
			:scroll-top="scrollTop"
			scroll-with-animation
			@scroll="onScroll"
		>
			<view class="message-container">
				<!-- 医生卡片（视频通话开始或结束时自动隐藏） -->
				<view v-if="doctorInfo && showDoctorCard" class="doctor-card" @click="goDoctorDetail">
					<view class="doctor-card-topbar">
						<text class="doctor-card-label">接诊医生</text>
						<view class="doctor-card-status-pill" :class="{ 'doctor-card-status-pill--entered': doctorEnteredConsultRoom }">
							<view class="doctor-card-status-pulse" :class="{ 'doctor-card-status-pulse--entered': doctorEnteredConsultRoom }"></view>
							<text class="doctor-card-status-text">{{ doctorEnteredConsultRoom ? '已进入诊室' : '等待接诊' }}</text>
						</view>
					</view>
					<view class="doctor-card-main">
						<view class="doctor-card-avatar-wrap">
							<view class="doctor-card-avatar-ring">
								<image :src="doctorInfo.avatar || '/static/user/user.png'" class="doctor-card-avatar" mode="aspectFill" />
							</view>
						</view>
						<view class="doctor-card-info">
							<text class="doctor-card-name">{{ doctorInfo.name || '医生' }}</text>
							<view class="doctor-card-meta">
								<text class="doctor-card-title">{{ doctorInfo.title || '主任医师' }}</text>
								<text class="doctor-card-dot">·</text>
								<text class="doctor-card-hospital">{{ doctorInfo.hospital || '甄养堂医院' }}</text>
							</view>
						</view>
						<view class="doctor-card-chevron" aria-hidden="true">
							<text class="doctor-card-chevron-icon">›</text>
						</view>
					</view>
					<view class="doctor-card-tip">
						<view class="doctor-card-tip-bar"></view>
						<view class="doctor-card-tip-inner">
							<text class="doctor-card-tip-title">{{ doctorEnteredConsultRoom ? '面诊提示' : '温馨提示' }}</text>
							<text class="doctor-card-tip-text">{{ doctorEnteredConsultRoom ? '医生已进入诊室，即将为您视频面诊，请保持网络畅通' : '请稍候，医生进入诊室后将与您在线沟通' }}</text>
						</view>
					</view>
				</view>
				<view v-for="(msg, index) in messages" :key="index" class="message-wrapper" :class="[msg.flow === 'out' ? 'message-wrapper-out' : 'message-wrapper-in', msg.type === 'confirmation' ? 'message-wrapper-confirmation' : '']">
					<!-- 头像 -->
				<!-- 头像 -->
				<view class="avatar" :class="msg.flow === 'out' ? 'avatar-out' : 'avatar-in'">
					<image 
						v-if="msg.avatar" 
						:src="msg.avatar" 
						class="avatar-image"
						mode="aspectFill"
					/>
					<text v-else class="avatar-text">{{ msg.flow === 'out' ? '我' : '医' }}</text>
				</view>
				
				<!-- 消息内容 -->
				<view class="message-bubble" :class="[msg.flow === 'out' ? 'bubble-out' : 'bubble-in', msg.type === 'confirmation' ? 'bubble-confirmation' : '']">
					<!-- 知情确认：独立卡片样式 -->
					<view v-if="msg.type === 'confirmation'" class="confirmation-card">
						<view class="confirmation-card-head">
							<view class="confirmation-card-badge">
								<text class="confirmation-card-badge-icon">✓</text>
							</view>
							<view class="confirmation-card-head-text">
								<text class="confirmation-card-title">知情同意确认</text>
								<text class="confirmation-card-sub">请确认您已阅读并同意以下内容</text>
							</view>
						</view>
						<view class="confirmation-card-divider"></view>
						<view class="confirmation-card-lines">
							<text v-for="(line, li) in getConfirmationLines(msg.text)" :key="li" class="confirmation-line">{{ line }}</text>
						</view>
					</view>
					<!-- 文本消息 -->
					<text v-if="msg.type === 'text'" class="message-text">{{ msg.text }}</text>
					
					<!-- 图片消息 -->
					<image v-if="msg.type === 'image'" class="message-image" :src="msg.imageUrl" mode="widthFix" @click="previewImage(msg.imageUrl)" />
					
					<!-- 文件消息 -->
					<view v-if="msg.type === 'file'" class="message-file" @click="downloadFile(msg)">
						<view class="file-icon-wrapper">
							<text class="file-icon">*</text>
						</view>
						<view class="file-details">
							<text class="file-name">{{ msg.fileName }}</text>
							<text class="file-size">{{ formatFileSize(msg.fileSize) }}</text>
						</view>
						<text class="download-icon">⬇</text>
					</view>
					
					<!-- 表情消息 -->
					<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>
					</view>
				</view>
			</view>
			
			<!-- 空状态 -->
			<view v-if="messages.length === 0" class="empty-state">
				<text class="empty-icon">💬</text>
				<text class="empty-text">暂无消息</text>
				<text class="empty-hint">开始聊天吧</text>
			</view>
			
			<!-- 底部占位，确保最后一条消息不被遮挡 -->
			<view class="bottom-placeholder"></view>
			</view>
		</scroll-view>
		
		<!-- 输入区域 -->
		<view class="input-container" :style="{ paddingBottom: keyboardHeight + 'px' }">
			<!-- 工具栏 -->
			<view class="toolbar">
				<!-- <view class="tool-item" @click="showEmojiPicker">
				<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="28" 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="#576b95"></uni-icons>
					<text class="tool-label">图片</text>
				</view>
				<view class="tool-item" @click="chooseFile">
					<uni-icons type="paperclip " size="30" color="#576b95"></uni-icons>
					<text class="tool-label">文件</text>
				</view>
			</view>
			
			<!-- 输入区域：文字模式 -->
			<view v-if="inputMode === 'text'" class="input-wrapper">
				<input 
					class="message-input" 
					v-model="inputText" 
					placeholder="请输入消息内容..." 
					:adjust-position="false"
					:hold-keyboard="true"
					@focus="onInputFocus"
					@blur="onInputBlur"
					@confirm="sendTextMessage" 
				/>
				<view class="send-button" :class="{ 'send-button-active': inputText.trim() }" @click="sendTextMessage">
					<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" 
			@click="forceCancelRecording"
			@touchend="onVoiceTouchEnd"
			@touchcancel="onVoiceTouchCancel"
		>
			<view 
				class="recording-popup" 
				:class="{ 'recording-cancel': willCancel }" 
				@click.stop 
				@touchend.stop="onVoiceTouchEnd"
				@touchcancel.stop="onVoiceTouchCancel"
			>
				<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>
		
		<!-- 表情选择器 -->
		<view v-if="showEmoji" class="emoji-panel" @click.self="showEmoji = false">
			<view class="emoji-content">
				<view class="emoji-header">
					<text class="emoji-title">选择表情</text>
					<view class="emoji-close" @click="showEmoji = false">
						<text class="close-icon">✕</text>
					</view>
				</view>
				<scroll-view class="emoji-scroll" scroll-y>
					<view class="emoji-grid">
						<view v-for="(emoji, index) in emojiList" :key="index" class="emoji-cell" @click="sendEmoji(emoji)">
							<text class="emoji-icon">{{ emoji }}</text>
						</view>
					</view>
				</scroll-view>
			</view>
		</view>
	</view>
</template>

<script setup>
	import { ref, nextTick, getCurrentInstance } from 'vue';
	import { onLoad, onShow, onUnload, onHide } from '@dcloudio/uni-app';
	import TencentCloudChat from '@tencentcloud/lite-chat/basic';
	import conversationPlugin from '@tencentcloud/lite-chat/plugins/conversation';
	import messageEnhancerPlugin from '@tencentcloud/lite-chat/plugins/message-enhancer';
	import richMediaMessagePlugin from '@tencentcloud/lite-chat/plugins/rich-media-message';
	import groupPlugin from '@tencentcloud/lite-chat/plugins/group';
import { TUICallKitAPI } from '@/TUICallKit/src/TUICallService/index';
import { TUIStore } from '@/TUICallKit/src/TUICallService/CallService/index';
import { StoreName, NAME, CallStatus } from '@/TUICallKit/src/TUICallService/const/index';
// CallManager 在 App.vue 已挂载到 uni.CallManager，勿在此重复 new，否则会丢失通话状态
const { proxy } = getCurrentInstance()
	const messages = ref([]);
	const inputText = ref('');
	const scrollTop = ref(0);
	const showEmoji = ref(false);
	const keyboardHeight = ref(0);
	const userAvatar = ref(''); // 当前用户头像
	const doctorInfo = ref(null); // 医生信息（对方为医生时展示）
	const showDoctorCard = ref(true); // 是否显示医生卡片（通话开始/结束时隐藏）
	/** 收到医生端「已进入诊室」信令后为 true（聊天列表不展示该条自定义消息） */
	const doctorEnteredConsultRoom = ref(false);
	const doctorWaitSeconds = ref(0); // 等待医生计时（秒）
	let doctorWaitTimer = null; // 等待计时器
	const inputMode = ref('text'); // 'text' | 'voice'
	const isRecording = ref(false);
	const recordingDuration = ref(0);
	const willCancel = ref(false); // 上滑取消
	let voiceModeSwitchTime = 0; // 切换语音模式的时间戳，用于防误触
	let voiceHoldTimer = null; // 按住计时器，必须按住超过此时间才真正开始录音
	let isVoiceTouchDown = false; // 手指是否仍在按下
	const playingAudioId = ref('');
	let innerAudioContext = null;
	let chat = null;
	let recorderManager = null;
	let recordingTimer = null;
	let conversationID = '';
	let targetUserID = '';
	let currentUserID = ''; // 当前登录用户ID
	let scrollTimer = null; // 滚动定时器
	/** 微信小程序：从系统后台回前台时刷新 IM/TUICall，避免无法接通视频 */
	let unsubscribeWxAppShow = null;
	let allowChatForegroundRelogin = false;
	let foregroundReloginTimer = null;
	/** 避免 onUnload 重复上报离开 */
	let patientLeaveNotified = false;
	
	const CONFIRMATION_MESSAGE = `我已确认以下信息:
1、本人确认已在线下确诊;
2、本人确认问诊单信息无误;
3、本人确认已知晓病情评估风险告知;
4、本人确认已阅读并同意《互联网诊疗知情同意书》。`;

	function normalizeConfirmText(t) {
		return String(t || '').replace(/\r\n/g, '\n').trim();
	}
	function isConfirmationMessageText(text) {
		return normalizeConfirmText(text) === normalizeConfirmText(CONFIRMATION_MESSAGE);
	}
	function getConfirmationLines(text) {
		return normalizeConfirmText(text).split('\n').filter((line) => line.length > 0);
	}
	
	const emojiList = [
		'😀', '😃', '😄', '😁', '😆', '😅', '🤣', '😂',
		'🙂', '🙃', '😉', '😊', '😇', '🥰', '😍', '🤩',
		'😘', '😗', '😚', '😙', '😋', '😛', '😜', '🤪',
		'😝', '🤑', '🤗', '🤭', '🤫', '🤔', '🤐', '🤨',
		'😐', '😑', '😶', '😏', '😒', '🙄', '😬', '🤥',
		'😌', '😔', '😪', '🤤', '😴', '😷', '🤒', '🤕',
		'🤢', '🤮', '🤧', '🥵', '🥶', '😵', '🤯', '🤠',
		'🥳', '😎', '🤓', '🧐', '😕', '😟', '🙁', '☹️',
		'😮', '😯', '😲', '😳', '🥺', '😦', '😧', '😨',
		'😰', '😥', '😢', '😭', '😱', '😖', '😣', '😞',
		'👍', '👎', '👌', '✌️', '🤞', '🤟', '🤘', '🤙',
		'👏', '🙌', '👐', '🤲', '🤝', '🙏', '💪', '❤️'
	];

	// 切换输入模式（文字/语音）
	function toggleInputMode() {
		const isSwitchToVoice = inputMode.value === 'text';
		inputMode.value = isSwitchToVoice ? 'voice' : 'text';
		// 切换到语音时：重置录音状态，防止卡住；记录时间用于防误触
		if (isSwitchToVoice) {
			isVoiceTouchDown = false;
			if (voiceHoldTimer) {
				clearTimeout(voiceHoldTimer);
				voiceHoldTimer = null;
			}
			isRecording.value = false;
			willCancel.value = false;
			recordingDuration.value = 0;
			if (recordingTimer) {
				clearInterval(recordingTimer);
				recordingTimer = null;
			}
			if (recorderManager) {
				try { recorderManager.stop(); } catch (_) {}
			}
			voiceModeSwitchTime = Date.now();
		}
	}

	// 初始化录音管理器
	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
	}

	// 按住说话 - 开始（必须按住超过 300ms 才真正开始录音，点击一下不会触发）
	function onVoiceTouchStart(e) {
		// #ifdef MP-WEIXIN
		// 防误触：刚切换语音模式 400ms 内忽略触摸
		if (Date.now() - voiceModeSwitchTime < 400) return;
		e.preventDefault();
		isVoiceTouchDown = true;
		willCancel.value = false;
		touchStartY = e.touches[0]?.clientY || 0;
		if (!recorderManager) initRecorderManager();
		// 必须按住 300ms 才真正开始录音，点击一下不会触发
		voiceHoldTimer = setTimeout(() => {
			voiceHoldTimer = null;
			if (isVoiceTouchDown) {
				startRecord();
			}
		}, 300);
		// #endif
		// #ifndef MP-WEIXIN
		uni.showToast({ title: '当前平台暂不支持语音', icon: 'none' });
		// #endif
	}

	// 松开 - 发送或取消
	function onVoiceTouchEnd(e) {
		// #ifdef MP-WEIXIN
		e.preventDefault();
		isVoiceTouchDown = false;
		if (voiceHoldTimer) {
			clearTimeout(voiceHoldTimer);
			voiceHoldTimer = null;
			// 未按住满 300ms 就松开了，不开始录音
			return;
		}
		if (isRecording.value && recorderManager) {
			recorderManager.stop();
		}
		// #endif
	}

	// 触摸取消（如滑出区域）
	function onVoiceTouchCancel(e) {
		onVoiceTouchEnd(e);
	}

	// 捕获阶段监听：录音时任意位置松开都能结束（解决浮层遮挡导致 touchend 收不到的问题）
	function onRecordingTouchEnd(e) {
		if (isRecording.value) {
			e.stopPropagation();
			onVoiceTouchEnd(e);
		}
	}
	function onRecordingTouchCancel(e) {
		if (isRecording.value) {
			e.stopPropagation();
			onVoiceTouchCancel(e);
		}
	}

	// 强制取消录音（点击蒙层逃生，防止卡住）
	function forceCancelRecording() {
		isVoiceTouchDown = false;
		if (voiceHoldTimer) {
			clearTimeout(voiceHoldTimer);
			voiceHoldTimer = null;
		}
		if (!isRecording.value) return;
		willCancel.value = true;
		if (recorderManager) {
			try { recorderManager.stop(); } catch (_) {}
		}
		isRecording.value = false;
		recordingDuration.value = 0;
		if (recordingTimer) {
			clearInterval(recordingTimer);
			recordingTimer = null;
		}
	}

	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' });
		});
	}  
	let patient_data=ref();
	const video =async (userid)=>{
		
		const uid=userid.replace('patient_', '')
		console.log('userid',userid,uid)
		const res = await proxy.apiUrl({
		  url: '/api/tcm/getPatientSignature',
		  method: 'GET',
		  data: {
		     patient_id: uid
		  },
		}, false) 
		
		const { userId, userSig, sdkAppId: SDKAppID } = res.data;
		patient_data.value=res.data.data
			
		getApp().globalData.userID = userId;
		getApp().globalData.userSig = userSig;
		getApp().globalData.SDKAppID = SDKAppID;
		uni.setStorageSync('CallManager', userId)
		// TUICallKit 必须在 TIM chat 登录成功后再 init，且必须传入 tim，否则无法接收通话信令（聊天正常、视频接不到）
	}
	/** 与当前页 IM 实例绑定后初始化音视频（TUICallEngine 依赖 tim 收发自定义信令） */
	async function initTUICallKitWithTim(imChat) {
		const app = getApp();
		const sdkAppID = app.globalData.SDKAppID;
		const userSig = app.globalData.userSig;
		// 必须与 chat.login 的 userID 一致（globalData.userId 与路由可能不同步）
		const userID = currentUserID || app.globalData.userID;
		if (!imChat || !sdkAppID || !userSig || !userID) {
			console.warn('initTUICallKitWithTim: 缺少 im / SDKAppID / userSig / userID');
			return;
		}
		// 非 IDLE 时 destroyed 会抛错，随后 _doInit 会因旧 engine 仍存在而直接 return，导致未绑定新 tim、接不到来电
		try {
			await TUICallKitAPI.handleExceptionExit();
		} catch (_) {}
		try {
			await uni.CallManager.destroyed();
		} catch (e) {
			console.warn('CallManager.destroyed:', e);
			try {
				await TUICallKitAPI.handleExceptionExit();
			} catch (_) {}
			try {
				await uni.CallManager.destroyed();
			} catch (e2) {
				console.warn('CallManager.destroyed retry:', e2);
			}
		}
		await uni.CallManager.init({
			sdkAppID: Number(sdkAppID),
			userID,
			userSig,
			globalCallPagePath: 'TUICallKit/src/Components/TUICallKit',
			tim: imChat
		});
	}
	onLoad(async (options) => {
		try {
		patientLeaveNotified = false;
		console.log('进入onMounted:', options);
			const app = getApp();
			const userID = options.userID|| app.globalData.userID;
			if(!userID.includes('patient')){
				userID='patient_'+userID
			}
			await video(userID)
			const userSig =  options.userSig||app.globalData.userSig;
			const SDKAppID = options.SDKAppID|| app.globalData.SDKAppID ;
			targetUserID = options.targetUserID;
			currentUserID = userID; // 保存当前用户ID
			console.log('currentUserID',currentUserID)
			// 获取用户头像
			const userData = uni.getStorageSync('userData');
			if (userData && userData.avatar) {
				userAvatar.value = userData.avatar;
			}
			
			if (!userID || !userSig || !SDKAppID) {
				uni.showToast({ title: '登录信息缺失', icon: 'none' });
				setTimeout(() => uni.navigateBack(), 1500);
				return;
			}
			
			conversationID = `C2C${targetUserID}`;
			doctorEnteredConsultRoom.value = false;
			console.log('currentUserID:', currentUserID, '对方用户:', targetUserID,'conversationID'+conversationID);
		
			console.log('onMounted',userID,userSig,SDKAppID,conversationID)
			// 切换账户前先登出，避免 IM 登录用户不匹配
			const prevUserID = app.globalData.imChatUserID;
			if (app.globalData.imChat && prevUserID !== userID) {
				try {
					app.globalData.imChat.off?.(TencentCloudChat.EVENT.MESSAGE_RECEIVED, onMessageReceived);
					await app.globalData.imChat.logout();
					await app.globalData.imChat.destroy?.();
				} catch (e) {
					console.warn('IM logout error:', e);
				}
				app.globalData.imChat = null;
				app.globalData.imChatUserID = null;
			}
			if (!app.globalData.imChat) {
				chat = TencentCloudChat.create({
					SDKAppID: Number(SDKAppID),
					scene: 'uniapp-wx'
				});
				
				chat.use(conversationPlugin);
				chat.use(messageEnhancerPlugin);
				chat.use(richMediaMessagePlugin);
				chat.use(groupPlugin);
				
				const doLogin = () => chat.login({ userID, userSig });
				const getLoginCode = (err) => {
					const c = err?.code ?? err?.error?.code;
					if (c != null) return c;
					const msg = String(err?.message ?? err?.error?.message ?? '');
					const m = msg.match(/["']?code["']?\s*:\s*(\d+)/);
					return m ? parseInt(m[1], 10) : null;
				};
				let loginOk = false;
				for (let retry = 0; retry <= 2; retry++) {
					try {
						await doLogin();
						loginOk = true;
						break;
					} catch (loginErr) {
						const code = getLoginCode(loginErr);
						const msg = String(loginErr?.message ?? loginErr?.error?.message ?? '');
						const isDuplicateLogin = code === 2025 || msg.includes('重复登录');
						if (isDuplicateLogin && retry < 2) {
							// 2025 重复登录：销毁当前实例，等待后重建并重试
							try {
								await chat.destroy?.();
							} catch (_) {}
							chat = null;
							await new Promise(r => setTimeout(r, 600));
							chat = TencentCloudChat.create({
								SDKAppID: Number(SDKAppID),
								scene: 'uniapp-wx'
							});
							chat.use(conversationPlugin);
							chat.use(messageEnhancerPlugin);
							chat.use(richMediaMessagePlugin);
							chat.use(groupPlugin);
						} else {
							throw loginErr;
						}
					}
				}
				if (!loginOk) throw new Error('IM 登录失败');
				// updateMyProfile 非必需，失败不阻塞聊天（如 2024 用户未登录时可忽略）
				try {
					await chat.updateMyProfile({
						avatar: userData?.avatar,
						gender: TencentCloudChat.TYPES.GENDER_MALE,
					});
				} catch (profileErr) {
					console.warn('updateMyProfile error:', profileErr);
				}
				app.globalData.imChat = chat;
				app.globalData.imChatUserID = userID;
				chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED, onMessageReceived);
			} else {
				chat = app.globalData.imChat;
				chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED, onMessageReceived);
			}

			await initTUICallKitWithTim(chat);
			
			await loadHistoryMessages();
			
			// 打开会话时通知医生（IM自定义消息 + 后端全局事件）
			await notifyDoctorOnChatOpen();
			
			if(options.msg==1){
				console.log('msg我进来了',options.msg)
				await waitForIMReady(); // 确保 SDK 网络就绪后再发消息
				await sendConfirmationMessage(); // 进入聊天时自动发送知情确认
			}
			loadDoctorInfo(); // 加载医生卡片信息（对方为医生时）
			uni.$on('TUICall:callStart', onVideoCallStart);
			uni.$on('TUICall:callEnd', onVideoCallEnd);
			// 提前申请音视频权限，避免来电接通时才弹「去设置」提示
			requestAVPermissions({ silent: true, scene: 'onLoad' });
			//uni.showToast({ title: '聊天已就绪', icon: 'success' });
			
			// #ifdef MP-WEIXIN
			// 系统级前后台切换时页面 onShow 不一定等价于「回前台」，用 wx.onAppShow 刷新音视频信令链路
			if (typeof wx !== 'undefined' && wx.onAppShow) {
				unsubscribeWxAppShow = wx.onAppShow(() => {
					if (!allowChatForegroundRelogin) return;
					const pages = getCurrentPages();
					const top = pages[pages.length - 1];
					if (!(top?.route || '').includes('pages/chat/chat')) return;
					reloginAfterMiniProgramForeground();
				});
			}
			setTimeout(() => {
				allowChatForegroundRelogin = true;
			}, 800);
			// #endif
			
			// 监听键盘高度变化
			uni.onKeyboardHeightChange((res) => {
				keyboardHeight.value = res.height;
				if (res.height > 0) {
					// 键盘弹起时滚动到底部
					setTimeout(() => scrollToBottom(), 100);
				}
			});
		} catch (error) {
			console.error('初始化失败:', error);
			uni.showModal({
				title: '初始化失败',
				content: error.message || '聊天功能初始化失败',
				showCancel: false,
				success: () => uni.navigateBack()
			});
		}
	});
	
	onHide(() => {
		const pages = getCurrentPages();
		const top = pages[pages.length - 1];
		if (top?.route?.includes('TUICallKit')) {
			getApp().globalData._chatFromCallPage = true;
		}
	});
	
	onShow(() => {
		if (getApp().globalData._chatFromCallPage) {
			getApp().globalData._chatFromCallPage = false;
			hideDoctorCardByCall();
		}
	});
	
	onUnload(() => {
		// 患者退出聊天页：通知后端（与 notifyOpen 对称，供管理端轮询）
		if (!patientLeaveNotified && targetUserID && String(targetUserID).startsWith('doctor_')) {
			patientLeaveNotified = true;
			void notifyDoctorOnChatClose();
		}
		allowChatForegroundRelogin = false;
		// #ifdef MP-WEIXIN
		if (typeof unsubscribeWxAppShow === 'function') {
			unsubscribeWxAppShow();
			unsubscribeWxAppShow = null;
		}
		// #endif
		if (foregroundReloginTimer) {
			clearTimeout(foregroundReloginTimer);
			foregroundReloginTimer = null;
		}
		uni.$off('TUICall:callStart', onVideoCallStart);
		uni.$off('TUICall:callEnd', onVideoCallEnd);
		stopDoctorWaitTimer();
		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();
		}
	});

	/** 打开会话时通知医生：发送IM自定义消息 + 调用后端全局事件 */
	async function notifyDoctorOnChatOpen() {
		if (!targetUserID || !targetUserID.startsWith('doctor_')) return;
		const doctorId = targetUserID.replace('doctor_', '');
		const userData = uni.getStorageSync('userData') || {};
		const patientName = userData.name || patient_data.value?.patient_name || '患者';
		const patientId = (currentUserID || '').replace('patient_', '') || '';
		try {
			// 1. 发送 IM 自定义消息给医生
			const customPayload = {
				businessID: 'patient_opened_chat',
				patientId,
				patientName,
				doctorId,
				time: Date.now()
			};
			const imMsg = chat.createCustomMessage({
				to: targetUserID,
				conversationType: TencentCloudChat.TYPES.CONV_C2C,
				payload: { data: JSON.stringify(customPayload) }
			});
			await chat.sendMessage(imMsg);
		} catch (e) {
			console.warn('IM 自定义消息发送失败:', e);
		}
		try {
			// 2. 调用后端 API 触发全局事件（供 admin 轮询）
			await proxy.apiUrl({
				url: '/api/chat/notifyOpen',
				method: 'POST',
				data: {
					doctor_id: doctorId,
					patient_id: patientId,
					patient_name: patientName
				}
			}, false);
		} catch (e) {
			console.warn('后端通知失败:', e);
		}
	}

	/** 离开会话页时通知医生：IM 自定义消息 + 后端全局事件 */
	async function notifyDoctorOnChatClose() {
		if (!targetUserID || !targetUserID.startsWith('doctor_')) return;
		const doctorId = targetUserID.replace('doctor_', '');
		const userData = uni.getStorageSync('userData') || {};
		const patientName = userData.name || patient_data.value?.patient_name || '患者';
		const patientId = (currentUserID || '').replace('patient_', '') || '';
		try {
			if (chat) {
				const customPayload = {
					businessID: 'patient_closed_chat',
					patientId,
					patientName,
					doctorId,
					time: Date.now()
				};
				const imMsg = chat.createCustomMessage({
					to: targetUserID,
					conversationType: TencentCloudChat.TYPES.CONV_C2C,
					payload: { data: JSON.stringify(customPayload) }
				});
				await chat.sendMessage(imMsg);
			}
		} catch (e) {
			console.warn('IM 离开通知发送失败:', e);
		}
		try {
			await proxy.apiUrl({
				url: '/api/chat/notifyClose',
				method: 'POST',
				data: {
					doctor_id: doctorId,
					patient_id: patientId,
					patient_name: patientName
				}
			}, false);
		} catch (e) {
			console.warn('后端离开通知失败:', e);
		}
	}
	
	function applyDoctorEnteredSignalFromRawMessage(msg) {
		if (!msg || msg.type !== TencentCloudChat.TYPES.MSG_CUSTOM) return;
		try {
			const d = JSON.parse(msg.payload.data);
			if (d.businessID === 'doctor_entered_consult_room' && msg.from !== currentUserID) {
				doctorEnteredConsultRoom.value = true;
			}
		} catch (_) {}
	}

	async function loadHistoryMessages() {
		try {
			const res = await chat.getMessageList({ conversationID, count: 15 });
			console.log('消息',conversationID);
			if (res.data && res.data.messageList) {
				for (const raw of res.data.messageList) {
					applyDoctorEnteredSignalFromRawMessage(raw);
				}
				// SDK返回的消息是从新到旧，reverse后变成从旧到新，最新消息在底部
				const parsedMessages = res.data.messageList
					.map(parseMessage)
					.filter(msg => msg !== null) // 过滤掉null消息
					
				messages.value = parsedMessages;
				console.log('加载历史消息:', parsedMessages.length, '条');
				scrollToBottom();
			}
		} catch (error) {
			console.error('加载历史消息失败:', error);
		}
	}
	
	function parseMessage(msg) {
		// 根据消息的 from 字段判断是发出还是接收
		const actualFlow = msg.from === currentUserID ? 'out' : 'in';
		
		// 获取头像：发出的消息使用当前用户头像，接收的消息从消息对象中获取
		let avatar = '';
		if (actualFlow === 'out') {
			avatar = userAvatar.value;
		} else {
			// 尝试从消息对象中获取对方的头像
			// 消息对象可能包含 fromUserProfile 或其他头像字段
			avatar = msg.fromUserProfile?.avatar || 
					msg.fromProfile?.avatar || 
					msg.senderProfile?.avatar || 
					msg.avatar || 
					'';
		}
		
		const baseMsg = { 
			time: msg.time * 1000, 
			flow: actualFlow,
			avatar: avatar
		};
		
		switch (msg.type) {
			case TencentCloudChat.TYPES.MSG_TEXT: {
				const text = msg.payload.text;
				if (isConfirmationMessageText(text)) {
					return { ...baseMsg, type: 'confirmation', text };
				}
				return { ...baseMsg, type: 'text', text };
			}
				
			case TencentCloudChat.TYPES.MSG_IMAGE:
				// 优先使用原图，如果没有则使用缩略图
				const imageUrl = msg.payload.imageInfoArray?.[0]?.url || 
								msg.payload.imageInfoArray?.[1]?.url || 
								msg.payload.imageInfoArray?.[2]?.url;
				return { ...baseMsg, type: 'image', imageUrl };
				
			case TencentCloudChat.TYPES.MSG_FILE:
				return { 
					...baseMsg, 
					type: 'file', 
					fileName: msg.payload.fileName, 
					fileSize: msg.payload.fileSize, 
					fileUrl: msg.payload.fileUrl 
				};
				
			case TencentCloudChat.TYPES.MSG_FACE:
				// 表情消息，data字段包含表情符号
				return { ...baseMsg, type: 'face', emoji: msg.payload.data };
				
			case TencentCloudChat.TYPES.MSG_CUSTOM:
				// 自定义消息（如通话消息）
				try {
					const customData = JSON.parse(msg.payload.data);
					console.log('自定义消息:', customData);

					// 过滤掉输入状态消息
					if (customData.businessID === 'user_typing_status') {
						console.log('过滤输入状态消息');
						return null;
					}
					// 过滤掉患者打开会话通知（仅用于医生端提醒，不在聊天列表展示）
					if (customData.businessID === 'patient_opened_chat') {
						return null; // 返回null表示不显示此消息
					}
					if (customData.businessID === 'patient_closed_chat') {
						return null;
					}
					// 医生进入诊室信令（admin 发送）：仅更新顶部卡片，不在聊天列表展示
					if (customData.businessID === 'doctor_entered_consult_room') {
						return null;
					}
					
					// 通话消息
					if (customData.businessID === 1 || customData.businessID === 'av_call') {
						const callType = customData.call_type === 1 ? '语音通话' : '视频通话';
						return { ...baseMsg, type: 'text', text: `[${callType}]` };
					}
					
					// 其他自定义消息，如果有text字段则显示
					if (customData.text) {
						const t = customData.text;
						if (isConfirmationMessageText(t)) {
							return { ...baseMsg, type: 'confirmation', text: t };
						}
						return { ...baseMsg, type: 'text', text: t };
					}
					
					// 没有可显示内容的自定义消息，不显示
					console.log('过滤无内容的自定义消息');
					return null;
				} catch (e) {
					console.error('解析自定义消息失败:', e);
					return null; // 解析失败的消息不显示
				}
				
			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: '[视频消息]' };
				
			case TencentCloudChat.TYPES.MSG_LOCATION:
				return { ...baseMsg, type: 'text', text: '[位置消息]' };
				
			default:
				console.log('未知消息类型:', msg.type);
				return null; // 未知类型不显示
		}
	}
	
	function onMessageReceived(event) {
		event.data.forEach(msg => {
			if (msg.conversationID === conversationID) {
				applyDoctorEnteredSignalFromRawMessage(msg);
				const parsedMsg = parseMessage(msg);
				// 只添加非null的消息
				if (parsedMsg !== null) {
					messages.value.push(parsedMsg);
					scrollToBottom();
					setTimeout(() => scrollToBottom(), 150);
				}
			}
		});
	}

	function imGetLoginCode(err) {
		const c = err?.code ?? err?.error?.code;
		if (c != null) return c;
		const msg = String(err?.message ?? err?.error?.message ?? '');
		const m = msg.match(/["']?code["']?\s*:\s*(\d+)/);
		return m ? parseInt(m[1], 10) : null;
	}

	/** 拉取最新 userSig 并重新登录 IM，必要时重建 chat 实例（与 onLoad 逻辑一致） */
	async function reloginIMChat() {
		const app = getApp();
		await video(currentUserID);
		const userSig = app.globalData.userSig;
		const SDKAppID = app.globalData.SDKAppID;
		const userID = currentUserID;
		if (!userSig || !SDKAppID) {
			throw new Error('IM 凭证缺失');
		}
		if (!chat) {
			throw new Error('IM 未初始化');
		}
		try {
			chat.off(TencentCloudChat.EVENT.MESSAGE_RECEIVED, onMessageReceived);
		} catch (_) {}
		try {
			await chat.logout();
		} catch (e) {
			console.warn('IM logout:', e);
		}
		const attachPlugins = (instance) => {
			instance.use(conversationPlugin);
			instance.use(messageEnhancerPlugin);
			instance.use(richMediaMessagePlugin);
			instance.use(groupPlugin);
		};
		const doLogin = async (instance) => instance.login({ userID, userSig });
		try {
			await doLogin(chat);
		} catch (loginErr) {
			const code = imGetLoginCode(loginErr);
			const msg = String(loginErr?.message ?? loginErr?.error?.message ?? '');
			const isDuplicateLogin = code === 2025 || msg.includes('重复登录');
			if (!isDuplicateLogin) throw loginErr;
			try {
				await chat.destroy?.();
			} catch (_) {}
			chat = TencentCloudChat.create({
				SDKAppID: Number(SDKAppID),
				scene: 'uniapp-wx'
			});
			attachPlugins(chat);
			await doLogin(chat);
		}
		app.globalData.imChat = chat;
		app.globalData.imChatUserID = userID;
		chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED, onMessageReceived);
		await initTUICallKitWithTim(chat);
	}

	/** 小程序切后台再回前台：拉取新 UserSig 并重登 IM、重绑 TUICallEngine（与发消息失败时的 relogin 一致） */
	async function reloginAfterMiniProgramForeground() {
		if (!chat || !currentUserID) return;
		if (foregroundReloginTimer) clearTimeout(foregroundReloginTimer);
		foregroundReloginTimer = setTimeout(async () => {
			foregroundReloginTimer = null;
			// reloginIMChat 会先 IM logout 再 initTUICallKit；若非 IDLE，destroyed 会失败且 IM 已断开，故仅 IDLE 时刷新
			try {
				const status = TUIStore?.getData?.(StoreName.CALL, NAME.CALL_STATUS);
				if (status != null && status !== CallStatus.IDLE) {
					console.warn('前台恢复: 通话未结束，跳过 IM/TUICall 重绑');
					return;
				}
				await reloginIMChat();
			} catch (e) {
				console.warn('前台恢复后 IM/TUICall 刷新失败:', e);
			}
		}, 400);
	}
	
	async function sendTextMessage() {
		if (!inputText.value.trim()) return;
		const text = inputText.value.trim();
		const pushSentAndClear = () => {
			messages.value.push({
				type: isConfirmationMessageText(text) ? 'confirmation' : 'text',
				text,
				time: Date.now(),
				flow: 'out',
				avatar: userAvatar.value
			});
			inputText.value = '';
			uni.hideKeyboard();
			scrollToBottom();
			setTimeout(() => scrollToBottom(), 150);
		};
		const doSend = async () => {
			const message = chat.createTextMessage({
				to: targetUserID,
				conversationType: TencentCloudChat.TYPES.CONV_C2C,
				payload: { text }
			});
			await chat.sendMessage(message);
		};
		try {
			await doSend();
			pushSentAndClear();
		} catch (error) {
			console.error('发送消息失败:', error);
			try {
				uni.showLoading({ title: '重新登录中', mask: true });
				await reloginIMChat();
				await doSend();
				pushSentAndClear();
			} catch (e2) {
				console.error('重登后仍发送失败:', e2);
				uni.showToast({ title: '发送失败', icon: 'none' });
			} finally {
				uni.hideLoading();
			}
		}
	}
	
	/** 等待 IM SDK 网络握手完成，给 SDK 一段时间确保就绪后再发消息 */
	function waitForIMReady() {
		// 使用固定延迟代替事件监听：SDK_READY 事件可能在注册前已触发，
		// 事件监听会等满 maxMs 超时，在微信小程序中引发 "Error: timeout"。
		return new Promise((resolve) => setTimeout(resolve, 800));
	}

	async function sendConfirmationMessage() {
		const alreadySent = messages.value.some(
			(m) => m.type === 'confirmation' || (m.type === 'text' && isConfirmationMessageText(m.text))
		);
		if (alreadySent) return;

		const pushSent = () => {
			// 防止重复 push（relogin 后重试时再次检查）
			const already = messages.value.some(
				(m) => m.type === 'confirmation' || (m.type === 'text' && isConfirmationMessageText(m.text))
			);
			if (already) return;
			messages.value.push({
				type: 'confirmation',
				text: CONFIRMATION_MESSAGE,
				time: Date.now(),
				flow: 'out',
				avatar: userAvatar.value
			});
			scrollToBottom();
			setTimeout(() => scrollToBottom(), 150);
		};
		const doSend = async () => {
			const message = chat.createTextMessage({
				to: targetUserID,
				conversationType: TencentCloudChat.TYPES.CONV_C2C,
				payload: { text: CONFIRMATION_MESSAGE }
			});
			await chat.sendMessage(message);
		};
		try {
			await doSend();
			pushSent();
		} catch (error) {
			console.error('发送确认信息失败，尝试重新登录后重试:', error);
			try {
				await reloginIMChat();
				await doSend();
				pushSent();
			} catch (e2) {
				console.error('重登后确认信息仍发送失败:', e2);
			}
		}
	}

	function chooseImage() {
		// #ifdef MP-WEIXIN
		uni.chooseMedia({
			count: 1,
			mediaType: ['image'],
			sizeType: ['original', 'compressed'],
			sourceType: ['album', 'camera'],
			success: (res) => {
				console.log('选择图片成功:', res);
				sendImageMessage(res);
			},
			fail: (err) => {
				console.error('选择图片失败:', err);
				uni.showToast({ title: '选择图片失败', icon: 'none' });
			}
		});
		// #endif
		// #ifndef MP-WEIXIN
		uni.chooseImage({
			count: 1,
			sizeType: ['original', 'compressed'],
			sourceType: ['album', 'camera'],
			success: (res) => {
				console.log('选择图片成功:', res);
				sendImageMessage(res);
			},
			fail: (err) => {
				console.error('选择图片失败:', err);
				uni.showToast({ title: '选择图片失败', icon: 'none' });
			}
		});
		// #endif
	}
	
	async function sendImageMessage(res) {
		try {
			console.log('开始发送图片，res对象:', res);
			uni.showLoading({ title: '发送中...' });
			
			// 根据文档，直接传入 uni.chooseMedia 或 uni.chooseImage 的 success 回调参数
			const message = chat.createImageMessage({
				to: targetUserID,
				conversationType: TencentCloudChat.TYPES.CONV_C2C,
				payload: { 
					file: res  // 直接传入整个res对象
				},
				onProgress: (event) => {
					console.log('图片上传进度:', event);
				}
			});
			
			console.log('图片消息创建成功，开始发送');
			const sendResult = await chat.sendMessage(message);
			console.log('图片发送成功:', sendResult);
			
			// 从发送结果中获取图片URL
			const imageUrl = sendResult.data.message.payload.imageInfoArray?.[0]?.url || res.tempFilePaths?.[0] || res.tempFiles?.[0]?.tempFilePath;
			messages.value.push({ type: 'image', imageUrl, 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: '发送失败: ' + (error.message || '未知错误'), icon: 'none', duration: 3000 });
		}
	}
	
	function chooseFile() {
		// #ifdef MP-WEIXIN
		wx.chooseMessageFile({
			count: 1,
			type: 'all',
			success: (res) => {
				console.log('选择文件成功:', res);
				sendFileMessage(res);
			},
			fail: (err) => {
				console.error('选择文件失败:', err);
				uni.showToast({ title: '选择文件失败', icon: 'none' });
			}
		});
		// #endif
		// #ifndef MP-WEIXIN
		uni.showToast({ title: '当前平台不支持', icon: 'none' });
		// #endif
	}
	
	async function sendFileMessage(res) {
		try {
			console.log('开始发送文件，res对象:', res);
			uni.showLoading({ title: '发送中...' });
			
			// 根据文档，直接传入 wx.chooseMessageFile 的 success 回调参数
			const message = chat.createFileMessage({
				to: targetUserID,
				conversationType: TencentCloudChat.TYPES.CONV_C2C,
				payload: { 
					file: res  // 直接传入整个res对象
				},
				onProgress: (event) => {
					console.log('文件上传进度:', event);
				}
			});
			
			console.log('文件消息创建成功，开始发送');
			const sendResult = await chat.sendMessage(message);
			console.log('文件发送成功:', sendResult);
			
			const file = res.tempFiles?.[0] || {};
			messages.value.push({ 
				type: 'file', 
				fileName: file.name || '文件', 
				fileSize: file.size || 0, 
				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: '发送失败: ' + (error.message || '未知错误'), icon: 'none', duration: 3000 });
		}
	}

	function showEmojiPicker() {
		showEmoji.value = !showEmoji.value;
	}
	
	async function sendEmoji(emoji) {
		try {
			const message = chat.createFaceMessage({
				to: targetUserID,
				conversationType: TencentCloudChat.TYPES.CONV_C2C,
				payload: { index: 0, data: emoji }
			});
			await chat.sendMessage(message);
			messages.value.push({ type: 'face', emoji, time: Date.now(), flow: 'out', avatar: userAvatar.value });
			showEmoji.value = false;
			scrollToBottom();
			setTimeout(() => scrollToBottom(), 150);
		} catch (error) {
			console.error('发送表情失败:', error);
			uni.showToast({ title: '发送失败', icon: 'none' });
		}
	}
	
	function previewImage(url) {
		uni.previewImage({ urls: [url], current: url });
	}
	
	function downloadFile(msg) {
		uni.showModal({
			title: '下载文件',
			content: `是否下载文件：${msg.fileName}？`,
			success: (res) => {
				if (res.confirm) {
					uni.downloadFile({
						url: msg.fileUrl,
						success: (res) => {
							if (res.statusCode === 200) uni.showToast({ title: '下载成功', icon: 'success' });
						}
					});
				}
			}
		});
	}
	
	/**
	 * 申请摄像头 + 麦克风权限。
	 * silent=true 时仅静默尝试，不弹引导框（用于页面加载时提前申请）。
	 * silent=false 时若被拒绝主动引导用户去设置页开启。
	 * scene 标识触发场景（makeCall / incoming），用于日志分析。
	 */
	// #ifdef MP-WEIXIN
	function requestAVPermissions({ silent = false, scene = 'unknown' } = {}) {
		/** 上报权限被拒日志到后端 */
		const logDenied = (deniedScope, action) => {
			try {
				const wxVersion = wx.getSystemInfoSync?.()?.SDKVersion || '';
				proxy.apiUrl({
					url: '/api/chat/logAvPermission',
					method: 'POST',
					data: {
						patient_id: currentUserID || '',
						doctor_id:  targetUserID   || '',
						denied_scope: deniedScope,
						scene,
						action,
						wx_version: wxVersion
					}
				}, false).catch(() => {});
			} catch (_) {}
		};

		const guideToSetting = (deniedScope) => {
			if (silent) {
				// 静默模式：仅记录被拒，不弹弹窗
				logDenied(deniedScope, 'silent_denied');
				return;
			}
			uni.showModal({
				title: '需要摄像头和麦克风权限',
				content: '视频通话需要摄像头和麦克风权限，请点击「去设置」开启后重试',
				confirmText: '去设置',
				cancelText: '取消',
				success: (res) => {
					if (res.confirm) {
						logDenied(deniedScope, 'open_setting');
						wx.openSetting();
					} else {
						// 用户选择「取消」，记录日志
						logDenied(deniedScope, 'cancel');
					}
				}
			});
		};
		return new Promise((resolve) => {
			wx.authorize({
				scope: 'scope.camera',
				success: () => {
					wx.authorize({
						scope: 'scope.record',
						success: () => resolve(true),
						fail: () => { guideToSetting('record'); resolve(false); }
					});
				},
				fail: () => { guideToSetting('camera'); resolve(false); }
			});
		});
	}
	// #endif
	// #ifndef MP-WEIXIN
	function requestAVPermissions() { return Promise.resolve(true); }
	// #endif

	async function makeVideoCall() {
		// 发起通话前先检查权限，若被拒引导去设置
		const granted = await requestAVPermissions({ silent: false, scene: 'makeCall' });
		if (!granted) return;
		try {
			hideDoctorCardByCall();
			if (!uni.CallManager) {
				uni.showToast({ title: '通话功能未初始化', icon: 'none' });
				return;
			}
			await uni.CallManager.call({ userID: targetUserID, type: 2 });
		} catch (error) {
			console.error('发起通话失败:', error);
			uni.showToast({ title: '发起通话失败', icon: 'none' });
		}
	}
	
	function startVideoCall() {
		makeVideoCall();
	}
	
	async function loadDoctorInfo() {
		if (!targetUserID || !targetUserID.startsWith('doctor_')) return;
		const doctorId = targetUserID.replace('doctor_', '');
		try {
			const res = await proxy.apiUrl({
				url: '/api/doctor/detail',
				method: 'GET',
				data: { id: doctorId }
			}, false);
			if (res && res.code === 1 && res.data) {
			doctorInfo.value = res.data;
			startDoctorWaitTimer();
		}
		} catch (e) {
			console.warn('加载医生信息失败:', e);
		}
	}
	
	function formatDoctorWaitTime(seconds) {
		const m = Math.floor(seconds / 60);
		const s = seconds % 60;
		return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
	}
	
	function startDoctorWaitTimer() {
		stopDoctorWaitTimer();
		doctorWaitSeconds.value = 0;
		doctorWaitTimer = setInterval(() => {
			doctorWaitSeconds.value++;
		}, 1000);
	}
	
	function stopDoctorWaitTimer() {
		if (doctorWaitTimer) {
			clearInterval(doctorWaitTimer);
			doctorWaitTimer = null;
		}
	}
	
	function goDoctorDetail() {
		if (!doctorInfo.value?.id) return;
		uni.navigateTo({ url: `/doctor/pages/doctor/doctor?id=${doctorInfo.value.id}` });
	}
	
	function hideDoctorCardByCall() {
		showDoctorCard.value = false;
		stopDoctorWaitTimer();
	}
	
	function onVideoCallStart() {
		hideDoctorCardByCall();
	}
	
	async function onVideoCallEnd() {
		hideDoctorCardByCall();
		uni.showToast({ title: '视频通话已结束', icon: 'success' });
	}
	
	
	function onInputFocus() {
		console.log('输入框获得焦点');
		showEmoji.value = false;
	}
	
	function onInputBlur() {
		console.log('输入框失去焦点');
	}
	
	function onScroll(e) {
		// 监听滚动事件，可用于判断是否在底部
	}
	
	function scrollToBottom() {
		if (scrollTimer) {
			clearTimeout(scrollTimer);
		}
		
		nextTick(() => {
			// 使用查询节点的方式获取准确高度
			const query = uni.createSelectorQuery();
			query.select('.message-container').boundingClientRect();
			query.exec((res) => {
				if (res && res[0]) {
					const height = res[0].height;
					console.log('消息容器高度:', height);
					// 设置一个足够大的值确保滚动到底部
					scrollTop.value = height + 1000;
					
					// 再次确认滚动
					scrollTimer = setTimeout(() => {
						scrollTop.value = height + 1000 + 1;
					}, 100);
				}
			});
		});
	}
	
	function goBack() {
		uni.navigateBack();
	}
	
	function formatTime(timestamp) {
		const date = new Date(timestamp);
		return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
	}
	
	function formatFileSize(bytes) {
		if (bytes < 1024) return bytes + ' B';
		if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
		return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
	}
</script>

<style scoped>
/* 适老化设计：50-80岁 - 大字号、高对比、大触控区 */
.chat-container {
	display: flex;
	flex-direction: column;
	width: 100vw;
	height: 100vh;
	background: #e8eaed;
}

.chat-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	height: 100rpx;
	padding: 0 28rpx;
	background: #fff;
	border-bottom: 4rpx solid #e0e0e0;
}

.header-left {
	width: 100rpx;
	height: 100rpx;
	display: flex;
	align-items: center;
	justify-content: flex-start;
}

.back-icon {
	font-size: 52rpx;
	color: #1890ff;
	font-weight: 500;
}

.header-center {
	flex: 1;
	display: flex;
	flex-direction: column;
	align-items: center;
}

.header-title {
	font-size: 40rpx;
	font-weight: 500;
	color: #1a1a1a;
}

.header-status {
	font-size: 28rpx;
	color: #555;
	margin-top: 4rpx;
}

.header-right {
	width: 100rpx;
	display: flex;
	justify-content: flex-end;
}

.video-call-btn {
	width: 88rpx;
	height: 88rpx;
	display: flex;
	align-items: center;
	justify-content: center;
}

.video-icon {
	font-size: 52rpx;
	color: #1890ff;
}

.message-list {
	flex: 1;
	overflow: hidden;
}

.message-container {
	padding: 28rpx 22rpx;
	padding-bottom: 320rpx;
	min-height: 100%;
}

/* 医生卡片：问诊场景、层次清晰 */
.doctor-card {
	display: flex;
	flex-direction: column;
	margin-bottom: 32rpx;
	border-radius: 20rpx;
	overflow: hidden;
	background: #fff;
	box-shadow:
		0 2rpx 8rpx rgba(15, 23, 42, 0.04),
		0 12rpx 40rpx rgba(13, 148, 136, 0.08);
	border: 1rpx solid rgba(13, 148, 136, 0.12);
	position: relative;
	transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.doctor-card:active {
	transform: scale(0.992);
	box-shadow:
		0 2rpx 6rpx rgba(15, 23, 42, 0.06),
		0 8rpx 28rpx rgba(13, 148, 136, 0.1);
}
.doctor-card::before {
	content: '';
	position: absolute;
	left: 0;
	top: 0;
	right: 0;
	height: 6rpx;
	background: linear-gradient(90deg, #0d9488 0%, #14b8a6 45%, #5eead4 100%);
}
.doctor-card-topbar {
	display: flex;
	flex-direction: row;
	align-items: center;
	justify-content: space-between;
	padding: 22rpx 28rpx 12rpx;
	padding-top: 28rpx;
}
.doctor-card-label {
	font-size: 24rpx;
	font-weight: 600;
	color: #64748b;
	letter-spacing: 2rpx;
}
.doctor-card-status-pill {
	display: flex;
	flex-direction: row;
	align-items: center;
	gap: 10rpx;
	padding: 8rpx 18rpx;
	border-radius: 999rpx;
	background: linear-gradient(135deg, #fff7ed 0%, #ffedd5 100%);
	border: 1rpx solid rgba(251, 146, 60, 0.35);
}
.doctor-card-status-pulse {
	width: 12rpx;
	height: 12rpx;
	border-radius: 50%;
	background: #ea580c;
	box-shadow: 0 0 0 0 rgba(234, 88, 12, 0.45);
	animation: doctorWaitPulse 1.8s ease-out infinite;
}
@keyframes doctorWaitPulse {
	0% {
		box-shadow: 0 0 0 0 rgba(234, 88, 12, 0.5);
	}
	70% {
		box-shadow: 0 0 0 10rpx rgba(234, 88, 12, 0);
	}
	100% {
		box-shadow: 0 0 0 0 rgba(234, 88, 12, 0);
	}
}
.doctor-card-status-text {
	font-size: 22rpx;
	font-weight: 600;
	color: #c2410c;
	letter-spacing: 0.5rpx;
}
.doctor-card-status-pill--entered {
	background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%);
	border: 1rpx solid rgba(16, 185, 129, 0.45);
}
.doctor-card-status-pill--entered .doctor-card-status-text {
	color: #047857;
}
.doctor-card-status-pulse--entered {
	background: #10b981;
	box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.45);
	animation: doctorEnteredPulse 1.6s ease-out infinite;
}
@keyframes doctorEnteredPulse {
	0% {
		box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.55);
	}
	70% {
		box-shadow: 0 0 0 10rpx rgba(16, 185, 129, 0);
	}
	100% {
		box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
	}
}
.doctor-card-main {
	display: flex;
	flex-direction: row;
	align-items: center;
	padding: 8rpx 24rpx 28rpx;
	gap: 20rpx;
}
.doctor-card-avatar-wrap {
	flex-shrink: 0;
}
.doctor-card-avatar-ring {
	width: 112rpx;
	height: 112rpx;
	padding: 4rpx;
	border-radius: 50%;
	background: linear-gradient(145deg, #ccfbf1 0%, #99f6e4 50%, #5eead4 100%);
	box-shadow: inset 0 1rpx 2rpx rgba(255, 255, 255, 0.8);
}
.doctor-card-avatar {
	width: 104rpx;
	height: 104rpx;
	border-radius: 50%;
	display: block;
	background: #e2e8f0;
	border: 4rpx solid #fff;
	box-sizing: border-box;
}
.doctor-card-info {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 10rpx;
}
.doctor-card-name {
	font-size: 36rpx;
	font-weight: 700;
	color: #0f172a;
	letter-spacing: 0.5rpx;
	line-height: 1.3;
}
.doctor-card-meta {
	display: flex;
	flex-direction: row;
	flex-wrap: wrap;
	align-items: center;
	gap: 6rpx;
}
.doctor-card-title {
	font-size: 24rpx;
	color: #0d9488;
	font-weight: 500;
}
.doctor-card-dot {
	font-size: 24rpx;
	color: #cbd5e1;
}
.doctor-card-hospital {
	font-size: 24rpx;
	color: #64748b;
	max-width: 100%;
}
.doctor-card-chevron {
	flex-shrink: 0;
	width: 48rpx;
	height: 48rpx;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: 50%;
	background: #f1f5f9;
}
.doctor-card-chevron-icon {
	font-size: 40rpx;
	line-height: 1;
	color: #94a3b8;
	font-weight: 300;
}
.doctor-card-tip {
	display: flex;
	flex-direction: row;
	align-items: stretch;
	background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
	border-top: 1rpx solid rgba(148, 163, 184, 0.2);
}
.doctor-card-tip-bar {
	width: 6rpx;
	flex-shrink: 0;
	background: linear-gradient(180deg, #14b8a6 0%, #0d9488 100%);
}
.doctor-card-tip-inner {
	flex: 1;
	padding: 20rpx 24rpx 22rpx 20rpx;
	display: flex;
	flex-direction: column;
	gap: 8rpx;
}
.doctor-card-tip-title {
	font-size: 22rpx;
	font-weight: 600;
	color: #475569;
	letter-spacing: 1rpx;
}
.doctor-card-tip-text {
	font-size: 26rpx;
	color: #64748b;
	line-height: 1.55;
}

.message-wrapper {
	display: flex;
	margin-bottom: 32rpx;
	animation: fadeIn 0.25s ease;
}

@keyframes fadeIn {
	from { opacity: 0; transform: translateY(10rpx); }
	to { opacity: 1; transform: translateY(0); }
}

.message-wrapper-in {
	flex-direction: row;
}

.message-wrapper-out {
	flex-direction: row-reverse;
}

/* 头像 - 更大 */
.avatar {
	width:80rpx;
	height: 80rpx;
	border-radius: 12rpx;
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	font-weight: 500;
	
}

.avatar-in {
	
	margin-right: 20rpx;
}

.avatar-out {
	
	margin-left: 10rpx;
}

.avatar-text {
	font-size: 36rpx;
	color: #fff;
}

.avatar-image {
	width: 100%;
	height: 100%;
	border-radius: 12rpx;
}

/* 消息气泡 - 更大内边距 */
.message-bubble {
	max-width: 540rpx;
	padding: 28rpx 32rpx;
	border-radius: 16rpx;
	position: relative;
}

.bubble-in {
	background: #fff;
}

.bubble-out {
	background: #95ec69;
}

/* 知情确认：全宽略增、与绿气泡区分 */
.message-wrapper-confirmation .message-bubble {
	max-width: 620rpx;
}
.bubble-confirmation.bubble-out {
	background: linear-gradient(165deg, #f0faf4 0%, #e3f2e8 55%, #d8ebe3 100%);
	border: 1rpx solid rgba(46, 139, 87, 0.28);
	box-shadow: 0 4rpx 20rpx rgba(34, 100, 70, 0.08);
}
.bubble-confirmation.bubble-in {
	background: linear-gradient(165deg, #f7fbff 0%, #eef6fc 100%);
	border: 1rpx solid rgba(87, 107, 149, 0.22);
	box-shadow: 0 4rpx 20rpx rgba(50, 80, 120, 0.06);
}

.confirmation-card {
	width: 100%;
}
.confirmation-card-head {
	display: flex;
	flex-direction: row;
	align-items: center;
	gap: 20rpx;
}
.confirmation-card-badge {
	width: 56rpx;
	height: 56rpx;
	border-radius: 50%;
	background: linear-gradient(135deg, #2e8b57 0%, #3cb371 100%);
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	box-shadow: 0 2rpx 8rpx rgba(46, 139, 87, 0.35);
}
.confirmation-card-badge-icon {
	font-size: 28rpx;
	color: #fff;
	font-weight: 700;
	line-height: 1;
}
.confirmation-card-head-text {
	flex: 1;
	display: flex;
	flex-direction: column;
	gap: 4rpx;
}
.confirmation-card-title {
	font-size: 32rpx;
	font-weight: 600;
	color: #1a4d2e;
	letter-spacing: 0.5rpx;
}
.bubble-in .confirmation-card-title {
	color: #1a3a5c;
}
.confirmation-card-sub {
	font-size: 20rpx;
	color: rgba(26, 77, 46, 0.45);
	text-transform: uppercase;
	letter-spacing: 1rpx;
}
.bubble-in .confirmation-card-sub {
	color: rgba(26, 58, 92, 0.4);
}
.confirmation-card-divider {
	height: 1rpx;
	background: linear-gradient(90deg, transparent, rgba(46, 139, 87, 0.25), transparent);
	margin: 20rpx 0 16rpx;
}
.bubble-in .confirmation-card-divider {
	background: linear-gradient(90deg, transparent, rgba(87, 107, 149, 0.22), transparent);
}
.confirmation-card-lines {
	display: flex;
	flex-direction: column;
	gap: 14rpx;
}
.confirmation-line {
	font-size: 28rpx;
	line-height: 1.65;
	color: #2d4a3a;
	display: block;
	padding-left: 4rpx;
}
.bubble-in .confirmation-line {
	color: #2c3d52;
}

/* 文本消息 - 大字号 */
.message-text {
	font-size: 30rpx;
	line-height: 1.6;
	color: #1a1a1a;
	word-break: break-all;
}

.bubble-out .message-text {
	color: #1a1a1a;
}

.message-image {
	max-width: 100%;
	border-radius: 12rpx;
	display: block;
}

/* 文件消息 - 大触控区 */
.message-file {
	display: flex;
	align-items: center;
	gap: 24rpx;
	padding: 20rpx;
	background: rgba(0, 0, 0, 0.05);
	border-radius: 12rpx;
}

.bubble-out .message-file {
	background: rgba(0, 0, 0, 0.08);
}

.file-icon-wrapper {
	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.2);
}

.file-icon {
	font-size: 48rpx;
}

.file-details {
	flex: 1;
	display: flex;
	flex-direction: column;
	gap: 12rpx;
}

.file-name {
	font-size: 34rpx;
	color: #1a1a1a;
	font-weight: 500;
}

.bubble-out .file-name {
	color: #1a1a1a;
}

.file-size {
	font-size: 28rpx;
	color: #555;
}

.bubble-out .file-size {
	color: #555;
}

.download-icon {
	font-size: 36rpx;
	color: #1890ff;
}

.bubble-out .download-icon {
	color: #1a1a1a;
}

.message-emoji {
	font-size: 112rpx;
	line-height: 1;
}

.message-meta {
	margin-top: 12rpx;
	display: flex;
	align-items: center;
	justify-content: flex-end;
}

.message-time {
	font-size: 26rpx;
	color: #666;
}

.bubble-out .message-time {
	color: #555;
}

/* 空状态 */
.empty-state {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	height: 600rpx;
	gap: 24rpx;
}

.empty-icon {
	font-size: 120rpx;
	opacity: 0.3;
}

.empty-text {
	font-size: 38rpx;
	color: #555;
	font-weight: 500;
}

.empty-hint {
	font-size: 32rpx;
	color: #888;
}

.bottom-placeholder {
	height: 56rpx;
}

/* 输入区域 - 大触控区 */
.input-container {
	position: fixed;
	bottom: 0;
	left: 0;
	right: 0;
	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: 48rpx;
	margin-bottom: 24rpx;
	padding: 0 12rpx;
}

.tool-item {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 12rpx;
	min-height: 100rpx;
}

.tool-item-left {
	margin-right: 16rpx;
}

.tool-icon {
	font-size: 56rpx;
}

.tool-label {
	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: 20rpx;
	margin-bottom: 56rpx;
}

.message-input {
	flex: 1;
	height: 96rpx;
	padding: 0 32rpx;
	background: #fff;
	border-radius: 16rpx;
	border: 4rpx solid #e0e0e0;
	font-size: 36rpx;
	color: #1a1a1a;
}

.send-button {
	width: 140rpx;
	height: 96rpx;
	background: #e0e0e0;
	border-radius: 16rpx;
	display: flex;
	align-items: center;
	justify-content: center;
	transition: all 0.2s ease;
}

.send-button-active {
	background: #07c160;
}

.send-text {
	font-size: 36rpx;
	color: #888;
	font-weight: 500;
}

.send-button-active .send-text {
	color: #fff;
}

.emoji-panel {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	background: rgba(0, 0, 0, 0.4);
	display: flex;
	align-items: flex-end;
	z-index: 1000;
	animation: fadeIn 0.25s ease;
}

.emoji-content {
	width: 100%;
	max-height: 65vh;
	background: #e8eaed;
	border-radius: 28rpx 28rpx 0 0;
	animation: slideUp 0.25s ease;
}

@keyframes slideUp {
	from { transform: translateY(100%); }
	to { transform: translateY(0); }
}

.emoji-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 36rpx 36rpx 28rpx;
	border-bottom: 4rpx solid #e0e0e0;
	background: #fff;
}

.emoji-title {
	font-size: 38rpx;
	font-weight: 500;
	color: #1a1a1a;
}

.emoji-close {
	width: 72rpx;
	height: 72rpx;
	background: #f0f0f0;
	border-radius: 50%;
	display: flex;
	align-items: center;
	justify-content: center;
}

.close-icon {
	font-size: 36rpx;
	color: #555;
}

.emoji-scroll {
	max-height: 55vh;
	padding: 28rpx;
	background: #e8eaed;
}

.emoji-grid {
	display: flex;
	flex-wrap: wrap;
	gap: 16rpx;
}

.emoji-cell {
	width: 112rpx;
	height: 112rpx;
	background: #fff;
	border-radius: 16rpx;
	display: flex;
	align-items: center;
	justify-content: center;
	transition: all 0.15s ease;
}

.emoji-cell:active {
	transform: scale(0.92);
	background: #f0f0f0;
}

.emoji-icon {
	font-size: 60rpx;
}
</style>
