452 lines
14 KiB
Vue
452 lines
14 KiB
Vue
<script lang="ts" setup>
|
||
/**
|
||
* 就诊人详情 Modal(患者视角)
|
||
* 与 Drawer 内容一致:资料 + 挂号/处方/订单;会员管理、订单「就诊人」入口使用
|
||
*/
|
||
import { h, ref } from 'vue';
|
||
|
||
import { useVbenModal } from '@vben/common-ui';
|
||
|
||
import {
|
||
Avatar,
|
||
Button,
|
||
Descriptions,
|
||
Empty,
|
||
Spin,
|
||
Table,
|
||
Tabs,
|
||
Tag,
|
||
message,
|
||
} from 'ant-design-vue';
|
||
|
||
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
|
||
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
|
||
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
|
||
import OrderDetail from '#/views/business/order/product-order/components/detail.vue';
|
||
|
||
import {
|
||
getUserPatientOrderListApi,
|
||
getUserPatientPrescriptionListApi,
|
||
getUserPatientProfileDetailApi,
|
||
getUserPatientRegisterListApi,
|
||
} from './api';
|
||
import VisitTypeTag from './VisitTypeTag.vue';
|
||
|
||
defineOptions({ name: 'WxUserPatientDetailModal' });
|
||
|
||
const defaultAvatar = '/img/user-default-avatar.png';
|
||
|
||
const upId = ref(0);
|
||
const loading = ref(false);
|
||
const activeTab = ref('info');
|
||
const profile = ref<Record<string, any> | null>(null);
|
||
|
||
const registerList = ref<any[]>([]);
|
||
const prescriptionList = ref<any[]>([]);
|
||
const orderList = ref<any[]>([]);
|
||
const registerLoading = ref(false);
|
||
const prescriptionLoading = ref(false);
|
||
const orderLoading = ref(false);
|
||
|
||
const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
|
||
connectedComponent: PrescriptionDetail,
|
||
});
|
||
|
||
const [OrderDetailModal, OrderDetailModalApi] = useVbenModal({
|
||
connectedComponent: OrderDetail,
|
||
});
|
||
|
||
const [Modal, modalApi] = useVbenModal({
|
||
footer: false,
|
||
class: 'w-[760px]',
|
||
onOpenChange(isOpen: boolean) {
|
||
if (!isOpen) {
|
||
profile.value = null;
|
||
registerList.value = [];
|
||
prescriptionList.value = [];
|
||
orderList.value = [];
|
||
return;
|
||
}
|
||
const data = modalApi.getData<{ upId?: number; patientName?: string }>();
|
||
upId.value = Number(data?.upId || 0);
|
||
activeTab.value = 'info';
|
||
const name = String(data?.patientName || '').trim();
|
||
modalApi.setState({ title: name ? `就诊人:${name}` : '就诊人详情' });
|
||
if (upId.value > 0) {
|
||
void loadDetail();
|
||
}
|
||
},
|
||
});
|
||
|
||
/** 解析头像 URL */
|
||
function avatarSrc(raw?: string) {
|
||
const s = String(raw ?? '').trim();
|
||
if (!s) return defaultAvatar;
|
||
return resolveAvatarUrl(s) || defaultAvatar;
|
||
}
|
||
|
||
/** 既往/过敏/家族:0 无,否则展示 history */
|
||
function historyText(status?: number, history?: string) {
|
||
if (Number(status) === 0) return '无';
|
||
const t = String(history || '').trim();
|
||
return t || '有';
|
||
}
|
||
|
||
/** 肝/肾功能:0 正常,异常时附带指标文案 */
|
||
function functionText(flag?: number, indexText?: string) {
|
||
if (Number(flag) === 0) return '正常';
|
||
const t = String(indexText || '').trim();
|
||
return t ? `异常 · ${t}` : '异常';
|
||
}
|
||
|
||
async function loadDetail() {
|
||
loading.value = true;
|
||
try {
|
||
profile.value = await getUserPatientProfileDetailApi(upId.value);
|
||
} catch (e) {
|
||
console.error(e);
|
||
message.error('获取用户信息失败');
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
async function loadRegisterList() {
|
||
registerLoading.value = true;
|
||
try {
|
||
const res = await getUserPatientRegisterListApi(upId.value);
|
||
registerList.value = res?.items ?? [];
|
||
} catch (e) {
|
||
console.error(e);
|
||
message.error('获取挂号记录失败');
|
||
} finally {
|
||
registerLoading.value = false;
|
||
}
|
||
}
|
||
|
||
async function loadPrescriptionList() {
|
||
prescriptionLoading.value = true;
|
||
try {
|
||
const res = await getUserPatientPrescriptionListApi(upId.value);
|
||
prescriptionList.value = res?.items ?? [];
|
||
} catch (e) {
|
||
console.error(e);
|
||
message.error('获取处方记录失败');
|
||
} finally {
|
||
prescriptionLoading.value = false;
|
||
}
|
||
}
|
||
|
||
async function loadOrderList() {
|
||
orderLoading.value = true;
|
||
try {
|
||
const res = await getUserPatientOrderListApi(upId.value);
|
||
orderList.value = res?.items ?? [];
|
||
} catch (e) {
|
||
console.error(e);
|
||
message.error('获取订单记录失败');
|
||
} finally {
|
||
orderLoading.value = false;
|
||
}
|
||
}
|
||
|
||
/** Tab 切换时懒加载对应列表 */
|
||
function onTabChange(key: string | number) {
|
||
const k = String(key);
|
||
activeTab.value = k;
|
||
if (k === 'register' && registerList.value.length === 0) {
|
||
void loadRegisterList();
|
||
} else if (k === 'prescription' && prescriptionList.value.length === 0) {
|
||
void loadPrescriptionList();
|
||
} else if (k === 'order' && orderList.value.length === 0) {
|
||
void loadOrderList();
|
||
}
|
||
}
|
||
|
||
function openPrescription(id: number) {
|
||
PrescriptionDetailModalApi.setData({ values: id });
|
||
PrescriptionDetailModalApi.open();
|
||
}
|
||
|
||
function openOrder(id: number) {
|
||
OrderDetailModalApi.setData({ id });
|
||
OrderDetailModalApi.open();
|
||
}
|
||
|
||
const registerColumns = [
|
||
{ title: '订单编号', dataIndex: 'order_no', key: 'order_no' },
|
||
{ title: '医生', dataIndex: 'doctor', key: 'doctor' },
|
||
{ title: '诊所', dataIndex: 'store', key: 'store' },
|
||
{
|
||
title: '费用',
|
||
dataIndex: 'price',
|
||
key: 'price',
|
||
customRender: ({ text }: { text: number }) =>
|
||
`¥${Number(text || 0).toFixed(2)}`,
|
||
},
|
||
{
|
||
title: '支付',
|
||
dataIndex: 'is_pay',
|
||
key: 'is_pay',
|
||
customRender: ({ text }: { text: number }) =>
|
||
h(Tag, { color: text === 1 ? 'green' : 'red' }, () =>
|
||
text === 1 ? '已支付' : '未支付',
|
||
),
|
||
},
|
||
{ title: '状态', dataIndex: 'status_text', key: 'status_text' },
|
||
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at' },
|
||
];
|
||
|
||
const prescriptionColumns = [
|
||
{
|
||
title: '处方编号',
|
||
dataIndex: 'prescription_no',
|
||
key: 'prescription_no',
|
||
customRender: ({ text, record }: { text: string; record: any }) =>
|
||
h(
|
||
Button,
|
||
{ type: 'link', onClick: () => openPrescription(record.id) },
|
||
() => text,
|
||
),
|
||
},
|
||
{
|
||
title: '就诊类型',
|
||
dataIndex: 'is_online',
|
||
key: 'is_online',
|
||
customRender: ({ text }: { text: number }) =>
|
||
h(VisitTypeTag, { isOnline: text }),
|
||
},
|
||
{ title: '医生', dataIndex: 'doctor', key: 'doctor' },
|
||
{ title: '诊所', dataIndex: 'store', key: 'store' },
|
||
{ title: '诊断', dataIndex: 'clinical_diagnose', key: 'clinical_diagnose' },
|
||
{
|
||
title: '状态',
|
||
dataIndex: 'status',
|
||
key: 'status',
|
||
customRender: ({ text }: { text: number }) => {
|
||
const map: Record<number, string> = {
|
||
0: '待审核',
|
||
1: '已通过',
|
||
2: '未通过',
|
||
3: '无需审核',
|
||
};
|
||
return map[text] || '未知';
|
||
},
|
||
},
|
||
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at' },
|
||
];
|
||
|
||
const orderColumns = [
|
||
{
|
||
title: '订单号',
|
||
dataIndex: 'order_no',
|
||
key: 'order_no',
|
||
customRender: ({ text, record }: { text: string; record: any }) =>
|
||
h(Button, { type: 'link', onClick: () => openOrder(record.id) }, () => text),
|
||
},
|
||
{
|
||
title: '就诊类型',
|
||
dataIndex: 'is_online',
|
||
key: 'is_online',
|
||
customRender: ({ text }: { text: number }) =>
|
||
h(VisitTypeTag, { isOnline: text }),
|
||
},
|
||
{ title: '医生', dataIndex: 'doctor', key: 'doctor' },
|
||
{ title: '诊所', dataIndex: 'store', key: 'store' },
|
||
{
|
||
title: '实付',
|
||
dataIndex: 'total_pay_price',
|
||
key: 'total_pay_price',
|
||
customRender: ({ text }: { text: number }) =>
|
||
`¥${Number(text || 0).toFixed(2)}`,
|
||
},
|
||
{
|
||
title: '支付',
|
||
dataIndex: 'is_pay',
|
||
key: 'is_pay',
|
||
customRender: ({ text }: { text: number }) =>
|
||
h(Tag, { color: text === 1 ? 'green' : 'red' }, () =>
|
||
text === 1 ? '已支付' : '未支付',
|
||
),
|
||
},
|
||
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at' },
|
||
];
|
||
</script>
|
||
|
||
<template>
|
||
<Modal>
|
||
<PrescriptionDetailModal />
|
||
<OrderDetailModal />
|
||
<Spin :spinning="loading">
|
||
<template v-if="profile">
|
||
<div class="mb-4 flex items-center gap-3 rounded-lg bg-gray-50 p-3 dark:bg-slate-800">
|
||
<Avatar :size="48" :src="avatarSrc(profile.user?.avatarurl)" />
|
||
<div class="min-w-0">
|
||
<div class="text-sm font-medium">
|
||
{{ profile.user?.nickname || '—' }}
|
||
</div>
|
||
<div class="text-xs text-gray-500">
|
||
用户 ID:{{ profile.user?.id ?? '—' }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<Tabs :active-key="activeTab" @change="onTabChange">
|
||
<Tabs.TabPane key="info" tab="就诊人信息">
|
||
<Descriptions :column="2" bordered size="small">
|
||
<Descriptions.Item label="姓名">
|
||
{{ profile.patient?.name || '—' }}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="性别">
|
||
{{
|
||
profile.patient?.sex === 1
|
||
? '男'
|
||
: profile.patient?.sex === 2
|
||
? '女'
|
||
: '—'
|
||
}}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="年龄">
|
||
{{ profile.patient?.age ? `${profile.patient.age}岁` : '—' }}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="手机号">
|
||
<SensitiveText
|
||
v-if="profile.patient"
|
||
:record="profile.patient"
|
||
field="mobile"
|
||
/>
|
||
<span v-else>—</span>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item
|
||
v-if="profile.patient?.id_card"
|
||
label="身份证"
|
||
:span="2"
|
||
>
|
||
<SensitiveText
|
||
:record="profile.patient"
|
||
field="id_card"
|
||
/>
|
||
</Descriptions.Item>
|
||
</Descriptions>
|
||
<!-- 健康问诊 / 功能异常(与小程序资料 Tab 一致) -->
|
||
<div class="mt-4">
|
||
<div class="mb-2 text-sm font-medium text-gray-700">健康信息</div>
|
||
<Descriptions
|
||
v-if="profile.health_inquiry"
|
||
:column="2"
|
||
bordered
|
||
size="small"
|
||
>
|
||
<Descriptions.Item label="既往史">
|
||
{{
|
||
historyText(
|
||
profile.health_inquiry.person_status,
|
||
profile.health_inquiry.person_history,
|
||
)
|
||
}}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="过敏史">
|
||
<span
|
||
:class="
|
||
Number(profile.health_inquiry.allergic_status) !== 0
|
||
? 'text-red-600 font-medium'
|
||
: ''
|
||
"
|
||
>
|
||
{{
|
||
historyText(
|
||
profile.health_inquiry.allergic_status,
|
||
profile.health_inquiry.allergic_history,
|
||
)
|
||
}}
|
||
</span>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="家族遗传史">
|
||
{{
|
||
historyText(
|
||
profile.health_inquiry.family_status,
|
||
profile.health_inquiry.family_history,
|
||
)
|
||
}}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="肝功能异常">
|
||
<span
|
||
:class="
|
||
Number(profile.health_inquiry.liver_function) !== 0
|
||
? 'text-red-600 font-medium'
|
||
: ''
|
||
"
|
||
>
|
||
{{
|
||
functionText(
|
||
profile.health_inquiry.liver_function,
|
||
profile.health_inquiry.liver_index,
|
||
)
|
||
}}
|
||
</span>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="肾功能异常" :span="2">
|
||
<span
|
||
:class="
|
||
Number(profile.health_inquiry.renal_function) !== 0
|
||
? 'text-red-600 font-medium'
|
||
: ''
|
||
"
|
||
>
|
||
{{
|
||
functionText(
|
||
profile.health_inquiry.renal_function,
|
||
profile.health_inquiry.renal_index,
|
||
)
|
||
}}
|
||
</span>
|
||
</Descriptions.Item>
|
||
</Descriptions>
|
||
<Empty v-else description="暂无健康问诊记录" />
|
||
</div>
|
||
</Tabs.TabPane>
|
||
<Tabs.TabPane key="register" tab="挂号记录">
|
||
<Spin :spinning="registerLoading">
|
||
<Table
|
||
v-if="registerList.length"
|
||
:columns="registerColumns"
|
||
:data-source="registerList"
|
||
:pagination="false"
|
||
row-key="id"
|
||
size="small"
|
||
/>
|
||
<Empty v-else description="暂无挂号记录" />
|
||
</Spin>
|
||
</Tabs.TabPane>
|
||
<Tabs.TabPane key="prescription" tab="处方记录">
|
||
<Spin :spinning="prescriptionLoading">
|
||
<Table
|
||
v-if="prescriptionList.length"
|
||
:columns="prescriptionColumns"
|
||
:data-source="prescriptionList"
|
||
:pagination="false"
|
||
row-key="id"
|
||
size="small"
|
||
/>
|
||
<Empty v-else description="暂无处方记录" />
|
||
</Spin>
|
||
</Tabs.TabPane>
|
||
<Tabs.TabPane key="order" tab="订单记录">
|
||
<Spin :spinning="orderLoading">
|
||
<Table
|
||
v-if="orderList.length"
|
||
:columns="orderColumns"
|
||
:data-source="orderList"
|
||
:pagination="false"
|
||
row-key="id"
|
||
size="small"
|
||
/>
|
||
<Empty v-else description="暂无订单记录" />
|
||
</Spin>
|
||
</Tabs.TabPane>
|
||
</Tabs>
|
||
</template>
|
||
<Empty v-else-if="!loading" description="暂无数据" />
|
||
</Spin>
|
||
</Modal>
|
||
</template>
|