This commit is contained in:
xianyi
2025-11-28 16:55:31 +08:00
4 changed files with 139 additions and 74 deletions

View File

@@ -28,6 +28,7 @@ export interface PatientInfo {
phone: string; phone: string;
marital: number; marital: number;
marital_name: string; marital_name: string;
is_valid_exam: number;
} }
// VIP认证结果 // VIP认证结果

View File

@@ -13,40 +13,18 @@ const U1: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [reading, setReading] = useState(false); const [reading, setReading] = useState(false);
const [countdown, setCountdown] = useState(6);
const timerRef = useRef<number | null>(null); const timerRef = useRef<number | null>(null);
const intervalRef = useRef<number | null>(null);
const handleStart = () => { // 实时监听身份证读取
// localStorage.setItem("lastIdCardNo", "31010919571209004X"); useEffect(() => {
// navigate("/u2"); // 启动后端监听
if (reading) return; // 避免重复点击
setReading(true);
// 启动后端监听;如果启动失败立即恢复 UI 状态
window.electronAPI.startIdCardListen().catch((e: any) => { window.electronAPI.startIdCardListen().catch((e: any) => {
console.error("start_idcard_listen failed", e); console.error("start_idcard_listen failed", e);
window.electronAPI.log("error", `start_idcard_listen failed: ${e}`); window.electronAPI.log("error", `start_idcard_listen failed: ${e}`);
setReading(false);
}); });
// 6 秒超时恢复
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
timerRef.current = window.setTimeout(() => {
console.warn("未在 6 秒内读取到身份证信息,恢复初始状态");
window.electronAPI.log(
"warn",
"未在 6 秒内读取到身份证信息,恢复初始状态"
);
setReading(false);
window.electronAPI.stopIdCardListen().catch(() => {});
timerRef.current = null;
}, 6000);
};
useEffect(() => {
if (!reading) return;
// 监听数据 // 监听数据
window.electronAPI.onIdCardData((e: any) => { window.electronAPI.onIdCardData((e: any) => {
const payload = e.payload; const payload = e.payload;
@@ -64,11 +42,18 @@ const U1: React.FC = () => {
"info", "info",
`Read IDCard success: ${payload.name} ${payload.id_card_no}` `Read IDCard success: ${payload.name} ${payload.id_card_no}`
); );
// 成功:清理定时器,停止监听
// 无论是否点击了开始签到,只要读到卡就跳转
// 清理定时器
if (timerRef.current) { if (timerRef.current) {
clearTimeout(timerRef.current); clearTimeout(timerRef.current);
timerRef.current = null; timerRef.current = null;
} }
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
// 停止监听(跳转后不再需要监听)
window.electronAPI.stopIdCardListen().catch(() => {}); window.electronAPI.stopIdCardListen().catch(() => {});
setReading(false); setReading(false);
navigate("/u2"); navigate("/u2");
@@ -78,20 +63,54 @@ const U1: React.FC = () => {
window.electronAPI.onIdCardError((e: any) => { window.electronAPI.onIdCardError((e: any) => {
console.error("[idcard-error]", e.payload); console.error("[idcard-error]", e.payload);
window.electronAPI.log("error", `[idcard-error] ${e.payload}`); window.electronAPI.log("error", `[idcard-error] ${e.payload}`);
// 发生错误时立即恢复 UI 状态 // 错误不停止监听,继续等待下一次读取,除非是严重错误?
// 这里仅记录日志,或者在 reading 状态下提示用户
if (reading) {
// 如果正在倒计时中出错,可以选择停止倒计时或者忽略
// 目前保持监听
}
});
return () => {
window.electronAPI.stopIdCardListen().catch(() => {});
window.electronAPI.removeIdCardListeners();
if (timerRef.current) clearTimeout(timerRef.current);
if (intervalRef.current) clearInterval(intervalRef.current);
};
}, [navigate, reading]);
const handleStart = () => {
if (reading) return; // 避免重复点击
setReading(true);
setCountdown(6);
// 6 秒倒计时逻辑
if (intervalRef.current) clearInterval(intervalRef.current);
intervalRef.current = window.setInterval(() => {
setCountdown((prev) => {
if (prev <= 1) {
if (intervalRef.current) clearInterval(intervalRef.current);
return 0;
}
return prev - 1;
});
}, 1000);
// 6 秒超时恢复
if (timerRef.current) { if (timerRef.current) {
clearTimeout(timerRef.current); clearTimeout(timerRef.current);
timerRef.current = null; timerRef.current = null;
} }
timerRef.current = window.setTimeout(() => {
console.warn("未在 6 秒内读取到身份证信息,恢复初始状态");
setReading(false); setReading(false);
window.electronAPI.stopIdCardListen().catch(() => {}); if (intervalRef.current) {
alert(`读取身份证失败: ${e.payload}`); clearInterval(intervalRef.current);
}); intervalRef.current = null;
}
return () => { timerRef.current = null;
window.electronAPI.removeIdCardListeners(); }, 6000);
}; };
}, [reading, navigate]);
return ( return (
<div className="u1-root"> <div className="u1-root">
@@ -105,7 +124,9 @@ const U1: React.FC = () => {
<span className="u1-instruction"></span> <span className="u1-instruction"></span>
<img className="u1-semicircle" alt="start button" src={semicircle} /> <img className="u1-semicircle" alt="start button" src={semicircle} />
{!reading && <LongButton text="开始签到" onClick={handleStart} />} {!reading && <LongButton text="开始签到" onClick={handleStart} />}
{reading && <LongButtonLoading text="身份信息读取中..." />} {reading && (
<LongButtonLoading text={`身份信息读取中... ${countdown}s`} />
)}
</div> </div>
); );
}; };

View File

@@ -27,10 +27,26 @@
background: url(../../assets/u2-card.png) 100% no-repeat; background: url(../../assets/u2-card.png) 100% no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
box-sizing: border-box; box-sizing: border-box;
display: flex;
flex-direction: column;
padding: 40px 48px;
}
.u2-info-top {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
padding: 40px 48px; width: 100%;
flex: 1;
}
.u2-error-msg {
width: 100%;
text-align: center;
color: red;
font-size: 28px;
font-weight: bold;
margin-top: 10px;
} }
.u2-avatar-container { .u2-avatar-container {

View File

@@ -32,7 +32,8 @@ const U2: React.FC = () => {
localStorage.setItem("name", res.Data.name); localStorage.setItem("name", res.Data.name);
localStorage.setItem("gender", res.Data.gender_name); localStorage.setItem("gender", res.Data.gender_name);
} else { } else {
alert(`${res.Message},请联系前台`); alert(`未查询到您的档案信息,请联系前台工作人员`);
navigate("/");
} }
}) })
.catch((err) => { .catch((err) => {
@@ -70,6 +71,11 @@ const U2: React.FC = () => {
}; };
const handleConfirm = () => { const handleConfirm = () => {
// 判断是否为有效签到
if (!patientInfo?.is_valid_exam || patientInfo.is_valid_exam !== 1) {
// alert("当前签到无效,请联系前台工作人员处理");
return;
}
if (!idCardNo) { if (!idCardNo) {
alert("未获取到身份证号,请重新刷卡"); alert("未获取到身份证号,请重新刷卡");
navigate("/"); navigate("/");
@@ -130,7 +136,7 @@ const U2: React.FC = () => {
else if (genderName === "女") title = "女士"; else if (genderName === "女") title = "女士";
return `尊敬的${ return `尊敬的${
firstChar ? `${firstChar}${title}` : title firstChar ? `${firstChar}${title}` : title
}欢迎您的到来`; }请确认您的个人信息`;
}; };
// 返回头像地址:使用 gender_name 字段("女" 显示女性头像),忽略 photo_path // 返回头像地址:使用 gender_name 字段("女" 显示女性头像),忽略 photo_path
@@ -147,6 +153,17 @@ const U2: React.FC = () => {
return genderName; return genderName;
}; };
// 脱敏证件号保留前6后4其余替换为*
const maskIdCard = (id?: string | null) => {
if (!id) return "---";
const s = String(id).trim();
if (s.length <= 10) return s.replace(/.(?!.{4})/g, "*");
const head = s.slice(0, 6);
const tail = s.slice(-4);
const middleLen = s.length - 10;
return head + "*".repeat(middleLen) + tail;
};
// 在数据加载时显示占位,加载完成后再渲染完整内容 // 在数据加载时显示占位,加载完成后再渲染完整内容
if (loading) { if (loading) {
return <></>; return <></>;
@@ -157,6 +174,7 @@ const U2: React.FC = () => {
<span className="u2-title">{getGreeting()}</span> <span className="u2-title">{getGreeting()}</span>
<DecorLine /> <DecorLine />
<div className="u2-info-card"> <div className="u2-info-card">
<div className="u2-info-top">
<div className="u2-avatar-container"> <div className="u2-avatar-container">
<img className="u2-avatar" src={getAvatarSrc()} alt="avatar" /> <img className="u2-avatar" src={getAvatarSrc()} alt="avatar" />
</div> </div>
@@ -185,7 +203,8 @@ const U2: React.FC = () => {
<div className="u2-detail-row"> <div className="u2-detail-row">
<div className="u2-detail-bar" /> <div className="u2-detail-bar" />
<div className="u2-detail-text"> <div className="u2-detail-text">
{idCardNo || patientInfo?.IdCard || "---"}
{loading ? "" : maskIdCard(idCardNo || patientInfo?.IdCard)}
</div> </div>
</div> </div>
@@ -204,6 +223,14 @@ const U2: React.FC = () => {
</div> </div>
</div> </div>
</div> </div>
{!patientInfo?.is_valid_exam || patientInfo.is_valid_exam !== 1 ? (
<div className="u2-error-msg">
!
</div>
) : (
""
)}
</div>
<span className="u2-instruction"> <span className="u2-instruction">
<span className="u2-asterisk">*</span> <span className="u2-asterisk">*</span>
</span> </span>