feat: 库存筛选"车型名称"改为二级选择"车型→批次"
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- 新增车型下拉(4.5T普货/冷链/18T/49T/挂车/其他) - 批次下拉根据所选车型联动过滤,显示该车型下的具体型号 - 切换车型时自动清空批次选择 - 筛选标签栏对应更新:车型/批次 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
47
src/App.tsx
47
src/App.tsx
@@ -172,7 +172,7 @@ export default function App() {
|
|||||||
const [inventoryTab, setInventoryTab] = useState<'region' | 'model'>('region');
|
const [inventoryTab, setInventoryTab] = useState<'region' | 'model'>('region');
|
||||||
const [expandedInventoryRegions, setExpandedInventoryRegions] = useState<Set<string>>(new Set());
|
const [expandedInventoryRegions, setExpandedInventoryRegions] = useState<Set<string>>(new Set());
|
||||||
const [expandedInventoryTypes, setExpandedInventoryTypes] = useState<Set<string>>(new Set(['4.5T普货']));
|
const [expandedInventoryTypes, setExpandedInventoryTypes] = useState<Set<string>>(new Set(['4.5T普货']));
|
||||||
const [inventoryFilters, setInventoryFilters] = useState({ region: '', city: '', brand: '', batch: '', model: '' });
|
const [inventoryFilters, setInventoryFilters] = useState({ region: '', city: '', brand: '', type: '', model: '' });
|
||||||
const [isInventoryFilterOpen, setIsInventoryFilterOpen] = useState(false);
|
const [isInventoryFilterOpen, setIsInventoryFilterOpen] = useState(false);
|
||||||
|
|
||||||
// Chart view states
|
// Chart view states
|
||||||
@@ -363,15 +363,23 @@ export default function App() {
|
|||||||
const mr = !inventoryFilters.region || s.region === inventoryFilters.region;
|
const mr = !inventoryFilters.region || s.region === inventoryFilters.region;
|
||||||
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 mt = !inventoryFilters.type || s.type === inventoryFilters.type;
|
||||||
const mm = !inventoryFilters.model || s.model === inventoryFilters.model;
|
const mm = !inventoryFilters.model || s.model === inventoryFilters.model;
|
||||||
return mr && mc && mb && mbt && mm;
|
return mr && mc && mb && mt && mm;
|
||||||
}), [inventoryData, inventoryFilters]);
|
}), [inventoryData, inventoryFilters]);
|
||||||
|
|
||||||
const uniqueInventoryBrands = useMemo(() => Array.from(new Set(inventoryData.map((s) => s.brand).filter(Boolean))), [inventoryData]);
|
const uniqueInventoryBrands = useMemo(() => Array.from(new Set(inventoryData.map((s) => s.brand).filter(Boolean))), [inventoryData]);
|
||||||
const uniqueInventoryRegions = useMemo(() => Array.from(new Set(inventoryData.map((s) => s.region))), [inventoryData]);
|
const uniqueInventoryRegions = useMemo(() => Array.from(new Set(inventoryData.map((s) => s.region))), [inventoryData]);
|
||||||
const uniqueInventoryCities = useMemo(() => Array.from(new Set(inventoryData.map((s) => s.city).filter(Boolean))), [inventoryData]);
|
const uniqueInventoryCities = useMemo(() => Array.from(new Set(inventoryData.map((s) => s.city).filter(Boolean))), [inventoryData]);
|
||||||
const uniqueInventoryBatches = useMemo(() => Array.from(new Set(inventoryData.map((s) => s.batch).filter(Boolean))), [inventoryData]);
|
const INVENTORY_TYPE_ORDER_LIST = ['4.5T普货', '4.5T冷链', '18T', '49T', '挂车', '其他'];
|
||||||
|
const uniqueInventoryTypes = useMemo(() => {
|
||||||
|
const types = Array.from(new Set(inventoryData.map((s) => s.type).filter(Boolean)));
|
||||||
|
return types.sort((a, b) => INVENTORY_TYPE_ORDER_LIST.indexOf(a) - INVENTORY_TYPE_ORDER_LIST.indexOf(b));
|
||||||
|
}, [inventoryData]);
|
||||||
|
const uniqueInventoryModelsForType = useMemo(() => {
|
||||||
|
const source = inventoryFilters.type ? inventoryData.filter((s) => s.type === inventoryFilters.type) : inventoryData;
|
||||||
|
return Array.from(new Set(source.map((s) => s.model).filter(Boolean)));
|
||||||
|
}, [inventoryData, inventoryFilters.type]);
|
||||||
|
|
||||||
const inventoryByRegion = useMemo(() => {
|
const inventoryByRegion = useMemo(() => {
|
||||||
const result: Record<string, Record<string, RegionalInventoryStats[]>> = {};
|
const result: Record<string, Record<string, RegionalInventoryStats[]>> = {};
|
||||||
@@ -992,14 +1000,14 @@ export default function App() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setIsInventoryFilterOpen(!isInventoryFilterOpen)}
|
onClick={() => setIsInventoryFilterOpen(!isInventoryFilterOpen)}
|
||||||
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-bold transition-all ${
|
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-bold transition-all ${
|
||||||
isInventoryFilterOpen || (inventoryFilters.region || inventoryFilters.city || inventoryFilters.brand || inventoryFilters.batch || inventoryFilters.model)
|
isInventoryFilterOpen || (inventoryFilters.region || inventoryFilters.city || inventoryFilters.brand || inventoryFilters.type || inventoryFilters.model)
|
||||||
? 'bg-blue-600 text-white shadow-md'
|
? 'bg-blue-600 text-white shadow-md'
|
||||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Filter size={14} />
|
<Filter size={14} />
|
||||||
<span>筛选</span>
|
<span>筛选</span>
|
||||||
{(inventoryFilters.region || inventoryFilters.city || inventoryFilters.brand || inventoryFilters.batch || inventoryFilters.model) && (
|
{(inventoryFilters.region || inventoryFilters.city || inventoryFilters.brand || inventoryFilters.type || inventoryFilters.model) && (
|
||||||
<span className="w-2 h-2 bg-white rounded-full animate-pulse"></span>
|
<span className="w-2 h-2 bg-white rounded-full animate-pulse"></span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
@@ -1017,7 +1025,7 @@ export default function App() {
|
|||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3 className="text-xs font-bold text-slate-800">库存统计 - 数据筛选</h3>
|
<h3 className="text-xs font-bold text-slate-800">库存统计 - 数据筛选</h3>
|
||||||
<button
|
<button
|
||||||
onClick={() => setInventoryFilters({ region: '', city: '', brand: '', batch: '', model: '' })}
|
onClick={() => setInventoryFilters({ region: '', city: '', brand: '', type: '', model: '' })}
|
||||||
className="text-[10px] text-blue-500 hover:underline"
|
className="text-[10px] text-blue-500 hover:underline"
|
||||||
>
|
>
|
||||||
重置所有
|
重置所有
|
||||||
@@ -1047,10 +1055,17 @@ export default function App() {
|
|||||||
</select>
|
</select>
|
||||||
</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>
|
||||||
<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">
|
<select value={inventoryFilters.type} onChange={(e) => setInventoryFilters({...inventoryFilters, type: e.target.value, model: ''})} 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>
|
<option value="">全部车型</option>
|
||||||
{uniqueInventoryModels.map(m => <option key={m} value={m}>{m}</option>)}
|
{uniqueInventoryTypes.map(t => <option key={t} value={t}>{t}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-[10px] text-slate-400 block mb-1">批次</label>
|
||||||
|
<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>
|
||||||
|
{uniqueInventoryModelsForType.map(m => <option key={m} value={m}>{m}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1085,7 +1100,7 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Active Filters Bar */}
|
{/* Active Filters Bar */}
|
||||||
{(inventoryFilters.region || inventoryFilters.city || inventoryFilters.brand || inventoryFilters.batch || inventoryFilters.model) && (
|
{(inventoryFilters.region || inventoryFilters.city || inventoryFilters.brand || inventoryFilters.type || inventoryFilters.model) && (
|
||||||
<div className="px-4 py-2 bg-white border-b border-slate-50 flex flex-wrap gap-2 items-center">
|
<div className="px-4 py-2 bg-white border-b border-slate-50 flex flex-wrap gap-2 items-center">
|
||||||
<span className="text-[10px] text-slate-400 mr-1">当前筛选:</span>
|
<span className="text-[10px] text-slate-400 mr-1">当前筛选:</span>
|
||||||
{inventoryFilters.region && (
|
{inventoryFilters.region && (
|
||||||
@@ -1106,20 +1121,20 @@ export default function App() {
|
|||||||
<button onClick={() => setInventoryFilters({...inventoryFilters, brand: ''})} className="hover:text-blue-800">×</button>
|
<button onClick={() => setInventoryFilters({...inventoryFilters, brand: ''})} className="hover:text-blue-800">×</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{inventoryFilters.batch && (
|
{inventoryFilters.type && (
|
||||||
<span className="px-2 py-0.5 bg-blue-50 text-blue-600 rounded text-[10px] flex items-center gap-1">
|
<span className="px-2 py-0.5 bg-blue-50 text-blue-600 rounded text-[10px] flex items-center gap-1">
|
||||||
批次: {inventoryFilters.batch}
|
车型: {inventoryFilters.type}
|
||||||
<button onClick={() => setInventoryFilters({...inventoryFilters, batch: ''})} className="hover:text-blue-800">×</button>
|
<button onClick={() => setInventoryFilters({...inventoryFilters, type: '', model: ''})} className="hover:text-blue-800">×</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{inventoryFilters.model && (
|
{inventoryFilters.model && (
|
||||||
<span className="px-2 py-0.5 bg-blue-50 text-blue-600 rounded text-[10px] flex items-center gap-1">
|
<span className="px-2 py-0.5 bg-blue-50 text-blue-600 rounded text-[10px] flex items-center gap-1">
|
||||||
车型: {inventoryFilters.model}
|
批次: {inventoryFilters.model}
|
||||||
<button onClick={() => setInventoryFilters({...inventoryFilters, model: ''})} className="hover:text-blue-800">×</button>
|
<button onClick={() => setInventoryFilters({...inventoryFilters, model: ''})} className="hover:text-blue-800">×</button>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => setInventoryFilters({ region: '', city: '', brand: '', batch: '', model: '' })}
|
onClick={() => setInventoryFilters({ region: '', city: '', brand: '', type: '', model: '' })}
|
||||||
className="text-[10px] text-slate-400 hover:text-red-500 ml-auto"
|
className="text-[10px] text-slate-400 hover:text-red-500 ml-auto"
|
||||||
>
|
>
|
||||||
清除全部
|
清除全部
|
||||||
|
|||||||
Reference in New Issue
Block a user