single-choice.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. <template>
  2. <div class="app-container">
  3. <a-form :model="form" ref="formRef" :rules="rules" layout="vertical">
  4. <!-- <a-form-item label="年级:" name="gradeLevel" required>
  5. <a-select v-model:value="form.gradeLevel" placeholder="年级" @change="levelChange">
  6. <a-select-option v-for="item in levelEnum" :key="item.key" :value="item.key">
  7. {{ item.value }}
  8. </a-select-option>
  9. </a-select>
  10. </a-form-item>
  11. <a-form-item label="学科:" name="subjectId" required>
  12. <a-select v-model:value="form.subjectId" placeholder="学科">
  13. <a-select-option v-for="item in subjectFilter" :key="item.id" :value="item.id">
  14. {{ item.name + ' ( ' + item.levelName + ' )' }}
  15. </a-select-option>
  16. </a-select>
  17. </a-form-item> -->
  18. <a-form-item label="学期" name="semesterId" :rules="rules.semesterId">
  19. <a-select v-model:value="form.semesterId" placeholder="请选择学期" allowClear @change="handleSemesterChange">
  20. <a-select-option v-for="item in semesterList" :key="item.id" :value="item.id">
  21. {{ item.name }}
  22. </a-select-option>
  23. </a-select>
  24. </a-form-item>
  25. <!-- <a-form-item label="专业" name="majorId">
  26. <a-select v-model:value="form.majorId" placeholder="请选择专业" allowClear @change="handleMajorChange">
  27. <a-select-option v-for="item in majorList" :key="item.id" :value="item.id">
  28. {{ item.majorName }}
  29. </a-select-option>
  30. </a-select>
  31. </a-form-item> -->
  32. <a-form-item label="课程" name="courseId" :rules="rules.courseId">
  33. <a-select v-model:value="form.courseId" placeholder="请选择课程" allowClear :disabled="!form.semesterId">
  34. <a-select-option v-for="item in courseList" :key="item.courseId" :value="item.courseId">
  35. {{ item.courseName }}
  36. </a-select-option>
  37. </a-select>
  38. </a-form-item>
  39. <a-form-item label="题库类型:" name="bankType" required>
  40. <a-select v-model:value="form.bankType" placeholder="题库类型">
  41. <a-select-option v-for="item in bankTypeEnum" :key="item.key" :value="item.key">
  42. {{ item.value }}
  43. </a-select-option>
  44. </a-select>
  45. </a-form-item>
  46. <a-form-item label="题干:" name="title" required>
  47. <div v-if="form.title" class="rich-text-preview" v-html="form.title" @click="inputClick(form, 'title')"></div>
  48. <a-input
  49. v-else
  50. v-model:value="form.title"
  51. readonly
  52. placeholder="点击编辑题干内容"
  53. @click="inputClick(form, 'title')"
  54. />
  55. </a-form-item>
  56. <a-form-item label="选项:" required>
  57. <div v-for="(item, index) in form.items" :key="item.prefix" class="question-item-label">
  58. <a-input v-model:value="item.prefix" style="width: 50px; margin-right: 8px" />
  59. <div
  60. v-if="item.content"
  61. class="rich-text-preview question-item-content-input"
  62. style="width: 60%"
  63. v-html="item.content"
  64. @click="inputClick(item, 'content')"
  65. ></div>
  66. <a-input
  67. v-else
  68. v-model:value="item.content"
  69. readonly
  70. placeholder="点击编辑选项内容"
  71. @click="inputClick(item, 'content')"
  72. class="question-item-content-input"
  73. style="width: 60%"
  74. />
  75. <a-button danger size="small" class="question-item-remove" @click="questionItemRemove(index)">删除</a-button>
  76. </div>
  77. </a-form-item>
  78. <a-form-item label="解析:" name="analyze" required>
  79. <div
  80. v-if="form.analyze"
  81. class="rich-text-preview"
  82. v-html="form.analyze"
  83. @click="inputClick(form, 'analyze')"
  84. ></div>
  85. <a-input
  86. v-else
  87. v-model:value="form.analyze"
  88. readonly
  89. placeholder="点击编辑解析内容"
  90. @click="inputClick(form, 'analyze')"
  91. />
  92. </a-form-item>
  93. <a-form-item v-if="form.bankType !== '2'" label="分数:" name="score" required>
  94. <a-input-number v-model:value="form.score" :precision="1" :step="1" :max="100" />
  95. </a-form-item>
  96. <a-form-item label="难度:" required>
  97. <a-rate v-model:value="form.difficult" class="question-item-rate" />
  98. </a-form-item>
  99. <a-form-item label="正确答案:" name="correct" required>
  100. <a-radio-group v-model:value="form.correct">
  101. <a-radio v-for="item in form.items" :value="item.prefix" :key="item.prefix">{{ item.prefix }}</a-radio>
  102. </a-radio-group>
  103. </a-form-item>
  104. <a-form-item>
  105. <a-button type="primary" @click="submitForm" :loading="formLoading">提交</a-button>
  106. <a-button @click="resetForm">重置</a-button>
  107. <a-button type="success" @click="questionItemAdd">添加选项</a-button>
  108. <a-button type="success" @click="showQuestion">预览</a-button>
  109. </a-form-item>
  110. </a-form>
  111. <a-modal
  112. v-model:visible="richEditor.dialogVisible"
  113. width="70%"
  114. :footer="null"
  115. :closable="false"
  116. centered
  117. destroy-on-close
  118. >
  119. <Editor v-model="richEditorContent" :toolbar="toolbar" :height="300" />
  120. <div style="text-align: right; margin-top: 16px">
  121. <a-button type="primary" @click="editorConfirm">确定</a-button>
  122. <a-button @click="richEditor.dialogVisible = false">取消</a-button>
  123. </div>
  124. </a-modal>
  125. <a-modal v-model:visible="questionShow.dialog" width="800px" :footer="null" :bodyStyle="{ padding: '24px' }">
  126. <QuestionShow :qType="questionShow.qType" :question="questionShow.question" :qLoading="questionShow.loading" />
  127. </a-modal>
  128. </div>
  129. </template>
  130. <script setup>
  131. import { ref, reactive, computed, onMounted } from 'vue'
  132. import { message } from 'ant-design-vue'
  133. import { useExamStore } from '@/store/exam'
  134. import tQuestionApi from '@/api/exam/question/tQuestionApi'
  135. import QuestionShow from '../components/Show.vue'
  136. import Editor from '@/components/Editor/index.vue'
  137. import '../style/common.less'
  138. import resourceAuditApi from '@/api/resourceAudit.js'
  139. const bankTypeEnum = computed(() => examStore.getBankTypeEnum)
  140. const toolbar = computed(() => examStore.toolbar)
  141. const examStore = useExamStore()
  142. const props = defineProps({
  143. id: {
  144. type: Number,
  145. default: 0
  146. }
  147. })
  148. const emit = defineEmits(['successful'])
  149. const formRef = ref()
  150. const semesterList = ref([])
  151. const majorList = ref([])
  152. const courseList = ref([])
  153. const form = reactive({
  154. id: null,
  155. questionType: 1,
  156. // gradeLevel: null,
  157. // subjectId: null,
  158. title: '',
  159. items: [
  160. { prefix: 'A', content: '' },
  161. { prefix: 'B', content: '' },
  162. { prefix: 'C', content: '' },
  163. { prefix: 'D', content: '' }
  164. ],
  165. analyze: '',
  166. correct: '',
  167. score: '',
  168. difficult: 0,
  169. bankType: null,
  170. courseId: null,
  171. semesterId: null,
  172. majorId: null
  173. })
  174. const subjectFilter = ref([])
  175. const formLoading = ref(false)
  176. const rules = {
  177. bankType: [{ required: true, message: '请选择题库类型', trigger: 'change' }],
  178. gradeLevel: [{ required: true, message: '请选择年级', trigger: 'change' }],
  179. subjectId: [{ required: true, message: '请选择学科', trigger: 'change' }],
  180. title: [{ required: true, message: '请输入题干', trigger: 'blur' }],
  181. analyze: [{ required: true, message: '请输入解析', trigger: 'blur' }],
  182. score: [{ required: true, message: '请输入分数', trigger: 'blur' }],
  183. correct: [{ required: true, message: '请选择正确答案', trigger: 'change' }],
  184. semesterId: [{ required: true, message: '请选择学期', trigger: 'change' }],
  185. courseId: [{ required: true, message: '请选择课程', trigger: 'change' }]
  186. }
  187. const richEditor = reactive({
  188. dialogVisible: false,
  189. object: null,
  190. parameterName: '',
  191. instance: null
  192. })
  193. const richEditorContent = ref('')
  194. const questionShow = reactive({
  195. qType: 0,
  196. dialog: false,
  197. question: null,
  198. loading: false
  199. })
  200. const levelEnum = computed(() => examStore.levelEnum)
  201. const subjects = computed(() => examStore.subjects)
  202. onMounted(async () => {
  203. await examStore.initSubject()
  204. subjectFilter.value = subjects.value
  205. const id = props.id
  206. if (id && parseInt(id) !== 0) {
  207. formLoading.value = true
  208. tQuestionApi.select(id).then((re) => {
  209. Object.assign(form, re)
  210. formLoading.value = false
  211. // 如果是编辑模式,需要根据已有数据加载相应的课程列表
  212. if (re.semesterId) {
  213. // 直接加载课程列表
  214. loadCourseList()
  215. }
  216. })
  217. }
  218. // 加载学期列表
  219. const semesterLoading = message.loading('正在加载学期列表...', 0)
  220. resourceAuditApi
  221. .semesterDownList()
  222. .then((res) => {
  223. if (res.code === 200) {
  224. semesterList.value = res.data
  225. } else {
  226. message.error('加载学期列表失败:' + (res.msg || '未知错误'))
  227. }
  228. })
  229. .catch((err) => {
  230. message.error('加载学期列表失败:' + (err.message || '网络错误'))
  231. })
  232. .finally(() => {
  233. semesterLoading()
  234. })
  235. // 加载专业
  236. // const majorLoading = message.loading('正在加载专业列表...', 0)
  237. // resourceAuditApi
  238. // .majordownList()
  239. // .then((res) => {
  240. // if (res.code === 200) {
  241. // majorList.value = res.data
  242. // } else {
  243. // message.error('加载专业列表失败:' + (res.msg || '未知错误'))
  244. // }
  245. // })
  246. // .catch((err) => {
  247. // message.error('加载专业列表失败:' + (err.message || '网络错误'))
  248. // })
  249. // .finally(() => {
  250. // majorLoading()
  251. // })
  252. })
  253. // 学期变更处理函数
  254. const handleSemesterChange = (value) => {
  255. form.courseId = null
  256. courseList.value = []
  257. // 如果学期和专业都已选择,则查询课程列表
  258. if (value) {
  259. loadCourseList()
  260. }
  261. }
  262. // 专业变更处理函数
  263. // const handleMajorChange = (value) => {
  264. // form.courseId = null
  265. // courseList.value = []
  266. // // 如果学期和专业都已选择,则查询课程列表
  267. // if (value && form.semesterId) {
  268. // loadCourseList()
  269. // }
  270. // }
  271. // 加载课程列表
  272. const loadCourseList = () => {
  273. // 添加加载状态
  274. const courseLoading = message.loading('正在加载课程列表...', 0)
  275. // 根据选择的学期和专业加载课程列表
  276. resourceAuditApi
  277. .courseAllList({
  278. semesterId: form.semesterId,
  279. majorId: form.majorId
  280. })
  281. .then((res) => {
  282. if (res.code === 200) {
  283. courseList.value = res.data
  284. } else {
  285. message.error('加载课程列表失败:' + (res.msg || '未知错误'))
  286. }
  287. })
  288. .catch((err) => {
  289. message.error('加载课程列表失败:' + (err.message || '网络错误'))
  290. })
  291. .finally(() => {
  292. courseLoading()
  293. })
  294. }
  295. function inputClick(object, parameterName) {
  296. richEditor.object = object
  297. richEditor.parameterName = parameterName
  298. richEditorContent.value = object[parameterName] || ''
  299. richEditor.dialogVisible = true
  300. }
  301. function editorConfirm() {
  302. richEditor.object[richEditor.parameterName] = richEditorContent.value
  303. richEditor.dialogVisible = false
  304. }
  305. function questionItemRemove(index) {
  306. form.items.splice(index, 1)
  307. }
  308. function questionItemAdd() {
  309. const items = form.items
  310. let newLastPrefix
  311. if (items.length > 0) {
  312. let last = items[items.length - 1]
  313. newLastPrefix = String.fromCharCode(last.prefix.charCodeAt() + 1)
  314. } else {
  315. newLastPrefix = 'A'
  316. }
  317. items.push({ prefix: newLastPrefix, content: '' })
  318. }
  319. function submitForm() {
  320. formRef.value.validate().then((valid) => {
  321. if (valid) {
  322. formLoading.value = true
  323. tQuestionApi
  324. .edit(form)
  325. .then((re) => {
  326. emit('successful')
  327. formLoading.value = false
  328. })
  329. .catch(() => {
  330. formLoading.value = false
  331. })
  332. }
  333. })
  334. }
  335. function resetForm() {
  336. const lastId = form.id
  337. formRef.value.resetFields()
  338. Object.assign(form, {
  339. id: null,
  340. questionType: 1,
  341. // gradeLevel: null,
  342. // subjectId: null,
  343. semesterId: null,
  344. majorId: null,
  345. courseId: null,
  346. title: '',
  347. items: [
  348. { prefix: 'A', content: '' },
  349. { prefix: 'B', content: '' },
  350. { prefix: 'C', content: '' },
  351. { prefix: 'D', content: '' }
  352. ],
  353. analyze: '',
  354. correct: '',
  355. score: '',
  356. difficult: 0,
  357. bankType: null
  358. })
  359. form.id = lastId
  360. majorList.value = [] // 清空专业列表
  361. courseList.value = [] // 清空课程列表
  362. }
  363. function levelChange() {
  364. form.subjectId = null
  365. subjectFilter.value = subjects.value.filter((data) => data.level === form.gradeLevel)
  366. }
  367. function showQuestion() {
  368. questionShow.dialog = true
  369. questionShow.qType = form.questionType
  370. questionShow.question = { ...form }
  371. }
  372. </script>
  373. <style lang="less" scoped>
  374. .app-container {
  375. background: #fff;
  376. padding: 24px;
  377. border-radius: 8px;
  378. }
  379. </style>