question-management.blade.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. {{-- 访问计算属性触发懒加载 --}}
  2. @php
  3. $questionsData = $this->questions;
  4. $metaData = $this->meta;
  5. $statisticsData = $this->statistics;
  6. @endphp
  7. <div class="filament-page">
  8. <div class="filament-page-header">
  9. <div class="filament-page-header-actions">
  10. {{-- 头部操作按钮将在此处 --}}
  11. </div>
  12. </div>
  13. <div class="filament-page-content">
  14. {{-- 页面内容 --}}
  15. <x-filament::section>
  16. <div class="flex items-center justify-between mb-6">
  17. <div>
  18. <h2 class="text-xl font-bold tracking-tight">题库管理</h2>
  19. <p class="mt-1 text-sm text-gray-500">
  20. 管理和浏览题库中的所有题目
  21. </p>
  22. </div>
  23. <div class="flex gap-3">
  24. <button
  25. type="button"
  26. class="filament-button filament-button-size-sm filament-button-color-success filament-button-icon-start inline-flex items-center justify-center px-4 py-2 text-sm font-medium transition-colors border border-transparent rounded-lg focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
  27. wire:click="$dispatch('ai-generate')"
  28. >
  29. <span class="filament-button-icon mr-2">
  30. <!-- heroicon -->
  31. </span>
  32. AI 生成题目
  33. </button>
  34. <button
  35. type="button"
  36. class="filament-button filament-button-size-sm filament-button-color-warning filament-button-icon-start inline-flex items-center justify-center px-4 py-2 text-sm font-medium transition-colors border border-transparent rounded-lg focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
  37. wire:click="$dispatch('refresh-data')"
  38. >
  39. <span class="filament-button-icon mr-2">
  40. <!-- heroicon -->
  41. </span>
  42. 刷新
  43. </button>
  44. </div>
  45. </div>
  46. {{-- 统计信息卡片 --}}
  47. <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
  48. <div class="bg-white p-4 rounded-lg border">
  49. <div class="text-sm text-gray-500">题目总数</div>
  50. <div class="text-2xl font-bold text-primary-600">
  51. {{ $statisticsData['total'] ?? 0 }}
  52. </div>
  53. </div>
  54. <div class="bg-white p-4 rounded-lg border">
  55. <div class="text-sm text-gray-500">基础难度</div>
  56. <div class="text-2xl font-bold text-green-600">
  57. {{ $statisticsData['by_difficulty']['0.3'] ?? 0 }}
  58. </div>
  59. </div>
  60. <div class="bg-white p-4 rounded-lg border">
  61. <div class="text-sm text-gray-500">中等难度</div>
  62. <div class="text-2xl font-bold text-yellow-600">
  63. {{ $statisticsData['by_difficulty']['0.6'] ?? 0 }}
  64. </div>
  65. </div>
  66. <div class="bg-white p-4 rounded-lg border">
  67. <div class="text-sm text-gray-500">拔高难度</div>
  68. <div class="text-2xl font-bold text-red-600">
  69. {{ $statisticsData['by_difficulty']['0.85'] ?? 0 }}
  70. </div>
  71. </div>
  72. </div>
  73. {{-- 搜索和筛选 --}}
  74. <div class="bg-white p-4 rounded-lg border mb-6">
  75. <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
  76. <div>
  77. <label class="block text-sm font-medium text-gray-700 mb-2">
  78. 搜索题目
  79. </label>
  80. <input
  81. type="text"
  82. wire:model.live.debounce.300ms="search"
  83. placeholder="输入题目内容、答案或编号"
  84. class="filament-forms-input block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm"
  85. />
  86. </div>
  87. <div>
  88. <label class="block text-sm font-medium text-gray-700 mb-2">
  89. 知识点筛选
  90. </label>
  91. <input
  92. type="text"
  93. wire:model.live="selectedKpCode"
  94. placeholder="如:KP1001"
  95. class="filament-forms-input block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm"
  96. />
  97. </div>
  98. <div>
  99. <label class="block text-sm font-medium text-gray-700 mb-2">
  100. 难度筛选
  101. </label>
  102. <input
  103. type="text"
  104. wire:model.live="selectedDifficulty"
  105. placeholder="0.3/0.6/0.85"
  106. class="filament-forms-input block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm"
  107. />
  108. </div>
  109. <div>
  110. <label class="block text-sm font-medium text-gray-700 mb-2">
  111. 每页显示
  112. </label>
  113. <input
  114. type="number"
  115. wire:model.live="perPage"
  116. min="10"
  117. max="100"
  118. step="5"
  119. class="filament-forms-input block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm"
  120. />
  121. </div>
  122. </div>
  123. </div>
  124. {{-- 题目列表 --}}
  125. <div class="bg-white rounded-lg border overflow-hidden">
  126. <div class="overflow-x-auto" wire:loading.class="opacity-50">
  127. <table class="min-w-full divide-y divide-gray-200">
  128. <thead class="bg-gray-50">
  129. <tr>
  130. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
  131. 题目编号
  132. </th>
  133. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
  134. 知识点
  135. </th>
  136. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
  137. 题干
  138. </th>
  139. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
  140. 关联技能
  141. </th>
  142. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
  143. 难度
  144. </th>
  145. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
  146. 来源
  147. </th>
  148. <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
  149. 操作
  150. </th>
  151. </tr>
  152. </thead>
  153. <tbody class="bg-white divide-y divide-gray-200">
  154. @forelse($questionsData as $question)
  155. <tr class="hover:bg-gray-50">
  156. <td class="px-6 py-4 whitespace-nowrap">
  157. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
  158. {{ $question['question_code'] ?? 'N/A' }}
  159. </span>
  160. </td>
  161. <td class="px-6 py-4 whitespace-nowrap">
  162. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800">
  163. {{ $question['kp_code'] ?? 'N/A' }}
  164. </span>
  165. </td>
  166. <td class="px-6 py-4">
  167. <div class="text-sm text-gray-900 max-w-xs">
  168. {{ \Illuminate\Support\Str::limit($question['stem'] ?? 'N/A', 80) }}
  169. </div>
  170. </td>
  171. <td class="px-6 py-4">
  172. @php
  173. $skills = $question['skills'] ?? [];
  174. if (is_string($skills)) {
  175. $skills = json_decode($skills, true) ?? [];
  176. }
  177. $skillNames = [];
  178. foreach ($skills as $skill) {
  179. $skillNames[] = $skill['skill_name'] ?? ($skill['skill_code'] ?? 'N/A');
  180. }
  181. $skillText = implode(', ', array_slice($skillNames, 0, 2));
  182. if (count($skillNames) > 2) {
  183. $skillText .= ' ...';
  184. }
  185. @endphp
  186. <div class="text-xs text-gray-600 max-w-xs">
  187. @if(!empty($skillText))
  188. <span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-800">
  189. {{ $skillText }}
  190. </span>
  191. @else
  192. <span class="text-gray-400">无关联技能</span>
  193. @endif
  194. </div>
  195. </td>
  196. <td class="px-6 py-4 whitespace-nowrap">
  197. @php
  198. $difficulty = $question['difficulty'] ?? null;
  199. $difficultyLabel = match (true) {
  200. !$difficulty => 'N/A',
  201. (float)$difficulty <= 0.4 => '基础',
  202. (float)$difficulty <= 0.7 => '中等',
  203. default => '拔高',
  204. };
  205. $difficultyColor = match (true) {
  206. !$difficulty => 'gray',
  207. (float)$difficulty <= 0.4 => 'success',
  208. (float)$difficulty <= 0.7 => 'warning',
  209. default => 'danger',
  210. };
  211. @endphp
  212. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-{{ $difficultyColor }}-100 text-{{ $difficultyColor }}-800">
  213. {{ $difficultyLabel }}
  214. </span>
  215. </td>
  216. <td class="px-6 py-4 whitespace-nowrap">
  217. @php
  218. $source = $question['source'] ?? '';
  219. $sourceLabel = str_contains($source, 'ai::') ? 'AI 生成' : '手工录入';
  220. $sourceColor = str_contains($source, 'ai::') ? 'blue' : 'gray';
  221. @endphp
  222. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-{{ $sourceColor }}-100 text-{{ $sourceColor }}-800">
  223. {{ $sourceLabel }}
  224. </span>
  225. </td>
  226. <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
  227. <button type="button" class="text-indigo-600 hover:text-indigo-900 mr-3">
  228. 查看
  229. </button>
  230. <button type="button" class="text-red-600 hover:text-red-900">
  231. 删除
  232. </button>
  233. </td>
  234. </tr>
  235. @empty
  236. <tr>
  237. <td colspan="7" class="px-6 py-12 text-center text-sm text-gray-500">
  238. <div class="flex flex-col items-center">
  239. <x-heroicon-m-document-magnifying-glass class="w-12 h-12 text-gray-400 mb-3" />
  240. 暂无题目数据
  241. <p class="mt-2 text-xs text-gray-400">
  242. 请尝试调整搜索条件或生成新题目
  243. </p>
  244. </div>
  245. </td>
  246. </tr>
  247. @endforelse
  248. </tbody>
  249. </table>
  250. </div>
  251. {{-- 分页信息 --}}
  252. @if(!empty($metaData) && ($metaData['total'] ?? 0) > 0)
  253. <div class="bg-white px-4 py-3 border-t border-gray-200 sm:px-6">
  254. <div class="flex items-center justify-between">
  255. <div class="text-sm text-gray-700">
  256. 显示第 {{ (($metaData['page'] ?? 1) - 1) * ($metaData['per_page'] ?? 25) + 1 }} 到
  257. {{ min(($metaData['page'] ?? 1) * ($metaData['per_page'] ?? 25), $metaData['total'] ?? 0) }} 条,
  258. 共 {{ $metaData['total'] ?? 0 }} 条记录
  259. </div>
  260. <div class="flex items-center gap-2">
  261. <button
  262. type="button"
  263. class="px-3 py-1 text-sm border rounded {{ $currentPage <= 1 ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50' }}"
  264. wire:click="previousPage"
  265. @disabled($currentPage <= 1)
  266. >
  267. 上一页
  268. </button>
  269. @foreach($this->getPages() as $page)
  270. <button
  271. type="button"
  272. class="px-3 py-1 text-sm border rounded {{ $page === $currentPage ? 'bg-primary-50 text-primary-700 border-primary-300' : 'hover:bg-gray-50' }}"
  273. wire:click="gotoPage({{ $page }})"
  274. >
  275. {{ $page }}
  276. </button>
  277. @endforeach
  278. <button
  279. type="button"
  280. class="px-3 py-1 text-sm border rounded {{ $currentPage >= ($metaData['total_pages'] ?? 1) ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50' }}"
  281. wire:click="nextPage"
  282. @disabled($currentPage >= ($metaData['total_pages'] ?? 1))
  283. >
  284. 下一页
  285. </button>
  286. </div>
  287. </div>
  288. </div>
  289. @endif
  290. </div>
  291. {{-- 加载指示器 --}}
  292. <div wire:loading class="fixed top-4 right-4 bg-primary-600 text-white px-4 py-2 rounded-lg shadow-lg flex items-center gap-2 z-50">
  293. <div class="animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full"></div>
  294. <span>加载中...</span>
  295. </div>
  296. </x-filament::section>
  297. </div>