转接方
Some checks failed
Close stale issues / stale (push) Has been cancelled
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
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
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled

This commit is contained in:
李琦
2026-02-04 08:53:52 +08:00
parent 4c03953127
commit 37d42263a8
22 changed files with 2120 additions and 97 deletions

View File

@@ -794,7 +794,7 @@ export const usePrescriptionStore = defineStore('prescription', () => {
return false;
}
try {
await addWestPrescription({
const response = await addWestPrescription({
patient: activePatient.value,
drugs: currentDrugs.value,
diagnosis: diagnosis.value,
@@ -821,36 +821,44 @@ export const usePrescriptionStore = defineStore('prescription', () => {
// 诊所选择参数(使用传入的参数)
send_mode: customSendMode,
custom_store_id: customSendMode === 1 ? customStoreId : null,
}).then((res) => {
message.success('处方已发送');
sendMessage({
roomId: chatStore.currentFriend.room_id,
senderId: userStore.currentUser.doctor_id,
receiverId: chatStore.currentFriend.id,
type: 'prescription',
content: JSON.stringify(res),
});
chatStore.addMessage(
{
id: Date.now().toString(),
room_id: chatStore.currentFriend.room_id,
sender_user_id: `doctor-${userStore.currentUser.doctor_id}`,
receiver_user_id: chatStore.currentFriend.id,
message_type: 4,
message_content: JSON.stringify(res),
messageTypeName: 'prescription',
created_at: Date.now(),
created_at_text: new Date().toLocaleTimeString().slice(0, 5),
timestamp: Date.now(),
isSent: true,
read: true,
duration: 0,
},
userStore.currentUser.doctor_id,
);
resetForm();
});
return true;
const res = await response;
message.success('处方已发送');
sendMessage({
roomId: chatStore.currentFriend.room_id,
senderId: userStore.currentUser.doctor_id,
receiverId: chatStore.currentFriend.id,
type: 'prescription',
content: JSON.stringify(res),
});
chatStore.addMessage(
{
id: Date.now().toString(),
room_id: chatStore.currentFriend.room_id,
sender_user_id: `doctor-${userStore.currentUser.doctor_id}`,
receiver_user_id: chatStore.currentFriend.id,
message_type: 4,
message_content: JSON.stringify(res),
messageTypeName: 'prescription',
created_at: Date.now(),
created_at_text: new Date().toLocaleTimeString().slice(0, 5),
timestamp: Date.now(),
isSent: true,
read: true,
duration: 0,
},
userStore.currentUser.doctor_id,
);
resetForm();
// 返回包含转诊信息的响应数据
return {
success: true,
result: res?.result || res?.data || res,
need_transfer: res?.result?.need_transfer || res?.data?.need_transfer || res?.need_transfer,
transfer_prescription_id: res?.result?.transfer_prescription_id || res?.data?.transfer_prescription_id || res?.transfer_prescription_id,
};
} catch (error) {
console.error('发送处方失败:', error);
message.error('发送处方失败');

View File

@@ -0,0 +1,36 @@
import { requestClient } from '#/api/request';
const prefix = 'transfer-prescription/';
/**
* 获取转诊详情
* @param id 转诊ID
*/
export async function getTransferPrescriptionDetailApi(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 根据挂号ID获取转诊信息
* @param registerId 挂号ID
*/
export async function getTransferPrescriptionByRegisterApi(registerId: number) {
return requestClient.get<any>(`${prefix}get-by-register`, { params: { register_id: registerId } });
}
/**
* 确认转诊
* @param id 转诊ID
*/
export async function confirmTransferPrescriptionApi(id: number) {
return requestClient.post<any>(`${prefix}confirm`, { id });
}
/**
* 一键导入处方到在线问诊
* @param id 转诊ID
* @param registerId 挂号ID
*/
export async function importTransferPrescriptionApi(id: number, registerId: number) {
return requestClient.post<any>(`${prefix}import`, { id, register_id: registerId });
}

View File

@@ -14,6 +14,7 @@ const emit = defineEmits([
// ... existing emits
'openPrescription',
'end-consultation-success',
'transfer-import-success',
]);
// 开方功能触发函数
@@ -35,12 +36,17 @@ const handleEndConsultationSuccess = () => {
<ChatHeader />
<!-- 消息区域 -->
<MessageList class="flex-1" />
<MessageList
class="flex-1"
@open-prescription="handleOpenPrescription"
@transfer-import-success="(data) => emit('transfer-import-success', data)"
/>
<!-- 输入区域 -->
<MessageInput
@open-prescription="handleOpenPrescription"
@end-consultation-success="handleEndConsultationSuccess"
@transfer-import-success="(data) => emit('transfer-import-success', data)"
/>
</div>
</template>

View File

@@ -12,8 +12,8 @@ import { sendMessage } from '#/views/business/chat/utils/request';
// 辅助函数:按需解析消息内容
const getParsedContent = (messageType, messageContent) => {
// 对于需要 JSON 解析的消息类型4, 9, 10, 11, 12, 13
if ([4, 9, 10, 11, 12, 13].includes(messageType)) {
// 对于需要 JSON 解析的消息类型4, 9, 10, 11, 12, 13, 14
if ([4, 9, 10, 11, 12, 13, 14].includes(messageType)) {
try {
return JSON.parse(messageContent);
} catch (e) {
@@ -41,6 +41,7 @@ const getMessageTypeName = (messageType) => {
11: 'patient-experience',
12: 'product-card',
13: 'end-consultation',
14: 'transfer-prescription',
};
return types[messageType] || 'unknown';
};
@@ -56,6 +57,8 @@ import AudioMessage from './AudioMessage.vue';
import PatientExperienceCard from '#/views/doctor/online-consultation/components/PatientExperienceCard.vue';
import ProductCard from '#/views/doctor/online-consultation/components/ProductCard.vue';
import EndConsultationCard from '#/views/doctor/online-consultation/components/EndConsultationCard.vue';
import TransferPrescriptionMessageCard from '#/views/doctor/online-consultation/components/TransferPrescriptionMessageCard.vue';
import TransferPrescriptionViewDrawer from '#/views/doctor/online-consultation/components/TransferPrescriptionViewDrawer.vue';
const props = defineProps({
message: {
@@ -71,12 +74,16 @@ const props = defineProps({
required: true,
},
});
const emit = defineEmits(['show-user-profile']);
const emit = defineEmits(['show-user-profile', 'open-prescription', 'transfer-import-success']);
const chatUserStore = chatUseUserStore();
const userStore = useUserStore();
const chatStore = useChatStore();
const themeStore = useThemeStore();
// 转接方查看抽屉相关
const showTransferDrawer = ref(false);
const transferRegisterId = ref(null);
const [RefusalOfTreatmentModals, RefusalOfTreatmentModalApi] = useVbenModal({
connectedComponent: RefusalOfTreatmentModal,
});
@@ -199,6 +206,21 @@ const acceptRegister = async (registerId, orderNo) => {
}
};
// 查看转接方详情
const handleViewTransferDetail = (transferData) => {
transferRegisterId.value = chatStore.currentFriend?.register_id;
showTransferDrawer.value = true;
};
// 处理转接方导入成功
const handleTransferImportSuccess = (prescriptionData) => {
// 触发打开处方模态框事件
const registerId = transferRegisterId.value || chatStore.currentFriend?.register_id;
if (registerId) {
emit('open-prescription', registerId);
}
};
// 拒诊操作
const rejectRegister = async (registerId, orderNo) => {
if (registerOperating.value) return;
@@ -821,6 +843,15 @@ if (props.message.message_type === 10) {
:content="parsedContent"
/>
<!-- 转接方消息卡片 (type=14) -->
<TransferPrescriptionMessageCard
v-else-if="message.message_type === 14"
:content="parsedContent"
:register-id="chatStore.currentFriend?.register_id"
@view-detail="handleViewTransferDetail"
@import-success="handleTransferImportSuccess"
/>
<!-- 通话消息 -->
<div
v-else-if="
@@ -861,6 +892,13 @@ if (props.message.message_type === 10) {
></i>
</div>
</div>
<!-- 转接方查看抽屉 -->
<TransferPrescriptionViewDrawer
v-model:visible="showTransferDrawer"
:register-id="transferRegisterId"
@import-success="handleTransferImportSuccess"
/>
</div>
</template>

View File

@@ -12,6 +12,8 @@ import { useThemeStore } from '../stores/theme.ts';
import { useUserStore } from '../stores/user.ts';
import { sendMessage } from '../utils/request.ts';
import { usePrescriptionStore } from '#/store/prescription';
import { getTransferPrescriptionByRegisterApi } from '#/views/business/chat/api/transferPrescription';
import TransferPrescriptionViewDrawer from '#/views/doctor/online-consultation/components/TransferPrescriptionViewDrawer.vue';
// 根据路由判断是在线复诊-药店还是在线复诊-诊所
const route = useRoute();
@@ -58,6 +60,8 @@ const prescriptionStore = usePrescriptionStore();
const messageText = ref('');
const messageInputRef = ref(null);
const showEmojiPicker = ref(false);
const showTransferDrawer = ref(false);
const transferRegisterId = ref(null);
// 使用文件上传组合式函数
const {
@@ -325,7 +329,8 @@ onUnmounted(() => {
const emit = defineEmits([
'sendMessage',
'openPrescription', // 添加开方事件
'end-consultation-success' // 结束接诊成功事件
'end-consultation-success', // 结束接诊成功事件
'transfer-import-success' // 转接方导入成功事件
]);
// 开方功能触发函数
@@ -453,6 +458,37 @@ const handleEndConsultation = () => {
});
};
// 打开转接方查看抽屉
const handleOpenTransferView = async () => {
const registerId = chatStore.currentFriend?.register_id;
if (!registerId) {
message.warning('患者信息不完整,无法查看转接方');
return;
}
// 检查是否有转接方
try {
const res = await getTransferPrescriptionByRegisterApi(registerId);
const data = res?.result || res?.data || res;
if (data && data.id) {
transferRegisterId.value = registerId;
showTransferDrawer.value = true;
} else {
message.warning('该患者没有转接方信息');
}
} catch (error) {
console.error('检查转接方信息失败:', error);
message.error('检查转接方信息失败,请重试');
}
};
// 处理转接方导入成功
const handleTransferImportSuccess = (prescriptionData) => {
// 触发打开处方模态框的事件
emit('transfer-import-success', prescriptionData);
emit('openPrescription', transferRegisterId.value || chatStore.currentFriend?.register_id);
};
</script>
<template>
@@ -522,6 +558,15 @@ const handleEndConsultation = () => {
>
<PlusOutlined />
</button>
<!-- 转接方查看按钮仅在线复诊场景显示 -->
<button
v-if="showOnlineConsultationButtons"
class="function-btn transfer-view-btn"
title="查看转接方"
@click="handleOpenTransferView"
>
<i class="fas fa-exchange-alt"></i>
</button>
<!-- 结束问诊按钮仅在线复诊场景显示 -->
<button
v-if="showOnlineConsultationButtons"
@@ -575,6 +620,13 @@ const handleEndConsultation = () => {
type="file"
@change="handleFileUpload"
/>
<!-- 转接方查看抽屉 -->
<TransferPrescriptionViewDrawer
v-model:visible="showTransferDrawer"
:register-id="transferRegisterId"
@import-success="handleTransferImportSuccess"
/>
</div>
</template>
@@ -654,6 +706,21 @@ const handleEndConsultation = () => {
color: #ff4d4f;
}
.transfer-view-btn {
color: #f97316;
font-weight: 500;
}
.transfer-view-btn:hover {
background-color: #fff7ed;
color: #f97316;
}
.dark .transfer-view-btn:hover {
background-color: rgba(249, 115, 22, 0.2);
color: #f97316;
}
.function-buttons {
display: flex;
gap: 12px;

View File

@@ -11,6 +11,8 @@ import MessageBubble from './MessageBubble.vue';
const userStore = useUserStore();
const chatStore = useChatStore();
const emit = defineEmits(['open-prescription', 'transfer-import-success']);
const messagesContainer = ref(null);
const loadingMessages = ref(false);
const loadingOlderMessages = ref(false); // 加载历史消息状态
@@ -163,6 +165,8 @@ onMounted(() => {
:is-sent="msg.isSent"
:message="msg"
:sender-info="getSenderInfo(msg)"
@open-prescription="(registerId) => emit('open-prescription', registerId)"
@transfer-import-success="(data) => emit('transfer-import-success', data)"
/>
</div>
</div>

View File

@@ -227,12 +227,38 @@ const executeSendPrescription = async (
sendMode: number,
customStoreId: number | null,
) => {
const success = await prescriptionStore.sendPrescription(
const result = await prescriptionStore.sendPrescription(
doctorSecondSignValue,
sendMode,
customStoreId,
);
// 检查返回结果可能是boolean或包含转诊信息的对象
const success = typeof result === 'boolean' ? result : result?.success !== false;
const prescriptionResult = typeof result === 'object' ? result : null;
if (success) {
// 检查是否需要转诊(西医诊所开中药处方)
if (prescriptionResult?.need_transfer || prescriptionResult?.result?.need_transfer) {
const transferId = prescriptionResult?.transfer_prescription_id || prescriptionResult?.result?.transfer_prescription_id;
if (transferId) {
// 显示转诊确认提示
AntModal.info({
title: '转诊提示',
content: '该处方已触发转诊流程,转诊消息已发送给委托诊所的在线问诊医生。',
okText: '确定',
onOk: () => {
if (modalData.value?.onPrescriptionSent) {
modalData.value.onPrescriptionSent();
}
submittingType.value = false;
modalApi.close();
},
});
return;
}
}
if (modalData.value?.onPrescriptionSent) {
modalData.value.onPrescriptionSent();
}
@@ -1356,65 +1382,90 @@ const cancelSaveCommonPrescription = () => {
</div>
<div class="mt-5 flex gap-4 items-center flex-wrap">
<div class="flex items-center">
<span class="mr-2">用量:</span>
<InputNumber v-model:value="prescriptionStore.dosage" style="width: 100px">
<template #addonBefore>
<MinusOutlined
class="cursor-pointer"
@click="
prescriptionStore.dosage =
prescriptionStore.dosage > 1
? prescriptionStore.dosage - 1
: 1
"
/>
</template>
<template #addonAfter>
<PlusOutlined
class="cursor-pointer"
@click="
prescriptionStore.dosage =
prescriptionStore.dosage < 100
? prescriptionStore.dosage + 1
: 100
"
/>
</template>
</InputNumber>
<span class="ml-2">天 / {{ prescriptionStore.dosage }} 剂</span>
<div class="flex items-center gap-2">
<span class="mr-2 whitespace-nowrap">用量:</span>
<div class="flex items-center border rounded" style="border-color: #d9d9d9;">
<Button
type="text"
size="small"
class="border-0 border-r rounded-l"
style="border-color: #d9d9d9;"
@click="
prescriptionStore.dosage =
prescriptionStore.dosage > 1
? prescriptionStore.dosage - 1
: 1
"
>
<MinusOutlined />
</Button>
<InputNumber
v-model:value="prescriptionStore.dosage"
:controls="false"
:min="1"
:max="100"
class="border-0"
style="width: 80px; text-align: center;"
/>
<Button
type="text"
size="small"
class="border-0 border-l rounded-r"
style="border-color: #d9d9d9;"
@click="
prescriptionStore.dosage =
prescriptionStore.dosage < 100
? prescriptionStore.dosage + 1
: 100
"
>
<PlusOutlined />
</Button>
</div>
<span class="ml-2 whitespace-nowrap">天 / {{ prescriptionStore.dosage }} 剂</span>
</div>
<div class="flex items-center">
<span class="mr-2">频次:</span>
<InputNumber
v-model:value="prescriptionStore.dayDosage"
style="width: 100px"
>
<template #addonBefore>
<MinusOutlined
class="cursor-pointer"
@click="
prescriptionStore.dayDosage =
prescriptionStore.dayDosage > 1
? prescriptionStore.dayDosage - 1
: 1
"
/>
</template>
<template #addonAfter>
<PlusOutlined
class="cursor-pointer"
@click="
prescriptionStore.dayDosage =
prescriptionStore.dayDosage < 100
? prescriptionStore.dayDosage + 1
: 100
"
/>
</template>
</InputNumber>
<span class="ml-2">次/天</span>
<div class="flex items-center gap-2">
<span class="mr-2 whitespace-nowrap">频次:</span>
<div class="flex items-center border rounded" style="border-color: #d9d9d9;">
<Button
type="text"
size="small"
class="border-0 border-r rounded-l"
style="border-color: #d9d9d9;"
@click="
prescriptionStore.dayDosage =
prescriptionStore.dayDosage > 1
? prescriptionStore.dayDosage - 1
: 1
"
>
<MinusOutlined />
</Button>
<InputNumber
v-model:value="prescriptionStore.dayDosage"
:controls="false"
:min="1"
:max="100"
class="border-0"
style="width: 80px; text-align: center;"
/>
<Button
type="text"
size="small"
class="border-0 border-l rounded-r"
style="border-color: #d9d9d9;"
@click="
prescriptionStore.dayDosage =
prescriptionStore.dayDosage < 100
? prescriptionStore.dayDosage + 1
: 100
"
>
<PlusOutlined />
</Button>
</div>
<span class="ml-2 whitespace-nowrap">次/天</span>
</div>
</div>
</div>

View File

@@ -58,6 +58,14 @@ export async function updateNavSortApi(ids: number[]) {
return requestClient.post<any>(`${prefix}update-nav-sort`, { ids });
}
/**
* 更新诊所类型
* @param data 包含clinic_type的对象clinic_type: 1=西医诊所, 2=中医诊所
*/
export async function updateClinicTypeApi(data: { clinic_type: number }) {
return requestClient.post<any>(`${prefix}update-clinic-type`, data);
}
/*
----------------------------推广员---------------------------------
*/

View File

@@ -12,6 +12,7 @@ import {
ImagePreviewGroup,
InputNumber,
message,
Modal,
notification,
Popconfirm,
RadioButton,
@@ -27,6 +28,7 @@ import {
getStoreInfoApi,
saveZSalePercentApi,
updateNavSortApi,
updateClinicTypeApi,
} from '#/views/business/store/settings/api';
import UploadModal from './components/uploadModal.vue';
@@ -41,6 +43,7 @@ interface TabBarItem {
interface StoreInfo {
name: string;
type: number; // 0=诊所, 1=药店
clinic_type?: number; // 1=西医诊所, 2=中医诊所
star?: any;
title: { name: string };
contact: string;
@@ -295,6 +298,44 @@ const openPreview = (index: number) => {
previewCurrent.value = index;
previewVisible.value = true;
};
/**
* 切换诊所类型
*/
const handleSwitchClinicType = () => {
if (!data.value) return;
const currentType = data.value.clinic_type || 2; // 默认为中医诊所
const newType = currentType === 1 ? 2 : 1;
const currentTypeText = currentType === 1 ? '西医诊所' : '中医诊所';
const newTypeText = newType === 1 ? '西医诊所' : '中医诊所';
Modal.confirm({
title: '确认切换诊所类型',
content: `确定要将诊所类型从【${currentTypeText}】切换为【${newTypeText}】吗?`,
okText: '确定',
cancelText: '取消',
onOk: async () => {
try {
await updateClinicTypeApi({ clinic_type: newType });
notification.success({
message: '切换成功',
description: `诊所类型已切换为【${newTypeText}`,
duration: 2,
});
// 刷新诊所信息
getMyInfo();
} catch (error) {
console.error('切换诊所类型失败:', error);
notification.error({
message: '切换失败',
description: '请稍后重试',
duration: 2,
});
}
}
});
};
</script>
<template>
@@ -410,6 +451,25 @@ const openPreview = (index: number) => {
></div>
</div>
</div>
<!-- 诊所类型 -->
<div v-if="!isPharmacy" class="mt-6 rounded-lg bg-gray-50 p-4 dark:bg-[#020817]">
<div class="flex items-center justify-between">
<span class="text-sm font-medium">诊所类型</span>
<div class="flex items-center gap-2">
<Tag
:color="data.clinic_type === 1 ? 'blue' : 'green'"
class="clinic-type-tag cursor-pointer"
@click="handleSwitchClinicType"
>
{{ data.clinic_type === 1 ? '西医诊所' : data.clinic_type === 2 ? '中医诊所' : '未设置' }}
</Tag>
<Button type="link" size="small" @click="handleSwitchClinicType">
切换
</Button>
</div>
</div>
</div>
</div>
</div>
</Card>
@@ -733,4 +793,19 @@ const openPreview = (index: number) => {
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
}
/* 诊所类型标签样式 */
.clinic-type-tag {
transition: all 0.2s ease;
user-select: none;
&:hover {
opacity: 0.8;
transform: scale(1.05);
}
&:active {
transform: scale(0.95);
}
}
</style>

View File

@@ -66,6 +66,9 @@ import {
saveChineseCommonPrescriptionApi,
saveWestCommonPrescriptionApi,
} from '#/views/doctor/settings/api';
// 转诊相关
import TransferPrescriptionCard from '#/views/doctor/online-consultation/components/TransferPrescriptionCard.vue';
import { getTransferPrescriptionByRegisterApi } from '#/views/business/chat/api/transferPrescription';
interface Patient {
id: number;
@@ -191,6 +194,12 @@ const commonPrescriptionName = ref('');
/** 是否正在保存常用方 */
const isSavingCommonPrescription = ref(false);
// ==================== 转诊相关状态 ====================
/** 转诊处方信息 */
const transferPrescription = ref(null);
/** 是否正在加载转诊信息 */
const loadingTransfer = ref(false);
/**
* 获取药品使用方式列表
*/
@@ -564,8 +573,22 @@ const sendPrescription = () => {
day_dosage: dayDosage.value,
// 是否二次签名
doctor_second_sign: doctorSecondSign.value,
}).then(() => {
}).then((res) => {
message.success('处方已发送');
// 检查是否需要转诊
const needTransfer = res?.result?.need_transfer || res?.data?.need_transfer || res?.need_transfer;
const transferPrescriptionId = res?.result?.transfer_prescription_id || res?.data?.transfer_prescription_id || res?.transfer_prescription_id;
if (needTransfer && transferPrescriptionId) {
// 如果需要转诊,获取转诊信息
fetchTransferPrescription(transferPrescriptionId);
message.info('该处方需要转诊,请等待中医诊所确认');
} else {
// 不需要转诊,清空转诊信息
transferPrescription.value = null;
}
// 清空当前数据
currentDrugs.value = [];
diagnosis.value = '';
@@ -577,6 +600,18 @@ const sendPrescription = () => {
tabType.value = 1;
newDrugInfo.value = {};
updateLocalStorage();
// 刷新患者信息
if (selectPatientId.value) {
getPatientItem(selectPatientId.value).then((value) => {
patientInfo.value = value;
userPatientHealthInquiry.value = value.user_patient_health_inquiry;
prescriptionList.value = value.prescription;
});
}
}).catch((error) => {
console.error('发送处方失败:', error);
message.error('发送处方失败,请重试');
});
};
@@ -1436,6 +1471,49 @@ function handleSimpleProductSelect(drug: any) {
const newDrugInfo = ref({});
/**
* 获取转诊信息
* @param transferPrescriptionId 转诊处方ID
*/
const fetchTransferPrescription = async (transferPrescriptionId: number) => {
if (!transferPrescriptionId) return;
loadingTransfer.value = true;
try {
// 通过转诊处方ID获取转诊信息
// 注意这里需要根据实际API调整可能需要通过register_id获取
const registerId = Number.parseInt(localStorage.getItem(`doctorReception-id`));
if (registerId) {
const res = await getTransferPrescriptionByRegisterApi(registerId);
const data = res?.result || res?.data || res;
if (data && data.id) {
transferPrescription.value = data;
} else {
transferPrescription.value = null;
}
}
} catch (error) {
console.error('获取转诊信息失败:', error);
transferPrescription.value = null;
} finally {
loadingTransfer.value = false;
}
};
/**
* 处理转诊导入成功
*/
const handleTransferImportSuccess = () => {
// 转诊导入成功后,可以刷新患者信息
if (selectPatientId.value) {
getPatientItem(selectPatientId.value).then((value) => {
patientInfo.value = value;
userPatientHealthInquiry.value = value.user_patient_health_inquiry;
prescriptionList.value = value.prescription;
});
}
};
function switchStore(id) {
switchStoreApi({store_id: id}).then(() => {
myStoreId.value = id;
@@ -1710,6 +1788,15 @@ watch(
</Timeline>
</Descriptions.Item>
</Descriptions>
<!-- 转诊处方卡片 -->
<div v-if="transferPrescription" class="transfer-section mt-5">
<TransferPrescriptionCard
:transfer-data="transferPrescription"
:register-id="Number.parseInt(localStorage.getItem(`doctorReception-id`))"
@import-success="handleTransferImportSuccess"
/>
</div>
</div>
</Page>

View File

@@ -5,8 +5,12 @@ import { getPatientDetail, reception } from '../api/index.ts';
import { message } from 'ant-design-vue';
import { sendMessage } from '#/views/business/chat/utils/request';
import { useUserStore } from '#/views/business/chat/stores/user';
import { getTransferPrescriptionByRegisterApi } from '#/views/business/chat/api/transferPrescription';
import TransferPrescriptionCard from '#/views/doctor/online-consultation/components/TransferPrescriptionCard.vue';
import { usePrescriptionStore } from '#/store/prescription';
const userStore = useUserStore();
const prescriptionStore = usePrescriptionStore();
const props = defineProps({
patient: {
@@ -19,11 +23,13 @@ const props = defineProps({
}
});
const emit = defineEmits(['reception-success', 'enter-chat', 'refresh-list']);
const emit = defineEmits(['reception-success', 'enter-chat', 'refresh-list', 'transfer-import-success']);
const patientDetail = ref(null);
const loading = ref(false);
const receptionLoading = ref(false);
const transferPrescription = ref(null);
const loadingTransfer = ref(false);
// 获取患者详情
const fetchPatientDetail = async () => {
@@ -49,6 +55,13 @@ const fetchPatientDetail = async () => {
// 确保正确解析返回数据
const data = res?.result || res?.data || res || null;
patientDetail.value = data;
// 检查是否有转诊挂号
if (data?.register_order?.is_from_transfer === 1 && registerId) {
await fetchTransferPrescription(registerId);
} else {
transferPrescription.value = null;
}
} catch (error) {
console.error('获取患者详情失败:', error);
message.error('获取患者详情失败,请重试');
@@ -92,6 +105,33 @@ const handleReception = async () => {
}
};
// 获取转诊信息
const fetchTransferPrescription = async (registerId) => {
if (!registerId) return;
loadingTransfer.value = true;
try {
const res = await getTransferPrescriptionByRegisterApi(registerId);
const data = res?.result || res?.data || res;
if (data && data.id) {
transferPrescription.value = data;
} else {
transferPrescription.value = null;
}
} catch (error) {
console.error('获取转诊信息失败:', error);
transferPrescription.value = null;
} finally {
loadingTransfer.value = false;
}
};
// 处理转诊导入成功
const handleTransferImportSuccess = (prescriptionData) => {
// 触发打开处方模态框的事件
emit('transfer-import-success', prescriptionData);
};
// 获取性别文本
const getSexText = (sex) => {
if (sex === 1) return '男';
@@ -173,6 +213,15 @@ watch(
</Descriptions.Item>
</Descriptions>
<!-- 转诊处方卡片 -->
<div v-if="transferPrescription" class="transfer-section">
<TransferPrescriptionCard
:transfer-data="transferPrescription"
:register-id="patient?.register_id || patientDetail?.register_order?.id"
@import-success="handleTransferImportSuccess"
/>
</div>
<!-- 就诊记录 -->
<div v-if="patientDetail.register_infos && patientDetail.register_infos.length > 0" class="register-info-section">
<h3 class="section-title text-gray-900 dark:text-gray-100 border-b-2 border-blue-600 dark:border-blue-400">就诊记录</h3>
@@ -266,6 +315,10 @@ watch(
padding-bottom: 8px;
}
.transfer-section {
margin-top: 24px;
}
.register-info-section {
margin-top: 24px;
}

View File

@@ -106,6 +106,12 @@ const handlePrescriptionSent = () => {
// 可以在这里添加其他逻辑,如刷新聊天记录等
};
// 处理转诊导入成功
const handleTransferImportSuccess = (prescriptionData) => {
// 打开处方模态框
openPrescriptionModule(registerId.value);
};
// 处理选择患者
const handleSelectPatient = (patient) => {
selectedPatient.value = patient;
@@ -513,6 +519,7 @@ const handleAddPatientProductsToPrescription = async () => {
@reception-success="handleReceptionSuccess"
@enter-chat="handleEnterChat"
@refresh-list="handleRefreshList"
@transfer-import-success="handleTransferImportSuccess"
class="patient-info-panel"
/>
</template>

View File

@@ -5,8 +5,12 @@ import { getPatientDetail, reception } from '../api/index.ts';
import { message } from 'ant-design-vue';
import { sendMessage } from '#/views/business/chat/utils/request';
import { useUserStore } from '#/views/business/chat/stores/user';
import { getTransferPrescriptionByRegisterApi } from '#/views/business/chat/api/transferPrescription';
import TransferPrescriptionCard from './TransferPrescriptionCard.vue';
import { usePrescriptionStore } from '#/store/prescription';
const userStore = useUserStore();
const prescriptionStore = usePrescriptionStore();
const props = defineProps({
patient: {
@@ -19,11 +23,13 @@ const props = defineProps({
}
});
const emit = defineEmits(['reception-success', 'enter-chat']);
const emit = defineEmits(['reception-success', 'enter-chat', 'transfer-import-success']);
const patientDetail = ref(null);
const loading = ref(false);
const receptionLoading = ref(false);
const transferPrescription = ref(null);
const loadingTransfer = ref(false);
// 获取患者详情
const fetchPatientDetail = async () => {
@@ -49,6 +55,13 @@ const fetchPatientDetail = async () => {
// 确保正确解析返回数据
const data = res?.result || res?.data || res || null;
patientDetail.value = data;
// 检查是否有转诊挂号
if (data?.register_order?.is_from_transfer === 1 && registerId) {
await fetchTransferPrescription(registerId);
} else {
transferPrescription.value = null;
}
} catch (error) {
console.error('获取患者详情失败:', error);
message.error('获取患者详情失败,请重试');
@@ -58,6 +71,33 @@ const fetchPatientDetail = async () => {
}
};
// 获取转诊信息
const fetchTransferPrescription = async (registerId) => {
if (!registerId) return;
loadingTransfer.value = true;
try {
const res = await getTransferPrescriptionByRegisterApi(registerId);
const data = res?.result || res?.data || res;
if (data && data.id) {
transferPrescription.value = data;
} else {
transferPrescription.value = null;
}
} catch (error) {
console.error('获取转诊信息失败:', error);
transferPrescription.value = null;
} finally {
loadingTransfer.value = false;
}
};
// 处理转诊导入成功
const handleTransferImportSuccess = (prescriptionData) => {
// 触发打开处方模态框的事件
emit('transfer-import-success', prescriptionData);
};
// 接诊
const handleReception = async () => {
if (!props.patient) {
@@ -173,6 +213,15 @@ watch(
</Descriptions.Item>
</Descriptions>
<!-- 转诊处方卡片 -->
<div v-if="transferPrescription" class="transfer-section">
<TransferPrescriptionCard
:transfer-data="transferPrescription"
:register-id="patient?.register_id || patientDetail?.register_order?.id"
@import-success="handleTransferImportSuccess"
/>
</div>
<!-- 就诊记录 -->
<div v-if="patientDetail.register_infos && patientDetail.register_infos.length > 0" class="register-info-section">
<h3 class="section-title text-gray-900 dark:text-gray-100 border-b-2 border-blue-600 dark:border-blue-400">就诊记录</h3>
@@ -265,6 +314,10 @@ watch(
padding-bottom: 8px;
}
.transfer-section {
margin-top: 24px;
}
.register-info-section {
margin-top: 24px;
}

View File

@@ -177,6 +177,9 @@ defineExpose({
<span :class="['status', getStatusClass(patient.status)]">
{{ getStatusText(patient.status) }}
</span>
<span v-if="patient.is_from_transfer === 1" class="transfer-tag">
转诊
</span>
<span v-if="patient.register_type" class="register-type-tag">
{{ getRegisterTypeText(patient.register_type) }}
</span>
@@ -294,6 +297,23 @@ defineExpose({
border-color: rgba(24, 144, 255, 0.3);
}
.transfer-tag {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
background: #fff7ed;
color: #ea580c;
border: 1px solid #fed7aa;
}
.dark .transfer-tag {
background: rgba(234, 88, 12, 0.2);
color: #fb923c;
border-color: rgba(234, 88, 12, 0.3);
}
.status {
display: inline-block;
padding: 2px 8px;

View File

@@ -0,0 +1,481 @@
<script setup>
import { computed, ref } from 'vue';
import { Button, message, Tag } from 'ant-design-vue';
import { usePrescriptionStore } from '#/store/prescription';
const props = defineProps({
transferData: {
type: Object,
required: true,
},
registerId: {
type: [Number, String],
required: true,
},
});
const prescriptionStore = usePrescriptionStore();
const importing = ref(false);
// 解析转诊数据
const transferInfo = computed(() => {
if (typeof props.transferData === 'string') {
try {
return JSON.parse(props.transferData);
} catch {
return {};
}
}
return props.transferData;
});
// 解析处方内容从content字段后端已解析为对象
const prescriptionContent = computed(() => {
if (!transferInfo.value) return null;
// 如果已经有解析好的prescription字段直接使用
if (transferInfo.value.prescription && transferInfo.value.prescription.content) {
return {
clinical_diagnose: transferInfo.value.prescription.clinical_diagnose || '',
doctor_order: transferInfo.value.prescription.doctor_order || '',
content: transferInfo.value.prescription.content || [],
};
}
// 从content字段获取后端已解析为对象
const content = transferInfo.value.content;
if (!content) return null;
// 构建药品列表
let drugList = [];
// 如果content是对象检查repice字段
if (typeof content === 'object' && !Array.isArray(content)) {
// 从repice数组中提取药品
if (content.repice && Array.isArray(content.repice)) {
for (const recipe of content.repice) {
if (recipe.content) {
// recipe.content可能是已解析的数组也可能是JSON字符串
let recipeContent = recipe.content;
if (typeof recipeContent === 'string') {
try {
recipeContent = JSON.parse(recipeContent);
} catch {
recipeContent = [];
}
}
// 如果recipeContent是数组合并到drugList
if (Array.isArray(recipeContent)) {
drugList = drugList.concat(recipeContent);
}
}
}
}
return {
clinical_diagnose: content.clinical_diagnose || transferInfo.value.original_prescription?.clinical_diagnose || '',
doctor_order: content.doctor_order || transferInfo.value.original_prescription?.doctor_order || '',
content: drugList,
};
} else if (Array.isArray(content)) {
// 如果content直接是数组说明是药品列表
return {
clinical_diagnose: transferInfo.value.original_prescription?.clinical_diagnose || '',
doctor_order: transferInfo.value.original_prescription?.doctor_order || '',
content: content,
};
}
return null;
});
// 转诊状态文本
const statusText = computed(() => {
const status = transferInfo.value.status;
const statusMap = {
0: { text: '待确认', color: 'orange' },
1: { text: '已确认', color: 'blue' },
2: { text: '已导入', color: 'green' },
3: { text: '已取消', color: 'red' },
};
return statusMap[status] || { text: '未知', color: 'default' };
});
// 将转接方药品数据转换成addProducts需要的格式
const convertDrugDataForImport = (drug) => {
// addProducts期望的数据结构
// {
// id: ...,
// drug: {
// id: ...,
// drug_name: ...,
// number: ...,
// time_id: ...,
// type_id: ...,
// frequency_id: ...,
// unit_id: ...,
// way_id: ...,
// image: ...,
// instruction: ...,
// type: ...,
// },
// price: ...,
// }
// 转接方的药品数据结构:
// {
// id: ...,
// drug_id: ...,
// name: ...,
// number: ...,
// price: ...,
// unit: {...},
// use_unit: {...},
// ...
// }
return {
id: drug.id || drug.drug_id || 0,
drug: {
id: drug.drug_id || drug.id || 0,
drug_name: drug.name || drug.drug_name || '',
number: drug.number || 1,
time_id: drug.time_id || drug.use_num?.id || 0,
type_id: drug.type_id || drug.use_type?.id || 0,
frequency_id: drug.frequency_id || drug.use_frequency?.id || 0,
unit_id: drug.unit_id || drug.unit?.id || drug.use_unit?.id || 0,
way_id: drug.way_id || drug.use_way?.id || 0,
image: drug.image || '',
instruction: drug.instruction || '',
type: drug.type || transferInfo.value?.prescription_type || 1,
},
price: parseFloat(drug.price) || 0,
};
};
// 一键导入处方到在线问诊(从前端数据导入)
const handleImportToPrescription = async () => {
if (!props.transferData?.id) {
message.warning('转诊信息不完整');
return;
}
if (!props.registerId) {
message.warning('挂号ID不能为空');
return;
}
if (!prescriptionContent.value || !prescriptionContent.value.content || !Array.isArray(prescriptionContent.value.content)) {
message.warning('处方内容格式错误,无法导入');
return;
}
importing.value = true;
try {
// 先将处方类型设置为中药1
prescriptionStore.changeCategory(1);
// 直接使用前端已获取的数据进行导入
const drugList = prescriptionContent.value.content;
if (!drugList || drugList.length === 0) {
message.warning('处方中没有药品信息');
return;
}
// 导入药品(需要转换数据格式)
let addedCount = 0;
for (const drug of drugList) {
// 转换数据格式
const convertedDrugData = convertDrugDataForImport(drug);
const success = prescriptionStore.addProducts(convertedDrugData);
if (success) {
addedCount++;
}
}
// 导入诊断和医嘱
if (prescriptionContent.value.clinical_diagnose) {
prescriptionStore.diagnosis = prescriptionContent.value.clinical_diagnose;
}
if (prescriptionContent.value.doctor_order) {
prescriptionStore.medicalAdvice = prescriptionContent.value.doctor_order;
}
if (addedCount > 0) {
message.success(`已成功导入 ${addedCount} 个药品到处方单`);
// 构建处方数据对象,用于触发事件
const prescriptionData = {
content: drugList,
clinical_diagnose: prescriptionContent.value.clinical_diagnose,
doctor_order: prescriptionContent.value.doctor_order,
register_id: props.registerId,
};
// 触发事件,通知父组件打开处方模态框
emit('import-success', prescriptionData);
} else {
message.warning('所有药品已在处方中');
}
} catch (error) {
console.error('导入转诊处方失败:', error);
message.error('导入转诊处方失败,请重试');
} finally {
importing.value = false;
}
};
const emit = defineEmits(['import-success']);
</script>
<template>
<div class="transfer-prescription-card">
<!-- 卡片头部 -->
<div class="card-header">
<div class="header-icon">
<i class="fas fa-exchange-alt"></i>
</div>
<div class="header-title">转诊处方</div>
<Tag :color="statusText.color" class="status-tag">
{{ statusText.text }}
</Tag>
</div>
<!-- 卡片内容 -->
<div class="card-body">
<!-- 转诊诊所信息 -->
<div v-if="transferInfo.transfer_store" class="info-row">
<div class="info-icon">
<i class="fas fa-hospital"></i>
</div>
<div class="info-content">
<div class="info-label">转诊诊所</div>
<div class="info-value">{{ transferInfo.transfer_store.name || '未知诊所' }}</div>
</div>
</div>
<!-- 委托诊所信息 -->
<div v-if="transferInfo.delegate_store" class="info-row">
<div class="info-icon">
<i class="fas fa-clinic-medical"></i>
</div>
<div class="info-content">
<div class="info-label">委托诊所</div>
<div class="info-value">{{ transferInfo.delegate_store.name || '未知诊所' }}</div>
</div>
</div>
<!-- 处方编号 -->
<div v-if="transferInfo.prescription_no" class="info-row">
<div class="info-icon">
<i class="fas fa-file-prescription"></i>
</div>
<div class="info-content">
<div class="info-label">处方编号</div>
<div class="info-value">{{ transferInfo.prescription_no }}</div>
</div>
</div>
<!-- 转诊时间 -->
<div v-if="transferInfo.transfer_time" class="info-row">
<div class="info-icon">
<i class="fas fa-clock"></i>
</div>
<div class="info-content">
<div class="info-label">转诊时间</div>
<div class="info-value">{{ transferInfo.transfer_time }}</div>
</div>
</div>
<!-- 转诊原因 -->
<div v-if="transferInfo.transfer_reason" class="info-row">
<div class="info-icon">
<i class="fas fa-info-circle"></i>
</div>
<div class="info-content">
<div class="info-label">转诊原因</div>
<div class="info-value">{{ transferInfo.transfer_reason }}</div>
</div>
</div>
</div>
<!-- 卡片底部操作 -->
<div v-if="transferInfo.status !== 3" class="card-footer">
<Button
v-if="transferInfo.status !== 2"
type="primary"
:loading="importing"
@click="handleImportToPrescription"
class="import-btn"
>
<i class="fas fa-download mr-1"></i>
添加到处方单
</Button>
<Button v-else type="default" disabled class="import-btn">
已导入
</Button>
</div>
</div>
</template>
<style scoped>
.transfer-prescription-card {
width: 100%;
min-width: 420px;
max-width: 520px;
background: linear-gradient(135deg, #fff7ed 0%, #ffedd5 100%);
border-radius: 16px;
border: 1px solid #fed7aa;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
overflow: hidden;
transition: all 0.3s ease;
}
.transfer-prescription-card:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
/* 卡片头部 */
.card-header {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 20px;
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
color: white;
}
.header-icon {
width: 40px;
height: 40px;
background: rgba(255, 255, 255, 0.2);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
}
.header-title {
flex: 1;
font-size: 17px;
font-weight: 600;
letter-spacing: 0.5px;
}
.status-tag {
font-size: 12px;
font-weight: 500;
padding: 4px 10px;
}
/* 卡片内容 */
.card-body {
padding: 20px;
}
.info-row {
display: flex;
align-items: flex-start;
gap: 14px;
padding: 14px 16px;
background: white;
border-radius: 12px;
margin-bottom: 12px;
border: 1px solid #fed7aa;
transition: all 0.2s ease;
}
.info-row:last-child {
margin-bottom: 0;
}
.info-row:hover {
border-color: #f97316;
background: #fff7ed;
}
.info-icon {
width: 36px;
height: 36px;
background: linear-gradient(135deg, #fff7ed 0%, #ffedd5 100%);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: #f97316;
font-size: 14px;
flex-shrink: 0;
}
.info-content {
flex: 1;
min-width: 0;
}
.info-label {
font-size: 12px;
color: #64748b;
margin-bottom: 4px;
font-weight: 500;
}
.info-value {
font-size: 14px;
color: #1e293b;
line-height: 1.6;
word-break: break-word;
}
/* 卡片底部 */
.card-footer {
padding: 16px 20px;
background: #fff7ed;
border-top: 1px solid #fed7aa;
display: flex;
justify-content: flex-end;
}
.import-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 20px;
font-size: 14px;
font-weight: 500;
border-radius: 8px;
transition: all 0.2s ease;
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
border: none;
}
.import-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(249, 115, 22, 0.3);
}
/* 暗黑模式适配 */
.dark .transfer-prescription-card {
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
border-color: #334155;
}
.dark .info-row {
background: #1e293b;
border-color: #334155;
}
.dark .info-row:hover {
background: #334155;
}
.dark .info-value {
color: #e2e8f0;
}
.dark .card-footer {
background: #0f172a;
border-color: #334155;
}
</style>

View File

@@ -0,0 +1,442 @@
<script setup>
import { computed, ref } from 'vue';
import { Card, Button, Tag, message } from 'ant-design-vue';
import { usePrescriptionStore } from '#/store/prescription';
const props = defineProps({
content: {
type: Object,
required: true,
},
registerId: {
type: [Number, String],
default: null,
},
});
const emit = defineEmits(['view-detail', 'import-success']);
const prescriptionStore = usePrescriptionStore();
const importing = ref(false);
// 解析转接方数据
const transferInfo = computed(() => {
if (typeof props.content === 'string') {
try {
return JSON.parse(props.content);
} catch {
return {};
}
}
return props.content;
});
// 解析处方内容从content字段后端已解析为对象
const prescriptionContent = computed(() => {
if (!transferInfo.value) return null;
// 如果已经有解析好的prescription字段直接使用
if (transferInfo.value.prescription && transferInfo.value.prescription.content) {
return {
clinical_diagnose: transferInfo.value.prescription.clinical_diagnose || '',
doctor_order: transferInfo.value.prescription.doctor_order || '',
content: transferInfo.value.prescription.content || [],
};
}
// 从content字段获取后端已解析为对象
const content = transferInfo.value.content;
if (!content) return null;
// 构建药品列表
let drugList = [];
// 如果content是对象检查repice字段
if (typeof content === 'object' && !Array.isArray(content)) {
// 从repice数组中提取药品
if (content.repice && Array.isArray(content.repice)) {
for (const recipe of content.repice) {
if (recipe.content) {
// recipe.content可能是已解析的数组也可能是JSON字符串
let recipeContent = recipe.content;
if (typeof recipeContent === 'string') {
try {
recipeContent = JSON.parse(recipeContent);
} catch {
recipeContent = [];
}
}
// 如果recipeContent是数组合并到drugList
if (Array.isArray(recipeContent)) {
drugList = drugList.concat(recipeContent);
}
}
}
}
return {
clinical_diagnose: content.clinical_diagnose || transferInfo.value.original_prescription?.clinical_diagnose || '',
doctor_order: content.doctor_order || transferInfo.value.original_prescription?.doctor_order || '',
content: drugList,
};
} else if (Array.isArray(content)) {
// 如果content直接是数组说明是药品列表
return {
clinical_diagnose: transferInfo.value.original_prescription?.clinical_diagnose || '',
doctor_order: transferInfo.value.original_prescription?.doctor_order || '',
content: content,
};
}
return null;
});
// 转诊状态文本
const statusText = computed(() => {
const status = transferInfo.value.status;
const statusMap = {
0: { text: '待确认', color: 'orange' },
1: { text: '已确认', color: 'blue' },
2: { text: '已导入', color: 'green' },
3: { text: '已取消', color: 'red' },
};
return statusMap[status] || { text: '未知', color: 'default' };
});
// 查看详情
const handleViewDetail = () => {
emit('view-detail', transferInfo.value);
};
// 将转接方药品数据转换成addProducts需要的格式
const convertDrugDataForImport = (drug) => {
// addProducts期望的数据结构
// {
// id: ...,
// drug: {
// id: ...,
// drug_name: ...,
// number: ...,
// time_id: ...,
// type_id: ...,
// frequency_id: ...,
// unit_id: ...,
// way_id: ...,
// image: ...,
// instruction: ...,
// type: ...,
// },
// price: ...,
// }
// 转接方的药品数据结构:
// {
// id: ...,
// drug_id: ...,
// name: ...,
// number: ...,
// price: ...,
// unit: {...},
// use_unit: {...},
// ...
// }
return {
id: drug.id || drug.drug_id || 0,
drug: {
id: drug.drug_id || drug.id || 0,
drug_name: drug.name || drug.drug_name || '',
number: drug.number || 1,
time_id: drug.time_id || drug.use_num?.id || 0,
type_id: drug.type_id || drug.use_type?.id || 0,
frequency_id: drug.frequency_id || drug.use_frequency?.id || 0,
unit_id: drug.unit_id || drug.unit?.id || drug.use_unit?.id || 0,
way_id: drug.way_id || drug.use_way?.id || 0,
image: drug.image || '',
instruction: drug.instruction || '',
type: drug.type || transferInfo.value?.prescription_type || 1,
},
price: parseFloat(drug.price) || 0,
};
};
// 一键导入处方到在线问诊(从前端数据导入)
const handleImportToPrescription = async () => {
if (!transferInfo.value?.transfer_id && !transferInfo.value?.id) {
message.warning('转接方信息不完整');
return;
}
const registerId = props.registerId || transferInfo.value.register_id;
if (!registerId) {
message.warning('挂号ID不能为空');
return;
}
if (!prescriptionContent.value || !prescriptionContent.value.content || !Array.isArray(prescriptionContent.value.content)) {
message.warning('处方内容格式错误,无法导入');
return;
}
importing.value = true;
try {
// 先将处方类型设置为中药1
prescriptionStore.changeCategory(1);
// 直接使用前端已获取的数据进行导入
const drugList = prescriptionContent.value.content;
if (!drugList || drugList.length === 0) {
message.warning('处方中没有药品信息');
return;
}
// 导入药品(需要转换数据格式)
let addedCount = 0;
for (const drug of drugList) {
// 转换数据格式
const convertedDrugData = convertDrugDataForImport(drug);
const success = prescriptionStore.addProducts(convertedDrugData);
if (success) {
addedCount++;
}
}
// 导入诊断和医嘱
if (prescriptionContent.value.clinical_diagnose) {
prescriptionStore.diagnosis = prescriptionContent.value.clinical_diagnose;
}
if (prescriptionContent.value.doctor_order) {
prescriptionStore.medicalAdvice = prescriptionContent.value.doctor_order;
}
if (addedCount > 0) {
message.success(`已成功导入 ${addedCount} 个药品到处方单`);
// 构建处方数据对象,用于触发事件
const prescriptionData = {
content: drugList,
clinical_diagnose: prescriptionContent.value.clinical_diagnose,
doctor_order: prescriptionContent.value.doctor_order,
register_id: registerId,
};
// 触发事件,通知父组件打开处方模态框
emit('import-success', prescriptionData);
} else {
message.warning('所有药品已在处方中');
}
} catch (error) {
console.error('导入转接方处方失败:', error);
message.error('导入转接方处方失败,请重试');
} finally {
importing.value = false;
}
};
</script>
<template>
<Card class="transfer-prescription-message-card" :bordered="true" style="max-width: 400px">
<!-- 卡片头部 -->
<div class="card-header">
<div class="header-icon">
<i class="fas fa-exchange-alt"></i>
</div>
<div class="header-title">转接方</div>
<Tag :color="statusText.color" class="status-tag">
{{ statusText.text }}
</Tag>
</div>
<!-- 卡片内容 -->
<div class="card-body">
<!-- 转诊诊所信息 -->
<div v-if="transferInfo.transfer_store" class="info-row">
<div class="info-label">转诊诊所</div>
<div class="info-value">{{ transferInfo.transfer_store.name || '未知诊所' }}</div>
</div>
<!-- 委托诊所信息 -->
<div v-if="transferInfo.delegate_store" class="info-row">
<div class="info-label">委托诊所</div>
<div class="info-value">{{ transferInfo.delegate_store.name || '未知诊所' }}</div>
</div>
<!-- 处方编号 -->
<div v-if="transferInfo.prescription_no" class="info-row">
<div class="info-label">处方编号</div>
<div class="info-value">{{ transferInfo.prescription_no }}</div>
</div>
<!-- 转诊时间 -->
<div v-if="transferInfo.transfer_time" class="info-row">
<div class="info-label">转诊时间</div>
<div class="info-value">{{ transferInfo.transfer_time }}</div>
</div>
</div>
<!-- 卡片底部操作 -->
<div class="card-footer">
<Button
type="default"
size="small"
@click="handleViewDetail"
class="view-btn"
>
查看详情
</Button>
<Button
v-if="transferInfo.status !== 3 && transferInfo.status !== 2"
type="primary"
size="small"
:loading="importing"
@click="handleImportToPrescription"
class="import-btn"
>
<i class="fas fa-download mr-1"></i>
导入处方
</Button>
<Button
v-else-if="transferInfo.status === 2"
type="default"
size="small"
disabled
class="import-btn"
>
已导入
</Button>
</div>
</Card>
</template>
<style scoped>
.transfer-prescription-message-card {
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s;
overflow: hidden;
}
.transfer-prescription-message-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
/* 卡片头部 */
.card-header {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
color: white;
}
.header-icon {
width: 32px;
height: 32px;
background: rgba(255, 255, 255, 0.2);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
}
.header-title {
flex: 1;
font-size: 16px;
font-weight: 600;
letter-spacing: 0.5px;
}
.status-tag {
font-size: 12px;
font-weight: 500;
padding: 2px 8px;
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
}
/* 卡片内容 */
.card-body {
padding: 16px;
}
.info-row {
display: flex;
align-items: flex-start;
margin-bottom: 12px;
font-size: 14px;
line-height: 1.6;
}
.info-row:last-child {
margin-bottom: 0;
}
.info-label {
color: #666;
font-weight: 500;
min-width: 80px;
flex-shrink: 0;
}
.dark .info-label {
color: #94a3b8;
}
.info-value {
color: #1e293b;
flex: 1;
word-break: break-word;
}
.dark .info-value {
color: #e2e8f0;
}
/* 卡片底部 */
.card-footer {
padding: 12px 16px;
background: #f5f5f5;
border-top: 1px solid #e5e7eb;
display: flex;
gap: 8px;
justify-content: flex-end;
}
.dark .card-footer {
background: #1e293b;
border-top-color: #374151;
}
.view-btn,
.import-btn {
font-size: 13px;
padding: 4px 12px;
height: auto;
}
.import-btn {
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
border: none;
color: white;
}
.import-btn:hover:not(:disabled) {
background: linear-gradient(135deg, #ea580c 0%, #dc2626 100%);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(249, 115, 22, 0.3);
}
/* 暗黑模式适配 */
.dark .transfer-prescription-message-card {
background: #1e293b;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.dark .transfer-prescription-message-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
</style>

View File

@@ -0,0 +1,490 @@
<script setup>
import { ref, computed, watch } from 'vue';
import { Drawer, Descriptions, Button, Tag, message, Empty } from 'ant-design-vue';
import { usePrescriptionStore } from '#/store/prescription';
import { getTransferPrescriptionByRegisterApi } from '#/views/business/chat/api/transferPrescription';
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
registerId: {
type: [Number, String],
default: null,
},
});
const emit = defineEmits(['update:visible', 'import-success']);
const prescriptionStore = usePrescriptionStore();
const transferData = ref(null);
const loading = ref(false);
const importing = ref(false);
// 解析转诊数据
const transferInfo = computed(() => {
if (!transferData.value) return null;
if (typeof transferData.value === 'string') {
try {
return JSON.parse(transferData.value);
} catch {
return null;
}
}
return transferData.value;
});
// 解析处方内容从content字段后端已解析为对象
const prescriptionContent = computed(() => {
if (!transferInfo.value) return null;
// 如果已经有解析好的prescription字段直接使用
if (transferInfo.value.prescription && transferInfo.value.prescription.content) {
return {
clinical_diagnose: transferInfo.value.prescription.clinical_diagnose || '',
doctor_order: transferInfo.value.prescription.doctor_order || '',
content: transferInfo.value.prescription.content || [],
};
}
// 从content字段获取后端已解析为对象
const content = transferInfo.value.content;
if (!content) return null;
// 构建药品列表
let drugList = [];
// 如果content是对象检查repice字段
if (typeof content === 'object' && !Array.isArray(content)) {
// 从repice数组中提取药品
if (content.repice && Array.isArray(content.repice)) {
for (const recipe of content.repice) {
if (recipe.content) {
// recipe.content可能是已解析的数组也可能是JSON字符串
let recipeContent = recipe.content;
if (typeof recipeContent === 'string') {
try {
recipeContent = JSON.parse(recipeContent);
} catch {
recipeContent = [];
}
}
// 如果recipeContent是数组合并到drugList
if (Array.isArray(recipeContent)) {
drugList = drugList.concat(recipeContent);
}
}
}
}
return {
clinical_diagnose: content.clinical_diagnose || transferInfo.value.original_prescription?.clinical_diagnose || '',
doctor_order: content.doctor_order || transferInfo.value.original_prescription?.doctor_order || '',
content: drugList,
};
} else if (Array.isArray(content)) {
// 如果content直接是数组说明是药品列表
return {
clinical_diagnose: transferInfo.value.original_prescription?.clinical_diagnose || '',
doctor_order: transferInfo.value.original_prescription?.doctor_order || '',
content: content,
};
}
return null;
});
// 转诊状态文本
const statusText = computed(() => {
if (!transferInfo.value) return { text: '未知', color: 'default' };
const status = transferInfo.value.status;
const statusMap = {
0: { text: '待确认', color: 'orange' },
1: { text: '已确认', color: 'blue' },
2: { text: '已导入', color: 'green' },
3: { text: '已取消', color: 'red' },
};
return statusMap[status] || { text: '未知', color: 'default' };
});
// 获取转接方数据
const fetchTransferPrescription = async () => {
if (!props.registerId) {
message.warning('挂号ID不能为空');
return;
}
loading.value = true;
try {
const res = await getTransferPrescriptionByRegisterApi(props.registerId);
const data = res?.result || res?.data || res;
if (data && data.id) {
transferData.value = data;
} else {
transferData.value = null;
message.warning('未找到转接方信息');
}
} catch (error) {
console.error('获取转接方信息失败:', error);
message.error('获取转接方信息失败,请重试');
transferData.value = null;
} finally {
loading.value = false;
}
};
// 将转接方药品数据转换成addProducts需要的格式
const convertDrugDataForImport = (drug) => {
// addProducts期望的数据结构
// {
// id: ...,
// drug: {
// id: ...,
// drug_name: ...,
// number: ...,
// time_id: ...,
// type_id: ...,
// frequency_id: ...,
// unit_id: ...,
// way_id: ...,
// image: ...,
// instruction: ...,
// type: ...,
// },
// price: ...,
// }
// 转接方的药品数据结构:
// {
// id: ...,
// drug_id: ...,
// name: ...,
// number: ...,
// price: ...,
// unit: {...},
// use_unit: {...},
// ...
// }
return {
id: drug.id || drug.drug_id || 0,
drug: {
id: drug.drug_id || drug.id || 0,
drug_name: drug.name || drug.drug_name || '',
number: drug.number || 1,
time_id: drug.time_id || drug.use_num?.id || 0,
type_id: drug.type_id || drug.use_type?.id || 0,
frequency_id: drug.frequency_id || drug.use_frequency?.id || 0,
unit_id: drug.unit_id || drug.unit?.id || drug.use_unit?.id || 0,
way_id: drug.way_id || drug.use_way?.id || 0,
image: drug.image || '',
instruction: drug.instruction || '',
type: drug.type || transferInfo.value?.prescription_type || 1,
},
price: parseFloat(drug.price) || 0,
};
};
// 一键导入处方到在线问诊(从前端数据导入)
const handleImportToPrescription = async () => {
if (!transferInfo.value?.id) {
message.warning('转接方信息不完整');
return;
}
if (!props.registerId) {
message.warning('挂号ID不能为空');
return;
}
if (!prescriptionContent.value || !prescriptionContent.value.content || !Array.isArray(prescriptionContent.value.content)) {
message.warning('处方内容格式错误,无法导入');
return;
}
importing.value = true;
try {
// 先将处方类型设置为中药1
prescriptionStore.changeCategory(1);
// 直接使用前端已获取的数据进行导入
const drugList = prescriptionContent.value.content;
if (!drugList || drugList.length === 0) {
message.warning('处方中没有药品信息');
return;
}
// 导入药品(需要转换数据格式)
let addedCount = 0;
for (const drug of drugList) {
// 转换数据格式
const convertedDrugData = convertDrugDataForImport(drug);
const success = prescriptionStore.addProducts(convertedDrugData);
if (success) {
addedCount++;
}
}
// 导入诊断和医嘱
if (prescriptionContent.value.clinical_diagnose) {
prescriptionStore.diagnosis = prescriptionContent.value.clinical_diagnose;
}
if (prescriptionContent.value.doctor_order) {
prescriptionStore.medicalAdvice = prescriptionContent.value.doctor_order;
}
if (addedCount > 0) {
message.success(`已成功导入 ${addedCount} 个药品到处方单`);
// 构建处方数据对象,用于触发事件
const prescriptionData = {
content: drugList,
clinical_diagnose: prescriptionContent.value.clinical_diagnose,
doctor_order: prescriptionContent.value.doctor_order,
register_id: props.registerId,
};
// 触发事件,通知父组件打开处方模态框
emit('import-success', prescriptionData);
// 关闭抽屉
emit('update:visible', false);
} else {
message.warning('所有药品已在处方中');
}
} catch (error) {
console.error('导入转接方处方失败:', error);
message.error('导入转接方处方失败,请重试');
} finally {
importing.value = false;
}
};
// 关闭抽屉
const handleClose = () => {
emit('update:visible', false);
};
// 监听visible变化打开时获取数据
watch(
() => props.visible,
(newVal) => {
if (newVal && props.registerId) {
fetchTransferPrescription();
} else {
transferData.value = null;
}
},
{ immediate: true }
);
</script>
<template>
<Drawer
:open="visible"
title="转接方查看"
:width="600"
@close="handleClose"
>
<div v-if="loading" class="loading-container">
<text>加载中...</text>
</div>
<div v-else-if="!transferInfo" class="empty-container">
<Empty description="未找到转接方信息" />
</div>
<div v-else class="transfer-prescription-drawer">
<!-- 状态标签 -->
<div class="status-section">
<Tag :color="statusText.color" class="status-tag">
{{ statusText.text }}
</Tag>
</div>
<!-- 基本信息 -->
<Descriptions
:column="1"
bordered
class="info-descriptions"
title="基本信息"
>
<Descriptions.Item v-if="transferInfo.transfer_store" label="转诊诊所">
{{ transferInfo.transfer_store.name || '未知诊所' }}
</Descriptions.Item>
<Descriptions.Item v-if="transferInfo.delegate_store" label="委托诊所">
{{ transferInfo.delegate_store.name || '未知诊所' }}
</Descriptions.Item>
<Descriptions.Item v-if="transferInfo.prescription_no" label="处方编号">
{{ transferInfo.prescription_no }}
</Descriptions.Item>
<Descriptions.Item v-if="transferInfo.transfer_time" label="转诊时间">
{{ transferInfo.transfer_time }}
</Descriptions.Item>
<Descriptions.Item v-if="transferInfo.transfer_reason" label="转诊原因">
{{ transferInfo.transfer_reason }}
</Descriptions.Item>
</Descriptions>
<!-- 处方内容 -->
<div v-if="prescriptionContent" class="prescription-section">
<h3 class="section-title">处方内容</h3>
<div v-if="prescriptionContent.clinical_diagnose" class="prescription-item">
<strong>临床诊断</strong>
<span>{{ prescriptionContent.clinical_diagnose }}</span>
</div>
<div v-if="prescriptionContent.doctor_order" class="prescription-item">
<strong>医嘱</strong>
<span>{{ prescriptionContent.doctor_order }}</span>
</div>
<div v-if="prescriptionContent.content && prescriptionContent.content.length > 0" class="prescription-content">
<strong>药品列表</strong>
<div class="drug-list">
<div
v-for="(drug, index) in prescriptionContent.content"
:key="index"
class="drug-item"
>
<span>{{ drug.drug_name || drug.name || '未知药品' }}</span>
<span v-if="drug.number"> × {{ drug.number }}</span>
<span v-if="drug.unit?.name || drug.unit"> {{ drug.unit?.name || drug.unit }}</span>
<span v-if="drug.use_type?.name || drug.use_frequency?.name" class="drug-usage">
{{ [drug.use_type?.name, drug.use_frequency?.name, drug.use_num?.name].filter(Boolean).join('') }}
</span>
</div>
</div>
</div>
<div v-else-if="prescriptionContent.content && prescriptionContent.content.length === 0" class="prescription-item">
<span class="text-gray-400">暂无药品信息</span>
</div>
</div>
<!-- 操作按钮 -->
<div v-if="transferInfo.status !== 3" class="action-section">
<Button
v-if="transferInfo.status !== 2"
type="primary"
:loading="importing"
block
size="large"
@click="handleImportToPrescription"
>
<i class="fas fa-download mr-1"></i>
导入到处方单
</Button>
<Button v-else type="default" block disabled>
已导入
</Button>
</div>
</div>
</Drawer>
</template>
<style scoped>
.loading-container,
.empty-container {
display: flex;
align-items: center;
justify-content: center;
min-height: 300px;
}
.transfer-prescription-drawer {
padding: 16px 0;
}
.status-section {
margin-bottom: 24px;
text-align: center;
}
.status-tag {
font-size: 14px;
font-weight: 500;
padding: 6px 16px;
}
.info-descriptions {
margin-bottom: 24px;
}
.prescription-section {
margin-bottom: 24px;
padding: 16px;
background: #f5f5f5;
border-radius: 8px;
}
.dark .prescription-section {
background: #1e293b;
}
.section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
color: #333;
}
.dark .section-title {
color: #e2e8f0;
}
.prescription-item {
margin-bottom: 12px;
font-size: 14px;
line-height: 1.6;
}
.prescription-item strong {
color: #666;
margin-right: 8px;
}
.dark .prescription-item strong {
color: #94a3b8;
}
.prescription-content {
margin-top: 16px;
}
.drug-list {
margin-top: 12px;
padding-left: 20px;
}
.drug-item {
padding: 8px 0;
border-bottom: 1px solid #e5e7eb;
font-size: 14px;
}
.dark .drug-item {
border-bottom-color: #374151;
}
.drug-item:last-child {
border-bottom: none;
}
.drug-usage {
color: #666;
font-size: 12px;
margin-left: 8px;
}
.dark .drug-usage {
color: #94a3b8;
}
.action-section {
margin-top: 32px;
padding-top: 24px;
border-top: 1px solid #e5e7eb;
}
.dark .action-section {
border-top-color: #374151;
}
</style>

View File

@@ -272,6 +272,15 @@ const handleEndConsultationSuccess = () => {
patientListRef.value?.fetchPatientList();
};
// 处理转诊导入成功
const handleTransferImportSuccess = (prescriptionData) => {
// 打开处方模态框
const targetRegisterId = registerId.value || prescriptionData?.register_id;
if (targetRegisterId) {
openPrescriptionModule(targetRegisterId);
}
};
// 将患者购买的药品加入处方单
const handleAddPatientProductsToPrescription = async () => {
// 检查处方store是否已初始化
@@ -374,6 +383,7 @@ const handleAddPatientProductsToPrescription = async () => {
:doctor-id="userStore.currentUser?.doctor_id"
@reception-success="handleReceptionSuccess"
@enter-chat="handleEnterChat"
@transfer-import-success="handleTransferImportSuccess"
class="patient-info-panel"
/>
</template>
@@ -385,6 +395,7 @@ const handleAddPatientProductsToPrescription = async () => {
v-if="chatStore.currentFriend"
@open-prescription="openPrescriptionModule"
@end-consultation-success="handleEndConsultationSuccess"
@transfer-import-success="handleTransferImportSuccess"
/>
<EmptyState v-else />
</div>

View File

@@ -149,3 +149,11 @@ export async function batchSyncDrugPriceApi(ids: number[]) {
ids,
});
}
/**
* 更新诊所类型
* @param data 包含id和clinic_type的对象clinic_type: 1=西医诊所, 2=中医诊所
*/
export async function updateClinicTypeApi(data: { id: number; clinic_type: number }) {
return requestClient.post<any>(`${prefix}update-clinic-type`, data);
}

View File

@@ -106,6 +106,22 @@ export const modalFormProps: VbenFormProps = {
label: '是否包邮',
rules: 'required',
},
{
// 诊所类型(单选)
component: 'RadioGroup',
formItemClass: 'col-span-6',
componentProps: {
options: [
{ label: '西医诊所', value: 1 },
{ label: '中医诊所', value: 2 },
],
placeholder: '诊所类型',
},
fieldName: 'clinic_type',
label: '诊所类型',
rules: 'required',
defaultValue: 2, // 默认中医诊所
},
{
// 是否订阅价格波动(单选,新增和编辑时都显示)
component: 'RadioGroup',

View File

@@ -25,6 +25,13 @@ export const gridOptions: VxeGridProps<RowType> = {
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'name', align: 'left', title: '诊所名称' },
{
field: 'clinic_type',
align: 'left',
title: '诊所类型',
width: 120,
slots: { default: 'clinic_type' },
},
{
field: 'online_consultation_config',
align: 'left',

View File

@@ -7,7 +7,7 @@ import { Page, useVbenModal } from '@vben/common-ui';
import { useUserStore } from '@vben/stores';
import { useClipboard } from '@vueuse/core';
import { Button, Image, message, Switch, Tag } from 'ant-design-vue';
import { Button, Image, message, Modal, Switch, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { Icon } from '#/components/icon';
@@ -19,6 +19,7 @@ import {
deleteStore,
openPcWindowsApiByStore,
openQrCodeApi,
updateClinicTypeApi,
updateStoreShippingFree,
updateStoreSubscribeStatus,
} from './api';
@@ -189,6 +190,36 @@ const batchSyncDrugPrice = () => {
message.error('批量同步失败!');
});
};
/**
* 切换诊所类型
*/
const handleSwitchClinicType = (row: any) => {
if (!row) return;
const currentType = row.clinic_type || 2; // 默认为中医诊所
const newType = currentType === 1 ? 2 : 1;
const currentTypeText = currentType === 1 ? '西医诊所' : '中医诊所';
const newTypeText = newType === 1 ? '西医诊所' : '中医诊所';
const clinicName = row.name || '该诊所';
Modal.confirm({
title: '确认切换诊所类型',
content: `确定要将【${clinicName}】的诊所类型从【${currentTypeText}】切换为【${newTypeText}】吗?`,
okText: '确定',
cancelText: '取消',
onOk: async () => {
try {
await updateClinicTypeApi({ id: row.id, clinic_type: newType });
message.success(`诊所类型已切换为【${newTypeText}`);
gridApi.query();
} catch (error) {
console.error('切换诊所类型失败:', error);
message.error('切换失败,请稍后重试');
}
}
});
};
</script>
<template>
@@ -268,6 +299,15 @@ const batchSyncDrugPrice = () => {
</template>
<template #type="{ row }">
</template>
<template #clinic_type="{ row }">
<Tag
:color="row.clinic_type === 1 ? 'blue' : row.clinic_type === 2 ? 'green' : 'default'"
class="clinic-type-tag cursor-pointer"
@click="handleSwitchClinicType(row)"
>
{{ row.clinic_type === 1 ? '西医诊所' : row.clinic_type === 2 ? '中医诊所' : '未设置' }}
</Tag>
</template>
<template #online_consultation_config="{ row }">
<div
v-if="row.is_internet_medical === 1 && row.online_consultation_doctor_name"
@@ -398,4 +438,19 @@ const batchSyncDrugPrice = () => {
.qr_code:hover {
cursor: pointer;
}
/* 诊所类型标签样式 */
.clinic-type-tag {
transition: all 0.2s ease;
user-select: none;
&:hover {
opacity: 0.8;
transform: scale(1.05);
}
&:active {
transform: scale(0.95);
}
}
</style>