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
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:
@@ -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>
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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;勿只读 localStorage(computed 不追踪 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开金方导入弹窗(VIP:golden_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"
|
||||
|
||||
Reference in New Issue
Block a user