A1/A3/A5/A7/A8 完成员工头像前端、组织架构页与QA回填
这个提交包含在:
@@ -0,0 +1,72 @@
|
||||
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 "员工";
|
||||
}
|
||||
|
||||
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 trimmed.slice(0, 2);
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -29,6 +29,7 @@ const STATUS_MAP: Record<string, { label: string; className: string }> = {
|
||||
skipped: { label: "已跳过", className: "badge-neutral" },
|
||||
revoked: { label: "已撤销", className: "badge-neutral" },
|
||||
expired: { label: "已过期", className: "badge-neutral" },
|
||||
terminated: { label: "已终止", className: "badge-error" },
|
||||
scheduled: { label: "已排期", className: "badge-primary" },
|
||||
};
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ export const NAV_ITEMS: NavItem[] = [
|
||||
{ to: "/projects", label: "项目", icon: "workspaces", anyOf: ["project.view_own", "project.view_all", "project.manage"] },
|
||||
{ to: "/system/workspace", label: "工作区设置", icon: "tune", anyOf: ["system.settings"] },
|
||||
{ to: "/people", label: "人员管理", icon: "group", anyOf: ["system.users", "report.dashboard.view"] },
|
||||
{ to: "/organization", label: "组织架构", icon: "account_tree", anyOf: ["system.users", "report.dashboard.view"] },
|
||||
{ to: "/system/workflow-definitions", label: "流程定义", icon: "schema", anyOf: ["system.settings"] },
|
||||
{ to: "/system/settings", label: "系统设置", icon: "settings", anyOf: ["system.settings", "system.users", "masterdata.view", "masterdata.manage", "notification.channel.manage", "integration.manage"] },
|
||||
{ to: "/system/feature-flags", label: "功能开关", icon: "toggle_on", anyOf: ["system.settings"] },
|
||||
|
||||
在新工单中引用
屏蔽一个用户