Merge remote-tracking branch 'origin/dev-wdb' into dev-hjf

This commit is contained in:
Jafeng 2026-02-12 14:21:20 +08:00
commit 150c6072d7
6 changed files with 112 additions and 52 deletions

View File

@ -6,13 +6,15 @@ $text-color-sub: #999;
$primary-color: #0877F1; $primary-color: #0877F1;
.chat-page { .chat-page {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100vh;
background-color: #f5f5f5; background-color: #f5f5f5;
overflow: hidden; overflow: hidden;
position: relative;
width: 100%;
} }
/* 患者信息栏样式 - 固定在顶部 */ /* 患者信息栏样式 - 固定在顶部 */
@ -24,10 +26,8 @@ $primary-color: #0877F1;
background: #fff; background: #fff;
border-bottom: 1rpx solid #f0f0f0; border-bottom: 1rpx solid #f0f0f0;
padding: 20rpx 32rpx; padding: 20rpx 32rpx;
z-index: 100; z-index: 10;
flex-shrink: 0; /* 防止被压缩 */ flex-shrink: 0;
width: 100%;
box-sizing: border-box;
} }
.patient-info-content { .patient-info-content {
@ -91,15 +91,14 @@ $primary-color: #0877F1;
} }
.chat-content { .chat-content {
flex: 1; position: fixed;
top: 100rpx; /* 患者信息栏高度,根据实际调整 */
left: 0;
right: 0;
bottom: 200rpx; /* 输入框高度,根据实际调整 */
box-sizing: border-box; box-sizing: border-box;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
min-height: 0;
margin-top: 120rpx;
margin-bottom: 0;
position: relative;
z-index: 1;
} }
.chat-content-compressed { .chat-content-compressed {
@ -364,19 +363,31 @@ $primary-color: #0877F1;
} }
.input-section { .input-section {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff; background: #fff;
border-top: 1rpx solid #e0e0e0;
position: relative;
z-index: 200; z-index: 200;
padding-bottom: env(safe-area-inset-bottom);
flex-shrink: 0; flex-shrink: 0;
transform: translateZ(0); /* 开启硬件加速,提升性能 */
display: flex;
flex-direction: column;
}
.ai-assistant-slot {
width: 100%;
background: #fff;
} }
.input-toolbar { .input-toolbar {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 16rpx 20rpx; padding: 12rpx 20rpx;
padding-bottom: env(safe-area-inset-bottom);
gap: 12rpx; gap: 12rpx;
border-top: 1rpx solid #e0e0e0;
background: #fff;
} }
.voice-toggle-btn { .voice-toggle-btn {
@ -499,7 +510,8 @@ $primary-color: #0877F1;
justify-content: flex-start; justify-content: flex-start;
background: #fff; background: #fff;
border-top: 1rpx solid #eee; border-top: 1rpx solid #eee;
padding: 20rpx 0 40rpx 60rpx; padding: 20rpx 0 20rpx 60rpx;
padding-bottom: env(safe-area-inset-bottom);
gap: 40rpx 50rpx; gap: 40rpx 50rpx;
flex-wrap: wrap; flex-wrap: wrap;
background-color: #f5f5f5; background-color: #f5f5f5;

View File

@ -383,7 +383,6 @@ defineExpose({
gap: 16rpx; gap: 16rpx;
padding: 16rpx 24rpx; padding: 16rpx 24rpx;
background-color: #f8f9fa; background-color: #f8f9fa;
border-bottom: 1rpx solid #e5e5e5;
.ai-button { .ai-button {
display: flex; display: flex;

View File

@ -1,5 +1,10 @@
<template> <template>
<view class="input-section"> <view class="input-section">
<!-- AI助手按钮组 -->
<view class="ai-assistant-slot">
<slot name="ai-assistant"></slot>
</view>
<view class="input-toolbar"> <view class="input-toolbar">
<view @click="toggleVoiceInput" class="voice-toggle-btn"> <view @click="toggleVoiceInput" class="voice-toggle-btn">
<image v-if="showVoiceInput" src="/static/jianpan.png" class="voice-toggle-icon" mode="aspectFit"></image> <image v-if="showVoiceInput" src="/static/jianpan.png" class="voice-toggle-icon" mode="aspectFit"></image>
@ -8,7 +13,8 @@
<view class="input-area"> <view class="input-area">
<textarea v-if="!showVoiceInput" class="text-input" v-model="inputText" placeholder="我来说两句..." <textarea v-if="!showVoiceInput" class="text-input" v-model="inputText" placeholder="我来说两句..."
@confirm="sendTextMessage" @focus="handleInputFocus" @input="handleInput" @confirm="sendTextMessage" @focus="handleInputFocus" @input="handleInput"
:auto-height="true" :show-confirm-bar="false" :adjust-position="true" :cursor-spacing="30" :auto-height="true" :show-confirm-bar="false" :hold-keyboard="true"
ref="textareaRef"
/> />
<input v-else class="voice-input-btn" :class="{ recording: isRecording }" @touchstart="startRecord" <input v-else class="voice-input-btn" :class="{ recording: isRecording }" @touchstart="startRecord"
@touchmove="onRecordTouchMove" @touchend="stopRecord" @touchcancel="cancelRecord" :placeholder="isRecording ? '松开发送' : '按住说话'" disabled> @touchmove="onRecordTouchMove" @touchend="stopRecord" @touchcancel="cancelRecord" :placeholder="isRecording ? '松开发送' : '按住说话'" disabled>
@ -100,6 +106,7 @@ const showVoiceInput = ref(false);
const showMorePanel = ref(false); const showMorePanel = ref(false);
const isRecording = ref(false); const isRecording = ref(false);
const recordingText = ref("录音中..."); const recordingText = ref("录音中...");
const textareaRef = ref(null);
const cloudCustomData = computed(() => { const cloudCustomData = computed(() => {
const arr = [ const arr = [
props.chatRoomBusiness.businessType, props.chatRoomBusiness.businessType,
@ -167,8 +174,19 @@ const initRecorderManager = () => {
const sendTextMessage = async () => { const sendTextMessage = async () => {
if (!inputText.value.trim()) return; if (!inputText.value.trim()) return;
await sendMessage("text", inputText.value); const textToSend = inputText.value;
inputText.value = ""; inputText.value = "";
await sendMessage("text", textToSend);
//
nextTick(() => {
// focus
//
setTimeout(() => {
// hold-keyboard
}, 50);
});
}; };
// //

View File

@ -133,23 +133,6 @@
@cancel="handleRejectReasonCancel" @cancel="handleRejectReasonCancel"
/> />
<!-- AI助手按钮组 -->
<AIAssistantButtons
v-if="
!isEvaluationPopupOpen &&
!showConsultAccept &&
orderStatus === 'processing'
"
ref="aiAssistantRef"
:groupId="groupId"
:patientAccountId="chatInfo.userID || ''"
:patientId="patientId"
:corpId="corpId"
@streamText="handleStreamText"
@clearInput="handleClearInput"
@generatingStateChange="handleGeneratingStateChange"
/>
<!-- 聊天输入组件 --> <!-- 聊天输入组件 -->
<ChatInput <ChatInput
v-if="!isEvaluationPopupOpen && !showConsultAccept" v-if="!isEvaluationPopupOpen && !showConsultAccept"
@ -172,7 +155,22 @@
@messageSent="() => scrollToBottom(true)" @messageSent="() => scrollToBottom(true)"
@endConsult="handleEndConsult" @endConsult="handleEndConsult"
@openConsult="handleOpenConsult" @openConsult="handleOpenConsult"
/> >
<!-- AI助手按钮组插槽 -->
<template #ai-assistant>
<AIAssistantButtons
v-if="orderStatus === 'processing'"
ref="aiAssistantRef"
:groupId="groupId"
:patientAccountId="chatInfo.userID || ''"
:patientId="patientId"
:corpId="corpId"
@streamText="handleStreamText"
@clearInput="handleClearInput"
@generatingStateChange="handleGeneratingStateChange"
/>
</template>
</ChatInput>
</view> </view>
</template> </template>
@ -214,6 +212,7 @@ const timChatManager = globalTimChatManager;
const PENDING_FOLLOWUP_SEND_STORAGE_KEY = "ykt_followup_pending_send"; const PENDING_FOLLOWUP_SEND_STORAGE_KEY = "ykt_followup_pending_send";
const pendingFollowUpSendConsumed = ref(false); const pendingFollowUpSendConsumed = ref(false);
const initialMessageListLoaded = ref(false); const initialMessageListLoaded = ref(false);
const isCallbacksInitialized = ref(false); //
function normalizeGroupId(v) { function normalizeGroupId(v) {
const s = String(v || "").trim(); const s = String(v || "").trim();
@ -302,6 +301,7 @@ const chatInfo = ref({
userID: "", userID: "",
avatar: "/static/home/avatar.svg", avatar: "/static/home/avatar.svg",
}); });
// //
const isEvaluationPopupOpen = ref(false); const isEvaluationPopupOpen = ref(false);
@ -492,6 +492,9 @@ const checkLoginAndInitTIM = async () => {
// IM // IM
const initTIMCallbacks = async () => { const initTIMCallbacks = async () => {
//
isCallbacksInitialized.value = true;
timChatManager.setCallback("onSDKReady", () => { timChatManager.setCallback("onSDKReady", () => {
if (messageList.value.length === 0 && !isLoading.value) { if (messageList.value.length === 0 && !isLoading.value) {
loadMessageList(); loadMessageList();
@ -878,26 +881,49 @@ onShow(() => {
if (!isIMInitialized.value) { if (!isIMInitialized.value) {
checkLoginAndInitTIM(); checkLoginAndInitTIM();
} else if (timChatManager.tim && !timChatManager.isLoggedIn) { } else if (timChatManager.tim && !timChatManager.isLoggedIn) {
timChatManager.ensureIMConnection(); // IM
timChatManager.ensureIMConnection().then(() => {
//
if (timChatManager.isLoggedIn && chatInfo.value.conversationID) {
console.log("✓ 重连成功,重新注册消息监听回调");
initTIMCallbacks();
timChatManager.setConversationID(chatInfo.value.conversationID);
}
});
} else if ( } else if (
timChatManager.tim && timChatManager.tim &&
timChatManager.isLoggedIn && timChatManager.isLoggedIn &&
chatInfo.value.conversationID chatInfo.value.conversationID
) ) {
// IMID
console.log("✓ 页面显示重新设置当前会话ID");
timChatManager.setConversationID(chatInfo.value.conversationID);
//
if (!isCallbacksInitialized.value) {
console.log("✓ 回调未初始化,进行初始化");
initTIMCallbacks();
}
//
if (timChatManager.tim && timChatManager.isLoggedIn) {
timChatManager.tim
.setMessageRead({
conversationID: chatInfo.value.conversationID,
})
.then(() => {
console.log("✓ 页面显示时已标记会话为已读");
})
.catch((error) => {
console.error("✗ 标记会话已读失败:", error);
});
}
startIMMonitoring(30000); startIMMonitoring(30000);
}
// 访 // 访
uni.$on("send-followup-message", handleSendFollowUpMessage); uni.$on("send-followup-message", handleSendFollowUpMessage);
//
uni.onKeyboardHeightChange((res) => {
if (res.height > 0) {
//
setTimeout(() => {
scrollToBottom(true);
}, 100);
}
});
}); });
// 访 // 访

View File

@ -96,4 +96,8 @@ onLoad((opt) => {
width: 40rpx; width: 40rpx;
height: 40rpx; height: 40rpx;
} }
.pt-12 {
padding-top: 24rpx;
}
</style> </style>

View File

@ -192,6 +192,7 @@ export async function sendArticleMessage(article, options = {}) {
userId: options.userId, userId: options.userId,
customerId: options.customerId, customerId: options.customerId,
corpId: options.corpId, corpId: options.corpId,
uniqueRecord: 'YES'
}; };
if (options.teamId) { if (options.teamId) {
params.teamId = options.teamId; params.teamId = options.teamId;
@ -424,7 +425,7 @@ export async function handleFollowUpMessages(messages, context = {}) {
corpId: context.corpId, corpId: context.corpId,
}); });
} else if (msg.type === 'questionnaire') { } else if (msg.type === 'questionnaire') {
success = await sendSurveyMessage(msg.content, { success = await sendSurveyMessage(msg.content, {
userId: context.userId, userId: context.userId,
customerId: context.customerId, customerId: context.customerId,