import { useEffect, useMemo, useState, useRef } from 'react'; import type { ExamClient } from '../../data/mockData'; import { searchPhysicalExamAddItem, getAddItemCustomerInfo, getChannelCompanyList, createNativePaymentQrcode, checkNativePaymentStatus, getAddItemBillPdf } from '../../api'; import { Button, Input } from '../ui'; import { cls } from '../../utils/cls'; import nozImage from '../../assets/image/noz.png'; interface AddonTag { title: string; type: 1 | 2 | 3 | 4; // 1: 热门(红), 2: 普通(灰), 3: 医生推荐(蓝), 4: 折扣信息 } interface AddonItem { id?: string; name: string; paid?: boolean; tags?: AddonTag[]; originalPrice?: string; currentPrice?: string; combinationItemCode?: number | null; isEnjoyDiscount?: number | null; discount_name?: string | null; } interface ExamAddonPanelProps { client: ExamClient; onGoToSign?: () => void; } export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => { const [addonList, setAddonList] = useState([]); // 防抖:内部输入值(用于显示) const [addonSearchInput, setAddonSearchInput] = useState(''); // 防抖:实际用于 API 调用的值(延迟更新) const [debouncedAddonSearch, setDebouncedAddonSearch] = useState(''); const debounceTimerRef = useRef(null); const [addonLoading, setAddonLoading] = useState(false); const [addonError, setAddonError] = useState(null); // 折扣比例(1 = 100%) const [discountRatio, setDiscountRatio] = useState(1); // 渠道折扣列表 const [channelDiscounts, setChannelDiscounts] = useState< { channel_id?: string | null; channel_name?: string | null; discount_rate?: number | null; discount_name?: string | null }[] >([]); // 下拉框状态 const [isDiscountDropdownOpen, setIsDiscountDropdownOpen] = useState(false); const discountDropdownRef = useRef(null); // 结算方式:'self' 自费, 'account' 挂账 const [paymentMethod, setPaymentMethod] = useState<'self' | 'account'>('self'); const [isPaymentMethodDropdownOpen, setIsPaymentMethodDropdownOpen] = useState(false); const paymentMethodDropdownRef = useRef(null); // 挂账公司 const [accountCompany, setAccountCompany] = useState('圆和'); const [isAccountCompanyDropdownOpen, setIsAccountCompanyDropdownOpen] = useState(false); const [accountCompanySearch, setAccountCompanySearch] = useState(''); const accountCompanyDropdownRef = useRef(null); // 挂账公司列表 const [accountCompanyList, setAccountCompanyList] = useState< Array<{ company_id: number; company_name?: string | null; pinyin?: string | null }> >([]); // 客户信息 const [customerInfo, setCustomerInfo] = useState<{ customer_name?: string | null; phone?: string | null; patient_id?: number | null; } | null>(null); // 支付相关状态 const [showQrcodeModal, setShowQrcodeModal] = useState(false); const [qrcodeUrl, setQrcodeUrl] = useState(null); const [paymentLoading, setPaymentLoading] = useState(false); const [paymentMessage, setPaymentMessage] = useState(null); const pollingTimerRef = useRef(null); // 点击外部关闭下拉框 useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (discountDropdownRef.current && !discountDropdownRef.current.contains(event.target as Node)) { setIsDiscountDropdownOpen(false); } if (paymentMethodDropdownRef.current && !paymentMethodDropdownRef.current.contains(event.target as Node)) { setIsPaymentMethodDropdownOpen(false); } if (accountCompanyDropdownRef.current && !accountCompanyDropdownRef.current.contains(event.target as Node)) { setIsAccountCompanyDropdownOpen(false); } }; if (isDiscountDropdownOpen || isPaymentMethodDropdownOpen || isAccountCompanyDropdownOpen) { document.addEventListener('mousedown', handleClickOutside); } return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [isDiscountDropdownOpen, isPaymentMethodDropdownOpen, isAccountCompanyDropdownOpen]); // 获取体检加项客户信息(用于拿到渠道折扣等信息) useEffect(() => { const physical_exam_id = Number(client.id); if (!physical_exam_id) return; const fetchCustomerInfo = async () => { try { const res = await getAddItemCustomerInfo({ physical_exam_id }); if (res.Status === 200) { // 保存客户信息 if (res.Data?.customerInfo) { setCustomerInfo({ patient_id: res.Data.customerInfo.patient_id, customer_name: res.Data.customerInfo.customer_name, phone: res.Data.customerInfo.phone, }); } // 保存渠道折扣信息 if (res.Data?.listChannelDiscount && res.Data.listChannelDiscount.length > 0) { setChannelDiscounts(res.Data.listChannelDiscount); const rate = res.Data.listChannelDiscount[0]?.discount_rate; if (typeof rate === 'number' && rate > 0) { setDiscountRatio(rate); } } else { setChannelDiscounts([]); } } else { setChannelDiscounts([]); } } catch (err) { console.error('获取加项客户信息失败', err); } }; fetchCustomerInfo(); }, [client.id]); // 获取挂账公司列表 useEffect(() => { const fetchCompanyList = async () => { try { const res = await getChannelCompanyList({}); if (res.Status === 200 && Array.isArray(res.Data) && res.Data.length > 0) { setAccountCompanyList(res.Data); // 设置默认值为第一个公司 const firstCompany = res.Data[0]; if (firstCompany?.company_name) { setAccountCompany(firstCompany.company_name); } } else { setAccountCompanyList([]); } } catch (err) { console.error('获取挂账公司列表失败', err); setAccountCompanyList([]); } }; fetchCompanyList(); }, []); // 当挂账公司列表更新时,确保当前选中的公司在列表中 useEffect(() => { if (accountCompanyList.length > 0) { const companyNames = accountCompanyList.map((c) => c.company_name).filter(Boolean) as string[]; if (!companyNames.includes(accountCompany)) { // 如果当前选中的公司不在列表中,更新为第一个公司 const firstCompany = accountCompanyList[0]; if (firstCompany?.company_name) { setAccountCompany(firstCompany.company_name); } } } }, [accountCompanyList, accountCompany]); // 防抖:当输入值变化时,延迟 0.5 秒后更新 debouncedAddonSearch(用于 API 调用) useEffect(() => { if (debounceTimerRef.current) { window.clearTimeout(debounceTimerRef.current); } debounceTimerRef.current = window.setTimeout(() => { setDebouncedAddonSearch(addonSearchInput); }, 500); return () => { if (debounceTimerRef.current) { window.clearTimeout(debounceTimerRef.current); } }; }, [addonSearchInput]); // 拉取加项列表 useEffect(() => { const fetchList = async () => { setAddonLoading(true); setAddonError(null); try { const res = await searchPhysicalExamAddItem({ physical_exam_id: Number(client.id), discount_ratio: discountRatio || 1, item_name: debouncedAddonSearch.trim() || "", }); if (res.Status === 200 && Array.isArray(res.Data)) { const list: AddonItem[] = res.Data.map((item) => ({ id: item.item_id ? String(item.item_id) : `addon_${item.item_name}`, name: item.item_name || '', originalPrice: item.original_price !== undefined && item.original_price !== null ? Number(item.original_price).toFixed(2) : '0.00', currentPrice: item.actual_received_amount !== undefined && item.actual_received_amount !== null ? Number(item.actual_received_amount).toFixed(2) : item.original_price !== undefined && item.original_price !== null ? Number(item.original_price).toFixed(2) : '0.00', combinationItemCode: item.combination_item_code ?? null, isEnjoyDiscount: item.is_enjoy_discount ?? null, discount_name: item.discount_rate ?? null, tags: [], paid: false, })); setAddonList(list); } else { setAddonError(res.Message || '获取加项列表失败'); setAddonList([]); } } catch (err) { console.error('获取加项列表失败', err); setAddonError('获取加项列表失败,请稍后重试'); setAddonList([]); } finally { setAddonLoading(false); } }; fetchList(); }, [debouncedAddonSearch, discountRatio]); const allAddons = useMemo(() => addonList, [addonList]); const [selectedIds, setSelectedIds] = useState>(new Set()); const maxSelect = 15; const selectedCount = selectedIds.size; const filteredAddons = allAddons; const toggleSelect = (id: string) => { if (selectedIds.has(id)) { setSelectedIds(prev => { const next = new Set(prev); next.delete(id); return next; }); } else { if (selectedCount < maxSelect) { setSelectedIds(prev => new Set(prev).add(id)); } } }; // 计算价格汇总 const selectedItems = allAddons.filter(item => selectedIds.has(item.id || item.name)); const totalOriginal = selectedItems.reduce((sum, item) => { return sum + parseFloat(item.originalPrice || item.currentPrice || '0'); }, 0); const totalCurrent = selectedItems.reduce((sum, item) => { return sum + parseFloat(item.currentPrice || item.originalPrice || '0'); }, 0); const discount = totalOriginal - totalCurrent; // 获取标签样式 const getTagStyle = (tag: AddonTag) => { switch (tag.type) { case 1: // 热门 return 'bg-[#FDF0F0] text-[#BC4845]'; case 3: // 医生推荐 return 'bg-[#ECF0FF] text-[#6A6AE5]'; case 4: // 折扣信息 return 'bg-[#4C5460] text-[#F1F2F5]'; default: // 2: 普通 return 'bg-[#F1F2F5] text-[#464E5B]'; } }; // 获取折扣信息文字(从 tags 中提取 type 4 的标签,或计算折扣) const getDiscountText = (item: AddonItem) => { return item.discount_name; }; // 构建折扣选项列表 const discountOptions = useMemo(() => { const options: Array<{ value: number; label: string }> = []; channelDiscounts.forEach((item) => { const rate = typeof item.discount_rate === 'number' && item.discount_rate > 0 ? item.discount_rate : 1; const percent = Math.round(rate * 100); const label = item.discount_name || `${percent}%`; options.push({ value: rate, label }); }); return options; }, [channelDiscounts]); // 获取当前选中的标签 const currentDiscountLabel = useMemo(() => { const option = discountOptions.find(opt => opt.value === discountRatio); return option?.label; }, [discountRatio, discountOptions]); // 处理折扣选择 const handleDiscountSelect = (value: number) => { setDiscountRatio(value); setIsDiscountDropdownOpen(false); }; // 处理结算方式选择 const handlePaymentMethodSelect = (value: 'self' | 'account') => { setPaymentMethod(value); setIsPaymentMethodDropdownOpen(false); }; // 处理挂账公司选择 const handleAccountCompanySelect = (value: string, label: string) => { setAccountCompany(value); setAccountCompanySearch(label); setIsAccountCompanyDropdownOpen(false); }; // 结算方式选项 const paymentMethodOptions: Array<{ value: 'self' | 'account'; label: string }> = [ { value: 'self', label: '自费' }, { value: 'account', label: '挂账' }, ]; // 挂账公司选项(从接口获取) const accountCompanyOptions = useMemo(() => { if (accountCompanyList.length === 0) { // 如果没有数据,返回默认的"圆和" return [{ value: '圆和', label: '圆和' }]; } return accountCompanyList.map((company) => ({ value: company.company_name || `公司${company.company_id}`, label: company.company_name || `公司${company.company_id}`, })); }, [accountCompanyList]); // 挂账公司模糊过滤 const filteredAccountCompanyOptions = useMemo(() => { const kw = accountCompanySearch.trim().toLowerCase(); if (!kw) return accountCompanyOptions; return accountCompanyOptions.filter((opt) => opt.label.toLowerCase().includes(kw)); }, [accountCompanyOptions, accountCompanySearch]); // 清理轮询定时器 useEffect(() => { return () => { if (pollingTimerRef.current) { clearInterval(pollingTimerRef.current); pollingTimerRef.current = null; } }; }, []); // 获取加项PDF(按本次支付的组合代码生成对应的加项单) const fetchAddItemBillPdf = async (examId: number, combinationItemCodes: string) => { try { // 调用接口获取加项PDF const res = await getAddItemBillPdf({ exam_id: examId, CombinationCode: combinationItemCodes, }); if (res.Status === 200 && res.Data?.pdf_url && res.Data?.pdf_sort !== undefined && res.Data?.pdf_sort !== null) { return true; } else { console.error('获取加项PDF失败', res.Message); return false; } } catch (err) { console.error('获取加项PDF失败', err); return false; } }; // 轮询查询支付结果 const startPaymentPolling = ( physical_exam_id: number, patient_id: number, listAddItemCombination: Array<{ combination_item_code: string; combination_item_price: number; discount_rate: number; }>, pay_type: number, company_id: number, combinationItemCodes: string ) => { if (pollingTimerRef.current) { clearInterval(pollingTimerRef.current); } pollingTimerRef.current = window.setInterval(async () => { try { const res = await checkNativePaymentStatus({ physical_exam_id, patient_id, listAddItemCombination, pay_type, company_id, }); if (res.Status === 200) { const result = res.Data; console.log(result); // 支付成功:返回 "true" if (result === 'true') { // 支付成功,停止轮询 if (pollingTimerRef.current) { clearInterval(pollingTimerRef.current); pollingTimerRef.current = null; } setPaymentMessage('支付成功,正在生成加项单...'); setShowQrcodeModal(false); setQrcodeUrl(null); // 清空已选项目 setSelectedIds(new Set()); // 获取本次支付对应的加项PDF fetchAddItemBillPdf(physical_exam_id, combinationItemCodes).then((success) => { if (success) { setPaymentMessage('支付成功,加项单已生成,正在跳转签署...'); // 跳转到签署页面 onGoToSign?.(); } else { setPaymentMessage('支付成功,但加项单生成失败'); } setTimeout(() => { setPaymentMessage(null); }, 3000); }); } else if (result === 'false') { // 支付失败,停止轮询 if (pollingTimerRef.current) { clearInterval(pollingTimerRef.current); pollingTimerRef.current = null; } setPaymentMessage('支付失败'); setTimeout(() => { setPaymentMessage(null); }, 3000); } // 其他状态(如 "pending"、"processing" 等)继续轮询 } } catch (err) { console.error('查询支付状态失败', err); } }, 2000); // 每2秒轮询一次 }; // 处理支付 const handlePayment = async () => { if (selectedCount === 0) return; const physical_exam_id = Number(client.id); if (!physical_exam_id) { setPaymentMessage('缺少体检ID'); return; } if (!customerInfo?.customer_name || !customerInfo?.phone) { setPaymentMessage('缺少客户信息,请稍后重试'); return; } setPaymentLoading(true); setPaymentMessage(null); try { const selectedItems = allAddons.filter((item) => selectedIds.has(item.id || item.name)); // 构建加项组合项目信息列表 const listAddItemCombination = selectedItems .map((item) => { const combinationItemCode = item.combinationItemCode; if (combinationItemCode === null || combinationItemCode === undefined) { return null; } const combinationItemPrice = parseFloat(item.currentPrice || item.originalPrice || '0'); return { combination_item_code: String(combinationItemCode), combination_item_price: combinationItemPrice, discount_rate: discountRatio, }; }) .filter((item): item is { combination_item_code: string; combination_item_price: number; discount_rate: number } => item !== null); if (listAddItemCombination.length === 0) { setPaymentMessage('缺少加项信息,请稍后重试'); setPaymentLoading(false); return; } // 获取组合项目代码(用于生成二维码接口 & 生成加项单,多个加项逗号分隔) const combinationItemCodes = listAddItemCombination .map((item) => item.combination_item_code) .join(','); // 获取 patient_id(必须从接口返回的客户信息中获取) if (!customerInfo?.patient_id) { setPaymentMessage('缺少患者ID,请稍后重试'); setPaymentLoading(false); return; } const patient_id = customerInfo.patient_id; if (paymentMethod === 'self') { // 自费模式:生成二维码 const res = await createNativePaymentQrcode({ physical_exam_id, userName: customerInfo.customer_name!, userPhone: customerInfo.phone!, combinationItemCodes, orderAmount: totalCurrent, }); if (res.Status === 200 && res.Data) { // res.Data 是微信支付 URL(如:weixin://wxpay/bizpayurl?pr=xxx) // 需要将 URL 转换为二维码图片 const paymentUrl = res.Data; // 使用在线二维码生成 API 生成二维码图片 const qrcodeImageUrl = `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encodeURIComponent(paymentUrl)}`; setQrcodeUrl(qrcodeImageUrl); setShowQrcodeModal(true); // 开始轮询(自费模式:pay_type=12, company_id=0) startPaymentPolling( physical_exam_id, patient_id, listAddItemCombination, 12, // 微信支付 0, // 自费模式,company_id 传 0 combinationItemCodes ); } else { setPaymentMessage(res.Message || '生成支付二维码失败'); } } else { // 挂账模式:直接查询 const selectedCompany = accountCompanyList.find( (c) => c.company_name === accountCompany ); if (!selectedCompany) { setPaymentMessage('请选择挂账公司'); setPaymentLoading(false); return; } // 挂账模式:直接查询(pay_type=13, company_id=选中的公司ID) const res = await checkNativePaymentStatus({ physical_exam_id, patient_id, listAddItemCombination, pay_type: 13, // 挂账公司 company_id: selectedCompany.company_id, }); if (res.Status === 200) { const result = res.Data; if (result === 'success' || result === '1' || result === 'SUCCESS') { setPaymentMessage('挂账成功,正在生成加项单...'); setSelectedIds(new Set()); // 获取本次挂账对应的加项PDF fetchAddItemBillPdf(physical_exam_id, combinationItemCodes).then((success) => { if (success) { setPaymentMessage('挂账成功,加项单已生成,正在跳转签署...'); onGoToSign?.(); } else { setPaymentMessage('挂账成功,但加项单生成失败'); } setTimeout(() => { setPaymentMessage(null); }, 3000); }); } else { setPaymentMessage('挂账失败'); setTimeout(() => { setPaymentMessage(null); }, 3000); } } else { setPaymentMessage(res.Message || '挂账失败'); } } } catch (err) { console.error('支付处理失败', err); setPaymentMessage('支付处理失败,请稍后重试'); } finally { setPaymentLoading(false); } }; return (
{/* 标题和说明 */}

体检套餐加项选择

{/* 折扣比例 */}
{isDiscountDropdownOpen && (
{discountOptions.map((option) => ( ))}
)}
setAddonSearchInput(e.target.value)} className='text-sm flex-1' /> {addonSearchInput && ( )}
{/*
最多可选 {maxSelect} 项 · 一排 5 个
已勾选 {selectedCount} 项,自费加项费用按渠道折扣价结算。
*/}
{/* 加项网格 */}
{addonError && (
{addonError}
)} {addonLoading && (
加载中...
)} {!addonLoading && !addonError && filteredAddons.length === 0 && (
暂无数据
)} {filteredAddons.map((item) => { const id = item.id || item.name; const isSelected = selectedIds.has(id); const origPrice = parseFloat(item.originalPrice || '0'); const currPrice = parseFloat(item.currentPrice || '0'); return (
toggleSelect(id)} > {/* 无折扣标签图片 - 浮动在右上角(当 is_enjoy_discount 为 0 或 null 时显示) */} {(!item.isEnjoyDiscount || item.isEnjoyDiscount === 0) && ( 无折扣 )} {/* 第一行:复选框 + 名称 */}
toggleSelect(id)} onClick={(e) => e.stopPropagation()} className='mt-0.5 w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500' />
{item.name}
{/* 第二行:标签 */} {item.tags && item.tags.length >= 0 && (
{item.tags .filter(t => t.type !== 4) // 折扣信息单独显示 .map((tag, idx) => ( {tag.title} ))}
)} {/* 第三行:价格信息(固定在卡片底部) */}
{origPrice >= 0 && origPrice >= currPrice && ( ¥{origPrice.toFixed(0)} )}
¥{currPrice.toFixed(2)} {getDiscountText(item)}
); })}
{/* 底部汇总和支付 */}
加项原价合计: ¥{totalOriginal.toFixed(2)}
渠道折扣价: ¥{totalCurrent.toFixed(2)} {discount > 0 && ( 已优惠 ¥{discount.toFixed(2)} )}
{/*
结算方式: 个人支付 (微信 / 支付宝)
*/}
{paymentMessage && (
{paymentMessage}
)}
{/* 结算方式 */}
结算方式
{isPaymentMethodDropdownOpen && (
{paymentMethodOptions.map((option) => ( ))}
)}
{/* 挂账公司 */} {paymentMethod === 'account' && (
挂账公司
setIsAccountCompanyDropdownOpen(true)} > { setAccountCompanySearch(e.target.value); setIsAccountCompanyDropdownOpen(true); }} placeholder={accountCompany || '请输入公司名称'} className='w-full outline-none text-xs bg-transparent' />
{isAccountCompanyDropdownOpen && (
{filteredAccountCompanyOptions.length === 0 && (
无匹配结果
)} {filteredAccountCompanyOptions.map((option, idx) => ( ))}
)}
)}
{/* 二维码支付弹窗 */} {showQrcodeModal && qrcodeUrl && (
扫码支付
支付二维码
请使用微信扫描上方二维码完成支付
{paymentMessage && (
{paymentMessage}
)}
)}
); };