<template>
    <view>
        <!-- pages/treatment/treatment.wxml -->
        <nav-bar :navbarData="navbarData"></nav-bar>
        <!-- 图文接诊：右上角"结束就诊"按钮 -->
        <view
            v-if="admType == 'graphic' && !record && !fromMessage && patientData.statusCode != 'F'"
            class="nav-end-visit"
            :style="'height: ' + (height * 2 + 80) + 'rpx;'"
            @tap="endVisit"
        >
            <text class="nav-end-visit-text">结束就诊</text>
        </view>
        <view class="top-toast" v-if="topToast.show">{{ topToast.text }}</view>
        <view :class="'treatment ' + (admType == 'graphic' ? 'treatment-graphic' : '')" :style="'height: calc(100vh - ' + (height * 2 + 80) + 'rpx)'">
            <canvas canvas-id="canvas" :style="'width:' + cWidth + 'px;height:' + cHeight + 'px;position: absolute;left:-1000px;top:-1000px;'"></canvas>
            <!-- <view class="top-section">
                <view :class="tipsClose == 'N' && admType == 'video' ? 'tips' : 'tipsClose'">
                    <image :src="imgDomain + '/images/icon/doctor/tips.png'" class="tipsimg"></image>
                    <view class="tipstext">视频时间不超过5分钟, 加时请点击"延时"</view>
                    <image :src="imgDomain + '/images/icon/doctor/closeimg.png'" class="closeimg"></image>
                </view>
                <view class="row topBtn" v-if="admType == 'video' && !record">
                    <view class="col-4 button" @tap="allowToJoin">
                        <image :src="imgDomain + '/images/icon/doctor/vedio.png'" class="tipsimg"></image>
                        <view style="padding-left: 60rpx">{{ clickButton ? '正在打开视频' : '发起视频' }}</view>
                    </view> -->
                    <!-- 邀请医生功能暂时下线 -->
                    <!-- <view class="col-4 button" @tap="inviteDoc">
                        <image :src="imgDomain + '/images/icon/doctor/inviteDoc.png'" class="tipsimg"></image>
                        <view style="padding-left: 60rpx">邀请医生</view>
                    </view> -->
                    <!-- <view class="col-4 button" @tap="finish" v-if="patientData.statusCode != 'F'">
                        <image :src="imgDomain + '/images/icon/doctor/finishservice.png'" class="tipsimg"></image>
                        <view style="padding-left: 60rpx">结束服务</view>
                    </view> -->
                    <!-- <view class="col-3" bindtap="inviteDoctor" wx:if="{{admType=='video'}}">
        <mp-icon type="outline" icon="me" color="#FE9400" size="{{23}}"></mp-icon>
        邀请医生
      </view>
      <view class="col-3" bindtap="delayed" wx:if="{{admType=='video'}}">
        <mp-icon type="outline" icon="copy" color="#FE9400" size="{{23}}"></mp-icon>
        延时
      </view> -->
                <!-- </view>
                <view class="row topBtn record" v-if="record">
                    <view class="col-4" @tap="cancelFinish" v-if="patientData.statusCode == 'F'">
                        <image :src="imgDomain + '/images/icon/doctor/finishservice.png'" class="tipsimg"></image>
                        <view style="padding-left: 60rpx">取消结束</view>
                    </view>
                </view>
                <view class="row topBtn graphic-topBtn" v-if="admType == 'graphic'">
                    <view class="graphic-btn graphic-btn-finish" @tap="finish">
                        <image :src="imgDomain + '/images/icon/doctor/finishservice.png'" class="tipsimg"></image>
                        结束服务
                    </view>
                </view>
            </view> -->
            <view :class="body" :style="bodyStyle">
                <view class="chartRoom">
                    <view class="chatContent">
                        <scroll-view
                            id="body"
                            class="chatList"
                            :scroll-y="true"
                            :scroll-with-animation="scrollWithAnimation"
                            :scroll-top="scrollTop"
                            :scroll-into-view="scrollToMessage"
                            @scrolltoupper="onScrollToUpper"
                        >
                            <view id="viewHight">
                                <view class="mw-content mw-message" v-if="!help && !fromMessage">
                                    <view class="col-1" style="display: block">
                                        <image v-if="patientData.patImage && !patImageError" :src="getFullImageUrl(patientData.patImage)" @error="onImageError('patImage')"></image>
                                        <text v-else class="avatar-fallback">{{ getAvatarText(patientData.patName) }}</text>
                                    </view>
                                    <view class="text patState">
                                        <view class="message-content message-received background-color-fff" style="display: block !important">
                                            {{ patientData.symptom }}
                                        </view>
                                        <view class="row lookDetail" @tap="lookDetail">
                                           查看详情
                                        </view>
                                    </view>
                                </view>
                                <view
                                    :class="
                                        item.msgType == '1' && item.direction == 'right'
                                            ? 'mw-content mw-content-right mw-message'
                                            : item.msgType == '1' && item.direction == 'left'
                                            ? 'mw-content mw-message'
                                            : 'mw-message mw-content-content'
                                    "
                                    :data-index="index"
                                    @tap.stop.prevent="viewOriginal"
                                    v-for="(item, index) in messageList"
                                    :key="index"
                                >
                                    <view class="col-1" :style="'display: ' + (item.msgType === '1' ? 'block' : 'none')">
                                        <image v-if="help" :src="getFullImageUrl(imgDomain + '/images/icon/doctor/helper.png')"></image>
                                        <image v-else-if="item.direction === 'left' && patientData.patImage && !patImageError" :src="getFullImageUrl(patientData.patImage)" @error="onImageError('patImage')"></image>
                                        <text v-else-if="item.direction === 'left'" class="avatar-fallback">{{ getAvatarText(patientData.patName) }}</text>
                                        <image v-else-if="userData.image && !userImageError" :src="getFullImageUrl(userData.image)" @error="onImageError('userImage')"></image>
                                        <text v-else class="avatar-fallback">{{ getAvatarText(userData.userName) }}</text>
                                    </view>

                                    <view class="text" :style="'display: ' + (item.msgType === '1' ? 'block' : 'none')">
                                        <view
                                            @longpress="longClick"
                                            :data-item="item"
                                            :class="
                                                item.direction === 'left' && item.picture
                                                    ? 'message-content message-received background-color-fff'
                                                    : item.direction === 'right' && item.picture
                                                    ? 'message-content message-send background-color-fff'
                                                    : item.direction === 'left'
                                                    ? 'message-content message-received'
                                                    : 'message-content message-send'
                                            "
                                        >
                                            <text v-if="item.consultText ? true : false">{{ item.consultText }}</text>
                                            <image :data-picture="item.msgID" class="image" :src="item.picture" alt="" v-if="item.picture ? true : false" mode="widthFix"></image>
                                            <view v-if="item.pictureLoading" class="image img-loading">
                                                <van-loading size="24px" type="spinner" vertical>图片加载中...</van-loading>
                                            </view>
                                            <view v-if="item.videoLoading" class="image img-loading">
                                                <van-loading size="24px" type="spinner" vertical>视频上传中...</van-loading>
                                            </view>
                                            <view v-if="item.soundLoading" class="image img-loading">
                                                <van-loading size="24px" type="spinner" vertical>语音发送中...</van-loading>
                                            </view>
                                            <!-- 语音消息气泡 -->
                                            <view
                                                v-if="(item.sound || item.voice) && !item.consultText && !item.picture && !item.video && !item.pictureLoading && !item.videoLoading && !item.soundLoading"
                                                class="bubble-audio"
                                                @tap.stop.prevent="playAudio"
                                                :data-page="item"
                                            >
                                                <view
                                                    class="audio-icon-wrap"
                                                    :class="[item.direction === 'right' ? 'icon-sent' : 'icon-received', playingAudioId === item.msgID ? 'is-playing' : '']"
                                                >
                                                    <view class="audio-icon">
                                                        <text class="audio-icon-speaker">🔊</text>
                                                        <view class="audio-icon-waves">
                                                            <view class="audio-icon-wave"></view>
                                                            <view class="audio-icon-wave"></view>
                                                            <view class="audio-icon-wave"></view>
                                                        </view>
                                                    </view>
                                                </view>
                                                <text v-if="item.soundDuration || item.voiceDuration" class="audio-duration-text">
                                                    {{ formatAudioDuration(item.soundDuration || item.voiceDuration) }}"
                                                </text>
                                            </view>
                                            <!-- 视频消息气泡 -->
                                            <view
                                                v-if="item.video && !item.consultText && !item.picture && !item.pictureLoading"
                                                class="video-message"
                                                @tap.stop.prevent="playVideo"
                                                :data-url="item.video"
                                            >
                                                <view class="video-thumbnail-wrap">
                                                    <image
                                                        class="video-thumbnail"
                                                        :src="imgDomain + '/images/icon/doctor/uploadimg.png'"
                                                        mode="aspectFill"
                                                    />
                                                    <view class="video-play-btn-big">
                                                        <text class="video-play-triangle">▶</text>
                                                    </view>
                                                </view>
                                                <view class="video-duration-tag">{{ item.videoDuration || 0 }}"</view>
                                            </view>
                                        </view>
                                    </view>

                                    <view class="col-12" :style="'display: ' + (item.msgType === '2' || item.msgType === '3' ? 'block' : 'none')">
                                        <view v-if="item.msgType === '2' ? true : false">
                                            {{ item.direction === 'left' ? '对方撤回了一条消息' : '你撤回了一条消息' }}
                                            <text
                                                :data-text="item.consultText"
                                                @tap="reEdit"
                                                class="re-edit"
                                                :style="'display: ' + (item.direction === 'right' && item.reEditFlag == 'Y' && item.consultText ? 'inline-block' : 'none')"
                                            >
                                                重新编辑
                                            </text>
                                        </view>
                                        <text class="timespan" :style="'display: ' + (item.msgType === '3' ? 'block' : 'none')">{{ item.sendTime }}</text>
                                    </view>
                                </view>
                            </view>
                        </scroll-view>
                    </view>
                </view>
                <view v-if="patientData.statusCode != 'F'" class="chatbottom" :style="'min-height: ' + textareaHeight + 'rpx'">
                    <!-- 文字输入模式 -->
                    <template v-if="inputMode !== 'voice'">
                        <!-- 文字模式下：语音切换按钮（左侧） -->
                        <view class="switch-btn" @tap="switchInputMode">
                            <text class="switch-icon">🎤</text>
                        </view>

                        <view class="inputview">
                            <textarea
                                :show-confirm-bar="false"
                                @focus="bindfocus"
                                @blur="bindblur"
                                @linechange="bindlinechange"
                                :auto-height="false"
                                :adjust-position="false"
                                :focus="messageFocus"
                                id="messageValue"
                                :hold-keyboard="true"
                                class="input inputTrea"
                                type="text"
                                :value="messageValue"
                                @input="messageValueFun"
                                name="code"
                                confirm-type="send"
                                :cursor-spacing="spacing"
                                :style="'height:' + textareaHeight + 'rpx'"
                            ></textarea>
                        </view>
                    </template>

                    <!-- 语音录制模式 -->
                    <template v-else>
                        <!-- 语音模式下：键盘切换按钮（左侧） -->
                        <view class="switch-btn keyboard-btn" @tap="switchInputMode">
                            <text class="keyboard-icon-text">键盘</text>
                        </view>

                        <view
                            class="voice-record-area"
                            :class="{ 'voice-recording': recording }"
                            @touchstart.prevent="startVoiceRecord"
                            @touchmove.prevent
                            @touchend.prevent="stopVoiceRecord"
                            @touchcancel="onRecordTouchCancel"
                            @contextmenu.prevent
                        >
                            <text v-if="!recording" selectable="false">{{ recordText }}</text>
                            <text v-else selectable="false">松开 结束</text>
                        </view>
                    </template>

                    <button class="sendbtn" @tap="handleSendText" :disabled="clickState || btnLoading">
                        {{ clickState && clickState ? '发送中' : '发送' }}
                    </button>
                    <view class="other" @tap="moreBtnFun">
                        <text class="other-icon">{{ moreBtn ? '×' : '+' }}</text>
                    </view>
                </view>
            </view>
            <view class="bottomView" v-if="moreBtn">
                <view class="bottomIcon" @tap="addEmr" v-if="showMedicalRecordAndDiagnosis && !fromMessage">
                    <view class="iconview">
                        <image :src="imgDomain + '/images/icon/doctor/emr.png'" class="bottomimg"></image>
                    </view>
                    <view class="icontext">写病历</view>
                </view>
                <view class="bottomIcon" @tap="addDiag" v-if="showMedicalRecordAndDiagnosis && !fromMessage">
                    <view class="iconview">
                        <image :src="imgDomain + '/images/icon/doctor/diag.png'" class="bottomimg"></image>
                    </view>
                    <view class="icontext">下诊断</view>
                </view>
                <view class="bottomIcon" @tap="addOrder" v-if="showMedicalRecordAndDiagnosis && !fromMessage">
                    <view class="iconview">
                        <image :src="imgDomain + '/images/icon/doctor/order.png'" class="bottomimg"></image>
                    </view>
                    <view class="icontext">开医嘱</view>
                </view>

                <view class="bottomIcon" @tap="toMessageTemplate">
                    <view class="iconview">
                        <image :src="imgDomain + '/images/icon/doctor/consult.png'" class="bottomimg"></image>
                    </view>
                    <view class="icontext">消息模板</view>
                </view>
                <view class="bottomIcon" @tap="onChooseImage">
                    <view class="iconview">
                        <image :src="imgDomain + '/images/icon/doctor/uploadimg.png'" class="bottomimg"></image>
                    </view>
                    <view class="icontext">相册</view>
                </view>
                <view class="bottomIcon" @tap="onChooseVideo">
                    <view class="iconview">
                        <image :src="imgDomain + '/images/icon/doctor/uploadimg.png'" class="bottomimg"></image>
                    </view>
                    <view class="icontext">视频</view>
                </view>
            </view>
            <!-- <van-popup show="{{ showpopup }}" bind:close="onClose" closeable>
		<view class="doclist">
			<view class="title">请选择邀请医生</view>
			<view class="docitem" bindtap="inviteToDoc" data-item="{{item}}" wx:for="{{doclist}}" wx:if="{{item.userID!=userData.userID}}" wx:key="index">
				{{item.userDesc}}
			</view>
		</view>
	</van-popup> -->
            <mp-dialog :mask-closeable="false" title="提醒" style="top: 40%" :show="showEndMessage" @buttontap="tapDialogButton" :buttons="endButtonArr">
                <view :style="'color:' + endItem.colour" v-if="endItem.flag == 'Y' && endItem.code == '2' && endItem.prompt != ''">
                    <icon class="icon-box-img" type="warn" size="20"></icon>
                    {{ endItem.prompt }}
                </view>

                <view>{{ endItem.descripts }}</view>
            </mp-dialog>
        </view>
        <view v-if="showlongBtn" class="longView" @tap="closeLongBtn">
            <view class="longbtn" :style="'top:' + longBtnTop + 'px; left:' + longBtnLeft + 'px'" @tap.stop>
                <view class="longbtn-item" @tap="copyTextFun" v-if="showCopyBtn">复制</view>
                <view class="longbtn-item" @tap="cancelSend">撤回</view>
            </view>
        </view>
        <!-- 视频播放弹窗（H5） -->
        <view class="video-preview-mask" v-if="showVideoPreview" @tap="closeVideoPreview">
            <view class="video-preview-container" @tap.stop>
                <video
                    class="video-preview-player"
                    :src="videoPreviewUrl"
                    controls
                    autoplay
                    object-fit="contain"
                    @error="onVideoPreviewError"
                ></video>
                <view class="video-preview-close" @tap="closeVideoPreview">×</view>
            </view>
        </view>
    </view>
