完善签到可选项
This commit is contained in:
@@ -2,8 +2,8 @@ import { useEffect, useRef, useState } from 'react';
|
|||||||
import * as pdfjsLib from 'pdfjs-dist';
|
import * as pdfjsLib from 'pdfjs-dist';
|
||||||
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url';
|
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url';
|
||||||
|
|
||||||
import type { OutputTongyishuFileInfo, OutputTijianPdfFileInfo } from '../../api';
|
import type { OutputTongyishuFileInfo, OutputTijianPdfFileInfo, OutputPhysicalExamItemInfo } from '../../api';
|
||||||
import { signInMedicalExamCenter, submitTongyishuSign, submitDaojiandanSign, editDaojiandanPrintStatus, submitAddItemBillSign, getTijianPdfFile, getTongyishuPdf, getDaojiandanPdf } from '../../api';
|
import { signInMedicalExamCenter, submitTongyishuSign, submitDaojiandanSign, editDaojiandanPrintStatus, submitAddItemBillSign, getTijianPdfFile, getTongyishuPdf, getDaojiandanPdf, getExamOptionalItemList, removeOptionalPackage } from '../../api';
|
||||||
import type { SignaturePadHandle } from '../ui';
|
import type { SignaturePadHandle } from '../ui';
|
||||||
import { Button, SignaturePad } from '../ui';
|
import { Button, SignaturePad } from '../ui';
|
||||||
|
|
||||||
@@ -81,8 +81,13 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
|||||||
const [batchPrintLoading, setBatchPrintLoading] = useState(false);
|
const [batchPrintLoading, setBatchPrintLoading] = useState(false);
|
||||||
const addItemBillCanvasContainerRef = useRef<HTMLDivElement>(null);
|
const addItemBillCanvasContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// 可选项目列表相关状态
|
||||||
|
const [optionalItemList, setOptionalItemList] = useState<OutputPhysicalExamItemInfo[]>([]);
|
||||||
|
const [optionalItemLoading, setOptionalItemLoading] = useState(false);
|
||||||
|
const [selectedOptionalItem, setSelectedOptionalItem] = useState<number | null>(null);
|
||||||
|
|
||||||
const busy =
|
const busy =
|
||||||
signLoading || submitLoading || consentLoading || pdfLoading || daojiandanSubmitLoading || addItemBillSubmitLoading || batchPrintLoading;
|
signLoading || submitLoading || consentLoading || pdfLoading || daojiandanSubmitLoading || addItemBillSubmitLoading || batchPrintLoading || optionalItemLoading;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onBusyChange?.(busy);
|
onBusyChange?.(busy);
|
||||||
@@ -194,8 +199,24 @@ 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);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取可选项目列表失败', err);
|
||||||
|
} finally {
|
||||||
|
setOptionalItemLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 先加载知情同意书和导检单,然后刷新所有PDF状态(包括签名状态)
|
// 先加载知情同意书和导检单,然后刷新所有PDF状态(包括签名状态)
|
||||||
Promise.all([loadTongyishu(), loadDaojiandan()]).then(() => {
|
Promise.all([loadTongyishu(), loadDaojiandan(), loadOptionalItems()]).then(() => {
|
||||||
refreshTijianPdfs(examId);
|
refreshTijianPdfs(examId);
|
||||||
});
|
});
|
||||||
}, [examId]);
|
}, [examId]);
|
||||||
@@ -268,6 +289,36 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
|||||||
setMessage('请先上传身份证照片');
|
setMessage('请先上传身份证照片');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!examId) {
|
||||||
|
setMessage('缺少体检ID');
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setSignLoading(true);
|
setSignLoading(true);
|
||||||
setMessage(null);
|
setMessage(null);
|
||||||
try {
|
try {
|
||||||
@@ -1620,6 +1671,7 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='grid grid-cols-2 gap-4 text-sm'>
|
<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='p-4 rounded-2xl border bg-gray-50/60 flex flex-col gap-3'>
|
||||||
@@ -1652,6 +1704,42 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
|||||||
{message && (
|
{message && (
|
||||||
<div className={`text-xs ${message.includes('成功') ? 'text-green-600' : 'text-amber-600'}`}>{message}</div>
|
<div className={`text-xs ${message.includes('成功') ? 'text-green-600' : 'text-amber-600'}`}>{message}</div>
|
||||||
)}
|
)}
|
||||||
|
{/* 可选项目列表 */}
|
||||||
|
{optionalItemLoading ? (
|
||||||
|
<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='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;
|
||||||
|
return (
|
||||||
|
<label
|
||||||
|
key={combinationCode ?? `item-${item.package_code}`}
|
||||||
|
className='flex items-center gap-2 cursor-pointer text-xs'
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type='radio'
|
||||||
|
name='optional-item'
|
||||||
|
checked={isSelected}
|
||||||
|
onChange={() => {
|
||||||
|
if (combinationCode != null) {
|
||||||
|
setSelectedOptionalItem(combinationCode);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={busy}
|
||||||
|
className='w-3 h-3'
|
||||||
|
/>
|
||||||
|
<span className={isSelected ? 'text-blue-600 font-medium' : 'text-gray-700'}>
|
||||||
|
{item.combination_name || '未命名项目'}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{showImagePreview && previewImage && (
|
{showImagePreview && previewImage && (
|
||||||
<div className='fixed inset-0 z-[80] bg-black/90 flex items-center justify-center p-6' onClick={() => setShowImagePreview(false)}>
|
<div className='fixed inset-0 z-[80] bg-black/90 flex items-center justify-center p-6' onClick={() => setShowImagePreview(false)}>
|
||||||
|
|||||||
Reference in New Issue
Block a user