dvc890 commited on
Commit
f9f39d1
·
verified ·
1 Parent(s): a333f61

Upload 65 files

Browse files
Files changed (2) hide show
  1. Sidebar.tsx +174 -0
  2. types.ts +1 -1
Sidebar.tsx ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useState, useEffect } from 'react';
3
+ import { LayoutDashboard, Users, BookOpen, GraduationCap, Settings, LogOut, FileText, School, UserCog, Palette, X, Building, Gamepad2, CalendarCheck, UserCircle, MessageSquare, Bot, ArrowUp, ArrowDown, Save, UserCheck } from 'lucide-react';
4
+ import { UserRole } from '../types';
5
+ import { api } from '../services/api';
6
+
7
+ interface SidebarProps {
8
+ currentView: string;
9
+ onChangeView: (view: string) => void;
10
+ userRole: UserRole;
11
+ onLogout: () => void;
12
+ isOpen: boolean;
13
+ onClose: () => void;
14
+ }
15
+
16
+ interface MenuItem {
17
+ id: string;
18
+ label: string;
19
+ icon: React.ElementType;
20
+ roles: UserRole[];
21
+ }
22
+
23
+ export const Sidebar: React.FC<SidebarProps> = ({ currentView, onChangeView, userRole, onLogout, isOpen, onClose }) => {
24
+ const currentUser = api.auth.getCurrentUser();
25
+ const canSeeAI = userRole === UserRole.ADMIN || (userRole === UserRole.TEACHER && currentUser?.aiAccess);
26
+ const isHomeroom = userRole === UserRole.TEACHER && !!currentUser?.homeroomClass;
27
+
28
+ // Default Items
29
+ const defaultItems: MenuItem[] = [
30
+ { id: 'dashboard', label: '工作台', icon: LayoutDashboard, roles: [UserRole.ADMIN, UserRole.PRINCIPAL, UserRole.TEACHER, UserRole.STUDENT] },
31
+ { id: 'my-class', label: '我的班级', icon: UserCheck, roles: isHomeroom ? [UserRole.TEACHER] : [] },
32
+ { id: 'ai-assistant', label: 'AI 智能助教', icon: Bot, roles: canSeeAI ? [UserRole.ADMIN, UserRole.TEACHER] : [] },
33
+ { id: 'attendance', label: '考勤管理', icon: CalendarCheck, roles: [UserRole.TEACHER, UserRole.PRINCIPAL] },
34
+ { id: 'games', label: '互动教学', icon: Gamepad2, roles: [UserRole.TEACHER, UserRole.STUDENT] },
35
+ { id: 'wishes', label: '心愿与反馈', icon: MessageSquare, roles: [UserRole.ADMIN, UserRole.PRINCIPAL, UserRole.TEACHER, UserRole.STUDENT] },
36
+ { id: 'students', label: '学生管理', icon: Users, roles: [UserRole.ADMIN, UserRole.PRINCIPAL, UserRole.TEACHER] },
37
+ { id: 'classes', label: '班级管理', icon: School, roles: [UserRole.ADMIN, UserRole.PRINCIPAL] },
38
+ { id: 'schools', label: '学校管理', icon: Building, roles: [UserRole.ADMIN] },
39
+ { id: 'courses', label: '课程管理', icon: BookOpen, roles: [UserRole.ADMIN, UserRole.PRINCIPAL, UserRole.TEACHER] },
40
+ { id: 'grades', label: '成绩管理', icon: GraduationCap, roles: [UserRole.ADMIN, UserRole.PRINCIPAL, UserRole.TEACHER] },
41
+ { id: 'reports', label: '报表统计', icon: FileText, roles: [UserRole.ADMIN, UserRole.PRINCIPAL, UserRole.TEACHER, UserRole.STUDENT] },
42
+ { id: 'subjects', label: '学科设置', icon: Palette, roles: [UserRole.ADMIN, UserRole.PRINCIPAL, UserRole.TEACHER] },
43
+ { id: 'users', label: '用户管理', icon: UserCog, roles: [UserRole.ADMIN, UserRole.PRINCIPAL, UserRole.TEACHER] },
44
+ { id: 'profile', label: '个人中心', icon: UserCircle, roles: [UserRole.ADMIN, UserRole.PRINCIPAL, UserRole.TEACHER, UserRole.STUDENT] },
45
+ { id: 'settings', label: '系统设置', icon: Settings, roles: [UserRole.ADMIN, UserRole.PRINCIPAL] },
46
+ ];
47
+
48
+ const [menuItems, setMenuItems] = useState<MenuItem[]>(defaultItems);
49
+ const [isEditing, setIsEditing] = useState(false);
50
+
51
+ useEffect(() => {
52
+ // Load saved order
53
+ if (currentUser?.menuOrder && currentUser.menuOrder.length > 0) {
54
+ const ordered: MenuItem[] = [];
55
+ const map = new Map(defaultItems.map(i => [i.id, i]));
56
+ // Add saved items in order
57
+ currentUser.menuOrder.forEach(id => {
58
+ if (map.has(id)) {
59
+ ordered.push(map.get(id)!);
60
+ map.delete(id);
61
+ }
62
+ });
63
+ // Append any new/remaining items
64
+ map.forEach(item => ordered.push(item));
65
+ setMenuItems(ordered);
66
+ }
67
+ }, []);
68
+
69
+ const handleMove = (index: number, direction: -1 | 1) => {
70
+ const newItems = [...menuItems];
71
+ const targetIndex = index + direction;
72
+ if (targetIndex < 0 || targetIndex >= newItems.length) return;
73
+ [newItems[index], newItems[targetIndex]] = [newItems[targetIndex], newItems[index]];
74
+ setMenuItems(newItems);
75
+ };
76
+
77
+ const saveOrder = async () => {
78
+ setIsEditing(false);
79
+ const orderIds = menuItems.map(i => i.id);
80
+ if (currentUser && currentUser._id) {
81
+ try {
82
+ await api.users.saveMenuOrder(currentUser._id, orderIds);
83
+ // Update local user object
84
+ const updatedUser = { ...currentUser, menuOrder: orderIds };
85
+ localStorage.setItem('user', JSON.stringify(updatedUser));
86
+ } catch(e) { console.error("Failed to save menu order"); }
87
+ }
88
+ };
89
+
90
+ const sidebarClasses = `
91
+ fixed inset-y-0 left-0 z-50 w-64 bg-slate-900 text-white transition-transform duration-300 ease-in-out transform
92
+ ${isOpen ? 'translate-x-0' : '-translate-x-full'}
93
+ lg:relative lg:translate-x-0 lg:flex lg:flex-col lg:h-screen shadow-2xl lg:shadow-none
94
+ `;
95
+
96
+ return (
97
+ <>
98
+ {isOpen && <div className="fixed inset-0 bg-black/50 z-40 lg:hidden backdrop-blur-sm" onClick={onClose}></div>}
99
+
100
+ <div className={sidebarClasses}>
101
+ <div className="flex items-center justify-between h-20 border-b border-slate-700 px-6">
102
+ <div className="flex items-center space-x-2">
103
+ <GraduationCap className="h-8 w-8 text-blue-400" />
104
+ <span className="text-xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-teal-300">
105
+ 智慧校园
106
+ </span>
107
+ </div>
108
+ <button onClick={onClose} className="lg:hidden text-slate-400 hover:text-white"><X size={24} /></button>
109
+ </div>
110
+
111
+ <div className="flex-1 overflow-y-auto py-4 custom-scrollbar">
112
+ <div className="px-4 mb-2 flex justify-between items-center">
113
+ <span className="text-xs text-slate-500 font-bold uppercase">主菜单</span>
114
+ <button
115
+ onClick={() => isEditing ? saveOrder() : setIsEditing(true)}
116
+ className={`text-xs px-2 py-1 rounded hover:bg-slate-800 ${isEditing ? 'text-green-400' : 'text-slate-600'}`}
117
+ >
118
+ {isEditing ? '完成' : '调整'}
119
+ </button>
120
+ </div>
121
+ <nav className="space-y-1 px-2">
122
+ {menuItems.map((item, idx) => {
123
+ if (item.roles.length > 0 && !item.roles.includes(userRole)) return null;
124
+ if (item.id === 'ai-assistant' && !canSeeAI) return null;
125
+ // Special check for 'my-class': only homeroom teachers
126
+ if (item.id === 'my-class' && !isHomeroom) return null;
127
+
128
+ const Icon = item.icon;
129
+ const isActive = currentView === item.id;
130
+
131
+ return (
132
+ <div key={item.id} className="flex items-center gap-1 group">
133
+ <button
134
+ onClick={() => !isEditing && onChangeView(item.id)}
135
+ disabled={isEditing}
136
+ className={`flex-1 flex items-center space-x-3 px-4 py-3 rounded-lg transition-colors duration-200 ${
137
+ isActive
138
+ ? 'bg-blue-600 text-white shadow-lg shadow-blue-900/50'
139
+ : 'text-slate-300 hover:bg-slate-800 hover:text-white'
140
+ } ${isEditing ? 'opacity-70 cursor-grab' : ''}`}
141
+ >
142
+ <Icon size={20} />
143
+ <span className="font-medium">{item.label}</span>
144
+ </button>
145
+ {isEditing && (
146
+ <div className="flex flex-col gap-1 pr-1">
147
+ <button onClick={()=>handleMove(idx, -1)} className="p-1 hover:bg-slate-700 rounded text-slate-400 hover:text-white"><ArrowUp size={12}/></button>
148
+ <button onClick={()=>handleMove(idx, 1)} className="p-1 hover:bg-slate-700 rounded text-slate-400 hover:text-white"><ArrowDown size={12}/></button>
149
+ </div>
150
+ )}
151
+ </div>
152
+ );
153
+ })}
154
+ </nav>
155
+ </div>
156
+
157
+ <div className="p-4 border-t border-slate-700">
158
+ <div className="px-4 py-2 mb-2 text-xs text-slate-500 flex justify-between">
159
+ <span>当前角色:</span>
160
+ <span className="text-slate-300 font-bold">
161
+ {userRole === 'ADMIN' ? '超级管理员' :
162
+ userRole === 'PRINCIPAL' ? '校长' :
163
+ userRole === 'TEACHER' ? '教师' : '学生'}
164
+ </span>
165
+ </div>
166
+ <button onClick={onLogout} className="w-full flex items-center space-x-3 px-4 py-3 rounded-lg text-slate-400 hover:bg-red-500/10 hover:text-red-400 transition-all duration-200">
167
+ <LogOut size={20} />
168
+ <span className="font-medium">退出登录</span>
169
+ </button>
170
+ </div>
171
+ </div>
172
+ </>
173
+ );
174
+ };
types.ts CHANGED
@@ -93,7 +93,7 @@ export interface ClassInfo {
93
  teacherName?: string;
94
  homeroomTeacherIds?: string[];
95
  studentCount?: number;
96
- periodConfig?: PeriodConfig[]; // Added class-specific period config
97
  seatingConfig?: { rows: number; cols: number; }; // New layout config
98
  }
99
 
 
93
  teacherName?: string;
94
  homeroomTeacherIds?: string[];
95
  studentCount?: number;
96
+ periodConfig?: PeriodConfig[];
97
  seatingConfig?: { rows: number; cols: number; }; // New layout config
98
  }
99