统计面板继续迭代

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

@@ -55,6 +55,54 @@ export interface ActiveTaskStat {
}
export class ExamTaskModel {
private static buildActiveTaskStat(input: {
taskId: string;
taskName: string;
subjectName: string;
totalScore: number;
startAt: string;
endAt: string;
report: TaskReport;
}): ActiveTaskStat {
const { report } = input;
const completionRate =
report.totalUsers > 0
? Math.round((report.completedUsers / report.totalUsers) * 100)
: 0;
const passingUsers = report.details.filter((d) => {
if (d.score === null) return false;
return d.score / input.totalScore >= 0.6;
}).length;
const passRate =
report.totalUsers > 0 ? Math.round((passingUsers / report.totalUsers) * 100) : 0;
const excellentUsers = report.details.filter((d) => {
if (d.score === null) return false;
return d.score / input.totalScore >= 0.8;
}).length;
const excellentRate =
report.totalUsers > 0
? Math.round((excellentUsers / report.totalUsers) * 100)
: 0;
return {
taskId: input.taskId,
taskName: input.taskName,
subjectName: input.subjectName,
totalUsers: report.totalUsers,
completedUsers: report.completedUsers,
completionRate,
passRate,
excellentRate,
startAt: input.startAt,
endAt: input.endAt,
};
}
static async findAll(): Promise<(TaskWithSubject & {
completedUsers: number;
passRate: number;
@@ -178,6 +226,98 @@ export class ExamTaskModel {
return stats;
}
static async getHistoryTasksWithStatsPaged(
page: number,
limit: number,
): Promise<{ data: ActiveTaskStat[]; total: number }> {
const now = new Date().toISOString();
const offset = (page - 1) * limit;
const totalRow = await get(
`SELECT COUNT(*) as total FROM exam_tasks t WHERE t.end_at < ?`,
[now],
);
const total = Number(totalRow?.total || 0);
const tasks = await all(
`
SELECT
t.id, t.name as taskName, s.name as subjectName, s.total_score as totalScore,
t.start_at as startAt, t.end_at as endAt
FROM exam_tasks t
JOIN exam_subjects s ON t.subject_id = s.id
WHERE t.end_at < ?
ORDER BY t.end_at DESC
LIMIT ? OFFSET ?
`,
[now, limit, offset],
);
const data: ActiveTaskStat[] = [];
for (const task of tasks) {
const report = await this.getReport(task.id);
data.push(
this.buildActiveTaskStat({
taskId: task.id,
taskName: task.taskName,
subjectName: task.subjectName,
totalScore: Number(task.totalScore) || 0,
startAt: task.startAt,
endAt: task.endAt,
report,
}),
);
}
return { data, total };
}
static async getUpcomingTasksWithStatsPaged(
page: number,
limit: number,
): Promise<{ data: ActiveTaskStat[]; total: number }> {
const now = new Date().toISOString();
const offset = (page - 1) * limit;
const totalRow = await get(
`SELECT COUNT(*) as total FROM exam_tasks t WHERE t.start_at > ?`,
[now],
);
const total = Number(totalRow?.total || 0);
const tasks = await all(
`
SELECT
t.id, t.name as taskName, s.name as subjectName, s.total_score as totalScore,
t.start_at as startAt, t.end_at as endAt
FROM exam_tasks t
JOIN exam_subjects s ON t.subject_id = s.id
WHERE t.start_at > ?
ORDER BY t.start_at ASC
LIMIT ? OFFSET ?
`,
[now, limit, offset],
);
const data: ActiveTaskStat[] = [];
for (const task of tasks) {
const report = await this.getReport(task.id);
data.push(
this.buildActiveTaskStat({
taskId: task.id,
taskName: task.taskName,
subjectName: task.subjectName,
totalScore: Number(task.totalScore) || 0,
startAt: task.startAt,
endAt: task.endAt,
report,
}),
);
}
return { data, total };
}
static async findById(id: string): Promise<ExamTask | null> {
const sql = `SELECT id, name, subject_id as subjectId, start_at as startAt, end_at as endAt, created_at as createdAt, selection_config as selectionConfig FROM exam_tasks WHERE id = ?`;
const row = await get(sql, [id]);
@@ -505,4 +645,4 @@ export class ExamTaskModel {
timeLimitMinutes: row.timeLimitMinutes
}));
}
}
}