Преглед на файлове

重构分析,增加错题本逻辑

yemeishu преди 3 седмици
родител
ревизия
d82a837172

+ 0 - 1
app/Filament/AdminPanelProvider.php

@@ -33,7 +33,6 @@ class AdminPanelProvider extends PanelProvider
             ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
             ->widgets([
                 \Filament\Widgets\AccountWidget::class,
-                \Filament\Widgets\FilamentInfoWidget::class,
             ])
             ->middleware([
                 \Illuminate\Cookie\Middleware\EncryptCookies::class,

+ 11 - 0
app/Filament/Pages/ExamAnalysis.php

@@ -574,10 +574,12 @@ class ExamAnalysis extends Page
                 // 从AI分析结果中获取正确答案和判断
                 $correctAnswer = null;
                 $isCorrect = false;
+                $solution = null;
                 if (isset($analysisMap[$oq->question_number])) {
                     $analysis = $analysisMap[$oq->question_number];
                     $correctAnswer = $analysis['correct_answer'] ?? $analysis['correct_solution'] ?? null;
                     $isCorrect = $analysis['is_correct'] ?? false;
+                    $solution = $analysis['correct_solution'] ?? null;
                 }
 
                 // 显示答案对比(如果有AI分析的正确答案)
@@ -599,6 +601,7 @@ class ExamAnalysis extends Page
                     'stem' => $oq->question_text ?? '题目内容缺失',
                     'answer' => $correctAnswer ?? '', // 正确答案(从AI分析获取)
                     'reference_answer' => $correctAnswer ?? '',
+                    'solution' => $solution, // 解题步骤
                     'score_total' => $oq->score_total ?? null, // OCR题目的分数可能为空
                     'score_obtained' => $oq->score_obtained ?? null, // OCR题目的分数可能为空
                     'student_answer' => $displayAnswer, // 学生答案:未作答/空/实际答案(校准后)
@@ -738,6 +741,13 @@ class ExamAnalysis extends Page
                     ];
                 }
 
