合并同意书导检单
This commit is contained in:
@@ -22,7 +22,7 @@ export const ExamModal = ({ client, tab, onTabChange, onClose }: ExamModalProps)
|
|||||||
{ key: 'detail', label: '详情' },
|
{ key: 'detail', label: '详情' },
|
||||||
{ key: 'sign', label: '签到' },
|
{ key: 'sign', label: '签到' },
|
||||||
{ key: 'addon', label: '加项' },
|
{ key: 'addon', label: '加项' },
|
||||||
{ key: 'print', label: '打印导检单' },
|
// { key: 'print', label: '打印导检单' },
|
||||||
{ key: 'delivery', label: '报告寄送' },
|
{ key: 'delivery', label: '报告寄送' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ export const ExamSection = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>签到</span>
|
<span>签到</span>
|
||||||
{signDone && <span>✅</span>}
|
{(signDone && printDone) && <span>✅</span>}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
@@ -279,7 +279,7 @@ export const ExamSection = ({
|
|||||||
<span>加项</span>
|
<span>加项</span>
|
||||||
{addonCount > 0 && <span className='opacity-80'>({addonCount})</span>}
|
{addonCount > 0 && <span className='opacity-80'>({addonCount})</span>}
|
||||||
</button>
|
</button>
|
||||||
<button
|
{/* <button
|
||||||
type='button'
|
type='button'
|
||||||
className='px-2 py-0.5 rounded-2xl border bg-white hover:bg-gray-100 flex items-center gap-1'
|
className='px-2 py-0.5 rounded-2xl border bg-white hover:bg-gray-100 flex items-center gap-1'
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -289,7 +289,7 @@ export const ExamSection = ({
|
|||||||
>
|
>
|
||||||
<span>打印导检单</span>
|
<span>打印导检单</span>
|
||||||
{printDone && <span>✅</span>}
|
{printDone && <span>✅</span>}
|
||||||
</button>
|
</button> */}
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
className='px-2 py-0.5 rounded-2xl border bg-white hover:bg-gray-100 flex items-center gap-1'
|
className='px-2 py-0.5 rounded-2xl border bg-white hover:bg-gray-100 flex items-center gap-1'
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import * as pdfjsLib from 'pdfjs-dist';
|
|||||||
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url';
|
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url';
|
||||||
|
|
||||||
import type { OutputTongyishuFileInfo } from '../../api';
|
import type { OutputTongyishuFileInfo } from '../../api';
|
||||||
import { getTongyishuPdf, signInMedicalExamCenter, submitTongyishuSign } from '../../api';
|
import { getTongyishuPdf, signInMedicalExamCenter, submitTongyishuSign, submitDaojiandanSign, editDaojiandanPrintStatus } from '../../api';
|
||||||
import {
|
import {
|
||||||
setExamActionRecord,
|
setExamActionRecord,
|
||||||
setTongyishuPdfList,
|
setTongyishuPdfList,
|
||||||
getTongyishuPdfList,
|
getTongyishuPdfList,
|
||||||
|
setDaojiandanPdf,
|
||||||
|
getDaojiandanPdf as getDaojiandanPdfFromStorage,
|
||||||
type TongyishuPdfInfo,
|
type TongyishuPdfInfo,
|
||||||
} from '../../utils/examActions';
|
} from '../../utils/examActions';
|
||||||
import type { SignaturePadHandle } from '../ui';
|
import type { SignaturePadHandle } from '../ui';
|
||||||
@@ -55,7 +57,16 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
|||||||
const [pdfLoading, setPdfLoading] = useState(false);
|
const [pdfLoading, setPdfLoading] = useState(false);
|
||||||
const [pdfBlobUrl, setPdfBlobUrl] = useState<string | null>(null);
|
const [pdfBlobUrl, setPdfBlobUrl] = useState<string | null>(null);
|
||||||
const canvasContainerRef = useRef<HTMLDivElement>(null);
|
const canvasContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const busy = signLoading || submitLoading || consentLoading || pdfLoading;
|
|
||||||
|
// 导检单相关状态
|
||||||
|
const [daojiandanUrl, setDaojiandanUrl] = useState<string | null>(null);
|
||||||
|
const [showDaojiandanSignature, setShowDaojiandanSignature] = useState(false);
|
||||||
|
const daojiandanSignaturePadRef = useRef<SignaturePadHandle | null>(null);
|
||||||
|
const [daojiandanSubmitLoading, setDaojiandanSubmitLoading] = useState(false);
|
||||||
|
const [daojiandanSubmitMessage, setDaojiandanSubmitMessage] = useState<string | null>(null);
|
||||||
|
const [showDaojiandanPreview, setShowDaojiandanPreview] = useState(false);
|
||||||
|
|
||||||
|
const busy = signLoading || submitLoading || consentLoading || pdfLoading || daojiandanSubmitLoading;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onBusyChange?.(busy);
|
onBusyChange?.(busy);
|
||||||
@@ -282,6 +293,16 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
|||||||
.finally(() => setConsentLoading(false));
|
.finally(() => setConsentLoading(false));
|
||||||
}, [examId]);
|
}, [examId]);
|
||||||
|
|
||||||
|
// 组件加载时检查导检单localStorage
|
||||||
|
useEffect(() => {
|
||||||
|
if (!examId) return;
|
||||||
|
|
||||||
|
const storedPdf = getDaojiandanPdfFromStorage(examId);
|
||||||
|
if (storedPdf && storedPdf.pdf_url) {
|
||||||
|
setDaojiandanUrl(storedPdf.pdf_url);
|
||||||
|
}
|
||||||
|
}, [examId]);
|
||||||
|
|
||||||
// 加载 PDF 数据
|
// 加载 PDF 数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!previewPdf?.pdf_url) {
|
if (!previewPdf?.pdf_url) {
|
||||||
@@ -538,6 +559,364 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 导检单签名提交
|
||||||
|
const handleSubmitDaojiandanSign = async () => {
|
||||||
|
if (!examId) {
|
||||||
|
setDaojiandanSubmitMessage('缺少必要信息,无法提交签名');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataUrl = daojiandanSignaturePadRef.current?.toDataURL('image/png');
|
||||||
|
if (!dataUrl) {
|
||||||
|
setDaojiandanSubmitMessage('请先完成签名');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDaojiandanSubmitLoading(true);
|
||||||
|
setDaojiandanSubmitMessage(null);
|
||||||
|
try {
|
||||||
|
const blob = await fetch(dataUrl).then((r) => r.blob());
|
||||||
|
|
||||||
|
const res = await submitDaojiandanSign({
|
||||||
|
exam_id: examId,
|
||||||
|
sign_file: blob,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.Status === 200 && res.Data?.pdf_url) {
|
||||||
|
setDaojiandanSubmitMessage('签名提交成功');
|
||||||
|
const pdfUrlValue = res.Data.pdf_url;
|
||||||
|
const pdfNameValue = res.Data.pdf_name || '导检单';
|
||||||
|
setDaojiandanUrl(pdfUrlValue);
|
||||||
|
|
||||||
|
// 保存导检单PDF信息到localStorage
|
||||||
|
setDaojiandanPdf(examId, {
|
||||||
|
pdf_name: pdfNameValue,
|
||||||
|
pdf_url: pdfUrlValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 记录打印导检单是否签名操作
|
||||||
|
setExamActionRecord(examId, 'printSign', true);
|
||||||
|
|
||||||
|
// 更新导检单打印状态
|
||||||
|
try {
|
||||||
|
await editDaojiandanPrintStatus({ exam_id: examId });
|
||||||
|
} catch (err) {
|
||||||
|
console.error('更新导检单打印状态失败', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowDaojiandanSignature(false);
|
||||||
|
setDaojiandanSubmitMessage(null);
|
||||||
|
daojiandanSignaturePadRef.current?.clear();
|
||||||
|
setShowDaojiandanPreview(true);
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
setDaojiandanSubmitMessage(res.Message || '签名提交失败');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('提交签名失败', err);
|
||||||
|
setDaojiandanSubmitMessage('签名提交失败,请稍后重试');
|
||||||
|
} finally {
|
||||||
|
setDaojiandanSubmitLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导检单直接打印
|
||||||
|
const handleDaojiandanDirectPrint = async () => {
|
||||||
|
if (!daojiandanUrl) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(daojiandanUrl);
|
||||||
|
if (!response.ok) throw new Error('获取PDF文件失败');
|
||||||
|
const blob = await response.blob();
|
||||||
|
const arrayBuffer = await blob.arrayBuffer();
|
||||||
|
|
||||||
|
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
||||||
|
const scale = 1.2;
|
||||||
|
const canvasImages: string[] = [];
|
||||||
|
|
||||||
|
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
|
||||||
|
const page = await pdf.getPage(pageNum);
|
||||||
|
const viewport = page.getViewport({ scale });
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
if (!context) continue;
|
||||||
|
|
||||||
|
canvas.height = viewport.height;
|
||||||
|
canvas.width = viewport.width;
|
||||||
|
|
||||||
|
const renderContext = {
|
||||||
|
canvasContext: context,
|
||||||
|
viewport: viewport,
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await page.render(renderContext).promise;
|
||||||
|
canvasImages.push(canvas.toDataURL('image/png'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const printWindow = window.open('', '_blank');
|
||||||
|
if (!printWindow) return;
|
||||||
|
|
||||||
|
printWindow.document.write(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>导检单打印</title>
|
||||||
|
<style>
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
page-break-after: always;
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
img:last-child {
|
||||||
|
page-break-after: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
`);
|
||||||
|
|
||||||
|
canvasImages.forEach((imgData) => {
|
||||||
|
printWindow.document.write(`<img src="${imgData}" />`);
|
||||||
|
});
|
||||||
|
|
||||||
|
printWindow.document.write(`
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
|
||||||
|
printWindow.document.close();
|
||||||
|
|
||||||
|
// 更新导检单打印状态
|
||||||
|
if (examId) {
|
||||||
|
try {
|
||||||
|
await editDaojiandanPrintStatus({ exam_id: examId });
|
||||||
|
} catch (err) {
|
||||||
|
console.error('更新导检单打印状态失败', err);
|
||||||
|
// 不阻塞打印流程,仅记录错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printWindow.onload = () => {
|
||||||
|
printWindow.focus();
|
||||||
|
setTimeout(() => {
|
||||||
|
printWindow.print();
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.error('打印失败', err);
|
||||||
|
alert('打印失败,请稍后重试');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查所有文档是否都已签名
|
||||||
|
const checkAllSigned = () => {
|
||||||
|
// 检查所有知情同意书是否都已签名
|
||||||
|
const allConsentsSigned = consentList.every((item) => {
|
||||||
|
if (item.combination_code === undefined || item.combination_code === null) return true;
|
||||||
|
return signedCombinationCodes.includes(Number(item.combination_code));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查导检单是否已签名
|
||||||
|
const daojiandanSigned = !!daojiandanUrl;
|
||||||
|
|
||||||
|
return allConsentsSigned && consentList.length > 0 && daojiandanSigned;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 一键打印所有文档
|
||||||
|
const handleBatchPrint = async () => {
|
||||||
|
if (busy) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const allImages: string[] = [];
|
||||||
|
const scale = 1.2;
|
||||||
|
|
||||||
|
// 处理所有已签名的知情同意书
|
||||||
|
for (const item of consentList) {
|
||||||
|
if (item.combination_code === undefined || item.combination_code === null) continue;
|
||||||
|
if (!signedCombinationCodes.includes(Number(item.combination_code))) continue;
|
||||||
|
|
||||||
|
let targetUrl = item.pdf_url;
|
||||||
|
|
||||||
|
// 如果本地已保存签名后的PDF列表,则优先使用签名后的PDF地址
|
||||||
|
if (examId) {
|
||||||
|
const storedList = getTongyishuPdfList(examId);
|
||||||
|
if (storedList && item.combination_code !== undefined && item.combination_code !== null) {
|
||||||
|
const code = Number(item.combination_code);
|
||||||
|
const matched = storedList.find(
|
||||||
|
(pdf) => pdf.combination_code !== null && Number(pdf.combination_code) === code,
|
||||||
|
);
|
||||||
|
if (matched && matched.pdf_url) {
|
||||||
|
targetUrl = matched.pdf_url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetUrl) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(targetUrl);
|
||||||
|
if (!response.ok) continue;
|
||||||
|
const blob = await response.blob();
|
||||||
|
const arrayBuffer = await blob.arrayBuffer();
|
||||||
|
|
||||||
|
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
||||||
|
|
||||||
|
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
|
||||||
|
const page = await pdf.getPage(pageNum);
|
||||||
|
const viewport = page.getViewport({ scale });
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
if (!context) continue;
|
||||||
|
|
||||||
|
canvas.height = viewport.height;
|
||||||
|
canvas.width = viewport.width;
|
||||||
|
|
||||||
|
const renderContext = {
|
||||||
|
canvasContext: context,
|
||||||
|
viewport: viewport,
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await page.render(renderContext).promise;
|
||||||
|
allImages.push(canvas.toDataURL('image/png'));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`处理知情同意书 ${item.pdf_name} 失败`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理导检单
|
||||||
|
if (daojiandanUrl) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(daojiandanUrl);
|
||||||
|
if (response.ok) {
|
||||||
|
const blob = await response.blob();
|
||||||
|
const arrayBuffer = await blob.arrayBuffer();
|
||||||
|
|
||||||
|
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
||||||
|
|
||||||
|
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
|
||||||
|
const page = await pdf.getPage(pageNum);
|
||||||
|
const viewport = page.getViewport({ scale });
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
if (!context) continue;
|
||||||
|
|
||||||
|
canvas.height = viewport.height;
|
||||||
|
canvas.width = viewport.width;
|
||||||
|
|
||||||
|
const renderContext = {
|
||||||
|
canvasContext: context,
|
||||||
|
viewport: viewport,
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await page.render(renderContext).promise;
|
||||||
|
allImages.push(canvas.toDataURL('image/png'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('处理导检单失败', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allImages.length === 0) {
|
||||||
|
alert('没有可打印的内容');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建打印窗口
|
||||||
|
const printWindow = window.open('', '_blank');
|
||||||
|
if (!printWindow) {
|
||||||
|
alert('无法打开打印窗口,请检查浏览器弹窗设置');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printWindow.document.write(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>一键打印</title>
|
||||||
|
<style>
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
page-break-after: always;
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
img:last-child {
|
||||||
|
page-break-after: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
`);
|
||||||
|
|
||||||
|
allImages.forEach((imgData) => {
|
||||||
|
printWindow.document.write(`<img src="${imgData}" />`);
|
||||||
|
});
|
||||||
|
|
||||||
|
printWindow.document.write(`
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
|
||||||
|
printWindow.document.close();
|
||||||
|
|
||||||
|
// 如果包含导检单,更新导检单打印状态
|
||||||
|
if (examId && daojiandanUrl) {
|
||||||
|
try {
|
||||||
|
await editDaojiandanPrintStatus({ exam_id: examId });
|
||||||
|
} catch (err) {
|
||||||
|
console.error('更新导检单打印状态失败', err);
|
||||||
|
// 不阻塞打印流程,仅记录错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待图片加载完成后执行打印
|
||||||
|
printWindow.onload = () => {
|
||||||
|
printWindow.focus();
|
||||||
|
setTimeout(() => {
|
||||||
|
printWindow.print();
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.error('一键打印失败', err);
|
||||||
|
alert('一键打印失败,请稍后重试');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='grid grid-cols-2 gap-4 text-sm'>
|
<div className='grid grid-cols-2 gap-4 text-sm'>
|
||||||
<div className='p-4 rounded-2xl border bg-gray-50/60 flex flex-col gap-3'>
|
<div className='p-4 rounded-2xl border bg-gray-50/60 flex flex-col gap-3'>
|
||||||
@@ -582,12 +961,23 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className='p-4 rounded-2xl border bg-gray-50/60 flex flex-col gap-3'>
|
<div className='p-4 rounded-2xl border bg-gray-50/60 flex flex-col gap-3'>
|
||||||
<div className='font-medium'>体检知情同意书</div>
|
<div className='flex items-center justify-between'>
|
||||||
<div className='text-xs text-gray-500'>点击后弹出知情同意书全文及签名区域,签署完成后回到签到页面。</div>
|
<div className='font-medium'>体检知情同意书</div>
|
||||||
|
{checkAllSigned() && (
|
||||||
|
<Button
|
||||||
|
className='py-1 px-3 bg-green-600 hover:bg-green-700 text-white text-xs'
|
||||||
|
onClick={handleBatchPrint}
|
||||||
|
disabled={busy}
|
||||||
|
>
|
||||||
|
一键打印
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* <div className='text-xs text-gray-500'>点击后弹出知情同意书全文及签名区域,签署完成后回到签到页面。</div> */}
|
||||||
<div className='flex flex-col gap-2'>
|
<div className='flex flex-col gap-2'>
|
||||||
{consentLoading && <div className='text-xs text-gray-500'>加载中...</div>}
|
{consentLoading && <div className='text-xs text-gray-500'>加载中...</div>}
|
||||||
{!consentLoading && consentMessage && <div className='text-xs text-amber-600'>{consentMessage}</div>}
|
{!consentLoading && consentMessage && <div className='text-xs text-amber-600'>{consentMessage}</div>}
|
||||||
{!consentLoading && consentList.length > 0 && (
|
{!consentLoading && (consentList.length > 0 || true) && (
|
||||||
<div className='space-y-2'>
|
<div className='space-y-2'>
|
||||||
{consentList.map((item) => (
|
{consentList.map((item) => (
|
||||||
<div
|
<div
|
||||||
@@ -668,6 +1058,56 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
<div className='flex items-center justify-between gap-3 p-2 rounded-xl border bg-white shadow-sm'>
|
||||||
|
<div className='text-sm text-gray-800 truncate flex items-center gap-2 relative pr-20'>
|
||||||
|
<span className='truncate'>导检单</span>
|
||||||
|
{daojiandanUrl && (
|
||||||
|
<img
|
||||||
|
src='/sign.png'
|
||||||
|
alt='已签名'
|
||||||
|
className='w-16 h-16 absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none select-none'
|
||||||
|
loading='lazy'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
{daojiandanUrl ? (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
className='py-1.5 px-3 bg-blue-600 hover:bg-blue-700 text-white'
|
||||||
|
onClick={() => {
|
||||||
|
if (busy) return;
|
||||||
|
handleDaojiandanDirectPrint();
|
||||||
|
}}
|
||||||
|
disabled={busy}
|
||||||
|
>
|
||||||
|
打印
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className='py-1.5 px-3'
|
||||||
|
onClick={() => {
|
||||||
|
if (busy) return;
|
||||||
|
setShowDaojiandanPreview(true);
|
||||||
|
}}
|
||||||
|
disabled={busy}
|
||||||
|
>
|
||||||
|
查看
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
className='py-1.5 px-3'
|
||||||
|
onClick={() => {
|
||||||
|
if (busy) return;
|
||||||
|
setShowDaojiandanSignature(true);
|
||||||
|
}}
|
||||||
|
disabled={busy}
|
||||||
|
>
|
||||||
|
签名
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -715,7 +1155,7 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
|||||||
showSignature && (
|
showSignature && (
|
||||||
<div className='fixed inset-0 z-[70] bg-black/80 flex items-center justify-center px-6'>
|
<div className='fixed inset-0 z-[70] bg-black/80 flex items-center justify-center px-6'>
|
||||||
<div className='bg-white rounded-2xl w-full max-w-3xl shadow-2xl p-4 flex flex-col gap-4'>
|
<div className='bg-white rounded-2xl w-full max-w-3xl shadow-2xl p-4 flex flex-col gap-4'>
|
||||||
<div className='flex items-center justify之间'>
|
<div className='flex items-center justify-between'>
|
||||||
<div className='text-base font-semibold text-gray-900'>签署知情同意书</div>
|
<div className='text-base font-semibold text-gray-900'>签署知情同意书</div>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<Button className='py-1 px-3' onClick={() => setShowSignature(false)} disabled={busy}>
|
<Button className='py-1 px-3' onClick={() => setShowSignature(false)} disabled={busy}>
|
||||||
@@ -762,6 +1202,87 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
showDaojiandanSignature && (
|
||||||
|
<div className='fixed inset-0 z-[70] bg-black/80 flex items-center justify-center px-6'>
|
||||||
|
<div className='bg-white rounded-2xl w-full max-w-3xl shadow-2xl p-4 flex flex-col gap-4'>
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<div className='text-base font-semibold text-gray-900'>签署导检单</div>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<Button className='py-1 px-3' onClick={() => setShowDaojiandanSignature(false)} disabled={busy}>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='ui7-signature-wrapper border rounded-xl overflow-hidden bg-gray-50'>
|
||||||
|
<SignaturePad
|
||||||
|
ref={daojiandanSignaturePadRef}
|
||||||
|
className='ui7-signature-canvas w-full h-72 bg-white touch-none'
|
||||||
|
/>
|
||||||
|
<div className='flex items-center justify-between px-3 py-2 bg-gray-50 border-t'>
|
||||||
|
<div className='text-xs text-gray-500'>请在上方区域签名</div>
|
||||||
|
<Button className='ui7-clear-button py-1 px-3' onClick={() => daojiandanSignaturePadRef.current?.clear()} disabled={daojiandanSubmitLoading}>
|
||||||
|
清除
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{daojiandanSubmitMessage && (
|
||||||
|
<div className={`text-sm text-center ${daojiandanSubmitMessage.includes('成功') ? 'text-green-600' : 'text-amber-600'}`}>{daojiandanSubmitMessage}</div>
|
||||||
|
)}
|
||||||
|
<div className='flex items-center justify-end gap-3'>
|
||||||
|
<Button
|
||||||
|
className='py-2 px-6'
|
||||||
|
onClick={() => {
|
||||||
|
setShowDaojiandanSignature(false);
|
||||||
|
setDaojiandanSubmitMessage(null);
|
||||||
|
daojiandanSignaturePadRef.current?.clear();
|
||||||
|
}}
|
||||||
|
disabled={daojiandanSubmitLoading}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className='py-2 px-6 bg-blue-600 text-white hover:bg-blue-700'
|
||||||
|
onClick={handleSubmitDaojiandanSign}
|
||||||
|
disabled={daojiandanSubmitLoading}
|
||||||
|
>
|
||||||
|
{daojiandanSubmitLoading ? '提交中...' : '提交签名'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
showDaojiandanPreview && daojiandanUrl && (
|
||||||
|
<div className='fixed inset-0 z-[60] bg-black/75 flex flex-col'>
|
||||||
|
<div className='flex items-center justify-between p-4 text-white bg-gray-900/80'>
|
||||||
|
<div className='text-sm font-medium truncate pr-3'>导检单</div>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<Button
|
||||||
|
className='py-1 px-3 bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
|
onClick={handleDaojiandanDirectPrint}
|
||||||
|
disabled={busy}
|
||||||
|
>
|
||||||
|
打印
|
||||||
|
</Button>
|
||||||
|
<Button className='py-1 px-3' onClick={() => !busy && setShowDaojiandanPreview(false)} disabled={busy}>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex-1 bg-gray-100 overflow-auto'>
|
||||||
|
<div className='flex justify-center p-4'>
|
||||||
|
<iframe
|
||||||
|
src={daojiandanUrl}
|
||||||
|
className='w-full h-full min-h-[600px] border rounded-lg bg-white'
|
||||||
|
title='导检单预览'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div >
|
</div >
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user