Spaces:
Sleeping
Sleeping
Upload 45 files
Browse files- pages/Games.tsx +17 -19
- server.js +5 -5
- services/api.ts +1 -1
pages/Games.tsx
CHANGED
|
@@ -129,30 +129,28 @@ export const Games: React.FC = () => {
|
|
| 129 |
<button onClick={() => setActiveGame('lucky')} className={`px-3 py-1.5 rounded-lg text-xs font-bold transition-all whitespace-nowrap ${activeGame === 'lucky' ? 'bg-red-100 text-red-700 border border-red-200' : 'bg-white text-gray-500 border border-transparent'}`}>
|
| 130 |
🧧 抽奖
|
| 131 |
</button>
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
</div>
|
| 142 |
|
| 143 |
<div className="flex-1 bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden relative">
|
| 144 |
-
{/* Pass the selectedClass as homeroomClass prop override to children components if they use it */}
|
| 145 |
-
{/* NOTE: Most child components were fetching `currentUser.homeroomClass`. We need to update them or patch the API call context.
|
| 146 |
-
Since we can't easily change all child internal logic without rewriting them, we will use a key to force re-mount and
|
| 147 |
-
pass the class context explicitly via props (we'd need to update children to accept props) OR
|
| 148 |
-
we rely on the fact that `Game*` components usually fetch based on a prop or assume currentUser.
|
| 149 |
-
|
| 150 |
-
UPDATE: I will assume children need `className` prop.
|
| 151 |
-
*/}
|
| 152 |
{activeGame === 'mountain' ? <GameMountain className={selectedClass} /> :
|
| 153 |
activeGame === 'lucky' ? <GameLucky className={selectedClass} /> :
|
| 154 |
-
activeGame === 'random' ? <GameRandom className={selectedClass} /> :
|
| 155 |
-
activeGame === 'zen' ? <GameZen className={selectedClass} /> :
|
|
|
|
|
|
|
| 156 |
</div>
|
| 157 |
</div>
|
| 158 |
)}
|
|
|
|
| 129 |
<button onClick={() => setActiveGame('lucky')} className={`px-3 py-1.5 rounded-lg text-xs font-bold transition-all whitespace-nowrap ${activeGame === 'lucky' ? 'bg-red-100 text-red-700 border border-red-200' : 'bg-white text-gray-500 border border-transparent'}`}>
|
| 130 |
🧧 抽奖
|
| 131 |
</button>
|
| 132 |
+
{isTeacher && (
|
| 133 |
+
<>
|
| 134 |
+
<button onClick={() => setActiveGame('random')} className={`px-3 py-1.5 rounded-lg text-xs font-bold transition-all whitespace-nowrap ${activeGame === 'random' ? 'bg-yellow-100 text-yellow-700 border border-yellow-200' : 'bg-white text-gray-500 border border-transparent'}`}>
|
| 135 |
+
<Zap size={14} className="inline mr-1"/> 点名
|
| 136 |
+
</button>
|
| 137 |
+
<button onClick={() => setActiveGame('monster')} className={`px-3 py-1.5 rounded-lg text-xs font-bold transition-all whitespace-nowrap ${activeGame === 'monster' ? 'bg-purple-100 text-purple-700 border border-purple-200' : 'bg-white text-gray-500 border border-transparent'}`}>
|
| 138 |
+
<Mic size={14} className="inline mr-1"/> 怪兽
|
| 139 |
+
</button>
|
| 140 |
+
<button onClick={() => setActiveGame('zen')} className={`px-3 py-1.5 rounded-lg text-xs font-bold transition-all whitespace-nowrap ${activeGame === 'zen' ? 'bg-teal-100 text-teal-700 border border-teal-200' : 'bg-white text-gray-500 border border-transparent'}`}>
|
| 141 |
+
<Moon size={14} className="inline mr-1"/> 禅道
|
| 142 |
+
</button>
|
| 143 |
+
</>
|
| 144 |
+
)}
|
| 145 |
</div>
|
| 146 |
|
| 147 |
<div className="flex-1 bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden relative">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
{activeGame === 'mountain' ? <GameMountain className={selectedClass} /> :
|
| 149 |
activeGame === 'lucky' ? <GameLucky className={selectedClass} /> :
|
| 150 |
+
(isTeacher && activeGame === 'random') ? <GameRandom className={selectedClass} /> :
|
| 151 |
+
(isTeacher && activeGame === 'zen') ? <GameZen className={selectedClass} /> :
|
| 152 |
+
(isTeacher && activeGame === 'monster') ? <GameMonster className={selectedClass} /> :
|
| 153 |
+
<div className="h-full flex items-center justify-center text-gray-400">请选择游戏</div>}
|
| 154 |
</div>
|
| 155 |
</div>
|
| 156 |
)}
|
server.js
CHANGED
|
@@ -95,18 +95,19 @@ const generateStudentNo = async () => {
|
|
| 95 |
// Insert helper route for fetching teachers of a class
|
| 96 |
app.get('/api/classes/:className/teachers', async (req, res) => {
|
| 97 |
const { className } = req.params;
|
|
|
|
| 98 |
const schoolId = req.headers['x-school-id'];
|
| 99 |
|
| 100 |
// 1. Get Homeroom Teachers
|
| 101 |
const cls = await ClassModel.findOne({
|
| 102 |
-
$expr: { $eq: [{ $concat: ["$grade", "$className"] },
|
| 103 |
schoolId
|
| 104 |
});
|
| 105 |
|
| 106 |
let teacherIds = new Set(cls ? (cls.homeroomTeacherIds || []) : []);
|
| 107 |
|
| 108 |
// 2. Get Subject Teachers from Courses
|
| 109 |
-
const courses = await Course.find({ className, schoolId });
|
| 110 |
courses.forEach(c => {
|
| 111 |
if(c.teacherId) teacherIds.add(c.teacherId);
|
| 112 |
});
|
|
@@ -115,9 +116,8 @@ app.get('/api/classes/:className/teachers', async (req, res) => {
|
|
| 115 |
res.json(teachers);
|
| 116 |
});
|
| 117 |
|
| 118 |
-
// ... (
|
| 119 |
-
|
| 120 |
-
// GAMES: Lucky Config (Modified to support ownerId query)
|
| 121 |
app.get('/api/games/lucky-config', async (req, res) => {
|
| 122 |
const filter = getQueryFilter(req);
|
| 123 |
// If explicit ownerId passed (e.g. from student view), use it.
|
|
|
|
| 95 |
// Insert helper route for fetching teachers of a class
|
| 96 |
app.get('/api/classes/:className/teachers', async (req, res) => {
|
| 97 |
const { className } = req.params;
|
| 98 |
+
const cleanName = decodeURIComponent(className).trim();
|
| 99 |
const schoolId = req.headers['x-school-id'];
|
| 100 |
|
| 101 |
// 1. Get Homeroom Teachers
|
| 102 |
const cls = await ClassModel.findOne({
|
| 103 |
+
$expr: { $eq: [{ $concat: ["$grade", "$className"] }, cleanName] },
|
| 104 |
schoolId
|
| 105 |
});
|
| 106 |
|
| 107 |
let teacherIds = new Set(cls ? (cls.homeroomTeacherIds || []) : []);
|
| 108 |
|
| 109 |
// 2. Get Subject Teachers from Courses
|
| 110 |
+
const courses = await Course.find({ className: cleanName, schoolId });
|
| 111 |
courses.forEach(c => {
|
| 112 |
if(c.teacherId) teacherIds.add(c.teacherId);
|
| 113 |
});
|
|
|
|
| 116 |
res.json(teachers);
|
| 117 |
});
|
| 118 |
|
| 119 |
+
// ... (Rest of the file follows)
|
| 120 |
+
// ...
|
|
|
|
| 121 |
app.get('/api/games/lucky-config', async (req, res) => {
|
| 122 |
const filter = getQueryFilter(req);
|
| 123 |
// If explicit ownerId passed (e.g. from student view), use it.
|
services/api.ts
CHANGED
|
@@ -118,7 +118,7 @@ export const api = {
|
|
| 118 |
delete: (id: string) => request(`/users/${id}`, { method: 'DELETE' }),
|
| 119 |
applyClass: (data: { userId: string, type: 'CLAIM'|'RESIGN', targetClass?: string, action: 'APPLY'|'APPROVE'|'REJECT' }) =>
|
| 120 |
request('/users/class-application', { method: 'POST', body: JSON.stringify(data) }),
|
| 121 |
-
getTeachersForClass: (className: string) => request(`/classes/${className}/teachers`),
|
| 122 |
},
|
| 123 |
|
| 124 |
students: {
|
|
|
|
| 118 |
delete: (id: string) => request(`/users/${id}`, { method: 'DELETE' }),
|
| 119 |
applyClass: (data: { userId: string, type: 'CLAIM'|'RESIGN', targetClass?: string, action: 'APPLY'|'APPROVE'|'REJECT' }) =>
|
| 120 |
request('/users/class-application', { method: 'POST', body: JSON.stringify(data) }),
|
| 121 |
+
getTeachersForClass: (className: string) => request(`/classes/${encodeURIComponent(className)}/teachers`),
|
| 122 |
},
|
| 123 |
|
| 124 |
students: {
|