import { v4 as uuidv4 } from 'uuid'; import { get, query, run, all } from '../database'; export interface ExamTask { id: string; name: string; subjectId: string; startAt: string; endAt: string; createdAt: string; } export interface ExamTaskUser { id: string; taskId: string; userId: string; createdAt: string; } export interface TaskWithSubject extends ExamTask { subjectName: string; userCount: number; } export interface TaskReport { taskId: string; taskName: string; subjectName: string; totalUsers: number; completedUsers: number; averageScore: number; topScore: number; lowestScore: number; details: Array<{ userId: string; userName: string; userPhone: string; score: number | null; completedAt: string | null; }>; } export class ExamTaskModel { static async findAll(): Promise { const sql = ` SELECT t.id, t.name, t.subject_id as subjectId, t.start_at as startAt, t.end_at as endAt, t.created_at as createdAt, s.name as subjectName, COUNT(DISTINCT etu.user_id) as userCount FROM exam_tasks t JOIN exam_subjects s ON t.subject_id = s.id LEFT JOIN exam_task_users etu ON t.id = etu.task_id GROUP BY t.id ORDER BY t.created_at DESC `; return query(sql); } static async findById(id: string): Promise { const sql = `SELECT id, name, subject_id as subjectId, start_at as startAt, end_at as endAt, created_at as createdAt FROM exam_tasks WHERE id = ?`; const row = await get(sql, [id]); return row || null; } static async create(data: { name: string; subjectId: string; startAt: string; endAt: string; userIds: string[]; }): Promise { if (!data.name.trim()) throw new Error('任务名称不能为空'); if (!data.userIds.length) throw new Error('至少选择一位用户'); const subject = await import('./examSubject').then(({ ExamSubjectModel }) => ExamSubjectModel.findById(data.subjectId)); if (!subject) throw new Error('科目不存在'); const id = uuidv4(); const sqlTask = ` INSERT INTO exam_tasks (id, name, subject_id, start_at, end_at) VALUES (?, ?, ?, ?, ?) `; const sqlTaskUser = ` INSERT INTO exam_task_users (id, task_id, user_id) VALUES (?, ?, ?) `; await run(sqlTask, [id, data.name.trim(), data.subjectId, data.startAt, data.endAt]); for (const userId of data.userIds) { await run(sqlTaskUser, [uuidv4(), id, userId]); } return (await this.findById(id)) as ExamTask; } static async update(id: string, data: { name: string; subjectId: string; startAt: string; endAt: string; userIds: string[]; }): Promise { const existing = await this.findById(id); if (!existing) throw new Error('任务不存在'); if (!data.name.trim()) throw new Error('任务名称不能为空'); if (!data.userIds.length) throw new Error('至少选择一位用户'); const subject = await import('./examSubject').then(({ ExamSubjectModel }) => ExamSubjectModel.findById(data.subjectId)); if (!subject) throw new Error('科目不存在'); await run(`UPDATE exam_tasks SET name = ?, subject_id = ?, start_at = ?, end_at = ? WHERE id = ?`, [ data.name.trim(), data.subjectId, data.startAt, data.endAt, id ]); await run(`DELETE FROM exam_task_users WHERE task_id = ?`, [id]); for (const userId of data.userIds) { await run(`INSERT INTO exam_task_users (id, task_id, user_id) VALUES (?, ?, ?)`, [uuidv4(), id, userId]); } return (await this.findById(id)) as ExamTask; } static async delete(id: string): Promise { const existing = await this.findById(id); if (!existing) throw new Error('任务不存在'); await run(`DELETE FROM exam_tasks WHERE id = ?`, [id]); } static async getReport(taskId: string): Promise { const task = await this.findById(taskId); if (!task) throw new Error('任务不存在'); const subject = await import('./examSubject').then(({ ExamSubjectModel }) => ExamSubjectModel.findById(task.subjectId)); if (!subject) throw new Error('科目不存在'); const sqlUsers = ` SELECT u.id as userId, u.name as userName, u.phone as userPhone, qr.total_score as score, qr.created_at as completedAt FROM exam_task_users etu JOIN users u ON etu.user_id = u.id LEFT JOIN quiz_records qr ON u.id = qr.user_id AND qr.task_id = ? WHERE etu.task_id = ? `; const rows = await query(sqlUsers, [taskId, taskId]); const details = rows.map((r) => ({ userId: r.userId, userName: r.userName, userPhone: r.userPhone, score: r.score !== null ? r.score : null, completedAt: r.completedAt || null })); const completedUsers = details.filter((d) => d.score !== null).length; const scores = details.map((d) => d.score).filter((s) => s !== null) as number[]; return { taskId, taskName: task.name, subjectName: subject.name, totalUsers: details.length, completedUsers, averageScore: scores.length > 0 ? scores.reduce((a, b) => a + b, 0) / scores.length : 0, topScore: scores.length > 0 ? Math.max(...scores) : 0, lowestScore: scores.length > 0 ? Math.min(...scores) : 0, details }; } static async generateQuizQuestions(taskId: string, userId: string): Promise<{ questions: Awaited>; totalScore: number; timeLimitMinutes: number; }> { const task = await this.findById(taskId); if (!task) throw new Error('任务不存在'); const now = new Date(); if (now < new Date(task.startAt) || now > new Date(task.endAt)) { throw new Error('当前时间不在任务有效范围内'); } const isAssigned = await get( `SELECT 1 FROM exam_task_users WHERE task_id = ? AND user_id = ?`, [taskId, userId] ); if (!isAssigned) throw new Error('用户未被分派到此任务'); const subject = await import('./examSubject').then(({ ExamSubjectModel }) => ExamSubjectModel.findById(task.subjectId)); if (!subject) throw new Error('科目不存在'); const { QuestionModel } = await import('./question'); const questions: Awaited> = []; for (const [type, ratio] of Object.entries(subject.typeRatios)) { if (ratio <= 0) continue; const typeScore = Math.floor((ratio / 100) * subject.totalScore); const avgScore = 10; const count = Math.max(1, Math.round(typeScore / avgScore)); const categories = Object.entries(subject.categoryRatios) .filter(([, r]) => r > 0) .map(([c]) => c); const qs = await QuestionModel.getRandomQuestions(type as any, count, categories); questions.push(...qs); } const totalScore = questions.reduce((sum, q) => sum + q.score, 0); return { questions, totalScore, timeLimitMinutes: subject.timeLimitMinutes }; } static async getUserTasks(userId: string): Promise { const now = new Date().toISOString(); const rows = await all(` SELECT t.*, s.name as subjectName, s.totalScore, s.timeLimitMinutes FROM exam_tasks t INNER JOIN exam_task_users tu ON t.id = tu.task_id INNER JOIN exam_subjects s ON t.subject_id = s.id WHERE tu.user_id = ? AND t.start_at <= ? AND t.end_at >= ? ORDER BY t.start_at DESC `, [userId, now, now]); return rows.map(row => ({ id: row.id, name: row.name, subjectId: row.subject_id, startAt: row.start_at, endAt: row.end_at, createdAt: row.created_at, subjectName: row.subjectName, totalScore: row.totalScore, timeLimitMinutes: row.timeLimitMinutes })); } }