Spaces:
Running
Running
Upload 45 files
Browse files- pages/AchievementTeacher.tsx +1 -1
- server.js +95 -0
pages/AchievementTeacher.tsx
CHANGED
|
@@ -47,7 +47,7 @@ export const AchievementTeacher: React.FC<{className?: string}> = ({ className }
|
|
| 47 |
const stus = await api.students.getAll();
|
| 48 |
// Filter students for homeroom & Sort by SeatNo > Name
|
| 49 |
const sortedStudents = stus
|
| 50 |
-
.filter((s: Student) => s.className === homeroomClass)
|
| 51 |
.sort((a: Student, b: Student) => {
|
| 52 |
const seatA = parseInt(a.seatNo || '99999');
|
| 53 |
const seatB = parseInt(b.seatNo || '99999');
|
|
|
|
| 47 |
const stus = await api.students.getAll();
|
| 48 |
// Filter students for homeroom & Sort by SeatNo > Name
|
| 49 |
const sortedStudents = stus
|
| 50 |
+
.filter((s: Student) => s.className.trim() === homeroomClass.trim())
|
| 51 |
.sort((a: Student, b: Student) => {
|
| 52 |
const seatA = parseInt(a.seatNo || '99999');
|
| 53 |
const seatB = parseInt(b.seatNo || '99999');
|
server.js
CHANGED
|
@@ -654,6 +654,101 @@ app.post('/api/games/grant-reward', async (req, res) => {
|
|
| 654 |
res.json({});
|
| 655 |
});
|
| 656 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 657 |
// Update Class (Support multi-teachers)
|
| 658 |
app.put('/api/classes/:id', async (req, res) => {
|
| 659 |
const classId = req.params.id;
|
|
|
|
| 654 |
res.json({});
|
| 655 |
});
|
| 656 |
|
| 657 |
+
// ACHIEVEMENT ROUTES (New)
|
| 658 |
+
app.get('/api/achievements/config', async (req, res) => {
|
| 659 |
+
const { className } = req.query;
|
| 660 |
+
const filter = getQueryFilter(req);
|
| 661 |
+
if (className) filter.className = className;
|
| 662 |
+
res.json(await AchievementConfigModel.findOne(filter));
|
| 663 |
+
});
|
| 664 |
+
|
| 665 |
+
app.post('/api/achievements/config', async (req, res) => {
|
| 666 |
+
const data = injectSchoolId(req, req.body);
|
| 667 |
+
await AchievementConfigModel.findOneAndUpdate(
|
| 668 |
+
{ className: data.className, ...getQueryFilter(req) },
|
| 669 |
+
data,
|
| 670 |
+
{ upsert: true }
|
| 671 |
+
);
|
| 672 |
+
res.json({ success: true });
|
| 673 |
+
});
|
| 674 |
+
|
| 675 |
+
app.get('/api/achievements/student', async (req, res) => {
|
| 676 |
+
const { studentId, semester } = req.query;
|
| 677 |
+
const filter = { studentId };
|
| 678 |
+
if (semester) filter.semester = semester;
|
| 679 |
+
res.json(await StudentAchievementModel.find(filter).sort({ createTime: -1 }));
|
| 680 |
+
});
|
| 681 |
+
|
| 682 |
+
app.post('/api/achievements/grant', async (req, res) => {
|
| 683 |
+
const { studentId, achievementId, semester } = req.body;
|
| 684 |
+
const sId = req.headers['x-school-id'];
|
| 685 |
+
|
| 686 |
+
const student = await Student.findById(studentId);
|
| 687 |
+
if (!student) return res.status(404).json({ error: 'Student not found' });
|
| 688 |
+
|
| 689 |
+
const config = await AchievementConfigModel.findOne({ className: student.className, schoolId: sId });
|
| 690 |
+
const achievement = config?.achievements.find(a => a.id === achievementId);
|
| 691 |
+
|
| 692 |
+
if (!achievement) return res.status(404).json({ error: 'Achievement not found' });
|
| 693 |
+
|
| 694 |
+
// Add Record
|
| 695 |
+
await StudentAchievementModel.create({
|
| 696 |
+
schoolId: sId,
|
| 697 |
+
studentId,
|
| 698 |
+
studentName: student.name,
|
| 699 |
+
achievementId: achievement.id,
|
| 700 |
+
achievementName: achievement.name,
|
| 701 |
+
achievementIcon: achievement.icon,
|
| 702 |
+
semester,
|
| 703 |
+
createTime: new Date()
|
| 704 |
+
});
|
| 705 |
+
|
| 706 |
+
// Add Points (Flowers)
|
| 707 |
+
await Student.findByIdAndUpdate(studentId, { $inc: { flowerBalance: achievement.points } });
|
| 708 |
+
|
| 709 |
+
res.json({ success: true });
|
| 710 |
+
});
|
| 711 |
+
|
| 712 |
+
app.post('/api/achievements/exchange', async (req, res) => {
|
| 713 |
+
const { studentId, ruleId } = req.body;
|
| 714 |
+
const sId = req.headers['x-school-id'];
|
| 715 |
+
|
| 716 |
+
const student = await Student.findById(studentId);
|
| 717 |
+
if (!student) return res.status(404).json({ error: 'Student not found' });
|
| 718 |
+
|
| 719 |
+
const config = await AchievementConfigModel.findOne({ className: student.className, schoolId: sId });
|
| 720 |
+
const rule = config?.exchangeRules.find(r => r.id === ruleId);
|
| 721 |
+
|
| 722 |
+
if (!rule) return res.status(404).json({ error: 'Rule not found' });
|
| 723 |
+
|
| 724 |
+
if (student.flowerBalance < rule.cost) {
|
| 725 |
+
return res.status(400).json({ error: 'INSUFFICIENT_FUNDS', message: '小红花余额不足' });
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
// Deduct Flowers
|
| 729 |
+
await Student.findByIdAndUpdate(studentId, { $inc: { flowerBalance: -rule.cost } });
|
| 730 |
+
|
| 731 |
+
// Add Reward Record (using StudentRewardModel so it appears in "GameRewards" / "Shop History")
|
| 732 |
+
await StudentRewardModel.create({
|
| 733 |
+
schoolId: sId,
|
| 734 |
+
studentId,
|
| 735 |
+
studentName: student.name,
|
| 736 |
+
rewardType: rule.rewardType, // ITEM or DRAW_COUNT
|
| 737 |
+
name: rule.rewardName,
|
| 738 |
+
count: rule.rewardValue,
|
| 739 |
+
status: rule.rewardType === 'DRAW_COUNT' ? 'REDEEMED' : 'PENDING',
|
| 740 |
+
source: '积分兑换',
|
| 741 |
+
createTime: new Date()
|
| 742 |
+
});
|
| 743 |
+
|
| 744 |
+
// If Draw Count, add attempts immediately
|
| 745 |
+
if (rule.rewardType === 'DRAW_COUNT') {
|
| 746 |
+
await Student.findByIdAndUpdate(studentId, { $inc: { drawAttempts: rule.rewardValue } });
|
| 747 |
+
}
|
| 748 |
+
|
| 749 |
+
res.json({ success: true });
|
| 750 |
+
});
|
| 751 |
+
|
| 752 |
// Update Class (Support multi-teachers)
|
| 753 |
app.put('/api/classes/:id', async (req, res) => {
|
| 754 |
const classId = req.params.id;
|