DetailTargetStatistics.vue 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151
  1. <template>
  2. <el-dialog
  3. class="page-dialog"
  4. :visible.sync="modalIsShow"
  5. :close-on-click-modal="false"
  6. :close-on-press-escape="false"
  7. :show-close="false"
  8. append-to-body
  9. fullscreen
  10. @opened="dialogOpend"
  11. @close="dialogClose"
  12. >
  13. <div slot="title" class="box-justify">
  14. <div>
  15. <span>{{ course.courseName }}</span>
  16. </div>
  17. <div>
  18. <el-button
  19. type="primary"
  20. :loading="downloading"
  21. icon="el-icon-download"
  22. @click="toExportNormalScore"
  23. >导出平时成绩</el-button
  24. >
  25. <el-button
  26. type="primary"
  27. :loading="downloading"
  28. icon="el-icon-download"
  29. @click="toExport"
  30. >导出报告</el-button
  31. >
  32. <el-button
  33. type="primary"
  34. :loading="downloading"
  35. icon="el-icon-document-checked"
  36. @click="toSave"
  37. >保存报告</el-button
  38. >
  39. <el-button class="btn-back" @click="cancel">
  40. 返回<i class="el-icon-arrow-right"></i>
  41. </el-button>
  42. </div>
  43. </div>
  44. <div v-if="modalIsShow">
  45. <!-- base info -->
  46. <div class="page-head">
  47. <h2>
  48. <span>课程基本情况</span>
  49. </h2>
  50. </div>
  51. <div class="part-box part-box-pad">
  52. <table class="table table-tiny">
  53. <colgroup>
  54. <col width="100" />
  55. <col />
  56. <col width="100" />
  57. <col />
  58. <col width="110" />
  59. <col />
  60. <col width="100" />
  61. <col />
  62. </colgroup>
  63. <tr>
  64. <td class="td-bg">课程名称</td>
  65. <td>{{ commonInfo.courseName }}</td>
  66. <td class="td-bg">课程代码</td>
  67. <td>{{ commonInfo.courseCode }}</td>
  68. <td class="td-bg">课程英文名称</td>
  69. <td>
  70. <el-input
  71. v-model.trim="courseBasicInfo.courseEnName"
  72. placeholder="请填写"
  73. size="small"
  74. :maxlength="50"
  75. ></el-input>
  76. </td>
  77. <td class="td-bg">学分</td>
  78. <td>
  79. <el-input-number
  80. v-model="courseBasicInfo.credit"
  81. placeholder="请录入学分"
  82. size="small"
  83. :min="0"
  84. :max="999"
  85. :controls="false"
  86. style="width: 100px"
  87. ></el-input-number>
  88. </td>
  89. </tr>
  90. <tr>
  91. <td class="td-bg">课程性质</td>
  92. <td>
  93. <el-input
  94. v-model.trim="courseBasicInfo.courseType"
  95. placeholder="请填写"
  96. size="small"
  97. :maxlength="50"
  98. ></el-input>
  99. </td>
  100. <td class="td-bg">开课学院</td>
  101. <td>{{ courseBasicInfo.college }}</td>
  102. <td class="td-bg">开课专业</td>
  103. <td>{{ courseBasicInfo.profession }}</td>
  104. <td class="td-bg">学时</td>
  105. <td>
  106. <el-input-number
  107. v-model="courseBasicInfo.period"
  108. placeholder="请填写"
  109. :min="0"
  110. :max="999"
  111. :controls="false"
  112. size="small"
  113. style="width: 100px"
  114. ></el-input-number>
  115. </td>
  116. </tr>
  117. <tr>
  118. <td class="td-bg">开课班级</td>
  119. <td>
  120. <el-input
  121. v-model.trim="courseBasicInfo.teachingObject"
  122. placeholder="请填写"
  123. size="small"
  124. :maxlength="50"
  125. ></el-input>
  126. </td>
  127. <td class="td-bg">开课学期</td>
  128. <td>{{ courseBasicInfo.openTime }}</td>
  129. <td class="td-bg">学生人数</td>
  130. <td>{{ courseBasicInfo.participantCount }}</td>
  131. <td class="td-bg">期望值</td>
  132. <td>
  133. {{ courseBasicInfo.courseDegree }}
  134. </td>
  135. </tr>
  136. <tr>
  137. <td class="td-bg">任课老师</td>
  138. <td>
  139. <el-input
  140. v-model.trim="courseBasicInfo.teacher"
  141. placeholder="请填写"
  142. size="small"
  143. :maxlength="50"
  144. ></el-input>
  145. </td>
  146. <td class="td-bg">评价人</td>
  147. <td colspan="5">
  148. <el-input
  149. v-model.trim="courseBasicInfo.director"
  150. placeholder="请填写评价负责人姓名"
  151. size="small"
  152. :maxlength="50"
  153. ></el-input>
  154. </td>
  155. </tr>
  156. </table>
  157. </div>
  158. <div class="page-head">
  159. <h2>
  160. <span>课程目标考核分布</span>
  161. <el-popover
  162. popper-class="el-popper-dark"
  163. placement="right-start"
  164. width="500"
  165. trigger="hover"
  166. >
  167. <i class="el-icon-question" slot="reference"></i>
  168. <div>
  169. <p>
  170. 课程目标分布会自动按题库系统里试卷下试题标记的属性内容进行显示
  171. </p>
  172. </div>
  173. </el-popover>
  174. </h2>
  175. </div>
  176. <div class="part-box part-box-pad">
  177. <el-table :data="questionInfoTable">
  178. <el-table-column
  179. label="试题号"
  180. prop="name"
  181. width="200"
  182. align="center"
  183. fixed="left"
  184. ></el-table-column>
  185. <el-table-column
  186. label="合计"
  187. prop="totalScore"
  188. width="60"
  189. align="center"
  190. fixed="left"
  191. ></el-table-column>
  192. <el-table-column
  193. v-for="struct in paperStructs"
  194. :key="struct.mainNumber"
  195. :label="struct.mainNumber"
  196. align="center"
  197. >
  198. <el-table-column
  199. v-for="subNumber in struct.subNumbers"
  200. :key="subNumber"
  201. :label="subNumber"
  202. :prop="`${struct.mainNumber}_${subNumber}`"
  203. align="center"
  204. min-width="50"
  205. >
  206. </el-table-column>
  207. </el-table-column>
  208. </el-table>
  209. <div class="chart-box" style="height: 300px; margin-top: 20px">
  210. <v-chart
  211. v-if="questionInfoChartOption"
  212. :option="questionInfoChartOption"
  213. ></v-chart>
  214. </div>
  215. </div>
  216. <div class="page-head">
  217. <h2>
  218. <span>课程目标达成评价结果</span>
  219. <el-popover
  220. popper-class="el-popper-dark"
  221. placement="right-start"
  222. width="500"
  223. trigger="hover"
  224. >
  225. <i class="el-icon-question" slot="reference"></i>
  226. <div>
  227. <p>1.评价依据:填写通过考核什么内容实现课程目标的评价;</p>
  228. <p>2.目标分值:对应考核环节的满分;</p>
  229. <p>
  230. 3.权重:对应考核环节在对应的课程目标中的权重,目标下各项权重相加等于1;
  231. </p>
  232. <p>4.实际平均分:为参与评价的学生在该环节的平均分;</p>
  233. <p>5.整体课程目标达成评价值:为课程分目标达成评价值的最小值。</p>
  234. </div>
  235. </el-popover>
  236. </h2>
  237. </div>
  238. <div class="part-box part-box-pad">
  239. <h4 class="part-title mb-2">课程考核成绩评价结果</h4>
  240. <table class="table">
  241. <tr class="td-bg">
  242. <th>课程目标</th>
  243. <th>评价依据</th>
  244. <th>评价环节</th>
  245. <th>权重</th>
  246. <th>目标分值</th>
  247. <th>实际平均分</th>
  248. <th>目标达成评价值</th>
  249. </tr>
  250. <template v-for="item in courseTargetList">
  251. <tr
  252. v-for="(evaluation, eindex) in item.evaluationList"
  253. :key="`${item.targetId}-${eindex}`"
  254. >
  255. <!-- 课程目标 -->
  256. <td
  257. v-if="!eindex"
  258. :rowspan="item.evaluationList.length"
  259. style="width: 240px"
  260. >
  261. {{ item.targetName }}
  262. </td>
  263. <!-- 评价依据 -->
  264. <td v-if="!eindex" :rowspan="item.evaluationList.length">
  265. <p
  266. v-for="edesc in item.graduationRequirementPoint.split(',')"
  267. :key="edesc"
  268. >
  269. {{ edesc }}
  270. </p>
  271. </td>
  272. <!-- 评价环节 -->
  273. <td style="width: 140px">{{ evaluation.evaluation }}</td>
  274. <!-- 权重 -->
  275. <td style="width: 80px">
  276. {{ evaluation.targetWeight }}
  277. </td>
  278. <!-- 目标分值 -->
  279. <td style="width: 100px">{{ evaluation.targetScore }}</td>
  280. <!-- 实际平均分 -->
  281. <td style="width: 100px">{{ evaluation.targetAvgScore }}</td>
  282. <!-- 目标达成评价值 -->
  283. <td
  284. v-if="!eindex"
  285. :rowspan="item.evaluationList.length"
  286. style="width: 140px"
  287. >
  288. {{ item.evaluationValue }}
  289. </td>
  290. </tr>
  291. </template>
  292. <tr>
  293. <td colspan="6">课程总目标</td>
  294. <td>{{ courseTargetValue }}</td>
  295. </tr>
  296. </table>
  297. <div class="chart-box" style="height: 400px; margin-top: 20px">
  298. <v-chart
  299. v-if="courseTargetListChartOption"
  300. :option="courseTargetListChartOption"
  301. ></v-chart>
  302. </div>
  303. <h4 class="part-title mb-2">课程考核成绩评价明细结果</h4>
  304. <el-table
  305. :data="studentScoreTable"
  306. :span-method="studentScoreSpanMethod"
  307. >
  308. <el-table-column
  309. label="姓名"
  310. prop="name"
  311. min-width="120"
  312. align="center"
  313. fixed="left"
  314. ></el-table-column>
  315. <el-table-column
  316. label="学号"
  317. prop="studentCode"
  318. width="160"
  319. align="center"
  320. fixed="left"
  321. ></el-table-column>
  322. <el-table-column
  323. v-for="(target, tindex) in courseTargets"
  324. :key="tindex"
  325. :label="target.targetName"
  326. align="center"
  327. >
  328. <el-table-column
  329. v-if="target.finalDimensions.length"
  330. :label="`期末成绩(${target.finalWeight}%)`"
  331. :prop="`${target.targetId}-final`"
  332. width="140"
  333. align="center"
  334. >
  335. </el-table-column>
  336. <el-table-column
  337. v-for="work in target.usualWorks"
  338. :key="`${target.targetId}${work.evaluation}`"
  339. :label="`${work.evaluation}(${work.targetWeight}%)`"
  340. :prop="`${target.targetId}-usual-${work.evaluation}`"
  341. min-width="100"
  342. align="center"
  343. >
  344. </el-table-column>
  345. </el-table-column>
  346. <el-table-column
  347. label="综合成绩"
  348. prop="score"
  349. align="center"
  350. width="100"
  351. ></el-table-column>
  352. </el-table>
  353. </div>
  354. <div class="page-head">
  355. <h2>
  356. <span> 课程目标达成度评价结果分析及改进措施 </span>
  357. </h2>
  358. </div>
  359. <div class="part-box part-box-pad">
  360. <div
  361. v-for="item in courseSuggest"
  362. :key="item.targetId"
  363. class="target-suggest mb-4"
  364. >
  365. <h4 class="part-title">{{ item.targetName }}</h4>
  366. <div class="chart-box" style="height: 400px; margin-top: 20px">
  367. <v-chart
  368. v-if="item.chartOption"
  369. :option="item.chartOption"
  370. ></v-chart>
  371. </div>
  372. <el-form label-position="top">
  373. <el-form-item label="达成情况">
  374. <el-input
  375. v-model="item.finishPoints"
  376. type="textarea"
  377. :autosize="{ minRows: 2, maxRows: 5 }"
  378. resize="none"
  379. placeholder="请输入"
  380. clearable
  381. maxlength="999"
  382. show-word-limit
  383. ></el-input>
  384. </el-form-item>
  385. <el-form-item label="课程支撑毕业要求达成情况评价">
  386. <el-input
  387. v-model="item.requirementPoints"
  388. type="textarea"
  389. :autosize="{ minRows: 2, maxRows: 5 }"
  390. resize="none"
  391. placeholder="请输入"
  392. clearable
  393. maxlength="999"
  394. show-word-limit
  395. ></el-input>
  396. </el-form-item>
  397. <el-form-item label="课程持续改进">
  398. <el-input
  399. v-model="item.courseSuggest"
  400. type="textarea"
  401. :autosize="{ minRows: 2, maxRows: 5 }"
  402. resize="none"
  403. placeholder="请输入"
  404. clearable
  405. maxlength="999"
  406. show-word-limit
  407. ></el-input>
  408. </el-form-item>
  409. </el-form>
  410. </div>
  411. </div>
  412. </div>
  413. <div slot="footer"></div>
  414. </el-dialog>
  415. </template>
  416. <script>
  417. // import { reportData } from "./data";
  418. import {
  419. targetStatisticsDetail,
  420. targetStatisticsReport,
  421. targetStatisticsNormalScoreExport,
  422. targetStatisticsSave,
  423. targetStatisticsChangeCheck,
  424. } from "../../api";
  425. import { downloadByApi } from "@/plugins/download";
  426. import { calcSum } from "@/plugins/utils";
  427. import timeMixin from "@/mixins/timeMixin";
  428. export default {
  429. name: "detail-target-statistics",
  430. mixins: [timeMixin],
  431. props: {
  432. course: {
  433. type: Object,
  434. default() {
  435. return {};
  436. },
  437. },
  438. },
  439. filters: {
  440. percentFilter(val) {
  441. const num = val || 0;
  442. const perc = num % 10 ? 2 : 1;
  443. return ((num || 0) / 100).toFixed(perc);
  444. },
  445. },
  446. data() {
  447. return {
  448. modalIsShow: false,
  449. expectancyList: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
  450. modalForm: {},
  451. commonInfo: {},
  452. courseBasicInfo: {},
  453. paperStructs: [],
  454. questionInfoTable: [],
  455. courseTargetList: [],
  456. courseTargetValue: 0.67,
  457. questionInfoChartOption: null,
  458. courseTargetListChartOption: null,
  459. courseTargets: [],
  460. targetColumnCounts: [],
  461. studentScoreTable: [],
  462. courseSuggest: [],
  463. downloading: false,
  464. };
  465. },
  466. beforeDestroy() {
  467. this.clearSetTs();
  468. },
  469. methods: {
  470. cancel() {
  471. this.modalIsShow = false;
  472. },
  473. open() {
  474. this.modalIsShow = true;
  475. },
  476. dialogClose() {
  477. this.clearSetTs();
  478. },
  479. dialogOpend() {
  480. this.initData();
  481. this.openAutoSave();
  482. },
  483. openAutoSave() {
  484. if (!this.modalIsShow) return;
  485. this.clearSetTs();
  486. this.addSetTime(async () => {
  487. await this.toSave(true);
  488. this.openAutoSave();
  489. }, 1 * 60 * 1000);
  490. },
  491. resetData() {
  492. this.commonInfo = {};
  493. this.courseBasicInfo = {};
  494. this.paperStructs = [];
  495. this.questionInfoTable = [];
  496. this.courseTargetList = [];
  497. this.courseTargetValue = 0.67;
  498. this.questionInfoChartOption = null;
  499. this.courseTargetListChartOption = null;
  500. this.courseTargets = [];
  501. this.targetColumnCounts = [];
  502. this.studentScoreTable = [];
  503. this.courseSuggest = [];
  504. this.downloading = false;
  505. },
  506. buildData(data) {
  507. this.commonInfo = data.commonInfo;
  508. this.courseBasicInfo = data.courseBasicInfo;
  509. const courseSuggest = data.courseSuggest
  510. ? JSON.parse(data.courseSuggest)
  511. : [];
  512. const targetSuggestsMap = {};
  513. courseSuggest.forEach((item) => {
  514. targetSuggestsMap[item.targetId] = {
  515. finishPoints: item.finishPoints,
  516. requirementPoints: item.requirementPoints,
  517. courseSuggest: item.courseSuggest,
  518. };
  519. });
  520. const courseTargetScatterMap = data.courseTargetScatterMap;
  521. this.courseSuggest = data.courseEvaluationResultInfo.targetList.map(
  522. (target) => {
  523. const suggest = targetSuggestsMap[target.targetId] || {};
  524. const finishPoints = suggest.finishPoints || "";
  525. const requirementPoints = suggest.requirementPoints || "";
  526. const courseSuggest = suggest.courseSuggest || "";
  527. const chartData = courseTargetScatterMap[target.targetId] || [];
  528. return {
  529. targetId: target.targetId,
  530. targetName: target.targetName,
  531. finishPoints,
  532. requirementPoints,
  533. courseSuggest,
  534. chartOption: this.getTargetChartOption(
  535. chartData,
  536. target.targetName
  537. ),
  538. };
  539. }
  540. );
  541. this.courseTargetValue =
  542. data.courseEvaluationResultInfo.targetEvaluationSumValue;
  543. this.courseTargetList = data.courseEvaluationResultInfo.targetList.map(
  544. (target) => {
  545. target.normalTargetWeight = calcSum(
  546. target.evaluationList.slice(0, -1).map((elem) => elem.targetWeight)
  547. );
  548. return target;
  549. }
  550. );
  551. const {
  552. courseEvaluationSpreadInfo: { questionInfo, scoreList },
  553. } = data;
  554. this.parsePaperStructs(questionInfo);
  555. this.parseQuestionInfoTable(questionInfo);
  556. this.questionInfoChartOption = this.getQuestionInfoChartOption(scoreList);
  557. this.courseTargetListChartOption = this.getCourseTargetListChartOption();
  558. let examStudentList =
  559. data.courseEvaluationResultDetailInfo.examStudentList;
  560. examStudentList.pop();
  561. examStudentList.splice(examStudentList.length - 2, 1);
  562. this.parseCourseTargets(examStudentList);
  563. this.parseStudentScoreTable(examStudentList);
  564. },
  565. async initData() {
  566. this.resetData();
  567. await this.checkChange();
  568. const data = await targetStatisticsDetail({
  569. cultureProgramId: this.course.cultureProgramId,
  570. courseId: this.course.courseId,
  571. });
  572. this.buildData(data);
  573. },
  574. async checkChange() {
  575. const res = await targetStatisticsChangeCheck({
  576. cultureProgramId: this.course.cultureProgramId,
  577. courseId: this.course.courseId,
  578. report: true,
  579. });
  580. if (res.courseTargetChange) {
  581. this.$notify.warning("课程目标与已保存不一致,请重新设置权重!");
  582. }
  583. if (res.evaluationChange) {
  584. this.$notify.warning(
  585. "评价方式与已保存不一致,请重新设置权重及导入新的平时成绩!"
  586. );
  587. }
  588. if (res.targetScoreChange) {
  589. this.$notify.warning(res.targetScoreChangeStr);
  590. }
  591. },
  592. parsePaperStructs(questionInfo) {
  593. if (!questionInfo || !questionInfo.length) return;
  594. const structMap = {};
  595. questionInfo.forEach((item) => {
  596. if (!structMap[item.mainNumber]) {
  597. structMap[item.mainNumber] = [];
  598. }
  599. structMap[item.mainNumber].push(item.subNumber + "");
  600. });
  601. this.paperStructs = Object.keys(structMap).map((mainNumber) => {
  602. return {
  603. mainNumber,
  604. subNumbers: structMap[mainNumber],
  605. };
  606. });
  607. },
  608. parseQuestionInfoTable(questionInfo) {
  609. const targetMap = {};
  610. const tData = {
  611. name: "目标分值",
  612. totalScore: calcSum(questionInfo.map((item) => item.score || 0)),
  613. };
  614. questionInfo.forEach((question) => {
  615. const qno = `${question.mainNumber}_${question.subNumber}`;
  616. tData[qno] = question.score;
  617. question.targetList.forEach((target) => {
  618. if (!targetMap[target.targetId]) {
  619. targetMap[target.targetId] = {
  620. name: target.targetName,
  621. totalScore: 0,
  622. };
  623. }
  624. if (target.dimensionList.length) {
  625. targetMap[target.targetId][qno] = question.score;
  626. targetMap[target.targetId].totalScore += question.score;
  627. } else {
  628. targetMap[target.targetId][qno] = "";
  629. }
  630. });
  631. });
  632. this.questionInfoTable = [tData, ...Object.values(targetMap)];
  633. },
  634. getQuestionInfoChartOption(scoreList) {
  635. const scoreRange = scoreList.scoreRange.map((item) => {
  636. return {
  637. name: `${item.minScore}~${item.maxScore}`,
  638. value: item.rate,
  639. };
  640. });
  641. scoreRange.unshift({
  642. name: "不及格",
  643. value: scoreList.failRate,
  644. });
  645. let options = {
  646. color: ["#556dff", "#3A5AE5"],
  647. title: {
  648. text: "成绩分布图",
  649. left: "center",
  650. },
  651. grid: {
  652. left: 40,
  653. top: 60,
  654. right: 80,
  655. bottom: 40,
  656. containLabel: true,
  657. },
  658. xAxis: {
  659. type: "category",
  660. name: "成绩(分)",
  661. nameTextStyle: {
  662. color: "#363D59",
  663. },
  664. data: scoreRange.map((item) => item.name),
  665. axisLabel: {
  666. color: "#6F7482",
  667. interval: 0,
  668. fontSize: 12,
  669. margin: 12,
  670. },
  671. axisLine: {
  672. show: true,
  673. lineStyle: {
  674. color: "#EFF0F5",
  675. },
  676. },
  677. splitLine: {
  678. show: false,
  679. },
  680. axisTick: {
  681. show: false,
  682. },
  683. axisPointer: {
  684. type: "shadow",
  685. },
  686. },
  687. yAxis: {
  688. type: "value",
  689. name: "百分比(%)",
  690. nameTextStyle: {
  691. color: "#363D59",
  692. },
  693. axisLabel: {
  694. color: "#6F7482",
  695. },
  696. axisLine: {
  697. lineStyle: {
  698. color: "#EFF0F5",
  699. },
  700. },
  701. splitLine: {
  702. lineStyle: {
  703. color: "#EFF0F5",
  704. },
  705. },
  706. },
  707. series: [
  708. {
  709. name: "占比",
  710. type: "bar",
  711. barWidth: 60,
  712. data: scoreRange.map((item) => item.value),
  713. label: {
  714. show: true,
  715. formatter: "{c}%",
  716. position: "top",
  717. },
  718. },
  719. ],
  720. };
  721. return options;
  722. },
  723. getCourseTargetListChartOption() {
  724. let options = {
  725. color: ["#556dff", "#f59a23"],
  726. title: {
  727. text: "课程目标达成评价值",
  728. left: "center",
  729. },
  730. grid: {
  731. left: 40,
  732. top: 60,
  733. right: 80,
  734. bottom: 30,
  735. containLabel: true,
  736. },
  737. legend: {
  738. top: 0,
  739. data: ["期望值", "达成值"],
  740. itemWidth: 12,
  741. itemHeight: 4,
  742. itemGap: 22,
  743. right: 40,
  744. },
  745. xAxis: {
  746. type: "category",
  747. name: "课程目标",
  748. nameTextStyle: {
  749. color: "#363D59",
  750. },
  751. data: this.courseTargetList.map((item) => item.targetName),
  752. axisLabel: {
  753. color: "#6F7482",
  754. interval: 0,
  755. fontSize: 12,
  756. margin: 12,
  757. },
  758. axisLine: {
  759. show: true,
  760. lineStyle: {
  761. color: "#EFF0F5",
  762. },
  763. },
  764. splitLine: {
  765. show: false,
  766. },
  767. axisTick: {
  768. show: false,
  769. },
  770. axisPointer: {
  771. type: "shadow",
  772. },
  773. },
  774. yAxis: {
  775. type: "value",
  776. name: "期望值",
  777. min: 0,
  778. max: 1,
  779. interval: 0.1,
  780. nameTextStyle: {
  781. color: "#363D59",
  782. },
  783. axisLabel: {
  784. color: "#6F7482",
  785. },
  786. axisLine: {
  787. lineStyle: {
  788. color: "#EFF0F5",
  789. },
  790. },
  791. splitLine: {
  792. lineStyle: {
  793. color: "#EFF0F5",
  794. },
  795. },
  796. },
  797. series: [
  798. {
  799. name: "达成值",
  800. type: "bar",
  801. barWidth: 60,
  802. data: this.courseTargetList.map((item) => item.evaluationValue),
  803. label: {
  804. show: true,
  805. position: "top",
  806. },
  807. },
  808. {
  809. name: "期望值",
  810. type: "line",
  811. symbol: "none",
  812. data: this.courseTargetList.map(
  813. (item) => this.courseBasicInfo.courseDegree || 0
  814. ),
  815. },
  816. ],
  817. };
  818. return options;
  819. },
  820. getTargetChartOption(data, targetName) {
  821. const dataList = data.map((item, index) => {
  822. const val = Object.entries(item)[0];
  823. return {
  824. index: index + 1,
  825. name: val[0],
  826. value: val[1],
  827. };
  828. });
  829. let options = {
  830. color: ["#556dff", "#f59a23"],
  831. title: {
  832. text: `${targetName}个体达成情况`,
  833. top: "center",
  834. },
  835. grid: {
  836. left: 40,
  837. top: 60,
  838. right: 80,
  839. bottom: 30,
  840. containLabel: true,
  841. },
  842. legend: {
  843. top: 0,
  844. data: ["期望值", "达成值"],
  845. itemWidth: 12,
  846. itemHeight: 4,
  847. itemGap: 22,
  848. right: 40,
  849. },
  850. xAxis: {
  851. type: "category",
  852. name: "学生代号",
  853. nameTextStyle: {
  854. color: "#363D59",
  855. },
  856. data: dataList.map((item) => item.index),
  857. axisLabel: {
  858. color: "#6F7482",
  859. interval: 0,
  860. fontSize: 12,
  861. margin: 12,
  862. },
  863. axisLine: {
  864. show: true,
  865. lineStyle: {
  866. color: "#EFF0F5",
  867. },
  868. },
  869. splitLine: {
  870. show: false,
  871. },
  872. axisTick: {
  873. show: false,
  874. },
  875. axisPointer: {
  876. type: "shadow",
  877. },
  878. },
  879. yAxis: {
  880. type: "value",
  881. name: "期望值",
  882. min: 0,
  883. max: 1,
  884. interval: 0.1,
  885. nameTextStyle: {
  886. color: "#363D59",
  887. },
  888. axisLabel: {
  889. color: "#6F7482",
  890. },
  891. axisLine: {
  892. lineStyle: {
  893. color: "#EFF0F5",
  894. },
  895. },
  896. splitLine: {
  897. lineStyle: {
  898. color: "#EFF0F5",
  899. },
  900. },
  901. },
  902. series: [
  903. {
  904. name: "达成值",
  905. type: "scatter",
  906. data: dataList.map((item) => item.value),
  907. },
  908. {
  909. name: "期望值",
  910. type: "line",
  911. symbol: "none",
  912. data: dataList.map(
  913. (item) => this.courseBasicInfo.courseDegree || 0
  914. ),
  915. },
  916. ],
  917. };
  918. return options;
  919. },
  920. parseCourseTargets(examStudentList) {
  921. if (!examStudentList || !examStudentList.length) return;
  922. const targetList = examStudentList[0].targetList;
  923. const targetVals = {};
  924. this.courseTargetList.forEach((target) => {
  925. targetVals[target.targetId] = target.evaluationValue;
  926. });
  927. let tColumnCounts = [];
  928. this.courseTargets = targetList.map((target) => {
  929. const ntarget = {
  930. targetId: target.targetId,
  931. targetName: target.targetName,
  932. finalWeight: target.finalScore?.targetWeight,
  933. usualWeight: 0,
  934. evaluationValue: targetVals[target.targetId],
  935. };
  936. ntarget.usualWeight = calcSum(
  937. (target.usualScore?.scoreList || []).map((item) => item.targetWeight)
  938. );
  939. ntarget.finalDimensions = target.finalScore ? ["期末成绩"] : [];
  940. ntarget.usualWorks = (target.usualScore?.scoreList || []).map(
  941. (item) => {
  942. return {
  943. evaluation: item.evaluation,
  944. targetWeight: item.targetWeight,
  945. };
  946. }
  947. );
  948. tColumnCounts.push(
  949. ntarget.finalDimensions.length + ntarget.usualWorks.length
  950. );
  951. return ntarget;
  952. });
  953. // 只展示有期末成绩或平时成绩的目标
  954. this.courseTargets = this.courseTargets.filter(
  955. (item) => item.finalDimensions.length || item.usualWorks.length
  956. );
  957. let tCount = 0;
  958. this.targetColumnCounts = tColumnCounts.map((item) => {
  959. tCount += item;
  960. return tCount;
  961. });
  962. },
  963. getTargetFirseKey(target) {
  964. if (target.finalDimensions.length) {
  965. return `${target.targetId}-final`;
  966. } else {
  967. return `${target.targetId}-usual-${target.usualWorks[0].evaluation}`;
  968. }
  969. },
  970. parseStudentScoreTable(examStudentList) {
  971. if (!this.courseTargets.length) return;
  972. const lastIndex = examStudentList.length - 1;
  973. const studentScoreTable = examStudentList.map((student, sindex) => {
  974. const nitem = {
  975. name: student.name,
  976. studentCode: student.studentCode,
  977. score: student.score,
  978. };
  979. const finalScoreKey =
  980. lastIndex === sindex ? "matrixAvgScore" : "targetScoreSum";
  981. const workScoreKey = lastIndex === sindex ? "matrixAvgScore" : "score";
  982. student.targetList.forEach((target, index) => {
  983. nitem[`${target.targetId}-final`] =
  984. target.finalScore && target.finalScore[finalScoreKey];
  985. (target.usualScore?.scoreList || []).forEach((work) => {
  986. nitem[`${target.targetId}-usual-${work.evaluation}`] =
  987. work[workScoreKey];
  988. });
  989. });
  990. return nitem;
  991. });
  992. const targetData = {
  993. name: "课程目标达成度",
  994. score: "",
  995. };
  996. this.courseTargets.forEach((target) => {
  997. targetData[this.getTargetFirseKey(target)] = target.evaluationValue;
  998. });
  999. studentScoreTable.push(targetData);
  1000. const fTarget = this.courseTargets[0];
  1001. const fKey = this.getTargetFirseKey(fTarget);
  1002. studentScoreTable.push({
  1003. name: "课程达成度",
  1004. [fKey]: this.courseTargetValue,
  1005. });
  1006. this.studentScoreTable = studentScoreTable;
  1007. },
  1008. studentScoreSpanMethod({ row, column, rowIndex, columnIndex }) {
  1009. const lineCount = this.studentScoreTable.length - 1;
  1010. const maxTargetColumnCount = this.targetColumnCounts.slice(-1)[0];
  1011. if (rowIndex === lineCount) {
  1012. if (columnIndex === 0) {
  1013. return [1, 2];
  1014. } else if (columnIndex === 2) {
  1015. return [1, maxTargetColumnCount + 1];
  1016. } else {
  1017. return [0, 0];
  1018. }
  1019. }
  1020. if (rowIndex === lineCount - 1) {
  1021. const colsMap = {};
  1022. const targetColumns = [0, ...this.targetColumnCounts.slice(0, -1)];
  1023. targetColumns.map((item, index) => {
  1024. colsMap[item] = this.targetColumnCounts[index] - item;
  1025. });
  1026. if (columnIndex === 0) {
  1027. return [1, 2];
  1028. } else {
  1029. if (targetColumns.includes(columnIndex - 2)) {
  1030. return [1, colsMap[columnIndex - 2]];
  1031. } else if (columnIndex < maxTargetColumnCount + 2) {
  1032. return [0, 0];
  1033. } else {
  1034. return [1, 1];
  1035. }
  1036. }
  1037. }
  1038. if (rowIndex === lineCount - 2) {
  1039. if (columnIndex === 0) {
  1040. return [1, 2];
  1041. } else if (columnIndex === 1) {
  1042. return [0, 0];
  1043. } else {
  1044. return [1, 1];
  1045. }
  1046. }
  1047. },
  1048. async toSave(autoSave = false) {
  1049. if (this.downloading) return;
  1050. this.downloading = true;
  1051. const res = await targetStatisticsSave({
  1052. cultureProgramId: this.course.cultureProgramId,
  1053. courseId: this.course.courseId,
  1054. ...this.courseBasicInfo,
  1055. courseSuggest: JSON.stringify(this.courseSuggest),
  1056. }).catch(() => {});
  1057. this.downloading = false;
  1058. if (!res) return;
  1059. this.$message.success("保存成功!");
  1060. if (autoSave) {
  1061. this.openAutoSave();
  1062. } else {
  1063. this.initData();
  1064. }
  1065. },
  1066. async toExport() {
  1067. if (this.downloading) return;
  1068. this.downloading = true;
  1069. const res = await downloadByApi(() => {
  1070. const datas = {
  1071. cultureProgramId: this.course.cultureProgramId,
  1072. courseId: this.course.courseId,
  1073. };
  1074. return targetStatisticsReport(datas);
  1075. }).catch((e) => {
  1076. this.$message.error(e || "下载失败,请重新尝试!");
  1077. });
  1078. this.downloading = false;
  1079. if (!res) return;
  1080. this.$message.success("下载成功!");
  1081. },
  1082. async toExportNormalScore() {
  1083. if (this.downloading) return;
  1084. this.downloading = true;
  1085. const res = await downloadByApi(() => {
  1086. const datas = {
  1087. cultureProgramId: this.course.cultureProgramId,
  1088. courseId: this.course.courseId,
  1089. };
  1090. return targetStatisticsNormalScoreExport(datas);
  1091. }).catch((e) => {
  1092. this.$message.error(e || "下载失败,请重新尝试!");
  1093. });
  1094. this.downloading = false;
  1095. if (!res) return;
  1096. this.$message.success("下载成功!");
  1097. },
  1098. },
  1099. };
  1100. </script>
  1101. <style scoped>
  1102. .td-bg {
  1103. color: #8b8fa1;
  1104. font-weight: 500;
  1105. }
  1106. </style>