92 lines
4.4 KiB
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,
|
|
]);
|
|
}
|
|
}
|