381f38bd52
Deploy / Build Production Runtime Images (push) Has been cancelled
Deploy / Deploy Production (push) Has been cancelled
Deploy / Production Browser E2E (push) Has been cancelled
Deploy / Deploy Staging (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Failing after 207h40m49s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 207h40m59s
- 新增 Sidebar.tsx:分组导航菜单(创作工具/资源管理/系统) - 每个菜单项包含图标 + 文字,基于路由自动高亮当前页 - 支持折叠态:通过 SidebarContext 读取 collapsed 状态,折叠时仅显示图标 - 新增 Sidebar.css:全部使用 CSS 变量,无硬编码样式 - 集成到 MainLayout:替换占位符为实际 Sidebar 组件 - 清理 MainLayout.css 中已废弃的占位符样式 - 响应式:移动端菜单项增大点击区域(44px min-height)
172 lines
4.1 KiB
TypeScript
172 lines
4.1 KiB
TypeScript
/**
|
||
* Sidebar - 侧边栏导航组件(Task 1.3)
|
||
*
|
||
* 功能:
|
||
* - 分组导航菜单(创作工具 / 资源管理 / 系统)
|
||
* - 每个菜单项:图标 + 文字
|
||
* - 当前页面对应菜单项高亮(基于路由匹配)
|
||
* - 点击跳转路由
|
||
* - 折叠态:仅显示图标,隐藏文字(通过 SidebarContext 读取 collapsed 状态)
|
||
* - 响应式:移动端自动折叠
|
||
*/
|
||
import React, { useContext } from "react";
|
||
import { useLocation, useNavigate } from "react-router-dom";
|
||
import {
|
||
DashboardOutlined,
|
||
VideoCameraOutlined,
|
||
FileOutlined,
|
||
AudioOutlined,
|
||
FileTextOutlined,
|
||
TrophyOutlined,
|
||
AppstoreOutlined,
|
||
HistoryOutlined,
|
||
ControlOutlined,
|
||
CrownOutlined,
|
||
} from "@ant-design/icons";
|
||
import { SidebarContext } from "./MainLayout";
|
||
import "./Sidebar.css";
|
||
|
||
/** 导航项定义 */
|
||
interface SidebarMenuItem {
|
||
key: string;
|
||
label: string;
|
||
path: string;
|
||
icon: React.ReactNode;
|
||
}
|
||
|
||
/** 导航分组定义 */
|
||
interface SidebarMenuGroup {
|
||
title: string;
|
||
items: SidebarMenuItem[];
|
||
}
|
||
|
||
/** 侧边栏导航分组 */
|
||
const MENU_GROUPS: SidebarMenuGroup[] = [
|
||
{
|
||
title: "创作工具",
|
||
items: [
|
||
{
|
||
key: "dashboard",
|
||
label: "首页",
|
||
path: "/dashboard",
|
||
icon: <DashboardOutlined />,
|
||
},
|
||
{
|
||
key: "generate",
|
||
label: "一键生成",
|
||
path: "/generate",
|
||
icon: <VideoCameraOutlined />,
|
||
},
|
||
],
|
||
},
|
||
{
|
||
title: "资源管理",
|
||
items: [
|
||
{
|
||
key: "assets",
|
||
label: "素材库",
|
||
path: "/assets",
|
||
icon: <FileOutlined />,
|
||
},
|
||
{
|
||
key: "voices",
|
||
label: "配音库",
|
||
path: "/voices",
|
||
icon: <AudioOutlined />,
|
||
},
|
||
{
|
||
key: "titles",
|
||
label: "标题库",
|
||
path: "/titles",
|
||
icon: <FileTextOutlined />,
|
||
},
|
||
{
|
||
key: "products",
|
||
label: "成片库",
|
||
path: "/products",
|
||
icon: <TrophyOutlined />,
|
||
},
|
||
{
|
||
key: "templates",
|
||
label: "模板库",
|
||
path: "/templates",
|
||
icon: <AppstoreOutlined />,
|
||
},
|
||
],
|
||
},
|
||
{
|
||
title: "系统",
|
||
items: [
|
||
{
|
||
key: "history",
|
||
label: "任务历史",
|
||
path: "/history",
|
||
icon: <HistoryOutlined />,
|
||
},
|
||
{
|
||
key: "admin",
|
||
label: "控制台",
|
||
path: "/admin",
|
||
icon: <ControlOutlined />,
|
||
},
|
||
{
|
||
key: "subscription",
|
||
label: "订阅管理",
|
||
path: "/subscription",
|
||
icon: <CrownOutlined />,
|
||
},
|
||
],
|
||
},
|
||
];
|
||
|
||
/** 判断菜单项是否激活 */
|
||
const isMenuItemActive = (pathname: string, path: string): boolean => {
|
||
if (path === "/dashboard") {
|
||
return pathname === "/" || pathname === "/dashboard";
|
||
}
|
||
return pathname.startsWith(path);
|
||
};
|
||
|
||
const Sidebar: React.FC = () => {
|
||
const { collapsed } = useContext(SidebarContext);
|
||
const location = useLocation();
|
||
const navigate = useNavigate();
|
||
|
||
const handleNavigate = (path: string) => {
|
||
navigate(path);
|
||
};
|
||
|
||
return (
|
||
<div className={`xx-sidebar-nav${collapsed ? " xx-sidebar-nav--collapsed" : ""}`}>
|
||
{MENU_GROUPS.map((group) => (
|
||
<div className="xx-sidebar-group" key={group.title}>
|
||
{!collapsed && (
|
||
<div className="xx-sidebar-group-title">{group.title}</div>
|
||
)}
|
||
<ul className="xx-sidebar-menu" role="menu">
|
||
{group.items.map((item) => {
|
||
const active = isMenuItemActive(location.pathname, item.path);
|
||
return (
|
||
<li
|
||
key={item.key}
|
||
className={`xx-sidebar-menu-item${active ? " xx-active" : ""}`}
|
||
role="menuitem"
|
||
title={collapsed ? item.label : undefined}
|
||
onClick={() => handleNavigate(item.path)}
|
||
>
|
||
<span className="xx-sidebar-menu-icon">{item.icon}</span>
|
||
{!collapsed && (
|
||
<span className="xx-sidebar-menu-label">{item.label}</span>
|
||
)}
|
||
</li>
|
||
);
|
||
})}
|
||
</ul>
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default Sidebar;
|