Files
jiang13-bbs/frontend/components/Avatar.tsx
freefire ff2ab286fb feat: 书库导入导出/图片变体/书籍搜索/小组件运行时等
新增:
- 书库导入导出(library_import/library_export)及测试
- 图片变体生成(image_variants)与响应式图片(responsiveImage)
- 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件
- 小组件运行时(widgetRuntime)与静态检查(widgetLint)
- 上传缓存中间件(upload_cache)与字体 CSS 提取脚本

其它:
- 后端 handlers/services 全量调整
- 前端页面、组件、库函数与配置更新
2026-10-01 03:06:05 +08:00

63 lines
2.0 KiB
TypeScript
Raw Permalink 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.
"use client";
import { useEffect, useState } from "react";
import { densitySrcSet, pickVariantWidth, variantURL } from "@/lib/responsiveImage";
// 柔和多色头像:按用户名确定性取色,圆形软底,浅/暗主题在 globals.css 各自配色
function hash(s: string) {
let h = 0;
for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0;
return h;
}
export default function Avatar({
name,
size = 36,
src,
className = "",
square = false,
}: {
name: string;
size?: number;
src?: string | null;
className?: string;
/** 方形(带小圆角)轮廓,用于帖子列表行;默认圆形 */
square?: boolean;
}) {
const idx = hash(name || "?") % 8;
const initial = (name || "?").trim().charAt(0).toUpperCase();
// 图片 404(如附件已被彻底删除)时回退到字母头像,不显示裂图
const [imgFailed, setImgFailed] = useState(false);
// 切换到新的图片地址时重置失败状态
useEffect(() => setImgFailed(false), [src]);
const showImg = !!src && !imgFailed;
// showImg 为真时 src 必为字符串;统一收敛类型供变体 helper 使用
const imgSrc = src ?? "";
return (
<span
className={`inline-flex items-center justify-center shrink-0 select-none font-bold overflow-hidden av-${idx} ${
square ? "rounded-[9px]" : "rounded-full"
} ${className}`}
style={{ width: size, height: size, fontSize: size * 0.42 }}
aria-hidden="true"
>
{showImg ? (
// 源图 ≤512px,按显示尺寸取 1x/2x 变体(PageSpeed:38px 位不再下 512 原图)
// eslint-disable-next-line @next/next/no-img-element
<img
src={variantURL(imgSrc, pickVariantWidth(size))}
srcSet={densitySrcSet(imgSrc, size)}
alt={name.trim() || "用户头像"}
className="w-full h-full object-cover"
draggable={false}
onContextMenu={(e) => e.preventDefault()}
onError={() => setImgFailed(true)}
/>
) : (
initial
)}
</span>
);
}