212 lines
7.2 KiB
TypeScript
212 lines
7.2 KiB
TypeScript
import React from 'react';
|
||
import {
|
||
RefreshCw,
|
||
AlertCircle,
|
||
Sparkles,
|
||
Loader2,
|
||
ShoppingBag,
|
||
} from 'lucide-react';
|
||
import { OutfitRecommendationListProps } from '../../types/outfitRecommendation';
|
||
import OutfitRecommendationCard from './OutfitRecommendationCard';
|
||
import { EmptyState } from '../EmptyState';
|
||
|
||
/**
|
||
* 穿搭方案推荐列表组件
|
||
* 遵循设计系统规范,提供统一的列表展示界面
|
||
*/
|
||
export const OutfitRecommendationList: React.FC<OutfitRecommendationListProps> = ({
|
||
recommendations,
|
||
isLoading = false,
|
||
error,
|
||
onRecommendationSelect,
|
||
onSceneSearch,
|
||
onRegenerate,
|
||
className = '',
|
||
}) => {
|
||
// 加载状态
|
||
if (isLoading) {
|
||
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">
|
||
AI正在生成穿搭方案...
|
||
</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 xl:grid-cols-4 2xl:grid-cols-5 gap-6">
|
||
{[1, 2, 3].map((index) => (
|
||
<div
|
||
key={index}
|
||
className="card p-6 animate-pulse"
|
||
>
|
||
<div className="space-y-4">
|
||
{/* 标题骨架 */}
|
||
<div className="h-6 bg-gray-200 rounded-lg w-3/4"></div>
|
||
|
||
{/* 描述骨架 */}
|
||
<div className="space-y-2">
|
||
<div className="h-4 bg-gray-200 rounded w-full"></div>
|
||
<div className="h-4 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 className="flex gap-2">
|
||
{[1, 2, 3, 4].map((colorIndex) => (
|
||
<div
|
||
key={colorIndex}
|
||
className="w-6 h-6 bg-gray-200 rounded-full"
|
||
></div>
|
||
))}
|
||
</div>
|
||
|
||
{/* 按钮骨架 */}
|
||
<div className="flex justify-between items-center pt-4 border-t border-gray-100">
|
||
<div className="h-4 bg-gray-200 rounded w-20"></div>
|
||
<div className="h-8 bg-gray-200 rounded-lg w-24"></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={onRegenerate}
|
||
illustration="error"
|
||
size="md"
|
||
className="py-12"
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// 空状态
|
||
if (!recommendations || recommendations.length === 0) {
|
||
return (
|
||
<div className={`${className}`}>
|
||
<EmptyState
|
||
variant="default"
|
||
icon={<ShoppingBag className="w-12 h-12 text-gray-400" />}
|
||
title="暂无穿搭方案"
|
||
description="点击上方的 ✨ 图标开始生成个性化穿搭推荐"
|
||
actionText="开始生成"
|
||
onAction={onRegenerate}
|
||
illustration="folder"
|
||
size="md"
|
||
className="py-12"
|
||
showTips={true}
|
||
tips={[
|
||
'输入关键词如"休闲"、"正式"、"约会"等',
|
||
'描述场合如"工作"、"聚会"、"旅行"等',
|
||
'指定风格如"简约"、"复古"、"街头"等',
|
||
]}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// 正常状态 - 显示推荐列表
|
||
return (
|
||
<div className={`space-y-6 ${className}`}>
|
||
{/* 列表标题和操作 */}
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-3">
|
||
<div className="icon-container primary w-8 h-8">
|
||
<Sparkles className="w-4 h-4" />
|
||
</div>
|
||
<div>
|
||
<h3 className="text-lg font-semibold text-high-emphasis">
|
||
AI穿搭推荐
|
||
</h3>
|
||
<p className="text-sm text-medium-emphasis">
|
||
共为您生成 {recommendations.length} 个个性化穿搭方案
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{onRegenerate && (
|
||
<button
|
||
onClick={onRegenerate}
|
||
className="flex items-center gap-2 px-4 py-2 text-primary-600 hover:text-primary-700 hover:bg-primary-50 rounded-lg transition-all duration-200"
|
||
>
|
||
<RefreshCw className="w-4 h-4" />
|
||
<span className="text-sm font-medium">重新生成</span>
|
||
</button>
|
||
)}
|
||
</div>
|
||
|
||
{/* 推荐卡片网格 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||
{recommendations.map((recommendation, index) => (
|
||
<OutfitRecommendationCard
|
||
key={recommendation.id || index}
|
||
recommendation={recommendation}
|
||
onSelect={onRecommendationSelect}
|
||
onSceneSearch={onSceneSearch}
|
||
showDetails={true}
|
||
compact={false}
|
||
className="animate-fade-in-up"
|
||
/>
|
||
))}
|
||
</div>
|
||
|
||
{/* 底部提示 */}
|
||
<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">
|
||
<Sparkles 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>
|
||
每个方案都包含TikTok优化建议,助力内容创作
|
||
</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>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default OutfitRecommendationList;
|