"use client"
import {
Button,
AppShell,
Box,
Burger,
Divider,
Flex,
Group,
Modal,
Menu,
NavLink,
PasswordInput,
ScrollArea,
Space,
Stack,
Text,
Tooltip,
UnstyledButton,
useComputedColorScheme,
useMantineColorScheme,
} from "@mantine/core"
import Avvvatars from "avvvatars-react"
import { useDisclosure, useMediaQuery } from "@mantine/hooks"
import { useForm } from "@mantine/form"
import { notifications } from "@mantine/notifications"
import {
IconChevronDown,
IconChevronLeft,
IconChevronRight,
IconLock,
IconLogout,
IconMoon,
IconSun,
} from "@tabler/icons-react"
import cx from "clsx"
import { Suspense, useEffect, useMemo, useState } from "react"
import actionToggleClasses from "./ActionToggle.module.css"
import headerLinkClasses from "./HeaderLink.module.css"
import { useUserStore } from "@/app/store/user"
import { usePathname, useRouter } from "next/navigation"
import { usePermissionStore } from "../label/store/auth"
import { userModifyPassword } from "../label/api/user"
import { deleteCookie } from "../login/api"
import {
settingModalActionsClassName,
settingModalClassNames,
settingModalFormClassName,
settingModalMetaTextClassName,
} from "../setting/PageSurface"
import classes from "./AppLayout.module.css"
import { MenuItem, withoutLayoutRoutes } from "./common"
import { ClientIcon } from "./components/ClientIcon"
import { LinksGroup } from "./components/NavbarLinks"
import { getShellNavLinkStyles } from "./navStyles"
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,
}: {
menu: MenuItem[]
children: React.ReactNode
}) {
const [mobileOpened, { toggle: toggleMobile }] = useDisclosure()
const [
passwordModalOpened,
{ open: openPasswordModal, close: closePasswordModal },
] = useDisclosure(false)
const { updateIsOpen } = useAppLayoutStore.getState()
const { isOpen } = useAppLayoutStore()
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
const { setColorScheme } = useMantineColorScheme()
const computedColorScheme = useComputedColorScheme("light", {
getInitialValueInEffect: true,
})
const pathname = usePathname()
const withoutLayout = useMemo(
() => withoutLayoutRoutes.includes(pathname),
[pathname]
)
const router = useRouter()
const activeHeaderMenu = useMemo(() => {
return menu?.find((child) => pathname.startsWith(`/${child.url}`))
}, [pathname, menu])
const { menuDefaultRouter } = useAppLayoutStore.getState()
const passwordForm = useForm({
initialValues: {
old_password: "",
new_password: "",
confirm_new_password: "",
},
validate: {
old_password: (value) => (value ? null : "请输入当前密码"),
new_password: (value) => (value ? null : "请输入新密码"),
confirm_new_password: (value) => (value ? null : "请再次确认新密码"),
},
validateInputOnChange: true,
validateInputOnBlur: true,
clearInputErrorOnChange: true,
})
const [passwordSubmitting, setPasswordSubmitting] = useState(false)
const handleOpenPasswordModal = () => {
passwordForm.reset()
openPasswordModal()
}
const handleClosePasswordModal = () => {
passwordForm.reset()
closePasswordModal()
}
const handlePasswordSubmit = passwordForm.onSubmit(async (values) => {
if (values.new_password !== values.confirm_new_password) {
passwordForm.setFieldError(
"confirm_new_password",
"两次输入的新密码不一致"
)
return
}
setPasswordSubmitting(true)
try {
await userModifyPassword({
old_password: values.old_password,
new_password: values.new_password,
})
handleClosePasswordModal()
notifications.show({
color: "green",
title: "修改成功",
message: "密码已更新",
})
} catch (error) {
notifications.show({
color: "red",
title: "修改失败",
message:
error instanceof Error ? error.message : "密码修改失败,请重试",
})
} finally {
setPasswordSubmitting(false)
}
})
const headItems = menu.map((item) => (
{
e.preventDefault()
if (activeHeaderMenu?.url === item.url) return
let toPathname = menuDefaultRouter[`/${item.url}`] || `/${item.url}`
window.location.href = toPathname
// router.replace(toPathname)
}}>