MarkPaperMarker.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. <template>
  2. <div class="mark-paper-marker">
  3. <div class="marker-header">
  4. <p
  5. class="marker-desc color-danger"
  6. v-if="subjectiveQuestionCount > groupQuestionCount"
  7. >
  8. 本试卷共<span class="mlr-1">{{ questionCount }}</span
  9. >道小题,客观题<span class="mlr-1">{{ objectiveQuestionCount }}</span
  10. >道,主观题<span class="mlr-1">{{ subjectiveQuestionCount }}</span
  11. >道,已经设置<span class="mlr-1">{{ groupQuestionCount }}</span
  12. >道主观题,还有<span class="mlr-1">{{
  13. subjectiveQuestionCount - groupQuestionCount
  14. }}</span
  15. >道主观题未设置评卷员,请继续设置,确保全部主观题均已分配评卷员!
  16. </p>
  17. <p class="marker-desc color-success" v-else>
  18. 本试卷共<span class="mlr-1">{{ questionCount }}</span
  19. >道小题,客观题<span class="mlr-1">{{ objectiveQuestionCount }}</span
  20. >道,主观题<span class="mlr-1">{{ subjectiveQuestionCount }}</span
  21. >道,主观题已全部设置评卷员!
  22. </p>
  23. <el-button v-if="!onlyMarker" type="primary" @click="toAdd"
  24. >新增</el-button
  25. >
  26. </div>
  27. <el-table :data="groupInfo" border>
  28. <el-table-column type="index" width="50"> </el-table-column>
  29. <el-table-column label="评卷员">
  30. <template slot-scope="scope">
  31. <el-tag
  32. v-for="user in scope.row.markerList"
  33. :key="user.id"
  34. class="tag-spin"
  35. size="medium"
  36. >
  37. {{ user.name }}({{ user.orgName }})
  38. </el-tag>
  39. </template>
  40. </el-table-column>
  41. <el-table-column v-if="!onlyMarker" label="评卷方式">
  42. <template slot-scope="scope">
  43. <el-radio-group v-model="scope.row.doubleRate">
  44. <el-radio
  45. v-for="(val, key) in MARK_TYPE"
  46. :key="key"
  47. :label="key * 1"
  48. >{{ val }}</el-radio
  49. >
  50. </el-radio-group>
  51. <span v-if="scope.row.doubleRate === 1">
  52. 仲裁阀值:<el-input-number
  53. v-model="scope.row.arbitrateThreshold"
  54. class="width-80"
  55. size="small"
  56. :min="0"
  57. :max="999999"
  58. :step="0.01"
  59. step-strictly
  60. :controls="false"
  61. >
  62. </el-input-number>
  63. </span>
  64. </template>
  65. </el-table-column>
  66. <el-table-column label="评阅题目">
  67. <template slot-scope="scope">
  68. {{ scope.row.questions | questionsFilter }}
  69. </template>
  70. </el-table-column>
  71. <el-table-column
  72. v-if="!onlyMarker"
  73. label="评卷区"
  74. width="80"
  75. align="center"
  76. >
  77. <template slot-scope="scope">
  78. <i
  79. v-if="scope.row.pictureConfigList.length"
  80. class="el-icon-success color-success"
  81. ></i>
  82. </template>
  83. </el-table-column>
  84. <el-table-column class-name="action-column" label="操作" width="160px">
  85. <template slot-scope="scope">
  86. <el-button class="btn-primary" type="text" @click="toEdit(scope.row)"
  87. >编辑</el-button
  88. >
  89. <el-button
  90. v-if="!onlyMarker"
  91. class="btn-primary"
  92. type="text"
  93. :disabled="!paperList.length"
  94. @click="toSetArea(scope.row)"
  95. >评卷区</el-button
  96. >
  97. <el-button
  98. v-if="!onlyMarker"
  99. class="btn-danger"
  100. type="text"
  101. @click="toDelete(scope.row)"
  102. >删除</el-button
  103. >
  104. </template>
  105. </el-table-column>
  106. </el-table>
  107. <!-- ModifyMarkerQuestion -->
  108. <modify-marker-question
  109. ref="ModifyMarkerQuestion"
  110. :course-code="datas.basicPaperInfo.courseCode"
  111. :instance="curGroupInfo"
  112. :disabled-question-ids="disabledQuestionIds"
  113. :paper-structure="subjectiveQuestionList"
  114. @modified="groupModified"
  115. ></modify-marker-question>
  116. <!-- ModifyMarkArea -->
  117. <modify-mark-area
  118. v-if="!onlyMarker"
  119. ref="ModifyMarkArea"
  120. :base-info="datas.basicPaperInfo"
  121. :group="curGroupInfo"
  122. :paper-list="paperList"
  123. @modified="areaModified"
  124. ></modify-mark-area>
  125. </div>
  126. </template>
  127. <script>
  128. import ModifyMarkerQuestion from "./ModifyMarkerQuestion";
  129. import ModifyMarkArea from "./ModifyMarkArea.vue";
  130. import { examStructureFindJpg, examBindMarker } from "../../api";
  131. import { cardDetail } from "../../../card/api";
  132. export default {
  133. name: "mark-paper-marker",
  134. components: { ModifyMarkerQuestion, ModifyMarkArea },
  135. props: {
  136. datas: {
  137. type: Object,
  138. default() {
  139. return {
  140. basicPaperInfo: {},
  141. paperStructureInfo: [],
  142. groupInfo: [],
  143. };
  144. },
  145. },
  146. onlyMarker: {
  147. type: Boolean,
  148. default: false,
  149. },
  150. },
  151. data() {
  152. return {
  153. questionCount: 0,
  154. groupQuestionCount: 0,
  155. groupInfo: [],
  156. disabledQuestionIds: [],
  157. curGroupInfo: {},
  158. subjectiveQuestionList: [],
  159. subjectiveQuestionCount: 0,
  160. objectiveQuestionCount: 0,
  161. MARK_TYPE: {
  162. 0: "单评",
  163. 1: "双评",
  164. },
  165. paperList: [],
  166. cardPages: [],
  167. };
  168. },
  169. filters: {
  170. questionsFilter(val) {
  171. return val
  172. .map((item) => `${item.mainNumber}-${item.subNumber}`)
  173. .join(",");
  174. },
  175. },
  176. mounted() {
  177. this.initData();
  178. if (!this.onlyMarker) {
  179. this.getPaperList();
  180. this.getCardPages();
  181. }
  182. },
  183. methods: {
  184. async getPaperList() {
  185. this.paperList = [];
  186. const data = await examStructureFindJpg({
  187. examId: this.datas.basicPaperInfo.examId,
  188. courseCode: this.datas.basicPaperInfo.courseCode,
  189. paperNumber: this.datas.basicPaperInfo.paperNumber,
  190. paperType: this.datas.basicPaperInfo.paperType,
  191. });
  192. const papers = data || [];
  193. papers.sort((a, b) => a.index - b.index);
  194. this.paperList = papers.map((paper) => {
  195. return {
  196. imgUrl: paper.path,
  197. areas: [],
  198. };
  199. });
  200. },
  201. async getCardPages() {
  202. const detData = await cardDetail(this.datas.basicPaperInfo.cardId);
  203. const cardContent = JSON.parse(detData.content);
  204. this.cardPages = cardContent.pages;
  205. },
  206. initData() {
  207. this.groupInfo = this.datas.groupInfo.map((item, index) => {
  208. return { ...item, groupNumber: index + 1 };
  209. });
  210. this.questionCount = this.datas.paperStructureInfo.length;
  211. this.updateDisableQuestionIds();
  212. this.subjectiveQuestionList = this.datas.paperStructureInfo.filter(
  213. (item) => item.qType === "subjective"
  214. );
  215. this.subjectiveQuestionCount = this.subjectiveQuestionList.length;
  216. this.objectiveQuestionCount =
  217. this.questionCount - this.subjectiveQuestionCount;
  218. this.$emit("on-ready");
  219. },
  220. updateDisableQuestionIds(filterId) {
  221. let groupInfo = this.groupInfo;
  222. if (filterId)
  223. groupInfo = this.groupInfo.filter((item) => item.id !== filterId);
  224. let disabledQuestionIds = [];
  225. groupInfo.forEach((item) => {
  226. disabledQuestionIds = [
  227. ...disabledQuestionIds,
  228. ...item.questions.map((item) => item.id),
  229. ];
  230. });
  231. this.disabledQuestionIds = disabledQuestionIds;
  232. if (!filterId) this.groupQuestionCount = disabledQuestionIds.length;
  233. },
  234. updateGroupNumber() {
  235. this.groupInfo.forEach((group, index) => {
  236. group.groupNumber = index + 1;
  237. });
  238. },
  239. toAdd() {
  240. this.updateDisableQuestionIds();
  241. if (this.groupQuestionCount === this.subjectiveQuestionCount) {
  242. this.$message.error("当前已经没有主观题目可供设置!");
  243. return;
  244. }
  245. this.curGroupInfo = {
  246. id: this.$randomCode(),
  247. groupNumber: null,
  248. markerList: [],
  249. doubleRate: 0,
  250. arbitrateThreshold: 1,
  251. questions: [],
  252. pictureConfigList: [],
  253. };
  254. this.$refs.ModifyMarkerQuestion.open();
  255. },
  256. toEdit(row) {
  257. this.curGroupInfo = row;
  258. this.updateDisableQuestionIds(row.id);
  259. this.$refs.ModifyMarkerQuestion.open();
  260. },
  261. toSetArea(row) {
  262. this.curGroupInfo = row;
  263. this.$refs.ModifyMarkArea.open();
  264. },
  265. toDelete(row) {
  266. const pos = this.groupInfo.findIndex((item) => item.id === row.id);
  267. this.groupInfo.splice(pos, 1);
  268. this.updateDisableQuestionIds();
  269. this.updateGroupNumber();
  270. },
  271. groupModified(row) {
  272. const pos = this.groupInfo.findIndex((item) => item.id === row.id);
  273. if (!row.pictureConfigList.length) {
  274. row.pictureConfigList = this.autoParsePictureConfigList(row.questions);
  275. }
  276. if (pos === -1) {
  277. this.groupInfo.push(row);
  278. } else {
  279. this.groupInfo.splice(pos, 1, row);
  280. }
  281. this.updateDisableQuestionIds();
  282. this.updateGroupNumber();
  283. if (this.onlyMarker) {
  284. this.updateGroupMarker(row);
  285. }
  286. },
  287. autoParsePictureConfigList(questions) {
  288. if (!questions.length) return [];
  289. let pictureConfigList = [];
  290. const structs = questions.map(
  291. (item) => `${item.mainNumber}_${item.subNumber}`
  292. );
  293. this.cardPages.forEach((page, pindex) => {
  294. page.exchange.answer_area.forEach((area) => {
  295. const [x, y, w, h] = area.area;
  296. const qStruct = `${area.main_number}_${area.sub_number}`;
  297. const pConfig = {
  298. i: pindex + 1,
  299. x,
  300. y,
  301. w,
  302. h,
  303. qStruct,
  304. };
  305. if (typeof area.sub_number === "number") {
  306. if (!structs.includes(qStruct)) return;
  307. pictureConfigList.push(pConfig);
  308. return;
  309. }
  310. // 复合区域处理,比如填空题,多个小题合并为一个区域
  311. if (typeof area.sub_number === "string") {
  312. const areaStructs = area.sub_number
  313. .split(",")
  314. .map((subNumber) => `${area.main_number}_${subNumber}`);
  315. if (
  316. structs.some((struct) => areaStructs.includes(struct)) &&
  317. !pictureConfigList.find((item) => item.qStruct === qStruct)
  318. ) {
  319. pictureConfigList.push(pConfig);
  320. }
  321. }
  322. });
  323. });
  324. pictureConfigList.forEach((item) => {
  325. delete item.qStruct;
  326. });
  327. return pictureConfigList;
  328. },
  329. async updateGroupMarker(group) {
  330. console.log(group);
  331. await examBindMarker({
  332. paperStructureId: this.datas.basicPaperInfo.id,
  333. groupInfo: group,
  334. });
  335. },
  336. areaModified(row) {
  337. const pos = this.groupInfo.findIndex((item) => item.id === row.id);
  338. if (pos === -1) return;
  339. this.groupInfo.splice(pos, 1, row);
  340. },
  341. checkData() {
  342. let errorMessages = [];
  343. if (this.subjectiveQuestionCount > this.groupQuestionCount) {
  344. errorMessages.push("当前还有题目未设置评卷员");
  345. }
  346. this.groupInfo.forEach((item, index) => {
  347. if (item.doubleRate === 1 && !item.arbitrateThreshold) {
  348. errorMessages.push(`序号${index + 1}设置中,仲裁阀值不能为空`);
  349. }
  350. });
  351. if (errorMessages.length) {
  352. this.$message.error(errorMessages.join("。"));
  353. return;
  354. }
  355. this.updateData();
  356. this.$emit("next-step");
  357. },
  358. getData() {
  359. return this.groupInfo.map((item) => {
  360. return { ...item };
  361. });
  362. },
  363. updateData() {
  364. this.$emit("data-change", { groupInfo: this.getData() });
  365. },
  366. },
  367. };
  368. </script>