index.vue 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043
  1. <template>
  2. <div class="app-container">
  3. <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
  4. <el-form-item label="题目ID" prop="id">
  5. <el-input v-model.number="queryParams.id" type="number" placeholder="请输入题目ID" clearable
  6. @keyup.enter="handleQuery"/>
  7. </el-form-item>
  8. <el-form-item label="题干" prop="title">
  9. <el-input v-model="queryParams.title" placeholder="请输入题干关键词" clearable
  10. @keyup.enter="handleQuery"/>
  11. </el-form-item>
  12. <el-form-item label="题型" prop="qtpye">
  13. <!-- <el-input v-model="queryParams.qtpye" placeholder="请输入题型" clearable @keyup.enter="handleQuery" /> -->
  14. <!-- <dict-tag :options="question_type" :value="queryParams.qtpye"/> -->
  15. <el-select v-model="queryParams.typeId" clearable @change="handleQuery" style="width: 172px">
  16. <el-option v-for="q in question_type" :label="q.label" :value="q.value"/>
  17. </el-select>
  18. </el-form-item>
  19. <el-form-item label="科目" prop="subjectId">
  20. <el-select v-model="queryParams.subjectId" clearable @change="handleSubjectChange" style="width: 172px">
  21. <el-option v-for="s in subjectList" :label="s.subjectName" :value="s.subjectId"/>
  22. </el-select>
  23. </el-form-item>
  24. <el-form-item label="知识点" prop="knowledgeId">
  25. <el-tree-select node-key="id" v-model="queryParams.knowledgeId" :data="filteredKnowledgeTreeList" check-strictly
  26. :render-after-expand="false" style="" :props="{ label: 'name', children: 'children' }"
  27. placeholder="请选择知识点" class="w-[172px]!" @change="handleQuery"/>
  28. </el-form-item>
  29. <!-- <el-form-item label="难度" prop="diff">
  30. <el-input v-model="queryParams.diff" placeholder="请输入难度" clearable @keyup.enter="handleQuery"/>
  31. </el-form-item> -->
  32. <!-- <el-form-item label="相似度" prop="similarity">
  33. <el-input v-model="queryParams.similarity" placeholder="请输入试题在题库中的相似度" clearable @keyup.enter="handleQuery" />
  34. </el-form-item> -->
  35. <!-- <el-form-item label="试题年份" prop="year">
  36. <el-input v-model="queryParams.year" placeholder="请输入试题年份" clearable @keyup.enter="handleQuery"/>
  37. </el-form-item> -->
  38. <!-- <el-form-item label="试题类型" prop="paperTpye">
  39. <el-select v-model="queryParams.paperTpye" clearable @change="handleQuery" style="width: 172px">
  40. <el-option v-for="p in paper_type" :label="p.label" :value="p.value" />
  41. </el-select>
  42. </el-form-item> -->
  43. <!-- <el-form-item label="来源" prop="source">
  44. <el-input v-model="queryParams.source" placeholder="请输入来源" clearable @keyup.enter="handleQuery"/>
  45. </el-form-item> -->
  46. <!-- <el-form-item label="试题来源" prop="fromSite">
  47. <el-input v-model="queryParams.fromSite" placeholder="请输入试题来源" clearable
  48. @keyup.enter="handleQuery"/>
  49. </el-form-item> -->
  50. <el-form-item label="包含子题" prop="isSubType">
  51. <el-select v-model="queryParams.isSubType" placeholder="请选择包含子题" clearable style="width: 172px">
  52. <el-option v-for="item in bool_values" :key="item.value" :label="item.label" :value="item.value" />
  53. </el-select>
  54. </el-form-item>
  55. <el-form-item>
  56. <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
  57. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  58. </el-form-item>
  59. </el-form>
  60. <el-row :gutter="10" class="mb8">
  61. <el-col :span="1.5">
  62. <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['learn:questions:add']">新增
  63. </el-button>
  64. </el-col>
  65. <el-col :span="1.5">
  66. <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate"
  67. v-hasPermi="['learn:questions:edit']">修改
  68. </el-button>
  69. </el-col>
  70. <el-col :span="1.5">
  71. <el-button type="info" plain icon="Switch" :disabled="!ids.length" @click="handleChangeType"
  72. v-hasPermi="['learn:questions:changeType']">修改题型
  73. </el-button>
  74. </el-col>
  75. <el-col :span="1.5">
  76. <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete"
  77. v-hasPermi="['learn:questions:remove']">删除
  78. </el-button>
  79. </el-col>
  80. <el-col :span="1.5">
  81. <el-button type="warning" plain icon="Download" @click="handleExport"
  82. v-hasPermi="['learn:questions:export']">导出
  83. </el-button>
  84. </el-col>
  85. <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
  86. </el-row>
  87. <el-table v-loading="loading" :data="questionsList" @selection-change="handleSelectionChange">
  88. <el-table-column type="selection" width="55" align="center" fixed="left"/>
  89. <el-table-column label="题目ID" align="center" prop="id" fixed="left" />
  90. <el-table-column label="试题-题干" align="left" prop="title" min-width="350" header-align="center"
  91. fixed="left">
  92. <template #default="scope">
  93. <div class="table-cell-content" v-html="formatContentWithImages(scope.row.title)"></div>
  94. </template>
  95. </el-table-column>
  96. <el-table-column label="题型" align="center" prop="qtpye"/>
  97. <el-table-column label="学科" align="center" prop="subjectId">
  98. <template #default="scope">
  99. <span v-if="scope.row.subjectId">
  100. {{ getSubjectName(scope.row.subjectId) }}
  101. </span>
  102. <span v-else>-</span>
  103. </template>
  104. </el-table-column>
  105. <el-table-column label="知识点" align="center" prop="knowledgeId"/>
  106. <el-table-column label="选项" align="center" prop="optionA" show-overflow-tooltip>
  107. <template #default="scope">
  108. <el-link type="primary" @click="handleShowOptions(scope.row)" :underline="false">
  109. <span v-if="scope.row.optionA" class="table-cell-content" v-html="formatContentWithImages(scope.row.optionA)"></span>
  110. <span v-else>-</span>
  111. </el-link>
  112. </template>
  113. </el-table-column>
  114. <!-- <el-table-column label="选项B" align="center" prop="optionB" show-overflow-tooltip>
  115. <template #default="scope">
  116. <el-link type="primary" @click="handleShowOptions(scope.row)" :underline="false">
  117. {{ scope.row.optionB || '-' }}
  118. </el-link>
  119. </template>
  120. </el-table-column>
  121. <el-table-column label="选项C" align="center" prop="optionC" show-overflow-tooltip>
  122. <template #default="scope">
  123. <el-link type="primary" @click="handleShowOptions(scope.row)" :underline="false">
  124. {{ scope.row.optionC || '-' }}
  125. </el-link>
  126. </template>
  127. </el-table-column>
  128. <el-table-column label="选项D" align="center" prop="optionD" show-overflow-tooltip>
  129. <template #default="scope">
  130. <el-link type="primary" @click="handleShowOptions(scope.row)" :underline="false">
  131. {{ scope.row.optionD || '-' }}
  132. </el-link>
  133. </template>
  134. </el-table-column>
  135. <el-table-column label="选项E" align="center" prop="optionE" show-overflow-tooltip>
  136. <template #default="scope">
  137. <el-link type="primary" @click="handleShowOptions(scope.row)" :underline="false">
  138. {{ scope.row.optionE || '-' }}
  139. </el-link>
  140. </template>
  141. </el-table-column>
  142. <el-table-column label="选项F" align="center" prop="optionF" show-overflow-tooltip>
  143. <template #default="scope">
  144. <el-link type="primary" @click="handleShowOptions(scope.row)" :underline="false">
  145. {{ scope.row.optionF || '-' }}
  146. </el-link>
  147. </template>
  148. </el-table-column>
  149. <el-table-column label="选项G" align="center" prop="optionG" show-overflow-tooltip>
  150. <template #default="scope">
  151. <el-link type="primary" @click="handleShowOptions(scope.row)" :underline="false">
  152. {{ scope.row.optionG || '-' }}
  153. </el-link>
  154. </template>
  155. </el-table-column> -->
  156. <el-table-column label="标准答案" align="center" prop="answer1" show-overflow-tooltip>
  157. <template #default="scope">
  158. <div class="table-cell-content" v-html="formatContentWithImages(scope.row.answer1 || '-')"></div>
  159. </template>
  160. </el-table-column>
  161. <el-table-column label="答案2" align="center" prop="answer2" show-overflow-tooltip>
  162. <template #default="scope">
  163. <div class="table-cell-content" v-html="formatContentWithImages(scope.row.answer2 || '-')"></div>
  164. </template>
  165. </el-table-column>
  166. <!-- <el-table-column label="试卷Id" align="center" prop="paperId"/> -->
  167. <!-- <el-table-column label="难易度" align="center" prop="diff"/> -->
  168. <!-- <el-table-column label="相似度" align="center" prop="similarity"/> -->
  169. <el-table-column label="试题解析" align="center" prop="parse" min-width="250" show-overflow-tooltip>
  170. <template #default="scope">
  171. <div class="table-cell-content" v-html="formatContentWithImages(scope.row.parse || '-')"></div>
  172. </template>
  173. </el-table-column>
  174. <!-- <el-table-column label="knowId" align="center" prop="knowId" /> -->
  175. <!-- <el-table-column label="年级ID" align="center" prop="gradeId"/> -->
  176. <!-- <el-table-column label="knowledges" align="center" prop="knowledges" /> -->
  177. <!-- <el-table-column label="试题区域" align="center" prop="area"/> -->
  178. <el-table-column label="试题年份" align="center" prop="year"/>
  179. <!-- <el-table-column label="试题类型" align="center" prop="paperTpye"/> -->
  180. <el-table-column label="来源" align="center" prop="source"/>
  181. <!-- <el-table-column label="试题来源" align="center" prop="fromSite"/> -->
  182. <!-- <el-table-column label="图片水印" align="center" prop="isSub"/> -->
  183. <!-- <el-table-column label="常规题" align="center" prop="isNormal"/> -->
  184. <el-table-column label="匹配知识点" align="center" prop="isKonw" min-width="120"/>
  185. <el-table-column label="tiid" align="center" prop="tiid" min-width="100"/>
  186. <!-- <el-table-column label="试题题干的md5值" align="center" prop="md5" /> -->
  187. <!-- <el-table-column label="是否唯一" align="center" prop="isunique"/> -->
  188. <!-- <el-table-column label="md52" align="center" prop="md52" /> -->
  189. <el-table-column label="分值" align="center" prop="score"/>
  190. <!-- <el-table-column label="选项" align="center" prop="options"/> -->
  191. <!-- <el-table-column label="选项" align="center" prop="options0"/> -->
  192. <!-- <el-table-column label="number" align="center" prop="number" /> -->
  193. <!-- <el-table-column label="paperTypeTitle" align="center" prop="paperTypeTitle" /> -->
  194. <!-- <el-table-column label="试题-材料题题干" align="center" prop="title0" min-width="130" show-overflow-tooltip/> -->
  195. <!-- <el-table-column label="试题-材料题题干" align="center" prop="title1" min-width="130" show-overflow-tooltip/> -->
  196. <!-- <el-table-column label="试题解析" align="left" prop="parse0" header-align="center" show-overflow-tooltip/> -->
  197. <!-- <el-table-column label="answer0" align="center" prop="answer0"/> -->
  198. <el-table-column label="是否更新" align="center" prop="isUpdate">
  199. <template #default="scope">
  200. <dict-tag :options="bool_values" :value="scope.row.isUpdate" />
  201. </template>
  202. </el-table-column>
  203. <el-table-column label="包含子题" align="center" prop="isSubType">
  204. <template #default="scope">
  205. <dict-tag :options="bool_values" :value="scope.row.isSubType" />
  206. </template>
  207. </el-table-column>
  208. <el-table-column label="操作" align="center" class-name="small-padding fixed-width" min-width="140"
  209. fixed="right">
  210. <template #default="scope">
  211. <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
  212. v-hasPermi="['learn:questions:edit']">修改
  213. </el-button>
  214. <el-button link type="primary" icon="" @click="handleTextUpdate(scope.row)"
  215. v-hasPermi="['learn:questions:edit']">文本修改
  216. </el-button>
  217. <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
  218. v-hasPermi="['learn:questions:remove']">删除
  219. </el-button>
  220. </template>
  221. </el-table-column>
  222. </el-table>
  223. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
  224. v-model:limit="queryParams.pageSize" @pagination="getList"/>
  225. <!-- 添加或修改试题对话框 -->
  226. <el-dialog :title="title" v-model="open" width="1200px" append-to-body>
  227. <div class="form-content-wrapper">
  228. <el-form ref="questionsRef" :model="form" :rules="rules" label-width="140px">
  229. <!-- 只读显示区域:题目标题和选项(仅在修改时显示) -->
  230. <div class="readonly-section" v-if="form.id != null">
  231. <el-form-item label="标题">
  232. <div class="readonly-content" v-html="formatContentWithImages(form.title) || '-'"></div>
  233. </el-form-item>
  234. <el-row :gutter="20" v-if="form.optionA || form.optionB || form.optionC || form.optionD || form.optionE || form.optionF || form.optionG">
  235. <el-col :span="12" v-if="form.optionA">
  236. <el-form-item label="选项A">
  237. <div class="readonly-content" v-html="formatContentWithImages(form.optionA)"></div>
  238. </el-form-item>
  239. </el-col>
  240. <el-col :span="12" v-if="form.optionB">
  241. <el-form-item label="选项B">
  242. <div class="readonly-content" v-html="formatContentWithImages(form.optionB)"></div>
  243. </el-form-item>
  244. </el-col>
  245. <el-col :span="12" v-if="form.optionC">
  246. <el-form-item label="选项C">
  247. <div class="readonly-content" v-html="formatContentWithImages(form.optionC)"></div>
  248. </el-form-item>
  249. </el-col>
  250. <el-col :span="12" v-if="form.optionD">
  251. <el-form-item label="选项D">
  252. <div class="readonly-content" v-html="formatContentWithImages(form.optionD)"></div>
  253. </el-form-item>
  254. </el-col>
  255. <el-col :span="12" v-if="form.optionE">
  256. <el-form-item label="选项E">
  257. <div class="readonly-content" v-html="formatContentWithImages(form.optionE)"></div>
  258. </el-form-item>
  259. </el-col>
  260. <el-col :span="12" v-if="form.optionF">
  261. <el-form-item label="选项F">
  262. <div class="readonly-content" v-html="formatContentWithImages(form.optionF)"></div>
  263. </el-form-item>
  264. </el-col>
  265. <el-col :span="12" v-if="form.optionG">
  266. <el-form-item label="选项G">
  267. <div class="readonly-content" v-html="formatContentWithImages(form.optionG)"></div>
  268. </el-form-item>
  269. </el-col>
  270. </el-row>
  271. <el-row :gutter="20" v-if="form.answer1 || form.answer2">
  272. <el-col :span="12" v-if="form.answer1">
  273. <el-form-item label="答案1">
  274. <div class="readonly-content" v-html="formatContentWithImages(form.answer1)"></div>
  275. </el-form-item>
  276. </el-col>
  277. <el-col :span="12" v-if="form.answer2">
  278. <el-form-item label="答案2">
  279. <div class="readonly-content" v-html="formatContentWithImages(form.answer2)"></div>
  280. </el-form-item>
  281. </el-col>
  282. </el-row>
  283. </div>
  284. <!-- 横线分隔(仅在修改时显示) -->
  285. <el-divider v-if="form.id != null"></el-divider>
  286. <!-- 可修改内容区域,两列布局 -->
  287. <el-row :gutter="20">
  288. <el-col :span="12">
  289. <el-form-item label="科目" prop="subjectId">
  290. <el-select v-model="form.subjectId" placeholder="请选择科目" style="width: 100%">
  291. <el-option v-for="s in subjectList" :label="s.subjectName" :value="s.subjectId"/>
  292. </el-select>
  293. </el-form-item>
  294. </el-col>
  295. <el-col :span="12">
  296. <el-form-item label="题型" prop="qtpye">
  297. <el-input v-model="form.qtpye" type="text" placeholder="请输入内容"/>
  298. </el-form-item>
  299. </el-col>
  300. </el-row>
  301. <el-form-item label="标题" prop="title">
  302. <Editor v-if="!isTextMode" v-model="form.title" :min-height="120" />
  303. <el-input v-else v-model="form.title" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入标题内容"/>
  304. </el-form-item>
  305. <el-row :gutter="20">
  306. <el-col :span="12">
  307. <el-form-item label="选项A" prop="optionA">
  308. <Editor v-if="!isTextMode" v-model="form.optionA" :min-height="120" />
  309. <el-input v-else v-model="form.optionA" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
  310. </el-form-item>
  311. </el-col>
  312. <el-col :span="12">
  313. <el-form-item label="选项B" prop="optionB">
  314. <Editor v-if="!isTextMode" v-model="form.optionB" :min-height="120" />
  315. <el-input v-else v-model="form.optionB" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
  316. </el-form-item>
  317. </el-col>
  318. </el-row>
  319. <el-row :gutter="20">
  320. <el-col :span="12">
  321. <el-form-item label="选项C" prop="optionC">
  322. <Editor v-if="!isTextMode" v-model="form.optionC" :min-height="120" />
  323. <el-input v-else v-model="form.optionC" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
  324. </el-form-item>
  325. </el-col>
  326. <el-col :span="12">
  327. <el-form-item label="选项D" prop="optionD">
  328. <Editor v-if="!isTextMode" v-model="form.optionD" :min-height="120" />
  329. <el-input v-else v-model="form.optionD" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
  330. </el-form-item>
  331. </el-col>
  332. </el-row>
  333. <el-row :gutter="20">
  334. <el-col :span="12">
  335. <el-form-item label="选项E" prop="optionE">
  336. <Editor v-if="!isTextMode" v-model="form.optionE" :min-height="120" />
  337. <el-input v-else v-model="form.optionE" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
  338. </el-form-item>
  339. </el-col>
  340. <el-col :span="12">
  341. <el-form-item label="选项F" prop="optionF">
  342. <Editor v-if="!isTextMode" v-model="form.optionF" :min-height="120" />
  343. <el-input v-else v-model="form.optionF" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
  344. </el-form-item>
  345. </el-col>
  346. </el-row>
  347. <el-row :gutter="20">
  348. <el-col :span="12">
  349. <el-form-item label="选项G" prop="optionG">
  350. <Editor v-if="!isTextMode" v-model="form.optionG" :min-height="120" />
  351. <el-input v-else v-model="form.optionG" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
  352. </el-form-item>
  353. </el-col>
  354. </el-row>
  355. <el-row :gutter="20">
  356. <el-col :span="12">
  357. <el-form-item label="答案1" prop="answer1">
  358. <Editor v-if="!isTextMode" v-model="form.answer1" :min-height="120" />
  359. <el-input v-else v-model="form.answer1" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
  360. </el-form-item>
  361. </el-col>
  362. <el-col :span="12">
  363. <el-form-item label="答案2" prop="answer2">
  364. <Editor v-if="!isTextMode" v-model="form.answer2" :min-height="120" />
  365. <el-input v-else v-model="form.answer2" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
  366. </el-form-item>
  367. </el-col>
  368. </el-row>
  369. <!-- <el-form-item label="题型" prop="qtpye">
  370. <el-select v-model="form.typeId" placeholder="请选择类型">
  371. <el-option v-for="q in question_type" :label="q.label" :value="q.value"/>
  372. </el-select>
  373. </el-form-item> -->
  374. <el-row :gutter="20">
  375. <el-col :span="12">
  376. <el-form-item label="知识点" prop="knowledgeId">
  377. <el-tree-select node-key="id" v-model="form.knowledgeId" :data="knowledgeTreeList" check-strictly
  378. :render-after-expand="false" style="width: 100%"
  379. :props="{ label: 'name', children: 'children' }"
  380. placeholder="请选择知识点"/>
  381. </el-form-item>
  382. </el-col>
  383. <el-col :span="12">
  384. <el-form-item label="来源" prop="source">
  385. <el-input v-model="form.source" placeholder="请输入来源"/>
  386. </el-form-item>
  387. </el-col>
  388. </el-row>
  389. <el-row :gutter="20">
  390. <el-col :span="12">
  391. <el-form-item label="源ID" prop="tiid">
  392. <el-input v-model="form.tiid" placeholder="请输入试题的源ID"/>
  393. </el-form-item>
  394. </el-col>
  395. <el-col :span="12">
  396. <el-form-item label="试题解析" prop="parse">
  397. <Editor v-if="!isTextMode" v-model="form.parse" :min-height="120" />
  398. <el-input v-else v-model="form.parse" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
  399. </el-form-item>
  400. </el-col>
  401. </el-row>
  402. <!-- <el-form-item label="试题题干的md5值" prop="md5">
  403. <el-input v-model="form.md5" placeholder="请输入试题题干的md5值" />
  404. </el-form-item> -->
  405. <!-- <el-form-item label="${comment}" prop="isunique">
  406. <el-input v-model="form.isunique" placeholder="请输入${comment}" />
  407. </el-form-item> -->
  408. <!-- <el-form-item label="${comment}" prop="md52">
  409. <el-input v-model="form.md52" placeholder="请输入${comment}" />
  410. </el-form-item> -->
  411. <!-- <el-form-item label="分值" prop="score">
  412. <el-input v-model="form.score" placeholder="请输入分值"/>
  413. </el-form-item>
  414. <el-form-item label="选项" prop="options">
  415. <el-input v-model="form.options" type="textarea" placeholder="请输入内容"/>
  416. </el-form-item> -->
  417. <!-- <el-form-item label="${comment}" prop="number">
  418. <el-input v-model="form.number" placeholder="请输入${comment}" />
  419. </el-form-item> -->
  420. <!-- <el-form-item label="${comment}" prop="paperTypeTitle">
  421. <el-input v-model="form.paperTypeTitle" placeholder="请输入${comment}" />
  422. </el-form-item> -->
  423. <!-- <el-form-item label="选项" prop="options0">
  424. <el-input v-model="form.options0" type="textarea" placeholder="请输入内容"/>
  425. </el-form-item>
  426. <el-form-item label="试题-材料题题干" prop="title0">
  427. <el-input v-model="form.title0" type="textarea" placeholder="请输入内容"/>
  428. </el-form-item>
  429. <el-form-item label="试题-材料题题干" prop="title1">
  430. <el-input v-model="form.title1" type="textarea" placeholder="请输入内容"/>
  431. </el-form-item>
  432. <el-form-item label="试题解析" prop="parse0">
  433. <el-input v-model="form.parse0" type="textarea" placeholder="请输入内容"/>
  434. </el-form-item> -->
  435. <!-- <el-form-item label="${comment}" prop="answer0">
  436. <el-input v-model="form.answer0" type="textarea" placeholder="请输入内容" />
  437. </el-form-item> -->
  438. <!-- <el-form-item label="是否更新" prop="isUpdate">
  439. <el-input v-model="form.isUpdate" placeholder="请输入是否更新"/>
  440. </el-form-item> -->
  441. </el-form>
  442. </div>
  443. <template #footer>
  444. <div class="dialog-footer">
  445. <el-button type="primary" @click="submitForm">确 定</el-button>
  446. <el-button @click="cancel">取 消</el-button>
  447. </div>
  448. </template>
  449. </el-dialog>
  450. <!-- 选项详情弹窗 -->
  451. <el-dialog v-model="showOptionsDialog" title="选项详情" width="600px" append-to-body>
  452. <div class="options-detail">
  453. <div class="option-item" v-if="currentRow.optionA">
  454. <div class="option-label">选项A:</div>
  455. <div class="option-content">{{ currentRow.optionA }}</div>
  456. </div>
  457. <div class="option-item" v-if="currentRow.optionB">
  458. <div class="option-label">选项B:</div>
  459. <div class="option-content">{{ currentRow.optionB }}</div>
  460. </div>
  461. <div class="option-item" v-if="currentRow.optionC">
  462. <div class="option-label">选项C:</div>
  463. <div class="option-content">{{ currentRow.optionC }}</div>
  464. </div>
  465. <div class="option-item" v-if="currentRow.optionD">
  466. <div class="option-label">选项D:</div>
  467. <div class="option-content">{{ currentRow.optionD }}</div>
  468. </div>
  469. <div class="option-item" v-if="currentRow.optionE">
  470. <div class="option-label">选项E:</div>
  471. <div class="option-content">{{ currentRow.optionE }}</div>
  472. </div>
  473. <div class="option-item" v-if="currentRow.optionF">
  474. <div class="option-label">选项F:</div>
  475. <div class="option-content">{{ currentRow.optionF }}</div>
  476. </div>
  477. <div class="option-item" v-if="currentRow.optionG">
  478. <div class="option-label">选项G:</div>
  479. <div class="option-content">{{ currentRow.optionG }}</div>
  480. </div>
  481. <div v-if="!currentRow.optionA && !currentRow.optionB && !currentRow.optionC && !currentRow.optionD && !currentRow.optionE && !currentRow.optionF && !currentRow.optionG" class="no-options">
  482. 暂无选项内容
  483. </div>
  484. </div>
  485. <template #footer>
  486. <div class="dialog-footer">
  487. <el-button @click="showOptionsDialog = false">关 闭</el-button>
  488. </div>
  489. </template>
  490. </el-dialog>
  491. <el-dialog v-model="showTypeDialog" title="修改题型" show-close width="500px" append-to-body>
  492. <div class="text-content">题号:{{typeForm.ids}}</div>
  493. <div class="mt-2 mb-2">共<el-text type="primary">{{typeForm.ids.length}}</el-text>项</div>
  494. <div>
  495. <el-select v-model="typeForm.type" placeholder="请选择题型或者输入自定义题型"
  496. filterable allow-create default-first-option>
  497. <el-option v-for="t in question_type" :label="t.label" :value="t.value" />
  498. </el-select>
  499. </div>
  500. <template #footer>
  501. <div class="dialog-footer">
  502. <el-button type="primary" @click="handleUpdateType">确 定</el-button>
  503. <el-button @click="showTypeDialog=false">取 消</el-button>
  504. </div>
  505. </template>
  506. </el-dialog>
  507. </div>
  508. </template>
  509. <script setup name="Questions">
  510. import {listQuestions, getQuestions, delQuestions, addQuestions, updateQuestions, changeQuestionType} from "@/api/learn/questions"
  511. import {listKnowledgeTree} from "@/api/learn/knowledgeTree"
  512. import {listAllSubject} from "@/api/dz/subject"
  513. import {ElMessage} from "element-plus";
  514. import DictTag from '@/components/DictTag/index.vue'
  515. import Editor from '@/components/Editor/index.vue'
  516. import { computed } from 'vue'
  517. const {proxy} = getCurrentInstance()
  518. const {question_type} = proxy.useDict('question_type')
  519. const {paper_type} = proxy.useDict('paper_type')
  520. const {bool_values} = proxy.useDict('bool_values')
  521. const questionsList = ref([])
  522. const open = ref(false)
  523. const loading = ref(true)
  524. const showSearch = ref(true)
  525. const ids = ref([])
  526. const single = ref(true)
  527. const multiple = ref(true)
  528. const total = ref(0)
  529. const title = ref("")
  530. const typeForm = ref({
  531. ids: [],
  532. type: ''
  533. })
  534. const showTypeDialog = ref(false)
  535. const showOptionsDialog = ref(false)
  536. const currentRow = ref({})
  537. const isTextMode = ref(false) // 是否为文本模式(false为富文本模式)
  538. const subjectList = ref([])
  539. const knowledgeTreeList = ref([])
  540. /** 根据subjectId过滤知识点树 */
  541. const filteredKnowledgeTreeList = computed(() => {
  542. if (!queryParams.value.subjectId) {
  543. return knowledgeTreeList.value
  544. }
  545. return filterKnowledgeTreeBySubjectId(knowledgeTreeList.value, queryParams.value.subjectId)
  546. })
  547. /** 递归过滤知识点树 */
  548. function filterKnowledgeTreeBySubjectId(tree, subjectId) {
  549. if (!tree || !Array.isArray(tree)) {
  550. return []
  551. }
  552. const result = []
  553. for (const node of tree) {
  554. // 如果当前节点的subjectId匹配,或者有子节点匹配,则保留该节点
  555. const filteredChildren = node.children ? filterKnowledgeTreeBySubjectId(node.children, subjectId) : []
  556. if (node.subjectId === subjectId || filteredChildren.length > 0) {
  557. result.push({
  558. ...node,
  559. children: filteredChildren.length > 0 ? filteredChildren : (node.children || [])
  560. })
  561. }
  562. }
  563. return result
  564. }
  565. const data = reactive({
  566. form: {},
  567. queryParams: {
  568. pageNum: 1,
  569. pageSize: 10,
  570. id: null,
  571. title: null,
  572. optionA: null,
  573. optionB: null,
  574. optionC: null,
  575. optionD: null,
  576. optionE: null,
  577. optionF: null,
  578. optionG: null,
  579. answer1: null,
  580. answer2: null,
  581. qtpye: null,
  582. typeId: null,
  583. subjectId: null,
  584. paperId: null,
  585. knowledgeId: null,
  586. diff: null,
  587. similarity: null,
  588. parse: null,
  589. knowId: null,
  590. gradeId: null,
  591. knowledges: null,
  592. area: null,
  593. year: null,
  594. paperTpye: null,
  595. source: null,
  596. fromSite: null,
  597. isSub: null,
  598. isNormal: null,
  599. isKonw: null,
  600. tiid: null,
  601. md5: null,
  602. isunique: null,
  603. md52: null,
  604. score: null,
  605. options: null,
  606. number: null,
  607. paperTypeTitle: null,
  608. options0: null,
  609. title0: null,
  610. title1: null,
  611. parse0: null,
  612. answer0: null,
  613. isUpdate: null,
  614. isSubType: null
  615. },
  616. rules: {}
  617. })
  618. setTimeout(() => {
  619. console.log(question_type)
  620. }, 1000)
  621. const {queryParams, form, rules} = toRefs(data)
  622. /** 查询试题列表 */
  623. function getList() {
  624. loading.value = true
  625. // 处理查询参数,确保id是数字类型
  626. const params = { ...queryParams.value }
  627. if (params.id) {
  628. // 将id转换为数字类型
  629. const idNum = Number(params.id)
  630. if (!isNaN(idNum)) {
  631. params.id = idNum
  632. } else {
  633. params.id = null
  634. }
  635. }
  636. listQuestions(params).then(response => {
  637. questionsList.value = response.rows
  638. total.value = response.total
  639. loading.value = false
  640. })
  641. }
  642. function getKnowledgeTreeList() {
  643. listKnowledgeTree({}).then(res => {
  644. knowledgeTreeList.value = res.data
  645. })
  646. }
  647. function getSubjectList() {
  648. listAllSubject({}).then(res => {
  649. subjectList.value = res.data
  650. })
  651. }
  652. /** 根据学科ID获取学科名称 */
  653. function getSubjectName(subjectId) {
  654. if (!subjectId || !subjectList.value || subjectList.value.length === 0) {
  655. return '-'
  656. }
  657. const subject = subjectList.value.find(s => s.subjectId === subjectId)
  658. return subject ? subject.subjectName : '-'
  659. }
  660. // 取消按钮
  661. function cancel() {
  662. open.value = false
  663. isTextMode.value = false // 重置模式
  664. reset()
  665. }
  666. // 表单重置
  667. function reset() {
  668. form.value = {
  669. id: null,
  670. title: null,
  671. optionA: null,
  672. optionB: null,
  673. optionC: null,
  674. optionD: null,
  675. optionE: null,
  676. optionF: null,
  677. optionG: null,
  678. answer1: null,
  679. answer2: null,
  680. qtpye: null,
  681. subjectId: null,
  682. paperId: null,
  683. knowledgeId: null,
  684. diff: null,
  685. similarity: null,
  686. parse: null,
  687. knowId: null,
  688. gradeId: null,
  689. knowledges: null,
  690. area: null,
  691. year: null,
  692. paperTpye: null,
  693. source: null,
  694. fromSite: null,
  695. isSub: null,
  696. isNormal: null,
  697. isKonw: null,
  698. tiid: null,
  699. md5: null,
  700. isunique: null,
  701. md52: null,
  702. createTime: null,
  703. score: null,
  704. options: null,
  705. number: null,
  706. paperTypeTitle: null,
  707. options0: null,
  708. title0: null,
  709. title1: null,
  710. parse0: null,
  711. answer0: null,
  712. isUpdate: null,
  713. isSubType: null
  714. }
  715. // 先重置表单验证状态
  716. if (proxy.$refs["questionsRef"]) {
  717. proxy.$refs["questionsRef"].resetFields()
  718. proxy.$refs["questionsRef"].clearValidate()
  719. }
  720. }
  721. /** 科目变化处理 */
  722. function handleSubjectChange() {
  723. // 清空知识点选择,因为科目变化后知识点列表会变化
  724. queryParams.value.knowledgeId = null
  725. handleQuery()
  726. }
  727. /** 搜索按钮操作 */
  728. function handleQuery() {
  729. queryParams.value.pageNum = 1
  730. getList()
  731. }
  732. /** 重置按钮操作 */
  733. function resetQuery() {
  734. proxy.resetForm("queryRef")
  735. // 手动清除 typeId,因为 resetForm 可能无法清除 el-select 的值
  736. queryParams.value.typeId = null
  737. handleQuery()
  738. }
  739. // 多选框选中数据
  740. function handleSelectionChange(selection) {
  741. ids.value = selection.map(item => item.id)
  742. single.value = selection.length != 1
  743. multiple.value = !selection.length
  744. }
  745. /** 新增按钮操作 */
  746. function handleAdd() {
  747. reset()
  748. isTextMode.value = false // 新增使用富文本模式
  749. open.value = true
  750. title.value = "添加试题"
  751. }
  752. /** 文本修改按钮操作 */
  753. function handleTextUpdate(row) {
  754. reset()
  755. isTextMode.value = true // 文本修改使用普通文本模式
  756. const _id = row.id || ids.value
  757. getQuestions(_id).then(response => {
  758. form.value = response.data
  759. open.value = true
  760. title.value = "文本修改"
  761. })
  762. }
  763. /** 修改按钮操作 */
  764. function handleUpdate(row) {
  765. reset()
  766. isTextMode.value = false // 修改使用富文本模式
  767. const _id = row.id || ids.value
  768. getQuestions(_id).then(response => {
  769. form.value = response.data
  770. open.value = true
  771. title.value = "修改试题"
  772. })
  773. }
  774. /** 提交按钮 */
  775. function submitForm() {
  776. proxy.$refs["questionsRef"].validate(valid => {
  777. if (valid) {
  778. // 提交所有可编辑字段(包括题干和选项)
  779. if (form.value.id != null) {
  780. updateQuestions(form.value).then(response => {
  781. proxy.$modal.msgSuccess("修改成功")
  782. open.value = false
  783. isTextMode.value = false // 重置模式
  784. getList()
  785. })
  786. } else {
  787. addQuestions(form.value).then(response => {
  788. proxy.$modal.msgSuccess("新增成功")
  789. open.value = false
  790. isTextMode.value = false // 重置模式
  791. getList()
  792. })
  793. }
  794. }
  795. })
  796. }
  797. /** 删除按钮操作 */
  798. function handleDelete(row) {
  799. const _ids = row.id || ids.value
  800. proxy.$modal.confirm('是否确认删除试题编号为"' + _ids + '"的数据项?').then(function () {
  801. return delQuestions(_ids)
  802. }).then(() => {
  803. getList()
  804. proxy.$modal.msgSuccess("删除成功")
  805. }).catch(() => {
  806. })
  807. }
  808. /** 导出按钮操作 */
  809. function handleExport() {
  810. proxy.download('learn/questions/export', {
  811. ...queryParams.value
  812. }, `questions_${new Date().getTime()}.xlsx`)
  813. }
  814. function handleChangeType() {
  815. typeForm.value.ids = ids.value
  816. typeForm.value.type = ''
  817. showTypeDialog.value = true
  818. }
  819. async function handleUpdateType() {
  820. const {ids, type} = typeForm.value
  821. if (!ids.length) return ElMessage.error('请选择试题')
  822. if (!type) return ElMessage.error('请选择题型')
  823. await changeQuestionType({ids, type})
  824. ElMessage.success('保存成功')
  825. showTypeDialog.value = false
  826. getList()
  827. }
  828. /** 显示选项详情 */
  829. function handleShowOptions(row) {
  830. currentRow.value = row || {}
  831. showOptionsDialog.value = true
  832. }
  833. /** 获取图片代理URL(如果需要后端代理,可以在这里实现) */
  834. function getImageProxyUrl(imageUrl) {
  835. if (!imageUrl) return ''
  836. // 如果是相对路径或本地路径,直接返回
  837. if (!imageUrl.startsWith('http://') && !imageUrl.startsWith('https://') && !imageUrl.startsWith('//')) {
  838. return imageUrl
  839. }
  840. // 如果需要通过后端代理访问第三方图片,可以取消下面的注释
  841. // 注意:需要后端提供图片代理接口
  842. // const encodedUrl = encodeURIComponent(imageUrl)
  843. // return `${import.meta.env.VITE_APP_BASE_API}/common/image/proxy?url=${encodedUrl}`
  844. // 暂时直接返回原URL,通过添加跨域属性来处理
  845. return imageUrl
  846. }
  847. /** 格式化内容,将图片URL转换为img标签 */
  848. function formatContentWithImages(content) {
  849. if (!content) return ''
  850. // 如果内容已经是HTML格式(包含img标签),需要处理其中的图片URL
  851. if (content.includes('<img') || content.includes('<IMG')) {
  852. // 提取所有img标签中的src属性
  853. const imgSrcRegex = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi
  854. let formattedContent = content
  855. formattedContent = formattedContent.replace(imgSrcRegex, (match, src) => {
  856. // 如果是第三方图片URL,转换为代理URL
  857. let imageUrl = src
  858. if (src.startsWith('http://') || src.startsWith('https://') || src.startsWith('//')) {
  859. // 对于第三方图片,尝试使用代理
  860. // 如果代理不可用,则使用原URL并添加跨域属性
  861. imageUrl = getImageProxyUrl(src)
  862. // 如果代理URL和原URL相同(代理不可用),添加跨域属性
  863. if (imageUrl === src) {
  864. return match.replace(src, src).replace('<img', '<img crossorigin="anonymous" referrerpolicy="no-referrer"')
  865. } else {
  866. return match.replace(src, imageUrl)
  867. }
  868. }
  869. return match
  870. })
  871. return formattedContent
  872. }
  873. // 匹配图片URL的正则表达式
  874. // 支持:https://、http://、//开头的完整URL,以及相对路径
  875. const imageUrlRegex = /(https?:\/\/[^\s<>"']+\.(jpg|jpeg|png|gif|bmp|webp|svg)(\?[^\s<>"']*)?)|(\/\/[^\s<>"']+\.(jpg|jpeg|png|gif|bmp|webp|svg)(\?[^\s<>"']*)?)|(\/[^\s<>"']+\.(jpg|jpeg|png|gif|bmp|webp|svg)(\?[^\s<>"']*)?)/gi
  876. // 将纯文本的图片URL转换为img标签
  877. let formattedContent = content
  878. const matches = content.match(imageUrlRegex)
  879. if (matches) {
  880. matches.forEach(url => {
  881. // 确保URL是完整的
  882. let imageUrl = url.trim()
  883. // 如果是以//开头的,添加https:
  884. if (imageUrl.startsWith('//')) {
  885. imageUrl = 'https:' + imageUrl
  886. }
  887. // 对于第三方图片,尝试使用代理
  888. const proxyUrl = getImageProxyUrl(imageUrl)
  889. // 如果代理不可用,使用原URL并添加跨域属性
  890. const finalUrl = proxyUrl !== imageUrl ? proxyUrl : imageUrl
  891. const crossOriginAttr = proxyUrl === imageUrl ? 'crossorigin="anonymous" referrerpolicy="no-referrer"' : ''
  892. // 将URL替换为img标签
  893. const imgTag = `<img src="${finalUrl}" ${crossOriginAttr} alt="图片" style="max-width: 100%; height: auto; display: block; margin: 10px 0;" onerror="this.onerror=null; this.src='${imageUrl}'; this.style.border='1px solid #ddd'; this.alt='图片加载失败,点击查看原图'; this.style.cursor='pointer'; this.title='点击查看原图'; this.onclick='window.open(this.src)';" />`
  894. formattedContent = formattedContent.replace(url, imgTag)
  895. })
  896. }
  897. return formattedContent
  898. }
  899. getKnowledgeTreeList()
  900. getSubjectList()
  901. getList()
  902. </script>
  903. <style scoped>
  904. .options-detail {
  905. padding: 10px 0;
  906. }
  907. .option-item {
  908. margin-bottom: 15px;
  909. display: flex;
  910. align-items: flex-start;
  911. }
  912. .option-label {
  913. font-weight: bold;
  914. min-width: 60px;
  915. color: #606266;
  916. }
  917. .option-content {
  918. flex: 1;
  919. word-break: break-word;
  920. white-space: pre-wrap;
  921. line-height: 1.6;
  922. }
  923. .no-options {
  924. text-align: center;
  925. color: #909399;
  926. padding: 20px 0;
  927. }
  928. .readonly-section {
  929. background-color: #f5f7fa;
  930. padding: 15px;
  931. border-radius: 4px;
  932. margin-bottom: 20px;
  933. }
  934. .readonly-content {
  935. color: #606266;
  936. word-break: break-word;
  937. white-space: pre-wrap;
  938. line-height: 1.6;
  939. min-height: 20px;
  940. }
  941. .readonly-content :deep(img) {
  942. max-width: 100%;
  943. height: auto;
  944. display: block;
  945. margin: 10px 0;
  946. border-radius: 4px;
  947. object-fit: contain;
  948. }
  949. .readonly-content :deep(img[src=""]) {
  950. display: none;
  951. }
  952. .readonly-content :deep(img:not([src])) {
  953. display: none;
  954. }
  955. .form-content-wrapper {
  956. max-width: 1100px;
  957. margin: 0 auto;
  958. }
  959. /* 表格单元格内容样式 */
  960. .table-cell-content {
  961. word-break: break-word;
  962. line-height: 1.6;
  963. }
  964. .table-cell-content :deep(img) {
  965. max-width: 100%;
  966. height: auto;
  967. display: block;
  968. margin: 5px 0;
  969. border-radius: 4px;
  970. object-fit: contain;
  971. }
  972. .table-cell-content :deep(img[src=""]) {
  973. display: none;
  974. }
  975. .table-cell-content :deep(img:not([src])) {
  976. display: none;
  977. }
  978. </style>