subject_detail.html 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. {% extends "base.html" %}
  2. {% block title %}{{ subject.name }} - 满意错题本{% endblock %}
  3. {% block page_title %}{{ subject.name }}{% endblock %}
  4. {% block content %}
  5. <div class="flex items-center gap-3 mb-6">
  6. <a href="/subjects" class="text-sm text-gray-400 hover:text-gray-600 transition flex items-center gap-1">
  7. <i class="fa-solid fa-arrow-left"></i>返回
  8. </a>
  9. <span class="w-2.5 h-2.5 rounded-full" style="background-color:{{ subject.color }}"></span>
  10. <span class="text-xs text-gray-400">{{ images|length }} 条记录</span>
  11. </div>
  12. <!-- Date nav -->
  13. <div class="bg-white rounded-2xl border border-gray-100 p-4 mb-6">
  14. <div class="flex items-center gap-3 overflow-x-auto pb-1">
  15. {% if prev_date %}
  16. <a href="?date={{ prev_date }}" class="flex-shrink-0 w-9 h-9 rounded-xl bg-gray-50 hover:bg-gray-100 flex items-center justify-center text-gray-500 transition" title="上一日期">
  17. <i class="fa-solid fa-chevron-left text-sm"></i>
  18. </a>
  19. {% else %}
  20. <span class="flex-shrink-0 w-9 h-9 rounded-xl bg-gray-50 flex items-center justify-center text-gray-200">
  21. <i class="fa-solid fa-chevron-left text-sm"></i>
  22. </span>
  23. {% endif %}
  24. {% if today not in dates %}
  25. <a href="?date={{ today }}" class="date-chip flex-shrink-0 px-3 py-1.5 rounded-xl text-xs font-medium transition {% if current_date == today %}bg-primary text-white shadow-sm{% else %}bg-gray-50 text-gray-500 hover:bg-gray-100{% endif %}">
  26. 今日
  27. </a>
  28. {% endif %}
  29. {% for d in dates %}
  30. <a href="?date={{ d }}" class="date-chip flex-shrink-0 px-3 py-1.5 rounded-xl text-xs font-medium transition {% if d == current_date %}bg-primary text-white shadow-sm{% else %}bg-gray-50 text-gray-500 hover:bg-gray-100{% endif %}">
  31. {{ d }}
  32. </a>
  33. {% endfor %}
  34. {% if next_date %}
  35. <a href="?date={{ next_date }}" class="flex-shrink-0 w-9 h-9 rounded-xl bg-gray-50 hover:bg-gray-100 flex items-center justify-center text-gray-500 transition" title="下一日期">
  36. <i class="fa-solid fa-chevron-right text-sm"></i>
  37. </a>
  38. {% else %}
  39. <span class="flex-shrink-0 w-9 h-9 rounded-xl bg-gray-50 flex items-center justify-center text-gray-200">
  40. <i class="fa-solid fa-chevron-right text-sm"></i>
  41. </span>
  42. {% endif %}
  43. <span class="text-gray-200 flex-shrink-0">|</span>
  44. <form method="GET" action="" class="flex-shrink-0 flex items-center gap-2">
  45. <input type="date" name="date" value="{{ current_date }}"
  46. onchange="this.form.submit()"
  47. class="border border-gray-200 rounded-xl px-3 py-1.5 text-xs focus:outline-none focus:ring-2 focus:ring-primary/30 text-gray-500">
  48. </form>
  49. </div>
  50. </div>
  51. <!-- Input tabs -->
  52. <div class="bg-white rounded-2xl border border-gray-100 p-5 mb-6">
  53. <div class="flex gap-2 mb-5">
  54. <button id="tabImage" onclick="switchTab('image')" class="tab-btn flex-1 py-2.5 rounded-xl text-sm font-medium transition bg-primary text-white shadow-sm">
  55. <i class="fa-solid fa-image mr-1.5"></i>上传错题图片
  56. </button>
  57. <button id="tabText" onclick="switchTab('text')" class="tab-btn flex-1 py-2.5 rounded-xl text-sm font-medium transition bg-gray-100 text-gray-500 hover:bg-gray-50">
  58. <i class="fa-solid fa-keyboard mr-1.5"></i>文字录入错题
  59. </button>
  60. </div>
  61. <!-- Image upload panel -->
  62. <div id="panelImage">
  63. <form method="POST" action="/subject/{{ subject.id }}/upload" enctype="multipart/form-data" id="uploadForm">
  64. <input type="hidden" name="date" value="{{ current_date }}">
  65. <div class="flex items-start gap-4">
  66. <label class="flex-1 cursor-pointer">
  67. <div class="border-2 border-dashed border-gray-200 rounded-xl p-6 text-center hover:border-primary/50 hover:bg-indigo-50/30 transition" id="dropZone">
  68. <i class="fa-solid fa-images text-2xl text-gray-300 mb-2 block"></i>
  69. <p class="text-sm text-gray-500">点击选择或拖拽图片到此</p>
  70. <p class="text-xs text-gray-300 mt-1">支持 JPG、PNG、WEBP,可多选</p>
  71. <input type="file" name="files" multiple accept="image/*" class="hidden" id="fileInput" onchange="updateFileLabel()">
  72. </div>
  73. </label>
  74. <button type="submit" class="flex-shrink-0 bg-primary text-white px-5 py-2.5 rounded-xl text-sm font-medium hover:bg-indigo-600 transition shadow-sm">
  75. <i class="fa-solid fa-upload mr-1.5"></i>上传
  76. </button>
  77. </div>
  78. <p id="fileLabel" class="text-xs text-primary mt-2 min-h-[18px]"></p>
  79. </form>
  80. </div>
  81. <!-- Text input panel -->
  82. <div id="panelText" class="hidden">
  83. <form method="POST" action="/subject/{{ subject.id }}/text">
  84. <input type="hidden" name="date" value="{{ current_date }}">
  85. <div class="space-y-4">
  86. <div>
  87. <label class="block text-sm font-medium text-gray-600 mb-1.5">错题题目 <span class="text-red-400">*</span></label>
  88. <textarea name="question" required rows="3" placeholder="请输入错题的题目内容..."
  89. class="w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary transition resize-none"></textarea>
  90. </div>
  91. <div>
  92. <label class="block text-sm font-medium text-gray-600 mb-1.5">正确答案 / 解析</label>
  93. <textarea name="answer" rows="2" placeholder="请输入正确答案或解题思路..."
  94. class="w-full border border-gray-200 rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary transition resize-none"></textarea>
  95. </div>
  96. <div class="flex justify-end">
  97. <button type="submit" class="bg-primary text-white px-6 py-2.5 rounded-xl text-sm font-medium hover:bg-indigo-600 transition shadow-sm">
  98. <i class="fa-solid fa-check mr-1.5"></i>保存错题
  99. </button>
  100. </div>
  101. </div>
  102. </form>
  103. </div>
  104. </div>
  105. <!-- Content grid -->
  106. {% set text_errors = images | selectattr('filename', 'equalto', '') | list %}
  107. {% set img_errors = images | selectattr('filename') | list %}
  108. {% if img_errors or text_errors %}
  109. <div class="mb-4 flex items-center justify-between">
  110. <h3 class="font-semibold text-gray-700 text-sm">{{ current_date }} 的错题({{ images|length }} 条)</h3>
  111. <span class="text-xs text-gray-400">{% if img_errors %}{{ img_errors|length }} 张图片{% if text_errors %} · {{ text_errors|length }} 条文字{% endif %}{% else %}{{ text_errors|length }} 条文字记录{% endif %}</span>
  112. </div>
  113. <div class="grid grid-cols-4 gap-4">
  114. {% for img in img_errors %}
  115. <div class="image-card group relative aspect-[3/4] rounded-2xl overflow-hidden bg-gray-100 shadow-sm cursor-pointer" onclick="openLightbox('/static/uploads/{{ img.filename }}','{{ img.original_name }}',lbAllImages,{{ loop.index0 }})">
  116. <img src="/static/uploads/{{ img.filename }}" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" loading="lazy">
  117. <div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition z-10">
  118. <form method="POST" action="/image/{{ img.id }}/delete" onsubmit="return confirm('确定删除这条错题记录?')" onclick="event.stopPropagation()">
  119. <button type="submit" class="w-8 h-8 rounded-full bg-white/90 flex items-center justify-center text-red-500 hover:bg-red-50 transition shadow-sm" title="删除">
  120. <i class="fa-solid fa-trash-can text-sm"></i>
  121. </button>
  122. </form>
  123. </div>
  124. <div class="image-actions absolute inset-0 bg-black/30 opacity-0 transition flex items-center justify-center">
  125. <button onclick="event.stopPropagation();openLightbox('/static/uploads/{{ img.filename }}','{{ img.original_name }}',lbAllImages,{{ loop.index0 }})" class="w-9 h-9 rounded-full bg-white/90 flex items-center justify-center text-gray-700 hover:bg-white transition" title="查看">
  126. <i class="fa-solid fa-magnifying-glass-plus text-sm"></i>
  127. </button>
  128. </div>
  129. <div class="absolute bottom-2 left-2 right-2">
  130. <p class="text-[10px] text-white/80 truncate px-1.5 py-0.5 bg-black/30 rounded">{{ img.original_name }}</p>
  131. </div>
  132. </div>
  133. {% endfor %}
  134. {% for txt in text_errors %}
  135. <div class="image-card group relative rounded-2xl overflow-hidden bg-white shadow-sm border border-gray-100 cursor-pointer hover:shadow-md transition" onclick="toggleAnswer({{ txt.id }})">
  136. <div class="p-4 flex flex-col h-full">
  137. <div class="flex items-center gap-2 mb-2">
  138. <span class="w-2 h-2 rounded-full bg-violet-400 flex-shrink-0"></span>
  139. <span class="text-[10px] text-gray-400 flex-shrink-0">文字记录</span>
  140. <div class="ml-auto flex gap-1 image-actions opacity-0 transition" onclick="event.stopPropagation()">
  141. <form method="POST" action="/image/{{ txt.id }}/delete" onsubmit="return confirm('确定删除这条错题记录?')">
  142. <button type="submit" class="w-7 h-7 rounded-full bg-gray-100 hover:bg-red-50 flex items-center justify-center text-gray-400 hover:text-red-500 transition" title="删除">
  143. <i class="fa-solid fa-trash-can text-xs"></i>
  144. </button>
  145. </form>
  146. </div>
  147. </div>
  148. <p class="text-sm text-gray-800 font-medium leading-relaxed flex-1 {{ 'line-clamp-4' if not txt.answer else 'line-clamp-3' }}">{{ txt.question }}</p>
  149. {% if txt.answer %}
  150. <div id="answer-{{ txt.id }}" class="hidden mt-3 pt-3 border-t border-gray-100">
  151. <p class="text-xs text-gray-500 leading-relaxed">{{ txt.answer }}</p>
  152. </div>
  153. {% endif %}
  154. </div>
  155. <div class="px-4 py-2.5 bg-gray-50 border-t border-gray-100 flex items-center justify-between">
  156. <span class="text-[10px] text-gray-400">{% if txt.answer %}展开解析 / 收起解析{% else %}查看题目{% endif %}</span>
  157. {% if txt.answer %}
  158. <span class="text-[10px] text-primary flex items-center gap-1"><i class="fa-solid fa-lightbulb"></i>有解析</span>
  159. {% endif %}
  160. </div>
  161. </div>
  162. {% endfor %}
  163. </div>
  164. {% else %}
  165. <div class="bg-white rounded-2xl border border-gray-100 p-12 text-center">
  166. <div class="text-5xl text-gray-200 mb-4"><i class="fa-regular fa-folder-open"></i></div>
  167. <p class="text-gray-400 text-sm">该日期暂无错题记录</p>
  168. </div>
  169. {% endif %}
  170. {% endblock %}
  171. {% block extra_js %}
  172. <script>
  173. function switchTab(tab) {
  174. var pi = document.getElementById('panelImage');
  175. var pt = document.getElementById('panelText');
  176. var bi = document.getElementById('tabImage');
  177. var bt = document.getElementById('tabText');
  178. if (tab === 'text') {
  179. pi.classList.add('hidden');
  180. pt.classList.remove('hidden');
  181. bi.classList.remove('bg-primary','text-white','shadow-sm');
  182. bi.classList.add('bg-gray-100','text-gray-500','hover:bg-gray-50');
  183. bt.classList.remove('bg-gray-100','text-gray-500','hover:bg-gray-50');
  184. bt.classList.add('bg-primary','text-white','shadow-sm');
  185. } else {
  186. pt.classList.add('hidden');
  187. pi.classList.remove('hidden');
  188. bt.classList.remove('bg-primary','text-white','shadow-sm');
  189. bt.classList.add('bg-gray-100','text-gray-500','hover:bg-gray-50');
  190. bi.classList.remove('bg-gray-100','text-gray-500','hover:bg-gray-50');
  191. bi.classList.add('bg-primary','text-white','shadow-sm');
  192. }
  193. }
  194. function toggleAnswer(id) {
  195. var el = document.getElementById('answer-' + id);
  196. if (el) el.classList.toggle('hidden');
  197. }
  198. const lbAllImages = [
  199. {% for img in images %}{% if img.filename %}"/static/uploads/{{ img.filename }}",{% endif %}
  200. {% endfor %}
  201. ];
  202. function updateFileLabel(){
  203. const f=document.getElementById('fileInput').files;
  204. document.getElementById('fileLabel').textContent=f.length?f.length+' 个文件已选择':'';
  205. }
  206. const dz=document.getElementById('dropZone');
  207. dz.addEventListener('dragover',e=>{e.preventDefault();dz.classList.add('border-primary','bg-indigo-50/30');});
  208. dz.addEventListener('dragleave',()=>dz.classList.remove('border-primary','bg-indigo-50/30'));
  209. dz.addEventListener('drop',e=>{e.preventDefault();dz.classList.remove('border-primary','bg-indigo-50/30');document.getElementById('fileInput').files=e.dataTransfer.files;updateFileLabel();});
  210. </script>
  211. {% endblock %}