import { useEffect, useMemo, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { api } from '../api/client'; import type { Category, ModelListItem, ModelQuery, Paged } from '../types'; import { FilterBar } from '../components/FilterBar'; import { TimelineView } from '../components/TimelineView'; import { GalleryView } from '../components/GalleryView'; import { useCollection } from '../lib/useCollection'; import { IconGallery, IconTimeline, IconFilter } from '../components/icons'; import { Button } from '../components/ui'; type ViewMode = 'gallery' | 'timeline'; export function ListPage() { const [searchParams, setSearchParams] = useSearchParams(); const [categories, setCategories] = useState([]); const [query, setQuery] = useState({ page: 1, pageSize: 1000, sort: 'first_year', order: 'asc', category: searchParams.get('category') ?? undefined, }); const [data, setData] = useState | null>(null); const [view, setView] = useState('gallery'); const [showFilters, setShowFilters] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [kw, setKw] = useState(''); const { collectedIds, toggle } = useCollection(); useEffect(() => { api.categories().then(setCategories).catch(() => {}); }, []); // URL ?category= 变化时同步(从首页章节跳入) useEffect(() => { const cat = searchParams.get('category') ?? undefined; setQuery((q) => (q.category === cat ? q : { ...q, category: cat, page: 1 })); }, [searchParams]); useEffect(() => { setLoading(true); setError(''); api .models(query) .then(setData) .catch((e) => setError(String(e))) .finally(() => setLoading(false)); }, [query]); const patch = (p: Partial) => { setQuery((q) => ({ ...q, ...p, page: 1 })); // 任意筛选变化都回到图鉴第 1 页 const next = new URLSearchParams(searchParams); next.delete('gp'); if ('category' in p) { if (p.category) next.set('category', p.category); else next.delete('category'); } setSearchParams(next, { replace: true }); }; // 图鉴分页:页码存于 URL ?gp=,从详情返回时可回到原页 const galleryPage = Math.max(1, Number(searchParams.get('gp')) || 1); const setGalleryPage = (gp: number) => { const next = new URLSearchParams(searchParams); if (gp <= 1) next.delete('gp'); else next.set('gp', String(gp)); setSearchParams(next, { replace: true }); }; // 关键字(型号)输入防抖 → query.q useEffect(() => { const t = setTimeout(() => { setQuery((q) => q.q === (kw || undefined) ? q : { ...q, q: kw || undefined, page: 1 }, ); }, 300); return () => clearTimeout(t); }, [kw]); const activeFilterCount = useMemo(() => { let n = 0; if (query.category) n++; if (query.status) n++; if (query.country) n++; if (query.yearFrom) n++; if (query.yearTo) n++; if (query.speedMin) n++; return n; }, [query]); return (
{ setKw(e.target.value); setGalleryPage(1); }} /> {data && {data.total} 个车型}
{showFilters && (
)} {error &&

加载失败:{error}

} {loading &&

加载中…

} {data && !loading && ( <> {view === 'gallery' && ( )} {view === 'timeline' && } )}
); }