detail.html 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. {% extends "layout.html" %}
  2. {% block page_title %}题目详情 - {{ q.question_code }}{% endblock %}
  3. {% block header_actions %}
  4. <div class="flex items-center gap-4">
  5. <div class="text-sm font-medium text-gray-500">第 {{ curr_num }} / {{ total }} 题</div>
  6. <div class="flex items-center gap-2">
  7. {% if prev_code %}
  8. <a href="/detail/{{ prev_code }}" class="p-2 hover:bg-gray-100 rounded-lg transition-colors">
  9. <i class="ri-arrow-left-line text-gray-600"></i>
  10. </a>
  11. {% endif %}
  12. {% if next_code %}
  13. <a href="/detail/{{ next_code }}" id="next-btn" class="p-2 hover:bg-gray-100 rounded-lg transition-colors">
  14. <i class="ri-arrow-right-line text-gray-600"></i>
  15. </a>
  16. {% endif %}
  17. </div>
  18. </div>
  19. {% endblock %}
  20. {% block content %}
  21. <div class="mb-8 no-print flex items-center justify-between">
  22. <div class="flex items-center gap-4">
  23. {% if kp_code %}
  24. <a href="/question_management?kp_code={{ kp_code }}" class="text-blue-600 font-medium hover:underline">← 返回列表</a>
  25. {% elif node_id %}
  26. <a href="/textbook/{{ node_id }}" class="text-blue-600 font-medium hover:underline">← 返回列表</a>
  27. <a href="/#textbook-{{ node_id }}" class="text-blue-600 font-medium hover:underline">← 返回教材库</a>
  28. {% else %}
  29. <a href="javascript:history.back()" class="text-blue-600 font-medium hover:underline">← 返回上一页</a>
  30. {% endif %}
  31. </div>
  32. {% if add_question_url %}
  33. <a href="{{ add_question_url }}" id="continue-add-btn" class="btn-apple bg-gradient-to-r from-blue-600 to-indigo-600 text-white hover:from-blue-700 hover:to-indigo-700 text-sm py-2 px-4 shadow-lg shadow-blue-200">
  34. ➕ 继续录题 <span class="text-xs opacity-75 ml-1">(R)</span>
  35. </a>
  36. {% endif %}
  37. </div>
  38. <div class="grid grid-cols-1 gap-8">
  39. <!-- 题目卡片 -->
  40. <div class="apple-card p-10">
  41. <div class="flex items-center justify-between mb-8">
  42. <div class="space-y-1">
  43. <span class="text-xs font-bold text-gray-400 uppercase tracking-widest">Question Details</span>
  44. <div class="flex items-center gap-3">
  45. <h2 class="text-2xl font-bold">{{ q.question_code }}</h2>
  46. {% if q.difficulty is not none %}
  47. {% set diff = q.difficulty %}
  48. {% if (diff | float) == 0.2 or (diff | string) == '0.2' %}
  49. <span class="px-3 py-1 rounded-full text-xs font-bold bg-green-100 text-green-700 border border-green-200">筑基</span>
  50. {% elif (diff | float) == 0.4 or (diff | string) == '0.4' %}
  51. <span class="px-3 py-1 rounded-full text-xs font-bold bg-yellow-100 text-yellow-700 border border-yellow-200">提分</span>
  52. {% elif (diff | float) == 0.7 or (diff | string) == '0.7' %}
  53. <span class="px-3 py-1 rounded-full text-xs font-bold bg-orange-100 text-orange-700 border border-orange-200">培优</span>
  54. {% endif %}
  55. {% endif %}
  56. </div>
  57. {% set pk = (q.get('id') or q.get('question_id') or q.get('pk_id') or q.get('qid')) %}
  58. {% if pk %}
  59. <div class="text-xs text-gray-400 font-mono">主键ID: {{ pk }}</div>
  60. {% endif %}
  61. </div>
  62. <div class="flex items-center space-x-4 no-print">
  63. <a href="/edit/{{ q.question_code }}" class="btn-apple bg-gradient-to-r from-blue-500 to-blue-600 text-white hover:from-blue-600 hover:to-blue-700 shadow-lg shadow-blue-200 flex items-center gap-2 px-4 py-2.5">
  64. <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  65. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
  66. </svg>
  67. <span>编辑题目</span>
  68. </a>
  69. <a href="/export_pdf_remote/{{ q.question_code }}" target="_blank" class="btn-apple bg-gradient-to-r from-slate-500 to-slate-600 text-white hover:from-slate-600 hover:to-slate-700 shadow-lg shadow-slate-200 flex items-center gap-2 px-4 py-2.5">
  70. <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  71. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"/>
  72. </svg>
  73. <span>导出 PDF</span>
  74. </a>
  75. <button onclick="deleteQuestion('{{ q.question_code }}')" class="btn-apple bg-gradient-to-r from-red-500 to-red-600 text-white hover:from-red-600 hover:to-red-700 shadow-lg shadow-red-200 flex items-center gap-2 px-4 py-2.5">
  76. <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  77. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
  78. </svg>
  79. <span>删除题目</span>
  80. </button>
  81. </div>
  82. </div>
  83. <div class="space-y-12">
  84. <!-- 题干 -->
  85. <section>
  86. <h4 class="text-sm font-bold text-gray-400 mb-4 uppercase">题干</h4>
  87. <div id="stem-container" class="text-xl leading-relaxed text-gray-800 math-render bg-gray-50/50 rounded-xl p-6 border border-gray-100">
  88. {{ q.stem | safe }}
  89. </div>
  90. </section>
  91. <!-- 选项 -->
  92. {% if options %}
  93. <section>
  94. <h4 class="text-sm font-bold text-gray-400 mb-4 uppercase">选项</h4>
  95. <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
  96. {% for k, v in options %}
  97. <div class="p-4 rounded-xl border border-gray-100 bg-gray-50/50 flex items-start space-x-4">
  98. <span class="bg-white w-8 h-8 rounded-full flex items-center justify-center font-bold text-blue-600 shadow-sm border border-gray-100">{{ k }}</span>
  99. <div class="math-render pt-0.5">{{ v }}</div>
  100. </div>
  101. {% endfor %}
  102. </div>
  103. </section>
  104. {% endif %}
  105. <!-- 答案与解析 -->
  106. <section class="pt-8 border-t border-gray-100">
  107. <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
  108. <div>
  109. <h4 class="text-sm font-bold text-gray-400 mb-4 uppercase">正确答案</h4>
  110. <div class="text-lg font-bold text-green-600 bg-green-50 px-6 py-4 rounded-2xl inline-block math-render">
  111. {{ q.answer }}
  112. </div>
  113. </div>
  114. <div>
  115. <h4 class="text-sm font-bold text-gray-400 mb-4 uppercase">解析</h4>
  116. <div class="text-gray-600 leading-relaxed math-render">
  117. {{ q.solution | safe }}
  118. </div>
  119. </div>
  120. </div>
  121. </section>
  122. </div>
  123. </div>
  124. <!-- 审核控制台 (固定在底部) -->
  125. <div class="no-print fixed bottom-8 left-1/2 -translate-x-1/2 w-full max-w-2xl px-6">
  126. <div class="bg-white/90 backdrop-blur-2xl p-4 rounded-3xl shadow-2xl border border-white/20 flex items-center justify-between">
  127. <div class="flex items-center space-x-4 ml-2">
  128. <div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center">
  129. <svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><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"/></svg>
  130. </div>
  131. <div class="text-sm">
  132. <div class="font-bold">审核状态: {{ q.audit_reason or '待处理' }}</div>
  133. <div class="text-gray-400 text-xs">{{ q.kp_code }}</div>
  134. </div>
  135. </div>
  136. <div class="flex items-center space-x-3">
  137. <button onclick="postAudit('{{ q.question_code }}', '不合格')" class="btn-apple bg-red-50 text-red-600 hover:bg-red-100">❌ 判定不合格</button>
  138. <button onclick="postAudit('{{ q.question_code }}', '合格')" class="btn-apple bg-blue-600 text-white hover:bg-blue-700 shadow-xl shadow-blue-200">✅ 判定合格</button>
  139. </div>
  140. </div>
  141. </div>
  142. </div>
  143. <script>
  144. const QUESTION_CODE = "{{ q.question_code }}";
  145. // 键盘快捷键:Q 判定不合格,E 判定合格,R 继续录题
  146. document.addEventListener('keydown', function(e) {
  147. // 如果焦点在输入框、文本域等可输入元素上,不触发快捷键
  148. if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) {
  149. return;
  150. }
  151. // Q 键:判定不合格
  152. if (e.key === 'q' || e.key === 'Q') {
  153. e.preventDefault();
  154. postAudit(QUESTION_CODE, '不合格');
  155. }
  156. // E 键:判定合格
  157. if (e.key === 'e' || e.key === 'E') {
  158. e.preventDefault();
  159. postAudit(QUESTION_CODE, '合格');
  160. }
  161. // R 键:继续录题
  162. if ((e.key === 'r' || e.key === 'R') && document.getElementById('continue-add-btn')) {
  163. e.preventDefault();
  164. document.getElementById('continue-add-btn').click();
  165. }
  166. });
  167. // 优化题干显示:自动渲染数学公式
  168. function renderMathInStem() {
  169. const stemContainer = document.getElementById("stem-container");
  170. if (stemContainer && window.renderMathInElement) {
  171. try {
  172. window.renderMathInElement(stemContainer, {
  173. delimiters: [
  174. {left: "$$", right: "$$", display: true},
  175. {left: "$", right: "$", display: false},
  176. {left: "\\(", right: "\\)", display: false},
  177. {left: "\\[", right: "\\]", display: true}
  178. ],
  179. throwOnError: false
  180. });
  181. } catch (e) {
  182. // 渲染失败不阻塞功能
  183. console.warn("数学公式渲染失败:", e);
  184. }
  185. }
  186. }
  187. // 页面加载完成后自动渲染题干中的数学公式
  188. if (document.readyState === 'loading') {
  189. document.addEventListener('DOMContentLoaded', renderMathInStem);
  190. } else {
  191. // DOM已经加载完成,立即执行
  192. renderMathInStem();
  193. }
  194. function deleteQuestion(questionCode) {
  195. if (!confirm(`确定要删除题目 ${questionCode} 吗?\n\n此操作不可恢复!`)) {
  196. return;
  197. }
  198. fetch(`/api/delete_question/${encodeURIComponent(questionCode)}`, {
  199. method: "POST",
  200. headers: {"Content-Type": "application/json"}
  201. }).then(r => r.json()).then(data => {
  202. if (!data || !data.success) {
  203. throw new Error((data && data.error) ? data.error : "未知错误");
  204. }
  205. // 优先跳转到下一题,没有则上一题,都没有则返回上一页
  206. if (data.next_code) {
  207. window.location.href = `/detail/${data.next_code}`;
  208. } else if (data.prev_code) {
  209. window.location.href = `/detail/${data.prev_code}`;
  210. } else if (data.kp_code) {
  211. window.location.href = `/questions/${data.kp_code}`;
  212. } else if (data.node_id) {
  213. window.location.href = `/textbook/${data.node_id}`;
  214. } else {
  215. window.history.back();
  216. }
  217. }).catch(err => {
  218. if (window.customAlert) {
  219. window.customAlert("删除失败:" + (err && err.message ? err.message : String(err)));
  220. } else {
  221. alert("删除失败:" + (err && err.message ? err.message : String(err)));
  222. }
  223. });
  224. }
  225. </script>
  226. {% endblock %}