mixvideo-v2/apps/desktop/src/components/outfit/OutfitRecommendationList.tsx

212 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;