ModifyMarkGroup.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. <template>
  2. <el-dialog
  3. class="modify-mark-group modify-marker-question"
  4. :visible.sync="modalIsShow"
  5. append-to-body
  6. top="10vh"
  7. width="900px"
  8. title="分组设置"
  9. :close-on-click-modal="false"
  10. :close-on-press-escape="false"
  11. :show-close="false"
  12. @opened="visibleChange"
  13. >
  14. <!-- <div slot="title"></div> -->
  15. <el-form ref="rowInfoFormRef" :model="rowInfo" :rules="rowInfoRules" inline>
  16. <el-form-item label="评卷方式:" label-width="80px">
  17. <el-radio-group v-model="rowInfo.doubleEnable">
  18. <el-radio :label="false">单评</el-radio>
  19. <el-radio :label="true">双评</el-radio>
  20. </el-radio-group>
  21. </el-form-item>
  22. <br />
  23. <template v-if="rowInfo.doubleEnable">
  24. <el-form-item label="仲裁阀值:" prop="arbitrateThreshold">
  25. <el-input-number
  26. v-model="rowInfo.arbitrateThreshold"
  27. class="width-80"
  28. size="small"
  29. :min="0"
  30. :max="999999"
  31. :step="0.01"
  32. step-strictly
  33. :controls="false"
  34. >
  35. </el-input-number>
  36. </el-form-item>
  37. <el-form-item label="合分策略:" prop="scorePolicy">
  38. <el-select v-model="rowInfo.scorePolicy">
  39. <el-option
  40. v-for="(val, key) in SCORE_POLICY_TYPE"
  41. :key="key"
  42. :label="val"
  43. :value="key"
  44. ></el-option>
  45. </el-select>
  46. </el-form-item>
  47. <el-form-item label="双评比例:" prop="doubleRate">
  48. <el-input-number
  49. v-model="rowInfo.doubleRate"
  50. class="width-80"
  51. size="small"
  52. :min="1"
  53. :max="100"
  54. :step="1"
  55. step-strictly
  56. :controls="false"
  57. >
  58. </el-input-number>
  59. <span style="margin-left: 5px">%</span>
  60. </el-form-item>
  61. </template>
  62. </el-form>
  63. <el-row type="flex" :gutter="10">
  64. <el-col :span="12">
  65. <div class="marker-box">
  66. <div class="user-title">评卷员</div>
  67. <div class="user-search">
  68. <el-input
  69. v-model="filterLabel"
  70. placeholder="请输入评卷员名称"
  71. clearable
  72. size="mini"
  73. prefix-icon="el-icon-search"
  74. @input="labelChange"
  75. ></el-input>
  76. </div>
  77. <div class="user-types">
  78. <div
  79. :class="['user-type', { 'is-active': userType === 'org' }]"
  80. @click="switchUserType('org')"
  81. >
  82. 组织架构
  83. </div>
  84. <div
  85. :class="['user-type', { 'is-active': userType === 'course' }]"
  86. @click="switchUserType('course')"
  87. >
  88. 课程
  89. </div>
  90. </div>
  91. <div class="user-tree">
  92. <el-tree
  93. ref="UserTree"
  94. :data="userTree"
  95. node-key="id"
  96. :default-checked-keys="selectedUserIds"
  97. :props="defaultProps"
  98. default-expand-all
  99. >
  100. <span class="custom-tree-node" slot-scope="{ node, data }">
  101. <el-checkbox
  102. v-if="data.isUser"
  103. v-model="node.checked"
  104. @change="(val) => userChange(val, data)"
  105. >
  106. {{ node.label }}
  107. </el-checkbox>
  108. <span v-else>{{ node.label }}</span>
  109. <div title="全选" @click.stop>
  110. <el-checkbox
  111. v-if="!data.isUser && data.children.length"
  112. v-model="data.selected"
  113. @change="(checked) => selectNodeAll(checked, data)"
  114. ></el-checkbox>
  115. </div>
  116. </span>
  117. </el-tree>
  118. </div>
  119. </div>
  120. </el-col>
  121. <el-col :span="12">
  122. <div class="marker-box marker-box-uq">
  123. <el-form
  124. ref="modalFormRef"
  125. :rules="rules"
  126. :model="{}"
  127. label-width="100px"
  128. label-position="top"
  129. >
  130. <el-form-item prop="users" label="已选评卷员:">
  131. <el-tag
  132. v-for="user in selectedUsers"
  133. :key="user.id"
  134. closable
  135. :disable-transitions="false"
  136. @close="toDeleteUser(user)"
  137. >
  138. {{ user.name }}{{ user.orgName }}
  139. </el-tag>
  140. </el-form-item>
  141. <el-form-item
  142. prop="questions"
  143. :label="canSelectQuestion ? '选择评卷题目:' : '评卷题目:'"
  144. >
  145. <div class="marker-paper-struct">
  146. <div
  147. v-for="mainItem in paperStructs"
  148. :key="mainItem.mainNumber"
  149. class="struct-item"
  150. >
  151. <div class="struct-header box-justify">
  152. <h4>{{ mainItem.mainNumber }}{{ mainItem.mainTitle }}</h4>
  153. <el-checkbox
  154. v-if="
  155. canSelectQuestion &&
  156. mainItem.children &&
  157. mainItem.children.length
  158. "
  159. v-model="mainItem.selected"
  160. title="全选"
  161. @change="
  162. (checked) => selectQuestionAll(checked, mainItem)
  163. "
  164. ></el-checkbox>
  165. </div>
  166. <div
  167. v-if="mainItem.children && mainItem.children.length"
  168. class="struct-questions"
  169. >
  170. <template v-if="canSelectQuestion">
  171. <el-checkbox
  172. v-for="question in mainItem.children"
  173. :key="question.id"
  174. v-model="question.selected"
  175. :disabled="question.disabled"
  176. @change="questionChange"
  177. >
  178. {{ question.subNumber }}
  179. </el-checkbox>
  180. </template>
  181. <template v-else>
  182. <template v-for="question in mainItem.children">
  183. <el-tag v-if="question.selected" :key="question.id">{{
  184. question.subNumber
  185. }}</el-tag>
  186. </template>
  187. </template>
  188. </div>
  189. </div>
  190. </div>
  191. </el-form-item>
  192. </el-form>
  193. </div>
  194. </el-col>
  195. </el-row>
  196. <div class="marker-footer">
  197. <el-button type="primary" @click="confirm">确认</el-button>
  198. <el-button @click="cancel">取消</el-button>
  199. </div>
  200. <div slot="footer"></div>
  201. </el-dialog>
  202. </template>
  203. <script>
  204. import { deepCopy } from "../../../../plugins/utils";
  205. import { organizationList } from "../../../base/api";
  206. import { SCORE_POLICY_TYPE } from "@/constants/enumerate";
  207. export default {
  208. name: "modify-mark-group",
  209. props: {
  210. instance: {
  211. type: Object,
  212. default() {
  213. return {};
  214. },
  215. },
  216. disabledQuestionNos: {
  217. type: Array,
  218. default() {
  219. return [];
  220. },
  221. },
  222. paperStructure: {
  223. type: Array,
  224. default() {
  225. return [];
  226. },
  227. },
  228. courseCode: {
  229. type: String,
  230. default: "",
  231. },
  232. canSelectQuestion: {
  233. type: Boolean,
  234. default: true,
  235. },
  236. },
  237. data() {
  238. const usersValidator = (rule, value, callback) => {
  239. if (!this.selectedUserIds.length) {
  240. callback(new Error("请选择评卷员"));
  241. } else {
  242. callback();
  243. }
  244. };
  245. const questionsValidator = (rule, value, callback) => {
  246. if (!this.selectedQuestionNos.length) {
  247. callback(new Error("请选择试题"));
  248. } else {
  249. callback();
  250. }
  251. };
  252. return {
  253. modalIsShow: false,
  254. SCORE_POLICY_TYPE,
  255. filterLabel: "",
  256. userType: "course",
  257. courseUsers: [],
  258. orgUsers: [],
  259. userTree: [],
  260. userList: [],
  261. selectedUsers: [],
  262. selectedUserIds: [],
  263. selectedQuestions: [],
  264. selectedQuestionNos: [],
  265. paperStructs: [],
  266. defaultProps: {
  267. children: "children",
  268. label: "label",
  269. },
  270. rules: {
  271. users: [
  272. {
  273. required: true,
  274. validator: usersValidator,
  275. trigger: "change",
  276. },
  277. ],
  278. questions: [
  279. {
  280. required: true,
  281. validator: questionsValidator,
  282. trigger: "change",
  283. },
  284. ],
  285. },
  286. rowInfo: {},
  287. rowInfoRules: {
  288. doubleRate: [
  289. {
  290. required: true,
  291. message: "双评比例必填",
  292. trigger: "change",
  293. },
  294. ],
  295. arbitrateThreshold: [
  296. {
  297. required: true,
  298. message: "仲裁阀值必填",
  299. trigger: "change",
  300. },
  301. ],
  302. scorePolicy: [
  303. {
  304. required: true,
  305. message: "合分策略必选",
  306. trigger: "change",
  307. },
  308. ],
  309. },
  310. };
  311. },
  312. mounted() {
  313. this.getOrgData();
  314. },
  315. methods: {
  316. visibleChange() {
  317. this.parseStructs();
  318. this.filterLabel = "";
  319. this.userType = "course";
  320. this.selectedQuestions = this.instance.questions;
  321. this.selectedQuestionNos = this.selectedQuestions.map((item) => item.id);
  322. this.selectedUsers = this.instance.markers.map((item) => {
  323. return { ...item, id: item.userId };
  324. });
  325. this.selectedUserIds = this.instance.markers.map((item) => item.userId);
  326. this.labelChange();
  327. this.rowInfo = this.$objAssign(
  328. {
  329. doubleEnable: false,
  330. doubleRate: null,
  331. arbitrateThreshold: null,
  332. scorePolicy: "AVG",
  333. },
  334. this.instance
  335. );
  336. this.rowInfo.doubleRate = this.rowInfo.doubleRate || undefined;
  337. this.rowInfo.arbitrateThreshold =
  338. this.rowInfo.arbitrateThreshold || undefined;
  339. },
  340. cancel() {
  341. this.modalIsShow = false;
  342. this.$emit("cancel");
  343. },
  344. open() {
  345. this.modalIsShow = true;
  346. },
  347. // user
  348. switchUserType(type) {
  349. this.filterLabel = "";
  350. this.userType = type;
  351. this.userTree =
  352. type === "org" ? deepCopy(this.orgUsers) : deepCopy(this.courseUsers);
  353. this.$refs.UserTree.setCheckedKeys(this.selectedUserIds);
  354. },
  355. async getOrgData() {
  356. let params = {
  357. specialPrivilege: "MARKER",
  358. };
  359. if (this.courseCode) params.courseCode = this.courseCode;
  360. const data = await organizationList(params);
  361. this.parseUserData(data);
  362. this.getUserList();
  363. },
  364. parseUserData(data) {
  365. const parseUser = (list) => {
  366. return list.map((item) => {
  367. // org
  368. let nitem = {
  369. id: item.id,
  370. label: item.name,
  371. isUser: false,
  372. selected: false,
  373. children: [],
  374. };
  375. if (item["children"] && item["children"].length) {
  376. nitem.children = [...nitem.children, ...parseUser(item.children)];
  377. }
  378. // user
  379. if (item["sysUserList"] && item["sysUserList"].length) {
  380. let sysUserList = item.sysUserList;
  381. const users = sysUserList.map((user) => {
  382. const nuser = {
  383. id: user.id,
  384. userId: user.id,
  385. label: user.realName,
  386. name: user.realName,
  387. orgName: item.name,
  388. loginName: user.loginName,
  389. selected: false,
  390. isUser: true,
  391. };
  392. return nuser;
  393. });
  394. nitem.children = [...nitem.children, ...users];
  395. }
  396. if (item["courseUserList"] && item["courseUserList"].length) {
  397. nitem.courseUserList = item.courseUserList.map((user) => {
  398. return {
  399. id: user.id,
  400. userId: user.id,
  401. label: user.realName,
  402. name: user.realName,
  403. orgName: user.orgName,
  404. loginName: user.loginName,
  405. selected: false,
  406. isUser: true,
  407. };
  408. });
  409. }
  410. return nitem;
  411. });
  412. };
  413. this.orgUsers = parseUser(data);
  414. this.userTree = deepCopy(this.orgUsers);
  415. if (this.courseCode && this.orgUsers[0].courseUserList) {
  416. this.courseUsers = deepCopy(this.orgUsers[0].courseUserList);
  417. }
  418. this.getUserList();
  419. },
  420. getUserList() {
  421. let userList = [];
  422. const fetchUser = (users) => {
  423. users.forEach((item) => {
  424. if (item["children"] && item["children"].length) {
  425. fetchUser(item.children);
  426. } else {
  427. if (item.isUser) {
  428. let nitem = { ...item };
  429. nitem.label = `${nitem.name}(${nitem.orgName})`;
  430. userList.push(nitem);
  431. }
  432. }
  433. });
  434. };
  435. fetchUser(this.orgUsers);
  436. this.userList = userList;
  437. },
  438. labelChange() {
  439. if (!this.filterLabel) {
  440. this.switchUserType(this.userType);
  441. } else {
  442. const escapeRegexpString = (value = "") =>
  443. String(value).replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
  444. const reg = new RegExp(escapeRegexpString(this.filterLabel), "i");
  445. if (this.userType === "org") {
  446. this.userTree = this.userList.filter((item) => reg.test(item.name));
  447. } else {
  448. this.userTree = this.courseUsers.filter((item) =>
  449. reg.test(item.name)
  450. );
  451. }
  452. }
  453. this.$refs.UserTree.setCheckedKeys(this.selectedUserIds);
  454. },
  455. selectNodeAll(checked, data) {
  456. let users = [];
  457. const getUserIds = (list) => {
  458. list.forEach((item) => {
  459. item.selected = checked;
  460. if (item.children && item.children.length) {
  461. getUserIds(item.children);
  462. } else {
  463. if (item.isUser) users.push(item);
  464. }
  465. });
  466. };
  467. getUserIds(data.children);
  468. const userIds = users.map((u) => u.id);
  469. const selectedUserIds = this.selectedUsers.map((item) => item.id);
  470. let deleteUserIds = [];
  471. userIds.forEach((userId, uindex) => {
  472. const userPos = selectedUserIds.indexOf(userId);
  473. const includeUser = userPos !== -1;
  474. if (checked) {
  475. if (!includeUser) this.selectedUsers.push(users[uindex]);
  476. } else {
  477. if (includeUser) {
  478. deleteUserIds.push(userId);
  479. }
  480. }
  481. });
  482. this.selectedUsers = this.selectedUsers.filter(
  483. (u) => !deleteUserIds.includes(u.id)
  484. );
  485. this.selectedUserIds = this.selectedUsers.map((item) => item.id);
  486. this.$refs.UserTree.setCheckedKeys(this.selectedUserIds);
  487. this.$refs.modalFormRef.validateField("users");
  488. },
  489. updateSelectedUsersFromUserIds() {
  490. this.selectedUsers = this.userList.filter((user) =>
  491. this.selectedUserIds.includes(user.id)
  492. );
  493. },
  494. userChange(checked, user) {
  495. if (checked) {
  496. this.selectedUsers.push(user);
  497. } else {
  498. this.selectedUsers = this.selectedUsers.filter(
  499. (item) => item.id !== user.id
  500. );
  501. }
  502. this.selectedUserIds = this.selectedUsers.map((item) => item.id);
  503. this.$refs.modalFormRef.validateField("users");
  504. },
  505. toDeleteUser(user) {
  506. const pos = this.selectedUsers.findIndex((item) => item.id === user.id);
  507. this.selectedUsers.splice(pos, 1);
  508. this.selectedUserIds = this.selectedUsers.map((item) => item.id);
  509. this.$refs.UserTree.setCheckedKeys(this.selectedUserIds);
  510. this.$refs.modalFormRef.validateField("users");
  511. },
  512. // question
  513. parseStructs() {
  514. this.selectedQuestionNos = this.instance.questions.map((q) => q.qno);
  515. let paperStructs = [];
  516. let struct = null;
  517. this.paperStructure.forEach((item) => {
  518. if (item.mainFirstSub) {
  519. if (struct) paperStructs.push(struct);
  520. struct = {
  521. mainTitle: item.mainTitle,
  522. mainNumber: item.mainNumber,
  523. selected: false,
  524. children: [],
  525. };
  526. }
  527. struct.children.push({
  528. ...item,
  529. disabled: this.disabledQuestionNos.includes(item.qno),
  530. selected: this.selectedQuestionNos.includes(item.qno),
  531. });
  532. });
  533. if (struct) paperStructs.push(struct);
  534. this.paperStructs = paperStructs;
  535. },
  536. selectQuestionAll(checked, mainItem) {
  537. mainItem.children.forEach((q) => {
  538. if (!q.disabled) q.selected = checked;
  539. });
  540. this.questionChange();
  541. },
  542. updateSelectedQuestions() {
  543. let selectedQuestions = [];
  544. let selectedQuestionNos = [];
  545. this.paperStructs.forEach((s) => {
  546. s.children.forEach((q) => {
  547. if (q.selected && !q.disabled) {
  548. selectedQuestions.push(q);
  549. selectedQuestionNos.push(q.qno);
  550. }
  551. });
  552. });
  553. this.selectedQuestions = selectedQuestions;
  554. this.selectedQuestionNos = selectedQuestionNos;
  555. },
  556. questionChange() {
  557. this.updateSelectedQuestions();
  558. this.$refs.modalFormRef.validateField("questions");
  559. },
  560. getQuestionStruct(questions) {
  561. let structs = questions.map((item) => {
  562. return {
  563. mainNumber: item.mainNumber,
  564. subNumber: item.subNumber,
  565. };
  566. });
  567. structs.sort(
  568. (a, b) => a.mainNumber - b.mainNumber || a.subNumber - b.subNumber
  569. );
  570. return structs
  571. .map((item) => `${item.mainNumber}_${item.subNumber}`)
  572. .join();
  573. },
  574. // confirm
  575. async confirm() {
  576. const valid1 = await this.$refs.rowInfoFormRef.validate().catch(() => {});
  577. const valid2 = await this.$refs.modalFormRef.validate().catch(() => {});
  578. if (!valid1 || !valid2) return;
  579. let datas = Object.assign({}, this.instance, this.rowInfo);
  580. datas.markers = this.selectedUsers.map((item) => {
  581. return {
  582. id: item.id,
  583. userId: item.id,
  584. name: item.name,
  585. loginName: item.loginName,
  586. orgName: item.orgName,
  587. };
  588. });
  589. datas.questions = this.selectedQuestions.map((item) => {
  590. let nitem = { ...item };
  591. delete nitem.disabled;
  592. delete nitem.selected;
  593. return nitem;
  594. });
  595. // 评卷题目变动时,清空评卷区
  596. if (
  597. this.getQuestionStruct(this.instance.questions) !==
  598. this.getQuestionStruct(datas.questions)
  599. ) {
  600. datas.pictureConfigs = [];
  601. }
  602. this.$emit("modified", datas);
  603. this.modalIsShow = false;
  604. },
  605. },
  606. };
  607. </script>