939 lines
24 KiB
Vue
939 lines
24 KiB
Vue
<script setup>
|
||
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 { 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';
|
||
import { useChatStore } from '../stores/chat.ts';
|
||
import { useThemeStore } from '../stores/theme.ts';
|
||
import { useUserStore } from '../stores/user.ts';
|
||
import { sendMessage } from '../utils/request.ts';
|
||
|
||
// 定义 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');
|
||
});
|
||
|
||
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(() => {
|
||
return isClinicModule.value ? endConsultationClinic : endConsultationPharmacy;
|
||
});
|
||
|
||
const getDrugsByRegisterId = computed(() => {
|
||
return isClinicModule.value
|
||
? getDrugsByRegisterIdClinic
|
||
: getDrugsByRegisterIdPharmacy;
|
||
});
|
||
|
||
const QuickReplyBubbles = computed(() => {
|
||
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';
|
||
|
||
const userStore = useUserStore();
|
||
const chatStore = useChatStore();
|
||
const themeStore = useThemeStore();
|
||
const prescriptionStore = usePrescriptionStore();
|
||
|
||
const messageText = ref('');
|
||
const messageInputRef = ref(null);
|
||
const showEmojiPicker = ref(false);
|
||
const showTransferDrawer = ref(false);
|
||
const transferRegisterId = ref(null);
|
||
|
||
// 使用文件上传组合式函数
|
||
const {
|
||
uploadPreview,
|
||
imageInput,
|
||
videoInput,
|
||
triggerFileInput,
|
||
handleFileUpload,
|
||
cancelUpload,
|
||
} = useFileUpload();
|
||
|
||
// 使用录音组合式函数
|
||
const { isRecording, startRecording, stopRecording } = useRecording();
|
||
|
||
// 发送上传的文件
|
||
const sendUploadedFile = () => {
|
||
if (!uploadPreview.value) return;
|
||
|
||
// 消息类型映射
|
||
const messageTypeMap = {
|
||
text: 0,
|
||
image: 1,
|
||
audio: 2,
|
||
video: 3,
|
||
prescription: 4,
|
||
'medical-record': 5,
|
||
'video-call': 6,
|
||
'audio-call': 7,
|
||
file: 8,
|
||
};
|
||
|
||
const messageObj = {
|
||
message_type: messageTypeMap[uploadPreview.value.type] || 0,
|
||
message_content: uploadPreview.value.url,
|
||
created_at_text: new Date().toLocaleTimeString().slice(0, 5),
|
||
created_at: Date.now(),
|
||
sender_user_id: `doctor-${userStore.currentUser.doctor_id}`,
|
||
receiver_user_id: chatStore.currentFriend.id,
|
||
room_id: chatStore.currentFriend.room_id,
|
||
read: true,
|
||
duration: 0,
|
||
isSent: true, // 标记为自己发送的消息,确保显示在右侧
|
||
};
|
||
|
||
chatStore.addMessage(messageObj, userStore.currentUser.doctor_id);
|
||
|
||
// 发送到服务器
|
||
sendMessage({
|
||
senderId: userStore.currentUser.doctor_id,
|
||
receiverId: chatStore.currentFriend.id,
|
||
roomId: chatStore.currentFriend.room_id, // 添加房间ID
|
||
type: uploadPreview.value.type,
|
||
content: uploadPreview.value.url,
|
||
}).catch((error) => {
|
||
console.error('发送文件失败:', error);
|
||
message.error('文件发送失败');
|
||
});
|
||
|
||
uploadPreview.value = null;
|
||
};
|
||
|
||
// 提供给子组件
|
||
provide('uploadPreview', uploadPreview);
|
||
provide('cancelUpload', cancelUpload);
|
||
provide('sendFile', sendUploadedFile);
|
||
|
||
// 发送文本消息
|
||
const sendTextMessage = async () => {
|
||
const content = messageText.value.trim();
|
||
if (!content && !uploadPreview.value) return;
|
||
|
||
let messageContent = content;
|
||
let messageType = 'text';
|
||
|
||
// 如果有文件预览,发送文件
|
||
if (uploadPreview.value) {
|
||
messageContent = uploadPreview.value.url;
|
||
messageType = uploadPreview.value.type;
|
||
uploadPreview.value = null;
|
||
}
|
||
|
||
// 消息类型映射
|
||
const messageTypeMap = {
|
||
text: 0,
|
||
image: 1,
|
||
audio: 2,
|
||
video: 3,
|
||
prescription: 4,
|
||
'medical-record': 5,
|
||
'video-call': 6,
|
||
'audio-call': 7,
|
||
file: 8,
|
||
register: 10,
|
||
'patient-experience': 11,
|
||
'product-card': 12,
|
||
'end-consultation': 13,
|
||
};
|
||
|
||
// 创建消息对象 - 使用数据库字段名
|
||
const messageObj = {
|
||
message_type: messageTypeMap[messageType] || 0,
|
||
message_content: messageContent,
|
||
created_at_text: new Date().toLocaleTimeString().slice(0, 5),
|
||
created_at: Date.now(),
|
||
sender_user_id: `doctor-${userStore.currentUser.doctor_id}`,
|
||
receiver_user_id: chatStore.currentFriend.id,
|
||
room_id: chatStore.currentFriend.room_id,
|
||
isSent: true,
|
||
read: true,
|
||
duration: 0,
|
||
};
|
||
|
||
// return console.log('发送消息:' + userStore.currentUser.doctor_id, messageObj);
|
||
// 添加到本地消息列表
|
||
chatStore.addMessage(messageObj, userStore.currentUser.doctor_id);
|
||
|
||
// 发送到服务器
|
||
try {
|
||
await sendMessage({
|
||
senderId: userStore.currentUser.doctor_id,
|
||
receiverId: chatStore.currentFriend.id,
|
||
roomId: chatStore.currentFriend.room_id,
|
||
type: messageType,
|
||
content: messageContent,
|
||
});
|
||
} catch (error) {
|
||
console.error('发送消息失败:', error);
|
||
message.error('消息发送失败');
|
||
}
|
||
|
||
// 清空输入框
|
||
messageText.value = '';
|
||
|
||
// 重置焦点
|
||
nextTick(() => {
|
||
if (messageInputRef.value) {
|
||
messageInputRef.value.focus();
|
||
}
|
||
});
|
||
};
|
||
|
||
// 处理键盘事件
|
||
const handleKeydown = (event) => {
|
||
if (event.key === 'Enter' && !event.shiftKey) {
|
||
event.preventDefault();
|
||
sendTextMessage();
|
||
}
|
||
};
|
||
|
||
// 处理粘贴文件
|
||
const handlePasteFile = (file) => {
|
||
console.log('检测到粘贴文件:', file);
|
||
|
||
// 根据文件类型自动选择处理方式
|
||
if (file.type.startsWith('image/')) {
|
||
handleFileFromPaste(file, 'image');
|
||
} else if (file.type.startsWith('video/')) {
|
||
handleFileFromPaste(file, 'video');
|
||
} else {
|
||
message.warning('不支持的文件类型');
|
||
}
|
||
};
|
||
|
||
// 处理粘贴的文件
|
||
const handleFileFromPaste = async (file, type) => {
|
||
// 文件大小检查
|
||
const maxSize = type === 'image' ? 10 * 1024 * 1024 : 50 * 1024 * 1024;
|
||
if (file.size > maxSize) {
|
||
const maxSizeMB = maxSize / 1024 / 1024;
|
||
message.error(
|
||
`文件大小超过限制!${type === 'image' ? '图片' : '视频'}最大${maxSizeMB}MB`,
|
||
);
|
||
return;
|
||
}
|
||
|
||
let uploadFile = file;
|
||
let compressMeta = null;
|
||
|
||
if (type === 'image') {
|
||
const prepared = await prepareImageForUpload(file);
|
||
if (!prepared.file) {
|
||
return;
|
||
}
|
||
uploadFile = prepared.file;
|
||
compressMeta = prepared.meta;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.addEventListener('load', (e) => {
|
||
uploadPreview.value = {
|
||
type,
|
||
url: e.target.result,
|
||
name: file.name,
|
||
size: uploadFile.size,
|
||
file: uploadFile,
|
||
compressMeta,
|
||
};
|
||
});
|
||
|
||
reader.onerror = (error) => {
|
||
console.error('文件读取失败:', error);
|
||
message.error('文件读取失败,请重试');
|
||
};
|
||
|
||
reader.readAsDataURL(file);
|
||
};
|
||
|
||
// 切换表情选择器
|
||
const toggleEmojiPicker = () => {
|
||
showEmojiPicker.value = !showEmojiPicker.value;
|
||
};
|
||
|
||
// 插入表情
|
||
const insertEmoji = (emoji) => {
|
||
messageText.value += emoji;
|
||
showEmojiPicker.value = false;
|
||
|
||
nextTick(() => {
|
||
if (messageInputRef.value) {
|
||
messageInputRef.value.focus();
|
||
}
|
||
});
|
||
};
|
||
|
||
// 监听录音完成事件
|
||
const handleAudioRecorded = (event) => {
|
||
const { url, duration } = event.detail;
|
||
|
||
const messageObj = {
|
||
message_type: 2, // audio
|
||
message_content: url,
|
||
created_at_text: new Date().toLocaleTimeString().slice(0, 5),
|
||
created_at: Date.now(),
|
||
sender_user_id: `doctor-${userStore.currentUser.doctor_id}`,
|
||
receiver_user_id: chatStore.currentFriend.id,
|
||
room_id: chatStore.currentFriend.room_id,
|
||
isSent: true,
|
||
read: true,
|
||
duration,
|
||
};
|
||
|
||
chatStore.addMessage(messageObj, userStore.currentUser.doctor_id);
|
||
|
||
// 发送到服务器
|
||
sendMessage({
|
||
senderId: userStore.currentUser.doctor_id,
|
||
receiverId: chatStore.currentFriend.id,
|
||
roomId: chatStore.currentFriend.room_id,
|
||
isSent: true,
|
||
type: 'audio',
|
||
content: url,
|
||
duration,
|
||
}).catch((error) => {
|
||
console.error('发送音频失败:', error);
|
||
message.error('音频发送失败');
|
||
});
|
||
};
|
||
|
||
onMounted(() => {
|
||
window.addEventListener('audioRecorded', handleAudioRecorded);
|
||
|
||
// 点击外部关闭表情选择器
|
||
document.addEventListener('click', (event) => {
|
||
if (
|
||
!event.target.closest('.emoji-picker') &&
|
||
!event.target.closest('.function-btn')
|
||
) {
|
||
showEmojiPicker.value = false;
|
||
}
|
||
});
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
window.removeEventListener('audioRecorded', handleAudioRecorded);
|
||
});
|
||
|
||
// 开方功能触发函数
|
||
const handleOpenPrescription = () => {
|
||
// 获取当前会话的register_id,这里假设可以通过某种方式获取
|
||
// 例如从聊天存储或props中获取
|
||
const registerId = chatStore.currentFriend?.register_id || 66;
|
||
|
||
if (registerId) {
|
||
emit('openPrescription', registerId);
|
||
} else {
|
||
console.warn('无法获取当前会话的register_id');
|
||
// 可以添加一个提示或使用默认值
|
||
emit('openPrescription', 0);
|
||
}
|
||
};
|
||
|
||
// 处理快速回复选择
|
||
const handleQuickReplySelect = (content) => {
|
||
// 设置输入框内容并发送
|
||
messageText.value = content;
|
||
sendTextMessage();
|
||
};
|
||
|
||
// 判断是否显示在线复诊相关按钮
|
||
const showOnlineConsultationButtons = computed(() => {
|
||
console.log('检查是否显示在线复诊相关按钮:', chatStore.currentFriend);
|
||
// 检查是否有 register_id,如果有则说明是在线复诊场景
|
||
return !!chatStore.currentFriend?.register_id;
|
||
});
|
||
|
||
// 将患者就诊经历中的药品加入处方单
|
||
const handleAddPatientProductsToPrescription = async () => {
|
||
// 检查处方store是否已初始化
|
||
if (!prescriptionStore.currentRegisterId) {
|
||
const registerId = chatStore.currentFriend?.register_id;
|
||
if (registerId) {
|
||
// 根据模块选择对应的存储前缀
|
||
const storagePrefix = isClinicModule.value
|
||
? 'onlineConsultationClinic-'
|
||
: 'onlineConsultation-';
|
||
prescriptionStore.initializePrescription(
|
||
registerId,
|
||
false,
|
||
storagePrefix,
|
||
);
|
||
} else {
|
||
message.warning('请先接诊患者');
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 获取诊所ID
|
||
let storeId = prescriptionStore.myStoreId;
|
||
if (!storeId || storeId === 0) {
|
||
if (
|
||
prescriptionStore.myStoreList &&
|
||
prescriptionStore.myStoreList.length > 0
|
||
) {
|
||
storeId = prescriptionStore.myStoreList[0].id;
|
||
} else {
|
||
message.warning('请先选择诊所');
|
||
return;
|
||
}
|
||
}
|
||
|
||
try {
|
||
// 根据 register_id 获取药品信息
|
||
const res = await getDrugsByRegisterId.value(
|
||
prescriptionStore.currentRegisterId ||
|
||
chatStore.currentFriend?.register_id,
|
||
storeId,
|
||
);
|
||
const drugList = res?.result || res?.data || res || [];
|
||
|
||
if (!drugList || drugList.length === 0) {
|
||
message.warning('未找到药品信息');
|
||
return;
|
||
}
|
||
|
||
// 遍历药品列表,添加到处方单
|
||
let addedCount = 0;
|
||
for (const drugData of drugList) {
|
||
const success = prescriptionStore.addProducts(drugData);
|
||
if (success) {
|
||
addedCount++;
|
||
}
|
||
}
|
||
|
||
if (addedCount > 0) {
|
||
message.success(`已成功添加 ${addedCount} 个药品到处方单`);
|
||
// 触发打开处方模态框事件
|
||
emit(
|
||
'openPrescription',
|
||
prescriptionStore.currentRegisterId ||
|
||
chatStore.currentFriend?.register_id,
|
||
);
|
||
} else {
|
||
message.warning('所有药品已在处方中');
|
||
}
|
||
} catch (error) {
|
||
console.error('添加药品失败:', error);
|
||
message.error('添加药品失败,请重试');
|
||
}
|
||
};
|
||
|
||
// 结束问诊
|
||
const handleEndConsultation = () => {
|
||
const registerId = chatStore.currentFriend?.register_id;
|
||
if (!registerId) {
|
||
message.warning('患者信息不完整,无法结束接诊');
|
||
return;
|
||
}
|
||
|
||
const patientName = chatStore.currentFriend?.nick_name || '患者';
|
||
|
||
Modal.confirm({
|
||
title: '确认结束接诊',
|
||
content: `确定要结束${patientName}的复诊吗?`,
|
||
okText: '确定',
|
||
cancelText: '取消',
|
||
onOk: async () => {
|
||
try {
|
||
// 使用计算属性获取对应的endConsultation函数
|
||
const endConsultationFn = endConsultation.value;
|
||
await endConsultationFn({
|
||
register_id: registerId,
|
||
doctor_id: userStore.currentUser?.doctor_id || '39',
|
||
reason: '医生主动结束',
|
||
});
|
||
|
||
message.success('结束接诊成功');
|
||
emit('end-consultation-success');
|
||
} catch (error) {
|
||
console.error('结束接诊失败:', error);
|
||
message.error('结束接诊失败,请重试');
|
||
}
|
||
},
|
||
});
|
||
};
|
||
|
||
// 打开转接方查看抽屉
|
||
const handleOpenTransferView = async () => {
|
||
const registerId = chatStore.currentFriend?.register_id;
|
||
if (!registerId) {
|
||
message.warning('患者信息不完整,无法查看转接方');
|
||
return;
|
||
}
|
||
|
||
// 检查是否有转接方
|
||
try {
|
||
const res = await getTransferPrescriptionByRegisterApi(registerId);
|
||
const data = res?.result || res?.data || res;
|
||
if (data && data.id) {
|
||
transferRegisterId.value = registerId;
|
||
showTransferDrawer.value = true;
|
||
} else {
|
||
message.warning('该患者没有转接方信息');
|
||
}
|
||
} catch (error) {
|
||
console.error('检查转接方信息失败:', error);
|
||
message.error('检查转接方信息失败,请重试');
|
||
}
|
||
};
|
||
|
||
// 处理转接方导入成功
|
||
const handleTransferImportSuccess = (prescriptionData) => {
|
||
// 触发打开处方模态框的事件
|
||
emit('transfer-import-success', prescriptionData);
|
||
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"
|
||
>
|
||
<!-- 快速回复气泡(仅在线复诊场景显示) -->
|
||
<component
|
||
:is="QuickReplyBubbles"
|
||
v-if="chatStore.currentFriend"
|
||
doctor-id="32"
|
||
@select-reply="handleQuickReplySelect"
|
||
/>
|
||
|
||
<!-- 文件上传预览 -->
|
||
<FileUploadPreview v-if="uploadPreview" />
|
||
<!-- 表情选择器 -->
|
||
<EmojiPicker v-if="showEmojiPicker" class="mb-10" @select="insertEmoji" />
|
||
<!-- 工具栏 -->
|
||
<div class="toolbar">
|
||
<button
|
||
class="function-btn prescription-button"
|
||
@click="handleOpenPrescription"
|
||
title="开方"
|
||
>
|
||
<i class="fas fa-file-medical"></i>
|
||
<!-- <i class="fas fa-prescription"></i>-->
|
||
</button>
|
||
<button
|
||
class="function-btn"
|
||
@click="triggerFileInput('image')"
|
||
title="发送图片"
|
||
>
|
||
<i class="fas fa-image"></i>
|
||
</button>
|
||
<!-- <button-->
|
||
<!-- class="function-btn"-->
|
||
<!-- @click="triggerFileInput('video')"-->
|
||
<!-- title="发送视频"-->
|
||
<!-- >-->
|
||
<!-- <i class="fas fa-video"></i>-->
|
||
<!-- </button>-->
|
||
<button
|
||
:class="{ active: showEmojiPicker }"
|
||
class="function-btn"
|
||
title="选择表情"
|
||
@click="toggleEmojiPicker"
|
||
>
|
||
<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
|
||
v-if="showOnlineConsultationButtons"
|
||
class="function-btn"
|
||
title="将患者就诊经历中的药品加入处方单"
|
||
@click="handleAddPatientProductsToPrescription"
|
||
>
|
||
<PlusOutlined />
|
||
</button>
|
||
<!-- 转接方查看按钮(仅在线复诊场景显示) -->
|
||
<button
|
||
v-if="showOnlineConsultationButtons"
|
||
class="function-btn transfer-view-btn"
|
||
title="查看转接方"
|
||
@click="handleOpenTransferView"
|
||
>
|
||
<i class="fas fa-exchange-alt"></i>
|
||
</button>
|
||
<!-- 结束问诊按钮(仅在线复诊场景显示) -->
|
||
<button
|
||
v-if="showOnlineConsultationButtons"
|
||
class="function-btn end-consultation-btn"
|
||
title="结束问诊"
|
||
@click="handleEndConsultation"
|
||
>
|
||
<CloseOutlined />
|
||
</button>
|
||
</div>
|
||
|
||
<div class="input-container">
|
||
<!-- 输入框区域 -->
|
||
<div class="input-section">
|
||
<CustomTextarea
|
||
ref="messageInputRef"
|
||
v-model="messageText"
|
||
:auto-resize="true"
|
||
:detect-paste="true"
|
||
:max-rows="4"
|
||
class="message-textarea"
|
||
placeholder="输入消息... (支持拖拽文件)"
|
||
@keydown="handleKeydown"
|
||
@paste-file="handlePasteFile"
|
||
/>
|
||
|
||
<button
|
||
:disabled="!messageText.trim() && !uploadPreview"
|
||
class="send-btn"
|
||
title="发送消息"
|
||
@click="sendTextMessage"
|
||
>
|
||
<i class="fas fa-paper-plane"></i>
|
||
<div class="send-ripple"></div>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 隐藏的文件输入框 -->
|
||
<input
|
||
ref="imageInput"
|
||
accept="image/*"
|
||
class="hidden"
|
||
type="file"
|
||
@change="handleFileUpload"
|
||
/>
|
||
<input
|
||
ref="videoInput"
|
||
accept="video/*"
|
||
class="hidden"
|
||
type="file"
|
||
@change="handleFileUpload"
|
||
/>
|
||
|
||
<!-- 转接方查看抽屉 -->
|
||
<TransferPrescriptionViewDrawer
|
||
v-model:visible="showTransferDrawer"
|
||
:register-id="transferRegisterId"
|
||
@import-success="handleTransferImportSuccess"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.message-input-container {
|
||
position: relative;
|
||
padding: var(--chat-input-pad, 12px);
|
||
background: linear-gradient(135deg, #fff 0%, #f8fafc 100%);
|
||
}
|
||
|
||
.dark .message-input-container {
|
||
background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
|
||
}
|
||
|
||
.input-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--chat-input-gap, 10px);
|
||
}
|
||
|
||
.toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: var(--chat-toolbar-pad, 4px) 0;
|
||
border-top: none;
|
||
}
|
||
|
||
.dark .toolbar {
|
||
border-top-color: #374151;
|
||
}
|
||
|
||
.function-btn {
|
||
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 {
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.function-btn:hover {
|
||
background-color: #f0f0f0;
|
||
}
|
||
|
||
.dark .function-btn:hover {
|
||
background-color: #374151;
|
||
}
|
||
|
||
.prescription-button {
|
||
font-weight: 500;
|
||
color: #455cda;
|
||
}
|
||
|
||
.prescription-button:hover {
|
||
background-color: #e6e9ff;
|
||
}
|
||
|
||
.end-consultation-btn {
|
||
font-weight: 500;
|
||
color: #ff4d4f;
|
||
}
|
||
|
||
.end-consultation-btn:hover {
|
||
color: #ff4d4f;
|
||
background-color: #fff1f0;
|
||
}
|
||
|
||
.dark .end-consultation-btn:hover {
|
||
color: #ff4d4f;
|
||
background-color: rgb(255 77 79 / 20%);
|
||
}
|
||
|
||
.transfer-view-btn {
|
||
font-weight: 500;
|
||
color: #f97316;
|
||
}
|
||
|
||
.transfer-view-btn:hover {
|
||
color: #f97316;
|
||
background-color: #fff7ed;
|
||
}
|
||
|
||
.dark .transfer-view-btn:hover {
|
||
color: #f97316;
|
||
background-color: rgb(249 115 22 / 20%);
|
||
}
|
||
|
||
.function-buttons {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
}
|
||
|
||
.function-btn {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: var(--chat-fn-btn, 36px);
|
||
height: var(--chat-fn-btn, 36px);
|
||
padding: 0;
|
||
margin-right: 6px;
|
||
overflow: hidden;
|
||
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 {
|
||
color: white;
|
||
background: linear-gradient(135deg, #4361ee, #3f37c9);
|
||
box-shadow: 0 8px 20px rgb(67 97 238 / 30%);
|
||
transform: translateY(-2px) scale(1.05);
|
||
}
|
||
|
||
.function-btn.active {
|
||
color: white;
|
||
background: linear-gradient(135deg, #4361ee, #3f37c9);
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.function-btn.recording {
|
||
color: white;
|
||
background: linear-gradient(135deg, #ff6b6b, #ee5a52);
|
||
animation: pulse-recording 1s infinite;
|
||
}
|
||
|
||
@keyframes pulse-recording {
|
||
0% {
|
||
transform: scale(1);
|
||
}
|
||
|
||
50% {
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
100% {
|
||
transform: scale(1);
|
||
}
|
||
}
|
||
|
||
.recording-wave {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
width: 60px;
|
||
height: 60px;
|
||
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% {
|
||
opacity: 1;
|
||
transform: translate(-50%, -50%) scale(0.8);
|
||
}
|
||
|
||
100% {
|
||
opacity: 0;
|
||
transform: translate(-50%, -50%) scale(1.5);
|
||
}
|
||
}
|
||
|
||
.btn-ripple {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
width: 0;
|
||
height: 0;
|
||
background: rgb(255 255 255 / 30%);
|
||
border-radius: 50%;
|
||
transform: translate(-50%, -50%);
|
||
transition: all 0.6s ease;
|
||
}
|
||
|
||
.function-btn:active .btn-ripple {
|
||
width: 100px;
|
||
height: 100px;
|
||
}
|
||
|
||
.dark .function-btn {
|
||
color: #9ca3af;
|
||
background: linear-gradient(135deg, #374151, #4b5563);
|
||
}
|
||
|
||
.dark .function-btn:hover {
|
||
color: white;
|
||
background: linear-gradient(135deg, #4361ee, #3f37c9);
|
||
}
|
||
|
||
.input-section {
|
||
position: relative;
|
||
display: flex;
|
||
gap: var(--chat-input-gap, 10px);
|
||
align-items: end;
|
||
}
|
||
|
||
.message-textarea {
|
||
flex: 1;
|
||
}
|
||
|
||
.send-btn {
|
||
position: relative;
|
||
display: flex;
|
||
flex-shrink: 0;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: var(--chat-send-btn, 40px);
|
||
height: var(--chat-send-btn, 40px);
|
||
overflow: hidden;
|
||
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);
|
||
}
|
||
|
||
.send-btn:disabled {
|
||
cursor: not-allowed;
|
||
background: #94a3b8;
|
||
box-shadow: none;
|
||
transform: none;
|
||
}
|
||
|
||
.send-ripple {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
width: 0;
|
||
height: 0;
|
||
background: rgb(255 255 255 / 40%);
|
||
border-radius: 50%;
|
||
transform: translate(-50%, -50%);
|
||
transition: all 0.6s ease;
|
||
}
|
||
|
||
.send-btn:active:not(:disabled) .send-ripple {
|
||
width: 80px;
|
||
height: 80px;
|
||
}
|
||
|
||
.hidden {
|
||
display: none;
|
||
}
|
||
</style>
|