| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- <x-filament::page>
- <div
- x-data
- x-on:keydown.window.prevent.arrow-right="$wire.nextCandidate()"
- x-on:keydown.window.prevent.arrow-left="$wire.previousCandidate()"
- class="space-y-4"
- >
- <div class="flex flex-wrap items-center gap-3">
- <x-filament::input.wrapper class="w-64">
- <x-filament::input wire:model.debounce.400ms="search" placeholder="搜索题干/Markdown" />
- </x-filament::input.wrapper>
- <x-filament::input.wrapper class="w-40">
- <x-filament::input.select wire:model="statusFilter">
- <option value="">全部状态</option>
- <option value="pending">待审核</option>
- <option value="reviewed">已审核</option>
- <option value="accepted">已接受</option>
- </x-filament::input.select>
- </x-filament::input.wrapper>
- <x-filament::input.wrapper class="w-56">
- <x-filament::input.select wire:model="sourcePaperFilter">
- <option value="">全部卷子</option>
- @foreach($this->sourcePaperOptions() as $id => $title)
- <option value="{{ $id }}">{{ $title }}</option>
- @endforeach
- </x-filament::input.select>
- </x-filament::input.wrapper>
- <x-filament::input.wrapper class="w-48">
- <x-filament::input.select wire:model="partFilter">
- <option value="">全部区块</option>
- @foreach($this->partOptions() as $id => $title)
- <option value="{{ $id }}">{{ $title }}</option>
- @endforeach
- </x-filament::input.select>
- </x-filament::input.wrapper>
- <x-filament::button color="gray" wire:click="previousCandidate">上一题 ←</x-filament::button>
- <x-filament::button color="gray" wire:click="nextCandidate">下一题 →</x-filament::button>
- <x-filament::button color="primary" wire:click="saveCandidate">保存当前</x-filament::button>
- <x-filament::button color="gray" wire:click="$set('dense', ! $wire.dense)">
- 密度切换
- </x-filament::button>
- <x-filament::button color="gray" wire:click="$set('viewMode', 'list')">表格视图</x-filament::button>
- <x-filament::button color="gray" wire:click="$set('viewMode', 'card')">题卡视图</x-filament::button>
- <x-filament::button color="gray" wire:click="$set('viewMode', 'review')">审核模式</x-filament::button>
- <div class="text-xs text-slate-500">快捷键:← → 切换题目</div>
- </div>
- <div class="grid grid-cols-12 gap-6">
- <div class="col-span-8 space-y-4">
- <x-filament::section>
- <div class="text-sm text-slate-500 mb-2">题目列表(勾选后批量编辑)</div>
- @if($viewMode === 'card')
- <div class="grid grid-cols-2 gap-3 max-h-64 overflow-y-auto">
- @foreach($this->candidates() as $candidate)
- <div class="border rounded-lg p-3 hover:border-primary-400">
- <label class="flex items-start gap-2">
- <input type="checkbox" wire:model="selectedIds" value="{{ $candidate->id }}" class="mt-1 rounded border-gray-300">
- <button type="button" wire:click="selectCandidate({{ $candidate->id }})" class="text-left">
- <div class="text-sm font-medium text-gray-900">{{ \Illuminate\Support\Str::limit($candidate->stem ?? $candidate->raw_markdown, 60) }}</div>
- <div class="text-xs text-gray-500">#{{ $candidate->index }} · {{ $candidate->part?->title }}</div>
- <div class="mt-1 flex flex-wrap gap-2 text-xs">
- @if(($candidate->ai_confidence ?? 0) < 0.6)
- <span class="ui-tag text-amber-700 border-amber-200 bg-amber-50">AI不确定</span>
- @endif
- @if(empty($candidate->meta['difficulty'] ?? null))
- <span class="ui-tag text-rose-700 border-rose-200 bg-rose-50">缺难度</span>
- @endif
- @if(empty($candidate->meta['kp_codes'] ?? []))
- <span class="ui-tag text-rose-700 border-rose-200 bg-rose-50">缺知识点</span>
- @endif
- </div>
- </button>
- </label>
- </div>
- @endforeach
- </div>
- @else
- <div class="{{ $dense ? 'max-h-64' : 'max-h-64' }} overflow-y-auto divide-y divide-gray-100">
- @foreach($this->candidates() as $candidate)
- <label class="flex items-start gap-3 {{ $dense ? 'py-1' : 'py-2' }}">
- <input type="checkbox" wire:model="selectedIds" value="{{ $candidate->id }}" class="mt-1 rounded border-gray-300">
- <button type="button" wire:click="selectCandidate({{ $candidate->id }})" class="text-left flex-1">
- <div class="text-sm font-medium text-gray-900">{{ \Illuminate\Support\Str::limit($candidate->stem ?? $candidate->raw_markdown, 80) }}</div>
- <div class="text-xs text-gray-500">#{{ $candidate->index }} · {{ $candidate->sourcePaper?->title }} · {{ $candidate->part?->title }}</div>
- <div class="mt-1 flex flex-wrap gap-2 text-xs">
- @if(($candidate->ai_confidence ?? 0) < 0.6)
- <span class="ui-tag text-amber-700 border-amber-200 bg-amber-50">AI不确定</span>
- @endif
- @if(empty($candidate->meta['difficulty'] ?? null))
- <span class="ui-tag text-rose-700 border-rose-200 bg-rose-50">缺难度</span>
- @endif
- @if(empty($candidate->meta['kp_codes'] ?? []))
- <span class="ui-tag text-rose-700 border-rose-200 bg-rose-50">缺知识点</span>
- @endif
- @if(empty($candidate->meta['answer'] ?? null))
- <span class="ui-tag text-rose-700 border-rose-200 bg-rose-50">缺答案</span>
- @endif
- </div>
- </button>
- </label>
- @endforeach
- @if($this->candidates()->isEmpty())
- <div class="py-6 text-center text-sm text-gray-500">暂无候选题目</div>
- @endif
- </div>
- @endif
- </x-filament::section>
- <x-filament::section>
- <div class="text-sm text-slate-500 mb-2">题目预览</div>
- <div class="prose prose-sm max-w-none bg-gray-50 p-4 rounded-lg min-h-[240px]">
- @if($this->currentCandidate())
- {!! \App\Services\MathFormulaProcessor::processFormulas($this->currentCandidate()?->stem ?? $this->currentCandidate()?->raw_markdown ?? '') !!}
- @else
- <div class="text-sm text-gray-400">暂无选中题目</div>
- @endif
- </div>
- @if($this->currentCandidate()?->images)
- <div class="mt-3 grid grid-cols-3 gap-3">
- @foreach(($this->currentCandidate()?->images ?? []) as $img)
- <div class="rounded-lg border border-slate-200 bg-white p-2">
- <img src="{{ $img }}" alt="题目图片" class="w-full h-24 object-contain" />
- </div>
- @endforeach
- </div>
- @endif
- </x-filament::section>
- </div>
- <div class="col-span-4 space-y-4">
- <x-filament::section heading="题目属性">
- <div class="grid grid-cols-2 gap-3">
- <x-filament::input.wrapper>
- <x-filament::input.select wire:model="form.question_type">
- <option value="">题型</option>
- <option value="choice">选择题</option>
- <option value="fill">填空题</option>
- <option value="short">简答题</option>
- <option value="calc">计算题</option>
- </x-filament::input.select>
- </x-filament::input.wrapper>
- <x-filament::input.wrapper>
- <x-filament::input.select wire:model="form.difficulty">
- <option value="">难度</option>
- <option value="1">★</option>
- <option value="2">★★</option>
- <option value="3">★★★</option>
- <option value="4">★★★★</option>
- <option value="5">★★★★★</option>
- </x-filament::input.select>
- </x-filament::input.wrapper>
- <x-filament::input.wrapper>
- <x-filament::input wire:model="form.score" placeholder="分值" />
- </x-filament::input.wrapper>
- <x-filament::input.wrapper>
- <x-filament::input.select wire:model="form.part_id">
- <option value="">题型区块</option>
- @foreach($this->partOptions() as $id => $title)
- <option value="{{ $id }}">{{ $title }}</option>
- @endforeach
- </x-filament::input.select>
- </x-filament::input.wrapper>
- <x-filament::input.wrapper class="col-span-2">
- <x-filament::input.select wire:model="form.source_paper_id">
- <option value="">来源卷子</option>
- @foreach($this->sourcePaperOptions() as $id => $title)
- <option value="{{ $id }}">{{ $title }}</option>
- @endforeach
- </x-filament::input.select>
- </x-filament::input.wrapper>
- </div>
- <div class="mt-3 space-y-2">
- <x-filament::input.wrapper>
- <x-filament::input wire:model.debounce.300ms="kpSearch" placeholder="搜索知识点(编码/名称)" />
- </x-filament::input.wrapper>
- <x-filament::input.wrapper>
- <x-filament::input.select wire:model="form.kp_codes" multiple>
- @foreach($this->filteredKnowledgePointOptions() as $code => $name)
- <option value="{{ $code }}">{{ $name }} ({{ $code }})</option>
- @endforeach
- </x-filament::input.select>
- </x-filament::input.wrapper>
- </div>
- <div class="mt-3">
- <x-filament::input.wrapper>
- <x-filament::input wire:model="form.tags" placeholder="标签(逗号分隔)" />
- </x-filament::input.wrapper>
- </div>
- <div class="mt-3">
- <x-filament::input.wrapper>
- <textarea wire:model="form.stem" rows="5" placeholder="题干编辑" class="w-full rounded-lg border-gray-300 focus:border-primary-500 focus:ring-primary-500"></textarea>
- </x-filament::input.wrapper>
- </div>
- <div class="mt-3">
- <x-filament::input.wrapper>
- <textarea wire:model="form.options" rows="4" placeholder="选项 JSON" class="w-full rounded-lg border-gray-300 focus:border-primary-500 focus:ring-primary-500"></textarea>
- </x-filament::input.wrapper>
- </div>
- <div class="mt-3">
- <x-filament::input.wrapper>
- <x-filament::input wire:model="form.order_index" placeholder="题目顺序" />
- </x-filament::input.wrapper>
- </div>
- <div class="mt-3">
- <x-filament::input.wrapper>
- <x-filament::input wire:model="form.images" placeholder="SVG/图片 URL(逗号分隔)" />
- </x-filament::input.wrapper>
- </div>
- </x-filament::section>
- <x-filament::section heading="AI 辅助">
- <div class="flex flex-wrap gap-2">
- <x-filament::button color="gray" wire:click="aiMatchKnowledge">推荐知识点</x-filament::button>
- <x-filament::button color="gray" wire:click="aiGenerateSolution">生成解析</x-filament::button>
- <x-filament::button color="primary" wire:click="aiAutoFill">智能补全</x-filament::button>
- </div>
- <div class="mt-3">
- <x-filament::input.wrapper>
- <x-filament::input wire:model="form.answer" placeholder="答案" />
- </x-filament::input.wrapper>
- </div>
- <div class="mt-3">
- <x-filament::input.wrapper>
- <textarea wire:model="form.solution" rows="3" placeholder="AI 解析文本" class="w-full rounded-lg border-gray-300 focus:border-primary-500 focus:ring-primary-500"></textarea>
- </x-filament::input.wrapper>
- </div>
- <div class="mt-3">
- <x-filament::input.wrapper>
- <textarea wire:model="form.solution_steps" rows="5" placeholder="分步 JSON" class="w-full rounded-lg border-gray-300 focus:border-primary-500 focus:ring-primary-500"></textarea>
- </x-filament::input.wrapper>
- </div>
- </x-filament::section>
- <x-filament::section heading="批量设置">
- <div class="grid grid-cols-2 gap-2">
- <x-filament::input.wrapper>
- <x-filament::input.select wire:model="batch.question_type">
- <option value="">题型</option>
- <option value="choice">选择题</option>
- <option value="fill">填空题</option>
- <option value="short">简答题</option>
- <option value="calc">计算题</option>
- </x-filament::input.select>
- </x-filament::input.wrapper>
- <x-filament::input.wrapper>
- <x-filament::input wire:model="batch.difficulty" placeholder="难度" />
- </x-filament::input.wrapper>
- <x-filament::input.wrapper>
- <x-filament::input wire:model="batch.score" placeholder="分值" />
- </x-filament::input.wrapper>
- <x-filament::input.wrapper>
- <x-filament::input.select wire:model="batch.part_id">
- <option value="">区块</option>
- @foreach($this->partOptions() as $id => $title)
- <option value="{{ $id }}">{{ $title }}</option>
- @endforeach
- </x-filament::input.select>
- </x-filament::input.wrapper>
- </div>
- <div class="mt-2">
- <x-filament::input.wrapper>
- <x-filament::input wire:model="batch.tags" placeholder="标签(逗号分隔)" />
- </x-filament::input.wrapper>
- </div>
- <div class="mt-2">
- <x-filament::input.wrapper>
- <x-filament::input.select wire:model="batch.source_paper_id">
- <option value="">来源卷子</option>
- @foreach($this->sourcePaperOptions() as $id => $title)
- <option value="{{ $id }}">{{ $title }}</option>
- @endforeach
- </x-filament::input.select>
- </x-filament::input.wrapper>
- </div>
- <div class="mt-2">
- <x-filament::input.wrapper>
- <x-filament::input.select wire:model="aiBatchMode">
- <option value="missing">AI 只补空字段</option>
- <option value="overwrite">AI 覆盖全部字段</option>
- </x-filament::input.select>
- </x-filament::input.wrapper>
- </div>
- <div class="mt-2 text-xs text-slate-500">按题序难度仅基于选中题目;批量 AI 会逐题执行,题量较大时可能耗时。</div>
- <div class="mt-3">
- <x-filament::button color="warning" x-on:click.prevent="if(confirm('确认批量覆盖选中题目?')) { $wire.applyBatch() }">批量应用</x-filament::button>
- <x-filament::button color="gray" wire:click="seedBatchFromCurrent">以当前题为默认</x-filament::button>
- <x-filament::button color="gray" x-on:click.prevent="if(confirm('确认对选中题按题序自动设置难度?')) { $wire.applyDifficultyByOrder() }">按题序自动难度</x-filament::button>
- <x-filament::button color="primary" x-on:click.prevent="if(confirm('确认对选中题进行 AI 批量补全?')) { $wire.aiBatchAutoFill() }">AI 批量补全</x-filament::button>
- </div>
- </x-filament::section>
- </div>
- </div>
- </div>
- </x-filament::page>
|