import { Request, Response } from 'express'; import { SystemConfigModel } from '../models'; import { query } from '../database'; const toPositiveInt = (value: unknown, defaultValue: number) => { const n = Number(value); if (!Number.isFinite(n) || n <= 0) return defaultValue; return Math.floor(n); }; export class AdminController { // 管理员登录 static async login(req: Request, res: Response) { try { const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ success: false, message: '用户名和密码不能为空' }); } // 直接验证用户名和密码,不依赖数据库 // 初始管理员账号:admin / admin123 const isValid = username === 'admin' && password === 'admin123'; if (!isValid) { return res.status(401).json({ success: false, message: '用户名或密码错误' }); } // 直接返回成功,不生成真实的JWT token res.json({ success: true, message: '登录成功', data: { username, token: 'admin-token' // 简化处理 } }); } catch (error: any) { console.error('管理员登录失败:', error); res.status(500).json({ success: false, message: error.message || '登录失败' }); } } // 获取抽题配置 static async getQuizConfig(req: Request, res: Response) { try { const config = await SystemConfigModel.getQuizConfig(); res.json({ success: true, data: config }); } catch (error: any) { console.error('获取抽题配置失败:', error); res.status(500).json({ success: false, message: error.message || '获取抽题配置失败' }); } } // 更新抽题配置 static async updateQuizConfig(req: Request, res: Response) { try { const { singleRatio, multipleRatio, judgmentRatio, textRatio, totalScore } = req.body; const config = { singleRatio, multipleRatio, judgmentRatio, textRatio, totalScore }; await SystemConfigModel.updateQuizConfig(config); res.json({ success: true, message: '抽题配置更新成功' }); } catch (error: any) { console.error('更新抽题配置失败:', error); res.status(500).json({ success: false, message: error.message || '更新抽题配置失败' }); } } // 获取统计数据 static async getStatistics(req: Request, res: Response) { try { const { QuizModel } = await import('../models'); const statistics = await QuizModel.getStatistics(); res.json({ success: true, data: statistics }); } catch (error: any) { console.error('获取统计数据失败:', error); res.status(500).json({ success: false, message: error.message || '获取统计数据失败' }); } } // 获取活跃任务统计数据 static async getActiveTasksStats(req: Request, res: Response) { try { const { ExamTaskModel } = await import('../models/examTask'); const stats = await ExamTaskModel.getActiveTasksWithStats(); res.json({ success: true, data: stats }); } catch (error: any) { console.error('获取活跃任务统计数据失败:', error); res.status(500).json({ success: false, message: error.message || '获取活跃任务统计数据失败' }); } } static async getHistoryTaskStats(req: Request, res: Response) { try { const page = toPositiveInt(req.query.page, 1); const limit = toPositiveInt(req.query.limit, 5); const { ExamTaskModel } = await import('../models/examTask'); const result = await ExamTaskModel.getHistoryTasksWithStatsPaged(page, limit); res.json({ success: true, data: result.data, pagination: { page, limit, total: result.total, pages: Math.ceil(result.total / limit), }, }); } catch (error: any) { console.error('获取历史任务统计失败:', error); res.status(500).json({ success: false, message: error.message || '获取历史任务统计失败', }); } } static async getUpcomingTaskStats(req: Request, res: Response) { try { const page = toPositiveInt(req.query.page, 1); const limit = toPositiveInt(req.query.limit, 5); const { ExamTaskModel } = await import('../models/examTask'); const result = await ExamTaskModel.getUpcomingTasksWithStatsPaged(page, limit); res.json({ success: true, data: result.data, pagination: { page, limit, total: result.total, pages: Math.ceil(result.total / limit), }, }); } catch (error: any) { console.error('获取未开始任务统计失败:', error); res.status(500).json({ success: false, message: error.message || '获取未开始任务统计失败', }); } } static async getDashboardOverview(req: Request, res: Response) { try { const { QuizModel } = await import('../models'); const statistics = await QuizModel.getStatistics(); const now = new Date().toISOString(); const [categoryRows, activeSubjectRow, statusRow] = await Promise.all([ query( ` SELECT COALESCE(category, '未分类') as category, COUNT(*) as count FROM questions GROUP BY COALESCE(category, '未分类') ORDER BY count DESC `, ), query( ` SELECT COUNT(DISTINCT subject_id) as total FROM exam_tasks WHERE start_at <= ? AND end_at >= ? `, [now, now], ).then((rows: any[]) => rows[0]), query( ` SELECT SUM(CASE WHEN end_at < ? THEN 1 ELSE 0 END) as completed, SUM(CASE WHEN start_at <= ? AND end_at >= ? THEN 1 ELSE 0 END) as ongoing, SUM(CASE WHEN start_at > ? THEN 1 ELSE 0 END) as notStarted FROM exam_tasks `, [now, now, now, now], ).then((rows: any[]) => rows[0]), ]); const questionCategoryStats = (categoryRows as any[]).map((r) => ({ category: r.category, count: Number(r.count) || 0, })); res.json({ success: true, data: { totalUsers: statistics.totalUsers, activeSubjectCount: Number(activeSubjectRow?.total) || 0, questionCategoryStats, taskStatusDistribution: { completed: Number(statusRow?.completed) || 0, ongoing: Number(statusRow?.ongoing) || 0, notStarted: Number(statusRow?.notStarted) || 0, }, }, }); } catch (error: any) { console.error('获取仪表盘概览失败:', error); res.status(500).json({ success: false, message: error.message || '获取仪表盘概览失败', }); } } static async getUserStats(req: Request, res: Response) { try { const rows = await query( ` SELECT u.id as userId, u.name as userName, COUNT(qr.id) as totalRecords, COALESCE(ROUND(AVG(qr.total_score), 2), 0) as averageScore, COALESCE(MAX(qr.total_score), 0) as highestScore, COALESCE(MIN(qr.total_score), 0) as lowestScore FROM users u LEFT JOIN quiz_records qr ON u.id = qr.user_id GROUP BY u.id ORDER BY totalRecords DESC, u.created_at DESC `, ); const data = (rows as any[]).map((row) => ({ userId: row.userId, userName: row.userName, totalRecords: Number(row.totalRecords) || 0, averageScore: Number(row.averageScore) || 0, highestScore: Number(row.highestScore) || 0, lowestScore: Number(row.lowestScore) || 0, })); res.json({ success: true, data, }); } catch (error: any) { console.error('获取用户统计失败:', error); res.status(500).json({ success: false, message: error.message || '获取用户统计失败', }); } } static async getSubjectStats(req: Request, res: Response) { try { const rows = await query( ` SELECT s.id as subjectId, s.name as subjectName, COUNT(qr.id) as totalRecords, COALESCE(ROUND(AVG(qr.total_score), 2), 0) as averageScore, COALESCE(ROUND(AVG( CASE WHEN qr.total_count > 0 THEN (qr.correct_count * 100.0 / qr.total_count) ELSE 0 END ), 2), 0) as averageCorrectRate FROM exam_subjects s LEFT JOIN quiz_records qr ON s.id = qr.subject_id GROUP BY s.id ORDER BY totalRecords DESC, s.created_at DESC `, ); const data = (rows as any[]).map((row) => ({ subjectId: row.subjectId, subjectName: row.subjectName, totalRecords: Number(row.totalRecords) || 0, averageScore: Number(row.averageScore) || 0, averageCorrectRate: Number(row.averageCorrectRate) || 0, })); res.json({ success: true, data, }); } catch (error: any) { console.error('获取科目统计失败:', error); res.status(500).json({ success: false, message: error.message || '获取科目统计失败', }); } } static async getTaskStats(req: Request, res: Response) { try { const rows = await query( ` SELECT t.id as taskId, t.name as taskName, COALESCE(rs.totalRecords, 0) as totalRecords, COALESCE(rs.averageScore, 0) as averageScore, COALESCE(us.totalUsers, 0) as totalUsers, COALESCE(rs.completedUsers, 0) as completedUsers FROM exam_tasks t LEFT JOIN ( SELECT task_id, COUNT(*) as totalRecords, ROUND(AVG(total_score), 2) as averageScore, COUNT(DISTINCT user_id) as completedUsers FROM quiz_records WHERE task_id IS NOT NULL GROUP BY task_id ) rs ON rs.task_id = t.id LEFT JOIN ( SELECT task_id, COUNT(DISTINCT user_id) as totalUsers FROM exam_task_users GROUP BY task_id ) us ON us.task_id = t.id ORDER BY t.created_at DESC `, ); const data = rows.map((row: any) => { const totalUsers = Number(row.totalUsers) || 0; const completedUsers = Number(row.completedUsers) || 0; const completionRate = totalUsers > 0 ? (completedUsers / totalUsers) * 100 : 0; return { taskId: row.taskId, taskName: row.taskName, totalRecords: Number(row.totalRecords) || 0, averageScore: Number(row.averageScore) || 0, completionRate, }; }); res.json({ success: true, data, }); } catch (error: any) { console.error('获取任务统计失败:', error); res.status(500).json({ success: false, message: error.message || '获取任务统计失败', }); } } // 修改管理员密码 static async updatePassword(req: Request, res: Response) { try { const { username, oldPassword, newPassword } = req.body; if (!username || !oldPassword || !newPassword) { return res.status(400).json({ success: false, message: '参数不完整' }); } // 验证旧密码 const isValid = await SystemConfigModel.validateAdminLogin(username, oldPassword); if (!isValid) { return res.status(401).json({ success: false, message: '原密码错误' }); } // 更新密码 await SystemConfigModel.updateAdminPassword(username, newPassword); res.json({ success: true, message: '密码修改成功' }); } catch (error: any) { console.error('修改密码失败:', error); res.status(500).json({ success: false, message: error.message || '修改密码失败' }); } } // 获取所有配置(管理员用) static async getAllConfigs(req: Request, res: Response) { try { const configs = await SystemConfigModel.getAllConfigs(); res.json({ success: true, data: configs }); } catch (error: any) { console.error('获取配置失败:', error); res.status(500).json({ success: false, message: error.message || '获取配置失败' }); } } }