QuestionManage.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. <template>
  2. <div class="content question-manage">
  3. <!-- 正文信息 -->
  4. <div class="part-box">
  5. <h2 class="part-box-title">题库列表</h2>
  6. <el-form class="part-filter-form" :inline="true" :model="filter">
  7. <el-form-item label="课程名称">
  8. <course-select v-model="filter.courseId" @change="courseChange">
  9. </course-select>
  10. </el-form-item>
  11. <el-form-item label="题型">
  12. <source-detail-select v-model="filter" :course-id="filter.courseId">
  13. </source-detail-select>
  14. </el-form-item>
  15. <el-form-item label="题目内容">
  16. <el-input
  17. v-model="filter.questionBody"
  18. placeholder="题目内容"
  19. ></el-input>
  20. </el-form-item>
  21. <el-form-item label="属性">
  22. <property-tree-select
  23. v-model="filter.questionProperty"
  24. :course-id="filter.courseId"
  25. ></property-tree-select>
  26. </el-form-item>
  27. <el-form-item>
  28. <el-button type="danger" @click="toPage(1)">查询</el-button>
  29. </el-form-item>
  30. </el-form>
  31. <div class="part-box-action">
  32. <div>
  33. <el-button
  34. type="primary"
  35. plain
  36. icon="el-icon-data-analysis"
  37. @click="toStatistics"
  38. >试题统计</el-button
  39. >
  40. <el-button
  41. type="danger"
  42. plain
  43. icon="el-icon-lock"
  44. @click="toSafetySet"
  45. >安全设置</el-button
  46. >
  47. <el-button
  48. type="primary"
  49. plain
  50. icon="el-icon-setting"
  51. @click="toSourceDetailManage"
  52. >题型管理</el-button
  53. >
  54. <el-button
  55. type="danger"
  56. plain
  57. icon="el-icon-delete"
  58. @click="toRecycle"
  59. >回收站</el-button
  60. >
  61. </div>
  62. <div>
  63. <el-button
  64. type="danger"
  65. plain
  66. icon="el-icon-circle-close"
  67. @click="toBatchDelete"
  68. >删除</el-button
  69. >
  70. <el-button
  71. type="primary"
  72. plain
  73. icon="el-icon-folder-opened"
  74. @click="toAddFolder"
  75. >新建文件夹</el-button
  76. >
  77. <el-dropdown>
  78. <el-button type="primary" plain icon="el-icon-circle-plus-outline"
  79. >创建试题</el-button
  80. >
  81. <el-dropdown-menu slot="dropdown" class="action-dropdown">
  82. <el-dropdown-item>
  83. <el-button
  84. size="mini"
  85. type="primary"
  86. plain
  87. @click="toCreateQuestion"
  88. >手动创建</el-button
  89. >
  90. </el-dropdown-item>
  91. <el-dropdown-item>
  92. <el-button
  93. v-if="gptQuestionEnable"
  94. size="mini"
  95. type="primary"
  96. plain
  97. @click="toGPTQuestion"
  98. >AI出题</el-button
  99. >
  100. </el-dropdown-item>
  101. </el-dropdown-menu>
  102. </el-dropdown>
  103. <el-button
  104. type="primary"
  105. plain
  106. icon="el-icon-upload2"
  107. @click="toImportQuestion"
  108. >批量导入</el-button
  109. >
  110. </div>
  111. </div>
  112. </div>
  113. <div class="part-box">
  114. <el-table
  115. v-loading="loading"
  116. element-loading-text="加载中"
  117. :data="questionList"
  118. @selection-change="tableSelectChange"
  119. >
  120. <el-table-column
  121. type="selection"
  122. width="50"
  123. align="center"
  124. ></el-table-column>
  125. <el-table-column label="题干" min-width="200">
  126. <div slot-scope="scope" @click="toViewQuestion(scope.row)">
  127. <rich-text
  128. class="row-question-body"
  129. title="点击查看试题"
  130. :text-json="scope.row.quesBody"
  131. ></rich-text>
  132. </div>
  133. </el-table-column>
  134. <el-table-column label="课程" width="120">
  135. <template slot-scope="scope">
  136. <span>{{ scope.row.course.name }}</span>
  137. </template>
  138. </el-table-column>
  139. <el-table-column label="题型" prop="sourceDetailName" width="100">
  140. </el-table-column>
  141. <el-table-column label="难度" prop="difficulty" width="80">
  142. </el-table-column>
  143. <el-table-column label="使用量" prop="usageAmount" width="80">
  144. </el-table-column>
  145. <el-table-column label="创建人" prop="creator" width="120">
  146. </el-table-column>
  147. <el-table-column label="创建时间" width="170" prop="creationTime">
  148. </el-table-column>
  149. <el-table-column label="操作" width="180" fixed="right">
  150. <template slot-scope="scope">
  151. <div class="operate_left">
  152. <el-button
  153. size="mini"
  154. type="primary"
  155. plain
  156. @click="toEditQuestion(scope.row)"
  157. >编辑</el-button
  158. >
  159. <el-dropdown>
  160. <el-button type="primary" size="mini" plain>
  161. 更多 <i class="el-icon-more el-icon--right"></i>
  162. </el-button>
  163. <el-dropdown-menu slot="dropdown" class="action-dropdown">
  164. <el-dropdown-item>
  165. <el-button
  166. size="mini"
  167. type="primary"
  168. plain
  169. @click="toMoveQuestion(scope.row)"
  170. >移动</el-button
  171. >
  172. </el-dropdown-item>
  173. <el-dropdown-item>
  174. <el-button
  175. size="mini"
  176. type="primary"
  177. plain
  178. @click="toCopyQuestion(scope.row)"
  179. >复制</el-button
  180. >
  181. </el-dropdown-item>
  182. <el-dropdown-item>
  183. <el-button
  184. size="mini"
  185. type="danger"
  186. plain
  187. @click="toDeleteQuestion(scope.row)"
  188. >删除</el-button
  189. >
  190. </el-dropdown-item>
  191. </el-dropdown-menu>
  192. </el-dropdown>
  193. </div>
  194. </template>
  195. </el-table-column>
  196. </el-table>
  197. <div class="part-page">
  198. <el-pagination
  199. :current-page="currentPage"
  200. :page-size="pageSize"
  201. :page-sizes="[10, 20, 50, 100, 200, 300]"
  202. layout="total, sizes, prev, pager, next, jumper"
  203. :total="total"
  204. @current-change="toPage"
  205. @size-change="handleSizeChange"
  206. >
  207. </el-pagination>
  208. </div>
  209. </div>
  210. <!-- QuestionEditDialog -->
  211. <question-edit-dialog
  212. ref="QuestionEditDialog"
  213. :question="curQuestion"
  214. @modified="getList"
  215. ></question-edit-dialog>
  216. <!-- QuestionPreviewDialog -->
  217. <question-preview-dialog
  218. ref="QuestionPreviewDialog"
  219. :question="curQuestion"
  220. ></question-preview-dialog>
  221. <!-- QuestionStatisticsDialog -->
  222. <question-statistics-dialog
  223. ref="QuestionStatisticsDialog"
  224. :course-id="filter.courseId"
  225. ></question-statistics-dialog>
  226. <!-- QuestionSafetySetDialog -->
  227. <question-safety-set-dialog
  228. ref="QuestionSafetySetDialog"
  229. ></question-safety-set-dialog>
  230. <!-- QuestionFolderDialog -->
  231. <question-folder-dialog
  232. ref="QuestionFolderDialog"
  233. :is-edit="false"
  234. @selected="folderSelected"
  235. ></question-folder-dialog>
  236. <!-- QuestionImportDialog -->
  237. <question-import-dialog
  238. ref="QuestionImportDialog"
  239. @modified="questionImported"
  240. ></question-import-dialog>
  241. <!-- FolderQuestionManageDialog -->
  242. <folder-question-manage-dialog
  243. ref="FolderQuestionManageDialog"
  244. ></folder-question-manage-dialog>
  245. <!-- QuestionImportEdit -->
  246. <question-import-edit
  247. ref="QuestionImportEdit"
  248. :data="questionImportData"
  249. @modified="toPage(1)"
  250. ></question-import-edit>
  251. <!-- GptQuestionDialog -->
  252. <gpt-question-dialog
  253. v-if="gptQuestionEnable"
  254. ref="GptQuestionDialog"
  255. :course="{
  256. courseId: curCourse.id,
  257. courseCode: curCourse.code,
  258. courseName: curCourse.name,
  259. }"
  260. @modified="getList"
  261. ></gpt-question-dialog>
  262. </div>
  263. </template>
  264. <script>
  265. import {
  266. questionPageListApi,
  267. deleteQuestionApi,
  268. moveQuestionApi,
  269. copyQuestionApi,
  270. checkGptQuestionEnableApi,
  271. aiQuestionConfirmApi,
  272. } from "../api";
  273. import QuestionStatisticsDialog from "../components/QuestionStatisticsDialog.vue";
  274. import QuestionSafetySetDialog from "../components/QuestionSafetySetDialog.vue";
  275. import QuestionFolderDialog from "../components/QuestionFolderDialog.vue";
  276. import QuestionImportDialog from "../components/QuestionImportDialog.vue";
  277. import QuestionEditDialog from "../components/QuestionEditDialog.vue";
  278. import QuestionPreviewDialog from "../components/QuestionPreviewDialog.vue";
  279. import FolderQuestionManageDialog from "../components/FolderQuestionManageDialog.vue";
  280. import PropertyTreeSelect from "../components/PropertyTreeSelect.vue";
  281. import QuestionImportEdit from "../components/QuestionImportEdit.vue";
  282. import GptQuestionDialog from "../components/GptQuestionDialog.vue";
  283. import { mapActions } from "vuex";
  284. import { USER_SIGNIN } from "../../portal/store/user";
  285. export default {
  286. name: "QuestionMamage",
  287. components: {
  288. QuestionStatisticsDialog,
  289. QuestionSafetySetDialog,
  290. QuestionFolderDialog,
  291. QuestionImportDialog,
  292. QuestionEditDialog,
  293. QuestionPreviewDialog,
  294. FolderQuestionManageDialog,
  295. PropertyTreeSelect,
  296. QuestionImportEdit,
  297. GptQuestionDialog,
  298. },
  299. data() {
  300. return {
  301. filter: {
  302. courseId: "",
  303. questionType: "",
  304. sourceDetailId: "",
  305. questionBody: "",
  306. questionProperty: [],
  307. },
  308. curFolderAction: "",
  309. folderList: [],
  310. questionList: [],
  311. selectedQuestionIds: [],
  312. currentPage: 1,
  313. pageSize: 10,
  314. total: 0,
  315. loading: false,
  316. curQuestion: {},
  317. curFolder: {},
  318. curCourse: {},
  319. curMoveType: "",
  320. questionImportData: {},
  321. gptQuestionEnable: false,
  322. };
  323. },
  324. computed: {
  325. user() {
  326. return this.$store.state.user;
  327. },
  328. },
  329. mounted() {
  330. this.checkGptEnable();
  331. this.toPage(1);
  332. },
  333. methods: {
  334. ...mapActions([USER_SIGNIN]),
  335. async checkGptEnable() {
  336. const res = await checkGptQuestionEnableApi();
  337. this.gptQuestionEnable = res.data;
  338. },
  339. toPage(page) {
  340. this.currentPage = page;
  341. this.getList();
  342. },
  343. async getList() {
  344. this.loading = true;
  345. this.selectedQuestionIds = [];
  346. let data = {
  347. ...this.filter,
  348. pageNumber: this.currentPage,
  349. pageSize: this.pageSize,
  350. };
  351. data.questionProperty = data.questionProperty.join();
  352. const res = await questionPageListApi(data).catch(() => {});
  353. this.loading = false;
  354. if (!res) return;
  355. this.questionList = res.data.content;
  356. this.total = res.data.totalElements;
  357. },
  358. handleSizeChange(val) {
  359. this.pageSize = val;
  360. this.toPage(1);
  361. },
  362. tableSelectChange(selections) {
  363. this.selectedQuestionIds = selections.map((item) => item.id);
  364. },
  365. courseChange(val) {
  366. this.curCourse = val || {};
  367. },
  368. toStatistics() {
  369. if (!this.filter.courseId) {
  370. this.$message.error("请先选择课程!");
  371. return;
  372. }
  373. this.$refs.QuestionStatisticsDialog.open();
  374. },
  375. toSafetySet() {
  376. this.$refs.QuestionSafetySetDialog.open();
  377. },
  378. toSourceDetailManage() {
  379. if (!this.filter.courseId) {
  380. this.$message.error("请先选择课程!");
  381. return;
  382. }
  383. window.sessionStorage.setItem(
  384. "courseInfo",
  385. JSON.stringify(this.curCourse)
  386. );
  387. this.$router.push({
  388. name: "SourceDetailManage",
  389. });
  390. },
  391. toCreateQuestion() {
  392. if (!this.filter.courseId) {
  393. this.$message.error("请先选择课程!");
  394. return;
  395. }
  396. this.curQuestion = {
  397. courseId: this.curCourse.id,
  398. courseCode: this.curCourse.code,
  399. courseName: this.curCourse.name,
  400. };
  401. this.$refs.QuestionEditDialog.open();
  402. },
  403. toImportQuestion() {
  404. this.$refs.QuestionImportDialog.open();
  405. // this.questionImportData = {
  406. // importData: {
  407. // courseId: 9,
  408. // courseName: "化学",
  409. // },
  410. // };
  411. // this.$refs.QuestionImportEdit.open();
  412. },
  413. toViewQuestion(row) {
  414. this.curQuestion = row;
  415. this.$refs.QuestionPreviewDialog.open();
  416. },
  417. toEditQuestion(row) {
  418. this.curQuestion = row;
  419. this.$refs.QuestionEditDialog.open();
  420. },
  421. toMoveQuestion(row) {
  422. this.curQuestion = row;
  423. this.curFolderAction = "move";
  424. this.$refs.QuestionFolderDialog.open();
  425. },
  426. toCopyQuestion(row) {
  427. this.curQuestion = row;
  428. this.curFolderAction = "copy";
  429. this.$refs.QuestionFolderDialog.open();
  430. },
  431. async folderSelected(folder) {
  432. let res = null;
  433. if (this.curFolderAction === "move") {
  434. res = await moveQuestionApi(this.curQuestion.id, folder.id).catch(
  435. () => {}
  436. );
  437. } else {
  438. res = await copyQuestionApi(this.curQuestion.id, folder.id).catch(
  439. () => {}
  440. );
  441. }
  442. if (!res) return;
  443. this.$message.success("操作成功!");
  444. this.getList();
  445. },
  446. async deleteQuestion(ids) {
  447. this.loading = true;
  448. const res = await deleteQuestionApi(ids.join()).catch(() => {});
  449. this.loading = false;
  450. if (!res) return;
  451. this.$notify({
  452. message: "删除成功",
  453. type: "success",
  454. });
  455. this.getList();
  456. },
  457. async toDeleteQuestion(row) {
  458. const confirm = await this.$confirm("确认删除试题吗?", "提示", {
  459. type: "warning",
  460. }).catch(() => {});
  461. if (confirm !== "confirm") return;
  462. this.deleteQuestion([row.id]);
  463. },
  464. async toBatchDelete() {
  465. if (!this.selectedQuestionIds.length) {
  466. this.$message.error("请选择试题!");
  467. return;
  468. }
  469. const confirm = await this.$confirm("确认删除选中试题吗?", "提示", {
  470. type: "warning",
  471. }).catch(() => {});
  472. if (confirm !== "confirm") return;
  473. this.deleteQuestion(this.selectedQuestionIds);
  474. },
  475. toRecycle() {
  476. this.$router.push({
  477. name: "QuestionRecycle",
  478. });
  479. },
  480. toAddFolder() {
  481. this.$refs.FolderQuestionManageDialog.open();
  482. },
  483. questionImported(data) {
  484. if (data.importType !== "word") {
  485. this.getList();
  486. return;
  487. }
  488. this.questionImportData = data;
  489. this.$refs.QuestionImportEdit.open();
  490. },
  491. async toGPTQuestion() {
  492. if (!this.filter.courseId) {
  493. this.$message.error("请先选择课程!");
  494. return;
  495. }
  496. if (!this.user.aiQuestionConfirm) {
  497. const confirm = await this.$confirm(
  498. `请务必仔细阅读本协议内容,若您不同意本协议的任何内容,将无法使用该功能,AI出题功能借助AI工具来辅助出题,提高出题效率,减轻命题老师工作量,并不确保出题的正确性及适用性,请在出题后务必仔细检查核对,以免将有问题的试题加入试卷,用于考试。`,
  499. "提示",
  500. {
  501. type: "warning",
  502. }
  503. ).catch(() => {});
  504. if (confirm !== "confirm") return;
  505. await aiQuestionConfirmApi();
  506. this.USER_SIGNIN(
  507. Object.assign({}, this.user, { aiQuestionConfirm: true })
  508. );
  509. }
  510. this.$refs.GptQuestionDialog.open();
  511. },
  512. },
  513. };
  514. </script>