添加接口

This commit is contained in:
xianyi
2026-01-15 15:24:34 +08:00
parent 34b9a872f0
commit 7205c13c76
3 changed files with 327 additions and 40 deletions

View File

@@ -3,7 +3,19 @@ import * as pdfjsLib from 'pdfjs-dist';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url';
import type { OutputTongyishuFileInfo, OutputTijianPdfFileInfo, OutputPhysicalExamItemInfo } from '../../api';
import { signInMedicalExamCenter, submitTongyishuSign, submitDaojiandanSign, editDaojiandanPrintStatus, submitAddItemBillSign, getTijianPdfFile, getTongyishuPdf, getDaojiandanPdf, getExamOptionalItemList, removeOptionalPackage } from '../../api';
import {
signInMedicalExamCenter,
submitTongyishuSign,
submitDaojiandanSign,
editDaojiandanPrintStatus,
submitAddItemBillSign,
getTijianPdfFile,
getTongyishuPdf,
getDaojiandanPdf,
getExamOptionalItemList,
removeOptionalPackage,
getExamOptionRecordList,
} from '../../api';
import type { SignaturePadHandle } from '../ui';
import { Button, SignaturePad } from '../ui';
@@ -85,9 +97,20 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
const [optionalItemList, setOptionalItemList] = useState<OutputPhysicalExamItemInfo[]>([]);
const [optionalItemLoading, setOptionalItemLoading] = useState(false);
const [selectedOptionalItem, setSelectedOptionalItem] = useState<number | null>(null);
// 是否已经确认过可选项目(如果后端已有记录,视为已确认)
const [optionalConfirmed, setOptionalConfirmed] = useState(false);
// 是否显示“请先确认体检项目,确认后不可修改”的提示
const [showOptionalConfirmTip, setShowOptionalConfirmTip] = useState(false);
const busy =
signLoading || submitLoading || consentLoading || pdfLoading || daojiandanSubmitLoading || addItemBillSubmitLoading || batchPrintLoading || optionalItemLoading;
signLoading ||
submitLoading ||
consentLoading ||
pdfLoading ||
daojiandanSubmitLoading ||
addItemBillSubmitLoading ||
batchPrintLoading ||
optionalItemLoading;
useEffect(() => {
onBusyChange?.(busy);
@@ -199,17 +222,67 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
}
};
// 加载可选项目列表
// 加载可选项目列表 & 操作记录(用于判断是否已选择)
const loadOptionalItems = async () => {
if (!examId) return;
setOptionalItemLoading(true);
try {
const res = await getExamOptionalItemList({ physical_exam_id: examId });
if (res.Status === 200 && res.Data?.listOptionalItem) {
setOptionalItemList(res.Data.listOptionalItem);
const [listRes, recordRes] = await Promise.all([
getExamOptionalItemList({ physical_exam_id: examId }),
getExamOptionRecordList({ exam_id: examId }),
]);
if (listRes.Status === 200 && listRes.Data?.listOptionalItem) {
const items = listRes.Data.listOptionalItem;
setOptionalItemList(items);
// 根据操作记录判断是否已经选择过is_abandon = 0
let selectedFromRecord: number | null = null;
if (recordRes.Status === 200 && recordRes.Data) {
const selectedRecord = recordRes.Data.find(
(r) => r.is_abandon === 0 && r.combination_code
);
if (selectedRecord?.combination_code) {
const codeNum = Number(selectedRecord.combination_code);
if (Number.isFinite(codeNum)) {
selectedFromRecord = codeNum;
}
}
}
if (items.length > 0) {
if (
selectedFromRecord != null &&
items.some((i) => i.combination_code === selectedFromRecord)
) {
// 后端已有已选记录
setSelectedOptionalItem(selectedFromRecord);
setOptionalConfirmed(true);
setShowOptionalConfirmTip(false);
} else {
// 尚未选择,需要前端引导用户选择并确认
setSelectedOptionalItem(null);
setOptionalConfirmed(false);
setShowOptionalConfirmTip(false);
}
} else {
// 没有可选项目,则视为无需确认
setSelectedOptionalItem(null);
setOptionalConfirmed(true);
setShowOptionalConfirmTip(false);
}
} else {
setOptionalItemList([]);
setSelectedOptionalItem(null);
setOptionalConfirmed(true);
setShowOptionalConfirmTip(false);
}
} catch (err) {
console.error('获取可选项目列表失败', err);
console.error('获取可选项目/记录失败', err);
setOptionalItemList([]);
setSelectedOptionalItem(null);
setOptionalConfirmed(true);
setShowOptionalConfirmTip(false);
} finally {
setOptionalItemLoading(false);
}
@@ -222,6 +295,12 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
}, [examId]);
const handlePickFile = () => {
// 有可选项目但尚未确认时,禁止拍照并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) {
setMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
fileInputRef.current?.click();
};
@@ -294,29 +373,11 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
return;
}
// 如果可选项目,先移除未选中的项目
if (optionalItemList.length > 0 && selectedOptionalItem != null) {
const unselectedCodes = optionalItemList
.map((item) => item.combination_code)
.filter((code): code is number => code != null && code !== selectedOptionalItem);
if (unselectedCodes.length > 0) {
try {
const combinationCodeIds = unselectedCodes.join(',');
const removeRes = await removeOptionalPackage({
physical_exam_id: examId,
combination_code_ids: combinationCodeIds,
});
if (removeRes.Status !== 200 || removeRes.Data?.is_success !== 1) {
setMessage('移除未选项目失败:' + (removeRes.Message || '未知错误'));
return;
}
} catch (err) {
console.error('移除未选项目失败', err);
setMessage('移除未选项目失败,请稍后重试');
return;
}
}
// 如果存在可选项目但尚未确认,则不允许直接签到
if (optionalItemList.length > 0 && !optionalConfirmed) {
setMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
setSignLoading(true);
@@ -337,7 +398,74 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
}
};
// 确认当前选择的可选项目:移除未选择的项目
const handleConfirmOptionalSelection = async () => {
if (!examId) {
setMessage('缺少体检ID');
return;
}
if (optionalItemList.length === 0) {
setOptionalConfirmed(true);
setShowOptionalConfirmTip(false);
return;
}
if (selectedOptionalItem == null) {
setMessage('请选择一个可选项目');
setShowOptionalConfirmTip(true);
return;
}
const unselectedCodes = optionalItemList
.map((item) => item.combination_code)
.filter((code): code is number => code != null && code !== selectedOptionalItem);
// 没有未选项目需要移除,直接标记为已确认
if (unselectedCodes.length === 0) {
setOptionalConfirmed(true);
setShowOptionalConfirmTip(false);
setMessage('可选项目已确定');
setTimeout(() => setMessage(null), 3000);
return;
}
setOptionalItemLoading(true);
try {
const combinationCodeIds = unselectedCodes.join(',');
const res = await removeOptionalPackage({
physical_exam_id: examId,
combination_code_ids: combinationCodeIds,
});
if (res.Status === 200 && res.Data?.is_success === 1) {
setOptionalConfirmed(true);
setShowOptionalConfirmTip(false);
// 刷新剩余可选项目列表(理论上只剩一个)
const refreshRes = await getExamOptionalItemList({ physical_exam_id: examId });
if (refreshRes.Status === 200 && refreshRes.Data?.listOptionalItem) {
setOptionalItemList(refreshRes.Data.listOptionalItem);
}
setMessage('可选项目已确定');
setTimeout(() => setMessage(null), 3000);
} else {
setMessage('可选项目确定失败:' + (res.Message || '未知错误'));
setTimeout(() => setMessage(null), 3000);
}
} catch (err) {
console.error('可选项目确定失败', err);
setMessage('可选项目确定失败,请稍后重试');
setTimeout(() => setMessage(null), 3000);
} finally {
setOptionalItemLoading(false);
}
};
const handleSubmitSign = async () => {
// 有可选项目但尚未确认时,禁止签名并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) {
setSubmitMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
if (!examId || !previewPdf?.combination_code) {
setSubmitMessage('缺少必要信息,无法提交签名');
return;
@@ -1006,6 +1134,13 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
// 导检单签名提交
const handleSubmitDaojiandanSign = async () => {
// 有可选项目但尚未确认时,禁止签名并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) {
setDaojiandanSubmitMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
if (!examId) {
setDaojiandanSubmitMessage('缺少必要信息,无法提交签名');
return;
@@ -1697,7 +1832,11 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
<img src={previewImage} alt='身份证预览' className='w-full h-full object-cover' />
</div>
)}
<Button className='py-1.5 px-4 flex-1' onClick={handleSign} disabled={busy || !idCardFile}>
<Button
className='py-1.5 px-4 flex-1'
onClick={handleSign}
disabled={busy || !idCardFile || (optionalItemList.length > 0 && !optionalConfirmed)}
>
{signLoading ? '签到中...' : '签到'}
</Button>
</div>
@@ -1709,11 +1848,14 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
<div className='text-xs text-gray-500'>...</div>
) : optionalItemList.length > 0 ? (
<div className='mt-2 space-y-2'>
<div className='text-xs font-medium text-gray-700'></div>
<div className='text-xs font-medium text-gray-700'>
{optionalConfirmed ? '(已确定)' : ''}
</div>
<div className='max-h-32 overflow-y-auto custom-scroll space-y-1'>
{optionalItemList.map((item) => {
const combinationCode = item.combination_code;
const isSelected = combinationCode != null && combinationCode === selectedOptionalItem;
const isSelected =
combinationCode != null && combinationCode === selectedOptionalItem;
return (
<label
key={combinationCode ?? `item-${item.package_code}`}
@@ -1724,11 +1866,11 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
name='optional-item'
checked={isSelected}
onChange={() => {
if (combinationCode != null) {
if (combinationCode != null && !optionalConfirmed) {
setSelectedOptionalItem(combinationCode);
}
}}
disabled={busy}
disabled={busy || optionalConfirmed}
className='w-3 h-3'
/>
<span className={isSelected ? 'text-blue-600 font-medium' : 'text-gray-700'}>
@@ -1738,6 +1880,22 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
);
})}
</div>
{!optionalConfirmed && (
<div className='flex items-center gap-2'>
<Button
className='py-1 px-3 text-xs bg-blue-600 hover:bg-blue-700 text-white'
onClick={handleConfirmOptionalSelection}
disabled={busy || selectedOptionalItem == null}
>
</Button>
{showOptionalConfirmTip && (
<span className='text-[11px] text-gray-500'>
</span>
)}
</div>
)}
</div>
) : null}
</div>
@@ -1757,7 +1915,15 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
{checkAllSigned() && (
<Button
className='py-1 px-3 bg-green-600 hover:bg-green-700 text-white text-xs'
onClick={handleBatchPrint}
onClick={() => {
// 有可选项目但尚未确认时,禁止打印并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) {
setMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
handleBatchPrint();
}}
disabled={busy || batchPrintLoading}
>
{batchPrintLoading ? '加载中...' : '一键打印'}
@@ -1792,6 +1958,12 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
className='py-1.5 px-3 bg-blue-600 hover:bg-blue-700 text-white'
onClick={() => {
if (busy) return;
// 有可选项目但尚未确认时,禁止打印并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) {
setMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
handleDirectPrint(item);
}}
disabled={busy}
@@ -1803,7 +1975,12 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
className='py-1.5 px-3'
onClick={() => {
if (busy) return;
// 有可选项目但尚未确认时,禁止查看并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) {
setMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
setPreviewPdf(item);
}}
disabled={busy}
@@ -1833,6 +2010,12 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
className='py-1.5 px-3 bg-blue-600 hover:bg-blue-700 text-white'
onClick={() => {
if (busy) return;
// 有可选项目但尚未确认时,禁止打印并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) {
setMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
handleDaojiandanDirectPrint();
}}
disabled={busy}
@@ -1843,6 +2026,12 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
className='py-1.5 px-3'
onClick={() => {
if (busy) return;
// 有可选项目但尚未确认时,禁止查看并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) {
setMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
setShowDaojiandanPreview(true);
}}
disabled={busy}
@@ -1857,6 +2046,12 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
className='py-1.5 px-3'
onClick={() => {
if (busy) return;
// 有可选项目但尚未确认时,禁止查看并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) {
setMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
setShowDaojiandanPreview(true);
}}
disabled={busy}
@@ -1867,6 +2062,12 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
className='py-1.5 px-3'
onClick={() => {
if (busy) return;
// 有可选项目但尚未确认时,禁止签名并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) {
setMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
setShowDaojiandanSignature(true);
}}
disabled={busy}
@@ -1880,6 +2081,12 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
className='py-1.5 px-3'
onClick={() => {
if (busy) return;
// 有可选项目但尚未确认时,禁止签名并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) {
setMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
setShowDaojiandanSignature(true);
}}
disabled={busy}
@@ -1972,12 +2179,33 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
<div className='flex items-center gap-2'>
<Button
className='py-1 px-3 bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50 disabled:cursor-not-allowed'
onClick={handlePrint}
onClick={() => {
// 有可选项目但尚未确认时,禁止打印并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) {
setMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
handlePrint();
}}
disabled={pdfLoading || !pdfReady || !pdfBlobUrl}
>
</Button>
<Button className='py-1 px-3' onClick={() => !busy && setShowSignature(true)} disabled={busy}>
<Button
className='py-1 px-3'
onClick={() => {
if (busy) return;
// 有可选项目但尚未确认时,禁止签名并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) {
setMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
setShowSignature(true);
}}
disabled={busy}
>
</Button>
<Button className='py-1 px-3' onClick={() => !busy && setPreviewPdf(null)} disabled={busy}>
@@ -2113,7 +2341,15 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
<div className='flex items-center gap-2'>
<Button
className='py-1 px-3 bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50 disabled:cursor-not-allowed'
onClick={handleDaojiandanDirectPrint}
onClick={() => {
// 有可选项目但尚未确认时,禁止打印并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) {
setMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
handleDaojiandanDirectPrint();
}}
disabled={busy}
>
@@ -2122,6 +2358,12 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
className='py-1 px-3 hover:bg-blue-700 text-white'
onClick={() => {
if (busy) return;
// 有可选项目但尚未确认时,禁止签名并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) {
setMessage('请先确认体检项目');
setShowOptionalConfirmTip(true);
return;
}
setShowDaojiandanPreview(false);
setShowDaojiandanSignature(true);
}}