</template>

<script>
import Cache from '../../../../utils/cache';
import navBar from '@/pages/components/navbar/index';
import keyboard from '@/pages/components/keyboard/keyboard';
// pages/treatment/treatment.js
// 诊疗页面
import { SDKAPPID, genTestUserSig } from '../../../../utils/GenerateTestUserSig.js';
import messageUtil from '../../../../utils/message.js';
import Util from '../../../../utils/util.js';
import commonUtil from '../../../../utils/common.js';
// #ifdef MP-WEIXIN
import TIM from 'tim-wx-sdk';
// #endif
// #ifndef MP-WEIXIN
import TIM from 'tim-js-sdk';
// #endif
import { $http } from '../../../../config/https.js';
import { httpconfig as httpConfig } from '../../../../config/httpConfig.js';
import Toast from '../../../../utils/toast';
// #ifdef H5
import wechath5 from '../../../../utils/wechath5.js';
// #endif
const app = getApp();
export default {
    components: {
        navBar,
        keyboard
    },
    data() {
        return {
            imgDomain: httpConfig.imgDomain,
            clickState: false,
            videoStatus: 'N',

            // 是否打开视频
            tipsClose: 'Y',

            status: 6,
            trtcConfig: {},
            moreBtn: true,
            tim: null,
            isKickedOut: true,
            messageList: [],
            messageValue: '',
            moreMsgFlag: 'Y',
            newMessage: '',
            refreshing: false,
            body: 'body-bottom',
            templateFlag: false,
            scrollTop: 0,
            scrollToMessage: '',
            isScroll: false,
            admType: '',

            patientData: {
                statusCode: '',
                patImage: '',
                symptom: ''
            },

            userData: {
                image: ''
            },

            pictureData: [],
            cWidth: 0,
            cHeight: 0,

            navbarData: {
                isGoBack: true,
                background: '',
                title: '我的诊室',
                //导航栏 中间的标题
                bottom: false,
                imMessage: false,
                titleAlign: 'left'
            },

            height: app.globalData.height,
            chatbottomHeight: 40,
            help: false,
            showpopup: false,

            doclist: [
                {
                    docName: '测试1',
                    id: '1'
                },
                {
                    docName: '测试2',
                    id: '2'
                },
                {
                    docName: '测试3',
                    id: '3'
                },
                {
                    docName: '测试4',
                    id: '4'
                }
            ],

            record: false,
            spacing: '20',
            textareaHeight: '72',
            returnObj: {},
            endButtonArr: [],

            endItem: {
                colour: '',
                flag: '',
                code: '',
                prompt: '',
                descripts: ''
            },

            showEndMessage: false,
            toURL: '',
            btnLoading: false,
            clickButton: false,
            downLoadUrl: '',
            upLoadUrl: '',
            showlongBtn: '',

            // 患者/医生头像加载失败标志（true 时回退到首字占位）
            patImageError: false,
            userImageError: false,

            // 页面顶部自定义 toast
            topToast: {
                show: false,
                text: '',
                timer: null
            },

            //显示复制撤回按钮
            longBtnTop: '0',

            //按钮top位置
            longBtnLeft: '0',

            //按钮tleft位置
            copyText: '',

            //要复制的文本
            clickMsgID: '',

            //当前点击消息ID
            //显示复制按钮
            showCopyBtn: true,

            messageFocus: false,
            pictureLoading: false,
            chats: '',
            scrollWithAnimation: false,
            visitRelatedInformation: '',
            showMedicalRecordAndDiagnosis: true, // RevisitFlag为0时隐藏写病历和下诊断
            fromMessage: false, // 来源为消息列表(from=message)时，隐藏结束就诊/写病历/下诊断/开医嘱
            bodyStyle: '',

            // === 语音 ===
            inputMode: 'text', // 'text' | 'voice'
            recorderManager: null,
            recording: false,
            recordText: '按住 说话', // 录音按钮提示文字
            recordStartTime: 0,
            recordCanceled: false, // 录音取消标志（停止但不发送）
            h5StartRecordPromise: null, // H5 录音启动 Promise（wechath5.startRecord）
            _h5RecordingPhase: 'idle', // H5 录音阶段: 'idle' | 'starting' | 'recording' | 'stopping'

            // === 语音播放 ===
            innerAudioContext: null,
            playingAudioId: '',
            h5AudioContext: null,

            // === 视频 ===
            videoUploading: false,
            showVideoPreview: false,
            videoPreviewUrl: ''
        };
    },
    /**
     * 生命周期函数--监听页面加载
     */
    onLoad: function (options) {
        let { body, tipsClose, navbarData, help, moreBtn, admType, record, textareaHeight } = this;
        const patientData = Cache.get('patientData');
        if (options.admtype && options.admtype != 'video') {
            tipsClose = 'Y';
            navbarData.title = '图文-' + patientData.patName + ' ' + patientData.patAge + ' ' + patientData.patSexDesc;
        } else {
            navbarData.title = '视频-' + patientData.patName + ' ' + patientData.patAge + ' ' + patientData.patSexDesc;
        }
        admType = options.admtype;
        if (options.help) {
            help = options.help;
            body = 'body-master';
            moreBtn = false;
            admType = 'help';
            navbarData.title = '客服帮助';
        } else {
            help = false;
        }
        if (options.record) {
            if (patientData.statusCode == 'F') {
                record = true;
            } else {
                record = false;
            }
            textareaHeight = 0;
            //body = "body-onlybottom";
        } else {
            record = false;
            textareaHeight = 72;
        }
        // 来源为消息列表：仅查看会话，隐藏就诊相关操作
        const fromMessage = options.from === 'message';
        this.setData({
            body: body,
            moreBtn: moreBtn,
            tipsClose: tipsClose,
            admType: admType,
            patientData: Cache.get('patientData'),
            userData: Cache.get('userData'),
            navbarData,
            help,
            record,
            textareaHeight,
            fromMessage,
            showMedicalRecordAndDiagnosis: fromMessage ? false : this.showMedicalRecordAndDiagnosis
        });
        if (!help && !fromMessage) {
            this.getVisitRelatedInformation();
        }
    },
    /**
     * 生命周期函数--监听页面初次渲染完成
     */
    onReady: function () {
        this.getConsultRecord();
        // 初始化语音录制
        this.initVoiceRecorder();
        // 确保医生端 TIM 已初始化并登录，以便接收患者消息
        if (!app.globalData.timObj) {
            console.log('[treatment] app.globalData.timObj 为空，调用 messageUtil.initTim()');
            messageUtil.initTim();
        }
        if (app.globalData.timObj) {
            console.log('[treatment] 注册 MESSAGE_RECEIVED 监听');
            app.globalData.timObj.on(TIM.EVENT.MESSAGE_RECEIVED, this.onMessageReceived);
        } else {
            console.warn('[treatment] timObj 仍为 null，无法注册消息监听');
        }
    },
    /**
     * 生命周期函数--监听页面显示
     */
    onShow: function () {
        let that = this;
        if (typeof uni.onKeyboardHeightChange === 'function') {
            uni.onKeyboardHeightChange((result) => {
                that.onKeyboardHeightChange(result);
            });
        }
        this.setData({
            patientData: Cache.get('patientData'),
            userData: Cache.get('userData'),
            // 切换会话时重置头像加载状态
            patImageError: false,
            userImageError: false
        });
        let { templateFlag } = this;
        if (templateFlag) {
            this.setData(
                {
                    templateFlag: false
                },
                () => {
                    //this.handleSendText();
                }
            );
        }
        //获取图片文件路径
        this.getFileAddress();
    },
    /**
     * 生命周期函数--监听页面隐藏
     */
    onHide: function () {},
    /**
     * 生命周期函数--监听页面卸载
     */
    onUnload: function () {
        // 清除顶部 toast 定时器
        if (this.topToast && this.topToast.timer) {
            clearTimeout(this.topToast.timer);
        }
        // 销毁语音播放上下文
        if (this.innerAudioContext) {
            this.innerAudioContext.destroy();
            this.innerAudioContext = null;
        }
        // #ifdef H5
        // 释放 H5 录音资源（JSSDK / MediaRecorder 由 wechath5 统一管理）
        if (this.h5StartRecordPromise) {
            try { wechath5.cancelRecord(); } catch (e) {}
            this.h5StartRecordPromise = null;
        }
        this._h5RecordingPhase = 'idle';
        // #endif
        // 注销 TIM 消息监听
        if (app.globalData.timObj) {
            app.globalData.timObj.off(TIM.EVENT.MESSAGE_RECEIVED, this.onMessageReceived);
        }
    },
    /**
     * 页面相关事件处理函数--监听用户下拉动作
     */
    onPullDownRefresh: function () {},
    /**
     * 页面上拉触底事件的处理函数
     */
    onReachBottom: function () {},
    /**
     * 用户点击右上角分享
     */
    onShareAppMessage: function () {},
    methods: {
        /**
         * 拼接图片完整 URL
         * 后端返回的 image / patImage 可能是相对路径（如 /images/photo/xxx.png），
         * H5 端需要拼上 imgDomain 才能正确加载
         */
        getFullImageUrl: function (url) {
            if (!url) return '';
            if (/^(https?:)?\/\//i.test(url) || url.startsWith('data:')) {
                return url;
            }
            return this.imgDomain + url;
        },

        /**
         * 头像为空时显示首字作为占位
         */
        getAvatarText: function (name) {
            if (!name) return '?';
            return String(name).charAt(0);
        },

        /**
         * 头像 <image> 加载失败回调
         * 后端返回的 patImage / image 可能是个失效的 CDN 资源（如 404），
         * 加载失败时回退到首字占位
         */
        onImageError: function (key) {
            if (key === 'patImage' && !this.patImageError) {
                this.setData({ patImageError: true });
            } else if (key === 'userImage' && !this.userImageError) {
                this.setData({ userImageError: true });
            }
        },

        /**
         * 页面顶部自定义 toast
         */
        showTopToast: function (text, duration) {
            const that = this;
            // 清除之前的定时器
            if (this.topToast.timer) {
                clearTimeout(this.topToast.timer);
            }
            this.setData({
                'topToast.show': true,
                'topToast.text': text || '发送成功'
            });
            const timer = setTimeout(function () {
                that.setData({ 'topToast.show': false });
            }, duration || 2000);
            this.setData({ 'topToast.timer': timer });
        },

        loginOut: function (callback) {},
        checkImUser: function () {},

        login: function () {
            // 医生端 TIM 统一由 messageUtil 维护（app.globalData.timObj），这里转发一下即可
            messageUtil.loginTim && messageUtil.loginTim();
        },

        messageValueFun: function (e) {
            this.setData({
                messageValue: e.detail.value
            });
        },

        moreBtnFun: function () {
            let { tipsClose, body, moreBtn } = this;
            if (tipsClose == 'Y') {
                if (moreBtn) {
                    body = 'body-notips';
                } else {
                    body = 'body-bottom';
                }
            } else {
                if (moreBtn) {
                    body = 'body';
                } else {
                    body = 'body-bottom-tips';
                }
            }
            this.setData({
                moreBtn: !moreBtn,
                messageFocus: false,
                body
            });
        },

        /**
         * 发送文字
         *
         */
        handleSendText: Util.debounce(function () {
            // this.setData({clickState: true})//请求前禁用标记改为true,防止重复请求
            if (!this.ifOnLine()) {
                return;
            }
            const rawMessageValue = this.messageValue;
            if (typeof rawMessageValue !== 'string' && typeof rawMessageValue !== 'number') {
                uni.showToast({
                    title: '发送内容异常',
                    icon: 'none'
                });
                return;
            }
            const messageValue = String(rawMessageValue).trim();
            if (!messageValue) {
                uni.showToast({
                    title: '发送内容不能为空',
                    icon: 'none'
                });
                return;
            }
            try {
                this.setData(
                    {
                        btnLoading: true,
                        clickState: true
                    },
                    () => {
                        const patientData = Cache.get('patientData');
                        const userData = Cache.get('userData');
                        let { admType, help } = this;
                        let admID = patientData.admID;
                        let toUser = 'patient' + patientData.patID;
                        if (help) {
                            toUser = 'userKF001';
                            admID = '';
                        }
                        let data = {
                            params: [
                                {
                                    admID: admID,
                                    toUser: toUser,
                                    consultText: messageValue + '',
                                    frUser: this.fromMessage ? 'userKF001' : 'user' + userData.userID
                                }
                            ]
                        };
                        let _this = this;
                        // 发送成功
                        $http.post(
                            'urlDeault',
                            this,
                            {
                                code: '02090004',
                                data: data,
                                success: function (res) {
                                    let errorCode = res.errorCode;
                                    if (errorCode == '0') {
                                        _this.setData(
                                            {
                                                messageValue: '',
                                                textareaHeight: '72',
                                                btnLoading: false,
                                                clickState: false
                                            },
                                            () => {
                                                _this.getConsultRecord();
                                            }
                                        );
                                        _this.showTopToast('发送成功');
                                        // setTimeout(function () {
                                        //   wx.navigateBack();
                                        // }, 2000)
                                        if (admType != 'video') {
                                            _this.arriveAdm();
                                        }
                                        let messageContent = {};
                                        messageContent.userID = userData.userID;
                                        messageContent.admID = patientData.admID;
                                        messageContent.patID = patientData.patID;
                                        messageContent.patName = patientData.patName;
                                        messageContent.userName = userData.userName;
                                        messageContent.type = 'M';
                                        messageContent.text = messageValue + '';
                                        let message = app.globalData.timObj.createTextMessage({
                                            to: toUser,
                                            conversationType: TIM.TYPES.CONV_C2C,
                                            payload: {
                                                text: JSON.stringify(messageContent)
                                            }
                                        });
                                        // 2. 发送消息
                                        let promise = app.globalData.timObj.sendMessage(message);
                                        if (promise) {
                                            promise
                                                .then((imResponse) => {})
                                                .catch(function (imError) {
                                                    if (imError.code == '20003') {
                                                    }
                                                });
                                        }
                                    } else {
                                        uni.showToast({
                                            title: res.errorMessage,
                                            icon: 'none'
                                        });
                                        this.setData({
                                            btnLoading: false,
                                            clickState: false
                                        });
                                    }
                                }
                            },
                            {
                                error: 'Y'
                            }
                        );
                    }
                );
            } catch (error) {
                console.log('CatchClause', error);
                console.log('CatchClause', error);
                this.setData({
                    btnLoading: false,
                    clickState: false
                });
            }
        }),

        /**
         * 发送文本消息
         */
        getConsultRecord: function () {
            try {
                let { messageList, moreMsgFlag, noMoreFlag, help } = this;
                const patientData = Cache.get('patientData');
                const userData = Cache.get('userData');
                let toUser = 'patient' + patientData.patID;
                let frUser = 'user' + userData.userID;
                let lastMsgID = '';
                if (messageList.length > 0) {
                    lastMsgID = messageList[0].ID;
                }
                let admID = patientData.admID;
                if (help) {
                    toUser = 'userKF001';
                    admID = '';
                }
                let params = {
                    params: [
                        {
                            admID: admID,
                            patID: patientData.patID,
                            frUser: this.fromMessage ? 'userKF001' :frUser,
                            toUser: toUser,
                            lastMsgID: lastMsgID
                        }
                    ]
                };
                $http.post('urlDeault', this, {
                    code: '02090005',
                    data: params,
                    success: function (res) {
                        let errorCode = res.errorCode;
                        if (errorCode == '0') {
                            let messageList = res.result.msgData;
                            let pictureData = [];
                            for (var i = 0; i < messageList.length; i++) {
                                if (messageList[i].picture != '' && messageList[i].picture != undefined && messageList[i].msgType == '1') {
                                    let pictureObj = {};
                                    pictureObj.index = i;
                                    pictureObj.src = messageList[i].picture;
                                    pictureData.push(pictureObj);
                                }
                            }
                            this.setData(
                                {
                                    messageList: messageList,
                                    moreMsgFlag: res.result.moreMsgFlag,
                                    refreshing: false,
                                    pictureData: pictureData
                                },
                                () => {
                                    this.getBodyHeight();
                                }
                            );
                        }
                    }
                });
            } catch (error) {
                console.log('CatchClause', error);
                console.log('CatchClause', error);
                this.setData({
                    messageList: [],
                    moreMsgFlag: 'N',
                    refreshing: false
                });
            }
        },

        onMessageReceived: function (event) {
            let { messageList } = this;
            let that = this;
            if (event.data) {
                event.data.map((item) => {
                    const patientData = Cache.get('patientData');
                    const userData = Cache.get('userData');
                    let fromuser = item.from;
                    if (that.help) {
                        if ('userKF001' === fromuser) {
                            this.getConsultRecord();
                        }
                    } else {
                        if (patientData && 'patient' + patientData.patID === fromuser) {
                            this.getConsultRecord();
                        }
                    }
                });
            }
        },

        /***图片预览 */
        priviewImage: function (e) {
            //
            console.log(e.currentTarget.dataset.picture);
        },

        /**登出 */
        monitorKickedOut: function (event) {
            let that = this;
            let { isKickedOut } = this;
            this.setData(
                {
                    isKickedOut: false
                },
                () => {
                    if (isKickedOut) {
                        uni.showModal({
                            title: '账号异常',
                            content: '您的账号在别处登录，是否重新登录???',
                            success: function (e) {
                                console.log(e);
                                if (e.cancel) {
                                    that.confirmLoginAgain(1);
                                }
                                if (e.confirm) {
                                    that.confirmLoginAgain(2);
                                }
                            },
                            fail: function (e) {}
                        });
                    }
                }
            );
        },

        /**
         * 确认重新登录
         */
        confirmLoginAgain: function (num) {
            if (num == 1) {
                uni.navigateBack({});
            } else {
                this.login();
            }
        },

        addDiag: function (e) {
            uni.navigateTo({
                // url: '/pages/Entry/Entry?select=0',
                url: '../../../entryWrite/page/Entry/Entry?select=1&addText=添加诊断'
            });
        },

        addOrder: function (e) {
            uni.navigateTo({
                url: '../../../entryWrite/page/Entry/Entry?select=2&addText=添加医嘱'
            });
        },

        addEmr: function (e) {
            uni.navigateTo({
                url: '../../../entryWrite/page/Entry/Entry?select=0&addText=添加病历'
            });
        },

        toMessageTemplate: function () {
            uni.navigateTo({
                url: './messageTemplate/messageTemplate'
            });
        },

        // 获取滚动区域的高度
        getBodyHeight() {
            let _this = this;
            var query = uni.createSelectorQuery();
            query.select('#viewHight').boundingClientRect();
            query.exec(function (viewRes) {
                let viewHeight = viewRes[0].height;
                uni.getSystemInfo({
                    success: (res) => {
                        console.log(res, viewHeight);
                        if (res.windowHeight < viewHeight + 60) {
                            _this.setData({
                                isScroll: true,
                                scrollTop: viewHeight
                            });
                        } else {
                            _this.setData({
                                isScroll: false
                            });
                        }
                    }
                });
            });
        },

        // 图片上传
        handleUploadFile: function (fileData) {
            let that = this;
            let path = fileData.tempFilePaths instanceof Array && fileData.tempFilePaths.length > 0 ? fileData.tempFilePaths[0] : '';
            let imgType;
            // #ifdef MP
            imgType = path.split('.')[path.split('.').length - 1];
            // #endif
            // #ifdef H5
            imgType = fileData.tempFiles instanceof Array && fileData.tempFiles.length > 0 ? fileData.tempFiles[0].type : '';
            imgType = imgType ? imgType.split('/')[1] : '';
            // #endif
            console.log('[handleUploadFile] imgType=', imgType, 'path=', path);
            if (imgType === 'jpg' || imgType === 'png' || imgType === 'jpeg' || imgType === 'gif') {
                if (!that.upLoadUrl) {
                    uni.showToast({ title: '上传地址未就绪，请稍后再试', icon: 'none' });
                    that.hidePictureLoading();
                    return;
                }
                uni.uploadFile({
                    url: that.upLoadUrl,
                    //仅为示例，非真实的接口地址
                    filePath: path,
                    name: 'file',
                    success: (unloadres) => {
                        if (unloadres.statusCode === 200) {
                            let imgName = JSON.parse(unloadres.data).filePath;
                            that.handleSendImage(fileData, imgName);
                        } else {
                            uni.showToast({
                                title: '图片发送失败，请稍后再试1。',
                                icon: 'none'
                            });
                            that.hidePictureLoading();
                        }
                    },
                    fail: (res) => {
                        uni.showToast({
                            title: '图片发送失败，请稍后再试2。',
                            icon: 'none'
                        });
                        that.hidePictureLoading();
                    }
                });
            } else {
                uni.showToast({
                    title: '发送失败：图片类型有误或图片图片太大。',
                    icon: 'none'
                });
                that.hidePictureLoading();
            }
        },

        hidePictureLoading: function () {
            this.setData({
                pictureLoading: false
            });
        },

        handleCompressImage: function (imgName, image) {
            let that = this;
            uni.getImageInfo({
                src: image.tempFilePaths[0],
                success: function (res) {
                    //---------利用canvas压缩图片--------------
                    var ratio = 2;
                    var canvasWidth = res.width; //图片原始长宽
                    var canvasHeight = res.height;
                    while (canvasWidth > 200 || canvasHeight > 200) {
                        // 保证宽高在400以内
                        canvasWidth = Math.trunc(res.width / ratio);
                        canvasHeight = Math.trunc(res.height / ratio);
                        ratio++;
                    }
                    that.setData({
                        cWidth: canvasWidth,
                        cHeight: canvasHeight
                    });
                    //----------绘制图形并取出图片路径--------------
                    var ctx = uni.createCanvasContext('canvas');
                    ctx.drawImage(res.path, 0, 0, canvasWidth, canvasHeight);
                    ctx.draw(
                        false,
                        setTimeout(function () {
                            uni.canvasToTempFilePath({
                                fileType: 'jpg',
                                canvasId: 'canvas',
                                destWidth: canvasWidth,
                                destHeight: canvasHeight,
                                quality: 0.4,
                                success: function (res) {
                                    this.handleSendImage(res.tempFilePath, imgName);
                                },
                                fail: function (res) {
                                    console.log(res.errMsg);
                                }
                            });
                        }, 100)
                    );
                }
            });
        },

        handleSendImage: function (fileData, imgName) {
            let that = this;
            // H5 环境不支持 uni.getFileSystemManager，且 base64 当前未使用，安全跳过
            let base64 = '';
            try {
                // #ifdef MP-WEIXIN
                let fsManager = uni.getFileSystemManager();
                if (fsManager && typeof fsManager.readFileSync === 'function') {
                    base64 = 'data:image/png;base64,' + fsManager.readFileSync(fileData.tempFilePaths[0], 'base64');
                }
                // #endif
            } catch (e) {
                console.warn('[handleSendImage] 读取文件 base64 失败:', e.message);
            }
            const patientData = Cache.get('patientData');
            const userData = Cache.get('userData');
            let toUser = 'patient' + patientData.patID;
            let help = this.help;
            let admID = patientData.admID;
            if (help) {
                toUser = 'userKF001';
                admID = '';
            }
            console.log(that.downLoadUrl , imgName, )
            let data = {
                params: [
                    {
                        admID: admID,
                        toUser: toUser,
                        consultText: '',
                        picture: that.downLoadUrl + imgName,
                        // pictureBase64: base64,
                        frUser: this.fromMessage ? 'userKF001' : 'user' + userData.userID
                    }
                ]
            };
            let { admType } = this;
            // 发送图片
            $http.post('urlDeault', that, {
                code: '02090004',
                data: data,
                success: function (res) {
                    let errorCode = res.errorCode;
                    if (errorCode == '0') {
                        that.getConsultRecord();
                        if (admType != 'video') {
                            that.arriveAdm();
                        }
                        that.hidePictureLoading();
                        that.showTopToast('发送成功');
                        // 发送 IM 实时消息通知（H5 下 TIM 可能未初始化，需安全处理）
                        let timObj = app && app.globalData && app.globalData.timObj;
                        if (timObj) {
                            try {
                                let messageContent = {};
                                messageContent.userID = userData.userID;
                                messageContent.admID = patientData.admID;
                                messageContent.patID = patientData.patID;
                                messageContent.patName = patientData.patName;
                                messageContent.userName = userData.userName;
                                messageContent.type = 'M';
                                messageContent.image = 'Y';
                                let message = timObj.createTextMessage({
                                    to: toUser,
                                    conversationType: TIM.TYPES.CONV_C2C,
                                    payload: {
                                        text: JSON.stringify(messageContent)
                                    }
                                });
                                let promise = timObj.sendMessage(message);
                                if (promise) {
                                    promise
                                        .then(function (imResponse) {
                                            // 发送成功
                                        })
                                        .catch(function (imError) {
                                            console.warn('[handleSendImage] IM 消息发送失败:', imError);
                                        });
                                }
                            } catch (e) {
                                console.warn('[handleSendImage] IM 消息构造失败:', e.message);
                            }
                        }
                    } else {
                        uni.showToast({
                            title: res.errorMessage || '图片发送失败',
                            icon: 'none'
                        });
                        that.hidePictureLoading();
                    }
                },
                fail: function () {
                    uni.showToast({
                        title: '图片发送失败，请稍后再试',
                        icon: 'none'
                    });
                    that.hidePictureLoading();
                }
            });
        },

        onChooseImage: async function (e) {
            if (!this.ifOnLine()) {
                return;
            }
            let that = this;
            let { messageList } = this;
            uni.chooseImage({
                count: 1,
                sourceType: ['album', 'camera'],
                success: async (imgres) => {
                    let obj = {
                        pictureLoading: true,
                        direction: 'right',
                        msgType: '1',
                        isWithdraw: true,
                        key: messageList.length + 1,
                        msgID: 'loading' + (messageList.length + 1)
                    };
                    messageList.push(obj);
                    that.setData(
                        {
                            messageList
                        },
                        () => {
                            that.getBodyHeight();
                            that.handleUploadFile(imgres);
                        }
                    );
                },
                fail: async function (res) {
                    console.log('fail', res);
                }
            });
        },

        mergeCommonCriteria(criteria) {
            return {
                groupId: this.groupId,
                ...criteria
            };
        },

        async onScrollToUpper() {
            if (this.db && this.chats.length) {
                const { collection } = this;
                const _ = this.db.command;
                const { data } = await this.db
                    .collection(collection)
                    .where(
                        this.mergeCommonCriteria({
                            sendTimeTS: _.lt(this.chats[0].sendTimeTS)
                        })
                    )
                    .orderBy('sendTimeTS', 'desc')
                    .get();
                this.chats.unshift(...data.reverse());
                this.setData({
                    chats: this.chats,
                    scrollToMessage: `item-${data.length}`,
                    scrollWithAnimation: false
                });
            }
        },

        // 根据就诊ID获取挂号就诊相关信息
        getVisitRelatedInformation() {
            const patientData = Cache.get('patientData');
            let data = {
                params: [
                    {
                        admID: patientData.admID || ''
                    }
                ]
            };
            $http.post('urlS', this, {
                code: '03100075',
                data: data,
                namespace: 'Y',
                success: function (res) {
                    if (+res.errorCode === 0) {
                        const revisitFlag = res.result && res.result.RevisitFlag;
                        this.setData({
                            visitRelatedInformation: res.result,
                            showMedicalRecordAndDiagnosis: !this.fromMessage && revisitFlag !== 0
                        });
                    } else {
                        this.setData({
                            visitRelatedInformation: {},
                            showMedicalRecordAndDiagnosis: !this.fromMessage
                        });
                    }
                }
            });
        },

        callPat: function (e) {
            const patientData = Cache.get('patientData');
            const userData = Cache.get('userData');
            let data = {
                params: [
                    {
                        hospID: userData.hospID,
                        userID: userData.userID,
                        locID: userData.locID,
                        admID: patientData.admID
                    }
                ]
            };
            $http.post('urlS', this, {
                code: '03100058',
                data: data,
                namespace: 'Y',
                success: function (res) {
                    if (+res.errorCode === 0) {
                        patientData.statusCode = 'CI';
                        Cache.set('patientData', patientData);
                    } else {
                        Toast(res.errorMessage);
                    }
                }
            });
        },

        // 检查人员是否允许加入房间
        allowToJoin: Util.debounce(function () {
            if (this.clickButton && !app.globalData[timer]) {
                return;
            }
            this.setData(
                {
                    clickButton: true
                },
                () => {
                    const userData = Cache.get('userData');
                    let timer = 'patient' + userData.userID;
                    if (app.globalData[timer]) {
                        //已经发起视频，不再重复发起；
                        return;
                    }
                    if (!this.ifOnLine()) {
                        return;
                    }
                    let path = '../trtcRoom/trtcRoom';
                    messageUtil.allowToJoin(path);
                    setTimeout(() => {
                        this.setData({
                            clickButton: false
                        });
                    }, 1000);
                }
            );
        }),

        inviteDoctor: function (e) {
            console.log('已邀请，等待医生接听');
        },

        caldelayedlPat: function (e) {
            console.log('已延长视频时间');
        },

        finishStatus: function (flag) {
            var that = this;
            const patientData = Cache.get('patientData');
            let data = {
                params: [
                    {
                        admID: patientData.admID,
                        flag: flag || 'N'
                    }
                ]
            };
            // 结束服务
            $http.post('urlDeault', this, {
                code: '03100116',
                data: data,
                success: function (res) {
                    let errorCode = res.errorCode;
                    if (errorCode == '0') {
                        var endItem = res.result;
                        endItem.flag = flag;
                        // var knowButton = [{
                        //   text: '知道了',
                        //   code: '1^2^3'
                        // }]
                        // var signaButton = [{
                        //   text: '暂不签名',
                        //   code: '4^'
                        // }, {
                        //   text: '完成签名',
                        //   code: '2'
                        // }]
                        // var endButton = [{
                        //   text: '不操作',
                        //   code: '5^'
                        // }, {
                        //   text: '返回诊室',
                        //   code: '3'
                        // }]
                        var endButtonArr = endItem.button;
                        if (res.result.code == 3) {
                            that.endServer();
                        }
                        // if (res.result.code == 1) {
                        //   endButtonArr = knowButton
                        // } else if (res.result.code == 2) {
                        //   endButtonArr = signaButton
                        // } else if (res.result.code == 3) {
                        //   that.endServer()
                        //   endButtonArr = endButton
                        // }
                        this.setData({
                            endItem: endItem,
                            showEndMessage: true,
                            endButtonArr: endButtonArr
                        });
                    }
                }
            });
        },

        tapDialogButton: function (e) {
            const patientData = Cache.get('patientData');
            var item = e.detail.item;
            if (item.code == '2') {
                this.finishStatus('Y');
                return;
            }
            if (item.code == '6') {
                this.finishStatus();
                return;
            }
            if (item.code == '3') {
                messageUtil.handleSendMessage('F', '医生已结束问诊,请您评价!', '', '', '', patientData);
                let pages = getCurrentPages(); //获取当前页面js里面的pages里的所有信息。
                let prevPage = pages[pages.length - 2];
                prevPage.setData(
                    {
                        // 将我们想要传递的参数在这里直接setData。上个页面就会执行这里的操作。
                        refreshing: true
                    },
                    () => {
                        uni.reLaunch({
                            url: this.toURL
                        });
                    }
                );
            }
            this.setData({
                showEndMessage: false
            });
        },

        endServer: function (e) {
            const patientData = Cache.get('patientData');
            const userData = Cache.get('userData');
            let data = {
                params: [
                    {
                        admID: patientData.admID,
                        userID: userData.userID
                    }
                ]
            };
            // 结束服务
            $http.post('urlDeault', this, {
                code: '03100061',
                data: data,
                success: function (res) {
                    let errorCode = res.errorCode;
                    if (errorCode == '0') {
                        this.setData({
                            toURL: res.result.toURL
                        });
                    }
                }
            });
        },

        /**
         * 结束就诊（图文接诊右上角按钮）
         * 调用接口 code: 03100061
         */
        endVisit: function () {
            let that = this;
            const patientData = Cache.get('patientData');
            const userData = Cache.get('userData');
            uni.showModal({
                title: '确认结束就诊',
                content: '结束后患者将无法继续咨询，确认结束就诊？',
                confirmText: '结束就诊',
                confirmColor: '#f56c6c',
                success: function (e) {
                    if (e.confirm) {
                        let data = {
                            params: [
                                {
                                    admID: patientData.admID,
                                    userID: userData.userID,
                                    hospID: userData.hospID,
                                    locID: userData.locID
                                }
                            ]
                        };
                        // 结束就诊
                        $http.post('urlDeault', that, {
                            code: '03100061',
                            data: data,
                            success: function (res) {
                                let errorCode = res.errorCode;
                                if (errorCode == '0') {
                                    messageUtil.handleSendMessage('F', '医生已结束问诊,请您评价!');
                                    let pages = getCurrentPages();
                                    let prevPage = pages[pages.length - 2];
                                    prevPage.setData(
                                        {
                                            refreshing: true
                                        },
                                        () => {
                                            uni.navigateBack({});
                                        }
                                    );
                                }
                            }
                        });
                    }
                }
            });
        },

        /**
         * 结束服务
         */
        finish: function () {
            let that = this;
            var admType = this.admType;
            if (admType == 'video') {
                let patientData = Cache.get('patientData');
                commonUtil.getOrders(patientData.admID, (res) => {
                    if (res.errorCode == '0') {
                        if (res.result.flag == 'N') {
                            that.finishStatus();
                        } else {
                            uni.showModal({
                                title: '提示',
                                content: res.result.message,
                                confirmText: '确认',
                                success(res) {
                                    if (res.confirm) {
                                        that.finishStatus();
                                    }
                                }
                            });
                        }
                    } else {
                        uni.showModal({
                            title: '提示',
                            content: res.errorMessage,
                            showCancel: false,
                            confirmText: '我知道了',
                            success(res) {}
                        });
                    }
                });
            } else {
                uni.showModal({
                    title: '确认结束服务',
                    content: '正在接诊患者，结束后患者将无法继续咨询???',
                    success: function (e) {
                        console.log(e);
                        if (e.cancel) {
                        }
                        if (e.confirm) {
                            const patientData = Cache.get('patientData');
                            const userData = Cache.get('userData');
                            let data = {
                                params: [
                                    {
                                        admID: patientData.admID,
                                        userID: userData.userID
                                    }
                                ]
                            };
                            // 结束服务
                            $http.post('urlDeault', this, {
                                code: '03100061',
                                data: data,
                                success: function (res) {
                                    let errorCode = res.errorCode;
                                    if (errorCode == '0') {
                                        messageUtil.handleSendMessage('F', '医生已结束问诊,请您评价!');
                                        let pages = getCurrentPages(); //获取当前页面js里面的pages里的所有信息。
                                        let prevPage = pages[pages.length - 2];
                                        prevPage.setData(
                                            {
                                                // 将我们想要传递的参数在这里直接setData。上个页面就会执行这里的操作。
                                                refreshing: true
                                            },
                                            () => {
                                                uni.navigateBack({});
                                            }
                                        );
                                    }
                                }
                            });
                        }
                    },
                    fail: function (e) {}
                });
            }
        },

        closeTips: function (e) {
            let { moreBtnFun: moreBtn, body } = this;
            if (moreBtn) {
                body = 'body-bottom';
            } else {
                body = 'body-notips';
            }
            this.setData({
                tipsClose: 'Y',
                body
            });
        },

        /**
         * 获取图片
         */
        getPictureData: function () {
            return this.pictureData;
        },

        // 查看原图
        viewOriginal(e) {
            if (this.showlongBtn) {
                return;
            }
            let { messageList } = this;
            let { index } = e.currentTarget.dataset;
            let urls = [];
            let curIndex = '';
            if (this.messageList[index].picture != '' && this.messageList[index].picture != undefined) {
                for (var i = 0; i < messageList.length; i++) {
                    if (messageList[i].picture && messageList[i].msgType === '1') {
                        urls.push(messageList[i].picture);
                        if (i == index) {
                            curIndex = messageList[i].picture;
                        }
                    }
                }
                uni.previewImage({
                    current: curIndex,
                    // 当前显示图片的http链接
                    urls // 需要预览的图片http链接列表
                });
            }
        },

        shwoImage: function (e) {
            var index = e.currentTarget.dataset.index;
            let { pictureData, messageArr } = this;
            if (this.messageList[index].picture != '' && this.messageList[index].picture != undefined) {
                var currentIndex = '';
                for (var i = 0; i < pictureData.length; i++) {
                    if (pictureData[i].index == index) {
                        currentIndex = i;
                    }
                }
                Cache.set('tempPicture', pictureData);
                uni.navigateTo({
                    url: '/pages/myConsultRoom/page/showPicture/showPicture?&currentIndex=' + currentIndex
                });
            }
        },

        lookDetail: function () {
            uni.navigateTo({
                url: '../graphicDateil/graphicDateil'
            });
        },

        arriveAdm: function (e) {
            let that = this;
            if (this.help) {
                return;
            }
            var patientData = Cache.get('patientData');
            var userData = Cache.get('userData');
            if (patientData.statusCode == 'R') {
                let data = {
                    params: [
                        {
                            hospID: userData.hospID,
                            userID: userData.userID,
                            locID: userData.locID,
                            admID: patientData.admID
                        }
                    ]
                };
                $http.post('urlDeault', this, {
                    code: '03100060',
                    data: data,
                    success: function (res) {
                        patientData.statusCode = 'A';
                        Cache.set('patientData', patientData);
                    }
                });
            }
        },

        /**
         * 判断是否在线
         */
        ifOnLine: function () {
            let TIMInfo = Cache.get('TIMInfo');
            let { navbarData } = this;
            if (TIMInfo.loginStatus == 'N' || !TIMInfo) {
                navbarData.overlayVisible = true;
                navbarData.message = '您处于离线状态，请先登录';
                navbarData.login = true;
                this.setData({
                    navbarData
                });
                return false;
            } else {
                return true;
            }
        },

        bindfocus: function (event) {},

        onTextKeyboardHeightChange: function (event) {
            let height = this.height;
            if (!this.moreBtn) {
                height = height + 220;
            }
            this.setData({
                chatbottomHeight: event.detail.height + 120 + height
            });
        },

        onKeyboardHeightChange: function (event) {
            let height = this.height;
            if (!this.moreBtn) {
                height = height + 220;
            }
            this.setData(
                {
                    chatbottomHeight: event.height + 120 + height
                },
                () => {
                    this.getBodyHeight();
                }
            );
        },

        bindblur: function (event) {
            this.setData({
                chatbottomHeight: 40
            });
        },

        bindlinechange: function (event) {
            let { lineCount, heightRpx } = event.detail;
            let { textareaHeight } = this;
            if (lineCount <= 5 && lineCount > 1) {
                textareaHeight = heightRpx;
            }
            this.setData({
                textareaHeight
            });
        },

        /**
         * 点击邀请医生按钮
         */
        inviteDoc: function () {
            const userData = Cache.get('userData');
            let that = this;
            let params = {
                params: [
                    {
                        locID: userData.locID,
                        userActive: 'Y',
                        userCode: '',
                        userCredNo: '',
                        userCredTypeID: '',
                        userDesc: ''
                    }
                ],
                pagination: [
                    {
                        pageSize: 200,
                        currentPage: 1
                    }
                ]
            };
            $http.post('urlDeault', this, {
                code: '01040106',
                data: params,
                success: function (res) {
                    let showpopup = true;
                    if (res.result.data.length == 0) {
                        showpopup = false;
                    }
                    if (res.errorCode == '0') {
                        that.setData(
                            {
                                doclist: res.result.data,
                                showpopup: showpopup
                            },
                            () => {
                                setTimeout(() => {
                                    uni.navigateTo({
                                        url: '../InviteDoctor/InviteDoctor'
                                    });
                                }, 1000);
                            }
                        );
                    }
                }
            });
        },

        /**
         * 选择邀请医生后发送
         */
        inviteToDoc: function (event) {
            const userData = Cache.get('userData');
            const patientData = Cache.get('patientData');
            let item = event.currentTarget.dataset.item;
            let toUser = 'user' + item.userID;
            let roomNo = patientData.roomNo;
            let flag = messageUtil.handleSendMessage('I', '医生' + userData.userName + '邀请您视频问诊', toUser, '', roomNo);
            uni.showModal({
                title: '提示',
                content: '已发送邀请，您进入视频后，邀请医生才可进入视频!',
                showCancel: false,
                confirmText: '我知道了',
                success(res) {}
            });
            if (flag) {
                this.setData({
                    showpopup: false
                });
            }
        },

        /**
         * 关闭邀请医生界面
         */
        onClose: function (event) {
            this.setData({
                showpopup: false
            });
        },

        /**取消结束 */
        cancelFinish: Util.debounce(function () {
            let { patientData } = this;
            commonUtil.cancelFinish(patientData.admID, (res) => {
                if (res.errorCode == '0') {
                    patientData.statusCode = 'A';
                    patientData.status = '接诊中';
                    this.setData({
                        record: false,
                        patientData
                    });
                    Cache.set('patientData', patientData);
                }
            });
        }),

        /**
         * 获取图片文件服务器地址
         */
        getFileAddress: function () {
            const userData = Cache.get('userData');
            let that = this;
            var accountInfo = typeof uni.getAccountInfoSync === 'function' ? uni.getAccountInfoSync() : { miniProgram: { appId: '' } };
            var appID = accountInfo.miniProgram.appId;
            let params = {
                params: [
                    {
                        appID: appID
                    }
                ]
            };
            $http.post('urlDeault', this, {
                code: '02080001',
                data: params,
                success: function (res) {
                    if (res.errorCode == '0') {
                        console.log(res);
                        that.setData({
                            downLoadUrl: '',
                            upLoadUrl: '/uploadFile?fileType=consult'
                        });
                    }
                }
            });
        },

        /**
         * 长按
         */
        longClick: function (event) {
            const userData = Cache.get('userData');
            let { showCopyBtn } = this;
            let { item } = event.currentTarget.dataset;
            if (userData.userID != item.sendValue) {
                return;
            }
            if (item.picture) {
                showCopyBtn = false;
            } else {
                showCopyBtn = true;
            }

            // 计算弹层位置：以触摸点为锚，优先显示在上方，并做边界裁剪，避免溢出屏幕
            let pos = this._calcLongBtnPos(event, showCopyBtn);

            this.setData({
                showlongBtn: true,
                longBtnLeft: pos.left,
                longBtnTop: pos.top,
                copyText: item.consultText,
                clickMsgID: item.msgID,
                showCopyBtn: showCopyBtn
            });
        },

        /**
         * 计算长按弹层位置
         * @param {Object} event 长按事件
         * @param {Boolean} showCopyBtn 是否显示复制按钮（决定弹层高度）
         */
        _calcLongBtnPos: function (event, showCopyBtn) {
            const sys = uni.getSystemInfoSync();
            const vw = sys.windowWidth;
            const vh = sys.windowHeight;
            const ratio = vw / 750; // rpx -> px
            const popupW = 140 * ratio; // .longbtn 宽度 140rpx
            const items = showCopyBtn ? 2 : 1;
            const popupH = items * 80 * ratio + 2; // 每项 80rpx + 分割线
            const margin = 12; // 距屏幕边缘安全间距(px)

            // 触摸点坐标（多源兼容：H5 原生触摸 / uni longpress detail）
            let px = 0;
            let py = 0;
            const detail = event.detail || {};
            const changed = event.changedTouches && event.changedTouches[0];
            const touches = event.touches && event.touches[0];
            if (changed) {
                px = changed.clientX;
                py = changed.clientY;
            } else if (touches) {
                px = touches.clientX;
                py = touches.clientY;
            } else if (detail.x !== undefined && detail.x !== null) {
                px = detail.x;
                py = detail.y;
            }

            // 水平：以触摸点为中心，裁剪到屏幕内
            let left = px - popupW / 2;
            if (left < margin) {
                left = margin;
            }
            if (left + popupW > vw - margin) {
                left = vw - popupW - margin;
            }
            if (left < margin) {
                left = margin;
            }

            // 垂直：优先在触摸点上方，空间不足则下方，再裁剪到屏幕内
            let top = py - popupH - 10;
            if (top < margin) {
                top = py + 10; // 上方空间不足，改到下方
            }
            if (top + popupH > vh - margin) {
                top = vh - popupH - margin;
            }
            if (top < margin) {
                top = margin;
            }

            return { left: left, top: top };
        },

        /**
         * 关闭长按弹框
         * @param {*} event
         */
        closeLongBtn: function (event) {
            if (this.showlongBtn) {
                this.setData({
                    showlongBtn: false,
                    longBtnLeft: '0',
                    longBtnTop: '0'
                });
            }
        },

        /**
         * 复制文本
         * @param {*} event
         */
        copyTextFun: function (event) {
            let copyText = this.copyText;
            this.closeLongBtn();
            uni.setClipboardData({
                data: copyText,
                complete: function () {},
                success(res) {}
            });
        },

        /**
         * 撤回消息
         */
        cancelSend: function (event) {
            if (!this.ifOnLine()) {
                return;
            }
            this.closeLongBtn();
            let { clickMsgID } = this;
            const patientData = Cache.get('patientData');
            const userData = Cache.get('userData');
            let toUser = 'patient' + patientData.patID;
            let help = this.help;
            if (help) {
                toUser = 'userKF001';
                admID = '';
            }
            let data = {
                params: [
                    {
                        msgID: clickMsgID
                    }
                ]
            };
            let that = this;
            // 发送成功
            $http.post(
                'urlDeault',
                this,
                {
                    code: '02090008',
                    data: data,
                    success: function (res) {
                        let errorCode = res.errorCode;
                        if (errorCode == '0') {
                            that.getConsultRecord();
                            Toast.success('已撤回!');
                            let messageContent = {};
                            messageContent.userID = userData.userID;
                            messageContent.admID = patientData.admID;
                            messageContent.patID = patientData.patID;
                            messageContent.patName = patientData.patName;
                            messageContent.userName = userData.userName;
                            messageContent.type = 'M';
                            messageContent.text = '';
                            let message = app.globalData.timObj.createTextMessage({
                                to: toUser,
                                conversationType: TIM.TYPES.CONV_C2C,
                                payload: {
                                    text: JSON.stringify(messageContent)
                                }
                            });
                            // 2. 发送消息
                            let promise = app.globalData.timObj.sendMessage(message);
                            if (promise) {
                                promise
                                    .then((imResponse) => {})
                                    .catch(function (imError) {
                                        if (imError.code == '20003') {
                                        }
                                    });
                            }
                        } else {
                            uni.showToast({
                                title: res.errorMessage,
                                icon: 'none'
                            });
                        }
                    }
                },
                {
                    error: 'Y'
                }
            );
        },

        /**
         * 重新编辑
         */
        reEdit: function (event) {
            this.setData({
                messageValue: event.currentTarget.dataset.text
            });
        },

        // ==================== 语音录制 ====================

        /**
         * 初始化语音录制管理器
         */
        initVoiceRecorder: function () {
            const that = this;
            // #ifdef MP-WEIXIN
            this.recorderManager = uni.getRecorderManager();

            this.recorderManager.onStart(() => {
                console.log('录音开始');
                that.setData({ recording: true });
            });

            this.recorderManager.onStop((res) => {
                that.setData({ recording: false });
                if (that.recordCanceled) {
                    that.recordCanceled = false;
                    return;
                }
                // 参考 room.vue：用毫秒判断，>= 1000ms（1秒）即可
                if (res.duration < 1000) {
                    uni.showToast({ title: '录音时长太短', icon: 'none' });
                    return;
                }
                that.uploadVoiceFile(res.tempFilePath, res.duration / 1000);
            });

            this.recorderManager.onError((err) => {
                console.error('录音失败:', err);
                that.setData({ recording: false });
                uni.showToast({ title: '录音失败', icon: 'none' });
            });
            // #endif

            // #ifdef H5
            // H5 端录音由 wechath5 统一管理（JSSDK 优先，MediaRecorder 降级），
            // 录音权限已在 App.vue onLaunch 中通过 wechath5.config() + checkRecordApi() 注册，
            // 此处无需额外初始化。
            // #endif
        },

        /**
         * 切换文字/语音输入模式
         */
        switchInputMode: function () {
            this.setData({
                inputMode: this.inputMode === 'voice' ? 'text' : 'voice',
                messageFocus: false,
                moreBtn: false
            });
        },

        /**
         * H5 取消未完成的上次录音（解决快速连击问题）
         */
        _cancelPendingRecord: function () {
            if (this.h5StartRecordPromise) {
                try { wechath5.cancelRecord(); } catch (e) {}
                this.h5StartRecordPromise = null;
            }
            this._h5RecordingPhase = 'idle';
            this.setData({ recording: false });
        },

        /**
         * 开始录音
         */
        startVoiceRecord: function (e) {
            if (!this.ifOnLine()) {
                return;
            }
            const that = this;

            // #ifdef MP-WEIXIN
            const startRecording = () => {
                try {
                    that.recordCanceled = false;
                    // 参考 room.vue：去掉 duration 限制和多余参数，简化配置
                    that.recorderManager.start({
                        format: 'mp3',
                        sampleRate: 16000,
                        numberOfChannels: 1
                    });
                } catch (err) {
                    console.warn('[startVoiceRecord] 录音启动失败:', err);
                    uni.showToast({ title: '录音启动失败', icon: 'none' });
                }
            };
            uni.authorize({
                scope: 'scope.record',
                success: () => {
                    startRecording();
                },
                fail: () => {
                    uni.showToast({ title: '请授权录音权限', icon: 'none' });
                }
            });
            // #endif

            // #ifdef H5
            // H5 端：wechath5.startRecord() 内部 JSSDK 优先，失败降级 MediaRecorder
            console.log('[H5-录音] 开始录音, _recordApiReady:', wechath5._recordApiReady);

            // 防重入：如果上次录音 Promise 未完成，先取消（解决快速连击问题）
            if (that.h5StartRecordPromise) {
                console.warn('[H5-录音] 检测到未完成的录音启动 Promise，先取消');
                that._cancelPendingRecord();
            }

            // 标记"启动中"，按钮显示按下状态
            that._h5RecordingPhase = 'starting';
            that.setData({ recording: true });
            that.h5StartRecordPromise = wechath5.startRecord();
            that.h5StartRecordPromise.then(() => {
                console.log('[H5-录音] 录音已开始');
                // 防竞态：若期间已被 cancel/stop，不要把状态改回 recording
                if (that._h5RecordingPhase === 'starting') {
                    that._h5RecordingPhase = 'recording';
                }
            }).catch((err) => {
                console.error('[H5-录音] ❌ 录音启动失败:', err);
                that._h5RecordingPhase = 'idle';
                that.setData({ recording: false });
                that.h5StartRecordPromise = null;
                if (err && (err.showModal || (err.message && err.message.indexOf('权限') !== -1))) {
                    uni.showModal({
                        title: '无法使用录音',
                        content: (err.message || '录音启动失败') + '\n\n如果是首次使用，请点击麦克风图标允许权限。\n如已拒绝过，请到浏览器/微信"设置-隐私-麦克风"中开启。',
                        confirmText: '我知道了',
                        showCancel: false
                    });
                } else {
                    uni.showToast({ title: (err && err.message) || '录音启动失败', icon: 'none', duration: 2500 });
                }
            });
            // #endif
        },

        /**
         * 停止录音
         */
        stopVoiceRecord: function () {
            const that = this;
            // #ifdef MP-WEIXIN
            if (this.recorderManager) {
                this.recorderManager.stop();
            }
            // #endif
            // #ifdef H5
            // 用 phase 状态防止竞态（touchend/touchcancel 同时触发）
            if (that._h5RecordingPhase === 'idle' || that._h5RecordingPhase === 'stopping') {
                console.warn('[H5-录音] 非录音状态，跳过 stopVoiceRecord, phase:', that._h5RecordingPhase);
                return;
            }
            // 标记 stopping 防止重复进入
            that._h5RecordingPhase = 'stopping';
            that.setData({ recording: false });
            const startPromise = that.h5StartRecordPromise || Promise.resolve();
            startPromise.then(() => {
                return wechath5.stopRecord();
            }).then((recordResult) => {
                that._h5RecordingPhase = 'idle';
                that.h5StartRecordPromise = null;
                console.log('recordResult:', recordResult);
                if (recordResult.type === 'jssdk') {
                    // JSSDK 路径：serverId 已上传微信服务器，直接发送
                    console.log('[H5-录音] JSSDK 路径, serverId:', recordResult.serverId, 'duration:', recordResult.duration);
                    // 添加 loading 占位，后续由 handleSendVoice 替换
                    that.addAudioLoadingPlaceholder(recordResult.duration);
                    that.handleSendVoice(recordResult.serverId, recordResult.duration);
                } else {
                    // MediaRecorder 路径：Blob → wechath5.uploadVoice → filePath
                    console.log('[H5-录音] MediaRecorder 路径, blob size:', recordResult.blob.size, 'duration:', recordResult.duration);
                    // 添加 loading 占位
                    that.addAudioLoadingPlaceholder(recordResult.duration);
                    wechath5.uploadVoice(recordResult.blob, recordResult.duration, 'consult').then((uploadRes) => {
                        console.log('[H5-录音] uploadVoice 成功, filePath:', uploadRes.filePath);
                        that.handleSendVoice(uploadRes.filePath, recordResult.duration);
                    }).catch((err) => {
                        console.error('[H5-录音] ❌ uploadVoice 失败:', err);
                        that.deleteAudioLoading();
                        uni.showToast({ title: (err && err.message) || '语音上传失败', icon: 'none' });
                    });
                }
            }).catch((err) => {
                console.error('[H5-录音] ❌ 停止录音失败:', err);
                that._h5RecordingPhase = 'idle';
                that.h5StartRecordPromise = null;
                // 区分 JSSDK uploadVoice 失败（需删除 loading）和 stopRecord 失败（无 loading）
                if (err && err.message && err.message.indexOf('语音上传') !== -1) {
                    that.deleteAudioLoading();
                }
                uni.showToast({ title: (err && err.message) || '停止录音失败', icon: 'none' });
            });
            // #endif
        },

        /**
         * touchcancel 容错处理
         * 手机上 touchcancel 多为系统打断（页面滚动/来通知/手势冲突），并非用户主动取消，
         * 因此按"松开停止并发送"处理，避免丢失用户录音。
         */
        onRecordTouchCancel: function () {
            const that = this;
            // #ifdef H5
            // 复用 stopVoiceRecord：它内部有 phase 防重入，可安全调用
            // 只有在确实处于录音阶段才处理，避免误触发
            if (that._h5RecordingPhase === 'recording' || that._h5RecordingPhase === 'starting') {
                console.log('[H5-录音] touchcancel 触发，按停止发送处理');
                that.stopVoiceRecord();
            }
            // #endif
            // #ifdef MP-WEIXIN
            // 小程序端 touchcancel 也按停止处理（保留录音，不丢弃）
            if (that.recording && that.recorderManager) {
                that.stopVoiceRecord();
            }
            // #endif
        },

        /**
         * 取消录音（仅用于明确的"主动取消"场景，如上滑取消；
         * 系统级 touchcancel 已改由 onRecordTouchCancel 处理）
         */
        cancelVoiceRecord: function () {
            const that = this;
            // H5 端：用 phase 状态防止竞态，stopping 防重复取消
            if (that._h5RecordingPhase === 'idle' || that._h5RecordingPhase === 'stopping') return;
            // #ifdef MP-WEIXIN
            if (!this.recording) return;
            this.recordCanceled = true;
            if (this.recorderManager) {
                this.recorderManager.stop();
            }
            // #endif
            // #ifdef H5
            that._h5RecordingPhase = 'stopping';
            const startPromise = that.h5StartRecordPromise || Promise.resolve();
            startPromise.then(() => {
                wechath5.cancelRecord();
                that._h5RecordingPhase = 'idle';
            }).catch(() => {
                wechath5.cancelRecord();
                that._h5RecordingPhase = 'idle';
            });
            that.h5StartRecordPromise = null;
            // #endif
            this.setData({ recording: false });
        },

        /**
         * 上传语音文件（MP 端）
         */
        uploadVoiceFile: function (filePath, duration) {
            const that = this;
            // 添加 loading 占位
            that.addAudioLoadingPlaceholder(duration);

            uni.uploadFile({
                url: that.upLoadUrl,
                filePath: filePath,
                name: 'file',
                success: (res) => {
                    if (res.statusCode === 200) {
                        try {
                            const voiceName = JSON.parse(res.data).filePath;
                            that.handleSendVoice(voiceName, duration);
                        } catch (e) {
                            that.deleteAudioLoading();
                            uni.showToast({ title: '语音上传失败', icon: 'none' });
                        }
                    } else {
                        that.deleteAudioLoading();
                        uni.showToast({ title: '语音发送失败', icon: 'none' });
                    }
                },
                fail: () => {
                    that.deleteAudioLoading();
                    uni.showToast({ title: '语音上传失败', icon: 'none' });
                }
            });
        },

        /**
         * 发送语音消息（参考 room.vue uploadAudioByServerId + handleSendAudio）
         */
        handleSendVoice: function (voiceName, duration) {
            console.log('===== 语音消息发送开始 =====');
            console.log('[语音] voiceName:', voiceName, 'duration:', duration);
            const patientData = Cache.get('patientData');
            const userData = Cache.get('userData');
            let { admType, help } = this;
            let admID = patientData.admID;
            let toUser = 'patient' + patientData.patID;
            if (help) {
                toUser = 'userKF001';
                admID = '';
            }

            // 参考 room.vue：统一使用 sound 字段
            let data = {
                params: [{
                    admID: admID,
                    toUser: toUser,
                    consultText: '',
                    sound: voiceName,
                    soundDuration: duration,
                    frUser: this.fromMessage ? 'userKF001' : 'user' + userData.userID
                }]
            };
            console.log('[语音] 请求参数:', JSON.stringify(data));

            const that = this;
            $http.post('urlDeault', this, {
                code: '02090004',
                data: data,
                success: function (res) {
                    console.log('===== 语音消息响应 =====');
                    console.log('[语音] 完整响应:', JSON.stringify(res));
                    if (res.errorCode == '0') {
                        // 替换 loading 占位
                        let soundPath = res.result && res.result.sound ? res.result.sound : voiceName;
                        let obj = {
                            sound: soundPath,
                            soundDuration: duration,
                            direction: 'right',
                            msgType: '1',
                            isWithdraw: true,
                            key: that.messageList.length,
                            msgID: res.result && res.result.msgID ? res.result.msgID : '',
                            mediaType: 'audio'
                        };
                        that.setData({
                            messageList: that.replaceAudioLoading([...that.messageList], obj)
                        }, () => {
                            that.getBodyHeight();
                            // 滚动到底部
                            var len = that.messageList.length;
                            if (len > 0) {
                                var last = that.messageList[len - 1];
                                that.setData({ scrollToMessage: 'message' + (last.msgID || ('load' + len)) });
                            }
                        });
                        if (admType != 'video') { that.arriveAdm(); }
                        that.showTopToast('发送成功');

                        // 发送 IM 实时消息通知
                        let timObj = app && app.globalData && app.globalData.timObj;
                        if (timObj) {
                            try {
                                let messageContent = {};
                                messageContent.userID = userData.userID;
                                messageContent.admID = patientData.admID;
                                messageContent.patID = patientData.patID;
                                messageContent.patName = patientData.patName;
                                messageContent.userName = userData.userName;
                                messageContent.type = 'M';
                                messageContent.text = '';
                                messageContent.sound = soundPath;
                                messageContent.soundDuration = duration;

                                let message = timObj.createTextMessage({
                                    to: toUser,
                                    conversationType: TIM.TYPES.CONV_C2C,
                                    payload: {
                                        text: JSON.stringify(messageContent)
                                    }
                                });
                                let promise = timObj.sendMessage(message);
                                if (promise) {
                                    promise.then(function () {
                                        console.log('[IM-语音] ✅ 发送成功');
                                    }).catch(function (imError) {
                                        console.warn('[handleSendVoice] IM 消息发送失败:', imError);
                                    });
                                }
                            } catch (e) {
                                console.warn('[handleSendVoice] IM 消息构造失败:', e.message);
                            }
                        }
                    } else {
                        that.deleteAudioLoading();
                        uni.showToast({ title: res.errorMessage || '发送失败', icon: 'none' });
                    }
                },
                error: function (err) {
                    console.error('[语音] ❌ 请求失败:', err);
                    that.deleteAudioLoading();
                    uni.showToast({ title: '网络异常', icon: 'none' });
                }
            });
        },

        /**
         * 添加音频 loading 占位
         */
        addAudioLoadingPlaceholder: function (duration) {
            var len = this.messageList && this.messageList.length ? this.messageList.length : 0;
            var obj = {
                soundLoading: true,
                direction: 'right',
                msgType: '1',
                isWithdraw: true,
                sendStatus: 'sending',
                key: len + 1,
                msgID: 'audioLoading' + (len + 1),
                mediaType: 'audio',
                soundDuration: duration || 0
            };
            this.setData({
                messageList: [...this.messageList, obj]
            }, () => {
                this.getBodyHeight();
                var last = this.messageList[this.messageList.length - 1];
                this.setData({ scrollToMessage: 'message' + (last.msgID || '') });
            });
        },

        /**
         * 删除音频 loading 占位
         */
        deleteAudioLoading: function () {
            if (this.messageList && this.messageList.length > 1) {
                this.setData({
                    messageList: this.replaceAudioLoading([...this.messageList], {})
                });
            }
        },

        /**
         * 替换音频 loading 占位
         */
        replaceAudioLoading: function (arr, obj) {
            var newArr = JSON.parse(JSON.stringify(arr));
            if (obj && obj.key) {
                var loadingId = 'audioLoading' + obj.key;
                newArr = newArr.map(function (item) {
                    return item.msgID === loadingId ? obj : item;
                });
                return newArr;
            } else {
                var key = newArr[newArr.length - 1].key;
                return newArr.filter(function (item) {
                    return item.key !== key;
                });
            }
        },

        // ==================== 语音播放 ====================

        /**
         * 获取语音完整 URL（支持 sound 和 voice 字段）
         */
        getFullVoiceUrl: function (voice) {
            if (!voice) return '';
            if (/^(https?:)?\/\//i.test(voice) || voice.startsWith('data:')) {
                return voice;
            }
            // JSSDK serverId：不包含路径分隔符，直接返回原值（由 wechath5.playVoice 走 JSSDK 下载播放）
            if (voice.indexOf('/') === -1) {
                return voice;
            }
            if (this.downLoadUrl) {
                return this.downLoadUrl + voice;
            }
            // H5 端兜底：downLoadUrl 为空时，用 consultFile 拼接完整 URL
            const userData = Cache.get('userData');
            const origin = typeof window !== 'undefined' && window.location
                ? window.location.origin : '';
            if (userData && userData.consultFile) {
                const base = userData.consultFile.indexOf('http') !== -1
                    ? userData.consultFile
                    : origin + userData.consultFile;
                return base + voice;
            }
            return origin + '/images/consult/' + voice;
        },

        /**
         * 格式化音频时长
         */
        formatAudioDuration: function (duration) {
            if (!duration && duration !== 0) return '0';
            var d = Number(duration);
            var minutes = Math.floor(d / 60);
            var seconds = Math.floor(d % 60);
            if (minutes > 0) {
                return minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
            }
            return String(seconds);
        },

        /**
         * 播放/停止语音（参考 room.vue 的 playAudio，支持 sound 字段）
         */
        playAudio: function (e) {
            var item = e.currentTarget.dataset.page;
            // 兼容 sound 和 voice 两个字段名
            var sound = item.sound || item.voice;
            var msgID = item.msgID;
            var that = this;

            if (!sound) return;

            var fullUrl = this.getFullVoiceUrl(sound);

            // 如果正在播放同一条，则停止
            if (this.playingAudioId === msgID) {
                // #ifdef MP-WEIXIN
                if (this.innerAudioContext) {
                    this.innerAudioContext.stop();
                }
                // #endif
                // #ifdef H5
                // H5 端：wechath5.playVoice 内部管理播放状态，重置图标即可
                // #endif
                this.setData({ playingAudioId: '' });
                return;
            }

            // #ifdef MP-WEIXIN
            if (!this.innerAudioContext) {
                this.innerAudioContext = uni.createInnerAudioContext();
                this.innerAudioContext.obeyMuteSwitch = false;
            }
            // 清理旧的 listener 避免重复触发
            this.innerAudioContext.offEnded();
            this.innerAudioContext.offError();
            this.innerAudioContext.stop();
            this.innerAudioContext.src = fullUrl;
            this.innerAudioContext.play();
            this.setData({ playingAudioId: msgID });
            this.innerAudioContext.onEnded(function () {
                console.log('音频播放结束');
                that.setData({ playingAudioId: '' });
            });
            this.innerAudioContext.onError(function (err) {
                console.error('音频播放失败:', err);
                uni.showToast({ title: '音频播放失败', icon: 'none' });
                that.setData({ playingAudioId: '' });
            });
            // #endif

            // #ifdef H5
            // H5 端：wechath5.playVoice 自动区分 serverId（JSSDK downloadVoice→playVoice）与 http URL（InnerAudioContext）
            this.setData({ playingAudioId: msgID });
            wechath5.playVoice(sound, {
                onEnded: function () {
                    console.log('音频播放结束');
                    that.setData({ playingAudioId: '' });
                },
                onError: function () {
                    console.log('音频播放失败');
                    that.setData({ playingAudioId: '' });
                    uni.showToast({ title: '播放失败', icon: 'none' });
                }
            });
            // #endif
        },

        // ==================== 视频选择与发送 ====================

        /**
         * 选择视频
         */
        onChooseVideo: function () {
            if (!this.ifOnLine()) {
                return;
            }
            const that = this;

            uni.chooseVideo({
                sourceType: ['album', 'camera'],
                maxDuration: 60,
                camera: 'back',
                compressed: true,
                success: (res) => {
                    const duration = Math.ceil(res.duration);
                    let { messageList } = that;
                    let obj = {
                        videoLoading: true,
                        direction: 'right',
                        msgType: '1',
                        isWithdraw: true,
                        key: messageList.length + 1,
                        msgID: 'loading' + (messageList.length + 1)
                    };
                    messageList.push(obj);
                    that.setData({ messageList }, () => {
                        that.getBodyHeight();
                        that.uploadVideoFile(res.tempFilePath, duration);
                    });
                },
                fail: () => {
                    // 用户取消选择
                }
            });
        },

        /**
         * 上传视频文件
         */
        uploadVideoFile: function (filePath, duration) {
            const that = this;
            if (!that.upLoadUrl) {
                uni.showToast({ title: '上传地址未就绪，请稍后再试', icon: 'none' });
                that.hideVideoLoading();
                return;
            }
            uni.showLoading({ title: '视频上传中...' });
            uni.uploadFile({
                url: that.upLoadUrl,
                filePath: filePath,
                name: 'file',
                success: (res) => {
                    uni.hideLoading();
                    if (res.statusCode === 200) {
                        try {
                            const videoName = JSON.parse(res.data).filePath;
                            that.handleSendVideo(videoName, duration);
                        } catch (e) {
                            console.warn('[uploadVideoFile] 解析上传响应失败:', e, res.data);
                            uni.showToast({ title: '视频发送失败', icon: 'none' });
                            that.hideVideoLoading();
                        }
                    } else {
                        uni.showToast({ title: '视频发送失败', icon: 'none' });
                        that.hideVideoLoading();
                    }
                },
                fail: (err) => {
                    uni.hideLoading();
                    console.warn('[uploadVideoFile] 上传失败:', err);
                    uni.showToast({ title: '视频上传失败', icon: 'none' });
                    that.hideVideoLoading();
                }
            });
        },

        /**
         * 移除视频上传 loading 占位
         */
        hideVideoLoading: function () {
            let { messageList } = this;
            messageList = messageList.filter(item => !item.videoLoading);
            this.setData({ messageList });
        },

        /**
         * 发送视频消息
         */
        handleSendVideo: function (videoName, duration) {
            const patientData = Cache.get('patientData');
            const userData = Cache.get('userData');
            let { admType, help } = this;
            let admID = patientData.admID;
            let toUser = 'patient' + patientData.patID;
            if (help) {
                toUser = 'userKF001';
                admID = '';
            }

            let data = {
                params: [{
                    admID: admID,
                    toUser: toUser,
                    consultText: '',
                    video: videoName,
                    videoDuration: duration,
                    frUser: this.fromMessage ? 'userKF001' : 'user' + userData.userID
                }]
            };

            const that = this;
            $http.post('urlDeault', that, {
                code: '02090004',
                data: data,
                success: function (res) {
                    if (res.errorCode == '0') {
                        that.getConsultRecord();
                        if (admType != 'video') { that.arriveAdm(); }
                        that.hideVideoLoading();
                        that.showTopToast('发送成功');

                        // 发送 IM 实时消息通知（H5 下 TIM 可能未初始化，需安全处理）
                        let timObj = app && app.globalData && app.globalData.timObj;
                        if (timObj) {
                            try {
                                let messageContent = {};
                                messageContent.userID = userData.userID;
                                messageContent.admID = patientData.admID;
                                messageContent.patID = patientData.patID;
                                messageContent.patName = patientData.patName;
                                messageContent.userName = userData.userName;
                                messageContent.type = 'M';
                                messageContent.text = '';
                                messageContent.video = videoName;
                                messageContent.videoDuration = duration;

                                let message = timObj.createTextMessage({
                                    to: toUser,
                                    conversationType: TIM.TYPES.CONV_C2C,
                                    payload: { text: JSON.stringify(messageContent) }
                                });
                                timObj.sendMessage(message)
                                    .then(() => {})
                                    .catch((imError) => {
                                        console.warn('[handleSendVideo] IM 消息发送失败:', imError);
                                    });
                            } catch (e) {
                                console.warn('[handleSendVideo] IM 消息构造失败:', e.message);
                            }
                        }
                    } else {
                        uni.showToast({ title: res.errorMessage, icon: 'none' });
                        that.hideVideoLoading();
                    }
                },
                fail: function () {
                    that.hideVideoLoading();
                }
            });
        },

        // ==================== 视频播放 ====================

        /**
         * 获取视频完整 URL
         */
        getFullVideoUrl: function (video) {
            if (!video) return '';
            if (/^(https?:)?\/\//i.test(video) || video.startsWith('data:')) {
                return video;
            }
            if (this.downLoadUrl) {
                return this.downLoadUrl + video;
            }
            // H5 端兜底：downLoadUrl 为空时，用 consultFile 拼接完整 URL
            const userData = Cache.get('userData');
            const origin = typeof window !== 'undefined' && window.location
                ? window.location.origin : '';
            if (userData && userData.consultFile) {
                const base = userData.consultFile.indexOf('http') !== -1
                    ? userData.consultFile
                    : origin + userData.consultFile;
                return base + video;
            }
            return origin + '/images/consult/' + video;
        },

        /**
         * 播放视频
         */
        playVideo: function (e) {
            const url = e.currentTarget.dataset.url;
            const fullUrl = this.getFullVideoUrl(url);
            if (!fullUrl) {
                uni.showToast({ title: '视频地址无效', icon: 'none' });
                return;
            }
            // #ifdef MP-WEIXIN
            uni.previewMedia({
                sources: [{ url: fullUrl, type: 'video' }],
                current: 0,
                success: () => {},
                fail: () => {
                    uni.showToast({ title: '视频播放失败', icon: 'none' });
                }
            });
            // #endif
            // #ifndef MP-WEIXIN
            // H5 环境下 uni.previewMedia 不可用，使用自定义弹窗播放
            this.setData({
                showVideoPreview: true,
                videoPreviewUrl: fullUrl
            });
            // #endif
        },

        /**
         * 关闭视频播放弹窗
         */
        closeVideoPreview: function () {
            this.setData({
                showVideoPreview: false,
                videoPreviewUrl: ''
            });
        },

        /**
         * 视频播放出错
         */
        onVideoPreviewError: function (e) {
            console.warn('[playVideo] 视频播放失败:', e);
            uni.showToast({ title: '视频播放失败', icon: 'none' });
            this.closeVideoPreview();
        }
    }
};
</script>
<style>
/* ============== 顶部 Toast ============== */
/* 右上角"结束就诊"按钮：垂直居中于导航栏，右侧对齐 */
.nav-end-visit {
    position: fixed;
    top: 0;
    right: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding-right: 24rpx;
    z-index: 9999999;
}

