zhangjie 3 gadi atpakaļ
vecāks
revīzija
71bf93e573

+ 13 - 0
src/assets/styles/element-ui-costom.scss

@@ -604,3 +604,16 @@
     border-left-color: $--color-text-dark-1;
   }
 }
+// popper-list
+.popper-list {
+  min-width: auto;
+
+  .el-button {
+    display: block;
+    width: 100%;
+    margin: 0;
+    &:not(:last-child) {
+      margin-bottom: 5px;
+    }
+  }
+}

+ 3 - 3
src/assets/styles/home.scss

@@ -154,12 +154,12 @@
       margin-right: 12px;
     }
   }
-  .el-submenu .el-menu-item {
+  .el-menu-item {
     height: 40px;
     line-height: 40px;
-    padding: 0 40px;
+    padding: 0 40px !important;
   }
-  .el-submenu .el-menu-item.is-active {
+  .el-menu-item.is-active {
     font-weight: 600;
   }
   .el-submenu__icon-arrow {

+ 15 - 6
src/modules/stmms/api.js

@@ -1,20 +1,29 @@
 import { $postParam, $post } from "@/plugins/axios";
 
-export const markTaskListPage = datas => {
+export const examStructureListPage = datas => {
   return $postParam("/api/admin/exam/structure/list", datas);
 };
-export const uploadPaperAndAnswer = datas => {
+export const examStructureUpload = datas => {
   return $post("/api/admin/exam/structure/upload", datas);
 };
-export const paperStructureDetail = datas => {
+export const examStructurePreviewStructure = datas => {
   return $post("/api/admin/exam/structure/preview_structure", datas);
 };
 // score-archive
 export const scoreListPage = datas => {
-  return $postParam("/api/admin/sys/user/user_list", datas);
+  return $postParam("/api/admin/sync/score/list", datas);
 };
-export const scorePaperDetail = datas => {
-  return $postParam("/api/admin/sys/user/user_list", datas);
+export const scoreExport = datas => {
+  return $postParam("/api/admin/sync/score/export", datas);
+};
+export const scoreSync = datas => {
+  return $postParam("/api/admin/sync/score/sync", datas);
+};
+export const scoreDownload = studentCode => {
+  return $postParam("/api/admin/sync/score/download", { studentCode });
+};
+export const scoreBatchDownload = datas => {
+  return $postParam("/api/admin/sync/score/batch_download", datas);
 };
 // 第三方登录ypt
 export const userSysRoles = () => {

+ 84 - 0
src/modules/stmms/components/PreviewPaperStructureDialog.vue

@@ -0,0 +1,84 @@
+<template>
+  <el-dialog
+    class="preview-paper-structure-dialog"
+    :visible.sync="modalIsShow"
+    title="试卷结构"
+    top="0"
+    width="800px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    fullscreen
+    @open="visibleChange"
+  >
+    <div class="mb-4 tab-btns">
+      <el-button
+        v-for="tab in dataList"
+        :key="tab.paperType"
+        size="medium"
+        :type="curTab == tab.paperType ? 'primary' : 'default'"
+        @click="switchPaper(tab)"
+        >{{ tab.paperType }}卷</el-button
+      >
+    </div>
+
+    <el-table ref="TableList" :data="dataList">
+      <el-table-column prop="objective" label="题型">
+        <span slot-scope="scope">
+          {{ scope.row.objective ? "客观题" : "主观题" }}
+        </span>
+      </el-table-column>
+      <el-table-column prop="mainNumber" label="大题号"></el-table-column>
+      <el-table-column prop="subNumber" label="小题号"></el-table-column>
+      <el-table-column prop="mainTitle" label="大题名称"></el-table-column>
+      <el-table-column prop="totalScore" label="分数"></el-table-column>
+      <el-table-column prop="answer" label="答案"></el-table-column>
+    </el-table>
+
+    <div slot="footer"></div>
+  </el-dialog>
+</template>
+
+<script>
+import { examStructurePreviewStructure } from "../api";
+
+export default {
+  name: "preview-paper-structure-dialog",
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      dataList: [],
+      curTab: "",
+      paperList: []
+    };
+  },
+  methods: {
+    visibleChange() {
+      this.getData();
+    },
+    async getData() {
+      this.dataList = [];
+      const data = await examStructurePreviewStructure(this.instance.id);
+      this.dataList = data || [];
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    switchPaper(paper) {
+      this.curTab = paper.paperType;
+      this.paperList = paper.content;
+    }
+  }
+};
+</script>

+ 2 - 2
src/modules/stmms/components/UploadPaperAnswerDialog.vue

@@ -49,7 +49,7 @@
 </template>
 
 <script>
-import { uploadPaperAndAnswer } from "../api";
+import { examStructureUpload } from "../api";
 import SelectFile from "./SelectFile.vue";
 
 export default {
@@ -171,7 +171,7 @@ export default {
       //     formData.append(`${indexName}.${typeKey}Md5`, vals[typeKey].md5);
       //   });
       // });
-      const data = await uploadPaperAndAnswer(formData).catch(() => {});
+      const data = await examStructureUpload(formData).catch(() => {});
       this.isSubmit = false;
       if (!data) return;
 

+ 4 - 2
src/modules/stmms/views/MarkerLogin.vue

@@ -47,8 +47,10 @@ export default {
     async toAuth(item) {
       if (this.loading) return;
       this.loading = true;
-      const data = await yptAuth(item.type).catch(() => {});
-      this.loading = false;
+      const data = await yptAuth(item.type).catch(() => {
+        this.loading = false;
+      });
+
       if (!data) return;
       const url = data.redirectUrl;
       const params = { ...data, returnUrl: window.location.href };

+ 69 - 61
src/modules/stmms/views/ScoreArchive.vue

@@ -19,49 +19,26 @@
             </el-select>
           </el-form-item>
           <el-form-item label="学院:">
-            <el-select
+            <college-select
               v-model="filter.collegeId"
               placeholder="学院"
-              filterable
-              clearable
-            >
-              <el-option
-                v-for="item in colleges"
-                :key="item.id"
-                :value="item.id"
-                :label="item.name"
-              ></el-option>
-            </el-select>
+            ></college-select>
           </el-form-item>
           <el-form-item label="专业:">
-            <el-select
-              v-model="filter.subjectId"
+            <major-select
+              v-model="filter.majorId"
+              :college-id="filter.collegeId"
+              cascader
               placeholder="专业"
-              filterable
-              clearable
-            >
-              <el-option
-                v-for="item in subjects"
-                :key="item.id"
-                :value="item.id"
-                :label="item.name"
-              ></el-option>
-            </el-select>
+            ></major-select>
           </el-form-item>
           <el-form-item label="班级:">
-            <el-select
+            <class-select
               v-model="filter.clazzId"
+              :major-id="filter.majorId"
+              cascader
               placeholder="班级"
-              filterable
-              clearable
-            >
-              <el-option
-                v-for="item in clazzes"
-                :key="item.id"
-                :value="item.id"
-                :label="item.name"
-              ></el-option>
-            </el-select>
+            ></class-select>
           </el-form-item>
           <el-form-item label="课程:">
             <el-select
@@ -90,17 +67,25 @@
       </el-form>
       <div class="part-box-action">
         <el-button
-          v-if="checkPrivilege('button', 'BatchDownload')"
           type="primary"
           icon="el-icon-refresh"
           :loading="loading"
+          @click="toSync"
+          >同步</el-button
+        >
+        <el-button
+          v-if="checkPrivilege('button', 'BatchDownload')"
+          type="primary"
+          icon="el-icon-download"
+          :loading="loading"
           @click="toDownloadAll"
           >一键下载</el-button
         >
         <el-button
           v-if="checkPrivilege('button', 'export')"
           type="primary"
-          icon="el-icon-circle-plus-outline"
+          icon="el-icon-download"
+          :loading="loading"
           @click="toExport"
           >成绩导出</el-button
         >
@@ -117,16 +102,16 @@
         ></el-table-column>
         <el-table-column prop="examTime" label="考试时间"></el-table-column>
         <el-table-column
-          prop="realName"
+          prop="examStudentName"
           label="姓名"
           width="120"
         ></el-table-column>
-        <el-table-column prop="studentNo" label="学号"></el-table-column>
-        <el-table-column prop="college" label="院系"></el-table-column>
-        <el-table-column prop="college" label="专业"></el-table-column>
-        <el-table-column prop="college" label="班级"></el-table-column>
-        <el-table-column prop="college" label="课程名"></el-table-column>
-        <el-table-column prop="college" label="成绩"></el-table-column>
+        <el-table-column prop="studentCode" label="学号"></el-table-column>
+        <el-table-column prop="collegeName" label="院系"></el-table-column>
+        <el-table-column prop="majorName" label="专业"></el-table-column>
+        <el-table-column prop="clazzName" label="班级"></el-table-column>
+        <el-table-column prop="courseName" label="课程名"></el-table-column>
+        <el-table-column prop="totalScore" label="成绩"></el-table-column>
         <el-table-column class-name="action-column" label="操作" width="160px">
           <template slot-scope="scope">
             <el-button
@@ -159,25 +144,28 @@
         </el-pagination>
       </div>
     </div>
-
-    <PaperPreviewDialog ref="PaperPreviewDialog" :instance="curRow" />
   </div>
 </template>
 
 <script>
-import { scoreListPage } from "../api";
-import PaperPreviewDialog from "../components/PaperPreviewDialog";
-import { downloadPaper } from "../components/downloadPaper";
+import {
+  scoreListPage,
+  scoreExport,
+  scoreBatchDownload,
+  scoreDownload,
+  scoreSync
+} from "../api";
+// import { downloadPaper } from "../components/downloadPaper";
+import { downloadByUrl } from "@/plugins/download";
 
 export default {
   name: "score-archive",
-  components: { PaperPreviewDialog },
   data() {
     return {
       filter: {
         semester: "",
         collegeId: "",
-        subjectId: "",
+        majorId: "",
         clazzId: "",
         courseCode: ""
       },
@@ -186,9 +174,6 @@ export default {
       total: 0,
       dataList: [],
       semesters: [],
-      colleges: [],
-      subjects: [],
-      clazzes: [],
       courses: [],
       curRow: {},
       curPapers: [],
@@ -215,25 +200,48 @@ export default {
       this.current = page;
       this.getList();
     },
-    toDownloadAll() {},
-    toExport() {
-      this.$refs.PaperPreviewDialog.open();
+    async toSync() {
+      if (this.loading) return;
+      this.loading = true;
+      const res = await scoreSync({ examId: "" }).catch(() => {});
+      this.loading = false;
+      if (!res) return;
+      this.$message.success("同步任务已经提交");
+    },
+    async toDownloadAll() {
+      if (this.loading) return;
+      this.loading = true;
+      const res = await scoreBatchDownload(this.filter).catch(() => {});
+      this.loading = false;
+      if (!res) return;
+      this.$message.success("下载任务已经提交");
+    },
+    async toExport() {
+      if (this.loading) return;
+      this.loading = true;
+      const res = await scoreExport(this.filter).catch(() => {});
+      this.loading = false;
+      if (!res) return;
+      this.$message.success("导出任务已经提交");
     },
     toViewPaper(row) {
-      this.curRow = row;
-      this.$refs.PaperPreviewDialog.open();
+      if (!row.sheetUrl) {
+        this.$message.error("原卷缺失!");
+        return;
+      }
+      // window.open(row.sheetUrl);
+      downloadByUrl(row.sheetUrl);
     },
     async toDownload(row) {
       if (!this.downloading) return;
       this.downloading = true;
-      const papers = row.papers;
-      const filename = ``;
-      const res = await downloadPaper(papers, filename).catch(() => {});
+      const res = await scoreDownload(row.studentCode).catch(() => {});
       this.downloading = false;
       if (!res) {
         this.$message.error("下载失败,请重新尝试!");
         return;
       }
+      downloadByUrl(res.trajectoryUrls);
 
       this.$message.success("下载成功!");
     }

+ 63 - 15
src/modules/stmms/views/UploadStructure.vue

@@ -25,7 +25,7 @@
             {{ scope.row.status | markTaskSyncStatusFilter }}
           </template>
         </el-table-column>
-        <el-table-column class-name="action-column" label="操作" width="220px">
+        <el-table-column class-name="action-column" label="操作" width="180px">
           <template slot-scope="scope">
             <el-button
               v-if="checkPrivilege('link', 'Upload')"
@@ -35,19 +35,47 @@
               >上传试卷结构/标答</el-button
             >
             <el-button
-              v-if="checkPrivilege('link', 'Preview')"
+              v-if="
+                checkPrivilege('link', 'Preview') &&
+                  scope.row.status === 'FINISH'
+              "
               class="btn-primary"
               type="text"
               @click="toViewPaper(scope.row)"
               >查看试卷结构</el-button
             >
-            <el-button
-              v-if="checkPrivilege('link', 'Preview')"
-              class="btn-primary"
-              type="text"
-              @click="toViewAnswer(scope.row)"
-              >查看标答</el-button
+            <template
+              v-if="
+                checkPrivilege('link', 'Preview') &&
+                  scope.row.status === 'FINISH'
+              "
             >
+              <el-popover
+                v-if="scope.row.paperTypes.length > 1"
+                popper-class="popper-list"
+                placement="bottom-start"
+                width="80"
+                trigger="hover"
+              >
+                <el-button
+                  v-for="paperType in scope.row.paperTypes"
+                  :key="paperType"
+                  class="btn-primary"
+                  @click="toViewAnswer(scope.row, paperType)"
+                  >{{ paperType }}卷</el-button
+                >
+                <el-button class="btn-primary" type="text" slot="reference"
+                  >查看标答</el-button
+                >
+              </el-popover>
+              <el-button
+                v-else
+                class="btn-primary"
+                type="text"
+                @click="toViewAnswer(scope.row, scope.row.paperType)"
+                >查看标答</el-button
+              >
+            </template>
           </template>
         </el-table-column>
       </el-table>
@@ -69,16 +97,21 @@
       :instance="curTask"
       @modified="getList"
     />
+    <PreviewPaperStructureDialog
+      ref="PreviewPaperStructureDialog"
+      :instance="curTask"
+    />
   </div>
 </template>
 
 <script>
-import { markTaskListPage } from "../api";
+import { examStructureListPage } from "../api";
 import UploadPaperAnswerDialog from "../components/UploadPaperAnswerDialog";
+import PreviewPaperStructureDialog from "../components/PreviewPaperStructureDialog";
 
 export default {
   name: "upload-structure",
-  components: { UploadPaperAnswerDialog },
+  components: { UploadPaperAnswerDialog, PreviewPaperStructureDialog },
   data() {
     return {
       filter: {},
@@ -100,8 +133,11 @@ export default {
         pageNumber: this.current,
         pageSize: this.size
       };
-      const data = await markTaskListPage(datas);
-      this.dataList = data.records;
+      const data = await examStructureListPage(datas);
+      this.dataList = data.records.map(item => {
+        item.paperTypes = item.paperType.split(",");
+        return item;
+      });
       this.total = data.total;
     },
     toPage(page) {
@@ -113,10 +149,22 @@ export default {
       this.$refs.UploadPaperAnswerDialog.open();
     },
     toViewPaper(row) {
-      console.log(row);
+      this.curTask = row;
+      this.$refs.PreviewPaperStructureDialog.open();
     },
-    toViewAnswer(row) {
-      console.log(row);
+    toViewAnswer(row, paperType) {
+      if (!row.previewList || !row.previewList.length) {
+        this.$message.error("暂时还没有标答可预览!");
+        return;
+      }
+
+      const paper = row.previewList.find(item => item.paperType === paperType);
+      if (!paper) {
+        this.$message.error("标答文件不存在!");
+        return;
+      }
+
+      window.open(paper.url);
     }
   }
 };

+ 57 - 15
src/plugins/download.js

@@ -1,8 +1,23 @@
+const parseDownloadFilename = dispositionInfo => {
+  if (!dispositionInfo) return;
+
+  const strs = dispositionInfo.split(";");
+  let filename = "";
+  strs
+    .map(item => item.split("="))
+    .find(item => {
+      if (item[0].indexOf("filename") !== -1) {
+        filename = decodeURI(item[1]);
+      }
+    });
+  return filename;
+};
+
 /**
  * 通过api下载文件
  * @param {AxiosPromise} fetchFunc 下载接口
  * @param {String}} fileName 文件名
- * @returns boolean
+ * @returns Promise<Boolean>
  */
 export function downloadByApi(fetchFunc, fileName) {
   return fetchFunc()
@@ -24,9 +39,18 @@ export function downloadByApi(fetchFunc, fileName) {
  */
 export function downloadByBlob(data, filename) {
   const blobUrl = URL.createObjectURL(data);
+  downloadByUrl(blobUrl, filename);
+}
+
+/**
+ * 下载url
+ * @param {String} url 文件下载地址
+ * @param {String}} filename 文件名
+ */
+export function downloadByUrl(url, filename) {
   const tempLink = document.createElement("a");
   tempLink.style.display = "none";
-  tempLink.href = blobUrl;
+  tempLink.href = url;
   tempLink.setAttribute("download", filename);
   if (tempLink.download === "undefined") {
     tempLink.setAttribute("target", "_blank");
@@ -34,20 +58,38 @@ export function downloadByBlob(data, filename) {
   document.body.appendChild(tempLink);
   tempLink.click();
   document.body.removeChild(tempLink);
-  window.URL.revokeObjectURL(blobUrl);
+  window.URL.revokeObjectURL(url);
 }
 
-const parseDownloadFilename = dispositionInfo => {
-  if (!dispositionInfo) return;
+/**
+ * 下载图片
+ * @param {String} url 图片地址
+ * @param {String} filename 下载的文件名
+ * @returns Promise<Boolean>
+ */
+export function downloadByImgUrl(url, filename) {
+  return new Promise((resolve, reject) => {
+    const img = new Image();
+    img.crossOrigin = "";
+    img.onload = function() {
+      const canvas = document.createElement("canvas");
+      const ctx = canvas.getContext("2d");
 
-  const strs = dispositionInfo.split(";");
-  let filename = "";
-  strs
-    .map(item => item.split("="))
-    .find(item => {
-      if (item[0].indexOf("filename") !== -1) {
-        filename = decodeURI(item[1]);
+      if (!canvas || !ctx) {
+        return reject("不支持下载!");
       }
-    });
-  return filename;
-};
+
+      canvas.width = img.width;
+      canvas.height = img.height;
+      ctx.drawImage(img, 0, 0);
+      canvas.toBlob(blob => {
+        downloadByBlob(blob, filename);
+        resolve(true);
+      });
+    };
+    img.onerror = function(e) {
+      reject(e);
+    };
+    img.src = url;
+  });
+}

+ 27 - 20
src/views/Home.vue

@@ -58,29 +58,36 @@
         :default-active="curRouteName"
         :default-openeds="curSubMenuNames"
       >
-        <el-submenu
-          v-for="submenu in curMenu.children"
-          :key="submenu.id"
-          :index="submenu.url"
-        >
-          <template slot="title">
-            <span>{{ submenu.name }}</span>
-          </template>
-
-          <el-menu-item
-            v-for="nav in submenu.children"
-            :key="nav.id"
-            :index="nav.url"
-            :route="{ name: nav.url }"
+        <template v-for="submenu in curMenu.children">
+          <el-submenu
+            v-if="submenu.children && submenu.children.length"
+            :key="submenu.id"
+            :index="submenu.url"
           >
-            <span>{{ nav.name }}</span>
-            <span
-              class="nav-item-info"
-              v-if="nav.url === 'WaitTask' && waitTaskCount"
-              >{{ waitTaskCount }}</span
+            <span slot="title">{{ submenu.name }}</span>
+            <el-menu-item
+              v-for="nav in submenu.children"
+              :key="nav.id"
+              :index="nav.url"
+              :route="{ name: nav.url }"
             >
+              <span>{{ nav.name }}</span>
+              <span
+                class="nav-item-info"
+                v-if="nav.url === 'WaitTask' && waitTaskCount"
+                >{{ waitTaskCount }}</span
+              >
+            </el-menu-item>
+          </el-submenu>
+          <el-menu-item
+            v-else
+            :key="submenu.id"
+            :index="submenu.url"
+            :route="{ name: submenu.url }"
+          >
+            <span>{{ submenu.name }}</span>
           </el-menu-item>
-        </el-submenu>
+        </template>
       </el-menu>
     </div>