Merge remote-tracking branch 'origin/dev-wdb' into dev-hjf
This commit is contained in:
commit
150c6072d7
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 从常用语发送文本消息
|
// 从常用语发送文本消息
|
||||||
|
|||||||
@ -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
|
||||||
)
|
) {
|
||||||
|
// IM已登录,只需要重新设置会话ID,不重新注册回调(避免影响正在发送的消息)
|
||||||
|
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 处理发送回访任务消息
|
// 处理发送回访任务消息
|
||||||
|
|||||||
@ -96,4 +96,8 @@ onLoad((opt) => {
|
|||||||
width: 40rpx;
|
width: 40rpx;
|
||||||
height: 40rpx;
|
height: 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pt-12 {
|
||||||
|
padding-top: 24rpx;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user