feat: 医生创建就诊人发送前确认收货地址,成功页与处方记录可看订单
发送前可选填/一键诊所地址;开方成功展示收货信息;处方记录支持查看订单详情。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 医生创建就诊人:发送处方前确认收货地址
|
||||
* 地址选填;无地址可一键填诊所或打开编辑;确认后继续发送
|
||||
*/
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, Spin, message } from 'ant-design-vue';
|
||||
|
||||
import { formatShipAddressCopyText } from '#/util/ship-address-copy';
|
||||
import {
|
||||
getCreatedUserPatientDetailApi,
|
||||
getCurrentStoreTypeApi,
|
||||
updateCreatedUserPatientApi,
|
||||
} from '#/views/doctor/doctor-reception/api';
|
||||
|
||||
import EditCreatedUserPatientModal from './EditCreatedUserPatientModal.vue';
|
||||
|
||||
export type ShipAddressSnapshot = {
|
||||
name?: string;
|
||||
mobile?: string;
|
||||
province?: string;
|
||||
city?: string;
|
||||
area?: string;
|
||||
region?: string;
|
||||
detail_address?: string;
|
||||
path_label?: string;
|
||||
};
|
||||
|
||||
type ClinicAddress = {
|
||||
available: boolean;
|
||||
city: string;
|
||||
city_id: number;
|
||||
contact: string;
|
||||
mobile: string;
|
||||
path_label: string;
|
||||
position: string;
|
||||
province: string;
|
||||
province_id: number;
|
||||
};
|
||||
|
||||
const loading = ref(false);
|
||||
const applyingClinic = ref(false);
|
||||
const userPatientId = ref(0);
|
||||
const patientName = ref('');
|
||||
const shipAddress = ref<ShipAddressSnapshot>({});
|
||||
const clinicAddress = ref<ClinicAddress | null>(null);
|
||||
const onConfirmCb = ref<((ship: ShipAddressSnapshot) => void) | null>(null);
|
||||
|
||||
const shipText = computed(() =>
|
||||
formatShipAddressCopyText(shipAddress.value || {}),
|
||||
);
|
||||
const hasShip = computed(() => !!shipText.value);
|
||||
const clinicAddressText = computed(() => {
|
||||
const a = clinicAddress.value;
|
||||
if (!a?.available) return '';
|
||||
return `${a.path_label || ''}${a.position || ''}`;
|
||||
});
|
||||
|
||||
const [EditShipModal, editShipModalApi] = useVbenModal({
|
||||
connectedComponent: EditCreatedUserPatientModal,
|
||||
});
|
||||
|
||||
/** 拉取就诊人地址与诊所地址 */
|
||||
async function loadData() {
|
||||
if (!userPatientId.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const [detail, storeRes] = await Promise.all([
|
||||
getCreatedUserPatientDetailApi(userPatientId.value),
|
||||
getCurrentStoreTypeApi().catch(() => null),
|
||||
]);
|
||||
shipAddress.value = (detail?.ship_address || {}) as ShipAddressSnapshot;
|
||||
clinicAddress.value = (storeRes?.store_address ||
|
||||
null) as ClinicAddress | null;
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '加载收货地址失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 一键写入诊所地址到就诊人(选填场景的快捷填充) */
|
||||
async function applyClinicAddress() {
|
||||
const a = clinicAddress.value;
|
||||
if (!a?.available) {
|
||||
message.warning('当前诊所未配置完整地址');
|
||||
return;
|
||||
}
|
||||
if (!userPatientId.value) return;
|
||||
applyingClinic.value = true;
|
||||
try {
|
||||
const detail = await getCreatedUserPatientDetailApi(userPatientId.value);
|
||||
// full 模式后端会写 name/sex 等;必须带回详情原值,避免只传 ship 时把姓名刷空
|
||||
const payload: Record<string, any> = {
|
||||
user_patient_id: userPatientId.value,
|
||||
mobile: String(detail?.mobile || '').trim(),
|
||||
ship_name: String(a.contact || detail?.name || '').slice(0, 10),
|
||||
ship_mobile: String(a.mobile || detail?.mobile || '').trim(),
|
||||
ship_province: a.province || '',
|
||||
ship_city: a.city || '',
|
||||
ship_area: '',
|
||||
ship_region: a.path_label || `${a.province || ''}${a.city || ''}`,
|
||||
ship_detail_address: a.position || '',
|
||||
};
|
||||
if (String(detail?.edit_mode || '') !== 'limited') {
|
||||
payload.name = String(detail?.name || '').trim();
|
||||
payload.id_card = String(detail?.id_card || '').trim();
|
||||
payload.birthday = String(detail?.birthday || '').trim();
|
||||
payload.sex = Number(detail?.sex || 1);
|
||||
payload.age = detail?.age;
|
||||
}
|
||||
await updateCreatedUserPatientApi(payload);
|
||||
message.success('已填入诊所地址');
|
||||
await loadData();
|
||||
} catch (error: any) {
|
||||
message.error(error?.message || '填入诊所地址失败');
|
||||
} finally {
|
||||
applyingClinic.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开编辑弹窗手填/粘贴修改地址 */
|
||||
function openEditShip() {
|
||||
editShipModalApi.setData({
|
||||
user_patient_id: userPatientId.value,
|
||||
onSaved: () => {
|
||||
void loadData();
|
||||
},
|
||||
});
|
||||
editShipModalApi.open();
|
||||
}
|
||||
|
||||
function confirmSend() {
|
||||
onConfirmCb.value?.(shipAddress.value || {});
|
||||
modalApi.close();
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
draggable: true,
|
||||
title: '确认收货地址',
|
||||
confirmText: '确认发送',
|
||||
cancelText: '取消',
|
||||
contentClass: 'max-h-[70vh]',
|
||||
onConfirm: () => {
|
||||
confirmSend();
|
||||
},
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) return;
|
||||
const data = modalApi.getData() as {
|
||||
user_patient_id?: number;
|
||||
patient_name?: string;
|
||||
onConfirm?: (ship: ShipAddressSnapshot) => void;
|
||||
};
|
||||
userPatientId.value = Number(data?.user_patient_id || 0);
|
||||
patientName.value = String(data?.patient_name || '');
|
||||
onConfirmCb.value = data?.onConfirm || null;
|
||||
shipAddress.value = {};
|
||||
clinicAddress.value = null;
|
||||
void loadData();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-[560px] max-w-[95vw]">
|
||||
<EditShipModal />
|
||||
<Spin :spinning="loading">
|
||||
<p class="mb-3 text-sm text-[hsl(var(--muted-foreground))]">
|
||||
就诊人
|
||||
<b class="text-[hsl(var(--foreground))]">{{ patientName || '—' }}</b>
|
||||
由医生创建,请确认收货地址后发送。地址选填,可不填直接发送。
|
||||
</p>
|
||||
<div
|
||||
v-if="hasShip"
|
||||
class="rounded border border-[hsl(var(--primary)/30%)] px-3 py-2 shadow-[0_0_6px_hsl(var(--primary)/18%)]"
|
||||
>
|
||||
<div class="mb-1 text-xs text-[hsl(var(--muted-foreground))]">
|
||||
当前收货地址
|
||||
</div>
|
||||
<div class="break-all text-sm text-[hsl(var(--foreground))]">
|
||||
{{ shipText }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="rounded border border-dashed border-[hsl(var(--border))] bg-[hsl(var(--muted)/0.25)] px-3 py-3 text-sm text-[hsl(var(--muted-foreground))]"
|
||||
>
|
||||
暂无收货地址(选填)。可一键填入诊所地址,或手动填写后再发送。
|
||||
</div>
|
||||
<div
|
||||
v-if="clinicAddress?.available"
|
||||
class="mt-3 text-xs text-[hsl(var(--muted-foreground))]"
|
||||
>
|
||||
诊所地址:{{ clinicAddressText }}
|
||||
</div>
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
<Button
|
||||
v-if="clinicAddress?.available"
|
||||
type="primary"
|
||||
ghost
|
||||
:loading="applyingClinic"
|
||||
@click="applyClinicAddress"
|
||||
>
|
||||
使用诊所地址
|
||||
</Button>
|
||||
<Button @click="openEditShip">
|
||||
{{ hasShip ? '修改地址' : '填写地址' }}
|
||||
</Button>
|
||||
</div>
|
||||
</Spin>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -33,6 +33,8 @@ type DrawerData = {
|
||||
onReuse?: (prescriptionId: number) => void | Promise<void>;
|
||||
/** 点击处方号打开详情 */
|
||||
onDetail?: (prescriptionId: number) => void;
|
||||
/** 查看商品订单详情(有 order_id 时) */
|
||||
onOrderDetail?: (orderId: number) => void;
|
||||
/** 是否允许撤回(线下接诊) */
|
||||
allowWithdraw?: boolean;
|
||||
/** 撤回成功后刷新 */
|
||||
@@ -178,6 +180,20 @@ function handleOpenDetail(row: Record<string, any>) {
|
||||
message.warning('暂无法打开处方详情');
|
||||
}
|
||||
|
||||
/** 打开商品订单详情(父级挂载 OrderDetail 弹窗) */
|
||||
function handleOpenOrderDetail(row: Record<string, any>) {
|
||||
const orderId = Number(row.order_id || 0);
|
||||
if (!orderId) {
|
||||
message.warning('暂无关联订单');
|
||||
return;
|
||||
}
|
||||
if (typeof meta.value.onOrderDetail === 'function') {
|
||||
meta.value.onOrderDetail(orderId);
|
||||
return;
|
||||
}
|
||||
message.warning('暂无法打开订单详情');
|
||||
}
|
||||
|
||||
async function handleWithdraw(row: Record<string, any>) {
|
||||
const prescriptionId = Number(row.id || 0);
|
||||
const registerId = Number(row.register_id || meta.value.registerId || 0);
|
||||
@@ -302,8 +318,16 @@ defineExpose({ drawerApi });
|
||||
<div class="rx-history-meta text-muted-foreground">
|
||||
<span>开方时间:{{ row.created_at || '—' }}</span>
|
||||
<span class="ml-3">金额:¥{{ row.total_pay_price ?? '—' }}</span>
|
||||
<span v-if="row.order_no" class="ml-3">订单号:{{ row.order_no }}</span>
|
||||
<span v-if="row.order_id" class="ml-3">订单ID:{{ row.order_id }}</span>
|
||||
<span v-if="row.order_no" class="ml-3">
|
||||
订单号:
|
||||
<a
|
||||
class="cursor-pointer text-primary hover:underline"
|
||||
title="查看订单详情"
|
||||
@click.prevent="handleOpenOrderDetail(row)"
|
||||
>
|
||||
{{ row.order_no }}
|
||||
</a>
|
||||
</span>
|
||||
<span
|
||||
v-if="listTab === 'history' && row.register?.order_no"
|
||||
class="ml-3"
|
||||
@@ -313,6 +337,14 @@ defineExpose({ drawerApi });
|
||||
</div>
|
||||
</div>
|
||||
<div class="rx-history-actions">
|
||||
<Button
|
||||
v-if="row.order_id"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleOpenOrderDetail(row)"
|
||||
>
|
||||
查看订单
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
|
||||
@@ -72,6 +72,7 @@ import { DrugSearchSelect } from '#/components/drug-search-select';
|
||||
import InfoModal from '#/components/modal/InfoModal.vue';
|
||||
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
|
||||
import { useOrderPriceAdjust } from '#/composables/useOrderPriceAdjust';
|
||||
import { formatShipAddressCopyText } from '#/util/ship-address-copy';
|
||||
import { getRegisterStatus } from '#/util/tool';
|
||||
import {
|
||||
calcItemMarginPercent,
|
||||
@@ -150,6 +151,11 @@ import {
|
||||
saveChineseCommonPrescriptionApi,
|
||||
saveWestCommonPrescriptionApi,
|
||||
} from '#/views/doctor/settings/api';
|
||||
import OrderDetail from '#/views/business/order/product-order/components/detail.vue';
|
||||
import {
|
||||
openOrderPopup,
|
||||
ORDER_POPUP_Z_INDEX,
|
||||
} from '#/views/business/order/utils/popup-z-index';
|
||||
import { applyPublicAccountPay } from '#/views/finance/public-account-pay/api';
|
||||
import ApplyPublicAccountPayModal from '#/views/finance/public-account-pay/components/apply-modal.vue';
|
||||
|
||||
@@ -160,6 +166,7 @@ import ClaimQrcodeModal from './components/ClaimQrcodeModal.vue';
|
||||
import CommonPrescriptionModal from './components/CommonPrescriptionModal.vue';
|
||||
// 确认模态框组件
|
||||
import ConfirmModal from './components/ConfirmModal.vue';
|
||||
import ConfirmShipAddressBeforeSendModal from './components/ConfirmShipAddressBeforeSendModal.vue';
|
||||
import CreateUserPatientModal from './components/CreateUserPatientModal.vue';
|
||||
import DoctorCreatedPatientModal from './components/DoctorCreatedPatientModal.vue';
|
||||
import GoldenFormulaModal from './components/GoldenFormulaModal.vue';
|
||||
@@ -1553,8 +1560,11 @@ const checkChineseMedicineConflict = () => {
|
||||
});
|
||||
return;
|
||||
};
|
||||
/** 发送前确认弹窗带回的收货快照,成功页展示用 */
|
||||
const pendingShipForSuccess = ref<Record<string, any> | null>(null);
|
||||
|
||||
/**
|
||||
* 发送处方
|
||||
* 发送处方:校验通过后,医生创建就诊人先确认收货地址(选填 / 可一键诊所地址)
|
||||
*/
|
||||
const sendPrescription = () => {
|
||||
if (currentDrugs.value.length === 0) {
|
||||
@@ -1585,6 +1595,29 @@ const sendPrescription = () => {
|
||||
message.error('医嘱不能为空');
|
||||
return;
|
||||
}
|
||||
const up = activePatient.value as any;
|
||||
const upId = Number(up?.id || 0);
|
||||
const isDoctorCreated = Number(up?.created_by_doctor_id || 0) > 0;
|
||||
if (isDoctorCreated && upId > 0) {
|
||||
ConfirmShipAddressBeforeSendModalApi.setData({
|
||||
user_patient_id: upId,
|
||||
patient_name: String(up?.name || ''),
|
||||
onConfirm: (ship: Record<string, any>) => {
|
||||
pendingShipForSuccess.value = ship || {};
|
||||
doSendPrescription();
|
||||
},
|
||||
});
|
||||
ConfirmShipAddressBeforeSendModalApi.open();
|
||||
return;
|
||||
}
|
||||
pendingShipForSuccess.value = null;
|
||||
doSendPrescription();
|
||||
};
|
||||
|
||||
/**
|
||||
* 真正调用开方接口(确认地址后或非医生创建就诊人直接走这里)
|
||||
*/
|
||||
const doSendPrescription = () => {
|
||||
// 发送处方逻辑
|
||||
addWestPrescription({
|
||||
// 当前活动患者的个人信息
|
||||
@@ -1647,6 +1680,9 @@ const sendPrescription = () => {
|
||||
message.success('处方已发送');
|
||||
// 处方已提交,重置 AI 关联,避免下一张处方误关联
|
||||
aiRxGenerationId.value = 0;
|
||||
const shipText = formatShipAddressCopyText(
|
||||
pendingShipForSuccess.value || {},
|
||||
);
|
||||
// 先记录本次发送结果(下面会清空诊断/购物车,成功页要用的数据在这里先落住)
|
||||
sentPrescriptionInfo.value = {
|
||||
order_id: Number(res?.order_id || 0),
|
||||
@@ -1655,7 +1691,11 @@ const sendPrescription = () => {
|
||||
is_pay: Number(res?.is_pay || 0),
|
||||
patient_name: activePatient.value?.name || '',
|
||||
diagnosis: diagnosis.value,
|
||||
// 医生创建就诊人发送前确认过的收货地址(选填时可为空)
|
||||
ship_address_text: shipText,
|
||||
has_ship_confirm: pendingShipForSuccess.value !== null,
|
||||
};
|
||||
pendingShipForSuccess.value = null;
|
||||
// 有 VIP 时同步保存病历(诊断/医嘱已与处方同源)
|
||||
if (canUseMedicalRecord.value) {
|
||||
try {
|
||||
@@ -1818,15 +1858,44 @@ const [DoctorCreatedPatientModals, DoctorCreatedPatientModalApi] = useVbenModal(
|
||||
},
|
||||
);
|
||||
|
||||
/** 医生创建就诊人:发送前确认收货地址(选填 / 一键诊所地址) */
|
||||
const [
|
||||
ConfirmShipAddressBeforeSendModals,
|
||||
ConfirmShipAddressBeforeSendModalApi,
|
||||
] = useVbenModal({
|
||||
connectedComponent: ConfirmShipAddressBeforeSendModal,
|
||||
});
|
||||
|
||||
/** 医生代支付码弹窗 */
|
||||
const [ProxyPayQrcodeModals, ProxyPayQrcodeModalApi] = useVbenModal({
|
||||
connectedComponent: ProxyPayQrcodeModal,
|
||||
});
|
||||
/** 商品订单详情(开方成功 / 处方记录入口,复用订单管理详情弹窗) */
|
||||
const [ProductOrderDetailModal, ProductOrderDetailModalApi] = useVbenModal({
|
||||
connectedComponent: OrderDetail,
|
||||
});
|
||||
const [ApplyPublicAccountPayModals, ApplyPublicAccountPayModalApi] =
|
||||
useVbenModal({
|
||||
connectedComponent: ApplyPublicAccountPayModal,
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开商品订单详情
|
||||
* 为什么用 NESTED:处方记录是抽屉,详情需盖在抽屉之上
|
||||
*/
|
||||
function openProductOrderDetail(orderId: number) {
|
||||
const id = Number(orderId || 0);
|
||||
if (!id) {
|
||||
message.warning('暂无关联订单');
|
||||
return;
|
||||
}
|
||||
openOrderPopup(
|
||||
ProductOrderDetailModalApi,
|
||||
{ id },
|
||||
ORDER_POPUP_Z_INDEX.NESTED,
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== 常用方弹窗 ====================
|
||||
const [CommonPrescriptionModals, CommonPrescriptionModalApi] = useVbenModal({
|
||||
connectedComponent: CommonPrescriptionModal,
|
||||
@@ -3354,6 +3423,8 @@ async function openPatientRxHistoryDrawer() {
|
||||
// 点击处方号打开详情(与医生名片/患者管理一致)
|
||||
onDetail: (prescriptionId: number) =>
|
||||
openPrescriptionDetail(prescriptionId),
|
||||
// 查看关联商品订单详情
|
||||
onOrderDetail: (orderId: number) => openProductOrderDetail(orderId),
|
||||
publicAccountPayEnabled: publicAccountPayEnabled.value,
|
||||
publicAccountPayTrigger: publicAccountPayTrigger.value,
|
||||
publicAccountBillMode: publicAccountBillMode.value,
|
||||
@@ -4254,7 +4325,9 @@ function onStoreSelectOpenChange(open: boolean) {
|
||||
<CreateUserPatientModals />
|
||||
<ClaimQrcodeModals />
|
||||
<DoctorCreatedPatientModals />
|
||||
<ConfirmShipAddressBeforeSendModals />
|
||||
<ProxyPayQrcodeModals />
|
||||
<ProductOrderDetailModal />
|
||||
<ApplyPublicAccountPayModals />
|
||||
<InfoModalComponent />
|
||||
<!-- 左侧患者列表:悬停展开、移出收起;收起态仅箭头+背景提示 -->
|
||||
@@ -5761,13 +5834,25 @@ function onStoreSelectOpenChange(open: boolean) {
|
||||
<h3 class="rx-sent-title">处方已发送</h3>
|
||||
<p class="rx-sent-sub">已推送到患者微信 · 支付完成后门店按方发药</p>
|
||||
<div class="rx-sent-pills">
|
||||
<Button
|
||||
v-if="sentPrescriptionInfo?.order_id"
|
||||
shape="round"
|
||||
size="small"
|
||||
type="primary"
|
||||
ghost
|
||||
@click="
|
||||
openProductOrderDetail(Number(sentPrescriptionInfo.order_id))
|
||||
"
|
||||
>
|
||||
查看订单
|
||||
</Button>
|
||||
<Button
|
||||
shape="round"
|
||||
size="small"
|
||||
@click="openPatientRxHistoryDrawer"
|
||||
>
|
||||
处方记录
|
||||
</Button>
|
||||
>
|
||||
处方记录
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canEndReception"
|
||||
shape="round"
|
||||
@@ -5784,12 +5869,31 @@ function onStoreSelectOpenChange(open: boolean) {
|
||||
<div class="rx-sent-card rx-sent-detail">
|
||||
<div class="rx-sent-row">
|
||||
<span>订单号</span>
|
||||
<b>{{ sentPrescriptionInfo?.order_no || '—' }}</b>
|
||||
<a
|
||||
v-if="sentPrescriptionInfo?.order_id"
|
||||
class="cursor-pointer font-medium text-primary hover:underline"
|
||||
title="查看订单详情"
|
||||
@click.prevent="
|
||||
openProductOrderDetail(Number(sentPrescriptionInfo.order_id))
|
||||
"
|
||||
>
|
||||
{{ sentPrescriptionInfo?.order_no || '—' }}
|
||||
</a>
|
||||
<b v-else>{{ sentPrescriptionInfo?.order_no || '—' }}</b>
|
||||
</div>
|
||||
<div class="rx-sent-row">
|
||||
<span>患者</span>
|
||||
<b>{{ sentPrescriptionInfo?.patient_name || '—' }}</b>
|
||||
</div>
|
||||
<div
|
||||
v-if="sentPrescriptionInfo?.has_ship_confirm"
|
||||
class="rx-sent-row"
|
||||
>
|
||||
<span>收货地址</span>
|
||||
<b class="break-all text-right">{{
|
||||
sentPrescriptionInfo?.ship_address_text || '未填写'
|
||||
}}</b>
|
||||
</div>
|
||||
<div v-if="sentPrescriptionInfo?.diagnosis" class="rx-sent-row">
|
||||
<span>诊断</span>
|
||||
<b>{{ sentPrescriptionInfo.diagnosis }}</b>
|
||||
|
||||
Reference in New Issue
Block a user