fix:
1. 优化了审方pc的效果 2. 就诊人信息优化
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 处方溯源展示内容(对齐小程序药师溯源:就诊→开具→审核)
|
||||
* 由审方/业务处方两个 Modal 共用,避免双份漂移
|
||||
*/
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Timeline, TimelineItem } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
FileTextOutlined,
|
||||
MedicineBoxOutlined,
|
||||
ShopOutlined,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
const props = defineProps<{
|
||||
data: Record<string, any> | null;
|
||||
}>();
|
||||
|
||||
const formatTime = (timestamp: unknown) => {
|
||||
if (!timestamp) return '--';
|
||||
if (typeof timestamp === 'string' && timestamp.includes('-')) return timestamp;
|
||||
const n = Number(timestamp);
|
||||
if (!n) return '--';
|
||||
return new Date(n * 1000).toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
});
|
||||
};
|
||||
|
||||
const prescriptionTypeMap: Record<number, string> = {
|
||||
1: '中药处方',
|
||||
2: '西药处方',
|
||||
3: '保健食品',
|
||||
5: '产品服务包',
|
||||
6: '非药品',
|
||||
7: '医疗器械',
|
||||
};
|
||||
|
||||
const statusMap: Record<number, string> = {
|
||||
0: '待审核',
|
||||
1: '已通过',
|
||||
2: '已拒绝',
|
||||
3: '已过期',
|
||||
};
|
||||
|
||||
const prescriptionTypeText = computed(() => {
|
||||
if (!props.data) return '--';
|
||||
return prescriptionTypeMap[props.data.prescription_type] || '未知';
|
||||
});
|
||||
|
||||
const statusText = computed(() => {
|
||||
if (!props.data) return '--';
|
||||
return statusMap[props.data.status] || '未知';
|
||||
});
|
||||
|
||||
const statusColor = computed(() => {
|
||||
if (!props.data) return 'gray';
|
||||
const map: Record<number, string> = {
|
||||
0: 'orange',
|
||||
1: 'green',
|
||||
2: 'red',
|
||||
3: 'gray',
|
||||
};
|
||||
return map[props.data.status] || 'gray';
|
||||
});
|
||||
|
||||
const expireTime = computed(() =>
|
||||
props.data ? formatTime(props.data.auto_expire_time) : '--',
|
||||
);
|
||||
|
||||
const patientSexText = computed(() => {
|
||||
const sex = props.data?.user_patient?.sex;
|
||||
if (sex === 1) return '男';
|
||||
if (sex === 2) return '女';
|
||||
return '--';
|
||||
});
|
||||
|
||||
const doctorInfo = computed(
|
||||
() => props.data?.doctor_info || props.data?.doctorInfo || null,
|
||||
);
|
||||
const pharmacistInfo = computed(
|
||||
() => props.data?.pharmacist_info || props.data?.pharmacistInfo || null,
|
||||
);
|
||||
const userPatient = computed(
|
||||
() => props.data?.user_patient || props.data?.userPatient || null,
|
||||
);
|
||||
const registerInfo = computed(
|
||||
() => props.data?.register || null,
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="data" class="prescription-source-container">
|
||||
<div class="mb-6 rounded-lg p-6 shadow-md">
|
||||
<div class="mb-4 flex items-center">
|
||||
<ShopOutlined class="mr-2 text-xl text-purple-500" />
|
||||
<h2 class="text-xl font-bold">开具机构</h2>
|
||||
</div>
|
||||
<div v-if="data.store" class="grid grid-cols-1 gap-4">
|
||||
<div class="info-item">
|
||||
<span class="label">机构名称:</span>
|
||||
<span class="value">{{ data.store.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="italic text-gray-500">暂无机构信息</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 rounded-lg p-6 shadow-md">
|
||||
<div class="mb-4 flex items-center">
|
||||
<FileTextOutlined class="mr-2 text-xl text-blue-500" />
|
||||
<h2 class="text-xl font-bold">时间线</h2>
|
||||
</div>
|
||||
<Timeline>
|
||||
<TimelineItem>
|
||||
<div class="font-medium">就诊</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
{{ formatTime(registerInfo?.created_at) }}
|
||||
· {{ userPatient?.name || '--' }}
|
||||
</div>
|
||||
<div class="mt-1 text-sm">
|
||||
性别:{{ patientSexText }} · 年龄:{{ userPatient?.age ?? '--' }}
|
||||
</div>
|
||||
<div v-if="data.clinical_diagnose" class="mt-1 text-sm">
|
||||
诊断:{{ data.clinical_diagnose }}
|
||||
</div>
|
||||
</TimelineItem>
|
||||
<TimelineItem>
|
||||
<div class="font-medium">开具</div>
|
||||
<div class="text-sm text-gray-500">{{ data.created_at || '--' }}</div>
|
||||
<div class="mt-1 text-sm">
|
||||
医生:{{ doctorInfo?.name || '--' }} · 科室:{{
|
||||
doctorInfo?.depart?.name || '--'
|
||||
}}
|
||||
</div>
|
||||
<div class="mt-1 text-sm">
|
||||
处方编号:{{ data.prescription_no }} · 类型:{{ prescriptionTypeText }}
|
||||
</div>
|
||||
</TimelineItem>
|
||||
<TimelineItem>
|
||||
<div class="font-medium">审核</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
{{ data.pharmacist_view_time || '--' }}
|
||||
</div>
|
||||
<div class="mt-1 text-sm">
|
||||
状态:
|
||||
<span :class="`text-${statusColor}-500`">{{ statusText }}</span>
|
||||
· 药师:{{ pharmacistInfo?.name || '--' }}
|
||||
</div>
|
||||
<div v-if="data.reject_reason" class="mt-1 text-sm text-red-500">
|
||||
驳回原因:{{ data.reject_reason }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="data.cancel_remark && Number(data.status) === 2"
|
||||
class="mt-1 text-sm text-red-500"
|
||||
>
|
||||
驳回原因:{{ data.cancel_remark }}
|
||||
</div>
|
||||
</TimelineItem>
|
||||
</Timeline>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 rounded-lg p-6 shadow-md">
|
||||
<div class="mb-4 flex items-center">
|
||||
<FileTextOutlined class="mr-2 text-xl text-blue-500" />
|
||||
<h2 class="text-xl font-bold">处方基本信息</h2>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div class="info-item">
|
||||
<span class="label">处方编号:</span>
|
||||
<span class="value">{{ data.prescription_no }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">处方类型:</span>
|
||||
<span class="value">{{ prescriptionTypeText }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">处方状态:</span>
|
||||
<span :class="`text-${statusColor}-500`" class="value">{{
|
||||
statusText
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">总金额:</span>
|
||||
<span class="value font-bold text-red-500"
|
||||
>¥{{ data.total_pay_price }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">过期时间:</span>
|
||||
<span class="value">{{ expireTime }}</span>
|
||||
</div>
|
||||
<div v-if="data.reject_reason" class="info-item">
|
||||
<span class="label">驳回原因:</span>
|
||||
<span class="value text-red-500">{{ data.reject_reason }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 rounded-lg p-6 shadow-md">
|
||||
<div class="mb-4 flex items-center">
|
||||
<MedicineBoxOutlined class="mr-2 text-xl text-green-500" />
|
||||
<h2 class="text-xl font-bold">医生信息</h2>
|
||||
</div>
|
||||
<div v-if="doctorInfo" class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="info-item">
|
||||
<span class="label">开具医生:</span>
|
||||
<span class="value">{{ doctorInfo.name }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">开具科室:</span>
|
||||
<span class="value">{{ doctorInfo.depart?.name || '--' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">开具机构:</span>
|
||||
<span class="value">{{ data.store?.name || '--' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">开具时间:</span>
|
||||
<span class="value">{{ data.created_at || '--' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="italic text-gray-500">暂无医生信息</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg p-6 shadow-md">
|
||||
<div class="mb-4 flex items-center">
|
||||
<UserOutlined class="mr-2 text-xl text-amber-500" />
|
||||
<h2 class="text-xl font-bold">患者信息</h2>
|
||||
</div>
|
||||
<div
|
||||
v-if="userPatient"
|
||||
class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"
|
||||
>
|
||||
<div class="info-item">
|
||||
<span class="label">患者姓名:</span>
|
||||
<span class="value">{{ userPatient.name }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">年龄:</span>
|
||||
<span class="value">{{ userPatient.age }}岁</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">性别:</span>
|
||||
<span class="value">{{ patientSexText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="italic text-gray-500">暂无患者信息</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex h-64 items-center justify-center">
|
||||
<div
|
||||
class="h-12 w-12 animate-spin rounded-full border-b-2 border-t-2 border-blue-500"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.prescription-source-container {
|
||||
@apply max-h-[70vh] overflow-auto p-4;
|
||||
}
|
||||
.info-item {
|
||||
@apply flex flex-col rounded-md p-3;
|
||||
}
|
||||
.label {
|
||||
@apply mb-1 text-sm text-gray-500;
|
||||
}
|
||||
.value {
|
||||
@apply font-medium;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,451 @@
|
||||
<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>
|
||||
@@ -300,13 +300,15 @@ const orderColumns = [
|
||||
/>
|
||||
<span v-else>—</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="身份证" :span="2">
|
||||
<Descriptions.Item
|
||||
v-if="profile.patient?.id_card"
|
||||
label="身份证"
|
||||
:span="2"
|
||||
>
|
||||
<SensitiveText
|
||||
v-if="profile.patient"
|
||||
:record="profile.patient"
|
||||
field="id_card"
|
||||
/>
|
||||
<span v-else>—</span>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Tabs.TabPane>
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 某微信用户下的就诊人列表 Modal
|
||||
* 点行再打开就诊人详情 Modal(由父级或本组件内嵌 DetailModal 处理)
|
||||
*/
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Avatar, Button, Empty, Spin, Table, message } from 'ant-design-vue';
|
||||
|
||||
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
|
||||
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
|
||||
|
||||
import { getPatientListByUserApi } from './api';
|
||||
import WxUserPatientDetailModal from './WxUserPatientDetailModal.vue';
|
||||
|
||||
defineOptions({ name: 'WxUserPatientsModal' });
|
||||
|
||||
const defaultAvatar = '/img/user-default-avatar.png';
|
||||
|
||||
const userId = ref(0);
|
||||
const userNickname = ref('');
|
||||
const userAvatar = ref('');
|
||||
const loading = ref(false);
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
const [DetailModal, DetailModalApi] = useVbenModal({
|
||||
connectedComponent: WxUserPatientDetailModal,
|
||||
});
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
footer: false,
|
||||
class: 'w-[640px]',
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
list.value = [];
|
||||
return;
|
||||
}
|
||||
const data = modalApi.getData<{
|
||||
userId?: number;
|
||||
nickname?: string;
|
||||
avatarurl?: string;
|
||||
}>();
|
||||
userId.value = Number(data?.userId || 0);
|
||||
userNickname.value = String(data?.nickname || '');
|
||||
userAvatar.value = String(data?.avatarurl || '');
|
||||
modalApi.setState({
|
||||
title: userNickname.value
|
||||
? `就诊人列表 · ${userNickname.value}`
|
||||
: '就诊人列表',
|
||||
});
|
||||
if (userId.value > 0) {
|
||||
void loadList();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function avatarSrc(raw?: string) {
|
||||
const s = String(raw ?? '').trim();
|
||||
if (!s) return defaultAvatar;
|
||||
return resolveAvatarUrl(s) || defaultAvatar;
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getPatientListByUserApi(userId.value);
|
||||
list.value = res?.items ?? [];
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
message.error('加载就诊人失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开就诊人详情 Modal(与微信用户列表分离) */
|
||||
function openPatientDetail(row: Record<string, any>) {
|
||||
const upId = Number(row.up_id || 0);
|
||||
if (!upId) {
|
||||
message.warning('缺少就诊人信息');
|
||||
return;
|
||||
}
|
||||
DetailModalApi.setData({
|
||||
upId,
|
||||
patientName: row.name || '',
|
||||
});
|
||||
DetailModalApi.open();
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{ title: '姓名', key: 'name' },
|
||||
{ title: '性别', key: 'sex', width: 80 },
|
||||
{ title: '手机', key: 'mobile' },
|
||||
{ title: '操作', key: 'action', width: 100 },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal>
|
||||
<DetailModal />
|
||||
<div class="mb-3 flex items-center gap-2">
|
||||
<Avatar :size="36" :src="avatarSrc(userAvatar)" />
|
||||
<div class="min-w-0 text-sm">
|
||||
<div class="font-medium">{{ userNickname || '—' }}</div>
|
||||
<div class="text-xs text-gray-500">用户 ID:{{ userId || '—' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Spin :spinning="loading">
|
||||
<Table
|
||||
v-if="list.length"
|
||||
:columns="columns"
|
||||
:data-source="list"
|
||||
:pagination="false"
|
||||
row-key="up_id"
|
||||
size="small"
|
||||
:custom-row="
|
||||
(record) => ({
|
||||
onClick: () => openPatientDetail(record),
|
||||
style: { cursor: 'pointer' },
|
||||
})
|
||||
"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
{{ record.name || '—' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'sex'">
|
||||
<template v-if="record.sex === 1">男</template>
|
||||
<template v-else-if="record.sex === 2">女</template>
|
||||
<template v-else>—</template>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'mobile'">
|
||||
<SensitiveText :record="record" field="mobile" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<Button type="link" @click.stop="openPatientDetail(record)">
|
||||
详情
|
||||
</Button>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
<Empty v-else-if="!loading" description="暂无就诊人" />
|
||||
</Spin>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -5,6 +5,31 @@ import { requestClient } from '#/api/request';
|
||||
|
||||
const prefix = 'user-patient-profile/';
|
||||
|
||||
/** 用户管理列表(微信用户 + 就诊人,旧接口) */
|
||||
export async function getUserPatientProfileListApi(params?: {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
keyword?: string;
|
||||
}) {
|
||||
return requestClient.get<any>(`${prefix}list`, { params });
|
||||
}
|
||||
|
||||
/** 会员管理:微信用户分页 */
|
||||
export async function getWxUserListApi(params?: {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
keyword?: string;
|
||||
}) {
|
||||
return requestClient.get<any>(`${prefix}user-list`, { params });
|
||||
}
|
||||
|
||||
/** 某微信用户下的就诊人列表 */
|
||||
export async function getPatientListByUserApi(userId: number) {
|
||||
return requestClient.get<any>(`${prefix}patient-list-by-user`, {
|
||||
params: { user_id: userId },
|
||||
});
|
||||
}
|
||||
|
||||
/** 小程序用户 + 就诊人基础信息 */
|
||||
export async function getUserPatientProfileDetailApi(upId: number) {
|
||||
return requestClient.get<any>(`${prefix}detail`, {
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
*/
|
||||
export { default as WxUserPatientCell } from './WxUserPatientCell.vue';
|
||||
export { default as WxUserPatientDrawer } from './WxUserPatientDrawer.vue';
|
||||
export { default as WxUserPatientDetailModal } from './WxUserPatientDetailModal.vue';
|
||||
export { default as WxUserPatientsModal } from './WxUserPatientsModal.vue';
|
||||
export { default as VisitTypeTag } from './VisitTypeTag.vue';
|
||||
export { default as PrescriptionExpireTime } from './PrescriptionExpireTime.vue';
|
||||
export * from './api';
|
||||
|
||||
Reference in New Issue
Block a user