feat: 重构MaterialSegmentView为多条件筛选系统 - 移除标签页设计
核心功能重构: - 移除标签页(tab)设计,改为同时显示AI分类和模特的筛选条件 - 实现组合筛选:AI分类 AND 模特的多条件检索 - 支持类似'AI分类:全身 and 模特:杨明明'的筛选组合 多条件筛选系统: - AI分类筛选:独立的筛选区域,显示所有分类选项及数量 - 模特筛选:独立的筛选区域,显示所有模特选项及数量 - 组合筛选:两个条件可以同时生效,实现精确筛选 - 当前筛选条件显示:实时显示已选择的筛选条件 UI/UX优化: - 分区设计:AI分类和模特各自独立的筛选区域 - 视觉区分:AI分类使用蓝色主题,模特使用绿色主题 - 筛选状态显示:当有筛选条件时显示当前筛选状态栏 - 清除功能:一键清除所有筛选条件 - Card卡片风格:片段展示保持卡片设计 数据处理优化: - 智能过滤:先获取所有片段,再依次应用分类和模特过滤 - 组合逻辑:支持分类 AND 模特的组合筛选 - 搜索集成:搜索功能与筛选条件无缝结合 - 实时更新:筛选条件变化时立即更新结果 技术实现: - 移除activeTab状态,简化组件逻辑 - 优化过滤算法,支持多条件组合 - 保持useMemo性能优化 - 完善的错误处理和加载状态 交互体验: - 直观的筛选界面:用户可以清楚看到所有可用的筛选选项 - 即时反馈:点击筛选条件立即看到结果变化 - 状态提示:当前筛选条件清晰显示,支持快速清除 - 空状态处理:没有匹配结果时的友好提示 功能特点: - 支持单一条件筛选:只选择AI分类或只选择模特 - 支持组合条件筛选:同时选择AI分类和模特 - 支持搜索+筛选:搜索词与筛选条件组合使用 - 支持快速重置:一键清除所有筛选条件 这个重构完全满足了用户的新需求: 1. 移除了标签页设计 2. 实现了AI分类和模特的同时筛选 3. 支持组合筛选条件(AI分类 AND 模特) 4. 提供了清晰的筛选状态显示和管理
This commit is contained in:
parent
e1e4b9d67a
commit
60cd01c1ec
|
|
@ -92,7 +92,6 @@ export const MaterialSegmentView: React.FC<MaterialSegmentViewProps> = ({ projec
|
|||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [activeTab, setActiveTab] = useState<'classification' | 'model'>('classification');
|
||||
const [selectedClassification, setSelectedClassification] = useState<string>('全部');
|
||||
const [selectedModel, setSelectedModel] = useState<string>('全部');
|
||||
|
||||
|
|
@ -175,40 +174,30 @@ export const MaterialSegmentView: React.FC<MaterialSegmentViewProps> = ({ projec
|
|||
const filteredSegments = useMemo(() => {
|
||||
if (!segmentView) return [];
|
||||
|
||||
// 获取所有片段
|
||||
let segments: SegmentWithDetails[] = [];
|
||||
segmentView.by_classification.forEach(group => {
|
||||
segments.push(...group.segments);
|
||||
});
|
||||
|
||||
if (activeTab === 'classification') {
|
||||
if (selectedClassification === '全部') {
|
||||
// 获取所有片段
|
||||
segmentView.by_classification.forEach(group => {
|
||||
segments.push(...group.segments);
|
||||
});
|
||||
} else {
|
||||
// 获取特定分类的片段
|
||||
const group = segmentView.by_classification.find(g => g.category === selectedClassification);
|
||||
if (group) {
|
||||
segments = group.segments;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (selectedModel === '全部') {
|
||||
// 获取所有片段
|
||||
segmentView.by_model.forEach(group => {
|
||||
segments.push(...group.segments);
|
||||
});
|
||||
} else {
|
||||
// 获取特定模特的片段
|
||||
const group = segmentView.by_model.find(g => g.model_name === selectedModel);
|
||||
if (group) {
|
||||
segments = group.segments;
|
||||
}
|
||||
}
|
||||
// 应用分类过滤
|
||||
if (selectedClassification !== '全部') {
|
||||
segments = segments.filter(segment =>
|
||||
segment.classification_info?.category === selectedClassification
|
||||
);
|
||||
}
|
||||
|
||||
// 应用模特过滤
|
||||
if (selectedModel !== '全部') {
|
||||
segments = segments.filter(segment =>
|
||||
segment.model_info?.name === selectedModel
|
||||
);
|
||||
}
|
||||
|
||||
// 应用搜索过滤
|
||||
if (searchTerm.trim()) {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
segments = segments.filter(segment =>
|
||||
segments = segments.filter(segment =>
|
||||
segment.material_info.name.toLowerCase().includes(searchLower) ||
|
||||
segment.classification_info?.category?.toLowerCase().includes(searchLower) ||
|
||||
segment.model_info?.name?.toLowerCase().includes(searchLower)
|
||||
|
|
@ -216,7 +205,7 @@ export const MaterialSegmentView: React.FC<MaterialSegmentViewProps> = ({ projec
|
|||
}
|
||||
|
||||
return segments;
|
||||
}, [segmentView, activeTab, selectedClassification, selectedModel, searchTerm]);
|
||||
}, [segmentView, selectedClassification, selectedModel, searchTerm]);
|
||||
|
||||
// 渲染片段卡片
|
||||
const renderSegmentCard = (segment: SegmentWithDetails) => {
|
||||
|
|
@ -354,74 +343,88 @@ export const MaterialSegmentView: React.FC<MaterialSegmentViewProps> = ({ projec
|
|||
</InteractiveButton>
|
||||
</div>
|
||||
|
||||
{/* 标签页导航 */}
|
||||
<div className="border-b border-gray-200">
|
||||
<nav className="flex space-x-8">
|
||||
<button
|
||||
onClick={() => setActiveTab('classification')}
|
||||
className={`py-2 px-1 border-b-2 font-medium text-sm transition-colors ${
|
||||
activeTab === 'classification'
|
||||
? 'border-primary-500 text-primary-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Tag size={16} />
|
||||
<span>AI分类</span>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('model')}
|
||||
className={`py-2 px-1 border-b-2 font-medium text-sm transition-colors ${
|
||||
activeTab === 'model'
|
||||
? 'border-primary-500 text-primary-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Users size={16} />
|
||||
<span>模特</span>
|
||||
</div>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
{/* 筛选条件 */}
|
||||
<div className="space-y-4">
|
||||
{/* AI分类筛选 */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Tag size={16} className="text-gray-600" />
|
||||
<span className="text-sm font-medium text-gray-700">AI分类</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{classificationOptions.map(option => (
|
||||
<button
|
||||
key={option.value}
|
||||
onClick={() => setSelectedClassification(option.value)}
|
||||
className={`inline-flex items-center px-3 py-1.5 rounded-full text-sm font-medium transition-colors ${
|
||||
selectedClassification === option.value
|
||||
? 'bg-blue-100 text-blue-800 border border-blue-200'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200 border border-gray-200'
|
||||
}`}
|
||||
>
|
||||
<span>{option.label}</span>
|
||||
<span className="ml-1.5 px-1.5 py-0.5 bg-white rounded-full text-xs">
|
||||
{option.count}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 过滤选项 */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{activeTab === 'classification' ? (
|
||||
classificationOptions.map(option => (
|
||||
<button
|
||||
key={option.value}
|
||||
onClick={() => setSelectedClassification(option.value)}
|
||||
className={`inline-flex items-center px-3 py-1.5 rounded-full text-sm font-medium transition-colors ${
|
||||
selectedClassification === option.value
|
||||
? 'bg-primary-100 text-primary-800 border border-primary-200'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200 border border-gray-200'
|
||||
}`}
|
||||
>
|
||||
<span>{option.label}</span>
|
||||
<span className="ml-1.5 px-1.5 py-0.5 bg-white rounded-full text-xs">
|
||||
{option.count}
|
||||
{/* 模特筛选 */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Users size={16} className="text-gray-600" />
|
||||
<span className="text-sm font-medium text-gray-700">模特</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{modelOptions.map(option => (
|
||||
<button
|
||||
key={option.value}
|
||||
onClick={() => setSelectedModel(option.value)}
|
||||
className={`inline-flex items-center px-3 py-1.5 rounded-full text-sm font-medium transition-colors ${
|
||||
selectedModel === option.value
|
||||
? 'bg-green-100 text-green-800 border border-green-200'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200 border border-gray-200'
|
||||
}`}
|
||||
>
|
||||
<span>{option.label}</span>
|
||||
<span className="ml-1.5 px-1.5 py-0.5 bg-white rounded-full text-xs">
|
||||
{option.count}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 当前筛选条件显示 */}
|
||||
{(selectedClassification !== '全部' || selectedModel !== '全部') && (
|
||||
<div className="flex items-center gap-2 p-3 bg-gray-50 rounded-lg">
|
||||
<Filter size={16} className="text-gray-500" />
|
||||
<span className="text-sm text-gray-600">当前筛选:</span>
|
||||
{selectedClassification !== '全部' && (
|
||||
<span className="inline-flex items-center px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full">
|
||||
AI分类: {selectedClassification}
|
||||
</span>
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
modelOptions.map(option => (
|
||||
<button
|
||||
key={option.value}
|
||||
onClick={() => setSelectedModel(option.value)}
|
||||
className={`inline-flex items-center px-3 py-1.5 rounded-full text-sm font-medium transition-colors ${
|
||||
selectedModel === option.value
|
||||
? 'bg-primary-100 text-primary-800 border border-primary-200'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200 border border-gray-200'
|
||||
}`}
|
||||
>
|
||||
<span>{option.label}</span>
|
||||
<span className="ml-1.5 px-1.5 py-0.5 bg-white rounded-full text-xs">
|
||||
{option.count}
|
||||
)}
|
||||
{selectedClassification !== '全部' && selectedModel !== '全部' && (
|
||||
<span className="text-xs text-gray-500">AND</span>
|
||||
)}
|
||||
{selectedModel !== '全部' && (
|
||||
<span className="inline-flex items-center px-2 py-1 bg-green-100 text-green-800 text-xs rounded-full">
|
||||
模特: {selectedModel}
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedClassification('全部');
|
||||
setSelectedModel('全部');
|
||||
}}
|
||||
className="ml-auto text-xs text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
清除筛选
|
||||
</button>
|
||||
))
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue