Files
xiaoxia-saas/apps/web/src/components/layout/Sidebar.tsx
T
CI Bot 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
feat(ui): 实现侧边栏导航组件 Sidebar
- 新增 Sidebar.tsx:分组导航菜单(创作工具/资源管理/系统)
- 每个菜单项包含图标 + 文字,基于路由自动高亮当前页
- 支持折叠态:通过 SidebarContext 读取 collapsed 状态,折叠时仅显示图标
- 新增 Sidebar.css:全部使用 CSS 变量,无硬编码样式
- 集成到 MainLayout:替换占位符为实际 Sidebar 组件
- 清理 MainLayout.css 中已废弃的占位符样式
- 响应式:移动端菜单项增大点击区域(44px min-height)
2026-06-30 21:13:31 +08:00

172 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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;