feat: GlassCard双视图、数据看板重构、地址粘贴识别、AI平台与素材库增强

This commit is contained in:
李琦
2026-10-19 16:20:07 +08:00
parent 50fc55925f
commit 98eb751392
180 changed files with 32500 additions and 7812 deletions

View File

@@ -1,9 +1,13 @@
<script setup>
import { ref, provide, nextTick, onMounted, onUnmounted, computed } from 'vue';
import { computed, nextTick, onMounted, onUnmounted, provide, ref } from 'vue';
import { useRoute } from 'vue-router';
import { CloseOutlined, PlusOutlined } from '@ant-design/icons-vue';
import { message, Modal } from 'ant-design-vue';
import { PlusOutlined, CloseOutlined } from '@ant-design/icons-vue';
import { usePrescriptionStore } from '#/store/prescription';
import { getTransferPrescriptionByRegisterApi } from '#/views/business/chat/api/transferPrescription';
import TransferPrescriptionViewDrawer from '#/views/doctor/online-consultation/components/TransferPrescriptionViewDrawer.vue';
import { useFileUpload } from '../composables/useFileUpload.ts';
import { useRecording } from '../composables/useRecording.ts';
@@ -11,21 +15,38 @@ import { useChatStore } from '../stores/chat.ts';
import { useThemeStore } from '../stores/theme.ts';
import { useUserStore } from '../stores/user.ts';
import { sendMessage } from '../utils/request.ts';
import { usePrescriptionStore } from '#/store/prescription';
import { getTransferPrescriptionByRegisterApi } from '#/views/business/chat/api/transferPrescription';
import TransferPrescriptionViewDrawer from '#/views/doctor/online-consultation/components/TransferPrescriptionViewDrawer.vue';
// 定义 props
const props = defineProps({
currentFriend: {
type: Object,
default: () => ({}),
},
});
// 定义 emits
const emit = defineEmits([
'sendMessage',
'openPrescription', // 添加开方事件
'end-consultation-success', // 结束接诊成功事件
'transfer-import-success', // 转接方导入成功事件
]);
// 根据路由判断是在线复诊-药店还是在线复诊-诊所
const route = useRoute();
const isClinicModule = computed(() => {
return route.path.includes('online-consultation-clinic');
});
// 动态导入对应的API和组件
import { endConsultation as endConsultationPharmacy, getDrugsByRegisterId as getDrugsByRegisterIdPharmacy } from '#/views/doctor/online-consultation/api/index';
import { endConsultation as endConsultationClinic, getDrugsByRegisterId as getDrugsByRegisterIdClinic } from '#/views/doctor/online-consultation-clinic/api/index';
import QuickReplyBubblesPharmacy from '#/views/doctor/online-consultation/components/QuickReplyBubbles.vue';
import {
endConsultation as endConsultationClinic,
getDrugsByRegisterId as getDrugsByRegisterIdClinic,
} from '#/views/doctor/online-consultation-clinic/api/index';
import QuickReplyBubblesClinic from '#/views/doctor/online-consultation-clinic/components/QuickReplyBubbles.vue';
// 动态导入对应的API和组件
import {
endConsultation as endConsultationPharmacy,
getDrugsByRegisterId as getDrugsByRegisterIdPharmacy,
} from '#/views/doctor/online-consultation/api/index';
import QuickReplyBubblesPharmacy from '#/views/doctor/online-consultation/components/QuickReplyBubbles.vue';
// 根据模块选择对应的API和组件
const endConsultation = computed(() => {
@@ -33,25 +54,22 @@ const endConsultation = computed(() => {
});
const getDrugsByRegisterId = computed(() => {
return isClinicModule.value ? getDrugsByRegisterIdClinic : getDrugsByRegisterIdPharmacy;
return isClinicModule.value
? getDrugsByRegisterIdClinic
: getDrugsByRegisterIdPharmacy;
});
const QuickReplyBubbles = computed(() => {
return isClinicModule.value ? QuickReplyBubblesClinic : QuickReplyBubblesPharmacy;
return isClinicModule.value
? QuickReplyBubblesClinic
: QuickReplyBubblesPharmacy;
});
import { prepareImageForUpload } from '#/utils/prepare-image-upload';
import CustomTextarea from './CustomTextarea.vue';
import EmojiPicker from './EmojiPicker.vue';
import FileUploadPreview from './FileUploadPreview.vue';
import { prepareImageForUpload } from '#/utils/prepare-image-upload';
// 定义 props
const props = defineProps({
currentFriend: {
type: Object,
default: () => ({})
}
});
const userStore = useUserStore();
const chatStore = useChatStore();
@@ -104,7 +122,7 @@ const sendUploadedFile = () => {
room_id: chatStore.currentFriend.room_id,
read: true,
duration: 0,
isSent: true, // 标记为自己发送的消息,确保显示在右侧
isSent: true, // 标记为自己发送的消息,确保显示在右侧
};
chatStore.addMessage(messageObj, userStore.currentUser.doctor_id);
@@ -113,7 +131,7 @@ const sendUploadedFile = () => {
sendMessage({
senderId: userStore.currentUser.doctor_id,
receiverId: chatStore.currentFriend.id,
roomId: chatStore.currentFriend.room_id, // 添加房间ID
roomId: chatStore.currentFriend.room_id, // 添加房间ID
type: uploadPreview.value.type,
content: uploadPreview.value.url,
}).catch((error) => {
@@ -339,14 +357,6 @@ onUnmounted(() => {
window.removeEventListener('audioRecorded', handleAudioRecorded);
});
// 定义 emits
const emit = defineEmits([
'sendMessage',
'openPrescription', // 添加开方事件
'end-consultation-success', // 结束接诊成功事件
'transfer-import-success' // 转接方导入成功事件
]);
// 开方功能触发函数
const handleOpenPrescription = () => {
// 获取当前会话的register_id,这里假设可以通过某种方式获取
@@ -383,8 +393,14 @@ const handleAddPatientProductsToPrescription = async () => {
const registerId = chatStore.currentFriend?.register_id;
if (registerId) {
// 根据模块选择对应的存储前缀
const storagePrefix = isClinicModule.value ? 'onlineConsultationClinic-' : 'onlineConsultation-';
prescriptionStore.initializePrescription(registerId, false, storagePrefix);
const storagePrefix = isClinicModule.value
? 'onlineConsultationClinic-'
: 'onlineConsultation-';
prescriptionStore.initializePrescription(
registerId,
false,
storagePrefix,
);
} else {
message.warning('请先接诊患者');
return;
@@ -394,7 +410,10 @@ const handleAddPatientProductsToPrescription = async () => {
// 获取诊所ID
let storeId = prescriptionStore.myStoreId;
if (!storeId || storeId === 0) {
if (prescriptionStore.myStoreList && prescriptionStore.myStoreList.length > 0) {
if (
prescriptionStore.myStoreList &&
prescriptionStore.myStoreList.length > 0
) {
storeId = prescriptionStore.myStoreList[0].id;
} else {
message.warning('请先选择诊所');
@@ -405,8 +424,9 @@ const handleAddPatientProductsToPrescription = async () => {
try {
// 根据 register_id 获取药品信息
const res = await getDrugsByRegisterId.value(
prescriptionStore.currentRegisterId || chatStore.currentFriend?.register_id,
storeId
prescriptionStore.currentRegisterId ||
chatStore.currentFriend?.register_id,
storeId,
);
const drugList = res?.result || res?.data || res || [];
@@ -427,7 +447,11 @@ const handleAddPatientProductsToPrescription = async () => {
if (addedCount > 0) {
message.success(`已成功添加 ${addedCount} 个药品到处方单`);
// 触发打开处方模态框事件
emit('openPrescription', prescriptionStore.currentRegisterId || chatStore.currentFriend?.register_id);
emit(
'openPrescription',
prescriptionStore.currentRegisterId ||
chatStore.currentFriend?.register_id,
);
} else {
message.warning('所有药品已在处方中');
}
@@ -459,7 +483,7 @@ const handleEndConsultation = () => {
await endConsultationFn({
register_id: registerId,
doctor_id: userStore.currentUser?.doctor_id || '39',
reason: '医生主动结束'
reason: '医生主动结束',
});
message.success('结束接诊成功');
@@ -468,7 +492,7 @@ const handleEndConsultation = () => {
console.error('结束接诊失败:', error);
message.error('结束接诊失败,请重试');
}
}
},
});
};
@@ -500,18 +524,22 @@ const handleOpenTransferView = async () => {
const handleTransferImportSuccess = (prescriptionData) => {
// 触发打开处方模态框的事件
emit('transfer-import-success', prescriptionData);
emit('openPrescription', transferRegisterId.value || chatStore.currentFriend?.register_id);
emit(
'openPrescription',
transferRegisterId.value || chatStore.currentFriend?.register_id,
);
};
</script>
<template>
<div class="message-input-container flex-shrink-0 bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700">
<div
class="message-input-container flex-shrink-0 bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700"
>
<!-- 快速回复气泡(仅在线复诊场景显示) -->
<component
:is="QuickReplyBubbles"
v-if="chatStore.currentFriend"
:doctor-id="'32'"
doctor-id="32"
@select-reply="handleQuickReplySelect"
/>
@@ -527,7 +555,7 @@ const handleTransferImportSuccess = (prescriptionData) => {
title="开方"
>
<i class="fas fa-file-medical"></i>
<!-- <i class="fas fa-prescription"></i>-->
<!-- <i class="fas fa-prescription"></i>-->
</button>
<button
class="function-btn"
@@ -536,13 +564,13 @@ const handleTransferImportSuccess = (prescriptionData) => {
>
<i class="fas fa-image"></i>
</button>
<!-- <button-->
<!-- class="function-btn"-->
<!-- @click="triggerFileInput('video')"-->
<!-- title="发送视频"-->
<!-- >-->
<!-- <i class="fas fa-video"></i>-->
<!-- </button>-->
<!-- <button-->
<!-- class="function-btn"-->
<!-- @click="triggerFileInput('video')"-->
<!-- title="发送视频"-->
<!-- >-->
<!-- <i class="fas fa-video"></i>-->
<!-- </button>-->
<button
:class="{ active: showEmojiPicker }"
class="function-btn"
@@ -551,18 +579,18 @@ const handleTransferImportSuccess = (prescriptionData) => {
>
<i class="fas fa-smile"></i>
</button>
<!-- <button-->
<!-- :class="{ recording: isRecording }"-->
<!-- class="function-btn record-btn"-->
<!-- title="按住录音"-->
<!-- @mousedown="startRecording"-->
<!-- @mouseleave="stopRecording"-->
<!-- @mouseup="stopRecording"-->
<!-- @touchend="stopRecording"-->
<!-- @touchstart="startRecording"-->
<!-- >-->
<!-- <i class="fas fa-microphone"></i>-->
<!-- </button>-->
<!-- <button-->
<!-- :class="{ recording: isRecording }"-->
<!-- class="function-btn record-btn"-->
<!-- title="按住录音"-->
<!-- @mousedown="startRecording"-->
<!-- @mouseleave="stopRecording"-->
<!-- @mouseup="stopRecording"-->
<!-- @touchend="stopRecording"-->
<!-- @touchstart="startRecording"-->
<!-- >-->
<!-- <i class="fas fa-microphone"></i>-->
<!-- </button>-->
<!-- 添加药品按钮(仅在线复诊场景显示) -->
<button
v-if="showOnlineConsultationButtons"
@@ -646,9 +674,9 @@ const handleTransferImportSuccess = (prescriptionData) => {
<style scoped>
.message-input-container {
padding: 20px;
position: relative;
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
padding: var(--chat-input-pad, 12px);
background: linear-gradient(135deg, #fff 0%, #f8fafc 100%);
}
.dark .message-input-container {
@@ -658,14 +686,14 @@ const handleTransferImportSuccess = (prescriptionData) => {
.input-container {
display: flex;
flex-direction: column;
gap: 16px;
gap: var(--chat-input-gap, 10px);
}
.toolbar {
display: flex;
align-items: center;
padding: 8px;
border-top: 1px solid #e2e8f0;
padding: var(--chat-toolbar-pad, 4px) 0;
border-top: none;
}
.dark .toolbar {
@@ -673,15 +701,15 @@ const handleTransferImportSuccess = (prescriptionData) => {
}
.function-btn {
background: none;
border: none;
cursor: pointer;
padding: 5px 10px;
border-radius: 4px;
margin-right: 5px;
display: flex;
align-items: center;
padding: 5px 10px;
margin-right: 5px;
color: #666;
cursor: pointer;
background: none;
border: none;
border-radius: 4px;
}
.dark .function-btn {
@@ -697,8 +725,8 @@ const handleTransferImportSuccess = (prescriptionData) => {
}
.prescription-button {
color: #455cda;
font-weight: 500;
color: #455cda;
}
.prescription-button:hover {
@@ -706,33 +734,33 @@ const handleTransferImportSuccess = (prescriptionData) => {
}
.end-consultation-btn {
color: #ff4d4f;
font-weight: 500;
color: #ff4d4f;
}
.end-consultation-btn:hover {
background-color: #fff1f0;
color: #ff4d4f;
background-color: #fff1f0;
}
.dark .end-consultation-btn:hover {
background-color: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
background-color: rgb(255 77 79 / 20%);
}
.transfer-view-btn {
color: #f97316;
font-weight: 500;
color: #f97316;
}
.transfer-view-btn:hover {
background-color: #fff7ed;
color: #f97316;
background-color: #fff7ed;
}
.dark .transfer-view-btn:hover {
background-color: rgba(249, 115, 22, 0.2);
color: #f97316;
background-color: rgb(249 115 22 / 20%);
}
.function-buttons {
@@ -743,38 +771,40 @@ const handleTransferImportSuccess = (prescriptionData) => {
.function-btn {
position: relative;
width: 44px;
height: 44px;
border-radius: 50%;
border: none;
background: linear-gradient(135deg, #f1f5f9, #e2e8f0);
color: #64748b;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
width: var(--chat-fn-btn, 36px);
height: var(--chat-fn-btn, 36px);
padding: 0;
margin-right: 6px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
font-size: var(--chat-fn-font, 15px);
color: #64748b;
cursor: pointer;
background: linear-gradient(135deg, #f1f5f9, #e2e8f0);
border: none;
border-radius: 50%;
box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.function-btn:hover {
background: linear-gradient(135deg, #4361ee, #3f37c9);
color: white;
background: linear-gradient(135deg, #4361ee, #3f37c9);
box-shadow: 0 8px 20px rgb(67 97 238 / 30%);
transform: translateY(-2px) scale(1.05);
box-shadow: 0 8px 20px rgba(67, 97, 238, 0.3);
}
.function-btn.active {
background: linear-gradient(135deg, #4361ee, #3f37c9);
color: white;
background: linear-gradient(135deg, #4361ee, #3f37c9);
transform: scale(1.1);
}
.function-btn.recording {
background: linear-gradient(135deg, #ff6b6b, #ee5a52);
color: white;
background: linear-gradient(135deg, #ff6b6b, #ee5a52);
animation: pulse-recording 1s infinite;
}
@@ -782,9 +812,11 @@ const handleTransferImportSuccess = (prescriptionData) => {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
@@ -794,22 +826,23 @@ const handleTransferImportSuccess = (prescriptionData) => {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 60px;
height: 60px;
border: 2px solid rgba(255, 255, 255, 0.3);
border: 2px solid rgb(255 255 255 / 30%);
border-radius: 50%;
transform: translate(-50%, -50%);
animation: wave-expand 1.5s infinite;
}
@keyframes wave-expand {
0% {
transform: translate(-50%, -50%) scale(0.8);
opacity: 1;
transform: translate(-50%, -50%) scale(0.8);
}
100% {
transform: translate(-50%, -50%) scale(1.5);
opacity: 0;
transform: translate(-50%, -50%) scale(1.5);
}
}
@@ -819,8 +852,8 @@ const handleTransferImportSuccess = (prescriptionData) => {
left: 50%;
width: 0;
height: 0;
background: rgb(255 255 255 / 30%);
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
transform: translate(-50%, -50%);
transition: all 0.6s ease;
}
@@ -831,20 +864,20 @@ const handleTransferImportSuccess = (prescriptionData) => {
}
.dark .function-btn {
background: linear-gradient(135deg, #374151, #4b5563);
color: #9ca3af;
background: linear-gradient(135deg, #374151, #4b5563);
}
.dark .function-btn:hover {
background: linear-gradient(135deg, #4361ee, #3f37c9);
color: white;
background: linear-gradient(135deg, #4361ee, #3f37c9);
}
.input-section {
display: flex;
align-items: end;
gap: 12px;
position: relative;
display: flex;
gap: var(--chat-input-gap, 10px);
align-items: end;
}
.message-textarea {
@@ -853,32 +886,33 @@ const handleTransferImportSuccess = (prescriptionData) => {
.send-btn {
position: relative;
width: 52px;
height: 52px;
border-radius: 50%;
border: none;
background: linear-gradient(135deg, #4361ee, #3f37c9);
color: white;
cursor: pointer;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
font-size: 18px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
width: var(--chat-send-btn, 40px);
height: var(--chat-send-btn, 40px);
overflow: hidden;
box-shadow: 0 4px 12px rgba(67, 97, 238, 0.3);
font-size: var(--chat-send-font, 15px);
color: white;
cursor: pointer;
background: linear-gradient(135deg, #4361ee, #3f37c9);
border: none;
border-radius: 50%;
box-shadow: 0 4px 12px rgb(67 97 238 / 30%);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.send-btn:hover:not(:disabled) {
box-shadow: 0 8px 20px rgb(67 97 238 / 40%);
transform: translateY(-2px) scale(1.05);
box-shadow: 0 8px 20px rgba(67, 97, 238, 0.4);
}
.send-btn:disabled {
background: #94a3b8;
cursor: not-allowed;
transform: none;
background: #94a3b8;
box-shadow: none;
transform: none;
}
.send-ripple {
@@ -887,8 +921,8 @@ const handleTransferImportSuccess = (prescriptionData) => {
left: 50%;
width: 0;
height: 0;
background: rgb(255 255 255 / 40%);
border-radius: 50%;
background: rgba(255, 255, 255, 0.4);
transform: translate(-50%, -50%);
transition: all 0.6s ease;
}
@@ -901,23 +935,4 @@ const handleTransferImportSuccess = (prescriptionData) => {
.hidden {
display: none;
}
/* 响应式设计 */
@media (max-width: 768px) {
.function-buttons {
gap: 8px;
}
.function-btn {
width: 40px;
height: 40px;
font-size: 16px;
}
.send-btn {
width: 48px;
height: 48px;
font-size: 16px;
}
}
</style>