.nav-end-visit-text {
    font-size: 28rpx;
    color: #f56c6c;
    font-weight: 500;
}

.nav-end-visit:active .nav-end-visit-text {
    opacity: 0.6;
}

.top-toast {
    position: fixed;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(0, 0, 0, 0.75);
    color: #fff;
    font-size: 28rpx;
    padding: 16rpx 48rpx;
    border-radius: 8rpx;
    z-index: 9999;
    white-space: nowrap;
    animation: toastFadeIn 0.3s ease;
}

@keyframes toastFadeIn {
    from {
        opacity: 0;
        transform: translateX(-50%) translateY(-20rpx);
    }
    to {
        opacity: 1;
        transform: translateX(-50%) translateY(0);
    }
}

/* ============== 全局 ============== */
page {
    width: 100%;
    height: 100%;
    overflow: hidden;
    background-color: #f4f6fa;
}

.treatment {
    width: 100%;
    height: calc(100% - 5px);
    background: #f4f6fa;
    padding-top: 5px;
    box-sizing: border-box;
    overflow: hidden;
    position: relative;
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.top-section {
    flex-shrink: 0;
}

.treatment-graphic .top-section {
    padding-top: 10rpx;
}

/* ============== 顶部黄色提示条 ============== */
.tips {
    margin: 12rpx 24rpx;
    padding: 16rpx 20rpx;
    background: #fff7e6;
    border-radius: 12rpx;
    border-left: 6rpx solid #fa8c16;
    font-size: 26rpx;
    height: auto;
    line-height: 1.4;
    overflow: hidden;
}

.tipsClose {
    display: none;
}

.tipsimg {
    float: left;
    width: 32rpx;
    height: 32rpx;
    margin-top: 4rpx;
}

.tipstext {
    float: left;
    padding-left: 12rpx;
    color: #874d00;
    line-height: 1.4;
}

.closeimg {
    width: 28rpx;
    height: 28rpx;
    margin-top: 4rpx;
    margin-left: 12rpx;
    border: none;
    float: right;
}

/* ============== 顶部功能按钮区 ============== */
.topBtn {
    font-size: 26rpx;
    height: auto;
    line-height: 1.4;
    background: #ffffff;
    margin: 0 24rpx 16rpx;
    padding: 12rpx 8rpx;
    border-radius: 16rpx;
    box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
    overflow: hidden;
    display: flex;
    flex-direction: row;
    align-items: center;
}

.topBtn.record {
    color: #f56c6c;
}

.topBtn .button,
.topBtn .col-4 {
    flex: 1;
    width: auto !important;
    background: transparent;
    color: #1e90ff;
    border: none !important;
    border-radius: 0;
    margin: 0;
    padding: 0 8rpx;
    height: 64rpx;
    line-height: 64rpx;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 26rpx;
    position: relative;
    box-sizing: border-box;
}

.topBtn .button::after,
.topBtn .col-4::after {
    border: none;
}

.topBtn .button:not(:last-child)::before,
.topBtn .col-4:not(:last-child)::before {
    content: '';
    position: absolute;
    right: 0;
    top: 14rpx;
    bottom: 14rpx;
    width: 1rpx;
    background: #eef0f3;
}

.topBtn .button image,
.topBtn .col-4 image {
    width: 36rpx;
    height: 36rpx;
    margin: 0;
    float: none;
}

.topBtn .button > view,
.topBtn .col-4 > view {
    padding-left: 12rpx !important;
}

.topBtn .pbutton {
    flex: 1;
    width: auto;
    margin: 0 8rpx;
    padding: 0 24rpx;
    height: 72rpx;
    line-height: 72rpx;
    background: #1e90ff;
    color: #ffffff;
    border: none;
    border-radius: 36rpx;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 28rpx;
}

.treatment-graphic .topBtn {
    margin: 0 24rpx 14rpx;
    padding: 10rpx;
    border-radius: 18rpx;
    background: #ffffff;
    box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
}

/* 图文端：白色底 + 红色文字 + 红色图标，和视频端"结束服务"风格一致 */
.graphic-topBtn {
    justify-content: center;
}

.graphic-btn {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 72rpx;
    line-height: 72rpx;
    border-radius: 12rpx;
    font-size: 28rpx;
    font-weight: 500;
    color: #f56c6c;
    background: #ffffff;
    border: 1rpx solid #fde2e2;
    box-sizing: border-box;
}

.graphic-btn image {
    width: 36rpx;
    height: 36rpx;
    margin-right: 12rpx;
    float: none;
}

.graphic-btn-finish {
    color: #f56c6c;
    border-color: #fde2e2;
}

.graphic-btn-finish:active {
    background: #fef0f0;
}

.topBtn .pbutton image {
    width: 36rpx;
    height: 36rpx;
    margin-right: 12rpx;
    float: none;
}

/* ============== 主体（flex 布局，自动分配高度） ============== */
.body-master,
.body,
.body-notips,
.body-bottom,
.body-onlybottom,
.body-bottom-tips {
    flex: 1;
    min-height: 0;
    width: 100%;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.chartRoom {
    flex: 1;
    min-height: 0;
    width: 100%;
}

.chatContent {
    height: 100%;
    width: 100%;
    overflow-y: auto;
    padding: 8rpx 16rpx;
    box-sizing: border-box;
}

.treatment-graphic .chatContent {
    padding: 12rpx 24rpx 10rpx;
    background: #f4f6fa;
}

.treatment-graphic .chatContent::-webkit-scrollbar,
.treatment-graphic .chatList::-webkit-scrollbar {
    width: 0;
    height: 0;
}

.chatList {
    width: 100%;
    height: 100%;
}

/* ============== 输入栏（高特异性锁定 flex 布局，安全区独立处理） ============== */
.treatment .chatbottom {
    display: flex !important;
    flex-direction: row;
    flex-wrap: nowrap;
    box-sizing: border-box;
    min-height: 88rpx !important;
    width: 100%;
    flex-shrink: 0;
    border-top: 1rpx solid #eef0f3;
    background: #ffffff;
    padding: 8rpx 12rpx calc(8rpx + env(safe-area-inset-bottom));
    align-items: center;
    gap: 10rpx;
}

.treatment-graphic .chatbottom {
    min-height: 96rpx !important;
    padding: 10rpx 16rpx calc(10rpx + env(safe-area-inset-bottom));
    box-shadow: 0 -4rpx 16rpx rgba(15, 23, 42, 0.04);
}

.audioicon {
    width: 50rpx;
    flex-shrink: 0;
    position: relative;
    height: 72rpx;
    line-height: 72rpx;
}

.treatment .chatbottom .inputview {
    flex: 1 1 0;
    min-width: 0;
    min-height: 72rpx;
    line-height: 72rpx;
    position: relative;
    background: #f4f6fa;
    margin: 0;
    padding: 0 20rpx;
    border-radius: 16rpx;
    box-sizing: border-box;
    float: none;
    width: auto;
}

.treatment-graphic .chatbottom .inputview {
    min-height: 72rpx;
    border-radius: 18rpx;
    background: #f2f5fa;
}

.input {
    height: 72rpx;
    line-height: 72rpx;
    font-size: 30rpx;
    background: transparent;
    box-sizing: border-box;
}

.inputTrea {
    width: 100%;
    line-height: 50rpx;
    overflow: auto;
}

.treatment .chatbottom .sendbtn {
    height: 64rpx !important;
    line-height: 64rpx !important;
    flex: 0 0 auto;
    flex-shrink: 0;
    position: relative;
    font-size: 26rpx;
    text-align: center;
    border-radius: 32rpx !important;
    color: #fff;
    background: #0079fe;
    border: 0;
    padding: 0 20rpx;
    margin: 0;
    min-width: 96rpx;
    width: auto;
    float: none;
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    white-space: nowrap;
}

.treatment-graphic .chatbottom .sendbtn {
    min-width: 104rpx;
    background: #1687ff;
}

.treatment .chatbottom .sendbtn::after {
    border: none;
}

.treatment .chatbottom .sendbtn[disabled] {
    opacity: 0.65;
    color: #fff;
    background: #7dbbff;
}

.treatment .chatbottom .other {
    width: 64rpx;
    flex: 0 0 64rpx;
    flex-shrink: 0;
    position: relative;
    height: 64rpx;
    line-height: 64rpx;
    text-align: center;
    float: none;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: #f4f7ff;
}

.treatment-graphic .chatbottom .other {
    background: #edf4ff;
}

.other-icon {
    display: block;
    width: 64rpx;
    height: 64rpx;
    line-height: 60rpx;
    color: #0079fe;
    font-size: 44rpx;
    font-weight: 400;
    text-align: center;
}

/* ============== 底部 5 个图标（紧凑自适应，锁定 flex 布局） ============== */
.treatment .bottomView {
    width: 100%;
    flex-shrink: 0;
    min-height: 100rpx;
    border: none;
    border-top: 1rpx solid #eef0f3;
    text-align: center;
    background: #ffffff;
    padding: 6rpx 0 calc(6rpx + env(safe-area-inset-bottom));
    box-sizing: border-box;
    display: flex !important;
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: space-around;
}

.treatment-graphic .bottomView {
    min-height: 112rpx;
    box-shadow: 0 -4rpx 16rpx rgba(15, 23, 42, 0.04);
}

.bottomimg {
    width: 28rpx;
    height: 28rpx;
    padding: 0;
}

.treatment .bottomView .bottomIcon {
    flex: 1 1 0;
    min-width: 0;
    width: auto;
    height: 88rpx;
    font-size: 22rpx;
    float: none;
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 0;
}

.iconview {
    width: 52rpx;
    height: 52rpx;
    background: #f4f6fa;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0;
}

.treatment-graphic .iconview {
    width: 58rpx;
    height: 58rpx;
    background: #f2f5fa;
}

.icontext {
    margin-top: 2rpx;
    padding-top: 0;
    font-size: 20rpx;
    color: #4a5160;
    line-height: 1.2;
}

/* ============== 患者主诉卡片 ============== */
.patState {
    background: #ffffff;
    border-radius: 16rpx;
    display: block;
    max-width: 70vw;
    margin-left: 16rpx;
    box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
    overflow: hidden;
}

.treatment-graphic .patState {
    max-width: 76vw;
    border-radius: 14rpx;
    box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.05);
    background: #ffffff;
    overflow: hidden;
}

.lookDetail {
    height: 64rpx;
    line-height: 64rpx;
    width: 100%;
    padding: 0 20rpx;
    border-top: 1rpx solid #f0f2f5;
    box-sizing: border-box;
    font-size: 26rpx;
    font-weight: 500;
    color: #1e90ff;
}

/* ============== 消息气泡 ============== */
.mw-content {
    margin: 16rpx 12rpx;
    display: flex;
    align-items: flex-start;
    font-size: 30rpx;
}

.treatment-graphic .mw-content {
    margin: 12rpx 0;
}

.mw-message {
    width: calc(100% - 24rpx);
}

.mw-content .col-1 {
    height: 80rpx;
    width: 80rpx;
    flex-shrink: 0;
    background-color: #5cadff;
    border-radius: 50%;
    text-align: center;
    line-height: 80rpx;
    font-size: 28rpx;
    color: #fff;
    white-space: nowrap;
    overflow: hidden;
}

.mw-content .col-1 image {
    width: 80rpx;
    height: 80rpx;
    border-radius: 50%;
}

.mw-content .col-1 .avatar-fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 80rpx;
    height: 80rpx;
    border-radius: 50%;
    color: #ffffff;
    font-size: 32rpx;
    font-weight: 500;
    line-height: 1;
}

