feat(web): 开发账户修改、密码修改功能

This commit is contained in:
R524809
2026-09-03 16:56:53 +08:00
parent 46d98ab11e
commit 06a3a4da75
9 changed files with 435 additions and 25 deletions
+18 -10
View File
@@ -102,22 +102,30 @@
gap: 12px;
}
.header-user {
/* 头像 + 用户名触发区:hover 有浅色背景反馈,暗示可下拉 */
.header-user-trigger {
display: flex;
flex-direction: column;
align-items: flex-end;
line-height: 1.25;
align-items: center;
gap: 8px;
padding: 4px 8px;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s;
max-width: 240px;
}
.header-user-workspace {
.header-user-trigger:hover {
background: #f3f4f6;
}
/* 用户名超长省略 */
.header-user-name {
font-size: 13px;
font-weight: 600;
color: #1f2937;
}
.header-user-email {
font-size: 12px;
color: #6b7280;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.collapse-trigger,
+49 -11
View File
@@ -1,7 +1,14 @@
import { useEffect, useMemo, useState } from 'react';
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
import { Breadcrumb, Button, Drawer, Layout } from 'antd';
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
import { Avatar, Breadcrumb, Drawer, Dropdown, Layout } from 'antd';
import type { MenuProps } from 'antd';
import {
LockOutlined,
LogoutOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
UserOutlined,
} from '@ant-design/icons';
import SidebarMenu from './SidebarMenu';
import { getBreadcrumbItems } from './menuConfig';
import { useSession } from '@/services/session';
@@ -31,6 +38,9 @@ const MainLayout = () => {
[location.pathname]
);
const userDisplayName = session ? (session.user.display_name ?? session.user.email) : '';
const userInitial = userDisplayName.charAt(0).toUpperCase();
const handleLogout = async () => {
setLoggingOut(true);
try {
@@ -41,6 +51,28 @@ const MainLayout = () => {
}
};
const handleUserMenuClick: MenuProps['onClick'] = ({ key }) => {
if (key === 'profile') {
navigate('/settings/profile');
} else if (key === 'password') {
navigate('/settings/password');
} else if (key === 'logout') {
void handleLogout();
}
};
const userMenuItems: MenuProps['items'] = [
{ key: 'profile', icon: <UserOutlined />, label: '修改资料' },
{ key: 'password', icon: <LockOutlined />, label: '修改密码' },
{ type: 'divider' },
{
key: 'logout',
icon: <LogoutOutlined />,
label: loggingOut ? '退出中…' : '退出登录',
disabled: loggingOut,
},
];
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < 768);
@@ -124,15 +156,21 @@ const MainLayout = () => {
</div>
{session && (
<div className="header-right">
<div className="header-user">
<span className="header-user-workspace">
{session.workspace.name}
</span>
<span className="header-user-email">{session.user.email}</span>
</div>
<Button size="small" loading={loggingOut} onClick={handleLogout}>
退出登录
</Button>
<Dropdown
menu={{ items: userMenuItems, onClick: handleUserMenuClick }}
trigger={['hover']}
placement="bottomRight"
>
<div className="header-user-trigger" title={userDisplayName}>
<Avatar
size={28}
style={{ backgroundColor: 'var(--sider-bg)' }}
>
{userInitial}
</Avatar>
<span className="header-user-name">{userDisplayName}</span>
</div>
</Dropdown>
</div>
)}
</Header>
+16 -1
View File
@@ -4,8 +4,11 @@ import {
EditOutlined,
GlobalOutlined,
InboxOutlined,
LockOutlined,
PictureOutlined,
SettingOutlined,
ShopOutlined,
UserOutlined,
} from '@ant-design/icons';
import type { ReactNode } from 'react';
@@ -59,7 +62,7 @@ export interface BreadcrumbCrumb {
label: string;
}
/** 页头面包屑:一级菜单页显示单级;商品 AI 编辑页不在菜单中,显示「商品 > 商品编辑」两级 */
/** 页头面包屑:一级菜单页显示单级;不在菜单中的页面显示两级 */
export const getBreadcrumbItems = (path: string): BreadcrumbCrumb[] => {
if (path.startsWith('/products/')) {
return [
@@ -67,6 +70,18 @@ export const getBreadcrumbItems = (path: string): BreadcrumbCrumb[] => {
{ icon: <EditOutlined />, label: '商品编辑' },
];
}
if (path === '/settings/profile') {
return [
{ icon: <SettingOutlined />, label: '设置' },
{ icon: <UserOutlined />, label: '修改资料' },
];
}
if (path === '/settings/password') {
return [
{ icon: <SettingOutlined />, label: '设置' },
{ icon: <LockOutlined />, label: '修改密码' },
];
}
const config = routeMenuConfig.find((item) => path.startsWith(item.path)) ?? routeMenuConfig[0];
return [{ icon: config.icon, label: config.label }];
};