265 lines
8.7 KiB
TypeScript
265 lines
8.7 KiB
TypeScript
import React, { useCallback, useState } from 'react';
|
||
import {
|
||
Grid,
|
||
List,
|
||
Loader2,
|
||
AlertCircle,
|
||
Package,
|
||
} from 'lucide-react';
|
||
import { MaterialSearchResultsProps } from '../../types/outfitRecommendation';
|
||
import { MaterialCard, MaterialSearchPagination } from './index';
|
||
import { EmptyState } from '../EmptyState';
|
||
import MaterialSearchService from '../../services/materialSearchService';
|
||
|
||
/**
|
||
* 素材检索结果列表组件
|
||
* 遵循设计系统规范,提供统一的结果展示界面
|
||
*/
|
||
const MaterialSearchResults: React.FC<MaterialSearchResultsProps> = ({
|
||
results,
|
||
totalSize,
|
||
currentPage,
|
||
pageSize,
|
||
isLoading = false,
|
||
error,
|
||
onPageChange,
|
||
onMaterialSelect,
|
||
className = '',
|
||
}) => {
|
||
// 视图模式状态
|
||
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
|
||
|
||
// 计算分页信息
|
||
const totalPages = MaterialSearchService.getTotalPages(totalSize, pageSize);
|
||
const pageInfo = MaterialSearchService.getPageRangeInfo(currentPage, pageSize, totalSize);
|
||
|
||
// 处理页面变化
|
||
const handlePageChange = useCallback((page: number) => {
|
||
if (page >= 1 && page <= totalPages && !isLoading) {
|
||
onPageChange(page);
|
||
}
|
||
}, [totalPages, onPageChange, isLoading]);
|
||
|
||
// 处理素材选择
|
||
const handleMaterialSelect = useCallback((material: any) => {
|
||
if (onMaterialSelect) {
|
||
onMaterialSelect(material);
|
||
}
|
||
}, [onMaterialSelect]);
|
||
|
||
// 加载状态
|
||
if (isLoading && results.length === 0) {
|
||
return (
|
||
<div className={`space-y-6 ${className}`}>
|
||
{/* 加载标题 */}
|
||
<div className="flex items-center justify-center gap-3 p-6">
|
||
<Loader2 className="w-6 h-6 text-primary-500 animate-spin" />
|
||
<div className="text-center">
|
||
<h3 className="text-lg font-semibold text-high-emphasis mb-1">
|
||
正在检索素材...
|
||
</h3>
|
||
<p className="text-sm text-medium-emphasis">
|
||
请稍候,我们正在为您搜索最合适的素材
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 加载骨架屏 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||
{[1, 2, 3, 4, 5, 6].map((index) => (
|
||
<div
|
||
key={index}
|
||
className="card p-4 animate-pulse"
|
||
>
|
||
<div className="space-y-4">
|
||
{/* 图片骨架 */}
|
||
<div className="aspect-square bg-gray-200 rounded-lg"></div>
|
||
|
||
{/* 标题骨架 */}
|
||
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
|
||
|
||
{/* 描述骨架 */}
|
||
<div className="space-y-2">
|
||
<div className="h-3 bg-gray-200 rounded w-full"></div>
|
||
<div className="h-3 bg-gray-200 rounded w-2/3"></div>
|
||
</div>
|
||
|
||
{/* 标签骨架 */}
|
||
<div className="flex gap-2">
|
||
<div className="h-6 bg-gray-200 rounded-full w-16"></div>
|
||
<div className="h-6 bg-gray-200 rounded-full w-12"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// 错误状态
|
||
if (error) {
|
||
return (
|
||
<div className={`${className}`}>
|
||
<EmptyState
|
||
variant="error"
|
||
icon={<AlertCircle className="w-12 h-12 text-red-500" />}
|
||
title="检索失败"
|
||
description={error}
|
||
actionText="重新检索"
|
||
onAction={() => onPageChange(currentPage)}
|
||
illustration="error"
|
||
size="md"
|
||
className="py-12"
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// 空状态
|
||
if (!results || results.length === 0) {
|
||
return (
|
||
<div className={`${className}`}>
|
||
<EmptyState
|
||
variant="default"
|
||
icon={<Package className="w-12 h-12 text-gray-400" />}
|
||
title="暂无素材结果"
|
||
description="未找到符合条件的素材,请尝试调整检索条件"
|
||
actionText="重新检索"
|
||
onAction={() => onPageChange(1)}
|
||
illustration="folder"
|
||
size="md"
|
||
className="py-12"
|
||
showTips={true}
|
||
tips={[
|
||
'尝试使用更通用的关键词',
|
||
'调整颜色和风格过滤条件',
|
||
'检查网络连接是否正常',
|
||
]}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// 正常状态 - 显示结果列表
|
||
return (
|
||
<div className={`space-y-6 max-h-full overflow-y-auto ${className}`}>
|
||
{/* 列表标题和统计信息 */}
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-3">
|
||
<div className="icon-container primary w-8 h-8">
|
||
<Grid className="w-4 h-4" />
|
||
</div>
|
||
<div>
|
||
<h3 className="text-lg font-semibold text-high-emphasis">
|
||
素材检索结果
|
||
</h3>
|
||
<p className="text-sm text-medium-emphasis">
|
||
第 {pageInfo.start}-{pageInfo.end} 项,共 {totalSize} 个素材
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 视图切换 */}
|
||
<div className="flex items-center gap-2">
|
||
<button
|
||
onClick={() => setViewMode('grid')}
|
||
className={`p-2 rounded-lg transition-colors duration-200 ${
|
||
viewMode === 'grid'
|
||
? 'text-primary-600 bg-primary-50'
|
||
: 'text-gray-400 hover:text-gray-600 hover:bg-gray-100'
|
||
}`}
|
||
title="网格视图"
|
||
>
|
||
<Grid className="w-4 h-4" />
|
||
</button>
|
||
<button
|
||
onClick={() => setViewMode('list')}
|
||
className={`p-2 rounded-lg transition-colors duration-200 ${
|
||
viewMode === 'list'
|
||
? 'text-primary-600 bg-primary-50'
|
||
: 'text-gray-400 hover:text-gray-600 hover:bg-gray-100'
|
||
}`}
|
||
title="列表视图"
|
||
>
|
||
<List className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 素材卡片展示 */}
|
||
<div className={
|
||
viewMode === 'grid'
|
||
? "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
|
||
: "space-y-3"
|
||
}>
|
||
{results.map((material, index) => (
|
||
<MaterialCard
|
||
key={material.id || index}
|
||
material={material}
|
||
onSelect={handleMaterialSelect}
|
||
showScore={true}
|
||
compact={viewMode === 'list'}
|
||
className={`animate-fade-in-up ${isLoading ? 'opacity-50 pointer-events-none' : ''}`}
|
||
/>
|
||
))}
|
||
</div>
|
||
|
||
{/* 分页控制 */}
|
||
{totalPages > 1 && (
|
||
<MaterialSearchPagination
|
||
currentPage={currentPage}
|
||
totalPages={totalPages}
|
||
pageSize={pageSize}
|
||
totalSize={totalSize}
|
||
isLoading={isLoading}
|
||
onPageChange={handlePageChange}
|
||
className="mt-8"
|
||
/>
|
||
)}
|
||
|
||
{/* 底部提示 */}
|
||
<div className="card p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-200">
|
||
<div className="flex items-start gap-3">
|
||
<div className="icon-container blue w-6 h-6 mt-0.5 shadow-sm">
|
||
<Package className="w-3 h-3" />
|
||
</div>
|
||
<div className="flex-1">
|
||
<h4 className="text-sm font-semibold text-blue-900 mb-2">💡 使用提示</h4>
|
||
<ul className="text-xs text-blue-700 space-y-1.5">
|
||
<li className="flex items-center gap-2">
|
||
<div className="w-1 h-1 bg-blue-400 rounded-full"></div>
|
||
点击素材卡片查看详细信息和产品描述
|
||
</li>
|
||
<li className="flex items-center gap-2">
|
||
<div className="w-1 h-1 bg-blue-400 rounded-full"></div>
|
||
使用分页控制浏览更多素材结果
|
||
</li>
|
||
<li className="flex items-center gap-2">
|
||
<div className="w-1 h-1 bg-blue-400 rounded-full"></div>
|
||
相关性评分越高表示与穿搭方案越匹配
|
||
</li>
|
||
<li className="flex items-center gap-2">
|
||
<div className="w-1 h-1 bg-blue-400 rounded-full"></div>
|
||
可以重新生成检索条件获得不同的结果
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 加载遮罩 */}
|
||
{isLoading && results.length > 0 && (
|
||
<div className="absolute inset-0 bg-white/50 backdrop-blur-sm flex items-center justify-center">
|
||
<div className="flex items-center gap-3 bg-white rounded-lg shadow-lg p-4">
|
||
<Loader2 className="w-5 h-5 text-primary-500 animate-spin" />
|
||
<span className="text-sm font-medium text-gray-700">正在加载...</span>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default MaterialSearchResults;
|