.mw-content .col-2 {
    position: relative;
    margin-top: 8rpx;
}

.mw-content .text {
    max-width: 70vw;
}

.timespan {
    display: inline-block;
    padding: 4rpx 16rpx;
    border-radius: 16rpx;
    margin-top: 8rpx;
    color: #8a93a0;
    background-color: #eef0f3;
    font-size: 22rpx;
}

.mw-content .col-2 .icon-juxingbeifen {
    position: absolute;
    right: 0;
    top: -2rpx;
    color: #5cadff;
    display: inline-block;
    width: 32rpx;
    height: 32rpx;
}

.mw-content .col-2 .icon-juxingbeifen.icon-color-fff {
    color: #fff;
}

.mw-content .col-2 .icon-juxingbeifen.icon-left {
    left: 0;
    top: 0;
    transform: rotate(90deg);
}

.mw-content-right {
    flex-direction: row-reverse;
}

.mw-content-content {
    text-align: center;
    margin: 12rpx 0;
    font-size: 22rpx;
}

.mw-content-content .col-3 text {
    display: inline-block;
    padding: 6rpx 20rpx;
    border-radius: 16rpx;
    background-color: #eef0f3;
    margin: 8rpx 0;
    color: #8a93a0;
    font-size: 22rpx;
}

.mw-content-content .re-edit {
    color: #1e90ff;
    margin-left: 12rpx;
    font-weight: normal;
}

