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 mb = !inventoryFilters.brand || s.brand === inventoryFilters.brand;
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;
}), [inventoryData, inventoryFilters]);
@@ -336,10 +336,10 @@ export default function App() {
// Derived data for customer section
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 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;
return mc && mb && md && mm && mr;
}), [customerData, customerFilters]);
@@ -360,7 +360,7 @@ export default function App() {
// Filtered modal vehicles based on modal filters
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 mb = !modalFilters.brand || v.brandLabel === modalFilters.brand;
const ml = !modalFilters.location || v.location === modalFilters.location;
@@ -368,7 +368,7 @@ export default function App() {
}), [modalVehicles, modalFilters]);
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;
}), [modalWeeklyDetail, modalFilters.plateNumber]);
@@ -983,11 +983,10 @@ export default function App() {
</div>
<div>
<label className="text-[10px] text-slate-400 block mb-1"></label>
<div className="relative">
<Search size={12} className="absolute left-2 top-1/2 -translate-y-1/2 text-slate-400" />
<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" />
<datalist id="dl-inv-model">{uniqueInventoryModels.map(m => <option key={m} value={m} />)}</datalist>
</div>
<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">
<option value=""></option>
{uniqueInventoryModels.map(m => <option key={m} value={m}>{m}</option>)}
</select>
</div>
</div>
</motion.div>
@@ -1873,11 +1872,10 @@ export default function App() {
<div className="space-y-4">
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label>
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" size={14} />
<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 }))} />
<datalist id="dl-region-customer">{uniqueCustomerNames.map(c => <option key={c} value={c} />)}</datalist>
</div>
<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 }))}>
<option value=""></option>
{uniqueCustomerNames.map(c => <option key={c} value={c}>{c}</option>)}
</select>
</div>
<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-1.5">
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label>
<div className="relative">
<Search className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400" size={12} />
<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 }))} />
<datalist id="dl-cust-customer">{uniqueCustomerNames.map(c => <option key={c} value={c} />)}</datalist>
</div>
<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 }))}>
<option value=""></option>
{uniqueCustomerNames.map(c => <option key={c} value={c}>{c}</option>)}
</select>
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-gray-400 uppercase tracking-wider"></label>
<div className="relative">
<Search className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400" size={12} />
<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 }))} />
<datalist id="dl-cust-manager">{uniqueCustomerManagers.map(m => <option key={m} value={m} />)}</datalist>
</div>
<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 }))}>
<option value=""></option>
{uniqueCustomerManagers.map(m => <option key={m} value={m}>{m}</option>)}
</select>
</div>
<div className="grid grid-cols-2 gap-3">
@@ -2469,9 +2465,11 @@ export default function App() {
<div className="flex items-center gap-3">
{/* Quick Search always visible when collapsed */}
{!isModalFilterExpanded && (
<div className="relative 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" />
<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" />
<div className="w-40 sm:w-64" onClick={(e) => e.stopPropagation()}>
<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">
<option value="">...</option>
{uniqueModalPlates.map(p => <option key={p} value={p}>{p}</option>)}
</select>
</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="space-y-1">
<label className="block text-[10px] text-gray-500 font-medium"></label>
<div className="relative">
<Search size={12} className="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400" />
<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" />
<datalist id="dl-modal-plate">{uniqueModalPlates.map(p => <option key={p} value={p} />)}</datalist>
</div>
<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">
<option value=""></option>
{uniqueModalPlates.map(p => <option key={p} value={p}>{p}</option>)}
</select>
</div>
<div className="space-y-1">
<label className="block text-[10px] text-gray-500 font-medium"></label>