components([ Select::make('student_id') ->label('选择学生') ->options(function () { return Student::query() ->orderBy('name') ->get() ->mapWithKeys(fn ($student) => [ $student->student_id => "{$student->name} ({$student->grade} - {$student->class_name})" ]); }) ->searchable() ->required(), FileUpload::make('image_path') ->label('卷子图片') ->image() ->directory('ocr-uploads') ->required() ->maxSize(10240) ->acceptedFileTypes(['image/jpeg', 'image/png', 'image/webp']) ->helperText('支持 JPG、PNG、WebP 格式,最大 10MB'), Hidden::make('status') ->default('pending'), Hidden::make('total_questions') ->default(0), Hidden::make('processed_questions') ->default(0), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('student.name') ->label('学生姓名') ->searchable() ->sortable(), Tables\Columns\TextColumn::make('student.grade') ->label('年级') ->sortable(), Tables\Columns\TextColumn::make('student.class_name') ->label('班级') ->sortable(), Tables\Columns\TextColumn::make('image_filename') ->label('图片名称') ->searchable() ->wrap(), Tables\Columns\TextColumn::make('total_questions') ->label('题目总数') ->sortable() ->alignCenter(), Tables\Columns\TextColumn::make('processed_questions') ->label('已处理') ->sortable() ->alignCenter(), Tables\Columns\TextColumn::make('progress') ->label('处理进度') ->formatStateUsing(function ($record) { $total = $record->total_questions ?? 0; $processed = $record->processed_questions ?? 0; if ($total > 0) { return round(($processed / $total) * 100, 1) . '%'; } return '-'; }) ->alignCenter(), Tables\Columns\TextColumn::make('confidence_avg') ->label('平均置信度') ->formatStateUsing(fn ($state) => $state ? round($state * 100, 2) . '%' : '-') ->color(fn ($state) => $state && $state > 0.7 ? 'success' : ($state && $state > 0.5 ? 'warning' : 'danger')) ->alignCenter(), Tables\Columns\TextColumn::make('status') ->label('状态') ->badge() ->formatStateUsing(fn (string $state): string => match ($state) { 'pending' => '待处理', 'processing' => '处理中', 'completed' => '已完成', 'failed' => '失败', default => $state, }) ->color(fn (string $state): string => match ($state) { 'pending' => 'gray', 'processing' => 'info', 'completed' => 'success', 'failed' => 'danger', default => 'gray', }) ->alignCenter(), Tables\Columns\TextColumn::make('created_at') ->label('创建时间') ->dateTime('Y-m-d H:i:s') ->sortable() ->toggleable(isToggledHiddenByDefault: false), Tables\Columns\TextColumn::make('processed_at') ->label('处理完成时间') ->dateTime('Y-m-d H:i:s') ->sortable() ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ Tables\Filters\SelectFilter::make('status') ->label('处理状态') ->options([ 'pending' => '待处理', 'processing' => '处理中', 'completed' => '已完成', 'failed' => '失败', ]), Tables\Filters\SelectFilter::make('student_grade') ->label('年级') ->options(fn () => self::gradeOptions()) ->query(function (Builder $query, array $data): void { if (!empty($data['value'])) { $query->whereHas('student', fn ($q) => $q->where('grade', $data['value'])); } }), Tables\Filters\SelectFilter::make('student_class') ->label('班级') ->options(fn () => self::classOptions()) ->query(function (Builder $query, array $data): void { if (!empty($data['value'])) { $query->whereHas('student', fn ($q) => $q->where('class_name', $data['value'])); } }), Tables\Filters\Filter::make('created_today') ->label('今日创建') ->query(fn (Builder $query): Builder => $query->whereDate('created_at', today())) ->toggle(), ]) ->actions([]) ->defaultSort('created_at', 'desc') ->paginated([10, 25, 50, 100]) ->poll('10s'); } public static function getRelations(): array { return [ // ]; } public static function getPages(): array { return [ 'index' => Pages\ListOCRRecords::route('/'), 'create' => Pages\CreateOCRRecord::route('/create'), 'view' => Pages\ViewOCRRecord::route('/{record}'), ]; } protected static function gradeOptions(): array { return DB::table('students') ->distinct() ->pluck('grade', 'grade') ->toArray(); } protected static function classOptions(): array { return DB::table('students') ->distinct() ->pluck('class_name', 'class_name') ->toArray(); } public static function getRecordTitle(?Model $record): string|null { if (!$record) { return null; } $studentName = optional($record->student)->name ?? '未知学生'; return "{$studentName} - {$record->image_filename}"; } }