.message-content {
    display: inline-block;
    outline: none;
    font-size: 30rpx;
    position: relative;
    word-wrap: break-word;
    word-break: break-all;
    padding: 16rpx 20rpx;
    box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
    max-width: 70vw;
    line-height: 1.5;
}

.treatment-graphic .message-content {
    border-radius: 14rpx;
    padding: 16rpx 20rpx;
    box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.05);
}

.message-content .image {
    min-width: 240rpx;
    max-width: 320rpx;
    max-height: 600rpx;
}

.message-content .img-loading {
    text-align: center;
    line-height: 240rpx;
}

.message-received {
    background-color: #ffffff;
    margin-left: 16rpx;
    border-radius: 4rpx 16rpx 16rpx 16rpx;
    text-align: left;
}

.treatment-graphic .message-received {
    border-radius: 6rpx 14rpx 14rpx 14rpx;
}

.message-send {
    background-color: #1e90ff;
    margin-right: 16rpx;
    border-radius: 16rpx 4rpx 16rpx 16rpx;
    color: #ffffff;
}

.treatment-graphic .message-send {
    background-color: #1687ff;
    border-radius: 14rpx 6rpx 14rpx 14rpx;
}

.message-send.background-color-fff {
    background-color: #ffffff;
    color: inherit;
}

