chore: 初始化仓库

中华文明全图鉴——文物全图系统(PC Web 地图 + NestJS API + 管理后台)。
含三大 IP(文物南迁北归 / 国宝海外回归 / 博物馆手艺人)、AI 文物对话、
文物地图与详情、以及 demo-video-kit 演示视频生成工具。
This commit is contained in:
selfrelease
2026-06-13 20:55:44 +08:00
commit 2d847e154f
161 changed files with 22629 additions and 0 deletions
+124
View File
@@ -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>
);
}