分离体检打印面板

This commit is contained in:
xianyi
2025-12-15 15:57:53 +08:00
parent f4f218dec3
commit 1b1a484e55
2 changed files with 165 additions and 198 deletions

View File

@@ -6,6 +6,7 @@ import { getCustomerDetail, getPhysicalExamProgressDetail, getTongyishuPdf, sign
import { Button, Input, SignaturePad, type SignaturePadHandle } from '../ui'; import { Button, Input, SignaturePad, type SignaturePadHandle } from '../ui';
import { ExamDetailPanel } from './ExamDetailPanel'; import { ExamDetailPanel } from './ExamDetailPanel';
import { ExamAddonPanel } from './ExamAddonPanel'; import { ExamAddonPanel } from './ExamAddonPanel';
import { ExamPrintPanel } from './ExamPrintPanel';
interface ExamModalProps { interface ExamModalProps {
client: ExamClient; client: ExamClient;
@@ -164,7 +165,7 @@ export const ExamModal = ({ client, tab, onTabChange, onClose }: ExamModalProps)
{tab === 'sign' && <ExamSignPanel examId={Number(client.id)} />} {tab === 'sign' && <ExamSignPanel examId={Number(client.id)} />}
{tab === 'addon' && <ExamAddonPanel client={client} />} {tab === 'addon' && <ExamAddonPanel client={client} />}
{tab === 'print' && <ExamPrintPanel client={client} />} {tab === 'print' && <ExamPrintPanel client={client} />}
{tab === 'delivery' && <ExamDeliveryPanel client={client} />} {/* {tab === 'delivery' && <ExamDeliveryPanel client={client} />} */}
</div> </div>
</div> </div>
@@ -559,201 +560,4 @@ const ExamSignPanel = ({ examId }: { examId?: number }) => {
); );
}; };
const ExamDeliveryPanel = ({ client }: { client: ExamClient }) => (
<div className='flex justify-center'>
<div className='w-full rounded-2xl border shadow-sm px-6 py-4 text-xs text-gray-800'>
<div className='text-lg font-semibold text-gray-900 mb-3'></div>
<div className='grid grid-cols-2 gap-3 mb-3'>
<div>
<Input placeholder='请输入收件人姓名' className='mt-1' />
</div>
<div>
<Input placeholder='用于快递联系' className='mt-1' />
</div>
<div className='col-span-2'>
<Input placeholder='请输入详细寄送地址' className='mt-1' />
</div>
</div>
<div className='space-y-2'>
<div></div>
<textarea
className='w-full rounded-2xl border px-3 py-2 text-xs outline-none focus:ring-2 focus:ring-gray-200 min-h-[80px]'
placeholder='如需多份报告、加急寄送等,请在此备注'
/>
</div>
<div className='mt-4 flex items-center justify-between text-[11px] text-gray-500'>
<div>
<span className='font-medium text-gray-800'>{client.name}</span>{client.id}
</div>
<Button className='px-4 py-1.5 text-xs'></Button>
</div>
</div>
</div>
);
const ExamPrintPanel = ({ client }: { client: ExamClient }) => {
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [pdfReady, setPdfReady] = useState(false);
const [pdfBlobUrl, setPdfBlobUrl] = useState<string | null>(null);
const printRef = useRef<HTMLDivElement>(null);
const iframeRef = useRef<HTMLIFrameElement>(null);
// 获取 PDF
useEffect(() => {
const physical_exam_id = Number(client.id);
if (!physical_exam_id) {
setError('无效的体检ID');
setLoading(false);
return;
}
setPdfReady(false);
getTongyishuPdf({ exam_id: physical_exam_id })
.then((res) => {
if (res.Status === 200 && res.Data?.list_pdf_url && res.Data.list_pdf_url.length > 0) {
// 取第一个PDF URL
setPdfUrl(res.Data.list_pdf_url[0].pdf_url);
setPdfReady(false);
} else {
setError(res.Message || '未获取到PDF文件');
}
})
.catch((err) => {
console.error('获取PDF失败', err);
setError('获取PDF失败请稍后重试');
})
.finally(() => {
setLoading(false);
});
}, [client.id]);
// 将 PDF 拉取为同源 blob避免跨域打印限制
useEffect(() => {
if (!pdfUrl) return;
let objectUrl: string | null = null;
setPdfReady(false);
setLoading(true);
fetch(pdfUrl)
.then((resp) => {
if (!resp.ok) throw new Error('获取PDF文件失败');
return resp.blob();
})
.then((blob) => {
objectUrl = URL.createObjectURL(blob);
setPdfBlobUrl(objectUrl);
})
.catch((err) => {
console.error('PDF 拉取失败', err);
setError('PDF 加载失败,请稍后重试');
})
.finally(() => {
setLoading(false);
});
return () => {
if (objectUrl) {
URL.revokeObjectURL(objectUrl);
}
};
}, [pdfUrl]);
const handlePrint = () => {
if (!pdfBlobUrl || !pdfReady) return;
// 打开新窗口打印(同源 blob避免跨域和空白页问题
const printWindow = window.open(pdfBlobUrl, '_blank');
if (printWindow) {
printWindow.onload = () => {
printWindow.focus();
printWindow.print();
};
}
};
return (
<div className='flex justify-center'>
<div className='w-full max-w-[95%] bg-white rounded-2xl border shadow-sm px-6 py-4 text-xs text-gray-800'>
<div className='flex items-center justify-between border-b pb-3 mb-3'>
<div>
<div className='text-sm font-semibold'> · </div>
<div className='text-[11px] text-gray-500 mt-1'></div>
</div>
<div className='flex items-center gap-3'>
<div className='text-right text-[11px] text-gray-500'>
<div>{client.id}</div>
<div>{new Date().toLocaleDateString('zh-CN')}</div>
</div>
<Button
className='px-4 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50 disabled:cursor-not-allowed'
onClick={handlePrint}
disabled={loading || !pdfReady || !pdfUrl}
>
</Button>
</div>
</div>
{loading ? (
<div className='flex items-center justify-center py-12 text-gray-500'>
<div>PDF...</div>
</div>
) : error ? (
<div className='flex flex-col items-center justify-center py-12 text-gray-500'>
<div className='mb-2'>{error}</div>
<Button
className='px-4 py-1.5 text-xs'
onClick={() => {
setLoading(true);
setError(null);
const physical_exam_id = Number(client.id);
if (physical_exam_id) {
getTongyishuPdf({ exam_id: physical_exam_id })
.then((res) => {
if (res.Status === 200 && res.Data?.list_pdf_url && res.Data.list_pdf_url.length > 0) {
setPdfUrl(res.Data.list_pdf_url[0].pdf_url);
} else {
setError(res.Message || '未获取到PDF文件');
}
})
.catch((err) => {
console.error('获取PDF失败', err);
setError('获取PDF失败请稍后重试');
})
.finally(() => {
setLoading(false);
});
}
}}
>
</Button>
</div>
) : pdfUrl ? (
<div className='w-full'>
{/* PDF预览区域 */}
<div ref={printRef} className='print-content'>
<div className='flex justify-center border rounded-lg p-4 bg-gray-50 overflow-auto max-h-[600px]'>
<iframe
src={pdfBlobUrl || ''}
className='w-full h-[600px] border rounded-lg'
title='导检单PDF预览'
onLoad={() => setPdfReady(true)}
ref={iframeRef}
/>
</div>
</div>
</div>
) : null}
</div>
</div>
);
};

