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:
imeepos 2025-07-15 21:36:33 +08:00
parent e1e4b9d67a
commit 60cd01c1ec
1 changed files with 96 additions and 93 deletions

View File

@ -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>