diff --git a/api/controllers/quizController.ts b/api/controllers/quizController.ts index f5ea83f..cb823fc 100644 --- a/api/controllers/quizController.ts +++ b/api/controllers/quizController.ts @@ -163,12 +163,24 @@ export class QuizController { const result = await QuizModel.submitQuiz({ userId, answers: processedAnswers }); if (subjectId || taskId) { + let finalSubjectId: string | null = subjectId || null; + const finalTaskId: string | null = taskId || null; + + // 任务考试场景下,如果前端未传subjectId,则从任务中反查 + if (!finalSubjectId && finalTaskId) { + const { ExamTaskModel } = await import('../models/examTask'); + const task = await ExamTaskModel.findById(finalTaskId); + if (task?.subjectId) { + finalSubjectId = task.subjectId; + } + } + const sql = ` UPDATE quiz_records SET subject_id = ?, task_id = ? WHERE id = ? `; - await import('../database').then(({ run }) => run(sql, [subjectId || null, taskId || null, result.record.id])); + await import('../database').then(({ run }) => run(sql, [finalSubjectId, finalTaskId, result.record.id])); } res.json({ diff --git a/api/models/quiz.ts b/api/models/quiz.ts index d925e3e..07571b9 100644 --- a/api/models/quiz.ts +++ b/api/models/quiz.ts @@ -140,11 +140,13 @@ export class QuizModel { const recordsSql = ` SELECT r.id, r.user_id as userId, r.total_score as totalScore, r.correct_count as correctCount, r.total_count as totalCount, r.score_percentage as scorePercentage, r.status, r.created_at as createdAt, - r.subject_id as subjectId, s.name as subjectName, + COALESCE(r.subject_id, t.subject_id) as subjectId, + COALESCE(s.name, ts.name) as subjectName, r.task_id as taskId, t.name as taskName FROM quiz_records r - LEFT JOIN exam_subjects s ON r.subject_id = s.id LEFT JOIN exam_tasks t ON r.task_id = t.id + LEFT JOIN exam_subjects s ON r.subject_id = s.id + LEFT JOIN exam_subjects ts ON t.subject_id = ts.id WHERE r.user_id = ? ORDER BY r.created_at DESC LIMIT ? OFFSET ? @@ -169,11 +171,16 @@ export class QuizModel { SELECT r.id, r.user_id as userId, u.name as userName, u.phone as userPhone, r.total_score as totalScore, r.correct_count as correctCount, r.total_count as totalCount, r.score_percentage as scorePercentage, r.status, - r.created_at as createdAt, r.subject_id as subjectId, s.name as subjectName, - r.task_id as taskId + r.created_at as createdAt, + COALESCE(r.subject_id, t.subject_id) as subjectId, + COALESCE(s.name, ts.name) as subjectName, + r.task_id as taskId, + t.name as taskName FROM quiz_records r JOIN users u ON r.user_id = u.id + LEFT JOIN exam_tasks t ON r.task_id = t.id LEFT JOIN exam_subjects s ON r.subject_id = s.id + LEFT JOIN exam_subjects ts ON t.subject_id = ts.id ORDER BY r.created_at DESC LIMIT ? OFFSET ? `; diff --git a/data/survey.db b/data/survey.db index 7b37a2b..b43fe6d 100644 Binary files a/data/survey.db and b/data/survey.db differ diff --git a/package.json b/package.json index bd745a5..56ae8d3 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "build": "tsc && vite build", "preview": "vite preview", "start": "node dist/api/server.js", - "test": "node --import tsx --test test/admin-task-stats.test.ts test/question-text-import.test.ts test/swipe-detect.test.ts test/user-tasks.test.ts", + "test": "node --import tsx --test test/admin-task-stats.test.ts test/question-text-import.test.ts test/swipe-detect.test.ts test/user-tasks.test.ts test/user-records-subjectname.test.ts", "check": "tsc --noEmit" }, "dependencies": { diff --git a/src/layouts/AdminLayout.tsx b/src/layouts/AdminLayout.tsx index eb595b2..c13337a 100644 --- a/src/layouts/AdminLayout.tsx +++ b/src/layouts/AdminLayout.tsx @@ -128,7 +128,7 @@ const AdminLayout = ({ children }: { children: React.ReactNode }) => {
- 欢迎,{admin?.username} + 欢迎您,{admin?.username} { d="M12 3.6l2.63 5.33 5.89.86-4.26 4.15 1.01 5.86L12 17.69 6.73 19.8l1.01-5.86-4.26-4.15 5.89-.86L12 3.6z" fill="currentColor" stroke="none" + className="text-yellow-500" /> ); } diff --git a/test/user-records-subjectname.test.ts b/test/user-records-subjectname.test.ts new file mode 100644 index 0000000..bc1681b --- /dev/null +++ b/test/user-records-subjectname.test.ts @@ -0,0 +1,92 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { randomUUID } from 'node:crypto'; + +process.env.NODE_ENV = 'test'; +process.env.DB_PATH = ':memory:'; + +const jsonFetch = async ( + baseUrl: string, + path: string, + options?: { method?: string; body?: unknown }, +) => { + const res = await fetch(`${baseUrl}${path}`, { + method: options?.method ?? 'GET', + headers: options?.body ? { 'Content-Type': 'application/json' } : undefined, + body: options?.body ? JSON.stringify(options.body) : undefined, + }); + + const text = await res.text(); + let json: any = null; + try { + json = text ? JSON.parse(text) : null; + } catch { + json = null; + } + return { status: res.status, json, text }; +}; + +test('用户答题记录接口应返回subjectName(任务记录可从任务反推科目)', async () => { + const { initDatabase, run } = await import('../api/database'); + await initDatabase(); + + const { app } = await import('../api/server'); + const server = app.listen(0); + + try { + const addr = server.address(); + assert.ok(addr && typeof addr === 'object'); + const baseUrl = `http://127.0.0.1:${addr.port}`; + + const userId = randomUUID(); + const subjectId = randomUUID(); + const taskId = randomUUID(); + const recordId = randomUUID(); + + await run(`INSERT INTO users (id, name, phone, password) VALUES (?, ?, ?, ?)`, [ + userId, + '测试用户', + '13800138111', + '', + ]); + + await run( + `INSERT INTO exam_subjects (id, name, type_ratios, category_ratios, total_score, duration_minutes) + VALUES (?, ?, ?, ?, ?, ?)`, + [ + subjectId, + '测试科目-任务关联', + JSON.stringify({ single: 100 }), + JSON.stringify({ 通用: 100 }), + 100, + 60, + ], + ); + + const now = new Date().toISOString(); + await run( + `INSERT INTO exam_tasks (id, name, subject_id, start_at, end_at, selection_config) + VALUES (?, ?, ?, ?, ?, ?)`, + [taskId, '测试任务', subjectId, now, now, null], + ); + + // 模拟旧数据:quiz_records.subject_id 为空,仅有 task_id + await run( + `INSERT INTO quiz_records (id, user_id, subject_id, task_id, total_score, correct_count, total_count, created_at, score_percentage, status) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [recordId, userId, null, taskId, 80, 16, 20, now, 80, '优秀'], + ); + + const res = await jsonFetch(baseUrl, `/api/quiz/records/${userId}`); + assert.equal(res.status, 200); + assert.equal(res.json?.success, true); + assert.ok(Array.isArray(res.json?.data)); + + const row = (res.json?.data as any[]).find((r) => r.id === recordId); + assert.ok(row); + assert.equal(row.taskName, '测试任务'); + assert.equal(row.subjectName, '测试科目-任务关联'); + } finally { + server.close(); + } +});