upload-exam-paper.blade.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. <x-filament-panels::page>
  2. <div class="space-y-6">
  3. {{-- 上传表单卡片 --}}
  4. <div class="card bg-base-100 shadow-lg border">
  5. <div class="card-body">
  6. <h2 class="card-title text-xl mb-4">
  7. <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  8. <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>
  9. </svg>
  10. 上传考试卷子
  11. </h2>
  12. <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
  13. {{-- 左侧:选择老师和学生 --}}
  14. <div class="space-y-4">
  15. <livewire:teacher-student-selector
  16. wire:model.selectedTeacherId="selectedTeacherId"
  17. wire:model.selectedStudentId="selectedStudentId"
  18. :required="true"
  19. teacher-label="选择老师"
  20. student-label="选择学生"
  21. teacher-placeholder="请选择老师..."
  22. student-placeholder="请选择学生..."
  23. teacher-helper-text="选择要上传给的老师"
  24. student-helper-text="选择要上传给的学生"
  25. />
  26. </div>
  27. {{-- 右侧:上传图片 --}}
  28. <div class="form-control w-full">
  29. <label class="label">
  30. <span class="label-text font-medium">卷子图片 <span class="text-error">*</span></span>
  31. </label>
  32. @if($uploadedImage)
  33. {{-- 图片预览 --}}
  34. <div class="relative">
  35. <img
  36. src="{{ $uploadedImage->temporaryUrl() }}"
  37. class="w-full h-48 object-cover rounded-lg border"
  38. alt="预览"
  39. >
  40. <button
  41. type="button"
  42. wire:click="removeImage"
  43. class="btn btn-circle btn-sm btn-error absolute top-2 right-2"
  44. >
  45. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  46. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
  47. </svg>
  48. </button>
  49. </div>
  50. <label class="label">
  51. <span class="label-text-alt text-success">
  52. {{ $uploadedImage->getClientOriginalName() }}
  53. ({{ number_format($uploadedImage->getSize() / 1024, 1) }} KB)
  54. </span>
  55. </label>
  56. @else
  57. {{-- 上传区域 --}}
  58. <div
  59. x-data="{ uploading: false, progress: 0 }"
  60. x-on:livewire-upload-start="uploading = true"
  61. x-on:livewire-upload-finish="uploading = false"
  62. x-on:livewire-upload-error="uploading = false"
  63. x-on:livewire-upload-progress="progress = $event.detail.progress"
  64. class="relative"
  65. >
  66. <input
  67. type="file"
  68. id="uploadedImage"
  69. wire:model.live="uploadedImage"
  70. class="hidden"
  71. accept="image/jpeg,image/png,image/webp"
  72. >
  73. <label
  74. for="uploadedImage"
  75. class="flex flex-col items-center justify-center w-full h-48 border-2 border-dashed rounded-lg cursor-pointer hover:bg-base-200 transition-colors"
  76. x-bind:class="{ 'border-primary bg-primary/5': uploading }"
  77. >
  78. {{-- 上传进度 --}}
  79. <div x-show="uploading" class="flex flex-col items-center justify-center">
  80. <div class="radial-progress text-primary" x-bind:style="'--value:' + progress + '; --size: 5rem; --thickness: 4px;'" role="progressbar">
  81. <span class="text-sm font-bold" x-text="progress + '%'"></span>
  82. </div>
  83. <p class="mt-3 text-base font-semibold text-primary">正在上传...</p>
  84. </div>
  85. {{-- 默认上传提示 --}}
  86. <div x-show="!uploading" class="flex flex-col items-center justify-center pt-5 pb-6">
  87. <svg class="w-10 h-10 mb-3 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  88. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
  89. </svg>
  90. <p class="mb-2 text-sm text-gray-500">
  91. <span class="font-semibold">点击上传</span> 或拖拽文件
  92. </p>
  93. <p class="text-xs text-gray-400">
  94. 支持 JPG、PNG、WebP (最大 10MB)
  95. </p>
  96. </div>
  97. </label>
  98. </div>
  99. @endif
  100. </div>
  101. </div>
  102. {{-- 提交按钮 --}}
  103. <div class="card-actions justify-end mt-6">
  104. <button
  105. type="button"
  106. wire:click="submitUpload"
  107. class="btn btn-primary"
  108. @if($isUploading) disabled @endif
  109. >
  110. @if($isUploading)
  111. <span class="loading loading-spinner"></span>
  112. 上传中...
  113. @else
  114. <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  115. <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>
  116. </svg>
  117. 上传并识别
  118. @endif
  119. </button>
  120. </div>
  121. </div>
  122. </div>
  123. {{-- 最近上传记录 --}}
  124. <div class="card bg-base-100 shadow-lg border">
  125. <div class="card-body">
  126. <h2 class="card-title text-lg mb-4">
  127. <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  128. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
  129. </svg>
  130. 最近上传记录
  131. </h2>
  132. @if(count($this->recentRecords) > 0)
  133. <div class="overflow-x-auto">
  134. <table class="table table-zebra">
  135. <thead>
  136. <tr>
  137. <th>学生</th>
  138. <th>文件名</th>
  139. <th>状态</th>
  140. <th>进度</th>
  141. <th>上传时间</th>
  142. </tr>
  143. </thead>
  144. <tbody>
  145. @foreach($this->recentRecords as $record)
  146. <tr>
  147. <td>{{ $record['student']['name'] ?? '未知' }}</td>
  148. <td class="max-w-xs truncate">{{ $record['image_filename'] }}</td>
  149. <td>
  150. @php
  151. $statusClass = match($record['status']) {
  152. 'pending' => 'badge-ghost',
  153. 'processing' => 'badge-info',
  154. 'completed' => 'badge-success',
  155. 'failed' => 'badge-error',
  156. default => 'badge-ghost',
  157. };
  158. $statusText = match($record['status']) {
  159. 'pending' => '待处理',
  160. 'processing' => '处理中',
  161. 'completed' => '已完成',
  162. 'failed' => '失败',
  163. default => $record['status'],
  164. };
  165. @endphp
  166. <span class="badge {{ $statusClass }}">{{ $statusText }}</span>
  167. </td>
  168. <td>
  169. @if($record['total_questions'] > 0)
  170. <progress
  171. class="progress progress-primary w-20"
  172. value="{{ $record['processed_questions'] }}"
  173. max="{{ $record['total_questions'] }}"
  174. ></progress>
  175. <span class="text-xs ml-1">
  176. {{ $record['processed_questions'] }}/{{ $record['total_questions'] }}
  177. </span>
  178. @else
  179. <span class="text-gray-400">-</span>
  180. @endif
  181. </td>
  182. <td class="text-sm">
  183. {{ \Carbon\Carbon::parse($record['created_at'])->format('m-d H:i') }}
  184. </td>
  185. </tr>
  186. @endforeach
  187. </tbody>
  188. </table>
  189. </div>
  190. @else
  191. <div class="text-center py-8 text-gray-500">
  192. <svg class="w-12 h-12 mx-auto mb-3 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  193. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 13h6m-3-3v6m5 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>
  194. </svg>
  195. <p>暂无上传记录</p>
  196. </div>
  197. @endif
  198. </div>
  199. </div>
  200. </div>
  201. </x-filament-panels::page>