dvc890 commited on
Commit
4513def
·
verified ·
1 Parent(s): dbc6fe3

Upload 39 files

Browse files
Files changed (3) hide show
  1. pages/SchoolList.tsx +35 -6
  2. server.js +41 -0
  3. services/api.ts +2 -1
pages/SchoolList.tsx CHANGED
@@ -1,8 +1,7 @@
1
-
2
  import React, { useState, useEffect } from 'react';
3
  import { School } from '../types';
4
  import { api } from '../services/api';
5
- import { Loader2, Plus, Edit, Save, X, School as SchoolIcon } from 'lucide-react';
6
 
7
  export const SchoolList: React.FC = () => {
8
  const [schools, setSchools] = useState<School[]>([]);
@@ -31,11 +30,38 @@ export const SchoolList: React.FC = () => {
31
  setIsAdding(false);
32
  setForm({ name: '', code: '' });
33
  loadSchools();
34
- // Notify Header to update
35
  window.dispatchEvent(new Event('school-updated'));
36
  } catch (e) { alert('保存失败,代码可能重复'); }
37
  };
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  const startEdit = (s: School) => {
40
  setEditId(s._id || String(s.id));
41
  setForm({ name: s.name, code: s.code });
@@ -80,14 +106,17 @@ export const SchoolList: React.FC = () => {
80
  <td className="px-6 py-4 font-mono text-sm text-gray-600">
81
  {isEditing ? <input className="border p-1 rounded" value={form.code} onChange={e=>setForm({...form, code:e.target.value})}/> : s.code}
82
  </td>
83
- <td className="px-6 py-4 text-right">
84
  {isEditing ? (
85
  <>
86
- <button onClick={handleSave} className="text-green-600 mr-2"><Save size={18}/></button>
87
  <button onClick={()=>setEditId(null)} className="text-gray-500"><X size={18}/></button>
88
  </>
89
  ) : (
90
- <button onClick={() => startEdit(s)} className="text-blue-500 hover:text-blue-700"><Edit size={18}/></button>
 
 
 
91
  )}
92
  </td>
93
  </tr>
 
 
1
  import React, { useState, useEffect } from 'react';
2
  import { School } from '../types';
3
  import { api } from '../services/api';
4
+ import { Loader2, Plus, Edit, Save, X, School as SchoolIcon, Trash2, AlertTriangle } from 'lucide-react';
5
 
6
  export const SchoolList: React.FC = () => {
7
  const [schools, setSchools] = useState<School[]>([]);
 
30
  setIsAdding(false);
31
  setForm({ name: '', code: '' });
32
  loadSchools();
 
33
  window.dispatchEvent(new Event('school-updated'));
34
  } catch (e) { alert('保存失败,代码可能重复'); }
35
  };
36
 
37
+ const handleDelete = async (s: School) => {
38
+ if (!confirm(`⚠️ 极度危险操作警告!\n\n您确定要删除学校“${s.name}”吗?\n\n此操作将不可逆地删除:\n1. 该学校的所有老师、学生、管理员账号\n2. 所有班级、成绩、考勤、课表数据\n3. 所有游戏配置和奖励记录\n\n请输入“确认删除”以继续:`)) return;
39
+
40
+ // Basic safeguard
41
+ const currentUser = api.auth.getCurrentUser();
42
+ if (currentUser?.schoolId === s._id) {
43
+ if(!confirm('警告:您正在删除自己当前归属的学校,操作成功后您的账号也将被删除且强制登出。确定继续?')) return;
44
+ }
45
+
46
+ try {
47
+ await api.schools.delete(s._id!);
48
+ // If deleted currently viewed school in admin view, reset local storage
49
+ const currentViewId = localStorage.getItem('admin_view_school_id');
50
+ if (currentViewId === s._id) {
51
+ localStorage.removeItem('admin_view_school_id');
52
+ localStorage.removeItem('admin_view_school_id_init');
53
+ }
54
+
55
+ loadSchools();
56
+ window.dispatchEvent(new Event('school-updated'));
57
+
58
+ if (currentUser?.schoolId === s._id) {
59
+ api.auth.logout();
60
+ window.location.reload();
61
+ }
62
+ } catch (e) { alert('删除失败'); }
63
+ };
64
+
65
  const startEdit = (s: School) => {
66
  setEditId(s._id || String(s.id));
67
  setForm({ name: s.name, code: s.code });
 
106
  <td className="px-6 py-4 font-mono text-sm text-gray-600">
107
  {isEditing ? <input className="border p-1 rounded" value={form.code} onChange={e=>setForm({...form, code:e.target.value})}/> : s.code}
108
  </td>
109
+ <td className="px-6 py-4 text-right flex justify-end gap-2">
110
  {isEditing ? (
111
  <>
112
+ <button onClick={handleSave} className="text-green-600"><Save size={18}/></button>
113
  <button onClick={()=>setEditId(null)} className="text-gray-500"><X size={18}/></button>
114
  </>
115
  ) : (
116
+ <>
117
+ <button onClick={() => startEdit(s)} className="text-blue-500 hover:text-blue-700"><Edit size={18}/></button>
118
+ <button onClick={() => handleDelete(s)} className="text-gray-400 hover:text-red-600" title="删除学校及所有数据"><Trash2 size={18}/></button>
119
+ </>
120
  )}
121
  </td>
122
  </tr>
server.js CHANGED
@@ -635,6 +635,47 @@ app.post('/api/auth/login', async (req, res) => {
635
  app.get('/api/schools', async (req, res) => { res.json(await School.find()); });
636
  app.post('/api/schools', async (req, res) => { res.json(await School.create(req.body)); });
637
  app.put('/api/schools/:id', async (req, res) => { await School.findByIdAndUpdate(req.params.id, req.body); res.json({}); });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
638
  app.delete('/api/users/:id', async (req, res) => { await User.findByIdAndDelete(req.params.id); res.json({}); });
639
  app.get('/api/students', async (req, res) => { res.json(await Student.find(getQueryFilter(req))); });
640
 
 
635
  app.get('/api/schools', async (req, res) => { res.json(await School.find()); });
636
  app.post('/api/schools', async (req, res) => { res.json(await School.create(req.body)); });
637
  app.put('/api/schools/:id', async (req, res) => { await School.findByIdAndUpdate(req.params.id, req.body); res.json({}); });
638
+ // NEW: Delete School and ALL associated data
639
+ app.delete('/api/schools/:id', async (req, res) => {
640
+ const schoolId = req.params.id;
641
+ try {
642
+ // 1. Delete the School itself
643
+ await School.findByIdAndDelete(schoolId);
644
+
645
+ // 2. Delete Users (Teachers, Students, Admins linked to this school)
646
+ await User.deleteMany({ schoolId });
647
+
648
+ // 3. Delete Student Profiles
649
+ await Student.deleteMany({ schoolId });
650
+
651
+ // 4. Delete Classes
652
+ await ClassModel.deleteMany({ schoolId });
653
+
654
+ // 5. Delete Academic Data
655
+ await SubjectModel.deleteMany({ schoolId });
656
+ await Course.deleteMany({ schoolId });
657
+ await Score.deleteMany({ schoolId });
658
+ await ExamModel.deleteMany({ schoolId });
659
+ await ScheduleModel.deleteMany({ schoolId });
660
+
661
+ // 6. Delete Operational Data
662
+ await NotificationModel.deleteMany({ schoolId });
663
+ await AttendanceModel.deleteMany({ schoolId });
664
+ await LeaveRequestModel.deleteMany({ schoolId });
665
+
666
+ // 7. Delete Interactive/Game Data
667
+ await GameSessionModel.deleteMany({ schoolId });
668
+ await StudentRewardModel.deleteMany({ schoolId });
669
+ await LuckyDrawConfigModel.deleteMany({ schoolId });
670
+ await AchievementConfigModel.deleteMany({ schoolId });
671
+ await StudentAchievementModel.deleteMany({ schoolId });
672
+
673
+ res.json({ success: true });
674
+ } catch (e) {
675
+ console.error('Delete School Error:', e);
676
+ res.status(500).json({ error: e.message });
677
+ }
678
+ });
679
  app.delete('/api/users/:id', async (req, res) => { await User.findByIdAndDelete(req.params.id); res.json({}); });
680
  app.get('/api/students', async (req, res) => { res.json(await Student.find(getQueryFilter(req))); });
681
 
services/api.ts CHANGED
@@ -99,7 +99,8 @@ export const api = {
99
  getPublic: () => request('/public/schools'),
100
  getAll: () => request('/schools'),
101
  add: (data: School) => request('/schools', { method: 'POST', body: JSON.stringify(data) }),
102
- update: (id: string, data: Partial<School>) => request(`/schools/${id}`, { method: 'PUT', body: JSON.stringify(data) })
 
103
  },
104
 
105
  users: {
 
99
  getPublic: () => request('/public/schools'),
100
  getAll: () => request('/schools'),
101
  add: (data: School) => request('/schools', { method: 'POST', body: JSON.stringify(data) }),
102
+ update: (id: string, data: Partial<School>) => request(`/schools/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
103
+ delete: (id: string) => request(`/schools/${id}`, { method: 'DELETE' }) // NEW
104
  },
105
 
106
  users: {