统计面板继续迭代

This commit is contained in:
2025-12-23 00:35:57 +08:00
parent 24984796cf
commit e2a1555b46
15 changed files with 875 additions and 251 deletions

View File

@@ -2,6 +2,12 @@ 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) {
@@ -127,6 +133,125 @@ export class AdminController {
}
}
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(