ModifyMarkGroup.vue 20 KB

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