+                // 获取解题步骤solution(从Question Bank或AI分析)
+                $solution = $detail['solution'] ?? $detail['correct_solution'] ?? null;
+                if (!$solution && isset($analysisMap[$pq->question_bank_id])) {
+                    $analysis = $analysisMap[$pq->question_bank_id];
+                    $solution = $analysis['correct_solution'] ?? null;
+                }
+
                 $questions[] = [
                     'id' => $pq->id,
                     'question_number' => $pq->question_number,
@@ -748,6 +758,7 @@ class ExamAnalysis extends Page
                     'stem' => $questionText,
                     'answer' => $detail['answer'] ?? '',
                     'reference_answer' => $detail['answer'] ?? '',
+                    'solution' => $solution, // 解题步骤
                     'score_total' => $pq->score ?? 5,
                     'score_obtained' => $pq->score_obtained ?? 0,
                     'student_answer' => '老师已评分', // 隐藏学生答案,显示老师评分状态

+ 15 - 15
app/Filament/Pages/IntelligentExamGeneration.php

@@ -24,7 +24,7 @@ class IntelligentExamGeneration extends Page
     protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-document-duplicate';
     protected static ?string $navigationLabel = '智能出卷';
     protected static string|UnitEnum|null $navigationGroup = '操作';
-    protected static ?int $navigationSort = 1;
+    protected static ?int $navigationSort = 2;
 
     protected string $view = 'filament.pages.intelligent-exam-generation-simple';
 
@@ -950,7 +950,7 @@ Cache::put('generated_exam_' . $paperId, $examData, now()->addHour());
         // 3. 根据题型配比计算每种题型应选择的题目数量
         $selectedQuestions = [];
         $selectedIds = []; // 用于追踪已选题目ID
-        
+
         // 优先保证每种题型至少一题(适用于总题目数>=3的情况)
         if ($targetCount >= 3) {
             foreach (['choice', 'fill', 'answer'] as $typeKey) {
@@ -959,7 +959,7 @@ Cache::put('generated_exam_' . $paperId, $examData, now()->addHour());
                     $randomIndex = array_rand($difficultyFilteredQuestions[$typeKey]);
                     $q = $difficultyFilteredQuestions[$typeKey][$randomIndex];
                     $id = $q['id'] ?? $q['question_id'];
-                    
+
                     if (!in_array($id, $selectedIds)) {
                         $selectedQuestions[] = $q;
                         $selectedIds[] = $id;
@@ -977,14 +977,14 @@ Cache::put('generated_exam_' . $paperId, $examData, now()->addHour());
         // 根据题型配比计算每种题型应选择的题目数量
         foreach ($questionTypeRatio as $type => $ratio) {
             $typeKey = $type === '选择题' ? 'choice' : ($type === '填空题' ? 'fill' : 'answer');
-            
+
             // 计算该类型目标数量
             $targetTypeCount = floor($targetCount * $ratio / 100);
-            
+
             // 调整目标数量:如果总数>=3,需要考虑已经选了的题目
             // 简单起见,我们计算总共需要的数量,然后减去已经选了的数量
             // 但这里为了保证比例,我们还是尽量多选
-            
+
             if ($targetTypeCount <= 0) continue;
 
             if (!empty($difficultyFilteredQuestions[$typeKey])) {
@@ -994,10 +994,10 @@ Cache::put('generated_exam_' . $paperId, $examData, now()->addHour());
                     $id = $q['id'] ?? $q['question_id'];
                     return !in_array($id, $selectedIds);
                 });
-                
+
                 // 如果没有可用题目了,跳过
                 if (empty($availableQuestions)) continue;
-                
+
                 $availableCount = count($availableQuestions);
                 // 还需要选多少:目标数量 - 已选该类型的数量
                 $currentTypeCount = 0;
@@ -1006,18 +1006,18 @@ Cache::put('generated_exam_' . $paperId, $examData, now()->addHour());
                         $currentTypeCount++;
                     }
                 }
-                
+
                 $needToSelect = $targetTypeCount - $currentTypeCount;
-                
+
                 if ($needToSelect > 0) {
                     $takeCount = min($needToSelect, $availableCount, $targetCount - count($selectedQuestions));
-                    
+
                     if ($takeCount > 0) {
                         $randomKeys = array_rand($availableQuestions, $takeCount);
                         if (!is_array($randomKeys)) {
                             $randomKeys = [$randomKeys];
                         }
-                        
+
                         foreach ($randomKeys as $key) {
                             $q = $availableQuestions[$key];
                             $selectedQuestions[] = $q;
@@ -1035,16 +1035,16 @@ Cache::put('generated_exam_' . $paperId, $examData, now()->addHour());
                 $id = $q['id'] ?? $q['question_id'];
                 return !in_array($id, $selectedIds);
             });
-            
+
             if (!empty($remainingQuestions)) {
                 $needed = $targetCount - count($selectedQuestions);
                 $take = min($needed, count($remainingQuestions));
-                
+
                 $randomKeys = array_rand($remainingQuestions, $take);
                 if (!is_array($randomKeys)) {
                     $randomKeys = [$randomKeys];
                 }
-                
+
                 foreach ($randomKeys as $key) {
                     $selectedQuestions[] = $remainingQuestions[$key];
                 }

+ 33 - 2
app/Filament/Pages/MistakeBook.php

@@ -26,9 +26,9 @@ class MistakeBook extends Page
 
     protected static ?string $navigationLabel = '错题本';
 
-    protected static string|UnitEnum|null $navigationGroup = '资源';
+    protected static string|UnitEnum|null $navigationGroup = '操作';
 
-    protected static ?int $navigationSort = 6;
+    protected static ?int $navigationSort = 5;
 
     protected static ?string $slug = 'mistake-book';
 
@@ -71,6 +71,11 @@ class MistakeBook extends Page
 
     public string $actionMessageType = 'success';
 
+    // 分页
+    public int $page = 1;
+    public int $perPage = 10;
+    public int $total = 0;
+
     public function mount(Request $request): void
     {
         // 初始化用户角色检查
@@ -129,8 +134,11 @@ class MistakeBook extends Page
             $list = $service->listMistakes([
                 ...$this->filters,
                 'student_id' => $this->studentId,
+                'page' => $this->page,
+                'per_page' => $this->perPage,
             ]);
             $this->mistakes = $list['data'] ?? [];
+            $this->total = $list['meta']['total'] ?? 0;
 
             $this->summary = $service->summarize($this->studentId);
             $this->patterns = $service->getMistakePatterns($this->studentId);
@@ -151,6 +159,29 @@ class MistakeBook extends Page
         }
     }
 
+    public function gotoPage(int $page): void
+    {
+        $this->page = max(1, $page);
+        $this->loadMistakeData();
+    }
+
+    public function nextPage(): void
+    {
+        $maxPage = (int) ceil($this->total / $this->perPage);
+        if ($this->page < $maxPage) {
+            $this->page++;
+            $this->loadMistakeData();
+        }
+    }
+
+    public function prevPage(): void
+    {
+        if ($this->page > 1) {
+            $this->page--;
+            $this->loadMistakeData();
+        }
+    }
+
     public function refreshPatterns(): void
     {
         if (!$this->studentId) {

+ 1 - 1
app/Filament/Pages/StudentDashboard.php

@@ -33,7 +33,7 @@ class StudentDashboard extends Page
 
     protected static ?string $navigationLabel = '学生仪表板';
 
-    protected static ?int $navigationSort = 3;
+    protected static ?int $navigationSort = 4;
 
     protected ?string $heading = '学生仪表板';
 

+ 3 - 3
app/Filament/Pages/StudentManagement.php

@@ -27,9 +27,9 @@ class StudentManagement extends Page implements HasTable
 
     protected static ?string $navigationLabel = '师生管理';
 
-    protected static string|UnitEnum|null $navigationGroup = '管理';
+    protected static string|UnitEnum|null $navigationGroup = '操作';
 
-    protected static ?int $navigationSort = 16;
+    protected static ?int $navigationSort = 1;
 
     protected string $view = 'filament.pages.student-management';
 
@@ -52,7 +52,7 @@ class StudentManagement extends Page implements HasTable
         return '师生管理';
     }
 
-  
+
     public function filterByTeacher(?string $teacherId): void
     {
         $this->selectedTeacherId = $teacherId;

+ 12 - 7
app/Filament/Pages/UploadExamPaper.php

@@ -21,7 +21,7 @@ class UploadExamPaper extends Page
     protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-cloud-arrow-up';
     protected static ?string $navigationLabel = '上传试卷';
     protected static string|UnitEnum|null $navigationGroup = '操作';
-    protected static ?int $navigationSort = 2;
+    protected static ?int $navigationSort = 3;
     protected static ?string $slug = 'upload-exam-paper';
     protected string $view = 'filament.pages.upload-exam-paper';
 
@@ -29,7 +29,7 @@ class UploadExamPaper extends Page
     public ?string $studentId = null;
 
     // 模式选择
-    public string $mode = 'upload'; // 'upload' 或 'select_paper'
+    public string $mode = 'select_paper'; // 'upload' 或 'select_paper'
     public ?string $selectedPaperId = null;
     public array $questions = [];
     public array $gradingData = [];
@@ -51,7 +51,7 @@ class UploadExamPaper extends Page
         }
 
         $this->studentId = null;
-        $this->mode = 'upload';
+        $this->mode = 'select_paper';
         $this->selectedPaperId = null;
         $this->questionGrades = [];
     }
@@ -183,7 +183,7 @@ class UploadExamPaper extends Page
         // 如果子组件传递了 questionGrades,我们直接使用它。
         // 如果没有(比如直接在父组件调用),我们需要 convertGradingDataToQuestionGrades。
         // 但目前逻辑是子组件调用 handleSubmitFromParent 传递数据。
-        
+
         if (empty($this->questionGrades)) {
             Notification::make()
                 ->title('请至少为一道题目评分')
@@ -367,6 +367,11 @@ class UploadExamPaper extends Page
                         $kpCode = $detail['kp_code'] ?? $detail['knowledge_point_code'] ?? null;
                     }
 
+                    // 计算满分
+                    $maxScore = floatval($question['score'] ?? 0);
+                    $scoreValue = floatval($grade['score'] ?? 0);
+                    $isCorrect = $maxScore > 0 ? ($scoreValue >= $maxScore) : ($grade['is_correct'] ?? false);
+
                     $analysisQuestions[] = [
                         'question_id' => $question['question_bank_id'],
                         'question_number' => (string)$question['question_number'],
@@ -374,9 +379,9 @@ class UploadExamPaper extends Page
                         'student_answer' => $grade['student_answer'] ?? '',
                         'correct_answer' => $question['answer'] ?? '',
                         'kp_code' => $kpCode,
-                        'score_value' => $grade['score'] ?? 0,
-                        'max_score' => $question['score'],
-                        'is_correct' => $grade['is_correct'] ?? false,
+                        'score_value' => $scoreValue,
+                        'max_score' => $maxScore,
+                        'is_correct' => $isCorrect,
                         'teacher_validated' => true, // 手动评分即为教师验证
                         'ocr_confidence' => 1.0, // 手动评分置信度为1
                     ];

+ 15 - 0
app/Filament/Widgets/DashboardQuickLinks.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Filament\Widgets;
+
+use Filament\Widgets\Widget;
+
+class DashboardQuickLinks extends Widget
+{
+    protected string $view = 'filament.widgets.dashboard-quick-links';
+
+    protected int|string|array $columnSpan = 'full';
+
+    // Push quick actions to the bottom of the dashboard
+    protected static ?int $sort = 50;
+}

+ 2 - 2
app/Providers/Filament/AdminPanelProvider.php

@@ -8,6 +8,7 @@ use App\Filament\Pages\PromptManagement;
 use App\Filament\Pages\StudentDashboard;
 use App\Filament\Pages\StudentManagement;
 use App\Filament\Pages\StudentKnowledgeGraphPage;
+use App\Filament\Widgets\DashboardQuickLinks;
 use Filament\Http\Middleware\Authenticate;
 use Filament\Http\Middleware\AuthenticateSession;
 use Filament\Http\Middleware\DisableBladeIconComponents;
@@ -18,7 +19,6 @@ use Filament\PanelProvider;
 use App\Filament\Auth\Pages\CustomLogin;
 use Filament\Support\Colors\Color;
 use Filament\Widgets\AccountWidget;
-use Filament\Widgets\FilamentInfoWidget;
 use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
 use Illuminate\Cookie\Middleware\EncryptCookies;
 use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
@@ -52,8 +52,8 @@ class AdminPanelProvider extends PanelProvider
             ])
             ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\Filament\Widgets')
             ->widgets([
+                DashboardQuickLinks::class,
                 AccountWidget::class,
-                FilamentInfoWidget::class,
             ])
             ->renderHook('panels::head.end', fn (): string =>
                 view('filament.layout.vite-styles')->render() . view('filament.layout.vite-scripts')->render()

+ 368 - 0
app/Services/ExamPaperService.php

@@ -0,0 +1,368 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\OCRRecord;
+use App\Models\Paper;
+use App\Models\Student;
+use App\Models\Teacher;
+use Illuminate\Support\Facades\Log;
+
+class ExamPaperService
+{
+    protected QuestionBankService $questionBankService;
+
+    public function __construct(QuestionBankService $questionBankService)
+    {
+        $this->questionBankService = $questionBankService;
+    }
+
+    /**
+     * 获取所有老师列表
+     */
+    public function getTeachers(?string $currentTeacherId = null): array
+    {
+        try {
+            $query = Teacher::query()
+                ->leftJoin('users as u', 'teachers.teacher_id', '=', 'u.user_id')
+                ->select(
+                    'teachers.teacher_id',
+                    'teachers.name',
+                    'teachers.subject',
+                    'u.username',
+                    'u.email'
+                );
+
+            // 如果指定了当前老师ID,只返回该老师
+            if ($currentTeacherId) {
+                $query->where('teachers.teacher_id', $currentTeacherId);
+            }
+
+            $teachers = $query->orderBy('teachers.name')->get();
+
+            // 检查是否有学生没有对应的老师记录
+            $teacherIds = $teachers->pluck('teacher_id')->toArray();
+            $missingTeacherIds = Student::query()
+                ->distinct()
+                ->whereNotIn('teacher_id', $teacherIds)
+                ->pluck('teacher_id')
+                ->toArray();
+
+            $teachersArray = $teachers->all();
+
+            if (!empty($missingTeacherIds)) {
+                foreach ($missingTeacherIds as $missingId) {
+                    $teachersArray[] = (object) [
+                        'teacher_id' => $missingId,
+                        'name' => '未知老师 (' . $missingId . ')',
+                        'subject' => '未知',
+                        'username' => null,
+                        'email' => null
+                    ];
+                }
+
+                usort($teachersArray, function($a, $b) {
+                    return strcmp($a->name, $b->name);
+                });
+            }
+
+            return $teachersArray;
+        } catch (\Exception $e) {
+            Log::error('ExamPaperService: 加载老师列表失败', [
+                'error' => $e->getMessage()
+            ]);
+            return [];
+        }
+    }
+
+    /**
+     * 获取指定老师的学生列表
+     */
+    public function getStudents(?string $teacherId): array
+    {
+        if (empty($teacherId)) {
+            return [];
+        }
+
+        try {
+            return Student::query()
+                ->leftJoin('users as u', 'students.student_id', '=', 'u.user_id')
+                ->where('students.teacher_id', $teacherId)
+                ->select(
+                    'students.student_id',
+                    'students.name',
+                    'students.grade',
+                    'students.class_name',
+                    'u.username',
+                    'u.email'
+                )
+                ->orderBy('students.grade')
+                ->orderBy('students.class_name')
+                ->orderBy('students.name')
+                ->get()
+                ->all();
+        } catch (\Exception $e) {
+            Log::error('ExamPaperService: 加载学生列表失败', [
+                'teacher_id' => $teacherId,
+                'error' => $e->getMessage()
+            ]);
+            return [];
+        }
+    }
+
+    /**
+     * 获取最近的记录(OCR和试卷)
+     */
+    public function getRecentRecords(?string $studentId = null, int $limit = 10): array
+    {
+        // 1. 获取OCR记录(图片上传)
+        $ocrQuery = OCRRecord::with('student');
+
+        if (!empty($studentId)) {
+            $ocrQuery->where('user_id', $studentId);
+        }
+
+        $ocrRecords = $ocrQuery->latest()->take(5)->get()
+            ->map(function($record) {
+                $studentName = $record->student?->name ?: ('学生ID: ' . $record->user_id);
+
+                return [
+                    'type' => 'ocr_upload',
+                    'id' => $record->id,
+                    'record_id' => $record->id,
+                    'paper_id' => null,
+                    'student_id' => $record->user_id,
+                    'student_name' => $studentName,
+                    'paper_type' => $record->paper_type_label,
+                    'paper_name' => $record->image_filename ?: '未命名图片',
+                    'status' => $record->status,
+                    'total_questions' => $record->total_questions,
+                    'processed_questions' => $record->processed_questions ?? 0,
+                    'created_at' => $record->created_at->format('Y-m-d H:i'),
+                    'is_completed' => $record->status === 'completed',
+                ];
+            })->toArray();
+
+        // 2. 获取所有Paper记录(包括草稿和已评分)
+        $paperQuery = Paper::with('student');
+
+        if (!empty($studentId)) {
+            $paperQuery->where('student_id', $studentId);
+        }
+
+        $allPapers = $paperQuery->latest()->take(5)->get()
+            ->map(function($paper) {
+                $type = $paper->status === 'completed' ? 'graded_paper' : 'generated';
+                $paperType = $paper->status === 'completed' ? '已评分试卷' : '系统生成试卷';
+                $iconColor = $paper->status === 'completed' ? 'text-green-500' : 'text-blue-500';
+
+                $studentName = $paper->student?->name ?: ('学生ID: ' . $paper->student_id);
+
+                return [
+                    'type' => $type,
+                    'id' => $paper->paper_id,
+                    'record_id' => null,
+                    'paper_id' => $paper->paper_id,
+                    'student_id' => $paper->student_id,
+                    'student_name' => $studentName,
+                    'paper_type' => $paperType,
+                    'paper_name' => $paper->paper_name ?? '未命名试卷',
+                    'status' => $paper->difficulty_category,
+                    'total_questions' => $paper->question_count ?? 0,
+                    'created_at' => $paper->created_at->format('Y-m-d H:i'),
+                    'is_completed' => $paper->status === 'completed',
+                    'icon_color' => $iconColor,
+                ];
+            })->toArray();
+
+        // 3. 合并并按时间排序
+        $allRecords = array_merge($ocrRecords, $allPapers);
+        usort($allRecords, function($a, $b) {
+            return strcmp($b['created_at'], $a['created_at']);
+        });
+
+        return array_slice($allRecords, 0, $limit);
+    }
+
+    /**
+     * 获取学生的试卷列表
+     */
+    public function getStudentPapers(?string $studentId): array
+    {
+        if (empty($studentId)) {
+            return [];
+        }
+
+        try {
+            $student = Student::find($studentId);
+            if (!$student) {
+                Log::warning('ExamPaperService: 未找到指定学生', ['student_id' => $studentId]);
+                return [];
+            }
+
+            return $student->papers()
+                ->withCount('questions')
+                ->orderBy('created_at', 'desc')
+                ->take(20)
+                ->get()
+                ->map(function($paper) {
+                    return [
+                        'paper_id' => $paper->paper_id,
+                        'paper_name' => $paper->paper_name ?? '未命名试卷',
+                        'total_questions' => $paper->questions_count ?? 0,
+                        'total_score' => $paper->total_score ?? 0,
+                        'created_at' => $paper->created_at->format('Y-m-d H:i'),
+                    ];
+                })
+                ->toArray();
+        } catch (\Exception $e) {
+            Log::error('ExamPaperService: 获取学生试卷列表失败', [
+                'student_id' => $studentId,
+                'error' => $e->getMessage()
+            ]);
+            return [];
+        }
+    }
+
+    /**
+     * 获取试卷题目详情
+     */
+    public function getPaperQuestions(?string $paperId): array
+    {
+        if (empty($paperId)) {
+            return [];
+        }
+
+        try {
+            // 首先检查试卷是否存在
+            $paper = Paper::where('paper_id', $paperId)->first();
+            if (!$paper) {
+                Log::warning('ExamPaperService: 未找到指定试卷', ['paper_id' => $paperId]);
+                return [];
+            }
+
+            // 使用关联关系查询题目 - 按题型和题号排序
+            $paperWithQuestions = Paper::with(['questions' => function($query) {
+                // 自定义排序:选择题、填空题、解答题的顺序
+                $query->orderByRaw("
+                    CASE question_type
+                        WHEN 'choice' THEN 1
+                        WHEN 'fill' THEN 2
+                        WHEN 'answer' THEN 3
+                        ELSE 4
+                    END
+                ")->orderBy('question_number');
+            }])->where('paper_id', $paperId)->first();
+
+            $questions = $paperWithQuestions ? $paperWithQuestions->questions : collect([]);
+
+            // 处理数据不一致的情况:如果题目为空但试卷显示有题目
+            if ($questions->isEmpty() && ($paper->question_count ?? 0) > 0) {
+                Log::warning('ExamPaperService: 试卷显示有题目但实际题目数据缺失', [
+                    'paper_id' => $paperId,
+                    'expected_questions' => $paper->question_count,
+                    'actual_questions' => 0
+                ]);
+
+                return [
+                    [
+                        'id' => 'missing_data',
+                        'question_number' => 1,
+                        'question_bank_id' => null,
+                        'question_type' => 'info',
+                        'content' => "⚠️ 数据异常:试卷显示应有 {$paper->question_count} 道题目,但未找到题目数据。这通常是试卷创建过程中断导致的。请联系管理员或重新创建试卷。",
+                        'answer' => '',
+                        'score' => 0,
+                        'is_missing_data' => true
+                    ]
+                ];
+            }
+
+            if ($questions->isEmpty()) {
+                Log::info('ExamPaperService: 试卷确实没有题目', ['paper_id' => $paperId]);
+                return [
+                    [
+                        'id' => 'no_questions',
+                        'question_number' => 1,
+                        'question_bank_id' => null,
+                        'question_type' => 'info',
+                        'content' => '该试卷暂无题目数据',
+                        'answer' => '',
+                        'score' => 0,
+                        'is_empty' => true
+                    ]
+                ];
+            }
+
+            // 获取题目详情
+            $questionIds = $questions->pluck('question_bank_id')->filter()->unique()->toArray();
+
+            if (empty($questionIds)) {
+                Log::info('ExamPaperService: 题目没有关联题库ID', ['paper_id' => $paperId]);
+                return $questions->map(function($q) {
+                    return [
+                        'id' => $q->id,
+                        'question_number' => $q->question_number,
+                        'question_bank_id' => $q->question_bank_id,
+                        'question_type' => $q->question_type,
+                        'content' => '题目内容未关联到题库',
+                        'answer' => '',
+                        'score' => $q->score ?? 5,
+                    ];
+                })->toArray();
+            }
+
+            $questionsResponse = $this->questionBankService->getQuestionsByIds($questionIds);
+            $questionDetails = collect($questionsResponse['data'] ?? [])->keyBy('id');
+
+            return $questions->map(function($q) use ($questionDetails) {
+                $detail = $questionDetails->get($q->question_bank_id);
+
+                // 获取题目内容,优先从 Question Bank 获取 stem,然后fallback
+                $questionContent = null;
+                if ($detail) {
+                    $questionContent = $detail['stem'] ?? $detail['content'] ?? $detail['question_text'] ?? null;
+                }
+
+                // 如果 Question Bank 中没有内容,尝试从本地数据库的 question_text 获取
+                if (!$questionContent && $q->question_text) {
+                    $questionContent = $q->question_text;
+                }
+
+                // 如果还是没有内容,显示友好提示
+                if (!$questionContent) {
+                    $questionContent = '⚠️ 题目内容暂未加载完整';
+                }
+
+                return [
+                    'id' => $q->id,
+                    'question_number' => $q->question_number,
+                    'question_bank_id' => $q->question_bank_id,
+                    'question_type' => $q->question_type,
+                    'content' => $questionContent,
+                    'answer' => $detail['answer'] ?? $detail['correct_answer'] ?? $q->answer ?? '',
+                    'score' => $q->score ?? 5,
+                    'kp_code' => $q->knowledge_point, // 从本地数据库获取知识点代码
+                ];
+            })->toArray();
+        } catch (\Exception $e) {
+            Log::error('ExamPaperService: 获取试卷题目失败', [
+                'paper_id' => $paperId,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString()
+            ]);
+            return [
+                [
+                    'id' => 'error',
+                    'question_number' => 1,
+                    'question_bank_id' => null,
+                    'question_type' => 'error',
+                    'content' => '获取题目数据时发生错误:' . $e->getMessage(),
+                    'answer' => '',
+                    'score' => 0,
+                    'is_error' => true
+                ]
+            ];
+        }
+    }
+}

+ 1 - 0
app/Services/MistakeBookService.php

@@ -54,6 +54,7 @@ class MistakeBookService
                 ->get($this->learningAnalyticsBase . '/api/mistake-book', $query);
 
             if ($response->successful()) {
+                info("MistakeBookService::listMistakes", [$response->json()]);
                 $body = $response->json();
                 return is_array($body) ? $body : ['data' => $body];
             }

+ 2 - 0
app/Services/QuestionBankService.php

@@ -74,6 +74,7 @@ class QuestionBankService
                 ]);
 
             if ($response->successful()) {
+                info("QuestionBankService::listQuestions", [$response->json()]);
                 return $response->json();
             }
 
@@ -154,6 +155,7 @@ class QuestionBankService
                 ->get($this->baseUrl . '/questions', $params);
 
             if ($response->successful()) {
+                info("QuestionBankService::filterQuestions", [$response->json()]);
                 return $response->json();
             }
 

+ 14 - 4
resources/views/components/exam-analysis/question-details.blade.php

@@ -119,10 +119,20 @@
                     </div>
                     @endif
 
-                    @if($question['ai_analysis']['correct_solution'])
-                    <div class="mb-2">
-                        <p class="text-xs font-medium text-green-700 mb-1">正确答案:</p>
-                        <p class="text-xs text-green-600 bg-green-100 rounded p-2">{{ $question['ai_analysis']['correct_solution'] }}</p>
+                    @if($question['ai_analysis']['correct_solution'] || isset($question['solution']))
+                    <div class="grid grid-cols-1 md:grid-cols-2 gap-2 mb-2">
+                        @if(isset($question['solution']))
+                        <div>
+                            <p class="text-xs font-medium text-blue-700 mb-1">解题步骤:</p>
+                            <p class="text-xs text-blue-600 bg-blue-50 rounded p-2">{{ $question['solution'] }}</p>
+                        </div>
+                        @endif
+                        @if($question['ai_analysis']['correct_solution'])
+                        <div>
+                            <p class="text-xs font-medium text-green-700 mb-1">正确答案:</p>
+                            <p class="text-xs text-green-600 bg-green-100 rounded p-2">{{ $question['ai_analysis']['correct_solution'] }}</p>
+                        </div>
+                        @endif
                     </div>
                     @endif
 

+ 482 - 501
resources/views/filament/pages/mistake-book.blade.php

@@ -1,572 +1,553 @@
-<div class="min-h-screen bg-[#f5f7fb] p-6">
-    <div class="max-w-7xl mx-auto space-y-6">
-        <div class="rounded-2xl bg-gradient-to-r from-sky-50 via-white to-indigo-50 border border-slate-100 shadow-sm p-6">
-            <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
-                <div class="lg:col-span-1">
-                    <p class="text-xs uppercase tracking-[0.2em] text-slate-400">MistakeBook</p>
-                    <h1 class="text-3xl font-bold text-slate-900 mt-1">错题本 · 诊断与重练</h1>
-                    <p class="text-sm text-slate-500 mt-1">完全复用上传卷子/智能出卷的师生联动:先选老师,再选学生,再刷新数据。</p>
+<div class="min-h-screen bg-gray-50 p-8">
+    {{-- 页面标题区域 --}}
+    <div class="mb-8">
+        <div class="bg-white rounded-xl shadow-sm p-6 border border-gray-200">
+            <div class="flex items-center justify-between mb-6">
+                <div>
+                    <h1 class="text-3xl font-bold text-gray-900 flex items-center">
+                        <svg class="w-8 h-8 mr-3 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path>
+                        </svg>
+                        错题本
+                    </h1>
+                    <p class="mt-2 text-sm text-gray-600 ml-11">查看学生错题记录与AI分析,生成针对性练习</p>
                 </div>
-                <div class="lg:col-span-2">
-                    <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
-                        {{-- 选择老师(老师登录时隐藏) --}}
-                        @if(!$this->isTeacher)
-                        <div class="form-control w-full">
-                            <label class="label">
-                                <span class="label-text font-medium">选择老师 <span class="text-error">*</span></span>
-                            </label>
-                            <select
-                                wire:model.live="teacherId"
-                                class="select select-bordered w-full select-lg"
-                            >
-                                <option value="">请选择老师...</option>
-                                @foreach($this->teachers as $teacher)
-                                    <option value="{{ $teacher->teacher_id }}">
-                                        {{ trim($teacher->name ?? $teacher->teacher_id) . ($teacher->subject ? " ({$teacher->subject})" : '') }}
-                                    </option>
-                                @endforeach
-                            </select>
-                        </div>
-                        @endif
-                        <div class="form-control w-full">
-                            <label class="label">
-                                <span class="label-text font-medium">选择学生 <span class="text-error">*</span></span>
-                            </label>
-                            <select
-                                wire:model.live="studentId"
-                                class="select select-bordered w-full select-lg"
-                                @if(empty($teacherId)) disabled @endif
-                            >
-                                <option value="">
-                                    @if(empty($teacherId))
-                                        请先选择老师
-                                    @else
-                                        请选择学生...
-                                    @endif
+            </div>
+
+            {{-- 选择器区域 --}}
+            <div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
+                <div class="flex flex-col gap-3 lg:flex-row lg:items-center">
+                    @if(!$this->isTeacher)
+                    <div class="form-control w-full lg:flex-1">
+                        <label class="label"><span class="label-text font-medium">选择老师</span></label>
+                        <select wire:model.live="teacherId" class="select select-bordered w-full h-11">
+                            <option value="">请选择老师...</option>
+                            @foreach($this->teachers as $teacher)
+                                <option value="{{ $teacher->teacher_id }}">
+                                    {{ trim($teacher->name ?? $teacher->teacher_id) . ($teacher->subject ? " ({$teacher->subject})" : '') }}
                                 </option>
-                                @foreach($this->students as $student)
-                                    <option value="{{ $student->student_id }}">
-                                        {{ trim($student->name ?? $student->student_id) . " ({$student->grade} - {$student->class_name})" }}
-                                    </option>
-                                @endforeach
-                            </select>
-                        </div>
+                            @endforeach
+                        </select>
                     </div>
-                    <div class="mt-3 flex items-center justify-between">
-                        <div class="text-xs text-slate-500">按照上传卷子/智能出卷同款逻辑联动师生</div>
-                        <button
-                            wire:click="loadMistakeData"
-                            class="btn btn-primary btn-md"
-                        >
-                            <span wire:loading.remove>刷新</span>
-                            <span wire:loading class="loading loading-spinner loading-xs"></span>
+                    @endif
+
+                    <div class="form-control w-full lg:flex-1">
+                        <label class="label"><span class="label-text font-medium">选择学生</span></label>
+                        <select wire:model.live="studentId" class="select select-bordered w-full h-11" @if(empty($teacherId)) disabled @endif>
+                            <option value="">{{ empty($teacherId) ? '请先选择老师' : '请选择学生...' }}</option>
+                            @foreach($this->students as $student)
+                                <option value="{{ $student->student_id }}">
+                                    {{ trim($student->name ?? $student->student_id) . " ({$student->grade} - {$student->class_name})" }}
+                                </option>
+                            @endforeach
+                        </select>
+                    </div>
+
+                    <div class="w-full lg:w-auto flex items-center lg:pt-6">
+                        <button wire:click="loadMistakeData" wire:loading.attr="disabled"
+                            class="inline-flex items-center justify-center w-full h-11 px-6 border border-transparent text-sm font-medium rounded-lg shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200">
+                            <svg wire:loading class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
+                                <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
+                                <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
+                            </svg>
+                            刷新数据
                         </button>
                     </div>
                 </div>
             </div>
         </div>
+    </div>
 
-        @if ($actionMessage)
-            <div class="alert {{ $actionMessageType === 'danger' ? 'alert-error' : ($actionMessageType === 'warning' ? 'alert-warning' : 'alert-success') }} shadow-sm">
-                <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
-                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
-                </svg>
-                <span>{{ $actionMessage }}</span>
+    {{-- 错误提示 --}}
+    @if ($errorMessage)
+        <div class="mb-8">
+            <div class="bg-red-50 border border-red-200 rounded-xl p-4">
+                <div class="flex items-start">
+                    <svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
+                        <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
+                    </svg>
+                    <div class="ml-3">
+                        <h3 class="text-sm font-medium text-red-800">加载错误</h3>
+                        <p class="mt-1 text-sm text-red-700">{{ $errorMessage }}</p>
+                    </div>
+                </div>
             </div>
-        @endif
+        </div>
+    @endif
 
-        @if ($errorMessage)
-            <div class="alert alert-error shadow-sm">
-                <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
-                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
-                </svg>
-                <div>
-                    <h3 class="font-bold">加载失败</h3>
-                    <div class="text-xs">{{ $errorMessage }}</div>
-                </div>
+    @if ($actionMessage)
+        <div class="mb-8">
+            <div class="bg-green-50 border border-green-200 rounded-xl p-4">
+                <p class="text-sm text-green-700">{{ $actionMessage }}</p>
             </div>
-        @endif
+        </div>
+    @endif
 
-        <div class="grid grid-cols-1 lg:grid-cols-4 gap-4">
-            <div class="rounded-2xl bg-white border border-slate-200 shadow-sm p-5 flex items-center gap-3">
-                <div class="w-10 h-10 rounded-full bg-sky-100 text-sky-600 flex items-center justify-center font-semibold">总</div>
-                <div>
-                    <p class="text-xs text-slate-500">总错题</p>
-                    <p class="text-3xl font-bold text-slate-900">{{ $summary['total'] ?? 0 }}</p>
+    {{-- 统计卡片 --}}
+    <div class="mb-8">
+        <div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
+            <div class="bg-white overflow-hidden shadow-sm rounded-xl border border-gray-200 hover:shadow-md transition-shadow duration-200">
+                <div class="p-6">
+                    <div class="flex items-center">
+                        <div class="w-12 h-12 bg-gradient-to-br from-indigo-500 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg">
+                            <span class="text-white font-bold">总</span>
+                        </div>
+                        <div class="ml-4">
+                            <p class="text-sm font-medium text-gray-600">总错题</p>
+                            <p class="text-2xl font-bold text-gray-900 mt-1">{{ $summary['total'] ?? 0 }}</p>
+                        </div>
+                    </div>
                 </div>
             </div>
-            <div class="rounded-2xl bg-white border border-slate-200 shadow-sm p-5 flex items-center gap-3">
-                <div class="w-10 h-10 rounded-full bg-indigo-100 text-indigo-600 flex items-center justify-center font-semibold">7d</div>
-                <div>
-                    <p class="text-xs text-slate-500">本周错题</p>
-                    <p class="text-3xl font-bold text-slate-900">{{ $summary['this_week'] ?? 0 }}</p>
+            <div class="bg-white overflow-hidden shadow-sm rounded-xl border border-gray-200 hover:shadow-md transition-shadow duration-200">
+                <div class="p-6">
+                    <div class="flex items-center">
+                        <div class="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center shadow-lg">
+                            <span class="text-white font-bold">周</span>
+                        </div>
+                        <div class="ml-4">
+                            <p class="text-sm font-medium text-gray-600">本周新增</p>
+                            <p class="text-2xl font-bold text-gray-900 mt-1">{{ $summary['this_week'] ?? 0 }}</p>
+                        </div>
+                    </div>
                 </div>
             </div>
-            <div class="rounded-2xl bg-white border border-slate-200 shadow-sm p-5 flex items-center gap-3">
-                <div class="w-10 h-10 rounded-full bg-amber-100 text-amber-600 flex items-center justify-center font-semibold">待</div>
-                <div>
-                    <p class="text-xs text-slate-500">待复习</p>
-                    <p class="text-3xl font-bold text-amber-600">{{ $summary['pending_review'] ?? 0 }}</p>
+            <div class="bg-white overflow-hidden shadow-sm rounded-xl border border-gray-200 hover:shadow-md transition-shadow duration-200">
+                <div class="p-6">
+                    <div class="flex items-center">
+                        <div class="w-12 h-12 bg-gradient-to-br from-yellow-500 to-orange-500 rounded-xl flex items-center justify-center shadow-lg">
+                            <span class="text-white font-bold">待</span>
+                        </div>
+                        <div class="ml-4">
+                            <p class="text-sm font-medium text-gray-600">待复习</p>
+                            <p class="text-2xl font-bold text-orange-600 mt-1">{{ $summary['pending_review'] ?? 0 }}</p>
+                        </div>
+                    </div>
                 </div>
             </div>
-            <div class="rounded-2xl bg-white border border-slate-200 shadow-sm p-5 flex items-center gap-3">
-                <div class="w-10 h-10 rounded-full bg-emerald-100 text-emerald-600 flex items-center justify-center font-semibold">AI</div>
-                <div class="flex-1">
-                    <p class="text-xs text-slate-500">掌握率</p>
-                    @php $masteryRate = $summary['mastery_rate'] ?? null; @endphp
-                    <p class="text-3xl font-bold text-emerald-700">
-                        {{ $masteryRate !== null ? number_format($masteryRate * 100, 1) . '%' : '--' }}
-                    </p>
-                    <div class="mt-1 h-2 bg-slate-200 rounded-full overflow-hidden">
-                        <div class="h-2 bg-emerald-500" style="width: {{ $masteryRate ? $masteryRate * 100 : 0 }}%"></div>
+            <div class="bg-white overflow-hidden shadow-sm rounded-xl border border-gray-200 hover:shadow-md transition-shadow duration-200">
+                <div class="p-6">
+                    <div class="flex items-center">
+                        <div class="w-12 h-12 bg-gradient-to-br from-green-500 to-green-600 rounded-xl flex items-center justify-center shadow-lg">
+                            <span class="text-white font-bold">%</span>
+                        </div>
+                        <div class="ml-4">
+                            <p class="text-sm font-medium text-gray-600">掌握率</p>
+                            @php $rate = $summary['mastery_rate'] ?? null; @endphp
+                            <p class="text-2xl font-bold text-green-600 mt-1">{{ $rate !== null ? number_format($rate * 100, 1) . '%' : '--' }}</p>
+                        </div>
                     </div>
                 </div>
             </div>
         </div>
+    </div>
 
-        <div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
-            <div class="lg:col-span-4 space-y-4">
-                <div class="rounded-2xl bg-white border border-slate-200 shadow-sm p-5 space-y-4">
-                    <div class="flex items-center justify-between">
-                        <h3 class="font-semibold text-slate-900">多维筛选</h3>
-                        <button class="btn btn-primary btn-sm" wire:click="applyFilters">应用</button>
+    {{-- 加载状态 --}}
+    @if ($isLoading)
+        <div class="mb-8">
+            <div class="bg-white rounded-xl shadow-sm p-12 border border-gray-200">
+                <div class="flex flex-col items-center justify-center">
+                    <svg class="animate-spin h-12 w-12 text-indigo-600" fill="none" viewBox="0 0 24 24">
+                        <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
+                        <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
+                    </svg>
+                    <p class="mt-4 text-sm text-gray-600">正在加载数据,请稍候...</p>
+                </div>
+            </div>
+        </div>
+    @else
+        {{-- 主内容区域 --}}
+        <div class="grid grid-cols-1 gap-6 xl:grid-cols-4">
+            {{-- 左侧筛选 --}}
+            <div class="xl:col-span-1">
+                <div class="bg-white shadow-sm rounded-xl border border-gray-200 sticky top-4">
+                    <div class="px-6 py-5 border-b border-gray-100">
+                        <h3 class="text-lg font-semibold text-gray-900">筛选条件</h3>
                     </div>
-                    <div class="space-y-3">
-                        <div class="form-control">
-                            <label class="label pb-1">
-                                <span class="label-text font-medium text-slate-800">知识点</span>
-                                <span class="text-xs text-slate-400">多选</span>
-                            </label>
-                            <select
-                                multiple
-                                size="6"
-                                wire:model="filters.kp_ids"
-                                class="select select-bordered w-full bg-white"
-                            >
-                                @foreach($filterOptions['knowledge_points'] as $kp)
-                                    <option value="{{ $kp['code'] }}">{{ $kp['name'] }} ({{ $kp['code'] }})</option>
-                                @endforeach
-                            </select>
-                        </div>
-                        <div class="form-control">
-                            <label class="label pb-1">
-                                <span class="label-text font-medium text-slate-800">技能</span>
-                                <span class="text-xs text-slate-400">联动</span>
-                            </label>
-                            @php
-                                $selectedKps = $filters['kp_ids'] ?? [];
-                                $skillOptions = collect($filterOptions['skills'] ?? [])
-                                    ->when(!empty($selectedKps), function($c) use ($selectedKps) {
-                                        return $c->filter(fn($item) => empty($item['kp_code']) || in_array($item['kp_code'], $selectedKps));
-                                    })
-                                    ->values()
-                                    ->all();
-                            @endphp
-                            <select
-                                multiple
-                                size="6"
-                                wire:model="filters.skill_ids"
-                                class="select select-bordered w-full bg-white"
-                            >
-                                @foreach($skillOptions as $skill)
-                                    <option value="{{ $skill['id'] }}">
-                                        {{ $skill['name'] ?? $skill['id'] }}
-                                        @if(!empty($skill['kp_code']))
-                                            · {{ $skill['kp_code'] }}
-                                        @endif
-                                    </option>
-                                @endforeach
-                            </select>
-                        </div>
+                    <div class="p-6 space-y-6">
+                        {{-- 时间范围 --}}
                         <div>
-                            <p class="label-text font-medium mb-2">错误类型</p>
-                            <div class="grid grid-cols-2 gap-2">
-                                @foreach(['计算错误', '概念错误', '方法错误', '审题错误', '表达错误'] as $type)
-                                    <label class="flex items-center gap-2 rounded-lg px-2 py-1 bg-slate-50 border border-slate-200">
-                                        <input
-                                            type="checkbox"
-                                            class="checkbox checkbox-sm checkbox-primary"
-                                            value="{{ $type }}"
-                                            wire:model="filters.error_types"
-                                        >
-                                        <span class="text-sm text-slate-700">{{ $type }}</span>
-                                    </label>
-                                @endforeach
+                            <label class="text-sm font-medium text-gray-700 mb-3 block">时间范围</label>
+                            <div class="flex gap-2">
+                                <button wire:click="$set('filters.time_range', 'last_7')"
+                                    class="flex-1 px-4 py-2 text-sm font-medium rounded-lg transition-colors {{ $filters['time_range'] === 'last_7' ? 'bg-indigo-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }}">
+                                    7天
+                                </button>
+                                <button wire:click="$set('filters.time_range', 'last_30')"
+                                    class="flex-1 px-4 py-2 text-sm font-medium rounded-lg transition-colors {{ $filters['time_range'] === 'last_30' ? 'bg-indigo-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }}">
+                                    30天
+                                </button>
                             </div>
                         </div>
+
+                        {{-- 错误类型 --}}
                         <div>
-                            <p class="label-text font-medium mb-2">时间范围</p>
-                            <div class="grid grid-cols-3 gap-2">
-                                <button class="btn btn-sm {{ $filters['time_range'] === 'last_7' ? 'btn-primary' : 'btn-outline' }}" wire:click="$set('filters.time_range', 'last_7')">7天</button>
-                                <button class="btn btn-sm {{ $filters['time_range'] === 'last_30' ? 'btn-primary' : 'btn-outline' }}" wire:click="$set('filters.time_range', 'last_30')">30天</button>
-                                <button class="btn btn-sm {{ $filters['time_range'] === 'custom' ? 'btn-primary' : 'btn-outline' }}" wire:click="$set('filters.time_range', 'custom')">自定义</button>
+                            <label class="text-sm font-medium text-gray-700 mb-3 block">错误类型</label>
+                            <div class="space-y-2">
+                                @foreach(['计算错误', '概念错误', '方法错误', '审题错误'] as $type)
+                                    <label class="flex items-center gap-3 cursor-pointer p-2 rounded-lg hover:bg-gray-50">
+                                        <input type="checkbox" value="{{ $type }}" wire:model="filters.error_types"
+                                            class="w-4 h-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
+                                        <span class="text-sm text-gray-700">{{ $type }}</span>
+                                    </label>
+                                @endforeach
                             </div>
-                            @if($filters['time_range'] === 'custom')
-                                <div class="mt-3 space-y-2">
-                                    <input type="date" class="input input-bordered w-full" wire:model="filters.start_date">
-                                    <input type="date" class="input input-bordered w-full" wire:model="filters.end_date">
-                                    <button class="btn btn-ghost btn-xs" wire:click="clearCustomRange">清空</button>
-                                </div>
-                            @endif
                         </div>
-                        <button
-                            wire:click="applyFilters"
-                            class="btn btn-primary btn-md w-full"
-                        >
-                            <span wire:loading.remove>应用筛选</span>
-                            <span wire:loading class="loading loading-spinner"></span>
+
+                        <button wire:click="applyFilters"
+                            class="w-full px-4 py-2.5 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition-colors">
+                            应用筛选
                         </button>
                     </div>
                 </div>
-
-                <div class="rounded-2xl bg-white border border-slate-200 shadow-sm p-5 space-y-3">
-                    <div class="flex items-center justify-between">
-                        <h3 class="font-semibold text-slate-900">推荐补救路径</h3>
-                        <button class="btn btn-ghost btn-sm text-indigo-600" wire:click="refreshPatterns">刷新</button>
-                    </div>
-                    @if(!empty($patterns['recommend_path']))
-                        <ul class="timeline timeline-vertical">
-                            @foreach($patterns['recommend_path'] as $step)
-                                <li>
-                                    <div class="timeline-middle">
-                                        <div class="w-2.5 h-2.5 rounded-full bg-indigo-500"></div>
-                                    </div>
-                                    <div class="timeline-end timeline-box bg-white border border-indigo-100 text-sm text-slate-700">
-                                        {{ $step['title'] ?? ($step['kp'] ?? '学习步骤') }}
-                                        @if(!empty($step['description']))
-                                            <p class="text-xs text-slate-500 mt-1">{{ $step['description'] }}</p>
-                                        @endif
-                                    </div>
-                                    <hr class="bg-indigo-200"/>
-                                </li>
-                            @endforeach
-                        </ul>
-                    @else
-                        <p class="text-sm text-slate-500">暂无推荐路径,稍后重试</p>
-                    @endif
-                </div>
             </div>
 
-            <div class="lg:col-span-8 space-y-5">
-                <div class="rounded-2xl bg-white border border-slate-200 shadow-sm p-5">
-                    <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
-                        <div>
-                            <h3 class="text-lg font-semibold text-slate-900">错题列表</h3>
-                            <p class="text-sm text-slate-500">题干、作答、AI 解析与操作</p>
-                        </div>
-                        <div class="flex flex-wrap gap-2 items-center">
-                            <button class="btn btn-md btn-secondary" wire:click="generatePracticeFromSelection">
-                                📚 基于错题生成练习
-                            </button>
-                            <div class="badge badge-outline">
-                                已选 {{ count($selectedMistakeIds) }} / {{ count($mistakes) }}
-                            </div>
+            {{-- 右侧错题列表 --}}
+            <div class="xl:col-span-3 space-y-6">
+                {{-- 错题列表 --}}
+                <div class="bg-white shadow-sm rounded-xl border border-gray-200">
+                    <div class="px-6 py-5 border-b border-gray-100 flex items-center justify-between">
+                        <h3 class="text-lg font-semibold text-gray-900 flex items-center">
+                            <svg class="w-5 h-5 mr-2 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path>
+                            </svg>
+                            错题列表 ({{ $total }})
+                        </h3>
+                        <div class="flex items-center gap-3">
+                            @if(!empty($selectedMistakeIds))
+                                <button wire:click="generatePracticeFromSelection"
+                                    class="inline-flex items-center px-4 py-2 bg-green-600 text-white text-sm font-medium rounded-lg hover:bg-green-700 transition-colors">
+                                    生成练习 ({{ count($selectedMistakeIds) }})
+                                </button>
+                            @endif
                         </div>
                     </div>
 
-                    @if ($isLoading)
-                        <div class="flex items-center justify-center py-10 text-slate-500">
-                            <span class="loading loading-spinner loading-lg mr-3"></span>
-                            正在加载错题...
-                        </div>
-                    @elseif(empty($mistakes))
-                        <div class="text-center py-12 text-slate-500">
-                            <svg class="mx-auto h-12 w-12 text-slate-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
-                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6l4 2m6-2a9 9 0 11-18 0 9 9 0 0118 0z" />
-                            </svg>
-                            <p class="mt-3">暂无错题,先选择老师和学生</p>
-                        </div>
-                    @else
-                        <div class="space-y-4 mt-4">
-                            @foreach($mistakes as $mistake)
-                                <div class="rounded-xl border border-slate-200 bg-white shadow-sm p-4 space-y-4" wire:key="mistake-{{ $mistake['id'] ?? $loop->index }}">
-                                    <div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
-                                        <div class="flex items-center gap-3">
-                                            <input
-                                                type="checkbox"
-                                                class="checkbox checkbox-primary"
+                    <div class="p-6">
+                        @if(empty($mistakes))
+                            <div class="text-center py-12">
+                                <svg class="w-16 h-16 mx-auto text-gray-300 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
+                                </svg>
+                                <p class="text-sm font-medium text-gray-900 mb-1">暂无错题数据</p>
+                                <p class="text-xs text-gray-500">请先选择学生后刷新数据</p>
+                            </div>
+                        @else
+                            <div class="space-y-4">
+                                @foreach($mistakes as $mistake)
+                                    <div class="border border-gray-200 rounded-xl p-5 hover:border-gray-300 transition-colors" wire:key="m-{{ $mistake['id'] ?? $loop->index }}">
+                                        {{-- 头部 --}}
+                                        <div class="flex items-start gap-4">
+                                            <input type="checkbox"
                                                 wire:click="toggleSelection('{{ $mistake['id'] ?? '' }}')"
                                                 @checked(in_array($mistake['id'] ?? '', $selectedMistakeIds, true))
-                                            >
-                                            <div>
-                                                <p class="text-xs text-slate-400">{{ $mistake['id'] ?? 'ID' }}</p>
-                                                <p class="text-sm text-slate-500">
-                                                    {{ $mistake['created_at'] ?? '' }}
-                                                </p>
-                                            </div>
-                                        </div>
-                                        <div class="flex flex-wrap gap-2">
-                                            @foreach(($mistake['kp_ids'] ?? []) as $kp)
-                                                <span class="badge badge-ghost">KP {{ $kp }}</span>
-                                            @endforeach
-                                            @foreach(($mistake['skill_ids'] ?? []) as $skill)
-                                                <span class="badge badge-outline badge-info">{{ $skill }}</span>
-                                            @endforeach
-                                            @if(!empty($mistake['error_type']))
-                                                <span class="badge badge-warning badge-outline">{{ $mistake['error_type'] }}</span>
-                                            @endif
-                                            @if(isset($mistake['correct']))
-                                                <span class="badge {{ $mistake['correct'] ? 'badge-success' : 'badge-error' }}">
-                                                    {{ $mistake['correct'] ? '已掌握' : '错误' }}
-                                                </span>
-                                            @endif
-                                            @if(!empty($mistake['reviewed']))
-                                                <span class="badge badge-success badge-outline">已复习</span>
-                                            @endif
-                                            @if(!empty($mistake['favorite']))
-                                                <span class="badge badge-primary badge-outline">已收藏</span>
-                                            @endif
-                                        </div>
-                                    </div>
+                                                class="mt-1 w-5 h-5 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
 
-                                    <div class="rounded-lg bg-slate-50 p-4 border border-slate-100">
-                                        <p class="text-sm font-semibold text-slate-800 mb-2">题干</p>
-                                        <div class="prose max-w-none question-content text-slate-800">
-                                            <x-math-render :content="$mistake['question']['stem'] ?? ($mistake['question']['content'] ?? '暂无题干')" class="text-base" />
-                                        </div>
-                                    </div>
+                                            <div class="flex-1 min-w-0">
+                                                {{-- 标签行 --}}
+                                                <div class="flex flex-wrap items-center gap-2 mb-4">
+                                                    <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
+                                                        #{{ $mistake['id'] ?? '' }}
+                                                    </span>
+                                                    @if(!empty($mistake['question']['question_number']))
+                                                        <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
+                                                            第{{ $mistake['question']['question_number'] }}题
+                                                        </span>
+                                                    @endif
+                                                    <span class="text-xs text-gray-500">
+                                                        @php
+                                                            $createdAt = $mistake['created_at'] ?? null;
+                                                            if ($createdAt) {
+                                                                try {
+                                                                    $date = \Carbon\Carbon::parse($createdAt);
+                                                                    echo $date->format('Y-m-d H:i');
+                                                                } catch (\Exception $e) {
+                                                                    echo $createdAt;
+                                                                }
+                                                            }
+                                                        @endphp
+                                                    </span>
+                                                    @if(!empty($mistake['error_type']))
+                                                        <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
+                                                            {{ $mistake['error_type'] }}
+                                                        </span>
+                                                    @endif
+                                                    @if(!empty($mistake['mistake_category']))
+                                                        <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-orange-100 text-orange-800">
+                                                            {{ $mistake['mistake_category'] }}
+                                                        </span>
+                                                    @endif
+                                                </div>
 
-                                    <div class="grid grid-cols-1 md:grid-cols-3 gap-3">
-                                        <div class="bg-white border border-slate-200 rounded-lg p-3">
-                                            <p class="text-xs text-slate-500 mb-1">学生作答</p>
-                                            <p class="text-sm text-slate-800 break-words">{{ $mistake['student_answer'] ?? '无' }}</p>
-                                        </div>
-                                        <div class="bg-white border border-slate-200 rounded-lg p-3">
-                                            <p class="text-xs text-slate-500 mb-1">正确答案</p>
-                                            <p class="text-sm text-emerald-700 break-words">
-                                                {{ $mistake['question']['answer'] ?? ($mistake['correct_answer'] ?? '未知') }}
-                                            </p>
-                                        </div>
-                                        <div class="bg-white border border-slate-200 rounded-lg p-3">
-                                            <p class="text-xs text-slate-500 mb-1">错误类型</p>
-                                            <p class="text-sm text-amber-700">
-                                                {{ $mistake['error_type'] ?? '未分类' }}
-                                            </p>
-                                        </div>
-                                    </div>
-
-                                    <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
-                                        <div class="bg-gradient-to-br from-amber-50 to-white border border-amber-100 rounded-lg p-4 space-y-2">
-                                            <p class="text-sm font-semibold text-amber-800">错误原因分析</p>
-                                            <p class="text-sm text-amber-700">{{ $mistake['ai_analysis']['reason'] ?? $mistake['ai_analysis'] ?? '暂无分析' }}</p>
-                                            <p class="text-sm font-semibold text-amber-800">对应技能</p>
-                                            <p class="text-sm text-amber-700">{{ $mistake['ai_analysis']['skill'] ?? ($mistake['skill_desc'] ?? '未识别') }}</p>
-                                        </div>
-                                        <div class="bg-gradient-to-br from-emerald-50 to-white border border-emerald-100 rounded-lg p-4 space-y-2">
-                                            <p class="text-sm font-semibold text-emerald-800">正确解法</p>
-                                            <p class="text-sm text-emerald-700">{{ $mistake['ai_analysis']['solution'] ?? '可向AI请求解析' }}</p>
-                                            <p class="text-sm font-semibold text-emerald-800">避免类似错误</p>
-                                            <p class="text-sm text-emerald-700">{{ $mistake['ai_analysis']['tip'] ?? ($mistake['ai_analysis']['suggestion'] ?? '加强审题与演算步骤复查') }}</p>
-                                        </div>
-                                    </div>
+                                                {{-- 题干 --}}
+                                                @if(!empty($mistake['question']['stem']))
+                                                <div class="bg-gray-50 rounded-lg p-4 mb-4 border border-gray-100">
+                                                    <p class="text-xs font-medium text-gray-500 mb-2">题干</p>
+                                                    <div class="prose prose-sm max-w-none text-gray-900">
+                                                        <x-math-render :content="$mistake['question']['stem']" />
+                                                    </div>
+                                                </div>
+                                                @endif
 
-                                    <div class="divider my-2"></div>
-                                    <div class="flex flex-wrap gap-2">
-                                        <button class="btn btn-sm btn-ghost" wire:click="toggleFavorite('{{ $mistake['id'] ?? '' }}')">
-                                            {{ !empty($mistake['favorite']) ? '取消收藏' : '收藏' }}
-                                        </button>
-                                        <button class="btn btn-sm btn-ghost" wire:click="markReviewed('{{ $mistake['id'] ?? '' }}')">
-                                            标记已复习
-                                        </button>
-                                        <button class="btn btn-sm btn-ghost" wire:click="addToRetryList('{{ $mistake['id'] ?? '' }}')">
-                                            加入重练清单
-                                        </button>
-                                        <button class="btn btn-sm btn-outline" wire:click="loadRelatedQuestions('{{ $mistake['id'] ?? '' }}')">
-                                            查看关联题
-                                        </button>
-                                    </div>
+                                                {{-- 判卷结果 + 知识点 --}}
+                                                <div class="flex flex-wrap items-center gap-3 mb-4">
+                                                    @if(!empty($mistake['mark_detected']))
+                                                        <span class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium {{ $mistake['mark_detected'] === '✓' || $mistake['mark_detected'] === '√' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' }}">
+                                                            老师判卷: {{ $mistake['mark_detected'] }}
+                                                        </span>
+                                                    @endif
+                                                    @if(isset($mistake['score']) && isset($mistake['full_score']))
+                                                        @php
+                                                            $score = floatval($mistake['score'] ?? 0);
+                                                            $fullScore = floatval($mistake['full_score'] ?? 0);
+                                                            $isFullScore = $fullScore > 0 && $score >= $fullScore;
+                                                        @endphp
+                                                        <span class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium {{ $isFullScore ? 'bg-green-100 text-green-800' : 'bg-blue-100 text-blue-800' }}">
+                                                            得分: {{ $mistake['score'] }}/{{ $mistake['full_score'] }}
+                                                            @if($isFullScore)
+                                                                ✓
+                                                            @endif
+                                                        </span>
+                                                    @endif
+                                                    @if(!empty($mistake['question']['kp_code']))
+                                                        <a href="{{ url('/admin/knowledge-point-detail') }}?kp_code={{ urlencode($mistake['question']['kp_code']) }}"
+                                                           class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium bg-indigo-100 text-indigo-800 hover:bg-indigo-200 transition-colors">
+                                                            知识点: {{ $mistake['question']['kp_code'] }}
+                                                        </a>
+                                                    @endif
+                                                    @if(!empty($mistake['skill_ids']))
+                                                        @foreach($mistake['skill_ids'] as $skillId)
+                                                            <span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-teal-100 text-teal-800">
+                                                                {{ $skillId }}
+                                                            </span>
+                                                        @endforeach
+                                                    @endif
+                                                </div>
 
-                                    @if(!empty($relatedQuestions[$mistake['id'] ?? ''] ?? []))
-                                        <div class="bg-slate-50 border border-slate-200 rounded-lg p-3 space-y-2">
-                                            <p class="text-sm font-semibold text-slate-800">关联题目</p>
-                                            <div class="grid grid-cols-1 md:grid-cols-2 gap-2">
-                                                @foreach($relatedQuestions[$mistake['id']] as $related)
-                                                    <div class="p-3 bg-white border border-slate-200 rounded-lg">
-                                                        <p class="text-xs text-slate-400 mb-1">ID: {{ $related['id'] ?? '' }}</p>
-                                                        <p class="text-sm text-slate-800 overflow-hidden max-h-14">{{ $related['stem'] ?? $related['content'] ?? '相关题目' }}</p>
-                                                        <p class="text-xs text-slate-500 mt-2">
-                                                            难度: {{ $related['difficulty'] ?? '中等' }}
-                                                            @if(!empty($related['kp_codes']))
-                                                                · KP: {{ is_array($related['kp_codes']) ? implode(',', $related['kp_codes']) : $related['kp_codes'] }}
+                                                {{-- 作答对比与解题步骤 --}}
+                                                <div class="grid grid-cols-1 lg:grid-cols-3 gap-4 mb-4">
+                                                    @if(!empty($mistake['student_answer']) && trim($mistake['student_answer']) !== '' && trim($mistake['student_answer']) !== '未作答')
+                                                    <div class="bg-red-50 rounded-lg p-4 border border-red-100">
+                                                        <p class="text-xs font-medium text-red-600 mb-2">
+                                                            学生作答
+                                                            @if(!empty($mistake['answer_confidence']))
+                                                                <span class="text-gray-400">(置信度: {{ number_format($mistake['answer_confidence'] * 100, 1) }}%)</span>
+                                                            @endif
+                                                        </p>
+                                                        <p class="text-sm text-gray-900">{{ $mistake['student_answer'] }}</p>
+                                                    </div>
+                                                    @elseif(!empty($mistake['mark_detected']) && trim($mistake['student_answer']) === '未作答')
+                                                    {{-- 老师已评分但学生未作答的题目,不显示学生作答区域 --}}
+                                                    @elseif(!empty($mistake['student_answer']) && trim($mistake['student_answer']) !== '')
+                                                    <div class="bg-red-50 rounded-lg p-4 border border-red-100">
+                                                        <p class="text-xs font-medium text-red-600 mb-2">
+                                                            学生作答
+                                                            @if(!empty($mistake['answer_confidence']))
+                                                                <span class="text-gray-400">(置信度: {{ number_format($mistake['answer_confidence'] * 100, 1) }}%)</span>
                                                             @endif
                                                         </p>
+                                                        <p class="text-sm text-gray-900">{{ $mistake['student_answer'] }}</p>
                                                     </div>
-                                                @endforeach
+                                                    @endif
+
+                                                    @if(!empty($mistake['question']['solution']))
+                                                    <div class="bg-blue-50 rounded-lg p-4 border border-blue-100">
+                                                        <p class="text-xs font-medium text-blue-600 mb-2">解题步骤</p>
+                                                        <div class="prose prose-sm max-w-none text-gray-900">
+                                                            {{ $mistake['question']['solution'] }}
+                                                        </div>
+                                                    </div>
+                                                    @endif
+
+                                                    @if(!empty($mistake['question']['answer']))
+                                                    <div class="bg-green-50 rounded-lg p-4 border border-green-100">
+                                                        <p class="text-xs font-medium text-green-600 mb-2">正确答案</p>
+                                                        <p class="text-sm text-gray-900">{{ $mistake['question']['answer'] }}</p>
+                                                    </div>
+                                                    @endif
+                                                </div>
+
+                                                {{-- AI分析 --}}
+                                                @if(!empty($mistake['ai_analysis']['reason']) || !empty($mistake['ai_analysis']['solution']) || !empty($mistake['ai_analysis']['suggestions']))
+                                                <details class="bg-amber-50 rounded-lg border border-amber-100 mb-4">
+                                                    <summary class="px-4 py-3 text-sm font-medium text-amber-800 cursor-pointer hover:bg-amber-100 rounded-lg transition-colors">
+                                                        查看AI分析
+                                                        @if(!empty($mistake['ai_analysis']['model_used']))
+                                                            <span class="text-xs text-gray-500 ml-2">({{ $mistake['ai_analysis']['model_used'] }})</span>
+                                                        @endif
+                                                    </summary>
+                                                    <div class="px-4 pb-4 text-sm text-gray-700 space-y-2">
+                                                        @if(!empty($mistake['ai_analysis']['reason']))
+                                                            <p><span class="font-medium text-gray-900">错因:</span>{{ $mistake['ai_analysis']['reason'] }}</p>
+                                                        @endif
+                                                        @if(!empty($mistake['ai_analysis']['solution']))
+                                                            <p><span class="font-medium text-gray-900">解法:</span>{{ $mistake['ai_analysis']['solution'] }}</p>
+                                                        @endif
+                                                        @if(!empty($mistake['ai_analysis']['suggestions']))
+                                                            <p><span class="font-medium text-gray-900">建议:</span>{{ $mistake['ai_analysis']['suggestions'] }}</p>
+                                                        @endif
+                                                        @if(!empty($mistake['ai_analysis']['next_steps']))
+                                                            <div>
+                                                                <span class="font-medium text-gray-900">下一步:</span>
+                                                                <ul class="list-disc list-inside mt-1">
+                                                                    @foreach($mistake['ai_analysis']['next_steps'] as $step)
+                                                                        <li>{{ is_array($step) ? json_encode($step) : $step }}</li>
+                                                                    @endforeach
+                                                                </ul>
+                                                            </div>
+                                                        @endif
+                                                    </div>
+                                                </details>
+                                                @endif
+
+                                                {{-- 操作按钮 --}}
+                                                <div class="flex flex-wrap gap-2 pt-4 border-t border-gray-100">
+                                                    <button wire:click="toggleFavorite('{{ $mistake['id'] ?? '' }}')"
+                                                        class="inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-lg {{ !empty($mistake['favorite']) ? 'bg-yellow-100 text-yellow-800' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }} transition-colors">
+                                                        {{ !empty($mistake['favorite']) ? '★ 已收藏' : '☆ 收藏' }}
+                                                    </button>
+                                                    <button wire:click="markReviewed('{{ $mistake['id'] ?? '' }}')"
+                                                        class="inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-lg {{ !empty($mistake['reviewed']) ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }} transition-colors">
+                                                        {{ !empty($mistake['reviewed']) ? '✓ 已复习' : '标记复习' }}
+                                                    </button>
+                                                    <button wire:click="loadRelatedQuestions('{{ $mistake['id'] ?? '' }}')"
+                                                        class="inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors">
+                                                        关联题
+                                                    </button>
+                                                    @if(!empty($mistake['answer_area_crop_path']))
+                                                        <a href="{{ $mistake['answer_area_crop_path'] }}" target="_blank"
+                                                           class="inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors">
+                                                            查看作答图片
+                                                        </a>
+                                                    @endif
+                                                </div>
+
+                                                {{-- 关联题 --}}
+                                                @if(!empty($relatedQuestions[$mistake['id'] ?? ''] ?? []))
+                                                    <div class="mt-4 bg-gray-50 rounded-lg p-4 border border-gray-100">
+                                                        <p class="text-xs font-medium text-gray-500 mb-3">关联题目</p>
+                                                        <div class="space-y-2">
+                                                            @foreach($relatedQuestions[$mistake['id']] as $related)
+                                                                <div class="text-sm p-3 bg-white rounded-lg border border-gray-200">
+                                                                    <p class="text-gray-900 line-clamp-2">{{ $related['stem'] ?? '题目' }}</p>
+                                                                </div>
+                                                            @endforeach
+                                                        </div>
+                                                    </div>
+                                                @endif
                                             </div>
                                         </div>
-                                    @endif
+                                    </div>
+                                @endforeach
+                            </div>
+
+                            {{-- 分页 --}}
+                            @php
+                                $maxPage = (int) ceil($total / $perPage);
+                            @endphp
+                            @if($maxPage > 1)
+                            <div class="mt-6 flex items-center justify-between border-t border-gray-100 pt-4">
+                                <div class="text-sm text-gray-500">
+                                    共 {{ $total }} 条,第 {{ $page }}/{{ $maxPage }} 页
                                 </div>
-                            @endforeach
-                        </div>
-                    @endif
+                                <div class="flex items-center gap-2">
+                                    <button wire:click="prevPage" @disabled($page <= 1)
+                                        class="px-3 py-1.5 text-sm font-medium rounded-lg {{ $page <= 1 ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }} transition-colors">
+                                        上一页
+                                    </button>
+                                    @for($i = max(1, $page - 2); $i <= min($maxPage, $page + 2); $i++)
+                                        <button wire:click="gotoPage({{ $i }})"
+                                            class="px-3 py-1.5 text-sm font-medium rounded-lg {{ $i === $page ? 'bg-indigo-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }} transition-colors">
+                                            {{ $i }}
+                                        </button>
+                                    @endfor
+                                    <button wire:click="nextPage" @disabled($page >= $maxPage)
+                                        class="px-3 py-1.5 text-sm font-medium rounded-lg {{ $page >= $maxPage ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }} transition-colors">
+                                        下一页
+                                    </button>
+                                </div>
+                            </div>
+                            @endif
+                        @endif
+                    </div>
                 </div>
 
+                {{-- 推荐练习题 --}}
                 @if(!empty($recommendations))
-                    <div class="rounded-2xl bg-white border border-slate-200 shadow-sm p-5 space-y-4">
-                        <div class="flex items-center justify-between">
-                            <div>
-                                <h3 class="text-lg font-semibold text-slate-900">重练题单</h3>
-                                <p class="text-sm text-slate-500">基于错题推荐的新题,支持导出</p>
-                            </div>
-                            <a href="{{ url('/admin/question-management') }}" class="btn btn-outline btn-sm" target="_blank">打开题库</a>
+                    <div class="bg-white shadow-sm rounded-xl border border-gray-200">
+                        <div class="px-6 py-5 border-b border-gray-100">
+                            <h3 class="text-lg font-semibold text-gray-900 flex items-center">
+                                <svg class="w-5 h-5 mr-2 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
+                                </svg>
+                                推荐练习题 ({{ count($recommendations) }})
+                            </h3>
                         </div>
-                        <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
-                            @foreach($recommendations as $rec)
-                                <div class="p-4 border border-slate-200 rounded-lg bg-slate-50">
-                                    <p class="text-xs text-slate-400 mb-1">题目 ID: {{ $rec['id'] ?? '' }}</p>
-                                    <p class="text-sm text-slate-800 overflow-hidden max-h-16">{{ $rec['stem'] ?? $rec['content'] ?? '推荐题目' }}</p>
-                                    <div class="flex flex-wrap gap-2 mt-2">
+                        <div class="p-6">
+                            <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
+                                @foreach($recommendations as $rec)
+                                    <div class="p-4 border border-gray-200 rounded-lg bg-gray-50">
+                                        <p class="text-sm text-gray-900 line-clamp-2">{{ $rec['stem'] ?? '推荐题目' }}</p>
                                         @if(!empty($rec['kp_codes']))
-                                            <span class="badge badge-ghost">KP {{ is_array($rec['kp_codes']) ? implode(',', $rec['kp_codes']) : $rec['kp_codes'] }}</span>
-                                        @endif
-                                        @if(!empty($rec['skills']))
-                                            <span class="badge badge-outline badge-info">{{ is_array($rec['skills']) ? implode(',', $rec['skills']) : $rec['skills'] }}</span>
+                                            <span class="inline-flex items-center mt-2 px-2 py-0.5 rounded text-xs font-medium bg-indigo-100 text-indigo-800">
+                                                {{ is_array($rec['kp_codes']) ? implode(',', $rec['kp_codes']) : $rec['kp_codes'] }}
+                                            </span>
                                         @endif
                                     </div>
-                                </div>
-                            @endforeach
+                                @endforeach
+                            </div>
                         </div>
                     </div>
                 @endif
 
-                <div class="rounded-2xl bg-white border border-slate-200 shadow-sm p-5 space-y-6">
-                    <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
-                        <div>
-                            <h3 class="text-lg font-semibold text-slate-900">智能分析</h3>
-                            <p class="text-sm text-slate-500">错误类型雷达图 · 弱点技能排名 · 薄弱知识点</p>
+                {{-- 错误分析 --}}
+                @if(!empty($patterns['error_types']) || !empty($patterns['top_kps']))
+                    <div class="bg-white shadow-sm rounded-xl border border-gray-200">
+                        <div class="px-6 py-5 border-b border-gray-100">
+                            <h3 class="text-lg font-semibold text-gray-900 flex items-center">
+                                <svg class="w-5 h-5 mr-2 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
+                                </svg>
+                                错误分析
+                            </h3>
                         </div>
-                        <button class="btn btn-ghost btn-sm" wire:click="refreshPatterns">
-                            重新拉取
-                        </button>
-                    </div>
-
-                    <div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
-                        @if(!empty($patterns['error_types']))
-                            <div class="bg-slate-50 rounded-xl border border-slate-200 p-4">
-                                <div class="flex items-center justify-between mb-3">
-                                    <h4 class="font-semibold text-slate-800">错误类型雷达图</h4>
-                                    <span class="badge badge-ghost">AI</span>
-                                </div>
-                                <canvas id="mistakeRadarChart" class="w-full h-64"></canvas>
-                            </div>
-                        @endif
-                        @if(!empty($patterns['top_skills']))
-                            <div class="bg-slate-50 rounded-xl border border-slate-200 p-4">
-                                <div class="flex items-center justify-between mb-3">
-                                    <h4 class="font-semibold text-slate-800">弱点技能排名</h4>
-                                    <span class="badge badge-warning badge-outline">Top</span>
-                                </div>
-                                <canvas id="skillBarChart" class="w-full h-64"></canvas>
-                            </div>
-                        @endif
-                    </div>
-
-                    @if(!empty($patterns['top_kps']))
-                        <div class="bg-slate-50 rounded-xl border border-slate-200 p-4">
-                            <h4 class="font-semibold text-slate-800 mb-3">薄弱知识点热力</h4>
-                            <div class="space-y-2">
-                                @foreach(($patterns['top_kps'] ?? []) as $kp)
+                        <div class="p-6">
+                            <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
+                                @if(!empty($patterns['error_types']))
                                     <div>
-                                        <div class="flex items-center justify-between text-sm text-slate-700">
-                                            <span>{{ $kp['name'] ?? ($kp['kp'] ?? $kp['kp_code'] ?? '知识点') }}</span>
-                                            <span class="text-xs text-slate-500">错误 {{ $kp['count'] ?? $kp['mistake_count'] ?? 0 }}</span>
+                                        <p class="text-sm font-medium text-gray-900 mb-4">错误类型分布</p>
+                                        <div class="space-y-3">
+                                            @foreach($patterns['error_types'] as $et)
+                                                <div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
+                                                    <span class="text-sm text-gray-700">{{ $et['type'] ?? '未知' }}</span>
+                                                    <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-200 text-gray-800">
+                                                        {{ $et['count'] ?? 0 }}
+                                                    </span>
+                                                </div>
+                                            @endforeach
                                         </div>
-                                        @php
-                                            $score = ($kp['score'] ?? $kp['accuracy'] ?? 0);
-                                            if ($score > 1) $score = $score / 100;
-                                            $width = max(10, min(100, (1 - (float) $score) * 100));
-                                        @endphp
-       				            <progress class="progress progress-error w-full" value="{{ $width }}" max="100"></progress>
                                     </div>
-                                @endforeach
+                                @endif
+                                @if(!empty($patterns['top_kps']))
+                                    <div>
+                                        <p class="text-sm font-medium text-gray-900 mb-4">薄弱知识点</p>
+                                        <div class="space-y-3">
+                                            @foreach($patterns['top_kps'] as $kp)
+                                                <div class="flex items-center justify-between p-3 bg-red-50 rounded-lg">
+                                                    <span class="text-sm text-gray-700">{{ $kp['name'] ?? $kp['kp_code'] ?? '知识点' }}</span>
+                                                    <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
+                                                        {{ $kp['mistake_count'] ?? 0 }}
+                                                    </span>
+                                                </div>
+                                            @endforeach
+                                        </div>
+                                    </div>
+                                @endif
                             </div>
                         </div>
-                    @endif
-                </div>
+                    </div>
+                @endif
             </div>
         </div>
-    </div>
+    @endif
 </div>
-
-@push('scripts')
-    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
-    <script>
-        document.addEventListener('livewire:navigated', initMistakeCharts);
-        document.addEventListener('livewire:load', initMistakeCharts);
-
-        function initMistakeCharts() {
-            const radarCanvas = document.getElementById('mistakeRadarChart');
-            const barCanvas = document.getElementById('skillBarChart');
-
-            if ((!radarCanvas && !barCanvas)) {
-                return;
-            }
-
-            const errorTypes = @json($patterns['error_types'] ?? []);
-            const skills = @json($patterns['top_skills'] ?? []);
-
-            const radarLabels = errorTypes.map(e => e.name || e.type || '错误');
-            const radarData = errorTypes.map(e => e.count || e.value || 0);
-
-            const skillLabels = skills.map(s => s.name || s.skill || '技能');
-            const skillData = skills.map(s => s.score || s.count || 0);
-
-            if (window.mistakeRadarChart) {
-                window.mistakeRadarChart.destroy();
-            }
-            if (window.skillBarChart) {
-                window.skillBarChart.destroy();
-            }
-
-            if (radarCanvas && radarLabels.length) {
-                window.mistakeRadarChart = new Chart(radarCanvas.getContext('2d'), {
-                    type: 'radar',
-                    data: {
-                        labels: radarLabels,
-                        datasets: [{
-                            label: '错误频次',
-                            data: radarData,
-                            backgroundColor: 'rgba(248, 180, 0, 0.2)',
-                            borderColor: '#f59e0b',
-                            borderWidth: 2,
-                            pointBackgroundColor: '#f97316'
-                        }]
-                    },
-                    options: {
-                        plugins: { legend: { display: false } },
-                        scales: {
-                            r: {
-                                beginAtZero: true,
-                                ticks: { stepSize: 1 }
-                            }
-                        }
-                    }
-                });
-            }
-
-            if (barCanvas && skillLabels.length) {
-                window.skillBarChart = new Chart(barCanvas.getContext('2d'), {
-                    type: 'bar',
-                    data: {
-                        labels: skillLabels,
-                        datasets: [{
-                            label: '弱点指数',
-                            data: skillData,
-                            backgroundColor: 'rgba(59, 130, 246, 0.2)',
-                            borderColor: '#3b82f6',
-                            borderWidth: 1,
-                            borderRadius: 6
-                        }]
-                    },
-                    options: {
-                        plugins: { legend: { display: false }, tooltip: { mode: 'index' } },
-                        scales: {
-                            x: { ticks: { color: '#475569' } },
-                            y: { beginAtZero: true }
-                        }
-                    }
-                });
-            }
-        }
-    </script>
-@endpush

+ 8 - 8
resources/views/filament/pages/upload-exam-paper.blade.php

@@ -5,22 +5,22 @@
     <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
         <div class="flex gap-4">
             <button
-                wire:click="$set('mode', 'upload')"
-                class="px-4 py-2 rounded-md font-medium transition-colors {{ $mode === 'upload' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }}"
+                wire:click="$set('mode', 'select_paper')"
+                class="px-4 py-2 rounded-md font-medium transition-colors {{ $mode === 'select_paper' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }}"
             >
                 <svg class="w-5 h-5 inline-block mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
-                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003 3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path>
+                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.707.293V19a2 2 0 012-2H5a2 2 0 01-2 2v-14z"></path>
                 </svg>
-                上传试卷照片
+                选择已有试卷评分
             </button>
             <button
-                wire:click="$set('mode', 'select_paper')"
-                class="px-4 py-2 rounded-md font-medium transition-colors {{ $mode === 'select_paper' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }}"
+                wire:click="$set('mode', 'upload')"
+                class="px-4 py-2 rounded-md font-medium transition-colors {{ $mode === 'upload' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }}"
             >
                 <svg class="w-5 h-5 inline-block mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
-                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.707.293V19a2 2 0 012-2H5a2 2 0 01-2 2v-14z"></path>
+                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003 3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path>
                 </svg>
-                选择已有试卷评分
+                上传试卷照片
             </button>
         </div>
     </div>

+ 129 - 0
resources/views/filament/widgets/dashboard-quick-links.blade.php

@@ -0,0 +1,129 @@
+<x-filament-widgets::widget>
+    <x-filament::section class="!p-0">
+        <div class="px-6 py-5 border-b border-base-200 bg-base-100/80 backdrop-blur">
+            <div class="flex flex-wrap items-center gap-3">
+                <span class="inline-flex items-center gap-2 px-3 py-1 text-xs font-semibold rounded-full bg-primary/10 text-primary whitespace-nowrap">
+                    <x-heroicon-o-bolt class="w-4 h-4" /> 快捷操作
+                </span>
+                <div class="flex flex-col gap-1">
+                    <h2 class="text-lg font-semibold text-base-content">常用入口</h2>
+                    <p class="text-sm text-base-content/60">出卷、分析、管理和学习洞察,一处直达</p>
+                </div>
+            </div>
+        </div>
+
+        <div class="p-6">
+            <div class="grid auto-rows-fr gap-6 grid-cols-1 sm:grid-cols-2 w-full">
+                <a href="{{ url('/admin/intelligent-exam-generation') }}"
+                   class="group flex flex-col h-full rounded-xl border border-base-200 bg-base-100 shadow-sm hover:shadow-md transition duration-150 hover:-translate-y-1 overflow-hidden">
+                    <div class="p-5 flex flex-col gap-4 h-full">
+                        <div class="flex items-start justify-between gap-3">
+                            <div class="flex items-center gap-3">
+                                <div class="w-12 h-12 rounded-xl bg-primary/10 text-primary flex items-center justify-center">
+                                    <x-heroicon-o-sparkles class="w-6 h-6" />
+                                </div>
+                                <div class="leading-tight">
+                                    <h3 class="text-base font-semibold text-base-content">智能测试</h3>
+                                    <p class="text-xs text-base-content/60">Intelligent Exam Generation</p>
+                                </div>
+                            </div>
+                            <span class="badge badge-primary badge-outline whitespace-nowrap">热门</span>
+                        </div>
+                        <p class="text-sm text-base-content/70 leading-relaxed flex-1">生成贴合学生画像的智能试卷。</p>
+                        <div>
+                            <span class="btn btn-outline btn-sm w-full">进入</span>
+                        </div>
+                    </div>
+                </a>
+
+                <a href="{{ url('/admin/upload-exam-paper') }}"
+                   class="group flex flex-col h-full rounded-xl border border-base-200 bg-base-100 shadow-sm hover:shadow-md transition duration-150 hover:-translate-y-1 overflow-hidden">
+                    <div class="p-5 flex flex-col gap-4 h-full">
+                        <div class="flex items-start justify-between gap-3">
+                            <div class="flex items-center gap-3">
+                                <div class="w-12 h-12 rounded-xl bg-secondary/10 text-secondary flex items-center justify-center">
+                                    <x-heroicon-o-chart-pie class="w-6 h-6" />
+                                </div>
+                                <div class="leading-tight">
+                                    <h3 class="text-base font-semibold text-base-content">智能分析</h3>
+                                    <p class="text-xs text-base-content/60">Upload Exam Paper</p>
+                                </div>
+                            </div>
+                            <span class="badge badge-secondary badge-outline whitespace-nowrap">AI</span>
+                        </div>
+                        <p class="text-sm text-base-content/70 leading-relaxed flex-1">上传试卷并获取自动分析与评分。</p>
+                        <div>
+                            <span class="btn btn-outline btn-sm w-full">进入</span>
+                        </div>
+                    </div>
+                </a>
+
+                <a href="{{ url('/admin/student-management') }}"
+                   class="group flex flex-col h-full rounded-xl border border-base-200 bg-base-100 shadow-sm hover:shadow-md transition duration-150 hover:-translate-y-1 overflow-hidden">
+                    <div class="p-5 flex flex-col gap-4 h-full">
+                        <div class="flex items-start justify-between gap-3">
+                            <div class="flex items-center gap-3">
+                                <div class="w-12 h-12 rounded-xl bg-accent/10 text-accent flex items-center justify-center">
+                                    <x-heroicon-o-user-group class="w-6 h-6" />
+                                </div>
+                                <div class="leading-tight">
+                                    <h3 class="text-base font-semibold text-base-content">学员管理</h3>
+                                    <p class="text-xs text-base-content/60">Student Management</p>
+                                </div>
+                            </div>
+                            <span class="badge badge-accent badge-outline whitespace-nowrap">班级</span>
+                        </div>
+                        <p class="text-sm text-base-content/70 leading-relaxed flex-1">维护学生档案与班级分组。</p>
+                        <div>
+                            <span class="btn btn-outline btn-sm w-full">进入</span>
+                        </div>
+                    </div>
+                </a>
+
+                <a href="{{ url('/admin/student-dashboard') }}"
+                   class="group flex flex-col h-full rounded-xl border border-base-200 bg-base-100 shadow-sm hover:shadow-md transition duration-150 hover:-translate-y-1 overflow-hidden">
+                    <div class="p-5 flex flex-col gap-4 h-full">
+                        <div class="flex items-start justify-between gap-3">
+                            <div class="flex items-center gap-3">
+                                <div class="w-12 h-12 rounded-xl bg-warning/10 text-warning flex items-center justify-center">
+                                    <x-heroicon-o-presentation-chart-line class="w-6 h-6" />
+                                </div>
+                                <div class="leading-tight">
+                                    <h3 class="text-base font-semibold text-base-content">学生仪表板</h3>
+                                    <p class="text-xs text-base-content/60">Student Dashboard</p>
+                                </div>
+                            </div>
+                            <span class="badge badge-warning badge-outline whitespace-nowrap">洞察</span>
+                        </div>
+                        <p class="text-sm text-base-content/70 leading-relaxed flex-1">查看学生画像、薄弱点与学习路径。</p>
+                        <div>
+                            <span class="btn btn-outline btn-sm w-full">进入</span>
+                        </div>
+                    </div>
+                </a>
+
+                <a href="{{ url('/admin/mistake-book') }}"
+                   class="group flex flex-col h-full rounded-xl border border-base-200 bg-base-100 shadow-sm hover:shadow-md transition duration-150 hover:-translate-y-1 overflow-hidden">
+                    <div class="p-5 flex flex-col gap-4 h-full">
+                        <div class="flex items-start justify-between gap-3">
+                            <div class="flex items-center gap-3">
+                                <div class="w-12 h-12 rounded-xl bg-info/10 text-info flex items-center justify-center">
+                                    <x-heroicon-o-book-open class="w-6 h-6" />
+                                </div>
+                                <div class="leading-tight">
+                                    <h3 class="text-base font-semibold text-base-content">错题本</h3>
+                                    <p class="text-xs text-base-content/60">Mistake Book</p>
+                                </div>
+                            </div>
+                            <span class="badge badge-info badge-outline whitespace-nowrap">巩固</span>
+                        </div>
+                        <p class="text-sm text-base-content/70 leading-relaxed flex-1">集中管理错题,助力查漏补缺与巩固提升。</p>
+                        <div>
+                            <span class="btn btn-outline btn-sm w-full">进入</span>
+                        </div>
+                    </div>
+                </a>
+            </div>
+        </div>
+    </x-filament::section>
+</x-filament-widgets::widget>

+ 1 - 2
routes/web.php

@@ -3,7 +3,7 @@
 use Illuminate\Support\Facades\Route;
 
 Route::get('/', function () {
-    return view('welcome');
+    return redirect()->route('filament.admin.pages.dashboard');
 });
 
 // 包含API路由
@@ -14,4 +14,3 @@ Route::view('/knowledge-mindmap-public', 'public.knowledge-mindmap');
 Route::get('/admin/intelligent-exam/pdf/{paper_id}', [\App\Http\Controllers\ExamPdfController::class, 'show'])->name('filament.admin.auth.intelligent-exam.pdf');
 
 
-