CardManage.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. <template>
  2. <div
  3. v-loading="loading"
  4. class="card-manage content"
  5. element-loading-text="请稍后..."
  6. >
  7. <div class="part-box">
  8. <h2 class="part-box-title">题卡管理</h2>
  9. <!-- 搜索 -->
  10. <el-form class="part-filter-form" inline :model="searchForm">
  11. <el-form-item label="课程">
  12. <el-select
  13. v-model="searchForm.courseId"
  14. :remote-method="getCourses4Search"
  15. :loading="courseLoading4Search"
  16. remote
  17. filterable
  18. clearable
  19. placeholder="请选择"
  20. @clear="getCourses4Search('')"
  21. >
  22. <el-option
  23. v-for="item in courseList"
  24. :key="item.id"
  25. :label="item.name + ' - ' + item.code"
  26. :value="item.id"
  27. ></el-option>
  28. </el-select>
  29. </el-form-item>
  30. <el-form-item label="命题老师">
  31. <el-input
  32. v-model="searchForm.teacherName"
  33. placeholder="请输入命题老师"
  34. maxlength="20"
  35. />
  36. </el-form-item>
  37. <el-form-item>
  38. <el-button type="danger" @click="handleCurrentChange(1)">
  39. 查询
  40. </el-button>
  41. </el-form-item>
  42. </el-form>
  43. <div class="part-box-action">
  44. <div v-if="!onlyAssignTeacher">
  45. <el-button
  46. type="primary"
  47. plain
  48. icon="icon icon-edit"
  49. @click="toCardHead"
  50. >题卡版头管理
  51. </el-button>
  52. <el-button
  53. type="primary"
  54. plain
  55. icon="icon icon-edit"
  56. @click="toExportPaperStruct"
  57. >导入试卷结构
  58. </el-button>
  59. <el-button
  60. type="danger"
  61. plain
  62. icon="icon icon-delete"
  63. @click="toBatchDelete"
  64. >批量删除
  65. </el-button>
  66. </div>
  67. <div>
  68. <el-button
  69. type="primary"
  70. icon="icon icon-export-white"
  71. @click="toBatchDownload"
  72. >批量下载
  73. </el-button>
  74. </div>
  75. </div>
  76. </div>
  77. <div class="part-box">
  78. <!-- 页面列表 -->
  79. <el-table
  80. ref="table"
  81. :data="tableData"
  82. resizable
  83. @selection-change="selectChange"
  84. >
  85. <el-table-column
  86. type="selection"
  87. width="50"
  88. align="center"
  89. ></el-table-column>
  90. <el-table-column prop="course" label="所属课程"> </el-table-column>
  91. <el-table-column
  92. prop="creationTime"
  93. label="创建时间"
  94. width="170"
  95. ></el-table-column>
  96. <el-table-column prop="creator" label="创建人"></el-table-column>
  97. <el-table-column
  98. prop="submitTime"
  99. width="170"
  100. label="提交时间"
  101. ></el-table-column>
  102. <el-table-column prop="teacher" label="命题老师"></el-table-column>
  103. <el-table-column width="50" label="状态">
  104. <template slot-scope="scope">
  105. <span v-if="scope.row.enable">
  106. <el-tooltip
  107. class="item"
  108. effect="dark"
  109. content="启用"
  110. placement="left"
  111. >
  112. <i class="icon icon-right"></i>
  113. </el-tooltip>
  114. </span>
  115. <span v-else>
  116. <el-tooltip
  117. class="item"
  118. effect="dark"
  119. content="禁用"
  120. placement="left"
  121. >
  122. <i class="icon icon-error"></i>
  123. </el-tooltip>
  124. </span>
  125. </template>
  126. </el-table-column>
  127. <el-table-column width="170" label="操作">
  128. <template slot-scope="scope">
  129. <el-button
  130. v-if="!onlyAssignTeacher"
  131. size="mini"
  132. :type="scope.row.enable ? 'danger' : 'primary'"
  133. plain
  134. @click="toEnable(scope.row)"
  135. >
  136. {{ scope.row.enable ? "禁用" : "启用" }}
  137. </el-button>
  138. <el-dropdown>
  139. <el-button type="primary" plain size="mini">
  140. 更多<i class="el-icon-more el-icon--right"></i>
  141. </el-button>
  142. <el-dropdown-menu slot="dropdown" class="action-dropdown">
  143. <el-dropdown-item v-if="!onlyAssignTeacher">
  144. <el-button
  145. size="mini"
  146. type="primary"
  147. plain
  148. @click="toChangeTeacher(scope.row)"
  149. >编辑任务
  150. </el-button>
  151. </el-dropdown-item>
  152. <el-dropdown-item>
  153. <el-button
  154. size="mini"
  155. type="primary"
  156. plain
  157. @click="toEdit(scope.row)"
  158. >编辑题卡
  159. </el-button>
  160. </el-dropdown-item>
  161. <el-dropdown-item>
  162. <el-button
  163. size="mini"
  164. type="primary"
  165. plain
  166. @click="toView(scope.row)"
  167. >查看题卡
  168. </el-button>
  169. </el-dropdown-item>
  170. <el-dropdown-item v-if="!onlyAssignTeacher">
  171. <el-button
  172. size="mini"
  173. type="danger"
  174. plain
  175. @click="toDelete(scope.row)"
  176. >删除
  177. </el-button>
  178. </el-dropdown-item>
  179. <el-dropdown-item>
  180. <el-button
  181. size="mini"
  182. type="danger"
  183. plain
  184. @click="toDownload([scope.row.id])"
  185. >
  186. 下载题卡
  187. </el-button>
  188. </el-dropdown-item>
  189. </el-dropdown-menu>
  190. </el-dropdown>
  191. </template>
  192. </el-table-column>
  193. </el-table>
  194. <div class="part-page">
  195. <el-pagination
  196. :current-page="currentPage"
  197. :page-size="pageSize"
  198. :page-sizes="[10, 20, 50, 100, 200, 300]"
  199. layout="total, sizes, prev, pager, next, jumper"
  200. :total="total"
  201. @current-change="handleCurrentChange"
  202. @size-change="handleSizeChange"
  203. />
  204. </div>
  205. </div>
  206. <!-- ImportFileDialog -->
  207. <import-file-dialog
  208. ref="ImportFileDialog"
  209. dialog-title="导入试卷结构"
  210. :upload-url="uploadUrl"
  211. :template-url="templateUrl"
  212. @uploaded="batchAutoBuildCard"
  213. ></import-file-dialog>
  214. <!-- ModifyCard -->
  215. <modify-card
  216. ref="ModifyCard"
  217. :instance="curRow"
  218. @modified="search"
  219. ></modify-card>
  220. <!-- ProgressDialog -->
  221. <progress-dialog ref="ProgressDialog" :percentage="buildProgress"
  222. ><p><i class="el-icon-loading"></i>生成题卡中</p></progress-dialog
  223. >
  224. <!-- card-view-frame -->
  225. <div v-if="cardBuildUrl" class="card-build-frame">
  226. <iframe :src="cardBuildUrl" frameborder="0"></iframe>
  227. </div>
  228. </div>
  229. </template>
  230. <script>
  231. import {
  232. cardListApi,
  233. courseQueryApi,
  234. cardDeleteApi,
  235. cardEnableApi,
  236. cardConfigInfos,
  237. cardTemplateDetail,
  238. cardDownloadApi,
  239. } from "../api";
  240. import ImportFileDialog from "@/components/ImportFileDialog.vue";
  241. import ModifyCard from "../components/ModifyCard.vue";
  242. import ProgressDialog from "@/components/ProgressDialog.vue";
  243. import { QUESTION_API } from "@/constants/constants.js";
  244. import { mapState } from "vuex";
  245. import { getPaperStructSimpleStructInfo } from "../autoBuild/paperStruct";
  246. import { downloadByApi } from "@/plugins/download";
  247. export default {
  248. name: "CardManage",
  249. components: { ImportFileDialog, ModifyCard, ProgressDialog },
  250. data() {
  251. return {
  252. courseLoading4Search: false,
  253. loading: false,
  254. courseList: [],
  255. searchForm: {
  256. courseId: "",
  257. teacherName: "",
  258. },
  259. selectedIds: [],
  260. tableData: [],
  261. curRow: {},
  262. currentPage: 1,
  263. pageSize: 10,
  264. total: 10,
  265. downloading: false,
  266. // build task
  267. taskList: [],
  268. curTask: {},
  269. finishTaskList: [],
  270. cardBuildUrl: "",
  271. buildProgress: 0,
  272. // upload
  273. uploadUrl: `${QUESTION_API}/card/structure/import`,
  274. templateUrl: "",
  275. };
  276. },
  277. computed: {
  278. ...mapState({
  279. user: (state) => state.user,
  280. }),
  281. onlyAssignTeacher() {
  282. if (this.isAdmin) {
  283. return false;
  284. } else {
  285. return this.user.roleList.some(
  286. (role) => role.roleCode == "ASSIGN_TEACHER"
  287. );
  288. }
  289. },
  290. },
  291. mounted() {
  292. const cacheInfo = window.sessionStorage.getItem("card-manage");
  293. if (cacheInfo) {
  294. const { currentPage, pageSize, searchForm } = JSON.parse(cacheInfo);
  295. this.searchForm = { ...searchForm };
  296. this.currentPage = currentPage;
  297. this.pageSize = pageSize;
  298. this.handleCurrentChange(this.currentPage);
  299. window.sessionStorage.removeItem("card-manage");
  300. } else {
  301. this.handleCurrentChange(1);
  302. }
  303. this.getCoursesList();
  304. this.templateUrl = `${QUESTION_API}/card/import/template?$key=${this.user.key}&$token=${this.user.token}`;
  305. this.registBuildEmit();
  306. },
  307. beforeDestroy() {
  308. delete window.emitResult;
  309. },
  310. methods: {
  311. getCourses4Search(query) {
  312. this.courseLoading4Search = true;
  313. this.$httpWithMsg
  314. .get(QUESTION_API + "/course/query?name=" + query)
  315. .then((response) => {
  316. this.courseList = response.data;
  317. this.courseLoading4Search = false;
  318. });
  319. },
  320. async getCoursesList() {
  321. const res = await courseQueryApi();
  322. this.courseList = res.data || [];
  323. },
  324. async search() {
  325. if (this.loading) return;
  326. this.loading = true;
  327. const res = await cardListApi({
  328. ...this.searchForm,
  329. pageNumber: this.currentPage,
  330. pageSize: this.pageSize,
  331. }).catch(() => {});
  332. this.loading = false;
  333. if (!res) return;
  334. this.tableData = res.data.content;
  335. this.total = res.data.totalElements;
  336. },
  337. handleCurrentChange(val) {
  338. this.selectedIds = [];
  339. this.currentPage = val;
  340. this.search();
  341. },
  342. handleSizeChange(val) {
  343. this.selectedIds = [];
  344. this.currentPage = 1;
  345. this.pageSize = val;
  346. this.search();
  347. },
  348. selectChange(selections) {
  349. this.selectedIds = selections.map((item) => item.id);
  350. },
  351. toCardHead() {
  352. this.cacheSearchInfo();
  353. this.$router.push({ name: "CardHeadManage" });
  354. },
  355. toExportPaperStruct() {
  356. this.$refs.ImportFileDialog.open();
  357. },
  358. async batchAutoBuildCard(resData) {
  359. if (!resData.data || !resData.data.length) return;
  360. this.finishTaskList = [];
  361. this.curTask = {};
  362. this.taskList = [];
  363. const cardConfig = await this.getCardConfig();
  364. this.taskList = resData.data.map((item) => {
  365. return {
  366. cardId: item.id,
  367. cardConfig,
  368. paperSimpleStruct: getPaperStructSimpleStructInfo(item.structure),
  369. };
  370. });
  371. this.$refs.ProgressDialog.open();
  372. this.startTask();
  373. },
  374. startTask() {
  375. this.curTask = this.taskList.shift();
  376. window.cardData = this.curTask;
  377. const { href } = this.$router.resolve({
  378. name: "CardBuild",
  379. });
  380. this.cardBuildUrl = href + `?t=${Date.now()}`;
  381. // console.log(this.cardBuildUrl);
  382. },
  383. async getCardConfig() {
  384. const res = await cardConfigInfos();
  385. if (!res.data) {
  386. this.$message.error("找不到题卡版头!");
  387. return;
  388. }
  389. const tempRes = await cardTemplateDetail(res.data.cardTemplateId);
  390. if (!tempRes.data) {
  391. this.$message.error("找不到题卡模板!");
  392. return;
  393. }
  394. const config = {
  395. ...res.data,
  396. ...{
  397. pageSize: "A3",
  398. columnNumber: 2,
  399. columnGap: 20,
  400. showForbidArea: true,
  401. showScorePan: false,
  402. templateInfo: JSON.parse(tempRes.data.content),
  403. },
  404. };
  405. return config;
  406. },
  407. registBuildEmit() {
  408. window.emitResult = async (result) => {
  409. console.log(result);
  410. this.finishTaskList.push({ id: this.curTask.cardId, result });
  411. this.buildProgress =
  412. (
  413. (100 * this.finishTaskList.length) /
  414. (this.finishTaskList.length + this.taskList.length)
  415. ).toFixed(2) * 1;
  416. if (this.taskList.length) {
  417. this.cardBuildUrl = null;
  418. this.$nextTick(() => {
  419. this.startTask();
  420. });
  421. } else {
  422. this.$nextTick(() => {
  423. this.$refs.ProgressDialog.cancel();
  424. this.buildProgress = 0;
  425. const successCount = this.finishTaskList.filter(
  426. (item) => item.result
  427. ).length;
  428. delete window.cardData;
  429. const failCount = this.finishTaskList.length - successCount;
  430. this.$notify({
  431. message: `题卡生成完毕,成功${successCount}个,失败${failCount}个`,
  432. type: "success",
  433. });
  434. this.search();
  435. });
  436. }
  437. };
  438. },
  439. toBatchDownload() {
  440. if (!this.selectedIds.length) {
  441. this.$message.error("请选择数据");
  442. return;
  443. }
  444. this.toDownload(this.selectedIds);
  445. },
  446. async toBatchDelete() {
  447. if (!this.selectedIds.length) {
  448. this.$message.error("请选择数据");
  449. return;
  450. }
  451. const confirm = await this.$confirm(
  452. `确定要删除选中的这些数据吗?`,
  453. "提示",
  454. {
  455. type: "warning",
  456. }
  457. ).catch(() => {});
  458. if (confirm !== "confirm") return;
  459. await cardDeleteApi(this.selectedIds);
  460. this.$message.success("删除成功!");
  461. this.deletePageLastItem(this.selectedIds.length);
  462. },
  463. async toEnable(row) {
  464. const action = row.enable ? "禁用" : "启用";
  465. const confirm = await this.$confirm(`确定要${action}该题卡吗?`, "提示", {
  466. type: "warning",
  467. }).catch(() => {});
  468. if (confirm !== "confirm") return;
  469. const enable = !row.enable;
  470. await cardEnableApi({
  471. id: row.id,
  472. enable,
  473. });
  474. row.enable = enable;
  475. this.$message.success("操作成功!");
  476. },
  477. toChangeTeacher(row) {
  478. this.curRow = { ...row, teacherId: row.assignTeacher };
  479. this.$refs.ModifyCard.open();
  480. },
  481. toEdit(row) {
  482. this.cacheSearchInfo();
  483. this.$router.push({
  484. name: "CardEdit",
  485. params: {
  486. idType: "card",
  487. paperOrCardId: row.id,
  488. },
  489. });
  490. },
  491. toView(row) {
  492. const href = this.getRouterPath({
  493. name: "CardPreview",
  494. params: {
  495. viewType: "view",
  496. cardId: row.id,
  497. },
  498. });
  499. window.open(href);
  500. },
  501. async toDelete(row) {
  502. const confirm = await this.$confirm(`确定要删除题卡吗?`, "提示", {
  503. type: "warning",
  504. }).catch(() => {});
  505. if (confirm !== "confirm") return;
  506. await cardDeleteApi([row.id]);
  507. this.$message.success("删除成功!");
  508. this.deletePageLastItem();
  509. },
  510. async toDownload(ids) {
  511. if (this.downloading) return;
  512. this.downloading = true;
  513. const res = await downloadByApi(() => {
  514. return cardDownloadApi(ids);
  515. }).catch((e) => {
  516. this.$message.error(e || "下载失败,请重新尝试!");
  517. });
  518. this.downloading = false;
  519. if (!res) return;
  520. this.$message.success("下载成功!");
  521. },
  522. cacheSearchInfo() {
  523. window.sessionStorage.setItem(
  524. "card-mamange",
  525. JSON.stringify({
  526. searchForm: this.searchForm,
  527. currentPage: this.currentPage,
  528. pageSize: this.pageSize,
  529. })
  530. );
  531. },
  532. },
  533. };
  534. </script>