has('import_id') && !empty(request()->input('import_id')); // 2. 或者是管理员角色(直接检查 role 字段) $user = auth()->user(); $isAdmin = $user && in_array($user->role, ['super_admin', 'admin']); return $hasImportId || $isAdmin; } public static function canCreate(): bool { // 不允许手动创建候选题目,只能从 Markdown 解析生成 return false; } public static function canEdit(Model $record): bool { // 允许在“人工校对”阶段对候选题进行编辑(题干/选项/标记) return true; } public static function table(Tables\Table $table): Tables\Table { return $table ->columns([ Tables\Columns\TextColumn::make('sequence') ->label('序') ->sortable() ->width('60px'), Tables\Columns\TextColumn::make('index') ->label('题号') ->sortable() ->width('70px'), Tables\Columns\ViewColumn::make('raw_markdown') ->label('题目预览') ->view('filament.tables.columns.markdown-preview') ->columnSpanFull(), Tables\Columns\ImageColumn::make('first_image') ->label('图片') ->height(60) ->width(60) ->circular(), Tables\Columns\TextColumn::make('ai_confidence') ->label('AI 置信度') ->badge() ->color(fn (Model $record): string => $record->confidence_badge) ->formatStateUsing(fn (?float $state): string => $state ? number_format($state * 100, 1) . '%' : 'N/A'), Tables\Columns\ToggleColumn::make('is_question_candidate') ->label('是题目'), Tables\Columns\TextColumn::make('status') ->label('状态') ->badge() ->color(fn (Model $record): string => $record->status_badge), ]) ->filters([ Tables\Filters\SelectFilter::make('import_id') ->label('导入记录') ->options(function () { return \App\Models\MarkdownImport::query() ->pluck('file_name', 'id') ->toArray(); }) ->query(function ($query, $data) { if (!empty($data['value'])) { $query->where('import_id', $data['value']); } }), TernaryFilter::make('is_question_candidate') ->label('是否为题目'), Tables\Filters\SelectFilter::make('status') ->label('审核状态') ->options([ 'ai_pending' => 'AI 解析中', 'pending' => '待审核', 'reviewed' => '已审核', 'accepted' => '已接受', 'rejected' => '已拒绝', 'superseded' => '已被新解析覆盖', ]), ]) ->actions([ Action::make('review_edit') ->label('校对/编辑') ->icon('heroicon-o-pencil-square') ->color('primary') ->modalHeading(fn (Model $record): string => "校对候选题 #{$record->index}") ->form([ Section::make('审核标记') ->schema([ Toggle::make('is_question_candidate') ->label('是题目') ->default(fn (Model $record) => (bool) $record->is_question_candidate), TextInput::make('ai_confidence') ->label('AI 置信度') ->disabled(), ])->columns(2), Section::make('原始 Markdown(可编辑)') ->schema([ Textarea::make('raw_markdown') ->label('raw_markdown') ->rows(10) ->required(), ])->columnSpanFull(), Section::make('结构化字段(可编辑)') ->schema([ Textarea::make('stem') ->label('题干(stem)') ->rows(6), Textarea::make('options') ->label('选项(JSON)') ->rows(6) ->helperText('示例:{"A":"...","B":"..."};没有选项填空留空'), TagsInput::make('images') ->label('图片 URLs') ->placeholder('https://...'), Textarea::make('tables') ->label('表格(JSON 数组或 HTML)') ->rows(6) ->helperText('支持填写 JSON 数组(推荐)或直接粘贴 ...
'), ])->columns(2), ]) ->fillForm(function (Model $record): array { return [ 'is_question_candidate' => (bool) $record->is_question_candidate, 'ai_confidence' => $record->ai_confidence ? number_format($record->ai_confidence * 100, 1) : null, 'raw_markdown' => (string) $record->raw_markdown, 'stem' => $record->stem, 'options' => $record->options ? json_encode($record->options, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : null, 'images' => $record->images ?? [], 'tables' => $record->tables ? json_encode($record->tables, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : null, ]; }) ->action(function (array $data, Model $record): void { $options = null; if (!empty($data['options'])) { $decoded = json_decode((string) $data['options'], true); if (json_last_error() === JSON_ERROR_NONE) { $options = $decoded; } } $tables = []; if (!empty($data['tables'])) { $decoded = json_decode((string) $data['tables'], true); if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { $tables = $decoded; } else { $tables = [(string) $data['tables']]; } } $record->update([ 'raw_markdown' => (string) $data['raw_markdown'], 'stem' => $data['stem'] ?? null, 'options' => $options, 'images' => $data['images'] ?? [], 'tables' => $tables, 'is_question_candidate' => (bool) ($data['is_question_candidate'] ?? false), 'status' => 'reviewed', ]); }), ]) ->bulkActions([ BulkActionGroup::make([ \App\Filament\Resources\PreQuestionCandidateResource\Actions\MarkAsQuestionsBulkAction::make() ->label('标记为题目'), \App\Filament\Resources\PreQuestionCandidateResource\Actions\MarkAsNonQuestionsBulkAction::make() ->label('标记为非题目'), \App\Filament\Resources\PreQuestionCandidateResource\Actions\ConvertToPreQuestionsBulkAction::make() ->label('入库到筛选库'), ]), ]) ->defaultSort('sequence', 'asc') ->paginated([20, 50, 100]) ->poll('10s'); } public static function getPages(): array { return [ 'index' => Pages\ListPreQuestionCandidates::route('/'), ]; } }