chore: 初始化仓库
中华文明全图鉴——文物全图系统(PC Web 地图 + NestJS API + 管理后台)。 含三大 IP(文物南迁北归 / 国宝海外回归 / 博物馆手艺人)、AI 文物对话、 文物地图与详情、以及 demo-video-kit 演示视频生成工具。
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { useState } from "react";
|
||||
import { Layout, Menu, Avatar, Dropdown, theme, Typography } from "antd";
|
||||
import {
|
||||
DashboardOutlined,
|
||||
PicLeftOutlined,
|
||||
BankOutlined,
|
||||
TagsOutlined,
|
||||
LogoutOutlined,
|
||||
UserOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Outlet, useNavigate, useLocation } from "react-router-dom";
|
||||
import { useAuth } from "../store/auth";
|
||||
|
||||
const { Header, Sider, Content } = Layout;
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ key: "/dashboard", icon: <DashboardOutlined />, label: "概览" },
|
||||
{ key: "/artifacts", icon: <PicLeftOutlined />, label: "文物管理" },
|
||||
{ key: "/institutions", icon: <BankOutlined />, label: "机构管理" },
|
||||
{ key: "/tags", icon: <TagsOutlined />, label: "标签管理" },
|
||||
];
|
||||
|
||||
export default function AdminLayout() {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { user, logout } = useAuth();
|
||||
const { token } = theme.useToken();
|
||||
|
||||
const userMenuItems = [
|
||||
{
|
||||
key: "logout",
|
||||
icon: <LogoutOutlined />,
|
||||
label: "退出登录",
|
||||
onClick: () => {
|
||||
logout();
|
||||
navigate("/login");
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Layout style={{ minHeight: "100vh" }}>
|
||||
<Sider
|
||||
collapsible
|
||||
collapsed={collapsed}
|
||||
onCollapse={setCollapsed}
|
||||
trigger={null}
|
||||
style={{
|
||||
background: token.colorBgContainer,
|
||||
borderRight: `1px solid ${token.colorBorderSecondary}`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: 56,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: collapsed ? "center" : "flex-start",
|
||||
padding: collapsed ? 0 : "0 20px",
|
||||
borderBottom: `1px solid ${token.colorBorderSecondary}`,
|
||||
}}
|
||||
>
|
||||
{collapsed ? (
|
||||
<span style={{ fontSize: 18 }}>🏛️</span>
|
||||
) : (
|
||||
<Typography.Text strong style={{ color: token.colorPrimary, fontSize: 13, letterSpacing: 1 }}>
|
||||
文明全图鉴
|
||||
</Typography.Text>
|
||||
)}
|
||||
</div>
|
||||
<Menu
|
||||
mode="inline"
|
||||
selectedKeys={[location.pathname]}
|
||||
items={NAV_ITEMS}
|
||||
onClick={({ key }) => navigate(key)}
|
||||
style={{ border: "none", marginTop: 8 }}
|
||||
/>
|
||||
</Sider>
|
||||
|
||||
<Layout>
|
||||
<Header
|
||||
style={{
|
||||
background: token.colorBgContainer,
|
||||
borderBottom: `1px solid ${token.colorBorderSecondary}`,
|
||||
height: 56,
|
||||
padding: "0 20px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 16,
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
style={{
|
||||
background: "none",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
color: token.colorText,
|
||||
fontSize: 16,
|
||||
}}
|
||||
>
|
||||
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
</button>
|
||||
<div style={{ flex: 1 }} />
|
||||
<Dropdown menu={{ items: userMenuItems }} placement="bottomRight">
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer" }}>
|
||||
<Avatar size={28} icon={<UserOutlined />} style={{ background: token.colorPrimary }} />
|
||||
<Typography.Text style={{ fontSize: 13 }}>
|
||||
{user?.nickname ?? user?.username}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</Header>
|
||||
|
||||
<Content style={{ padding: 24, background: token.colorBgLayout }}>
|
||||
<Outlet />
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user