分离体检打印面板
This commit is contained in:
@@ -6,6 +6,7 @@ import { getCustomerDetail, getPhysicalExamProgressDetail, getTongyishuPdf, sign
|
||||
import { Button, Input, SignaturePad, type SignaturePadHandle } from '../ui';
|
||||
import { ExamDetailPanel } from './ExamDetailPanel';
|
||||
import { ExamAddonPanel } from './ExamAddonPanel';
|
||||
import { ExamPrintPanel } from './ExamPrintPanel';
|
||||
|
||||
interface ExamModalProps {
|
||||
client: ExamClient;
|
||||
@@ -164,7 +165,7 @@ export const ExamModal = ({ client, tab, onTabChange, onClose }: ExamModalProps)
|
||||
{tab === 'sign' && <ExamSignPanel examId={Number(client.id)} />}
|
||||
{tab === 'addon' && <ExamAddonPanel client={client} />}
|
||||
{tab === 'print' && <ExamPrintPanel client={client} />}
|
||||
{tab === 'delivery' && <ExamDeliveryPanel client={client} />}
|
||||
{/* {tab === 'delivery' && <ExamDeliveryPanel client={client} />} */}
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
163
src/components/exam/ExamPrintPanel.tsx
Normal file
163
src/components/exam/ExamPrintPanel.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user