78 行
1.9 KiB
TypeScript
78 行
1.9 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { cn } from "../../lib/utils";
|
|
|
|
type EmployeeAvatarSize = "sm" | "md" | "lg" | "xl";
|
|
|
|
const SIZE_CLASS_MAP: Record<EmployeeAvatarSize, string> = {
|
|
sm: "size-9 text-xs",
|
|
md: "size-11 text-sm",
|
|
lg: "size-16 text-lg",
|
|
xl: "size-24 text-2xl",
|
|
};
|
|
|
|
function buildInitials(name: string) {
|
|
const trimmed = name.trim();
|
|
if (!trimmed) {
|
|
return "员工";
|
|
}
|
|
|
|
const cjkMatch = trimmed.match(/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u);
|
|
if (cjkMatch?.[0]) {
|
|
return cjkMatch[0];
|
|
}
|
|
|
|
if (/^[A-Za-z0-9 ]+$/.test(trimmed)) {
|
|
const segments = trimmed.split(/\s+/).filter(Boolean);
|
|
return segments.slice(0, 2).map((item) => item[0]?.toUpperCase() ?? "").join("") || trimmed.slice(0, 2).toUpperCase();
|
|
}
|
|
|
|
return Array.from(trimmed).slice(0, 2).join("");
|
|
}
|
|
|
|
export function EmployeeAvatar({
|
|
name,
|
|
src,
|
|
size = "md",
|
|
className,
|
|
}: {
|
|
name: string;
|
|
src?: string | null;
|
|
size?: EmployeeAvatarSize;
|
|
className?: string;
|
|
}) {
|
|
const [imageBroken, setImageBroken] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setImageBroken(false);
|
|
}, [src]);
|
|
|
|
const showImage = Boolean(src) && !imageBroken;
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"relative shrink-0 overflow-hidden rounded-full border border-zinc-200 bg-gradient-to-br from-zinc-100 via-white to-zinc-200",
|
|
SIZE_CLASS_MAP[size],
|
|
className,
|
|
)}
|
|
aria-hidden="true"
|
|
>
|
|
{showImage ? (
|
|
<img
|
|
src={src ?? undefined}
|
|
alt=""
|
|
className="size-full object-cover"
|
|
loading="lazy"
|
|
onError={() => setImageBroken(true)}
|
|
/>
|
|
) : (
|
|
<div className="flex size-full items-center justify-center font-semibold text-zinc-600">
|
|
{buildInitials(name)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default EmployeeAvatar;
|