Browse Source

5.0.1需求开发

刘洋 2 years ago
parent
commit
94fdc73f4b

+ 1 - 0
.eslintrc.js

@@ -11,6 +11,7 @@ module.exports = {
     "no-console": process.env.NODE_ENV === "production" ? "off" : "off",
     "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
     "vue/no-v-html": "off",
+    "vue/multi-word-component-names": "off",
   },
   overrides: [
     {

+ 6 - 0
src/modules/oe/routes/routes.js

@@ -13,6 +13,7 @@ import examPaperDetail from "../views/examPaperDetail.vue";
 import examSummary from "../views/examSummary.vue";
 import exportTaskList from "../views/export_task_list.vue";
 import Tips from "../../portal/views/tips/Tips.vue";
+import ScoreStatistic from "../views/scoreStatistic.vue";
 
 export default [
   {
@@ -89,6 +90,11 @@ export default [
         name: "exportTaskList",
         component: exportTaskList, //导出任务列表
       },
+      {
+        path: "scoreStatistic",
+        name: "scoreStatistic",
+        component: ScoreStatistic,
+      },
     ],
   },
 ];

+ 57 - 1
src/modules/oe/views/examSummary.vue

@@ -1,6 +1,6 @@
 <template>
   <el-container>
-    <el-main class="el-main-padding">
+    <el-main class="el-main-padding main-wrap">
       <el-row>
         <el-col :span="7">
           <el-form>
@@ -73,6 +73,21 @@
           </el-form>
         </el-col>
       </el-row>
+      <div class="statistics-info">
+        <span class="title">考试实时统计</span>
+        <p>
+          全校在线人数:
+          <span>{{ examRealData.allOnlineCount }}</span>
+        </p>
+        <p>
+          全校在考人数:
+          <span>{{ examRealData.allExamOnlineCount }}</span>
+        </p>
+        <p>
+          当前批次在考人数:
+          <span>{{ examRealData.examOnlineCount }}</span>
+        </p>
+      </div>
       <el-row :gutter="2">
         <el-col :span="10" class="chart-border">
           <div class="chart-header">考试进度情况</div>
@@ -264,6 +279,7 @@ import "echarts/lib/component/tooltip";
 import "echarts/lib/component/title";
 import "echarts/lib/chart/bar";
 import "echarts/lib/chart/line";
+import qs from "qs";
 import { EXAM_WORK_API } from "@/constants/constants";
 export default {
   components: { "v-chart": ECharts },
@@ -288,6 +304,11 @@ export default {
       queryExamStages4SearchLoading: false,
       examStageList4Search: [],
       currentExamType: null,
+      examRealData: {
+        allExamOnlineCount: "",
+        allOnlineCount: "",
+        examOnlineCount: "",
+      },
     };
   },
   computed: {
@@ -419,6 +440,19 @@ export default {
           }
         }
       }
