diff --git a/src/components/exam/ExamAddonPanel.tsx b/src/components/exam/ExamAddonPanel.tsx index 58dffe2..9786021 100644 --- a/src/components/exam/ExamAddonPanel.tsx +++ b/src/components/exam/ExamAddonPanel.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState, useRef } from 'react'; import type { ExamClient } from '../../data/mockData'; import { searchPhysicalExamAddItem } from '../../api'; @@ -24,10 +24,31 @@ interface ExamAddonPanelProps { export const ExamAddonPanel = ({ client }: ExamAddonPanelProps) => { const [addonList, setAddonList] = useState([]); - const [addonSearch, setAddonSearch] = useState(''); + // 防抖:内部输入值(用于显示) + const [addonSearchInput, setAddonSearchInput] = useState(''); + // 防抖:实际用于 API 调用的值(延迟更新) + const [debouncedAddonSearch, setDebouncedAddonSearch] = useState(''); + const debounceTimerRef = useRef(null); const [addonLoading, setAddonLoading] = useState(false); const [addonError, setAddonError] = useState(null); + // 防抖:当输入值变化时,延迟 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 physical_exam_id = Number(client.id); @@ -42,7 +63,7 @@ export const ExamAddonPanel = ({ client }: ExamAddonPanelProps) => { try { const res = await searchPhysicalExamAddItem({ physical_exam_id, - item_name: addonSearch.trim() || null, + item_name: debouncedAddonSearch.trim() || null, }); if (res.Status === 200 && res.Data?.addItemList) { const list: AddonItem[] = res.Data.addItemList.map((item) => ({ @@ -69,7 +90,7 @@ export const ExamAddonPanel = ({ client }: ExamAddonPanelProps) => { } }; fetchList(); - }, [addonSearch, client.id]); + }, [debouncedAddonSearch, client.id]); const allAddons = useMemo(() => addonList, [addonList]); @@ -138,91 +159,101 @@ export const ExamAddonPanel = ({ client }: ExamAddonPanelProps) => {

体检套餐加项选择

-
+
setAddonSearch(e.target.value)} - className='text-sm' + value={addonSearchInput} + onChange={(e) => 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'); +
+
+ {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}
+ 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} + {/* 第二行:标签 */} + {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)} - ))} -
- )} - - {/* 第三行:价格信息(固定在卡片底部) */} -
-
- {origPrice >= 0 && origPrice >= currPrice && ( - ¥{origPrice.toFixed(0)} - )} -
- ¥{currPrice.toFixed(0)} - - {getDiscountText(item)} - +
-
- ); - })} + ); + })} +
{/* 底部汇总和支付 */}