优化体检知情同意书列表打印
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import * as pdfjsLib from 'pdfjs-dist';
|
||||||
|
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 } from '../../api';
|
||||||
@@ -11,6 +13,22 @@ import {
|
|||||||
import type { SignaturePadHandle } from '../ui';
|
import type { SignaturePadHandle } from '../ui';
|
||||||
import { Button, SignaturePad } from '../ui';
|
import { Button, SignaturePad } from '../ui';
|
||||||
|
|
||||||
|
// Polyfill for Promise.withResolvers
|
||||||
|
if (typeof (Promise as any).withResolvers === 'undefined') {
|
||||||
|
(Promise as any).withResolvers = function <T>() {
|
||||||
|
let resolve!: (value: T | PromiseLike<T>) => void;
|
||||||
|
let reject!: (reason?: any) => void;
|
||||||
|
const promise = new Promise<T>((res, rej) => {
|
||||||
|
resolve = res;
|
||||||
|
reject = rej;
|
||||||
|
});
|
||||||
|
return { promise, resolve, reject };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置 PDF.js worker
|
||||||
|
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;
|
||||||
|
|
||||||
interface ExamSignPanelProps {
|
interface ExamSignPanelProps {
|
||||||
examId?: number;
|
examId?: number;
|
||||||
onBusyChange?: (busy: boolean) => void;
|
onBusyChange?: (busy: boolean) => void;
|
||||||
@@ -32,7 +50,12 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
|||||||
const [submitLoading, setSubmitLoading] = useState(false);
|
const [submitLoading, setSubmitLoading] = useState(false);
|
||||||
const [submitMessage, setSubmitMessage] = useState<string | null>(null);
|
const [submitMessage, setSubmitMessage] = useState<string | null>(null);
|
||||||
const [signedCombinationCodes, setSignedCombinationCodes] = useState<number[]>([]);
|
const [signedCombinationCodes, setSignedCombinationCodes] = useState<number[]>([]);
|
||||||
const busy = signLoading || submitLoading || consentLoading;
|
const [pdfData, setPdfData] = useState<ArrayBuffer | null>(null);
|
||||||
|
const [pdfReady, setPdfReady] = useState(false);
|
||||||
|
const [pdfLoading, setPdfLoading] = useState(false);
|
||||||
|
const [pdfBlobUrl, setPdfBlobUrl] = useState<string | null>(null);
|
||||||
|
const canvasContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const busy = signLoading || submitLoading || consentLoading || pdfLoading;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onBusyChange?.(busy);
|
onBusyChange?.(busy);
|
||||||
@@ -259,6 +282,262 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
|||||||
.finally(() => setConsentLoading(false));
|
.finally(() => setConsentLoading(false));
|
||||||
}, [examId]);
|
}, [examId]);
|
||||||
|
|
||||||
|
// 加载 PDF 数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (!previewPdf?.pdf_url) {
|
||||||
|
setPdfData(null);
|
||||||
|
setPdfBlobUrl(null);
|
||||||
|
setPdfReady(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let objectUrl: string | null = null;
|
||||||
|
setPdfReady(false);
|
||||||
|
setPdfLoading(true);
|
||||||
|
setPdfData(null);
|
||||||
|
|
||||||
|
fetch(previewPdf.pdf_url)
|
||||||
|
.then((resp) => {
|
||||||
|
if (!resp.ok) throw new Error('获取PDF文件失败');
|
||||||
|
return resp.blob();
|
||||||
|
})
|
||||||
|
.then((blob) => {
|
||||||
|
objectUrl = URL.createObjectURL(blob);
|
||||||
|
setPdfBlobUrl(objectUrl);
|
||||||
|
return blob.arrayBuffer();
|
||||||
|
})
|
||||||
|
.then((arrayBuffer) => {
|
||||||
|
setPdfData(arrayBuffer);
|
||||||
|
setPdfLoading(false);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('PDF 拉取失败', err);
|
||||||
|
setPdfLoading(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (objectUrl) {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [previewPdf?.pdf_url]);
|
||||||
|
|
||||||
|
// 渲染 PDF
|
||||||
|
useEffect(() => {
|
||||||
|
if (!pdfData || !canvasContainerRef.current) return;
|
||||||
|
|
||||||
|
setPdfReady(false);
|
||||||
|
|
||||||
|
const renderAllPages = async () => {
|
||||||
|
try {
|
||||||
|
const pdf = await pdfjsLib.getDocument({ data: pdfData }).promise;
|
||||||
|
|
||||||
|
if (!canvasContainerRef.current) return;
|
||||||
|
|
||||||
|
// 清空容器
|
||||||
|
canvasContainerRef.current.innerHTML = '';
|
||||||
|
|
||||||
|
const scale = 1.2;
|
||||||
|
|
||||||
|
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;
|
||||||
|
canvas.style.display = 'block';
|
||||||
|
canvas.style.marginBottom = '10px';
|
||||||
|
canvas.className = 'mx-auto border rounded-lg shadow-sm';
|
||||||
|
|
||||||
|
canvasContainerRef.current.appendChild(canvas);
|
||||||
|
|
||||||
|
const renderContext = {
|
||||||
|
canvasContext: context,
|
||||||
|
viewport: viewport,
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await page.render(renderContext).promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPdfReady(true);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('PDF 渲染失败', err);
|
||||||
|
setPdfLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderAllPages();
|
||||||
|
}, [pdfData]);
|
||||||
|
|
||||||
|
const handlePrint = () => {
|
||||||
|
if (!pdfBlobUrl || !pdfReady || !canvasContainerRef.current) return;
|
||||||
|
|
||||||
|
// 创建打印窗口
|
||||||
|
const printWindow = window.open('', '_blank');
|
||||||
|
if (!printWindow) return;
|
||||||
|
|
||||||
|
// 获取所有的 canvas
|
||||||
|
const canvases = canvasContainerRef.current.querySelectorAll('canvas');
|
||||||
|
|
||||||
|
// 构建打印页面
|
||||||
|
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>
|
||||||
|
`);
|
||||||
|
|
||||||
|
// 将每个 canvas 转换为图片并添加到打印窗口
|
||||||
|
canvases.forEach((canvas) => {
|
||||||
|
const imgData = (canvas as HTMLCanvasElement).toDataURL('image/png');
|
||||||
|
printWindow.document.write(`<img src="${imgData}" />`);
|
||||||
|
});
|
||||||
|
|
||||||
|
printWindow.document.write(`
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
|
||||||
|
printWindow.document.close();
|
||||||
|
|
||||||
|
// 等待图片加载完成后执行打印
|
||||||
|
printWindow.onload = () => {
|
||||||
|
printWindow.focus();
|
||||||
|
setTimeout(() => {
|
||||||
|
printWindow.print();
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDirectPrint = async (pdfItem: OutputTongyishuFileInfo) => {
|
||||||
|
if (!pdfItem.pdf_url) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 加载PDF
|
||||||
|
const response = await fetch(pdfItem.pdf_url);
|
||||||
|
if (!response.ok) throw new Error('获取PDF文件失败');
|
||||||
|
const blob = await response.blob();
|
||||||
|
const arrayBuffer = await blob.arrayBuffer();
|
||||||
|
|
||||||
|
// 渲染PDF到临时canvas
|
||||||
|
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>${pdfItem.pdf_name || '知情同意书打印'}</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();
|
||||||
|
|
||||||
|
// 等待图片加载完成后执行打印
|
||||||
|
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'>
|
||||||
@@ -313,10 +592,10 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
|||||||
{consentList.map((item) => (
|
{consentList.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.pdf_url || item.pdf_name}
|
key={item.pdf_url || item.pdf_name}
|
||||||
className='flex items-center justify-between gap-3 p-3 rounded-xl border bg-white shadow-sm'
|
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'>
|
<div className='text-sm text-gray-800 truncate flex items-center gap-2 relative pr-20'>
|
||||||
<span className='truncate'>{item.pdf_name}</span>
|
<span className='truncate'>{item.pdf_name.length > 12 ? item.pdf_name.slice(0, 12) + "..." : item.pdf_name}</span>
|
||||||
{item.combination_code !== undefined && signedCombinationCodes.includes(Number(item.combination_code)) && (
|
{item.combination_code !== undefined && signedCombinationCodes.includes(Number(item.combination_code)) && (
|
||||||
<img
|
<img
|
||||||
src='/sign.png'
|
src='/sign.png'
|
||||||
@@ -326,110 +605,164 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<div className='flex items-center gap-2'>
|
||||||
className='py-1.5 px-3'
|
{item.combination_code !== undefined && signedCombinationCodes.includes(Number(item.combination_code)) && (
|
||||||
onClick={() => {
|
<Button
|
||||||
if (busy) return;
|
className='py-1.5 px-3 bg-blue-600 hover:bg-blue-700 text-white'
|
||||||
|
onClick={() => {
|
||||||
|
if (busy) return;
|
||||||
|
let target = item;
|
||||||
|
// 如果本地已保存签名后的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) {
|
||||||
|
target = {
|
||||||
|
...item,
|
||||||
|
pdf_url: matched.pdf_url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleDirectPrint(target);
|
||||||
|
}}
|
||||||
|
disabled={busy}
|
||||||
|
>
|
||||||
|
打印
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
className='py-1.5 px-3'
|
||||||
|
onClick={() => {
|
||||||
|
if (busy) return;
|
||||||
|
|
||||||
let target = item;
|
let target = item;
|
||||||
|
|
||||||
// 如果本地已保存签名后的PDF列表,则优先使用签名后的PDF地址
|
// 如果本地已保存签名后的PDF列表,则优先使用签名后的PDF地址
|
||||||
if (examId) {
|
if (examId) {
|
||||||
const storedList = getTongyishuPdfList(examId);
|
const storedList = getTongyishuPdfList(examId);
|
||||||
if (storedList && item.combination_code !== undefined && item.combination_code !== null) {
|
if (storedList && item.combination_code !== undefined && item.combination_code !== null) {
|
||||||
const code = Number(item.combination_code);
|
const code = Number(item.combination_code);
|
||||||
const matched = storedList.find(
|
const matched = storedList.find(
|
||||||
(pdf) => pdf.combination_code !== null && Number(pdf.combination_code) === code,
|
(pdf) => pdf.combination_code !== null && Number(pdf.combination_code) === code,
|
||||||
);
|
);
|
||||||
if (matched && matched.pdf_url) {
|
if (matched && matched.pdf_url) {
|
||||||
target = {
|
target = {
|
||||||
...item,
|
...item,
|
||||||
pdf_url: matched.pdf_url,
|
pdf_url: matched.pdf_url,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
setPreviewPdf(target);
|
setPreviewPdf(target);
|
||||||
}}
|
}}
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
>
|
>
|
||||||
查看
|
查看
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{previewPdf && (
|
{
|
||||||
<div className='fixed inset-0 z-[60] bg-black/75 flex flex-col'>
|
previewPdf && (
|
||||||
<div className='flex items-center justify-between p-4 text-white bg-gray-900/80'>
|
<div className='fixed inset-0 z-[60] bg-black/75 flex flex-col'>
|
||||||
<div className='text-sm font-medium truncate pr-3'>{previewPdf.pdf_name}</div>
|
<div className='flex items-center justify-between p-4 text-white bg-gray-900/80'>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='text-sm font-medium truncate pr-3'>{previewPdf.pdf_name}</div>
|
||||||
<Button className='py-1 px-3' onClick={() => !busy && setShowSignature(true)} disabled={busy}>
|
|
||||||
签到
|
|
||||||
</Button>
|
|
||||||
<Button className='py-1 px-3' onClick={() => !busy && setPreviewPdf(null)} disabled={busy}>
|
|
||||||
关闭
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='flex-1 bg-gray-100'>
|
|
||||||
<iframe src={previewPdf.pdf_url} title={previewPdf.pdf_name} className='w-full h-full' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{showSignature && (
|
|
||||||
<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之间'>
|
|
||||||
<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 bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
|
onClick={handlePrint}
|
||||||
|
disabled={pdfLoading || !pdfReady || !pdfBlobUrl}
|
||||||
|
>
|
||||||
|
打印
|
||||||
|
</Button>
|
||||||
|
<Button className='py-1 px-3' onClick={() => !busy && setShowSignature(true)} disabled={busy}>
|
||||||
|
签名
|
||||||
|
</Button>
|
||||||
|
<Button className='py-1 px-3' onClick={() => !busy && setPreviewPdf(null)} disabled={busy}>
|
||||||
关闭
|
关闭
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='ui7-signature-wrapper border rounded-xl overflow-hidden bg-gray-50'>
|
<div className='flex-1 bg-gray-100 overflow-auto'>
|
||||||
<SignaturePad
|
{pdfLoading ? (
|
||||||
ref={signaturePadRef}
|
<div className='flex items-center justify-center h-full text-white'>
|
||||||
className='ui7-signature-canvas w-full h-72 bg-white touch-none'
|
<div>正在加载PDF...</div>
|
||||||
/>
|
</div>
|
||||||
<div className='flex items-center justify-between px-3 py-2 bg-gray-50 border-t'>
|
) : pdfData ? (
|
||||||
<div className='text-xs text-gray-500'>请在上方区域签名完成签到</div>
|
<div className='flex justify-center p-4'>
|
||||||
<Button className='ui7-clear-button py-1 px-3' onClick={() => signaturePadRef.current?.clear()} disabled={submitLoading}>
|
<div ref={canvasContainerRef} className='w-full' />
|
||||||
清除
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='flex items-center justify-center h-full text-white'>
|
||||||
|
<div>PDF加载失败</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
showSignature && (
|
||||||
|
<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之间'>
|
||||||
|
<div className='text-base font-semibold text-gray-900'>签署知情同意书</div>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<Button className='py-1 px-3' onClick={() => setShowSignature(false)} disabled={busy}>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='ui7-signature-wrapper border rounded-xl overflow-hidden bg-gray-50'>
|
||||||
|
<SignaturePad
|
||||||
|
ref={signaturePadRef}
|
||||||
|
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={() => signaturePadRef.current?.clear()} disabled={submitLoading}>
|
||||||
|
清除
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{submitMessage && (
|
||||||
|
<div className={`text-sm text-center ${submitMessage.includes('成功') ? 'text-green-600' : 'text-amber-600'}`}>{submitMessage}</div>
|
||||||
|
)}
|
||||||
|
<div className='flex items-center justify-end gap-3'>
|
||||||
|
<Button
|
||||||
|
className='py-2 px-6'
|
||||||
|
onClick={() => {
|
||||||
|
setShowSignature(false);
|
||||||
|
setSubmitMessage(null);
|
||||||
|
signaturePadRef.current?.clear();
|
||||||
|
}}
|
||||||
|
disabled={submitLoading}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className='py-2 px-6 bg-blue-600 text-white hover:bg-blue-700'
|
||||||
|
onClick={handleSubmitSign}
|
||||||
|
disabled={submitLoading}
|
||||||
|
>
|
||||||
|
{submitLoading ? '提交中...' : '提交签名'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{submitMessage && (
|
|
||||||
<div className={`text-sm text-center ${submitMessage.includes('成功') ? 'text-green-600' : 'text-amber-600'}`}>{submitMessage}</div>
|
|
||||||
)}
|
|
||||||
<div className='flex items-center justify-end gap-3'>
|
|
||||||
<Button
|
|
||||||
className='py-2 px-6'
|
|
||||||
onClick={() => {
|
|
||||||
setShowSignature(false);
|
|
||||||
setSubmitMessage(null);
|
|
||||||
signaturePadRef.current?.clear();
|
|
||||||
}}
|
|
||||||
disabled={submitLoading}
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
className='py-2 px-6 bg-blue-600 text-white hover:bg-blue-700'
|
|
||||||
onClick={handleSubmitSign}
|
|
||||||
disabled={submitLoading}
|
|
||||||
>
|
|
||||||
{submitLoading ? '提交中...' : '提交签名'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
)}
|
}
|
||||||
</div>
|
</div >
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user