| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- <?php
- namespace App\Filament\Pages;
- use Filament\Forms;
- use Filament\Pages\Page;
- use Filament\Actions\Action;
- use Filament\Schemas\Schema;
- use Illuminate\Support\Facades\Http;
- use Illuminate\Support\Facades\Log;
- use Filament\Notifications\Notification;
- class QuestionReview extends Page implements Forms\Contracts\HasForms
- {
- use Forms\Concerns\InteractsWithForms;
- protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-clipboard-document-check';
- protected static string|\UnitEnum|null $navigationGroup = '题库校验';
- protected static ?string $title = '题目校验';
- protected string $view = 'filament.pages.question-review';
- public string $book = '';
- public int $page = 1;
- public array $mineru = [];
- public array $builder = [];
- public string $message = '';
- public bool $saving = false;
- public string $builderJson = '';
- public array $bookOptions = [];
- public array $pageOptions = [];
- public ?string $pagePngBase64 = null;
- public bool $showOverlay = true;
- public array $paths = [];
- public function mount(): void
- {
- // 默认值可在 .env 中配置
- $this->book = config('question_bank.default_book', '');
- $this->page = 1;
- // 预填可选书名(扫描 /data/mineru_raw)
- $root = base_path('../data/mineru_raw');
- $dirs = is_dir($root) ? scandir($root) : [];
- $opts = [];
- foreach ($dirs as $d) {
- if ($d === '.' || $d === '..') {
- continue;
- }
- if (is_dir($root . '/' . $d)) {
- $opts[] = $d;
- }
- }
- $this->bookOptions = $opts;
- if ($this->book) {
- $this->refreshPageOptions($this->book);
- }
- }
- public function form(Schema $form): Schema
- {
- return $form
- ->components([
- Forms\Components\Select::make('book')
- ->label('书名/目录名')
- ->options(array_combine($this->bookOptions, $this->bookOptions))
- ->native(false)
- ->searchable()
- ->placeholder('选择书名目录')
- ->reactive()
- ->afterStateUpdated(fn($state) => $this->onBookChange($state))
- ->required(),
- Forms\Components\Select::make('page')
- ->label('页码')
- ->options(fn() => array_combine(
- array_map('strval', $this->pageOptions),
- array_map(fn($p) => "第{$p}页", $this->pageOptions)
- ))
- ->native(false)
- ->searchable()
- ->placeholder('选择页码')
- ->reactive()
- ->afterStateUpdated(fn($state) => $this->onPageChange($state))
- ->required(),
- ]);
- }
- protected function getHeaderActions(): array
- {
- return [
- Action::make('load')
- ->label('加载')
- ->color('primary')
- ->action('loadPage'),
- Action::make('save')
- ->label('保存到草稿')
- ->color('success')
- ->disabled(fn() => empty($this->builder))
- ->action('saveDraft'),
- Action::make('toggleOverlay')
- ->label(fn() => $this->showOverlay ? '隐藏标框' : '显示标框')
- ->color('gray')
- ->action('toggleOverlay'),
- ];
- }
- protected function refreshPageOptions(string $book): void
- {
- $dir = base_path("../data/mineru_raw/{$book}/pages");
- $pages = [];
- if (is_dir($dir)) {
- foreach (glob($dir . '/page_*.json') as $file) {
- if (preg_match('/page_(\\d+)\\.json$/', $file, $m)) {
- $pages[] = (int)$m[1];
- }
- }
- }
- sort($pages);
- $this->pageOptions = $pages;
- if (!empty($pages)) {
- $this->page = $pages[0];
- }
- }
- public function onBookChange($book): void
- {
- $this->book = (string)$book;
- $this->refreshPageOptions($this->book);
- if ($this->book && $this->page) {
- $this->loadPage();
- }
- }
- public function onPageChange($page): void
- {
- if ($page === null || $page === '') {
- return;
- }
- $this->page = (int)$page;
- if ($this->book) {
- $this->loadPage();
- }
- }
- public function toggleOverlay(): void
- {
- $this->showOverlay = !$this->showOverlay;
- }
- public function loadPage(): void
- {
- $apiBase = rtrim(config('services.question_bank.base_url'), '/');
- $url = $apiBase . '/review/' . $this->book . '/' . $this->page;
- try {
- // 重置,避免读取失败时残留旧数据
- $this->mineru = [];
- $this->builder = [];
- $this->builderJson = '';
- $this->pagePngBase64 = null;
- $this->paths = [];
- $resp = Http::timeout(10)->get($url);
- if (!$resp->ok()) {
- $this->message = "加载失败: {$resp->status()} {$resp->body()} (检查 QuestionBankService /review/{book}/{page} 是否可用,或该页是否已生成)";
- return;
- }
- $data = $resp->json();
- $this->mineru = $data['mineru'] ?? [];
- $this->builder = $data['builder'] ?? [];
- $this->builderJson = json_encode($this->builder, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
- $this->pagePngBase64 = $data['page_png_base64'] ?? null;
- $this->paths = $data['paths'] ?? [];
- $this->message = '加载成功';
- } catch (\Throwable $e) {
- Log::warning('[QuestionReview] load failed: ' . $e->getMessage());
- $this->message = '加载异常: ' . $e->getMessage();
- }
- }
- public function saveDraft(): void
- {
- $payloadBuilder = $this->builder;
- if ($this->builderJson) {
- try {
- $decoded = json_decode($this->builderJson, true, 512, JSON_THROW_ON_ERROR);
- if (is_array($decoded)) {
- $payloadBuilder = $decoded;
- }
- } catch (\Throwable $e) {
- $this->message = '保存异常: 题目 JSON 解析失败 - ' . $e->getMessage();
- Notification::make()->title('保存异常')->body($this->message)->danger()->send();
- return;
- }
- }
- if (empty($payloadBuilder)) {
- $this->message = '无可保存的数据,请先加载';
- return;
- }
- $apiBase = rtrim(config('services.question_bank.base_url'), '/');
- $url = $apiBase . '/review/' . $this->book . '/' . $this->page;
- $payload = [
- 'status' => 'reviewed',
- 'questions' => $payloadBuilder['questions'] ?? $payloadBuilder,
- 'source_type' => 'workbook',
- ];
- try {
- $this->saving = true;
- $resp = Http::timeout(10)->post($url, $payload);
- $this->saving = false;
- if (!$resp->ok()) {
- $this->message = "保存失败: {$resp->status()} {$resp->body()}";
- Notification::make()->title('保存失败')->body($this->message)->danger()->send();
- return;
- }
- $this->message = '保存成功';
- Notification::make()->title('保存成功')->success()->send();
- } catch (\Throwable $e) {
- $this->saving = false;
- Log::warning('[QuestionReview] save failed: ' . $e->getMessage());
- $this->message = '保存异常: ' . $e->getMessage();
- Notification::make()->title('保存异常')->body($this->message)->danger()->send();
- }
- }
- public function saveQuestion(int $index): void
- {
- $list = $this->builder['questions'] ?? [];
- if (!isset($list[$index])) {
- $this->message = '未找到该题目';
- Notification::make()->title('保存失败')->body($this->message)->danger()->send();
- return;
- }
- $apiBase = rtrim(config('services.question_bank.base_url'), '/');
- $url = $apiBase . '/review/' . $this->book . '/' . $this->page;
- $payload = [
- 'status' => 'reviewed',
- 'questions' => [$list[$index]],
- 'source_type' => 'workbook',
- ];
- try {
- $resp = Http::timeout(10)->post($url, $payload);
- if (!$resp->ok()) {
- $this->message = "保存失败: {$resp->status()} {$resp->body()}";
- Notification::make()->title('保存失败')->body($this->message)->danger()->send();
- return;
- }
- $this->message = '单题保存成功';
- Notification::make()->title('保存成功')->success()->send();
- } catch (\Throwable $e) {
- $this->message = '保存异常: ' . $e->getMessage();
- Notification::make()->title('保存异常')->body($this->message)->danger()->send();
- }
- }
- }
|