fix: 所有筛选统一为select下拉,修复iOS不支持datalist
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

- 客户名称、业务负责人、车型名称、车牌号码全部从input+datalist
  改为select下拉,iOS Safari完全兼容
- 弹窗快速搜索也改为select
- 所有过滤逻辑统一为精确匹配(select值)
- 样式统一:所有筛选控件使用相同的select样式

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-03-28 23:33:53 +08:00
parent f4cf5d1cb6
commit 28dcab771f

View File

@@ -294,7 +294,7 @@ export default function App() {
const mc = !inventoryFilters.city || s.city === inventoryFilters.city; const mc = !inventoryFilters.city || s.city === inventoryFilters.city;
const mb = !inventoryFilters.brand || s.brand === inventoryFilters.brand; const mb = !inventoryFilters.brand || s.brand === inventoryFilters.brand;
const mbt = !inventoryFilters.batch || s.batch === inventoryFilters.batch; const mbt = !inventoryFilters.batch || s.batch === inventoryFilters.batch;
const mm = !inventoryFilters.model || s.model.toLowerCase().includes(inventoryFilters.model.toLowerCase()); const mm = !inventoryFilters.model || s.model === inventoryFilters.model;
return mr && mc && mb && mbt && mm; return mr && mc && mb && mbt && mm;
}), [inventoryData, inventoryFilters]); }), [inventoryData, inventoryFilters]);
@@ -336,10 +336,10 @@ export default function App() {
// Derived data for customer section // Derived data for customer section
const filteredCustomerStats = useMemo(() => customerData.filter((s) => { const filteredCustomerStats = useMemo(() => customerData.filter((s) => {
const mc = !customerFilters.customer || s.customer.toLowerCase().includes(customerFilters.customer.toLowerCase()); const mc = !customerFilters.customer || s.customer === customerFilters.customer;
const mb = !customerFilters.brand || s.brand === customerFilters.brand; const mb = !customerFilters.brand || s.brand === customerFilters.brand;
const md = !customerFilters.department || s.department === customerFilters.department; const md = !customerFilters.department || s.department === customerFilters.department;
const mm = !customerFilters.manager || s.manager.toLowerCase().includes(customerFilters.manager.toLowerCase()); const mm = !customerFilters.manager || s.manager === customerFilters.manager;
const mr = !customerFilters.region || s.region === customerFilters.region; const mr = !customerFilters.region || s.region === customerFilters.region;
return mc && mb && md && mm && mr; return mc && mb && md && mm && mr;
}), [customerData, customerFilters]); }), [customerData, customerFilters]);
@@ -360,7 +360,7 @@ export default function App() {
// Filtered modal vehicles based on modal filters // Filtered modal vehicles based on modal filters
const filteredModalVehicles = useMemo(() => modalVehicles.filter((v) => { const filteredModalVehicles = useMemo(() => modalVehicles.filter((v) => {
const mp = !modalFilters.plateNumber || (v.plateNumber || v.vin || '').toLowerCase().includes(modalFilters.plateNumber.toLowerCase()); const mp = !modalFilters.plateNumber || (v.plateNumber || v.vin) === modalFilters.plateNumber;
const mm = !modalFilters.model || v.model === modalFilters.model; const mm = !modalFilters.model || v.model === modalFilters.model;
const mb = !modalFilters.brand || v.brandLabel === modalFilters.brand; const mb = !modalFilters.brand || v.brandLabel === modalFilters.brand;
const ml = !modalFilters.location || v.location === modalFilters.location; const ml = !modalFilters.location || v.location === modalFilters.location;
@@ -368,7 +368,7 @@ export default function App() {
}), [modalVehicles, modalFilters]); }), [modalVehicles, modalFilters]);
const filteredModalWeeklyDetail = useMemo(() => modalWeeklyDetail.filter((v) => { const filteredModalWeeklyDetail = useMemo(() => modalWeeklyDetail.filter((v) => {
const mp = !modalFilters.plateNumber || v.plate_number.toLowerCase().includes(modalFilters.plateNumber.toLowerCase()); const mp = !modalFilters.plateNumber || v.plate_number === modalFilters.plateNumber;
return mp; return mp;
}), [modalWeeklyDetail, modalFilters.plateNumber]); }), [modalWeeklyDetail, modalFilters.plateNumber]);
@@ -983,11 +983,10 @@ export default function App() {
</div> </div>
<div> <div>
<label className="text-[10px] text-slate-400 block mb-1"></label> <label className="text-[10px] text-slate-400 block mb-1"></label>
<div className="relative"> <select value={inventoryFilters.model} onChange={(e) => setInventoryFilters({...inventoryFilters, model: e.target.value})} className="w-full text-xs bg-white border border-slate-200 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm cursor-pointer">
<Search size={12} className="absolute left-2 top-1/2 -translate-y-1/2 text-slate-400" /> <option value=""></option>
<input type="text" list="dl-inv-model" placeholder="搜索车型..." value={inventoryFilters.model} onChange={(e) => setInventoryFilters({...inventoryFilters, model: e.target.value})} className="w-full text-xs bg-white border border-slate-200 rounded-lg pl-7 pr-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm" /> {uniqueInventoryModels.map(m => <option key={m} value={m}>{m}</option>)}
<datalist id="dl-inv-model">{uniqueInventoryModels.map(m => <option key={m} value={m} />)}</datalist> </select>
</div>
</div> </div>
</div> </div>
</motion.div> </motion.div>
@@ -1873,11 +1872,10 @@ export default function App() {
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-1.5"> <div className="space-y-1.5">
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label> <label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label>
<div className="relative"> <select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-slate-500/20 focus:border-slate-500 outline-none transition-all cursor-pointer shadow-sm" value={regionFilters.customer} onChange={(e) => setRegionFilters(prev => ({ ...prev, customer: e.target.value }))}>
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" size={14} /> <option value=""></option>
<input type="text" list="dl-region-customer" placeholder="搜索客户名称..." className="w-full bg-white border border-gray-200 rounded-lg py-2 pl-9 pr-3 text-xs focus:ring-2 focus:ring-slate-500/20 focus:border-slate-500 outline-none transition-all shadow-sm" value={regionFilters.customer} onChange={(e) => setRegionFilters(prev => ({ ...prev, customer: e.target.value }))} /> {uniqueCustomerNames.map(c => <option key={c} value={c}>{c}</option>)}
<datalist id="dl-region-customer">{uniqueCustomerNames.map(c => <option key={c} value={c} />)}</datalist> </select>
</div>
</div> </div>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
@@ -2182,20 +2180,18 @@ export default function App() {
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-1.5"> <div className="space-y-1.5">
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label> <label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label>
<div className="relative"> <select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all cursor-pointer shadow-sm" value={customerFilters.customer} onChange={(e) => setCustomerFilters(prev => ({ ...prev, customer: e.target.value }))}>
<Search className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400" size={12} /> <option value=""></option>
<input type="text" list="dl-cust-customer" placeholder="搜索客户..." className="w-full bg-white border border-gray-200 rounded-lg py-2 pl-8 pr-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all shadow-sm" value={customerFilters.customer} onChange={(e) => setCustomerFilters(prev => ({ ...prev, customer: e.target.value }))} /> {uniqueCustomerNames.map(c => <option key={c} value={c}>{c}</option>)}
<datalist id="dl-cust-customer">{uniqueCustomerNames.map(c => <option key={c} value={c} />)}</datalist> </select>
</div>
</div> </div>
<div className="space-y-1.5"> <div className="space-y-1.5">
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label> <label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label>
<div className="relative"> <select className="w-full bg-white border border-gray-200 rounded-lg py-2 px-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all cursor-pointer shadow-sm" value={customerFilters.manager} onChange={(e) => setCustomerFilters(prev => ({ ...prev, manager: e.target.value }))}>
<Search className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400" size={12} /> <option value=""></option>
<input type="text" list="dl-cust-manager" placeholder="搜索负责人..." className="w-full bg-white border border-gray-200 rounded-lg py-2 pl-8 pr-2 text-xs focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500 outline-none transition-all shadow-sm" value={customerFilters.manager} onChange={(e) => setCustomerFilters(prev => ({ ...prev, manager: e.target.value }))} /> {uniqueCustomerManagers.map(m => <option key={m} value={m}>{m}</option>)}
<datalist id="dl-cust-manager">{uniqueCustomerManagers.map(m => <option key={m} value={m} />)}</datalist> </select>
</div>
</div> </div>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
@@ -2469,9 +2465,11 @@ export default function App() {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{/* Quick Search always visible when collapsed */} {/* Quick Search always visible when collapsed */}
{!isModalFilterExpanded && ( {!isModalFilterExpanded && (
<div className="relative w-40 sm:w-64" onClick={(e) => e.stopPropagation()}> <div className="w-40 sm:w-64" onClick={(e) => e.stopPropagation()}>
<Search size={12} className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400" /> <select value={modalFilters.plateNumber} onChange={(e) => setModalFilters({...modalFilters, plateNumber: e.target.value})} className="w-full text-[11px] px-2 py-1 bg-white border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm cursor-pointer">
<input type="text" value={modalFilters.plateNumber} onChange={(e) => setModalFilters({...modalFilters, plateNumber: e.target.value})} placeholder="快速搜索车牌..." className="w-full text-[11px] pl-7 pr-2 py-1 bg-white border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm" /> <option value="">...</option>
{uniqueModalPlates.map(p => <option key={p} value={p}>{p}</option>)}
</select>
</div> </div>
)} )}
<motion.div <motion.div
@@ -2494,11 +2492,10 @@ export default function App() {
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 py-3 border-t border-gray-100 mt-1"> <div className="grid grid-cols-2 sm:grid-cols-4 gap-3 py-3 border-t border-gray-100 mt-1">
<div className="space-y-1"> <div className="space-y-1">
<label className="block text-[10px] text-gray-500 font-medium"></label> <label className="block text-[10px] text-gray-500 font-medium"></label>
<div className="relative"> <select value={modalFilters.plateNumber} onChange={(e) => setModalFilters({...modalFilters, plateNumber: e.target.value})} className="w-full text-[11px] px-2 py-1.5 bg-white border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm cursor-pointer">
<Search size={12} className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400" /> <option value=""></option>
<input type="text" list="dl-modal-plate" placeholder="搜索车牌..." value={modalFilters.plateNumber} onChange={(e) => setModalFilters({...modalFilters, plateNumber: e.target.value})} className="w-full text-[11px] pl-7 pr-2 py-1.5 bg-white border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm" /> {uniqueModalPlates.map(p => <option key={p} value={p}>{p}</option>)}
<datalist id="dl-modal-plate">{uniqueModalPlates.map(p => <option key={p} value={p} />)}</datalist> </select>
</div>
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<label className="block text-[10px] text-gray-500 font-medium"></label> <label className="block text-[10px] text-gray-500 font-medium"></label>