/* ============== 邀请医生 / 弹层 ============== */
.doclist {
    width: 90vw;
}

/* .title {
    width: 100%;
    height: 96rpx;
    line-height: 96rpx;
    text-align: center;
    font-size: 30rpx;
    font-weight: 500;
    border-bottom: 1rpx solid #eef0f3;
} */

.docitem {
    height: 96rpx;
    line-height: 96rpx;
    border-bottom: 1rpx solid #f0f2f5;
    padding: 0 32rpx;
    font-size: 28rpx;
}

.weui-dialog {
    top: 40% !important;
}

/* ============== 长按弹层 ============== */
.longView {
    height: 100vh;
    width: 100vw;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 888;
}

.longbtn {
    width: 140rpx;
    position: absolute;
    z-index: 9999;
    background: rgba(38, 38, 42, 0.94);
    text-align: center;
    border-radius: 16rpx;
    box-shadow: 0 8rpx 28rpx rgba(0, 0, 0, 0.28);
    overflow: hidden;
    transform-origin: center top;
    animation: longbtn-in 0.16s ease-out both;
}

@keyframes longbtn-in {
    from {
        opacity: 0;
        transform: scale(0.9) translateY(6rpx);
    }
    to {
        opacity: 1;
        transform: scale(1) translateY(0);
    }
}

