wxs ce736f197d feat(ui): 完成 Phase 2 页面与组件开发
- 完成 T-011: TanStack Query 数据获取 hooks
- 完成 T-012: 内容卡片组件 ContentCard
- 完成 T-013: 响应式网格布局 + 骨架屏
- 完成 T-014: 平台 Tab 切换
- 完成 T-015: 排序工具栏
- 完成 T-016: 内容详情页
- 完成 T-017: 自动刷新机制
- 完成 T-018: 手动刷新 + 刷新时间
- 完成 T-019: 首页组装
- 完成 T-020: 收藏 store
- 完成 T-021: 收藏按钮组件
- 完成 T-022: 收藏页面
- 完成 T-023: 设置页面 - API Key
- 完成 T-024: 设置页面 - 刷新间隔
- 完成 T-025: Toast 通知 (sonner)
- 完成 T-026: 错误/空状态组件

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-02 19:27:53 +08:00

119 lines
3.8 KiB
TypeScript

"use client";
import Image from "next/image";
import Link from "next/link";
import { Play, Heart, MessageCircle } from "lucide-react";
import { getPlatformConfig } from "@/lib/platforms";
import { formatCount } from "@/lib/format";
import { FavoriteButton } from "@/components/common/FavoriteButton";
import type { ContentItem } from "@/types/content";
import { useState } from "react";
interface ContentCardProps {
item: ContentItem;
}
export function ContentCard({ item }: ContentCardProps) {
const platform = getPlatformConfig(item.platform);
const [imgError, setImgError] = useState(false);
const playCount = formatCount(item.play_count);
const likeCount = formatCount(item.like_count);
const commentCount = formatCount(item.comment_count);
return (
<Link
href={`/detail/${item.platform}/${encodeURIComponent(item.id)}`}
className="group block rounded-lg border border-slate-200 bg-white shadow-sm overflow-hidden transition-all hover:-translate-y-0.5 hover:shadow-md"
>
{/* Cover image */}
<div className="relative aspect-[4/3] bg-slate-100 overflow-hidden">
{item.cover_url && !imgError ? (
<Image
src={item.cover_url}
alt={item.title}
fill
className="object-cover"
loading="lazy"
sizes="(max-width: 640px) 100vw, (max-width: 960px) 50vw, (max-width: 1240px) 33vw, 25vw"
onError={() => setImgError(true)}
/>
) : (
<div className="flex items-center justify-center h-full text-3xl text-slate-300">
{platform?.icon || "📄"}
</div>
)}
</div>
{/* Content */}
<div className="p-3">
{/* Platform tag */}
{platform && (
<span
className="inline-block text-xs px-1.5 py-0.5 rounded mb-1.5"
style={{
backgroundColor: `${platform.color}15`,
color: platform.color,
}}
>
{platform.icon} {platform.name}
</span>
)}
{/* Title */}
<h3 className="text-sm font-medium text-slate-800 line-clamp-2 mb-2 leading-snug">
{item.title}
</h3>
{/* Author */}
<div className="flex items-center gap-1.5 mb-2">
{item.author_avatar ? (
<Image
src={item.author_avatar}
alt={item.author_name}
width={20}
height={20}
className="rounded-full object-cover"
onError={(e) => {
(e.target as HTMLImageElement).style.display = "none";
}}
/>
) : (
<div className="w-5 h-5 rounded-full bg-slate-200 flex items-center justify-center text-[10px] text-slate-500">
👤
</div>
)}
<span className="text-xs text-slate-500 truncate">
{item.author_name}
</span>
</div>
{/* Stats + Favorite */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3 text-xs text-slate-400">
{playCount && (
<span className="flex items-center gap-0.5">
<Play className="w-3 h-3" />
{playCount}
</span>
)}
{likeCount && (
<span className="flex items-center gap-0.5">
<Heart className="w-3 h-3" />
{likeCount}
</span>
)}
{commentCount && (
<span className="flex items-center gap-0.5">
<MessageCircle className="w-3 h-3" />
{commentCount}
</span>
)}
</div>
<FavoriteButton item={item} />
</div>
</div>
</Link>
);
}