| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- @php
- $rules = \App\Services\QuestionQualityCheckService::RULES;
- $btnSm = \Filament\Support\Enums\Size::Small;
- @endphp
- <x-filament::page
- x-data="{}"
- x-on:qtr-scroll-top.window="window.scrollTo({ top: 0, behavior: 'smooth' })"
- >
- {{-- 仅监听服务端动作,避免对现有组卷视图组件产生耦合副作用 --}}
- <div
- wire:loading.delay.shortest
- wire:target="selectKp, updatedSelectedKpCode, importSelected, importSelectedTemIdsFast, importAllCurrentKpToQuestions, importAssemblyQueueToQuestions, addSelectionToAssemblyQueue, clearAssemblyQueue, clearTemSelection, removeFromAssemblyQueue, generateTrialGradingPdf"
- class="fixed inset-0 z-[130] flex items-center justify-center bg-white/70 dark:bg-gray-950/70"
- >
- <div class="qtr-page-loading-card">
- <div class="qtr-page-loading-spinner" role="status" aria-hidden="true"></div>
- <span class="qtr-page-loading-text">{{ __('加载中…') }}</span>
- </div>
- </div>
- {{-- Filament 主题自带 fi-* 样式;此处用内联布局避免依赖 Tailwind 工具类是否被打进 app.css --}}
- <style>
- .qtr-page-loading-card {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 0.5rem;
- padding: 1rem 1.5rem;
- border-radius: 0.5rem;
- border: 1px solid rgb(226 232 240);
- background: #fff;
- box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.08);
- }
- .dark .qtr-page-loading-card {
- border-color: rgb(55 65 81);
- background: rgb(17 24 39);
- box-shadow: none;
- }
- .qtr-page-loading-text {
- font-size: 0.875rem;
- line-height: 1.25;
- color: #475569;
- margin: 0;
- }
- .dark .qtr-page-loading-text {
- color: #94a3b8;
- }
- /* 纯 CSS 小环,固定 1.5rem;勿用依赖 Tailwind 的 SVG */
- .qtr-page-loading-spinner {
- box-sizing: border-box;
- width: 1.5rem;
- height: 1.5rem;
- flex-shrink: 0;
- border: 2px solid rgba(100, 116, 139, 0.25);
- border-top-color: rgb(100, 116, 139);
- border-radius: 50%;
- animation: qtr-spin 0.7s linear infinite;
- }
- @@keyframes qtr-spin {
- to { transform: rotate(360deg); }
- }
- .qtr-shell {
- width: 100%;
- display: grid;
- grid-template-columns: minmax(13rem, 16rem) minmax(0, 1fr) minmax(15rem, 22rem);
- gap: 1rem;
- align-items: start;
- }
- /* 左右栏:滚动主内容时保持吸顶悬浮(中间栏正常滚动) */
- .qtr-sticky-side {
- position: sticky;
- top: 0.75rem;
- align-self: start;
- max-height: calc(100vh - 1.25rem);
- overflow-y: auto;
- overflow-x: hidden;
- z-index: 2;
- -webkit-overflow-scrolling: touch;
- }
- @@media (max-width: 1024px) {
- .qtr-shell {
- grid-template-columns: 1fr;
- }
- .qtr-sticky-side {
- position: static;
- max-height: none;
- overflow: visible;
- }
- }
- .qtr-kp-scroll {
- display: flex;
- flex-direction: column;
- gap: 0.375rem;
- }
- </style>
- <div class="qtr-shell">
- {{-- 左:知识点 --}}
- <div class="qtr-sticky-side">
- <x-filament::section
- heading="知识点"
- description="按 questions 正式题量升序;仅含 questions_tem 出现过的 KP。数字含义:表内=questions_tem 本 KP 总行数;可入库=去掉与正式库同题干重复后、与中间列表一致的可提交条数"
- :compact="true"
- >
- <div class="mb-3 space-y-2">
- @if (count($this->gradeOptions) > 0)
- <x-filament::input.wrapper>
- <x-filament::input.select wire:model.live="gradeFilter">
- <option value="">全部年级</option>
- @foreach ($this->gradeOptions as $opt)
- <option value="{{ $opt['value'] }}">{{ $opt['label'] }}</option>
- @endforeach
- </x-filament::input.select>
- </x-filament::input.wrapper>
- @endif
- @if (count($this->semesterOptions) > 0)
- <x-filament::input.wrapper>
- <x-filament::input.select wire:model.live="semesterFilter">
- <option value="">全部学期</option>
- @foreach ($this->semesterOptions as $opt)
- <option value="{{ $opt['value'] }}">{{ $opt['label'] }}</option>
- @endforeach
- </x-filament::input.select>
- </x-filament::input.wrapper>
- @endif
- <x-filament::input.wrapper
- inline-prefix
- prefix-icon="heroicon-m-magnifying-glass"
- >
- <x-filament::input
- type="search"
- wire:model.live.debounce.300ms="kpSearch"
- placeholder="搜索代码或名称…"
- autocomplete="off"
- />
- </x-filament::input.wrapper>
- @if (filled($this->kpSearch))
- <p class="text-xs text-gray-500 dark:text-gray-400">
- 显示 {{ count($this->filteredKpRows) }} / 共 {{ count($this->kpRows) }} 个知识点
- </p>
- @endif
- @if (filled($this->gradeFilter))
- @php
- $gradeLabel = collect($this->gradeOptions)->firstWhere('value', (string) $this->gradeFilter)['label'] ?? $this->gradeFilter;
- @endphp
- <p class="text-xs text-gray-500 dark:text-gray-400">
- 已按年级筛选:{{ $gradeLabel }}
- </p>
- @endif
- @if (filled($this->semesterFilter))
- @php
- $semesterLabel = collect($this->semesterOptions)->firstWhere('value', (string) $this->semesterFilter)['label'] ?? $this->semesterFilter;
- @endphp
- <p class="text-xs text-gray-500 dark:text-gray-400">
- 已按学期筛选:{{ $semesterLabel }}
- </p>
- @endif
- </div>
- <div class="qtr-kp-scroll">
- @forelse($this->filteredKpRows as $row)
- <div
- class="w-full"
- wire:key="kp-row-{{ $row['kp_code'] }}"
- x-data="{ kp: @js($row['kp_code']) }"
- >
- <x-filament::button
- x-on:click.prevent="$wire.selectKp(kp)"
- :outlined="$this->selectedKpCode !== $row['kp_code']"
- :color="$this->selectedKpCode === $row['kp_code'] ? 'primary' : 'gray'"
- :size="$btnSm"
- class="w-full"
- style="justify-content: flex-start;"
- >
- <div class="flex w-full flex-col items-stretch gap-1 text-left">
- @if (! empty($row['kp_name'] ?? ''))
- <span class="text-sm font-medium leading-snug line-clamp-2">{{ $row['kp_name'] }}</span>
- @endif
- <span class="font-mono text-xs break-all opacity-90">{{ $row['kp_code'] }}</span>
- <span class="text-xs opacity-80">
- 正式 {{ $row['questions_count'] }}
- · 表内 {{ $row['tem_count'] }}
- · 可入库 {{ $row['tem_importable_count'] ?? 0 }}
- </span>
- </div>
- </x-filament::button>
- </div>
- @empty
- <p class="text-sm text-gray-600 dark:text-gray-400">暂无数据(检查 questions_tem)</p>
- @endforelse
- </div>
- </x-filament::section>
- </div>
- {{-- 中:按 /api/questions/pdf 同源版式展示(交互独立) --}}
- <x-filament::section
- heading="待审题目(题目质检PDF同源版式)"
- description="中间区按 /api/questions/pdf(题目质检)同源布局展示;勾选/聚焦在独立控件中完成。"
- :compact="true"
- >
- @if (! $this->selectedKpCode)
- <p class="text-sm text-gray-600 dark:text-gray-400">请先在左侧选择一个知识点。</p>
- @else
- @php
- $qtrKpMeta = collect($this->kpRows)->firstWhere('kp_code', $this->selectedKpCode);
- @endphp
- <div class="mb-3 flex flex-wrap items-center gap-2 text-sm">
- <span>当前 KP</span>
- <code class="rounded bg-gray-100 px-2 py-0.5 text-xs dark:bg-white/10">{{ $this->selectedKpCode }}</code>
- <span class="text-xs text-gray-500">
- 本列表 <span class="font-semibold text-gray-700 dark:text-gray-200">{{ count($this->temQuestions) }}</span> 道可入库预览
- @if ($qtrKpMeta)
- <span class="text-gray-400 dark:text-gray-500">
- (表内 {{ (int) ($qtrKpMeta['tem_count'] ?? 0) }} 道,已与正式库题干重复而隐藏
- {{ max(0, (int) ($qtrKpMeta['tem_count'] ?? 0) - (int) ($qtrKpMeta['tem_importable_count'] ?? 0)) }}
- 道)
- </span>
- @endif
- </span>
- </div>
- <div class="mb-3 rounded-lg border border-gray-100 p-3 dark:border-white/10">
- <p class="mb-2 text-xs text-gray-500">独立勾选区(每题三项快检:题干/答案/解题思路)</p>
- @if (count($this->temQuestionCards) === 0)
- <p class="text-sm text-gray-600 dark:text-gray-400">当前知识点暂无可入库题目。</p>
- @else
- <style>
- .qtr-card-list { max-height: none; overflow: visible; display: grid; gap: 0.75rem; }
- .qtr-card { border: 1px solid rgb(229 231 235); border-radius: 0.5rem; padding: 0.5rem; }
- .dark .qtr-card { border-color: rgb(75 85 99); }
- .qtr-badge { font-size: 11px; border-radius: 999px; padding: 1px 8px; border: 1px solid transparent; }
- .qtr-badge-ok { color: #166534; background: #bbf7d0; border-color: #4ade80; }
- .qtr-badge-no { color: #991b1b; background: #fee2e2; border-color: #fca5a5; }
- .dark .qtr-badge-ok { color: #86efac; background: rgba(22,101,52,0.25); border-color: rgba(134,239,172,0.35); }
- .dark .qtr-badge-no { color: #fca5a5; background: rgba(127,29,29,0.25); border-color: rgba(252,165,165,0.35); }
- .qtr-full-preview-wrap { margin-top: 0.5rem; border-top: 1px dashed rgb(209 213 219); padding-top: 0.5rem; }
- .dark .qtr-full-preview-wrap { border-top-color: rgb(75 85 99); }
- </style>
- <div class="qtr-card-list">
- @foreach ($this->visibleTemQuestionCards as $card)
- @php
- $tid = (int) $card['id'];
- $checks = $card['checks'];
- $meta = $card['meta'] ?? [];
- $similar = $this->temSimilarityHints[$tid] ?? null;
- $similarScore = (float) ($similar['score'] ?? 0);
- $similarStyle = 'background:#f0fdf4;color:#166534;border:1px solid #86efac;';
- if ($similarScore >= 60.0 && $similarScore <= 80.0) {
- $similarStyle = 'background:#fff7ed;color:#c2410c;border:1px solid #fdba74;';
- } elseif ($similarScore > 80.0) {
- $similarStyle = 'background:#fee2e2;color:#b91c1c;border:1px solid #fca5a5;';
- }
- $auditReason = trim((string) ($meta['audit_reason'] ?? ''));
- $isQualified = $auditReason === '合格';
- $updatedDate = '';
- if (! empty($meta['updated_at']) && $meta['updated_at'] !== '-') {
- $updatedDate = substr((string) $meta['updated_at'], 0, 10);
- }
- $grouped = $card['grouped_questions'] ?? ['choice' => [], 'fill' => [], 'answer' => []];
- $q = $grouped['choice'][0] ?? $grouped['fill'][0] ?? $grouped['answer'][0] ?? null;
- @endphp
- <div class="qtr-card" wire:key="tem-card-{{ $tid }}">
- <div class="mb-1 flex items-start gap-2">
- <button type="button" wire:click="focusTemQuestion({{ $tid }})" class="text-left hover:underline">
- <span class="font-mono text-xs">tem #{{ $tid }}</span>
- </button>
- <span class="qtr-badge qtr-badge-ok">难度 {{ $meta['difficulty'] ?? '-' }}</span>
- @if ((int) ($this->selectedTemId ?? 0) === $tid)
- <span class="qtr-badge qtr-badge-ok">当前聚焦</span>
- @endif
- @if (in_array($tid, $this->pendingImportTemIds, true))
- <span class="qtr-badge qtr-badge-ok">待入库</span>
- @endif
- </div>
- <div class="flex flex-wrap gap-1.5">
- <span class="qtr-badge {{ $checks['stem'] ? 'qtr-badge-ok' : 'qtr-badge-no' }}">题干</span>
- <span class="qtr-badge {{ $checks['answer'] ? 'qtr-badge-ok' : 'qtr-badge-no' }}">答案</span>
- <span class="qtr-badge {{ $checks['solution'] ? 'qtr-badge-ok' : 'qtr-badge-no' }}">解题思路</span>
- @if ($updatedDate !== '')
- <span class="qtr-badge qtr-badge-ok">
- {{ $updatedDate }}
- </span>
- @endif
- </div>
- @if ($similar)
- <div class="mt-1 text-[11px] text-gray-600 dark:text-gray-300">
- <button
- type="button"
- class="inline-flex items-center rounded-full px-2 py-0.5 hover:opacity-90"
- style="background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;"
- x-on:click.prevent="
- const qid = {{ (int) ($similar['question_id'] ?? 0) }};
- window.dispatchEvent(new CustomEvent('qtr-scroll-to-question', { detail: { qid } }));
- "
- >
- 最相似 question #{{ (int) ($similar['question_id'] ?? 0) }}
- </button>
- <span class="ml-1 inline-flex items-center rounded-full px-2 py-0.5" style="{{ $similarStyle }}">
- 综合相似度 {{ (string) ($similar['score_text'] ?? '-') }}
- </span>
- <x-filament::button
- size="xs"
- color="danger"
- class="ml-1"
- wire:click="markAsSimilarQuestion({{ $tid }}, {{ (int) ($similar['question_id'] ?? 0) }})"
- >
- 判定相似题
- </x-filament::button>
- </div>
- @endif
- <div class="qtr-full-preview-wrap">
- @if ($q)
- @include('filament.pages.partials.question-tem-question-check-preview', [
- 'questions' => $card['grouped_questions'],
- 'student' => ['name' => '________', 'grade' => '________'],
- 'teacher' => ['name' => '________'],
- 'pdfMeta' => [
- 'exam_code' => 'tem_'.$tid,
- 'student_name' => '________',
- 'header_title' => '________|tem_'.$tid.'|题目质检',
- 'grading_pdf_title' => '题目质检_tem_'.$tid,
- ],
- ])
- @else
- <div class="text-xs text-gray-500">该题暂无可展示内容</div>
- @endif
- </div>
- <div class="mt-3 border-t border-gray-100 pt-2 dark:border-white/10" x-data="{ operated: false }">
- @php $st = $this->importStatusMap[$tid] ?? null; @endphp
- @php
- $directBtnBase = $isQualified ? '#16a34a' : '#dc2626';
- $directBtnHover = $isQualified ? '#15803d' : '#b91c1c';
- @endphp
- @if ($st)
- <div class="mb-1 text-[11px]">
- @if (($st['state'] ?? '') === 'queued')
- <span class="text-primary-600">状态:排队中</span>
- @elseif (($st['state'] ?? '') === 'running')
- <span class="text-primary-600">状态:入库中</span>
- @elseif (($st['state'] ?? '') === 'done')
- <span class="text-success-600">状态:已入库(question #{{ (int) ($st['question_id'] ?? 0) }})</span>
- @elseif (($st['state'] ?? '') === 'failed')
- <span class="text-danger-600">状态:失败({{ (string) ($st['message'] ?? '未知错误') }})</span>
- @endif
- </div>
- @endif
- <div class="flex flex-wrap items-center gap-2">
- <x-filament::button
- size="xs"
- color="{{ in_array($tid, $this->pendingImportTemIds, true) ? 'gray' : 'primary' }}"
- wire:click="addToPendingImport({{ $tid }})"
- :disabled="in_array($tid, $this->pendingImportTemIds, true)"
- >
- {{ in_array($tid, $this->pendingImportTemIds, true) ? '已在待入库' : '加入待入库' }}
- </x-filament::button>
- @if (in_array($tid, $this->pendingImportTemIds, true))
- <x-filament::button size="xs" color="danger" wire:click="removeFromPendingImport({{ $tid }})">
- 移除待入库
- </x-filament::button>
- @endif
- @php
- $opLocked = in_array(($st['state'] ?? ''), ['queued', 'running', 'done'], true);
- @endphp
- <button
- type="button"
- class="fi-btn fi-size-sm inline-flex items-center gap-1.5 rounded-lg px-3 py-2 text-sm font-semibold text-white shadow-sm transition"
- style="background:{{ $directBtnBase }};"
- onmouseover="this.style.background='{{ $directBtnHover }}'"
- onmouseout="this.style.background='{{ $directBtnBase }}'"
- :disabled="operated || {{ $opLocked ? 'true' : 'false' }}"
- :class="{ 'opacity-80 cursor-not-allowed': operated || {{ $opLocked ? 'true' : 'false' }} }"
- x-on:click.prevent="if (operated || {{ $opLocked ? 'true' : 'false' }}) return; operated = true; $wire.queueImportTem({{ $tid }})"
- >
- <span x-show="!(operated || {{ $opLocked ? 'true' : 'false' }})">直接入库</span>
- <span x-show="operated || {{ $opLocked ? 'true' : 'false' }}">已操作</span>
- </button>
- <span class="text-xs text-success-600" x-show="operated">已入库</span>
- @if ($auditReason !== '' && $auditReason !== '-')
- <span class="ml-auto qtr-badge {{ $isQualified ? 'qtr-badge-ok' : 'qtr-badge-no' }}">
- {{ $auditReason }}
- </span>
- @endif
- </div>
- </div>
- </div>
- @endforeach
- </div>
- @if (count($this->temQuestionCards) > count($this->visibleTemQuestionCards))
- <div class="mt-3">
- <x-filament::button size="sm" color="gray" wire:click="loadMoreCards">
- 加载更多(已显示 {{ count($this->visibleTemQuestionCards) }} / {{ count($this->temQuestionCards) }})
- </x-filament::button>
- </div>
- @endif
- @endif
- </div>
- @endif
- </x-filament::section>
- {{-- 右:待入库 + 人工判重 --}}
- <div
- class="qtr-sticky-side"
- wire:key="qtr-right-stack"
- wire:loading.class=""
- wire:target="addToPendingImport,removeFromPendingImport,queueImportTem,importPendingTem,importPendingAll,clearPendingImport,selectKp,updatedSelectedKpCode,updatedGradeFilter,updatedSemesterFilter"
- x-data="{}"
- x-on:qtr-scroll-to-question.window="
- const qid = Number($event.detail?.qid || 0);
- if (!qid) return;
- const target = document.getElementById('kp-question-' + qid);
- if (!target) return;
- // 优先滚动右侧容器本身(sticky side),避免只滚动主页面
- const targetTop = target.offsetTop;
- const targetHeight = target.offsetHeight || 0;
- const viewHeight = $el.clientHeight || 0;
- const nextTop = Math.max(0, targetTop - (viewHeight / 2) + (targetHeight / 2));
- $el.scrollTo({ top: nextTop, behavior: 'smooth' });
- // 兜底:再触发一次元素就位
- setTimeout(() => {
- target.scrollIntoView({ behavior: 'smooth', block: 'center' });
- target.classList.add('ring-2', 'ring-green-400');
- setTimeout(() => target.classList.remove('ring-2', 'ring-green-400'), 1200);
- }, 180);
- "
- >
- <x-filament::section
- heading="待入库"
- description="优先处理当前待入库列表,可直接入库、回到中间审核或移除。"
- :compact="true"
- >
- @if ($this->pendingImportRows === [])
- <p class="text-sm text-gray-600 dark:text-gray-400">先在中间卡片点击「加入待入库」。</p>
- @else
- <div class="mb-2">
- <x-filament::button size="xs" color="gray" wire:click="syncAsyncImportStatuses">
- 同步异步状态
- </x-filament::button>
- </div>
- <div class="mb-2 text-xs text-gray-500">
- 待入库 <span class="font-semibold text-gray-800 dark:text-gray-200">{{ count($this->pendingImportRows) }}</span> 道
- </div>
- <div class="mb-3 rounded-lg border border-gray-100 p-2 text-xs dark:border-white/10 space-y-2">
- @foreach ($this->pendingImportRows as $pr)
- <div class="rounded border border-gray-100 p-2 dark:border-white/10" wire:key="pending-{{ (int) ($pr->id ?? 0) }}">
- <div class="mb-1 font-mono">tem #{{ (int) ($pr->id ?? 0) }}</div>
- @php $st = $this->importStatusMap[(int) ($pr->id ?? 0)] ?? null; @endphp
- @if ($st)
- <div class="mb-1 text-[11px]">
- @if (($st['state'] ?? '') === 'queued')
- <span class="text-primary-600">排队中</span>
- @elseif (($st['state'] ?? '') === 'running')
- <span class="text-primary-600">入库中</span>
- @elseif (($st['state'] ?? '') === 'done')
- <span class="text-success-600">已入库 #{{ (int) ($st['question_id'] ?? 0) }}</span>
- @elseif (($st['state'] ?? '') === 'failed')
- <span class="text-danger-600">失败:{{ (string) ($st['message'] ?? '未知错误') }}</span>
- @endif
- </div>
- @endif
- <div class="flex flex-wrap gap-1">
- <x-filament::button size="xs" color="success" wire:click="queueImportTem({{ (int) ($pr->id ?? 0) }})">
- 入库
- </x-filament::button>
- <x-filament::button size="xs" color="gray" wire:click="focusTemQuestion({{ (int) ($pr->id ?? 0) }})">
- 审核
- </x-filament::button>
- <x-filament::button size="xs" color="danger" wire:click="removeFromPendingImport({{ (int) ($pr->id ?? 0) }})">
- 移除
- </x-filament::button>
- </div>
- </div>
- @endforeach
- </div>
- @endif
- <div class="flex flex-wrap gap-2">
- <x-filament::button
- color="success"
- wire:click="importPendingAll"
- wire:loading.attr="disabled"
- :disabled="$this->pendingImportRows === []"
- >
- 一键入库待入库全部
- </x-filament::button>
- <x-filament::button
- color="gray"
- wire:click="clearPendingImport"
- wire:loading.attr="disabled"
- :disabled="$this->pendingImportRows === []"
- >
- 清空待入库
- </x-filament::button>
- </div>
- </x-filament::section>
- <x-filament::section
- heading="同知识点正式库题目(人工判重)"
- description="当前筛选 + 当前知识点下,列出 questions 中已有题目(含题干/答案)用于人工核对是否重复。"
- :compact="true"
- class="mt-4"
- >
- @if (! $this->selectedKpCode)
- <p class="text-sm text-gray-600 dark:text-gray-400">先在左侧选择知识点。</p>
- @elseif ($this->currentKpQuestionStemRows === [])
- <p class="text-sm text-gray-600 dark:text-gray-400">正式库当前知识点暂无题目。</p>
- @else
- <div class="rounded-lg border border-gray-100 p-2 text-xs dark:border-white/10 space-y-2">
- @foreach ($this->currentKpQuestionStemRows as $qr)
- @php
- $qid = (int) ($qr->id ?? 0);
- $qStem = (string) ($qr->stem ?? '');
- $qAnswer = trim((string) ($qr->answer ?? ''));
- $qTypeRaw = mb_strtolower((string) ($qr->question_type ?? ''));
- $noPalette = [
- 'background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;',
- 'background:#ecfdf5;color:#047857;border:1px solid #a7f3d0;',
- 'background:#f5f3ff;color:#6d28d9;border:1px solid #ddd6fe;',
- 'background:#fffbeb;color:#b45309;border:1px solid #fde68a;',
- 'background:#fff1f2;color:#be123c;border:1px solid #fecdd3;',
- ];
- $noStyle = $noPalette[$qid % count($noPalette)];
- $typeLabel = '简答';
- $typeStyle = 'background:#f5f3ff;color:#6d28d9;border:1px solid #ddd6fe;';
- if (str_contains($qTypeRaw, 'choice') || str_contains($qTypeRaw, '选择')) {
- $typeLabel = '选择';
- $typeStyle = 'background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;';
- } elseif (str_contains($qTypeRaw, 'fill') || str_contains($qTypeRaw, 'blank') || str_contains($qTypeRaw, '填空')) {
- $typeLabel = '填空';
- $typeStyle = 'background:#fff7ed;color:#c2410c;border:1px solid #fed7aa;';
- }
- $bestForSelected = null;
- if ((int) ($this->selectedTemId ?? 0) > 0) {
- $bestForSelected = $this->temSimilarityHints[(int) $this->selectedTemId] ?? null;
- }
- $isTopSimilar = $bestForSelected && (int) ($bestForSelected['question_id'] ?? 0) === $qid;
- $bestScore = (float) ($bestForSelected['score'] ?? 0);
- // 右侧用“建议阈值”:>=90 绿,80-90 橙,<80 灰
- $bestScoreStyle = 'background:#f3f4f6;color:#4b5563;border:1px solid #d1d5db;';
- if ($bestScore >= 90.0) {
- $bestScoreStyle = 'background:#f0fdf4;color:#166534;border:1px solid #86efac;';
- } elseif ($bestScore >= 80.0) {
- $bestScoreStyle = 'background:#fff7ed;color:#c2410c;border:1px solid #fdba74;';
- }
- @endphp
- <div
- class="rounded border p-2 dark:border-white/10"
- style="{{ $isTopSimilar ? 'border-color:#22c55e;background:#f0fdf4;' : 'border-color:rgb(243 244 246);' }}"
- wire:key="kp-q-{{ (int) ($qr->id ?? 0) }}"
- id="kp-question-{{ $qid }}"
- data-kp-question-id="{{ $qid }}"
- >
- <div class="mb-1 flex items-center gap-1.5">
- <span class="inline-flex items-center rounded-full px-1.5 py-0.5 font-mono text-[10px]" style="{{ $noStyle }}">
- question #{{ $qid }}
- </span>
- <span class="inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px]" style="{{ $typeStyle }}">
- {{ $typeLabel }}
- </span>
- @if ($isTopSimilar && $bestForSelected)
- <span class="inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px]" style="{{ $bestScoreStyle }}">
- 综合相似 {{ number_format((float) ($bestForSelected['score'] ?? 0), 1) }}%
- </span>
- @endif
- </div>
- <div style="font-size:11px;line-height:1.35;color:#4b5563;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;">
- {{ $qStem }}
- </div>
- @if ($qAnswer !== '')
- <div class="mt-1 text-[11px] text-gray-500" style="display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden;">
- 答案:{{ $qAnswer }}
- </div>
- @endif
- </div>
- @endforeach
- </div>
- @endif
- </x-filament::section>
- </div>
- </div>
- </x-filament::page>
|