.longbtn-item {
    height: 80rpx;
    line-height: 80rpx;
    font-size: 28rpx;
    color: #ffffff;
    position: relative;
}

.longbtn-item + .longbtn-item::before {
    content: '';
    position: absolute;
    top: 0;
    left: 16rpx;
    right: 16rpx;
    height: 1rpx;
    background: rgba(255, 255, 255, 0.12);
}

.longbtn-item:active {
    background: rgba(255, 255, 255, 0.08);
}

/* ============== 其它兼容 ============== */
.small-grids {
    height: 300rpx;
}

.video {
    width: 100%;
    height: calc(100% - 370rpx);
}

.weui-grids {
    width: 100%;
    height: calc(100% - 370rpx);
    background: #f2f2f2;
}

.weui-grid {
    width: 50%;
    height: 50%;
}

.timeover {
    position: absolute;
    top: calc(50% - 60rpx);
    left: calc(50% - 120rpx);
    background: #fff5e6;
    margin: 20rpx 0;
}

.center {
    height: calc(100% - 700rpx);
}

.callPat {
    text-align: center;
    margin-top: 40rpx;
    color: #fff;
    width: 400rpx !important;
    background: #1e90ff;
    border-radius: 50rpx;
}

.otherDoc {
    color: #1e90ff;
}

