完善导检单预览
This commit is contained in:
@@ -18,6 +18,8 @@ import type {
|
|||||||
TongyishuGetResponse,
|
TongyishuGetResponse,
|
||||||
InputTongyishuSignSubmit,
|
InputTongyishuSignSubmit,
|
||||||
TongyishuSignSubmitResponse,
|
TongyishuSignSubmitResponse,
|
||||||
|
InputDaojiandanInfo,
|
||||||
|
DaojiandanGetResponse,
|
||||||
InputDaojiandanSignSubmit,
|
InputDaojiandanSignSubmit,
|
||||||
DaojiandanSignSubmitResponse,
|
DaojiandanSignSubmitResponse,
|
||||||
InputCustomerDetailEdit,
|
InputCustomerDetailEdit,
|
||||||
@@ -240,6 +242,18 @@ export const checkNativePaymentStatus = (
|
|||||||
).then(res => res.data);
|
).then(res => res.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取体检导检单PDF
|
||||||
|
*/
|
||||||
|
export const getDaojiandanPdf = (
|
||||||
|
data: InputDaojiandanInfo
|
||||||
|
): Promise<DaojiandanGetResponse> => {
|
||||||
|
return request.post<DaojiandanGetResponse>(
|
||||||
|
`${MEDICAL_EXAM_BASE_PATH}/daojiandan-get`,
|
||||||
|
data
|
||||||
|
).then(res => res.data);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提交体检签名生成导检单PDF
|
* 提交体检签名生成导检单PDF
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -346,6 +346,31 @@ export interface OutputTongyishuSignInfo {
|
|||||||
*/
|
*/
|
||||||
export type TongyishuSignSubmitResponse = CommonActionResult<OutputTongyishuSignInfo>;
|
export type TongyishuSignSubmitResponse = CommonActionResult<OutputTongyishuSignInfo>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取体检导检单PDF入参
|
||||||
|
*/
|
||||||
|
export interface InputDaojiandanInfo {
|
||||||
|
/** 体检ID */
|
||||||
|
exam_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取体检导检单PDF出参
|
||||||
|
*/
|
||||||
|
export interface OutputDaojiandanInfo {
|
||||||
|
/** 导检单文件名称 */
|
||||||
|
pdf_name?: string | null;
|
||||||
|
/** 导检单PDF文件地址 */
|
||||||
|
pdf_url?: string | null;
|
||||||
|
/** 提示信息 */
|
||||||
|
message?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取体检导检单PDF响应
|
||||||
|
*/
|
||||||
|
export type DaojiandanGetResponse = CommonActionResult<OutputDaojiandanInfo>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提交体检签名生成导检单PDF入参
|
* 提交体检签名生成导检单PDF入参
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { submitDaojiandanSign } from '../../api';
|
import { getDaojiandanPdf as getDaojiandanPdfApi, submitDaojiandanSign } from '../../api';
|
||||||
import type { ExamClient } from '../../data/mockData';
|
import type { ExamClient } from '../../data/mockData';
|
||||||
import { setExamActionRecord } from '../../utils/examActions';
|
import { setExamActionRecord, setDaojiandanPdf, getDaojiandanPdf } from '../../utils/examActions';
|
||||||
import type { SignaturePadHandle } from '../ui';
|
import type { SignaturePadHandle } from '../ui';
|
||||||
import { Button, SignaturePad } from '../ui';
|
import { Button, SignaturePad } from '../ui';
|
||||||
|
|
||||||
@@ -15,6 +15,8 @@ export const ExamPrintPanel = ({ client }: { client: ExamClient }) => {
|
|||||||
const [signatureSubmitted, setSignatureSubmitted] = useState(false);
|
const [signatureSubmitted, setSignatureSubmitted] = useState(false);
|
||||||
const [submitLoading, setSubmitLoading] = useState(false);
|
const [submitLoading, setSubmitLoading] = useState(false);
|
||||||
const [submitMessage, setSubmitMessage] = useState<string | null>(null);
|
const [submitMessage, setSubmitMessage] = useState<string | null>(null);
|
||||||
|
const [showPreview, setShowPreview] = useState(false);
|
||||||
|
const [fetchLoading, setFetchLoading] = useState(false);
|
||||||
const signaturePadRef = useRef<SignaturePadHandle | null>(null);
|
const signaturePadRef = useRef<SignaturePadHandle | null>(null);
|
||||||
const printRef = useRef<HTMLDivElement>(null);
|
const printRef = useRef<HTMLDivElement>(null);
|
||||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||||
@@ -47,7 +49,14 @@ export const ExamPrintPanel = ({ client }: { client: ExamClient }) => {
|
|||||||
if (res.Status === 200 && res.Data?.pdf_url) {
|
if (res.Status === 200 && res.Data?.pdf_url) {
|
||||||
setSubmitMessage('签名提交成功,正在加载导检单...');
|
setSubmitMessage('签名提交成功,正在加载导检单...');
|
||||||
setSignatureSubmitted(true);
|
setSignatureSubmitted(true);
|
||||||
setPdfUrl(res.Data.pdf_url);
|
const pdfUrlValue = res.Data.pdf_url;
|
||||||
|
const pdfNameValue = res.Data.pdf_name || '导检单';
|
||||||
|
setPdfUrl(pdfUrlValue);
|
||||||
|
// 保存导检单PDF信息到localStorage
|
||||||
|
setDaojiandanPdf(examId, {
|
||||||
|
pdf_name: pdfNameValue,
|
||||||
|
pdf_url: pdfUrlValue,
|
||||||
|
});
|
||||||
// 记录打印导检单是否签名操作
|
// 记录打印导检单是否签名操作
|
||||||
setExamActionRecord(examId, 'printSign', true);
|
setExamActionRecord(examId, 'printSign', true);
|
||||||
} else {
|
} else {
|
||||||
@@ -61,6 +70,62 @@ export const ExamPrintPanel = ({ client }: { client: ExamClient }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 组件加载时检查localStorage,如果有则直接展示
|
||||||
|
useEffect(() => {
|
||||||
|
const examId = Number(client.id);
|
||||||
|
if (!examId) return;
|
||||||
|
|
||||||
|
const storedPdf = getDaojiandanPdf(examId);
|
||||||
|
if (storedPdf && storedPdf.pdf_url) {
|
||||||
|
setPdfUrl(storedPdf.pdf_url);
|
||||||
|
setShowPreview(true);
|
||||||
|
}
|
||||||
|
}, [client.id]);
|
||||||
|
|
||||||
|
// 获取导检单PDF(优先使用localStorage,没有则调用接口)
|
||||||
|
const handleFetchPdf = async () => {
|
||||||
|
const examId = Number(client.id);
|
||||||
|
if (!examId) {
|
||||||
|
setError('无效的体检ID');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFetchLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 先尝试从localStorage获取
|
||||||
|
const storedPdf = getDaojiandanPdf(examId);
|
||||||
|
if (storedPdf && storedPdf.pdf_url) {
|
||||||
|
setPdfUrl(storedPdf.pdf_url);
|
||||||
|
setShowPreview(true);
|
||||||
|
setFetchLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有存储的,则调用接口获取
|
||||||
|
const res = await getDaojiandanPdfApi({ exam_id: examId });
|
||||||
|
if (res.Status === 200 && res.Data?.pdf_url) {
|
||||||
|
const pdfUrlValue = res.Data.pdf_url;
|
||||||
|
const pdfNameValue = res.Data.pdf_name || '导检单';
|
||||||
|
setPdfUrl(pdfUrlValue);
|
||||||
|
// 保存到localStorage
|
||||||
|
setDaojiandanPdf(examId, {
|
||||||
|
pdf_name: pdfNameValue,
|
||||||
|
pdf_url: pdfUrlValue,
|
||||||
|
});
|
||||||
|
setShowPreview(true);
|
||||||
|
} else {
|
||||||
|
setError(res.Message || '获取导检单失败');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取导检单失败', err);
|
||||||
|
setError('获取导检单失败,请稍后重试');
|
||||||
|
} finally {
|
||||||
|
setFetchLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!pdfUrl) return;
|
if (!pdfUrl) return;
|
||||||
|
|
||||||
@@ -110,7 +175,7 @@ export const ExamPrintPanel = ({ client }: { client: ExamClient }) => {
|
|||||||
<div>
|
<div>
|
||||||
<div className='text-sm font-semibold'>圆和医疗体检中心 · 导检单预览</div>
|
<div className='text-sm font-semibold'>圆和医疗体检中心 · 导检单预览</div>
|
||||||
<div className='text-[11px] text-gray-500 mt-1'>
|
<div className='text-[11px] text-gray-500 mt-1'>
|
||||||
{signatureSubmitted
|
{signatureSubmitted || showPreview
|
||||||
? '此为预览页面,实际打印效果以院内打印机为准。'
|
? '此为预览页面,实际打印效果以院内打印机为准。'
|
||||||
: '请先完成签名,签名后将生成导检单PDF'}
|
: '请先完成签名,签名后将生成导检单PDF'}
|
||||||
</div>
|
</div>
|
||||||
@@ -120,6 +185,38 @@ export const ExamPrintPanel = ({ client }: { client: ExamClient }) => {
|
|||||||
<div>体检号:{client.id}</div>
|
<div>体检号:{client.id}</div>
|
||||||
<div>日期:{new Date().toLocaleDateString('zh-CN')}</div>
|
<div>日期:{new Date().toLocaleDateString('zh-CN')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
{!showPreview && !signatureSubmitted && (
|
||||||
|
<Button
|
||||||
|
className='px-4 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
|
onClick={handleFetchPdf}
|
||||||
|
disabled={fetchLoading}
|
||||||
|
>
|
||||||
|
{fetchLoading ? '加载中...' : '查看'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{showPreview && !signatureSubmitted && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
className='px-4 py-1.5 text-xs bg-gray-600 hover:bg-gray-700 text-white'
|
||||||
|
onClick={() => {
|
||||||
|
setShowPreview(false);
|
||||||
|
setPdfUrl(null);
|
||||||
|
setPdfBlobUrl(null);
|
||||||
|
setPdfReady(false);
|
||||||
|
setError(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
签名
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className='px-4 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
|
onClick={handlePrint}
|
||||||
|
disabled={loading || !pdfReady || !pdfUrl}
|
||||||
|
>
|
||||||
|
打印
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{signatureSubmitted && (
|
{signatureSubmitted && (
|
||||||
<Button
|
<Button
|
||||||
className='px-4 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50 disabled:cursor-not-allowed'
|
className='px-4 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
@@ -132,7 +229,7 @@ export const ExamPrintPanel = ({ client }: { client: ExamClient }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!signatureSubmitted ? (
|
{!signatureSubmitted && !showPreview ? (
|
||||||
<div className='flex flex-col gap-4'>
|
<div className='flex flex-col gap-4'>
|
||||||
<div className='text-sm font-medium text-gray-900'>请在下方区域签名</div>
|
<div className='text-sm font-medium text-gray-900'>请在下方区域签名</div>
|
||||||
<div className='ui7-signature-wrapper border rounded-xl overflow-hidden bg-gray-50'>
|
<div className='ui7-signature-wrapper border rounded-xl overflow-hidden bg-gray-50'>
|
||||||
@@ -169,6 +266,10 @@ export const ExamPrintPanel = ({ client }: { client: ExamClient }) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : fetchLoading ? (
|
||||||
|
<div className='flex items-center justify-center py-12 text-gray-500'>
|
||||||
|
<div>正在加载导检单...</div>
|
||||||
|
</div>
|
||||||
) : loading ? (
|
) : loading ? (
|
||||||
<div className='flex items-center justify-center py-12 text-gray-500'>
|
<div className='flex items-center justify-center py-12 text-gray-500'>
|
||||||
<div>正在加载PDF...</div>
|
<div>正在加载PDF...</div>
|
||||||
@@ -176,17 +277,30 @@ export const ExamPrintPanel = ({ client }: { client: ExamClient }) => {
|
|||||||
) : error ? (
|
) : error ? (
|
||||||
<div className='flex flex-col items-center justify-center py-12 text-gray-500'>
|
<div className='flex flex-col items-center justify-center py-12 text-gray-500'>
|
||||||
<div className='mb-2'>{error}</div>
|
<div className='mb-2'>{error}</div>
|
||||||
<Button
|
{signatureSubmitted ? (
|
||||||
className='px-4 py-1.5 text-xs'
|
<Button
|
||||||
onClick={() => {
|
className='px-4 py-1.5 text-xs'
|
||||||
setError(null);
|
onClick={() => {
|
||||||
setPdfUrl(null);
|
setError(null);
|
||||||
setSignatureSubmitted(false);
|
setPdfUrl(null);
|
||||||
signaturePadRef.current?.clear();
|
setSignatureSubmitted(false);
|
||||||
}}
|
setShowPreview(false);
|
||||||
>
|
signaturePadRef.current?.clear();
|
||||||
重新签名
|
}}
|
||||||
</Button>
|
>
|
||||||
|
重新签名
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
className='px-4 py-1.5 text-xs'
|
||||||
|
onClick={() => {
|
||||||
|
setError(null);
|
||||||
|
setShowPreview(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : pdfUrl ? (
|
) : pdfUrl ? (
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const CLEAN_LAST_TIME_KEY = 'yh_local_clean_last_time';
|
|||||||
* - yh_tongyishu_pdf_list_2025-12-24_100057962
|
* - yh_tongyishu_pdf_list_2025-12-24_100057962
|
||||||
* - yh_exam_actions_2025-12-24_100057962
|
* - yh_exam_actions_2025-12-24_100057962
|
||||||
* - yh_signed_consents_2025-12-24
|
* - yh_signed_consents_2025-12-24
|
||||||
|
* - yh_daojiandan_pdf_2025-12-24_100057962
|
||||||
*/
|
*/
|
||||||
export const cleanLocalStorageIfNeeded = (): void => {
|
export const cleanLocalStorageIfNeeded = (): void => {
|
||||||
if (typeof window === 'undefined' || !window.localStorage) return;
|
if (typeof window === 'undefined' || !window.localStorage) return;
|
||||||
@@ -62,6 +63,16 @@ export const cleanLocalStorageIfNeeded = (): void => {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 匹配 yh_daojiandan_pdf_YYYY-MM-DD_xxx
|
||||||
|
const matchDaojiandan = key.match(/^yh_daojiandan_pdf_(\d{4}-\d{2}-\d{2})_/);
|
||||||
|
if (matchDaojiandan) {
|
||||||
|
const date = matchDaojiandan[1];
|
||||||
|
if (date !== today) {
|
||||||
|
keysToRemove.push(key);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行删除
|
// 执行删除
|
||||||
|
|||||||
@@ -138,3 +138,53 @@ export const getTongyishuPdfList = (examId: string | number): TongyishuPdfInfo[]
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导检单PDF信息
|
||||||
|
*/
|
||||||
|
export interface DaojiandanPdfInfo {
|
||||||
|
/** PDF文件名称 */
|
||||||
|
pdf_name: string;
|
||||||
|
/** PDF文件地址 */
|
||||||
|
pdf_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取导检单PDF的存储 key
|
||||||
|
*/
|
||||||
|
export const getDaojiandanPdfKey = (examId: string | number): string => {
|
||||||
|
const today = getTodayString();
|
||||||
|
return `yh_daojiandan_pdf_${today}_${examId}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储导检单PDF信息
|
||||||
|
*/
|
||||||
|
export const setDaojiandanPdf = (
|
||||||
|
examId: string | number,
|
||||||
|
pdfInfo: DaojiandanPdfInfo
|
||||||
|
): void => {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
|
const key = getDaojiandanPdfKey(examId);
|
||||||
|
localStorage.setItem(key, JSON.stringify(pdfInfo));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取导检单PDF信息
|
||||||
|
*/
|
||||||
|
export const getDaojiandanPdf = (examId: string | number): DaojiandanPdfInfo | null => {
|
||||||
|
if (typeof window === 'undefined') return null;
|
||||||
|
|
||||||
|
const key = getDaojiandanPdfKey(examId);
|
||||||
|
const raw = localStorage.getItem(key);
|
||||||
|
if (!raw) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
return parsed as DaojiandanPdfInfo;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('导检单PDF信息解析失败', err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user