upload-exam-paper.blade.php.backup 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. <x-filament-panels::page>
  2. <div class="space-y-6">
  3. {{-- 模式选择 --}}
  4. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
  5. <div class="flex gap-4">
  6. <button
  7. wire:click="$set('mode', 'upload')"
  8. class="px-4 py-2 rounded-md font-medium transition-colors {{ $mode === 'upload' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }}"
  9. >
  10. <svg class="w-5 h-5 inline-block mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  11. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003 3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path>
  12. </svg>
  13. 上传试卷照片
  14. </button>
  15. <button
  16. wire:click="$set('mode', 'select_paper')"
  17. class="px-4 py-2 rounded-md font-medium transition-colors {{ $mode === 'select_paper' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }}"
  18. >
  19. <svg class="w-5 h-5 inline-block mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  20. <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.707.293V19a2 2 0 012-2H5a2 2 0 01-2 2v-14z"></path>
  21. </svg>
  22. 选择已有试卷评分
  23. </button>
  24. </div>
  25. </div>
  26. {{-- 上传模式 --}}
  27. @if($mode === 'upload')
  28. {{-- 选择老师和学生 --}}
  29. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-6">
  30. <h2 class="text-lg font-semibold mb-4 flex items-center">
  31. <svg class="w-5 h-5 mr-2 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  32. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
  33. </svg>
  34. 选择老师和学生
  35. </h2>
  36. <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
  37. {{-- 选择老师 --}}
  38. <div class="form-control w-full">
  39. <label class="block text-sm font-medium text-gray-700 mb-2">
  40. 选择老师 <span class="text-red-500">*</span>
  41. @if($isTeacher)
  42. <span class="text-green-600 text-xs ml-2">(当前登录)</span>
  43. @endif
  44. </label>
  45. <select
  46. wire:model.live="teacherId"
  47. @if($isTeacher) disabled @endif
  48. class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 @if($isTeacher) bg-gray-100 @endif"
  49. >
  50. <option value="">请选择老师...</option>
  51. @foreach($this->teachers as $teacher)
  52. <option value="{{ $teacher->teacher_id }}">
  53. {{ trim($teacher->name ?? $teacher->teacher_id) . ($teacher->subject ? " ({$teacher->subject})" : '') }}
  54. </option>
  55. @endforeach
  56. </select>
  57. </div>
  58. {{-- 选择学生 --}}
  59. <div class="form-control w-full">
  60. <label class="block text-sm font-medium text-gray-700 mb-2">选择学生 <span class="text-red-500">*</span></label>
  61. <select
  62. wire:model.live="studentId"
  63. wire:loading.attr="disabled"
  64. @if(empty($teacherId)) disabled @endif
  65. class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 @if(empty($teacherId)) bg-gray-100 @endif"
  66. >
  67. <option value="">
  68. @if(empty($teacherId))
  69. 请先选择老师
  70. @else
  71. 请选择学生...
  72. @endif
  73. </option>
  74. @foreach($this->students as $student)
  75. <option value="{{ $student->student_id }}">
  76. {{ trim($student->name ?? $student->student_id) . " ({$student->grade} - {$student->class_name})" }}
  77. </option>
  78. @endforeach
  79. </select>
  80. </div>
  81. </div>
  82. </div>
  83. {{-- 图片上传和OCR识别组件 --}}
  84. @if(!empty($teacherId) && !empty($studentId))
  85. <livewire:upload-exam.upload-form :teacherId="$teacherId" :studentId="$studentId" />
  86. <livewire:upload-exam.ocr-results />
  87. @endif
  88. <div class="mt-6">
  89. <button
  90. type="button"
  91. wire:click="startAnalysis"
  92. wire:loading.attr="disabled"
  93. class="bg-blue-600 text-white px-6 py-2 rounded-md hover:bg-blue-700 disabled:opacity-50 transition-colors"
  94. >
  95. <span wire:loading wire:target="startAnalysis" class="inline-block mr-2">
  96. <svg class="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
  97. <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
  98. <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018 8 0 018 0v4a8 8 0 018-8h4a8 8 0 018-8v-8a8 8 0 018-8-4-4-4-4-4-4-4zm-2 4a6 6 0 016-6h4a6 6 0 016-6v8a6 6 0 016-6-4-4-4-4-4-4-4z"></path>
  99. </svg>
  100. </span>
  101. <span wire:loading.remove wire:target="startAnalysis">开始分析</span>
  102. <span wire:loading wire:target="startAnalysis">分析中...</span>
  103. </button>
  104. </div>
  105. @if($analyzing)
  106. <div class="mt-4 bg-blue-50 border border-blue-200 rounded-lg p-4">
  107. <div class="flex items-center">
  108. <svg class="animate-spin h-5 w-5 text-blue-600 mr-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
  109. <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
  110. <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018 8 0 018 0v4a8 8 0 018-8h4a8 8 0 018-8v8a8 8 0 018-8-4-4-4-4-4-4-4z"></path>
  111. </svg>
  112. <span class="text-blue-800">正在分析试卷,请稍候...</span>
  113. </div>
  114. </div>
  115. @endif
  116. @if($analysisError)
  117. <div class="mt-4 bg-red-50 border border-red-200 rounded-lg p-4">
  118. <div class="flex items-center">
  119. <svg class="h-5 w-5 text-red-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  120. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 011-18 0z"></path>
  121. </svg>
  122. <span class="text-red-800">{{ $analysisError }}</span>
  123. </div>
  124. </div>
  125. @endif
  126. </div>
  127. @endif
  128. {{-- 选择试卷评分模式 --}}
  129. @if($mode === 'select_paper')
  130. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
  131. <h2 class="text-xl font-semibold mb-6 flex items-center">
  132. <svg class="w-6 h-6 mr-2 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  133. <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.707.293V19a2 2 0 012-2H5a2 2 0 01-2 2v-14z"></path>
  134. </svg>
  135. 选择已有试卷评分
  136. </h2>
  137. {{-- 选择老师和学生 --}}
  138. <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
  139. {{-- 选择老师 --}}
  140. <div class="form-control w-full">
  141. <label class="block text-sm font-medium text-gray-700 mb-2">
  142. 选择老师 <span class="text-red-500">*</span>
  143. @if($isTeacher)
  144. <span class="text-green-600 text-xs ml-2">(当前登录)</span>
  145. @endif
  146. </label>
  147. <select
  148. wire:model.live="teacherId"
  149. @if($isTeacher) disabled @endif
  150. class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 @if($isTeacher) bg-gray-100 @endif"
  151. >
  152. <option value="">请选择老师...</option>
  153. @foreach($this->teachers as $teacher)
  154. <option value="{{ $teacher->teacher_id }}">
  155. {{ trim($teacher->name ?? $teacher->teacher_id) . ($teacher->subject ? " ({$teacher->subject})" : '') }}
  156. </option>
  157. @endforeach
  158. </select>
  159. </div>
  160. {{-- 选择学生 --}}
  161. <div class="form-control w-full">
  162. <label class="block text-sm font-medium text-gray-700 mb-2">选择学生 <span class="text-red-500">*</span></label>
  163. <select
  164. wire:model.live="studentId"
  165. wire:loading.attr="disabled"
  166. @if(empty($teacherId)) disabled @endif
  167. class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
  168. >
  169. <option value="">
  170. @if(empty($teacherId))
  171. 请先选择老师
  172. @else
  173. 请选择学生...
  174. @endif
  175. </option>
  176. @foreach($this->students as $student)
  177. <option value="{{ $student->student_id }}">
  178. {{ trim($student->name ?? $student->student_id) . " ({$student->grade} - {$student->class_name})" }}
  179. </option>
  180. @endforeach
  181. </select>
  182. </div>
  183. </div>
  184. {{-- 选择试卷 --}}
  185. @if(!empty($studentId))
  186. <div class="form-control w-full mt-6">
  187. <label class="block text-sm font-medium text-gray-700 mb-2">选择试卷 <span class="text-red-500">*</span></label>
  188. <select
  189. wire:model.live="selectedPaperId"
  190. class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
  191. >
  192. <option value="">请选择试卷...</option>
  193. @foreach($this->studentPapers as $paper)
  194. <option value="{{ $paper['paper_id'] }}">
  195. {{ $paper['paper_name'] }} ({{ $paper['total_questions'] }}题 / {{ $paper['total_score'] }}分) - {{ $paper['created_at'] }}
  196. </option>
  197. @endforeach
  198. </select>
  199. </div>
  200. @endif
  201. {{-- 评分界面 --}}
  202. @if($showGrading && count($questions) > 0)
  203. <div class="mt-6">
  204. {{-- 试卷信息 --}}
  205. <div class="bg-gray-50 rounded-lg p-4 mb-6">
  206. <div class="grid grid-cols-1 md:grid-cols-4 gap-4 text-sm">
  207. <div>
  208. <span class="font-medium">试卷名称:</span> {{ $paperName }}
  209. </div>
  210. <div>
  211. <span class="font-medium">班级:</span> {{ $paperClass }}
  212. </div>
  213. <div>
  214. <span class="font-medium">学生:</span> {{ $paperStudent }}
  215. </div>
  216. <div>
  217. <span class="font-medium">日期:</span> {{ $paperDate }}
  218. </div>
  219. </div>
  220. </div>
  221. {{-- 题目列表(左右布局) --}}
  222. <div class="space-y-6">
  223. @foreach($this->questions as $index => $question)
  224. <div class="border border-gray-200 rounded-lg p-4 md:p-6 shadow-sm hover:shadow-md transition-shadow">
  225. <div class="grid grid-cols-1 lg:grid-cols-3 gap-4 md:gap-6">
  226. {{-- 左侧:题目内容 --}}
  227. <div class="lg:col-span-2 order-2 lg:order-1">
  228. <div class="flex flex-wrap items-center gap-2 md:gap-3 mb-4">
  229. <span class="bg-blue-100 text-blue-800 px-2 py-1 rounded text-sm font-medium">
  230. 第 {{ $question['question_number'] }} 题
  231. </span>
  232. <span class="bg-gray-100 text-gray-800 px-2 py-1 rounded text-sm">
  233. {{ $question['question_type'] === 'choice' ? '选择题' : ($question['question_type'] === 'fill' ? '填空题' : '解答题') }}
  234. </span>
  235. <span class="text-sm text-gray-600">({{ $question['max_score'] }}分)</span>
  236. </div>
  237. <div class="prose max-w-none text-gray-800">
  238. <p>{!! $question['question_text'] !!}</p>
  239. {{-- 选择题选项 --}}
  240. @if($question['question_type'] === 'choice' && !empty($question['options']))
  241. <div class="mt-4 space-y-2">
  242. @foreach($question['options'] as $option)
  243. <div class="flex items-start">
  244. <span class="w-6 h-6 rounded-full border-2 border-gray-300 flex items-center justify-center text-sm font-medium mr-3 mt-0.5 flex-shrink-0">
  245. {{ $loop->index === 0 ? 'A' : ($loop->index === 1 ? 'B' : ($loop->index === 2 ? 'C' : 'D')) }}
  246. </span>
  247. <span class="text-sm">{{ $option }}</span>
  248. </div>
  249. @endforeach
  250. </div>
  251. @endif
  252. {{-- 参考答案 --}}
  253. @if(!empty($question['correct_answer']))
  254. <div class="mt-4 p-3 bg-green-50 border-l-4 border-green-400 rounded-r-md">
  255. <div class="flex items-start">
  256. <svg class="w-5 h-5 text-green-600 mr-2 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  257. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
  258. </svg>
  259. <div class="text-sm">
  260. <span class="font-medium text-green-800">参考答案:</span>
  261. <span class="text-green-700 font-mono">{{ $question['correct_answer'] }}</span>
  262. </div>
  263. </div>
  264. </div>
  265. @else
  266. <div class="mt-4 p-3 bg-yellow-50 border-l-4 border-yellow-400 rounded-r-md">
  267. <div class="flex items-start">
  268. <span class="w-6 h-6 rounded-full bg-yellow-200 text-yellow-700 flex items-center justify-center mr-3 mt-0.5 flex-shrink-0 font-bold">
  269. !
  270. </span>
  271. <div class="text-sm">
  272. <span class="font-medium text-yellow-800">注意:</span>
  273. <span class="text-yellow-700">暂无参考答案,请根据题目内容自行判断</span>
  274. </div>
  275. </div>
  276. </div>
  277. @endif
  278. {{-- 学生答案 --}}
  279. @if($question['student_answer'])
  280. <div class="mt-3 p-3 bg-blue-50 border-l-4 border-blue-400 rounded-r-md">
  281. <div class="flex items-start">
  282. <svg class="w-5 h-5 text-blue-600 mr-2 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  283. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
  284. </svg>
  285. <div class="text-sm">
  286. <span class="font-medium text-blue-800">学生答案:</span>
  287. <span class="text-blue-700 font-mono">{{ $question['student_answer'] }}</span>
  288. </div>
  289. </div>
  290. </div>
  291. @endif
  292. </div>
  293. </div>
  294. {{-- 右侧:老师判卷 --}}
  295. <div class="lg:col-span-1 order-1 lg:order-2">
  296. <div class="bg-gray-50 rounded-lg p-3 md:p-4 sticky top-4">
  297. <h3 class="font-medium text-gray-900 mb-3 md:mb-4">老师判卷</h3>
  298. {{-- 选择题显示选项 --}}
  299. @if($question['question_type'] === 'choice')
  300. <div class="mb-4">
  301. <label class="block text-sm font-medium text-gray-700 mb-2">
  302. 题目选项
  303. </label>
  304. <div class="space-y-2">
  305. @foreach($question['options'] ?? [] as $optionIndex => $option)
  306. <div class="flex items-center">
  307. <span class="w-6 h-6 rounded-full border-2 border-gray-300 flex items-center justify-center text-sm font-medium mr-2 flex-shrink-0">
  308. {{ chr(65 + $optionIndex) }}
  309. </span>
  310. <span class="text-sm">{{ $option }}</span>
  311. </div>
  312. @endforeach
  313. </div>
  314. </div>
  315. @endif
  316. {{-- 评分区域:根据题型不同显示不同评分方式 --}}
  317. @if($question['question_type'] === 'choice')
  318. {{-- 选择题:对错单选 --}}
  319. <div class="space-y-3">
  320. <label class="flex items-center cursor-pointer">
  321. <input
  322. type="radio"
  323. id="question_{{ $index }}_correct"
  324. name="question_{{ $index }}_is_correct"
  325. wire:model.live="gradingData.{{ $index }}.is_correct"
  326. value="true"
  327. class="mr-2"
  328. >
  329. <span class="text-green-700 font-medium">✓ 正确 ({{ $question['max_score'] }}分)</span>
  330. </label>
  331. <label class="flex items-center cursor-pointer">
  332. <input
  333. type="radio"
  334. id="question_{{ $index }}_incorrect"
  335. name="question_{{ $index }}_is_correct"
  336. wire:model.live="gradingData.{{ $index }}.is_correct"
  337. value="false"
  338. class="mr-2"
  339. >
  340. <span class="text-red-700 font-medium">✗ 错误 (0分)</span>
  341. </label>
  342. </div>
  343. @else
  344. {{-- 填空题和解答题/计算题:直接输入分数 --}}
  345. <div>
  346. <label class="block text-sm font-medium text-gray-700 mb-2">
  347. 得分 (0-{{ $question['max_score'] }}分)
  348. </label>
  349. <input
  350. type="number"
  351. id="question_{{ $index }}_score"
  352. name="question_{{ $index }}_score"
  353. wire:model.live="gradingData.{{ $index }}.score"
  354. min="0"
  355. max="{{ $question['max_score'] }}"
  356. step="0.5"
  357. class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
  358. placeholder="请输入得分"
  359. >
  360. </div>
  361. @endif
  362. </div>
  363. </div>
  364. </div>
  365. </div>
  366. @endforeach
  367. </div>
  368. {{-- 操作按钮 --}}
  369. <div class="flex justify-end mt-8">
  370. <div class="space-x-4">
  371. <button
  372. wire:click="saveGrading"
  373. type="button"
  374. class="bg-blue-600 text-white px-6 py-2 rounded-md hover:bg-blue-700 transition-colors"
  375. >
  376. 提交评分
  377. </button>
  378. </div>
  379. </div>
  380. @endif
  381. </div>
  382. @endif
  383. {{-- 最近上传记录 --}}
  384. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
  385. <h2 class="text-lg font-semibold mb-4 flex items-center">
  386. <svg class="w-5 h-5 mr-2 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  387. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6 2a9 9 0 11-18 0 9 9 0 011-18 0z"></path>
  388. </svg>
  389. 最近试卷记录
  390. </h2>
  391. @if(count($this->recentRecords) > 0)
  392. <div class="overflow-x-auto">
  393. <table class="min-w-full divide-y divide-gray-200">
  394. <thead class="bg-gray-50">
  395. <tr>
  396. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">试卷名称</th>
  397. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">学生</th>
  398. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">题目数</th>
  399. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
  400. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">创建时间</th>
  401. </tr>
  402. </thead>
  403. <tbody class="bg-white divide-y divide-gray-200">
  404. @foreach($this->recentRecords as $record)
  405. <tr class="hover:bg-gray-50">
  406. <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
  407. {{ $record['paper_name'] }}
  408. </td>
  409. <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
  410. {{ $record['student_name'] ?? '未知' }}
  411. </td>
  412. <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
  413. {{ $record['total_questions'] }}
  414. </td>
  415. <td class="px-6 py-4 whitespace-nowrap">
  416. <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
  417. @if($record['is_completed'])
  418. bg-green-100 text-green-800
  419. @elseif($record['status'] === 'processing')
  420. bg-yellow-100 text-yellow-800
  421. @else
  422. bg-gray-100 text-gray-800
  423. @endif">
  424. {{ $record['status_text'] ?? $record['status'] }}
  425. </span>
  426. </td>
  427. <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
  428. {{ $record['created_at'] }}
  429. </td>
  430. </tr>
  431. @endforeach
  432. </tbody>
  433. </table>
  434. </div>
  435. @else
  436. <div class="text-center py-8 text-gray-500">
  437. <svg class="w-12 h-12 mx-auto mb-3 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  438. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 13h6m-3-9v9h6m0 6v6m-6-6h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.707.293V19a2 2 0 012-2H5a2 2 0 01-2 2v-14z"></path>
  439. </svg>
  440. <p>暂无上传记录</p>
  441. </div>
  442. @endif
  443. </div>
  444. </x-filament-panels::page>