import { useEffect, useMemo, useState, useRef } from 'react'; import type { ExamClient } from '../../data/mockData'; import { searchPhysicalExamAddItem, getAddItemCustomerInfo } 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; } interface ExamAddonPanelProps { client: ExamClient; } export const ExamAddonPanel = ({ client }: 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 accountCompanyDropdownRef = 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 && 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([]); } } catch (err) { console.error('获取加项客户信息失败', err); } }; fetchCustomerInfo(); }, [client.id]); // 防抖:当输入值变化时,延迟 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({ discount_ratio: discountRatio || 1, item_name: debouncedAddonSearch.trim() || null, }); 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', 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) => { const discountTag = item.tags?.find(t => t.type === 4); if (discountTag) return discountTag.title; const orig = parseFloat(item.originalPrice || '0'); const curr = parseFloat(item.currentPrice || '0'); if (orig > 0 && curr < orig) { const percent = Math.round((curr / orig) * 100); return `渠道 ${percent} 折`; } return '渠道价'; }; // 构建折扣选项列表 const discountOptions = useMemo(() => { const options: Array<{ value: number; label: string }> = [ { value: 1, label: '100%(无折扣)' }, ]; 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 || '100%(无折扣)'; }, [discountRatio, discountOptions]); // 处理折扣选择 const handleDiscountSelect = (value: number) => { setDiscountRatio(value); setIsDiscountDropdownOpen(false); }; // 处理结算方式选择 const handlePaymentMethodSelect = (value: 'self' | 'account') => { setPaymentMethod(value); setIsPaymentMethodDropdownOpen(false); }; // 处理挂账公司选择 const handleAccountCompanySelect = (value: string) => { setAccountCompany(value); setIsAccountCompanyDropdownOpen(false); }; // 结算方式选项 const paymentMethodOptions: Array<{ value: 'self' | 'account'; label: string }> = [ { value: 'self', label: '自费' }, { value: 'account', label: '挂账' }, ]; // 挂账公司选项(可以根据实际需求从接口获取) const accountCompanyOptions: Array<{ value: string; label: string }> = [ { value: '圆和', label: '圆和' }, ]; 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)} > {/* 无折扣标签图片 - 浮动在右上角 */} 无折扣 {/* 第一行:复选框 + 名称 */}
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(0)} {getDiscountText(item)}
); })}
{/* 底部汇总和支付 */}
加项原价合计: ¥{totalOriginal.toFixed(0)}
渠道折扣价: ¥{totalCurrent.toFixed(0)} {discount > 0 && ( 已优惠 ¥{discount.toFixed(0)} )}
{/*
结算方式: 个人支付 (微信 / 支付宝)
*/}
{/* 结算方式 */}
结算方式
{isPaymentMethodDropdownOpen && (
{paymentMethodOptions.map((option) => ( ))}
)}
{/* 挂账公司 */} {paymentMethod === 'account' && (
挂账公司
{isAccountCompanyDropdownOpen && (
{accountCompanyOptions.map((option) => ( ))}
)}
)}
); };