/* ============== 语音切换按钮（左侧） ============== */
.switch-btn {
    width: 64rpx;
    height: 64rpx;
    flex: 0 0 64rpx;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: #f4f7ff;
    margin-right: 4rpx;
}

.treatment-graphic .switch-btn {
    background: #edf4ff;
}

.switch-icon {
    font-size: 32rpx;
    line-height: 1;
}

/* 语音模式下的键盘切换按钮文字（参考 room.vue） */
.keyboard-icon-text {
    font-size: 26rpx;
    color: #576b95;
    line-height: 1;
}

/* ============== 语音录制区域（参考 room.vue） ============== */
.voice-record-area {
    flex: 1;
    height: 64rpx;
    line-height: 64rpx;
    text-align: center;
    border-radius: 8rpx;
    background-color: #fff;
    font-size: 28rpx;
    color: #666;
    /* 防止手机长按触发复制/选择/弹出菜单 */
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    /* 限制触摸行为：允许点击，禁止滚动手势判断（减少 touchcancel）；
       不用 none 是避免某些安卓机吞掉 touchend */
    touch-action: manipulation;
}

.treatment-graphic .voice-record-area {
    height: 72rpx;
    line-height: 72rpx;
    border-radius: 18rpx;
    background: #f2f5fa;
}

.voice-recording {
    background: #e8f0fe !important;
    color: #1e90ff !important;
}

/* ============== 语音消息气泡（参考 room.vue 音频样式） ============== */
.bubble-audio {
    display: flex;
    align-items: center;
    min-width: 160rpx;
    max-width: 360rpx;
    padding: 12rpx 16rpx;
    border-radius: 8rpx;
    cursor: pointer;
}

/* 音频图标：扬声器 + 声波短线 */
.audio-icon-wrap {
    width: 80rpx;
    height: 44rpx;
    display: flex;
    align-items: center;
    flex-shrink: 0;
}

.audio-icon {
    width: 52rpx;
    height: 44rpx;
    display: flex;
    align-items: center;
    justify-content: flex-start;
}

/* 扬声器图标 */
.audio-icon-speaker {
    font-size: 32rpx;
    line-height: 1;
    margin-right: 4rpx;
}

.audio-icon-waves {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 26rpx;
    height: 22rpx;
}

.audio-icon-wave {
    width: 4rpx;
    border-radius: 2rpx;
    background-color: currentColor;
    transform-origin: center;
    transform: scaleY(1);
    opacity: 1;
}

.audio-icon-wave:nth-child(1) {
    height: 8rpx;
}

.audio-icon-wave:nth-child(2) {
    height: 14rpx;
}

.audio-icon-wave:nth-child(3) {
    height: 20rpx;
}

/* 播放动画：声波短线从近到远依次收缩 */
@keyframes voice-wave {
    0%,
    100% {
        transform: scaleY(1);
        opacity: 1;
    }
    50% {
        transform: scaleY(0.35);
        opacity: 0.4;
    }
}

.is-playing .audio-icon-wave {
    animation: voice-wave 0.8s ease-in-out infinite;
}

.is-playing .audio-icon-wave:nth-child(1) {
    animation-delay: 0s;
}

.is-playing .audio-icon-wave:nth-child(2) {
    animation-delay: 0.15s;
}

.is-playing .audio-icon-wave:nth-child(3) {
    animation-delay: 0.3s;
}

/* 发送方：白色 */
.icon-sent .audio-icon {
    color: rgba(255, 255, 255, 0.9);
}

/* 接收方：灰色 */
.icon-received .audio-icon {
    color: rgba(0, 0, 0, 0.45);
}

/* 音频时长 */
.audio-duration-text {
    font-size: 26rpx;
    flex-shrink: 0;
    margin-left: 8rpx;
}

.message-send .audio-duration-text {
    color: rgba(255, 255, 255, 0.85);
}

.message-received .audio-duration-text {
    color: rgba(0, 0, 0, 0.6);
}

/* ============== 视频消息气泡 ============== */
.video-message {
    display: inline-block;
    position: relative;
    max-width: 320rpx;
    min-width: 200rpx;
    border-radius: 12rpx;
    overflow: hidden;
}

.video-thumbnail-wrap {
    position: relative;
    width: 100%;
}

.video-thumbnail {
    width: 320rpx;
    height: 240rpx;
    border-radius: 12rpx;
    background: #eef0f3;
}

.video-play-btn-big {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 60rpx;
    height: 60rpx;
    background: rgba(0, 0, 0, 0.5);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
}

.video-play-triangle {
    color: #fff;
    font-size: 24rpx;
    margin-left: 4rpx;
}

.video-duration-tag {
    position: absolute;
    bottom: 8rpx;
    right: 8rpx;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    font-size: 20rpx;
    padding: 2rpx 8rpx;
    border-radius: 4rpx;
}

/* ============== 视频播放弹窗 ============== */
.video-preview-mask {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.85);
    z-index: 10000;
    display: flex;
    align-items: center;
    justify-content: center;
}

.video-preview-container {
    position: relative;
    width: 90%;
    max-width: 600px;
}

.video-preview-player {
    width: 100%;
    max-height: 80vh;
    background: #000;
    border-radius: 8rpx;
}

.video-preview-close {
    position: absolute;
    top: -60rpx;
    right: 0;
    width: 50rpx;
    height: 50rpx;
    line-height: 50rpx;
    text-align: center;
    font-size: 40rpx;
    color: #fff;
    background: rgba(255, 255, 255, 0.2);
    border-radius: 50%;
}
</style>
