inject_exam.js 64 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851
  1. const fs = require('fs');
  2. const path = require('path');
  3. const filePath = path.join(__dirname, 'admin.html');
  4. let html = fs.readFileSync(filePath, 'utf8');
  5. // ─── EXAM TEMPLATE PAGE HTML ───────────────────────────────────────────────
  6. const examPageHtml = `
  7. <div id="page-exam-template" class="page" style="padding:0;">
  8. <!-- Page Header -->
  9. <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;">
  10. <div>
  11. <div class="page-title">考试模版</div>
  12. <div class="page-subtitle">管理各类考试的模版配置,包含基本设置、考前配置、防作弊及证书颁发等。</div>
  13. </div>
  14. <span class="tag tag-blue" style="font-size:12px;padding:4px 12px;">唯一模版</span>
  15. </div>
  16. <!-- Top-level tabs: 开学考试 / 分级考试 / 应知应会 -->
  17. <div class="view-tabs" id="exam-top-tabs">
  18. <div class="view-tab active" onclick="switchExamTab('kaixue')">开学考试</div>
  19. <div class="view-tab" onclick="switchExamTab('fenji')">分级考试</div>
  20. <div class="view-tab" onclick="switchExamTab('yingzhi')">应知应会</div>
  21. </div>
  22. <!-- ══════════════════════════════════════════════════════════
  23. TAB 1: 开学考试
  24. ══════════════════════════════════════════════════════════ -->
  25. <div id="exam-tab-kaixue" class="exam-tab-panel" style="display:flex;gap:0;background:var(--white);border:1px solid var(--gray-border);border-radius:var(--radius);box-shadow:var(--shadow);overflow:hidden;">
  26. <!-- Left sidebar nav -->
  27. <div style="width:160px;flex-shrink:0;border-right:1px solid var(--gray-border);background:var(--gray-light);padding:12px 0;">
  28. <div class="nav-item active" id="kaixue-nav-basic" onclick="switchExamSection('kaixue','basic')" style="font-size:13px;">基本设置</div>
  29. <div class="nav-item" id="kaixue-nav-preexam" onclick="switchExamSection('kaixue','preexam')" style="font-size:13px;">考前配置</div>
  30. <div class="nav-item" id="kaixue-nav-cheat" onclick="switchExamSection('kaixue','cheat')" style="font-size:13px;">防作弊</div>
  31. <div class="nav-item" id="kaixue-nav-cert" onclick="switchExamSection('kaixue','cert')" style="font-size:13px;">获得证书</div>
  32. </div>
  33. <!-- Right content area -->
  34. <div style="flex:1;padding:24px;overflow-y:auto;max-height:calc(100vh - 200px);">
  35. <!-- 权限层级 + 每学年自动执行 (common header) -->
  36. <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;flex-wrap:wrap;gap:12px;">
  37. <div style="display:flex;align-items:center;gap:16px;">
  38. <span class="form-label">权限层级:</span>
  39. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;">
  40. <input type="radio" name="kaixue-perm" value="school" checked> 校级(全校/指定范围)
  41. </label>
  42. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;">
  43. <input type="radio" name="kaixue-perm" value="college"> 院级(所属学院)
  44. </label>
  45. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;">
  46. <input type="radio" name="kaixue-perm" value="lab"> 实验室(自己负责)
  47. </label>
  48. </div>
  49. <div style="display:flex;align-items:center;gap:8px;">
  50. <label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label>
  51. <span class="form-hint">每学年自动按配置日期创建考试</span>
  52. </div>
  53. </div>
  54. <!-- 基本设置 section -->
  55. <div id="kaixue-sec-basic" class="exam-section">
  56. <div class="card-title">基本设置</div>
  57. <div style="display:flex;flex-direction:column;gap:16px;">
  58. <div style="display:flex;align-items:center;gap:16px;">
  59. <span class="form-label" style="width:80px;flex-shrink:0;">考试类型</span>
  60. <input class="form-input" style="width:200px;" value="开学考试" disabled>
  61. <span class="form-hint">系统内置,不可更改</span>
  62. </div>
  63. <div style="display:flex;align-items:center;gap:16px;">
  64. <span class="form-label" style="width:80px;flex-shrink:0;">风险等级</span>
  65. <select class="form-input form-select" style="width:200px;">
  66. <option value="">请选择风险等级</option>
  67. <option>重大风险</option>
  68. <option>高风险</option>
  69. <option>中风险</option>
  70. <option>低风险</option>
  71. </select>
  72. </div>
  73. <div style="display:flex;align-items:center;gap:16px;">
  74. <span class="form-label" style="width:80px;flex-shrink:0;">考试名称</span>
  75. <input class="form-input" style="width:300px;" placeholder="请输入考试名称">
  76. </div>
  77. <div style="display:flex;align-items:center;gap:16px;">
  78. <span class="form-label" style="width:80px;flex-shrink:0;">答卷次数</span>
  79. <div style="display:flex;align-items:center;gap:8px;">
  80. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('kaixue-answer-cnt',-1)">-</button>
  81. <input id="kaixue-answer-cnt" type="number" class="form-input" style="width:60px;text-align:center;" value="2" min="1">
  82. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('kaixue-answer-cnt',1)">+</button>
  83. </div>
  84. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;margin-left:8px;">
  85. <input type="radio" name="kaixue-answer-mode" value="limit" checked> 限制次数
  86. </label>
  87. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;">
  88. <input type="radio" name="kaixue-answer-mode" value="unlimited"> 无限制
  89. </label>
  90. </div>
  91. <div style="display:flex;align-items:center;gap:16px;">
  92. <span class="form-label" style="width:80px;flex-shrink:0;">显示共享</span>
  93. <label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label>
  94. <span class="form-hint">开课共享,全程有学号信息不可匿名</span>
  95. </div>
  96. <div style="display:flex;align-items:center;gap:16px;">
  97. <span class="form-label" style="width:80px;flex-shrink:0;">启用</span>
  98. <label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label>
  99. </div>
  100. <div style="display:flex;align-items:flex-start;gap:16px;">
  101. <span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">考卷</span>
  102. <div style="display:flex;flex-direction:column;gap:8px;">
  103. <div style="display:flex;align-items:center;gap:8px;">
  104. <button class="btn btn-outline btn-sm">选择试卷</button>
  105. <span class="tag tag-blue" style="display:inline-flex;align-items:center;gap:4px;">实验室安全基础知识<span onclick="this.parentElement.remove()" style="cursor:pointer;margin-left:2px;">×</span></span>
  106. </div>
  107. <span class="form-hint">建议按知识点选择覆盖本校安全要求的试卷</span>
  108. </div>
  109. </div>
  110. <div style="display:flex;align-items:center;gap:16px;">
  111. <span class="form-label" style="width:80px;flex-shrink:0;">考试时间</span>
  112. <input type="date" class="form-input" style="width:150px;">
  113. <span style="color:var(--text-hint);font-size:13px;">至</span>
  114. <input type="date" class="form-input" style="width:150px;">
  115. </div>
  116. <div style="display:flex;align-items:center;gap:16px;">
  117. <span class="form-label" style="width:80px;flex-shrink:0;">考试时长</span>
  118. <div style="display:flex;align-items:center;gap:8px;">
  119. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('kaixue-duration',-5)">-</button>
  120. <input id="kaixue-duration" type="number" class="form-input" style="width:70px;text-align:center;" value="60" min="5" step="5">
  121. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('kaixue-duration',5)">+</button>
  122. <span style="font-size:13px;color:var(--text-secondary);">分钟</span>
  123. </div>
  124. <span class="form-hint">限制答卷最大时长</span>
  125. </div>
  126. <!-- 考试范围 -->
  127. <div style="display:flex;align-items:flex-start;gap:16px;">
  128. <span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">考试范围</span>
  129. <div style="display:flex;flex-direction:column;gap:10px;flex:1;">
  130. <div style="display:flex;gap:16px;">
  131. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;">
  132. <input type="radio" name="kaixue-scope" value="all" checked onclick="setKaixueScope('all')"> 全校
  133. </label>
  134. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;">
  135. <input type="radio" name="kaixue-scope" value="specify" onclick="setKaixueScope('specify')"> 考试范围(指定)
  136. </label>
  137. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;">
  138. <input type="radio" name="kaixue-scope" value="student" onclick="setKaixueScope('student')"> 指定学生
  139. </label>
  140. </div>
  141. <div id="kaixue-scope-table" style="display:none;">
  142. <div class="table-wrap" style="margin-bottom:8px;">
  143. <table>
  144. <thead><tr><th>学院</th><th>专业</th><th>角色</th><th>年级</th><th>操作</th></tr></thead>
  145. <tbody id="kaixue-scope-tbody">
  146. <tr><td colspan="5" style="text-align:center;color:var(--text-hint);">暂无数据,请添加</td></tr>
  147. </tbody>
  148. </table>
  149. </div>
  150. <button class="btn btn-outline btn-sm">+ 添加范围</button>
  151. </div>
  152. </div>
  153. </div>
  154. <!-- 用户类别 -->
  155. <div style="display:flex;align-items:flex-start;gap:16px;">
  156. <span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">用户类别</span>
  157. <div style="display:flex;flex-direction:column;gap:8px;">
  158. <label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;">
  159. <input type="checkbox" id="kaixue-user-undergrad" checked onchange="updateKaixueHours()"> 新入学本科生(默认勾选)
  160. </label>
  161. <label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;">
  162. <input type="checkbox" id="kaixue-user-postgrad" checked onchange="updateKaixueHours()"> 新入学研究生
  163. </label>
  164. <label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;">
  165. <input type="checkbox" id="kaixue-user-staff" checked onchange="updateKaixueHours()"> 新入职教职工
  166. </label>
  167. </div>
  168. </div>
  169. <!-- 学时配置 -->
  170. <div style="display:flex;align-items:flex-start;gap:16px;">
  171. <span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">学时配置</span>
  172. <div style="flex:1;">
  173. <div class="table-wrap">
  174. <table>
  175. <thead><tr><th>用户类别</th><th>所需学时</th><th>备注</th></tr></thead>
  176. <tbody id="kaixue-hours-tbody">
  177. <tr><td>新入学本科生</td><td><input type="number" class="form-input" style="width:80px;" value="4" min="0"></td><td><input class="form-input" placeholder="备注说明"></td></tr>
  178. <tr><td>新入学研究生</td><td><input type="number" class="form-input" style="width:80px;" value="4" min="0"></td><td><input class="form-input" placeholder="备注说明"></td></tr>
  179. <tr><td>新入职教职工</td><td><input type="number" class="form-input" style="width:80px;" value="2" min="0"></td><td><input class="form-input" placeholder="备注说明"></td></tr>
  180. </tbody>
  181. </table>
  182. </div>
  183. </div>
  184. </div>
  185. </div><!-- /flex col -->
  186. <div style="margin-top:24px;display:flex;gap:12px;">
  187. <button class="btn btn-primary">保存设置</button>
  188. <button class="btn btn-outline-gray">重置</button>
  189. </div>
  190. </div><!-- /kaixue-sec-basic -->
  191. <!-- 考前配置 section -->
  192. <div id="kaixue-sec-preexam" class="exam-section" style="display:none;">
  193. <div class="card-title">考前配置</div>
  194. <div style="display:flex;flex-direction:column;gap:20px;">
  195. <!-- 学时要求 -->
  196. <div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
  197. <span class="form-label" style="width:80px;flex-shrink:0;">学时要求</span>
  198. <label class="toggle"><input type="checkbox" id="kaixue-credit-toggle" onchange="document.getElementById('kaixue-credit-detail').style.display=this.checked?'flex':'none'"><span class="toggle-slider"></span></label>
  199. <div id="kaixue-credit-detail" style="display:none;align-items:center;gap:8px;">
  200. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('kaixue-credit-val',-1)">-</button>
  201. <input id="kaixue-credit-val" type="number" class="form-input" style="width:60px;text-align:center;" value="2" min="0">
  202. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('kaixue-credit-val',1)">+</button>
  203. <span style="font-size:13px;color:var(--text-secondary);">学时</span>
  204. <span class="form-hint">参加考试前需完成的最低学时数</span>
  205. </div>
  206. </div>
  207. <!-- 考前学习 -->
  208. <div style="display:flex;align-items:flex-start;gap:12px;">
  209. <span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">考前学习</span>
  210. <div style="display:flex;flex-direction:column;gap:8px;">
  211. <label class="toggle"><input type="checkbox" id="kaixue-prestudy-toggle" onchange="document.getElementById('kaixue-prestudy-detail').style.display=this.checked?'flex':'none'"><span class="toggle-slider"></span></label>
  212. <div id="kaixue-prestudy-detail" style="display:none;align-items:center;gap:8px;">
  213. <button class="btn btn-outline btn-sm">选择课程</button>
  214. <span class="tag tag-blue" style="display:inline-flex;align-items:center;gap:4px;">实验室安全基础课程<span onclick="this.parentElement.remove()" style="cursor:pointer;margin-left:2px;">×</span></span>
  215. </div>
  216. </div>
  217. </div>
  218. <!-- 模拟考试 -->
  219. <div style="display:flex;align-items:flex-start;gap:12px;">
  220. <span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">模拟考试</span>
  221. <div style="display:flex;flex-direction:column;gap:8px;">
  222. <label class="toggle"><input type="checkbox" id="kaixue-mock-toggle" onchange="document.getElementById('kaixue-mock-detail').style.display=this.checked?'block':'none'"><span class="toggle-slider"></span></label>
  223. <div id="kaixue-mock-detail" style="display:none;">
  224. <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
  225. <button class="btn btn-outline btn-sm">选择试卷</button>
  226. <span class="tag tag-gray" id="kaixue-mock-paper">未选择</span>
  227. </div>
  228. <div style="display:flex;gap:16px;font-size:12px;color:var(--text-hint);">
  229. <span>总题数:<strong style="color:var(--text-primary);">50</strong></span>
  230. <span>总分:<strong style="color:var(--text-primary);">100</strong></span>
  231. <span>及格分:<strong style="color:var(--green);">60</strong></span>
  232. </div>
  233. </div>
  234. </div>
  235. </div>
  236. <!-- 考试承诺书 -->
  237. <div style="display:flex;align-items:flex-start;gap:12px;">
  238. <span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">考试承诺书</span>
  239. <div style="display:flex;flex-direction:column;gap:8px;flex:1;">
  240. <label class="toggle"><input type="checkbox" id="kaixue-pledge-toggle" onchange="document.getElementById('kaixue-pledge-detail').style.display=this.checked?'block':'none'"><span class="toggle-slider"></span></label>
  241. <div id="kaixue-pledge-detail" style="display:none;">
  242. <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
  243. <span class="form-label">承诺书编辑</span>
  244. <label class="toggle"><input type="checkbox" onchange="document.getElementById('kaixue-pledge-editor').style.display=this.checked?'block':'none'"><span class="toggle-slider"></span></label>
  245. </div>
  246. <div id="kaixue-pledge-editor" style="display:none;">
  247. <div class="rich-editor">
  248. <div class="rich-toolbar">
  249. <button class="rich-tool-btn">B</button><button class="rich-tool-btn">I</button>
  250. <button class="rich-tool-btn">U</button><button class="rich-tool-btn">≡</button>
  251. <button class="rich-tool-btn">链接</button><button class="rich-tool-btn">图片</button>
  252. </div>
  253. <div class="rich-body" contenteditable="true">
  254. <p><strong>实验室安全承诺书</strong></p>
  255. <p>本人郑重承诺:在参加实验室安全考试及日常实验操作过程中,严格遵守实验室各项安全规章制度,认真执行安全操作规程,不擅自使用不熟悉的仪器设备,不私自改装或违规操作实验装置。发现安全隐患及时上报,如实反映实验情况,对违规行为承担相应法律及校规责任。</p>
  256. </div>
  257. </div>
  258. </div>
  259. </div>
  260. </div>
  261. </div>
  262. </div>
  263. <div style="margin-top:24px;display:flex;gap:12px;">
  264. <button class="btn btn-primary">保存设置</button>
  265. <button class="btn btn-outline-gray">重置</button>
  266. </div>
  267. </div><!-- /kaixue-sec-preexam -->
  268. <!-- 防作弊 section -->
  269. <div id="kaixue-sec-cheat" class="exam-section" style="display:none;">
  270. <div class="card-title">防作弊</div>
  271. <div style="display:flex;flex-direction:column;gap:16px;">
  272. <div style="display:flex;align-items:center;gap:12px;">
  273. <span class="form-label" style="width:100px;flex-shrink:0;">切屏限制</span>
  274. <label class="toggle"><input type="checkbox" id="kaixue-switchscreen" onchange="document.getElementById('kaixue-switchscreen-cnt-wrap').style.display=this.checked?'flex':'none'"><span class="toggle-slider"></span></label>
  275. <div id="kaixue-switchscreen-cnt-wrap" style="display:none;align-items:center;gap:6px;">
  276. <span style="font-size:13px;color:var(--text-secondary);">切屏</span>
  277. <input type="number" class="form-input" style="width:60px;text-align:center;" value="3" min="1">
  278. <span style="font-size:13px;color:var(--text-secondary);">次后提交</span>
  279. </div>
  280. </div>
  281. <div style="display:flex;align-items:center;gap:12px;">
  282. <span class="form-label" style="width:100px;flex-shrink:0;">随机题目</span>
  283. <label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label>
  284. </div>
  285. <div style="display:flex;align-items:center;gap:12px;">
  286. <span class="form-label" style="width:100px;flex-shrink:0;">随机选项</span>
  287. <label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label>
  288. </div>
  289. <div style="display:flex;align-items:center;gap:12px;">
  290. <span class="form-label" style="width:100px;flex-shrink:0;">人脸识别</span>
  291. <label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label>
  292. </div>
  293. </div>
  294. <div style="margin-top:24px;display:flex;gap:12px;">
  295. <button class="btn btn-primary">保存设置</button>
  296. <button class="btn btn-outline-gray">重置</button>
  297. </div>
  298. </div><!-- /kaixue-sec-cheat -->
  299. <!-- 获得证书 section -->
  300. <div id="kaixue-sec-cert" class="exam-section" style="display:none;">
  301. <div class="card-title">获得证书</div>
  302. <div style="display:flex;flex-direction:column;gap:16px;">
  303. <div style="display:flex;align-items:center;gap:12px;">
  304. <span class="form-label" style="width:80px;flex-shrink:0;">颁发证书</span>
  305. <label class="toggle"><input type="checkbox" id="kaixue-cert-toggle" onchange="document.getElementById('kaixue-cert-tmpl').style.display=this.checked?'flex':'none'"><span class="toggle-slider"></span></label>
  306. </div>
  307. <div id="kaixue-cert-tmpl" style="display:none;align-items:center;gap:12px;">
  308. <span class="form-label" style="width:80px;flex-shrink:0;">证书模板</span>
  309. <select class="form-input form-select" style="width:240px;">
  310. <option value="">请选择证书模板</option>
  311. <option>实验室安全教育证书(标准版)</option>
  312. <option>实验室安全教育证书(精英版)</option>
  313. </select>
  314. </div>
  315. </div>
  316. <div style="margin-top:24px;display:flex;gap:12px;">
  317. <button class="btn btn-primary">保存设置</button>
  318. <button class="btn btn-outline-gray">重置</button>
  319. </div>
  320. </div><!-- /kaixue-sec-cert -->
  321. </div><!-- /right content -->
  322. </div><!-- /exam-tab-kaixue -->
  323. <!-- ══════════════════════════════════════════════════════════
  324. TAB 2: 分级考试
  325. ══════════════════════════════════════════════════════════ -->
  326. <div id="exam-tab-fenji" class="exam-tab-panel" style="display:none;">
  327. <!-- Grade sub-tabs -->
  328. <div class="view-tabs" id="fenji-grade-tabs" style="margin-bottom:0;padding:0 20px;background:var(--white);border:1px solid var(--gray-border);border-bottom:none;border-radius:var(--radius) var(--radius) 0 0;">
  329. <div class="view-tab active" onclick="switchGradeTab('g1')">Ⅰ级(重大风险)</div>
  330. <div class="view-tab" onclick="switchGradeTab('g2')">Ⅱ级(高风险)</div>
  331. <div class="view-tab" onclick="switchGradeTab('g3')">Ⅲ级(中风险)</div>
  332. <div class="view-tab" onclick="switchGradeTab('g4')">Ⅳ级(低风险)</div>
  333. </div>
  334. <!-- Each grade panel shares the same two-column layout. We generate g1-g4. -->
  335. <div id="fenji-grade-g1" class="fenji-grade-panel" style="display:flex;gap:0;background:var(--white);border:1px solid var(--gray-border);border-radius:0 0 var(--radius) var(--radius);box-shadow:var(--shadow);overflow:hidden;">
  336. <div style="width:160px;flex-shrink:0;border-right:1px solid var(--gray-border);background:var(--gray-light);padding:12px 0;">
  337. <div class="nav-item active" onclick="switchExamSection('fenji-g1','basic')" style="font-size:13px;">基本设置</div>
  338. <div class="nav-item" onclick="switchExamSection('fenji-g1','preexam')" style="font-size:13px;">考前配置</div>
  339. <div class="nav-item" onclick="switchExamSection('fenji-g1','cheat')" style="font-size:13px;">防作弊</div>
  340. <div class="nav-item" onclick="switchExamSection('fenji-g1','cert')" style="font-size:13px;">获得证书</div>
  341. </div>
  342. <div style="flex:1;padding:24px;overflow-y:auto;max-height:calc(100vh - 240px);">
  343. <!-- Common header -->
  344. <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;flex-wrap:wrap;gap:12px;">
  345. <div style="display:flex;align-items:center;gap:16px;">
  346. <span class="form-label">权限层级:</span>
  347. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;"><input type="radio" name="fenji-g1-perm" value="school" checked> 校级(全校/指定范围)</label>
  348. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;"><input type="radio" name="fenji-g1-perm" value="college"> 院级(所属学院)</label>
  349. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;"><input type="radio" name="fenji-g1-perm" value="lab"> 实验室(自己负责)</label>
  350. </div>
  351. <div style="display:flex;align-items:center;gap:8px;">
  352. <label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label>
  353. <span class="form-hint">每学年自动按配置日期创建考试</span>
  354. </div>
  355. </div>
  356. <!-- 基本设置 -->
  357. <div id="fenji-g1-sec-basic" class="exam-section">
  358. <div class="card-title">基本设置</div>
  359. <div style="display:flex;flex-direction:column;gap:16px;">
  360. <div style="display:flex;align-items:center;gap:16px;"><span class="form-label" style="width:80px;flex-shrink:0;">考试类型</span><input class="form-input" style="width:200px;" value="Ⅰ级(重大风险)" disabled></div>
  361. <div style="display:flex;align-items:center;gap:16px;"><span class="form-label" style="width:80px;flex-shrink:0;">考试名称</span><input class="form-input" style="width:300px;" placeholder="请输入考试名称"></div>
  362. <div style="display:flex;align-items:center;gap:16px;">
  363. <span class="form-label" style="width:80px;flex-shrink:0;">答卷次数</span>
  364. <div style="display:flex;align-items:center;gap:8px;">
  365. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('fenji-g1-ans',-1)">-</button>
  366. <input id="fenji-g1-ans" type="number" class="form-input" style="width:60px;text-align:center;" value="2" min="1">
  367. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('fenji-g1-ans',1)">+</button>
  368. </div>
  369. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;margin-left:8px;"><input type="radio" name="fenji-g1-ans-mode" value="limit" checked> 限制次数</label>
  370. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;"><input type="radio" name="fenji-g1-ans-mode" value="unlimited"> 无限制</label>
  371. </div>
  372. <div style="display:flex;align-items:center;gap:16px;"><span class="form-label" style="width:80px;flex-shrink:0;">启用</span><label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
  373. <div style="display:flex;align-items:flex-start;gap:16px;">
  374. <span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">考卷</span>
  375. <div style="display:flex;flex-direction:column;gap:8px;">
  376. <div style="display:flex;align-items:center;gap:8px;"><button class="btn btn-outline btn-sm">选择试卷</button></div>
  377. <span class="form-hint">建议按知识点选择覆盖本校安全要求的试卷</span>
  378. </div>
  379. </div>
  380. <div style="display:flex;align-items:center;gap:16px;">
  381. <span class="form-label" style="width:80px;flex-shrink:0;">考试时间</span>
  382. <input type="date" class="form-input" style="width:150px;"><span style="color:var(--text-hint);font-size:13px;">至</span><input type="date" class="form-input" style="width:150px;">
  383. </div>
  384. <div style="display:flex;align-items:center;gap:16px;">
  385. <span class="form-label" style="width:80px;flex-shrink:0;">考试时长</span>
  386. <div style="display:flex;align-items:center;gap:8px;">
  387. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('fenji-g1-dur',-5)">-</button>
  388. <input id="fenji-g1-dur" type="number" class="form-input" style="width:70px;text-align:center;" value="60" min="5" step="5">
  389. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('fenji-g1-dur',5)">+</button>
  390. <span style="font-size:13px;color:var(--text-secondary);">分钟</span>
  391. </div>
  392. <span class="form-hint">限制答卷最大时长</span>
  393. </div>
  394. <!-- 用户类别 (分级考试专用) -->
  395. <div style="display:flex;align-items:flex-start;gap:16px;">
  396. <span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">用户类别</span>
  397. <div style="display:flex;flex-direction:column;gap:8px;">
  398. <label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;"><input type="checkbox" checked> 通过入学考试后绑定实验室的本科生</label>
  399. <label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;"><input type="checkbox" checked> 绑定实验室的研究生</label>
  400. </div>
  401. </div>
  402. <!-- 学时配置 (分级考试) -->
  403. <div style="display:flex;align-items:flex-start;gap:16px;">
  404. <span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">学时配置</span>
  405. <div style="padding:10px 14px;background:var(--gray-light);border-radius:6px;font-size:13px;color:var(--text-secondary);">按公共配置的分级规则执行</div>
  406. </div>
  407. </div>
  408. <div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button><button class="btn btn-outline-gray">重置</button></div>
  409. </div>
  410. <!-- 考前配置 -->
  411. <div id="fenji-g1-sec-preexam" class="exam-section" style="display:none;">
  412. <div class="card-title">考前配置</div>
  413. <div style="display:flex;flex-direction:column;gap:20px;">
  414. <div style="display:flex;align-items:center;gap:12px;">
  415. <span class="form-label" style="width:80px;flex-shrink:0;">学时要求</span>
  416. <label class="toggle"><input type="checkbox" onchange="this.closest('.exam-section').querySelector('.credit-detail').style.display=this.checked?'flex':'none'"><span class="toggle-slider"></span></label>
  417. <div class="credit-detail" style="display:none;align-items:center;gap:8px;">
  418. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('fenji-g1-credit',-1)">-</button>
  419. <input id="fenji-g1-credit" type="number" class="form-input" style="width:60px;text-align:center;" value="2" min="0">
  420. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('fenji-g1-credit',1)">+</button>
  421. <span style="font-size:13px;color:var(--text-secondary);">学时</span>
  422. </div>
  423. </div>
  424. <div style="display:flex;align-items:center;gap:12px;">
  425. <span class="form-label" style="width:80px;flex-shrink:0;">考前学习</span>
  426. <label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label>
  427. </div>
  428. <div style="display:flex;align-items:center;gap:12px;">
  429. <span class="form-label" style="width:80px;flex-shrink:0;">模拟考试</span>
  430. <label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label>
  431. </div>
  432. <div style="display:flex;align-items:center;gap:12px;">
  433. <span class="form-label" style="width:80px;flex-shrink:0;">考试承诺书</span>
  434. <label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label>
  435. </div>
  436. </div>
  437. <div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button><button class="btn btn-outline-gray">重置</button></div>
  438. </div>
  439. <!-- 防作弊 -->
  440. <div id="fenji-g1-sec-cheat" class="exam-section" style="display:none;">
  441. <div class="card-title">防作弊</div>
  442. <div style="display:flex;flex-direction:column;gap:16px;">
  443. <div style="display:flex;align-items:center;gap:12px;"><span class="form-label" style="width:100px;flex-shrink:0;">切屏限制</span><label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label></div>
  444. <div style="display:flex;align-items:center;gap:12px;"><span class="form-label" style="width:100px;flex-shrink:0;">随机题目</span><label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label></div>
  445. <div style="display:flex;align-items:center;gap:12px;"><span class="form-label" style="width:100px;flex-shrink:0;">随机选项</span><label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label></div>
  446. <div style="display:flex;align-items:center;gap:12px;"><span class="form-label" style="width:100px;flex-shrink:0;">人脸识别</span><label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label></div>
  447. </div>
  448. <div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button><button class="btn btn-outline-gray">重置</button></div>
  449. </div>
  450. <!-- 获得证书 -->
  451. <div id="fenji-g1-sec-cert" class="exam-section" style="display:none;">
  452. <div class="card-title">获得证书</div>
  453. <div style="display:flex;flex-direction:column;gap:16px;">
  454. <div style="display:flex;align-items:center;gap:12px;">
  455. <span class="form-label" style="width:80px;flex-shrink:0;">颁发证书</span>
  456. <label class="toggle"><input type="checkbox" onchange="this.closest('.exam-section').querySelector('.cert-tmpl-row').style.display=this.checked?'flex':'none'"><span class="toggle-slider"></span></label>
  457. </div>
  458. <div class="cert-tmpl-row" style="display:none;align-items:center;gap:12px;">
  459. <span class="form-label" style="width:80px;flex-shrink:0;">证书模板</span>
  460. <select class="form-input form-select" style="width:240px;"><option value="">请选择证书模板</option><option>实验室安全教育证书(标准版)</option></select>
  461. </div>
  462. </div>
  463. <div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button><button class="btn btn-outline-gray">重置</button></div>
  464. </div>
  465. </div>
  466. </div><!-- /fenji-grade-g1 -->
  467. <!-- Grades g2, g3, g4 share identical structure, only IDs and type labels differ -->
  468. <div id="fenji-grade-g2" class="fenji-grade-panel" style="display:none;gap:0;background:var(--white);border:1px solid var(--gray-border);border-radius:0 0 var(--radius) var(--radius);box-shadow:var(--shadow);overflow:hidden;flex-direction:row;">
  469. <div style="width:160px;flex-shrink:0;border-right:1px solid var(--gray-border);background:var(--gray-light);padding:12px 0;">
  470. <div class="nav-item active" onclick="switchExamSection('fenji-g2','basic')" style="font-size:13px;">基本设置</div>
  471. <div class="nav-item" onclick="switchExamSection('fenji-g2','preexam')" style="font-size:13px;">考前配置</div>
  472. <div class="nav-item" onclick="switchExamSection('fenji-g2','cheat')" style="font-size:13px;">防作弊</div>
  473. <div class="nav-item" onclick="switchExamSection('fenji-g2','cert')" style="font-size:13px;">获得证书</div>
  474. </div>
  475. <div style="flex:1;padding:24px;">
  476. <div id="fenji-g2-sec-basic" class="exam-section">
  477. <div class="card-title">基本设置</div>
  478. <div style="display:flex;flex-direction:column;gap:16px;">
  479. <div style="display:flex;align-items:center;gap:16px;"><span class="form-label" style="width:80px;flex-shrink:0;">考试类型</span><input class="form-input" style="width:200px;" value="Ⅱ级(高风险)" disabled></div>
  480. <div style="display:flex;align-items:center;gap:16px;"><span class="form-label" style="width:80px;flex-shrink:0;">考试名称</span><input class="form-input" style="width:300px;" placeholder="请输入考试名称"></div>
  481. <div style="display:flex;align-items:flex-start;gap:16px;"><span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">用户类别</span><div style="display:flex;flex-direction:column;gap:8px;"><label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;"><input type="checkbox" checked> 通过入学考试后绑定实验室的本科生</label><label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;"><input type="checkbox" checked> 绑定实验室的研究生</label></div></div>
  482. <div style="display:flex;align-items:flex-start;gap:16px;"><span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">学时配置</span><div style="padding:10px 14px;background:var(--gray-light);border-radius:6px;font-size:13px;color:var(--text-secondary);">按公共配置的分级规则执行</div></div>
  483. </div>
  484. <div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button><button class="btn btn-outline-gray">重置</button></div>
  485. </div>
  486. <div id="fenji-g2-sec-preexam" class="exam-section" style="display:none;"><div class="card-title">考前配置</div><div style="color:var(--text-hint);font-size:13px;">(配置同Ⅰ级)</div><div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button></div></div>
  487. <div id="fenji-g2-sec-cheat" class="exam-section" style="display:none;"><div class="card-title">防作弊</div><div style="color:var(--text-hint);font-size:13px;">(配置同Ⅰ级)</div><div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button></div></div>
  488. <div id="fenji-g2-sec-cert" class="exam-section" style="display:none;"><div class="card-title">获得证书</div><div style="color:var(--text-hint);font-size:13px;">(配置同Ⅰ级)</div><div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button></div></div>
  489. </div>
  490. </div><!-- /fenji-grade-g2 -->
  491. <div id="fenji-grade-g3" class="fenji-grade-panel" style="display:none;gap:0;background:var(--white);border:1px solid var(--gray-border);border-radius:0 0 var(--radius) var(--radius);box-shadow:var(--shadow);overflow:hidden;flex-direction:row;">
  492. <div style="width:160px;flex-shrink:0;border-right:1px solid var(--gray-border);background:var(--gray-light);padding:12px 0;">
  493. <div class="nav-item active" onclick="switchExamSection('fenji-g3','basic')" style="font-size:13px;">基本设置</div>
  494. <div class="nav-item" onclick="switchExamSection('fenji-g3','preexam')" style="font-size:13px;">考前配置</div>
  495. <div class="nav-item" onclick="switchExamSection('fenji-g3','cheat')" style="font-size:13px;">防作弊</div>
  496. <div class="nav-item" onclick="switchExamSection('fenji-g3','cert')" style="font-size:13px;">获得证书</div>
  497. </div>
  498. <div style="flex:1;padding:24px;">
  499. <div id="fenji-g3-sec-basic" class="exam-section">
  500. <div class="card-title">基本设置</div>
  501. <div style="display:flex;flex-direction:column;gap:16px;">
  502. <div style="display:flex;align-items:center;gap:16px;"><span class="form-label" style="width:80px;flex-shrink:0;">考试类型</span><input class="form-input" style="width:200px;" value="Ⅲ级(中风险)" disabled></div>
  503. <div style="display:flex;align-items:center;gap:16px;"><span class="form-label" style="width:80px;flex-shrink:0;">考试名称</span><input class="form-input" style="width:300px;" placeholder="请输入考试名称"></div>
  504. <div style="display:flex;align-items:flex-start;gap:16px;"><span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">用户类别</span><div style="display:flex;flex-direction:column;gap:8px;"><label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;"><input type="checkbox" checked> 通过入学考试后绑定实验室的本科生</label><label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;"><input type="checkbox" checked> 绑定实验室的研究生</label></div></div>
  505. <div style="display:flex;align-items:flex-start;gap:16px;"><span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">学时配置</span><div style="padding:10px 14px;background:var(--gray-light);border-radius:6px;font-size:13px;color:var(--text-secondary);">按公共配置的分级规则执行</div></div>
  506. </div>
  507. <div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button><button class="btn btn-outline-gray">重置</button></div>
  508. </div>
  509. <div id="fenji-g3-sec-preexam" class="exam-section" style="display:none;"><div class="card-title">考前配置</div><div style="color:var(--text-hint);font-size:13px;">(配置同Ⅰ级)</div><div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button></div></div>
  510. <div id="fenji-g3-sec-cheat" class="exam-section" style="display:none;"><div class="card-title">防作弊</div><div style="color:var(--text-hint);font-size:13px;">(配置同Ⅰ级)</div><div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button></div></div>
  511. <div id="fenji-g3-sec-cert" class="exam-section" style="display:none;"><div class="card-title">获得证书</div><div style="color:var(--text-hint);font-size:13px;">(配置同Ⅰ级)</div><div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button></div></div>
  512. </div>
  513. </div><!-- /fenji-grade-g3 -->
  514. <div id="fenji-grade-g4" class="fenji-grade-panel" style="display:none;gap:0;background:var(--white);border:1px solid var(--gray-border);border-radius:0 0 var(--radius) var(--radius);box-shadow:var(--shadow);overflow:hidden;flex-direction:row;">
  515. <div style="width:160px;flex-shrink:0;border-right:1px solid var(--gray-border);background:var(--gray-light);padding:12px 0;">
  516. <div class="nav-item active" onclick="switchExamSection('fenji-g4','basic')" style="font-size:13px;">基本设置</div>
  517. <div class="nav-item" onclick="switchExamSection('fenji-g4','preexam')" style="font-size:13px;">考前配置</div>
  518. <div class="nav-item" onclick="switchExamSection('fenji-g4','cheat')" style="font-size:13px;">防作弊</div>
  519. <div class="nav-item" onclick="switchExamSection('fenji-g4','cert')" style="font-size:13px;">获得证书</div>
  520. </div>
  521. <div style="flex:1;padding:24px;">
  522. <div id="fenji-g4-sec-basic" class="exam-section">
  523. <div class="card-title">基本设置</div>
  524. <div style="display:flex;flex-direction:column;gap:16px;">
  525. <div style="display:flex;align-items:center;gap:16px;"><span class="form-label" style="width:80px;flex-shrink:0;">考试类型</span><input class="form-input" style="width:200px;" value="Ⅳ级(低风险)" disabled></div>
  526. <div style="display:flex;align-items:center;gap:16px;"><span class="form-label" style="width:80px;flex-shrink:0;">考试名称</span><input class="form-input" style="width:300px;" placeholder="请输入考试名称"></div>
  527. <div style="display:flex;align-items:flex-start;gap:16px;"><span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">用户类别</span><div style="display:flex;flex-direction:column;gap:8px;"><label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;"><input type="checkbox" checked> 通过入学考试后绑定实验室的本科生</label><label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;"><input type="checkbox" checked> 绑定实验室的研究生</label></div></div>
  528. <div style="display:flex;align-items:flex-start;gap:16px;"><span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">学时配置</span><div style="padding:10px 14px;background:var(--gray-light);border-radius:6px;font-size:13px;color:var(--text-secondary);">按公共配置的分级规则执行</div></div>
  529. </div>
  530. <div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button><button class="btn btn-outline-gray">重置</button></div>
  531. </div>
  532. <div id="fenji-g4-sec-preexam" class="exam-section" style="display:none;"><div class="card-title">考前配置</div><div style="color:var(--text-hint);font-size:13px;">(配置同Ⅰ级)</div><div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button></div></div>
  533. <div id="fenji-g4-sec-cheat" class="exam-section" style="display:none;"><div class="card-title">防作弊</div><div style="color:var(--text-hint);font-size:13px;">(配置同Ⅰ级)</div><div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button></div></div>
  534. <div id="fenji-g4-sec-cert" class="exam-section" style="display:none;"><div class="card-title">获得证书</div><div style="color:var(--text-hint);font-size:13px;">(配置同Ⅰ级)</div><div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button></div></div>
  535. </div>
  536. </div><!-- /fenji-grade-g4 -->
  537. </div><!-- /exam-tab-fenji -->
  538. <!-- ══════════════════════════════════════════════════════════
  539. TAB 3: 应知应会
  540. ══════════════════════════════════════════════════════════ -->
  541. <div id="exam-tab-yingzhi" class="exam-tab-panel" style="display:none;gap:0;background:var(--white);border:1px solid var(--gray-border);border-radius:var(--radius);box-shadow:var(--shadow);overflow:hidden;flex-direction:row;">
  542. <div style="width:160px;flex-shrink:0;border-right:1px solid var(--gray-border);background:var(--gray-light);padding:12px 0;">
  543. <div class="nav-item active" id="yingzhi-nav-basic" onclick="switchExamSection('yingzhi','basic')" style="font-size:13px;">基本设置</div>
  544. <div class="nav-item" id="yingzhi-nav-preexam" onclick="switchExamSection('yingzhi','preexam')" style="font-size:13px;">考前配置</div>
  545. <div class="nav-item" id="yingzhi-nav-cheat" onclick="switchExamSection('yingzhi','cheat')" style="font-size:13px;">防作弊</div>
  546. <div class="nav-item" id="yingzhi-nav-cert" onclick="switchExamSection('yingzhi','cert')" style="font-size:13px;">获得证书</div>
  547. </div>
  548. <div style="flex:1;padding:24px;overflow-y:auto;max-height:calc(100vh - 200px);">
  549. <!-- Common header -->
  550. <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;flex-wrap:wrap;gap:12px;">
  551. <div style="display:flex;align-items:center;gap:16px;">
  552. <span class="form-label">权限层级:</span>
  553. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;"><input type="radio" name="yingzhi-perm" value="school" checked> 校级(全校/指定范围)</label>
  554. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;"><input type="radio" name="yingzhi-perm" value="college"> 院级(所属学院)</label>
  555. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;"><input type="radio" name="yingzhi-perm" value="lab"> 实验室(自己负责)</label>
  556. </div>
  557. <div style="display:flex;align-items:center;gap:8px;">
  558. <label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label>
  559. <span class="form-hint">每学年自动按配置日期创建考试</span>
  560. </div>
  561. </div>
  562. <!-- 基本设置 -->
  563. <div id="yingzhi-sec-basic" class="exam-section">
  564. <div class="card-title">基本设置</div>
  565. <div style="display:flex;flex-direction:column;gap:16px;">
  566. <div style="display:flex;align-items:center;gap:16px;">
  567. <span class="form-label" style="width:80px;flex-shrink:0;">考试类型</span>
  568. <input class="form-input" style="width:200px;" value="应知应会" disabled>
  569. <span class="form-hint">系统内置,不可更改</span>
  570. </div>
  571. <div style="display:flex;align-items:center;gap:16px;">
  572. <span class="form-label" style="width:80px;flex-shrink:0;">考试名称</span>
  573. <input class="form-input" style="width:300px;" placeholder="请输入考试名称">
  574. </div>
  575. <div style="display:flex;align-items:center;gap:16px;">
  576. <span class="form-label" style="width:80px;flex-shrink:0;">答卷次数</span>
  577. <div style="display:flex;align-items:center;gap:8px;">
  578. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('yingzhi-ans',-1)">-</button>
  579. <input id="yingzhi-ans" type="number" class="form-input" style="width:60px;text-align:center;" value="2" min="1">
  580. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('yingzhi-ans',1)">+</button>
  581. </div>
  582. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;margin-left:8px;"><input type="radio" name="yingzhi-ans-mode" value="limit" checked> 限制次数</label>
  583. <label style="display:flex;align-items:center;gap:5px;cursor:pointer;font-size:13px;"><input type="radio" name="yingzhi-ans-mode" value="unlimited"> 无限制</label>
  584. </div>
  585. <div style="display:flex;align-items:center;gap:16px;">
  586. <span class="form-label" style="width:80px;flex-shrink:0;">显示共享</span>
  587. <label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label>
  588. <span class="form-hint">开课共享,全程有学号信息不可匿名</span>
  589. </div>
  590. <div style="display:flex;align-items:center;gap:16px;">
  591. <span class="form-label" style="width:80px;flex-shrink:0;">启用</span>
  592. <label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label>
  593. </div>
  594. <div style="display:flex;align-items:flex-start;gap:16px;">
  595. <span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">考卷</span>
  596. <div style="display:flex;flex-direction:column;gap:8px;">
  597. <div style="display:flex;align-items:center;gap:8px;"><button class="btn btn-outline btn-sm">选择试卷</button></div>
  598. <span class="form-hint">建议按知识点选择覆盖本校安全要求的试卷</span>
  599. </div>
  600. </div>
  601. <div style="display:flex;align-items:center;gap:16px;">
  602. <span class="form-label" style="width:80px;flex-shrink:0;">考试时间</span>
  603. <input type="date" class="form-input" style="width:150px;"><span style="color:var(--text-hint);font-size:13px;">至</span><input type="date" class="form-input" style="width:150px;">
  604. </div>
  605. <div style="display:flex;align-items:center;gap:16px;">
  606. <span class="form-label" style="width:80px;flex-shrink:0;">考试时长</span>
  607. <div style="display:flex;align-items:center;gap:8px;">
  608. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('yingzhi-dur',-5)">-</button>
  609. <input id="yingzhi-dur" type="number" class="form-input" style="width:70px;text-align:center;" value="60" min="5" step="5">
  610. <button class="btn btn-outline-gray btn-sm" onclick="stepNum('yingzhi-dur',5)">+</button>
  611. <span style="font-size:13px;color:var(--text-secondary);">分钟</span>
  612. </div>
  613. <span class="form-hint">限制答卷最大时长</span>
  614. </div>
  615. <!-- 用户类别 (应知应会) -->
  616. <div style="display:flex;align-items:flex-start;gap:16px;">
  617. <span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">用户类别</span>
  618. <div style="display:flex;flex-direction:column;gap:8px;">
  619. <label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;"><input type="checkbox" checked> 无需进入实验室的新入学研究生</label>
  620. <label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;"><input type="checkbox" checked> 新入职教职工</label>
  621. <label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;" title="按实验室分级取年度学时值,不可取消">
  622. <input type="checkbox" checked disabled style="opacity:.5;"> 在读研究生 <span class="tag tag-gray" style="font-size:11px;margin-left:4px;">按实验室分级取年度学时值</span>
  623. </label>
  624. <label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;"><input type="checkbox" checked> 存量教职工</label>
  625. <label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;"><input type="checkbox" checked> 在读本科生</label>
  626. </div>
  627. </div>
  628. <!-- 学时配置 (应知应会) -->
  629. <div style="display:flex;align-items:flex-start;gap:16px;">
  630. <span class="form-label" style="width:80px;flex-shrink:0;padding-top:4px;">学时配置</span>
  631. <div style="flex:1;">
  632. <div class="table-wrap">
  633. <table>
  634. <thead><tr><th>用户类别</th><th>所需学时</th><th>备注</th></tr></thead>
  635. <tbody>
  636. <tr><td>无需进入实验室的新入学研究生</td><td><input type="number" class="form-input" style="width:80px;" value="2" min="0"></td><td><input class="form-input" placeholder="备注说明"></td></tr>
  637. <tr><td>新入职教职工</td><td><input type="number" class="form-input" style="width:80px;" value="2" min="0"></td><td><input class="form-input" placeholder="备注说明"></td></tr>
  638. <tr style="background:var(--gray-light);color:var(--text-hint);">
  639. <td>在读研究生</td>
  640. <td><input type="number" class="form-input" style="width:80px;background:var(--gray-light);color:var(--text-hint);" disabled placeholder="按分级取值"></td>
  641. <td><span style="font-size:12px;">按实验室分级取年度学时值</span></td>
  642. </tr>
  643. <tr><td>存量教职工</td><td><input type="number" class="form-input" style="width:80px;" value="1" min="0"></td><td><input class="form-input" placeholder="备注说明"></td></tr>
  644. <tr><td>在读本科生</td><td><input type="number" class="form-input" style="width:80px;" value="1" min="0"></td><td><input class="form-input" placeholder="备注说明"></td></tr>
  645. </tbody>
  646. </table>
  647. </div>
  648. </div>
  649. </div>
  650. </div>
  651. <div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button><button class="btn btn-outline-gray">重置</button></div>
  652. </div><!-- /yingzhi-sec-basic -->
  653. <!-- 考前配置 -->
  654. <div id="yingzhi-sec-preexam" class="exam-section" style="display:none;">
  655. <div class="card-title">考前配置</div>
  656. <div style="display:flex;flex-direction:column;gap:20px;">
  657. <div style="display:flex;align-items:center;gap:12px;"><span class="form-label" style="width:80px;flex-shrink:0;">学时要求</span><label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label></div>
  658. <div style="display:flex;align-items:center;gap:12px;"><span class="form-label" style="width:80px;flex-shrink:0;">考前学习</span><label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label></div>
  659. <div style="display:flex;align-items:center;gap:12px;"><span class="form-label" style="width:80px;flex-shrink:0;">模拟考试</span><label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label></div>
  660. <div style="display:flex;align-items:center;gap:12px;"><span class="form-label" style="width:80px;flex-shrink:0;">考试承诺书</span><label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label></div>
  661. </div>
  662. <div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button><button class="btn btn-outline-gray">重置</button></div>
  663. </div>
  664. <!-- 防作弊 -->
  665. <div id="yingzhi-sec-cheat" class="exam-section" style="display:none;">
  666. <div class="card-title">防作弊</div>
  667. <div style="display:flex;flex-direction:column;gap:16px;">
  668. <div style="display:flex;align-items:center;gap:12px;"><span class="form-label" style="width:100px;flex-shrink:0;">切屏限制</span><label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label></div>
  669. <div style="display:flex;align-items:center;gap:12px;"><span class="form-label" style="width:100px;flex-shrink:0;">随机题目</span><label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label></div>
  670. <div style="display:flex;align-items:center;gap:12px;"><span class="form-label" style="width:100px;flex-shrink:0;">随机选项</span><label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label></div>
  671. <div style="display:flex;align-items:center;gap:12px;"><span class="form-label" style="width:100px;flex-shrink:0;">人脸识别</span><label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label></div>
  672. </div>
  673. <div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button><button class="btn btn-outline-gray">重置</button></div>
  674. </div>
  675. <!-- 获得证书 -->
  676. <div id="yingzhi-sec-cert" class="exam-section" style="display:none;">
  677. <div class="card-title">获得证书</div>
  678. <div style="display:flex;flex-direction:column;gap:16px;">
  679. <div style="display:flex;align-items:center;gap:12px;"><span class="form-label" style="width:80px;flex-shrink:0;">颁发证书</span><label class="toggle"><input type="checkbox" onchange="document.getElementById('yingzhi-cert-tmpl').style.display=this.checked?'flex':'none'"><span class="toggle-slider"></span></label></div>
  680. <div id="yingzhi-cert-tmpl" style="display:none;align-items:center;gap:12px;">
  681. <span class="form-label" style="width:80px;flex-shrink:0;">证书模板</span>
  682. <select class="form-input form-select" style="width:240px;"><option value="">请选择证书模板</option><option>实验室安全教育证书(标准版)</option></select>
  683. </div>
  684. </div>
  685. <div style="margin-top:24px;display:flex;gap:12px;"><button class="btn btn-primary">保存设置</button><button class="btn btn-outline-gray">重置</button></div>
  686. </div>
  687. </div><!-- /right content yingzhi -->
  688. </div><!-- /exam-tab-yingzhi -->
  689. </div><!-- /page-exam-template -->
  690. `; // end examPageHtml
  691. // ─── JS FUNCTIONS TO INJECT ────────────────────────────────────────────────
  692. const examJsFunctions = `
  693. // ===== EXAM TEMPLATE PAGE =====
  694. function switchExamTab(tab) {
  695. document.querySelectorAll('.exam-tab-panel').forEach(function(p) { p.style.display = 'none'; });
  696. var panel = document.getElementById('exam-tab-' + tab);
  697. if (panel) {
  698. panel.style.display = tab === 'yingzhi' ? 'flex' : 'flex';
  699. if (tab === 'fenji') {
  700. // ensure flex for fenji sub-tab wrapper
  701. panel.style.display = 'block';
  702. }
  703. }
  704. document.querySelectorAll('#exam-top-tabs .view-tab').forEach(function(t) { t.classList.remove('active'); });
  705. var tabMap = { kaixue: 0, fenji: 1, yingzhi: 2 };
  706. var tabs = document.querySelectorAll('#exam-top-tabs .view-tab');
  707. if (tabs[tabMap[tab]]) tabs[tabMap[tab]].classList.add('active');
  708. }
  709. function switchExamSection(prefix, section) {
  710. // Hide all sections in this prefix's container
  711. var secs = document.querySelectorAll('#' + prefix + '-sec-basic, #' + prefix + '-sec-preexam, #' + prefix + '-sec-cheat, #' + prefix + '-sec-cert');
  712. secs.forEach(function(s) { s.style.display = 'none'; });
  713. var target = document.getElementById(prefix + '-sec-' + section);
  714. if (target) target.style.display = 'block';
  715. // Update nav active state - find nav items in same sidebar
  716. var secContainer = target && target.closest('[style*="overflow:hidden"]');
  717. if (secContainer) {
  718. secContainer.querySelectorAll('.nav-item').forEach(function(n) {
  719. n.classList.remove('active');
  720. var oc = n.getAttribute('onclick') || '';
  721. if (oc.includes("'" + section + "'")) n.classList.add('active');
  722. });
  723. }
  724. }
  725. function switchGradeTab(grade) {
  726. document.querySelectorAll('.fenji-grade-panel').forEach(function(p) { p.style.display = 'none'; });
  727. var panel = document.getElementById('fenji-grade-' + grade);
  728. if (panel) panel.style.display = 'flex';
  729. document.querySelectorAll('#fenji-grade-tabs .view-tab').forEach(function(t) { t.classList.remove('active'); });
  730. var gradeMap = { g1: 0, g2: 1, g3: 2, g4: 3 };
  731. var tabs = document.querySelectorAll('#fenji-grade-tabs .view-tab');
  732. if (tabs[gradeMap[grade]]) tabs[gradeMap[grade]].classList.add('active');
  733. }
  734. function setKaixueScope(mode) {
  735. document.getElementById('kaixue-scope-table').style.display = (mode === 'specify' || mode === 'student') ? 'block' : 'none';
  736. document.querySelectorAll('[name="kaixue-scope"]').forEach(function(r) {
  737. r.checked = (r.value === mode);
  738. });
  739. }
  740. function stepNum(id, delta) {
  741. var el = document.getElementById(id);
  742. if (!el) return;
  743. var val = (parseInt(el.value, 10) || 0) + delta;
  744. var min = parseInt(el.min, 10);
  745. if (!isNaN(min) && val < min) val = min;
  746. el.value = val;
  747. }
  748. function updateKaixueHours() {
  749. // Reflect checked user types in the hours table
  750. var tbody = document.getElementById('kaixue-hours-tbody');
  751. if (!tbody) return;
  752. var types = [
  753. { id: 'kaixue-user-undergrad', label: '新入学本科生', def: 4 },
  754. { id: 'kaixue-user-postgrad', label: '新入学研究生', def: 4 },
  755. { id: 'kaixue-user-staff', label: '新入职教职工', def: 2 }
  756. ];
  757. var rows = '';
  758. types.forEach(function(t) {
  759. var cb = document.getElementById(t.id);
  760. if (cb && cb.checked) {
  761. rows += '<tr><td>' + t.label + '</td><td><input type="number" class="form-input" style="width:80px;" value="' + t.def + '" min="0"></td><td><input class="form-input" placeholder="备注说明"></td></tr>';
  762. }
  763. });
  764. tbody.innerHTML = rows || '<tr><td colspan="3" style="text-align:center;color:var(--text-hint);">请先选择用户类别</td></tr>';
  765. }
  766. `; // end examJsFunctions
  767. // ─── PERFORM REPLACEMENTS ──────────────────────────────────────────────────
  768. const PLACEHOLDER = '<!-- EXAM TEMPLATE PAGE PLACEHOLDER -->';
  769. if (!html.includes(PLACEHOLDER)) {
  770. console.error('ERROR: Placeholder not found in admin.html');
  771. process.exit(1);
  772. }
  773. html = html.replace(PLACEHOLDER, examPageHtml);
  774. // Inject JS before closing </script> tag
  775. const SCRIPT_END = '</script>';
  776. const lastScriptEnd = html.lastIndexOf(SCRIPT_END);
  777. if (lastScriptEnd === -1) {
  778. console.error('ERROR: No closing </script> tag found');
  779. process.exit(1);
  780. }
  781. html = html.slice(0, lastScriptEnd) + examJsFunctions + '\n' + SCRIPT_END + html.slice(lastScriptEnd + SCRIPT_END.length);
  782. fs.writeFileSync(filePath, html, 'utf8');
  783. console.log('inject_exam.js: admin.html updated successfully.');