Merge branch 'dev-咨询助手优化' into release-20260901
This commit is contained in:
commit
091b6df55d
@ -1,5 +1,14 @@
|
||||
<template>
|
||||
<view class="chat-page">
|
||||
<page-meta page-style="overflow: hidden;"></page-meta>
|
||||
<view class="chat-page" :style="{ bottom: `${keyboardHeight}px` }">
|
||||
<view v-if="session" class="patient-info-bar" @click="openPatientInfo">
|
||||
<view class="patient-summary">
|
||||
<text class="patient-name">{{ patientDisplayName }}</text>
|
||||
<text v-if="patientDetail" class="patient-detail">{{ patientDetail }}</text>
|
||||
</view>
|
||||
<text class="patient-action">查看/修改</text>
|
||||
<text class="patient-arrow">›</text>
|
||||
</view>
|
||||
<scroll-view scroll-y class="chat-content" :scroll-into-view="bottomId">
|
||||
<view class="message-list">
|
||||
<view v-for="(item, index) in messages" :key="item._id" :id="`message-${item._id}`" class="message-item" :class="`message-${item.sender}`">
|
||||
@ -7,7 +16,7 @@
|
||||
<evaluation-card v-if="item.messageType === 'consult_ended' && (item.rateId || session?.rateId)" class="rate-card" :extension="{ rateId: item.rateId || session.rateId }" :corp-id="corpId" :doctor-info="rateAssistantInfo" />
|
||||
<view v-else-if="item.sender === 'system' && item.messageType !== 'risk_notice'" class="system-message" :class="{ warn: item.messageType === 'sensitive_notice' }">{{ displayContent(item) }}</view>
|
||||
<view v-else-if="item.messageType !== 'risk_notice'" class="message-content">
|
||||
<image v-if="item.sender === 'assistant'" class="avatar assistant-avatar" src="/static/home/ai-consult-assistant.png" mode="aspectFill" />
|
||||
<image v-if="item.sender === 'assistant'" class="avatar assistant-avatar" :src="assistantAvatar" mode="aspectFill" @error="handleAssistantAvatarError" />
|
||||
<view class="message-bubble-container">
|
||||
<view v-if="item.sender === 'assistant'" class="username-label">{{ assistantDisplayName }}</view>
|
||||
<view class="message-bubble" :class="{ pending: item._sendStatus === 'pending' }">
|
||||
@ -19,13 +28,13 @@
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="waitingAi" class="message-item message-assistant">
|
||||
<view class="message-content"><image class="avatar assistant-avatar" src="/static/home/ai-consult-assistant.png" mode="aspectFill" /><view class="message-bubble-container"><view class="username-label">{{ assistantDisplayName }}</view><view class="message-bubble thinking"><view class="thinking-dot"></view><view class="thinking-dot"></view><view class="thinking-dot"></view></view></view></view>
|
||||
<view class="message-content"><image class="avatar assistant-avatar" :src="assistantAvatar" mode="aspectFill" @error="handleAssistantAvatarError" /><view class="message-bubble-container"><view class="username-label">{{ assistantDisplayName }}</view><view class="message-bubble thinking"><view class="thinking-dot"></view><view class="thinking-dot"></view><view class="thinking-dot"></view></view></view></view>
|
||||
</view>
|
||||
<view id="bottom" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view v-if="active" class="input-section">
|
||||
<textarea v-model="content" class="text-input" maxlength="1000" auto-height confirm-type="send" :show-confirm-bar="false" placeholder="请输入消息" @confirm="send" />
|
||||
<textarea v-model="content" class="text-input" maxlength="1000" auto-height fixed confirm-type="send" :show-confirm-bar="false" :adjust-position="false" placeholder="请输入消息" @confirm="send" />
|
||||
<button class="send-btn" :disabled="!content.trim()" @click="send">发送</button>
|
||||
</view>
|
||||
<view v-else class="ended">本次咨询已结束</view>
|
||||
@ -34,7 +43,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, nextTick, computed } from 'vue';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import { onLoad, onShow, onUnload } from '@dcloudio/uni-app';
|
||||
import api from '@/utils/api';
|
||||
import useAccountStore from '@/store/account';
|
||||
import { storeToRefs } from 'pinia';
|
||||
@ -45,14 +54,28 @@ const { openid } = storeToRefs(useAccountStore());
|
||||
const corpId = ref('');
|
||||
const sessionId = ref('');
|
||||
const session = ref(null);
|
||||
const patientInfo = ref(null);
|
||||
const messages = ref([]);
|
||||
const content = ref('');
|
||||
const pendingRequestCount = ref(0);
|
||||
const waitingAi = ref(false);
|
||||
const bottomId = ref('');
|
||||
const active = ref(false);
|
||||
const keyboardHeight = ref(0);
|
||||
const avatarLoadFailed = ref(false);
|
||||
const defaultAssistantAvatar = '/static/home/ai-consult-assistant.png';
|
||||
const assistantDisplayName = computed(() => removeAiLabel(session.value?.assistantName, '咨询助理'));
|
||||
const rateAssistantInfo = computed(() => ({ name: assistantDisplayName.value, title: '咨询助理', department: session.value?.teamName || '', avatar: '' }));
|
||||
const assistantAvatar = computed(() => avatarLoadFailed.value ? defaultAssistantAvatar : (session.value?.assistantAvatar || defaultAssistantAvatar));
|
||||
const rateAssistantInfo = computed(() => ({ name: assistantDisplayName.value, title: '咨询助理', department: session.value?.teamName || '', avatar: assistantAvatar.value }));
|
||||
const patientDisplayName = computed(() => patientInfo.value?.name || session.value?.customerName || '未建档患者');
|
||||
const patientDetail = computed(() => {
|
||||
const patient = patientInfo.value || {};
|
||||
const details = [];
|
||||
if (patient.sex) details.push(patient.sex);
|
||||
if (patient.age !== '' && patient.age !== null && patient.age !== undefined) details.push(`${patient.age}岁`);
|
||||
if (patient.relationship) details.push(patient.relationship);
|
||||
return details.join(' · ');
|
||||
});
|
||||
|
||||
function displayContent(item) {
|
||||
const value = item?.content || '';
|
||||
@ -85,10 +108,29 @@ async function scrollToBottom() {
|
||||
await nextTick();
|
||||
bottomId.value = 'bottom';
|
||||
}
|
||||
function handleKeyboardHeightChange(res) {
|
||||
keyboardHeight.value = Math.max(0, Number(res?.height) || 0);
|
||||
if (keyboardHeight.value > 0) scrollToBottom();
|
||||
}
|
||||
function handleAssistantAvatarError() {
|
||||
avatarLoadFailed.value = true;
|
||||
}
|
||||
function openPatientInfo() {
|
||||
const customerId = session.value?.archiveCustomerId;
|
||||
if (!customerId) return uni.showToast({ title: '暂无可查看的患者档案', icon: 'none' });
|
||||
const params = [
|
||||
`teamId=${encodeURIComponent(session.value?.teamId || '')}`,
|
||||
`corpId=${encodeURIComponent(corpId.value)}`,
|
||||
`id=${encodeURIComponent(customerId)}`,
|
||||
];
|
||||
uni.navigateTo({ url: `/pages/archive/edit-archive?${params.join('&')}` });
|
||||
}
|
||||
async function detail() {
|
||||
const res = await api('getAiConsultSessionDetail', { corpId: corpId.value, sessionId: sessionId.value, miniAppId: openid.value || uni.getStorageSync('openid') }, false);
|
||||
if (!res?.success) return uni.showToast({ title: removeAiLabel(res?.message, '加载失败'), icon: 'none' });
|
||||
session.value = res.data.session;
|
||||
avatarLoadFailed.value = false;
|
||||
patientInfo.value = res.data.patient || null;
|
||||
messages.value = res.data.messages || [];
|
||||
active.value = session.value.status === 'active';
|
||||
uni.setNavigationBarTitle({ title: assistantDisplayName.value });
|
||||
@ -121,41 +163,55 @@ async function send() {
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(opts => { corpId.value = opts.corpId; sessionId.value = opts.sessionId; detail(); });
|
||||
onLoad(opts => {
|
||||
corpId.value = opts.corpId;
|
||||
sessionId.value = opts.sessionId;
|
||||
uni.onKeyboardHeightChange(handleKeyboardHeightChange);
|
||||
detail();
|
||||
});
|
||||
onShow(detail);
|
||||
onUnload(() => {
|
||||
uni.offKeyboardHeightChange(handleKeyboardHeightChange);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chat-page { height: 100vh; display: flex; flex-direction: column; background: #f5f7fb; overflow: hidden; }
|
||||
.chat-page { position: fixed; top: 0; right: 0; left: 0; display: flex; flex-direction: column; background: #f5f7fb; overflow: hidden; }
|
||||
.patient-info-bar { height: 80rpx; padding: 0 24rpx; display: flex; align-items: center; box-sizing: border-box; background: #fff; border-bottom: 1rpx solid #edf0f4; flex-shrink: 0; }
|
||||
.patient-summary { display: flex; align-items: center; gap: 16rpx; min-width: 0; flex: 1; }
|
||||
.patient-name { max-width: 220rpx; color: #283242; font-size: 30rpx; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.patient-detail { min-width: 0; color: #7d8590; font-size: 27rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.patient-action { margin-left: 16rpx; color: #0877f1; font-size: 26rpx; white-space: nowrap; flex-shrink: 0; }
|
||||
.patient-arrow { margin-left: 6rpx; color: #0877f1; font-size: 34rpx; line-height: 1; flex-shrink: 0; }
|
||||
.chat-content { flex: 1; height: 0; box-sizing: border-box; }
|
||||
.message-list { padding: 28rpx 24rpx 40rpx; }
|
||||
.message-item { margin-bottom: 28rpx; }
|
||||
.message-content { display: flex; align-items: flex-start; gap: 16rpx; }
|
||||
.message-patient .message-content { justify-content: flex-end; }
|
||||
.avatar { width: 72rpx; height: 72rpx; flex: 0 0 72rpx; display: flex; align-items: center; justify-content: center; border-radius: 50%; font-size: 24rpx; font-weight: 600; }
|
||||
.avatar { width: 72rpx; height: 72rpx; flex: 0 0 72rpx; display: flex; align-items: center; justify-content: center; border-radius: 50%; font-size: 26rpx; font-weight: 600; }
|
||||
.assistant-avatar { color: #fff; background: linear-gradient(135deg, #4d93ff, #0877f1); }
|
||||
.patient-avatar { color: #0877f1; background: #e2efff; }
|
||||
.message-bubble-container { max-width: 74%; min-width: 0; }
|
||||
.message-patient .message-bubble-container { display: flex; flex-direction: column; align-items: flex-end; }
|
||||
.username-label { margin: 2rpx 0 10rpx; color: #8a919f; font-size: 24rpx; line-height: 32rpx; }
|
||||
.username-label { margin: 2rpx 0 10rpx; color: #8a919f; font-size: 26rpx; line-height: 34rpx; }
|
||||
.message-bubble { padding: 18rpx 22rpx; border-radius: 8rpx 22rpx 22rpx; background: #fff; box-shadow: 0 4rpx 14rpx rgba(17, 48, 89, .06); }
|
||||
.message-patient .message-bubble { color: #fff; border-radius: 22rpx 8rpx 22rpx 22rpx; background: #0877f1; box-shadow: 0 4rpx 14rpx rgba(8, 119, 241, .18); }
|
||||
.message-bubble.pending { opacity: .72; }
|
||||
.message-text { color: inherit; font-size: 30rpx; line-height: 46rpx; white-space: pre-wrap; word-break: break-word; }
|
||||
.time-divider { margin: 2rpx 0 22rpx; color: #a0a6b0; text-align: center; font-size: 23rpx; }
|
||||
.system-message { display: inline-block; margin: 0 auto; padding: 10rpx 20rpx; color: #79808c; background: #e9ecf1; border-radius: 22rpx; font-size: 24rpx; line-height: 36rpx; }
|
||||
.message-text { color: inherit; font-size: 32rpx; line-height: 48rpx; white-space: pre-wrap; word-break: break-word; }
|
||||
.time-divider { margin: 2rpx 0 22rpx; color: #a0a6b0; text-align: center; font-size: 25rpx; }
|
||||
.system-message { display: inline-block; margin: 0 auto; padding: 10rpx 20rpx; color: #79808c; background: #e9ecf1; border-radius: 22rpx; font-size: 26rpx; line-height: 38rpx; }
|
||||
.message-system { text-align: center; }
|
||||
.system-message.warn { color: #d84256; background: #fff0f1; }
|
||||
.send-status { margin-top: 8rpx; color: #e34d59; font-size: 23rpx; }
|
||||
.send-status { margin-top: 8rpx; color: #e34d59; font-size: 25rpx; }
|
||||
.thinking { display: flex; align-items: center; gap: 8rpx; min-width: 84rpx; padding: 26rpx 28rpx; }
|
||||
.thinking-dot { width: 10rpx; height: 10rpx; border-radius: 50%; background: #7f8998; animation: thinking 1.2s infinite ease-in-out; }
|
||||
.thinking-dot:nth-child(2) { animation-delay: .16s; }
|
||||
.thinking-dot:nth-child(3) { animation-delay: .32s; }
|
||||
@keyframes thinking { 0%, 60%, 100% { opacity: .35; transform: translateY(0); } 30% { opacity: 1; transform: translateY(-7rpx); } }
|
||||
.input-section { display: flex; align-items: flex-end; gap: 16rpx; padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom)); background: #fff; border-top: 1rpx solid #edf0f4; }
|
||||
.text-input { flex: 1; max-height: 180rpx; padding: 16rpx 20rpx; box-sizing: border-box; color: #283242; background: #f3f5f8; border-radius: 12rpx; font-size: 29rpx; line-height: 40rpx; }
|
||||
.send-btn { flex: 0 0 auto; height: 72rpx; margin: 0; padding: 0 26rpx; color: #fff; background: #0877f1; border-radius: 12rpx; font-size: 27rpx; line-height: 72rpx; }
|
||||
.text-input { flex: 1; max-height: 180rpx; padding: 16rpx 20rpx; box-sizing: border-box; color: #283242; background: #f3f5f8; border-radius: 12rpx; font-size: 31rpx; line-height: 42rpx; }
|
||||
.send-btn { flex: 0 0 auto; height: 72rpx; margin: 0; padding: 0 26rpx; color: #fff; background: #0877f1; border-radius: 12rpx; font-size: 29rpx; line-height: 72rpx; }
|
||||
.send-btn[disabled] { color: #fff; background: #a9cfff; }
|
||||
.rate-card { display: block; margin: 20rpx 0 0; }
|
||||
.ended { padding: 30rpx; color: #7d8590; text-align: center; background: #fff; font-size: 27rpx; }
|
||||
.ended { padding: 30rpx; color: #7d8590; text-align: center; background: #fff; font-size: 29rpx; }
|
||||
</style>
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
<view v-for="conversation in conversationList" :key="conversation.source === 'ai' ? `ai-${conversation.sessionId}` : (conversation.groupID || conversation.conversationID)"
|
||||
class="message-item" @click="handleClickConversation(conversation)">
|
||||
<view class="avatar-container">
|
||||
<image v-if="conversation.source === 'ai'" class="avatar" src="/static/home/ai-consult-assistant.png" mode="aspectFill" />
|
||||
<image v-if="conversation.source === 'ai'" class="avatar" :src="conversation.assistantAvatar || defaultAssistantAvatar" mode="aspectFill" @error="handleConversationAvatarError(conversation)" />
|
||||
<GroupAvatar v-else :avatarList="getAvatarList(conversation.groupID)" :size="96" classType="square" />
|
||||
<view v-if="conversation.unreadCount > 0" class="unread-badge">
|
||||
<text class="unread-text">{{
|
||||
@ -101,6 +101,7 @@ const hasMore = ref(false);
|
||||
const refreshing = ref(false);
|
||||
const showSubscribeEntry = ref(false);
|
||||
const HOME_CURRENT_TEAM_CACHE_KEY = "home-current-team-info";
|
||||
const defaultAssistantAvatar = "/static/home/ai-consult-assistant.png";
|
||||
|
||||
// 群聊头像管理
|
||||
const { loadGroupAvatars, getAvatarList } = useGroupAvatars();
|
||||
@ -262,6 +263,10 @@ function mergeConsultationList(imConversations) {
|
||||
);
|
||||
}
|
||||
|
||||
function handleConversationAvatarError(conversation) {
|
||||
conversation.assistantAvatar = defaultAssistantAvatar;
|
||||
}
|
||||
|
||||
const loadAiConsultationList = async () => {
|
||||
const corpId = getCurrentTeamCorpId();
|
||||
const miniAppId = openid.value || account.value?.openid || "";
|
||||
@ -281,6 +286,7 @@ const loadAiConsultationList = async () => {
|
||||
sessionId: item.id,
|
||||
corpId: item.corpId,
|
||||
displayName: removeAiLabel(item.assistantName, "咨询助理"),
|
||||
assistantAvatar: item.assistantAvatar || defaultAssistantAvatar,
|
||||
teamName: item.teamName || "",
|
||||
patientName: item.customerName || "未命名患者",
|
||||
lastMessage: removeAiLabel(item.statusLabel, "咨询"),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user