Spaces:
Sleeping
Sleeping
Upload 39 files
Browse files- pages/SchoolList.tsx +35 -6
- server.js +41 -0
- 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
|
| 87 |
<button onClick={()=>setEditId(null)} className="text-gray-500"><X size={18}/></button>
|
| 88 |
</>
|
| 89 |
) : (
|
| 90 |
-
<
|
|
|
|
|
|
|
|
|
|
| 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: {
|