MarkParamStructure.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. <template>
  2. <div class="mark-param-structure">
  3. <div class="part-box part-box-pad">
  4. <p class="tips-info">
  5. 1.请确认展示的试卷结构与提交的试卷、答题卡是否一致?
  6. </p>
  7. <p class="tips-info">2.请补充所有题目的小题分值,并确认试卷总分!</p>
  8. <p class="tips-info tips-error">
  9. 3.开始阅卷后不允许修改试卷结构,请确认清楚后再提交!
  10. </p>
  11. </div>
  12. <div class="part-box part-box-pad mb-0">
  13. <el-table
  14. ref="TableList"
  15. :data="tableData"
  16. border
  17. :row-class-name="getRowClassName"
  18. >
  19. <el-table-column width="50" align="center">
  20. <template slot-scope="scope" v-if="scope.row.mainFirstSub">
  21. <div
  22. :class="[
  23. 'expand-btn',
  24. { 'expand-btn-unexpand': !scope.row.expandSub },
  25. ]"
  26. @click="switchExpandSub(scope.row)"
  27. >
  28. <i
  29. :class="scope.row.expandSub ? 'el-icon-minus' : 'el-icon-plus'"
  30. ></i>
  31. </div>
  32. </template>
  33. </el-table-column>
  34. <template v-if="structureCanEdit">
  35. <el-table-column prop="mainTitle" label="大题名称">
  36. <span slot-scope="scope" v-if="scope.row.mainFirstSub">
  37. <el-input
  38. v-model.trim="scope.row.mainTitle"
  39. size="small"
  40. :maxlength="32"
  41. clearable
  42. @change="mainTitleChange(scope.row)"
  43. ></el-input>
  44. </span>
  45. </el-table-column>
  46. <el-table-column prop="questionType" label="题型" width="120">
  47. <template slot-scope="scope" v-if="scope.row.mainFirstSub">
  48. <el-select
  49. v-model="scope.row.questionType"
  50. placeholder="请选择"
  51. class="width-full"
  52. @change="qTypeChange(scope.row)"
  53. >
  54. <el-option
  55. v-for="item in QUESTION_TYPE_LIST"
  56. :key="item.code"
  57. :value="item.code"
  58. :label="item.name"
  59. >
  60. </el-option>
  61. </el-select>
  62. </template>
  63. </el-table-column>
  64. <el-table-column label="选项个数" width="100">
  65. <template
  66. slot-scope="scope"
  67. v-if="scope.row.questionType <= 2 && scope.row.mainFirstSub"
  68. >
  69. <el-input-number
  70. v-model="scope.row.optionCount"
  71. class="width-full"
  72. size="small"
  73. :min="2"
  74. :max="26"
  75. :step="1"
  76. step-strictly
  77. :controls="false"
  78. @change="optionCountChange(scope.row)"
  79. ></el-input-number>
  80. </template>
  81. </el-table-column>
  82. </template>
  83. <template v-else>
  84. <el-table-column prop="mainTitle" label="大题名称">
  85. <span slot-scope="scope" v-if="scope.row.mainFirstSub">
  86. {{ scope.row.mainTitle }}
  87. </span>
  88. </el-table-column>
  89. <el-table-column label="题型" width="120">
  90. <template slot-scope="scope" v-if="scope.row.mainFirstSub">
  91. {{ questionTypeDict[scope.row.questionType] }}
  92. </template>
  93. </el-table-column>
  94. </template>
  95. <el-table-column label="每题分值" width="100">
  96. <template slot-scope="scope" v-if="scope.row.mainFirstSub">
  97. <el-input-number
  98. v-model="scoresPerTopic[scope.row.mainId]"
  99. class="width-full"
  100. size="small"
  101. :min="0.5"
  102. :max="500"
  103. :step="0.5"
  104. step-strictly
  105. :controls="false"
  106. @change="(val) => scorePerTopicChange(val, scope.row)"
  107. ></el-input-number>
  108. </template>
  109. </el-table-column>
  110. <el-table-column prop="mainNumber" label="大题号" width="80">
  111. <template slot-scope="scope" v-if="scope.row.mainFirstSub">
  112. <span>{{ scope.row.mainNumber }}</span>
  113. </template>
  114. </el-table-column>
  115. <el-table-column
  116. prop="subNumber"
  117. label="小题号"
  118. width="80"
  119. ></el-table-column>
  120. <el-table-column prop="totalScore" label="小题满分" width="105">
  121. <template slot-scope="scope">
  122. <el-input-number
  123. v-model="scope.row.totalScore"
  124. class="width-80"
  125. size="small"
  126. :min="0.5"
  127. :max="500"
  128. :step="0.5"
  129. step-strictly
  130. :controls="false"
  131. ></el-input-number>
  132. </template>
  133. </el-table-column>
  134. <el-table-column
  135. v-if="structureCanEdit"
  136. class-name="action-column"
  137. label="操作"
  138. width="200px"
  139. >
  140. <template slot-scope="scope">
  141. <el-button
  142. class="btn-primary"
  143. type="text"
  144. @click="toAddMain(scope.row)"
  145. >新增大题</el-button
  146. >
  147. <el-button
  148. class="btn-primary"
  149. type="text"
  150. @click="toAddSub(scope.row)"
  151. >新增小题</el-button
  152. >
  153. <el-button
  154. :disabled="tableData.length <= 1"
  155. class="btn-danger"
  156. type="text"
  157. @click="toDeleteSub(scope.row)"
  158. >删除</el-button
  159. >
  160. </template>
  161. </el-table-column>
  162. </el-table>
  163. </div>
  164. <div class="total-info">
  165. 试卷总分:<span>{{ paperTotalScore }}</span
  166. >分
  167. </div>
  168. <div class="mark-footer">
  169. <el-button type="primary" :disabled="loading" @click="submit"
  170. >提交</el-button
  171. >
  172. <el-button @click="cancel">取消</el-button>
  173. </div>
  174. </div>
  175. </template>
  176. <script>
  177. import { calcSum } from "@/plugins/utils";
  178. import { QUESTION_TYPE_LIST } from "@/constants/enumerate";
  179. import { mapState, mapMutations } from "vuex";
  180. import { markStructureSave } from "../../api";
  181. import { omit } from "lodash";
  182. export default {
  183. name: "mark-paper-structure",
  184. data() {
  185. return {
  186. tableData: [],
  187. QUESTION_TYPE_LIST,
  188. questionTypeDict: {},
  189. scoresPerTopic: {},
  190. loading: false,
  191. };
  192. },
  193. computed: {
  194. ...mapState("markParam", [
  195. "basicInfo",
  196. "structureCanEdit",
  197. "paperStructureInfo",
  198. ]),
  199. paperTotalScore() {
  200. return calcSum(this.tableData.map((item) => item.totalScore || 0));
  201. },
  202. },
  203. mounted() {
  204. this.initData();
  205. },
  206. methods: {
  207. ...mapMutations("markParam", ["setPaperStructureInfo"]),
  208. initData() {
  209. let questionTypeDict = {};
  210. QUESTION_TYPE_LIST.forEach((item) => {
  211. questionTypeDict[item.code] = item.name;
  212. });
  213. this.questionTypeDict = questionTypeDict;
  214. let curMainNumber = null;
  215. let curMainId = null;
  216. let scoresPerTopic = {};
  217. this.tableData = this.paperStructureInfo.map((item) => {
  218. let nitem = {
  219. ...item,
  220. key: this.$randomCode(),
  221. mainFirstSub: false,
  222. expandSub: true,
  223. };
  224. if (curMainNumber !== item.mainNumber) {
  225. curMainNumber = item.mainNumber;
  226. curMainId = this.$randomCode();
  227. scoresPerTopic[curMainNumber] = undefined;
  228. nitem.mainFirstSub = true;
  229. }
  230. nitem.mainId = curMainId;
  231. return nitem;
  232. });
  233. this.scoresPerTopic = scoresPerTopic;
  234. if (!this.tableData.length && this.structureCanEdit) {
  235. this.createMain();
  236. }
  237. },
  238. getNewRow(val) {
  239. return this.$objAssign(
  240. {
  241. id: null,
  242. key: this.$randomCode(),
  243. mainId: null,
  244. isObjective: true,
  245. mainNumber: 1,
  246. subNumber: 1,
  247. mainTitle: "",
  248. answer: "",
  249. totalScore: undefined,
  250. intervalScore: undefined,
  251. objectivePolicy: null,
  252. questionType: null,
  253. optionCount: undefined,
  254. mainFirstSub: true,
  255. expandSub: true,
  256. },
  257. val
  258. );
  259. },
  260. createMain() {
  261. this.tableData.push(this.getNewRow({}));
  262. },
  263. getRowClassName({ row }) {
  264. let classNames = [];
  265. if (row.mainFirstSub) {
  266. classNames.push("row-main-first-sub");
  267. }
  268. if (!row.mainFirstSub && !row.expandSub) {
  269. classNames.push("row-unexpand-sub");
  270. }
  271. return classNames.join(" ");
  272. },
  273. getNextMainStartPos(startPos, curMainId) {
  274. let nextMainStartPos = null;
  275. for (let i = startPos, len = this.tableData.length; i < len; i++) {
  276. const element = this.tableData[i];
  277. if (element.mainId !== curMainId) {
  278. nextMainStartPos = i;
  279. return nextMainStartPos;
  280. }
  281. }
  282. if (nextMainStartPos === null) return this.tableData.length;
  283. },
  284. toAddMain(row) {
  285. const startPos = this.tableData.findIndex((item) => item.id === row.id);
  286. let nextMainStartPos = this.getNextMainStartPos(startPos, row.mainId);
  287. this.tableData.splice(
  288. nextMainStartPos,
  289. 0,
  290. this.getNewRow({
  291. mainId: this.$randomCode(),
  292. mainNumber: row.mainNumber + 1,
  293. totalScore: row.totalScore,
  294. questionType: row.questionType,
  295. })
  296. );
  297. this.updateMainData();
  298. },
  299. updateMainData() {
  300. let curMainNumber = 0,
  301. curMainId = null;
  302. this.tableData.forEach((item) => {
  303. if (item.mainId !== curMainId) {
  304. curMainId = item.mainId;
  305. curMainNumber++;
  306. }
  307. item.mainNumber = curMainNumber;
  308. });
  309. },
  310. toAddSub(row) {
  311. const subPos = this.tableData.findIndex((item) => item.id === row.id);
  312. this.tableData.splice(
  313. subPos + 1,
  314. 0,
  315. this.getNewRow({
  316. ...row,
  317. mainFirstSub: false,
  318. answer: "",
  319. key: this.$randomCode(),
  320. })
  321. );
  322. this.updateSubData(row.mainId);
  323. },
  324. updateSubData(mainId) {
  325. this.tableData
  326. .filter((item) => item.mainId === mainId)
  327. .forEach((item, index) => {
  328. item.subNumber = index + 1;
  329. });
  330. },
  331. toDeleteSub(row) {
  332. const subPos = this.tableData.findIndex((item) => item.id === row.id);
  333. this.tableData.splice(subPos, 1);
  334. this.tableData
  335. .filter((item) => item.mainId === row.mainId)
  336. .forEach((item, index) => {
  337. item.mainFirstSub = !index;
  338. });
  339. this.updateSubData(row.mainId);
  340. this.updateMainData();
  341. },
  342. switchExpandSub(row) {
  343. row.expandSub = !row.expandSub;
  344. this.tableData
  345. .filter((item) => item.mainId === row.mainId && !item.mainFirstSub)
  346. .forEach((item) => (item.expandSub = row.expandSub));
  347. },
  348. mainTitleChange(row) {
  349. this.tableData
  350. .filter((item) => item.mainId === row.mainId && !item.mainFirstSub)
  351. .forEach((item) => (item.mainTitle = row.mainTitle));
  352. },
  353. qTypeChange(row) {
  354. const curQt = this.QUESTION_TYPE_LIST.find(
  355. (item) => item.code === row.type
  356. );
  357. if (!curQt) return;
  358. this.tableData
  359. .filter((item) => item.mainId === row.mainId)
  360. .forEach((item) => {
  361. item.questionType = curQt.code;
  362. item.isObjective = curQt.qType === "objective";
  363. item.optionCount = curQt.optionCount;
  364. });
  365. },
  366. optionCountChange(row) {
  367. if (!row.optionCount) return;
  368. this.tableData
  369. .filter((item) => item.mainId === row.mainId && !item.mainFirstSub)
  370. .forEach((item) => {
  371. item.optionCount = row.optionCount;
  372. });
  373. },
  374. scorePerTopicChange(val, row) {
  375. if (!val) return;
  376. this.tableData
  377. .filter((item) => item.mainId === row.mainId)
  378. .forEach((item) => {
  379. item.totalScore = val;
  380. });
  381. },
  382. checkData() {
  383. let errorMessages = [];
  384. this.tableData.forEach((item) => {
  385. let errorMsg = ``;
  386. if (item.mainFirstSub) {
  387. let errorFields = [];
  388. if (!item.mainTitle) {
  389. errorFields.push("大题名称");
  390. }
  391. if (!item.mainNumber) {
  392. errorFields.push("大题号");
  393. }
  394. if (!item.optionCount && item.questionType <= 2) {
  395. errorFields.push("选项个数");
  396. }
  397. if (errorFields.length) {
  398. errorMsg += `${errorFields.join("、")}不能为空,`;
  399. }
  400. }
  401. let errorFields = [];
  402. if (!item.subNumber) {
  403. errorFields.push("小题号");
  404. }
  405. if (!item.totalScore) {
  406. errorFields.push("小题满分");
  407. }
  408. if (!item.intervalScore) {
  409. errorFields.push("评卷间隔分");
  410. }
  411. if (errorFields.length) {
  412. errorMsg += `第${item.subNumber}小题,${errorFields.join(
  413. "、"
  414. )}不能为空,`;
  415. }
  416. if (errorMsg) {
  417. errorMsg = `第${item.mainNumber}大题,${errorMsg}`;
  418. errorMessages.push(errorMsg);
  419. }
  420. });
  421. if (errorMessages.length) {
  422. this.$message.error(errorMessages.join("。"));
  423. return;
  424. }
  425. return true;
  426. },
  427. getData() {
  428. return this.tableData.map((item) => {
  429. return omit(item, ["key", "mainId", "expandSub"]);
  430. });
  431. },
  432. async submit() {
  433. if (this.loading) return;
  434. if (!this.checkData()) return;
  435. this.loading = true;
  436. const questions = this.getData();
  437. const res = await markStructureSave({
  438. examId: this.basicInfo.examId,
  439. paperNumber: this.basicInfo.paperNumber,
  440. questions,
  441. }).catch(() => {});
  442. this.loading = false;
  443. if (!res) return;
  444. this.$message.success("保存成功!");
  445. this.setPaperStructureInfo(questions);
  446. this.$emit("confirm");
  447. },
  448. cancel() {
  449. this.$emit("cancel");
  450. },
  451. },
  452. };
  453. </script>