import { ExternalLink } from "lucide-react"; import { LocaleLink } from "@/components/locale-link"; import { ToolLogo } from "@/components/tool-logo"; import { FavoriteButton } from "@/features/tools/components/favorite-button"; import { VoteButton } from "@/features/voting/components/vote-button"; import { StarRating } from "@/features/reviews/components/star-rating"; import { cn } from "@/lib/utils"; export interface ToolCardData { id: string; slug: string; domain: string | null; websiteUrl: string | null; logoUrl: string | null; nameEn: string; nameZh: string | null; taglineEn?: string | null; taglineZh?: string | null; descriptionEn?: string | null; descriptionZh?: string | null; pricing?: string | null; voteCount?: number; ratingAvg?: number; reviewCount?: number; } interface ToolCardProps { tool: ToolCardData; locale: string; /** compact: 单行(榜单);default: 带描述卡片 */ variant?: "default" | "compact"; /** 排名序号(榜单场景展示) */ rank?: number; className?: string; } const pricingLabels: Record = { free: { en: "Free", zh: "免费" }, freemium: { en: "Freemium", zh: "免费增值" }, paid: { en: "Paid", zh: "付费" }, free_trial: { en: "Free Trial", zh: "免费试用" }, contact: { en: "Contact", zh: "联系定价" }, open_source: { en: "Open Source", zh: "开源" }, }; /** * # 组件 ToolCard * 说明:Core 通用工具卡片(服务端组件,内嵌客户端投票/收藏按钮)。 * 五套主题均可复用;variant 控制密度。 */ export function ToolCard({ tool, locale, variant = "default", rank, className, }: ToolCardProps) { const name = locale === "zh" && tool.nameZh ? tool.nameZh : tool.nameEn; const tagline = locale === "zh" ? tool.taglineZh || tool.descriptionZh || tool.taglineEn || tool.descriptionEn : tool.taglineEn || tool.descriptionEn || tool.taglineZh || tool.descriptionZh; const pricing = tool.pricing ? pricingLabels[tool.pricing] : null; return (
{typeof rank === "number" && ( {rank} )} {/* Product Hunt 风格投票块 */}

{name}

{pricing && ( {locale === "zh" ? pricing.zh : pricing.en} )} {(tool.ratingAvg ?? 0) > 0 && ( {(tool.ratingAvg ?? 0).toFixed(1)} )}
{tagline && (

{tagline}

)}
{tool.websiteUrl && ( )}
); } /** * # 组件 ToolCardGrid * 说明:卡片网格容器(响应式,Mobile First)。 */ export function ToolCardGrid({ tools, locale, ranked = false, className, }: { tools: ToolCardData[]; locale: string; ranked?: boolean; className?: string; }) { return (
{tools.map((tool, index) => ( ))}
); }