+      this.getRealTimeData(examId);
+    },
+    getRealTimeData(examId) {
+      this.$http
+        .post(
+          `/api/ecs_reports/examData/online/statistic?${qs.stringify({
+            examId,
+            rootOrgId: this.user.rootOrgId,
+          })}`
+        )
+        .then((res) => {
+          Object.assign(this.examRealData, res.data);
+        });
     },
     changeExamStage() {
       this.getPieData(this.currentExamType);
@@ -753,3 +787,25 @@ export default {
 }
 </style>
 <style scoped src="../style/common.css"></style>
+<style scoped lang="scss">
+.statistics-info {
+  font-size: 14px;
+  display: flex;
+  align-items: center;
+  padding: 10px 0;
+  border-top: 1px solid #eee;
+  margin-top: 10px;
+  p {
+    margin: 0;
+    margin-left: 80px;
+    font-size: 13px;
+    span {
+      font-weight: bold;
+    }
+  }
+  span.title {
+    font-weight: bold;
+    color: #909399;
+  }
+}
+</style>

+ 522 - 0
src/modules/oe/views/scoreStatistic.vue

@@ -0,0 +1,522 @@
+<template>
+  <div class="score-statistic main-wrap">
+    <el-form :inline="true" :model="searchParams">
+      <el-form-item label="考试">
+        <el-select
+          v-model="searchParams.examId"
+          filterable
+          remote
+          :remote-method="getExams"
+          clearable
+          placeholder="请选择考试"
+          size="small"
+          @change="changeExam"
+        >
+          <el-option
+            v-for="item in examList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="课程" label-width="70px" label-position="right">
+        <el-select
+          v-model="searchParams.courseId"
+          class="form_search_width"
+          clearable
+          filterable
+          placeholder="请选择课程"
+          size="small"
+        >
+          <el-option
+            v-for="item in courseList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+      <el-button type="primary" :disabled="paramsMiss()" @click="searchHandler"
+        >查询</el-button
+      >
+      <el-button type="primary" :disabled="paramsMiss()" @click="countHandler"
+        >刷新计算</el-button
+      >
+      <el-button
+        type="primary"
+        style="float: right"
+        :disabled="!searchParams.examId"
+        @click="openDialog"
+        >及格线设置</el-button
+      >
+    </el-form>
+
+    <div class="search-result flex">
+      <div class="result-column">
+        <div class="title">批次整体统计</div>
+        <div class="desc-item">
+          应考人次: <span>{{ examResult.allCount }}</span>
+        </div>
+        <div class="desc-item">
+          实考人次:<span> {{ examResult.finishCount }}</span>
+        </div>
+        <div class="desc-item">
+          缺考人次:<span> {{ examResult.unFinishCount }}</span>
+        </div>
+        <div class="desc-item">
+          缺考率:<span> {{ examResult.unFinishRate }}</span>
+        </div>
+      </div>
+      <div class="result-column">
+        <div class="title">整体成绩概况</div>
+        <div class="desc-item">
+          及格人数:<span> {{ examResult.passScoreCount }}</span>
+        </div>
+        <div class="desc-item">
+          及格率:<span> {{ examResult.passScoreRate }}</span>
+        </div>
+        <div class="desc-item">
+          优秀人数:<span> {{ examResult.goodScoreCount }}</span>
+        </div>
+        <div class="desc-item">
+          优秀率:<span> {{ examResult.goodScoreRate }}</span>
+        </div>
+      </div>
+      <div class="result-column">
+        <div class="title">
+          批次整体统计:<span>{{ courseResult.courseName }}</span>
+        </div>
+        <div class="desc-item">
+          应考人次: <span>{{ courseResult.allCount }}</span>
+        </div>
+        <div class="desc-item">
+          实考人次:<span> {{ courseResult.finishCount }}</span>
+        </div>
+        <div class="desc-item">
+          缺考人次:<span> {{ courseResult.unFinishCount }}</span>
+        </div>
+        <div class="desc-item">
+          缺考率:<span> {{ courseResult.unFinishRate }}</span>
+        </div>
+      </div>
+      <div class="result-column">
+        <div class="title">科目成绩概况</div>
+        <div class="desc-item">
+          及格人数:<span> {{ courseResult.passScoreCount }}</span>
+        </div>
+        <div class="desc-item">
+          及格率: <span>{{ courseResult.passScoreRate }}</span>
+        </div>
+        <div class="desc-item">
+          优秀人数: <span>{{ courseResult.goodScoreCount }}</span>
+        </div>
+        <div class="desc-item">
+          优秀率:<span> {{ courseResult.goodScoreRate }}</span>
+        </div>
+      </div>
+    </div>
+
+    <el-form :inline="true">
+      <el-form-item label="学习中心统计:">
+        <el-select v-model="subOrgId" clearable @change="filterSubOrg">
+          <el-option
+            v-for="item in orgOptions"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+
+      <el-button type="primary" :disabled="paramsMiss()" @click="exportExcel"
+        >导出</el-button
+      >
+    </el-form>
+
+    <el-table :data="tableData" style="margin-top: 10px">
+      <el-table-column
+        v-for="(item, index) in tableColumns"
+        :key="index"
+        :label="item.label"
+        :prop="item.prop"
+      >
+      </el-table-column>
+    </el-table>
+
+    <el-dialog
+      ref="dialog"
+      title="及格线设置"
+      width="600px"
+      :visible.sync="visible"
+      :close-on-click-modal="false"
+      @close="
+        visible = false;
+        dialogChecked = false;
+      "
+    >
+      <el-table
+        v-loading="dialogLoading"
+        :data="dialogTableData"
+        :columns="dialogTableColumns"
+        border
+      >
+        <el-table-column
+          v-for="(item, index) in dialogTableColumns"
+          :key="index"
+          :label="item.label"
+          :prop="item.prop"
+        >
+          <template slot-scope="scope">
+            <span v-if="item.prop === 'courseName'">
+              {{ scope.row[item.prop] }}
+            </span>
+            <div v-else>
+              <el-input-number
+                v-model="dialogTableData[scope.$index][item.prop]"
+                :class="{
+                  'none-value':
+                    dialogChecked &&
+                    !hasNumValue(dialogTableData[scope.$index][item.prop]),
+                }"
+                :controls="false"
+                style="width: 60px; margin-right: 5px"
+              ></el-input-number>
+              %
+
+              <span
+                v-if="
+                  dialogChecked &&
+                  !hasNumValue(dialogTableData[scope.$index][item.prop])
+                "
+                class="none-value-tip"
+                >请输入</span
+              >
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div style="font-size: 13px; margin-top: 30px; color: #e6a23c">
+        数值设置需要1~100的整数
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button
+          style="float: left"
+          type="primary"
+          plain
+          @click="resetDialogData"
+          >重置</el-button
+        >
+        <el-button @click="visible = false">取 消</el-button>
+        <el-button
+          type="primary"
+          :disabled="setLineLoading"
+          @click="passLineConfirm"
+          >确 定</el-button
+        >
+      </span>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { mapState } from "vuex";
+export default {
+  data() {
+    return {
+      dialogLoading: false,
+      searchParams: {
+        examId: "",
+        courseId: "",
+      },
+      subOrgId: "",
+      visible: false,
+      originDialogTableData: [],
+      dialogTableData: [],
+      dialogTableColumns: [
+        { label: "科目名称", prop: "courseName" },
+        { label: "及格线", prop: "passScoreLine" },
+        { label: "优秀线", prop: "goodScoreLine" },
+      ],
+      originTableData: [],
+      tableData: [],
+      tableColumns: [
+        { label: "学习中心", prop: "orgName" },
+        { label: "应考", prop: "allCount" },
+        { label: "实考", prop: "finishCount" },
+        { label: "缺考", prop: "unFinishCount" },
+        { label: "缺考率", prop: "unFinishRate" },
+        { label: "及格人数", prop: "passScoreCount" },
+        { label: "及格率", prop: "passScoreRate" },
+        { label: "优秀人数", prop: "goodScoreCount" },
+        { label: "优秀率", prop: "goodScoreRate" },
+      ],
+      examList: [],
+      courseList: [],
+      examResult: {
+        courseName: "",
+        allCount: "",
+        finishCount: "",
+        unFinishCount: "",
+        unFinishRate: "",
+        passScoreCount: "",
+        passScoreRate: "",
+        goodScoreCount: "",
+        goodScoreRate: "",
+      },
+      courseResult: {
+        allCount: "",
+        finishCount: "",
+        unFinishCount: "",
+        unFinishRate: "",
+        passScoreCount: "",
+        passScoreRate: "",
+        goodScoreCount: "",
+        goodScoreRate: "",
+      },
+      orgOptions: [],
+      dialogChecked: false,
+      counting: false,
+      setLineLoading: false,
+    };
+  },
+  created() {
+    this.getExams();
+    this.getOrgList();
+  },
+  computed: {
+    ...mapState({ user: (state) => state.user }),
+  },
+  methods: {
+    paramsMiss() {
+      return !this.searchParams.examId || !this.searchParams.courseId;
+    },
+    filterSubOrg() {
+      if (!this.subOrgId) {
+        this.tableData = this.originTableData;
+      } else {
+        this.tableData = this.originTableData.filter(
+          (item) => item.orgId == this.subOrgId
+        );
+      }
+    },
+    getOrgList() {
+      this.$http
+        .get("/api/ecs_core/org/query", {
+          params: { name: "", rootOrgId: this.user.rootOrgId, enable: true },
+        })
+        .then((res) => {
+          this.orgOptions = res.data;
+        });
+    },
+    openDialog() {
+      this.getDialogData();
+      this.visible = true;
+    },
+    getExams(examName) {
+      if (!examName) {
+        examName = "";
+      }
+      this.$http
+        .get("/api/ecs_exam_work/exam/queryByNameLike", {
+          params: {
+            enable: true,
+            name: examName,
+            examTypes: "ONLINE#OFFLINE#ONLINE_HOMEWORK",
+          },
+        })
+        .then((response) => {
+          this.examList = response.data;
+        });
+    },
+    getCourses() {
+      this.courseList = [];
+      this.searchParams.courseId = "";
+      var examId = this.searchParams.examId;
+      if (!examId) {
+        return false;
+      }
+      this.$http
+        .get("/api/ecs_oe_admin/exam/student/findCoursesByExamIdAndOrgId", {
+          params: {
+            examId: examId,
+            // orgId: this.user.rootOrgId,
+          },
+        })
+        .then((response) => {
+          this.courseList = response.data;
+        });
+    },
+    searchHandler() {
+      this.getStatisticInfo();
+      this.getTableData();
+    },
+    countHandler() {
+      this.counting = true;
+      this.$http
+        .post(
+          "/api/ecs_oe_admin/exam/statistic/overview/refresh",
+          {},
+          {
+            params: {
+              courseId: this.searchParams.courseId,
+              examId: this.searchParams.examId,
+            },
+          }
+        )
+        .then(() => {
+          this.counting = false;
+          this.searchHandler();
+        });
+    },
+    getStatisticInfo() {
+      const { examId, courseId } = this.searchParams;
+      this.$http
+        .post("/api/ecs_oe_admin/exam/statistic/overview", null, {
+          params: {
+            examId,
+            courseId,
+          },
+          headers: { "content-type": "application/x-www-form-urlencoded" },
+        })
+        .then((res) => {
+          Object.assign(this.examResult, res.data.examResult || {});
+          Object.assign(this.courseResult, res.data.courseResult || {});
+        });
+    },
+    getTableData() {
+      const { examId, courseId } = this.searchParams;
+      this.$http
+        .post("/api/ecs_oe_admin/exam/statistic/overview/for/org", null, {
+          params: { examId, courseId },
+          headers: { "content-type": "application/x-www-form-urlencoded" },
+        })
+        .then((res) => {
+          this.tableData = res.data || [];
+          this.originTableData = JSON.parse(JSON.stringify(res.data || []));
+        });
+    },
+    resetDialogData() {
+      this.getDialogData();
+    },
+    getDialogData() {
+      if (!this.searchParams.examId) {
+        return false;
+      }
+      this.dialogLoading = true;
+      this.$http
+        .post("/api/ecs_exam_work/exam/course/list", {
+          courseCode: "",
+          courseName: "",
+          examId: this.searchParams.examId,
+          pageNo: 1,
+          pageSize: 1000,
+          weixinAnswerEnabled: null,
+        })
+        .then((res) => {
+          this.dialogTableData = res.data.content || [];
+          this.originDialogTableData = JSON.parse(
+            JSON.stringify(res.data.content || [])
+          );
+          this.dialogLoading = false;
+        });
+    },
+    changeExam() {
+      this.getCourses();
+      // this.getDialogData();
+    },
+    exportExcel() {
+      const { examId, courseId } = this.searchParams;
+      let key = this.user.key;
+      let token = this.user.token;
+      let url = `/api/ecs_oe_admin/exam/statistic/overview/for/org/export?examId=${examId}&courseId=${courseId}&$key=${key}&$token=${token}`;
+      location.href = url;
+    },
+    hasNumValue(val) {
+      return !!val || val === 0;
+    },
+    passLineConfirm() {
+      this.dialogChecked = true;
+      if (
+        this.dialogTableData.every((item) => {
+          return (
+            this.hasNumValue(item.passScoreLine) &&
+            this.hasNumValue(item.goodScoreLine)
+          );
+        })
+      ) {
+        this.setLineLoading = true;
+        let num = this.dialogTableData.length;
+        this.dialogTableData.forEach((item) => {
+          this.$http
+            .post(
+              "/api/ecs_exam_work/exam/course/settingScoreLine",
+              {},
+              {
+                params: {
+                  courseId: item.courseId,
+                  examId: item.examId,
+                  goodScoreLine: item.goodScoreLine,
+                  passScoreLine: item.passScoreLine,
+                },
+              }
+            )
+            .then(() => {
+              num--;
+              if (num === 0) {
+                this.$message.success("设置成功");
+                this.setLineLoading = false;
+                this.visible = false;
+              }
+            })
+            .catch(() => {
+              this.setLineLoading = false;
+            });
+        });
+      }
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.score-statistic {
+  .none-value {
+    ::v-deep .el-input__inner {
+      border-color: #f56c6c;
+    }
+  }
+  ::v-deep .none-value-tip {
+    font-size: 12px;
+    color: #f56c6c;
+    margin-left: 10px;
+  }
+  .search-result {
+    padding: 15px 0;
+    margin-bottom: 30px;
+    .result-column {
+      width: 25%;
+      text-align: center;
+      .title {
+        color: #333;
+        font-size: 13px;
+        font-weight: bold;
+        line-height: 24px;
+        margin-bottom: 5px;
+        span {
+          font-weight: bold;
+          color: #409eff;
+        }
+      }
+      .desc-item {
+        line-height: 24px;
+        font-size: 12px;
+        color: #909399;
+        span {
+          font-weight: bold;
+          color: #409eff;
+        }
+      }
+    }
+  }
+}
+</style>

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

@@ -25,6 +25,8 @@ 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";
+import AddPaperSelect from "../views/AddPaperSelect.vue";
+import ExtractPaperTemplate from "../views/ExtractPaperTemplate.vue";
 
 export default [
   {
@@ -135,6 +137,18 @@ export default [
         path: "edit_select_question/:paperId/:paperDetailId/:questionType/:courseNo/:courseName",
         component: EditSelectQuestion,
       },
+      {
+        path: "add_paper_select",
+        component: AddPaperSelect,
+      },
+      {
+        path: "add_paper_select/:id",
+        component: AddPaperSelect,
+      },
+      {
+        path: "extract_paper_template",
+        component: ExtractPaperTemplate,
+      },
     ],
   },
   {

+ 478 - 0
src/modules/questions/views/AddPaperSelect.vue

@@ -0,0 +1,478 @@
+<template>
+  <section class="content add-paper-select">
+    <div>
+      <LinkTitlesCustom
+        :current-paths="['题库管理 ', '卷库管理', '抽题模板管理']"
+      />
+    </div>
+    <div class="box-body">
+      <div class="top">
+        <div class="flex items-center">
+          <p>课程名称:{{ $route.query.courseName }}</p>
+          <p style="margin-left: 100px">
+            课程代码:{{ $route.query.courseNo }}
+          </p>
+        </div>
+        <el-button type="primary" size="small" @click="save">保存</el-button>
+      </div>
+      <div class="form">
+        <el-form
+          ref="form"
+          :rules="rules"
+          :model="form"
+          label-position="left"
+          label-width="120px"
+        >
+          <el-form-item label="组卷模板名称:" prop="name">
+            <el-input v-model="form.name" style="width: 300px"></el-input>
+          </el-form-item>
+          <el-form-item label="选择组卷模式:" required>
+            <el-radio-group
+              v-model="form.paperStructType"
+              @change="paperStructTypeChange"
+            >
+              <el-radio label="EXACT">精确结构</el-radio>
+              <el-radio label="BLUEPRINT">蓝图结构</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="选择组卷结构:" prop="paperStructId">
+            <el-select v-model="form.paperStructId">
+              <el-option
+                v-for="item in options1"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id"
+              ></el-option>
+            </el-select>
+            <p style="display: inline-block; margin-left: 40px">
+              难度:<span style="color: #409eff; font-weight: bold">{{
+                curStruct?.difficulty
+              }}</span>
+            </p>
+          </el-form-item>
+
+          <el-table
+            v-loading="tableLoading1"
+            :data="tableData1"
+            border
+            style="margin-top: 10px; margin-bottom: 20px; width: 700px"
+          >
+            <el-table-column
+              v-for="(item, index) in tableColumns1"
+              :key="index"
+              :label="item.label"
+              :prop="item.prop"
+              :min-width="item.minWidth"
+            >
+              <template slot-scope="scope">
+                <span
+                  v-if="
+                    !['hardInfo', 'mediumInfo', 'easyInfo'].includes(item.prop)
+                  "
+                  >{{ scope.row[item.prop] }}</span
+                >
+                <span v-else>{{ scope.row[item.prop]?.count }}</span>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <div class="flex" style="margin-top: 30px">
+            <div style="width: 40%">
+              <el-form-item label="选择题源范围:" style="margin-bottom: 0">
+                <el-select
+                  v-model="form.paperType"
+                  placeholder="题源选择"
+                  style="width: 120px"
+                  @change="changePaperType"
+                >
+                  <el-option label="题库来源" value="IMPORT"></el-option>
+                  <el-option label="卷库来源" value="GENERATE"></el-option>
+                </el-select>
+                <span
+                  v-if="checked && !multipleSelection.length"
+                  class="red"
+                  style="font-size: 12px; font-weight: bold; margin-left: 10px"
+                  >请选择数据</span
+                >
+              </el-form-item>
+              <el-table
+                ref="table2"
+                :data="tableData2"
+                border
+                @selection-change="handleSelectionChange"
+              >
+                <el-table-column
+                  type="selection"
+                  width="55"
+                  :selectable="canSelect"
+                >
+                </el-table-column>
+                <el-table-column
+                  v-for="(item, index) in tableColumns2"
+                  :key="index"
+                  :label="item.label"
+                  :prop="item.prop"
+                  :width="item.width"
+                >
+                </el-table-column>
+              </el-table>
+            </div>
+            <div style="width: 60%; padding-left: 20px">
+              <div style="color: #606266; font-size: 14px; line-height: 32px">
+                <span>选中范围预览:</span>
+                <span
+                  v-if="hasError()"
+                  class="red"
+                  style="font-size: 12px; font-weight: bold"
+                  >不满足最低要求</span
+                >
+              </div>
+              <el-table
+                v-loading="tableLoading3"
+                :data="tableData3"
+                border
+                style="margin-top: 8px"
+              >
+                <el-table-column
+                  v-for="(item, index) in tableColumns3"
+                  :key="index"
+                  :label="item.label"
+                  :prop="item.prop"
+                >
+                  <template slot-scope="scope">
+                    <span
+                      v-if="
+                        !['hardInfo', 'mediumInfo', 'easyInfo'].includes(
+                          item.prop
+                        )
+                      "
+                      :class="{ red: hasNumError(scope.row, item.prop) }"
+                      >{{ scope.row[item.prop] }}</span
+                    >
+                    <span
+                      v-else
+                      :class="{ red: hasNumError(scope.row, item.prop) }"
+                      >{{ scope.row[item.prop]?.count }}</span
+                    >
+                  </template>
+                </el-table-column>
+              </el-table>
+            </div>
+          </div>
+        </el-form>
+      </div>
+    </div>
+  </section>
+</template>
+<script>
+import qs from "qs";
+import { mapState } from "vuex";
+export default {
+  data() {
+    return {
+      form: {
+        paperStructType: "BLUEPRINT",
+        name: "",
+        paperStructId: "",
+        paperType: "IMPORT",
+        paperIds: "",
+      },
+      paperIdsArr: [],
+      options1: [],
+      tableData1: [],
+      tableColumns1: [
+        { label: "题型", prop: "detailName", minWidth: "100" },
+        { label: "总分", prop: "totalScore", minWidth: "80" },
+        { label: "数量", prop: "totalCount", minWidth: "80" },
+        { label: "难", prop: "hardInfo", minWidth: "80" },
+        { label: "中", prop: "mediumInfo", minWidth: "80" },
+        { label: "易", prop: "easyInfo", minWidth: "80" },
+      ],
+      tableData2: [],
+      tableColumns2: [
+        { label: "名称", prop: "name" },
+        { label: "小题数量", prop: "unitCount", width: "100px" },
+      ],
+      multipleSelection: [],
+      tableData3: [],
+      tableColumns3: [
+        { label: "题型", prop: "detailName", minWidth: "100" },
+        { label: "数量", prop: "totalCount", minWidth: "80" },
+        { label: "难", prop: "hardInfo", minWidth: "80" },
+        { label: "中", prop: "mediumInfo", minWidth: "80" },
+        { label: "易", prop: "easyInfo", minWidth: "80" },
+      ],
+      lastRequestKey: "",
+      tableLoading3: false,
+      tableLoading1: false,
+      checked: false,
+      initSelectedRows: [],
+    };
+  },
+  computed: {
+    ...mapState({ user: (state) => state.user }),
+    curStruct() {
+      if (this.form.paperStructId) {
+        return this.options1.find(
+          (item) => item.id === this.form.paperStructId
+        );
+      } else {
+        return {};
+      }
+    },
+    rules() {
+      return {
+        name: { required: true, message: "请输入模板名称", trigger: "change" },
+        paperStructId: {
+          required: true,
+          message: "请选择组卷结构",
+          trigger: "change",
+        },
+      };
+    },
+  },
+  watch: {
+    "form.paperStructId"() {
+      this.structChange();
+    },
+    // "form.paperStructType"() {
+    //   this.form.paperStructId = "";
+    //   this.getStruct();
+    //   this.tableData1 = [];
+    //   this.$refs.table2.clearSelection();
+    // },
+    multipleSelection(val) {
+      this.form.paperIds = val.map((item) => item.id).join(",");
+    },
+  },
+  async created() {
+    let res = await this.getTplData();
+    if (res) {
+      this.getStruct();
+      this.getTable2(true);
+    }
+  },
+  methods: {
+    paperStructTypeChange() {
+      alert("paperStructTypeChange");
+      this.form.paperStructId = "";
+      this.getStruct();
+      this.tableData1 = [];
+      this.$refs.table2.clearSelection();
+    },
+    async getTplData() {
+      const { id } = this.$route.params;
+      if (id) {
+        try {
+          const data = await this.$http.post(
+            "/api/ecs_ques/randompaper/info",
+            null,
+            {
+              params: {
+                id,
+              },
+            }
+          );
+          const tplData = data.data;
+          this.paperIdsArr = tplData.paperIds || [];
+          Object.assign(this.form, tplData || {}, {
+            paperIds: (tplData.paperIds || []).join(","),
+          });
+          return true;
+        } catch (e) {
+          return false;
+        }
+      } else {
+        return true;
+      }
+    },
+    save() {
+      this.checked = true;
+      this.$refs.form.validate((valid) => {
+        if (valid) {
+          if (this.multipleSelection.length && !this.hasError()) {
+            let params = {
+              courseId: this.$route.query.courseId,
+              ...this.form,
+              rootOrgId: this.user.rootOrgId,
+            };
+            if (this.$route.params.id) {
+              params.id = this.$route.params.id;
+            }
+            this.$http
+              .post("/api/ecs_ques/randompaper/save", qs.stringify(params))
+              .then(() => {
+                this.$message.success("保存成功");
+                this.$router.back();
+              });
+          }
+        } else {
+          console.log("error submit!!");
+          return false;
+        }
+      });
+    },
+    canSelect() {
+      return !!this.form.paperStructId;
+    },
+    structChange() {
+      this.tableLoading1 = true;
+      this.$http
+        .post("/api/ecs_ques/randompaper/struct/question/info", null, {
+          params: { structId: this.form.paperStructId },
+          headers: { "content-type": "application/x-www-form-urlencoded" },
+        })
+        .then((res) => {
+          this.tableData1 = res.data.structQuestionInfo || [];
+          this.tableLoading1 = false;
+        });
+    },
+    getStruct() {
+      let apiUrl = "/api/ecs_ques/paperStruct/1/10000";
+      let params =
+        this.form.paperStructType == "EXACT"
+          ? { courseNo: "ALL", type: "EXACT" }
+          : { type: "BLUEPRINT" };
+      this.$http.get(apiUrl, { params }).then((res) => {
+        this.options1 = res.data.content;
+      });
+    },
+    getTable2(bool) {
+      let apiUrl =
+        this.form.paperType === "IMPORT"
+          ? "/api/ecs_ques/importPaper/huoge/1/10000"
+          : "/api/ecs_ques/genPaper/huoge/1/10000";
+      this.$http
+        .get(apiUrl, {
+          params: { courseNo: this.$route.query.courseNo, ids: "" },
+        })
+        .then((res) => {
+          this.tableData2 = res.data.content || [];
+          if (bool) {
+            this.tableData2.forEach((item) => {
+              if (this.paperIdsArr.includes(item.id)) {
+                this.initSelectedRows.push(item);
+              }
+            });
+            setTimeout(() => {
+              this.initSelectedRows.forEach((item) => {
+                this.$refs.table2.toggleRowSelection(item, true);
+              });
+            }, 0);
+          }
+        });
+    },
+    changePaperType() {
+      this.tableData2 = [];
+      this.multipleSelection = [];
+      this.getTable2();
+    },
+    getTable3() {
+      let paperIds = this.multipleSelection.map((item) => item.id);
+      if (!paperIds.length) {
+        this.tableData3 = [];
+        this.tableLoading3 = false;
+        return false;
+      }
+      this.tableLoading3 = true;
+      let str = new Date().getTime() + "";
+      this.lastRequestKey = str;
+      this.$http
+        .post(
+          "/api/ecs_ques/randompaper/struct/question/view/info",
+          qs.stringify({
+            paperIds: paperIds.join(","),
+            structId: this.form.paperStructId,
+          }),
+          {
+            headers: { "content-type": "application/x-www-form-urlencoded" },
+          }
+        )
+        .then((res) => {
+          if (this.lastRequestKey === str) {
+            this.tableData3 = res.data.structQuestionInfo || [];
+            this.tableLoading3 = false;
+          }
+        });
+    },
+    handleSelectionChange(val) {
+      this.multipleSelection = val;
+      this.getTable3();
+    },
+    hasNumError(row, prop) {
+      if (prop === "detailName") {
+        return false;
+      } else {
+        let targetName = row.detailName;
+        let find = this.tableData1.find(
+          (item) => item.detailName === targetName
+        );
+        if (!find) {
+          return false;
+        } else {
+          if (prop === "totalCount") {
+            return row[prop] < find[prop];
+          } else {
+            return row[prop].count < find[prop].count;
+          }
+        }
+      }
+    },
+    hasError() {
+      return (
+        this.tableData1.length &&
+        this.tableData3.length &&
+        this.tableData3.every((item) => {
+          return (
+            this.hasNumError(item, "totalCount") ||
+            this.hasNumError(item, "hardInfo") ||
+            this.hasNumError(item, "mediumInfo") ||
+            this.hasNumError(item, "easyInfo")
+          );
+        })
+      );
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.add-paper-select {
+  * {
+    box-sizing: border-box;
+  }
+
+  .el-form .el-form-item {
+    margin-bottom: 18px;
+  }
+
+  ::v-deep .red {
+    color: #f56c6c;
+  }
+
+  p {
+    margin: 0;
+  }
+
+  .top {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding-bottom: 15px;
+    border-bottom: 1px solid #eee;
+
+    p {
+      font-size: 14px;
+    }
+  }
+
+  .box-body {
+    background-color: #fff;
+    border-radius: 6px;
+    padding: 15px 10px;
+
+    .form {
+      margin-top: 15px;
+    }
+  }
+}
+</style>

+ 169 - 0
src/modules/questions/views/ExtractPaperTemplate.vue

@@ -0,0 +1,169 @@
+<template>
+  <div class="extract-paper-template">
+    <el-form :inline="true" :model="searchParams">
+      <el-form-item label="课程:">
+        <el-select
+          v-model="searchParams.courseId"
+          clearable
+          placeholder="请选择课程"
+          size="small"
+        >
+          <el-option
+            v-for="item in allCourseList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item
+        label="模板名称:"
+        label-width="100px"
+        label-position="right"
+      >
+        <el-input v-model="searchParams.name"></el-input>
+      </el-form-item>
+      <el-form-item label="状态:" label-width="100px" label-position="right">
+        <el-select
+          v-model="searchParams.enable"
+          clearable
+          placeholder="请选择状态"
+          size="small"
+          style="width: 120px"
+        >
+          <el-option label="启用" :value="true"></el-option>
+          <el-option label="禁用" :value="false"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-button type="primary" @click="search(1)">查询</el-button>
+    </el-form>
+
+    <el-table :data="tableData" style="margin-top: 20px">
+      <el-table-column
+        v-for="(column, index) in columns"
+        :key="index"
+        :prop="column.prop"
+        :label="column.label"
+        :width="column.width"
+        :min-width="column.minWidth"
+        show-overflow-tooltip
+      >
+      </el-table-column>
+      <el-table-column label="操作" :width="160">
+        <template slot-scope="scope">
+          <el-button type="primary" plain @click="editRow(scope.row)"
+            >编辑</el-button
+          >
+          <el-button
+            v-if="scope.row.enable"
+            type="danger"
+            plain
+            @click="changeStatus(scope.row)"
+            >禁用</el-button
+          >
+          <el-button
+            v-else
+            type="success"
+            plain
+            @click="changeStatus(scope.row)"
+            >启用</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+<script>
+export default {
+  data() {
+    return {
+      allCourseList: [],
+      searchParams: {
+        courseId: "",
+        name: "",
+        enable: "",
+      },
+      pageNumber: 1,
+      pageSize: 10,
+      tableData: [],
+      columns: [
+        { label: "ID", prop: "id", width: 178 },
+        { label: "模板名称", prop: "name" },
+        { label: "课程代码", prop: "courseCode" },
+        { label: "课程名称", prop: "courseName" },
+        { label: "组卷类型", prop: "paperStructTypeStr", width: 100 },
+        { label: "结构名称", prop: "paperStructName" },
+        { label: "状态", prop: "enableStr", width: 60 },
+        { label: "更新时间", prop: "updateDate", minWidth: 140 },
+        { label: "操作人", prop: "updateByName" },
+      ],
+    };
+  },
+  created() {
+    this.getAllCourses();
+    this.search();
+  },
+  methods: {
+    changeStatus(row) {
+      let str = row.enable ? "禁用" : "启用";
+      this.$confirm(`确认${str}?`, "提示", {
+        type: "warning",
+      }).then(() => {
+        this.loading = true;
+        this.$http
+          .post("/api/ecs_ques/randompaper/toggle", null, {
+            params: {
+              id: row.id,
+              enable: !row.enable,
+            },
+          })
+          .then(() => {
+            this.$notify({
+              message: str + "成功",
+              type: "success",
+            });
+            this.search();
+          });
+      });
+    },
+    editRow(row) {
+      this.$router.push(
+        `/questions/add_paper_select/${row.id}?courseNo=${row.courseCode}&courseName=${row.courseName}&courseId=${row.courseId}`
+      );
+    },
+    getAllCourses() {
+      this.$http
+        .get("/api/ecs_core/course/query", {
+          params: {
+            enable: true,
+            query: "",
+          },
+        })
+        .then((response) => {
+          this.allCourseList = response.data;
+        });
+    },
+    search(pageNumber) {
+      this.pageNumber = pageNumber;
+      this.$http
+        .post("/api/ecs_ques/randompaper/page", null, {
+          params: {
+            pageNumber: this.pageNumber,
+            pageSize: this.pageSize,
+            ...this.searchParams,
+          },
+        })
+        .then((res) => {
+          this.tableData = res.data.content || [];
+        });
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.extract-paper-template {
+  background-color: #fff;
+  border-radius: 6px;
+  padding: 15px;
+}
+</style>

+ 27 - 3
src/modules/questions/views/GenPaper.vue

@@ -116,7 +116,7 @@
           <el-col :span="6">
             <div class="search_down">
               <el-button size="small" type="primary" @click="searchFrom"
-                ><i class="el-icon-search"></i> 查询</el-button
+                ><i class="el-icon-search"></i> 查 询</el-button
               >
               <el-button size="small" @click="resetForm"
                 ><i class="el-icon-refresh"></i> 重 置</el-button
@@ -127,6 +127,13 @@
             </div>
           </el-col>
         </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-button size="small" type="primary" @click="toAddPaperSelect(0)"
+              ><i class="el-icon-plus"></i>随机抽卷模板</el-button
+            >
+          </el-col>
+        </el-row>
         <div
           style="width: 100%; border-bottom: 1px solid #ddd; margin: 10px 0"
         ></div>
@@ -256,7 +263,7 @@
           prop="updateDate"
         >
         </el-table-column>
-        <el-table-column label="操作" width="200" fixed="right">
+        <el-table-column label="操作" width="286" fixed="right">
           <template slot-scope="scope">
             <div class="operate_left">
               <el-button
@@ -273,7 +280,7 @@
                 @click="audit(scope.row)"
                 ><i class="el-icon-share"></i> 试卷审核</el-button
               >
-              <el-dropdown style="margin-top: 5px">
+              <el-dropdown style="margin-left: 10px">
                 <el-button type="primary" size="mini" plain>
                   更多 <i class="el-icon-arrow-down el-icon--right"></i>
                 </el-button>
@@ -650,6 +657,23 @@ export default {
     this.searchOrgName();
   },
   methods: {
+    toAddPaperSelect(id) {
+      var courseNo = this.formSearch.courseNo;
+      if (!courseNo) {
+        this.$notify({
+          title: "警告",
+          message: "请选择课程",
+          type: "warning",
+        });
+        return false;
+      }
+      let course = this.getCourseObj(courseNo);
+      let courseName = course.name;
+      this.$router.push({
+        path: "/questions/add_paper_select" + (id ? `/${id}` : ""),
+        query: { courseNo, courseName, courseId: course.id },
+      });
+    },
     audit(row) {
       this.$confirm(
         "请通过试卷预览或编辑功能,认真检查试卷结构和题目,确认试卷无问题,则可以审核确认。",

+ 1 - 1
src/plugins/element.js

@@ -2,4 +2,4 @@ import Vue from "vue";
 import Element from "element-ui";
 import "element-ui/lib/theme-chalk/index.css";
 
-Vue.use(Element);
+Vue.use(Element, { size: "small" });

+ 19 - 0
src/styles/global.css

@@ -122,3 +122,22 @@ body {
   border-bottom: 1px solid #ddd;
   margin: 10px 0;
 }
+.flex{
+  display:flex;
+}
+.justify-center{
+  justify-content: center;
+}
+.items-center{
+  align-items: center;
+}
+.flex-center{
+  display:flex;
+  justify-content: center;
+  align-items: center;
+}
+.main-wrap{
+  background-color:#fff;
+  border-radius:6px;
+  padding:15px !important;
+}

+ 1 - 1
src/utils/utils.js

@@ -1,4 +1,4 @@
-import queryString from "query-string";
+import queryString from "qs";
 
 export function object2QueryString(obj) {
   return queryString.stringify(obj);