feat:
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
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled

1. 医生线下接诊创建就诊人、患者端扫码认领就诊人
2. 生成处方后医生代付码
This commit is contained in:
李琦
2026-08-09 14:36:02 +08:00
parent 335b3535d5
commit 689a0f64a6
7 changed files with 755 additions and 21 deletions

View File

@@ -95,6 +95,64 @@ function handleOpenPatient() {
patientName: String(patientName(props.row)),
});
}
/** 付款人类型文案1用户 2医生 3就诊人 */
function payUserTypeLabel(row: Record<string, any>) {
const t = Number(row.pay_user_type || 0);
if (t === 2) return '医生';
if (t === 3) return '就诊人';
if (t === 1) return '用户';
return '—';
}
/** 实际付款人用户 ID */
function resolvePayUserId(row: Record<string, any>) {
return Number(row.pay_user_id || row.pay_user?.id || 0);
}
/** 是否展示代付行:有付款人 ID或至少有类型医生代付可回落医生名 */
function hasPayUserInfo(row: Record<string, any>) {
return resolvePayUserId(row) > 0 || Number(row.pay_user_type || 0) > 0;
}
/** 代付展示名:优先 pay_user 昵称;医生代付可回落医生名 */
function payUserDisplayName(row: Record<string, any>) {
const nick = String(row.pay_user?.nickname || '').trim();
if (nick) return nick;
if (Number(row.pay_user_type) === 2 && row.doctor?.name) {
return String(row.doctor.name);
}
if (Number(row.pay_user_type) === 3) {
return patientName(row);
}
return '—';
}
function getPayUserAvatarSrc(row: Record<string, any>) {
const raw = String(row.pay_user?.avatarurl ?? '').trim();
if (!raw) return defaultAvatar;
return resolveAvatarUrl(raw) || defaultAvatar;
}
/** 点代付用户:按类型打开对应用户/医生/就诊人 */
function handleOpenPayUser() {
const type = Number(props.row.pay_user_type || 0);
if (type === 2) {
emit('openDoctor', props.row);
return;
}
if (type === 3) {
handleOpenPatient();
return;
}
const userId = resolvePayUserId(props.row);
if (!userId) return;
emit('openUserPatients', {
userId,
nickname: String(props.row.pay_user?.nickname || ''),
avatarurl: String(props.row.pay_user?.avatarurl || ''),
});
}
</script>
<template>
@@ -163,6 +221,47 @@ function handleOpenPatient() {
</div>
</div>
</div>
<div class="order-user-info__row">
<Avatar :size="24" :src="getPayUserAvatarSrc(row)" class="shrink-0" />
<div class="order-user-info__text min-w-0">
<span class="text-[11px] leading-tight text-gray-400 dark:text-slate-500">
代付
</span>
<template v-if="hasPayUserInfo(row)">
<span class="mr-1 text-[11px] text-gray-500 dark:text-slate-400">
[{{ payUserTypeLabel(row) }}]
</span>
<Button
v-if="Number(row.pay_user_type) === 2 && row.doctor?.name"
class="order-user-info__link-btn !h-auto !px-0 !py-0"
type="link"
@click.stop="emit('openDoctor', row)"
>
{{ payUserDisplayName(row) }}
</Button>
<Button
v-else-if="Number(row.pay_user_type) === 3 && resolveUpId(row)"
class="order-user-info__link-btn !h-auto !px-0 !py-0"
type="link"
@click.stop="handleOpenPatient"
>
{{ payUserDisplayName(row) }}
</Button>
<Button
v-else-if="resolvePayUserId(row)"
class="order-user-info__link-btn !h-auto !px-0 !py-0"
type="link"
@click.stop="handleOpenPayUser"
>
{{ payUserDisplayName(row) }}
</Button>
<span v-else class="truncate text-xs text-gray-700 dark:text-slate-200">
{{ payUserDisplayName(row) }}
</span>
</template>
<span v-else class="truncate text-xs text-gray-700 dark:text-slate-200"></span>
</div>
</div>
</div>
</template>

View File

@@ -547,3 +547,30 @@ export async function goldenFormulaSaveMatchApi(data: {
}) {
return requestClient.post<any>(`${prefix}golden-formula-save-match`, data);
}
/**
* 医生新建就诊人并自动挂号
* waive_register_fee=1 时免除挂号费
*/
export async function createUserPatientApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create-user-patient`, data);
}
/** 当前医生未认领就诊人列表 */
export async function getUnclaimedUserPatientListApi() {
return requestClient.get<any>(`${prefix}unclaimed-user-patient-list`);
}
/** 就诊人认领小程序码base64不上传 */
export async function getUserPatientClaimQrcodeApi(userPatientId: number) {
return requestClient.get<any>(`${prefix}user-patient-claim-qrcode`, {
params: { user_patient_id: userPatientId },
});
}
/** 医生代支付小程序码(患者端扫码付) */
export async function getProxyPayQrcodeApi(orderId: number) {
return requestClient.post<any>(`${prefix}proxy-pay-qrcode`, {
order_id: orderId,
});
}

View File

@@ -0,0 +1,110 @@
<script lang="ts" setup>
/**
* 就诊人认领小程序码:选择未认领就诊人后展示 base64 码(不上传)
*/
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Empty, message, Select, Spin } from 'ant-design-vue';
import {
getUnclaimedUserPatientListApi,
getUserPatientClaimQrcodeApi,
} from '#/views/doctor/doctor-reception/api';
const loading = ref(false);
const list = ref<Array<{ id: number; name: string; mobile?: string; age?: number }>>([]);
const selectedId = ref<number | undefined>();
const qrcode = ref('');
const patientName = ref('');
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
showConfirmButton: false,
cancelText: '关闭',
onCancel() {
modalApi.close();
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) return;
selectedId.value = undefined;
qrcode.value = '';
patientName.value = '';
loading.value = true;
try {
const res = await getUnclaimedUserPatientListApi();
list.value = Array.isArray(res) ? res : [];
const preferId = Number(modalApi.getData()?.user_patient_id || 0);
if (preferId > 0 && list.value.some((i) => i.id === preferId)) {
selectedId.value = preferId;
await loadQrcode(preferId);
}
} catch (e: any) {
message.error(e?.message || '加载未认领就诊人失败');
} finally {
loading.value = false;
}
},
});
/**
* 拉取认领码并展示
*/
async function loadQrcode(id: number) {
if (!id) return;
loading.value = true;
qrcode.value = '';
try {
const res = await getUserPatientClaimQrcodeApi(id);
qrcode.value = res?.qrcode || '';
patientName.value = res?.name || '';
if (!qrcode.value) {
message.error('二维码生成失败');
}
} catch (e: any) {
message.error(e?.message || '生成认领码失败');
} finally {
loading.value = false;
}
}
function onSelectChange(id: number) {
selectedId.value = id;
loadQrcode(id);
}
</script>
<template>
<Modal class="w-[420px]" title="就诊人二维码认领">
<Spin :spinning="loading">
<div class="mb-3">
<div class="mb-1 text-xs text-[hsl(var(--muted-foreground))]">选择未认领就诊人</div>
<Select
class="w-full"
:value="selectedId"
placeholder="请选择"
:options="
list.map((i) => ({
label: `${i.name}ID:${i.id}${i.mobile ? ' ' + i.mobile : ''}`,
value: i.id,
}))
"
@change="onSelectChange"
/>
</div>
<div
v-if="qrcode"
class="flex flex-col items-center rounded border border-[hsl(var(--border))] bg-[hsl(var(--card,var(--background)))] p-4"
>
<div class="mb-2 text-sm text-[hsl(var(--foreground))]">
{{ patientName || '就诊人' }} · 请用患者端小程序扫码认领
</div>
<img :src="qrcode" alt="认领二维码" class="h-[220px] w-[220px]" />
</div>
<Empty v-else-if="!loading && list.length === 0" description="暂无未认领就诊人" />
<Empty v-else-if="!loading && !qrcode" description="请选择就诊人以展示认领码" />
</Spin>
</Modal>
</template>

View File

@@ -0,0 +1,276 @@
<script lang="ts" setup>
/**
* 医生新建就诊人:基础信息 + 健康信息 + 可选免除挂号费,创建后自动挂号
*/
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import {
Col,
DatePicker,
Form,
FormItem,
Input,
InputNumber,
message,
Radio,
RadioGroup,
Row,
Select,
Switch,
} from 'ant-design-vue';
import dayjs, { type Dayjs } from 'dayjs';
import { createUserPatientApi } from '#/views/doctor/doctor-reception/api';
const submitting = ref(false);
const form = ref(buildEmptyForm());
const onSuccessCb = ref<null | (() => void)>(null);
const showGuardian = computed(() => Number(form.value.age) > 0 && Number(form.value.age) <= 6);
function buildEmptyForm() {
return {
name: '',
id_card: '',
birthday: '' as string,
sex: 1,
age: undefined as number | undefined,
mobile: '',
relation: 7,
waive_register_fee: true,
liver_function: 0,
renal_function: 0,
liver_index: '',
renal_index: '',
person_status: 0,
allergic_status: 0,
family_status: 0,
epidemic_status: 0,
personal_status: 0,
menstrual_status: 0,
marital_status: 0,
present_status: 0,
person_history: '',
allergic_history: '',
family_history: '',
epidemic_history: '',
personal_history: '',
menstrual_history: '',
marital_history: '',
present_history: '',
guardian_name: '',
guardian_id_card: '',
guardian_mobile: '',
guardian_relation: 0,
};
}
const relationOptions = [
{ label: '本人', value: 0 },
{ label: '丈夫', value: 1 },
{ label: '妻子', value: 2 },
{ label: '爸爸', value: 3 },
{ label: '妈妈', value: 4 },
{ label: '儿子', value: 5 },
{ label: '女儿', value: 6 },
{ label: '其他', value: 7 },
];
const yesNoOptions = [
{ label: '无', value: 0 },
{ label: '有', value: 1 },
];
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
async onConfirm() {
if (!form.value.name?.trim()) {
message.error('请填写姓名');
return;
}
if (!form.value.mobile || !/^\d{11}$/.test(form.value.mobile)) {
message.error('请填写正确手机号');
return;
}
if (!form.value.id_card && !form.value.birthday) {
message.error('请填写身份证或生日');
return;
}
if (form.value.age == null || form.value.age < 1) {
message.error('请填写年龄');
return;
}
if (showGuardian.value) {
if (!form.value.guardian_name || !form.value.guardian_id_card || !form.value.guardian_mobile) {
message.error('6岁及以下请填写监护人信息');
return;
}
}
submitting.value = true;
modalApi.setState({ confirmLoading: true });
try {
await createUserPatientApi({
...form.value,
waive_register_fee: form.value.waive_register_fee ? 1 : 0,
});
message.success('创建成功,已自动挂号');
onSuccessCb.value?.();
modalApi.close();
} catch (e: any) {
message.error(e?.message || '创建失败');
} finally {
submitting.value = false;
modalApi.setState({ confirmLoading: false });
}
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
form.value = buildEmptyForm();
const data = modalApi.getData<{ onSuccess?: () => void }>() || {};
onSuccessCb.value = data.onSuccess || null;
}
},
});
/** 生日选择回填value-format 时 change 直接给字符串) */
function onBirthdayChange(v: Dayjs | string | null) {
if (!v) {
form.value.birthday = '';
return;
}
const str = typeof v === 'string' ? v : v.format('YYYY-MM-DD');
form.value.birthday = str;
form.value.age = dayjs().diff(dayjs(str), 'year');
}
</script>
<template>
<Modal class="w-[720px]" title="新建就诊人">
<Form layout="vertical" class="create-user-patient-form">
<div class="mb-3 text-sm font-medium text-[hsl(var(--foreground))]">基础信息</div>
<Row :gutter="12">
<Col :span="12">
<FormItem label="姓名" required>
<Input v-model:value="form.name" placeholder="真实姓名" />
</FormItem>
</Col>
<Col :span="12">
<FormItem label="手机号" required>
<Input v-model:value="form.mobile" maxlength="11" placeholder="11位手机号" />
</FormItem>
</Col>
<Col :span="12">
<FormItem label="身份证号">
<Input v-model:value="form.id_card" maxlength="18" placeholder="选填" />
</FormItem>
</Col>
<Col :span="12">
<FormItem label="生日" required>
<DatePicker
class="w-full"
:value="form.birthday ? dayjs(form.birthday) : undefined"
value-format="YYYY-MM-DD"
placeholder="请选择"
@change="onBirthdayChange"
/>
</FormItem>
</Col>
<Col :span="8">
<FormItem label="性别" required>
<RadioGroup v-model:value="form.sex">
<Radio :value="1"></Radio>
<Radio :value="2"></Radio>
</RadioGroup>
</FormItem>
</Col>
<Col :span="8">
<FormItem label="年龄" required>
<InputNumber v-model:value="form.age" class="w-full" :min="1" :max="200" />
</FormItem>
</Col>
<Col :span="8">
<FormItem label="关系">
<Select v-model:value="form.relation" :options="relationOptions" />
</FormItem>
</Col>
<Col :span="24">
<FormItem label="免除挂号费">
<Switch v-model:checked="form.waive_register_fee" checked-children="免除" un-checked-children="收取" />
<span class="ml-2 text-xs text-[hsl(var(--muted-foreground))]">
开启后挂号费为 0 且标记已支付可直接接诊
</span>
</FormItem>
</Col>
</Row>
<template v-if="showGuardian">
<div class="mb-3 mt-2 text-sm font-medium text-[hsl(var(--foreground))]">监护人信息</div>
<Row :gutter="12">
<Col :span="8">
<FormItem label="监护人姓名" required>
<Input v-model:value="form.guardian_name" />
</FormItem>
</Col>
<Col :span="8">
<FormItem label="监护人身份证" required>
<Input v-model:value="form.guardian_id_card" maxlength="18" />
</FormItem>
</Col>
<Col :span="8">
<FormItem label="监护人手机" required>
<Input v-model:value="form.guardian_mobile" maxlength="11" />
</FormItem>
</Col>
</Row>
</template>
<div class="mb-3 mt-2 text-sm font-medium text-[hsl(var(--foreground))]">健康信息</div>
<Row :gutter="12">
<Col :span="8">
<FormItem label="肝功能异常">
<Select v-model:value="form.liver_function" :options="yesNoOptions" />
</FormItem>
</Col>
<Col :span="8">
<FormItem label="肾功能异常">
<Select v-model:value="form.renal_function" :options="yesNoOptions" />
</FormItem>
</Col>
<Col :span="8">
<FormItem label="既往史">
<Select v-model:value="form.person_status" :options="yesNoOptions" />
</FormItem>
</Col>
<Col :span="8">
<FormItem label="过敏史">
<Select v-model:value="form.allergic_status" :options="yesNoOptions" />
</FormItem>
</Col>
<Col :span="8">
<FormItem label="家族史">
<Select v-model:value="form.family_status" :options="yesNoOptions" />
</FormItem>
</Col>
<Col :span="8">
<FormItem label="现病史">
<Select v-model:value="form.present_status" :options="yesNoOptions" />
</FormItem>
</Col>
<Col :span="12">
<FormItem label="过敏史说明">
<Input v-model:value="form.allergic_history" placeholder="有则填写" />
</FormItem>
</Col>
<Col :span="12">
<FormItem label="既往史说明">
<Input v-model:value="form.person_history" placeholder="有则填写" />
</FormItem>
</Col>
</Row>
</Form>
</Modal>
</template>

View File

@@ -37,6 +37,8 @@ type DrawerData = {
allowWithdraw?: boolean;
/** 撤回成功后刷新 */
onWithdrawn?: () => void;
/** 医生代支付:打开小程序码 */
onProxyPay?: (orderId: number) => void;
};
const meta = ref<DrawerData>({});
@@ -262,6 +264,20 @@ defineExpose({ drawerApi });
>
复用
</Button>
<Button
v-if="
listTab === 'current' &&
row.order_id &&
(row.can_withdraw ||
String(row.order_pay_text || '').includes('待支付') ||
Number(row.order_is_pay) === 0)
"
type="link"
size="small"
@click="meta.onProxyPay?.(Number(row.order_id))"
>
代支付
</Button>
<Button
v-if="meta.allowWithdraw && row.can_withdraw && listTab === 'current'"
type="link"

View File

@@ -0,0 +1,74 @@
<script lang="ts" setup>
/**
* 医生代支付:展示患者端小程序码,医生用患者端扫码后 JSAPI 付款
*/
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Empty, message, Spin } from 'ant-design-vue';
import { getProxyPayQrcodeApi } from '#/views/doctor/doctor-reception/api';
const loading = ref(false);
const qrcode = ref('');
const orderNo = ref('');
const totalPayPrice = ref('');
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
showConfirmButton: false,
cancelText: '关闭',
// 从处方记录抽屉内打开:抽屉默认 zIndex=1000弹窗需更高否则被盖住
zIndex: 2000,
onCancel() {
modalApi.close();
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) return;
qrcode.value = '';
orderNo.value = '';
totalPayPrice.value = '';
const orderId = Number(modalApi.getData()?.order_id || 0);
if (orderId < 1) {
message.error('订单ID无效');
return;
}
loading.value = true;
try {
const res = await getProxyPayQrcodeApi(orderId);
qrcode.value = res?.qrcode || '';
orderNo.value = res?.order_no || '';
totalPayPrice.value = res?.total_pay_price || '';
if (!qrcode.value) {
message.error('代支付码生成失败');
}
} catch (e: any) {
message.error(e?.message || '生成代支付码失败');
} finally {
loading.value = false;
}
},
});
</script>
<template>
<Modal class="w-[420px]" title="医生代支付">
<Spin :spinning="loading">
<div
v-if="qrcode"
class="flex flex-col items-center rounded border border-[hsl(var(--border))] bg-[hsl(var(--card,var(--background)))] p-4"
>
<div class="mb-1 text-sm text-[hsl(var(--foreground))]">
订单{{ orderNo || '—' }}
</div>
<div class="mb-3 text-sm text-[hsl(var(--muted-foreground))]">
应付¥{{ totalPayPrice || '0.00' }} · 请用患者端小程序扫码支付
</div>
<img :src="qrcode" alt="代支付二维码" class="h-[220px] w-[220px]" />
</div>
<Empty v-else-if="!loading" description="暂无二维码" />
</Spin>
</Modal>
</template>

View File

@@ -14,7 +14,10 @@ import {
MenuFoldOutlined,
MenuUnfoldOutlined,
PlusOutlined,
QrcodeOutlined,
ReloadOutlined,
RobotOutlined,
UserAddOutlined,
SaveOutlined,
StopOutlined,
SwapOutlined,
@@ -88,6 +91,9 @@ import WesternModal from './components/WesternModal.vue';
import SimpleProductModal from './components/SimpleProductModal.vue';
import RefusalOfTreatmentModal
from "#/views/doctor/doctor-reception/components/RefusalOfTreatmentModal.vue";
import CreateUserPatientModal from './components/CreateUserPatientModal.vue';
import ClaimQrcodeModal from './components/ClaimQrcodeModal.vue';
import ProxyPayQrcodeModal from './components/ProxyPayQrcodeModal.vue';
// 常用方选择弹窗组件
import CommonPrescriptionModal from './components/CommonPrescriptionModal.vue';
import GoldenFormulaModal from './components/GoldenFormulaModal.vue';
@@ -338,15 +344,27 @@ const salespersonTransferPrescriptionId = ref(0);
const salespersonTransferDrawerOpen = ref(false);
const hasSalespersonTransfer = ref(false);
/** 当前接诊挂号 ID模板中不可直接使用 localStorage */
/**
* 当前接诊挂号 ID
* 优先用响应式的 selectPatientId勿只读 localStoragecomputed 不追踪 storage会卡死旧挂号 id
*/
const doctorReceptionRegisterId = computed(() => {
if (selectPatientId.value > 0) {
return selectPatientId.value;
}
if (typeof window === 'undefined' || !window.localStorage) {
return 0;
}
return Number.parseInt(
window.localStorage.getItem('doctorReception-id') || '0',
10,
);
const raw = window.localStorage.getItem('doctorReception-id') || '0';
// 兼容历史 JSON.stringify(number) 写入
try {
const parsed = JSON.parse(raw);
const n = Number(parsed);
if (Number.isFinite(n) && n > 0) return n;
} catch {
/* ignore */
}
return Number.parseInt(raw, 10) || 0;
});
/** 门店类型接口短时缓存,供毛利率 / 转诊提示共用,避免进页双打 */
@@ -542,8 +560,29 @@ function updatePatientList() {
getPatientListByReception();
}
/** 患者列表手动刷新中(与轮询区分,避免连点) */
const patientListRefreshing = ref(false);
/**
* 手动刷新左侧患者列表
* 为什么单独抽:工具栏刷新需 loading且不改动当前/历史 tab 的 updateTabType 语义
*/
function refreshPatientList() {
if (patientListRefreshing.value) return;
patientListRefreshing.value = true;
getPatientList({
type: listType.value,
})
.then((res) => {
patients.value = res;
})
.finally(() => {
patientListRefreshing.value = false;
});
}
getPatientListByReception();
// 每5秒更新数据
// 每 30 秒轮询刷新患者列表
setInterval(getPatientListByReception, 30_000);
// 药品分类(后端下发;失败时用本地兜底)
@@ -830,11 +869,8 @@ const selectPatient = async (patient: Patient, isUpdateTabType = true) => {
}
});
localStorage.setItem(
`doctorReception-id`,
// JSON.stringify(selectData),
JSON.stringify(patient.id),
);
// 存纯数字字符串,避免 JSON.stringify 造成读取歧义
localStorage.setItem(`doctorReception-id`, String(patient.id));
getCurrentDrugs();
};
@@ -1305,6 +1341,21 @@ const [RefusalOfTreatmentModals, RefusalOfTreatmentModalApi] = useVbenModal({
connectedComponent: RefusalOfTreatmentModal,
});
/** 新建就诊人弹窗 */
const [CreateUserPatientModals, CreateUserPatientModalApi] = useVbenModal({
connectedComponent: CreateUserPatientModal,
});
/** 就诊人认领码弹窗 */
const [ClaimQrcodeModals, ClaimQrcodeModalApi] = useVbenModal({
connectedComponent: ClaimQrcodeModal,
});
/** 医生代支付码弹窗 */
const [ProxyPayQrcodeModals, ProxyPayQrcodeModalApi] = useVbenModal({
connectedComponent: ProxyPayQrcodeModal,
});
// ==================== 常用方弹窗 ====================
const [CommonPrescriptionModals, CommonPrescriptionModalApi] = useVbenModal({
connectedComponent: CommonPrescriptionModal,
@@ -1336,6 +1387,41 @@ function openCommonPrescriptionModal() {
CommonPrescriptionModalApi.open();
}
/** 打开新建就诊人弹窗 */
function openCreateUserPatientModal() {
CreateUserPatientModalApi.setData({
onSuccess: () => refreshPatientList(),
});
CreateUserPatientModalApi.open();
}
/**
* 当前就诊人是否未认领user_id=0
* 认领码入口仅在未认领时出现在右侧工具栏
*/
const isActivePatientUnclaimed = computed(() => {
const up = activePatient.value as any;
if (!up?.id) return false;
return Number(up.user_id || 0) === 0;
});
/** 打开就诊人认领二维码(优先当前选中患者) */
function openClaimQrcodeModal() {
const upId = Number(activePatient.value?.id || patientInfo.value?.user_patient_id || 0);
ClaimQrcodeModalApi.setData({ user_patient_id: upId || undefined });
ClaimQrcodeModalApi.open();
}
/** 打开医生代支付小程序码 */
function openProxyPayQrcode(orderId: number) {
if (!orderId) {
message.error('订单ID无效');
return;
}
ProxyPayQrcodeModalApi.setData({ order_id: orderId });
ProxyPayQrcodeModalApi.open();
}
/**
* 打开金方导入弹窗VIPgolden_formula仅中药处方可用
*/
@@ -2374,11 +2460,18 @@ function openPatientMrCiteDrawer() {
/** 打开处方记录抽屉 */
function openPatientRxHistoryDrawer() {
// 挂号 id 必须用 getRegisterId当前选中挂号禁止用就诊人 id 冒充
const registerId = getRegisterId();
if (!registerId) {
message.warning('请先选择接诊患者');
return;
}
patientRxHistoryDrawerApi.setData({
registerId: Number(doctorReceptionRegisterId.value || selectPatientId.value || 0),
registerId,
userPatientId: Number(activePatient.value?.id || 0),
patientName: activePatient.value?.name,
allowWithdraw: true,
onProxyPay: openProxyPayQrcode,
onReuse: reusePrescriptionFromList,
// 点击处方号打开详情(与医生名片/患者管理一致)
onDetail: (prescriptionId: number) => openPrescriptionDetail(prescriptionId),
@@ -3121,6 +3214,9 @@ function onStoreSelectOpenChange(open: boolean) {
<template>
<div class="reception-layout" :class="{ 'is-left-collapsed': !leftShow }">
<RefusalOfTreatmentModals />
<CreateUserPatientModals />
<ClaimQrcodeModals />
<ProxyPayQrcodeModals />
<InfoModalComponent />
<!-- 左侧患者列表悬停展开移出收起收起态仅箭头+背景提示 -->
<aside
@@ -3162,15 +3258,33 @@ function onStoreSelectOpenChange(open: boolean) {
{{ item.name }}
</SelectOption>
</Select>
<RadioGroup
v-model:value="listType"
class="w-full"
style="text-align: center"
@change="updatePatientList"
>
<RadioButton :value="1" class="w-1/2">当前</RadioButton>
<RadioButton :value="2" class="w-1/2">历史</RadioButton>
</RadioGroup>
<div class="patient-list-toolbar flex items-center gap-1">
<RadioGroup
v-model:value="listType"
class="min-w-0 flex-1"
style="text-align: center"
@change="updatePatientList"
>
<RadioButton :value="1" class="w-1/2">当前</RadioButton>
<RadioButton :value="2" class="w-1/2">历史</RadioButton>
</RadioGroup>
<Button
type="text"
size="small"
class="patient-list-refresh-btn shrink-0"
title="刷新患者列表"
:loading="patientListRefreshing"
@click="refreshPatientList"
>
<ReloadOutlined />
</Button>
</div>
<div class="patient-list-actions mb-2 mt-1 flex flex-wrap gap-1">
<Button size="small" type="primary" ghost @click="openCreateUserPatientModal">
<UserAddOutlined />
就诊人
</Button>
</div>
<div
v-for="(patient, index) in patients"
:key="index"
@@ -3244,6 +3358,15 @@ function onStoreSelectOpenChange(open: boolean) {
<Button type="link" size="small" @click="openPatientInfoDrawer">患者信息</Button>
<Button type="link" size="small" @click="openPatientMrCiteDrawer">患者病历</Button>
<Button type="link" size="small" @click="openPatientRxHistoryDrawer">处方记录</Button>
<Button
v-if="isActivePatientUnclaimed"
type="link"
size="small"
@click="openClaimQrcodeModal"
>
<QrcodeOutlined />
认领码
</Button>
</div>
<SpecialPrescriptionImportCard
v-if="specialPrescriptionRecord && hasSpecialPrescriptionRecord"
@@ -3296,6 +3419,15 @@ function onStoreSelectOpenChange(open: boolean) {
<MedicineBoxOutlined />
处方记录
</Button>
<Button
v-if="isActivePatientUnclaimed"
type="link"
size="small"
@click="openClaimQrcodeModal"
>
<QrcodeOutlined />
认领码
</Button>
<Button
v-if="receptionStatus === 2"
type="link"