添加iPad签到功能
This commit is contained in:
@@ -12,6 +12,8 @@ import type {
|
||||
PhysicalExamProgressDetailResponse,
|
||||
InputCustomerDetail,
|
||||
CustomerDetailResponse,
|
||||
InputMedicalExamCenterSignIn,
|
||||
PhysicalExamSignInResponse,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
@@ -98,3 +100,15 @@ export const getCustomerDetail = (
|
||||
).then(res => res.data);
|
||||
};
|
||||
|
||||
/**
|
||||
* iPad 体检中心签到
|
||||
*/
|
||||
export const signInMedicalExamCenter = (
|
||||
data: InputMedicalExamCenterSignIn
|
||||
): Promise<PhysicalExamSignInResponse> => {
|
||||
return request.post<PhysicalExamSignInResponse>(
|
||||
`${MEDICAL_EXAM_BASE_PATH}/sign-in`,
|
||||
data
|
||||
).then(res => res.data);
|
||||
};
|
||||
|
||||
|
||||
@@ -251,3 +251,24 @@ export interface OutputCustomerDetail {
|
||||
*/
|
||||
export type CustomerDetailResponse = CommonActionResult<OutputCustomerDetail>;
|
||||
|
||||
/**
|
||||
* 体检中心签到入参
|
||||
*/
|
||||
export interface InputMedicalExamCenterSignIn {
|
||||
/** 身份证号 */
|
||||
id_no: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 体检中心签到出参
|
||||
*/
|
||||
export interface OutputPhysicalExamSignIn {
|
||||
/** 是否签到成功(0-成功 1-失败) */
|
||||
is_success?: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 体检中心签到响应
|
||||
*/
|
||||
export type PhysicalExamSignInResponse = CommonActionResult<OutputPhysicalExamSignIn>;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import type { ExamClient, ExamModalTab } from '../../data/mockData';
|
||||
import type {
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
CustomerInfo,
|
||||
PhysicalExamProgressItem,
|
||||
} from '../../api';
|
||||
import { getCustomerDetail, getPhysicalExamProgressDetail } from '../../api';
|
||||
import { getCustomerDetail, getPhysicalExamProgressDetail, signInMedicalExamCenter } from '../../api';
|
||||
import { Button, Input } from '../ui';
|
||||
|
||||
interface ExamModalProps {
|
||||
@@ -175,28 +175,104 @@ export const ExamModal = ({ client, tab, onTabChange, onClose }: ExamModalProps)
|
||||
);
|
||||
};
|
||||
|
||||
const ExamSignPanel = () => (
|
||||
const ExamSignPanel = () => {
|
||||
const [idNo, setIdNo] = useState('');
|
||||
const [ocrLoading, setOcrLoading] = useState(false);
|
||||
const [signLoading, setSignLoading] = useState(false);
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const handlePickFile = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
const mockOcr = async (file: File) => {
|
||||
// 简单模拟 OCR:提取文件名中的数字,或返回示例身份证
|
||||
const match = file.name.match(/\d{6,18}/);
|
||||
await new Promise((r) => setTimeout(r, 600));
|
||||
return match?.[0] || '440101199001010010';
|
||||
};
|
||||
|
||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
setOcrLoading(true);
|
||||
setMessage(null);
|
||||
try {
|
||||
const ocrId = await mockOcr(file);
|
||||
setIdNo(ocrId);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setMessage('OCR 识别失败,请重试或手动输入');
|
||||
} finally {
|
||||
setOcrLoading(false);
|
||||
e.target.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleSign = async () => {
|
||||
const trimmed = idNo.trim();
|
||||
if (!trimmed) {
|
||||
setMessage('请输入身份证号');
|
||||
return;
|
||||
}
|
||||
setSignLoading(true);
|
||||
setMessage(null);
|
||||
try {
|
||||
const res = await signInMedicalExamCenter({ id_no: trimmed });
|
||||
const ok = res.Status === 200 && res.Data?.is_success === 0;
|
||||
setMessage(ok ? '签到成功' : res.Message || '签到失败');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setMessage('签到请求失败,请稍后重试');
|
||||
} finally {
|
||||
setSignLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<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='font-medium'>身份证上传</div>
|
||||
<div className='text-xs text-gray-500'>支持身份证正反面拍照或读取设备,自动识别姓名、证件号等信息。</div>
|
||||
<div className='flex gap-2 text-xs'>
|
||||
<Button className='py-1.5 px-3'>上传身份证正面</Button>
|
||||
<Button className='py-1.5 px-3'>上传身份证反面</Button>
|
||||
<div className='font-medium'>身份证扫描与签到</div>
|
||||
<div className='text-xs text-gray-500'>
|
||||
支持扫描身份证识别证号,确认后再点击签到。
|
||||
</div>
|
||||
<div className='text-[11px] text-gray-400'>上传后进入预览界面,确认无误后返回签到界面。</div>
|
||||
<div className='flex items-center gap-3'>
|
||||
<Button className='py-1.5 px-3' onClick={handlePickFile} disabled={ocrLoading || signLoading}>
|
||||
{ocrLoading ? '识别中...' : '扫描身份证'}
|
||||
</Button>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type='file'
|
||||
accept='image/*'
|
||||
className='hidden'
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
<Input
|
||||
placeholder='身份证号'
|
||||
value={idNo}
|
||||
onChange={(e) => setIdNo(e.target.value)}
|
||||
className='flex-1'
|
||||
/>
|
||||
<Button
|
||||
className='py-1.5 px-4'
|
||||
onClick={handleSign}
|
||||
disabled={signLoading}
|
||||
>
|
||||
{signLoading ? '签到中...' : '签到'}
|
||||
</Button>
|
||||
</div>
|
||||
{message && <div className='text-xs text-gray-600'>{message}</div>}
|
||||
<div className='text-[11px] text-gray-400'>如识别不准,可手动修改后再签到。</div>
|
||||
</div>
|
||||
<div className='p-4 rounded-2xl border bg-gray-50/60 flex flex-col gap-3'>
|
||||
<div className='font-medium'>体检知情同意书</div>
|
||||
<div className='text-xs text-gray-500'>点击后弹出知情同意书全文及签名区域,签署完成后回到签到页面。</div>
|
||||
{/* <div className='flex gap-2 text-xs text-gray-600'>
|
||||
<Badge>阅读记录</Badge>
|
||||
<Badge>签名图片</Badge>
|
||||
</div> */}
|
||||
<Button className='py-1.5 px-3'>打开知情同意书并签署</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface AddonTag {
|
||||
title: string;
|
||||
|
||||
Reference in New Issue
Block a user