47 lines
1.2 KiB
TypeScript
47 lines
1.2 KiB
TypeScript
|
|
'use client';
|
|||
|
|
|
|||
|
|
import { useState } from 'react';
|
|||
|
|
import { cn, resolveUploadUrl } from '@/lib/utils';
|
|||
|
|
|
|||
|
|
interface AvatarProps {
|
|||
|
|
src?: string | null;
|
|||
|
|
name?: string | null;
|
|||
|
|
size?: number;
|
|||
|
|
className?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 头像组件:有 src 显示图片(onError 兜底),否则显示首字符圆形。
|
|||
|
|
*/
|
|||
|
|
export function Avatar({ src, name, size = 32, className }: AvatarProps) {
|
|||
|
|
const [imgError, setImgError] = useState(false);
|
|||
|
|
const showImg = Boolean(src) && !imgError;
|
|||
|
|
const initial = (name?.trim()?.charAt(0) ?? '?').toUpperCase();
|
|||
|
|
const fontSize = Math.max(12, Math.round(size * 0.45));
|
|||
|
|
|
|||
|
|
if (showImg) {
|
|||
|
|
return (
|
|||
|
|
// eslint-disable-next-line @next/next/no-img-element
|
|||
|
|
<img
|
|||
|
|
src={resolveUploadUrl(src ?? '')}
|
|||
|
|
alt={name ?? 'avatar'}
|
|||
|
|
onError={() => setImgError(true)}
|
|||
|
|
style={{ width: size, height: size }}
|
|||
|
|
className={cn('rounded-full object-cover', className)}
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div
|
|||
|
|
style={{ width: size, height: size, fontSize }}
|
|||
|
|
className={cn(
|
|||
|
|
'flex shrink-0 items-center justify-center rounded-full bg-brand-600 font-medium text-white',
|
|||
|
|
className,
|
|||
|
|
)}
|
|||
|
|
>
|
|||
|
|
{initial}
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|