Initial commit
This commit is contained in:
284
src/pages/UI7/UI7.tsx
Normal file
284
src/pages/UI7/UI7.tsx
Normal file
@@ -0,0 +1,284 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import "./UI7.css";
|
||||
import "../../assets/css/basic.css";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import BackButton from "../../components/BackButton";
|
||||
import ConfirmButton from "../../components/ConfirmButton";
|
||||
import DecorLine from "../../components/DecorLine";
|
||||
import WaitButton from "../../components/WaitButton";
|
||||
|
||||
const UI6: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const [countdown, setCountdown] = useState(5);
|
||||
const [showWaitButton, setShowWaitButton] = useState(true);
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const [isDrawing, setIsDrawing] = useState(false);
|
||||
const dprRef = useRef<number>(1);
|
||||
const lastPointRef = useRef<{ x: number; y: number } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (countdown > 0) {
|
||||
const timer = setTimeout(() => {
|
||||
setCountdown(countdown - 1);
|
||||
}, 1000);
|
||||
return () => clearTimeout(timer);
|
||||
} else {
|
||||
setShowWaitButton(false);
|
||||
}
|
||||
}, [countdown]);
|
||||
|
||||
useEffect(() => {
|
||||
const initCanvas = () => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext("2d", {
|
||||
willReadFrequently: false,
|
||||
alpha: true
|
||||
});
|
||||
if (!ctx) return;
|
||||
|
||||
// 获取 canvas 的显示尺寸
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
// 使用更高的缩放倍数(3倍)来提高分辨率
|
||||
const scale = 3;
|
||||
dprRef.current = scale;
|
||||
|
||||
// 设置 canvas 内部尺寸(高分辨率,3倍)
|
||||
canvas.width = rect.width * scale;
|
||||
canvas.height = rect.height * scale;
|
||||
|
||||
// 缩放上下文以匹配显示尺寸
|
||||
ctx.scale(scale, scale);
|
||||
|
||||
// 设置 canvas 的 CSS 尺寸为显示尺寸
|
||||
canvas.style.width = `${rect.width}px`;
|
||||
canvas.style.height = `${rect.height}px`;
|
||||
|
||||
// 设置绘制样式(启用抗锯齿,优化参数)
|
||||
ctx.strokeStyle = "#000000";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.lineCap = "round";
|
||||
ctx.lineJoin = "round";
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.imageSmoothingQuality = "high";
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
};
|
||||
|
||||
// 延迟初始化以确保 DOM 完全渲染
|
||||
const timer = setTimeout(initCanvas, 100);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
const getCoordinates = (e: React.MouseEvent<HTMLCanvasElement> | React.TouchEvent<HTMLCanvasElement>) => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return { x: 0, y: 0 };
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
if ("touches" in e) {
|
||||
return {
|
||||
x: e.touches[0].clientX - rect.left,
|
||||
y: e.touches[0].clientY - rect.top,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
x: e.clientX - rect.left,
|
||||
y: e.clientY - rect.top,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const startDrawing = (e: React.MouseEvent<HTMLCanvasElement> | React.TouchEvent<HTMLCanvasElement>) => {
|
||||
e.preventDefault();
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
const { x, y } = getCoordinates(e);
|
||||
|
||||
// 记录起始点
|
||||
lastPointRef.current = { x, y };
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y);
|
||||
setIsDrawing(true);
|
||||
};
|
||||
|
||||
const draw = (e: React.MouseEvent<HTMLCanvasElement> | React.TouchEvent<HTMLCanvasElement>) => {
|
||||
e.preventDefault();
|
||||
if (!isDrawing || !lastPointRef.current) return;
|
||||
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
const { x, y } = getCoordinates(e);
|
||||
const lastPoint = lastPointRef.current;
|
||||
|
||||
// 使用二次贝塞尔曲线平滑绘制
|
||||
const midX = (lastPoint.x + x) / 2;
|
||||
const midY = (lastPoint.y + y) / 2;
|
||||
|
||||
ctx.quadraticCurveTo(lastPoint.x, lastPoint.y, midX, midY);
|
||||
ctx.stroke();
|
||||
|
||||
// 更新最后一个点
|
||||
lastPointRef.current = { x, y };
|
||||
};
|
||||
|
||||
const stopDrawing = () => {
|
||||
setIsDrawing(false);
|
||||
lastPointRef.current = null;
|
||||
};
|
||||
|
||||
const clearCanvas = () => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
// 清除整个 canvas(使用显示坐标系统)
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
ctx.clearRect(0, 0, rect.width, rect.height);
|
||||
|
||||
// 重新初始化绘制样式
|
||||
ctx.strokeStyle = "#000000";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.lineCap = "round";
|
||||
ctx.lineJoin = "round";
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) {
|
||||
navigate("/UI7");
|
||||
return;
|
||||
}
|
||||
|
||||
// 下载签名图片
|
||||
canvas.toBlob((blob) => {
|
||||
if (!blob) return;
|
||||
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `签名_${new Date().getTime()}.png`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
}, "image/png");
|
||||
|
||||
navigate("/UI7");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="basic-root">
|
||||
<div className="basic-white-block">
|
||||
<div className="basic-content">
|
||||
<span className="basic-title">体检知情同意书确认</span>
|
||||
<DecorLine />
|
||||
|
||||
<div className="ui7-text-wrapper">
|
||||
<div className="ui7-text-content">
|
||||
<span className="paragraph_1">
|
||||
尊敬的受检者:
|
||||
<br />
|
||||
<br />
|
||||
欢迎您参加本次健康体检!为保障您的合法权益,现就体检相关事项告知如下,请您认真阅读并确认签署。
|
||||
<br />
|
||||
一、体检目的
|
||||
<br />
|
||||
<br />
|
||||
本次健康体检旨在通过常规医学检查,了解您的身体健康状况,发现潜在的疾病风险,供您及医生进行健康指导与干预。
|
||||
<br />
|
||||
1.
|
||||
<br />
|
||||
体检结果仅供参考,不等同于临床诊断。如发现异常,请及时到相关专科医院进一步检查与治疗。
|
||||
<br />
|
||||
2.
|
||||
<br />
|
||||
二、体检须知
|
||||
<br />
|
||||
3.
|
||||
<br />
|
||||
空腹要求:抽血、腹部B超等项目需空腹8小时以上。
|
||||
<br />
|
||||
4.
|
||||
<br />
|
||||
女性注意事项:经期及妊娠期女性请提前告知医护人员,避免放射类检查(如胸片、CT等)。
|
||||
<br />
|
||||
5.
|
||||
<br />
|
||||
贵重物品:请妥善保管个人物品,中心不承担遗失责任。
|
||||
<br />
|
||||
身体状况告知:如有重大疾病史、药物过敏史、手术史等,请提前告知医护人员。
|
||||
<br />
|
||||
体检安全:如在体检过程中出现不适,应立即告知现场医护人员。
|
||||
<br />
|
||||
<br />
|
||||
三、隐私与信息保护
|
||||
<br />
|
||||
1.
|
||||
<br />
|
||||
体检机构将严格遵守国家相关法律法规,对您的个人信息和体检资料予以保密,未经本人授权,不会向第三方泄露。
|
||||
<br />
|
||||
2.
|
||||
<br />
|
||||
四、体检风险告知
|
||||
<br />
|
||||
3.
|
||||
<br />
|
||||
部分检查(如抽血、X射线、CT、宫颈刮片等)可能带来轻微不适或风险。
|
||||
<br />
|
||||
体检结果可能存在一定误差,需结合临床进一步判断。
|
||||
<br />
|
||||
若因个人隐瞒病史或未遵守体检要求导致结果偏差,责任由受检者自
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<span className="ui7-text_4">请阅读后在下方签名确认</span>
|
||||
|
||||
<div className="ui7-signature-wrapper">
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="ui7-signature-canvas"
|
||||
onMouseDown={startDrawing}
|
||||
onMouseMove={draw}
|
||||
onMouseUp={stopDrawing}
|
||||
onMouseLeave={stopDrawing}
|
||||
onTouchStart={startDrawing}
|
||||
onTouchMove={draw}
|
||||
onTouchEnd={stopDrawing}
|
||||
/>
|
||||
<button className="ui7-clear-button" onClick={clearCanvas}>
|
||||
清除
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="basic-confirm-section">
|
||||
<BackButton text="返回" onClick={handleBack} />
|
||||
{showWaitButton ? (
|
||||
<WaitButton text={`等待(${countdown})S`} onClick={handleConfirm} />
|
||||
) : (
|
||||
<ConfirmButton text="提交" onClick={handleConfirm} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UI6;
|
||||
Reference in New Issue
Block a user