Files
fad-trade-next/components/WebsiteLogo.tsx
砂糖 d8ec1d4384 feat: 添加产品中心功能并优化博客系统
- 新增产品中心功能,包括产品列表页和详情页
- 实现产品多语言支持(中文、英文、越南语)
- 重构博客系统,从API获取改为本地MDX文件管理
- 更新favicon为PNG格式并修复相关引用
- 添加产品类型定义和获取逻辑
- 优化首页应用场景图片和链接
- 完善国际化配置和翻译
- 新增产品详情页标签切换组件
- 修复代理配置中的favicon路径问题
2025-12-10 11:32:50 +08:00

108 lines
2.6 KiB
TypeScript

"use client";
import { getDomain } from "@/lib/utils";
import { useEffect, useState } from "react";
interface IProps {
url: string;
size?: number;
className?: string;
timeout?: number;
}
const WebsiteLogo = ({
url,
size = 32,
className = "",
timeout = 1000, // 1 second
}: IProps) => {
const domain = getDomain(url);
const [imgSrc, setImgSrc] = useState(`https://${domain}/logo.png`);
const [fallbackIndex, setFallbackIndex] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
const fallbackSources = [
`https://${domain}/logo.png`,
`https://${domain}/logo.png`,
`https://${domain}/apple-touch-icon.png`,
`https://${domain}/apple-touch-icon-precomposed.png`,
`https://www.google.com/s2/favicons?domain=${domain}&sz=64`,
`https://icons.duckduckgo.com/ip3/${domain}.ico`,
`https://${domain}/favicon.png`,
];
useEffect(() => {
let timeoutId: any;
if (isLoading) {
timeoutId = setTimeout(() => {
handleError();
}, timeout);
}
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
};
}, [imgSrc, isLoading]);
const handleError = () => {
const nextIndex = fallbackIndex + 1;
if (nextIndex < fallbackSources.length) {
setFallbackIndex(nextIndex);
setImgSrc(fallbackSources[nextIndex]);
setIsLoading(true);
} else {
setHasError(true);
setIsLoading(false);
}
};
const handleLoad = () => {
setIsLoading(false);
setHasError(false);
};
return (
<div
className={`relative inline-block ${className}`}
style={{ width: size, height: size }}
>
{/* placeholder */}
{isLoading && (
<div className="absolute inset-0 animate-pulse">
<div className="w-full h-full rounded-md bg-gray-200/60" />
</div>
)}
<img
src={imgSrc}
alt={`${domain} logo`}
width={size}
height={size}
onError={handleError}
onLoad={handleLoad}
className={`inline-block transition-opacity duration-300 ${isLoading ? "opacity-0" : "opacity-100"
}`}
style={{
objectFit: "contain",
display: hasError ? "none" : "inline-block",
}}
/>
{/* Fallback: Display first letter of domain when all image sources fail */}
{hasError && (
<div
className="w-full h-full flex items-center justify-center bg-gray-100 rounded-md"
style={{ fontSize: `${size * 0.5}px` }}
>
{domain.charAt(0).toUpperCase()}
</div>
)}
</div>
);
};
export default WebsiteLogo;