Bläddra i källkod

Merge remote-tracking branch 'origin/release_v4.1.0'

Michael Wang 3 år sedan
förälder
incheckning
b96c152f64
34 ändrade filer med 3290 tillägg och 336 borttagningar
  1. 1 0
      src/constants/constants.js
  2. 8 2
      src/modules/basic/view/clientConfig.vue
  3. 1 1
      src/modules/basic/view/course.vue
  4. 1 1
      src/modules/basic/view/specially.vue
  5. 5 0
      src/modules/examwork/routes/routes.js
  6. 22 0
      src/modules/examwork/view/examInfo.vue
  7. 842 0
      src/modules/examwork/view/examIpLimit.vue
  8. 45 22
      src/modules/examwork/view/notice.vue
  9. 15 27
      src/modules/examwork/view/onlineExam.vue
  10. 1 0
      src/modules/marking/filters/filters.js
  11. 8 1
      src/modules/marking/views/export_task_list.vue
  12. 59 1
      src/modules/oe/views/alreadyAudited.vue
  13. 104 1
      src/modules/oe/views/awaitingAudit.vue
  14. 3 0
      src/modules/oe/views/captureDetail.vue
  15. 17 0
      src/modules/oe/views/examDetail.vue
  16. 9 1
      src/modules/oe/views/export_task_list.vue
  17. 2 2
      src/modules/portal/views/home/Home.vue
  18. 4 0
      src/modules/portal/views/home/SiteMessageDetail.vue
  19. 11 2
      src/modules/portal/views/home/SiteMessageHome.vue
  20. 12 0
      src/modules/portal/views/home/SiteMessagePopup.vue
  21. 10 0
      src/modules/questions/routes/routes.js
  22. 207 0
      src/modules/questions/views/CourseProperty.vue
  23. 87 67
      src/modules/questions/views/EditPaper.vue
  24. 100 5
      src/modules/questions/views/GenPaper.vue
  25. 10 10
      src/modules/questions/views/GenPaperDetail.vue
  26. 28 13
      src/modules/questions/views/InsertBluePaperStructure.vue
  27. 4 4
      src/modules/questions/views/InsertBluePaperStructureInfo.vue
  28. 27 12
      src/modules/questions/views/InsertPaperStructure.vue
  29. 27 11
      src/modules/questions/views/InsertPaperStructureInfo.vue
  30. 556 0
      src/modules/questions/views/PaperStorage.vue
  31. 134 142
      src/modules/questions/views/PropertyInfo.vue
  32. 2 4
      src/modules/questions/views/Question.vue
  33. 1 7
      src/modules/questions/views/SelectQuestion.vue
  34. 927 0
      src/modules/questions/views/ViewPaper.vue

+ 1 - 0
src/constants/constants.js

