From 8e75a1629f3024b3007973dca418b2e759f78e26 Mon Sep 17 00:00:00 2001 From: zhangheng Date: Mon, 25 May 2026 13:42:38 +0800 Subject: [PATCH] feat(header): modify_password --- components/label/api/user/index.ts | 20 ++--- components/label/api/user/typing.ts | 5 ++ components/layout/AppLayout.tsx | 132 ++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 10 deletions(-) diff --git a/components/label/api/user/index.ts b/components/label/api/user/index.ts index 50de510..dab06ba 100644 --- a/components/label/api/user/index.ts +++ b/components/label/api/user/index.ts @@ -38,6 +38,15 @@ export const userEditById = (data: User.EditProps, id: number) => { }) } +// 修改用户密码 +export const userModifyPassword = (data: User.ModifyPasswordProps) => { + return httpFetch({ + url: BASE_LABEL_API + `/api/v1/label_server/user/modify_password`, + method: "PUT", + body: JSON.stringify(data), + }) +} + // 编辑用户组织 export const userUpdateGroup = (data: User.UpdateGroupProps) => { return httpFetch({ @@ -65,16 +74,7 @@ export const userGroupAdd = (data: Organization.CreateProps) => { } // 修改用户密码 -export const modifyUserPassword = (data: { - old_password: string - new_password: string -}) => { - return httpFetch({ - url: BASE_LABEL_API + "/api/v1/label_server/user/modify_password", - method: "PUT", - body: JSON.stringify(data), - }) -} +export const modifyUserPassword = userModifyPassword /* ****************************** Options ****************************** */ interface Option { diff --git a/components/label/api/user/typing.ts b/components/label/api/user/typing.ts index 6e4c62b..1a03c61 100644 --- a/components/label/api/user/typing.ts +++ b/components/label/api/user/typing.ts @@ -41,6 +41,11 @@ export namespace User { work_status?: number } + export interface ModifyPasswordProps { + new_password: string + old_password: string + } + export interface UpdateGroupProps { user_id: number group_uuid: string diff --git a/components/layout/AppLayout.tsx b/components/layout/AppLayout.tsx index 0f8ab9f..8a9b0ac 100644 --- a/components/layout/AppLayout.tsx +++ b/components/layout/AppLayout.tsx @@ -1,13 +1,17 @@ "use client" import { + Button, AppShell, Box, Burger, Divider, Flex, + Group, + Modal, Menu, NavLink, + PasswordInput, ScrollArea, Space, Stack, @@ -20,10 +24,13 @@ import { 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, @@ -36,7 +43,14 @@ 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" @@ -149,6 +163,10 @@ export default function AppLayout({ children: React.ReactNode }) { const [mobileOpened, { toggle: toggleMobile }] = useDisclosure() + const [ + passwordModalOpened, + { open: openPasswordModal, close: closePasswordModal }, + ] = useDisclosure(false) const { updateIsOpen } = useAppLayoutStore.getState() const { isOpen } = useAppLayoutStore() @@ -178,6 +196,67 @@ export default function AppLayout({ 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) => ( 操作 + } + onClick={handleOpenPasswordModal}> + 修改密码 + } onClick={handleLogout}> @@ -381,6 +465,54 @@ export default function AppLayout({ + +
+ + + 请输入当前密码,并再次确认新密码后提交修改。 + + + + + + + + + +
+