2026-01-20 13:21:50 +08:00
|
|
|
|
<template>
|
2026-02-11 17:07:11 +08:00
|
|
|
|
<view class="input-section" :style="{ bottom: props.keyboardHeight + 'px' }">
|
2026-01-20 13:21:50 +08:00
|
|
|
|
<view class="input-toolbar">
|
|
|
|
|
|
<view @click="toggleVoiceInput" class="voice-toggle-btn">
|
2026-02-06 17:33:42 +08:00
|
|
|
|
<image v-if="showVoiceInput" src="/static/jianpan.png" class="voice-toggle-icon" mode="aspectFit"></image>
|
2026-01-20 13:21:50 +08:00
|
|
|
|
<uni-icons v-else type="mic" size="28" color="#666" />
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="input-area">
|
2026-02-03 14:17:37 +08:00
|
|
|
|
<textarea v-if="!showVoiceInput" class="text-input" v-model="inputText" placeholder="我来说两句..."
|
2026-01-29 18:03:40 +08:00
|
|
|
|
@confirm="sendTextMessage" @focus="handleInputFocus" @input="handleInput"
|
2026-02-11 17:07:11 +08:00
|
|
|
|
:auto-height="true" :show-confirm-bar="false" :adjust-position="false"
|
2026-02-04 10:36:36 +08:00
|
|
|
|
/>
|
2026-01-20 13:21:50 +08:00
|
|
|
|
<input v-else class="voice-input-btn" :class="{ recording: isRecording }" @touchstart="startRecord"
|
|
|
|
|
|
@touchmove="onRecordTouchMove" @touchend="stopRecord" @touchcancel="cancelRecord" :placeholder="isRecording ? '松开发送' : '按住说话'" disabled>
|
|
|
|
|
|
</input>
|
|
|
|
|
|
</view>
|
2026-02-08 13:53:22 +08:00
|
|
|
|
<button v-if="inputText.trim() && !props.isGenerating" class="send-btn" @click="sendTextMessage">
|
2026-01-20 13:21:50 +08:00
|
|
|
|
发送
|
|
|
|
|
|
</button>
|
2026-02-08 13:53:22 +08:00
|
|
|
|
<view v-else-if="!inputText.trim() && !props.isGenerating" class="plus-btn" @click="toggleMorePanel()">
|
2026-01-20 13:21:50 +08:00
|
|
|
|
<uni-icons type="plusempty" size="28" color="#666" />
|
|
|
|
|
|
</view>
|
2026-02-08 13:53:22 +08:00
|
|
|
|
<view v-else class="send-btn disabled-btn">
|
|
|
|
|
|
<text>生成中...</text>
|
|
|
|
|
|
</view>
|
2026-01-20 13:21:50 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
<view class="more-panel" v-if="showMorePanel">
|
|
|
|
|
|
<view v-for="btn in morePanelButtons" :key="btn.text" class="more-btn" @click="btn.action">
|
|
|
|
|
|
<image :src="btn.icon" class="more-icon" mode="aspectFit"></image>
|
|
|
|
|
|
<text>{{ btn.text }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<!-- 录音遮罩层 -->
|
|
|
|
|
|
<view v-if="isRecording" class="recording-overlay">
|
|
|
|
|
|
<view class="recording-modal" :class="{ 'cancel-mode': isCancelMode }">
|
|
|
|
|
|
<view class="recording-icon-container">
|
|
|
|
|
|
<view v-if="!isCancelMode" class="wave-circle wave-1"></view>
|
|
|
|
|
|
<view v-if="!isCancelMode" class="wave-circle wave-2"></view>
|
|
|
|
|
|
<view v-if="!isCancelMode" class="wave-circle wave-3"></view>
|
|
|
|
|
|
<view class="mic-icon-wrapper" :class="{ 'cancel-icon': isCancelMode }">
|
|
|
|
|
|
<uni-icons v-if="!isCancelMode" type="mic-filled" size="60" color="#fff" />
|
|
|
|
|
|
<uni-icons v-else type="closeempty" size="60" color="#fff" />
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="recording-text" :class="{ 'cancel-text': isCancelMode }">
|
|
|
|
|
|
{{ isCancelMode ? '松开手指,取消录音' : '正在录音...' }}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view v-if="!isCancelMode" class="recording-hint">松开发送,上滑取消</view>
|
|
|
|
|
|
<view v-if="!isCancelMode" class="recording-duration">{{ recordingDuration }}s</view>
|
|
|
|
|
|
<view v-else class="cancel-hint">
|
|
|
|
|
|
<uni-icons type="up" size="20" color="#ff4757" />
|
|
|
|
|
|
<text>已上滑</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2026-01-23 11:00:00 +08:00
|
|
|
|
import { computed, ref, onMounted, onUnmounted, nextTick } from "vue";
|
2026-01-20 13:21:50 +08:00
|
|
|
|
import {
|
|
|
|
|
|
chooseImage,
|
|
|
|
|
|
takePhoto as takePhotoUtil,
|
|
|
|
|
|
initRecorderManager as initRecorderManagerUtil,
|
|
|
|
|
|
startRecord as startRecordUtil,
|
|
|
|
|
|
stopRecord as stopRecordUtil,
|
|
|
|
|
|
createCustomMessage,
|
|
|
|
|
|
sendCustomMessage as sendCustomMessageUtil,
|
|
|
|
|
|
sendMessage as sendMessageUtil,
|
|
|
|
|
|
checkRecordingDuration,
|
2026-01-23 11:00:00 +08:00
|
|
|
|
validateBeforeSend,
|
|
|
|
|
|
} from "@/utils/chat-utils.js";
|
2026-01-20 13:21:50 +08:00
|
|
|
|
|
|
|
|
|
|
// Props
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
timChatManager: { type: Object, required: true },
|
|
|
|
|
|
patientInfo: { type: Object, default: () => ({}) },
|
|
|
|
|
|
chatRoomBusiness: { type: Object, default: () => ({}) },
|
2026-01-23 11:00:00 +08:00
|
|
|
|
formatTime: { type: Function, required: true },
|
2026-02-03 14:17:37 +08:00
|
|
|
|
groupId: { type: String, default: "" },
|
|
|
|
|
|
userId: { type: String, default: "" },
|
2026-02-04 18:39:53 +08:00
|
|
|
|
teamId: { type: String, default: "" },
|
2026-02-03 14:17:37 +08:00
|
|
|
|
patientId: { type: String, default: "" },
|
|
|
|
|
|
corpId: { type: String, default: "" },
|
2026-02-04 17:13:27 +08:00
|
|
|
|
orderStatus: { type: String, default: "" },
|
2026-02-08 13:53:22 +08:00
|
|
|
|
isGenerating: { type: Boolean, default: false },
|
2026-02-11 17:07:11 +08:00
|
|
|
|
keyboardHeight: { type: Number, default: 0 },
|
2026-01-20 13:21:50 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Emits
|
2026-02-05 14:02:35 +08:00
|
|
|
|
const emit = defineEmits([
|
|
|
|
|
|
"messageSent",
|
|
|
|
|
|
"scrollToBottom",
|
|
|
|
|
|
"endConsult",
|
|
|
|
|
|
"openConsult",
|
|
|
|
|
|
]);
|
2026-01-20 13:21:50 +08:00
|
|
|
|
|
|
|
|
|
|
// 输入相关状态
|
|
|
|
|
|
const inputText = ref("");
|
|
|
|
|
|
const showVoiceInput = ref(false);
|
|
|
|
|
|
const showMorePanel = ref(false);
|
|
|
|
|
|
const isRecording = ref(false);
|
|
|
|
|
|
const recordingText = ref("录音中...");
|
|
|
|
|
|
const cloudCustomData = computed(() => {
|
2026-01-23 11:00:00 +08:00
|
|
|
|
const arr = [
|
|
|
|
|
|
props.chatRoomBusiness.businessType,
|
|
|
|
|
|
props.chatRoomBusiness.businessId,
|
|
|
|
|
|
];
|
|
|
|
|
|
return arr.filter(Boolean).join("|");
|
|
|
|
|
|
});
|
2026-01-20 13:21:50 +08:00
|
|
|
|
|
2026-01-29 18:03:40 +08:00
|
|
|
|
// 流式输入文本
|
|
|
|
|
|
const appendStreamText = (char) => {
|
|
|
|
|
|
inputText.value += char;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 录音相关扩展状态(特效 + 取消逻辑)
|
|
|
|
|
|
const recordingDuration = ref(0);
|
|
|
|
|
|
let recordingTimer = null;
|
|
|
|
|
|
const isCancelMode = ref(false);
|
|
|
|
|
|
let touchStartY = 0;
|
|
|
|
|
|
const CANCEL_DISTANCE = 100;
|
|
|
|
|
|
let discardRecording = false; // 取消后丢弃本次录音
|
|
|
|
|
|
|
|
|
|
|
|
// 录音管理器
|
|
|
|
|
|
let recorderManager = null;
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化录音管理器
|
|
|
|
|
|
const initRecorderManager = () => {
|
|
|
|
|
|
recorderManager = initRecorderManagerUtil(
|
|
|
|
|
|
{},
|
|
|
|
|
|
(res) => {
|
|
|
|
|
|
// 若本次被标记为丢弃(取消录音触发的 stop),则忽略发送
|
|
|
|
|
|
if (discardRecording) {
|
|
|
|
|
|
discardRecording = false;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查录音时长
|
2026-01-23 11:00:00 +08:00
|
|
|
|
if (
|
|
|
|
|
|
!checkRecordingDuration(res, () => {
|
|
|
|
|
|
isRecording.value = false;
|
|
|
|
|
|
recordingText.value = "录音中...";
|
|
|
|
|
|
})
|
|
|
|
|
|
) {
|
2026-01-20 13:21:50 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置录音状态
|
|
|
|
|
|
isRecording.value = false;
|
|
|
|
|
|
recordingText.value = "录音中...";
|
|
|
|
|
|
clearDurationTimer();
|
|
|
|
|
|
|
|
|
|
|
|
// 发送语音消息
|
|
|
|
|
|
const duration = Math.floor(res.duration / 1000);
|
|
|
|
|
|
sendVoiceMessage(res, duration);
|
|
|
|
|
|
},
|
|
|
|
|
|
(err) => {
|
|
|
|
|
|
isRecording.value = false;
|
|
|
|
|
|
recordingText.value = "录音中...";
|
|
|
|
|
|
clearDurationTimer();
|
|
|
|
|
|
discardRecording = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 发送文本消息
|
|
|
|
|
|
const sendTextMessage = async () => {
|
|
|
|
|
|
if (!inputText.value.trim()) return;
|
|
|
|
|
|
|
2026-01-23 11:00:00 +08:00
|
|
|
|
await sendMessage("text", inputText.value);
|
2026-01-20 13:21:50 +08:00
|
|
|
|
inputText.value = "";
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-23 16:09:34 +08:00
|
|
|
|
// 从常用语发送文本消息
|
|
|
|
|
|
const sendTextMessageFromPhrase = async (content) => {
|
|
|
|
|
|
if (!content.trim()) return;
|
2026-02-03 14:17:37 +08:00
|
|
|
|
|
2026-01-23 16:09:34 +08:00
|
|
|
|
await sendMessage("text", content);
|
2026-02-03 14:17:37 +08:00
|
|
|
|
|
2026-01-23 16:09:34 +08:00
|
|
|
|
// 发送成功后滚动到底部
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
emit("scrollToBottom");
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-29 18:03:40 +08:00
|
|
|
|
// 设置输入框文本(覆盖原内容)
|
|
|
|
|
|
const setInputText = (text) => {
|
|
|
|
|
|
inputText.value = text;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 清空输入框
|
|
|
|
|
|
const clearInputText = () => {
|
2026-02-03 14:17:37 +08:00
|
|
|
|
inputText.value = "";
|
2026-01-29 18:03:40 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-23 16:09:34 +08:00
|
|
|
|
// 暴露方法给父组件调用
|
|
|
|
|
|
defineExpose({
|
2026-01-29 18:03:40 +08:00
|
|
|
|
sendTextMessageFromPhrase,
|
|
|
|
|
|
appendStreamText,
|
|
|
|
|
|
setInputText,
|
2026-02-03 14:17:37 +08:00
|
|
|
|
clearInputText,
|
2026-01-23 16:09:34 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
// 发送图片消息
|
2026-01-22 16:35:05 +08:00
|
|
|
|
const sendImageMessage = async (imageFile) => {
|
2026-01-23 11:00:00 +08:00
|
|
|
|
console.log("chat-input sendImageMessage 被调用,参数:", imageFile);
|
|
|
|
|
|
await sendMessage("image", imageFile);
|
2026-01-20 13:21:50 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 发送语音消息
|
|
|
|
|
|
const sendVoiceMessage = async (voiceFile, duration) => {
|
2026-01-23 11:00:00 +08:00
|
|
|
|
await sendMessage("voice", { file: voiceFile, duration });
|
2026-01-20 13:21:50 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 发送消息的通用方法(文本、图片、语音)
|
|
|
|
|
|
const sendMessage = async (messageType, data) => {
|
|
|
|
|
|
await sendMessageUtil(
|
|
|
|
|
|
messageType,
|
|
|
|
|
|
data,
|
|
|
|
|
|
props.timChatManager,
|
|
|
|
|
|
() => validateBeforeSend(false, false, props.timChatManager),
|
|
|
|
|
|
() => {
|
|
|
|
|
|
showMorePanel.value = false;
|
2026-01-22 16:35:05 +08:00
|
|
|
|
// 发送成功后滚动到底部
|
2026-01-23 11:00:00 +08:00
|
|
|
|
emit("messageSent");
|
2026-01-20 13:21:50 +08:00
|
|
|
|
},
|
|
|
|
|
|
cloudCustomData.value
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 发送自定义消息的通用方法
|
|
|
|
|
|
const sendCustomMessage = async (messageData) => {
|
|
|
|
|
|
await sendCustomMessageUtil(
|
|
|
|
|
|
messageData,
|
|
|
|
|
|
props.timChatManager,
|
|
|
|
|
|
() => validateBeforeSend(false, false, props.timChatManager),
|
|
|
|
|
|
() => {
|
|
|
|
|
|
showMorePanel.value = false;
|
2026-01-23 11:00:00 +08:00
|
|
|
|
emit("messageSent");
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 输入相关方法
|
|
|
|
|
|
const toggleVoiceInput = () => {
|
|
|
|
|
|
showVoiceInput.value = !showVoiceInput.value;
|
|
|
|
|
|
showMorePanel.value = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const toggleMorePanel = () => {
|
|
|
|
|
|
showMorePanel.value = !showMorePanel.value;
|
|
|
|
|
|
showVoiceInput.value = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 处理图片选择
|
|
|
|
|
|
const showImagePicker = () => {
|
|
|
|
|
|
chooseImage(
|
2026-01-22 17:02:15 +08:00
|
|
|
|
(file) => {
|
2026-01-23 11:00:00 +08:00
|
|
|
|
console.log("选择图片成功,文件对象:", file);
|
2026-01-22 17:02:15 +08:00
|
|
|
|
// 直接传递文件对象,不需要额外处理
|
|
|
|
|
|
sendImageMessage(file);
|
2026-01-22 16:35:05 +08:00
|
|
|
|
},
|
2026-01-20 13:21:50 +08:00
|
|
|
|
(err) => {
|
2026-01-23 11:00:00 +08:00
|
|
|
|
console.error("选择图片失败:", err);
|
|
|
|
|
|
if (
|
|
|
|
|
|
!err.errMsg?.includes("permission") &&
|
|
|
|
|
|
!err.errMsg?.includes("auth") &&
|
|
|
|
|
|
!err.errMsg?.includes("拒绝") &&
|
|
|
|
|
|
!err.errMsg?.includes("未授权")
|
|
|
|
|
|
) {
|
2026-01-20 13:21:50 +08:00
|
|
|
|
uni.showToast({
|
2026-01-23 11:00:00 +08:00
|
|
|
|
title: "选择图片失败,请重试",
|
|
|
|
|
|
icon: "none",
|
|
|
|
|
|
duration: 2000,
|
2026-01-20 13:21:50 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const takePhoto = () => {
|
|
|
|
|
|
takePhotoUtil(
|
2026-01-22 17:02:15 +08:00
|
|
|
|
(file) => {
|
2026-01-23 11:00:00 +08:00
|
|
|
|
console.log("拍照成功,文件对象:", file);
|
2026-01-22 17:02:15 +08:00
|
|
|
|
// 直接传递文件对象,不需要额外处理
|
|
|
|
|
|
sendImageMessage(file);
|
2026-01-22 16:35:05 +08:00
|
|
|
|
},
|
2026-01-20 13:21:50 +08:00
|
|
|
|
(err) => {
|
2026-01-23 11:00:00 +08:00
|
|
|
|
console.error("拍照失败:", err);
|
|
|
|
|
|
if (
|
|
|
|
|
|
!err.errMsg?.includes("permission") &&
|
|
|
|
|
|
!err.errMsg?.includes("auth") &&
|
|
|
|
|
|
!err.errMsg?.includes("拒绝") &&
|
|
|
|
|
|
!err.errMsg?.includes("未授权")
|
|
|
|
|
|
) {
|
2026-01-20 13:21:50 +08:00
|
|
|
|
uni.showToast({
|
2026-01-23 11:00:00 +08:00
|
|
|
|
title: "拍照失败,请重试",
|
|
|
|
|
|
icon: "none",
|
|
|
|
|
|
duration: 2000,
|
2026-01-20 13:21:50 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
function startDurationTimer() {
|
|
|
|
|
|
clearDurationTimer();
|
|
|
|
|
|
recordingDuration.value = 0;
|
|
|
|
|
|
recordingTimer = setInterval(() => {
|
|
|
|
|
|
recordingDuration.value++;
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function clearDurationTimer() {
|
|
|
|
|
|
if (recordingTimer) {
|
|
|
|
|
|
clearInterval(recordingTimer);
|
|
|
|
|
|
recordingTimer = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const startRecord = (e) => {
|
|
|
|
|
|
isRecording.value = true;
|
|
|
|
|
|
recordingText.value = "录音中...";
|
|
|
|
|
|
isCancelMode.value = false;
|
|
|
|
|
|
discardRecording = false;
|
|
|
|
|
|
recordingDuration.value = 0;
|
|
|
|
|
|
if (e && e.touches && e.touches[0]) {
|
|
|
|
|
|
touchStartY = e.touches[0].clientY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确保录音管理器已初始化
|
|
|
|
|
|
if (!recorderManager) {
|
|
|
|
|
|
initRecorderManager();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
startDurationTimer();
|
|
|
|
|
|
startRecordUtil(recorderManager);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const onRecordTouchMove = (e) => {
|
|
|
|
|
|
if (!isRecording.value) return;
|
|
|
|
|
|
if (e && e.touches && e.touches[0]) {
|
|
|
|
|
|
const currentY = e.touches[0].clientY;
|
|
|
|
|
|
const deltaY = touchStartY - currentY; // 上滑为正
|
|
|
|
|
|
isCancelMode.value = deltaY > CANCEL_DISTANCE;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const stopRecord = () => {
|
|
|
|
|
|
if (!isRecording.value) return;
|
|
|
|
|
|
// 如果处于取消模式,则按取消处理
|
|
|
|
|
|
if (isCancelMode.value) {
|
|
|
|
|
|
cancelRecord();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
isRecording.value = false;
|
|
|
|
|
|
recordingText.value = "录音中...";
|
|
|
|
|
|
clearDurationTimer();
|
|
|
|
|
|
stopRecordUtil(recorderManager);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const cancelRecord = () => {
|
|
|
|
|
|
if (!isRecording.value) return;
|
|
|
|
|
|
isRecording.value = false;
|
|
|
|
|
|
isCancelMode.value = false;
|
|
|
|
|
|
recordingText.value = "已取消";
|
|
|
|
|
|
discardRecording = true; // 标记为丢弃,阻止 onStop 发送
|
|
|
|
|
|
clearDurationTimer();
|
|
|
|
|
|
stopRecordUtil(recorderManager);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-23 16:09:34 +08:00
|
|
|
|
// 跳转到常用语页面
|
|
|
|
|
|
const goToCommonPhrases = () => {
|
|
|
|
|
|
uni.navigateTo({
|
2026-02-03 14:17:37 +08:00
|
|
|
|
url: "/pages/message/common-phrases",
|
2026-01-23 16:09:34 +08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-04 10:36:36 +08:00
|
|
|
|
// 打开回访任务列表
|
|
|
|
|
|
const showFollowUpTasks = () => {
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
url: `/pages/case/followup-task-list?archiveId=${props.patientId}&patientName=${props.patientInfo.name}`,
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-26 18:08:01 +08:00
|
|
|
|
// 跳转到宣教文章页面
|
|
|
|
|
|
const goToArticleList = () => {
|
|
|
|
|
|
uni.navigateTo({
|
2026-02-04 18:39:53 +08:00
|
|
|
|
url: `/pages/message/article-list?groupId=${props.groupId}&patientId=${props.patientId}&corpId=${props.corpId}&teamId=${props.teamId}`,
|
2026-01-26 18:08:01 +08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到问卷列表页面
|
|
|
|
|
|
const goToSurveyList = () => {
|
|
|
|
|
|
uni.navigateTo({
|
2026-02-04 18:39:53 +08:00
|
|
|
|
url: `/pages/message/survey-list?groupId=${props.groupId}&patientId=${props.patientId}&corpId=${props.corpId}&teamId=${props.teamId}&customerName=${props.patientInfo.name}`,
|
2026-01-26 18:08:01 +08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-27 13:42:59 +08:00
|
|
|
|
// 结束问诊
|
|
|
|
|
|
const handleEndConsult = () => {
|
|
|
|
|
|
uni.showModal({
|
2026-02-03 14:17:37 +08:00
|
|
|
|
title: "确认结束问诊",
|
|
|
|
|
|
content: "确定要结束本次问诊吗?结束后将无法继续对话。",
|
|
|
|
|
|
confirmText: "确定结束",
|
|
|
|
|
|
cancelText: "取消",
|
2026-01-27 13:42:59 +08:00
|
|
|
|
success: (res) => {
|
|
|
|
|
|
if (res.confirm) {
|
|
|
|
|
|
// 关闭功能面板
|
|
|
|
|
|
showMorePanel.value = false;
|
|
|
|
|
|
// 触发父组件的结束问诊事件
|
2026-02-03 14:17:37 +08:00
|
|
|
|
emit("endConsult");
|
2026-01-27 13:42:59 +08:00
|
|
|
|
}
|
2026-02-03 14:17:37 +08:00
|
|
|
|
},
|
2026-01-27 13:42:59 +08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-04 17:09:20 +08:00
|
|
|
|
// 开启会话
|
|
|
|
|
|
const handleOpenConsult = () => {
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: "确认开启会话",
|
|
|
|
|
|
content: "确定要重新开启本次会话吗?",
|
|
|
|
|
|
confirmText: "确定开启",
|
|
|
|
|
|
cancelText: "取消",
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
if (res.confirm) {
|
|
|
|
|
|
// 关闭功能面板
|
|
|
|
|
|
showMorePanel.value = false;
|
|
|
|
|
|
// 触发父组件的开启会话事件
|
|
|
|
|
|
emit("openConsult");
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-04 17:13:27 +08:00
|
|
|
|
const morePanelButtons = computed(() => {
|
|
|
|
|
|
const buttons = [
|
2026-02-05 14:02:35 +08:00
|
|
|
|
{
|
|
|
|
|
|
text: "照片",
|
|
|
|
|
|
icon: "/static/icon/zhaopian.png",
|
|
|
|
|
|
action: showImagePicker,
|
|
|
|
|
|
},
|
2026-02-04 17:13:27 +08:00
|
|
|
|
{
|
|
|
|
|
|
text: "回访任务",
|
|
|
|
|
|
icon: "/static/icon/huifangrenwu.png",
|
|
|
|
|
|
action: showFollowUpTasks,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
text: "常用语",
|
|
|
|
|
|
icon: "/static/icon/changyongyu.png",
|
|
|
|
|
|
action: goToCommonPhrases,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
text: "宣教",
|
|
|
|
|
|
icon: "/static/icon/xuanjiaowenzhang.png",
|
|
|
|
|
|
action: goToArticleList,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
text: "问卷",
|
|
|
|
|
|
icon: "/static/icon/wenjuan.png",
|
|
|
|
|
|
action: goToSurveyList,
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 根据订单状态显示不同的按钮
|
|
|
|
|
|
if (props.orderStatus === "finished") {
|
|
|
|
|
|
// 已结束状态:显示"开启会话"按钮
|
|
|
|
|
|
buttons.push({
|
|
|
|
|
|
text: "开启会话",
|
2026-02-06 17:33:42 +08:00
|
|
|
|
icon: "/static/icon/openChat.png",
|
2026-02-04 17:13:27 +08:00
|
|
|
|
action: handleOpenConsult,
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 处理中状态:显示"结束问诊"按钮
|
|
|
|
|
|
buttons.push({
|
|
|
|
|
|
text: "结束问诊",
|
|
|
|
|
|
icon: "/static/icon/jieshuzixun.png",
|
|
|
|
|
|
action: handleEndConsult,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return buttons;
|
|
|
|
|
|
});
|
2026-01-20 13:21:50 +08:00
|
|
|
|
|
|
|
|
|
|
function handleInputFocus() {
|
2026-01-23 11:00:00 +08:00
|
|
|
|
console.log("handleInputFocus");
|
2026-01-20 13:21:50 +08:00
|
|
|
|
nextTick().then(() => {
|
2026-01-23 11:00:00 +08:00
|
|
|
|
emit("scrollToBottom");
|
|
|
|
|
|
});
|
2026-01-20 13:21:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-29 18:03:40 +08:00
|
|
|
|
function handleInput(e) {
|
|
|
|
|
|
// textarea 输入时触发,可以在这里处理额外逻辑
|
|
|
|
|
|
nextTick().then(() => {
|
|
|
|
|
|
emit("scrollToBottom");
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-20 13:21:50 +08:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
// 初始化录音管理器
|
|
|
|
|
|
initRecorderManager();
|
|
|
|
|
|
|
|
|
|
|
|
// 监听关闭功能栏事件
|
2026-01-23 11:00:00 +08:00
|
|
|
|
uni.$on("closeMorePanel", () => {
|
2026-01-20 13:21:50 +08:00
|
|
|
|
showMorePanel.value = false;
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
// 移除事件监听
|
2026-01-23 11:00:00 +08:00
|
|
|
|
uni.$off("closeMorePanel");
|
2026-01-20 13:21:50 +08:00
|
|
|
|
clearDurationTimer();
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
@import "../chat.scss";
|
2026-02-04 18:39:53 +08:00
|
|
|
|
</style>
|