Migrate project from typing_practiceweb
This commit is contained in:
699
server/routes/vocabulary.ts
Normal file
699
server/routes/vocabulary.ts
Normal file
@@ -0,0 +1,699 @@
|
||||
// server/routes/vocabulary.ts
|
||||
import express from 'express';
|
||||
import multer from 'multer';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { auth as authMiddleware } from '../middleware/auth';
|
||||
import { Word, WordSet, WordRecord, VocabularyTestRecord } from '../models/Vocabulary';
|
||||
import mongoose from 'mongoose';
|
||||
import csv from 'csv-parser';
|
||||
import { User } from '../models/User';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 配置 multer 用于文件上传
|
||||
const storage = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
const uploadDir = path.join(__dirname, '../uploads');
|
||||
// 确保上传目录存在
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true });
|
||||
}
|
||||
cb(null, uploadDir);
|
||||
},
|
||||
filename: function (req, file, cb) {
|
||||
cb(null, `${Date.now()}-${file.originalname}`);
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: 5 * 1024 * 1024 // 限制5MB
|
||||
},
|
||||
fileFilter: function (req, file, cb) {
|
||||
// 只允许上传CSV文件
|
||||
if (file.mimetype === 'text/csv' || file.originalname.endsWith('.csv')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('只支持CSV文件'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 获取所有单词集
|
||||
router.get('/word-sets', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const wordSets = await WordSet.find()
|
||||
.select('name description totalWords createdAt')
|
||||
.sort({ createdAt: -1 });
|
||||
|
||||
res.json(wordSets);
|
||||
} catch (error) {
|
||||
console.error('获取单词集失败:', error);
|
||||
res.status(500).json({ message: '获取单词集失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 创建新的单词集
|
||||
router.post('/word-sets', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const { name, description } = req.body;
|
||||
|
||||
// 检查是否已存在同名单词集
|
||||
const existingSet = await WordSet.findOne({
|
||||
name: name
|
||||
});
|
||||
|
||||
if (existingSet) {
|
||||
return res.status(400).json({ message: '已存在同名单词集' });
|
||||
}
|
||||
|
||||
const newWordSet = await WordSet.create({
|
||||
name,
|
||||
description,
|
||||
totalWords: 0
|
||||
});
|
||||
|
||||
res.status(201).json(newWordSet);
|
||||
} catch (error) {
|
||||
console.error('创建单词集失败:', error);
|
||||
res.status(500).json({ message: '创建单词集失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 上传单词文件并创建单词集
|
||||
router.post('/upload', authMiddleware, upload.single('file'), async (req, res) => {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ message: '请上传文件' });
|
||||
}
|
||||
|
||||
try {
|
||||
const { name } = req.body;
|
||||
const fileName = req.file.filename;
|
||||
const filePath = req.file.path;
|
||||
|
||||
// 检查是否已存在同名单词集
|
||||
const existingSet = await WordSet.findOne({
|
||||
name: name || path.basename(fileName, '.csv')
|
||||
});
|
||||
|
||||
if (existingSet) {
|
||||
// 删除上传的文件
|
||||
fs.unlinkSync(filePath);
|
||||
return res.status(400).json({ message: '已存在同名单词集' });
|
||||
}
|
||||
|
||||
// 创建单词集
|
||||
const wordSet = await WordSet.create({
|
||||
name: name || path.basename(fileName, '.csv'),
|
||||
totalWords: 0
|
||||
});
|
||||
|
||||
const results: any[] = [];
|
||||
let wordCount = 0;
|
||||
|
||||
// 处理CSV文件
|
||||
fs.createReadStream(filePath)
|
||||
.pipe(csv())
|
||||
.on('data', (data) => {
|
||||
// 检查必要的字段是否存在
|
||||
if (data.word && data.translation) {
|
||||
results.push({
|
||||
word: data.word.trim(),
|
||||
translation: data.translation.trim(),
|
||||
pronunciation: data.pronunciation?.trim(),
|
||||
example: data.example?.trim(),
|
||||
wordSet: wordSet._id
|
||||
});
|
||||
wordCount++;
|
||||
}
|
||||
})
|
||||
.on('end', async () => {
|
||||
try {
|
||||
// 批量插入单词
|
||||
if (results.length > 0) {
|
||||
await Word.insertMany(results);
|
||||
|
||||
// 更新单词集的单词数量
|
||||
await WordSet.findByIdAndUpdate(wordSet._id, { totalWords: wordCount });
|
||||
}
|
||||
|
||||
// 删除上传的文件
|
||||
fs.unlinkSync(filePath);
|
||||
|
||||
res.status(201).json({
|
||||
message: `成功创建单词集并导入${wordCount}个单词`,
|
||||
wordSet
|
||||
});
|
||||
} catch (error) {
|
||||
// 如果插入单词失败,删除创建的单词集
|
||||
await WordSet.findByIdAndDelete(wordSet._id);
|
||||
|
||||
console.error('导入单词失败:', error);
|
||||
res.status(500).json({ message: '导入单词失败' });
|
||||
}
|
||||
})
|
||||
.on('error', (error) => {
|
||||
console.error('处理CSV文件失败:', error);
|
||||
res.status(500).json({ message: '处理CSV文件失败' });
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('上传文件失败:', error);
|
||||
res.status(500).json({ message: '上传文件失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 获取学习单词(智能抽取算法,嵌套结构版)
|
||||
router.get('/study-words/:wordSetId', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const { wordSetId } = req.params;
|
||||
let targetCount = 100;
|
||||
if (req.query.count) {
|
||||
const parsed = parseInt(req.query.count as string, 10);
|
||||
if (!isNaN(parsed) && parsed > 0) {
|
||||
targetCount = Math.min(parsed, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查单词集是否存在
|
||||
const wordSet = await WordSet.findOne({ _id: wordSetId });
|
||||
if (!wordSet) {
|
||||
return res.status(404).json({ message: '未找到单词集' });
|
||||
}
|
||||
|
||||
// 1. 获取该单词集下所有单词
|
||||
const allWords = await Word.find({ wordSet: wordSetId });
|
||||
const allWordIds = allWords.map(w => w._id.toString());
|
||||
|
||||
// 2. 获取该用户所有WordRecord(嵌套结构)
|
||||
const allRecords = await WordRecord.find({ user: req.user._id, word: { $in: allWordIds } });
|
||||
// 用 wordId 做 key
|
||||
const recordMap = new Map();
|
||||
allRecords.forEach(r => {
|
||||
recordMap.set(r.word.toString(), r);
|
||||
});
|
||||
|
||||
// 3. 分类
|
||||
const wrongBookWords: any[] = [];
|
||||
const notMasteredWords: any[] = [];
|
||||
const masteredWords: { word: any, lastMasteredAt: Date }[] = [];
|
||||
const neverLearnedWords: any[] = [];
|
||||
|
||||
for (const word of allWords) {
|
||||
const wordId = word._id.toString();
|
||||
const record = recordMap.get(wordId);
|
||||
|
||||
// 没有任何记录
|
||||
if (!record) {
|
||||
neverLearnedWords.push(word);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 判断是否在错词本
|
||||
const modes = ['chineseToEnglish', 'audioToEnglish', 'multipleChoice'];
|
||||
const anyInWrongBook = modes.some(m => record[m]?.inWrongBook);
|
||||
if (anyInWrongBook) {
|
||||
wrongBookWords.push(word);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 判断是否全部掌握
|
||||
if (record.isFullyMastered) {
|
||||
masteredWords.push({ word, lastMasteredAt: record.lastFullyMasteredAt });
|
||||
continue;
|
||||
}
|
||||
|
||||
// 未完全掌握但有学习记录
|
||||
if (modes.some(m => record[m] && Object.keys(record[m]).length > 0)) {
|
||||
notMasteredWords.push(word);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤一周内掌握的单词
|
||||
const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
||||
let eligibleMasteredWords = masteredWords.filter(item =>
|
||||
item.lastMasteredAt && item.lastMasteredAt < oneWeekAgo
|
||||
);
|
||||
|
||||
// 4. 按比例抽取
|
||||
let wrongBookQuota = Math.round(targetCount * 0.4);
|
||||
let masteredQuota = Math.round(targetCount * 0.1);
|
||||
let neverLearnedQuota = targetCount - wrongBookQuota - masteredQuota; // 剩下的给未学和新词
|
||||
|
||||
// 错词本优先
|
||||
let selectedWrongBook = wrongBookWords.slice(0, wrongBookQuota);
|
||||
let selectedMastered = eligibleMasteredWords
|
||||
.sort((a, b) => (a.lastMasteredAt?.getTime() || 0) - (b.lastMasteredAt?.getTime() || 0))
|
||||
.slice(0, masteredQuota)
|
||||
.map(item => item.word);
|
||||
|
||||
function shuffle(arr) {
|
||||
for (let i = arr.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[arr[i], arr[j]] = [arr[j], arr[i]];
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
let selectedNeverLearned = shuffle([...neverLearnedWords]).slice(0, neverLearnedQuota);
|
||||
|
||||
// 补足不足部分
|
||||
let remain = targetCount - (selectedWrongBook.length + selectedMastered.length + selectedNeverLearned.length);
|
||||
// 先补未掌握(不在错词本的)
|
||||
let notMasteredPool = notMasteredWords.filter(w => !selectedWrongBook.includes(w));
|
||||
let selectedNotMastered: any[] = [];
|
||||
if (remain > 0 && notMasteredPool.length > 0) {
|
||||
selectedNotMastered = notMasteredPool.slice(0, remain);
|
||||
remain -= selectedNotMastered.length;
|
||||
}
|
||||
// 再补新词
|
||||
if (remain > 0) {
|
||||
const moreNew = neverLearnedWords.slice(neverLearnedQuota, neverLearnedQuota + remain);
|
||||
selectedNeverLearned = selectedNeverLearned.concat(moreNew);
|
||||
remain -= moreNew.length;
|
||||
}
|
||||
// 再补已掌握
|
||||
if (remain > 0) {
|
||||
const moreMastered = eligibleMasteredWords
|
||||
.sort((a, b) => (a.lastMasteredAt?.getTime() || 0) - (b.lastMasteredAt?.getTime() || 0))
|
||||
.slice(masteredQuota, masteredQuota + remain)
|
||||
.map(item => item.word);
|
||||
selectedMastered = selectedMastered.concat(moreMastered);
|
||||
remain -= moreMastered.length;
|
||||
}
|
||||
// 再补错词本
|
||||
if (remain > 0) {
|
||||
const moreWrong = wrongBookWords.slice(wrongBookQuota, wrongBookQuota + remain);
|
||||
selectedWrongBook = selectedWrongBook.concat(moreWrong);
|
||||
remain -= moreWrong.length;
|
||||
}
|
||||
|
||||
// 合并所有选中的单词
|
||||
let finalWords = [
|
||||
...selectedWrongBook,
|
||||
...selectedNotMastered,
|
||||
...selectedMastered,
|
||||
...selectedNeverLearned
|
||||
];
|
||||
// 去重
|
||||
const seen = new Set();
|
||||
finalWords = finalWords.filter(w => {
|
||||
const id = w._id.toString();
|
||||
if (seen.has(id)) return false;
|
||||
seen.add(id);
|
||||
return true;
|
||||
});
|
||||
// 最终数量限制
|
||||
finalWords = finalWords.slice(0, targetCount);
|
||||
|
||||
res.json(finalWords);
|
||||
} catch (error) {
|
||||
console.error('获取学习单词失败:', error);
|
||||
res.status(500).json({ message: '获取学习单词失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 记录单词学习结果(嵌套结构版,upsert防止重复)
|
||||
router.post('/word-record', (req, res, next) => {
|
||||
console.log('收到 /word-record 请求', req.method, req.body);
|
||||
next();
|
||||
}, authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const { wordId, isCorrect, testType } = req.body;
|
||||
const userId = req.user._id;
|
||||
|
||||
// 检查单词是否存在
|
||||
const word = await Word.findById(wordId);
|
||||
if (!word) {
|
||||
return res.status(404).json({ message: '未找到单词' });
|
||||
}
|
||||
|
||||
// 嵌套字段名映射
|
||||
const modeMap = {
|
||||
'chinese-to-english': 'chineseToEnglish',
|
||||
'audio-to-english': 'audioToEnglish',
|
||||
'multiple-choice': 'multipleChoice'
|
||||
};
|
||||
const modeKey = modeMap[testType];
|
||||
if (!modeKey) {
|
||||
return res.status(400).json({ message: '未知的测试类型' });
|
||||
}
|
||||
|
||||
// 先查当前记录
|
||||
let record = await WordRecord.findOne({ user: userId, word: wordId });
|
||||
let modeObj = record ? (record[modeKey] || {}) : {};
|
||||
|
||||
// 更新 streak、totalCorrect、totalWrong
|
||||
if (isCorrect) {
|
||||
modeObj.streak = (modeObj.streak || 0) + 1;
|
||||
modeObj.totalCorrect = (modeObj.totalCorrect || 0) + 1;
|
||||
} else {
|
||||
modeObj.streak = 0;
|
||||
modeObj.totalWrong = (modeObj.totalWrong || 0) + 1;
|
||||
}
|
||||
modeObj.lastTestedAt = new Date();
|
||||
|
||||
// 判定是否掌握
|
||||
if (modeObj.streak >= 1) {
|
||||
modeObj.mastered = true;
|
||||
modeObj.lastMasteredAt = new Date();
|
||||
modeObj.inWrongBook = false; // 掌握后自动移出错词本
|
||||
}
|
||||
|
||||
// 判定是否进入错词本
|
||||
if (!modeObj.mastered && modeObj.totalWrong >= 5) {
|
||||
modeObj.inWrongBook = true;
|
||||
}
|
||||
|
||||
// 构造更新对象
|
||||
const updateObj = {};
|
||||
updateObj[modeKey] = modeObj;
|
||||
|
||||
record = await WordRecord.findOneAndUpdate(
|
||||
{ user: userId, word: wordId },
|
||||
{ $set: updateObj, $setOnInsert: { user: userId, word: wordId, createdAt: new Date() } },
|
||||
{ upsert: true, new: true }
|
||||
);
|
||||
|
||||
// 检查三种模式是否都已掌握
|
||||
const allModes = ['chineseToEnglish', 'audioToEnglish', 'multipleChoice'];
|
||||
const isFullyMastered = allModes.every(m => record[m]?.mastered);
|
||||
let lastMasteredAt: Date | null = null;
|
||||
if (isFullyMastered) {
|
||||
// 取三种模式中最近一次掌握的时间
|
||||
lastMasteredAt = allModes.reduce((latest: Date | null, m) => {
|
||||
const t = record[m]?.lastMasteredAt;
|
||||
if (!latest && t) return t;
|
||||
if (t instanceof Date && latest instanceof Date && t > latest) {
|
||||
return t;
|
||||
}
|
||||
return latest;
|
||||
}, null);
|
||||
|
||||
// 取三种模式中最近一次测试的时间,如果比掌握时间更新则使用测试时间
|
||||
const lastTestedAt = allModes.reduce((latest: Date | null, m) => {
|
||||
const t = record[m]?.lastTestedAt;
|
||||
if (!latest && t) return t;
|
||||
if (t instanceof Date && latest instanceof Date && t > latest) {
|
||||
return t;
|
||||
}
|
||||
return latest;
|
||||
}, null);
|
||||
|
||||
// 使用最新的日期(掌握时间或测试时间)
|
||||
if (lastTestedAt && (!lastMasteredAt || lastTestedAt > lastMasteredAt)) {
|
||||
lastMasteredAt = lastTestedAt;
|
||||
}
|
||||
|
||||
// 更新isFullyMastered和lastFullyMasteredAt字段
|
||||
if (!record.isFullyMastered || !record.lastFullyMasteredAt ||
|
||||
(lastMasteredAt && record.lastFullyMasteredAt < lastMasteredAt)) {
|
||||
record = await WordRecord.findOneAndUpdate(
|
||||
{ _id: record._id },
|
||||
{
|
||||
$set: {
|
||||
isFullyMastered: true,
|
||||
lastFullyMasteredAt: lastMasteredAt
|
||||
}
|
||||
},
|
||||
{ new: true }
|
||||
);
|
||||
}
|
||||
} else if (record.isFullyMastered) {
|
||||
// 如果之前标记为完全掌握,但现在不是,更新状态
|
||||
record = await WordRecord.findOneAndUpdate(
|
||||
{ _id: record._id },
|
||||
{ $set: { isFullyMastered: false } },
|
||||
{ new: true }
|
||||
);
|
||||
}
|
||||
|
||||
// 返回当前单词的所有模式掌握状态和是否在错词本
|
||||
const masteryStatus = {};
|
||||
allModes.forEach(m => {
|
||||
masteryStatus[m] = {
|
||||
mastered: record[m]?.mastered || false,
|
||||
streak: record[m]?.streak || 0,
|
||||
totalCorrect: record[m]?.totalCorrect || 0,
|
||||
totalWrong: record[m]?.totalWrong || 0,
|
||||
inWrongBook: record[m]?.inWrongBook || false,
|
||||
lastMasteredAt: record[m]?.lastMasteredAt || null
|
||||
};
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
message: '记录已保存',
|
||||
masteryStatus,
|
||||
isFullyMastered: record.isFullyMastered,
|
||||
lastMasteredAt: record.lastFullyMasteredAt,
|
||||
inWrongBook: allModes.some(m => record[m]?.inWrongBook)
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('保存单词学习记录失败:', error);
|
||||
res.status(500).json({ message: '保存单词学习记录失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 保存测试记录
|
||||
router.post('/test-record', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const { wordSetId, testType, stats } = req.body;
|
||||
|
||||
// 检查单词集是否存在并属于当前用户
|
||||
const wordSet = await WordSet.findOne({
|
||||
_id: wordSetId
|
||||
});
|
||||
|
||||
if (!wordSet) {
|
||||
return res.status(404).json({ message: '未找到单词集' });
|
||||
}
|
||||
|
||||
// 创建测试记录
|
||||
await VocabularyTestRecord.create({
|
||||
user: req.user._id,
|
||||
wordSet: wordSetId,
|
||||
testType,
|
||||
stats: {
|
||||
totalWords: stats.totalWords,
|
||||
correctWords: stats.correctWords,
|
||||
accuracy: stats.accuracy,
|
||||
startTime: new Date(stats.startTime),
|
||||
endTime: new Date(stats.endTime),
|
||||
duration: stats.duration
|
||||
}
|
||||
});
|
||||
|
||||
res.status(201).json({ message: '测试记录已保存' });
|
||||
} catch (error) {
|
||||
console.error('保存测试记录失败:', error);
|
||||
res.status(500).json({ message: '保存测试记录失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 获取单词详情
|
||||
router.get('/word/:wordId', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const { wordId } = req.params;
|
||||
|
||||
const word = await Word.findById(wordId);
|
||||
if (!word) {
|
||||
return res.status(404).json({ message: '未找到单词' });
|
||||
}
|
||||
|
||||
// 检查用户是否有权限访问这个单词
|
||||
const wordSet = await WordSet.findOne({
|
||||
_id: word.wordSet
|
||||
});
|
||||
|
||||
if (!wordSet) {
|
||||
return res.status(403).json({ message: '没有权限访问该单词' });
|
||||
}
|
||||
|
||||
res.json(word);
|
||||
} catch (error) {
|
||||
console.error('获取单词详情失败:', error);
|
||||
res.status(500).json({ message: '获取单词详情失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 获取单词测试记录
|
||||
router.get('/test-records', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const testRecords = await VocabularyTestRecord.find({
|
||||
user: req.user._id
|
||||
}).populate('wordSet', 'name').sort({ createdAt: -1 });
|
||||
|
||||
res.json(testRecords);
|
||||
} catch (error) {
|
||||
console.error('获取测试记录失败:', error);
|
||||
res.status(500).json({ message: '获取测试记录失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 排行榜接口:按掌握单词数和正确率排序
|
||||
router.get('/leaderboard', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
// 1. 查出所有 WordRecord
|
||||
const allRecords = await WordRecord.find({});
|
||||
|
||||
// 2. 直接使用isFullyMastered统计每个用户掌握的单词数
|
||||
const userMasteredCount = {};
|
||||
const userStats = {};
|
||||
|
||||
for (const rec of allRecords) {
|
||||
const userId = rec.user.toString();
|
||||
|
||||
// 初始化用户统计数据
|
||||
if (!userStats[userId]) {
|
||||
userStats[userId] = { correct: 0, total: 0 };
|
||||
}
|
||||
|
||||
// 使用isFullyMastered直接统计掌握单词数
|
||||
if (rec.isFullyMastered) {
|
||||
userMasteredCount[userId] = (userMasteredCount[userId] || 0) + 1;
|
||||
}
|
||||
|
||||
// 统计正确率
|
||||
['chineseToEnglish', 'audioToEnglish', 'multipleChoice'].forEach(mode => {
|
||||
const m = rec[mode];
|
||||
if (m) {
|
||||
userStats[userId].correct += m.totalCorrect || 0;
|
||||
userStats[userId].total += (m.totalCorrect || 0) + (m.totalWrong || 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 查询用户名
|
||||
const userIds = Object.keys(userMasteredCount);
|
||||
const users = await mongoose.model('User').find({ _id: { $in: userIds } }, { fullname: 1 });
|
||||
const userMap = {};
|
||||
users.forEach(u => { userMap[u._id.toString()] = u.fullname; });
|
||||
|
||||
// 4. 组装排行榜数组
|
||||
const leaderboard = userIds.map(userId => ({
|
||||
userId,
|
||||
fullname: userMap[userId] || '未知用户',
|
||||
totalWordsLearned: userMasteredCount[userId],
|
||||
accuracy: userStats[userId] && userStats[userId].total > 0
|
||||
? Math.round((userStats[userId].correct / userStats[userId].total) * 100)
|
||||
: 0,
|
||||
rank: 0 // 添加rank属性,初始值为0
|
||||
}));
|
||||
|
||||
// 5. 排序
|
||||
leaderboard.sort((a, b) => {
|
||||
if (b.totalWordsLearned !== a.totalWordsLearned) {
|
||||
return b.totalWordsLearned - a.totalWordsLearned;
|
||||
}
|
||||
return b.accuracy - a.accuracy;
|
||||
});
|
||||
|
||||
// 6. 添加排名
|
||||
leaderboard.forEach((item, idx) => {
|
||||
item.rank = idx + 1;
|
||||
});
|
||||
|
||||
res.json(leaderboard);
|
||||
} catch (error) {
|
||||
console.error('获取排行榜失败:', error);
|
||||
res.status(500).json({ message: '获取排行榜失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 获取单词集详细信息
|
||||
router.get('/word-set/:id', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const wordSet = await WordSet.findById(req.params.id);
|
||||
if (!wordSet) {
|
||||
return res.status(404).json({ message: '未找到单词集' });
|
||||
}
|
||||
res.json(wordSet);
|
||||
} catch (error) {
|
||||
console.error('获取单词集详细信息失败:', error);
|
||||
res.status(500).json({ message: '获取单词集详细信息失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 更新单词集
|
||||
router.put('/word-set/:id', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const { name, description } = req.body;
|
||||
const wordSet = await WordSet.findByIdAndUpdate(
|
||||
req.params.id,
|
||||
{ name, description },
|
||||
{ new: true }
|
||||
);
|
||||
if (!wordSet) {
|
||||
return res.status(404).json({ message: '未找到单词集' });
|
||||
}
|
||||
res.json(wordSet);
|
||||
} catch (error) {
|
||||
console.error('更新单词集失败:', error);
|
||||
res.status(500).json({ message: '更新单词集失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 获取单词集中的单词
|
||||
router.get('/word-set/:id/words', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const wordSetId = req.params.id;
|
||||
console.log('Received request for wordSetId:', wordSetId); // 打印请求的 wordSetId
|
||||
|
||||
// 检查数据库中是否存在该单词集
|
||||
const wordSetExists = await WordSet.exists({ _id: wordSetId });
|
||||
console.log('WordSet exists:', wordSetExists); // 打印单词集是否存在
|
||||
|
||||
if (!wordSetExists) {
|
||||
console.log('WordSet not found for wordSetId:', wordSetId);
|
||||
return res.status(404).json({ message: '请求的资源不存在' });
|
||||
}
|
||||
|
||||
// 查询单词集中的单词
|
||||
const words = await Word.find({ wordSet: wordSetId });
|
||||
console.log('Words found:', words.length); // 打印找到的单词数量
|
||||
|
||||
if (!words || words.length === 0) {
|
||||
console.log('No words found for wordSetId:', wordSetId); // 打印未找到的情况
|
||||
return res.status(404).json({ message: '请求的资源不存在' });
|
||||
}
|
||||
|
||||
res.json(words);
|
||||
} catch (error) {
|
||||
console.error('获取单词失败:', error);
|
||||
res.status(500).json({ message: '获取单词失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 更新单词
|
||||
router.put('/words', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const { words } = req.body;
|
||||
console.log('Received words to update:', JSON.stringify(words, null, 2)); // 更详细的日志
|
||||
|
||||
for (const word of words) {
|
||||
const { _id, word: wordText, translation, pronunciation, example } = word;
|
||||
console.log(`Updating word ${_id}:`, { wordText, translation, pronunciation, example }); // 每个单词的更新日志
|
||||
|
||||
const updatedWord = await Word.findByIdAndUpdate(
|
||||
_id,
|
||||
{
|
||||
word: wordText,
|
||||
translation,
|
||||
pronunciation,
|
||||
example
|
||||
},
|
||||
{ new: true } // 返回更新后的文档
|
||||
);
|
||||
|
||||
console.log('Updated word result:', updatedWord); // 更新结果日志
|
||||
}
|
||||
res.json({ message: '单词更新成功' });
|
||||
} catch (error) {
|
||||
console.error('更新单词失败:', error);
|
||||
res.status(500).json({ message: '更新单词失败' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user