@@ -81,6 +81,7 @@ export const PUBLISH_STATUS = [
   { code: "TO_BE_PUBLISHED", name: "待发布" },
   { code: "PUBLISHING", name: "发布中" },
   { code: "PUBLISHED", name: "已发布" },
+  { code: "RECALLED", name: "已撤回" },
 ];
 //公告接受规则类型
 export const NOTICE_RECEIVER_RULE_TYPE = [

+ 8 - 2
src/modules/basic/view/clientConfig.vue

@@ -254,8 +254,8 @@
                 class="upload-width"
                 accept=".zip"
                 :action="uploadAnswer"
-                :headers="uploadHeaders"
-                :data="uploadData"
+                :headers="uploadAnswerHeaders"
+                :data="uploadAnswerData"
                 :before-upload="beforeUploadAnswer"
                 :on-progress="uploadProgress"
                 :on-success="uploadAnswerSuccess"
@@ -476,6 +476,8 @@ export default {
       logoDialog: false,
       uploadAction: "",
       uploadAnswer: "",
+      uploadAnswerHeaders: {},
+      uploadAnswerData: {},
       uploadHeaders: {},
       uploadData: {},
       fileList: [],
@@ -705,6 +707,10 @@ export default {
       key: this.user.key,
       token: this.user.token,
     };
+    this.uploadAnswerHeaders = {
+      key: this.user.key,
+      token: this.user.token,
+    };
     this.uploadMenuLogoHeaders = {
       key: this.user.key,
       token: this.user.token,

+ 1 - 1
src/modules/basic/view/course.vue

@@ -368,7 +368,7 @@
           title="添加关联专业"
           :visible.sync="addRelationDialog"
           width="400px"
-          @close="() => this.$refs.addRelationForm.clearValidate()"
+          @close="() => $refs.addRelationForm.clearValidate()"
         >
           <el-form
             ref="addRelationForm"

+ 1 - 1
src/modules/basic/view/specially.vue

@@ -229,7 +229,7 @@
         title="添加关联课程"
         width="400px"
         :visible.sync="addRelationDialog"
-        @close="() => this.$refs.addRelationForm.clearValidate()"
+        @close="() => $refs.addRelationForm.clearValidate()"
       >
         <el-form
           ref="addRelationForm"

+ 5 - 0
src/modules/examwork/routes/routes.js

@@ -16,6 +16,7 @@ import Tips from "../../portal/views/tips/Tips.vue";
 import notice from "../view/notice.vue";
 import studentSpecialSettings from "../view/studentSpecialSettings.vue";
 import stageSpecialSettings from "../view/stageSpecialSettings.vue";
+import examIpLimit from "../view/examIpLimit.vue";
 
 export default [
   {
@@ -91,6 +92,10 @@ export default [
         path: "stageSpecialSettings/:id/:examName/:examTypeName", //场次特殊设置
         component: stageSpecialSettings,
       },
+      {
+        path: "examIpLimit/:id/:examName/:examTypeName",
+        component: examIpLimit,
+      },
       {
         path: "notice",
         component: notice,

+ 22 - 0
src/modules/examwork/view/examInfo.vue

@@ -232,6 +232,16 @@
                         >场次特殊设置</el-button
                       >
                     </el-dropdown-item>
+                    <el-dropdown-item>
+                      <el-button
+                        :disabled="!scope.row.ipLimitSettingsEnabled"
+                        size="mini"
+                        type="primary"
+                        icon="el-icon-edit"
+                        @click="editIpSettings(scope.row)"
+                        >IP访问限制</el-button
+                      >
+                    </el-dropdown-item>
                   </el-dropdown-menu>
                 </el-dropdown>
               </div>
@@ -567,6 +577,18 @@ export default {
           this.getExamType(row.examType),
       });
     },
+    editIpSettings(row) {
+      this.setSearchParams();
+      this.$router.push({
+        path:
+          "/examwork/examIpLimit/" +
+          row.id +
+          "/" +
+          row.name +
+          "/" +
+          this.getExamType(row.examType),
+      });
+    },
     showExamCourseSettingsDialog(row) {
       this.setSearchParams();
       this.$router.push({ path: "/examwork/examCourseSettings/" + row.id });

+ 842 - 0
src/modules/examwork/view/examIpLimit.vue

@@ -0,0 +1,842 @@
+<template>
+  <section class="content">
+    <LinkTitlesCustom :current-paths="['考试管理', '考试信息', 'ip访问限制']" />
+    <div class="box box-info">
+      <!-- 正文信息 -->
+      <div class="box-body">
+        <el-form
+          ref="formSearch"
+          :model="formSearch"
+          :inline="true"
+          label-width="70px"
+        >
+          <el-form-item label="考试名称">
+            <el-input
+              v-model="formSearch.examName"
+              class="input"
+              :disabled="true"
+            ></el-input>
+          </el-form-item>
+          <el-form-item label="考试类型">
+            <el-input
+              v-model="formSearch.examType"
+              class="input"
+              :disabled="true"
+            ></el-input>
+          </el-form-item>
+          <el-form-item label="ip">
+            <el-input
+              v-model="formSearch.ip"
+              placeholder="请输入ip"
+              class="input"
+            ></el-input>
+          </el-form-item>
+          <el-form-item label="限制类型">
+            <el-select v-model="formSearch.limitType" class="input">
+              <el-option label="未选择" value=""></el-option>
+              <el-option label="允许访问" value="0"></el-option>
+              <el-option label="禁止访问" value="1"></el-option>
+            </el-select>
+          </el-form-item>
+
+          <el-form-item class="d-block">
+            <el-button
+              size="small"
+              type="primary"
+              icon="el-icon-search"
+              @click="resetPageAndSearchForm"
+              >查询</el-button
+            >
+            <el-button
+              size="small"
+              icon="el-icon-refresh"
+              @click="resetSearchForm"
+              >重置</el-button
+            >
+            <el-button
+              size="small"
+              type="primary"
+              icon="el-icon-arrow-left"
+              @click="back"
+              >返回</el-button
+            >
+          </el-form-item>
+        </el-form>
+
+        <div class="block-seperator"></div>
+        <el-button
+          size="small"
+          type="primary"
+          icon="el-icon-plus"
+          @click="openAddingDialog"
+          >新增</el-button
+        >
+        <el-button
+          size="small"
+          type="primary"
+          icon="el-icon-edit"
+          :disabled="noBatchSelected"
+          @click="editIpLimitType"
+          >类型设置</el-button
+        >
+        <el-button
+          size="small"
+          type="primary"
+          icon="el-icon-edit"
+          @click="editIpLimitProperty"
+          >控制设置</el-button
+        >
+        <el-button
+          size="small"
+          type="primary"
+          icon="el-icon-upload2"
+          @click="batchImport"
+          >批量导入</el-button
+        >
+        <el-button
+          size="small"
+          type="primary"
+          icon="el-icon-download"
+          @click="batchExport"
+          >批量导出</el-button
+        >
+        <el-button
+          size="small"
+          type="danger"
+          icon="el-icon-delete"
+          :disabled="noBatchSelected"
+          @click="batchDelete"
+          >批量删除</el-button
+        >
+        <el-button
+          size="small"
+          type="danger"
+          icon="el-icon-delete"
+          @click="allDelete"
+          >全部删除</el-button
+        >
+        <div style="width: 100%; margin-bottom: 10px"></div>
+
+        <!-- 页面列表 -->
+        <el-table
+          :data="tableData"
+          border
+          style="width: 100%; text-align: center"
+          @selection-change="selectChange"
+        >
+          <el-table-column type="selection" width="50"></el-table-column>
+          <el-table-column
+            prop="id"
+            width="100"
+            label="ID"
+            sortable
+          ></el-table-column>
+          <el-table-column label="IP地址">
+            <span slot-scope="scope" v-html="scope.row.ip"></span>
+          </el-table-column>
+          <el-table-column
+            prop="limitType"
+            label="限制类型"
+            sortable
+          ></el-table-column>
+          <el-table-column
+            width="180"
+            prop="creationTime"
+            label="创建时间"
+          ></el-table-column>
+          <el-table-column
+            width="180"
+            prop="updateTime"
+            label="修改时间"
+          ></el-table-column>
+          <el-table-column label="操作" width="300">
+            <div slot-scope="scope">
+              <el-button
+                size="mini"
+                type="primary"
+                icon="el-icon-edit"
+                @click="editIpLimit(scope.row)"
+                >修改</el-button
+              >
+              <el-button
+                size="mini"
+                type="danger"
+                icon="el-icon-delete"
+                @click="deleteIpLimit(scope.row)"
+                >删除</el-button
+              >
+              <el-button
+                size="mini"
+                type="primary"
+                icon="el-icon-edit"
+                @click="editIpLimitType(scope.row)"
+                >切换类型</el-button
+              >
+            </div>
+          </el-table-column>
+        </el-table>
+        <div class="page pull-right">
+          <el-pagination
+            :current-page="currentPage"
+            :page-size="pageSize"
+            :page-sizes="[10, 20, 50, 100, 200, 300]"
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="total"
+            @current-change="handleCurrentChange"
+            @size-change="handleSizeChange"
+          ></el-pagination>
+        </div>
+
+        <!-- 新增弹出窗口 -->
+        <el-dialog
+          title="IP限制信息页面"
+          :visible.sync="addingIpLimitDialog"
+          width="650px"
+          @close="closeAddIpLimitDialog"
+        >
+          <el-form
+            ref="addIpLimitForm"
+            :rules="rules"
+            :model="examIpLimitForm"
+            :inline="true"
+            label-width="85px"
+            class="editForm"
+          >
+            <el-form-item label="ip" prop="ip">
+              <el-input
+                v-model="examIpLimitForm.ip"
+                class="input"
+                auto-complete="off"
+                :disabled="!showIpColumn"
+                maxlength="20"
+              ></el-input>
+            </el-form-item>
+            <el-form-item label="限制类型" prop="limitType">
+              <el-select
+                v-model="examIpLimitForm.limitType"
+                class="input"
+                auto-complete="off"
+                maxlength="20"
+              >
+                <el-option
+                  v-for="item in limitTypeList"
+                  :key="item.id"
+                  :label="item.label"
+                  :value="item.id"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+          </el-form>
+          <div style="text-align: center">
+            <el-button type="primary" @click="addIpLimit">确 定</el-button>
+            <el-button @click="closeAddIpLimitDialog">取 消</el-button>
+          </div>
+        </el-dialog>
+
+        <!-- 切换类型弹出窗口 -->
+        <el-dialog
+          title="类型切换页面"
+          :visible.sync="editIpLimitTypeDialog"
+          width="650px"
+          @close="closeEditIpLimitTypeDialog"
+        >
+          <el-form
+            ref="addIpLimitTypeForm"
+            :rules="rules"
+            :model="examIpLimitTypeForm"
+            :inline="true"
+            label-width="85px"
+            style="text-align: center"
+            class="editForm"
+          >
+            <el-form-item label="限制类型" prop="limitType">
+              <el-select
+                v-model="examIpLimitTypeForm.limitType"
+                class="input"
+                auto-complete="off"
+                maxlength="20"
+              >
+                <el-option
+                  v-for="item in limitTypeList"
+                  :key="item.id"
+                  :label="item.label"
+                  :value="item.id"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+          </el-form>
+          <div style="text-align: center">
+            <el-button type="primary" @click="addIpLimitType">确 定</el-button>
+            <el-button @click="closeEditIpLimitTypeDialog">取 消</el-button>
+          </div>
+        </el-dialog>
+
+        <!-- 控制修改弹出窗口 -->
+        <el-dialog
+          title="控制修改页面"
+          :visible.sync="editIpLimitPropertyDialog"
+          width="650px"
+          @close="closeEditIpLimitPropertyDialog"
+        >
+          <el-form
+            ref="addIpLimitPropertyForm"
+            :rules="rules"
+            :model="examIpLimitPropertyForm"
+            :inline="true"
+            label-width="85px"
+            class="editForm"
+          >
+            <el-form-item label="整体控制" prop="totalLimit">
+              <el-select
+                v-model="examIpLimitPropertyForm.totalLimit"
+                class="input"
+                auto-complete="off"
+                maxlength="20"
+              >
+                <el-option
+                  v-for="item in totalList"
+                  :key="item.id"
+                  :label="item.label"
+                  :value="item.id"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="学习中心" prop="centerLimit">
+              <el-select
+                v-model="examIpLimitPropertyForm.centerLimit"
+                class="input"
+                auto-complete="off"
+                maxlength="20"
+              >
+                <el-option
+                  v-for="item in centerList"
+                  :key="item.id"
+                  :label="item.label"
+                  :value="item.id"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+          </el-form>
+          <div style="text-align: center">
+            <el-button type="primary" @click="addIpLimitProperty"
+              >确 定</el-button
+            >
+            <el-button @click="closeEditIpLimitPropertyDialog">取 消</el-button>
+          </div>
+        </el-dialog>
+
+        <!-- 导入弹窗 -->
+        <el-dialog
+          title="Ip限制信息导入页"
+          size="tiny"
+          :visible.sync="ipLimitImportDialog"
+        >
+          <el-form ref="ipLimitImportForm" :model="ipLimitImportForm">
+            <el-row>
+              <el-form-item style="margin-left: 30px">
+                <el-upload
+                  ref="upload"
+                  class="form_left"
+                  accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
+                  :action="uploadAction"
+                  :headers="uploadHeaders"
+                  :data="uploadData"
+                  :on-success="uploadSuccess"
+                  :on-error="uploadError"
+                  :file-list="fileList"
+                  :auto-upload="false"
+                  :multiple="false"
+                >
+                  <el-button
+                    slot="trigger"
+                    size="small"
+                    type="primary"
+                    icon="el-icon-search"
+                    >选择文件</el-button
+                  >
+                  <el-button
+                    size="small"
+                    style="margin-left: 10px"
+                    type="primary"
+                    icon="el-icon-check"
+                    @click="submitUpload"
+                    >确认上传
+                  </el-button>
+                  <el-button
+                    size="small"
+                    style="margin-left: 10px"
+                    type="primary"
+                    icon="el-icon-refresh"
+                    @click="removeFile"
+                    >清空文件
+                  </el-button>
+                  <el-button
+                    size="small"
+                    type="primary"
+                    icon="el-icon-download"
+                    @click="exportFile"
+                    >下载模板
+                  </el-button>
+                  <div slot="tip" class="el-upload__tip">只能上传xlsx文件</div>
+                </el-upload>
+              </el-form-item>
+            </el-row>
+          </el-form>
+        </el-dialog>
+        <!-- 导入错误信息列表 -->
+        <el-dialog title="错误提示" :visible.sync="errDialog">
+          <div
+            v-for="errMessage in errMessages"
+            :key="errMessage.lineNum"
+            class="text-danger"
+          >
+            第{{ errMessage.lineNum }}行:{{ errMessage.msg }}
+          </div>
+          <span slot="footer" class="dialog-footer">
+            <el-button @click="errDialog = false">确定</el-button>
+          </span>
+        </el-dialog>
+      </div>
+    </div>
+  </section>
+</template>
+
+<script>
+import { EXAM_WORK_API } from "@/constants/constants.js";
+import { mapState } from "vuex";
+
+export default {
+  data() {
+    return {
+      limitTypeList: [
+        {
+          id: 0,
+          label: "允许访问",
+        },
+        {
+          id: 1,
+          label: "禁止访问",
+        },
+      ],
+      centerList: [
+        {
+          id: false,
+          label: "关闭",
+        },
+        {
+          id: true,
+          label: "开启",
+        },
+      ],
+      totalList: [
+        {
+          id: false,
+          label: "允许访问",
+        },
+        {
+          id: true,
+          label: "禁止访问",
+        },
+      ],
+      showIpColumn: true,
+      button: {},
+      ipLimitImportDialog: false,
+      editIpLimitPropertyDialog: false,
+      examIpLimitForm: {
+        id: "",
+        ip: "",
+        examId: "",
+        limitType: "",
+      },
+      examIpLimitTypeForm: {
+        id: "",
+        examId: null,
+        limitType: "",
+      },
+      examIpLimitPropertyForm: {
+        totalLimit: "",
+        centerLimit: "",
+      },
+      formSearch: {
+        examId: "",
+        examName: "",
+        examType: "",
+        ip: "",
+        limitType: "",
+      },
+      selectedIpIds: [],
+
+      tableData: [],
+      currentPage: 1,
+      pageSize: 10,
+      total: 10,
+
+      rules: {
+        ip: [
+          { required: true, message: "Ip不能为空!", trigger: "change" },
+          {
+            pattern: /^(?:(?:1[0-9][0-9]\.)|(?:2[0-4][0-9]\.)|(?:25[0-5]\.)|(?:[1-9][0-9]\.)|(?:[0-9/*]\.)){3}(?:(?:1[0-9][0-9])|(?:2[0-4][0-9])|(?:25[0-5])|(?:[1-9][0-9])|(?:[0-9/*]))$/,
+            message: "请输入正确的ip格式",
+          },
+        ],
+        limitType: [
+          { required: true, message: "限制类型不能为空!", trigger: "change" },
+        ],
+      },
+      addingIpLimitDialog: false,
+      editIpLimitTypeDialog: false,
+      ipLimitImportForm: {
+        examId: "",
+      },
+      uploadAction: EXAM_WORK_API + "/exam/ipLimited/import",
+      uploadData: {},
+      fileLoading: false,
+      errDialog: false,
+      fileList: [],
+      uploadHeaders: {},
+    };
+  },
+  computed: {
+    ...mapState({ user: (state) => state.user }),
+    stuIds() {
+      var ipIds = "";
+      for (let ipId of this.selectedIpIds) {
+        if (!ipIds) {
+          ipIds += ipId;
+        } else {
+          ipIds += "," + ipId;
+        }
+      }
+      return ipIds;
+    },
+    noBatchSelected() {
+      return this.selectedIpIds.length === 0;
+    },
+    isSuperAdmin() {
+      return this.user.roleList.some((role) => role.roleCode == "SUPER_ADMIN");
+    },
+  },
+  created() {
+    this.init();
+    this.uploadHeaders = {
+      key: this.user.key,
+      token: this.user.token,
+    };
+    this.formSearch.examId = this.$route.params.id;
+    this.formSearch.examType = this.$route.params.examTypeName;
+    this.formSearch.examName = this.$route.params.examName;
+  },
+  methods: {
+    //新增
+    openAddingDialog() {
+      if (this.$refs["examIpLimitForm"]) {
+        this.$refs["examIpLimitForm"].resetFields();
+      }
+      this.examIpLimitForm.id = null;
+      this.examIpLimitForm.ip = null;
+      this.examIpLimitForm.limitType = null;
+      this.addingIpLimitDialog = true;
+      this.showIpColumn = true;
+    },
+    closeAddIpLimitDialog() {
+      this.addingIpLimitDialog = false;
+    },
+    closeEditIpLimitTypeDialog() {
+      this.editIpLimitTypeDialog = false;
+    },
+    closeEditIpLimitPropertyDialog() {
+      this.editIpLimitPropertyDialog = false;
+    },
+    //新增信息
+    addIpLimit() {
+      var url = EXAM_WORK_API + "/exam/ipLimited";
+      this.examIpLimitForm.examId = this.formSearch.examId;
+      this.$refs.addIpLimitForm.validate((valid) => {
+        if (valid) {
+          this.$httpWithMsg.post(url, this.examIpLimitForm).then(() => {
+            this.$notify({
+              type: "success",
+              message: "保存成功",
+            });
+            this.addingIpLimitDialog = false;
+            this.searchForm();
+          });
+        } else {
+          return false;
+        }
+      });
+    },
+    addIpLimitType() {
+      var ids = this.selectedIpIds;
+      var id = this.examIpLimitTypeForm.id;
+      var url = EXAM_WORK_API + "/exam/ipLimited";
+      if (!id) {
+        url = url + "?ids=" + ids;
+      }
+      this.examIpLimitTypeForm.examId = this.formSearch.examId;
+      this.$refs.addIpLimitTypeForm.validate((valid) => {
+        if (valid) {
+          this.$httpWithMsg.post(url, this.examIpLimitTypeForm).then(() => {
+            this.$notify({
+              type: "success",
+              message: "保存成功",
+            });
+            this.editIpLimitTypeDialog = false;
+            this.searchForm();
+          });
+        } else {
+          return false;
+        }
+      });
+    },
+    addIpLimitProperty() {
+      var url =
+        EXAM_WORK_API + "/exam/ipLimited/property/" + this.formSearch.examId;
+      this.$refs.addIpLimitPropertyForm.validate((valid) => {
+        if (valid) {
+          this.$httpWithMsg.post(url, this.examIpLimitPropertyForm).then(() => {
+            this.$notify({
+              type: "success",
+              message: "保存成功",
+            });
+            this.editIpLimitPropertyDialog = false;
+            this.searchForm();
+          });
+        } else {
+          return false;
+        }
+      });
+    },
+    editIpLimit(row) {
+      if (this.$refs.examIpLimitForm) {
+        this.$refs.examIpLimitForm.resetFields();
+      }
+      this.examIpLimitForm.id = row.id;
+      this.examIpLimitForm.ip = row.ip;
+      this.examIpLimitForm.limitType = this.getLimitType(row.limitType);
+      this.addingIpLimitDialog = true;
+      this.showIpColumn = true;
+    },
+    getLimitType(type) {
+      if (type === "允许访问") {
+        return 0;
+      } else if (type === "禁止访问") {
+        return 1;
+      }
+      return null;
+    },
+    batchDelete() {
+      this.$confirm("确定删除所选数据吗?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "error",
+      }).then(() => {
+        let url = EXAM_WORK_API + "/exam/ipLimited/" + this.selectedIpIds;
+        this.deleteIp(url);
+      });
+    },
+    allDelete() {
+      this.$confirm("确定删除该考试下的所有Ip限制吗?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "error",
+      }).then(() => {
+        let url =
+          EXAM_WORK_API + "/exam/ipLimited/all/" + this.formSearch.examId;
+        this.deleteIp(url);
+      });
+    },
+    deleteIpLimit(row) {
+      this.$confirm("确定删除当前数据吗?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "error",
+      }).then(() => {
+        let url = EXAM_WORK_API + "/exam/ipLimited/" + row.id;
+        this.deleteIp(url);
+      });
+    },
+    editIpLimitType(row) {
+      if (this.$refs.addIpLimitTypeForm) {
+        this.$refs.addIpLimitTypeForm.resetFields();
+      }
+      if (row) {
+        this.examIpLimitTypeForm.id = row.id;
+        this.examIpLimitTypeForm.limitType = row.limitType;
+      } else {
+        this.examIpLimitTypeForm.id = null;
+        this.examIpLimitTypeForm.limitType = null;
+      }
+      this.editIpLimitTypeDialog = true;
+    },
+    editIpLimitProperty() {
+      if (this.$refs.addIpLimitPropertyForm) {
+        this.$refs.addIpLimitPropertyForm.resetFields();
+      }
+      var url =
+        EXAM_WORK_API + "/exam/ipLimited/property/" + this.formSearch.examId;
+      this.$httpWithMsg.get(url).then((response) => {
+        this.examIpLimitPropertyForm.totalLimit = response.data.totalLimit;
+        this.examIpLimitPropertyForm.centerLimit = response.data.centerLimit;
+      });
+      this.editIpLimitPropertyDialog = true;
+    },
+    deleteIp(url) {
+      this.$httpWithMsg.delete(url).then(() => {
+        this.$notify({
+          type: "success",
+          message: "操作成功!",
+        });
+        this.searchForm();
+      });
+    },
+    selectChange(row) {
+      this.selectedIpIds = [];
+      row.forEach((element) => {
+        this.selectedIpIds.push(element.id);
+      });
+    },
+    handleCurrentChange(val) {
+      this.currentPage = val;
+      this.searchForm();
+    },
+    handleSizeChange(val) {
+      this.pageSize = val;
+      this.searchForm();
+    },
+    resetSearchForm() {
+      this.formSearch.ip = "";
+      this.formSearch.limitType = "";
+    },
+    resetPageAndSearchForm() {
+      this.currentPage = 1;
+      this.searchForm();
+    },
+    //查询方法
+    searchForm() {
+      this.formSearch.examId = this.$route.params.id;
+      var param = new URLSearchParams(this.formSearch);
+      var url =
+        EXAM_WORK_API +
+        "/exam/ipLimited/page/" +
+        (this.currentPage - 1) +
+        "/" +
+        this.pageSize +
+        "?" +
+        param;
+      this.$httpWithMsg.get(url).then((response) => {
+        this.tableData = response.data.list;
+        this.total = response.data.total;
+      });
+    },
+    batchExport() {
+      var param =
+        new URLSearchParams(this.formSearch) +
+        "&$key=" +
+        this.user.key +
+        "&$token=" +
+        this.user.token;
+      var url = EXAM_WORK_API + "/exam/ipLimited/export?" + param;
+      window.open(url);
+    },
+    initUpload() {
+      if (this.$refs.ipLimitImportForm) {
+        this.$refs.ipLimitImportForm.resetFields();
+      }
+      this.fileList = [];
+      this.uploadAction =
+        EXAM_WORK_API + "/exam/ipLimited/import/" + this.formSearch.examId;
+    },
+    batchImport() {
+      this.ipLimitImportDialog = true;
+      this.initUpload();
+    },
+    uploadSuccess(response) {
+      if (!response.hasError) {
+        this.$notify({
+          message: "上传成功",
+          type: "success",
+        });
+        this.fileLoading = false;
+        this.ipLimitImportDialog = false;
+        this.searchForm();
+      } else {
+        this.fileLoading = false;
+        this.ipLimitImportDialog = false;
+        this.errMessages = response.failRecords;
+        this.errDialog = true;
+      }
+    },
+    uploadError(response) {
+      console.log("uploadError");
+      var json = JSON.parse(response.message);
+      if (response.status == 500) {
+        this.$notify({
+          message: json.desc,
+          type: "error",
+        });
+      }
+      this.fileLoading = false;
+    },
+    //确定上传
+    submitUpload() {
+      if (!this.checkUpload()) {
+        return false;
+      }
+      this.$refs.upload.submit();
+      this.fileLoading = true;
+    },
+    checkUpload() {
+      var fileList = this.$refs.upload.uploadFiles;
+      if (fileList.length == 0) {
+        this.$notify({
+          message: "上传文件不能为空",
+          type: "error",
+        });
+        return false;
+      }
+      if (fileList.length > 1) {
+        this.$notify({
+          message: "每次只能上传一个文件",
+          type: "error",
+        });
+        return false;
+      }
+      for (let file of fileList) {
+        if (!file.name.endsWith(".xlsx")) {
+          this.$notify({
+            message: "上传文件必须为xlsx格式",
+            type: "error",
+          });
+          this.initUpload();
+          return false;
+        }
+      }
+      return true;
+    },
+    //清空文件
+    removeFile() {
+      this.$refs.upload.clearFiles();
+    },
+    //下载模板
+    exportFile() {
+      window.location.href =
+        "/api/ecs_exam_work/exam/ipLimited/downloadTemplate?$key=" +
+        this.user.key +
+        "&$token=" +
+        this.user.token;
+    },
+    back() {
+      this.$router.push({ path: "/examwork/examInfo" });
+    },
+    async init() {
+      this.searchForm();
+    },
+  },
+};
+</script>
+<style scoped>
+.input {
+  width: 180px;
+}
+</style>

+ 45 - 22
src/modules/examwork/view/notice.vue

@@ -91,11 +91,11 @@
             </template>
           </el-table-column>
           <el-table-column
-            width
+            width="80"
             prop="publisher"
             label="发布者"
           ></el-table-column>
-          <el-table-column width="130" label="状态" sortable>
+          <el-table-column width="80" label="状态" sortable>
             <template slot-scope="scope">
               <div>
                 <span>{{ getPublishStatus(scope.row.publishStatus) }}</span>
@@ -108,9 +108,10 @@
             label="发送时间"
             sortable
           ></el-table-column>
-          <el-table-column label="操作">
+          <el-table-column label="操作" width="270">
             <div slot-scope="scope">
               <el-button
+                :disabled="scope.row.publishStatus == 'RECALLED'"
                 size="mini"
                 type="primary"
                 icon="el-icon-view"
@@ -118,6 +119,14 @@
                 @click="viewNoticeDialog(scope.row)"
                 >详情</el-button
               >
+              <el-button
+                v-if="scope.row.publishStatus == 'DRAFT'"
+                size="mini"
+                type="primary"
+                icon="el-icon-message"
+                @click="sendMsg(scope.row.id)"
+                >发送</el-button
+              >
               <el-dropdown
                 style="margin-left: 10px"
                 :disabled="scope.row.publishStatus != 'DRAFT'"
@@ -147,6 +156,19 @@
                       >删除</el-button
                     >
                   </el-dropdown-item>
+                  <el-dropdown-item>
+                    <el-button
+                      v-if="
+                        scope.row.publishStatus == 'PUBLISHED' ||
+                        scope.row.publishStatus == 'TO_BE_PUBLISHED'
+                      "
+                      size="mini"
+                      type="warning"
+                      icon="el-icon-back"
+                      @click="recallMsg(scope.row.id)"
+                      >撤回</el-button
+                    >
+                  </el-dropdown-item>
                 </el-dropdown-menu>
               </el-dropdown>
             </div>
@@ -283,14 +305,7 @@
               v-show="operateType != 'view'"
               type="primary"
               :loading="noticeFormLoading"
-              @click="saveNotice(2)"
-              >确认发送</el-button
-            >
-            <el-button
-              v-show="operateType != 'view'"
-              type="primary"
-              :loading="noticeFormLoading"
-              @click="saveNotice(1)"
+              @click="saveNotice"
               >保 存</el-button
             >
             <el-button @click="cancel">关 闭</el-button>
@@ -883,13 +898,24 @@ export default {
         });
       }
     },
-    saveNotice(option) {
-      if (option == 1) {
-        this.noticeForm.noticeStatus = "DRAFT";
-      } else {
-        this.noticeForm.noticeStatus = "TO_BE_PUBLISHED";
-      }
-
+    sendMsg(id) {
+      this.$httpWithMsg
+        .get(EXAM_WORK_API + "/notice/sendMsg/" + id)
+        .then(() => {
+          this.success("发送成功");
+          this.searchForm();
+        });
+    },
+    recallMsg(id) {
+      this.$httpWithMsg
+        .get(EXAM_WORK_API + "/notice/recallMsg/" + id)
+        .then(() => {
+          this.success("撤回成功");
+          this.searchForm();
+        });
+    },
+    saveNotice() {
+      this.noticeForm.noticeStatus = "DRAFT";
       this.$refs["noticeForm"].validate((valid) => {
         if (valid) {
           if (
@@ -921,13 +947,12 @@ export default {
                 () => {
                   this.editNoticeDialogVisible = true;
                   this.noticeFormLoading = false;
-                  // this.searchForm();
-                  // this.editNoticeDialogVisible = false;
                 }
               );
           }
           //新增
           else {
+            this.noticeForm.noticeStatus = "DRAFT";
             this.$httpWithMsg
               .post(EXAM_WORK_API + "/notice/addNotice", this.noticeForm)
               .then(
@@ -940,8 +965,6 @@ export default {
                 () => {
                   this.editNoticeDialogVisible = true;
                   this.noticeFormLoading = false;
-                  // this.editNoticeDialogVisible = false;
-                  // return this.searchForm();
                 }
               );
           }

+ 15 - 27
src/modules/examwork/view/onlineExam.vue

@@ -230,6 +230,18 @@
                     >开启手机app考试,将不能开启人脸身份检测</span
                   >
                 </el-row>
+                <el-row>
+                  <el-form-item
+                    label="开启IP访问设置"
+                    :label-width="style.label_width_tab1"
+                  >
+                    <el-switch
+                      v-model="form.properties.IP_LIMIT"
+                      on-text="是"
+                      off-text="否"
+                    ></el-switch>
+                  </el-form-item>
+                </el-row>
               </el-tab-pane>
               <!-- 周期设置 -->
               <el-tab-pane label="周期设置" name="tab8">
@@ -817,33 +829,6 @@
                   </el-form-item>
                 </el-row>
               </el-tab-pane>
-              <el-tab-pane label="网络设置" name="tab6">
-                <el-row>
-                  <el-form-item
-                    label="IP限制"
-                    :label-width="style.label_width_tab6"
-                  >
-                    <el-radio-group v-model="form.properties.IP_LIMIT">
-                      <el-radio label="true">开启</el-radio>
-                      <el-radio label="false">关闭</el-radio>
-                    </el-radio-group>
-                  </el-form-item>
-                </el-row>
-                <el-row>
-                  <el-form-item
-                    label="IP段( *表示任意 )"
-                    :label-width="style.label_width_tab6"
-                  >
-                    <el-input
-                      v-model="form.properties.IP_ADDRESSES"
-                      maxlength="2000"
-                      class="input"
-                      type="textarea"
-                      rows="6"
-                    ></el-input>
-                  </el-form-item>
-                </el-row>
-              </el-tab-pane>
               <el-tab-pane label="其它" name="tab7">
                 <el-row>
                   <el-form-item
@@ -1570,6 +1555,9 @@ export default {
             this.form.properties.APP_EXAM_ENABLED =
               this.form.properties.APP_EXAM_ENABLED === "true";
 
+            this.form.properties.IP_LIMIT =
+              this.form.properties.IP_LIMIT === "true";
+
             if (this.form.properties.EXAM_CYCLE_TIME_RANGE) {
               this.examCycleTimeRangeArr = JSON.parse(
                 this.form.properties.EXAM_CYCLE_TIME_RANGE

+ 1 - 0
src/modules/marking/filters/filters.js

@@ -4,6 +4,7 @@ export const TAGS = [
   { label: "雷同卷", value: "SAME" },
   { label: "空白卷", value: "BLANK" },
   { label: "答非所问", value: "IRRELEVANT" },
+  { label: "作弊违纪", value: "CHEAT" },
   { label: "科目错误", value: "SUBJECT_ERROR" },
   { label: "非手写", value: "QUESTIONABLE" },
 ];

+ 8 - 1
src/modules/marking/views/export_task_list.vue

@@ -184,7 +184,6 @@ export default {
   },
   created() {
     this.getMarkWorks();
-    this.doSearch(1);
   },
   methods: {
     getMarkWorks() {
@@ -198,6 +197,14 @@ export default {
         });
     },
     doSearch(pageNo) {
+      if (!this.formSearch.workId) {
+        this.$notify({
+          title: "警告",
+          message: "请选择评卷名称",
+          type: "warning",
+        });
+        return false;
+      }
       this.formSearch.pageNo = pageNo;
 
       this.loading = true;

+ 59 - 1
src/modules/oe/views/alreadyAudited.vue

@@ -158,6 +158,19 @@
           >重置</el-button
         >
       </el-col>
+      <el-row>
+        <el-col>
+          <div class="block-seperator"></div>
+          <span>操作:</span>
+          <el-button
+            type="primary"
+            size="small"
+            icon="el-icon-download"
+            @click="exportData"
+            >导出</el-button
+          >
+        </el-col>
+      </el-row>
       <el-row class="margin-top-10">
         <el-col :span="24">
           <el-table
@@ -342,7 +355,7 @@ export default {
         auditEndTime: null, //审核时间止
         ip: null, //Ip
       },
-
+      exportUrl: "/api/ecs_oe_admin/exam/audit/export/async",
       getExamCondition: {
         params: {
           name: "",
@@ -522,6 +535,51 @@ export default {
           }
         });
     },
+    exportData() {
+      if (!this.form.examId) {
+        this.$notify({
+          title: "警告",
+          message: "请选择考试",
+          type: "warning",
+          duration: 1000,
+        });
+        return false;
+      }
+
+      this.$confirm("确定执行导出?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning",
+      }).then(() => {
+        this.form.rootOrgId = this.user.rootOrgId;
+        this.form.creator = this.user.userId;
+        this.$http
+          .get(
+            this.exportUrl +
+              "?$key=" +
+              this.user.key +
+              "&$token=" +
+              this.user.token,
+            {
+              params: {
+                query: this.form,
+              },
+            }
+          )
+          .then(() => {
+            this.$notify({
+              type: "success",
+              message: "正在后台导出中,请稍后到“导出任务列表”中下载!",
+            });
+          })
+          .catch((error) => {
+            this.$notify({
+              type: "error",
+              message: error.response.data.desc,
+            });
+          });
+      });
+    },
     changeStartExamDatetimeRange(e) {
       if (e && e.length > 0) {
         this.form.startTime = e[0];

+ 104 - 1
src/modules/oe/views/awaitingAudit.vue

@@ -191,7 +191,7 @@
                   size="mini"
                   type="danger"
                   :disabled="batchAuditBtnDisabled"
-                  @click="batchAudit('nopass')"
+                  @click="openBatchAudit()"
                 >
                   <i class="el-icon-error"></i> 不通过
                 </el-button>
@@ -274,6 +274,7 @@
               sortable
               label="虚拟设备"
               prop="virtualCameraNames"
+              :formatter="formatVirtualCameraName"
               width="120"
             ></el-table-column>
             <el-table-column
@@ -417,6 +418,51 @@
           </div>
         </el-form>
       </el-dialog>
+      <el-dialog
+        title="审核"
+        :visible.sync="dialogBatchFormVisible"
+        @closed="batchAuditDialogClosed"
+      >
+        <el-form ref="batchAuditForm" :model="batchAuditForm">
+          <el-form-item
+            label="违纪类型"
+            prop="illegallyTypeId"
+            :rules="[
+              { required: true, message: '请选择违纪类型', trigger: 'change' },
+            ]"
+          >
+            <el-select
+              v-model="batchAuditForm.illegallyTypeId"
+              filterable
+              remote
+              :remote-method="getDisciplineTypeList"
+              clearable
+              placeholder="请选择"
+              size="small"
+              @clear="getDisciplineTypeList"
+            >
+              <el-option
+                v-for="item in disciplineTypeList"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="详情描述" style="margin-top: 15px">
+            <el-input
+              v-model="batchAuditForm.disciplineDetail"
+              type="textarea"
+              :autosize="{ minRows: 6, maxRows: 10 }"
+              placeholder="请输入内容"
+            ></el-input>
+          </el-form-item>
+          <div class="dialog-footer margin-top-10 text-center">
+            <el-button type="primary" @click="doBatchAudit">确 定</el-button>
+            <el-button @click="dialogBatchFormVisible = false">取 消</el-button>
+          </div>
+        </el-form>
+      </el-dialog>
     </el-main>
   </el-container>
 </template>
@@ -433,6 +479,7 @@ export default {
       total: 0,
       tableLoading: false,
       exportLoading: false,
+      dialogBatchFormVisible: false,
       dialogFormVisible: false,
       showAllCondition: false,
       form: {
@@ -467,6 +514,12 @@ export default {
         disciplineDetail: "",
         isPass: null,
       },
+      batchAuditForm: {
+        examRecordDataIds: null,
+        illegallyTypeId: null,
+        disciplineDetail: "",
+        isPass: null,
+      },
       getExamCondition: {
         params: {
           name: "",
@@ -585,6 +638,13 @@ export default {
           });
         });
     },
+    formatVirtualCameraName(row) {
+      if (row.virtualCameraNames === "") {
+        return ";";
+      } else {
+        return row.virtualCameraNames;
+      }
+    },
     selectable(row) {
       return row.isWarn;
     },
@@ -644,6 +704,19 @@ export default {
         isPass: false,
       };
     },
+    openBatchAudit() {
+      this.dialogBatchFormVisible = true;
+      var examRecordDataIds = [];
+      for (var i = 0; i < this.multipleSelection.length; i++) {
+        examRecordDataIds.push(this.multipleSelection[i].dataId);
+      }
+      this.batchAuditForm = {
+        examRecordDataIds: examRecordDataIds,
+        illegallyTypeId: null,
+        disciplineDetail: "",
+        isPass: false,
+      };
+    },
     doAudit() {
       this.$refs["auditForm"].validate((valid) => {
         if (valid) {
@@ -665,9 +738,39 @@ export default {
         }
       });
     },
+    doBatchAudit() {
+      this.$refs["batchAuditForm"].validate((valid) => {
+        if (valid) {
+          this.$confirm("确定执行?", "提示", {
+            confirmButtonText: "确定",
+            cancelButtonText: "取消",
+            type: "warning",
+          }).then(() => {
+            var param = new URLSearchParams(this.batchAuditForm);
+            this.$http
+              .post("/api/ecs_oe_admin/exam/audit/batch/audit", param)
+              .then(() => {
+                this.$notify({
+                  title: "成功",
+                  message: "操作成功",
+                  type: "success",
+                  duration: 5000,
+                });
+                this.dialogBatchFormVisible = false;
+                this.search();
+              });
+          });
+        } else {
+          return false;
+        }
+      });
+    },
     auditDialogClosed() {
       this.$refs["auditForm"].resetFields();
     },
+    batchAuditDialogClosed() {
+      this.$refs["batchAuditForm"].resetFields();
+    },
     /**
      * 审核通过
      */

+ 3 - 0
src/modules/oe/views/captureDetail.vue

@@ -364,6 +364,9 @@ export default {
               isPendingAudit == "false" &&
               response.data.isWarn &&
               !response.data.isAudit;
+            if (response.data.virtualCameraNames === "") {
+              response.data.virtualCameraNames = ";";
+            }
             this.examAuditData = new Array(response.data);
             var studentId = response.data.studentId;
             this.syncCapturePhotoPath = response.data.syncCaptureFileUrl;

+ 17 - 0
src/modules/oe/views/examDetail.vue

@@ -59,6 +59,16 @@
               </el-select>
             </el-form-item>
           </el-col>
+          <el-col :span="6">
+            <el-form-item label="审核人">
+              <el-input
+                v-model="form.auditUserName"
+                class="form_search_width"
+                size="small"
+                placeholder="审核人"
+              ></el-input>
+            </el-form-item>
+          </el-col>
         </el-row>
       </commonFormVue>
       <el-col :span="24">
@@ -247,6 +257,12 @@
               prop="isIllegality"
               width="120"
             ></el-table-column>
+            <el-table-column
+              sortable
+              label="审核人"
+              prop="auditUserName"
+              width="120"
+            ></el-table-column>
             <el-table-column
               sortable
               label="是否提交"
@@ -489,6 +505,7 @@ export default {
         startTime: null,
         endTime: null,
         infoCollector: null,
+        auditUserName: null,
         hasVirtual: null,
         isIllegality: null,
         ORG_FIND_ALL: false, //查询所有机构

+ 9 - 1
src/modules/oe/views/export_task_list.vue

@@ -177,7 +177,6 @@ export default {
   },
   created() {
     this.getExams();
-    this.doSearch(1);
   },
   methods: {
     getExams(examName) {
@@ -197,6 +196,15 @@ export default {
         });
     },
     doSearch(pageNo) {
+      if (!this.formSearch.examId) {
+        this.$notify({
+          title: "警告",
+          message: "请选择考试",
+          type: "warning",
+          duration: 2000,
+        });
+        return false;
+      }
       this.formSearch.pageNo = pageNo;
 
       this.loading = true;

+ 2 - 2
src/modules/portal/views/home/Home.vue

@@ -76,7 +76,7 @@
       title="个人信息"
       width="410px"
       :visible.sync="userDialog"
-      @close="() => this.$refs.passForm.clearValidate()"
+      @close="() => $refs.passForm.clearValidate()"
     >
       <el-tabs value="first">
         <el-tab-pane label="用户权限" name="first">
@@ -143,7 +143,7 @@
       :close-on-press-escape="false"
       :show-close="false"
       :visible.sync="passWeakDialog"
-      @close="() => this.$refs.passWeakForm.clearValidate()"
+      @close="() => $refs.passWeakForm.clearValidate()"
     >
       <el-form
         ref="passWeakForm"

+ 4 - 0
src/modules/portal/views/home/SiteMessageDetail.vue

@@ -81,6 +81,10 @@ export default {
     init() {
       var url = EXAM_WORK_API + "/notice/" + this.$route.params.id;
       this.$httpWithMsg.get(url).then((response) => {
+        if (response.data.publishStatus === "RECALLED") {
+          response.data.title = "发送者已撤回消息:" + response.data.title;
+          response.data.content = "该消息已被发送者撤回。";
+        }
         this.message = response.data;
       });
     },

+ 11 - 2
src/modules/portal/views/home/SiteMessageHome.vue

@@ -64,9 +64,12 @@
                       : 'mhome-message-unread'
                   "
                 />
+                <span v-if="scope.row.hasRecalled" class="mhome-message-title"
+                  >发送者已撤回消息:</span
+                >
                 <span class="mhome-message-title">{{ scope.row.title }}</span>
-              </router-link></span
-            ></el-table-column
+              </router-link>
+            </span></el-table-column
           >
           <el-table-column width="180" prop="publishTime" label="发送时间">
           </el-table-column>
@@ -179,25 +182,31 @@ export default {
 .page {
   margin-top: 10px;
 }
+
 .pull-length {
   width: 300px;
 }
+
 .pull-center {
   margin-top: 20px;
 }
+
 .editForm .el-form-item {
   margin-bottom: 12px;
 }
+
 .mhome-message-read {
   width: 16px;
   height: 14px;
   background-image: url(./svgs/sms-read.svg);
 }
+
 .mhome-message-unread {
   width: 16px;
   height: 14px;
   background-image: url(./svgs/sms-unread.svg);
 }
+
 .mhome-message-title {
   line-height: 14px;
   margin-left: 8px;

+ 12 - 0
src/modules/portal/views/home/SiteMessagePopup.vue

@@ -130,6 +130,12 @@ export default {
         this.$httpWithoutBar
           .get("/api/ecs_exam_work/notice/getUserNoticeList?hasRead=false")
           .then((response) => {
+            for (let notice of response.data) {
+              if (notice.hasRecalled === true) {
+                notice.title = "发送者已撤回消息:" + notice.title;
+                notice.content = "该消息已被发送者撤回。";
+              }
+            }
             this.siteMessages = response.data;
             setTimeout(() => {
               this.getUnreadNoticeList();
@@ -143,6 +149,12 @@ export default {
       this.$httpWithoutBar
         .get("/api/ecs_exam_work/notice/getUserNoticeList?hasRead=false")
         .then((response) => {
+          for (let notice of response.data) {
+            if (notice.hasRecalled === true) {
+              notice.title = "发送者已撤回消息:" + notice.title;
+              notice.content = "该消息已被发送者撤回。";
+            }
+          }
           this.siteMessages = response.data;
         });
     },

+ 10 - 0
src/modules/questions/routes/routes.js

@@ -23,6 +23,8 @@ import PreviewPaper from "../views/PreviewPaper.vue";
 import SelectQuestion from "../views/SelectQuestion.vue";
 import Tips from "../../portal/views/tips/Tips.vue";
 import ExportTemplate from "../views/ExportTemplate.vue";
+import PaperStorage from "../views/PaperStorage.vue";
+import ViewPaper from "../views/ViewPaper.vue";
 
 export default [
   {
@@ -74,6 +76,10 @@ export default [
         path: "gen_paper/:isClear", //卷库试卷列表
         component: GenPaper,
       },
+      {
+        path: "paper_storage/:isClear", //卷库试卷列表
+        component: PaperStorage,
+      },
 
       {
         path: "import_paper_info", //导入试卷页面
@@ -146,4 +152,8 @@ export default [
     path: "/preview_paper/:paperId", //预览试卷
     component: PreviewPaper,
   },
+  {
+    path: "/view_paper/:id", //试卷查看
+    component: ViewPaper,
+  },
 ];

+ 207 - 0
src/modules/questions/views/CourseProperty.vue

@@ -76,6 +76,17 @@
               @click="closeCoursePropertys"
               ><i class="el-icon-close"></i> 禁用</el-button
             >
+            <el-button
+              size="small"
+              type="primary"
+              icon="el-icon-upload2"
+              @click="impCourseProperty"
+            >
+              导入
+            </el-button>
+            <el-button size="small" type="primary" @click="exportCourseProperty"
+              ><i class="el-icon-download"></i>导出</el-button
+            >
           </el-form-item>
         </el-row>
       </el-form>
@@ -224,12 +235,86 @@
         </el-row>
       </el-form>
     </el-dialog>
+    <!-- 导入弹窗 -->
+    <el-dialog title="导入窗口" width="520px" :visible.sync="impDialog">
+      <el-form>
+        <el-row>
+          <el-form-item style="margin-left: 20px">
+            <el-upload
+              ref="upload"
+              class="form_left"
+              accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
+              :action="uploadAction"
+              :headers="uploadHeaders"
+              :data="uploadData"
+              :before-upload="beforeUpload"
+              :on-progress="uploadProgress"
+              :on-success="uploadSuccess"
+              :on-error="uploadError"
+              :file-list="fileList"
+              :auto-upload="false"
+              :multiple="false"
+            >
+              <el-button
+                slot="trigger"
+                size="small"
+                type="primary"
+                icon="el-icon-search"
+              >
+                选择文件
+              </el-button>
+              &nbsp;
+              <el-button
+                size="small"
+                type="primary"
+                icon="el-icon-check"
+                @click="submitUpload"
+              >
+                确认上传
+              </el-button>
+              <el-button
+                size="small"
+                type="primary"
+                icon="el-icon-refresh"
+                @click="removeFile"
+              >
+                清空文件
+              </el-button>
+              <el-button
+                size="small"
+                type="primary"
+                icon="el-icon-download"
+                @click="exportFile"
+              >
+                下载模板
+              </el-button>
+              <div slot="tip" class="el-upload__tip">只能上传xlsx文件</div>
+            </el-upload>
+          </el-form-item>
+        </el-row>
+      </el-form>
+    </el-dialog>
+
+    <!-- 导入错误信息列表 -->
+    <el-dialog title="错误提示" :visible.sync="errDialog">
+      <div
+        v-for="errMessage in errMessages"
+        :key="errMessage.lineNum"
+        class="text-danger"
+      >
+        第{{ errMessage.lineNum }}行:{{ errMessage.msg }}
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="errDialog = false">确定</el-button>
+      </span>
+    </el-dialog>
   </section>
 </template>
 
 <script>
 import { CORE_API, QUESTION_API } from "@/constants/constants";
 import LinkTitlesCustom from "@/components/LinkTitlesCustom.vue";
+import { mapState } from "vuex";
 export default {
   components: { LinkTitlesCustom },
   data() {
@@ -238,6 +323,16 @@ export default {
         name: "",
         courseId: "",
       },
+
+      impDialog: false,
+      uploadAction: QUESTION_API + "/courseProperty/import",
+      uploadHeaders: {},
+      uploadData: {},
+      errMessages: [],
+      errDialog: false,
+      fileLoading: false,
+      fileList: [],
+
       courseList: [],
       loading: false,
       tableData: [],
@@ -260,6 +355,9 @@ export default {
     };
   },
   computed: {
+    ...mapState({
+      user: (state) => state.user,
+    }),
     selectedIds() {
       var selectedIdsStr = "";
       for (let id of this.selectedList) {
@@ -292,9 +390,118 @@ export default {
   },
   created() {
     this.initValue();
+    this.uploadHeaders = {
+      key: this.user.key,
+      token: this.user.token,
+    };
   },
 
   methods: {
+    exportCourseProperty() {
+      var key = this.user.key;
+      var token = this.user.token;
+      window.open(
+        QUESTION_API +
+          "/courseProperty/export?$key=" +
+          key +
+          "&$token=" +
+          token +
+          "&name=" +
+          this.formSearch.name +
+          "&courseId=" +
+          this.formSearch.courseId
+      );
+    },
+    //导入
+    impCourseProperty() {
+      this.impDialog = true;
+      this.initUpload();
+    },
+    initUpload() {
+      this.fileList = [];
+    },
+    beforeUpload(file) {
+      console.log(file);
+    },
+    uploadProgress() {
+      console.log("uploadProgress");
+    },
+    uploadSuccess(response) {
+      if (!response.hasError) {
+        this.$notify({
+          message: "上传成功",
+          type: "success",
+        });
+        this.fileLoading = false;
+        this.impDialog = false;
+        this.searchCourProperty();
+      } else {
+        this.fileLoading = false;
+        this.impDialog = false;
+        this.errMessages = response.failRecords;
+        this.errDialog = true;
+      }
+    },
+    uploadError(response) {
+      var json = JSON.parse(response.message);
+      if (response.status == 500) {
+        this.$notify({
+          message: json.desc,
+          type: "error",
+        });
+      }
+      this.fileLoading = false;
+    },
+    //确定上传
+    submitUpload() {
+      if (!this.checkUpload()) {
+        return false;
+      }
+      this.$refs.upload.submit();
+      this.fileLoading = true;
+    },
+    checkUpload() {
+      var fileList = this.$refs.upload.uploadFiles;
+      if (fileList.length == 0) {
+        this.$notify({
+          message: "上传文件不能为空",
+          type: "error",
+        });
+        return false;
+      }
+      if (fileList.length > 1) {
+        this.$notify({
+          message: "每次只能上传一个文件",
+          type: "error",
+        });
+        return false;
+      }
+      for (let file of fileList) {
+        if (!file.name.endsWith(".xlsx")) {
+          this.$notify({
+            message: "上传文件必须为xlsx格式",
+            type: "error",
+          });
+          this.initUpload();
+          return false;
+        }
+      }
+      return true;
+    },
+    //清空文件
+    removeFile() {
+      // this.fileList = [];
+      this.$refs.upload.clearFiles();
+    },
+    //下载模板
+    exportFile() {
+      window.location.href =
+        QUESTION_API +
+        "/courseProperty/importTemplate?$key=" +
+        this.user.key +
+        "&$token=" +
+        this.user.token;
+    },
     //查询所有课程属性
     searchFrom() {
       this.currentPage = 1;

+ 87 - 67
src/modules/questions/views/EditPaper.vue

@@ -48,6 +48,15 @@
               更多 <i class="el-icon-arrow-down el-icon--right"></i>
             </el-button>
             <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item>
+                <el-button
+                  v-show="parentView == 'gen_paper'"
+                  type="danger"
+                  size="small"
+                  @click="recoveryPaper(paper.id)"
+                  ><i class="el-icon-delete"></i>回收
+                </el-button>
+              </el-dropdown-item>
               <el-dropdown-item>
                 <el-button
                   type="primary"
@@ -512,10 +521,18 @@
                 >
                   <div slot="content">
                     <span v-if="content.firstProperty != null"
-                      >一级属性:{{ content.firstProperty.name }}</span
+                      >一级属性:{{ content.firstProperty.name }}({{
+                        content.firstProperty.code
+                      }})</span
                     ><br />
-                    <span v-if="content.secondProperty != null"
-                      >二级属性:{{ content.secondProperty.name }}</span
+                    <span
+                      v-if="
+                        content.secondProperty != null &&
+                        content.secondProperty.code
+                      "
+                      >二级属性:{{ content.secondProperty.name }}({{
+                        content.secondProperty.code
+                      }})</span
                     >
                   </div>
                   <span>
@@ -568,7 +585,7 @@
                   <el-option
                     v-for="item in firstPropertyList"
                     :key="item.id"
-                    :label="item.name"
+                    :label="item.name + '(' + item.code + ')'"
                     :value="item.id"
                   >
                   </el-option>
@@ -587,7 +604,7 @@
                   <el-option
                     v-for="item in secondPropertyList"
                     :key="item.id"
-                    :label="item.name"
+                    :label="item.name + '(' + item.code + ')'"
                     :value="item.id"
                   >
                   </el-option>
@@ -607,34 +624,7 @@
             </el-col>
           </el-row>
           <!-- end by weiwenhai -->
-          <div v-if="paper.paperType != 'IMPORT'">
-            <el-row>
-              <el-col>
-                <el-form-item label="题目">
-                  <span v-html="quesModel.quesBody"></span>
-                </el-form-item>
-              </el-col>
-            </el-row>
-
-            <el-form-item
-              v-for="(quesOption, optIndex) in quesModel.quesOptions"
-              :key="optIndex"
-              ><el-col :span="2">
-                <span>{{ optIndex | optionOrderWordFilter }}</span>
-              </el-col>
-              <el-col :span="20">
-                <span v-html="quesOption.optionBody"></span>
-              </el-col>
-            </el-form-item>
-
-            <div>
-              <el-form-item label="答案">
-                <span v-html="quesModel.quesAnswer"></span>
-              </el-form-item>
-            </div>
-            <!-- 单选或多选 -->
-          </div>
-          <div v-if="paper.paperType == 'IMPORT'">
+          <div>
             <el-row>
               <el-col>
                 <el-form-item label="题目">
@@ -718,21 +708,18 @@
           <div
             :class="{
               margin_left_30:
-                paper.paperType == 'IMPORT' &&
-                (quesModel.questionType == 'SINGLE_ANSWER_QUESTION' ||
-                  quesModel.questionType == 'MULTIPLE_ANSWER_QUESTION'),
+                quesModel.questionType == 'SINGLE_ANSWER_QUESTION' ||
+                quesModel.questionType == 'MULTIPLE_ANSWER_QUESTION',
               margin_left_40: !(
-                paper.paperType == 'IMPORT' &&
-                (quesModel.questionType == 'SINGLE_ANSWER_QUESTION' ||
-                  quesModel.questionType == 'MULTIPLE_ANSWER_QUESTION')
+                quesModel.questionType == 'SINGLE_ANSWER_QUESTION' ||
+                quesModel.questionType == 'MULTIPLE_ANSWER_QUESTION'
               ),
             }"
           >
             <el-button
               v-if="
-                paper.paperType == 'IMPORT' &&
-                (quesModel.questionType == 'SINGLE_ANSWER_QUESTION' ||
-                  quesModel.questionType == 'MULTIPLE_ANSWER_QUESTION')
+                quesModel.questionType == 'SINGLE_ANSWER_QUESTION' ||
+                quesModel.questionType == 'MULTIPLE_ANSWER_QUESTION'
               "
               type="primary"
               @click="addQuesOption"
@@ -966,9 +953,6 @@ export default {
       user: (state) => state.user,
     }),
     updatePorperty() {
-      if (this.parentView === "gen_paper") {
-        return true;
-      }
       return false;
     },
     answer() {
@@ -995,6 +979,32 @@ export default {
     };
   },
   methods: {
+    recoveryPaper(paperid) {
+      this.$confirm("确认回收试卷吗?", "提示", {
+        type: "warning",
+      }).then(() => {
+        this.loading = true;
+        this.$http
+          .put(QUESTION_API + "/paper_storage/recovery/" + paperid)
+          .then(
+            () => {
+              this.loading = false;
+              this.$notify({
+                message: "操作成功",
+                type: "success",
+              });
+              this.back();
+            },
+            (response) => {
+              this.loading = false;
+              this.$notify({
+                message: response.response.data.desc,
+                type: "error",
+              });
+            }
+          );
+      });
+    },
     movePaperDetail(detail, vector) {
       let vectorStr = vector == "up" ? "上移" : "下移";
       this.$alert("您确定" + vectorStr + "吗?", "提示", {
@@ -1002,7 +1012,7 @@ export default {
         callback: (action) => {
           if (action == "confirm") {
             this.loading = true;
-            this.$http
+            this.$httpWithMsg
               .put(
                 QUESTION_API +
                   "/paperDetail/" +
@@ -1019,8 +1029,8 @@ export default {
                   message: vectorStr + "成功",
                   type: "success",
                 });
-                this.loading = false;
-              });
+              })
+              .finally(() => (this.loading = false));
           }
         },
       });
@@ -1222,8 +1232,7 @@ export default {
             }
           }
           setTimeout(() => {
-            document.documentElement.scrollTop = document.body.scrollTop =
-              scrollPosition;
+            document.documentElement.scrollTop = document.body.scrollTop = scrollPosition;
             console.log(scrollPosition);
           }, 1000);
           this.loading = false;
@@ -1255,7 +1264,7 @@ export default {
           callback: (action) => {
             if (action == "confirm") {
               this.loading = true;
-              this.$http
+              this.$httpWithMsg
                 .delete(
                   QUESTION_API +
                     "/paperDetail/" +
@@ -1270,8 +1279,8 @@ export default {
                     message: "删除成功",
                     type: "success",
                   });
-                  this.loading = false;
-                });
+                })
+                .finally(() => (this.loading = false));
             }
           },
         });
@@ -1615,6 +1624,23 @@ export default {
         });
         return;
       }
+      if (this.paper.paperType == "GENERATE") {
+        this.$confirm(
+          "试题内容修改,会影响所有关联试卷,是否确定进行?",
+          "提示",
+          {
+            confirmButtonText: "确定",
+            cancelButtonText: "取消",
+            type: "warning",
+          }
+        ).then(() => {
+          this.submitPaperDetailUnit();
+        });
+      } else {
+        this.submitPaperDetailUnit();
+      }
+    },
+    submitPaperDetailUnit() {
       let paperDetailUnitExp = {
         id: this.editPaperDetailUnit.id,
         question: this.quesModel,
@@ -1630,7 +1656,7 @@ export default {
           type: "warning",
         }).then(() => {
           this.dialogLoading = true;
-          this.$http
+          this.$httpWithMsg
             .delete(
               QUESTION_API +
                 "/paper/deleteQuestion/" +
@@ -1654,8 +1680,8 @@ export default {
                   type: "success",
                 });
               }
-              this.dialogLoading = false;
-            });
+            })
+            .finally(() => (this.loading = false));
         });
       } else {
         this.dialogLoading = true;
@@ -1715,7 +1741,7 @@ export default {
           }
         }
         paperDetailUnitExp.question.quesAnswer = this.answer;
-        this.$http
+        this.$httpWithMsg
           .put(QUESTION_API + "/paperDetailUnit", paperDetailUnitExp)
           .then(() => {
             this.$notify({
@@ -1726,13 +1752,7 @@ export default {
             this.closeQuesDialog();
             this.initPaper();
           })
-          .catch((err) => {
-            this.dialogLoading = false;
-            this.$notify({
-              type: "error",
-              message: err.response.data.desc,
-            });
-          });
+          .finally(() => (this.dialogLoading = false));
       }
     },
     //在正确的option上设置isCorrect=1
@@ -1772,7 +1792,7 @@ export default {
         callback: (action) => {
           if (action == "confirm") {
             this.loading = true;
-            this.$http
+            this.$httpWithMsg
               .delete(QUESTION_API + "/paperDetailUnit/" + paperDetailUnitId)
               .then(() => {
                 this.initPaper();
@@ -1783,8 +1803,8 @@ export default {
                   message: "删除成功",
                   type: "success",
                 });
-                this.loading = false;
-              });
+              })
+              .finally(() => (this.loading = false));
           }
         },
       });

+ 100 - 5
src/modules/questions/views/GenPaper.vue

@@ -85,6 +85,20 @@
               ></el-input>
             </el-form-item>
           </el-col>
+          <el-col :span="6">
+            <el-form-item label="状态">
+              <el-select
+                v-model="formSearch.inUse"
+                class="search_width"
+                clearable
+                placeholder="请选择"
+                size="small"
+              >
+                <el-option value="1" label="已调用"> </el-option>
+                <el-option value="0" label="未调用"> </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
           <el-col :span="6">
             <div class="search_down">
               <el-button size="small" type="primary" @click="searchFrom"
@@ -105,27 +119,36 @@
         <el-row>
           <el-form-item>
             <span>批量操作:</span>
-            <!-- <el-button
+            <el-button
               size="small"
               type="danger"
               :disabled="noBatchSelected"
               @click="batchDeleteGenPaper"
             >
-              <i class="el-icon-delete"></i> 删除成卷
-            </el-button> -->
+              <i class="el-icon-delete"></i>删除成卷
+            </el-button>
+            <el-button
+              size="small"
+              type="danger"
+              :disabled="noBatchSelected"
+              @click="recoveryPapers"
+            >
+              <i class="el-icon-delete"></i>回收
+            </el-button>
+
             <el-button
               size="small"
               type="primary"
               :disabled="noBatchSelected"
               @click="openBatchExportPaperDialog"
-              ><i class="el-icon-download"></i> 下载成卷</el-button
+              ><i class="el-icon-download"></i>下载成卷</el-button
             >
             <el-button
               size="small"
               type="primary"
               :disabled="noBatchSelected"
               @click="openPrints"
-              ><i class="el-icon-share"></i> 发送成卷</el-button
+              ><i class="el-icon-share"></i>发送成卷</el-button
             >
             <el-button
               size="small"
@@ -183,6 +206,11 @@
           prop="paperDetailCount"
         >
         </el-table-column>
+        <el-table-column label="状态" width="150">
+          <template slot-scope="scope">
+            <span>{{ scope.row.inUse == 1 ? "已调用" : "未调用" }}</span>
+          </template>
+        </el-table-column>
         <el-table-column label="录入员" width="150">
           <template slot-scope="scope">
             <span>{{ scope.row.creator }}</span>
@@ -222,6 +250,15 @@
                   更多 <i class="el-icon-arrow-down el-icon--right"></i>
                 </el-button>
                 <el-dropdown-menu slot="dropdown">
+                  <el-dropdown-item>
+                    <el-button
+                      size="small"
+                      type="danger"
+                      @click="recoveryPaper(scope.row)"
+                    >
+                      <i class="el-icon-delete"></i>回收
+                    </el-button>
+                  </el-dropdown-item>
                   <el-dropdown-item>
                     <el-button
                       size="mini"
@@ -502,6 +539,7 @@ export default {
         lastModifyName: "",
         level: "",
         name: "",
+        inUse: "",
       },
       tableData: [],
       currentPage: 1,
@@ -762,6 +800,63 @@ export default {
         );
       });
     },
+    recoveryPaper(row) {
+      this.$confirm("确认回收试卷吗?", "提示", {
+        type: "warning",
+      }).then(() => {
+        this.loading = true;
+        this.$http.put(QUESTION_API + "/paper_storage/recovery/" + row.id).then(
+          () => {
+            this.$notify({
+              message: "操作成功",
+              type: "success",
+            });
+            this.searchGenPaper();
+          },
+          (response) => {
+            this.$notify({
+              message: response.response.data.desc,
+              type: "error",
+            });
+            this.loading = false;
+          }
+        );
+      });
+    },
+    recoveryPapers() {
+      var paperIds = this.paperIds;
+      if (this.selectedPaperIds.length != 0) {
+        this.$confirm("确认回收试卷吗?", "提示", {
+          type: "warning",
+        }).then(() => {
+          this.loading = true;
+          this.$http
+            .put(QUESTION_API + "/paper_storage/recovery/" + paperIds)
+            .then(
+              () => {
+                this.$notify({
+                  message: "操作成功",
+                  type: "success",
+                });
+                this.selectedPaperIds = [];
+                this.searchGenPaper();
+              },
+              (response) => {
+                this.$notify({
+                  message: response.response.data.desc,
+                  type: "error",
+                });
+                this.loading = false;
+              }
+            );
+        });
+      } else {
+        this.$notify({
+          message: "请勾选回收的试卷",
+          type: "warning",
+        });
+      }
+    },
     selectChange(row) {
       this.selectedPaperIds = [];
       row.forEach((element) => {

+ 10 - 10
src/modules/questions/views/GenPaperDetail.vue

@@ -875,39 +875,39 @@ export default {
         this.$http
           .get(
             QUESTION_API +
-              "/importPaper/" +
-              this.tempPaperIds +
-              "/" +
+              "/importPaper/huoge/" +
               this.curSelect +
               "/" +
               this.pageSize +
               "?courseNo=" +
-              this.genPaper.courseNo
+              this.genPaper.courseNo +
+              "&ids=" +
+              this.tempPaperIds
           )
           .then((response) => {
             console.log("response:", response);
             this.selectPapers = response.data.content;
             this.totalSelect = response.data.totalElements;
-            this.curSelect = response.data.pageable.pageNumber + 1;
+            this.curSelect = response.data.number + 1;
             this.loading = false;
           });
       } else if (this.paperType == "GENERATE") {
         this.$http
           .get(
             QUESTION_API +
-              "/genPaper/" +
-              this.tempPaperIds +
-              "/" +
+              "/genPaper/huoge/" +
               this.curSelect +
               "/" +
               this.pageSize +
               "?courseNo=" +
-              this.genPaper.courseNo
+              this.genPaper.courseNo +
+              "&ids=" +
+              this.tempPaperIds
           )
           .then((response) => {
             this.selectPapers = response.data.content;
             this.totalSelect = response.data.totalElements;
-            this.curSelect = response.data.pageable.pageNumber + 1;
+            this.curSelect = response.data.number + 1;
             this.loading = false;
           });
       } else {

+ 28 - 13
src/modules/questions/views/InsertBluePaperStructure.vue

@@ -38,12 +38,12 @@
             </el-col>
             <el-col :span="8">
               <el-form-item label="总 分">
-                <el-input
-                  v-model.number="blueStruct.totalScore"
-                  class="search_width"
-                  placeholder="请输入总分"
+                <el-input-number
+                  v-model="blueStruct.totalScore"
+                  :precision="1"
+                  :min="0"
                   size="small"
-                ></el-input>
+                ></el-input-number>
               </el-form-item>
             </el-col>
           </el-row>
@@ -295,7 +295,7 @@ export default {
     totalScore() {
       var sum = 0.0;
       for (let paperDetailStruct of this.paperDetailStructs) {
-        sum += paperDetailStruct.score;
+        sum = this.accAdd(paperDetailStruct.score, sum);
       }
       return sum;
     },
@@ -324,6 +324,21 @@ export default {
     this.searchForm();
   },
   methods: {
+    accAdd(num1, num2) {
+      let sq1, sq2, m;
+      try {
+        sq1 = num1.toString().split(".")[1].length;
+      } catch (e) {
+        sq1 = 0;
+      }
+      try {
+        sq2 = num2.toString().split(".")[1].length;
+      } catch (e) {
+        sq2 = 0;
+      }
+      m = Math.pow(10, Math.max(sq1, sq2));
+      return (num1 * m + num2 * m) / m;
+    },
     //查询大题结合
     searchForm() {
       this.loading = true;
@@ -421,7 +436,7 @@ export default {
     checkTotalScore() {
       var totalScore = 0;
       for (let paperDetailStruct of this.blueStruct.paperDetailStructs) {
-        totalScore = parseFloat(paperDetailStruct.totalScore) + totalScore;
+        totalScore = this.accAdd(paperDetailStruct.totalScore, totalScore);
       }
       if (totalScore != this.blueStruct.totalScore) {
         return false;
@@ -452,7 +467,7 @@ export default {
       var url = QUESTION_API + "/paperStruct";
       if (this.paperStructId != "add") {
         //假如没ID就是新增
-        this.$http
+        this.$httpWithMsg
           .put(url, this.blueStruct)
           .then(() => {
             this.$notify({
@@ -463,15 +478,15 @@ export default {
             this.removeItem();
             this.back();
           })
-          .catch(() => {
+          .catch((e) => {
             this.$notify({
               type: "error",
-              message: "试卷结构名称重复,请重新命名",
+              message: e.data.desc,
             });
             this.loading = false;
           });
       } else {
-        this.$http
+        this.$httpWithMsg
           .post(url, this.blueStruct)
           .then(() => {
             this.$notify({
@@ -482,11 +497,11 @@ export default {
             this.removeItem();
             this.back();
           })
-          .catch(() => {
+          .catch((e) => {
             this.loading = false;
             this.$notify({
               type: "error",
-              message: "试卷结构名称重复,请重新命名",
+              message: e.data.desc,
             });
           });
       }

+ 4 - 4
src/modules/questions/views/InsertBluePaperStructureInfo.vue

@@ -42,13 +42,13 @@
             </el-col>
             <el-col :span="6">
               <el-form-item label="每题分值" class="pull-left">
-                <el-input
+                <el-input-number
                   v-model="paperDetailStruct.score"
-                  class="search_width"
-                  placeholder="请输入"
                   size="small"
+                  :precision="1"
+                  :min="0"
                   @change="muli(paperDetailStruct)"
-                ></el-input>
+                ></el-input-number>
               </el-form-item>
             </el-col>
           </el-row>

+ 27 - 12
src/modules/questions/views/InsertPaperStructure.vue

@@ -27,12 +27,12 @@
             </el-col>
             <el-col :span="8">
               <el-form-item label="总分" prop="score">
-                <el-input
-                  v-model.number="paperStruct.totalScore"
-                  class="search_width"
-                  placeholder="请输入总分"
+                <el-input-number
+                  v-model="paperStruct.totalScore"
                   size="small"
-                ></el-input>
+                  :precision="1"
+                  :min="0"
+                ></el-input-number>
               </el-form-item>
             </el-col>
             <el-col :span="8">
@@ -279,7 +279,7 @@ export default {
     totalScore() {
       var sum = 0.0;
       for (let paperDetailStruct of this.paperDetailStructs) {
-        sum += paperDetailStruct.totalScore;
+        sum = this.accAdd(paperDetailStruct.totalScore, sum);
       }
       return sum;
     },
@@ -299,6 +299,21 @@ export default {
   },
 
   methods: {
+    accAdd(num1, num2) {
+      let sq1, sq2, m;
+      try {
+        sq1 = num1.toString().split(".")[1].length;
+      } catch (e) {
+        sq1 = 0;
+      }
+      try {
+        sq2 = num2.toString().split(".")[1].length;
+      } catch (e) {
+        sq2 = 0;
+      }
+      m = Math.pow(10, Math.max(sq1, sq2));
+      return (num1 * m + num2 * m) / m;
+    },
     //查询列表集合
     searchForm() {
       this.loading = true;
@@ -335,7 +350,7 @@ export default {
       console.log(this.paperStructId);
       if (this.paperStructId != "add") {
         //假如没ID就是新增
-        this.$http
+        this.$httpWithMsg
           .put(url, this.paperStruct)
           .then(() => {
             this.$notify({
@@ -346,16 +361,16 @@ export default {
             this.removeItem();
             this.back();
           })
-          .catch(() => {
+          .catch((e) => {
             this.$notify({
               type: "error",
-              message: "试卷结构名称重复,请重新命名",
+              message: e.data.desc,
             });
 
             this.loading = false;
           });
       } else {
-        this.$http
+        this.$httpWithMsg
           .post(url, this.paperStruct)
           .then(() => {
             this.$notify({
@@ -366,11 +381,11 @@ export default {
             this.removeItem();
             this.back();
           })
-          .catch(() => {
+          .catch((e) => {
             this.loading = false;
             this.$notify({
               type: "error",
-              message: "试卷结构名称重复,请重新命名",
+              message: e.data.desc,
             });
           });
       }

+ 27 - 11
src/modules/questions/views/InsertPaperStructureInfo.vue

@@ -32,13 +32,13 @@
           </el-col>
           <el-col :span="6">
             <el-form-item label="每题分值" prop="score">
-              <el-input
-                v-model.number="paperUnitForm.score"
-                class="search_width"
-                placeholder="请输入"
+              <el-input-number
+                v-model="paperUnitForm.score"
                 size="small"
+                :precision="1"
+                :min="0.1"
                 @change="muli"
-              ></el-input>
+              ></el-input-number>
             </el-form-item>
           </el-col>
           <el-col :span="6">
@@ -229,12 +229,13 @@
               </el-col>
               <el-col :span="12">
                 <el-form-item label="每题分值" prop="score" label-width="78px">
-                  <el-input
-                    v-model.number="paperUnitForm2.score"
-                    class="form_width"
-                    placeholder="请输入"
+                  <el-input-number
+                    v-model="paperUnitForm2.score"
+                    size="small"
+                    :precision="1"
+                    :min="0"
                     @change="muli2"
-                  ></el-input>
+                  ></el-input-number>
                 </el-form-item>
               </el-col>
             </el-row>
@@ -832,7 +833,7 @@ export default {
     totalScore() {
       var sum = 0.0;
       for (let unitStruct of this.unitStructs) {
-        sum += unitStruct.totalScore;
+        sum = this.accAdd(unitStruct.totalScore, sum);
       }
       return sum;
     },
@@ -860,6 +861,21 @@ export default {
     this.searchForm();
   },
   methods: {
+    accAdd(num1, num2) {
+      let sq1, sq2, m;
+      try {
+        sq1 = num1.toString().split(".")[1].length;
+      } catch (e) {
+        sq1 = 0;
+      }
+      try {
+        sq2 = num2.toString().split(".")[1].length;
+      } catch (e) {
+        sq2 = 0;
+      }
+      m = Math.pow(10, Math.max(sq1, sq2));
+      return (num1 * m + num2 * m) / m;
+    },
     //查询来源大题名称
     getQuesNameList(quesType) {
       if (quesType && quesType.length > 0) {

+ 556 - 0
src/modules/questions/views/PaperStorage.vue

@@ -0,0 +1,556 @@
+<template>
+  <section class="content">
+    <div v-show="isClear == 1">
+      <LinkTitlesCustom :current-paths="['题库管理 ', '试卷仓库管理']" />
+    </div>
+    <div class="box-body">
+      <el-form
+        :inline="true"
+        :model="formSearch"
+        label-position="right"
+        label-width="70px"
+      >
+        <el-row>
+          <el-col :span="6">
+            <el-form-item label="课程名称">
+              <el-select
+                v-model="formSearch.courseNo"
+                class="search_width"
+                filterable
+                :remote-method="getCourses"
+                remote
+                clearable
+                placeholder="全部"
+                size="small"
+                @focus="(e) => getCourses(e.target.value)"
+              >
+                <el-option
+                  v-for="item in courseInfoSelect"
+                  :key="item.courseNo"
+                  :label="item.courseInfo"
+                  :value="item.courseNo"
+                >
+                </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="试卷名称">
+              <el-input
+                v-model="formSearch.name"
+                class="search_width"
+                placeholder="试卷名称"
+                size="small"
+              ></el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="层次">
+              <el-select
+                v-model="formSearch.level"
+                class="search_width"
+                clearable
+                placeholder="请选择"
+                size="small"
+              >
+                <el-option
+                  v-for="item in levelList"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                >
+                </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="录入人">
+              <el-input
+                v-model="formSearch.creator"
+                class="search_width"
+                placeholder="录入人"
+                size="small"
+              ></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="6">
+            <el-form-item label="修改人">
+              <el-input
+                v-model="formSearch.lastModifyName"
+                class="search_width"
+                placeholder="修改人"
+                size="small"
+              ></el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <div class="search_down">
+              <el-button size="small" type="primary" @click="searchFrom"
+                ><i class="el-icon-search"></i> 查询</el-button
+              >
+              <el-button size="small" @click="resetForm"
+                ><i class="el-icon-refresh"></i> 重 置</el-button
+              >
+            </div>
+          </el-col>
+        </el-row>
+        <div
+          style="width: 100%; border-bottom: 1px solid #ddd; margin: 10px 0"
+        ></div>
+        <el-row>
+          <el-form-item>
+            <span>批量操作:</span>
+            <el-button
+              :disabled="noBatchSelected"
+              size="mini"
+              type="primary"
+              plain
+              @click="releasePapers"
+              ><i class="el-icon-share"></i>释放</el-button
+            >
+          </el-form-item>
+        </el-row>
+      </el-form>
+      <div style="width: 100%; margin-bottom: 10px"></div>
+      <el-table
+        v-loading="loading"
+        element-loading-text="拼命加载中"
+        :data="tableData"
+        border
+        style="width: 100%"
+        @selection-change="selectChange"
+      >
+        <el-table-column type="selection" width="40"></el-table-column>
+        <el-table-column label="课程名称" width="180">
+          <template slot-scope="scope">
+            <span>{{ scope.row.course.name }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="课程代码" width="80">
+          <template slot-scope="scope">
+            <span>{{ scope.row.course.code }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="试卷名称" width="180">
+          <template slot-scope="scope">
+            <span>{{ scope.row.name }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="试卷总分"
+          width="103"
+          sortable
+          prop="totalScore"
+        >
+        </el-table-column>
+        <el-table-column
+          label="试卷难度"
+          width="103"
+          sortable
+          prop="difficultyDegree"
+        >
+        </el-table-column>
+        <el-table-column
+          label="大题数量"
+          width="103"
+          sortable
+          prop="paperDetailCount"
+        >
+        </el-table-column>
+        <el-table-column label="录入员" width="150">
+          <template slot-scope="scope">
+            <span>{{ scope.row.creator }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="创建时间"
+          width="153"
+          sortable
+          prop="createTime"
+        >
+        </el-table-column>
+        <el-table-column label="修改人" width="150">
+          <template slot-scope="scope">
+            <span>{{ scope.row.lastModifyName }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="修改时间"
+          width="153"
+          sortable
+          prop="updateDate"
+        >
+        </el-table-column>
+        <el-table-column label="操作" width="180" fixed="right">
+          <template slot-scope="scope">
+            <div class="operate_left">
+              <el-button
+                size="mini"
+                type="primary"
+                plain
+                @click="viewPaper(scope.row)"
+                ><i class="el-icon-view"></i>查看</el-button
+              >
+              <el-dropdown class="button_left">
+                <el-button type="primary" size="mini" plain>
+                  更多 <i class="el-icon-arrow-down el-icon--right"></i>
+                </el-button>
+                <el-dropdown-menu slot="dropdown">
+                  <el-dropdown-item>
+                    <el-button
+                      size="mini"
+                      type="primary"
+                      plain
+                      @click="releasePaper(scope.row)"
+                      ><i class="el-icon-share"></i>释放</el-button
+                    >
+                  </el-dropdown-item>
+                </el-dropdown-menu>
+              </el-dropdown>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="page pull-right">
+        <el-pagination
+          :current-page="currentPage"
+          :page-size="pageSize"
+          :page-sizes="[10, 20, 50, 100, 200, 300]"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="total"
+          @current-change="handleCurrentChange"
+          @size-change="handleSizeChange"
+        >
+        </el-pagination>
+      </div>
+    </div>
+  </section>
+</template>
+
+<script>
+import { CORE_API, QUESTION_API, EXAM_WORK_API } from "@/constants/constants";
+import { LEVEL_TYPE, PUBLICITY_LIST } from "../constants/constants";
+import { mapState } from "vuex";
+import LinkTitlesCustom from "@/components/LinkTitlesCustom.vue";
+export default {
+  components: { LinkTitlesCustom },
+  data() {
+    return {
+      publicityDis: false,
+      difficultyDegreeDis: false,
+      quesLoading: false,
+      quesPropertyDialog: false,
+      difficultyDegree: 0.1,
+      publicity: true,
+      difficultyDegreeList: [
+        { label: 0.1, value: 0.1 },
+        { label: 0.2, value: 0.2 },
+        { label: 0.3, value: 0.3 },
+        { label: 0.4, value: 0.4 },
+        { label: 0.5, value: 0.5 },
+        { label: 0.6, value: 0.6 },
+        { label: 0.7, value: 0.7 },
+        { label: 0.8, value: 0.8 },
+        { label: 0.9, value: 0.9 },
+        { label: 1.0, value: 1.0 },
+      ],
+      publicityList: PUBLICITY_LIST,
+      isClear: 0,
+      courseLoading: false,
+      formSearch: {
+        courseNo: "",
+        courseName: "",
+        creator: "",
+        lastModifyName: "",
+        level: "",
+        name: "",
+      },
+      tableData: [],
+      currentPage: 1,
+      pageSize: 10,
+      total: 0,
+      loading: false,
+      courseList: [],
+      levelList: LEVEL_TYPE,
+      dialogVisible: false,
+      selectedPaperIds: [],
+      fileList: [],
+      uploadAction: "",
+      formUpload: {
+        paperName: "",
+      },
+      uploadData: {},
+      fileLoading: false,
+      exportDialog: false,
+      exportModel: {
+        id: "",
+        courseCode: "",
+        courseName: "",
+        exportContentList: [],
+        seqMode: "MODE1",
+      },
+      isShow: true,
+      examList: [],
+      Org: {},
+      dialogModel: false,
+      rowIds: [],
+      isShowPrintExamPackage: false,
+      isShowPrintExamPackagePassword: false,
+      printExamPackagePassword: "",
+      printFrom: {
+        examId: "",
+      },
+      rules: {
+        examId: [{ required: true, message: "请输入名称", trigger: "change" }],
+      },
+    };
+  },
+  computed: {
+    paperIds() {
+      var paperIds = "";
+      for (let paperId of this.selectedPaperIds) {
+        if (!paperIds) {
+          paperIds += paperId;
+        } else {
+          paperIds += "," + paperId;
+        }
+      }
+      return paperIds;
+    },
+    courseInfoSelect() {
+      var courseList = [];
+      for (let course of this.courseList) {
+        var courseInfo = course.name + "(" + course.code + ")";
+        var courseNo = course.code;
+        var courseName = course.name;
+        courseList.push({
+          courseNo: courseNo,
+          courseInfo: courseInfo,
+          courseName: courseName,
+        });
+      }
+      return courseList;
+    },
+    noBatchSelected() {
+      return this.selectedPaperIds.length === 0;
+    },
+    ...mapState({ user: (state) => state.user }),
+  },
+  watch: {
+    $route: "initVue",
+  },
+  created() {
+    this.initVue();
+    this.searchExamList();
+    this.searchOrgName();
+  },
+  methods: {
+    resetForm() {
+      this.formSearch = {
+        courseNo: "",
+        courseName: "",
+        level: "",
+        name: "",
+      };
+    },
+    //查询
+    searchFrom() {
+      this.currentPage = 1;
+      this.searchGenPaper();
+    },
+    searchGenPaper() {
+      var pageNo = this.currentPage;
+      this.currentPage = 1;
+      this.loading = true;
+      var url =
+        QUESTION_API +
+        "/paper_storage/findPage/" +
+        pageNo +
+        "/" +
+        this.pageSize;
+      this.$http.get(url, { params: this.formSearch }).then((response) => {
+        this.tableData = response.data.content;
+        this.total = response.data.totalElements;
+        this.currentPage = Number(pageNo);
+      });
+      this.loading = false;
+    },
+    handleCurrentChange(val) {
+      this.currentPage = val;
+      this.searchGenPaper();
+    },
+    handleSizeChange(val) {
+      this.pageSize = val;
+      this.currentPage = 1;
+      this.searchGenPaper();
+    },
+    getCourseObj(courseNo) {
+      for (let course of this.courseList) {
+        if (course.code == courseNo) {
+          return course;
+        }
+      }
+      return "";
+    },
+    viewPaper(row) {
+      var course = this.getCourseObj(this.formSearch.courseNo);
+      if (course) {
+        this.formSearch.courseName = course.name;
+      }
+      sessionStorage.setItem("gen_paper", JSON.stringify(this.formSearch));
+      sessionStorage.setItem("gen_paper_currentPage", this.currentPage);
+      sessionStorage.setItem("question_back", "false");
+      this.$router.push({
+        path: "/view_paper/" + row.id,
+      });
+    },
+    selectChange(row) {
+      this.selectedPaperIds = [];
+      row.forEach((element) => {
+        this.selectedPaperIds.push(element.id);
+      });
+    },
+    releasePapers() {
+      var paperIds = this.paperIds;
+      if (this.selectedPaperIds.length != 0) {
+        this.$confirm("确认释放试卷吗?", "提示", {
+          type: "warning",
+        }).then(() => {
+          this.loading = true;
+          this.$http
+            .put(QUESTION_API + "/paper_storage/release/" + paperIds)
+            .then(
+              () => {
+                this.$notify({
+                  message: "操作成功",
+                  type: "success",
+                });
+                this.searchGenPaper();
+                this.selectedPaperIds = [];
+              },
+              (response) => {
+                this.$notify({
+                  message: response.response.data.desc,
+                  type: "error",
+                });
+                this.loading = false;
+              }
+            );
+        });
+      } else {
+        this.$notify({
+          message: "请勾选释放的试卷",
+          type: "warning",
+        });
+      }
+    },
+    releasePaper(row) {
+      this.$confirm("确认释放试卷吗?", "提示", {
+        type: "warning",
+      }).then(() => {
+        this.loading = true;
+        this.$http.put(QUESTION_API + "/paper_storage/release/" + row.id).then(
+          () => {
+            this.$notify({
+              message: "操作成功",
+              type: "success",
+            });
+            this.selectedPaperIds = [];
+            this.searchGenPaper();
+          },
+          (response) => {
+            this.$notify({
+              message: response.response.data.desc,
+              type: "error",
+            });
+            this.loading = false;
+          }
+        );
+      });
+    },
+    //查询所有课程
+    getCourses(query) {
+      if (query) {
+        query = query.trim();
+      }
+      this.courseLoading = true;
+      this.$http
+        .get(CORE_API + "/course/query?name=" + query + "&enable=true")
+        .then((response) => {
+          this.courseList = response.data;
+          this.courseLoading = false;
+        });
+    },
+    removeItem() {
+      sessionStorage.removeItem("gen_paper");
+      sessionStorage.removeItem("gen_paper_currentPage");
+    },
+    cancel(formData) {
+      this.resetForm2(formData);
+      this.dialogModel = false;
+    },
+    resetForm2(formData) {
+      this.printFrom.examId = "";
+      this.$refs[formData].clearValidate();
+    },
+    //查询考试
+    getExams(query) {
+      if (query) {
+        query = query.trim();
+      }
+      this.$http
+        .get(
+          EXAM_WORK_API + "/exam/queryByNameLike?name=" + query + "&enable=true"
+        )
+        .then((response) => {
+          this.examList = response.data;
+        });
+    },
+    searchExamList() {
+      //查询所有考试
+      this.$http
+        .get(EXAM_WORK_API + "/exam/queryByNameLike?name=&enable=true")
+        .then((response) => {
+          var list = response.data;
+          for (var i = 0; i < list.length; i++) {
+            if (list[i].examType == "TRADITION") {
+              this.examList.push(list[i]);
+            }
+          }
+        });
+    },
+    //根据orgId查询学校名称
+    searchOrgName() {
+      this.$http
+        .get(CORE_API + "/org/rootOrg/" + this.user.rootOrgId)
+        .then((response) => {
+          this.Org = response.data;
+        });
+    },
+    initVue() {
+      this.isClear = this.$route.params.isClear;
+      if (this.isClear == 0 || !this.isClear) {
+        this.removeItem();
+        this.formSearch = {
+          courseNo: "",
+          courseName: "",
+          level: "",
+          name: "",
+        };
+        this.currentPage = 1;
+      } else {
+        this.formSearch = JSON.parse(sessionStorage.getItem("gen_paper"));
+        this.currentPage =
+          sessionStorage.getItem("gen_paper_currentPage") == null
+            ? 1
+            : parseInt(sessionStorage.getItem("gen_paper_currentPage"));
+      }
+      this.getCourses(this.formSearch.courseName);
+      this.searchGenPaper();
+    },
+  },
+};
+</script>
+<style scoped src="../styles/Common.css"></style>

+ 134 - 142
src/modules/questions/views/PropertyInfo.vue

@@ -96,50 +96,67 @@
       </el-form>
 
       <el-tree
-        :data="data"
+        :data="treeData"
         node-key="id"
         :props="defaultProps"
         :default-expanded-keys="ids"
         @node-click="handleNodeClick"
-      ></el-tree>
+        ><span slot-scope="{ data }" class="custom-tree-node">
+          <span>{{ data.name }}({{ data.code }})</span>
+        </span></el-tree
+      >
     </div>
-    <el-dialog :title="title" :visible.sync="propertyDialog">
-      <el-form :model="propertyForm">
-        <el-row v-show="parentName">
-          <el-form-item label="一级名称">
+    <el-dialog
+      :title="title"
+      :visible.sync="propertyDialog"
+      @close="closeModel"
+    >
+      <el-form
+        ref="propertyForm"
+        :model="propertyForm"
+        :inline="true"
+        :rules="rules"
+        :inline-message="true"
+      >
+        <el-row>
+          <el-form-item label="编码" label-width="100px" prop="code">
+            <el-input
+              v-model="propertyForm.code"
+              auto-complete="off"
+              style="width: 220px"
+            ></el-input>
+          </el-form-item>
+        </el-row>
+        <el-row v-if="isFirstLev()">
+          <el-form-item label="一级名称" label-width="100px" prop="name">
             <el-input
-              v-model="propertyForm.parentName"
+              v-model="propertyForm.name"
               auto-complete="off"
               style="width: 220px"
-              @change="showTitle"
             ></el-input>
-            <span v-show="showParentName" style="color: red">请输一级名称</span>
           </el-form-item>
         </el-row>
-        <el-row v-show="sonName">
-          <el-form-item label="二级名称">
+        <el-row v-if="isSecondLev()">
+          <el-form-item label="二级名称" label-width="100px" prop="name">
             <el-input
-              v-model="propertyForm.sonName"
+              v-model="propertyForm.name"
               auto-complete="off"
               style="width: 220px"
-              @change="showTitle"
             ></el-input>
-            <span v-show="showSonName" style="color: red">请输二级名称</span>
           </el-form-item>
         </el-row>
         <el-row>
-          <el-form-item label="名称备注">
+          <el-form-item label="名称备注" label-width="100px">
             <el-input
               v-model="propertyForm.remark"
               auto-complete="off"
               style="width: 220px"
-              @change="showTitle"
             ></el-input>
           </el-form-item>
         </el-row>
       </el-form>
       <div slot="footer" class="dialog-footer">
-        <el-button @click="propertyDialog = false">取 消</el-button>
+        <el-button @click="closeModel">取 消</el-button>
         <el-button type="primary" @click="submit">确 定</el-button>
       </div>
     </el-dialog>
@@ -160,16 +177,19 @@ export default {
       courseList: [],
       ids: [],
       loading: false,
-      parentName: false,
-      sonName: false,
       propertyDialog: false,
       propertyForm: {
-        parentName: "",
-        sonName: "",
+        id: "",
+        code: "",
+        name: "",
+        parentId: "",
+        number: "",
+        coursePropertyId: "",
         remark: "",
       },
-      property: {
+      curProperty: {
         id: "",
+        code: "",
         name: "",
         parentId: "",
         number: "",
@@ -178,15 +198,28 @@ export default {
       },
       showButton: true,
       showSonButtton: true,
-      showParentName: false,
-      showSonName: false,
-      data: [],
+      showMoveButtton: true,
+      treeData: [],
       defaultProps: {
         children: "propertyList",
-        label: "name",
       },
       title: "新增属性",
-      showMoveButtton: true,
+      rules: {
+        code: [
+          {
+            required: true,
+            message: "请输入编码",
+            trigger: "blur",
+          },
+        ],
+        name: [
+          {
+            required: true,
+            message: "请输入名称",
+            trigger: "blur",
+          },
+        ],
+      },
     };
   },
   computed: {
@@ -208,25 +241,40 @@ export default {
     this.searchProperty();
   },
   methods: {
+    disAllBtn() {
+      this.showButton = true;
+      this.showSonButtton = true;
+      this.showMoveButtton = true;
+    },
+    isFirstLev() {
+      if (this.propertyForm.parentId == "0") {
+        return true;
+      } else {
+        return false;
+      }
+    },
+    isSecondLev() {
+      if (this.propertyForm.parentId && this.propertyForm.parentId != "0") {
+        return true;
+      } else {
+        return false;
+      }
+    },
+    closeModel() {
+      this.propertyDialog = false;
+      this.$refs.propertyForm.resetFields();
+    },
     //树形节点选中
     handleNodeClick(object) {
-      //得到选中的节点
-      console.log(object);
       this.showButton = false;
       //判断选中节点,如果是父节点,可以新增二级
       if (object.parentId == "0") {
         this.showSonButtton = false;
-        this.showMoveButtton = false;
       } else {
         this.showSonButtton = true;
-        this.showMoveButtton = true;
       }
-      this.property.id = object.id;
-      this.property.name = object.name;
-      this.property.parentId = object.parentId;
-      this.property.number = object.number;
-      this.property.coursePropertyId = object.coursePropertyId;
-      this.property.remark = object.remark;
+      this.showMoveButtton = false;
+      this.curProperty = Object.assign({}, object);
     },
     //查询所有课程
     getCourses(query) {
@@ -252,9 +300,9 @@ export default {
       this.$http
         .get(QUESTION_API + "/property/all/" + this.coursePropertyId)
         .then((response) => {
-          this.data = response.data;
-          for (var i = 0; i < this.data.length; i++) {
-            var property = this.data[i];
+          this.treeData = response.data;
+          for (var i = 0; i < this.treeData.length; i++) {
+            var property = this.treeData[i];
             this.ids.push(property.id);
           }
           this.loading = false;
@@ -262,113 +310,70 @@ export default {
     },
     //新增一级
     insertParent() {
+      this.disAllBtn();
       this.title = "新增属性";
-      this.showParentName = false;
-      this.property = {
+      this.propertyForm = {
         id: "",
+        code: "",
         name: "",
         parentId: "0",
         number: "",
         coursePropertyId: this.coursePropertyId,
         remark: "",
       };
-      this.propertyForm.parentName = "";
-      this.propertyForm.remark = "";
-      this.sonName = false;
-      this.parentName = true;
       this.propertyDialog = true;
     },
     //新增二级
     insertSon() {
+      this.disAllBtn();
       this.title = "新增属性";
-      this.showSonName = false;
       //父对象id赋值
-      this.property.parentId = this.property.id;
-      this.property.id = "";
-      this.property.name = "";
-      this.property.remark = "";
-      this.property.coursePropertyId = this.coursePropertyId;
-      this.propertyForm.sonName = "";
-      this.propertyForm.remark = "";
-      this.parentName = false;
-      this.sonName = true;
+      this.propertyForm = {
+        id: "",
+        code: "",
+        name: "",
+        parentId: this.curProperty.id,
+        number: "",
+        coursePropertyId: this.coursePropertyId,
+        remark: "",
+      };
       this.propertyDialog = true;
     },
     //修改
     updateProperty() {
+      this.disAllBtn();
       this.title = "修改属性";
-      //判断是父节点还是子节点
-      if (this.property.parentId == "0") {
-        this.propertyForm.parentName = this.property.name;
-        this.propertyForm.remark = this.property.remark;
-        this.sonName = false;
-        this.parentName = true;
-      } else {
-        this.propertyForm.sonName = this.property.name;
-        this.propertyForm.remark = this.property.remark;
-        this.parentName = false;
-        this.sonName = true;
-      }
+      this.propertyForm = Object.assign({}, this.curProperty);
       this.propertyDialog = true;
     },
     //保存
-    submit() {
-      if (this.property.parentId == "0") {
-        //非空判断
-        if (!this.propertyForm.parentName) {
-          this.showParentName = true;
-          return 0;
-        }
-        this.property.name = this.propertyForm.parentName;
-      } else {
-        //非空判断
-        if (!this.propertyForm.sonName) {
-          this.showSonName = true;
-          return 0;
-        }
-        this.property.name = this.propertyForm.sonName;
+    async submit() {
+      const res = await this.$refs.propertyForm.validate();
+      if (res === false) {
+        return;
       }
-      this.property.remark = this.propertyForm.remark;
-      if (this.property.id) {
-        this.$http
-          .put(QUESTION_API + "/property/save", this.property)
-          .then(() => {
-            this.$notify({
-              message: "修改成功",
-              type: "success",
-            });
-            this.propertyDialog = false;
-            this.searchProperty();
-          })
-          .catch((error) => {
-            this.$notify({
-              type: "error",
-              message: error.response.data.desc,
-            });
+      this.$http
+        .post(QUESTION_API + "/property/save", this.propertyForm)
+        .then(() => {
+          this.$notify({
+            message: this.propertyForm.id ? "修改成功" : "新增成功",
+            type: "success",
           });
-      } else {
-        this.$http
-          .post(QUESTION_API + "/property/save", this.property)
-          .then(() => {
-            this.$notify({
-              message: "新增成功",
-              type: "success",
-            });
-            this.propertyDialog = false;
-            this.searchProperty();
-          })
-          .catch((error) => {
-            this.$notify({
-              type: "error",
-              message: error.response.data.desc,
-            });
+          this.propertyDialog = false;
+          this.searchProperty();
+        })
+        .catch((error) => {
+          this.$notify({
+            type: "error",
+            message: error.response.data.desc,
           });
-      }
+        });
       this.showButton = true;
       this.showSonButtton = true;
     },
     //删除
     deleteProperty() {
+      this.disAllBtn();
       this.$confirm("确认删除属性吗?", "提示", {
         type: "warning",
       }).then(() => {
@@ -377,9 +382,9 @@ export default {
           .delete(
             QUESTION_API +
               "/property/delete/" +
-              this.property.id +
+              this.curProperty.id +
               "/" +
-              this.property.coursePropertyId
+              this.curProperty.coursePropertyId
           )
           .then(() => {
             this.$notify({
@@ -388,31 +393,16 @@ export default {
             });
             this.searchProperty();
           })
-          .catch(() => {
+          .catch((error) => {
             this.$notify({
               type: "error",
-              message: "删除失败",
+              message: error.response.data.desc,
             });
           });
       });
       this.showButton = true;
       this.showSonButtton = true;
     },
-    //效验
-    showTitle() {
-      if (this.propertyForm.parentName) {
-        this.showParentName = false;
-      }
-      if (!this.propertyForm.parentName) {
-        this.showParentName = true;
-      }
-      if (this.propertyForm.sonName) {
-        this.showSonName = false;
-      }
-      if (!this.propertyForm.sonName) {
-        this.showSonName = true;
-      }
-    },
     //返回
     back() {
       this.$router.push({
@@ -421,32 +411,34 @@ export default {
     },
     //上移
     moveUp() {
+      this.disAllBtn();
       this.$http
-        .put(QUESTION_API + "/property/moveUp", this.property)
+        .put(QUESTION_API + "/property/moveUp", this.curProperty)
         .then(() => {
           this.searchProperty();
           this.showMoveButtton = true;
         })
-        .catch(() => {
+        .catch((error) => {
           this.$notify({
             type: "error",
-            message: "无法上移",
+            message: error.response.data.desc,
           });
           this.showMoveButtton = true;
         });
     },
     //下移
     moveDown() {
+      this.disAllBtn();
       this.$http
-        .put(QUESTION_API + "/property/moveDown", this.property)
+        .put(QUESTION_API + "/property/moveDown", this.curProperty)
         .then(() => {
           this.searchProperty();
           this.showMoveButtton = true;
         })
-        .catch(() => {
+        .catch((error) => {
           this.$notify({
             type: "error",
-            message: "无法下移",
+            message: error.response.data.desc,
           });
           this.showMoveButtton = true;
         });

+ 2 - 4
src/modules/questions/views/Question.vue

@@ -457,15 +457,13 @@ export default {
       this.searchQues();
     },
     searchQues() {
-      var pageNo = Number(this.currentPage | 1);
-      this.currentPage = 1;
       this.tableData = [];
-      var url = QUESTION_API + "/question/" + pageNo + "/" + this.pageSize;
+      var url =
+        QUESTION_API + "/question/" + this.currentPage + "/" + this.pageSize;
       this.loading = true;
       this.$http.get(url, { params: this.formSearch }).then((response) => {
         this.tableData = response.data.content;
         this.total = response.data.totalElements;
-        this.currentPage = Number(pageNo);
         this.loading = false;
       });
     },

+ 1 - 7
src/modules/questions/views/SelectQuestion.vue

@@ -319,7 +319,7 @@ export default {
           cancelButtonText: "取消",
           type: "warning",
         }).then(() => {
-          this.$http
+          this.$httpWithMsg
             .post(
               QUESTION_API +
                 "/paper/selectQuestions/" +
@@ -336,12 +336,6 @@ export default {
               this.$router.push({
                 path: "/edit_paper/" + this.paperId + "/" + this.parentView,
               });
-            })
-            .catch(() => {
-              this.$notify({
-                type: "error",
-                message: "添加失败!",
-              });
             });
         });
       }

+ 927 - 0
src/modules/questions/views/ViewPaper.vue

@@ -0,0 +1,927 @@
+<template>
+  <div
+    id="editPaperApp"
+    v-loading="loading"
+    class="paper"
+    element-loading-text="拼命加载中。。。"
+  >
+    <div class="edit-paper-top">
+      <div class="edit-paper-top-inline">
+        <div class="paper-top-div">
+          <span class="paper-top-title">课程代码:</span>
+          <span class="paper-top-value">{{ paper.course.code }}</span>
+        </div>
+        <div class="paper-top-div">
+          <span class="paper-top-title">课程名称:</span>
+          <span class="paper-top-value">{{ paper.course.name }}</span>
+        </div>
+        <div class="paper-top-div">
+          <span class="paper-top-title">试卷名称:</span>
+          <span class="paper-top-value">{{ paper.name }}</span>
+        </div>
+        <div class="paper-top-div">
+          <span class="paper-top-title">试卷难度:</span>
+          <span class="paper-top-value">{{ paper.difficultyDegree }}</span>
+        </div>
+        <div>
+          <el-button
+            size="small"
+            type="primary"
+            plain
+            @click="releasePaper(paper.id)"
+            ><i class="el-icon-share"></i>释放</el-button
+          >
+          <el-button
+            size="small"
+            type="primary"
+            style="margin-left: 10px"
+            @click="back"
+            ><i class="el-icon-arrow-left"></i> 返回</el-button
+          >
+        </div>
+      </div>
+    </div>
+    <div class="paperName">
+      <div>
+        <br />
+        <h3 class="text-center">{{ paper.course.name }}&nbsp;试卷</h3>
+        <h5 class="text-center">
+          <span v-show="paper.hasAudio">(含音频试卷)</span>
+        </h5>
+        <br />
+        <h4 class="text-center">(课程代码&nbsp;{{ paper.course.code }})</h4>
+        <br />
+      </div>
+      <div class="text-left">
+        <el-table :data="paper.paperDetails" border style="width: 100%">
+          <el-table-column header-align="center" label="大题名称">
+            <template slot-scope="scope">
+              <span>{{ scope.row.name }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column header-align="center" label="大题总分">
+            <template slot-scope="scope">
+              <span>{{ scope.row.score }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column header-align="center" label="小题数量">
+            <template slot-scope="scope">
+              <span>{{ scope.row.unitCount }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column header-align="center" label="公开数量">
+            <template slot-scope="scope">
+              <span>{{ scope.row.pubCount }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column header-align="center" label="非公开数量">
+            <template slot-scope="scope">
+              <span>{{ scope.row.noPubCount }}</span>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <div class="mainQues" style="margin-top: 20px; margin-left: 0px">
+          <div class="mainQuesTitle">
+            <span>考试说明:</span>
+            <div style="width: 550px; margin-left: 20px; margin-top: 20px">
+              <span v-html="paper.examRemark"></span>
+            </div>
+          </div>
+        </div>
+        <!-- end by wwh -->
+        <div>
+          <h1>本试卷满分{{ paper.totalScore }}分。</h1>
+        </div>
+        <br />
+      </div>
+    </div>
+
+    <div>
+      <!-- 循环大题 -->
+      <div
+        v-for="(paperDetail, detailIndex) in paper.paperDetails"
+        v-show="paperDetailShow(paperDetail)"
+        :key="detailIndex"
+        class="mainQues"
+      >
+        <div
+          class="mainQuesTitle"
+          @mouseover="quesMouseOver(paperDetail.id)"
+          @mouseout="quesMouseOut(paperDetail.id)"
+        >
+          <span>{{ paperDetail.cnNum }}</span> <span>.</span>
+          <span>{{ paperDetail.name }}</span>
+          <span>
+            ({{ !paperDetail.title ? "本大题" : paperDetail.title + "," }}共{{
+              paperDetail.unitCount
+            }}小题,满分{{ paperDetail.score }}分)
+          </span>
+
+          <span :id="paperDetail.id" class="btnDiv">
+            <el-button
+              v-show="showButtons[detailIndex].up"
+              size="small"
+              icon="el-icon-arrow-up"
+              @click.stop="hideContent(detailIndex)"
+            ></el-button>
+
+            <el-button
+              v-show="!showButtons[detailIndex].up"
+              size="small"
+              icon="el-icon-arrow-down"
+              @click.stop="showContent(detailIndex)"
+            ></el-button>
+          </span>
+        </div>
+
+        <!-- 循环小题 -->
+        <div v-show="showQuestions[detailIndex].is_show">
+          <div
+            v-for="(paperDetailUnit, unitIndex) in paperDetail.paperDetailUnits"
+            v-show="quesShow(paperDetailUnit.id)"
+            :key="unitIndex"
+            class="ques"
+          >
+            <reduplicate_mark
+              :id="paperDetailUnit.id"
+              :show="reduplicateMarkShow(paperDetailUnit.id)"
+              :fill-color="reduplicateMarkColor(paperDetailUnit.id)"
+              :checked="reduplicateMarkCheck(paperDetailUnit.id)"
+              @reduplicate_mark_check="reduplicate_mark_check"
+            >
+            </reduplicate_mark>
+
+            <div
+              class="quesSelect"
+              @mouseover="quesMouseOver(paperDetailUnit.id)"
+              @mouseout="quesMouseOut(paperDetailUnit.id)"
+            >
+              <div :id="paperDetailUnit.id" class="btnDiv">
+                <el-button
+                  size="small"
+                  @click="editQues(paperDetailUnit, paperDetailUnit.question)"
+                  >查看
+                </el-button>
+              </div>
+
+              <div class="quesBody">
+                <span class="ques-title">{{ paperDetailUnit.number }}.</span>
+                <span
+                  v-question-audio
+                  class="ques-body"
+                  :hasAudio="paperDetailUnit.question.hasAudio"
+                  :questionId="paperDetailUnit.question.id"
+                  v-html="paperDetailUnit.question.quesBody"
+                ></span>
+
+                <span class="score-span">
+                  ({{ paperDetailUnit.score }}分)
+                </span>
+              </div>
+
+              <div
+                v-for="(quesOption, optionIndex) in paperDetailUnit.question
+                  .quesOptions"
+                :key="optionIndex"
+                class="quesOption"
+              >
+                <span class="ques-title"
+                  >{{ optionIndex | optionOrderWordFilter }}.
+                </span>
+
+                <span
+                  v-question-audio
+                  class="ques-body"
+                  :hasAudio="paperDetailUnit.question.hasAudio"
+                  :questionId="paperDetailUnit.question.id"
+                  v-html="quesOption.optionBody"
+                ></span>
+              </div>
+            </div>
+            <br />
+
+            <div
+              v-for="(subQuestion, subIndex) in paperDetailUnit.question
+                .subQuestions"
+              v-show="quesShow(subQuestion.id)"
+              :key="subIndex"
+              class="subQues"
+            >
+              <reduplicate_mark
+                :show="reduplicateMarkShow(subQuestion.id)"
+              ></reduplicate_mark>
+
+              <div
+                class="quesSelect"
+                @mouseover="
+                  quesMouseOver(getSubQuesEditId(paperDetailUnit, subQuestion))
+                "
+                @mouseout="
+                  quesMouseOut(getSubQuesEditId(paperDetailUnit, subQuestion))
+                "
+              >
+                <div
+                  :id="getSubQuesEditId(paperDetailUnit, subQuestion)"
+                  class="btnDiv"
+                >
+                  <el-button
+                    size="small"
+                    @click="editQues(paperDetailUnit, subQuestion)"
+                    >查看
+                  </el-button>
+                </div>
+
+                <div class="quesBody">
+                  <span class="ques-title"
+                    >{{ subQuestion.quesParams.number }}.
+                  </span>
+
+                  <span v-html="subQuestion.quesBody"></span>
+                  <span
+                    >({{ paperDetailUnit.subScoreList[subIndex] }}分)</span
+                  >
+                </div>
+
+                <div
+                  v-for="(
+                    subQuesOption, subOptIndex
+                  ) in subQuestion.quesOptions"
+                  :key="subOptIndex"
+                  class="quesOption"
+                >
+                  <span class="ques-title"
+                    >{{ subOptIndex | optionOrderWordFilter }}.
+                  </span>
+                  <span v-html="subQuesOption.optionBody"></span>
+                </div>
+                <br />
+              </div>
+            </div>
+          </div>
+          <br />
+        </div>
+      </div>
+    </div>
+
+    <div class="text-left">
+      <el-dialog
+        v-loading.body="dialogLoading"
+        title="试题查看"
+        :visible.sync="quesDialog"
+        @close="closeQuesDialog"
+      >
+        <el-form :model="quesModel" label-position="right" label-width="80px">
+          <el-row :gutter="10">
+            <el-col :xs="10" :sm="10" :md="10" :lg="10">
+              <el-form-item label="题型">
+                <el-select
+                  v-model="quesModel.questionType"
+                  :disabled="true"
+                  placeholder="请输入题型"
+                >
+                  <el-option
+                    v-for="item in questionTypes"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :xs="10" :sm="10" :md="10" :lg="10">
+              <el-form-item label="分值">
+                <el-input
+                  v-model="quesModel.score"
+                  :disabled="true"
+                  placeholder="分值"
+                ></el-input>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <!-- create by  weiwenhai 添加难度,公开度,试题属性 -->
+          <el-row :gutter="10">
+            <el-col :xs="10" :sm="10" :md="10" :lg="10">
+              <el-form-item label="难度">
+                <el-select
+                  v-model="quesModel.difficultyDegree"
+                  placeholder="请输入难度"
+                  :disabled="true"
+                >
+                  <el-option
+                    v-for="item in difficultyDegreeList"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :xs="10" :sm="10" :md="10" :lg="10">
+              <el-form-item label="公开度">
+                <el-select
+                  v-model="quesModel.publicity"
+                  placeholder="请输入公开度"
+                  :disabled="true"
+                >
+                  <el-option
+                    v-for="item in publicityList"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+
+            <el-col
+              v-if="quesModel.questionType == 'TEXT_ANSWER_QUESTION'"
+              :xs="10"
+              :sm="10"
+              :md="10"
+              :lg="10"
+            >
+              <el-form-item label="作答类型">
+                <el-select v-model="quesModel.answerType" :disabled="true">
+                  <el-option
+                    v-for="item in answerTypes"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+          </el-row>
+
+          <el-row :gutter="10">
+            <el-col :xs="20" :sm="20" :md="20" :lg="20">
+              <el-form-item label="属性列表">
+                <el-tooltip
+                  v-for="(content, propIndex) in quesModel.quesProperties"
+                  :key="propIndex"
+                  placement="top"
+                >
+                  <div slot="content">
+                    <span v-if="content.firstProperty != null"
+                      >一级属性:{{ content.firstProperty.name }}</span
+                    ><br />
+                    <span v-if="content.secondProperty != null"
+                      >二级属性:{{ content.secondProperty.name }}</span
+                    >
+                  </div>
+                  <span>
+                    <el-tag
+                      :key="content.id"
+                      style="margin-right: 5px"
+                      :closable="false"
+                      type="primary"
+                    >
+                      {{ content.coursePropertyName }}
+                    </el-tag>
+                  </span>
+                </el-tooltip>
+              </el-form-item>
+            </el-col>
+          </el-row>
+
+          <!-- end by weiwenhai -->
+          <div>
+            <el-row>
+              <el-col>
+                <el-form-item label="题目">
+                  <span v-html="quesModel.quesBody"></span>
+                </el-form-item>
+              </el-col>
+            </el-row>
+
+            <el-form-item
+              v-for="(quesOption, optIndex) in quesModel.quesOptions"
+              :key="optIndex"
+              ><el-col :span="2">
+                <span>{{ optIndex | optionOrderWordFilter }}</span>
+              </el-col>
+              <el-col :span="20">
+                <span v-html="quesOption.optionBody"></span>
+              </el-col>
+            </el-form-item>
+
+            <div>
+              <el-form-item label="答案">
+                <span v-html="quesModel.quesAnswer"></span>
+              </el-form-item>
+            </div>
+            <div
+              :class="{
+                margin_left_40: true,
+              }"
+            >
+              <el-button @click="closeQuesDialog">关闭</el-button>
+            </div>
+          </div>
+        </el-form>
+      </el-dialog>
+    </div>
+  </div>
+</template>
+
+<script>
+import { QUESTION_API } from "@/constants/constants";
+import { isEmptyStr, QUESTION_TYPES } from "../constants/constants";
+import { mapState } from "vuex";
+import reduplicate_mark from "../component/reduplicate_mark.vue";
+import randomColor from "randomcolor";
+
+export default {
+  name: "EditPaperApp",
+  components: {
+    reduplicate_mark,
+  },
+  data() {
+    return {
+      hValue: "100px",
+      wValue: "500px",
+      display: "block",
+      uploadAction: "",
+      fileList: [],
+      answerFileList: [],
+      paperId: "",
+      paperDetailId: "",
+      editPaperDetailUnit: "",
+      quesDialog: false,
+      paperDatailDialog: false,
+      paperRemarkDialog: false,
+      parentView: "",
+      paper: {
+        course: {
+          code: "",
+          name: "",
+        },
+        examRemark: "",
+      },
+      loading: false,
+      dialogLoading: false,
+      detailLoading: false,
+      uploadAudioLoading: false,
+      uploadAnswerLoading: false,
+      questionTypes: QUESTION_TYPES,
+      questionType: "",
+      quesModel: { quesProperties: [] },
+      editpaperDetail: {},
+      reduplicateQuestions: [],
+      reduplicateGroup: [],
+      reduplicateQuesColor: [],
+      singleRightAnswer: "", //接收单选答案
+      multipleRightAnswer: [], //接收多选答案
+      options: ["正确", "错误"],
+      duplicateLoading: false,
+      dialogRadioFile: false,
+      dialogAnswerFile: false,
+      isUpload: true,
+      isUploadAnswer: true,
+      message: "",
+      answerMessage: "",
+      checkResult: false,
+      checkResultAnswer: false,
+      fileNameList: [],
+      defaultColor: [
+        "Red",
+        "Blue",
+        "LimeGreen",
+        "GoldenRod",
+        "Black",
+        "BlueViolet",
+        "Chocolate",
+        "DarkCyan",
+        "HotPink",
+        "Orange",
+        "IndianRed",
+        "Indigo",
+        "Green",
+        "Aqua",
+        "CadetBlue",
+        "SkyBlue",
+        "SlateBlue",
+        "SlateGray",
+        "Tomato",
+        "VioletRed",
+      ],
+      difficultyDegreeList: [
+        { label: 0.1, value: 0.1 },
+        { label: 0.2, value: 0.2 },
+        { label: 0.3, value: 0.3 },
+        { label: 0.4, value: 0.4 },
+        { label: 0.5, value: 0.5 },
+        { label: 0.6, value: 0.6 },
+        { label: 0.7, value: 0.7 },
+        { label: 0.8, value: 0.8 },
+        { label: 0.9, value: 0.9 },
+        { label: 1.0, value: 1.0 },
+      ],
+      publicityList: [
+        { label: "公开", value: true },
+        { label: "非公开", value: false },
+      ],
+      answerTypes: [
+        { label: "文本", value: "DIVERSIFIED_TEXT" },
+        { label: "音频", value: "SINGLE_AUDIO" },
+      ],
+      coursePropertyList: [],
+      coursePropertyName: "", //课程属性名
+      firstPropertyList: [], //一级属性集合
+      firstPropertyId: "", //一级属性id
+      secondPropertyList: [], //二级属性集合
+      secondPropertyId: "", //二级属性id
+      examRemark: "",
+      showQuestions: [],
+      showButtons: [],
+    };
+  },
+  computed: {
+    ...mapState({
+      user: (state) => state.user,
+    }),
+    updatePorperty() {
+      return false;
+    },
+    answer() {
+      if (this.quesModel.questionType == "SINGLE_ANSWER_QUESTION") {
+        return this.singleRightAnswer;
+      } else if (this.quesModel.questionType == "MULTIPLE_ANSWER_QUESTION") {
+        var obj = this.multipleRightAnswer;
+        return obj.sort().toString();
+      }
+      return this.quesModel.quesAnswer;
+    },
+  },
+  created() {
+    // $("body").attr("style", "");
+    document.getElementsByTagName("body")[0].style = "";
+    this.paperId = this.$route.params.id;
+    this.parentView = this.$route.params.parentView;
+    this.initPaper();
+    this.getreduplicateQuestions();
+    this.uploadAction = QUESTION_API + "/uploadRadio/" + this.paperId;
+    this.uploadHeaders = {
+      key: this.user.key,
+      token: this.user.token,
+    };
+  },
+  methods: {
+    releasePaper(paperid) {
+      this.$confirm("确认释放试卷吗?", "提示", {
+        type: "warning",
+      }).then(() => {
+        this.loading = true;
+        this.$http.put(QUESTION_API + "/paper_storage/release/" + paperid).then(
+          () => {
+            this.loading = false;
+            this.$notify({
+              message: "操作成功",
+              type: "success",
+            });
+            this.back();
+          },
+          (response) => {
+            this.loading = false;
+            this.$notify({
+              message: response.response.data.desc,
+              type: "error",
+            });
+          }
+        );
+      });
+    },
+
+    //隐藏大题下的所有小题
+    hideContent(index) {
+      console.log("up");
+      this.showQuestions[index].is_show = false;
+      this.showButtons[index].up = false;
+    },
+    //展开大题下所有小题
+    showContent(index) {
+      console.log("down");
+      this.showQuestions[index].is_show = true;
+      this.showButtons[index].up = true;
+    },
+    quesMouseOver(index) {
+      document.getElementById(index).style.visibility = "visible";
+    },
+    quesMouseOut(index) {
+      document.getElementById(index).style.visibility = "hidden";
+    },
+    //初始化试卷
+    initPaper() {
+      const scrollPosition =
+        document.documentElement.scrollTop || document.body.scrollTop;
+      this.loading = true;
+      this.paper = {
+        course: {
+          code: "",
+          name: "",
+        },
+      };
+      this.$http
+        .get(QUESTION_API + "/paper/" + this.paperId)
+        .then((response) => {
+          this.paper = response.data;
+          //查询所有课程属性名
+          this.initCourseProperty(this.paper.course.code);
+          //将所有小题分为公开和非公开
+          if (this.paper.paperDetails && this.paper.paperDetails.length > 0) {
+            for (let paperDetil of this.paper.paperDetails) {
+              this.showQuestions.push({ is_show: true });
+              this.showButtons.push({ up: true });
+              paperDetil.pubCount = 0;
+              paperDetil.noPubCount = 0;
+              if (
+                paperDetil.paperDetailUnits &&
+                paperDetil.paperDetailUnits.length > 0
+              ) {
+                for (let paperDetilUt of paperDetil.paperDetailUnits) {
+                  if (
+                    paperDetilUt.question.questionType !=
+                    "NESTED_ANSWER_QUESTION"
+                  ) {
+                    //非套题
+                    if (paperDetilUt.question.publicity) {
+                      paperDetil.pubCount = paperDetil.pubCount + 1;
+                    } else {
+                      paperDetil.noPubCount = paperDetil.noPubCount + 1;
+                    }
+                  } else {
+                    //循环所有子题
+                    for (let ques of paperDetilUt.question.subQuestions) {
+                      if (ques.publicity) {
+                        paperDetil.pubCount = paperDetil.pubCount + 1;
+                      } else {
+                        paperDetil.noPubCount = paperDetil.noPubCount + 1;
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+          setTimeout(() => {
+            document.documentElement.scrollTop = document.body.scrollTop = scrollPosition;
+            console.log(scrollPosition);
+          }, 1000);
+          this.loading = false;
+        });
+    },
+    //查询所有课程属性名
+    initCourseProperty(courseCode) {
+      this.$http
+        .get(QUESTION_API + "/courseProperty/enable/" + courseCode)
+        .then((response) => {
+          this.coursePropertyList = response.data;
+        });
+    },
+    quesShow(id) {
+      if (this.reduplicateGroup.length < 1) {
+        return true;
+      }
+      for (var i = 0, imax = this.reduplicateGroup.length; i < imax; i++) {
+        if (id == this.reduplicateGroup[i]) {
+          return true;
+        }
+      }
+      return false;
+    },
+    reduplicateMarkShow(id) {
+      var found = false;
+      for (var i = 0, imax = this.reduplicateQuestions.length; i < imax; i++) {
+        for (
+          var j = 0, jmax = this.reduplicateQuestions[i].length;
+          j < jmax;
+          j++
+        ) {
+          if (this.reduplicateQuestions[i][j] == id) {
+            found = true;
+            break;
+          }
+        }
+        if (found) {
+          break;
+        }
+      }
+      return found;
+    },
+    reduplicateMarkColor(id) {
+      for (var i = 0, imax = this.reduplicateQuestions.length; i < imax; i++) {
+        for (
+          var j = 0, jmax = this.reduplicateQuestions[i].length;
+          j < jmax;
+          j++
+        ) {
+          if (this.reduplicateQuestions[i][j] == id) {
+            return this.reduplicateQuesColor[i];
+          }
+        }
+      }
+    },
+    reduplicateMarkCheck(id) {
+      for (var i = 0, imax = this.reduplicateGroup.length; i < imax; i++) {
+        if (id == this.reduplicateGroup[i]) {
+          return true;
+        }
+      }
+      return false;
+    },
+    reduplicate_mark_check(id, checked) {
+      console.log(checked);
+      console.log(this.reduplicateQuestions);
+      console.log(id);
+      if (!checked) {
+        for (
+          var i = 0, imax = this.reduplicateQuestions.length;
+          i < imax;
+          i++
+        ) {
+          for (
+            var j = 0, jmax = this.reduplicateQuestions[i].length;
+            j < jmax;
+            j++
+          ) {
+            if (this.reduplicateQuestions[i][j] == id) {
+              this.reduplicateGroup = [];
+              for (
+                var k = 0, kmax = this.reduplicateQuestions[i].length;
+                k < kmax;
+                k++
+              ) {
+                this.reduplicateGroup.push(this.reduplicateQuestions[i][k]);
+              }
+              return;
+            }
+          }
+        }
+      } else {
+        this.reduplicateGroup = [];
+      }
+    },
+    //编辑题目
+    editQues(paperDetailUnit, question) {
+      console.log("question:", question);
+      this.coursePropertyName = "";
+      this.firstPropertyId = "";
+      this.secondPropertyId = "";
+      this.editPaperDetailUnit = paperDetailUnit;
+      this.quesModel = JSON.parse(JSON.stringify(question)); //深拷贝
+      this.quesModel.score = paperDetailUnit.score;
+      //如果是套题下面的小题编辑 ( paperDetailUnit的类型是套题,question的类型不是套题)
+      if (
+        paperDetailUnit.questionType == "NESTED_ANSWER_QUESTION" &&
+        question.questionType != "NESTED_ANSWER_QUESTION"
+      ) {
+        for (var i = 0; i < paperDetailUnit.question.subQuestions.length; i++) {
+          if (
+            paperDetailUnit.question.subQuestions[i].id == this.quesModel.id
+          ) {
+            this.quesModel.score = paperDetailUnit.subScoreList[i];
+            break;
+          }
+        }
+      }
+
+      if (isEmptyStr(this.quesModel.answerType)) {
+        this.quesModel.answerType = "DIVERSIFIED_TEXT";
+      }
+
+      if (this.quesModel.questionType == "FILL_BLANK_QUESTION") {
+        this.quesModel.quesBody = this.quesModel.quesBody.replace(
+          /______/g,
+          "###"
+        );
+      }
+
+      this.assignAnswers(); //给singleRightAnswer或multipleRightAnswer赋值
+      this.openQuesDialog();
+    },
+    //给singleRightAnswer和multipleRightAnswer赋值
+    assignAnswers() {
+      if (this.quesModel.quesOptions && this.quesModel.quesOptions.length > 0) {
+        this.singleRightAnswer = "";
+        this.multipleRightAnswer = [];
+        for (let i = 0; i < this.quesModel.quesOptions.length; i++) {
+          let option = this.quesModel.quesOptions[i];
+          if (
+            this.quesModel.questionType == "SINGLE_ANSWER_QUESTION" &&
+            option.isCorrect == 1
+          ) {
+            this.singleRightAnswer = String.fromCharCode(65 + i);
+          }
+          if (
+            this.quesModel.questionType == "MULTIPLE_ANSWER_QUESTION" &&
+            option.isCorrect == 1
+          ) {
+            this.multipleRightAnswer.push(String.fromCharCode(65 + i));
+          }
+        }
+      }
+    },
+    //打开修改试题编辑框
+    openQuesDialog() {
+      this.quesDialog = true;
+    },
+    //关闭试题编辑框
+    closeQuesDialog() {
+      this.quesDialog = false;
+      this.quesModel = {};
+    },
+    //新增属性
+    //在正确的option上设置isCorrect=1
+    setRightAnswer() {
+      if (
+        !this.quesModel.quesOptions ||
+        this.quesModel.quesOptions.length == 0
+      ) {
+        return false;
+      }
+      for (var i = 0; i < this.quesModel.quesOptions.length; i++) {
+        var option = this.quesModel.quesOptions[i];
+        var answerOrderNum = String.fromCharCode(65 + i);
+        if (this.quesModel.questionType == "SINGLE_ANSWER_QUESTION") {
+          option["isCorrect"] =
+            answerOrderNum == this.singleRightAnswer ? 1 : 0;
+        }
+        if (this.quesModel.questionType == "MULTIPLE_ANSWER_QUESTION") {
+          option["isCorrect"] =
+            this.multipleRightAnswer.indexOf(answerOrderNum) > -1 ? 1 : 0;
+        }
+      }
+    },
+    //获取重复试题
+    getreduplicateQuestions() {
+      this.duplicateLoading = true;
+      this.$http
+        .get(QUESTION_API + "/paper/" + this.paperId + "/reduplicate-questions")
+        .then((response) => {
+          this.reduplicateQuestions = response.data;
+          this.duplicateLoading = false;
+          this.initReduplicateQuesColor();
+          // var ques = document.getElementsByClassName("ques")[0];
+          // ques.style.display = "inline";
+        });
+    },
+    initReduplicateQuesColor() {
+      var colorCount = this.reduplicateQuestions.length;
+      if (colorCount > 20) {
+        this.reduplicateQuesColor = randomColor({
+          luminosity: "bright",
+          count: colorCount,
+        });
+      } else {
+        this.reduplicateQuesColor = this.defaultColor;
+      }
+    },
+    getSubQuesEditId(paperDetailUnit, subQuestion) {
+      return paperDetailUnit.question.id + "_" + subQuestion.quesParams.number;
+    },
+    //返回
+    back() {
+      if (sessionStorage.getItem("question_back") == "true") {
+        this.$router.push({
+          path: "/questions/paper_storage/0",
+        });
+      } else {
+        this.$router.push({
+          path: "/questions/paper_storage/1",
+        });
+      }
+    },
+    paperDetailShow(paperDetail) {
+      if (this.reduplicateGroup.length == 0) {
+        return true;
+      }
+      let paperDetailUnits = paperDetail.paperDetailUnits;
+      for (let i = 0, imax = paperDetailUnits.length; i < imax; i++) {
+        for (var j = 0, jmax = this.reduplicateGroup.length; j < jmax; j++) {
+          if (paperDetailUnits[i].id == this.reduplicateGroup[j]) {
+            return true;
+          }
+        }
+      }
+      return false;
+    },
+  },
+};
+</script>
+<style scoped src="../styles/EditPaper.css">
+.property_with {
+  width: 100px;
+}
+.ck-toolbar {
+  z-index: 9999;
+}
+#app {
+  background-color: white !important;
+}
+</style>
+<style scoped src="../styles/Common.css"></style>