QuickQuiz/app/Http/Controllers/Api/Admin/DashboardController.php

92 lines
4.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Models\Question;
use App\Models\QuestionBank;
use App\Models\QuizAttempt;
use App\Models\User;
use App\Models\WrongQuestion;
use App\Support\ApiResponse;
use hg\apidoc\annotation as Apidoc;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
#[Apidoc\Group('后台')]
#[Apidoc\Title('控制台')]
#[Apidoc\RouteMiddleware(['jwt.auth'])]
final class DashboardController extends Controller
{
#[Apidoc\Title('控制台概览')]
#[Apidoc\Url('/admin/dashboard')]
#[Apidoc\Method('GET')]
#[Apidoc\RouteMiddleware(['permission:dashboard'])]
public function index(Request $request): JsonResponse
{
$user = $request->user();
$bankScope = QuestionBank::query()->whereNull('deleted_at');
if ($user->role !== 'admin') {
$bankScope->where('owner_id', $user->id);
}
$bankIds = (clone $bankScope)->pluck('id');
$questionScope = Question::query()->whereIn('question_bank_id', $bankIds);
$todayAnswered = DB::table('quiz_attempt_questions')
->join('quiz_attempts', 'quiz_attempts.id', '=', 'quiz_attempt_questions.quiz_attempt_id')
->join('questions', 'questions.id', '=', 'quiz_attempt_questions.question_id')
->whereIn('questions.question_bank_id', $bankIds)
->whereDate('quiz_attempt_questions.answered_at', today())
->whereNotNull('quiz_attempt_questions.answered_at');
$answered = (clone $todayAnswered)->count();
$correct = (clone $todayAnswered)->where('quiz_attempt_questions.is_correct', true)->count();
$trend = DB::table('quiz_attempt_questions')
->join('questions', 'questions.id', '=', 'quiz_attempt_questions.question_id')
->whereIn('questions.question_bank_id', $bankIds)
->where('quiz_attempt_questions.answered_at', '>=', now()->subDays(6)->startOfDay())
->whereNotNull('quiz_attempt_questions.answered_at')
->selectRaw('date(quiz_attempt_questions.answered_at) as day, count(*) as answered_count')
->groupBy('day')
->orderBy('day')
->get();
$classRanking = DB::table('classes')
->leftJoin('class_members', 'class_members.class_id', '=', 'classes.id')
->leftJoin('quiz_attempts', 'quiz_attempts.user_id', '=', 'class_members.user_id')
->whereNull('classes.deleted_at')
->when($user->role !== 'admin', fn ($query) => $query->where('classes.owner_id', $user->id))
->selectRaw('classes.id, classes.name, count(distinct class_members.user_id) as members_count, count(distinct quiz_attempts.id) as attempts')
->groupBy('classes.id', 'classes.name')
->orderByDesc('attempts')
->limit(5)
->get();
$recentAttempts = QuizAttempt::query()
->join('users', 'users.id', '=', 'quiz_attempts.user_id')
->leftJoin('question_banks', 'question_banks.id', '=', 'quiz_attempts.question_bank_id')
->when($user->role !== 'admin', fn ($query) => $query->whereIn('quiz_attempts.question_bank_id', $bankIds))
->selectRaw('quiz_attempts.id, users.name as user_name, coalesce(question_banks.name, "固定试卷") as bank_name, quiz_attempts.mode, quiz_attempts.total_questions, quiz_attempts.correct_count, quiz_attempts.started_at')
->latest('quiz_attempts.started_at')
->limit(8)
->get();
return ApiResponse::success([
'stats' => [
'banks' => (clone $bankScope)->count(),
'questions' => (clone $questionScope)->count(),
'today_answered' => $answered,
'accuracy' => $answered > 0 ? round($correct / $answered * 100, 2) : 0,
'users' => $user->role === 'admin' ? User::query()->count() : null,
'wrong_questions' => WrongQuestion::query()
->join('questions', 'questions.id', '=', 'wrong_questions.question_id')
->whereIn('questions.question_bank_id', $bankIds)
->whereNull('wrong_questions.mastered_at')
->count(),
],
'trend' => $trend,
'class_ranking' => $classRanking,
'recent_attempts' => $recentAttempts,
]);
}
}