fix: 音频文件
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,617 @@
|
||||
<script setup>
|
||||
import { ref, provide, nextTick, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
import { message } from 'ant-design-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';
|
||||
import CustomTextarea from './CustomTextarea.vue';
|
||||
import EmojiPicker from './EmojiPicker.vue';
|
||||
import FileUploadPreview from './FileUploadPreview.vue';
|
||||
|
||||
// 定义 props
|
||||
const props = defineProps({
|
||||
currentFriend: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
const userStore = useUserStore();
|
||||
const chatStore = useChatStore();
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const messageText = ref('');
|
||||
const messageInputRef = ref(null);
|
||||
const showEmojiPicker = ref(false);
|
||||
|
||||
// 使用文件上传组合式函数
|
||||
const {
|
||||
uploadPreview,
|
||||
imageInput,
|
||||
videoInput,
|
||||
triggerFileInput,
|
||||
handleFileUpload,
|
||||
cancelUpload,
|
||||
} = useFileUpload();
|
||||
|
||||
// 使用录音组合式函数
|
||||
const { isRecording, startRecording, stopRecording } = useRecording();
|
||||
|
||||
// 发送上传的文件
|
||||
const sendUploadedFile = () => {
|
||||
if (!uploadPreview.value) return;
|
||||
|
||||
const messageObj = {
|
||||
type: uploadPreview.value.type,
|
||||
content: uploadPreview.value.url,
|
||||
time: new Date().toLocaleTimeString().slice(0, 5),
|
||||
senderId: `${userStore.currentUser.id}`,
|
||||
receiverId: chatStore.currentFriend.id,
|
||||
read: true,
|
||||
duration: 0,
|
||||
};
|
||||
|
||||
chatStore.addMessage(messageObj, userStore.currentUser.id);
|
||||
|
||||
// 发送到服务器
|
||||
sendMessage({
|
||||
senderId: userStore.currentUser.id,
|
||||
receiverId: chatStore.currentFriend.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 messageObj = {
|
||||
type: messageType,
|
||||
content: messageContent,
|
||||
time: new Date().toLocaleTimeString().slice(0, 5),
|
||||
senderId: `${userStore.currentUser.id}`,
|
||||
receiverId: chatStore.currentFriend.id,
|
||||
isSent: true,
|
||||
read: true,
|
||||
duration: 0,
|
||||
};
|
||||
|
||||
// return console.log('发送消息:' + userStore.currentUser.id, messageObj);
|
||||
// 添加到本地消息列表
|
||||
chatStore.addMessage(messageObj, userStore.currentUser.id);
|
||||
|
||||
// 发送到服务器
|
||||
try {
|
||||
await sendMessage({
|
||||
senderId: userStore.currentUser.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 = (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;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', (e) => {
|
||||
uploadPreview.value = {
|
||||
type,
|
||||
url: e.target.result,
|
||||
name: file.nick_name,
|
||||
size: file.size,
|
||||
file,
|
||||
};
|
||||
});
|
||||
|
||||
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 = {
|
||||
type: 'audio',
|
||||
content: url,
|
||||
time: new Date().toLocaleTimeString().slice(0, 5),
|
||||
senderId: `${userStore.currentUser.id}`,
|
||||
receiverId: chatStore.currentFriend.id,
|
||||
isSent: true,
|
||||
read: true,
|
||||
duration,
|
||||
};
|
||||
|
||||
chatStore.addMessage(messageObj, userStore.currentUser.id);
|
||||
|
||||
// 发送到服务器
|
||||
sendMessage({
|
||||
senderId: userStore.currentUser.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);
|
||||
});
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits([
|
||||
'sendMessage',
|
||||
'openPrescription' // 添加开方事件
|
||||
]);
|
||||
|
||||
// 开方功能触发函数
|
||||
const handleOpenPrescription = () => {
|
||||
// 获取当前会话的register_id,这里假设可以通过某种方式获取
|
||||
// 例如从聊天存储或props中获取
|
||||
const registerId = props.currentFriend?.register_id || 0;
|
||||
|
||||
if (registerId) {
|
||||
emit('openPrescription', registerId);
|
||||
} else {
|
||||
console.warn('无法获取当前会话的register_id');
|
||||
// 可以添加一个提示或使用默认值
|
||||
emit('openPrescription', 0);
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="message-input-container" :class="{ dark: themeStore.isDarkMode }">
|
||||
<!-- 工具栏 -->
|
||||
<div class="toolbar">
|
||||
<button
|
||||
class="toolbar-button prescription-button"
|
||||
@click="handleOpenPrescription"
|
||||
title="开方"
|
||||
>
|
||||
<i class="iconfont icon-prescription"></i>
|
||||
开方
|
||||
</button>
|
||||
<button
|
||||
class="toolbar-button"
|
||||
@click="triggerFileInput('image')"
|
||||
title="发送图片"
|
||||
>
|
||||
<i class="fas fa-image"></i>
|
||||
图片
|
||||
</button>
|
||||
<button
|
||||
class="toolbar-button"
|
||||
@click="triggerFileInput('video')"
|
||||
title="发送视频"
|
||||
>
|
||||
<i class="fas fa-video"></i>
|
||||
视频
|
||||
</button>
|
||||
<button
|
||||
:class="{ active: showEmojiPicker }"
|
||||
class="toolbar-button"
|
||||
title="选择表情"
|
||||
@click="toggleEmojiPicker"
|
||||
>
|
||||
<i class="fas fa-smile"></i>
|
||||
表情
|
||||
</button>
|
||||
<button
|
||||
:class="{ recording: isRecording }"
|
||||
class="toolbar-button record-btn"
|
||||
title="按住录音"
|
||||
@mousedown="startRecording"
|
||||
@mouseleave="stopRecording"
|
||||
@mouseup="stopRecording"
|
||||
@touchend="stopRecording"
|
||||
@touchstart="startRecording"
|
||||
>
|
||||
<i class="fas fa-microphone"></i>
|
||||
录音
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 文件上传预览 -->
|
||||
<FileUploadPreview v-if="uploadPreview" />
|
||||
<!-- 表情选择器 -->
|
||||
<EmojiPicker v-if="showEmojiPicker" class="mb-10" @select="insertEmoji" />
|
||||
|
||||
<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"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.message-input-container {
|
||||
padding: 20px;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-input-container.dark {
|
||||
border-top-color: #374151;
|
||||
background: linear-gradient(135deg, #2d2d2d 0%, #1f2937 100%);
|
||||
}
|
||||
|
||||
.input-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.toolbar-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
margin-right: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.toolbar-button:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.prescription-button {
|
||||
color: #455cda;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.prescription-button:hover {
|
||||
background-color: #e6e9ff;
|
||||
}
|
||||
|
||||
.function-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.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);
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.function-btn:hover {
|
||||
background: linear-gradient(135deg, #4361ee, #3f37c9);
|
||||
color: white;
|
||||
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;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.function-btn.recording {
|
||||
background: linear-gradient(135deg, #ff6b6b, #ee5a52);
|
||||
color: white;
|
||||
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%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
animation: wave-expand 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes wave-expand {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) scale(0.8);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) scale(1.5);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-ripple {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transform: translate(-50%, -50%);
|
||||
transition: all 0.6s ease;
|
||||
}
|
||||
|
||||
.function-btn:active .btn-ripple {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.message-input-container.dark .function-btn {
|
||||
background: linear-gradient(135deg, #374151, #4b5563);
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.message-input-container.dark .function-btn:hover {
|
||||
background: linear-gradient(135deg, #4361ee, #3f37c9);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.input-section {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
gap: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-textarea {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.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;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(67, 97, 238, 0.3);
|
||||
}
|
||||
|
||||
.send-btn:hover:not(:disabled) {
|
||||
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;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.send-ripple {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
transform: translate(-50%, -50%);
|
||||
transition: all 0.6s ease;
|
||||
}
|
||||
|
||||
.send-btn:active:not(:disabled) .send-ripple {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.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>
|
||||
@@ -0,0 +1,793 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, computed, onMounted } from 'vue';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Descriptions,
|
||||
Image,
|
||||
ImagePreviewGroup,
|
||||
InputNumber,
|
||||
message,
|
||||
Modal,
|
||||
notification,
|
||||
RadioGroup,
|
||||
RadioButton,
|
||||
Row,
|
||||
Select,
|
||||
SelectOption,
|
||||
Tag,
|
||||
Textarea,
|
||||
Timeline,
|
||||
TimelineItem
|
||||
} from 'ant-design-vue';
|
||||
import { DeleteTwoTone, MinusOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import { getRegisterStatus } from '#/util/tool';
|
||||
import {
|
||||
addWestPrescription,
|
||||
checkChineseMedicineConflictApi,
|
||||
getDrugUseList,
|
||||
getMyStoreListApi,
|
||||
getPatientItem,
|
||||
getProcessRuleList,
|
||||
getProductListDoctorReception,
|
||||
switchStoreApi,
|
||||
} from '#/views/doctor/doctor-reception/api';
|
||||
|
||||
// 定义 props
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
registerId: {
|
||||
type: [Number, String],
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits(['update:visible', 'prescriptionSent']);
|
||||
|
||||
// 患者数据
|
||||
const patientInfo = ref<null | any>(null);
|
||||
const userPatientHealthInquiry = ref<null | any>(null);
|
||||
const prescriptionList = ref([]);
|
||||
|
||||
// 药品使用相关数据
|
||||
const drugUseNum = ref([]);
|
||||
const drugUseFrequency = ref([]);
|
||||
const drugUseType = ref([]);
|
||||
const drugUnit = ref([]);
|
||||
const drugTime = ref([]);
|
||||
const drugUseWay = ref([]);
|
||||
const myStoreList = ref([]);
|
||||
|
||||
const myStoreId = ref(0);
|
||||
const activeCategory = ref(2);
|
||||
const diagnosis = ref('');
|
||||
const medicalAdvice = ref('');
|
||||
const treatmentPrice = ref(0);
|
||||
const ruleType = ref(1);
|
||||
const drugList = ref([]);
|
||||
|
||||
// 计算商品总价
|
||||
const totalProductCost = computed(() => {
|
||||
if (currentDrugs.value.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
// 如果是中药的时候,计算总价格
|
||||
if (activeCategory.value === 1) {
|
||||
return currentDrugs.value.reduce(
|
||||
(sum, drug) => sum + drug.price * (drug.number || 1) * dosage.value,
|
||||
0,
|
||||
);
|
||||
}
|
||||
return currentDrugs.value.reduce(
|
||||
(sum, drug) => sum + drug.price * (drug.select_number || 1),
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
// 计算总价
|
||||
const totalCost = computed(() => {
|
||||
return totalProductCost.value + processingFee.value;
|
||||
});
|
||||
|
||||
const processRulePrice = ref(0);
|
||||
const calcMethod = ref(0);
|
||||
|
||||
// 计算加工费
|
||||
const processingFee = computed(() => {
|
||||
if (ruleType.value === 1) {
|
||||
return 0;
|
||||
}
|
||||
// 已选择药品为空的时候是0
|
||||
if (currentDrugs.value.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
// 获取本次选择的加工规则
|
||||
childProcessRuleList.value.find((item) => {
|
||||
if (item.id === childProcessRuleId.value) {
|
||||
calcMethod.value = item.calc_method;
|
||||
processRulePrice.value = item.price;
|
||||
}
|
||||
});
|
||||
// 模式1:固定价格模式
|
||||
if (calcMethod.value === 1) {
|
||||
return processRulePrice.value;
|
||||
}
|
||||
// 模式2:单价*用量
|
||||
if (calcMethod.value === 2) {
|
||||
return processRulePrice.value * dosage.value;
|
||||
}
|
||||
// 模式3:单价*用量*数量
|
||||
if (calcMethod.value === 3) {
|
||||
const number = currentDrugs.value.reduce(
|
||||
(sum, drug) => sum + drug.number,
|
||||
0,
|
||||
);
|
||||
return processRulePrice.value * dosage.value * number;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
const visible = ref<boolean>(false);
|
||||
const previewImage = ref([]);
|
||||
|
||||
// 说明书预览
|
||||
const setVisible = (value, instruction = ''): void => {
|
||||
previewImage.value = [];
|
||||
if (instruction == '') {
|
||||
message.error('该产品没有说明书!');
|
||||
} else {
|
||||
if (typeof instruction === 'string') {
|
||||
// 如果有逗号,则进行分割 没有则追加到previewImage
|
||||
if (instruction.includes(',')) {
|
||||
instruction = instruction.split(',');
|
||||
previewImage.value = instruction;
|
||||
} else {
|
||||
previewImage.value.push(instruction);
|
||||
}
|
||||
}
|
||||
visible.value = value;
|
||||
}
|
||||
};
|
||||
|
||||
// 数量增加
|
||||
const increment = (index: number) => {
|
||||
currentDrugs.value[index].select_number++;
|
||||
saveToLocalStorage();
|
||||
};
|
||||
|
||||
// 数量减少
|
||||
const decrement = (index: number) => {
|
||||
if (currentDrugs.value[index].select_number > 1) {
|
||||
currentDrugs.value[index].select_number--;
|
||||
saveToLocalStorage();
|
||||
}
|
||||
};
|
||||
|
||||
// 保存到本地存储
|
||||
const saveToLocalStorage = () => {
|
||||
localStorage.setItem(
|
||||
`prescriptionData-chat-${props.registerId}`,
|
||||
JSON.stringify(currentDrugs.value),
|
||||
);
|
||||
};
|
||||
|
||||
// 删除药品(本地)
|
||||
const removeDrug = (index: number) => {
|
||||
currentDrugs.value.splice(index, 1);
|
||||
updateLocalStorage();
|
||||
};
|
||||
|
||||
// 修改缓存的处方信息
|
||||
const updateLocalStorage = () => {
|
||||
localStorage.setItem(
|
||||
`prescriptionData-chat-${props.registerId}`,
|
||||
JSON.stringify(currentDrugs.value),
|
||||
);
|
||||
getCurrentDrugs();
|
||||
};
|
||||
|
||||
const currentDrugs = ref([]);
|
||||
const selectProductId = ref(0);
|
||||
|
||||
// 获取缓存的处方信息
|
||||
const getCurrentDrugs = () => {
|
||||
currentDrugs.value = JSON.parse(
|
||||
localStorage.getItem(`prescriptionData-chat-${props.registerId}`) || '[]',
|
||||
);
|
||||
};
|
||||
|
||||
const doctorSecondSign = ref(0);
|
||||
const checkData = ref([]);
|
||||
|
||||
// 检查中药的相冲
|
||||
const checkChineseMedicineConflict = () => {
|
||||
if (activeCategory.value === 2) {
|
||||
sendPrescription();
|
||||
return;
|
||||
}
|
||||
notification.info({
|
||||
message: '正在检查药物相冲',
|
||||
duration: 1,
|
||||
description: '正在检查药物相冲,请稍等...',
|
||||
});
|
||||
const names = currentDrugs.value.map((item) => item.drug_name);
|
||||
checkChineseMedicineConflictApi({names: names}).then((res) => {
|
||||
if (res.is_exist === true) {
|
||||
checkData.value.message = res.message;
|
||||
doctorSecondSignModal.value = true;
|
||||
} else {
|
||||
notification.success({
|
||||
message: '检查成功',
|
||||
duration: 3,
|
||||
description: '暂无相冲药品',
|
||||
});
|
||||
sendPrescription();
|
||||
}
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
// 发送处方
|
||||
const sendPrescription = () => {
|
||||
if (currentDrugs.value.length === 0) {
|
||||
message.error('请选择药品');
|
||||
return;
|
||||
}
|
||||
if (activeCategory.value === 1) {
|
||||
if (ruleType.value === 1) {
|
||||
if (packageMethodId.value == null) {
|
||||
message.error('请选择包法');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (processRuleId.value == null) {
|
||||
message.error('请选择制剂');
|
||||
return;
|
||||
}
|
||||
if (processRuleNoteId.value == null) {
|
||||
message.error('请选择规格');
|
||||
return;
|
||||
}
|
||||
if (childProcessRuleId.value == null) {
|
||||
message.error('请选择备注');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (diagnosis.value === '') {
|
||||
message.error('诊断结果不能为空');
|
||||
return;
|
||||
}
|
||||
if (medicalAdvice.value === '') {
|
||||
message.error('医嘱不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送处方逻辑
|
||||
addWestPrescription({
|
||||
// 当前活动患者的个人信息
|
||||
patient: patientInfo.value,
|
||||
// 当前处方中的药品列表
|
||||
drugs: currentDrugs.value,
|
||||
// 诊断信息
|
||||
diagnosis: diagnosis.value,
|
||||
// 医嘱信息
|
||||
medicalAdvice: medicalAdvice.value,
|
||||
// 总费用
|
||||
total: totalCost.value,
|
||||
// 费用类别
|
||||
category: category.value,
|
||||
// 药品类型
|
||||
drug_type: 2, // 默认为处方类型
|
||||
// 从props获取的挂号ID
|
||||
register_id: props.registerId,
|
||||
// 治疗费用
|
||||
treatment_price: treatmentPrice.value,
|
||||
// 包装方式ID
|
||||
package_method_id: packageMethodId.value,
|
||||
// 加工规则ID
|
||||
process_rule_id: processRuleId.value,
|
||||
// 加工规则备注ID
|
||||
process_rule_note_id: processRuleNoteId.value,
|
||||
// 子加工规则ID
|
||||
child_process_rule_id: childProcessRuleId.value,
|
||||
// 加工规则类型
|
||||
process_rule_type: ruleType.value,
|
||||
// 处方类型
|
||||
prescription_type: activeCategory.value,
|
||||
// 加工费
|
||||
processing_fee: processRulePrice.value,
|
||||
// 用药剂量
|
||||
dosage: dosage.value,
|
||||
// 每日用药次数
|
||||
day_dosage: dayDosage.value,
|
||||
// 是否二次签名
|
||||
doctor_second_sign: doctorSecondSign.value,
|
||||
}).then(() => {
|
||||
message.success('处方已发送');
|
||||
// 清空当前数据
|
||||
currentDrugs.value = [];
|
||||
diagnosis.value = '';
|
||||
medicalAdvice.value = '';
|
||||
packageMethodId.value = 2;
|
||||
processRulePrice.value = 0;
|
||||
dosage.value = 7;
|
||||
dayDosage.value = 2;
|
||||
newDrugInfo.value = {};
|
||||
updateLocalStorage();
|
||||
emit('prescriptionSent');
|
||||
});
|
||||
};
|
||||
|
||||
// 药品分类
|
||||
const categories = [
|
||||
{label: '中药', value: 1},
|
||||
{label: '中成(西)药', value: 2},
|
||||
];
|
||||
|
||||
const category = ref(1);
|
||||
|
||||
// 基础配置
|
||||
const processRuleList = ref([]);
|
||||
// 子配置
|
||||
const childProcessRuleList = ref([]);
|
||||
// 规格
|
||||
const processRuleNoteList = ref([]);
|
||||
const processRuleId = ref();
|
||||
const processRuleNoteId = ref();
|
||||
const childProcessRuleId = ref();
|
||||
|
||||
function getProcessRuleListByDoctor(pid = 0, ruleId = 0) {
|
||||
let data = {};
|
||||
data =
|
||||
ruleId === 0
|
||||
? {
|
||||
pid,
|
||||
}
|
||||
: {
|
||||
rule_id: ruleId,
|
||||
};
|
||||
getProcessRuleList(data).then((value) => {
|
||||
if (ruleId !== 0) {
|
||||
processRuleNoteList.value = value;
|
||||
} else if (pid === 0) {
|
||||
processRuleList.value = value;
|
||||
} else {
|
||||
childProcessRuleList.value = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 选择煎法
|
||||
function selectProcessRule(id) {
|
||||
processRuleId.value = id;
|
||||
getProcessRuleListByDoctor(id);
|
||||
}
|
||||
|
||||
// 选择规格
|
||||
function selectProcessRuleNot(id) {
|
||||
childProcessRuleId.value = id;
|
||||
getProcessRuleListByDoctor(0, id);
|
||||
}
|
||||
|
||||
// 选择备注
|
||||
function selectProcessRuleNotCommit(id) {
|
||||
processRuleNoteId.value = id;
|
||||
}
|
||||
|
||||
const packageMethodId = ref(2);
|
||||
const packageMethod = [
|
||||
{label: '味包', value: 1},
|
||||
{label: '剂包', value: 2},
|
||||
];
|
||||
|
||||
function selectPackageMethod(id) {
|
||||
packageMethodId.value = id;
|
||||
}
|
||||
|
||||
// 剂量
|
||||
const dosage = ref(7);
|
||||
const dayDosage = ref(2);
|
||||
|
||||
function updateChineseNumber() {
|
||||
updateLocalStorage();
|
||||
}
|
||||
|
||||
// 新药品确认弹窗
|
||||
const showNewDrugModal = ref(false);
|
||||
// 二次签名确认弹窗
|
||||
const doctorSecondSignModal = ref(false);
|
||||
|
||||
// 在新的药品数量框失去焦点后弹出新药品弹窗
|
||||
function newDrugBlur() {
|
||||
if (newDrugInfo.value.id > 0 && newDrugInfo.value.number > 0) {
|
||||
showNewDrugModal.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function newDrugModalOk() {
|
||||
addDrugByChinese();
|
||||
showNewDrugModal.value = false;
|
||||
}
|
||||
|
||||
function doctorSecondSignModalOk() {
|
||||
doctorSecondSign.value = 1;
|
||||
doctorSecondSignModal.value = false;
|
||||
sendPrescription();
|
||||
}
|
||||
|
||||
function updateChineseNumberGoNewDrug(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
updateLocalStorage();
|
||||
setTimeout(() => {
|
||||
// 获取新药品卡片中的克数输入框并聚焦
|
||||
const newDrugNumberInput = document.querySelector(
|
||||
'.new-select-drug-name input',
|
||||
);
|
||||
if (newDrugNumberInput) {
|
||||
(newDrugNumberInput as HTMLElement).focus();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取药品列表
|
||||
const getDrugListByWesternModal = debounce(async (searchText = '') => {
|
||||
if (searchText === '' && activeCategory.value === 1) {
|
||||
drugList.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
getCurrentDrugs();
|
||||
|
||||
try {
|
||||
const res = await getProductListDoctorReception({
|
||||
store_id: myStoreId.value,
|
||||
type: activeCategory.value,
|
||||
name: searchText,
|
||||
});
|
||||
|
||||
drugList.value = res.map((item) => {
|
||||
const matched = currentDrugs.value.find((v) => v.index_id === item.id);
|
||||
return {
|
||||
...item,
|
||||
select_number: matched?.select_number || 0,
|
||||
drug: {
|
||||
...item.drug,
|
||||
number: matched?.number || 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取药品列表失败:', error);
|
||||
message.error('获取药品列表失败');
|
||||
}
|
||||
}, 300);
|
||||
|
||||
// 选择药品
|
||||
function selectNewDrugInfo() {
|
||||
// 如果是新药品输入框,调用添加新药品方法
|
||||
const check = currentDrugs.value.find((v) => v.id === newDrugInfo.value.id);
|
||||
if (check) {
|
||||
newDrugInfo.value.id = '';
|
||||
message.error('该药品已经在处方中了!');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = drugList.value.find((v) => v.drug_id === newDrugInfo.value.id);
|
||||
newDrugInfo.value.price = data.price;
|
||||
newDrugInfo.value.name = data.drug.drug_name;
|
||||
setTimeout(() => {
|
||||
// 获取新药品卡片中的克数输入框并聚焦
|
||||
const newDrugNumberInput = document.querySelector(
|
||||
'.new-number-input input',
|
||||
);
|
||||
if (newDrugNumberInput) {
|
||||
(newDrugNumberInput as HTMLElement).focus();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
const selectChineseIndex = ref(-1);
|
||||
const selectChineseId = ref(0);
|
||||
|
||||
// 选择药品
|
||||
function selectOldDrugInfo(id) {
|
||||
const check = JSON.parse(
|
||||
localStorage.getItem(`prescriptionData-chat-${props.registerId}`) || '[]',
|
||||
).find((v) => v.id === id);
|
||||
if (check) {
|
||||
currentDrugs.value[selectChineseIndex.value].id = selectChineseId.value;
|
||||
drugList.value = [];
|
||||
message.error('该药品已经在处方中了!');
|
||||
return;
|
||||
}
|
||||
const oldDrugInfo = currentDrugs.value[selectChineseIndex.value];
|
||||
// 如果是新药品输入框,调用添加新药品方法
|
||||
const data = drugList.value.find((v) => v.drug_id === id);
|
||||
|
||||
// 创建新的商品对象(避免直接修改原始数据)
|
||||
const newProduct = {
|
||||
// 索引ID(用于在列表中查找)
|
||||
index_id: data.id,
|
||||
// 药品ID
|
||||
id: data.drug.id,
|
||||
// 药品名称
|
||||
drug_name: data.drug.drug_name,
|
||||
// 药品数量
|
||||
number: data.drug.number,
|
||||
// 药品使用数量信息
|
||||
use_num: drugTime.value.find((item) => item.id === data.drug.time_id),
|
||||
// 药品使用类型信息
|
||||
use_type: drugUseType.value.find((item) => item.id === data.drug.type_id),
|
||||
// 药品使用频率信息
|
||||
use_frequency: drugUseFrequency.value.find(
|
||||
(item) => item.id === data.drug.frequency_id,
|
||||
),
|
||||
// 药品单位信息
|
||||
unit: drugUnit.value.find((item) => item.id === data.drug.unit_id),
|
||||
// 药品价格
|
||||
price: data.price,
|
||||
// 药品使用方式ID
|
||||
way_id: data.drug?.way_id,
|
||||
// 药品使用方式信息
|
||||
use_ways: drugUseWay.value.find((item) => item.id === data.drug.way_id),
|
||||
// 药品使用时间ID
|
||||
time_id: data.drug.time_id,
|
||||
// 药品类型ID
|
||||
type_id: data.drug.type_id,
|
||||
// 药品使用频率ID
|
||||
frequency_id: data.drug.frequency_id,
|
||||
// 药品单位ID
|
||||
unit_id: data.drug.unit_id,
|
||||
// 药品图片
|
||||
image: data.drug.image,
|
||||
// 药品说明书
|
||||
instruction: data.drug.instruction,
|
||||
// 药品类型
|
||||
type: data.drug.type,
|
||||
};
|
||||
currentDrugs.value[selectChineseIndex.value] = newProduct;
|
||||
updateLocalStorage();
|
||||
setTimeout(() => {
|
||||
// 获取新药品卡片中的克数输入框并聚焦
|
||||
const newDrugNumberInput = document.querySelector(
|
||||
`.old-number-input-${selectChineseIndex.value} input`,
|
||||
);
|
||||
if (newDrugNumberInput) {
|
||||
(newDrugNumberInput as HTMLElement).focus();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// 写入当前修改的下标
|
||||
function setSelectChineseIndex(index, id) {
|
||||
selectChineseIndex.value = index;
|
||||
selectChineseId.value = id;
|
||||
}
|
||||
|
||||
// 查询中药列表
|
||||
function searchOption(inputValue) {
|
||||
getDrugListByWesternModal(inputValue);
|
||||
}
|
||||
|
||||
// 添加中药
|
||||
function addDrugByChinese() {
|
||||
// 如果是新药品输入框,调用添加新药品方法
|
||||
const check = currentDrugs.value.find(
|
||||
(v) => v.id === newDrugInfo.value.id,
|
||||
);
|
||||
if (check) {
|
||||
message.error('该药品已经在处方中了!');
|
||||
return;
|
||||
}
|
||||
const data = drugList.value.find(
|
||||
(v) => v.drug_id === newDrugInfo.value.id,
|
||||
);
|
||||
if (data === null || data === undefined) {
|
||||
message.error('请选择药品');
|
||||
return;
|
||||
}
|
||||
data.drug.number = newDrugInfo.value.number;
|
||||
data.drug.way_id = newDrugInfo.value.way_id;
|
||||
addProducts(data);
|
||||
newDrugInfo.value = {};
|
||||
}
|
||||
|
||||
// 添加操作
|
||||
function selectDrugByNewDrugInfo(event: KeyboardEvent, isNewDrug: boolean) {
|
||||
// 如果按下的是回车键
|
||||
if (event.key === 'Enter') {
|
||||
// 阻止默认行为(防止表单提交)
|
||||
event.preventDefault();
|
||||
|
||||
if (isNewDrug) {
|
||||
// 如果是新药品输入框,调用添加新药品方法
|
||||
addDrugByChinese();
|
||||
} else {
|
||||
// 如果是已有药品输入框,调用更新数量方法
|
||||
updateChineseNumber();
|
||||
}
|
||||
setTimeout(() => {
|
||||
// 获取新药品卡片中的克数输入框并聚焦
|
||||
const newDrugNumberInput = document.querySelector(
|
||||
'.new-select-drug-name input',
|
||||
);
|
||||
if (newDrugNumberInput) {
|
||||
(newDrugNumberInput as HTMLElement).focus();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加商品到处方
|
||||
function addProducts(data) {
|
||||
// 查找是否已存在于选择列表中
|
||||
const existItem = currentDrugs.value.find(
|
||||
(item) => item.index_id === data.id,
|
||||
);
|
||||
|
||||
if (existItem) {
|
||||
message.warn('已经存在了');
|
||||
return;
|
||||
}
|
||||
// 创建新的商品对象(避免直接修改原始数据)
|
||||
const newProduct = {
|
||||
// 索引ID(用于在列表中查找)
|
||||
index_id: data.id,
|
||||
// 药品ID
|
||||
id: data.drug.id,
|
||||
// 药品名称
|
||||
drug_name: data.drug.drug_name,
|
||||
// 药品数量
|
||||
number: data.drug.number,
|
||||
// 药品使用数量信息
|
||||
use_num: drugTime.value.find((item) => item.id === data.drug.time_id),
|
||||
// 药品使用类型信息
|
||||
use_type: drugUseType.value.find((item) => item.id === data.drug.type_id),
|
||||
// 药品使用频率信息
|
||||
use_frequency: drugUseFrequency.value.find(
|
||||
(item) => item.id === data.drug.frequency_id,
|
||||
),
|
||||
// 药品单位信息
|
||||
unit: drugUnit.value.find((item) => item.id === data.drug.unit_id),
|
||||
// 药品价格
|
||||
price: data.price,
|
||||
// 药品使用方式ID
|
||||
way_id: data.drug?.way_id,
|
||||
// 药品使用方式信息
|
||||
use_ways: drugUseWay.value.find((item) => item.id === data.drug.way_id),
|
||||
// 药品使用时间ID
|
||||
time_id: data.drug.time_id,
|
||||
// 药品类型ID
|
||||
type_id: data.drug.type_id,
|
||||
// 药品使用频率ID
|
||||
frequency_id: data.drug.frequency_id,
|
||||
// 药品单位ID
|
||||
unit_id: data.drug.unit_id,
|
||||
// 药品图片
|
||||
image: data.drug.image,
|
||||
// 药品说明书
|
||||
instruction: data.drug.instruction,
|
||||
// 药品类型
|
||||
type: data.drug.type,
|
||||
};
|
||||
|
||||
// 首次添加,初始化数量为1
|
||||
newProduct.select_number = 1;
|
||||
|
||||
// 添加到选中列表
|
||||
currentDrugs.value.push(newProduct);
|
||||
|
||||
// 更新本地存储
|
||||
updateLocalStorage();
|
||||
|
||||
// 提示成功
|
||||
message.success(`已将${newProduct.drug_name}添加到清单中!`);
|
||||
}
|
||||
|
||||
const newDrugInfo = ref({});
|
||||
|
||||
function selectProductChange(id) {
|
||||
selectProductId.value = id;
|
||||
}
|
||||
|
||||
// 选择中药用法
|
||||
function selectDrugUseWayChange(id) {
|
||||
currentDrugs.value = currentDrugs.value.map((item) =>
|
||||
// 修改item下面drug的 frequency_id = id
|
||||
item.index_id === selectProductId.value
|
||||
? {
|
||||
...item,
|
||||
way_id: id,
|
||||
use_ways: drugUseWay.value.find((value) => value.id === id),
|
||||
}
|
||||
: item,
|
||||
);
|
||||
updateLocalStorage();
|
||||
}
|
||||
|
||||
// 切换tab
|
||||
function tabChange(id) {
|
||||
localStorage.setItem(`activeCategory-chat-${props.registerId}`, id);
|
||||
currentDrugs.value = [];
|
||||
updateLocalStorage();
|
||||
activeCategory.value = id;
|
||||
}
|
||||
|
||||
// 根据创制string来把字符串的逗号分开变成数组
|
||||
function splitString(str: string) {
|
||||
if (!str) {
|
||||
return [];
|
||||
}
|
||||
return str.split(',');
|
||||
}
|
||||
|
||||
// 获取我的诊所列表
|
||||
function getMyStoreList() {
|
||||
getMyStoreListApi().then((res) => {
|
||||
myStoreList.value = res;
|
||||
if (res.length > 0) {
|
||||
myStoreId.value = res[0].id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取药品使用方式列表
|
||||
function getDrugUseListByWesternModal() {
|
||||
getDrugUseList().then((res) => {
|
||||
// 更新各种药品使用相关数据
|
||||
drugUseNum.value = res.drug_use_num;
|
||||
drugUseFrequency.value = res.drug_use_frequency;
|
||||
drugUseType.value = res.drug_use_type;
|
||||
drugUnit.value = res.drug_unit;
|
||||
drugTime.value = res.drug_time;
|
||||
drugUseWay.value = res.drug_use_way;
|
||||
});
|
||||
}
|
||||
|
||||
// 获取患者信息
|
||||
async function getPatientInfo() {
|
||||
try {
|
||||
const res = await getPatientItem(props.registerId);
|
||||
patientInfo.value = res;
|
||||
userPatientHealthInquiry.value = res.user_patient_health_inquiry;
|
||||
prescriptionList.value = res.prescription;
|
||||
} catch (error) {
|
||||
console.error('获取患者信息失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 visible prop 变化
|
||||
watch(() => props.visible, (newVal) => {
|
||||
visible.value = newVal;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getMyStoreList();
|
||||
getDrugUseListByWesternModal();
|
||||
getProcessRuleListByDoctor();
|
||||
getPatientInfo();
|
||||
getCurrentDrugs();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:open="visible"
|
||||
:width="1200"
|
||||
title="开方模块"
|
||||
@cancel="emit('update:visible', false)"
|
||||
@ok="sendPrescription"
|
||||
>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,724 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="prescription-module">
|
||||
<div style="width: 0; height: 0; overflow: hidden">
|
||||
<ImagePreviewGroup
|
||||
:preview="{
|
||||
visible,
|
||||
onVisibleChange: setVisible,
|
||||
}"
|
||||
:style="{ display: 'none' }"
|
||||
>
|
||||
<Image
|
||||
v-for="image in previewImage"
|
||||
v-if="visible === true"
|
||||
:src="image"
|
||||
:width="200"
|
||||
/>
|
||||
</ImagePreviewGroup>
|
||||
</div>
|
||||
|
||||
<!-- 患者信息 -->
|
||||
<Descriptions
|
||||
v-if="patientInfo"
|
||||
:column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 2, xs: 2 }"
|
||||
bordered
|
||||
title="患者信息"
|
||||
class="mb-5"
|
||||
>
|
||||
<Descriptions.Item label="患者姓名">
|
||||
{{ patientInfo.user_patient?.name }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="患者年龄">
|
||||
{{ patientInfo.user_patient?.age }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="患者性别">
|
||||
{{
|
||||
patientInfo.user_patient?.sex === 1
|
||||
? '男'
|
||||
: patientInfo.user_patient?.sex === 2
|
||||
? '女'
|
||||
: '未填写'
|
||||
}}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
<Descriptions
|
||||
v-if="userPatientHealthInquiry"
|
||||
:column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 2, xs: 2 }"
|
||||
bordered
|
||||
class="mt-5 mb-5"
|
||||
title="健康信息"
|
||||
>
|
||||
<Descriptions.Item label="肝功能">
|
||||
<span>{{
|
||||
userPatientHealthInquiry.liver_function === 0 ? '正常' : '异常'
|
||||
}}</span>
|
||||
<p
|
||||
v-if="userPatientHealthInquiry.liver_function === 1"
|
||||
class="mt-3"
|
||||
>
|
||||
<Tag
|
||||
v-for="item in splitString(
|
||||
userPatientHealthInquiry.liver_index,
|
||||
)"
|
||||
color="#455cda"
|
||||
>
|
||||
{{ item }}
|
||||
</Tag>
|
||||
</p>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="肾功能">
|
||||
<span>{{
|
||||
userPatientHealthInquiry.renal_function === 0 ? '正常' : '异常'
|
||||
}}</span>
|
||||
<p
|
||||
v-if="userPatientHealthInquiry.renal_function === 1"
|
||||
class="mt-3"
|
||||
>
|
||||
<Tag
|
||||
v-for="item in splitString(
|
||||
userPatientHealthInquiry.renal_index,
|
||||
)"
|
||||
color="#455cda"
|
||||
>
|
||||
{{ item }}
|
||||
</Tag>
|
||||
</p>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="既往史">
|
||||
<span>{{
|
||||
userPatientHealthInquiry.person_status === 0 ? '无' : '有'
|
||||
}}</span>
|
||||
<p v-if="userPatientHealthInquiry.person_status === 1" class="mt-3">
|
||||
<Tag
|
||||
v-for="item in splitString(
|
||||
userPatientHealthInquiry.person_history,
|
||||
)"
|
||||
color="#455cda"
|
||||
>
|
||||
{{ item }}
|
||||
</Tag>
|
||||
</p>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="过敏史">
|
||||
<span>{{
|
||||
userPatientHealthInquiry.allergic_status === 0 ? '无' : '有'
|
||||
}}</span>
|
||||
<p
|
||||
v-if="userPatientHealthInquiry.allergic_status === 1"
|
||||
class="mt-3"
|
||||
>
|
||||
<Tag
|
||||
v-for="item in splitString(
|
||||
userPatientHealthInquiry.allergic_history,
|
||||
)"
|
||||
color="#455cda"
|
||||
>
|
||||
{{ item }}
|
||||
</Tag>
|
||||
</p>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="家庭遗传史">
|
||||
<span>{{
|
||||
userPatientHealthInquiry.family_status === 0 ? '无' : '有'
|
||||
}}</span>
|
||||
<p v-if="userPatientHealthInquiry.family_status === 1" class="mt-3">
|
||||
<Tag
|
||||
v-for="item in splitString(
|
||||
userPatientHealthInquiry.family_history,
|
||||
)"
|
||||
color="#455cda"
|
||||
>
|
||||
{{ item }}
|
||||
</Tag>
|
||||
</p>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
<Descriptions
|
||||
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"
|
||||
class="mt-5 mb-5"
|
||||
title="处方记录"
|
||||
>
|
||||
<Descriptions.Item>
|
||||
<Timeline>
|
||||
<TimelineItem v-for="item in prescriptionList" :key="item.id">
|
||||
<Tag color="#455cda">{{ item.prescription_no }}</Tag>
|
||||
<span class="time-line-item-created">{{
|
||||
item.created_at
|
||||
}}</span>
|
||||
<Tag class="ml-5" :color="item.category === 1? '' : '#455cda'">
|
||||
{{ item.prescription_type === 1 ? '中药处方' : item.prescription_type === 5 ? '产品服务包' : '西药处方' }} /
|
||||
{{ item.category === 1 ? '自费' : '医保' }}
|
||||
</Tag>
|
||||
|
||||
<Tag v-if="item.status === 0" color="warning">待审核</Tag>
|
||||
<Tag v-else-if="item.status === 1" color="success">已通过</Tag>
|
||||
<Tag v-else-if="item.status === 2" color="error">未通过</Tag>
|
||||
<Tag v-else-if="item.status === 3" color="#455cda">无需审核</Tag>
|
||||
<Tag v-else-if="item.status === 4" color="#455cda">无需审核</Tag>
|
||||
<Tag color="#455cda">处方ID:{{ item.id }}</Tag>
|
||||
</TimelineItem>
|
||||
</Timeline>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
<!-- 药品分类导航 -->
|
||||
<div class="drug-categories">
|
||||
<button
|
||||
v-for="categoryItem in categories"
|
||||
:key="categoryItem.value"
|
||||
:class="{ active: activeCategory === categoryItem.value }"
|
||||
@click="tabChange(categoryItem.value)"
|
||||
>
|
||||
{{ categoryItem.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
<RadioGroup
|
||||
v-model:value="category"
|
||||
>
|
||||
<RadioButton :value="1">自费</RadioButton>
|
||||
<RadioButton :value="2">医保</RadioButton>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<!-- 已选药品列表 -->
|
||||
<!-- 中药 -->
|
||||
<div v-if="activeCategory === 1" class="mb-5 mt-3 flex" style="flex-wrap: wrap; width: 100%;">
|
||||
<Row :gutter="[16, 16]">
|
||||
<Col
|
||||
v-for="(drug, index) in currentDrugs"
|
||||
:key="drug.id"
|
||||
:lg="12"
|
||||
:md="24"
|
||||
:sm="24"
|
||||
:xl="12"
|
||||
:xs="24"
|
||||
:xxl="8"
|
||||
>
|
||||
<Card class="mt-0 prescription-card" title="">
|
||||
<div>
|
||||
<span class="card-index">{{ index + 1 }}、</span>
|
||||
<p>
|
||||
<Select
|
||||
v-model:value="drug.id"
|
||||
:filter-option="false"
|
||||
class="old-select-drug-name"
|
||||
style="min-width: 100px"
|
||||
placeholder="药名"
|
||||
show-search
|
||||
@change="selectOldDrugInfo"
|
||||
@dropdown-visible-change="
|
||||
setSelectChineseIndex(index, drug.id)
|
||||
"
|
||||
@search="searchOption"
|
||||
>
|
||||
<SelectOption v-if="drugList.length === 0" :value="drug.id">
|
||||
{{ drug.drug_name }}
|
||||
</SelectOption>
|
||||
<SelectOption
|
||||
v-for="(value, index) in drugList"
|
||||
v-else
|
||||
:key="index"
|
||||
:value="value.drug.id"
|
||||
>
|
||||
{{ value.drug.drug_name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
,
|
||||
<InputNumber
|
||||
v-model:value="drug.number"
|
||||
:class="`w-1/5 old-number-input-${index}`"
|
||||
style="min-width: 100px"
|
||||
:controls="false"
|
||||
@blur="updateChineseNumber"
|
||||
@keydown="updateChineseNumberGoNewDrug($event)"
|
||||
>
|
||||
<template #addonAfter> g</template>
|
||||
</InputNumber>
|
||||
,
|
||||
<span>
|
||||
<Select
|
||||
:value="drug?.way_id"
|
||||
placeholder="用法"
|
||||
@change="selectDrugUseWayChange"
|
||||
@dropdown-visible-change="
|
||||
selectProductChange(drug.index_id)
|
||||
"
|
||||
>
|
||||
<SelectOption :value="0">煎服</SelectOption>
|
||||
<SelectOption
|
||||
v-for="(value, index) in drugUseWay"
|
||||
:key="index"
|
||||
:value="value.id"
|
||||
>
|
||||
{{ value.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</span>
|
||||
<p class="card-price">
|
||||
¥<span>{{ (drug.price * drug.number).toFixed(2) }}</span>
|
||||
</p>
|
||||
<Button
|
||||
class="card-delete"
|
||||
type="link"
|
||||
@click="removeDrug(index)"
|
||||
>
|
||||
<DeleteTwoTone/>
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col
|
||||
:lg="12"
|
||||
:md="24"
|
||||
:sm="24"
|
||||
:xl="12"
|
||||
:xs="24"
|
||||
:xxl="8"
|
||||
>
|
||||
<Card class="prescription-card" title="">
|
||||
<div>
|
||||
<span class="card-index">{{ currentDrugs.length + 1 }}、</span>
|
||||
<div>
|
||||
<Select
|
||||
v-model:value="newDrugInfo.id"
|
||||
:filter-option="false"
|
||||
class="new-select-drug-name w-1/5"
|
||||
style="min-width: 100px"
|
||||
placeholder="药名"
|
||||
show-search
|
||||
@change="selectNewDrugInfo"
|
||||
@search="searchOption"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(value, index) in drugList"
|
||||
:key="index"
|
||||
:value="value.drug_id"
|
||||
>
|
||||
{{ value.drug.drug_name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
,
|
||||
<InputNumber
|
||||
v-model:value="newDrugInfo.number"
|
||||
class="new-number-input w-1/5"
|
||||
style="min-width: 100px"
|
||||
@blur="newDrugBlur"
|
||||
@keydown="selectDrugByNewDrugInfo($event, true)"
|
||||
>
|
||||
<template #addonAfter> g</template>
|
||||
</InputNumber>
|
||||
,
|
||||
<span>
|
||||
<Select
|
||||
v-model:value="newDrugInfo.way_id"
|
||||
placeholder="用法"
|
||||
@keydown="selectDrugByNewDrugInfo($event, true)"
|
||||
>
|
||||
<SelectOption :value="0">煎服</SelectOption>
|
||||
<SelectOption
|
||||
v-for="(value, index) in drugUseWay"
|
||||
:key="index"
|
||||
:value="value.id"
|
||||
>
|
||||
{{ value.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</span>
|
||||
<p class="card-price">
|
||||
¥<span>{{
|
||||
(
|
||||
(newDrugInfo.price || 0) * (newDrugInfo.number || 1)
|
||||
).toFixed(2)
|
||||
}}元</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<!-- 西(中成)药列表 -->
|
||||
<div v-else class="selected-drugs">
|
||||
<div class="drug-table">
|
||||
<div class="table-header">
|
||||
<span v-if="activeCategory !== 1">商品图片</span>
|
||||
<span>商品名称</span>
|
||||
<span>数量</span>
|
||||
<span>单价</span>
|
||||
<span>操作</span>
|
||||
</div>
|
||||
<div class="table-body">
|
||||
<div
|
||||
v-for="(drug, index) in currentDrugs"
|
||||
:key="drug.id"
|
||||
class="table-row"
|
||||
>
|
||||
<div
|
||||
v-if="activeCategory !== 1"
|
||||
style="width: 120px; margin: 0 auto"
|
||||
>
|
||||
<Image :src="drug.image"/>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<span>药品名称:{{ drug.drug_name }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span>用法:{{
|
||||
`${drug.use_type?.name},${drug.use_frequency?.name},${drug.use_num?.name},每次${drug.number}${drug.unit?.name}`
|
||||
}}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="activeCategory === 1" class="quantity-control">
|
||||
<InputNumber
|
||||
v-model:value="drug.select_number"
|
||||
class="w-full"
|
||||
@blur="updateChineseNumber"
|
||||
>
|
||||
<template #addonAfter> g</template>
|
||||
</InputNumber>
|
||||
</div>
|
||||
<div v-else class="quantity-control">
|
||||
<Button type="primary" @click="decrement(index)">-</Button>
|
||||
<span style="margin: 0 20px">{{ drug.select_number }}</span>
|
||||
<Button type="primary" @click="increment(index)">+</Button>
|
||||
</div>
|
||||
</div>
|
||||
<span>{{ drug.price }}</span>
|
||||
<div>
|
||||
<Button type="link" @click="removeDrug(index)">删除</Button>
|
||||
<Button
|
||||
v-if="drug.instruction !== ''"
|
||||
type="link"
|
||||
@click="setVisible(true, drug.instruction)"
|
||||
>
|
||||
查看说明书
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 诊断和医嘱 -->
|
||||
<div class="diagnosis-area">
|
||||
<div v-if="activeCategory === 1" class="mb-5">
|
||||
<RadioGroup v-model:value="ruleType">
|
||||
<RadioButton :value="1">自制剂</RadioButton>
|
||||
<RadioButton :value="2">委托调剂</RadioButton>
|
||||
</RadioGroup>
|
||||
<div v-if="ruleType === 1">
|
||||
<!-- 选择包法-->
|
||||
<Select
|
||||
:value="packageMethodId"
|
||||
class="mt-3 w-1/5"
|
||||
placeholder="请选择包法"
|
||||
@change="selectPackageMethod"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(value, index) in packageMethod"
|
||||
:key="index"
|
||||
:value="value.value"
|
||||
>
|
||||
{{ value.label }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
<div v-else-if="ruleType === 2">
|
||||
<!-- 选择制剂-->
|
||||
<Select
|
||||
v-model:value="processRuleId"
|
||||
class="mt-3 w-1/5"
|
||||
placeholder="请选择制剂"
|
||||
@change="selectProcessRule"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(value, index) in processRuleList"
|
||||
:key="index"
|
||||
:value="value.id"
|
||||
>
|
||||
{{ value.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
<!-- 选择规格-->
|
||||
<Select
|
||||
v-model:value="childProcessRuleId"
|
||||
class="ml-3 mt-3 w-1/5"
|
||||
placeholder="请选择煎法"
|
||||
@change="selectProcessRuleNot"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(value, index) in childProcessRuleList"
|
||||
:key="index"
|
||||
:value="value.id"
|
||||
>
|
||||
{{ value.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
<!-- 选择备注-->
|
||||
<Select
|
||||
v-model:value="processRuleNoteId"
|
||||
class="ml-3 mt-3 w-1/5"
|
||||
placeholder="请选择备注"
|
||||
@change="selectProcessRuleNotCommit"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(value, index) in processRuleNoteList"
|
||||
:key="index"
|
||||
:value="value.id"
|
||||
>
|
||||
{{ value.note }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
用量:
|
||||
<InputNumber v-model:value="dosage" class="w-1/5">
|
||||
<template #addonBefore>
|
||||
<MinusOutlined
|
||||
key="prop"
|
||||
@click="dosage = dosage > 1 ? dosage - 1 : 1"
|
||||
/>
|
||||
</template>
|
||||
<template #addonAfter>
|
||||
<PlusOutlined
|
||||
key="add"
|
||||
@click="dosage = dosage < 100 ? dosage + 1 : 100"
|
||||
/>
|
||||
</template>
|
||||
</InputNumber>
|
||||
天 / {{ dosage }} 剂
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
频次:
|
||||
<InputNumber v-model:value="dayDosage" class="w-1/5">
|
||||
<template #addonBefore>
|
||||
<MinusOutlined
|
||||
key="prop"
|
||||
@click="dayDosage = dayDosage > 1 ? dayDosage - 1 : 1"
|
||||
/>
|
||||
</template>
|
||||
<template #addonAfter>
|
||||
<PlusOutlined
|
||||
key="add"
|
||||
@click="dayDosage = dayDosage < 100 ? dayDosage + 1 : 100"
|
||||
/>
|
||||
</template>
|
||||
</InputNumber>
|
||||
次/天
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Textarea v-model:value="diagnosis" placeholder="输入诊断结果..." class="mb-3"/>
|
||||
<Textarea v-model:value="medicalAdvice" placeholder="输入医嘱..." class="mb-3"/>
|
||||
|
||||
诊疗费用:
|
||||
<InputNumber
|
||||
v-model:value="treatmentPrice"
|
||||
placeholder="请输入诊疗价格"
|
||||
class="mb-3"
|
||||
>
|
||||
<template #addonAfter> /元</template>
|
||||
</InputNumber>
|
||||
</div>
|
||||
|
||||
<!-- 费用总计 -->
|
||||
<div class="price-box">
|
||||
<div class="total-cost">加工费:¥{{ processingFee.toFixed(2) }}</div>
|
||||
<div class="total-cost">
|
||||
商品价格:¥{{ totalProductCost.toFixed(2) }}
|
||||
</div>
|
||||
<div class="total-cost">总计:¥{{ totalCost.toFixed(2) }}</div>
|
||||
|
||||
<Button class="mt-5 w-full" style="display: block" type="primary" @click="checkChineseMedicineConflict">
|
||||
发送处方
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Modal v-model:open="showNewDrugModal" @ok="newDrugModalOk">
|
||||
要把【{{ newDrugInfo.name }}】添加到清单吗?
|
||||
</Modal>
|
||||
<Modal title="二次签名" v-model:open="doctorSecondSignModal" @ok="doctorSecondSignModalOk">
|
||||
<div class="doctor-second-sign-title">有毒:</div>
|
||||
<p v-for="item in checkData.message?.poisonous || []">【{{ item }}】</p>
|
||||
<div class="doctor-second-sign-title">十八反:</div>
|
||||
<p v-for="item in checkData.message?.opposition || []">【{{ item }}】</p>
|
||||
<div class="doctor-second-sign-title">十九畏:</div>
|
||||
<p v-for="item in checkData.message?.conflict || []">【{{ item }}】</p>
|
||||
<p class="mt-5" style="color: red;">如需继续开方需要二次签名,确认要二次签名吗?</p>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.doctor-second-sign-title {
|
||||
font-size: 16px;
|
||||
margin: 10px 0;
|
||||
font-weight: bold;
|
||||
border-left: 5px solid rgb(55, 71, 162);
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.card-index {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.card-price {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 8px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.card-delete {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.prescription-card {
|
||||
position: relative;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.drug-categories {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.drug-categories button {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.dark .drug-categories button {
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.drug-categories button.active {
|
||||
color: #fff;
|
||||
border-color: transparent;
|
||||
animation: pulse 0.5s /*infinite*/;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
background: #455cda;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.selected-drugs {
|
||||
margin: 1rem 0;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dark .selected-drugs {
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.table-body {
|
||||
max-height: 30vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: grid;
|
||||
grid-template-columns: 0.5fr 1fr 1fr 1fr 1fr;
|
||||
padding: 12px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: grid;
|
||||
grid-template-columns: 0.5fr 1fr 1fr 1fr 1fr;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #eee;
|
||||
text-align: center;
|
||||
height: auto;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dark .table-row {
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
|
||||
.diagnosis-area textarea {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
padding: 8px;
|
||||
margin: 8px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.dark .diagnosis-area textarea {
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.price-box {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.total-cost {
|
||||
text-align: right;
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
color: #f56c6c;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.time-line-item-created {
|
||||
color: #909399;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.drug-categories {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.table-header,
|
||||
.table-row {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
.table-header span:nth-child(4),
|
||||
.table-header span:nth-child(5),
|
||||
.table-row > span:nth-child(4),
|
||||
.table-row > div:nth-child(5) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
802
apps/web-antd/src/store/prescription.ts
Normal file
802
apps/web-antd/src/store/prescription.ts
Normal file
@@ -0,0 +1,802 @@
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
|
||||
import { message, notification } from 'ant-design-vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { useChatStore } from '#/views/business/chat/stores/chat';
|
||||
import { useUserStore } from '#/views/business/chat/stores/user';
|
||||
import { sendMessage } from '#/views/business/chat/utils/request';
|
||||
import {
|
||||
addWestPrescription,
|
||||
checkChineseMedicineConflictApi,
|
||||
getDrugUseList,
|
||||
getMyStoreListApi,
|
||||
getPatientItem,
|
||||
getProcessRuleList,
|
||||
getProductListDoctorReception,
|
||||
} from '#/views/doctor/doctor-reception/api';
|
||||
|
||||
export const usePrescriptionStore = defineStore('prescription', () => {
|
||||
// 基础数据
|
||||
const patientInfo = ref<any | null>(null);
|
||||
const userPatientHealthInquiry = ref<any | null>(null);
|
||||
const prescriptionList = ref([]);
|
||||
const currentRegisterId = ref<number | string>('');
|
||||
const activePatient = ref<any | null>(null);
|
||||
|
||||
const userStore = useUserStore();
|
||||
const chatStore = useChatStore();
|
||||
|
||||
// 药品使用相关数据
|
||||
const drugUseNum = ref([]);
|
||||
const drugUseFrequency = ref([]);
|
||||
const drugUseType = ref([]);
|
||||
const drugUnit = ref([]);
|
||||
const drugTime = ref([]);
|
||||
const drugUseWay = ref([]);
|
||||
const myStoreList = ref([]);
|
||||
|
||||
// 处方状态
|
||||
const myStoreId = ref(0);
|
||||
const activeCategory = ref(2);
|
||||
const diagnosis = ref('');
|
||||
const medicalAdvice = ref('');
|
||||
const treatmentPrice = ref(0);
|
||||
const ruleType = ref(1);
|
||||
const drugList = ref([]);
|
||||
const currentDrugs = ref([]);
|
||||
const category = ref(1);
|
||||
const selectProductId = ref(0);
|
||||
|
||||
// 中药相关配置
|
||||
const processRuleList = ref([]);
|
||||
const childProcessRuleList = ref([]);
|
||||
const processRuleNoteList = ref([]);
|
||||
const processRuleId = ref();
|
||||
const processRuleNoteId = ref();
|
||||
const childProcessRuleId = ref();
|
||||
const packageMethodId = ref(2);
|
||||
const dosage = ref(7);
|
||||
const dayDosage = ref(2);
|
||||
|
||||
// 新药品相关
|
||||
const newDrugInfo = ref<any>({});
|
||||
const selectChineseIndex = ref(-1);
|
||||
const selectChineseId = ref(0);
|
||||
|
||||
// 二次签名相关
|
||||
const doctorSecondSign = ref(0);
|
||||
const checkData = ref<any>({});
|
||||
|
||||
// 初始化状态标记
|
||||
const isInitialized = ref(false);
|
||||
const isPatientInfoLoaded = ref(false);
|
||||
|
||||
// 获取localStorage key
|
||||
const getStorageKey = () =>
|
||||
`prescriptionData-chat-${currentRegisterId.value}`;
|
||||
|
||||
// 计算属性
|
||||
const totalProductCost = computed(() => {
|
||||
if (currentDrugs.value.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (activeCategory.value === 1) {
|
||||
return currentDrugs.value.reduce(
|
||||
(sum, drug) => sum + drug.price * (drug.number || 1) * dosage.value,
|
||||
0,
|
||||
);
|
||||
}
|
||||
return currentDrugs.value.reduce(
|
||||
(sum, drug) => sum + drug.price * (drug.select_number || 1),
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
const processRulePrice = ref(0);
|
||||
const calcMethod = ref(0);
|
||||
|
||||
const processingFee = computed(() => {
|
||||
if (ruleType.value === 1) return 0;
|
||||
if (currentDrugs.value.length === 0) return 0;
|
||||
|
||||
const processRule = childProcessRuleList.value.find(
|
||||
(item) => item.id === childProcessRuleId.value,
|
||||
);
|
||||
if (!processRule) return 0;
|
||||
|
||||
calcMethod.value = processRule.calc_method;
|
||||
processRulePrice.value = processRule.price;
|
||||
|
||||
if (calcMethod.value === 1) return processRulePrice.value;
|
||||
if (calcMethod.value === 2) return processRulePrice.value * dosage.value;
|
||||
if (calcMethod.value === 3) {
|
||||
const totalNumber = currentDrugs.value.reduce(
|
||||
(sum, drug) => sum + drug.number,
|
||||
0,
|
||||
);
|
||||
return processRulePrice.value * dosage.value * totalNumber;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
const totalCost = computed(() => {
|
||||
return totalProductCost.value + processingFee.value;
|
||||
});
|
||||
|
||||
// localStorage同步方法
|
||||
const syncToLocalStorage = () => {
|
||||
try {
|
||||
const storageKey = getStorageKey();
|
||||
localStorage.setItem(storageKey, JSON.stringify(currentDrugs.value));
|
||||
console.log('已同步到localStorage:', storageKey, currentDrugs.value);
|
||||
} catch (error) {
|
||||
console.error('保存到localStorage失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const loadFromLocalStorage = () => {
|
||||
try {
|
||||
const storageKey = getStorageKey();
|
||||
const stored = localStorage.getItem(storageKey);
|
||||
console.log('从localStorage加载:', storageKey, stored);
|
||||
|
||||
if (stored) {
|
||||
const parsedData = JSON.parse(stored);
|
||||
currentDrugs.value.splice(0, currentDrugs.value.length, ...parsedData);
|
||||
} else {
|
||||
currentDrugs.value.splice(0, currentDrugs.value.length);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('从localStorage加载失败:', error);
|
||||
currentDrugs.value.splice(0, currentDrugs.value.length);
|
||||
}
|
||||
};
|
||||
|
||||
// 更新currentDrugs并同步到localStorage
|
||||
const updateCurrentDrugs = (newDrugs: any[]) => {
|
||||
currentDrugs.value.splice(0, currentDrugs.value.length, ...newDrugs);
|
||||
// 更新总价
|
||||
syncToLocalStorage();
|
||||
};
|
||||
|
||||
// 基础数据初始化(不包含患者信息)
|
||||
const initializeBasicData = async () => {
|
||||
if (isInitialized.value) {
|
||||
console.log('基础数据已初始化,跳过重复请求');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
getMyStoreList(),
|
||||
getDrugUseListData(),
|
||||
getProcessRuleListData(),
|
||||
]);
|
||||
isInitialized.value = true;
|
||||
console.log('基础数据初始化完成');
|
||||
} catch (error) {
|
||||
console.error('基础数据初始化失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 完整初始化(包含患者信息,仅用于 PrescriptionModal)
|
||||
const initializePrescription = async (registerId: number | string) => {
|
||||
console.log('初始化处方,registerId:', registerId);
|
||||
|
||||
// 设置当前注册ID
|
||||
currentRegisterId.value = registerId;
|
||||
|
||||
// 加载localStorage数据
|
||||
loadFromLocalStorage();
|
||||
|
||||
// 如果基础数据未初始化,先初始化基础数据
|
||||
if (!isInitialized.value) {
|
||||
await initializeBasicData();
|
||||
}
|
||||
|
||||
// 获取患者信息(只在 PrescriptionModal 中调用)
|
||||
if (
|
||||
!isPatientInfoLoaded.value ||
|
||||
patientInfo.value?.register_id !== registerId
|
||||
) {
|
||||
await getPatientInfo();
|
||||
isPatientInfoLoaded.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
// 轻量级初始化(仅用于 WesternModal 等子组件)
|
||||
const initializeForModal = async (registerId: number | string) => {
|
||||
console.log('轻量级初始化,registerId:', registerId);
|
||||
|
||||
// 设置当前注册ID
|
||||
currentRegisterId.value = registerId;
|
||||
|
||||
// 加载localStorage数据
|
||||
loadFromLocalStorage();
|
||||
|
||||
// 如果基础数据未初始化,先初始化基础数据
|
||||
if (!isInitialized.value) {
|
||||
await initializeBasicData();
|
||||
}
|
||||
};
|
||||
|
||||
const getMyStoreList = async () => {
|
||||
try {
|
||||
const res = await getMyStoreListApi();
|
||||
myStoreList.value = res;
|
||||
if (res.length > 0) {
|
||||
myStoreId.value = res[0].id;
|
||||
}
|
||||
console.log('获取诊所列表成功');
|
||||
} catch (error) {
|
||||
console.error('获取诊所列表失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const getDrugUseListData = async () => {
|
||||
try {
|
||||
const res = await getDrugUseList();
|
||||
drugUseNum.value = res.drug_use_num;
|
||||
drugUseFrequency.value = res.drug_use_frequency;
|
||||
drugUseType.value = res.drug_use_type;
|
||||
drugUnit.value = res.drug_unit;
|
||||
drugTime.value = res.drug_time;
|
||||
drugUseWay.value = res.drug_use_way;
|
||||
console.log('获取药品使用列表成功');
|
||||
} catch (error) {
|
||||
console.error('获取药品使用列表失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const getProcessRuleListData = async (pid = 0, ruleId = 0) => {
|
||||
try {
|
||||
const data = ruleId === 0 ? { pid } : { rule_id: ruleId };
|
||||
const res = await getProcessRuleList(data);
|
||||
|
||||
if (ruleId !== 0) {
|
||||
processRuleNoteList.value = res;
|
||||
} else if (pid === 0) {
|
||||
processRuleList.value = res;
|
||||
} else {
|
||||
childProcessRuleList.value = res;
|
||||
}
|
||||
console.log('获取加工规则成功');
|
||||
} catch (error) {
|
||||
console.error('获取加工规则失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const getPatientInfo = async () => {
|
||||
try {
|
||||
console.log('开始获取患者信息,registerId:', currentRegisterId.value);
|
||||
const res = await getPatientItem(currentRegisterId.value);
|
||||
patientInfo.value = res;
|
||||
activePatient.value = res.user_patient;
|
||||
userPatientHealthInquiry.value = res.user_patient_health_inquiry;
|
||||
prescriptionList.value = res.prescription;
|
||||
console.log('获取患者信息成功:', activePatient.value);
|
||||
} catch (error) {
|
||||
console.error('获取患者信息失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 药品搜索
|
||||
const getDrugList = debounce(async (searchText = '') => {
|
||||
if (searchText === '' && activeCategory.value === 1) {
|
||||
drugList.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getProductListDoctorReception({
|
||||
store_id: myStoreId.value,
|
||||
type: activeCategory.value,
|
||||
name: searchText,
|
||||
});
|
||||
|
||||
drugList.value = res.map((item) => {
|
||||
const matched = currentDrugs.value.find((v) => v.index_id === item.id);
|
||||
return {
|
||||
...item,
|
||||
select_number: matched?.select_number || 0,
|
||||
drug: {
|
||||
...item.drug,
|
||||
number: matched?.number || 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取药品列表失败:', error);
|
||||
message.error('获取药品列表失败');
|
||||
}
|
||||
}, 300);
|
||||
|
||||
// 药品操作
|
||||
const addProducts = (data: any) => {
|
||||
const existItem = currentDrugs.value.find(
|
||||
(item) => item.index_id === data.id,
|
||||
);
|
||||
if (existItem) {
|
||||
message.warn('已经存在了');
|
||||
return false;
|
||||
}
|
||||
|
||||
const newProduct = {
|
||||
index_id: data.id,
|
||||
id: data.drug.id,
|
||||
drug_name: data.drug.drug_name,
|
||||
number: data.drug.number,
|
||||
use_num: drugTime.value.find((item) => item.id === data.drug.time_id),
|
||||
use_type: drugUseType.value.find((item) => item.id === data.drug.type_id),
|
||||
use_frequency: drugUseFrequency.value.find(
|
||||
(item) => item.id === data.drug.frequency_id,
|
||||
),
|
||||
unit: drugUnit.value.find((item) => item.id === data.drug.unit_id),
|
||||
price: data.price,
|
||||
way_id: data.drug?.way_id,
|
||||
use_ways: drugUseWay.value.find((item) => item.id === data.drug.way_id),
|
||||
time_id: data.drug.time_id,
|
||||
type_id: data.drug.type_id,
|
||||
frequency_id: data.drug.frequency_id,
|
||||
unit_id: data.drug.unit_id,
|
||||
image: data.drug.image,
|
||||
instruction: data.drug.instruction,
|
||||
type: data.drug.type,
|
||||
select_number: 1,
|
||||
};
|
||||
|
||||
const newDrugs = [...currentDrugs.value, newProduct];
|
||||
updateCurrentDrugs(newDrugs);
|
||||
|
||||
message.success(`已将${newProduct.drug_name}添加到清单中!`);
|
||||
return true;
|
||||
};
|
||||
|
||||
const removeDrug = (index: number) => {
|
||||
const newDrugs = currentDrugs.value.filter((_, i) => i !== index);
|
||||
updateCurrentDrugs(newDrugs);
|
||||
};
|
||||
|
||||
const updateDrugQuantity = (index: number, quantity: number) => {
|
||||
if (quantity > 0) {
|
||||
const newDrugs = [...currentDrugs.value];
|
||||
newDrugs[index].select_number = quantity;
|
||||
updateCurrentDrugs(newDrugs);
|
||||
}
|
||||
};
|
||||
|
||||
const increment = (index: number) => {
|
||||
const newDrugs = [...currentDrugs.value];
|
||||
newDrugs[index].select_number++;
|
||||
updateCurrentDrugs(newDrugs);
|
||||
};
|
||||
|
||||
const decrement = (index: number) => {
|
||||
if (currentDrugs.value[index].select_number > 1) {
|
||||
const newDrugs = [...currentDrugs.value];
|
||||
newDrugs[index].select_number--;
|
||||
updateCurrentDrugs(newDrugs);
|
||||
}
|
||||
};
|
||||
|
||||
// 新药品操作
|
||||
const selectNewDrugInfo = () => {
|
||||
const check = currentDrugs.value.find((v) => v.id === newDrugInfo.value.id);
|
||||
if (check) {
|
||||
newDrugInfo.value.id = '';
|
||||
message.error('该药品已经在处方中了!');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = drugList.value.find((v) => v.drug_id === newDrugInfo.value.id);
|
||||
if (data) {
|
||||
newDrugInfo.value.price = data.price;
|
||||
newDrugInfo.value.name = data.drug.drug_name;
|
||||
nextTick(() => {
|
||||
const input = document.querySelector(
|
||||
'.new-number-input input',
|
||||
) as HTMLElement;
|
||||
input?.focus();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const selectOldDrugInfo = (id: number) => {
|
||||
const check = currentDrugs.value.find((v) => v.id === id);
|
||||
|
||||
if (check) {
|
||||
const newDrugs = [...currentDrugs.value];
|
||||
newDrugs[selectChineseIndex.value].id = selectChineseId.value;
|
||||
updateCurrentDrugs(newDrugs);
|
||||
drugList.value = [];
|
||||
message.error('该药品已经在处方中了!');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = drugList.value.find((v) => v.drug_id === id);
|
||||
if (data) {
|
||||
const newProduct = {
|
||||
index_id: data.id,
|
||||
id: data.drug.id,
|
||||
drug_name: data.drug.drug_name,
|
||||
number: data.drug.number,
|
||||
use_num: drugTime.value.find((item) => item.id === data.drug.time_id),
|
||||
use_type: drugUseType.value.find(
|
||||
(item) => item.id === data.drug.type_id,
|
||||
),
|
||||
use_frequency: drugUseFrequency.value.find(
|
||||
(item) => item.id === data.drug.frequency_id,
|
||||
),
|
||||
unit: drugUnit.value.find((item) => item.id === data.drug.unit_id),
|
||||
price: data.price,
|
||||
way_id: data.drug?.way_id,
|
||||
use_ways: drugUseWay.value.find((item) => item.id === data.drug.way_id),
|
||||
time_id: data.drug.time_id,
|
||||
type_id: data.drug.type_id,
|
||||
frequency_id: data.drug.frequency_id,
|
||||
unit_id: data.drug.unit_id,
|
||||
image: data.drug.image,
|
||||
instruction: data.drug.instruction,
|
||||
type: data.drug.type,
|
||||
};
|
||||
|
||||
const newDrugs = [...currentDrugs.value];
|
||||
newDrugs[selectChineseIndex.value] = newProduct;
|
||||
updateCurrentDrugs(newDrugs);
|
||||
|
||||
nextTick(() => {
|
||||
const input = document.querySelector(
|
||||
`.old-number-input-${selectChineseIndex.value} input`,
|
||||
) as HTMLElement;
|
||||
input?.focus();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const setSelectChineseIndex = (index: number, id: number) => {
|
||||
selectChineseIndex.value = index;
|
||||
selectChineseId.value = id;
|
||||
};
|
||||
|
||||
const addDrugByChinese = () => {
|
||||
const check = currentDrugs.value.find((v) => v.id === newDrugInfo.value.id);
|
||||
if (check) {
|
||||
message.error('该药品已经在处方中了!');
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = drugList.value.find((v) => v.drug_id === newDrugInfo.value.id);
|
||||
if (!data) {
|
||||
message.error('请选择药品');
|
||||
return false;
|
||||
}
|
||||
|
||||
data.drug.number = newDrugInfo.value.number;
|
||||
data.drug.way_id = newDrugInfo.value.way_id;
|
||||
const success = addProducts(data);
|
||||
if (success) {
|
||||
newDrugInfo.value = {};
|
||||
}
|
||||
return success;
|
||||
};
|
||||
|
||||
const selectDrugByNewDrugInfo = (
|
||||
event: KeyboardEvent,
|
||||
isNewDrug: boolean,
|
||||
) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
if (isNewDrug) {
|
||||
addDrugByChinese();
|
||||
} else {
|
||||
syncToLocalStorage();
|
||||
}
|
||||
nextTick(() => {
|
||||
const input = document.querySelector(
|
||||
'.new-select-drug-name input',
|
||||
) as HTMLElement;
|
||||
input?.focus();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const updateChineseNumber = () => {
|
||||
syncToLocalStorage();
|
||||
};
|
||||
|
||||
const updateChineseNumberGoNewDrug = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
syncToLocalStorage();
|
||||
nextTick(() => {
|
||||
const input = document.querySelector(
|
||||
'.new-select-drug-name input',
|
||||
) as HTMLElement;
|
||||
input?.focus();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 药品用法选择
|
||||
const selectProductChange = (id: number) => {
|
||||
selectProductId.value = id;
|
||||
};
|
||||
|
||||
const selectDrugUseWayChange = (id: number) => {
|
||||
const newDrugs = currentDrugs.value.map((item) =>
|
||||
item.index_id === selectProductId.value
|
||||
? {
|
||||
...item,
|
||||
way_id: id,
|
||||
use_ways: drugUseWay.value.find((value) => value.id === id),
|
||||
}
|
||||
: item,
|
||||
);
|
||||
updateCurrentDrugs(newDrugs);
|
||||
};
|
||||
|
||||
// 加工规则操作
|
||||
const selectProcessRule = (id: number) => {
|
||||
processRuleId.value = id;
|
||||
getProcessRuleListData(id);
|
||||
};
|
||||
|
||||
const selectProcessRuleNot = (id: number) => {
|
||||
childProcessRuleId.value = id;
|
||||
getProcessRuleListData(0, id);
|
||||
};
|
||||
|
||||
const selectProcessRuleNotCommit = (id: number) => {
|
||||
processRuleNoteId.value = id;
|
||||
};
|
||||
|
||||
const selectPackageMethod = (id: number) => {
|
||||
packageMethodId.value = id;
|
||||
};
|
||||
|
||||
// 中药相冲检查
|
||||
const checkChineseMedicineConflict = async () => {
|
||||
if (activeCategory.value === 2) {
|
||||
return { hasConflict: false };
|
||||
}
|
||||
|
||||
notification.info({
|
||||
message: '正在检查药物相冲',
|
||||
duration: 1,
|
||||
description: '正在检查药物相冲,请稍等...',
|
||||
});
|
||||
|
||||
try {
|
||||
const names = currentDrugs.value.map((item) => item.drug_name);
|
||||
const res = await checkChineseMedicineConflictApi({ names });
|
||||
|
||||
if (res.is_exist === true) {
|
||||
checkData.value = { message: res.message };
|
||||
return { hasConflict: true, message: res.message };
|
||||
} else {
|
||||
notification.success({
|
||||
message: '检查成功',
|
||||
duration: 3,
|
||||
description: '暂无相冲药品',
|
||||
});
|
||||
return { hasConflict: false };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查药物相冲失败:', error);
|
||||
return { hasConflict: false };
|
||||
}
|
||||
};
|
||||
|
||||
// 发送处方
|
||||
const sendPrescription = async (doctorSecondSignValue = 0) => {
|
||||
if (currentDrugs.value.length === 0) {
|
||||
message.error('请选择药品');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (activeCategory.value === 1) {
|
||||
if (ruleType.value === 1 && packageMethodId.value == null) {
|
||||
message.error('请选择包法');
|
||||
return false;
|
||||
}
|
||||
if (ruleType.value === 2) {
|
||||
if (processRuleId.value == null) {
|
||||
message.error('请选择制剂');
|
||||
return false;
|
||||
}
|
||||
if (processRuleNoteId.value == null) {
|
||||
message.error('请选择规格');
|
||||
return false;
|
||||
}
|
||||
if (childProcessRuleId.value == null) {
|
||||
message.error('请选择备注');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!diagnosis.value) {
|
||||
message.error('诊断结果不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!medicalAdvice.value) {
|
||||
message.error('医嘱不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await addWestPrescription({
|
||||
patient: activePatient.value,
|
||||
drugs: currentDrugs.value,
|
||||
diagnosis: diagnosis.value,
|
||||
medicalAdvice: medicalAdvice.value,
|
||||
total: totalCost.value,
|
||||
category: category.value,
|
||||
drug_type: 2,
|
||||
register_id: patientInfo.value.id,
|
||||
treatment_price: treatmentPrice.value,
|
||||
package_method_id: packageMethodId.value,
|
||||
process_rule_id: processRuleId.value,
|
||||
process_rule_note_id: processRuleNoteId.value,
|
||||
child_process_rule_id: childProcessRuleId.value,
|
||||
process_rule_type: ruleType.value,
|
||||
prescription_type: activeCategory.value,
|
||||
processing_fee: processRulePrice.value,
|
||||
dosage: dosage.value,
|
||||
day_dosage: dayDosage.value,
|
||||
doctor_second_sign: doctorSecondSignValue,
|
||||
}).then((res) => {
|
||||
message.success('处方已发送');
|
||||
sendMessage({
|
||||
roomId: chatStore.currentFriend.room_id,
|
||||
senderId: userStore.currentUser.id,
|
||||
receiverId: chatStore.currentFriend.id,
|
||||
type: 'prescription',
|
||||
content: JSON.stringify(res),
|
||||
});
|
||||
chatStore.addMessage(
|
||||
{
|
||||
roomId: chatStore.currentFriend.room_id,
|
||||
senderId: userStore.currentUser.id,
|
||||
receiverId: chatStore.currentFriend.id,
|
||||
type: 'prescription',
|
||||
content: JSON.stringify(res),
|
||||
isSent: true,
|
||||
time: new Date().toLocaleTimeString().slice(0, 5),
|
||||
},
|
||||
userStore.currentUser.id,
|
||||
);
|
||||
resetForm();
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('发送处方失败:', error);
|
||||
message.error('发送处方失败');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
updateCurrentDrugs([]);
|
||||
diagnosis.value = '';
|
||||
medicalAdvice.value = '';
|
||||
packageMethodId.value = 2;
|
||||
processRulePrice.value = 0;
|
||||
dosage.value = 7;
|
||||
dayDosage.value = 2;
|
||||
newDrugInfo.value = {};
|
||||
doctorSecondSign.value = 0;
|
||||
};
|
||||
|
||||
const changeCategory = (categoryValue: number) => {
|
||||
activeCategory.value = categoryValue;
|
||||
updateCurrentDrugs([]);
|
||||
localStorage.setItem(
|
||||
`activeCategory-chat-${currentRegisterId.value}`,
|
||||
categoryValue.toString(),
|
||||
);
|
||||
};
|
||||
|
||||
// 工具函数
|
||||
const splitString = (str: string) => {
|
||||
if (!str) return [];
|
||||
return str.split(',');
|
||||
};
|
||||
|
||||
const refreshCurrentDrugs = () => {
|
||||
loadFromLocalStorage();
|
||||
};
|
||||
|
||||
// 重置初始化状态(用于切换患者时)
|
||||
const resetInitializationState = () => {
|
||||
isInitialized.value = false;
|
||||
isPatientInfoLoaded.value = false;
|
||||
patientInfo.value = null;
|
||||
activePatient.value = null;
|
||||
userPatientHealthInquiry.value = null;
|
||||
prescriptionList.value = [];
|
||||
console.log('重置初始化状态');
|
||||
};
|
||||
|
||||
return {
|
||||
// 状态
|
||||
patientInfo,
|
||||
userPatientHealthInquiry,
|
||||
prescriptionList,
|
||||
currentRegisterId,
|
||||
activePatient,
|
||||
drugUseNum,
|
||||
drugUseFrequency,
|
||||
drugUseType,
|
||||
drugUnit,
|
||||
drugTime,
|
||||
drugUseWay,
|
||||
myStoreList,
|
||||
myStoreId,
|
||||
activeCategory,
|
||||
diagnosis,
|
||||
medicalAdvice,
|
||||
treatmentPrice,
|
||||
ruleType,
|
||||
drugList,
|
||||
currentDrugs,
|
||||
category,
|
||||
selectProductId,
|
||||
processRuleList,
|
||||
childProcessRuleList,
|
||||
processRuleNoteList,
|
||||
processRuleId,
|
||||
processRuleNoteId,
|
||||
childProcessRuleId,
|
||||
packageMethodId,
|
||||
dosage,
|
||||
dayDosage,
|
||||
newDrugInfo,
|
||||
selectChineseIndex,
|
||||
selectChineseId,
|
||||
doctorSecondSign,
|
||||
checkData,
|
||||
|
||||
// 计算属性
|
||||
totalProductCost,
|
||||
processingFee,
|
||||
totalCost,
|
||||
|
||||
// 方法
|
||||
initializePrescription,
|
||||
initializeForModal,
|
||||
initializeBasicData,
|
||||
resetInitializationState,
|
||||
getDrugList,
|
||||
addProducts,
|
||||
removeDrug,
|
||||
updateDrugQuantity,
|
||||
increment,
|
||||
decrement,
|
||||
selectNewDrugInfo,
|
||||
selectOldDrugInfo,
|
||||
setSelectChineseIndex,
|
||||
addDrugByChinese,
|
||||
selectDrugByNewDrugInfo,
|
||||
updateChineseNumber,
|
||||
updateChineseNumberGoNewDrug,
|
||||
selectProductChange,
|
||||
selectDrugUseWayChange,
|
||||
selectProcessRule,
|
||||
selectProcessRuleNot,
|
||||
selectProcessRuleNotCommit,
|
||||
selectPackageMethod,
|
||||
checkChineseMedicineConflict,
|
||||
sendPrescription,
|
||||
resetForm,
|
||||
changeCategory,
|
||||
getProcessRuleListData,
|
||||
splitString,
|
||||
refreshCurrentDrugs,
|
||||
syncToLocalStorage,
|
||||
loadFromLocalStorage,
|
||||
updateCurrentDrugs,
|
||||
};
|
||||
});
|
||||
674
apps/web-antd/src/store/usePrescription.ts
Normal file
674
apps/web-antd/src/store/usePrescription.ts
Normal file
@@ -0,0 +1,674 @@
|
||||
import { computed, ref, nextTick } from "vue"
|
||||
import { message, notification } from "ant-design-vue"
|
||||
import { debounce } from "lodash-es"
|
||||
|
||||
import {
|
||||
addWestPrescription,
|
||||
checkChineseMedicineConflictApi,
|
||||
getDrugUseList,
|
||||
getMyStoreListApi,
|
||||
getPatientItem,
|
||||
getProcessRuleList,
|
||||
getProductListDoctorReception,
|
||||
} from "#/views/doctor/doctor-reception/api"
|
||||
|
||||
export const usePrescriptionStore = () => {
|
||||
// 基础数据
|
||||
const patientInfo = ref<any | null>(null)
|
||||
const userPatientHealthInquiry = ref<any | null>(null)
|
||||
const prescriptionList = ref([])
|
||||
const currentRegisterId = ref<number | string>("")
|
||||
const activePatient = ref<any | null>(null)
|
||||
|
||||
// 药品使用相关数据
|
||||
const drugUseNum = ref([])
|
||||
const drugUseFrequency = ref([])
|
||||
const drugUseType = ref([])
|
||||
const drugUnit = ref([])
|
||||
const drugTime = ref([])
|
||||
const drugUseWay = ref([])
|
||||
const myStoreList = ref([])
|
||||
|
||||
// 处方状态
|
||||
const myStoreId = ref(0)
|
||||
const activeCategory = ref(2)
|
||||
const diagnosis = ref("")
|
||||
const medicalAdvice = ref("")
|
||||
const treatmentPrice = ref(0)
|
||||
const ruleType = ref(1)
|
||||
const drugList = ref([])
|
||||
const currentDrugs = ref([])
|
||||
const category = ref(1)
|
||||
const selectProductId = ref(0)
|
||||
|
||||
// 中药相关配置
|
||||
const processRuleList = ref([])
|
||||
const childProcessRuleList = ref([])
|
||||
const processRuleNoteList = ref([])
|
||||
const processRuleId = ref()
|
||||
const processRuleNoteId = ref()
|
||||
const childProcessRuleId = ref()
|
||||
const packageMethodId = ref(2)
|
||||
const dosage = ref(7)
|
||||
const dayDosage = ref(2)
|
||||
|
||||
// 新药品相关
|
||||
const newDrugInfo = ref<any>({})
|
||||
const selectChineseIndex = ref(-1)
|
||||
const selectChineseId = ref(0)
|
||||
|
||||
// 二次签名相关
|
||||
const doctorSecondSign = ref(0)
|
||||
const checkData = ref<any>({})
|
||||
|
||||
// 获取localStorage key
|
||||
const getStorageKey = () => `prescriptionData-chat-${currentRegisterId.value}`
|
||||
|
||||
// 计算属性
|
||||
const totalProductCost = computed(() => {
|
||||
if (currentDrugs.value.length === 0) {
|
||||
return 0
|
||||
}
|
||||
if (activeCategory.value === 1) {
|
||||
return currentDrugs.value.reduce((sum, drug) => sum + drug.price * (drug.number || 1) * dosage.value, 0)
|
||||
}
|
||||
return currentDrugs.value.reduce((sum, drug) => sum + drug.price * (drug.select_number || 1), 0)
|
||||
})
|
||||
|
||||
const processRulePrice = ref(0)
|
||||
const calcMethod = ref(0)
|
||||
|
||||
const processingFee = computed(() => {
|
||||
if (ruleType.value === 1) return 0
|
||||
if (currentDrugs.value.length === 0) return 0
|
||||
|
||||
const processRule = childProcessRuleList.value.find((item) => item.id === childProcessRuleId.value)
|
||||
if (!processRule) return 0
|
||||
|
||||
calcMethod.value = processRule.calc_method
|
||||
processRulePrice.value = processRule.price
|
||||
|
||||
if (calcMethod.value === 1) return processRulePrice.value
|
||||
if (calcMethod.value === 2) return processRulePrice.value * dosage.value
|
||||
if (calcMethod.value === 3) {
|
||||
const totalNumber = currentDrugs.value.reduce((sum, drug) => sum + drug.number, 0)
|
||||
return processRulePrice.value * dosage.value * totalNumber
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
const totalCost = computed(() => {
|
||||
return totalProductCost.value + processingFee.value
|
||||
})
|
||||
|
||||
// 重新设计的localStorage同步方法
|
||||
const syncToLocalStorage = () => {
|
||||
try {
|
||||
const storageKey = getStorageKey()
|
||||
localStorage.setItem(storageKey, JSON.stringify(currentDrugs.value))
|
||||
console.log("已同步到localStorage:", storageKey, currentDrugs.value)
|
||||
} catch (error) {
|
||||
console.error("保存到localStorage失败:", error)
|
||||
}
|
||||
}
|
||||
|
||||
const loadFromLocalStorage = () => {
|
||||
try {
|
||||
const storageKey = getStorageKey()
|
||||
const stored = localStorage.getItem(storageKey)
|
||||
console.log("从localStorage加载:", storageKey, stored)
|
||||
|
||||
if (stored) {
|
||||
const parsedData = JSON.parse(stored)
|
||||
// 直接替换整个数组以确保响应式更新
|
||||
currentDrugs.value.splice(0, currentDrugs.value.length, ...parsedData)
|
||||
} else {
|
||||
// 清空数组
|
||||
currentDrugs.value.splice(0, currentDrugs.value.length)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("从localStorage加载失败:", error)
|
||||
currentDrugs.value.splice(0, currentDrugs.value.length)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新currentDrugs并同步到localStorage
|
||||
const updateCurrentDrugs = (newDrugs: any[]) => {
|
||||
// 使用splice确保响应式更新
|
||||
currentDrugs.value.splice(0, currentDrugs.value.length, ...newDrugs)
|
||||
syncToLocalStorage()
|
||||
}
|
||||
|
||||
// 方法
|
||||
const initializePrescription = async (registerId: number | string) => {
|
||||
currentRegisterId.value = registerId
|
||||
|
||||
// 先加载localStorage数据
|
||||
loadFromLocalStorage()
|
||||
|
||||
await Promise.all([getMyStoreList(), getDrugUseListData(), getProcessRuleListData(), getPatientInfo()])
|
||||
}
|
||||
|
||||
const getMyStoreList = async () => {
|
||||
try {
|
||||
const res = await getMyStoreListApi()
|
||||
myStoreList.value = res
|
||||
if (res.length > 0) {
|
||||
myStoreId.value = res[0].id
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取诊所列表失败:", error)
|
||||
}
|
||||
}
|
||||
|
||||
const getDrugUseListData = async () => {
|
||||
try {
|
||||
const res = await getDrugUseList()
|
||||
drugUseNum.value = res.drug_use_num
|
||||
drugUseFrequency.value = res.drug_use_frequency
|
||||
drugUseType.value = res.drug_use_type
|
||||
drugUnit.value = res.drug_unit
|
||||
drugTime.value = res.drug_time
|
||||
drugUseWay.value = res.drug_use_way
|
||||
} catch (error) {
|
||||
console.error("获取药品使用列表失败:", error)
|
||||
}
|
||||
}
|
||||
|
||||
const getProcessRuleListData = async (pid = 0, ruleId = 0) => {
|
||||
try {
|
||||
const data = ruleId === 0 ? { pid } : { rule_id: ruleId }
|
||||
const res = await getProcessRuleList(data)
|
||||
|
||||
if (ruleId !== 0) {
|
||||
processRuleNoteList.value = res
|
||||
} else if (pid === 0) {
|
||||
processRuleList.value = res
|
||||
} else {
|
||||
childProcessRuleList.value = res
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取加工规则失败:", error)
|
||||
}
|
||||
}
|
||||
|
||||
const getPatientInfo = async () => {
|
||||
try {
|
||||
const res = await getPatientItem(currentRegisterId.value)
|
||||
patientInfo.value = res
|
||||
activePatient.value = res.user_patient
|
||||
userPatientHealthInquiry.value = res.user_patient_health_inquiry
|
||||
prescriptionList.value = res.prescription
|
||||
} catch (error) {
|
||||
console.error("获取患者信息失败:", error)
|
||||
}
|
||||
}
|
||||
|
||||
// 药品搜索
|
||||
const getDrugList = debounce(async (searchText = "") => {
|
||||
if (searchText === "" && activeCategory.value === 1) {
|
||||
drugList.value = []
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getProductListDoctorReception({
|
||||
store_id: myStoreId.value,
|
||||
type: activeCategory.value,
|
||||
name: searchText,
|
||||
})
|
||||
|
||||
drugList.value = res.map((item) => {
|
||||
const matched = currentDrugs.value.find((v) => v.index_id === item.id)
|
||||
return {
|
||||
...item,
|
||||
select_number: matched?.select_number || 0,
|
||||
drug: {
|
||||
...item.drug,
|
||||
number: matched?.number || 1,
|
||||
},
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("获取药品列表失败:", error)
|
||||
message.error("获取药品列表失败")
|
||||
}
|
||||
}, 300)
|
||||
|
||||
// 药品操作
|
||||
const addProducts = (data: any) => {
|
||||
const existItem = currentDrugs.value.find((item) => item.index_id === data.id)
|
||||
if (existItem) {
|
||||
message.warn("已经存在了")
|
||||
return false
|
||||
}
|
||||
|
||||
const newProduct = {
|
||||
index_id: data.id,
|
||||
id: data.drug.id,
|
||||
drug_name: data.drug.drug_name,
|
||||
number: data.drug.number,
|
||||
use_num: drugTime.value.find((item) => item.id === data.drug.time_id),
|
||||
use_type: drugUseType.value.find((item) => item.id === data.drug.type_id),
|
||||
use_frequency: drugUseFrequency.value.find((item) => item.id === data.drug.frequency_id),
|
||||
unit: drugUnit.value.find((item) => item.id === data.drug.unit_id),
|
||||
price: data.price,
|
||||
way_id: data.drug?.way_id,
|
||||
use_ways: drugUseWay.value.find((item) => item.id === data.drug.way_id),
|
||||
time_id: data.drug.time_id,
|
||||
type_id: data.drug.type_id,
|
||||
frequency_id: data.drug.frequency_id,
|
||||
unit_id: data.drug.unit_id,
|
||||
image: data.drug.image,
|
||||
instruction: data.drug.instruction,
|
||||
type: data.drug.type,
|
||||
select_number: 1,
|
||||
}
|
||||
|
||||
// 使用新的更新方法
|
||||
const newDrugs = [...currentDrugs.value, newProduct]
|
||||
updateCurrentDrugs(newDrugs)
|
||||
|
||||
message.success(`已将${newProduct.drug_name}添加到清单中!`)
|
||||
return true
|
||||
}
|
||||
|
||||
const removeDrug = (index: number) => {
|
||||
const newDrugs = currentDrugs.value.filter((_, i) => i !== index)
|
||||
updateCurrentDrugs(newDrugs)
|
||||
}
|
||||
|
||||
const updateDrugQuantity = (index: number, quantity: number) => {
|
||||
if (quantity > 0) {
|
||||
const newDrugs = [...currentDrugs.value]
|
||||
newDrugs[index].select_number = quantity
|
||||
updateCurrentDrugs(newDrugs)
|
||||
}
|
||||
}
|
||||
|
||||
const increment = (index: number) => {
|
||||
const newDrugs = [...currentDrugs.value]
|
||||
newDrugs[index].select_number++
|
||||
updateCurrentDrugs(newDrugs)
|
||||
}
|
||||
|
||||
const decrement = (index: number) => {
|
||||
if (currentDrugs.value[index].select_number > 1) {
|
||||
const newDrugs = [...currentDrugs.value]
|
||||
newDrugs[index].select_number--
|
||||
updateCurrentDrugs(newDrugs)
|
||||
}
|
||||
}
|
||||
|
||||
// 新药品操作
|
||||
const selectNewDrugInfo = () => {
|
||||
const check = currentDrugs.value.find((v) => v.id === newDrugInfo.value.id)
|
||||
if (check) {
|
||||
newDrugInfo.value.id = ""
|
||||
message.error("该药品已经在处方中了!")
|
||||
return
|
||||
}
|
||||
|
||||
const data = drugList.value.find((v) => v.drug_id === newDrugInfo.value.id)
|
||||
if (data) {
|
||||
newDrugInfo.value.price = data.price
|
||||
newDrugInfo.value.name = data.drug.drug_name
|
||||
nextTick(() => {
|
||||
const input = document.querySelector(".new-number-input input") as HTMLElement
|
||||
input?.focus()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const selectOldDrugInfo = (id: number) => {
|
||||
const check = currentDrugs.value.find((v) => v.id === id)
|
||||
|
||||
if (check) {
|
||||
const newDrugs = [...currentDrugs.value]
|
||||
newDrugs[selectChineseIndex.value].id = selectChineseId.value
|
||||
updateCurrentDrugs(newDrugs)
|
||||
drugList.value = []
|
||||
message.error("该药品已经在处方中了!")
|
||||
return
|
||||
}
|
||||
|
||||
const data = drugList.value.find((v) => v.drug_id === id)
|
||||
if (data) {
|
||||
const newProduct = {
|
||||
index_id: data.id,
|
||||
id: data.drug.id,
|
||||
drug_name: data.drug.drug_name,
|
||||
number: data.drug.number,
|
||||
use_num: drugTime.value.find((item) => item.id === data.drug.time_id),
|
||||
use_type: drugUseType.value.find((item) => item.id === data.drug.type_id),
|
||||
use_frequency: drugUseFrequency.value.find((item) => item.id === data.drug.frequency_id),
|
||||
unit: drugUnit.value.find((item) => item.id === data.drug.unit_id),
|
||||
price: data.price,
|
||||
way_id: data.drug?.way_id,
|
||||
use_ways: drugUseWay.value.find((item) => item.id === data.drug.way_id),
|
||||
time_id: data.drug.time_id,
|
||||
type_id: data.drug.type_id,
|
||||
frequency_id: data.drug.frequency_id,
|
||||
unit_id: data.drug.unit_id,
|
||||
image: data.drug.image,
|
||||
instruction: data.drug.instruction,
|
||||
type: data.drug.type,
|
||||
}
|
||||
|
||||
const newDrugs = [...currentDrugs.value]
|
||||
newDrugs[selectChineseIndex.value] = newProduct
|
||||
updateCurrentDrugs(newDrugs)
|
||||
|
||||
nextTick(() => {
|
||||
const input = document.querySelector(`.old-number-input-${selectChineseIndex.value} input`) as HTMLElement
|
||||
input?.focus()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const setSelectChineseIndex = (index: number, id: number) => {
|
||||
selectChineseIndex.value = index
|
||||
selectChineseId.value = id
|
||||
}
|
||||
|
||||
const addDrugByChinese = () => {
|
||||
const check = currentDrugs.value.find((v) => v.id === newDrugInfo.value.id)
|
||||
if (check) {
|
||||
message.error("该药品已经在处方中了!")
|
||||
return false
|
||||
}
|
||||
|
||||
const data = drugList.value.find((v) => v.drug_id === newDrugInfo.value.id)
|
||||
if (!data) {
|
||||
message.error("请选择药品")
|
||||
return false
|
||||
}
|
||||
|
||||
data.drug.number = newDrugInfo.value.number
|
||||
data.drug.way_id = newDrugInfo.value.way_id
|
||||
const success = addProducts(data)
|
||||
if (success) {
|
||||
newDrugInfo.value = {}
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
const selectDrugByNewDrugInfo = (event: KeyboardEvent, isNewDrug: boolean) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault()
|
||||
if (isNewDrug) {
|
||||
addDrugByChinese()
|
||||
} else {
|
||||
syncToLocalStorage()
|
||||
}
|
||||
nextTick(() => {
|
||||
const input = document.querySelector(".new-select-drug-name input") as HTMLElement
|
||||
input?.focus()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const updateChineseNumber = () => {
|
||||
syncToLocalStorage()
|
||||
}
|
||||
|
||||
const updateChineseNumberGoNewDrug = (event: KeyboardEvent) => {
|
||||
if (event.key === "Enter") {
|
||||
syncToLocalStorage()
|
||||
nextTick(() => {
|
||||
const input = document.querySelector(".new-select-drug-name input") as HTMLElement
|
||||
input?.focus()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 药品用法选择
|
||||
const selectProductChange = (id: number) => {
|
||||
selectProductId.value = id
|
||||
}
|
||||
|
||||
const selectDrugUseWayChange = (id: number) => {
|
||||
const newDrugs = currentDrugs.value.map((item) =>
|
||||
item.index_id === selectProductId.value
|
||||
? {
|
||||
...item,
|
||||
way_id: id,
|
||||
use_ways: drugUseWay.value.find((value) => value.id === id),
|
||||
}
|
||||
: item,
|
||||
)
|
||||
updateCurrentDrugs(newDrugs)
|
||||
}
|
||||
|
||||
// 加工规则操作
|
||||
const selectProcessRule = (id: number) => {
|
||||
processRuleId.value = id
|
||||
getProcessRuleListData(id)
|
||||
}
|
||||
|
||||
const selectProcessRuleNot = (id: number) => {
|
||||
childProcessRuleId.value = id
|
||||
getProcessRuleListData(0, id)
|
||||
}
|
||||
|
||||
const selectProcessRuleNotCommit = (id: number) => {
|
||||
processRuleNoteId.value = id
|
||||
}
|
||||
|
||||
const selectPackageMethod = (id: number) => {
|
||||
packageMethodId.value = id
|
||||
}
|
||||
|
||||
// 中药相冲检查
|
||||
const checkChineseMedicineConflict = async () => {
|
||||
if (activeCategory.value === 2) {
|
||||
return { hasConflict: false }
|
||||
}
|
||||
|
||||
notification.info({
|
||||
message: "正在检查药物相冲",
|
||||
duration: 1,
|
||||
description: "正在检查药物相冲,请稍等...",
|
||||
})
|
||||
|
||||
try {
|
||||
const names = currentDrugs.value.map((item) => item.drug_name)
|
||||
const res = await checkChineseMedicineConflictApi({ names })
|
||||
|
||||
if (res.is_exist === true) {
|
||||
checkData.value = { message: res.message }
|
||||
return { hasConflict: true, message: res.message }
|
||||
} else {
|
||||
notification.success({
|
||||
message: "检查成功",
|
||||
duration: 3,
|
||||
description: "暂无相冲药品",
|
||||
})
|
||||
return { hasConflict: false }
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("检查药物相冲失败:", error)
|
||||
return { hasConflict: false }
|
||||
}
|
||||
}
|
||||
|
||||
// 发送处方
|
||||
const sendPrescription = async (doctorSecondSignValue = 0) => {
|
||||
// 验证表单
|
||||
if (currentDrugs.value.length === 0) {
|
||||
message.error("请选择药品")
|
||||
return false
|
||||
}
|
||||
|
||||
if (activeCategory.value === 1) {
|
||||
if (ruleType.value === 1 && packageMethodId.value == null) {
|
||||
message.error("请选择包法")
|
||||
return false
|
||||
}
|
||||
if (ruleType.value === 2) {
|
||||
if (processRuleId.value == null) {
|
||||
message.error("请选择制剂")
|
||||
return false
|
||||
}
|
||||
if (processRuleNoteId.value == null) {
|
||||
message.error("请选择规格")
|
||||
return false
|
||||
}
|
||||
if (childProcessRuleId.value == null) {
|
||||
message.error("请选择备注")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!diagnosis.value) {
|
||||
message.error("诊断结果不能为空")
|
||||
return false
|
||||
}
|
||||
|
||||
if (!medicalAdvice.value) {
|
||||
message.error("医嘱不能为空")
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
await addWestPrescription({
|
||||
patient: activePatient.value,
|
||||
drugs: currentDrugs.value,
|
||||
diagnosis: diagnosis.value,
|
||||
medicalAdvice: medicalAdvice.value,
|
||||
total: totalCost.value,
|
||||
category: category.value,
|
||||
drug_type: 2,
|
||||
register_id: currentRegisterId.value,
|
||||
treatment_price: treatmentPrice.value,
|
||||
package_method_id: packageMethodId.value,
|
||||
process_rule_id: processRuleId.value,
|
||||
process_rule_note_id: processRuleNoteId.value,
|
||||
child_process_rule_id: childProcessRuleId.value,
|
||||
process_rule_type: ruleType.value,
|
||||
prescription_type: activeCategory.value,
|
||||
processing_fee: processRulePrice.value,
|
||||
dosage: dosage.value,
|
||||
day_dosage: dayDosage.value,
|
||||
doctor_second_sign: doctorSecondSignValue,
|
||||
})
|
||||
|
||||
message.success("处方已发送")
|
||||
resetForm()
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error("发送处方失败:", error)
|
||||
message.error("发送处方失败")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
updateCurrentDrugs([])
|
||||
diagnosis.value = ""
|
||||
medicalAdvice.value = ""
|
||||
packageMethodId.value = 2
|
||||
processRulePrice.value = 0
|
||||
dosage.value = 7
|
||||
dayDosage.value = 2
|
||||
newDrugInfo.value = {}
|
||||
doctorSecondSign.value = 0
|
||||
}
|
||||
|
||||
const changeCategory = (categoryValue: number) => {
|
||||
activeCategory.value = categoryValue
|
||||
updateCurrentDrugs([])
|
||||
localStorage.setItem(`activeCategory-chat-${currentRegisterId.value}`, categoryValue.toString())
|
||||
}
|
||||
|
||||
// 工具函数
|
||||
const splitString = (str: string) => {
|
||||
if (!str) return []
|
||||
return str.split(",")
|
||||
}
|
||||
|
||||
// 强制刷新currentDrugs(用于调试或特殊情况)
|
||||
const refreshCurrentDrugs = () => {
|
||||
loadFromLocalStorage()
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
patientInfo,
|
||||
userPatientHealthInquiry,
|
||||
prescriptionList,
|
||||
currentRegisterId,
|
||||
activePatient,
|
||||
drugUseNum,
|
||||
drugUseFrequency,
|
||||
drugUseType,
|
||||
drugUnit,
|
||||
drugTime,
|
||||
drugUseWay,
|
||||
myStoreList,
|
||||
myStoreId,
|
||||
activeCategory,
|
||||
diagnosis,
|
||||
medicalAdvice,
|
||||
treatmentPrice,
|
||||
ruleType,
|
||||
drugList,
|
||||
currentDrugs,
|
||||
category,
|
||||
selectProductId,
|
||||
processRuleList,
|
||||
childProcessRuleList,
|
||||
processRuleNoteList,
|
||||
processRuleId,
|
||||
processRuleNoteId,
|
||||
childProcessRuleId,
|
||||
packageMethodId,
|
||||
dosage,
|
||||
dayDosage,
|
||||
newDrugInfo,
|
||||
selectChineseIndex,
|
||||
selectChineseId,
|
||||
doctorSecondSign,
|
||||
checkData,
|
||||
|
||||
// 计算属性
|
||||
totalProductCost,
|
||||
processingFee,
|
||||
totalCost,
|
||||
|
||||
// 方法
|
||||
initializePrescription,
|
||||
getDrugList,
|
||||
addProducts,
|
||||
removeDrug,
|
||||
updateDrugQuantity,
|
||||
increment,
|
||||
decrement,
|
||||
selectNewDrugInfo,
|
||||
selectOldDrugInfo,
|
||||
setSelectChineseIndex,
|
||||
addDrugByChinese,
|
||||
selectDrugByNewDrugInfo,
|
||||
updateChineseNumber,
|
||||
updateChineseNumberGoNewDrug,
|
||||
selectProductChange,
|
||||
selectDrugUseWayChange,
|
||||
selectProcessRule,
|
||||
selectProcessRuleNot,
|
||||
selectProcessRuleNotCommit,
|
||||
selectPackageMethod,
|
||||
checkChineseMedicineConflict,
|
||||
sendPrescription,
|
||||
resetForm,
|
||||
changeCategory,
|
||||
getProcessRuleListData,
|
||||
splitString,
|
||||
refreshCurrentDrugs,
|
||||
|
||||
// 新增的方法
|
||||
syncToLocalStorage,
|
||||
loadFromLocalStorage,
|
||||
updateCurrentDrugs,
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,37 @@
|
||||
<script setup>
|
||||
import { defineEmits, defineProps } from 'vue';
|
||||
|
||||
import ChatHeader from './ChatHeader.vue';
|
||||
import MessageInput from './MessageInput.vue';
|
||||
import MessageList from './MessageList.vue';
|
||||
|
||||
// 定义 props 和 emits
|
||||
const props = defineProps({
|
||||
// ... existing props
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
// ... existing emits
|
||||
'openPrescription',
|
||||
]);
|
||||
|
||||
// 开方功能触发函数
|
||||
const handleOpenPrescription = (registerId) => {
|
||||
emit('openPrescription', registerId);
|
||||
};
|
||||
|
||||
// ... rest of the component
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="flex h-full flex-col">
|
||||
<!-- 聊天头部 -->
|
||||
<ChatHeader/>
|
||||
<ChatHeader />
|
||||
|
||||
<!-- 消息区域 -->
|
||||
<MessageList class="flex-1"/>
|
||||
<MessageList class="flex-1" />
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<MessageInput/>
|
||||
<MessageInput @open-prescription="handleOpenPrescription" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ChatHeader from './ChatHeader.vue';
|
||||
import MessageList from './MessageList.vue';
|
||||
import MessageInput from './MessageInput.vue';
|
||||
</script>
|
||||
|
||||
@@ -8,6 +8,8 @@ import {Avatar, Image} from 'ant-design-vue';
|
||||
import previewMedia from '../composables/useMediaPreview.ts';
|
||||
import {useThemeStore} from '../stores/theme.ts';
|
||||
import AudioMessage from './AudioMessage.vue';
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
|
||||
|
||||
const props = defineProps({
|
||||
message: {
|
||||
@@ -43,6 +45,10 @@ const bubbleClasses = computed(() => {
|
||||
baseClasses.push('received');
|
||||
}
|
||||
|
||||
if (props.message.type === 'text') {
|
||||
baseClasses.push('bubble-bg');
|
||||
}
|
||||
|
||||
if (themeStore.isDarkMode) {
|
||||
baseClasses.push('dark');
|
||||
}
|
||||
@@ -94,6 +100,26 @@ const handleImageError = (event) => {
|
||||
const handleVideoError = (event) => {
|
||||
console.error('视频加载失败:', event);
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 添加处方状态映射
|
||||
const prescriptionStatusMap = {
|
||||
0: '待审核',
|
||||
1: '已审核',
|
||||
2: '未通过',
|
||||
};
|
||||
|
||||
const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
|
||||
connectedComponent: PrescriptionDetail,
|
||||
});
|
||||
|
||||
const viewPrescription = (item) => {
|
||||
PrescriptionDetailModalApi.setData({
|
||||
values: item.id,
|
||||
})
|
||||
PrescriptionDetailModalApi.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -101,6 +127,7 @@ const handleVideoError = (event) => {
|
||||
:class="isSent ? 'flex-row-reverse' : 'flex-row'"
|
||||
class="mb-4 flex items-start gap-3"
|
||||
>
|
||||
<PrescriptionDetailModal />
|
||||
<!-- 头像 -->
|
||||
<!-- <div-->
|
||||
<!-- :style="avatarStyle"-->
|
||||
@@ -211,6 +238,61 @@ const handleVideoError = (event) => {
|
||||
:message="message"
|
||||
/>
|
||||
|
||||
<!-- 处方卡片消息 -->
|
||||
<!-- 处方卡片消息 -->
|
||||
<div
|
||||
v-else-if="message.type === 'prescription'"
|
||||
class="prescription-card"
|
||||
@click="viewPrescription(message.content)"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-12 h-12 rounded-full bg-blue-100 dark:bg-blue-900 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fas fa-file-prescription text-blue-500 dark:text-blue-300 text-xl"></i>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="font-semibold text-gray-800 dark:text-gray-100">电子处方</div>
|
||||
<div
|
||||
class="text-xs px-2 py-1 rounded-full font-medium"
|
||||
:class="{
|
||||
'bg-yellow-100 text-yellow-800': message.content.status === 0,
|
||||
'bg-green-100 text-green-800': message.content.status === 1,
|
||||
'bg-blue-100 text-blue-800': message.content.status === 2,
|
||||
'bg-gray-100 text-gray-800': message.content.status === 3,
|
||||
'bg-red-100 text-red-800': message.content.status === 4
|
||||
}"
|
||||
>
|
||||
{{ prescriptionStatusMap[message.content.status] }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-1 flex flex-wrap gap-2 text-xs text-gray-600 dark:text-gray-300">
|
||||
<div class="flex items-center gap-1">
|
||||
<i class="fas fa-hashtag text-xs"></i>
|
||||
<span class="truncate max-w-[100px]">{{ message.content.order_no }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1">
|
||||
<i class="fas fa-truck text-xs"></i>
|
||||
<span>{{ message.content.express_name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 pt-2 border-t border-gray-200 dark:border-gray-600 flex items-center justify-between">
|
||||
<div class="text-sm font-medium text-gray-700 dark:text-gray-200">
|
||||
支付金额: <span class="text-red-500 dark:text-red-400">¥{{ message.content.total_pay_price }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center text-blue-500 dark:text-blue-300 hover:text-blue-600 dark:hover:text-blue-200 transition-colors">
|
||||
<span class="text-sm font-medium">查看处方</span>
|
||||
<i class="fas fa-chevron-right ml-1 text-xs"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通话消息 -->
|
||||
<div
|
||||
v-else-if="message.type === 'video-call' || message.type === 'voice-call'"
|
||||
@@ -262,19 +344,25 @@ v-else-if="message.type === 'video-call' || message.type === 'voice-call'"
|
||||
}
|
||||
|
||||
.message-bubble.sent {
|
||||
background: linear-gradient(135deg, #4361ee, #3f37c9);
|
||||
color: white;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.message-bubble.received {
|
||||
.bubble-bg {
|
||||
background: linear-gradient(135deg, #4361ee, #3f37c9);
|
||||
}
|
||||
.bubble-bg.dark {
|
||||
background: linear-gradient(135deg, #4361ee, #3f37c9);
|
||||
}
|
||||
|
||||
.message-bubble.received.bubble-bg {
|
||||
background: white;
|
||||
color: #1a202c;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
.message-bubble.received.dark {
|
||||
.message-bubble.received.dark.bubble-bg {
|
||||
background: #374151;
|
||||
color: #f7fafc;
|
||||
border-color: #4b5563;
|
||||
@@ -514,4 +602,19 @@ v-else-if="message.type === 'video-call' || message.type === 'voice-call'"
|
||||
.message-bubble.sent :deep(.text-link:hover) {
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
/* 处方卡片样式 - 使用TailwindCSS类替代 */
|
||||
.prescription-card {
|
||||
@apply w-full max-w-xs md:max-w-sm cursor-pointer rounded-xl bg-white dark:bg-gray-700 p-4 shadow-sm transition-all duration-300;
|
||||
@apply border border-gray-200 dark:border-gray-600 hover:shadow-md hover:border-blue-300 dark:hover:border-blue-500;
|
||||
}
|
||||
|
||||
.message-bubble.sent .prescription-card {
|
||||
@apply bg-blue-50/30 dark:bg-blue-900/30 border-blue-200/50 dark:border-blue-700/70;
|
||||
}
|
||||
|
||||
.prescription-card:hover {
|
||||
@apply transform -translate-y-0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { nextTick, onMounted, onUnmounted, provide, ref } from 'vue';
|
||||
import { ref, provide, nextTick, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
@@ -13,6 +13,14 @@ import CustomTextarea from './CustomTextarea.vue';
|
||||
import EmojiPicker from './EmojiPicker.vue';
|
||||
import FileUploadPreview from './FileUploadPreview.vue';
|
||||
|
||||
// 定义 props
|
||||
const props = defineProps({
|
||||
currentFriend: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
const userStore = useUserStore();
|
||||
const chatStore = useChatStore();
|
||||
const themeStore = useThemeStore();
|
||||
@@ -244,65 +252,84 @@ onMounted(() => {
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('audioRecorded', handleAudioRecorded);
|
||||
});
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits([
|
||||
'sendMessage',
|
||||
'openPrescription' // 添加开方事件
|
||||
]);
|
||||
|
||||
// 开方功能触发函数
|
||||
const handleOpenPrescription = () => {
|
||||
// 获取当前会话的register_id,这里假设可以通过某种方式获取
|
||||
// 例如从聊天存储或props中获取
|
||||
const registerId = props.currentFriend?.register_id || 66;
|
||||
|
||||
if (registerId) {
|
||||
emit('openPrescription', registerId);
|
||||
} else {
|
||||
console.warn('无法获取当前会话的register_id');
|
||||
// 可以添加一个提示或使用默认值
|
||||
emit('openPrescription', 0);
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="{ dark: themeStore.isDarkMode }" class="message-input-area">
|
||||
<div class="message-input-container" :class="{ dark: themeStore.isDarkMode }">
|
||||
|
||||
<!-- 文件上传预览 -->
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="input-container">
|
||||
<!-- 功能按钮区域 -->
|
||||
<div class="function-buttons">
|
||||
<button
|
||||
class="function-btn"
|
||||
title="发送图片"
|
||||
@click="triggerFileInput('image')"
|
||||
>
|
||||
<i class="fas fa-image"></i>
|
||||
<div class="btn-ripple"></div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="function-btn"
|
||||
title="发送视频"
|
||||
@click="triggerFileInput('video')"
|
||||
>
|
||||
<i class="fas fa-video"></i>
|
||||
<div class="btn-ripple"></div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
:class="{ active: showEmojiPicker }"
|
||||
class="function-btn"
|
||||
title="选择表情"
|
||||
@click="toggleEmojiPicker"
|
||||
>
|
||||
<i class="fas fa-smile"></i>
|
||||
<div class="btn-ripple"></div>
|
||||
</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>
|
||||
<div class="btn-ripple"></div>
|
||||
<div v-if="isRecording" class="recording-wave"></div>
|
||||
</button>
|
||||
|
||||
<!-- 录音状态指示器 -->
|
||||
<!-- <RecordingIndicator />-->
|
||||
</div>
|
||||
|
||||
<!-- 输入框区域 -->
|
||||
<div class="input-section">
|
||||
<CustomTextarea
|
||||
@@ -348,14 +375,14 @@ onUnmounted(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.message-input-area {
|
||||
.message-input-container {
|
||||
padding: 20px;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-input-area.dark {
|
||||
.message-input-container.dark {
|
||||
border-top-color: #374151;
|
||||
background: linear-gradient(135deg, #2d2d2d 0%, #1f2937 100%);
|
||||
}
|
||||
@@ -366,6 +393,38 @@ onUnmounted(() => {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
border-top: 1px solid #666666;
|
||||
}
|
||||
|
||||
.function-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
margin-right: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.function-btn:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.prescription-button {
|
||||
color: #455cda;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.prescription-button:hover {
|
||||
background-color: #e6e9ff;
|
||||
}
|
||||
|
||||
.function-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
@@ -461,12 +520,12 @@ onUnmounted(() => {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.message-input-area.dark .function-btn {
|
||||
.message-input-container.dark .function-btn {
|
||||
background: linear-gradient(135deg, #374151, #4b5563);
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.message-input-area.dark .function-btn:hover {
|
||||
.message-input-container.dark .function-btn:hover {
|
||||
background: linear-gradient(135deg, #4361ee, #3f37c9);
|
||||
color: white;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import ChatArea from './components/ChatArea.vue';
|
||||
import ConnectionStatus from './components/ConnectionStatus.vue';
|
||||
@@ -9,7 +11,7 @@ import FriendsManagement from './components/FriendsManagement.vue';
|
||||
import GroupsManagement from './components/GroupsManagement.vue';
|
||||
import MediaPreview from './components/MediaPreview.vue';
|
||||
import MomentsView from './components/MomentsView.vue';
|
||||
// import SideNavigation from './components/SideNavigation.vue';
|
||||
import PrescriptionModal from './components/PrescriptionModal.vue';
|
||||
import VideoCallComponent from './components/VideoCallComponent.vue';
|
||||
import { useChatStore } from './stores/chat.ts';
|
||||
import { useThemeStore } from './stores/theme.ts';
|
||||
@@ -25,19 +27,39 @@ const callType = ref('video');
|
||||
const isIncoming = ref(false);
|
||||
const callerInfo = ref(null);
|
||||
|
||||
// 初始化录音功能
|
||||
// const { isRecording } = useRecording();
|
||||
// 使用 VbenModal 管理处方模态框
|
||||
const [PrescriptionModalComponent, prescriptionModalApi] = useVbenModal({
|
||||
connectedComponent: PrescriptionModal,
|
||||
});
|
||||
|
||||
// 处理导航切换
|
||||
const handleNavChange = (nav) => {
|
||||
currentNav.value = nav;
|
||||
};
|
||||
|
||||
// 显示开方模块 - 使用正确的VbenModal数据传递方式
|
||||
const openPrescriptionModule = (registerId) => {
|
||||
console.log('registerId', registerId);
|
||||
|
||||
// 通过 setData 传递数据,然后打开模态框
|
||||
prescriptionModalApi.setData({
|
||||
registerId,
|
||||
onPrescriptionSent: handlePrescriptionSent, // 传递回调函数
|
||||
});
|
||||
prescriptionModalApi.open();
|
||||
};
|
||||
|
||||
// 处理处方发送完成事件
|
||||
const handlePrescriptionSent = () => {
|
||||
console.log('处方发送完成');
|
||||
// 可以在这里添加其他逻辑,如刷新聊天记录等
|
||||
// 注意:模态框会在PrescriptionModal组件内部关闭
|
||||
};
|
||||
|
||||
// 监听通话状态变化
|
||||
watch(
|
||||
() => chatStore.callStatus,
|
||||
(status) => {
|
||||
// 只有当通话状态是活跃状态时才显示视频组件
|
||||
showVideoCall.value =
|
||||
status === 'connecting' ||
|
||||
status === 'calling' ||
|
||||
@@ -69,7 +91,6 @@ watch(
|
||||
callType.value = call.callType;
|
||||
isIncoming.value = false;
|
||||
} else {
|
||||
// 当通话结束时重置状态
|
||||
showVideoCall.value = false;
|
||||
}
|
||||
},
|
||||
@@ -85,7 +106,7 @@ const handleRejectCall = () => {
|
||||
chatStore.rejectCall();
|
||||
};
|
||||
|
||||
// 处理结束通话 - 修复挂断逻辑
|
||||
// 处理结束通话
|
||||
const handleEndCall = () => {
|
||||
chatStore.endCall();
|
||||
};
|
||||
@@ -94,33 +115,16 @@ const handleEndCall = () => {
|
||||
themeStore.loadTheme();
|
||||
|
||||
onUnmounted(() => {
|
||||
// disconnectWebSocket();
|
||||
// 清理资源
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
style="height: 95vh;"
|
||||
class="flex bg-gradient-to-br from-blue-900 via-purple-900 to-pink-300 p-5"
|
||||
class="chat-box-body flex bg-gradient-to-br from-blue-900 via-purple-900 to-pink-300 p-5"
|
||||
style="height: 95vh"
|
||||
>
|
||||
<!-- <!– 背景装饰 –>-->
|
||||
<!-- <div class="pointer-events-none fixed inset-0 z-0">-->
|
||||
<!-- <div-->
|
||||
<!-- class="animate-float absolute left-10 top-10 h-72 w-72 rounded-full bg-blue-500 opacity-10"-->
|
||||
<!-- ></div>-->
|
||||
<!-- <div-->
|
||||
<!-- class="right-15 animate-float-delayed absolute top-60 h-48 w-48 rounded-full bg-cyan-400 opacity-10"-->
|
||||
<!-- ></div>-->
|
||||
<!-- <div-->
|
||||
<!-- class="animate-float-slow absolute bottom-10 left-20 h-36 w-36 rounded-full bg-red-400 opacity-10"-->
|
||||
<!-- ></div>-->
|
||||
<!-- </div>-->
|
||||
|
||||
|
||||
<!-- <!– 录音状态指示器 –>-->
|
||||
<!-- <RecordingIndicator />-->
|
||||
|
||||
<!-- 视频通话组件(移动到此处) -->
|
||||
<!-- 视频通话组件 -->
|
||||
<VideoCallComponent
|
||||
v-if="showVideoCall"
|
||||
:call-type="callType"
|
||||
@@ -137,18 +141,19 @@ onUnmounted(() => {
|
||||
>
|
||||
<!-- 连接状态指示器 -->
|
||||
<ConnectionStatus />
|
||||
<!-- <!– 侧边导航 –>-->
|
||||
<!-- <SideNavigation-->
|
||||
<!-- :current-view="currentNav"-->
|
||||
<!-- @nav-change="handleNavChange"-->
|
||||
<!-- />-->
|
||||
|
||||
<!-- 好友列表 -->
|
||||
<FriendList class="w-80 min-w-80" />
|
||||
|
||||
<!-- 处方模态框 - 使用 VbenModal 模式 -->
|
||||
<PrescriptionModalComponent />
|
||||
|
||||
<!-- 聊天区域 -->
|
||||
<div class="min-w-0 flex-1">
|
||||
<ChatArea v-if="chatStore.currentFriend && currentNav === 'chat'" />
|
||||
<ChatArea
|
||||
v-if="chatStore.currentFriend && currentNav === 'chat'"
|
||||
@open-prescription="openPrescriptionModule"
|
||||
/>
|
||||
<FriendsManagement v-else-if="currentNav === 'friends'" />
|
||||
<GroupsManagement v-else-if="currentNav === 'groups'" />
|
||||
<MomentsView v-else-if="currentNav === 'moments'" />
|
||||
@@ -160,6 +165,7 @@ onUnmounted(() => {
|
||||
<MediaPreview />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/*@keyframes float {
|
||||
0% {
|
||||
|
||||
@@ -237,7 +237,10 @@ export const useChatStore = defineStore('chat', () => {
|
||||
return {
|
||||
id: message.id,
|
||||
type: getMessageType(message.message_type),
|
||||
content: message.message_content,
|
||||
content:
|
||||
message.message_type === 4
|
||||
? JSON.parse(message.message_content)
|
||||
: message.message_content,
|
||||
time: message.created_at_text,
|
||||
timestamp: message.created_at,
|
||||
senderId: message.sender_user_id,
|
||||
@@ -261,6 +264,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
if (message.type === 'image') lastMessage = '[图片]';
|
||||
if (message.type === 'video') lastMessage = '[视频]';
|
||||
if (message.type === 'audio') lastMessage = '[语音]';
|
||||
if (message.type === 'prescription') lastMessage = '[处方]';
|
||||
if (message.type === 'video-call' || message.type === 'audio-call')
|
||||
lastMessage = '[通话]';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {defineStore} from "pinia"
|
||||
import {ref} from "vue"
|
||||
import { defineStore } from "pinia"
|
||||
import { ref, nextTick } from "vue"
|
||||
|
||||
export const useThemeStore = defineStore("theme", () => {
|
||||
export const useThemeStore = defineStore('theme', () => {
|
||||
const isDarkMode = ref(false)
|
||||
|
||||
const toggleTheme = () => {
|
||||
@@ -10,24 +10,40 @@ export const useThemeStore = defineStore("theme", () => {
|
||||
updateTheme()
|
||||
}
|
||||
|
||||
// 修改:仅针对.chat-box-body元素应用主题
|
||||
const updateTheme = () => {
|
||||
if (isDarkMode.value) {
|
||||
document.documentElement.setAttribute("data-theme", "dark")
|
||||
document.documentElement.classList.add("dark")
|
||||
} else {
|
||||
document.documentElement.removeAttribute("data-theme")
|
||||
document.documentElement.classList.remove("dark")
|
||||
}
|
||||
nextTick(() => {
|
||||
const chatBoxBody = document.querySelector('.chat-box-body')
|
||||
if (!chatBoxBody) return
|
||||
|
||||
if (isDarkMode.value) {
|
||||
chatBoxBody.setAttribute("data-theme", "dark")
|
||||
chatBoxBody.classList.add("dark")
|
||||
} else {
|
||||
chatBoxBody.removeAttribute("data-theme")
|
||||
chatBoxBody.classList.remove("dark")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 修改:确保DOM就绪后再加载主题
|
||||
const loadTheme = () => {
|
||||
const savedTheme = localStorage.getItem("chatTheme")
|
||||
if (savedTheme) {
|
||||
isDarkMode.value = savedTheme === "dark"
|
||||
} else {
|
||||
isDarkMode.value = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
isDarkMode.value = window.matchMedia?.("(prefers-color-scheme: dark)").matches
|
||||
}
|
||||
updateTheme()
|
||||
|
||||
// 等待DOM渲染完成后再更新主题
|
||||
const applyTheme = () => {
|
||||
if (document.querySelector('.chat-box-body')) {
|
||||
updateTheme()
|
||||
} else {
|
||||
requestAnimationFrame(applyTheme)
|
||||
}
|
||||
}
|
||||
applyTheme()
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -126,7 +126,8 @@ export const sendMessage = (data) => {
|
||||
|
||||
const requestData = {
|
||||
room_id: data.roomId,
|
||||
sender_user_id: 'doctor-' + data.senderId.toString(),
|
||||
// sender_user_id: 'doctor-' + data.senderId.toString(),
|
||||
sender_user_id: data.senderId.toString(),
|
||||
receiver_user_id: data.receiverId.toString(),
|
||||
message_type: messageTypeMap[data.type] || 0,
|
||||
message_content: data.content || '',
|
||||
|
||||
@@ -23,19 +23,24 @@ import {
|
||||
Select,
|
||||
SelectOption,
|
||||
} from 'ant-design-vue';
|
||||
import { debounce } from 'lodash-es'; // 或者使用自定义防抖函数
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import {
|
||||
getDrugUseList,
|
||||
getProductListDoctorReception,
|
||||
} from '#/views/doctor/doctor-reception/api';
|
||||
import { usePrescriptionStore } from '#/store/prescription'
|
||||
|
||||
// 使用 Pinia store
|
||||
const prescriptionStore = usePrescriptionStore();
|
||||
const { currentDrugs, initializeForModal, updateCurrentDrugs } = prescriptionStore;
|
||||
|
||||
// 搜索关键词
|
||||
const searchKey = ref('');
|
||||
// 药品类型:1-中药,2-西药
|
||||
const type = ref(1);
|
||||
// 当前药品回调函数
|
||||
const currentDrugs = ref();
|
||||
const currentDrugsWestern = ref();
|
||||
// 当前患者ID
|
||||
const activePatientId = ref(0);
|
||||
// 药品列表
|
||||
@@ -51,6 +56,7 @@ const drugTime = ref([]);
|
||||
const drugUseWay = ref([]);
|
||||
// 当前选中的药品ID
|
||||
const selectProductId = ref(0);
|
||||
const ChatTypeCheck = ref('');
|
||||
// 预览图片URL
|
||||
const previewImage = ref('');
|
||||
|
||||
@@ -285,6 +291,10 @@ function propProducts(data) {
|
||||
function updateSelectStorage() {
|
||||
try {
|
||||
localStorage.setItem(storageKey.value, JSON.stringify(selectList.value));
|
||||
if (ChatTypeCheck.value === 'chat') {
|
||||
// 使用 Pinia store 的方法更新 currentDrugs
|
||||
updateCurrentDrugs(selectList.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存处方数据失败:', error);
|
||||
message.error('保存处方数据失败');
|
||||
@@ -311,11 +321,9 @@ const [Modal, modalApi] = useVbenModal({
|
||||
modalApi.close();
|
||||
},
|
||||
onConfirm: async () => {
|
||||
// 保存数据
|
||||
updateSelectStorage();
|
||||
// 调用回调函数
|
||||
if (typeof currentDrugs.value === 'function') {
|
||||
currentDrugs.value();
|
||||
if (typeof currentDrugsWestern.value === 'function') {
|
||||
currentDrugsWestern.value();
|
||||
}
|
||||
// 关闭modal
|
||||
modalApi.close();
|
||||
@@ -326,16 +334,22 @@ const [Modal, modalApi] = useVbenModal({
|
||||
if (isOpen) {
|
||||
// 获取回调函数
|
||||
const data = modalApi.getData();
|
||||
currentDrugs.value = data?.getCurrentDrugs;
|
||||
currentDrugsWestern.value = data?.getCurrentDrugs;
|
||||
|
||||
// 获取参数
|
||||
const { values, activePatient_id } = data || {};
|
||||
const { values, activePatient_id, isChat } = data || {};
|
||||
|
||||
if (values) {
|
||||
// 设置药品类型
|
||||
type.value = values;
|
||||
// 设置患者ID
|
||||
activePatientId.value = activePatient_id;
|
||||
if (isChat === 'chat') {
|
||||
ChatTypeCheck.value = isChat;
|
||||
activePatientId.value = `-chat-${activePatient_id.value}`;
|
||||
// 使用轻量级初始化,不获取患者信息
|
||||
initializeForModal(activePatient_id.value);
|
||||
}
|
||||
// 获取药品列表
|
||||
getDrugListByWesternModal();
|
||||
// 获取药品使用方式列表
|
||||
@@ -362,12 +376,12 @@ function selectDrugUseWayChange(id) {
|
||||
drugList.value = drugList.value.map((item) =>
|
||||
item.id === selectProductId.value
|
||||
? {
|
||||
...item,
|
||||
drug: {
|
||||
...item.drug,
|
||||
way_id: id,
|
||||
},
|
||||
}
|
||||
...item,
|
||||
drug: {
|
||||
...item.drug,
|
||||
way_id: id,
|
||||
},
|
||||
}
|
||||
: item,
|
||||
);
|
||||
|
||||
@@ -375,10 +389,10 @@ function selectDrugUseWayChange(id) {
|
||||
selectList.value = selectList.value.map((item) =>
|
||||
item.index_id === selectProductId.value
|
||||
? {
|
||||
...item,
|
||||
way_id: id,
|
||||
use_ways: drugUseWay.value.find((value) => value.id === id),
|
||||
}
|
||||
...item,
|
||||
way_id: id,
|
||||
use_ways: drugUseWay.value.find((value) => value.id === id),
|
||||
}
|
||||
: item,
|
||||
);
|
||||
|
||||
@@ -395,12 +409,12 @@ function selectFrequencyChange(id) {
|
||||
selectList.value = selectList.value.map((item) =>
|
||||
item.index_id === selectProductId.value
|
||||
? {
|
||||
...item,
|
||||
frequency_id: id,
|
||||
use_frequency: drugUseFrequency.value.find(
|
||||
(value) => value.id === id,
|
||||
),
|
||||
}
|
||||
...item,
|
||||
frequency_id: id,
|
||||
use_frequency: drugUseFrequency.value.find(
|
||||
(value) => value.id === id,
|
||||
),
|
||||
}
|
||||
: item,
|
||||
);
|
||||
|
||||
@@ -417,10 +431,10 @@ function selectTimeChange(id) {
|
||||
selectList.value = selectList.value.map((item) =>
|
||||
item.index_id === selectProductId.value
|
||||
? {
|
||||
...item,
|
||||
time_id: id,
|
||||
use_num: drugTime.value.find((value) => value.id === id),
|
||||
}
|
||||
...item,
|
||||
time_id: id,
|
||||
use_num: drugTime.value.find((value) => value.id === id),
|
||||
}
|
||||
: item,
|
||||
);
|
||||
|
||||
@@ -437,10 +451,10 @@ function selectTypeChange(id) {
|
||||
selectList.value = selectList.value.map((item) =>
|
||||
item.index_id === selectProductId.value
|
||||
? {
|
||||
...item,
|
||||
type_id: id,
|
||||
use_type: drugUseType.value.find((value) => value.id === id),
|
||||
}
|
||||
...item,
|
||||
type_id: id,
|
||||
use_type: drugUseType.value.find((value) => value.id === id),
|
||||
}
|
||||
: item,
|
||||
);
|
||||
|
||||
@@ -457,10 +471,10 @@ function selectUnitChange(id) {
|
||||
selectList.value = selectList.value.map((item) =>
|
||||
item.index_id === selectProductId.value
|
||||
? {
|
||||
...item,
|
||||
unit_id: id,
|
||||
unit: drugUnit.value.find((value) => value.id === id),
|
||||
}
|
||||
...item,
|
||||
unit_id: id,
|
||||
unit: drugUnit.value.find((value) => value.id === id),
|
||||
}
|
||||
: item,
|
||||
);
|
||||
|
||||
@@ -484,9 +498,9 @@ function updateProductNumber(id, number) {
|
||||
selectList.value = selectList.value.map((item) =>
|
||||
item.index_id === id
|
||||
? {
|
||||
...item,
|
||||
number,
|
||||
}
|
||||
...item,
|
||||
number,
|
||||
}
|
||||
: item,
|
||||
);
|
||||
|
||||
@@ -546,8 +560,7 @@ function updateProductNumber(id, number) {
|
||||
</template>
|
||||
</CardMeta>
|
||||
<div class="card-box mt-5" style="padding: 10px 5px">
|
||||
|
||||
<p>单价:{{item.price}}/g</p>
|
||||
<p>单价:{{ item.price }}/g</p>
|
||||
<!-- 选择中药煎法 -->
|
||||
<Select
|
||||
:value="item.drug?.way_id"
|
||||
@@ -641,7 +654,6 @@ function updateProductNumber(id, number) {
|
||||
<p class="drug-function text-xs">
|
||||
功效:{{ item.drug?.function }}
|
||||
</p>
|
||||
|
||||
<template #content>
|
||||
<p>功效:{{ item.drug?.function }}</p>
|
||||
<p>用法:{{ item.drug?.usage }}</p>
|
||||
|
||||
Reference in New Issue
Block a user