@@ -268,8 +299,82 @@ const ExamSignPanel = () => {
体检知情同意书
点击后弹出知情同意书全文及签名区域,签署完成后回到签到页面。
-
+
+ {consentLoading &&
加载中...
}
+ {!consentLoading && consentMessage && (
+
{consentMessage}
+ )}
+ {!consentLoading && consentList.length > 0 && (
+
+ {consentList.map((item) => (
+
+
{item.pdf_name}
+
+
+ ))}
+
+ )}
+
+ {previewPdf && (
+
+
+
{previewPdf.pdf_name}
+
+
+
+
+
+
+
+
+
+ )}
+ {showSignature && (
+
+
+
+
签署知情同意书
+
+
+
+
+
+
+
+
请在上方区域签名完成签到
+
+
+
+
+
+ )}
);
};
diff --git a/src/components/ui/SignaturePad.tsx b/src/components/ui/SignaturePad.tsx
new file mode 100644
index 0000000..3704c96
--- /dev/null
+++ b/src/components/ui/SignaturePad.tsx
@@ -0,0 +1,136 @@
+import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef } from 'react';
+import type { PointerEvent as ReactPointerEvent } from 'react';
+
+import { cls } from '../../utils/cls';
+
+export interface SignaturePadHandle {
+ clear: () => void;
+ toDataURL: (type?: string, quality?: number) => string | undefined;
+}
+
+interface SignaturePadProps {
+ className?: string;
+ lineWidth?: number;
+ strokeStyle?: string;
+ onDrawEnd?: (dataUrl: string) => void;
+}
+
+export const SignaturePad = forwardRef
(
+ ({ className = '', lineWidth = 5, strokeStyle = '#111827', onDrawEnd }, ref) => {
+ const canvasRef = useRef(null);
+ const isDrawingRef = useRef(false);
+ const lastPointRef = useRef<{ x: number; y: number } | null>(null);
+ const scaleRef = useRef({ scaleX: 1, scaleY: 1 });
+
+ const resizeCanvas = useCallback(() => {
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+ const rect = canvas.getBoundingClientRect();
+ const dpr = window.devicePixelRatio || 1;
+ canvas.width = rect.width * dpr;
+ canvas.height = rect.height * dpr;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return;
+ ctx.scale(dpr, dpr);
+ ctx.lineWidth = lineWidth;
+ ctx.lineCap = 'round';
+ ctx.strokeStyle = strokeStyle;
+ scaleRef.current = { scaleX: dpr, scaleY: dpr };
+ }, [lineWidth, strokeStyle]);
+
+ useEffect(() => {
+ resizeCanvas();
+ const handleResize = () => resizeCanvas();
+ window.addEventListener('resize', handleResize);
+ return () => window.removeEventListener('resize', handleResize);
+ }, [resizeCanvas]);
+
+ const getPoint = useCallback((e: ReactPointerEvent) => {
+ const canvas = canvasRef.current;
+ if (!canvas) return { x: 0, y: 0 };
+ const rect = canvas.getBoundingClientRect();
+ return {
+ // 使用 CSS 像素坐标,避免与 ctx.scale 的 DPR 缩放重复,防止漂移
+ x: e.clientX - rect.left,
+ y: e.clientY - rect.top,
+ };
+ }, []);
+
+ const startDrawing = useCallback(
+ (e: ReactPointerEvent) => {
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+ isDrawingRef.current = true;
+ lastPointRef.current = getPoint(e);
+ canvas.setPointerCapture(e.pointerId);
+ },
+ [getPoint],
+ );
+
+ const draw = useCallback(
+ (e: ReactPointerEvent) => {
+ if (!isDrawingRef.current) return;
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return;
+ const current = getPoint(e);
+ const last = lastPointRef.current || current;
+ ctx.beginPath();
+ ctx.moveTo(last.x, last.y);
+ ctx.lineTo(current.x, current.y);
+ ctx.stroke();
+ lastPointRef.current = current;
+ },
+ [getPoint],
+ );
+
+ const stopDrawing = useCallback(
+ (e?: ReactPointerEvent) => {
+ if (!isDrawingRef.current) return;
+ isDrawingRef.current = false;
+ lastPointRef.current = null;
+ if (e?.pointerId && canvasRef.current) {
+ canvasRef.current.releasePointerCapture(e.pointerId);
+ }
+ const dataUrl = canvasRef.current?.toDataURL();
+ if (dataUrl && onDrawEnd) {
+ onDrawEnd(dataUrl);
+ }
+ },
+ [onDrawEnd],
+ );
+
+ const clear = useCallback(() => {
+ const canvas = canvasRef.current;
+ const ctx = canvas?.getContext('2d');
+ if (canvas && ctx) {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ }
+ }, []);
+
+ useImperativeHandle(
+ ref,
+ () => ({
+ clear,
+ toDataURL: (type?: string, quality?: number) => canvasRef.current?.toDataURL(type, quality),
+ }),
+ [clear],
+ );
+
+ return (
+
+ );
+ },
+);
+
+SignaturePad.displayName = 'SignaturePad';
+
diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts
index 3838e3a..0b9e543 100644
--- a/src/components/ui/index.ts
+++ b/src/components/ui/index.ts
@@ -4,5 +4,6 @@ export * from './Card';
export * from './InfoCard';
export * from './Input';
export * from './Select';
+export * from './SignaturePad';