gongxiangp2/components/home/FilterPanel.tsx
2026-05-15 12:48:36 +08:00

170 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { memo, useCallback } from 'react'
export interface CategoryItem {
id: string
name: string
count: number
}
export interface MonthItem {
value: string
label: string
count: number
}
export interface FacetTotals {
materialType: number
category: number
scene: number
platform: number
productCategory: number
month: number
}
interface FilterPanelProps {
selectedMaterialType: string
setSelectedMaterialType: (value: string) => void
selectedCategory: string
setSelectedCategory: (value: string) => void
selectedScene: string
setSelectedScene: (value: string) => void
selectedPlatform: string
setSelectedPlatform: (value: string) => void
selectedProductCategory: string
setSelectedProductCategory: (value: string) => void
selectedMonth: string
setSelectedMonth: (value: string) => void
sortBy: string
setSortBy: (value: string) => void
sortOrder: 'desc' | 'asc'
setSortOrder: (value: 'desc' | 'asc') => void
materialTypes: { value: string; label: string; count?: number }[]
categories: CategoryItem[]
scenes: CategoryItem[]
platforms: CategoryItem[]
productCategories: CategoryItem[]
months: MonthItem[]
totalMaterialsCount: number
materialsCount: number
facetTotals?: FacetTotals
isFilterCollapsed: boolean
setIsFilterCollapsed: (value: boolean) => void
themeColor: string
}
function FilterPanel(props: FilterPanelProps) {
const {
selectedMaterialType, setSelectedMaterialType,
selectedCategory, setSelectedCategory,
selectedScene, setSelectedScene,
selectedPlatform, setSelectedPlatform,
selectedProductCategory, setSelectedProductCategory,
selectedMonth, setSelectedMonth,
sortBy, setSortBy, sortOrder, setSortOrder,
materialTypes, categories, scenes, platforms, productCategories, months,
totalMaterialsCount, materialsCount, facetTotals,
isFilterCollapsed, setIsFilterCollapsed, themeColor,
} = props
const handleSortChange = useCallback((newSortBy: string) => {
if (sortBy === newSortBy) {
setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc')
} else {
setSortBy(newSortBy)
setSortOrder('desc')
}
}, [sortBy, sortOrder, setSortBy, setSortOrder])
const totals = {
materialType: facetTotals?.materialType ?? totalMaterialsCount,
category: facetTotals?.category ?? totalMaterialsCount,
scene: facetTotals?.scene ?? totalMaterialsCount,
platform: facetTotals?.platform ?? totalMaterialsCount,
productCategory: facetTotals?.productCategory ?? totalMaterialsCount,
month: facetTotals?.month ?? totalMaterialsCount,
}
const activeFilterCount = [selectedMaterialType, selectedCategory, selectedScene, selectedPlatform, selectedProductCategory, selectedMonth].filter(Boolean).length
const btnBase = 'px-4 py-2 rounded-full text-sm font-medium transition-all'
const btnActive = 'text-white'
const btnInactive = 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700'
return (
<>
<div className="mb-4">
<div className="flex items-center justify-between mb-3">
<h2 className="text-sm font-medium text-gray-700 dark:text-gray-300">类型分页</h2>
{selectedMaterialType && <span className="text-xs text-gray-500 dark:text-gray-400">{materialsCount} 个素材</span>}
</div>
<div className="flex flex-wrap gap-2">
<button onClick={() => setSelectedMaterialType('')} className={`${btnBase} ${selectedMaterialType === '' ? btnActive : btnInactive}`} style={selectedMaterialType === '' ? { backgroundColor: themeColor } : {}}>全部<span className={`ml-1.5 text-xs ${selectedMaterialType === '' ? 'text-gray-300' : 'text-gray-500 dark:text-gray-400'}`}>({totals.materialType})</span></button>
{materialTypes.map((type) => (
<button key={type.value} onClick={() => setSelectedMaterialType(type.value === selectedMaterialType ? '' : type.value)} className={`${btnBase} ${selectedMaterialType === type.value ? btnActive : btnInactive}`} style={selectedMaterialType === type.value ? { backgroundColor: themeColor } : {}}>{type.label}<span className={`ml-1.5 text-xs ${selectedMaterialType === type.value ? 'text-gray-300' : 'text-gray-500 dark:text-gray-400'}`}>({type.count || 0})</span></button>
))}
</div>
</div>
<div className="mb-4 border-t border-gray-200 dark:border-gray-700 pt-4">
<button onClick={() => setIsFilterCollapsed(!isFilterCollapsed)} className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors">
<svg className={`w-4 h-4 transition-transform duration-200 ${isFilterCollapsed ? '' : 'rotate-180'}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
{isFilterCollapsed ? '展开更多筛选' : '收起筛选'}
{isFilterCollapsed && activeFilterCount > 0 && <span className="ml-1 px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 rounded text-xs">{activeFilterCount}</span>}
</button>
</div>
<div className={`overflow-hidden transition-all duration-300 ${isFilterCollapsed ? 'max-h-0 opacity-0' : 'max-h-[2000px] opacity-100'}`}>
<div className="mb-4">
<div className="flex items-center justify-between mb-3"><h2 className="text-sm font-medium text-gray-700 dark:text-gray-300">使用店铺</h2>{selectedCategory && <span className="text-xs text-gray-500 dark:text-gray-400">{materialsCount} 个素材</span>}</div>
<div className="flex flex-wrap gap-2">
<button onClick={() => setSelectedCategory('')} className={`${btnBase} ${selectedCategory === '' ? btnActive : btnInactive}`} style={selectedCategory === '' ? { backgroundColor: themeColor } : {}}>全部<span className={`ml-1.5 text-xs ${selectedCategory === '' ? 'text-gray-300' : 'text-gray-500 dark:text-gray-400'}`}>({totals.category})</span></button>
{categories.map((c) => <button key={c.id} onClick={() => setSelectedCategory(c.name === selectedCategory ? '' : c.name)} className={`${btnBase} ${selectedCategory === c.name ? btnActive : btnInactive}`} style={selectedCategory === c.name ? { backgroundColor: themeColor } : {}}>{c.name}<span className={`ml-1.5 text-xs ${selectedCategory === c.name ? 'text-gray-300' : 'text-gray-500 dark:text-gray-400'}`}>({c.count || 0})</span></button>)}
</div>
</div>
<div className="mb-4">
<div className="flex items-center justify-between mb-3"><h2 className="text-sm font-medium text-gray-700 dark:text-gray-300">场景类别</h2>{selectedScene && <span className="text-xs text-gray-500 dark:text-gray-400">{materialsCount} 个素材</span>}</div>
<div className="flex flex-wrap gap-2">
<button onClick={() => setSelectedScene('')} className={`${btnBase} ${selectedScene === '' ? btnActive : btnInactive}`} style={selectedScene === '' ? { backgroundColor: themeColor } : {}}>全部<span className={`ml-1.5 text-xs ${selectedScene === '' ? 'text-gray-300' : 'text-gray-500 dark:text-gray-400'}`}>({totals.scene})</span></button>
{scenes.map((s) => <button key={s.id} onClick={() => setSelectedScene(s.name === selectedScene ? '' : s.name)} className={`${btnBase} ${selectedScene === s.name ? btnActive : btnInactive}`} style={selectedScene === s.name ? { backgroundColor: themeColor } : {}}>{s.name}<span className={`ml-1.5 text-xs ${selectedScene === s.name ? 'text-gray-300' : 'text-gray-500 dark:text-gray-400'}`}>({s.count || 0})</span></button>)}
</div>
</div>
{platforms.length > 0 && <div className="mb-4">
<div className="flex items-center justify-between mb-3"><h2 className="text-sm font-medium text-gray-700 dark:text-gray-300">平台分类</h2>{selectedPlatform && <span className="text-xs text-gray-500 dark:text-gray-400">{materialsCount} 个素材</span>}</div>
<div className="flex flex-wrap gap-2">
<button onClick={() => setSelectedPlatform('')} className={`${btnBase} ${selectedPlatform === '' ? btnActive : btnInactive}`} style={selectedPlatform === '' ? { backgroundColor: themeColor } : {}}>全部<span className={`ml-1.5 text-xs ${selectedPlatform === '' ? 'text-gray-300' : 'text-gray-500 dark:text-gray-400'}`}>({totals.platform})</span></button>
{platforms.map((p) => <button key={p.id} onClick={() => setSelectedPlatform(p.name === selectedPlatform ? '' : p.name)} className={`${btnBase} ${selectedPlatform === p.name ? btnActive : btnInactive}`} style={selectedPlatform === p.name ? { backgroundColor: themeColor } : {}}>{p.name}<span className={`ml-1.5 text-xs ${selectedPlatform === p.name ? 'text-gray-300' : 'text-gray-500 dark:text-gray-400'}`}>({p.count || 0})</span></button>)}
</div>
</div>}
{productCategories.length > 0 && <div className="mb-4">
<div className="flex items-center justify-between mb-3"><h2 className="text-sm font-medium text-gray-700 dark:text-gray-300">产品类别</h2>{selectedProductCategory && <span className="text-xs text-gray-500 dark:text-gray-400">{materialsCount} 个素材</span>}</div>
<div className="flex flex-wrap gap-2">
<button onClick={() => setSelectedProductCategory('')} className={`${btnBase} ${selectedProductCategory === '' ? btnActive : btnInactive}`} style={selectedProductCategory === '' ? { backgroundColor: themeColor } : {}}>全部<span className={`ml-1.5 text-xs ${selectedProductCategory === '' ? 'text-gray-300' : 'text-gray-500 dark:text-gray-400'}`}>({totals.productCategory})</span></button>
{productCategories.map((c) => <button key={c.id} onClick={() => setSelectedProductCategory(c.name === selectedProductCategory ? '' : c.name)} className={`${btnBase} ${selectedProductCategory === c.name ? btnActive : btnInactive}`} style={selectedProductCategory === c.name ? { backgroundColor: themeColor } : {}}>{c.name}<span className={`ml-1.5 text-xs ${selectedProductCategory === c.name ? 'text-gray-300' : 'text-gray-500 dark:text-gray-400'}`}>({c.count || 0})</span></button>)}
</div>
</div>}
<div className="mb-4">
<div className="flex items-center justify-between mb-3"><h2 className="text-sm font-medium text-gray-700 dark:text-gray-300">上传月份</h2></div>
<div className="flex flex-wrap gap-2">
<button onClick={() => setSelectedMonth('')} className={`${btnBase} ${selectedMonth === '' ? btnActive : btnInactive}`} style={selectedMonth === '' ? { backgroundColor: themeColor } : {}}>全部<span className={`ml-1.5 text-xs ${selectedMonth === '' ? 'text-gray-300' : 'text-gray-500 dark:text-gray-400'}`}>({totals.month})</span></button>
{months.map((m) => <button key={m.value} onClick={() => setSelectedMonth(m.value === selectedMonth ? '' : m.value)} className={`${btnBase} ${selectedMonth === m.value ? btnActive : btnInactive}`} style={selectedMonth === m.value ? { backgroundColor: themeColor } : {}}>{m.label}<span className={`ml-1.5 text-xs ${selectedMonth === m.value ? 'text-gray-300' : 'text-gray-500 dark:text-gray-400'}`}>({m.count})</span></button>)}
</div>
</div>
<div className="mb-4">
<div className="flex items-center justify-between mb-3"><h2 className="text-sm font-medium text-gray-700 dark:text-gray-300">排序方式</h2></div>
<div className="flex flex-wrap gap-2">
<button onClick={() => handleSortChange('createdAt')} className={`${btnBase} flex items-center gap-1 ${sortBy === 'createdAt' ? btnActive : btnInactive}`} style={sortBy === 'createdAt' ? { backgroundColor: themeColor } : {}}>
{sortBy === 'createdAt' && sortOrder === 'asc' ? '最早上传' : '最新上传'}
{sortBy === 'createdAt' && <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={sortOrder === 'desc' ? 'M19 9l-7 7-7-7' : 'M5 15l7-7 7 7'} /></svg>}
</button>
<button onClick={() => handleSortChange('downloadCount')} className={`${btnBase} flex items-center gap-1 ${sortBy === 'downloadCount' ? btnActive : btnInactive}`} style={sortBy === 'downloadCount' ? { backgroundColor: themeColor } : {}}>
{sortBy === 'downloadCount' && sortOrder === 'asc' ? '下载最少' : '下载最多'}
{sortBy === 'downloadCount' && <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={sortOrder === 'desc' ? 'M19 9l-7 7-7-7' : 'M5 15l7-7 7 7'} /></svg>}
</button>
</div>
<div className="text-xs text-gray-500 dark:text-gray-400 mt-2">💡 提示:可以组合使用多个筛选条件,找到更精确的素材</div>
</div>
</div>
</>
)
}
export default memo(FilterPanel)