diff --git a/components/layout/AppLayout.tsx b/components/layout/AppLayout.tsx index a2b80f4..9ec9b02 100644 --- a/components/layout/AppLayout.tsx +++ b/components/layout/AppLayout.tsx @@ -17,8 +17,10 @@ import { useComputedColorScheme, useMantineColorScheme, } from "@mantine/core" +import Avvvatars from "avvvatars-react" + import { useDisclosure, useMediaQuery } from "@mantine/hooks" -import { IconLogout, IconMoon, IconSun, IconUser } from "@tabler/icons-react" +import { IconLogout, IconMoon, IconSun } from "@tabler/icons-react" import cx from "clsx" import { Suspense, useEffect, useMemo, useState } from "react" import actionToggleClasses from "./ActionToggle.module.css" @@ -33,6 +35,103 @@ import { ClientIcon } from "./components/ClientIcon" import { LinksGroup } from "./components/NavbarLinks" import { useAppLayoutStore } from "./store" +const COMPOUND_SURNAMES = new Set([ + "欧阳", + "太史", + "端木", + "上官", + "司马", + "东方", + "独孤", + "南宫", + "万俟", + "闻人", + "夏侯", + "诸葛", + "尉迟", + "公羊", + "赫连", + "澹台", + "皇甫", + "宗政", + "濮阳", + "公冶", + "太叔", + "申屠", + "公孙", + "慕容", + "仲孙", + "钟离", + "长孙", + "宇文", + "司徒", + "鲜于", + "司空", + "闾丘", + "子车", + "亓官", + "司寇", + "巫马", + "公西", + "颛孙", + "壤驷", + "公良", + "漆雕", + "乐正", + "宰父", + "谷梁", + "拓跋", + "轩辕", + "令狐", + "段干", + "百里", + "呼延", + "东郭", + "南门", + "羊舌", + "微生", + "梁丘", + "左丘", + "东门", + "西门", + "南荣", + "第五", +]) + +function isChineseName(value: string) { + return /[\u4e00-\u9fff]/.test(value) +} + +function getAvatarDisplayValue(rawValue?: string | null) { + const value = rawValue?.trim() ?? "" + + if (!value) return "-" + + if (isChineseName(value)) { + const normalized = value.replace(/[\s·•・・]/g, "") + if (normalized.length <= 1) return normalized + + const surnameLength = + normalized.length >= 3 && COMPOUND_SURNAMES.has(normalized.slice(0, 2)) + ? 2 + : 1 + const givenName = normalized.slice(surnameLength) + + if (!givenName) return normalized.slice(-1) + if (givenName.length <= 2) return givenName + return givenName.slice(-2) + } + + const tokens = value.split(/[\s-_]+/).filter(Boolean) + if (tokens.length >= 2) { + return `${tokens[0][0] ?? ""}${tokens[tokens.length - 1][0] ?? ""}` + .trim() + .toUpperCase() + } + + return value.slice(0, 2).toUpperCase() +} + export default function AppLayout({ menu, children, @@ -144,6 +243,10 @@ export default function AppLayout({ } const matches = useMediaQuery("(min-width: 48em)") const userName = usePermissionStore((s) => s.user_name) + const avatarDisplayValue = useMemo( + () => getAvatarDisplayValue(userName), + [userName] + ) return isClient ? ( withoutLayout ? ( @@ -199,10 +302,9 @@ export default function AppLayout({ - diff --git a/package.json b/package.json index d97f454..4f912fa 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@tiptap/react": "^3.15.3", "@tiptap/starter-kit": "^3.15.3", "@tmcw/togeojson": "^7.1.2", + "avvvatars-react": "^0.4.2", "classnames": "^2.5.1", "clsx": "^2.1.1", "dayjs": "^1.11.18",