View File

@@ -0,0 +1,163 @@
import { useEffect, useRef, useState } from 'react';
import { getTongyishuPdf } from '../../api';
import type { ExamClient } from '../../data/mockData';
import { Button } from '../ui';
export const ExamPrintPanel = ({ client }: { client: ExamClient }) => {
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [pdfReady, setPdfReady] = useState(false);
const [pdfBlobUrl, setPdfBlobUrl] = useState<string | null>(null);
const printRef = useRef<HTMLDivElement>(null);
const iframeRef = useRef<HTMLIFrameElement>(null);
useEffect(() => {
const physical_exam_id = Number(client.id);
if (!physical_exam_id) {
setError('无效的体检ID');
setLoading(false);
return;
}
setPdfReady(false);
getTongyishuPdf({ exam_id: physical_exam_id })
.then((res) => {
if (res.Status === 200 && res.Data?.list_pdf_url && res.Data.list_pdf_url.length > 0) {
setPdfUrl(res.Data.list_pdf_url[0].pdf_url);
setPdfReady(false);
} else {
setError(res.Message || '未获取到PDF文件');
}
})
.catch((err) => {
console.error('获取PDF失败', err);
setError('获取PDF失败请稍后重试');
})
.finally(() => {
setLoading(false);
});
}, [client.id]);
useEffect(() => {
if (!pdfUrl) return;
let objectUrl: string | null = null;
setPdfReady(false);
setLoading(true);
fetch(pdfUrl)
.then((resp) => {
if (!resp.ok) throw new Error('获取PDF文件失败');
return resp.blob();
})
.then((blob) => {
objectUrl = URL.createObjectURL(blob);
setPdfBlobUrl(objectUrl);
})
.catch((err) => {
console.error('PDF 拉取失败', err);
setError('PDF 加载失败,请稍后重试');
})
.finally(() => {
setLoading(false);
});
return () => {
if (objectUrl) {
URL.revokeObjectURL(objectUrl);
}
};
}, [pdfUrl]);
const handlePrint = () => {
if (!pdfBlobUrl || !pdfReady) return;
const printWindow = window.open(pdfBlobUrl, '_blank');
if (printWindow) {
printWindow.onload = () => {
printWindow.focus();
printWindow.print();
};
}
};
return (
<div className='flex justify-center'>
<div className='w-full max-w-[95%] bg-white rounded-2xl border shadow-sm px-6 py-4 text-xs text-gray-800'>
<div className='flex items-center justify-between border-b pb-3 mb-3'>
<div>
<div className='text-sm font-semibold'> · </div>
<div className='text-[11px] text-gray-500 mt-1'></div>
</div>
<div className='flex items-center gap-3'>
<div className='text-right text-[11px] text-gray-500'>
<div>{client.id}</div>
<div>{new Date().toLocaleDateString('zh-CN')}</div>
</div>
<Button
className='px-4 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50 disabled:cursor-not-allowed'
onClick={handlePrint}
disabled={loading || !pdfReady || !pdfUrl}
>
</Button>
</div>
</div>
{loading ? (
<div className='flex items-center justify-center py-12 text-gray-500'>
<div>PDF...</div>
</div>
) : error ? (
<div className='flex flex-col items-center justify-center py-12 text-gray-500'>
<div className='mb-2'>{error}</div>
<Button
className='px-4 py-1.5 text-xs'
onClick={() => {
setLoading(true);
setError(null);
const physical_exam_id = Number(client.id);
if (physical_exam_id) {
getTongyishuPdf({ exam_id: physical_exam_id })
.then((res) => {
if (res.Status === 200 && res.Data?.list_pdf_url && res.Data.list_pdf_url.length > 0) {
setPdfUrl(res.Data.list_pdf_url[0].pdf_url);
} else {
setError(res.Message || '未获取到PDF文件');
}
})
.catch((err) => {
console.error('获取PDF失败', err);
setError('获取PDF失败请稍后重试');
})
.finally(() => {
setLoading(false);
});
}
}}
>
</Button>
</div>
) : pdfUrl ? (
<div className='w-full'>
<div ref={printRef} className='print-content'>
<div className='flex justify-center border rounded-lg p-4 bg-gray-50 overflow-auto max-h-[600px]'>
<iframe
src={pdfBlobUrl || ''}
className='w-full h-[600px] border rounded-lg'
title='导检单PDF预览'
onLoad={() => setPdfReady(true)}
ref={iframeRef}
/>
</div>
</div>
</div>
) : null}
</div>
</div>
);
};