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 [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [activeTab, setActiveTab] = useState<'classification' | 'model'>('classification');
|
|
||||||
const [selectedClassification, setSelectedClassification] = useState<string>('全部');
|
const [selectedClassification, setSelectedClassification] = useState<string>('全部');
|
||||||
const [selectedModel, setSelectedModel] = useState<string>('全部');
|
const [selectedModel, setSelectedModel] = useState<string>('全部');
|
||||||
|
|
||||||
|
|
@ -175,40 +174,30 @@ export const MaterialSegmentView: React.FC<MaterialSegmentViewProps> = ({ projec
|
||||||
const filteredSegments = useMemo(() => {
|
const filteredSegments = useMemo(() => {
|
||||||
if (!segmentView) return [];
|
if (!segmentView) return [];
|
||||||
|
|
||||||
|
// 获取所有片段
|
||||||
let segments: SegmentWithDetails[] = [];
|
let segments: SegmentWithDetails[] = [];
|
||||||
|
segmentView.by_classification.forEach(group => {
|
||||||
|
segments.push(...group.segments);
|
||||||
|
});
|
||||||
|
|
||||||
if (activeTab === 'classification') {
|
// 应用分类过滤
|
||||||
if (selectedClassification === '全部') {
|
if (selectedClassification !== '全部') {
|
||||||
// 获取所有片段
|
segments = segments.filter(segment =>
|
||||||
segmentView.by_classification.forEach(group => {
|
segment.classification_info?.category === selectedClassification
|
||||||
segments.push(...group.segments);
|
);
|
||||||
});
|
}
|
||||||
} else {
|
|
||||||
// 获取特定分类的片段
|
// 应用模特过滤
|
||||||
const group = segmentView.by_classification.find(g => g.category === selectedClassification);
|
if (selectedModel !== '全部') {
|
||||||
if (group) {
|
segments = segments.filter(segment =>
|
||||||
segments = group.segments;
|
segment.model_info?.name === selectedModel
|
||||||
}
|
);
|
||||||
}
|
|
||||||
} 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 (searchTerm.trim()) {
|
if (searchTerm.trim()) {
|
||||||
const searchLower = searchTerm.toLowerCase();
|
const searchLower = searchTerm.toLowerCase();
|
||||||
segments = segments.filter(segment =>
|
segments = segments.filter(segment =>
|
||||||
segment.material_info.name.toLowerCase().includes(searchLower) ||
|
segment.material_info.name.toLowerCase().includes(searchLower) ||
|
||||||
segment.classification_info?.category?.toLowerCase().includes(searchLower) ||
|
segment.classification_info?.category?.toLowerCase().includes(searchLower) ||
|
||||||
segment.model_info?.name?.toLowerCase().includes(searchLower)
|
segment.model_info?.name?.toLowerCase().includes(searchLower)
|
||||||
|
|
@ -216,7 +205,7 @@ export const MaterialSegmentView: React.FC<MaterialSegmentViewProps> = ({ projec
|
||||||
}
|
}
|
||||||
|
|
||||||
return segments;
|
return segments;
|
||||||
}, [segmentView, activeTab, selectedClassification, selectedModel, searchTerm]);
|
}, [segmentView, selectedClassification, selectedModel, searchTerm]);
|
||||||
|
|
||||||
// 渲染片段卡片
|
// 渲染片段卡片
|
||||||
const renderSegmentCard = (segment: SegmentWithDetails) => {
|
const renderSegmentCard = (segment: SegmentWithDetails) => {
|
||||||
|
|
@ -354,74 +343,88 @@ export const MaterialSegmentView: React.FC<MaterialSegmentViewProps> = ({ projec
|
||||||
</InteractiveButton>
|
</InteractiveButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 标签页导航 */}
|
{/* 筛选条件 */}
|
||||||
<div className="border-b border-gray-200">
|
<div className="space-y-4">
|
||||||
<nav className="flex space-x-8">
|
{/* AI分类筛选 */}
|
||||||
<button
|
<div>
|
||||||
onClick={() => setActiveTab('classification')}
|
<div className="flex items-center gap-2 mb-3">
|
||||||
className={`py-2 px-1 border-b-2 font-medium text-sm transition-colors ${
|
<Tag size={16} className="text-gray-600" />
|
||||||
activeTab === 'classification'
|
<span className="text-sm font-medium text-gray-700">AI分类</span>
|
||||||
? 'border-primary-500 text-primary-600'
|
</div>
|
||||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
<div className="flex flex-wrap gap-2">
|
||||||
}`}
|
{classificationOptions.map(option => (
|
||||||
>
|
<button
|
||||||
<div className="flex items-center gap-2">
|
key={option.value}
|
||||||
<Tag size={16} />
|
onClick={() => setSelectedClassification(option.value)}
|
||||||
<span>AI分类</span>
|
className={`inline-flex items-center px-3 py-1.5 rounded-full text-sm font-medium transition-colors ${
|
||||||
</div>
|
selectedClassification === option.value
|
||||||
</button>
|
? 'bg-blue-100 text-blue-800 border border-blue-200'
|
||||||
<button
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200 border border-gray-200'
|
||||||
onClick={() => setActiveTab('model')}
|
}`}
|
||||||
className={`py-2 px-1 border-b-2 font-medium text-sm transition-colors ${
|
>
|
||||||
activeTab === 'model'
|
<span>{option.label}</span>
|
||||||
? 'border-primary-500 text-primary-600'
|
<span className="ml-1.5 px-1.5 py-0.5 bg-white rounded-full text-xs">
|
||||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
{option.count}
|
||||||
}`}
|
</span>
|
||||||
>
|
</button>
|
||||||
<div className="flex items-center gap-2">
|
))}
|
||||||
<Users size={16} />
|
</div>
|
||||||
<span>模特</span>
|
</div>
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 过滤选项 */}
|
{/* 模特筛选 */}
|
||||||
<div className="flex flex-wrap gap-2">
|
<div>
|
||||||
{activeTab === 'classification' ? (
|
<div className="flex items-center gap-2 mb-3">
|
||||||
classificationOptions.map(option => (
|
<Users size={16} className="text-gray-600" />
|
||||||
<button
|
<span className="text-sm font-medium text-gray-700">模特</span>
|
||||||
key={option.value}
|
</div>
|
||||||
onClick={() => setSelectedClassification(option.value)}
|
<div className="flex flex-wrap gap-2">
|
||||||
className={`inline-flex items-center px-3 py-1.5 rounded-full text-sm font-medium transition-colors ${
|
{modelOptions.map(option => (
|
||||||
selectedClassification === option.value
|
<button
|
||||||
? 'bg-primary-100 text-primary-800 border border-primary-200'
|
key={option.value}
|
||||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200 border border-gray-200'
|
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
|
||||||
<span>{option.label}</span>
|
? 'bg-green-100 text-green-800 border border-green-200'
|
||||||
<span className="ml-1.5 px-1.5 py-0.5 bg-white rounded-full text-xs">
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200 border border-gray-200'
|
||||||
{option.count}
|
}`}
|
||||||
|
>
|
||||||
|
<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>
|
</span>
|
||||||
</button>
|
)}
|
||||||
))
|
{selectedClassification !== '全部' && selectedModel !== '全部' && (
|
||||||
) : (
|
<span className="text-xs text-gray-500">AND</span>
|
||||||
modelOptions.map(option => (
|
)}
|
||||||
<button
|
{selectedModel !== '全部' && (
|
||||||
key={option.value}
|
<span className="inline-flex items-center px-2 py-1 bg-green-100 text-green-800 text-xs rounded-full">
|
||||||
onClick={() => setSelectedModel(option.value)}
|
模特: {selectedModel}
|
||||||
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}
|
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedClassification('全部');
|
||||||
|
setSelectedModel('全部');
|
||||||
|
}}
|
||||||
|
className="ml-auto text-xs text-gray-500 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
清除筛选
|
||||||
</button>
|
</button>
|
||||||
))
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue