| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- import React, { useState } from 'react';
- import { Outlet, useNavigate, useLocation } from 'react-router';
- import { useAuth } from '@/client/mobile/hooks/AuthProvider';
- import { UserType } from '@/server/modules/users/user.enum';
- import { getGlobalConfig } from '@/client/utils/utils';
- import { cn } from '@/client/lib/utils';
- import {
- MessageCircle,
- Users,
- FileText,
- Heart,
- Video,
- User,
- X,
- Menu
- } from 'lucide-react';
- /**
- * 主布局组件 - shadcn风格
- * 包含侧边栏、顶部导航和内容区域
- */
- export const MainLayout = () => {
- const [sidebarOpen, setSidebarOpen] = useState(false);
- const { user } = useAuth();
- const navigate = useNavigate();
- const location = useLocation();
- const handleClassroomClick = (classId = '') => {
- if (user?.userType === UserType.TEACHER) {
- navigate(`/mobile/classroom/${classId}/${UserType.TEACHER}`);
- } else {
- navigate(`/mobile/classroom/${classId}/${UserType.STUDENT}`);
- }
- setSidebarOpen(false);
- };
- const menuItems = [
- {
- id: 'public-chatroom',
- label: '公开解盘',
- icon: <MessageCircle className="w-5 h-5" />,
- onClick: () => handleClassroomClick(getGlobalConfig('PUBLIC_CHATROOM_ID'))
- },
- {
- id: 'private-chatroom',
- label: '学员解盘',
- icon: <Users className="w-5 h-5" />,
- onClick: () => handleClassroomClick(getGlobalConfig('PRIVATE_CHATROOM_ID'))
- },
- // {
- // id: 'exam',
- // label: '考试模式',
- // icon: <FileText className="w-5 h-5" />,
- // onClick: () => navigate('/mobile/exam')
- // },
- {
- id: 'training',
- label: '训练模式',
- icon: <Heart className="w-5 h-5" />,
- onClick: () => navigate('/mobile/xunlian')
- },
- {
- id: 'video-replay',
- label: '视频回放',
- icon: <Video className="w-5 h-5" />,
- onClick: () => navigate('/mobile/video-replay')
- }
- ];
- return (
- <div className="min-h-screen bg-background flex">
- {/* 侧边栏 - 桌面端收起状态 */}
- <aside className={cn(
- "fixed lg:static inset-y-0 left-0 z-50 w-16 lg:w-16 bg-card border-r border-border transition-all duration-300",
- sidebarOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0"
- )}>
- <div className="flex flex-col h-full">
- {/* 侧边栏头部 - 包含用户头像和用户名 */}
- <div className="flex flex-col items-center justify-center h-20 border-b border-border py-2">
- {user && (
- <>
- <button
- onClick={() => navigate('/mobile/member')}
- className="w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center mb-1"
- >
- <User className="w-4 h-4" />
- </button>
- <span className="text-xs text-muted-foreground truncate max-w-[60px]">
- {user.username}
- </span>
- </>
- )}
- <button
- onClick={() => setSidebarOpen(false)}
- className="lg:hidden p-1 rounded-md hover:bg-muted mt-2"
- >
- <X className="w-4 h-4" />
- </button>
- </div>
- {/* 菜单项 */}
- <nav className="flex-1 px-2 py-4 space-y-2">
- {menuItems.map((item) => (
- <button
- key={item.id}
- onClick={item.onClick}
- className={cn(
- "w-full flex flex-col items-center p-2 rounded-md text-sm font-medium transition-colors",
- "hover:bg-muted hover:text-foreground",
- "text-muted-foreground hover:text-foreground"
- )}
- >
- <div className="mb-1">
- {item.icon}
- </div>
- <span className="text-xs leading-tight whitespace-nowrap text-center">
- {item.label}
- </span>
- </button>
- ))}
- </nav>
- </div>
- </aside>
- {/* 遮罩层 - 移动端 */}
- {sidebarOpen && (
- <div
- className="fixed inset-0 bg-background/80 backdrop-blur-sm z-40 lg:hidden"
- onClick={() => setSidebarOpen(false)}
- />
- )}
- {/* 主内容区域 */}
- <div className="flex-1 flex flex-col min-w-0">
- {/* 顶部导航 */}
- <header className="bg-card border-b border-border sticky top-0 z-40">
- <div className="flex items-center justify-between h-16 px-4">
- <div className="flex items-center">
- <button
- onClick={() => setSidebarOpen(true)}
- className="lg:hidden p-2 rounded-md hover:bg-muted mr-2"
- >
- <Menu className="w-5 h-5" />
- </button>
- <h1 className="text-xl font-semibold text-foreground">股票训练系统</h1>
- </div>
- <div className="flex items-center space-x-4">
- {user ? null : (
- <div className="flex space-x-2">
- <button
- onClick={() => navigate('/mobile/login')}
- className="px-3 py-1.5 rounded-md text-sm bg-primary text-primary-foreground hover:bg-primary/90"
- >
- 登录
- </button>
- <button
- onClick={() => navigate('/mobile/register')}
- className="px-3 py-1.5 rounded-md text-sm bg-secondary text-secondary-foreground hover:bg-secondary/80"
- >
- 注册
- </button>
- </div>
- )}
- </div>
- </div>
- </header>
- {/* 内容区域 */}
- <main className="flex-1 overflow-auto p-6">
- <Outlet />
- </main>
- </div>
- </div>
- );
- };
|