307 lines
11 KiB
TypeScript
307 lines
11 KiB
TypeScript
import React, { useState, useCallback } from 'react';
|
|
import {
|
|
Sparkles,
|
|
MapPin,
|
|
Clock,
|
|
Palette,
|
|
Tag,
|
|
Camera,
|
|
ExternalLink,
|
|
ChevronRight,
|
|
Sun,
|
|
Moon,
|
|
Sunrise,
|
|
Sunset,
|
|
Search,
|
|
} from 'lucide-react';
|
|
import { OutfitRecommendationCardProps } from '../../types/outfitRecommendation';
|
|
|
|
/**
|
|
* 穿搭方案推荐卡片组件
|
|
* 遵循设计系统规范,提供统一的方案展示界面
|
|
*/
|
|
export const OutfitRecommendationCard: React.FC<OutfitRecommendationCardProps> = ({
|
|
recommendation,
|
|
onSelect,
|
|
onSceneSearch,
|
|
onMaterialSearch,
|
|
showDetails = true,
|
|
compact = false,
|
|
className = '',
|
|
}) => {
|
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
|
|
// 处理卡片点击
|
|
const handleCardClick = useCallback((e: React.MouseEvent) => {
|
|
// 如果点击的是按钮或其子元素,不触发卡片选择
|
|
const target = e.target as HTMLElement;
|
|
if (target.closest('button')) {
|
|
return;
|
|
}
|
|
|
|
if (onSelect) {
|
|
onSelect(recommendation);
|
|
}
|
|
}, [recommendation, onSelect]);
|
|
|
|
// 处理场景检索
|
|
const handleSceneSearch = useCallback((e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
if (onSceneSearch) {
|
|
onSceneSearch(recommendation);
|
|
}
|
|
}, [recommendation, onSceneSearch]);
|
|
|
|
// 处理素材检索
|
|
const handleMaterialSearch = useCallback((e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
if (onMaterialSearch) {
|
|
onMaterialSearch(recommendation);
|
|
}
|
|
}, [recommendation, onMaterialSearch]);
|
|
|
|
// 处理展开/收起
|
|
const handleToggleExpand = useCallback((e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
setIsExpanded(!isExpanded);
|
|
}, [isExpanded]);
|
|
|
|
// 获取时间段图标
|
|
const getTimeIcon = (timeOfDay: string) => {
|
|
switch (timeOfDay.toLowerCase()) {
|
|
case '早晨':
|
|
case '上午':
|
|
return <Sunrise className="w-3 h-3" />;
|
|
case '中午':
|
|
case '下午':
|
|
return <Sun className="w-3 h-3" />;
|
|
case '傍晚':
|
|
case '黄昏':
|
|
return <Sunset className="w-3 h-3" />;
|
|
case '晚上':
|
|
case '夜晚':
|
|
return <Moon className="w-3 h-3" />;
|
|
default:
|
|
return <Clock className="w-3 h-3" />;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={`
|
|
card card-interactive group cursor-pointer animate-fade-in-up
|
|
relative overflow-hidden bg-gradient-to-br from-white to-gray-50/50
|
|
hover:from-white hover:to-primary-50/30 transition-all duration-500
|
|
hover:shadow-lg hover:shadow-primary-500/10 hover:-translate-y-1
|
|
border border-gray-200 hover:border-primary-300
|
|
${compact ? 'p-4' : 'p-6'}
|
|
${className}
|
|
`}
|
|
onClick={handleCardClick}
|
|
>
|
|
{/* 装饰性背景 */}
|
|
<div className="absolute top-0 right-0 w-24 h-24 bg-gradient-to-br from-primary-100 to-primary-200 rounded-full -translate-y-12 translate-x-12 opacity-40 group-hover:opacity-60 transition-all duration-500 group-hover:scale-110"></div>
|
|
|
|
<div className="relative">
|
|
{/* 标题和描述 */}
|
|
<div className="mb-4">
|
|
<h3 className="text-lg font-bold text-high-emphasis mb-2 group-hover:text-primary-600 transition-colors duration-200">
|
|
{recommendation.title}
|
|
</h3>
|
|
<p className="text-sm text-medium-emphasis line-clamp-2">
|
|
{recommendation.description}
|
|
</p>
|
|
</div>
|
|
|
|
{/* 风格标签 */}
|
|
<div className="flex flex-wrap gap-2 mb-4">
|
|
<div className="flex items-center gap-1 px-2 py-1 bg-blue-100 text-blue-700 rounded-full text-xs font-medium">
|
|
<Tag className="w-3 h-3" />
|
|
{recommendation.overall_style}
|
|
</div>
|
|
{recommendation.style_tags.slice(0, 2).map((tag, index) => (
|
|
<div
|
|
key={index}
|
|
className="px-2 py-1 bg-gray-100 text-gray-700 rounded-full text-xs"
|
|
>
|
|
{tag}
|
|
</div>
|
|
))}
|
|
{recommendation.style_tags.length > 2 && (
|
|
<div className="px-2 py-1 bg-gray-100 text-gray-500 rounded-full text-xs">
|
|
+{recommendation.style_tags.length - 2}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* 主要色彩 */}
|
|
{recommendation.primary_colors.length > 0 && (
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<Palette className="w-4 h-4 text-gray-500" />
|
|
<div className="flex items-center gap-2">
|
|
{recommendation.primary_colors.slice(0, 4).map((color, index) => (
|
|
<div
|
|
key={index}
|
|
className="w-6 h-6 rounded-full border-2 border-white shadow-sm"
|
|
style={{ backgroundColor: color.hex }}
|
|
title={color.name}
|
|
/>
|
|
))}
|
|
{recommendation.primary_colors.length > 4 && (
|
|
<span className="text-xs text-gray-500">
|
|
+{recommendation.primary_colors.length - 4}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 适合场合和季节 */}
|
|
<div className="grid grid-cols-2 gap-4 mb-4 text-xs text-medium-emphasis">
|
|
<div>
|
|
<span className="font-medium">适合场合:</span>
|
|
<div className="mt-1">
|
|
{recommendation.occasions.slice(0, 2).join('、')}
|
|
{recommendation.occasions.length > 2 && '...'}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<span className="font-medium">适合季节:</span>
|
|
<div className="mt-1">
|
|
{recommendation.seasons.join('、')}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 详细信息 (可展开) */}
|
|
{showDetails && (
|
|
<>
|
|
{/* 展开/收起按钮 */}
|
|
<button
|
|
onClick={handleToggleExpand}
|
|
className="flex items-center gap-2 w-full text-left text-sm text-primary-600 hover:text-primary-700 mb-3 transition-colors duration-200"
|
|
>
|
|
<span>{isExpanded ? '收起详情' : '查看详情'}</span>
|
|
<ChevronRight
|
|
className={`w-4 h-4 transition-transform duration-200 ${
|
|
isExpanded ? 'rotate-90' : ''
|
|
}`}
|
|
/>
|
|
</button>
|
|
|
|
{/* 展开的详细内容 */}
|
|
{isExpanded && (
|
|
<div className="space-y-4 animate-fade-in">
|
|
{/* 穿搭单品 */}
|
|
{recommendation.items.length > 0 && (
|
|
<div>
|
|
<h4 className="text-sm font-semibold text-high-emphasis mb-2">穿搭单品</h4>
|
|
<div className="space-y-2">
|
|
{recommendation.items.map((item, index) => (
|
|
<div key={index} className="flex items-center gap-3 p-2 bg-gray-50 rounded-lg">
|
|
<div
|
|
className="w-4 h-4 rounded-full border border-gray-300"
|
|
style={{ backgroundColor: item.primary_color.hex }}
|
|
/>
|
|
<div className="flex-1">
|
|
<div className="text-sm font-medium">{item.category}</div>
|
|
<div className="text-xs text-gray-600">{item.description}</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 场景推荐 */}
|
|
{recommendation.scene_recommendations.length > 0 && (
|
|
<div>
|
|
<h4 className="text-sm font-semibold text-high-emphasis mb-2">推荐场景</h4>
|
|
<div className="space-y-2">
|
|
{recommendation.scene_recommendations.slice(0, 2).map((scene, index) => (
|
|
<div key={index} className="p-2 bg-blue-50 rounded-lg">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<MapPin className="w-3 h-3 text-blue-600" />
|
|
<span className="text-sm font-medium text-blue-900">{scene.name}</span>
|
|
<span className="text-xs text-blue-600 bg-blue-100 px-2 py-0.5 rounded">
|
|
{scene.scene_type}
|
|
</span>
|
|
</div>
|
|
<p className="text-xs text-blue-700 mb-2">{scene.description}</p>
|
|
{scene.time_of_day.length > 0 && (
|
|
<div className="flex items-center gap-2">
|
|
{scene.time_of_day.slice(0, 3).map((time, timeIndex) => (
|
|
<div
|
|
key={timeIndex}
|
|
className="flex items-center gap-1 text-xs text-blue-600"
|
|
>
|
|
{getTimeIcon(time)}
|
|
<span>{time}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* TikTok优化建议 */}
|
|
{recommendation.tiktok_tips.length > 0 && (
|
|
<div>
|
|
<h4 className="text-sm font-semibold text-high-emphasis mb-2 flex items-center gap-2">
|
|
<Camera className="w-4 h-4" />
|
|
TikTok优化建议
|
|
</h4>
|
|
<div className="space-y-1">
|
|
{recommendation.tiktok_tips.slice(0, 3).map((tip, index) => (
|
|
<div key={index} className="text-xs text-gray-600 flex items-start gap-2">
|
|
<div className="w-1 h-1 bg-primary-500 rounded-full mt-2 flex-shrink-0" />
|
|
<span>{tip}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{/* 底部操作按钮 */}
|
|
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
|
|
<div className="text-xs text-gray-500">
|
|
{recommendation.color_theme}
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
{onMaterialSearch && (
|
|
<button
|
|
onClick={handleMaterialSearch}
|
|
className="flex items-center gap-2 px-3 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors duration-200 text-sm font-medium"
|
|
>
|
|
<Search className="w-4 h-4" />
|
|
素材检索
|
|
</button>
|
|
)}
|
|
|
|
{onSceneSearch && (
|
|
<button
|
|
onClick={handleSceneSearch}
|
|
className="flex items-center gap-2 px-3 py-2 bg-primary-500 text-white rounded-lg hover:bg-primary-600 transition-colors duration-200 text-sm font-medium"
|
|
>
|
|
<MapPin className="w-4 h-4" />
|
|
场景检索
|
|
<ExternalLink className="w-3 h-3" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default OutfitRecommendationCard;
|