| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- <div>
- <div class="space-y-6">
- <!-- 页面顶部:返回按钮和标题 -->
- <div class="flex items-center justify-between">
- <div class="flex items-center gap-4">
- <a href="{{ url('/admin/exam-history') }}"
- class="btn btn-ghost btn-sm">
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
- </svg>
- 返回列表
- </a>
- <div>
- <h2 class="text-2xl font-bold text-gray-900">试卷详情</h2>
- <p class="mt-1 text-sm text-gray-500">
- 查看和编辑试卷信息,管理试卷中的题目
- </p>
- </div>
- </div>
- </div>
- @if(empty($paperDetail))
- <div class="alert alert-error">
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
- </svg>
- <span>试卷不存在或已被删除</span>
- </div>
- @else
- <!-- 试卷基本信息卡片 -->
- <div class="card bg-base-100 shadow-xl">
- <div class="card-body">
- <div class="flex items-start justify-between">
- <div class="flex-1">
- <div class="flex items-center gap-3 mb-4">
- <h3 class="card-title text-xl">{{ $paperDetail['paper_name'] ?? '未命名试卷' }}</h3>
- <span class="badge badge-{{ $this->getStatusColor($paperDetail['status']) }}">
- {{ $this->getStatusLabel($paperDetail['status']) }}
- </span>
- <span class="badge badge-{{ $this->getDifficultyColor($paperDetail['difficulty_category']) }}">
- {{ $paperDetail['difficulty_category'] }}
- </span>
- </div>
- <div class="stats stats-horizontal shadow bg-base-200">
- <div class="stat">
- <div class="stat-title">题目数量</div>
- <div class="stat-value text-primary">{{ $paperDetail['question_count'] }}</div>
- <div class="stat-desc">题</div>
- </div>
- <div class="stat">
- <div class="stat-title">总分</div>
- <div class="stat-value text-secondary">{{ $paperDetail['total_score'] }}</div>
- <div class="stat-desc">分</div>
- </div>
- <div class="stat">
- <div class="stat-title">创建时间</div>
- <div class="stat-value text-lg" style="font-size: 1rem;">
- {{ \Carbon\Carbon::parse($paperDetail['created_at'])->format('Y-m-d') }}
- </div>
- <div class="stat-desc">
- {{ \Carbon\Carbon::parse($paperDetail['created_at'])->format('H:i') }}
- </div>
- </div>
- </div>
- </div>
- <div class="flex gap-2">
- <button
- wire:click="startEditExam"
- class="btn btn-outline btn-sm">
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
- </svg>
- 编辑试卷
- </button>
- <button
- wire:click="previewPaper"
- class="btn btn-outline btn-sm"
- title="预览试卷卷子,可以直接打印">
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
- </svg>
- 预览卷子
- </button>
- <button
- wire:click="duplicateExam"
- class="btn btn-outline btn-sm">
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
- </svg>
- 复制配置
- </button>
- <button
- wire:click="$toggle('showAddQuestionModal')"
- class="btn btn-primary btn-sm">
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
- </svg>
- 添加题目
- </button>
- </div>
- </div>
- </div>
- </div>
- <!-- 题目列表 -->
- <div class="card bg-base-100 shadow-xl">
- <div class="card-body">
- <div class="flex items-center justify-between mb-4">
- <h3 class="card-title">试卷题目</h3>
- <span class="badge badge-lg">{{ count($paperDetail['questions']) }} 题</span>
- </div>
- @forelse($paperDetail['questions'] as $question)
- <div class="border rounded-lg p-4 mb-4 hover:bg-gray-50">
- <div class="flex items-start justify-between gap-4">
- <div class="flex-1">
- <!-- 题目头部信息 -->
- <div class="flex items-center gap-2 mb-3">
- <span class="badge badge-primary badge-lg">
- 第 {{ $question['question_number'] }} 题
- </span>
- <span class="badge badge-sm">
- {{ $question['question_type'] }}
- </span>
- <span class="badge badge-outline badge-sm">
- {{ $question['knowledge_point'] }}
- </span>
- <span class="badge badge-{{ $question['difficulty'] <= 0.4 ? 'success' : ($question['difficulty'] <= 0.7 ? 'warning' : 'error') }} badge-sm">
- {{ $question['difficulty_label'] }}
- </span>
- <span class="badge badge-secondary badge-sm">
- {{ $question['score'] }} 分
- </span>
- <span class="badge badge-ghost badge-sm">
- {{ $question['estimated_time'] }} 秒
- </span>
- </div>
- <!-- 题目题干 -->
- <div class="bg-base-200 p-4 rounded-lg mb-3">
- <div class="text-sm text-gray-500 mb-2">题干:</div>
- <div class="prose prose-sm max-w-none">
- @php
- $stemHtml = nl2br(\App\Services\MathFormulaProcessor::processFormulas($question['stem'] ?? ''));
- @endphp
- {!! $stemHtml !!}
- </div>
- </div>
- <!-- 答案和解析 -->
- <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
- @if($question['answer'])
- <div class="bg-success/10 p-3 rounded-lg">
- <div class="text-sm font-semibold text-success mb-1">答案:</div>
- <div class="text-sm">{!! nl2br(e($question['answer'])) !!}</div>
- </div>
- @endif
- @if($question['solution'])
- <div class="bg-info/10 p-3 rounded-lg">
- <div class="text-sm font-semibold text-info mb-1">解析:</div>
- <div class="text-sm">{!! nl2br(e($question['solution'])) !!}</div>
- </div>
- @endif
- </div>
- <!-- 题目代码 -->
- @if($question['question_code'])
- <div class="text-xs text-gray-400 mt-2">
- 题目编号:{{ $question['question_code'] }}
- </div>
- @endif
- </div>
- <!-- 操作按钮 -->
- <div class="flex flex-col gap-2">
- <button
- wire:click="deleteQuestion({{ $question['id'] }})"
- wire:confirm="确定要删除这道题目吗?"
- class="btn btn-error btn-outline btn-sm">
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
- </svg>
- 删除
- </button>
- </div>
- </div>
- </div>
- @empty
- <div class="text-center py-12 text-gray-400">
- <svg class="w-16 h-16 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
- </svg>
- <p>试卷中暂无题目</p>
- <button
- wire:click="$toggle('showAddQuestionModal')"
- class="btn btn-primary btn-sm mt-4">
- 立即添加题目
- </button>
- </div>
- @endforelse
- </div>
- </div>
- <!-- 试卷预览区域 -->
- @if($showPreview && !empty($paperDetail))
- @php
- $paperPreviewUrl = route('filament.admin.auth.intelligent-exam.pdf', [
- 'paper_id' => $paperDetail['paper_id'] ?? ($paperId ?? ''),
- 'answer' => 'false',
- ]);
- $gradingPreviewUrl = route('filament.admin.auth.intelligent-exam.grading', [
- 'paper_id' => $paperDetail['paper_id'] ?? ($paperId ?? ''),
- ]);
- @endphp
- <div class="bg-white p-6 rounded-lg border shadow-sm">
- <div class="flex items-center justify-between mb-4">
- <div class="flex items-center gap-3">
- <div class="w-10 h-10 bg-emerald-100 rounded-lg flex items-center justify-center">
- <svg class="w-6 h-6 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
- </svg>
- </div>
- <div>
- <h3 class="text-lg font-semibold text-gray-900">试卷预览 - {{ $paperDetail['paper_name'] }}</h3>
- <p class="text-sm text-gray-500">预览区默认展示试卷正文,底部自动附加判卷页,打印会连续输出两份</p>
- </div>
- </div>
- <div class="flex items-center gap-3">
- <a
- href="{{ $paperPreviewUrl }}"
- target="_blank"
- class="btn btn-ghost btn-sm"
- title="新窗口打开试卷预览">
- 试卷新窗口预览
- </a>
- <a
- href="{{ $gradingPreviewUrl }}"
- target="_blank"
- class="btn btn-ghost btn-sm"
- title="新窗口打开判卷预览">
- 判卷新窗口预览
- </a>
- <button
- wire:click="printPaper"
- class="btn btn-primary btn-sm">
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"></path>
- </svg>
- 打印试卷+判卷
- </button>
- <button
- wire:click="$set('showPreview', false)"
- class="btn btn-outline btn-sm">
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
- </svg>
- 关闭预览
- </button>
- </div>
- </div>
- @if(!empty($paperPreviewUrl))
- <div class="bg-gray-100 border rounded-lg overflow-hidden mb-4">
- <iframe
- id="examPreviewFrame"
- src="{{ $paperPreviewUrl }}"
- class="w-full"
- style="min-height: 900px; background: white;"
- title="试卷预览(智能出卷)">
- </iframe>
- </div>
- <div class="bg-amber-50 border rounded-lg overflow-hidden">
- <div class="px-4 py-2 text-sm text-amber-700 bg-amber-100 border-b">
- 判卷页面(将随试卷一起打印,含答题方框与答案解析)
- </div>
- <iframe
- id="examGradingFrame"
- src="{{ $gradingPreviewUrl }}"
- class="w-full"
- style="min-height: 800px; background: white;"
- title="判卷预览">
- </iframe>
- </div>
- @else
- <div class="alert alert-error">
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
- </svg>
- <span>未找到试卷 ID,无法加载智能出卷预览。</span>
- </div>
- @endif
- </div>
- @endif
- @endif
- </div>
- <!-- 编辑试卷模态框 -->
- @if($editingExamId)
- <div class="modal modal-open">
- <div class="modal-box">
- <h3 class="font-bold text-lg mb-4">编辑试卷</h3>
- <div class="space-y-4">
- <div class="form-control">
- <label class="label">
- <span class="label-text">试卷名称</span>
- </label>
- <input type="text" wire:model="editForm.paper_name"
- class="input input-bordered input-primary"
- placeholder="请输入试卷名称" />
- @error('editForm.paper_name')
- <label class="label">
- <span class="label-text-alt text-error">{{ $message }}</span>
- </label>
- @enderror
- </div>
- <div class="form-control">
- <label class="label">
- <span class="label-text">难度分类</span>
- </label>
- <select wire:model="editForm.difficulty_category"
- class="select select-bordered select-primary">
- <option value="">-- 请选择难度 --</option>
- <option value="基础">基础</option>
- <option value="进阶">进阶</option>
- <option value="竞赛">竞赛</option>
- </select>
- @error('editForm.difficulty_category')
- <label class="label">
- <span class="label-text-alt text-error">{{ $message }}</span>
- </label>
- @enderror
- </div>
- <div class="form-control">
- <label class="label">
- <span class="label-text">状态</span>
- </label>
- <select wire:model="editForm.status"
- class="select select-bordered select-primary">
- <option value="">-- 请选择状态 --</option>
- <option value="draft">草稿</option>
- <option value="completed">已完成</option>
- <option value="graded">已评分</option>
- </select>
- @error('editForm.status')
- <label class="label">
- <span class="label-text-alt text-error">{{ $message }}</span>
- </label>
- @enderror
- </div>
- </div>
- <div class="modal-action">
- <button wire:click="cancelEdit" class="btn btn-ghost">取消</button>
- <button wire:click="saveExamEdit" class="btn btn-primary">保存</button>
- </div>
- </div>
- </div>
- @endif
- <!-- 添加题目模态框 -->
- @if($showAddQuestionModal)
- <div class="modal modal-open">
- <div class="modal-box max-w-4xl">
- <h3 class="font-bold text-lg mb-4">添加题目</h3>
- <div class="space-y-4">
- <div class="alert alert-info">
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
- </svg>
- <span>
- <strong>提示:</strong>添加题目功能需要连接外部题库API,当前版本暂未开放此功能。
- 您可以删除现有题目,或通过其他方式管理试卷题目。
- </span>
- </div>
- </div>
- <div class="modal-action">
- <button wire:click="$toggle('showAddQuestionModal')" class="btn btn-ghost">关闭</button>
- </div>
- </div>
- </div>
- @endif
- <!-- 打印/预览功能脚本(统一使用智能出卷的 PDF 页面) -->
- <script>
- document.addEventListener('livewire:init', () => {
- Livewire.on('print-paper', (payload) => {
- const url = typeof payload === 'string' ? payload : payload?.url;
- if (!url) return;
- const printWindow = window.open(url, '_blank');
- if (!printWindow) return;
- const handleLoad = () => {
- try {
- printWindow.focus();
- printWindow.print();
- } catch (e) {
- console.warn('打印失败,请检查浏览器弹窗权限', e);
- }
- };
- if (printWindow.document?.readyState === 'complete') {
- handleLoad();
- } else {
- printWindow.addEventListener('load', handleLoad, { once: true });
- }
- });
- Livewire.on('refresh-preview', (payload) => {
- const previewUrl = typeof payload === 'string'
- ? payload
- : payload?.previewUrl || payload?.url;
- const gradingUrl = typeof payload === 'object' ? payload?.gradingUrl : null;
- const frame = document.getElementById('examPreviewFrame');
- if (frame && previewUrl) {
- frame.src = previewUrl;
- }
- const gradingFrame = document.getElementById('examGradingFrame');
- if (gradingFrame && gradingUrl) {
- gradingFrame.src = gradingUrl;
- }
- });
- });
- </script>
- </div>
|