GptQuestionDialog.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. <template>
  2. <div>
  3. <el-dialog
  4. custom-class="gpt-question-dialog"
  5. :visible.sync="modalIsShow"
  6. :close-on-click-modal="false"
  7. :close-on-press-escape="false"
  8. append-to-body
  9. fullscreen
  10. @opened="dialogOpened"
  11. @close="dialogClose"
  12. >
  13. <div slot="title">
  14. <h2>
  15. AI命题 --<span
  16. >{{ formModel.courseName }}({{ formModel.courseCode }})</span
  17. >
  18. </h2>
  19. </div>
  20. <div class="part-box">
  21. <h2 class="part-box-title">生成试题</h2>
  22. <el-form class="margin-top-20" :model="formModel" label-width="100px">
  23. <el-form-item label="题型">
  24. <el-button
  25. v-for="item in BASE_QUESTION_TYPES"
  26. :key="item.code"
  27. :type="
  28. formModel.questionType === item.code ? 'primary' : 'default'
  29. "
  30. size="small"
  31. @click="switchQuestionType(item.code)"
  32. >{{ item.name }}</el-button
  33. >
  34. </el-form-item>
  35. <el-form-item class="inline-top" label="出题数量">
  36. <el-input-number
  37. v-model="formModel.questionCount"
  38. placeholder="出题数量"
  39. style="width: 150px"
  40. :min="1"
  41. :max="10"
  42. :step="1"
  43. step-strictly
  44. :controls="false"
  45. ></el-input-number>
  46. </el-form-item>
  47. <el-form-item
  48. v-if="IS_SELECTION_QUESTION"
  49. class="inline-top"
  50. label="选项个数"
  51. >
  52. <el-input-number
  53. v-model="formModel.optionCount"
  54. placeholder="选项个数"
  55. style="width: 150px"
  56. :min="1"
  57. :max="10"
  58. :step="1"
  59. step-strictly
  60. :controls="false"
  61. ></el-input-number>
  62. </el-form-item>
  63. <el-form-item
  64. v-if="IS_FILL_QUESTION"
  65. class="inline-top"
  66. label="填空个数"
  67. >
  68. <el-input-number
  69. v-model="formModel.blankCount"
  70. placeholder="填空个数"
  71. style="width: 150px"
  72. :min="1"
  73. :max="5"
  74. :step="1"
  75. step-strictly
  76. :controls="false"
  77. ></el-input-number>
  78. </el-form-item>
  79. <el-form-item label="知识点">
  80. <property-tree-select
  81. ref="PropertyTreeSelect"
  82. class="width-full"
  83. v-model="formModel.propertyIdList"
  84. :course-id="formModel.courseId"
  85. ></property-tree-select>
  86. <br />
  87. <el-input
  88. class="padding-top-6"
  89. v-model="formModel.knowledgeNotes"
  90. placeholder="请录入知识点补充说明"
  91. type="textarea"
  92. maxlength="30"
  93. clearable
  94. ></el-input>
  95. </el-form-item>
  96. <el-form-item style="margin: 0">
  97. <el-button
  98. type="primary"
  99. :loading="hasTaskRunning || loading"
  100. @click="toProduct"
  101. >生成试题</el-button
  102. >
  103. </el-form-item>
  104. </el-form>
  105. </div>
  106. <div class="part-box">
  107. <!-- <div class="part-box-header">
  108. <h2 class="part-box-title">检查试题</h2>
  109. <div>
  110. <el-button
  111. type="danger"
  112. :disabled="loading"
  113. @click="toBatchDeleteQuestion"
  114. >批量删除</el-button
  115. >
  116. <el-button
  117. type="primary"
  118. :disabled="loading"
  119. @click="toSaveQuestion"
  120. >加入题库</el-button
  121. >
  122. </div>
  123. </div> -->
  124. <div class="icon-btn-group">
  125. <svg-btn name="jiarutiku" :disabled="loading" @click="toSaveQuestion"
  126. >加入题库</svg-btn
  127. >
  128. <svg-btn
  129. name="shanchu"
  130. :disabled="loading"
  131. @click="toBatchDeleteQuestion"
  132. >删除</svg-btn
  133. >
  134. </div>
  135. <el-table
  136. v-loading="loading"
  137. ref="table"
  138. element-loading-text="加载中"
  139. :data="questionList"
  140. @selection-change="tableSelectChange"
  141. >
  142. <el-table-column
  143. type="selection"
  144. width="50"
  145. align="center"
  146. ></el-table-column>
  147. <el-table-column label="试题" min-width="200">
  148. <div slot-scope="scope">
  149. <rich-text
  150. class="row-question-body"
  151. title="点击查看试题"
  152. :text-json="scope.row.quesBody"
  153. ></rich-text>
  154. </div>
  155. </el-table-column>
  156. <el-table-column label="题型" prop="questionType" width="100">
  157. <span slot-scope="scope">
  158. {{ scope.row.questionType | questionTypeFilter }}
  159. </span>
  160. </el-table-column>
  161. <el-table-column label="生成时间" prop="creationTime" width="170">
  162. </el-table-column>
  163. <el-table-column label="操作" width="180" fixed="right">
  164. <template slot-scope="scope">
  165. <div class="operate_left">
  166. <el-button
  167. size="mini"
  168. type="primary"
  169. plain
  170. @click="toEditQuestion(scope.row)"
  171. >查看</el-button
  172. >
  173. <el-button
  174. size="mini"
  175. type="danger"
  176. plain
  177. @click="toDeleteQuestion(scope.row)"
  178. >删除</el-button
  179. >
  180. </div>
  181. </template>
  182. </el-table-column>
  183. </el-table>
  184. <div class="part-page">
  185. <el-pagination
  186. :current-page="currentPage"
  187. :page-size="pageSize"
  188. :page-sizes="[10, 20, 50, 100, 200, 300]"
  189. layout="total, sizes, prev, pager, next, jumper"
  190. :total="total"
  191. @current-change="handleCurrentChange"
  192. @size-change="handleSizeChange"
  193. >
  194. </el-pagination>
  195. </div>
  196. </div>
  197. </el-dialog>
  198. <!-- GptQuestionEditDialog -->
  199. <gpt-question-edit-dialog
  200. ref="GptQuestionEditDialog"
  201. :question="curQuestion"
  202. @modified="getList"
  203. ></gpt-question-edit-dialog>
  204. </div>
  205. </template>
  206. <script>
  207. import { BASE_QUESTION_TYPES } from "@/constants/constants";
  208. import PropertyTreeSelect from "./PropertyTreeSelect.vue";
  209. import GptQuestionEditDialog from "./GptQuestionEditDialog.vue";
  210. import {
  211. buildGptQuestionApi,
  212. gptQuestionListApi,
  213. saveGptQuestionApi,
  214. deleteGptQuestionApi,
  215. gptTaskDetailApi,
  216. } from "../api";
  217. import timeMixin from "@/mixins/timeMixin";
  218. import SvgBtn from "@/components/SvgBtn.vue";
  219. const initFormModel = {
  220. courseId: null,
  221. courseCode: "",
  222. courseName: "",
  223. questionType: "SINGLE_ANSWER_QUESTION",
  224. questionCount: 1,
  225. optionCount: 4,
  226. blankCount: 1,
  227. propertyIdList: [],
  228. knowledgeNotes: "",
  229. };
  230. export default {
  231. name: "GptQuestionDialog",
  232. components: { PropertyTreeSelect, GptQuestionEditDialog, SvgBtn },
  233. mixins: [timeMixin],
  234. props: {
  235. course: {
  236. type: Object,
  237. default() {
  238. return {};
  239. },
  240. },
  241. },
  242. data() {
  243. return {
  244. curTaskId: null,
  245. hasTaskRunning: false,
  246. formModel: {
  247. ...initFormModel,
  248. },
  249. searchFormModel: {},
  250. BASE_QUESTION_TYPES,
  251. modalIsShow: false,
  252. loading: false,
  253. questionList: [],
  254. selectedQuestionIds: [],
  255. propertyInfos: [],
  256. currentPage: 1,
  257. pageSize: 10,
  258. total: 0,
  259. curQuestion: {},
  260. };
  261. },
  262. computed: {
  263. IS_SELECTION_QUESTION() {
  264. return ["SINGLE_ANSWER_QUESTION", "MULTIPLE_ANSWER_QUESTION"].includes(
  265. this.formModel.questionType
  266. );
  267. },
  268. IS_FILL_QUESTION() {
  269. return this.formModel.questionType === "FILL_BLANK_QUESTION";
  270. },
  271. },
  272. beforeDestroy() {
  273. this.clearSetTs();
  274. },
  275. methods: {
  276. dialogOpened() {
  277. this.formModel = this.$objAssign(initFormModel, this.course);
  278. this.checkTaskStatus(true);
  279. },
  280. dialogClose() {
  281. this.resetData();
  282. this.clearSetTs();
  283. },
  284. resetData() {
  285. this.questionList = [];
  286. this.selectedQuestionIds = [];
  287. this.propertyInfos = [];
  288. this.curQuestion = {};
  289. this.searchFormModel = {};
  290. this.formModel = { ...initFormModel };
  291. },
  292. cancel() {
  293. this.modalIsShow = false;
  294. },
  295. open() {
  296. this.modalIsShow = true;
  297. },
  298. async checkTaskStatus(isReview) {
  299. this.clearSetTs();
  300. const res = await gptTaskDetailApi({
  301. courseId: this.course.courseId,
  302. taskId: this.curTaskId,
  303. }).catch(() => {});
  304. if (!res || !res.data) return;
  305. this.curTaskId = res.data.id;
  306. if (res.data.status === "FAILED") {
  307. this.hasTaskRunning = false;
  308. if (!isReview) this.$message.error("出题失败,请重新尝试!");
  309. this.handleCurrentChange(1);
  310. return;
  311. }
  312. if (res.data.status === "FINISH") {
  313. this.hasTaskRunning = false;
  314. if (!isReview) this.$message.success("出题成功!");
  315. this.handleCurrentChange(1);
  316. return;
  317. }
  318. this.hasTaskRunning = ["RUNNING", "INIT"].includes(res.data.status);
  319. this.addSetTime(() => {
  320. this.checkTaskStatus();
  321. }, 3 * 1000);
  322. },
  323. async toProduct() {
  324. this.loading = true;
  325. const res = await buildGptQuestionApi({ ...this.formModel }).catch(
  326. () => {}
  327. );
  328. this.loading = false;
  329. if (!res) return;
  330. this.curTaskId = res.data;
  331. this.searchFormModel = { ...this.formModel };
  332. this.hasTaskRunning = true;
  333. this.addSetTime(() => {
  334. this.checkTaskStatus();
  335. }, 3 * 1000);
  336. },
  337. handleCurrentChange(page) {
  338. this.currentPage = page;
  339. this.getList();
  340. },
  341. async getList() {
  342. this.selectedQuestionIds = [];
  343. let data = {
  344. courseId: this.course.courseId,
  345. curPage: this.currentPage,
  346. pageSize: this.pageSize,
  347. };
  348. const res = await gptQuestionListApi(data).catch(() => {});
  349. this.loading = false;
  350. if (!res) return;
  351. this.questionList = res.data.content;
  352. this.total = res.data.totalElements;
  353. },
  354. handleSizeChange(val) {
  355. this.pageSize = val;
  356. this.handleCurrentChange(1);
  357. },
  358. tableSelectChange(selections) {
  359. this.selectedQuestionIds = selections.map((item) => item.id);
  360. },
  361. switchQuestionType(questionType) {
  362. this.formModel.questionType = questionType;
  363. this.$nextTick(() => {
  364. this.questionTypeChange();
  365. });
  366. },
  367. questionTypeChange() {
  368. if (this.IS_FILL_QUESTION) {
  369. this.formModel.optionCount = null;
  370. } else if (this.IS_SELECTION_QUESTION) {
  371. this.formModel.blankCount = null;
  372. } else {
  373. this.formModel.optionCount = null;
  374. this.formModel.blankCount = null;
  375. }
  376. },
  377. toEditQuestion(row) {
  378. const propertyInfos =
  379. !row.propertyIds || !row.propertyIds.length
  380. ? []
  381. : this.$refs.PropertyTreeSelect.getCheckedPropertyInfos(
  382. row.propertyIds
  383. );
  384. this.curQuestion = { ...row, ...this.course, propertyInfos };
  385. this.$refs.GptQuestionEditDialog.open();
  386. },
  387. async toDeleteQuestion(row) {
  388. const confirm = await this.$confirm("确认删除所选试题吗?", "系统通知", {
  389. type: "warning",
  390. }).catch(() => {});
  391. if (confirm !== "confirm") return;
  392. this.deleteQuestion([row.id]);
  393. },
  394. async toBatchDeleteQuestion() {
  395. if (!this.selectedQuestionIds.length) {
  396. this.$message.error("请选择需要删除的试题!");
  397. return;
  398. }
  399. const confirm = await this.$confirm("确认删除所选试题吗?", "系统通知", {
  400. type: "warning",
  401. }).catch(() => {});
  402. if (confirm !== "confirm") return;
  403. this.deleteQuestion(this.selectedQuestionIds);
  404. },
  405. async deleteQuestion(ids) {
  406. this.loading = true;
  407. const res = await deleteGptQuestionApi(ids.join()).catch(() => {});
  408. this.loading = false;
  409. if (!res) return;
  410. this.$notify({
  411. message: "删除成功",
  412. type: "success",
  413. });
  414. this.deletePageLastItem(ids.length);
  415. },
  416. async toSaveQuestion() {
  417. if (this.loading) return;
  418. if (!this.selectedQuestionIds.length) {
  419. this.$message.error("请选择需要保存的试题!");
  420. return;
  421. }
  422. const confirm = await this.$confirm(
  423. "确认保存所选择的试题吗?",
  424. "系统通知",
  425. {
  426. type: "warning",
  427. }
  428. ).catch(() => {});
  429. if (confirm !== "confirm") {
  430. return;
  431. }
  432. if (this.loading) return;
  433. this.loading = true;
  434. const res = await saveGptQuestionApi(
  435. this.selectedQuestionIds.join()
  436. ).catch(() => {});
  437. this.loading = false;
  438. if (!res) return;
  439. this.$message.success("保存成功!");
  440. this.deletePageLastItem(this.selectedQuestionIds.length);
  441. this.$emit("modified");
  442. },
  443. },
  444. };
  445. </script>