mistake-book.blade.php 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. <div class="min-h-screen bg-gray-50 p-8">
  2. {{-- 页面标题区域 --}}
  3. <div class="mb-8">
  4. <div class="bg-white rounded-xl shadow-sm p-6 border border-gray-200">
  5. <div class="flex items-center justify-between mb-6">
  6. <div>
  7. <h1 class="text-3xl font-bold text-gray-900 flex items-center">
  8. <svg class="w-8 h-8 mr-3 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  9. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path>
  10. </svg>
  11. 错题本
  12. </h1>
  13. <p class="mt-2 text-sm text-gray-600 ml-11">查看学生错题记录与AI分析,生成针对性练习</p>
  14. </div>
  15. </div>
  16. {{-- 选择器区域 --}}
  17. <div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
  18. <div class="flex flex-col gap-3 lg:flex-row lg:items-center">
  19. @if(!$this->isTeacher)
  20. <div class="form-control w-full lg:flex-1">
  21. <label class="label"><span class="label-text font-medium">选择老师</span></label>
  22. <select wire:model.live="teacherId" class="select select-bordered w-full h-11">
  23. <option value="">请选择老师...</option>
  24. @foreach($this->teachers as $teacher)
  25. <option value="{{ $teacher->teacher_id }}">
  26. {{ trim($teacher->name ?? $teacher->teacher_id) . ($teacher->subject ? " ({$teacher->subject})" : '') }}
  27. </option>
  28. @endforeach
  29. </select>
  30. </div>
  31. @endif
  32. <div class="form-control w-full lg:flex-1">
  33. <label class="label"><span class="label-text font-medium">选择学生</span></label>
  34. <select wire:model.live="studentId" class="select select-bordered w-full h-11" @if(empty($teacherId)) disabled @endif>
  35. <option value="">{{ empty($teacherId) ? '请先选择老师' : '请选择学生...' }}</option>
  36. @foreach($this->students as $student)
  37. <option value="{{ $student->student_id }}">
  38. {{ trim($student->name ?? $student->student_id) . " ({$student->grade} - {$student->class_name})" }}
  39. </option>
  40. @endforeach
  41. </select>
  42. </div>
  43. <div class="w-full lg:w-auto flex items-center lg:pt-6">
  44. <button wire:click="loadMistakeData" wire:loading.attr="disabled"
  45. class="inline-flex items-center justify-center w-full h-11 px-6 border border-transparent text-sm font-medium rounded-lg shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200">
  46. <svg wire:loading class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
  47. <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
  48. <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
  49. </svg>
  50. 刷新数据
  51. </button>
  52. </div>
  53. </div>
  54. </div>
  55. </div>
  56. </div>
  57. {{-- 错误提示 --}}
  58. @if ($errorMessage)
  59. <div class="mb-8">
  60. <div class="bg-red-50 border border-red-200 rounded-xl p-4">
  61. <div class="flex items-start">
  62. <svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
  63. <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
  64. </svg>
  65. <div class="ml-3">
  66. <h3 class="text-sm font-medium text-red-800">加载错误</h3>
  67. <p class="mt-1 text-sm text-red-700">{{ $errorMessage }}</p>
  68. </div>
  69. </div>
  70. </div>
  71. </div>
  72. @endif
  73. @if ($actionMessage)
  74. <div class="mb-8">
  75. <div class="bg-green-50 border border-green-200 rounded-xl p-4">
  76. <p class="text-sm text-green-700">{{ $actionMessage }}</p>
  77. </div>
  78. </div>
  79. @endif
  80. {{-- 统计卡片 --}}
  81. <div class="mb-8">
  82. <div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
  83. <div class="bg-white overflow-hidden shadow-sm rounded-xl border border-gray-200 hover:shadow-md transition-shadow duration-200">
  84. <div class="p-6">
  85. <div class="flex items-center">
  86. <div class="w-12 h-12 bg-gradient-to-br from-indigo-500 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg">
  87. <span class="text-white font-bold">总</span>
  88. </div>
  89. <div class="ml-4">
  90. <p class="text-sm font-medium text-gray-600">总错题</p>
  91. <p class="text-2xl font-bold text-gray-900 mt-1">{{ $summary['total'] ?? 0 }}</p>
  92. </div>
  93. </div>
  94. </div>
  95. </div>
  96. <div class="bg-white overflow-hidden shadow-sm rounded-xl border border-gray-200 hover:shadow-md transition-shadow duration-200">
  97. <div class="p-6">
  98. <div class="flex items-center">
  99. <div class="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center shadow-lg">
  100. <span class="text-white font-bold">周</span>
  101. </div>
  102. <div class="ml-4">
  103. <p class="text-sm font-medium text-gray-600">本周新增</p>
  104. <p class="text-2xl font-bold text-gray-900 mt-1">{{ $summary['this_week'] ?? 0 }}</p>
  105. </div>
  106. </div>
  107. </div>
  108. </div>
  109. <div class="bg-white overflow-hidden shadow-sm rounded-xl border border-gray-200 hover:shadow-md transition-shadow duration-200">
  110. <div class="p-6">
  111. <div class="flex items-center">
  112. <div class="w-12 h-12 bg-gradient-to-br from-yellow-500 to-orange-500 rounded-xl flex items-center justify-center shadow-lg">
  113. <span class="text-white font-bold">待</span>
  114. </div>
  115. <div class="ml-4">
  116. <p class="text-sm font-medium text-gray-600">待复习</p>
  117. <p class="text-2xl font-bold text-orange-600 mt-1">{{ $summary['pending_review'] ?? 0 }}</p>
  118. </div>
  119. </div>
  120. </div>
  121. </div>
  122. <div class="bg-white overflow-hidden shadow-sm rounded-xl border border-gray-200 hover:shadow-md transition-shadow duration-200">
  123. <div class="p-6">
  124. <div class="flex items-center">
  125. <div class="w-12 h-12 bg-gradient-to-br from-green-500 to-green-600 rounded-xl flex items-center justify-center shadow-lg">
  126. <span class="text-white font-bold">%</span>
  127. </div>
  128. <div class="ml-4">
  129. <p class="text-sm font-medium text-gray-600">掌握率</p>
  130. @php $rate = $summary['mastery_rate'] ?? null; @endphp
  131. <p class="text-2xl font-bold text-green-600 mt-1">{{ $rate !== null ? number_format($rate * 100, 1) . '%' : '--' }}</p>
  132. </div>
  133. </div>
  134. </div>
  135. </div>
  136. </div>
  137. </div>
  138. {{-- 加载状态 --}}
  139. @if ($isLoading)
  140. <div class="mb-8">
  141. <div class="bg-white rounded-xl shadow-sm p-12 border border-gray-200">
  142. <div class="flex flex-col items-center justify-center">
  143. <svg class="animate-spin h-12 w-12 text-indigo-600" fill="none" viewBox="0 0 24 24">
  144. <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
  145. <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
  146. </svg>
  147. <p class="mt-4 text-sm text-gray-600">正在加载数据,请稍候...</p>
  148. </div>
  149. </div>
  150. </div>
  151. @else
  152. {{-- 学习状态概览 --}}
  153. <div class="mb-8">
  154. <div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
  155. {{-- 今日必复习 --}}
  156. <div class="lg:col-span-2 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl shadow-sm p-6 text-white">
  157. <div class="flex items-center justify-between mb-4">
  158. <h3 class="text-lg font-semibold">今日必复习</h3>
  159. <span class="bg-white/20 px-3 py-1 rounded-full text-sm">
  160. {{ count($mistakes) }} 道错题
  161. </span>
  162. </div>
  163. <div class="space-y-3">
  164. @php
  165. $urgentMistakes = collect($mistakes)->take(3);
  166. @endphp
  167. @if($urgentMistakes->isNotEmpty())
  168. @foreach($urgentMistakes as $mistake)
  169. @php
  170. $tags = [];
  171. if (!empty($mistake['question']['question_number'])) {
  172. $tags[] = '第' . $mistake['question']['question_number'] . '题';
  173. }
  174. if (!empty($mistake['question']['kp_code'])) {
  175. $kpName = $knowledgePointOptions[$mistake['question']['kp_code']] ?? $mistake['question']['kp_code'];
  176. if ($kpName && $kpName !== $mistake['question']['kp_code']) {
  177. $tags[] = $kpName;
  178. }
  179. }
  180. if (!empty($mistake['error_type'])) {
  181. $tags[] = $mistake['error_type'];
  182. }
  183. @endphp
  184. <div class="bg-white/10 backdrop-blur rounded-lg p-4 hover:bg-white/20 transition-colors cursor-pointer">
  185. @if(!empty($tags))
  186. <div class="flex flex-wrap gap-1.5 mb-2">
  187. @foreach($tags as $tag)
  188. <span class="text-xs bg-white/20 px-2 py-0.5 rounded">{{ $tag }}</span>
  189. @endforeach
  190. </div>
  191. @endif
  192. <div class="mb-3">
  193. <p class="text-sm leading-relaxed">
  194. {{ $mistake['question']['stem'] ?? '暂无题干' }}
  195. </p>
  196. </div>
  197. <div class="flex justify-end">
  198. <a href="{{ url('/admin/question-detail') }}?mistake_id={{ $mistake['id'] ?? '' }}&student_id={{ $studentId }}"
  199. class="text-xs bg-white/20 hover:bg-white/30 px-3 py-1 rounded transition-colors inline-block">
  200. 查看详情
  201. </a>
  202. </div>
  203. </div>
  204. @endforeach
  205. @else
  206. <div class="text-center py-8 text-white/80">
  207. <svg class="w-16 h-16 mx-auto mb-4 text-white/50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  208. <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>
  209. </svg>
  210. <p class="text-sm">暂无错题数据</p>
  211. <p class="text-xs mt-1 text-white/60">请先选择学生并刷新数据</p>
  212. </div>
  213. @endif
  214. </div>
  215. @if($urgentMistakes->isNotEmpty())
  216. <div class="mt-4 pt-4 border-t border-white/20">
  217. <button wire:click="startQuickReview"
  218. class="w-full bg-white text-indigo-600 px-4 py-2 rounded-lg text-sm font-medium hover:bg-gray-50 transition-colors">
  219. 开始快速复习 (前5题)
  220. </button>
  221. </div>
  222. @endif
  223. </div>
  224. {{-- 学习进度 --}}
  225. <div class="bg-white rounded-xl shadow-sm p-6 border border-gray-200">
  226. <h3 class="text-lg font-semibold text-gray-900 mb-4">学习进度</h3>
  227. <!-- 掌握度显示 -->
  228. <div class="mb-6">
  229. <div class="flex items-center justify-between mb-2">
  230. <span class="text-sm text-gray-600">整体掌握度</span>
  231. <span class="text-xs text-gray-500">基于所有知识点平均</span>
  232. </div>
  233. <div class="relative">
  234. <div class="w-full bg-gray-200 rounded-full h-3">
  235. @php
  236. $masteryRate = $summary['mastery_rate'] ?? 0;
  237. $masteryPercentage = $masteryRate * 100;
  238. @endphp
  239. <div class="bg-indigo-600 h-3 rounded-full transition-all duration-500"
  240. style="width: {{ $masteryPercentage }}%"></div>
  241. </div>
  242. </div>
  243. <p class="text-2xl font-bold text-indigo-600 mt-2 text-center">
  244. {{ number_format($masteryPercentage, 0) }}%
  245. </p>
  246. </div>
  247. <!-- 统计信息 -->
  248. <div class="space-y-3">
  249. <div class="flex items-center justify-between p-3 bg-orange-50 rounded-lg">
  250. <span class="text-sm text-gray-600">待复习知识点</span>
  251. <span class="text-xl font-bold text-orange-600">{{ $summary['pending_review'] ?? 0 }}</span>
  252. </div>
  253. <div class="flex items-center justify-between p-3 bg-red-50 rounded-lg">
  254. <span class="text-sm text-gray-600">总错题数</span>
  255. <span class="text-xl font-bold text-red-600">{{ $summary['total'] ?? 0 }}</span>
  256. </div>
  257. <div class="flex items-center justify-between p-3 bg-blue-50 rounded-lg">
  258. <span class="text-sm text-gray-600">本周新增</span>
  259. <span class="text-xl font-bold text-blue-600">{{ $summary['this_week'] ?? 0 }}</span>
  260. </div>
  261. </div>
  262. </div>
  263. </div>
  264. </div>
  265. {{-- 主内容区域 --}}
  266. <div class="grid grid-cols-1 gap-6 xl:grid-cols-4">
  267. {{-- 左侧筛选 --}}
  268. <div class="xl:col-span-1">
  269. <div class="bg-white shadow-sm rounded-xl border border-gray-200 sticky top-4">
  270. <div class="px-6 py-5 border-b border-gray-100">
  271. <h3 class="text-lg font-semibold text-gray-900">筛选条件</h3>
  272. </div>
  273. <div class="p-6 space-y-6">
  274. {{-- 时间范围 --}}
  275. <div>
  276. <label class="text-sm font-medium text-gray-700 mb-3 block">时间范围</label>
  277. <div class="flex gap-2">
  278. <button wire:click="$set('filters.time_range', 'last_7')"
  279. class="flex-1 px-4 py-2 text-sm font-medium rounded-lg transition-colors {{ $filters['time_range'] === 'last_7' ? 'bg-indigo-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }}">
  280. 7天
  281. </button>
  282. <button wire:click="$set('filters.time_range', 'last_30')"
  283. class="flex-1 px-4 py-2 text-sm font-medium rounded-lg transition-colors {{ $filters['time_range'] === 'last_30' ? 'bg-indigo-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }}">
  284. 30天
  285. </button>
  286. </div>
  287. </div>
  288. {{-- 错误类型 --}}
  289. <div>
  290. <label class="text-sm font-medium text-gray-700 mb-3 block">错误类型</label>
  291. <div class="space-y-2">
  292. @foreach(['计算错误', '概念错误', '方法错误', '审题错误'] as $type)
  293. <label class="flex items-center gap-3 cursor-pointer p-2 rounded-lg hover:bg-gray-50">
  294. <input type="checkbox" value="{{ $type }}" wire:model="filters.error_types"
  295. class="w-4 h-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
  296. <span class="text-sm text-gray-700">{{ $type }}</span>
  297. </label>
  298. @endforeach
  299. </div>
  300. </div>
  301. <button wire:click="applyFilters"
  302. class="w-full px-4 py-2.5 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition-colors">
  303. 应用筛选
  304. </button>
  305. </div>
  306. </div>
  307. </div>
  308. {{-- 右侧错题列表 --}}
  309. <div class="xl:col-span-3 space-y-6">
  310. {{-- 错题列表 --}}
  311. <div class="bg-white shadow-sm rounded-xl border border-gray-200">
  312. <div class="px-6 py-5 border-b border-gray-100 flex items-center justify-between">
  313. <h3 class="text-lg font-semibold text-gray-900 flex items-center">
  314. <svg class="w-5 h-5 mr-2 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  315. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path>
  316. </svg>
  317. 错题列表 ({{ $total }})
  318. </h3>
  319. <div class="flex items-center gap-3">
  320. @if(!empty($selectedMistakeIds))
  321. <button wire:click="generatePracticeFromSelection"
  322. class="inline-flex items-center px-4 py-2 bg-green-600 text-white text-sm font-medium rounded-lg hover:bg-green-700 transition-colors">
  323. 生成练习 ({{ count($selectedMistakeIds) }})
  324. </button>
  325. @endif
  326. </div>
  327. </div>
  328. <div class="p-6">
  329. @if(empty($mistakes))
  330. <div class="text-center py-12">
  331. <svg class="w-16 h-16 mx-auto text-gray-300 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  332. <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>
  333. </svg>
  334. <p class="text-sm font-medium text-gray-900 mb-1">暂无错题数据</p>
  335. <p class="text-xs text-gray-500">请先选择学生后刷新数据</p>
  336. </div>
  337. @else
  338. <div class="space-y-4">
  339. @foreach($mistakes as $mistake)
  340. <div class="border border-gray-200 rounded-xl p-5 hover:border-gray-300 transition-colors" wire:key="m-{{ $mistake['id'] ?? $loop->index }}">
  341. {{-- 头部 --}}
  342. <div class="flex items-start gap-4">
  343. <input type="checkbox"
  344. wire:click="toggleSelection('{{ $mistake['id'] ?? '' }}')"
  345. @checked(in_array($mistake['id'] ?? '', $selectedMistakeIds, true))
  346. class="mt-1 w-5 h-5 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
  347. <div class="flex-1 min-w-0">
  348. {{-- 标签行 --}}
  349. <div class="flex flex-wrap items-center gap-2 mb-4">
  350. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
  351. #{{ $mistake['id'] ?? '' }}
  352. </span>
  353. @if(!empty($mistake['question']['question_number']))
  354. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
  355. 第{{ $mistake['question']['question_number'] }}题
  356. </span>
  357. @endif
  358. <span class="text-xs text-gray-500">
  359. @php
  360. $createdAt = $mistake['created_at'] ?? null;
  361. if ($createdAt) {
  362. try {
  363. $date = \Carbon\Carbon::parse($createdAt);
  364. echo $date->format('Y-m-d H:i');
  365. } catch (\Exception $e) {
  366. echo $createdAt;
  367. }
  368. }
  369. @endphp
  370. </span>
  371. @if(!empty($mistake['error_type']))
  372. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
  373. {{ $mistake['error_type'] }}
  374. </span>
  375. @endif
  376. @if(!empty($mistake['mistake_category']))
  377. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-orange-100 text-orange-800">
  378. {{ $mistake['mistake_category'] }}
  379. </span>
  380. @endif
  381. </div>
  382. {{-- 题干 --}}
  383. @if(!empty($mistake['question']['stem']))
  384. <div class="bg-gray-50 rounded-lg p-4 mb-4 border border-gray-100">
  385. <p class="text-xs font-medium text-gray-500 mb-2">题干</p>
  386. <div class="prose prose-sm max-w-none text-gray-900">
  387. <x-math-render :content="$mistake['question']['stem']" />
  388. </div>
  389. </div>
  390. @endif
  391. {{-- 判卷结果 + 知识点 --}}
  392. <div class="flex flex-wrap items-center gap-3 mb-4">
  393. @if(!empty($mistake['mark_detected']))
  394. <span class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium {{ $mistake['mark_detected'] === '✓' || $mistake['mark_detected'] === '√' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' }}">
  395. 老师判卷: {{ $mistake['mark_detected'] }}
  396. </span>
  397. @endif
  398. @if(isset($mistake['score']) && isset($mistake['full_score']))
  399. @php
  400. $score = floatval($mistake['score'] ?? 0);
  401. $fullScore = floatval($mistake['full_score'] ?? 0);
  402. $isFullScore = $fullScore > 0 && $score >= $fullScore;
  403. @endphp
  404. <span class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium {{ $isFullScore ? 'bg-green-100 text-green-800' : 'bg-blue-100 text-blue-800' }}">
  405. 得分: {{ $mistake['score'] }}/{{ $mistake['full_score'] }}
  406. @if($isFullScore)
  407. @endif
  408. </span>
  409. @endif
  410. @if(!empty($mistake['question']['kp_code']))
  411. <a href="{{ url('/admin/knowledge-point-detail') }}?kp_code={{ urlencode($mistake['question']['kp_code']) }}"
  412. class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium bg-indigo-100 text-indigo-800 hover:bg-indigo-200 transition-colors">
  413. 知识点: {{ $mistake['question']['kp_code'] }}
  414. </a>
  415. @endif
  416. @if(!empty($mistake['skill_ids']))
  417. @foreach($mistake['skill_ids'] as $skillId)
  418. <span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-teal-100 text-teal-800">
  419. {{ $skillId }}
  420. </span>
  421. @endforeach
  422. @endif
  423. </div>
  424. {{-- 作答对比与解题步骤 --}}
  425. <div class="grid grid-cols-1 lg:grid-cols-3 gap-4 mb-4">
  426. @if(!empty($mistake['student_answer']) && trim($mistake['student_answer']) !== '' && trim($mistake['student_answer']) !== '未作答')
  427. <div class="bg-red-50 rounded-lg p-4 border border-red-100">
  428. <p class="text-xs font-medium text-red-600 mb-2">
  429. 学生作答
  430. @if(!empty($mistake['answer_confidence']))
  431. <span class="text-gray-400">(置信度: {{ number_format($mistake['answer_confidence'] * 100, 1) }}%)</span>
  432. @endif
  433. </p>
  434. <p class="text-sm text-gray-900">{{ $mistake['student_answer'] }}</p>
  435. </div>
  436. @elseif(!empty($mistake['mark_detected']) && trim($mistake['student_answer']) === '未作答')
  437. {{-- 老师已评分但学生未作答的题目,不显示学生作答区域 --}}
  438. @elseif(!empty($mistake['student_answer']) && trim($mistake['student_answer']) !== '')
  439. <div class="bg-red-50 rounded-lg p-4 border border-red-100">
  440. <p class="text-xs font-medium text-red-600 mb-2">
  441. 学生作答
  442. @if(!empty($mistake['answer_confidence']))
  443. <span class="text-gray-400">(置信度: {{ number_format($mistake['answer_confidence'] * 100, 1) }}%)</span>
  444. @endif
  445. </p>
  446. <p class="text-sm text-gray-900">{{ $mistake['student_answer'] }}</p>
  447. </div>
  448. @endif
  449. @if(!empty($mistake['question']['solution']))
  450. <div class="bg-blue-50 rounded-lg p-4 border border-blue-100">
  451. <p class="text-xs font-medium text-blue-600 mb-2">解题步骤</p>
  452. <div class="prose prose-sm max-w-none text-gray-900">
  453. {{ $mistake['question']['solution'] }}
  454. </div>
  455. </div>
  456. @endif
  457. @if(!empty($mistake['question']['answer']))
  458. <div class="bg-green-50 rounded-lg p-4 border border-green-100">
  459. <p class="text-xs font-medium text-green-600 mb-2">正确答案</p>
  460. <p class="text-sm text-gray-900">{{ $mistake['question']['answer'] }}</p>
  461. </div>
  462. @endif
  463. </div>
  464. {{-- AI分析 --}}
  465. @if(!empty($mistake['ai_analysis']['reason']) || !empty($mistake['ai_analysis']['solution']) || !empty($mistake['ai_analysis']['suggestions']))
  466. <details class="bg-amber-50 rounded-lg border border-amber-100 mb-4">
  467. <summary class="px-4 py-3 text-sm font-medium text-amber-800 cursor-pointer hover:bg-amber-100 rounded-lg transition-colors">
  468. 查看AI分析
  469. @if(!empty($mistake['ai_analysis']['model_used']))
  470. <span class="text-xs text-gray-500 ml-2">({{ $mistake['ai_analysis']['model_used'] }})</span>
  471. @endif
  472. </summary>
  473. <div class="px-4 pb-4 text-sm text-gray-700 space-y-2">
  474. @if(!empty($mistake['ai_analysis']['reason']))
  475. <p><span class="font-medium text-gray-900">错因:</span>{{ $mistake['ai_analysis']['reason'] }}</p>
  476. @endif
  477. @if(!empty($mistake['ai_analysis']['solution']))
  478. <p><span class="font-medium text-gray-900">解法:</span>{{ $mistake['ai_analysis']['solution'] }}</p>
  479. @endif
  480. @if(!empty($mistake['ai_analysis']['suggestions']))
  481. <p><span class="font-medium text-gray-900">建议:</span>{{ $mistake['ai_analysis']['suggestions'] }}</p>
  482. @endif
  483. @if(!empty($mistake['ai_analysis']['next_steps']))
  484. <div>
  485. <span class="font-medium text-gray-900">下一步:</span>
  486. <ul class="list-disc list-inside mt-1">
  487. @foreach($mistake['ai_analysis']['next_steps'] as $step)
  488. <li>{{ is_array($step) ? json_encode($step) : $step }}</li>
  489. @endforeach
  490. </ul>
  491. </div>
  492. @endif
  493. </div>
  494. </details>
  495. @endif
  496. {{-- 操作按钮 --}}
  497. <div class="flex flex-wrap gap-2 pt-4 border-t border-gray-100">
  498. <a href="{{ url('/admin/question-detail') }}?mistake_id={{ $mistake['id'] ?? '' }}&student_id={{ $studentId }}"
  499. class="inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-lg bg-indigo-100 text-indigo-800 hover:bg-indigo-200 transition-colors">
  500. 详情
  501. </a>
  502. <button wire:click="toggleFavorite('{{ $mistake['id'] ?? '' }}')"
  503. class="inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-lg {{ !empty($mistake['favorite']) ? 'bg-yellow-100 text-yellow-800' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }} transition-colors">
  504. {{ !empty($mistake['favorite']) ? '★ 已收藏' : '☆ 收藏' }}
  505. </button>
  506. <button wire:click="markReviewed('{{ $mistake['id'] ?? '' }}')"
  507. class="inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-lg {{ !empty($mistake['reviewed']) ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }} transition-colors">
  508. {{ !empty($mistake['reviewed']) ? '✓ 已复习' : '标记复习' }}
  509. </button>
  510. <button wire:click="loadRelatedQuestions('{{ $mistake['id'] ?? '' }}')"
  511. class="inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors">
  512. 关联题
  513. </button>
  514. @if(!empty($mistake['answer_area_crop_path']))
  515. <a href="{{ $mistake['answer_area_crop_path'] }}" target="_blank"
  516. class="inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors">
  517. 查看作答图片
  518. </a>
  519. @endif
  520. </div>
  521. {{-- 关联题 --}}
  522. @if(!empty($relatedQuestions[$mistake['id'] ?? ''] ?? []))
  523. <div class="mt-4 bg-gray-50 rounded-lg p-4 border border-gray-100">
  524. <p class="text-xs font-medium text-gray-500 mb-3">关联题目</p>
  525. <div class="space-y-2">
  526. @foreach($relatedQuestions[$mistake['id']] as $related)
  527. <div class="text-sm p-3 bg-white rounded-lg border border-gray-200">
  528. <p class="text-gray-900 line-clamp-2">{{ $related['stem'] ?? '题目' }}</p>
  529. </div>
  530. @endforeach
  531. </div>
  532. </div>
  533. @endif
  534. </div>
  535. </div>
  536. </div>
  537. @endforeach
  538. </div>
  539. {{-- 分页 --}}
  540. @php
  541. $maxPage = (int) ceil($total / $perPage);
  542. @endphp
  543. @if($maxPage > 1)
  544. <div class="mt-6 flex items-center justify-between border-t border-gray-100 pt-4">
  545. <div class="text-sm text-gray-500">
  546. 共 {{ $total }} 条,第 {{ $page }}/{{ $maxPage }} 页
  547. </div>
  548. <div class="flex items-center gap-2">
  549. <button wire:click="prevPage" @disabled($page <= 1)
  550. class="px-3 py-1.5 text-sm font-medium rounded-lg {{ $page <= 1 ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }} transition-colors">
  551. 上一页
  552. </button>
  553. @for($i = max(1, $page - 2); $i <= min($maxPage, $page + 2); $i++)
  554. <button wire:click="gotoPage({{ $i }})"
  555. class="px-3 py-1.5 text-sm font-medium rounded-lg {{ $i === $page ? 'bg-indigo-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }} transition-colors">
  556. {{ $i }}
  557. </button>
  558. @endfor
  559. <button wire:click="nextPage" @disabled($page >= $maxPage)
  560. class="px-3 py-1.5 text-sm font-medium rounded-lg {{ $page >= $maxPage ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }} transition-colors">
  561. 下一页
  562. </button>
  563. </div>
  564. </div>
  565. @endif
  566. @endif
  567. </div>
  568. </div>
  569. {{-- 推荐练习题 --}}
  570. @if(!empty($recommendations))
  571. <div class="bg-white shadow-sm rounded-xl border border-gray-200">
  572. <div class="px-6 py-5 border-b border-gray-100">
  573. <h3 class="text-lg font-semibold text-gray-900 flex items-center">
  574. <svg class="w-5 h-5 mr-2 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  575. <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>
  576. </svg>
  577. 推荐练习题 ({{ count($recommendations) }})
  578. </h3>
  579. </div>
  580. <div class="p-6">
  581. <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
  582. @foreach($recommendations as $rec)
  583. <div class="p-4 border border-gray-200 rounded-lg bg-gray-50">
  584. <p class="text-sm text-gray-900 line-clamp-2">{{ $rec['stem'] ?? '推荐题目' }}</p>
  585. @if(!empty($rec['kp_codes']))
  586. <span class="inline-flex items-center mt-2 px-2 py-0.5 rounded text-xs font-medium bg-indigo-100 text-indigo-800">
  587. {{ is_array($rec['kp_codes']) ? implode(',', $rec['kp_codes']) : $rec['kp_codes'] }}
  588. </span>
  589. @endif
  590. </div>
  591. @endforeach
  592. </div>
  593. </div>
  594. </div>
  595. @endif
  596. {{-- 错误分析 --}}
  597. @if(!empty($patterns['error_types']) || !empty($patterns['top_kps']))
  598. <div class="bg-white shadow-sm rounded-xl border border-gray-200">
  599. <div class="px-6 py-5 border-b border-gray-100">
  600. <h3 class="text-lg font-semibold text-gray-900 flex items-center">
  601. <svg class="w-5 h-5 mr-2 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  602. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
  603. </svg>
  604. 错误分析
  605. </h3>
  606. </div>
  607. <div class="p-6">
  608. <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
  609. @if(!empty($patterns['error_types']))
  610. <div>
  611. <p class="text-sm font-medium text-gray-900 mb-4">错误类型分布</p>
  612. <div class="space-y-3">
  613. @foreach($patterns['error_types'] as $et)
  614. <div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
  615. <span class="text-sm text-gray-700">{{ $et['type'] ?? '未知' }}</span>
  616. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-200 text-gray-800">
  617. {{ $et['count'] ?? 0 }}
  618. </span>
  619. </div>
  620. @endforeach
  621. </div>
  622. </div>
  623. @endif
  624. @if(!empty($patterns['top_kps']))
  625. <div>
  626. <p class="text-sm font-medium text-gray-900 mb-4">薄弱知识点</p>
  627. <div class="space-y-3">
  628. @foreach($patterns['top_kps'] as $kp)
  629. <div class="flex items-center justify-between p-3 bg-red-50 rounded-lg">
  630. <span class="text-sm text-gray-700">{{ $kp['name'] ?? $kp['kp_code'] ?? '知识点' }}</span>
  631. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
  632. {{ $kp['mistake_count'] ?? 0 }}
  633. </span>
  634. </div>
  635. @endforeach
  636. </div>
  637. </div>
  638. @endif
  639. </div>
  640. </div>
  641. </div>
  642. @endif
  643. </div>
  644. </div>
  645. @endif
  646. </div>