From f6802a6e2643b7c4825d9078ce46da1bbb0f371f Mon Sep 17 00:00:00 2001 From: xianyi Date: Mon, 26 Jan 2026 16:37:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8A=A0=E9=A1=B9=E9=9C=80?= =?UTF-8?q?=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/his.ts | 14 + src/api/types.ts | 23 ++ src/components/exam/ExamAddonPanel.tsx | 537 ++++++++++++++++++++++++- 3 files changed, 569 insertions(+), 5 deletions(-) diff --git a/src/api/his.ts b/src/api/his.ts index 16c5c28..2967d4a 100644 --- a/src/api/his.ts +++ b/src/api/his.ts @@ -82,6 +82,8 @@ import type { CustomSettlementApproveStatusGetResponse, InputCustomSettlementApply, CustomSettlementApplyResponse, + InputCustomSettlementApplyCancel, + CustomSettlementApplyCancelResponse, } from './types'; /** @@ -633,3 +635,15 @@ export const customSettlementApply = ( data ).then(res => res.data); }; + +/** + * 体检加项自定义结算申请取消 + */ +export const customSettlementApplyCancel = ( + data: InputCustomSettlementApplyCancel +): Promise => { + return request.post( + `${MEDICAL_EXAM_BASE_PATH}/custom-settlement-apply-cancel`, + data + ).then(res => res.data); +}; diff --git a/src/api/types.ts b/src/api/types.ts index e4fe776..029a0fa 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -1461,3 +1461,26 @@ export interface OutputCustomSettlementApply { * 体检加项自定义结算申请响应 */ export type CustomSettlementApplyResponse = CommonActionResult; + +/** + * 体检加项自定义结算申请取消入参 + */ +export interface InputCustomSettlementApplyCancel { + /** 体检ID */ + physical_exam_id: number; + /** 体检加项组合ID(多个逗号分隔,例如:123,456) */ + add_item_id: string; +} + +/** + * 体检加项自定义结算申请取消出参 + */ +export interface OutputCustomSettlementApplyCancel { + /** 是否成功(1-成功 0-失败) */ + is_success?: number; +} + +/** + * 体检加项自定义结算申请取消响应 + */ +export type CustomSettlementApplyCancelResponse = CommonActionResult; diff --git a/src/components/exam/ExamAddonPanel.tsx b/src/components/exam/ExamAddonPanel.tsx index d9aa13b..98d81e3 100644 --- a/src/components/exam/ExamAddonPanel.tsx +++ b/src/components/exam/ExamAddonPanel.tsx @@ -1,7 +1,7 @@ -import { useEffect, useMemo, useState, useRef } from 'react'; +import { useEffect, useMemo, useState, useRef, useCallback } from 'react'; import type { ExamClient } from '../../data/mockData'; -import { searchPhysicalExamAddItem, getAddItemCustomerInfo, getChannelCompanyList, createNativePaymentQrcode, checkNativePaymentStatus, getAddItemBillPdf } from '../../api'; +import { searchPhysicalExamAddItem, getAddItemCustomerInfo, getChannelCompanyList, createNativePaymentQrcode, checkNativePaymentStatus, getAddItemBillPdf, getCustomSettlementApproveStatus, customSettlementApply, customSettlementApplyCancel } from '../../api'; import { Button, Input } from '../ui'; import { cls } from '../../utils/cls'; import nozImage from '../../assets/image/noz.png'; @@ -72,6 +72,24 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => { const [paymentLoading, setPaymentLoading] = useState(false); const [paymentMessage, setPaymentMessage] = useState(null); const pollingTimerRef = useRef(null); + // 跟踪客户信息是否已加载(用于避免重复请求加项列表) + const customerInfoLoadedRef = useRef(false); + // 自定义结算相关状态 + const customSettlementPollingTimerRef = useRef(null); + const lastFetchStatusTimeRef = useRef(0); // 上次获取审批状态的时间戳 + const [showCustomSettlementModal, setShowCustomSettlementModal] = useState(false); + const [customSettlementStatus, setCustomSettlementStatus] = useState<{ + apply_status?: number; + apply_status_name?: string | null; + final_settlement_price?: number | null; + } | null>(null); + const [customSettlementLoading, setCustomSettlementLoading] = useState(false); + const [customSettlementType, setCustomSettlementType] = useState<1 | 2>(1); // 1-按比例折扣 2-自定义结算价 + const [customDiscountRatio, setCustomDiscountRatio] = useState(100); // 折扣比例(如100代表10折,即原价) + const [customFinalPrice, setCustomFinalPrice] = useState(0); // 最终结算价 + const [customApplyReason, setCustomApplyReason] = useState(''); // 申请理由 + const [waitingSeconds, setWaitingSeconds] = useState(0); // 等待审核的秒数 + const waitingTimerRef = useRef(null); // 等待计时器 // 点击外部关闭下拉框 useEffect(() => { @@ -124,6 +142,7 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => { scrm_account_id, scrm_account_name, }); + customerInfoLoadedRef.current = true; // 设置挂账公司默认值 const companyName = res.Data.customerInfo.company_name; if (companyName && companyName.trim() !== '') { @@ -149,9 +168,14 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => { } } catch (err) { console.error('获取加项客户信息失败', err); + } finally { + // 无论成功或失败,都标记为已尝试加载 + customerInfoLoadedRef.current = true; } }; + // 当 client.id 变化时,重置加载状态 + customerInfoLoadedRef.current = false; fetchCustomerInfo(); }, [client.id]); @@ -205,6 +229,10 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => { // 拉取加项列表 useEffect(() => { + if (!customerInfoLoadedRef.current && customerInfo === null) { + return; + } + const fetchList = async () => { setAddonLoading(true); setAddonError(null); @@ -249,7 +277,7 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => { } }; fetchList(); - }, [debouncedAddonSearch, customerInfo?.scrm_account_id, customerInfo?.scrm_account_name, client.id]); + }, [debouncedAddonSearch, customerInfo?.scrm_account_id, customerInfo?.scrm_account_name, client.id, customerInfo]); const allAddons = useMemo(() => addonList, [addonList]); @@ -279,9 +307,15 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => { const totalOriginal = selectedItems.reduce((sum, item) => { return sum + parseFloat(item.originalPrice || item.currentPrice || '0'); }, 0); - const totalCurrent = selectedItems.reduce((sum, item) => { + const totalCurrentBase = selectedItems.reduce((sum, item) => { return sum + parseFloat(item.currentPrice || item.originalPrice || '0'); }, 0); + + // 如果自定义结算审核通过,使用审核后的金额 + const totalCurrent = (customSettlementStatus?.apply_status === 3 && typeof customSettlementStatus.final_settlement_price === 'number') + ? customSettlementStatus.final_settlement_price + : totalCurrentBase; + const discount = totalOriginal - totalCurrent; // 获取标签样式 @@ -382,6 +416,277 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => { }; }, []); + // 获取自定义结算审批状态(带节流,最多每秒一次) + const fetchCustomSettlementStatus = useCallback(async () => { + const physical_exam_id = Number(client.id); + const currentSelectedItems = allAddons.filter(item => selectedIds.has(item.id || item.name)); + + if (!physical_exam_id || currentSelectedItems.length === 0) { + setCustomSettlementStatus(null); + return false; + } + + const add_item_id = currentSelectedItems + .map(item => item.combinationItemCode) + .filter((code): code is number => code !== null && code !== undefined) + .join(','); + + if (!add_item_id) { + setCustomSettlementStatus(null); + return false; + } + + // 节流:如果距离上次调用不足0.8秒,则跳过 + const now = Date.now(); + const timeSinceLastCall = now - lastFetchStatusTimeRef.current; + if (timeSinceLastCall < 800) { + // 返回当前状态是否需要轮询 + return customSettlementStatus?.apply_status === 1; + } + lastFetchStatusTimeRef.current = now; + + try { + const res = await getCustomSettlementApproveStatus({ + physical_exam_id, + add_item_id, + }); + if (res.Status === 200 && res.Data) { + const status = { + apply_status: res.Data.apply_status, + apply_status_name: res.Data.apply_status_name, + final_settlement_price: res.Data.final_settlement_price, + }; + setCustomSettlementStatus(status); + // 如果状态变为审核中,重置等待时间 + if (res.Data.apply_status === 1 && customSettlementStatus?.apply_status !== 1) { + setWaitingSeconds(0); + } + // 返回是否需要继续轮询(1-审核中 需要轮询) + return res.Data.apply_status === 1; + } else { + setCustomSettlementStatus(null); + return false; + } + } catch (err) { + console.error('获取自定义结算审批状态失败', err); + setCustomSettlementStatus(null); + return false; + } + }, [client.id, selectedIds, allAddons, customSettlementStatus]); + + // 当选中加项变化时,获取审批状态 + useEffect(() => { + const currentSelectedItems = allAddons.filter(item => selectedIds.has(item.id || item.name)); + if (currentSelectedItems.length > 0) { + fetchCustomSettlementStatus().then((shouldPoll) => { + // 如果需要轮询(审核中),开始轮询 + if (shouldPoll) { + startCustomSettlementPolling(); + } else { + stopCustomSettlementPolling(); + } + }); + } else { + setCustomSettlementStatus(null); + stopCustomSettlementPolling(); + } + }, [selectedIds, client.id, allAddons, fetchCustomSettlementStatus]); + + // 开始轮询自定义结算审批状态 + const startCustomSettlementPolling = useCallback(() => { + // 清除之前的定时器 + stopCustomSettlementPolling(); + + // 每1秒轮询一次 + customSettlementPollingTimerRef.current = window.setInterval(() => { + fetchCustomSettlementStatus().then((shouldPoll) => { + // 如果不再需要轮询(审核完成),停止轮询 + if (!shouldPoll) { + stopCustomSettlementPolling(); + } + }); + }, 1000); + }, [fetchCustomSettlementStatus]); + + // 停止轮询自定义结算审批状态 + const stopCustomSettlementPolling = useCallback(() => { + if (customSettlementPollingTimerRef.current) { + clearInterval(customSettlementPollingTimerRef.current); + customSettlementPollingTimerRef.current = null; + } + }, []); + + // 清理轮询定时器 + useEffect(() => { + return () => { + stopCustomSettlementPolling(); + }; + }, [stopCustomSettlementPolling]); + + // 等待计时器:当审核中时开始计时 + useEffect(() => { + if (customSettlementStatus?.apply_status === 1) { + // 开始计时 + setWaitingSeconds(0); + waitingTimerRef.current = window.setInterval(() => { + setWaitingSeconds((prev) => prev + 1); + }, 1000); + } else { + // 停止计时并重置 + if (waitingTimerRef.current) { + clearInterval(waitingTimerRef.current); + waitingTimerRef.current = null; + } + setWaitingSeconds(0); + } + + return () => { + if (waitingTimerRef.current) { + clearInterval(waitingTimerRef.current); + waitingTimerRef.current = null; + } + }; + }, [customSettlementStatus?.apply_status]); + + // 提交自定义结算申请 + const handleSubmitCustomSettlement = async () => { + const physical_exam_id = Number(client.id); + if (!physical_exam_id || selectedItems.length === 0) { + setPaymentMessage('请先选择加项项目'); + return; + } + + if (!customApplyReason.trim()) { + setPaymentMessage('请输入申请理由'); + return; + } + + setCustomSettlementLoading(true); + setPaymentMessage(null); + + try { + // 构建加项项目明细列表 + const listAddItemDetail = selectedItems + .map(item => { + const combinationItemCode = item.combinationItemCode; + if (combinationItemCode === null || combinationItemCode === undefined) { + return null; + } + const originalPrice = parseFloat(item.originalPrice || '0'); + let settlementPrice = originalPrice; + + if (customSettlementType === 1) { + // 按比例折扣 + settlementPrice = originalPrice * (customDiscountRatio / 100); + } else { + // 自定义结算价 + settlementPrice = customFinalPrice / selectedItems.length; // 平均分配 + } + + return { + combination_item_code: String(combinationItemCode), + combination_item_name: item.name, + original_price: originalPrice, + settlement_price: settlementPrice, + }; + }) + .filter((item): item is { combination_item_code: string; combination_item_name: string; original_price: number; settlement_price: number } => item !== null); + + const original_settlement_price = selectedItems.reduce((sum, item) => { + return sum + parseFloat(item.currentPrice || item.originalPrice || '0'); + }, 0); + + const final_settlement_price = customSettlementType === 1 + ? original_settlement_price * (customDiscountRatio / 100) + : customFinalPrice; + + const res = await customSettlementApply({ + physical_exam_id, + listAddItemDetail, + original_settlement_price, + settlement_type: customSettlementType, + discount_ratio: customSettlementType === 1 ? customDiscountRatio : undefined, + final_settlement_price, + apply_reason: customApplyReason.trim(), + }); + + if (res.Status === 200) { + setPaymentMessage('自定义结算申请提交成功'); + setShowCustomSettlementModal(false); + // 重置表单 + setCustomSettlementType(1); + setCustomDiscountRatio(100); + setCustomFinalPrice(0); + setCustomApplyReason(''); + // 重新获取审批状态并开始轮询 + setTimeout(() => { + fetchCustomSettlementStatus().then((shouldPoll) => { + if (shouldPoll) { + startCustomSettlementPolling(); + } + }); + }, 500); + } else { + setPaymentMessage(res.Message || '提交失败,请稍后重试'); + } + } catch (err) { + console.error('提交自定义结算申请失败', err); + setPaymentMessage('提交失败,请稍后重试'); + } finally { + setCustomSettlementLoading(false); + } + }; + + // 取消自定义结算申请 + const handleCancelCustomSettlement = async () => { + const physical_exam_id = Number(client.id); + if (!physical_exam_id || selectedItems.length === 0) { + setPaymentMessage('请先选择加项项目'); + return; + } + + if (!window.confirm('确定要取消自定义结算申请吗?')) { + return; + } + + setCustomSettlementLoading(true); + setPaymentMessage(null); + + try { + const add_item_id = selectedItems + .map(item => item.combinationItemCode) + .filter((code): code is number => code !== null && code !== undefined) + .join(','); + + if (!add_item_id) { + setPaymentMessage('无法获取加项项目ID'); + return; + } + + const res = await customSettlementApplyCancel({ + physical_exam_id, + add_item_id, + }); + + if (res.Status === 200 && res.Data?.is_success === 1) { + setPaymentMessage('取消申请成功'); + // 停止轮询 + stopCustomSettlementPolling(); + // 重新获取审批状态 + setTimeout(() => { + fetchCustomSettlementStatus(); + }, 500); + } else { + setPaymentMessage(res.Message || '取消申请失败,请稍后重试'); + } + } catch (err) { + console.error('取消自定义结算申请失败', err); + setPaymentMessage('取消申请失败,请稍后重试'); + } finally { + setCustomSettlementLoading(false); + } + }; + // 获取加项PDF(按本次支付的组合代码生成对应的加项单) const fetchAddItemBillPdf = async (examId: number, combinationItemCodes: string) => { try { @@ -797,9 +1102,46 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => { {/*
结算方式: 个人支付 (微信 / 支付宝)
*/} + {/* 添加自定义结算 */} + {localStorage.getItem('authCode')?.includes('HisTijianPad_Btn_Tongji') && selectedItems.length > 0 && ( +
+ {customSettlementStatus && ( +
+ 审批状态: + {customSettlementStatus.apply_status_name || '未知'} + {customSettlementStatus.apply_status === 1 && ' (审核中...)'} + + {customSettlementStatus.final_settlement_price !== null && customSettlementStatus.final_settlement_price !== undefined && ( + + 结算价: ¥{customSettlementStatus.final_settlement_price.toFixed(2)} + + )} +
+ )} + +
+ )} + + {paymentMessage && (
- {paymentMessage}
)} @@ -959,6 +1301,191 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => { )} + + {/* 自定义结算弹窗 */} + {showCustomSettlementModal && ( +
setShowCustomSettlementModal(false)}> +
e.stopPropagation()} + > +
+
自定义结算申请
+ +
+ +
+ {/* 选中项目信息 */} +
+
选中项目 ({selectedItems.length}项):
+
+ {selectedItems.map((item, idx) => ( +
+ {item.name} + ¥{parseFloat(item.currentPrice || item.originalPrice || '0').toFixed(2)} +
+ ))} +
+
+ 渠道折扣价合计: + ¥{totalCurrent.toFixed(2)} +
+
+ + {/* 结算方式 */} +
+ +
+ + +
+
+ + {/* 折扣比例或最终结算价 */} + {customSettlementType === 1 ? ( +
+
+ + {customDiscountRatio / 10}折 +
+
+ { + setCustomDiscountRatio(Number(e.target.value)); + }} + className='w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-blue-600' + style={{ + background: `linear-gradient(to right, #3b82f6 0%, #3b82f6 ${((customDiscountRatio - 10) / (100 - 10)) * 100}%, #e5e7eb ${((customDiscountRatio - 10) / (100 - 10)) * 100}%, #e5e7eb 100%)` + }} + /> + {/* 刻度标记 */} +
+ {[10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100].map((value) => ( + + {value / 10} + + ))} +
+
+
+ 最终结算价: ¥{(totalCurrent * (customDiscountRatio / 100)).toFixed(2)} +
+
+ ) : ( +
+ + { + const val = Number(e.target.value); + if (val >= 0) { + setCustomFinalPrice(val); + } + }} + placeholder='请输入最终结算价' + className='text-sm' + /> +
+ )} + + {/* 申请理由 */} +
+ +