为登录后的页面新增切换项目弹窗

This commit is contained in:
JIANG
2026-02-10 17:11:04 +08:00
parent 1e8af75b88
commit 25bde02b43
2 changed files with 170 additions and 23 deletions

View File

@@ -3,15 +3,25 @@
import { ColorModeContext } from "@contexts/color-mode"; import { ColorModeContext } from "@contexts/color-mode";
import DarkModeOutlined from "@mui/icons-material/DarkModeOutlined"; import DarkModeOutlined from "@mui/icons-material/DarkModeOutlined";
import LightModeOutlined from "@mui/icons-material/LightModeOutlined"; import LightModeOutlined from "@mui/icons-material/LightModeOutlined";
import Logout from "@mui/icons-material/Logout";
import SwapHoriz from "@mui/icons-material/SwapHoriz";
import AppBar from "@mui/material/AppBar"; import AppBar from "@mui/material/AppBar";
import Avatar from "@mui/material/Avatar"; import Avatar from "@mui/material/Avatar";
import ButtonBase from "@mui/material/ButtonBase";
import Divider from "@mui/material/Divider";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
import Toolbar from "@mui/material/Toolbar"; import Toolbar from "@mui/material/Toolbar";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { useGetIdentity } from "@refinedev/core"; import { useGetIdentity, useLogout } from "@refinedev/core";
import { HamburgerMenu, RefineThemedLayoutHeaderProps } from "@refinedev/mui"; import { HamburgerMenu, RefineThemedLayoutHeaderProps } from "@refinedev/mui";
import React, { useContext } from "react"; import React, { useContext, useState } from "react";
import { ProjectSelector } from "@components/project/ProjectSelector";
import { setMapWorkspace, setNetworkName } from "@config/config";
type IUser = { type IUser = {
id: number; id: number;
@@ -23,9 +33,35 @@ export const Header: React.FC<RefineThemedLayoutHeaderProps> = ({
sticky = true, sticky = true,
}) => { }) => {
const { mode, setMode } = useContext(ColorModeContext); const { mode, setMode } = useContext(ColorModeContext);
const { mutate: logout } = useLogout();
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [showProjectSelector, setShowProjectSelector] = useState(false);
const open = Boolean(anchorEl);
const { data: user } = useGetIdentity<IUser>(); const { data: user } = useGetIdentity<IUser>();
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleMenuClose = () => {
setAnchorEl(null);
};
const handleSwitchProjectClick = () => {
handleMenuClose();
setShowProjectSelector(true);
};
const handleProjectSelect = (workspace: string, networkName: string) => {
setMapWorkspace(workspace);
setNetworkName(networkName);
localStorage.setItem("NEXT_PUBLIC_MAP_WORKSPACE", workspace);
localStorage.setItem("NEXT_PUBLIC_NETWORK_NAME", networkName);
setShowProjectSelector(false);
window.location.reload();
};
return ( return (
<AppBar position={sticky ? "sticky" : "relative"}> <AppBar position={sticky ? "sticky" : "relative"}>
<Toolbar> <Toolbar>
@@ -52,27 +88,118 @@ export const Header: React.FC<RefineThemedLayoutHeaderProps> = ({
</IconButton> </IconButton>
{(user?.avatar || user?.name) && ( {(user?.avatar || user?.name) && (
<Stack <>
direction="row" <ButtonBase
gap="16px" onClick={handleMenuOpen}
alignItems="center" sx={{
justifyContent="center" borderRadius: "30px",
> padding: "6px 12px",
{user?.name && ( marginLeft: "8px",
<Typography transition: "all 0.3s ease",
sx={{ border: "1px solid transparent",
display: { "&:hover": {
xs: "none", backgroundColor:
sm: "inline-block", mode === "dark"
}, ? "rgba(255, 255, 255, 0.05)"
}} : "rgba(0, 0, 0, 0.04)",
variant="subtitle2" transform: "translateY(-1px)",
border: `1px solid ${mode === "dark"
? "rgba(255, 255, 255, 0.2)"
: "rgba(0, 0, 0, 0.1)"
}`,
boxShadow:
mode === "dark"
? "0 4px 12px rgba(0,0,0,0.3)"
: "0 4px 12px rgba(0,0,0,0.08)",
},
"&:active": {
transform: "translateY(0px)",
boxShadow: "none",
},
}}
>
<Stack
direction="row"
gap="12px"
alignItems="center"
justifyContent="center"
> >
{user?.name} {user?.name && (
</Typography> <Typography
)} sx={{
<Avatar src={user?.avatar} alt={user?.name} /> display: {
</Stack> xs: "none",
sm: "inline-block",
},
fontWeight: 500,
}}
variant="subtitle2"
>
{user?.name}
</Typography>
)}
<Avatar
src={user?.avatar}
alt={user?.name}
sx={{
width: 32,
height: 32,
border: `2px solid ${mode === "dark"
? "rgba(255,255,255,0.2)"
: "rgba(0,0,0,0.1)"
}`,
transition: "transform 0.3s ease",
".MuiButtonBase-root:hover &": {
transform: "rotate(5deg) scale(1.05)",
borderColor: "primary.main",
},
}}
/>
</Stack>
</ButtonBase>
<Menu
anchorEl={anchorEl}
open={open}
onClose={handleMenuClose}
transformOrigin={{ horizontal: "right", vertical: "top" }}
anchorOrigin={{ horizontal: "right", vertical: "bottom" }}
PaperProps={{
sx: {
borderRadius: 2,
minWidth: 180,
marginTop: "8px",
background:
mode === "dark"
? "rgba(30, 30, 30, 0.95)"
: "rgba(255, 255, 255, 0.95)",
backdropFilter: "blur(10px)",
boxShadow:
mode === "dark"
? "0px 4px 20px rgba(0, 0, 0, 0.5)"
: "0px 4px 20px rgba(0, 0, 0, 0.1)",
},
}}
>
<MenuItem onClick={handleSwitchProjectClick}>
<ListItemIcon>
<SwapHoriz fontSize="small" />
</ListItemIcon>
<ListItemText></ListItemText>
</MenuItem>
<Divider />
<MenuItem onClick={() => logout()}>
<ListItemIcon>
<Logout fontSize="small" />
</ListItemIcon>
<ListItemText></ListItemText>
</MenuItem>
</Menu>
<ProjectSelector
open={showProjectSelector}
onSelect={handleProjectSelect}
onClose={() => setShowProjectSelector(false)}
/>
</>
)} )}
</Stack> </Stack>
</Stack> </Stack>

View File

@@ -12,12 +12,15 @@ import {
Box, Box,
Typography, Typography,
Fade, Fade,
IconButton,
} from "@mui/material"; } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { useState } from "react"; import { useState } from "react";
interface ProjectSelectorProps { interface ProjectSelectorProps {
open: boolean; open: boolean;
onSelect: (workspace: string, networkName: string) => void; onSelect: (workspace: string, networkName: string) => void;
onClose?: () => void;
} }
const PROJECTS = [ const PROJECTS = [
@@ -29,6 +32,7 @@ const PROJECTS = [
export const ProjectSelector: React.FC<ProjectSelectorProps> = ({ export const ProjectSelector: React.FC<ProjectSelectorProps> = ({
open, open,
onSelect, onSelect,
onClose,
}) => { }) => {
const [workspace, setWorkspace] = useState(PROJECTS[0].workspace); const [workspace, setWorkspace] = useState(PROJECTS[0].workspace);
const [networkName, setNetworkName] = useState(PROJECTS[0].networkName); const [networkName, setNetworkName] = useState(PROJECTS[0].networkName);
@@ -41,7 +45,8 @@ export const ProjectSelector: React.FC<ProjectSelectorProps> = ({
return ( return (
<Dialog <Dialog
open={open} open={open}
disableEscapeKeyDown disableEscapeKeyDown={!onClose}
onClose={onClose ? onClose : undefined}
slotProps={{ slotProps={{
paper: { paper: {
sx: { sx: {
@@ -50,12 +55,27 @@ export const ProjectSelector: React.FC<ProjectSelectorProps> = ({
minWidth: 400, minWidth: 400,
background: "rgba(255, 255, 255, 0.95)", background: "rgba(255, 255, 255, 0.95)",
backdropFilter: "blur(10px)", backdropFilter: "blur(10px)",
position: "relative",
} }
} }
}} }}
slots={{ transition: Fade }} slots={{ transition: Fade }}
transitionDuration={500} transitionDuration={500}
> >
{onClose && (
<IconButton
aria-label="close"
onClick={onClose}
sx={{
position: "absolute",
right: 8,
top: 8,
color: (theme) => theme.palette.grey[500],
}}
>
<CloseIcon />
</IconButton>
)}
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center", mb: 2 }}> <Box sx={{ display: "flex", flexDirection: "column", alignItems: "center", mb: 2 }}>
<Box sx={{ transform: "scale(1.5)", mb: 2, mt: 1 }}> <Box sx={{ transform: "scale(1.5)", mb: 2, mt: 1 }}>
<Title /> <Title />