|
|
@@ -2,14 +2,15 @@
|
|
|
|
|
|
namespace App\Filament\Pages;
|
|
|
|
|
|
-use App\Filament\Traits\HasUserRole;
|
|
|
use App\Jobs\ProcessOCRRecord;
|
|
|
use App\Models\OCRRecord;
|
|
|
use App\Models\Student;
|
|
|
use App\Models\Teacher;
|
|
|
+use App\Filament\Traits\HasUserRole;
|
|
|
use BackedEnum;
|
|
|
use Filament\Notifications\Notification;
|
|
|
use Filament\Pages\Page;
|
|
|
+use Filament\Forms;
|
|
|
use Livewire\WithFileUploads;
|
|
|
use Livewire\Attributes\Computed;
|
|
|
use Livewire\Attributes\On;
|
|
|
@@ -20,9 +21,9 @@ class UploadExamPaper extends Page
|
|
|
{
|
|
|
use HasUserRole, WithFileUploads;
|
|
|
|
|
|
- protected static ?string $title = '上传考试卷子';
|
|
|
- protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-arrow-up-tray';
|
|
|
- protected static ?string $navigationLabel = '上传考试卷子';
|
|
|
+ protected static ?string $title = '上传试卷';
|
|
|
+ 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 ?string $slug = 'upload-exam-paper';
|
|
|
@@ -33,15 +34,26 @@ class UploadExamPaper extends Page
|
|
|
public $uploadedImage = null;
|
|
|
public bool $isUploading = false;
|
|
|
public ?string $paperType = null; // 试卷类型:unit_test, midterm, final, homework, quiz, other
|
|
|
+ public $form;
|
|
|
+ public array $data = [];
|
|
|
+ public bool $analyzing = false;
|
|
|
+ public ?string $analysisError = null;
|
|
|
|
|
|
// 新增:模式选择
|
|
|
public string $mode = 'upload'; // 'upload' 或 'select_paper'
|
|
|
public ?string $selectedPaperId = null;
|
|
|
+ public bool $showGrading = false;
|
|
|
+ public array $questions = [];
|
|
|
+ public array $gradingData = [];
|
|
|
+ public ?string $paperName = null;
|
|
|
+ public ?string $paperClass = null;
|
|
|
+ public ?string $paperStudent = null;
|
|
|
+ public ?string $paperDate = null;
|
|
|
public array $questionGrades = []; // 存储每道题的评分
|
|
|
|
|
|
public function mount()
|
|
|
{
|
|
|
- // 初始化用户角色检查
|
|
|
+ // 初始化用户角色
|
|
|
$this->initializeUserRole();
|
|
|
|
|
|
// 如果是老师,自动选择当前老师
|
|
|
@@ -62,6 +74,63 @@ class UploadExamPaper extends Page
|
|
|
$this->questionGrades = [];
|
|
|
}
|
|
|
|
|
|
+ public function form(Forms\Form $form): Forms\Form
|
|
|
+ {
|
|
|
+ return $form
|
|
|
+ ->statePath('data')
|
|
|
+ ->schema([
|
|
|
+ Forms\Components\FileUpload::make('image')
|
|
|
+ ->label('上传试卷图片')
|
|
|
+ ->image()
|
|
|
+ ->multiple()
|
|
|
+ ->directory('exam-papers')
|
|
|
+ ->acceptedFileTypes(['image/png', 'image/jpeg', 'image/jpg'])
|
|
|
+ ->helperText('支持PNG、JPG、JPEG格式,可同时上传多张图片')
|
|
|
+ ->maxFiles(10)
|
|
|
+ ->required(),
|
|
|
+
|
|
|
+ Forms\Components\TextInput::make('paper_name')
|
|
|
+ ->label('试卷名称')
|
|
|
+ ->required()
|
|
|
+ ->placeholder('例如:数学期末考试'),
|
|
|
+
|
|
|
+ Forms\Components\Select::make('class')
|
|
|
+ ->label('班级')
|
|
|
+ ->options([
|
|
|
+ 'ClassA' => '三年级一班',
|
|
|
+ 'ClassB' => '三年级二班',
|
|
|
+ 'ClassC' => '四年级一班',
|
|
|
+ 'ClassD' => '四年级二班',
|
|
|
+ 'ClassE' => '五年级一班',
|
|
|
+ 'ClassF' => '五年级二班',
|
|
|
+ 'ClassG' => '六年级一班',
|
|
|
+ 'ClassH' => '六年级二班',
|
|
|
+ ])
|
|
|
+ ->required(),
|
|
|
+
|
|
|
+ Forms\Components\TextInput::make('student_name')
|
|
|
+ ->label('学生姓名')
|
|
|
+ ->required()
|
|
|
+ ->placeholder('请输入学生姓名'),
|
|
|
+
|
|
|
+ Forms\Components\Select::make('paper_type')
|
|
|
+ ->label('试卷类型')
|
|
|
+ ->options([
|
|
|
+ 'quiz' => '课堂测验',
|
|
|
+ 'midterm' => '期中考试',
|
|
|
+ 'final' => '期末考试',
|
|
|
+ 'homework' => '家庭作业',
|
|
|
+ ])
|
|
|
+ ->default('quiz')
|
|
|
+ ->required(),
|
|
|
+
|
|
|
+ Forms\Components\TextInput::make('paper_subject')
|
|
|
+ ->label('科目')
|
|
|
+ ->default('数学')
|
|
|
+ ->required(),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
#[Computed]
|
|
|
public function teachers(): array
|
|
|
{
|
|
|
@@ -158,23 +227,24 @@ class UploadExamPaper extends Page
|
|
|
public function recentRecords(): array
|
|
|
{
|
|
|
// 1. 获取OCR记录(图片上传)
|
|
|
- $ocrQuery = OCRRecord::with('student')->latest();
|
|
|
-
|
|
|
+ $ocrQuery = OCRRecord::with('student');
|
|
|
+
|
|
|
// 如果选择了学生,则筛选该学生的记录
|
|
|
if (!empty($this->studentId)) {
|
|
|
$ocrQuery->where('user_id', $this->studentId);
|
|
|
}
|
|
|
-
|
|
|
- $ocrRecords = $ocrQuery->take(5)
|
|
|
- ->get()
|
|
|
+
|
|
|
+ $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' => $record->student?->name ?? $record->user_id,
|
|
|
+ 'student_name' => $studentName,
|
|
|
'paper_type' => $record->paper_type_label,
|
|
|
'paper_name' => $record->image_filename ?: '未命名图片',
|
|
|
'status' => $record->status,
|
|
|
@@ -186,27 +256,28 @@ class UploadExamPaper extends Page
|
|
|
})->toArray();
|
|
|
|
|
|
// 2. 获取所有Paper记录(包括草稿和已评分)
|
|
|
- $paperQuery = \App\Models\Paper::with(['student'])->latest();
|
|
|
+ $paperQuery = \App\Models\Paper::with('student');
|
|
|
|
|
|
// 如果选择了学生,则筛选该学生的记录
|
|
|
if (!empty($this->studentId)) {
|
|
|
$paperQuery->where('student_id', $this->studentId);
|
|
|
}
|
|
|
|
|
|
- $allPapers = $paperQuery->take(5)
|
|
|
- ->get()
|
|
|
+ $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' => $paper->student?->name ?? $paper->student_id,
|
|
|
+ 'student_name' => $studentName,
|
|
|
'paper_type' => $paperType,
|
|
|
'paper_name' => $paper->paper_name ?? '未命名试卷',
|
|
|
'status' => $paper->difficulty_category,
|
|
|
@@ -444,7 +515,10 @@ class UploadExamPaper extends Page
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- if (!$this->uploadedImage) {
|
|
|
+ // 获取表单数据
|
|
|
+ $formData = $this->data;
|
|
|
+
|
|
|
+ if (empty($formData['image'])) {
|
|
|
Notification::make()
|
|
|
->title('请上传图片')
|
|
|
->danger()
|
|
|
@@ -452,28 +526,80 @@ class UploadExamPaper extends Page
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ if (empty($formData['paper_name'])) {
|
|
|
+ Notification::make()
|
|
|
+ ->title('请填写试卷名称')
|
|
|
+ ->danger()
|
|
|
+ ->send();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (empty($formData['class'])) {
|
|
|
+ Notification::make()
|
|
|
+ ->title('请选择班级')
|
|
|
+ ->danger()
|
|
|
+ ->send();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (empty($formData['student_name'])) {
|
|
|
+ Notification::make()
|
|
|
+ ->title('请填写学生姓名')
|
|
|
+ ->danger()
|
|
|
+ ->send();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
$this->isUploading = true;
|
|
|
|
|
|
try {
|
|
|
- // 保存图片
|
|
|
- $path = $this->uploadedImage->store('ocr-uploads', 'public');
|
|
|
- $filename = basename($path);
|
|
|
-
|
|
|
- // 创建OCR记录
|
|
|
- $record = OCRRecord::create([
|
|
|
- 'user_id' => $this->studentId,
|
|
|
- 'file_path' => $path,
|
|
|
- 'paper_title' => $filename,
|
|
|
- 'paper_type' => $this->paperType,
|
|
|
- 'status' => 'pending',
|
|
|
- 'total_questions' => 0,
|
|
|
- ]);
|
|
|
+ // 处理图片(可能是单张或多张)
|
|
|
+ $images = $formData['image'];
|
|
|
+ if (!is_array($images)) {
|
|
|
+ $images = [$images];
|
|
|
+ }
|
|
|
|
|
|
- // 立即更新状态为处理中,提供更好的用户体验
|
|
|
- $record->update(['status' => 'processing']);
|
|
|
+ $paths = [];
|
|
|
+ foreach ($images as $image) {
|
|
|
+ if ($image) {
|
|
|
+ $paths[] = storage_path('app/public/' . $image);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (empty($paths)) {
|
|
|
+ throw new \Exception('图片保存失败');
|
|
|
+ }
|
|
|
+
|
|
|
+ $paperId = 'paper_' . time() . '_' . substr(md5(uniqid()), 0, 8);
|
|
|
+
|
|
|
+ // AI分析服务调用
|
|
|
+ $response = \Http::timeout(300)
|
|
|
+ ->post('http://localhost:5016/analyze-exam', [
|
|
|
+ 'paper_id' => $paperId,
|
|
|
+ 'paper_name' => $formData['paper_name'],
|
|
|
+ 'student_name' => $formData['student_name'],
|
|
|
+ 'class_name' => $formData['class'],
|
|
|
+ 'paper_type' => $formData['paper_type'],
|
|
|
+ 'subject' => $formData['paper_subject'],
|
|
|
+ 'image_files' => $paths,
|
|
|
+ ]);
|
|
|
|
|
|
- // 自动触发OCR处理
|
|
|
- ProcessOCRRecord::dispatch($record->id);
|
|
|
+ if ($response->successful()) {
|
|
|
+ $result = $response->json();
|
|
|
+ $this->saveAnalysisResult($result, $paperId);
|
|
|
+ $this->analysisResult = $result;
|
|
|
+ Notification::make()
|
|
|
+ ->title('分析完成')
|
|
|
+ ->success()
|
|
|
+ ->send();
|
|
|
+ } else {
|
|
|
+ $this->analysisError = '分析服务响应失败: ' . $response->status();
|
|
|
+ Notification::make()
|
|
|
+ ->title('分析失败')
|
|
|
+ ->body($this->analysisError)
|
|
|
+ ->error()
|
|
|
+ ->send();
|
|
|
+ }
|
|
|
|
|
|
// 重置表单
|
|
|
$this->teacherId = null;
|
|
|
@@ -481,15 +607,6 @@ class UploadExamPaper extends Page
|
|
|
$this->uploadedImage = null;
|
|
|
$this->paperType = null;
|
|
|
|
|
|
- Notification::make()
|
|
|
- ->title('上传成功')
|
|
|
- ->body("卷子已上传并开始OCR处理,正在跳转到校准页面...")
|
|
|
- ->success()
|
|
|
- ->send();
|
|
|
-
|
|
|
- // 跳转到OCR记录详情页面进行校准和提交分析
|
|
|
- $this->redirect("/admin/ocr-record-view/{$record->id}");
|
|
|
-
|
|
|
} catch (\Exception $e) {
|
|
|
Notification::make()
|
|
|
->title('上传失败')
|
|
|
@@ -498,6 +615,7 @@ class UploadExamPaper extends Page
|
|
|
->send();
|
|
|
} finally {
|
|
|
$this->isUploading = false;
|
|
|
+ $this->analyzing = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -532,6 +650,9 @@ class UploadExamPaper extends Page
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ // 将 gradingData 转换为 questionGrades 格式
|
|
|
+ $this->convertGradingDataToQuestionGrades();
|
|
|
+
|
|
|
if (empty($this->questionGrades)) {
|
|
|
Notification::make()
|
|
|
->title('请至少为一道题目评分')
|
|
|
@@ -591,13 +712,22 @@ class UploadExamPaper extends Page
|
|
|
$kpCode = $detail['kp_code'] ?? $detail['knowledge_point_code'] ?? null;
|
|
|
}
|
|
|
|
|
|
+ // 确保 is_correct 有值(如果为 null,设置为 false)
|
|
|
+ $isCorrect = $grade['is_correct'];
|
|
|
+ if ($isCorrect === null) {
|
|
|
+ $isCorrect = false;
|
|
|
+ }
|
|
|
+
|
|
|
$analyticsData[] = [
|
|
|
'question_bank_id' => $question['question_bank_id'],
|
|
|
'student_answer' => $grade['student_answer'] ?? '',
|
|
|
- 'is_correct' => $grade['is_correct'] ?? null,
|
|
|
- 'score' => $grade['score'] ?? null,
|
|
|
- 'max_score' => $question['score'],
|
|
|
- 'kp_code' => $kpCode, // 添加 kp_code
|
|
|
+ 'is_correct' => $isCorrect,
|
|
|
+ 'score' => $grade['score'] ?? 0,
|
|
|
+ 'max_score' => $question['score'] ?? 0,
|
|
|
+ 'kp_code' => $kpCode,
|
|
|
+ 'ip_address' => '127.0.0.1', // 提供默认IP地址,避免PostgreSQL inet类型错误
|
|
|
+ 'device_type' => 'web', // 提供默认设备类型
|
|
|
+ 'feedback_provided' => false, // 提供默认反馈状态
|
|
|
];
|
|
|
}
|
|
|
|
|
|
@@ -606,10 +736,32 @@ class UploadExamPaper extends Page
|
|
|
|
|
|
// 步骤0: 保存学生答案到本地数据库 (重要:确保数据持久化)
|
|
|
foreach ($this->questionGrades as $questionId => $grade) {
|
|
|
+ // 确保 is_correct 是布尔值(转换字符串 'true'/'false' 为布尔值)
|
|
|
+ $isCorrect = $grade['is_correct'];
|
|
|
+ if ($isCorrect === 'true' || $isCorrect === true) {
|
|
|
+ $isCorrect = true;
|
|
|
+ } elseif ($isCorrect === 'false' || $isCorrect === false) {
|
|
|
+ $isCorrect = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保 score_obtained 是数字
|
|
|
+ $score = $grade['score'];
|
|
|
+ if ($score !== null) {
|
|
|
+ $score = is_numeric($score) ? (float)$score : 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // **关键修复**:确保 is_correct 和 score 的一致性
|
|
|
+ // 如果分数为满分或大于0,视为正确
|
|
|
+ if ($score > 0) {
|
|
|
+ $isCorrect = true;
|
|
|
+ } elseif ($score === 0) {
|
|
|
+ $isCorrect = false;
|
|
|
+ }
|
|
|
+
|
|
|
\App\Models\PaperQuestion::where('id', $questionId)->update([
|
|
|
'student_answer' => $grade['student_answer'] ?? '',
|
|
|
- 'is_correct' => $grade['is_correct'] ?? false,
|
|
|
- 'score_obtained' => $grade['score'] ?? 0,
|
|
|
+ 'is_correct' => $isCorrect,
|
|
|
+ 'score_obtained' => $score ?? 0,
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
@@ -761,4 +913,284 @@ class UploadExamPaper extends Page
|
|
|
->send();
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将 gradingData 转换为 questionGrades 格式
|
|
|
+ * gradingData: 索引数组 [{is_correct: bool, score: float}]
|
|
|
+ * questionGrades: 题目ID为键的数组 [questionId => {is_correct: bool, score: float, student_answer: string}]
|
|
|
+ */
|
|
|
+ private function convertGradingDataToQuestionGrades(): void
|
|
|
+ {
|
|
|
+ $this->questionGrades = [];
|
|
|
+
|
|
|
+ // 遍历 questions 数组(包含题目信息)
|
|
|
+ foreach ($this->questions as $index => $question) {
|
|
|
+ // 获取对应索引的 gradingData
|
|
|
+ $grading = $this->gradingData[$index] ?? null;
|
|
|
+
|
|
|
+ // 只有当 grading 不为空且有评分数据时才添加
|
|
|
+ if ($grading && (
|
|
|
+ $grading['is_correct'] !== null ||
|
|
|
+ ($grading['score'] ?? null) !== null
|
|
|
+ )) {
|
|
|
+ $questionId = $question['id'];
|
|
|
+
|
|
|
+ // 处理 is_correct 值(字符串 'true'/'false' 或布尔值)
|
|
|
+ $isCorrect = $grading['is_correct'];
|
|
|
+ if ($isCorrect === 'true') {
|
|
|
+ $isCorrect = true;
|
|
|
+ } elseif ($isCorrect === 'false') {
|
|
|
+ $isCorrect = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理 score 值
|
|
|
+ $score = $grading['score'];
|
|
|
+ if ($score !== null && $score !== '') {
|
|
|
+ $score = is_numeric($score) ? (float)$score : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // **关键修复**:根据题型处理缺失的字段
|
|
|
+ if ($question['question_type'] === 'choice') {
|
|
|
+ // 选择题:只有 is_correct,需要自动计算分数
|
|
|
+ if ($isCorrect === true) {
|
|
|
+ $score = $question['score'] ?? 0; // 正确给满分
|
|
|
+ } elseif ($isCorrect === false) {
|
|
|
+ $score = 0; // 错误给0分
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 填空/解答题:只有 score,需要自动计算 is_correct
|
|
|
+ if ($score !== null) {
|
|
|
+ $isCorrect = ($score >= ($question['score'] ?? 0)); // 得分>=满分视为正确
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取学生答案(优先使用 gradingData 中的值,如果没有则使用题目中的值)
|
|
|
+ $studentAnswer = $grading['student_answer'] ?? $question['student_answer'] ?? '';
|
|
|
+
|
|
|
+ // 对于选择题,如果学生答案为空,基于评分推断
|
|
|
+ if (empty($studentAnswer) && $question['question_type'] === 'choice') {
|
|
|
+ if ($isCorrect === true) {
|
|
|
+ // 如果选"正确",学生答案就是正确答案
|
|
|
+ $studentAnswer = $question['correct_answer'] ?? '正确答案';
|
|
|
+ } elseif ($isCorrect === false) {
|
|
|
+ // 如果选"错误",学生答案可以为空或者设置为特殊标记
|
|
|
+ $studentAnswer = '错误答案';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 转换格式
|
|
|
+ $this->questionGrades[$questionId] = [
|
|
|
+ 'is_correct' => $isCorrect,
|
|
|
+ 'score' => $score,
|
|
|
+ 'student_answer' => $studentAnswer,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ \Log::info('转换评分数据', [
|
|
|
+ 'grading_data_count' => count(array_filter($this->gradingData ?? [])),
|
|
|
+ 'question_grades_count' => count($this->questionGrades),
|
|
|
+ 'questions_count' => count($this->questions ?? []),
|
|
|
+ 'sample_question_grades' => array_slice($this->questionGrades, 0, 2, true),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[Computed]
|
|
|
+ public function gradingProgress(): string
|
|
|
+ {
|
|
|
+ $gradedCount = count(array_filter($this->gradingData ?? []));
|
|
|
+ $totalCount = count($this->questions ?? []);
|
|
|
+ return "已评分:{$gradedCount}/{$totalCount}题";
|
|
|
+ }
|
|
|
+
|
|
|
+ public function startAnalysis(): void
|
|
|
+ {
|
|
|
+ $this->analyzing = true;
|
|
|
+ $this->analysisError = null;
|
|
|
+
|
|
|
+ try {
|
|
|
+ $this->submitUpload();
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ $this->analysisError = $e->getMessage();
|
|
|
+ $this->analyzing = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public function saveGrading(): void
|
|
|
+ {
|
|
|
+ $this->submitManualGrading();
|
|
|
+ }
|
|
|
+
|
|
|
+ public function updatedSelectedPaperId($value): void
|
|
|
+ {
|
|
|
+ if (empty($value)) {
|
|
|
+ $this->questions = [];
|
|
|
+ $this->gradingData = [];
|
|
|
+ $this->showGrading = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载试卷信息和题目
|
|
|
+ $this->loadPaperForGrading($value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public function loadPaperForGrading($paperId): void
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $paper = \App\Models\Paper::where('paper_id', $paperId)->first();
|
|
|
+ if (!$paper) {
|
|
|
+ Notification::make()
|
|
|
+ ->title('试卷不存在')
|
|
|
+ ->danger()
|
|
|
+ ->send();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置试卷信息
|
|
|
+ $this->paperName = $paper->paper_name;
|
|
|
+ $this->paperClass = $paper->difficulty_category ?? '未设置';
|
|
|
+ $this->paperStudent = $paper->student_id;
|
|
|
+ $this->paperDate = $paper->created_at->format('Y-m-d H:i');
|
|
|
+
|
|
|
+ // 加载题目
|
|
|
+ $paperWithQuestions = \App\Models\Paper::with(['questions' => function($query) {
|
|
|
+ $query->orderBy('question_number');
|
|
|
+ }])->where('paper_id', $paperId)->first();
|
|
|
+
|
|
|
+ $questions = $paperWithQuestions ? $paperWithQuestions->questions : collect([]);
|
|
|
+
|
|
|
+ // 如果没有正确答案,先尝试从题库API获取
|
|
|
+ $apiDetailsMap = new \Illuminate\Support\Collection();
|
|
|
+ if (!$questions->isEmpty()) {
|
|
|
+ $questionBankIds = $questions->where('question_bank_id', '!=', null)->pluck('question_bank_id')->unique()->toArray();
|
|
|
+ if (!empty($questionBankIds)) {
|
|
|
+ try {
|
|
|
+ $questionBankService = app(\App\Services\QuestionBankService::class);
|
|
|
+ $apiResponse = $questionBankService->getQuestionsByIds($questionBankIds);
|
|
|
+
|
|
|
+ if (!empty($apiResponse['data'])) {
|
|
|
+ foreach ($apiResponse['data'] as $detail) {
|
|
|
+ $apiDetailsMap->put($detail['id'], $detail);
|
|
|
+ }
|
|
|
+ \Log::info('成功从题库API获取题目详情', [
|
|
|
+ 'count' => count($apiResponse['data']),
|
|
|
+ 'ids' => array_keys($apiResponse['data'])
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ \Log::warning('获取题库详情失败', ['error' => $e->getMessage()]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($questions->isEmpty()) {
|
|
|
+ $this->questions = [
|
|
|
+ [
|
|
|
+ 'id' => 'no_questions',
|
|
|
+ 'question_number' => 1,
|
|
|
+ 'question_type' => 'info',
|
|
|
+ 'content' => '该试卷暂无题目数据',
|
|
|
+ 'answer' => '',
|
|
|
+ 'score' => 0,
|
|
|
+ 'is_empty' => true
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+ } else {
|
|
|
+ $this->questions = $questions->map(function($question, $index) use ($apiDetailsMap) {
|
|
|
+ // 从 API 获取正确答案(优先使用 API 数据)
|
|
|
+ $correctAnswer = $question->correct_answer;
|
|
|
+ if (empty($correctAnswer) && $question->question_bank_id && $apiDetailsMap->has($question->question_bank_id)) {
|
|
|
+ $detail = $apiDetailsMap->get($question->question_bank_id);
|
|
|
+ $correctAnswer = $detail['answer'] ?? $detail['correct_answer'] ?? '';
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'id' => $question->id,
|
|
|
+ 'question_number' => $question->question_number,
|
|
|
+ 'question_type' => $question->question_type,
|
|
|
+ 'question_text' => $question->question_text,
|
|
|
+ 'content' => $question->question_text,
|
|
|
+ 'options' => json_decode($question->options, true) ?: [],
|
|
|
+ 'answer' => $correctAnswer,
|
|
|
+ 'correct_answer' => $correctAnswer,
|
|
|
+ 'student_answer' => '', // 学生答案暂不显示,等后续完善
|
|
|
+ 'score' => $question->score,
|
|
|
+ 'max_score' => $question->score,
|
|
|
+ 'question_bank_id' => $question->question_bank_id,
|
|
|
+ 'is_empty' => false
|
|
|
+ ];
|
|
|
+ })->toArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化评分数据
|
|
|
+ $this->gradingData = array_fill(0, count($this->questions), ['score' => null, 'is_correct' => null, 'comment' => '']);
|
|
|
+ $this->showGrading = true;
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ \Log::error('加载试卷题目失败', [
|
|
|
+ 'paper_id' => $paperId,
|
|
|
+ 'error' => $e->getMessage()
|
|
|
+ ]);
|
|
|
+
|
|
|
+ Notification::make()
|
|
|
+ ->title('加载失败')
|
|
|
+ ->body($e->getMessage())
|
|
|
+ ->danger()
|
|
|
+ ->send();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private function saveAnalysisResult(array $result, string $paperId): void
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ \DB::beginTransaction();
|
|
|
+
|
|
|
+ // 保存试卷基本信息
|
|
|
+ $examPaper = \App\Models\Paper::create([
|
|
|
+ 'paper_id' => $paperId,
|
|
|
+ 'paper_name' => $result['paper_name'] ?? '未命名试卷',
|
|
|
+ 'student_id' => $this->studentId,
|
|
|
+ 'teacher_id' => $this->teacherId,
|
|
|
+ 'paper_type' => $result['paper_type'] ?? 'quiz',
|
|
|
+ 'question_count' => count($result['questions'] ?? []),
|
|
|
+ 'total_score' => $result['total_score'] ?? 0,
|
|
|
+ 'status' => 'completed',
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 保存题目信息
|
|
|
+ foreach ($result['questions'] ?? [] as $index => $questionData) {
|
|
|
+ \App\Models\PaperQuestion::create([
|
|
|
+ 'paper_id' => $paperId,
|
|
|
+ 'question_number' => $index + 1,
|
|
|
+ 'question_text' => $questionData['question_text'] ?? '',
|
|
|
+ 'question_type' => $questionData['question_type'] ?? 'choice',
|
|
|
+ 'options' => json_encode($questionData['options'] ?? []),
|
|
|
+ 'correct_answer' => $questionData['correct_answer'] ?? '',
|
|
|
+ 'score' => $questionData['score'] ?? 1,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ \DB::commit();
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ \DB::rollBack();
|
|
|
+ \Log::error('保存分析结果失败: ' . $e->getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查看记录详情 - 使用页面跳转
|
|
|
+ */
|
|
|
+ public function getViewRecordUrl(string $type, string $paperId, string $recordId, string $studentId): string
|
|
|
+ {
|
|
|
+ // 返回ExamAnalysis详情页面URL
|
|
|
+ if (in_array($type, ['graded_paper', 'generated'])) {
|
|
|
+ // 系统生成或已评分试卷,使用paperId
|
|
|
+ return '/admin/exam-analysis?paperId=' . $paperId . '&studentId=' . $studentId;
|
|
|
+ } elseif ($type === 'ocr_upload') {
|
|
|
+ // OCR上传记录,也跳转到详情页
|
|
|
+ return '/admin/exam-analysis?recordId=' . $recordId . '&studentId=' . $studentId;
|
|
|
+ }
|
|
|
+ return '#';
|
|
|
+ }
|
|
|
}
|