添加接口
- 体检客户列表 - 体检进度详情 - 客户详情
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import type { ExamClient, ExamModalTab } from '../../data/mockData';
|
||||
import type {
|
||||
CustomerAppointmentInfo,
|
||||
CustomerExamAddItem,
|
||||
CustomerInfo,
|
||||
PhysicalExamProgressItem,
|
||||
} from '../../api';
|
||||
import { getCustomerDetail, getPhysicalExamProgressDetail } from '../../api';
|
||||
import { Button, Input } from '../ui';
|
||||
|
||||
interface ExamModalProps {
|
||||
@@ -43,6 +50,32 @@ export const ExamModal = ({ client, tab, onTabChange, onClose }: ExamModalProps)
|
||||
}
|
||||
};
|
||||
|
||||
const [detailLoading, setDetailLoading] = useState(false);
|
||||
const [customerInfo, setCustomerInfo] = useState<CustomerInfo | null>(null);
|
||||
const [appointmentInfo, setAppointmentInfo] = useState<CustomerAppointmentInfo | null>(null);
|
||||
const [addItemInfoList, setAddItemInfoList] = useState<CustomerExamAddItem[] | null>(null);
|
||||
const [progressList, setProgressList] = useState<PhysicalExamProgressItem[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const physical_exam_id = Number(client.id);
|
||||
if (!physical_exam_id) return;
|
||||
setDetailLoading(true);
|
||||
Promise.all([
|
||||
getCustomerDetail({ physical_exam_id }),
|
||||
getPhysicalExamProgressDetail({ physical_exam_id }),
|
||||
])
|
||||
.then(([detailRes, progressRes]) => {
|
||||
setCustomerInfo(detailRes.Data?.customerInfo ?? null);
|
||||
setAppointmentInfo(detailRes.Data?.appointmentInfo ?? null);
|
||||
setAddItemInfoList(detailRes.Data?.addItemInfoList ?? null);
|
||||
setProgressList(progressRes.Data?.examProgressesList ?? null);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('获取客户详情/进度失败', err);
|
||||
})
|
||||
.finally(() => setDetailLoading(false));
|
||||
}, [client.id]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className='fixed inset-0 z-40 flex items-center justify-center bg-black/50'
|
||||
@@ -121,7 +154,16 @@ export const ExamModal = ({ client, tab, onTabChange, onClose }: ExamModalProps)
|
||||
</div>
|
||||
|
||||
<div className='px-4 py-4 bg-gray-50/60'>
|
||||
{tab === 'detail' && <ExamDetailInfo client={client} />}
|
||||
{tab === 'detail' && (
|
||||
<ExamDetailInfo
|
||||
client={client}
|
||||
customerInfo={customerInfo}
|
||||
appointmentInfo={appointmentInfo}
|
||||
addItemInfoList={addItemInfoList}
|
||||
progressList={progressList}
|
||||
loading={detailLoading}
|
||||
/>
|
||||
)}
|
||||
{tab === 'sign' && <ExamSignPanel />}
|
||||
{tab === 'addon' && <ExamAddonPanel client={client} />}
|
||||
{tab === 'print' && <ExamPrintPanel client={client} />}
|
||||
@@ -364,20 +406,64 @@ const ExamAddonPanel = ({ client }: { client: ExamClient }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ExamDetailInfo = ({ client }: { client: ExamClient }) => {
|
||||
const [phone, setPhone] = useState((client['mobile' as keyof ExamClient] as string | undefined) || '137****9988');
|
||||
const [marital, setMarital] = useState(
|
||||
(client['maritalStatus' as keyof ExamClient] as string | undefined) || '未婚',
|
||||
);
|
||||
const ExamDetailInfo = ({
|
||||
client,
|
||||
customerInfo,
|
||||
appointmentInfo,
|
||||
addItemInfoList,
|
||||
progressList,
|
||||
loading,
|
||||
}: {
|
||||
client: ExamClient;
|
||||
customerInfo: CustomerInfo | null;
|
||||
appointmentInfo: CustomerAppointmentInfo | null;
|
||||
addItemInfoList: CustomerExamAddItem[] | null;
|
||||
progressList: PhysicalExamProgressItem[] | null;
|
||||
loading: boolean;
|
||||
}) => {
|
||||
const basePhone = customerInfo?.phone || (client['mobile' as keyof ExamClient] as string | undefined) || '';
|
||||
const baseMarital =
|
||||
customerInfo?.patient_marital_status_name ||
|
||||
(client['maritalStatus' as keyof ExamClient] as string | undefined) ||
|
||||
'—';
|
||||
const [phone, setPhone] = useState(basePhone || '—');
|
||||
const [marital, setMarital] = useState(baseMarital);
|
||||
const [phoneEditing, setPhoneEditing] = useState(false);
|
||||
const [maritalEditing, setMaritalEditing] = useState(false);
|
||||
|
||||
const customerChannel = client.customerType === '团客' ? '团体客户' : '散客客户';
|
||||
const familyDoctor = (client['familyDoctor' as keyof ExamClient] as string | undefined) || '—';
|
||||
const familyDoctor = customerInfo?.family_doctor_name || (client['familyDoctor' as keyof ExamClient] as string | undefined) || '—';
|
||||
const groupTag = client['groupTag' as keyof ExamClient] || (client.customerType === '团客' ? '团检' : '—');
|
||||
const bookingTime = client['bookingTime' as keyof ExamClient] || '—';
|
||||
const signTime = client['signTime' as keyof ExamClient] || '—';
|
||||
const addonSummary = client['addonSummary' as keyof ExamClient] || '—';
|
||||
const bookingTime = appointmentInfo?.appointment_time || (client['bookingTime' as keyof ExamClient] || '—');
|
||||
const signTime = appointmentInfo?.sign_in_time || (client['signTime' as keyof ExamClient] || '—');
|
||||
const addonSummary =
|
||||
addItemInfoList && addItemInfoList.length > 0
|
||||
? addItemInfoList.map((i) => `${i.dept_name ?? ''} ${i.combination_name ?? ''}`.trim()).join('、')
|
||||
: client['addonSummary' as keyof ExamClient] || '—';
|
||||
|
||||
const progressGroups = useMemo(() => {
|
||||
const checked: string[] = [];
|
||||
const abandoned: string[] = [];
|
||||
const pending: string[] = [];
|
||||
const deferred: string[] = [];
|
||||
(progressList || []).forEach((p) => {
|
||||
const name = p.project_name || p.department_name || '项目';
|
||||
switch (p.exam_status) {
|
||||
case 1:
|
||||
checked.push(name);
|
||||
break;
|
||||
case 2:
|
||||
abandoned.push(name);
|
||||
break;
|
||||
case 4:
|
||||
deferred.push(name);
|
||||
break;
|
||||
default:
|
||||
pending.push(name);
|
||||
}
|
||||
});
|
||||
return { checked, abandoned, pending, deferred };
|
||||
}, [progressList]);
|
||||
|
||||
return (
|
||||
<div className='space-y-4 text-sm'>
|
||||
@@ -387,17 +473,19 @@ const ExamDetailInfo = ({ client }: { client: ExamClient }) => {
|
||||
头像
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-xs text-gray-500'>基础信息:头像、姓名、证件号、手机号等(点击图标可进行编辑)</div>
|
||||
<div className='text-xs text-gray-500'>
|
||||
{loading ? '加载中…' : '基础信息:头像、姓名、证件号、手机号等(点击图标可进行编辑)'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='space-y-2 text-xs text-gray-700'>
|
||||
<div className='font-medium text-gray-900'>基础信息</div>
|
||||
<div className='grid grid-cols-2 gap-x-8 gap-y-1'>
|
||||
<div>
|
||||
姓名:<span className='text-gray-900'>{client.name}</span>
|
||||
姓名:<span className='text-gray-900'>{customerInfo?.customer_name || client.name}</span>
|
||||
</div>
|
||||
<div>
|
||||
证件号:<span className='text-gray-900'>4401********1234</span>
|
||||
证件号:<span className='text-gray-900'>{customerInfo?.id_no || '—'}</span>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<span>手机号:</span>
|
||||
@@ -424,7 +512,7 @@ const ExamDetailInfo = ({ client }: { client: ExamClient }) => {
|
||||
<div>
|
||||
性别/年龄:
|
||||
<span className='text-gray-900'>
|
||||
{client.gender} / {client.age}
|
||||
{customerInfo?.gender_name || client.gender} / {client.age}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -474,7 +562,12 @@ const ExamDetailInfo = ({ client }: { client: ExamClient }) => {
|
||||
签到时间:<span className='text-gray-900'>{signTime as string}</span>
|
||||
</div>
|
||||
<div>
|
||||
已消耗时长:<span className='text-gray-900'>{client.elapsed}</span>
|
||||
已消耗时长:
|
||||
<span className='text-gray-900'>
|
||||
{appointmentInfo?.physical_exam_complete_time && appointmentInfo?.sign_in_time
|
||||
? `${appointmentInfo.physical_exam_complete_time} - ${appointmentInfo.sign_in_time}`
|
||||
: client.elapsed}
|
||||
</span>
|
||||
</div>
|
||||
<div className='col-span-2'>
|
||||
体检套餐名称:<span className='text-gray-900'>{client.packageName}</span>
|
||||
@@ -486,36 +579,52 @@ const ExamDetailInfo = ({ client }: { client: ExamClient }) => {
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-2 gap-4 text-xs'>
|
||||
<div className='p-3 rounded-2xl bg-green-50 border'>
|
||||
<div className='font-medium mb-2'>已查项目 共 {client.checkedItems.length} 项</div>
|
||||
<div className='p-3 rounded-2xl bg-green-50 border max-h-48 overflow-auto custom-scroll'>
|
||||
<div className='font-medium mb-2'>已查项目 共 {progressGroups.checked.length} 项</div>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
{client.checkedItems.map((i) => (
|
||||
{(progressGroups.checked.length ? progressGroups.checked : client.checkedItems).map((i) => (
|
||||
<span key={i} className='px-2 py-0.5 rounded-full bg-white border text-[11px]'>
|
||||
{i}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-3 rounded-2xl bg-red-50 border'>
|
||||
<div className='font-medium mb-2'>弃检项目 共 0 项</div>
|
||||
<div className='p-3 rounded-2xl bg-red-50 border max-h-48 overflow-auto custom-scroll'>
|
||||
<div className='font-medium mb-2'>弃检项目 共 {progressGroups.abandoned.length} 项</div>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
<span className='text-gray-400 text-[11px]'>暂无弃检项目</span>
|
||||
{progressGroups.abandoned.length ? (
|
||||
progressGroups.abandoned.map((i) => (
|
||||
<span key={i} className='px-2 py-0.5 rounded-full bg-white border text-[11px]'>
|
||||
{i}
|
||||
</span>
|
||||
))
|
||||
) : (
|
||||
<span className='text-gray-400 text-[11px]'>暂无弃检项目</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-3 rounded-2xl bg-yellow-50 border'>
|
||||
<div className='font-medium mb-2'>未查项目 共 {client.pendingItems.length} 项</div>
|
||||
<div className='p-3 rounded-2xl bg-yellow-50 border max-h-48 overflow-auto custom-scroll'>
|
||||
<div className='font-medium mb-2'>未查项目 共 {progressGroups.pending.length} 项</div>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
{client.pendingItems.map((i) => (
|
||||
{(progressGroups.pending.length ? progressGroups.pending : client.pendingItems).map((i) => (
|
||||
<span key={i} className='px-2 py-0.5 rounded-full bg-white border text-[11px]'>
|
||||
{i}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-3 rounded-2xl bg-blue-50 border'>
|
||||
<div className='font-medium mb-2'>延期项目 共 0 项</div>
|
||||
<div className='p-3 rounded-2xl bg-blue-50 border max-h-48 overflow-auto custom-scroll'>
|
||||
<div className='font-medium mb-2'>延期项目 共 {progressGroups.deferred.length} 项</div>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
<span className='text-gray-400 text-[11px]'>暂无延期项目</span>
|
||||
{progressGroups.deferred.length ? (
|
||||
progressGroups.deferred.map((i) => (
|
||||
<span key={i} className='px-2 py-0.5 rounded-full bg-white border text-[11px]'>
|
||||
{i}
|
||||
</span>
|
||||
))
|
||||
) : (
|
||||
<span className='text-gray-400 text-[11px]'>暂无延期项目</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user