markdown-import-workbench.blade.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. <x-filament::page>
  2. <style>
  3. .command-bar {
  4. background: rgba(255, 255, 255, 0.85);
  5. backdrop-filter: blur(10px);
  6. -webkit-backdrop-filter: blur(10px);
  7. border: 1px solid rgba(255, 255, 255, 0.3);
  8. box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
  9. }
  10. .ui-tag {
  11. background: #f1f5f9;
  12. color: #475569;
  13. padding: 2px 8px;
  14. border-radius: 6px;
  15. font-size: 0.75rem;
  16. border: 1px solid #e2e8f0;
  17. }
  18. .custom-scroll::-webkit-scrollbar {
  19. width: 4px;
  20. }
  21. .custom-scroll::-webkit-scrollbar-track {
  22. background: transparent;
  23. }
  24. .custom-scroll::-webkit-scrollbar-thumb {
  25. background: #e2e8f0;
  26. border-radius: 10px;
  27. }
  28. .custom-scroll::-webkit-scrollbar-thumb:hover {
  29. background: #cbd5e1;
  30. }
  31. .btn-hover-zoom {
  32. transition: transform 0.2s;
  33. }
  34. .btn-hover-zoom:hover {
  35. transform: scale(1.02);
  36. }
  37. </style>
  38. <div class="space-y-4">
  39. @if(!$this->importRecord())
  40. <x-filament::section>
  41. @include('filament.partials.empty-state', [
  42. 'title' => '未选择导入记录',
  43. 'description' => '请先从 Markdown 导入列表进入工作台。',
  44. 'action' => new \Illuminate\Support\HtmlString('<a class="btn btn-primary btn-sm" href="' . route('filament.admin.resources.markdown-imports.index') . '">返回导入列表</a>'),
  45. ])
  46. </x-filament::section>
  47. @elseif(!$this->filenameValid)
  48. <x-filament::section>
  49. <div class="rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800">
  50. <div class="flex items-center gap-2 font-bold mb-1">
  51. <x-heroicon-o-exclamation-triangle class="w-5 h-5" />
  52. 文件名解析失败
  53. </div>
  54. {{ $this->filenameWarning }}
  55. </div>
  56. <div class="mt-3 text-sm text-slate-600">
  57. 请在导入列表中修改文件名并重新导入后再进入工作台。
  58. </div>
  59. </x-filament::section>
  60. @else
  61. {{-- 顶部高级指令中心 --}}
  62. <div class="sticky top-0 z-20 -mx-4 px-4 py-3 mb-6 command-bar rounded-xl flex items-center justify-between gap-4">
  63. <div class="flex items-center gap-4 flex-1">
  64. <x-filament::input.wrapper class="w-72">
  65. <x-filament::input
  66. wire:model.debounce.400ms="search"
  67. placeholder="搜索卷子标题/编码..."
  68. prefix-icon="heroicon-m-magnifying-glass"
  69. />
  70. </x-filament::input.wrapper>
  71. <div class="h-6 w-px bg-slate-200"></div>
  72. <div class="flex items-center gap-2">
  73. <x-filament::button
  74. color="gray"
  75. icon="heroicon-o-sparkles"
  76. wire:click="autoInfer"
  77. tooltip="基于文件名和内容智能推断缺失信息"
  78. class="btn-hover-zoom"
  79. >
  80. 自动推断
  81. </x-filament::button>
  82. <x-filament::button
  83. color="gray"
  84. icon="heroicon-o-document-duplicate"
  85. wire:click="autoInferSelected"
  86. tooltip="对勾选卷子执行智能推断"
  87. class="btn-hover-zoom"
  88. >
  89. 批量推断
  90. </x-filament::button>
  91. </div>
  92. </div>
  93. <div class="flex items-center gap-3">
  94. <x-filament::button
  95. color="warning"
  96. icon="heroicon-o-squares-plus"
  97. wire:click="mergeSelectedPapers"
  98. class="btn-hover-zoom"
  99. >
  100. 合并选定卷子
  101. </x-filament::button>
  102. <x-filament::button
  103. color="primary"
  104. icon="heroicon-o-check-circle"
  105. wire:click="savePaper"
  106. size="lg"
  107. class="btn-hover-zoom shadow-sm"
  108. >
  109. 保存当前设置
  110. </x-filament::button>
  111. </div>
  112. </div>
  113. <div class="grid grid-cols-12 gap-6 items-start">
  114. {{-- 左侧:资产与资源 --}}
  115. <div class="col-span-12 lg:col-span-8 space-y-6">
  116. {{-- 导入背景卡 --}}
  117. <div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
  118. <div class="px-4 py-3 border-b border-slate-100 bg-slate-50/50 flex items-center justify-between">
  119. <h3 class="font-bold text-slate-800 flex items-center gap-2">
  120. <x-heroicon-o-cloud-arrow-down class="w-4 h-4 text-primary-500" />
  121. 导入源信息
  122. </h3>
  123. <span class="text-xs font-mono text-slate-400">ID: #{{ $this->importId }}</span>
  124. </div>
  125. <div class="p-4">
  126. <div class="grid grid-cols-3 gap-6">
  127. <div class="space-y-1">
  128. <div class="text-[10px] uppercase tracking-wider font-bold text-slate-400">原始文件</div>
  129. <div class="text-sm font-medium text-slate-700 truncate" title="{{ $this->importRecord()?->file_name }}">
  130. {{ $this->importRecord()?->file_name ?? '-' }}
  131. </div>
  132. </div>
  133. <div class="space-y-1">
  134. <div class="text-[10px] uppercase tracking-wider font-bold text-slate-400">当前状态</div>
  135. <div>
  136. <x-filament::badge color="info" icon="heroicon-m-arrow-path">
  137. {{ $this->importRecord()?->status_label ?? '未知' }}
  138. </x-filament::badge>
  139. </div>
  140. </div>
  141. <div class="space-y-1 text-right">
  142. <div class="text-[10px] uppercase tracking-wider font-bold text-slate-400">总候选题</div>
  143. <div class="text-xl font-black text-primary-600">{{ $this->importRecord()?->parsed_count ?? 0 }}</div>
  144. </div>
  145. </div>
  146. @if(!empty($this->filenameParsed))
  147. <div class="mt-4 pt-4 border-t border-slate-50 flex flex-wrap gap-2 text-xs text-slate-500">
  148. <span class="ui-tag">系列:{{ $this->filenameParsed['series'] ?? '-' }}</span>
  149. <span class="ui-tag">年级:{{ $this->filenameParsed['grade'] ?? '-' }}</span>
  150. <span class="ui-tag">学期:{{ $this->filenameParsed['term'] ?? '-' }}</span>
  151. <span class="ui-tag">学科:{{ $this->filenameParsed['subject'] ?? '-' }}</span>
  152. <span class="ui-tag">名称:{{ $this->filenameParsed['name'] ?? '-' }}</span>
  153. </div>
  154. @endif
  155. </div>
  156. </div>
  157. {{-- 卷子切分列表 --}}
  158. <x-filament::section>
  159. <x-slot name="heading">
  160. <div class="flex items-center gap-2">
  161. <x-heroicon-o-list-bullet class="w-5 h-5 text-warning-500" />
  162. 待核对卷子列表
  163. </div>
  164. </x-slot>
  165. <x-slot name="headerEnd">
  166. <div class="flex items-center gap-2">
  167. <x-filament::link wire:click="selectAllVisible" class="text-xs cursor-pointer">全选当前</x-filament::link>
  168. <span class="text-slate-200">|</span>
  169. <x-filament::link wire:click="clearSelection" color="danger" class="text-xs cursor-pointer">清空选择</x-filament::link>
  170. </div>
  171. </x-slot>
  172. <div class="overflow-y-auto custom-scroll pr-2 divide-y divide-slate-50 last:border-b-0" style="height: 500px;">
  173. @foreach($this->groupedPapers() as $group => $items)
  174. <div class="sticky top-0 z-10 px-3 py-1.5 text-[10px] font-black uppercase tracking-widest text-slate-400 bg-white/95 backdrop-blur shadow-sm my-2 rounded-md">
  175. {{ $group }}
  176. </div>
  177. @foreach($items as $paper)
  178. @php
  179. $meta = $paper['meta'] ?? [];
  180. $expected = $meta['expected_count'] ?? null;
  181. $candidateCount = $paper['candidates_count'] ?? 0;
  182. $isSelected = ((int)$this->selectedPaperId === (int)$paper['id']);
  183. $catalogTitles = $this->catalogTitlesForPaper($paper);
  184. @endphp
  185. <div @class([
  186. 'group flex items-start gap-3 p-3 transition-all rounded-xl border border-transparent',
  187. 'bg-primary-50/40 border-primary-100 shadow-sm' => $isSelected,
  188. 'hover:bg-slate-50/80 hover:border-slate-100' => !$isSelected,
  189. ])>
  190. <input type="checkbox" wire:model="selectedIds" wire:click="selectPaper({{ $paper['id'] }})" value="{{ $paper['id'] }}" class="mt-1.5 rounded border-slate-300 text-primary-600 focus:ring-primary-500">
  191. <button type="button" wire:click="selectPaper({{ $paper['id'] }})" class="text-left flex-1 min-w-0">
  192. <div class="flex items-center justify-between gap-2 mb-1">
  193. <span @class([
  194. 'text-sm font-bold truncate transition-colors',
  195. 'text-primary-700' => $isSelected,
  196. 'text-slate-800' => !$isSelected,
  197. ])>
  198. {{ $paper['title'] ?? $paper['full_title'] ?? '未命名卷子' }}
  199. </span>
  200. @if($isSelected)
  201. <span class="inline-flex h-2 w-2 rounded-full bg-primary-500 animate-pulse"></span>
  202. @endif
  203. </div>
  204. <div class="flex flex-wrap items-center gap-3 text-[11px]">
  205. <div class="flex items-center gap-1 text-slate-500 font-medium">
  206. <x-heroicon-m-academic-cap class="w-3.5 h-3.5" />
  207. {{ $paper['grade'] ?? '-' }}年级 / {{ $paper['term'] ?? '-' }}
  208. </div>
  209. <div @class([
  210. 'px-2 py-0.5 rounded-full font-bold border',
  211. 'bg-emerald-50 text-emerald-700 border-emerald-100' => $candidateCount > 0,
  212. 'bg-slate-50 text-slate-400 border-slate-100' => $candidateCount === 0,
  213. ])>
  214. 题量: {{ $candidateCount }}
  215. @if($expected)
  216. <span class="text-[9px] opacity-70 ml-1"> (预期 {{ $expected }})</span>
  217. @endif
  218. </div>
  219. @if(empty($paper['textbook_id']))
  220. <span class="text-rose-500 font-bold flex items-center gap-0.5">
  221. <x-heroicon-m-x-circle class="w-3.5 h-3.5" /> 缺教材
  222. </span>
  223. @endif
  224. @if(empty($catalogTitles))
  225. <span class="text-rose-500 font-bold flex items-center gap-0.5">
  226. <x-heroicon-m-x-circle class="w-3.5 h-3.5" /> 缺目录
  227. </span>
  228. @endif
  229. </div>
  230. @if(!empty($catalogTitles))
  231. <div class="mt-1 text-[11px] text-slate-500 font-medium">
  232. 目录:{{ implode(' · ', array_slice($catalogTitles, 0, 3)) }}@if(count($catalogTitles) > 3)…@endif
  233. </div>
  234. @endif
  235. </button>
  236. </div>
  237. @endforeach
  238. @endforeach
  239. </div>
  240. </x-filament::section>
  241. {{-- 内容预览 --}}
  242. <div class="bg-white rounded-xl shadow-md border border-slate-200 overflow-hidden">
  243. <div class="px-4 py-3 border-b border-slate-100 bg-slate-50/50 flex items-center justify-between">
  244. <div class="flex items-center gap-2">
  245. <span class="text-xs font-black text-slate-500 uppercase tracking-widest">卷子内容预览</span>
  246. @if($this->selectedPaper())
  247. <x-filament::badge size="xs" color="gray" class="bg-slate-200/50 text-slate-600 font-medium border-slate-300">{{ $this->selectedPaper()?->title }}</x-filament::badge>
  248. @endif
  249. </div>
  250. </div>
  251. <div class="p-8 custom-scroll overflow-y-auto bg-slate-50/30" style="height: 600px;">
  252. @if($this->selectedPaper())
  253. <div class="prose prose-slate max-w-none transition-all duration-300">
  254. <div class="markdown-body !bg-transparent p-0">
  255. {!! \App\Services\MathFormulaProcessor::processFormulas($this->selectedPaper()?->raw_markdown ?? '') !!}
  256. </div>
  257. </div>
  258. @else
  259. <div class="h-full flex flex-col items-center justify-center text-slate-400 gap-4 py-20">
  260. <x-heroicon-o-document-magnifying-glass class="w-16 h-16 opacity-30" />
  261. <div class="text-sm font-medium">点击左侧列表查看原始文本内容</div>
  262. </div>
  263. @endif
  264. </div>
  265. </div>
  266. </div>
  267. {{-- 右侧:配置与控制 --}}
  268. <div class="col-span-12 lg:col-span-4 space-y-6">
  269. {{-- 1. 归属定义卡片 --}}
  270. <x-filament::section>
  271. <x-slot name="heading">
  272. <div class="flex items-center gap-2">
  273. <x-heroicon-o-identification class="w-4 h-4 text-primary-500" />
  274. 归属定义
  275. </div>
  276. </x-slot>
  277. <div class="space-y-4">
  278. <div class="space-y-1">
  279. <label class="text-[10px] font-black text-slate-400 uppercase tracking-widest">基础标题</label>
  280. <x-filament::input.wrapper>
  281. <x-filament::input wire:model="form.title" placeholder="例如:期中模拟卷 A" class="font-bold text-slate-800" />
  282. </x-filament::input.wrapper>
  283. </div>
  284. <div class="p-3 bg-slate-50/50 rounded-xl border border-slate-100 space-y-3">
  285. <div class="space-y-1">
  286. <label class="text-[10px] font-black text-slate-400 uppercase tracking-widest">教材系列</label>
  287. <x-filament::input.wrapper>
  288. <x-filament::input.select wire:model.live="form.textbook_series_id" class="font-medium">
  289. <option value="">[ 未选中系列 ]</option>
  290. @foreach($this->seriesOptions() as $id => $label)
  291. <option value="{{ $id }}">{{ $label }}</option>
  292. @endforeach
  293. </x-filament::input.select>
  294. </x-filament::input.wrapper>
  295. </div>
  296. <div class="grid grid-cols-2 gap-3">
  297. <div class="space-y-1">
  298. <label class="text-[10px] font-black text-slate-400 uppercase tracking-widest">年级</label>
  299. <x-filament::input.wrapper>
  300. <x-filament::input.select wire:model.live="form.grade">
  301. <option value="">选择年级</option>
  302. @foreach($this->gradeOptions() as $value => $label)
  303. <option value="{{ $value }}">{{ $label }}</option>
  304. @endforeach
  305. </x-filament::input.select>
  306. </x-filament::input.wrapper>
  307. </div>
  308. <div class="space-y-1">
  309. <label class="text-[10px] font-black text-slate-400 uppercase tracking-widest">学期</label>
  310. <x-filament::input.wrapper>
  311. <x-filament::input.select wire:model.live="form.term">
  312. <option value="">选择学期</option>
  313. @foreach($this->termOptions() as $value => $label)
  314. <option value="{{ $value }}">{{ $label }}</option>
  315. @endforeach
  316. </x-filament::input.select>
  317. </x-filament::input.wrapper>
  318. </div>
  319. </div>
  320. <div class="space-y-1 pt-1">
  321. <div class="flex items-center justify-between">
  322. <label class="text-[10px] font-black text-primary-500 uppercase tracking-widest">目标教材关联</label>
  323. <div wire:loading wire:target="form.grade, form.term, form.textbook_series_id, form.textbook_id" class="text-[9px] text-primary-500 animate-pulse font-bold">同步中...</div>
  324. </div>
  325. <x-filament::input.wrapper wire:key="textbook-select-parent-{{ $this->form['textbook_series_id'] ?? 'none' }}">
  326. <x-filament::input.select wire:model.live="form.textbook_id" @class([
  327. 'font-black',
  328. 'text-primary-600' => !empty($this->form['textbook_id']),
  329. 'text-rose-500 underline decoration-dotted' => empty($this->form['textbook_id']),
  330. ])>
  331. <option value="">[ 自动寻找最匹配教材 ]</option>
  332. @foreach($this->textbookOptions() as $id => $title)
  333. <option value="{{ $id }}">{{ $title }}</option>
  334. @endforeach
  335. </x-filament::input.select>
  336. </x-filament::input.wrapper>
  337. </div>
  338. </div>
  339. </div>
  340. </x-filament::section>
  341. {{-- 2. 章节目录卡片 (关键:修复点击保存不生效的逻辑引导) --}}
  342. <x-filament::section collapsible>
  343. <x-slot name="heading">
  344. <div class="flex items-center gap-2">
  345. <x-heroicon-o-list-bullet class="w-4 h-4 text-warning-500" />
  346. 关联目录章节
  347. </div>
  348. </x-slot>
  349. <div class="space-y-4">
  350. <div wire:key="catalog-list-{{ $this->form['textbook_id'] ?? 'none' }}" class="overflow-y-auto border border-slate-200 rounded-xl p-2 bg-slate-50/30 divide-y divide-slate-100 custom-scroll" style="max-height: 400px;">
  351. @forelse($this->catalogOptions() as $id => $label)
  352. <label class="flex items-center gap-3 py-2 px-3 hover:bg-white transition-all cursor-pointer group rounded-lg">
  353. <input type="checkbox" wire:model.defer="form.catalog_node_ids" value="{{ $id }}" class="rounded-sm border-slate-300 text-primary-600 focus:ring-primary-500">
  354. <span class="text-[11px] font-bold text-slate-700 group-hover:text-primary-700">{{ $label }}</span>
  355. </label>
  356. @empty
  357. <div class="py-12 text-center text-slate-400">
  358. <x-heroicon-m-document-magnifying-glass class="w-10 h-10 mx-auto mb-3 opacity-20" />
  359. <p class="text-[11px] italic font-medium">请先指定教材以加载章节路径</p>
  360. </div>
  361. @endforelse
  362. </div>
  363. @if(!empty($this->form['catalog_node_ids']))
  364. <div class="text-[10px] text-slate-400 italic flex items-center gap-1 px-1">
  365. <x-heroicon-m-check-circle class="w-3 h-3 text-emerald-500" />
  366. 已选择 {{ count($this->form['catalog_node_ids']) }} 个节点
  367. </div>
  368. @endif
  369. </div>
  370. </x-filament::section>
  371. {{-- 3. 补充元数据 (默认折叠,减少干扰) --}}
  372. <x-filament::section collapsible collapsed>
  373. <x-slot name="heading">
  374. <div class="flex items-center gap-2">
  375. <x-heroicon-o-plus-circle class="w-4 h-4 text-slate-400" />
  376. 补充元数据
  377. </div>
  378. </x-slot>
  379. <div class="space-y-3 mt-1">
  380. <div class="grid grid-cols-2 gap-3">
  381. <div class="space-y-1">
  382. <label class="text-[10px] font-bold text-slate-500">预期题数</label>
  383. <x-filament::input.wrapper>
  384. <x-filament::input wire:model="form.expected_count" placeholder="例如: 25" />
  385. </x-filament::input.wrapper>
  386. </div>
  387. <div class="space-y-1">
  388. <label class="text-[10px] font-bold text-slate-500">发布年份</label>
  389. <x-filament::input.wrapper>
  390. <x-filament::input wire:model="form.source_year" placeholder="例如: 2024" />
  391. </x-filament::input.wrapper>
  392. </div>
  393. </div>
  394. <div class="space-y-1">
  395. <label class="text-[10px] font-bold text-slate-500">来源名称</label>
  396. <x-filament::input.wrapper>
  397. <x-filament::input wire:model="form.source_name" placeholder="来源名称 (例: 课课练)" />
  398. </x-filament::input.wrapper>
  399. </div>
  400. <div class="grid grid-cols-2 gap-3">
  401. <div class="space-y-1">
  402. <label class="text-[10px] font-bold text-slate-500">页码范围</label>
  403. <x-filament::input.wrapper>
  404. <x-filament::input wire:model="form.source_page" placeholder="例如: 1-4" />
  405. </x-filament::input.wrapper>
  406. </div>
  407. <div class="space-y-1">
  408. <label class="text-[10px] font-bold text-slate-500">通用标签</label>
  409. <x-filament::input.wrapper>
  410. <x-filament::input wire:model="form.tags" placeholder="标签(逗号隔开)" />
  411. </x-filament::input.wrapper>
  412. </div>
  413. </div>
  414. <div class="bg-amber-50 rounded-xl p-3 border border-amber-100 shadow-sm">
  415. <div class="text-[10px] font-black text-amber-600 uppercase tracking-widest mb-1.5 flex items-center gap-1.5">
  416. <x-heroicon-m-sparkles class="w-3.5 h-3.5" />
  417. 智能预检测系统
  418. </div>
  419. <div class="text-[11px] text-amber-800 leading-relaxed font-medium space-y-1">
  420. <div>推断类型:<span class="font-bold underline decoration-amber-300">{{ $this->form['source_type'] ?? '未识别类型' }}</span></div>
  421. <div>推断章节:<span class="font-bold underline decoration-amber-300">{{ $this->form['chapter'] ?? '未识别章节' }}</span></div>
  422. </div>
  423. </div>
  424. </div>
  425. </x-filament::section>
  426. {{-- 批量操作区 (极致紧凑设计) --}}
  427. <div class="bg-slate-900 rounded-2xl p-5 space-y-4 shadow-xl border-t-2 border-primary-500">
  428. <div class="flex items-center justify-between gap-2 mb-2">
  429. <div class="flex items-center gap-2">
  430. <x-heroicon-o-bolt class="w-4 h-4 text-warning-400" />
  431. <span class="text-xs font-black text-white uppercase tracking-widest">批量高效工具柜</span>
  432. </div>
  433. <x-filament::badge color="warning" size="xs">{{ count($this->selectedIds) }} 卷选中</x-filament::badge>
  434. </div>
  435. <div class="space-y-2">
  436. <x-filament::input.select wire:model.live="batch.textbook_series_id" size="sm" class="bg-slate-800 border-none text-white text-[11px]">
  437. <option value="">批量系列...</option>
  438. @foreach($this->seriesOptions() as $id => $name)
  439. <option value="{{ $id }}">{{ $name }}</option>
  440. @endforeach
  441. </x-filament::input.select>
  442. <x-filament::input.select wire:model="batch.textbook_id" size="sm" class="bg-slate-800 border-none text-white text-[11px]" wire:key="batch-textbook-{{ $this->batch['textbook_series_id'] ?? 'none' }}">
  443. <option value="">批量教材...</option>
  444. @foreach($this->textbookOptions($this->batch['textbook_series_id'] ?? null) as $id => $label)
  445. <option value="{{ $id }}">{{ $label }}</option>
  446. @endforeach
  447. </x-filament::input.select>
  448. </div>
  449. <div class="grid grid-cols-2 gap-2">
  450. <x-filament::input.select wire:model="batch.grade" size="sm" class="bg-slate-800 border-none text-white text-[11px]">
  451. <option value="">年级...</option>
  452. @foreach($this->gradeOptions() as $value => $label)
  453. <option value="{{ $value }}">{{ $label }}</option>
  454. @endforeach
  455. </x-filament::input.select>
  456. <x-filament::input.select wire:model="batch.term" size="sm" class="bg-slate-800 border-none text-white text-[11px]">
  457. <option value="">学期...</option>
  458. @foreach($this->termOptions() as $value => $label)
  459. <option value="{{ $value }}">{{ $label }}</option>
  460. @endforeach
  461. </x-filament::input.select>
  462. </div>
  463. <div class="space-y-1 pt-1">
  464. <label class="text-[10px] font-black text-slate-400 uppercase tracking-widest px-1">关联目录批量</label>
  465. <div wire:key="batch-catalog-{{ $this->batch['textbook_id'] ?? 'none' }}" class="overflow-y-auto border border-slate-700/50 rounded-xl p-2 bg-slate-800 divide-y divide-slate-700/30 custom-scroll" style="height: 150px;">
  466. @forelse($this->catalogOptions($this->batch['textbook_id'] ?? null) as $id => $label)
  467. <label class="flex items-center gap-2 py-1.5 px-2 hover:bg-slate-700 transition-colors cursor-pointer rounded-lg group">
  468. <input type="checkbox" wire:model.defer="batch.catalog_node_ids" value="{{ $id }}" class="rounded-sm border-slate-600 bg-slate-700 text-primary-500 focus:ring-primary-400">
  469. <span class="text-[10px] text-slate-300 group-hover:text-white font-medium">{{ $label }}</span>
  470. </label>
  471. @empty
  472. <div class="text-[10px] text-slate-500 py-10 italic text-center">选择教材以加载目录</div>
  473. @endforelse
  474. </div>
  475. </div>
  476. <div class="grid grid-cols-2 gap-3 pt-2">
  477. <x-filament::button
  478. color="gray"
  479. size="sm"
  480. icon="heroicon-m-arrow-left-on-rectangle"
  481. wire:click="seedBatchFromCurrent"
  482. class="bg-slate-800 border-none hover:bg-slate-700 text-slate-300"
  483. >
  484. 复制配置
  485. </x-filament::button>
  486. <x-filament::button
  487. color="warning"
  488. icon="heroicon-o-fire"
  489. size="sm"
  490. class="shadow-orange-500/20 shadow-lg"
  491. x-on:click.prevent="if(confirm('确认将批量设置覆盖到勾选的卷子吗?')) { $wire.applyBatch() }"
  492. >
  493. 执行覆盖
  494. </x-filament::button>
  495. </div>
  496. </div>
  497. </div>
  498. </div>
  499. @endif
  500. </div>
  501. </x-filament::page>