Browse Source

feat: 统计页面完成

zhangjie 1 year ago
parent
commit
d04bf1a4ce

+ 46 - 10
src/modules/target/api.js

@@ -1,5 +1,6 @@
 import { $postParam, $post } from "@/plugins/axios";
 import { $postParam, $post } from "@/plugins/axios";
 
 
+// 培养方案管理相关页面 =====================>
 // 培养方案管理 ------------------->
 // 培养方案管理 ------------------->
 export const trainingPlanListPage = (datas) => {
 export const trainingPlanListPage = (datas) => {
   return $postParam("/api/admin/obe/culture/program/page", datas);
   return $postParam("/api/admin/obe/culture/program/page", datas);
@@ -19,7 +20,6 @@ export const updateTrainingPlanDescription = (datas) => {
 export const updateTrainingPlanDetail = (id) => {
 export const updateTrainingPlanDetail = (id) => {
   return $postParam("/api/admin/obe/culture/program/get", { id });
   return $postParam("/api/admin/obe/culture/program/get", { id });
 };
 };
-
 // 培养方案管理-培养目标 ------------------->
 // 培养方案管理-培养目标 ------------------->
 export const trainingPlanTargetListPage = (datas) => {
 export const trainingPlanTargetListPage = (datas) => {
   return $postParam("/api/admin/obe/culture/program/target/list", datas);
   return $postParam("/api/admin/obe/culture/program/target/list", datas);
@@ -30,7 +30,6 @@ export const deleteTrainingPlanTarget = (id) => {
 export const updateTrainingPlanTarget = (datas) => {
 export const updateTrainingPlanTarget = (datas) => {
   return $post("/api/admin/obe/culture/program/target/save", datas);
   return $post("/api/admin/obe/culture/program/target/save", datas);
 };
 };
-
 // 培养方案管理-毕业要求 ------------------->
 // 培养方案管理-毕业要求 ------------------->
 export const trainingPlanRequirementListPage = (datas) => {
 export const trainingPlanRequirementListPage = (datas) => {
   return $postParam("/api/admin/obe/culture/program/requirement/list", datas);
   return $postParam("/api/admin/obe/culture/program/requirement/list", datas);
@@ -52,7 +51,6 @@ export const updateTrainingPlanRequirement = (datas) => {
 export const updateTrainingPlanRequirementPredict = (datas) => {
 export const updateTrainingPlanRequirementPredict = (datas) => {
   return $post("/api/admin/obe/culture/program/requirement/expect/save", datas);
   return $post("/api/admin/obe/culture/program/requirement/expect/save", datas);
 };
 };
-
 // 培养方案管理-培养目标与毕业要求关系矩阵 ------------------->
 // 培养方案管理-培养目标与毕业要求关系矩阵 ------------------->
 export const trainingPlanMatrixListPage = (datas) => {
 export const trainingPlanMatrixListPage = (datas) => {
   return $postParam("/api/admin/obe/culture/program/target/matrix/get", datas);
   return $postParam("/api/admin/obe/culture/program/target/matrix/get", datas);
@@ -60,7 +58,6 @@ export const trainingPlanMatrixListPage = (datas) => {
 export const updateRrainingPlanMatrix = (datas) => {
 export const updateRrainingPlanMatrix = (datas) => {
   return $post("/api/admin/obe/culture/program/target/matrix/save", datas);
   return $post("/api/admin/obe/culture/program/target/matrix/save", datas);
 };
 };
-
 // 培养方案管理-课程体系 ------------------->
 // 培养方案管理-课程体系 ------------------->
 export const trainingPlanCourseListPage = (datas) => {
 export const trainingPlanCourseListPage = (datas) => {
   return $postParam("/api/admin/obe/culture/program/course/list", datas);
   return $postParam("/api/admin/obe/culture/program/course/list", datas);
@@ -77,7 +74,6 @@ export const trainingPlanCourseQueryList = (datas) => {
 export const trainingPlanCourseSave = (datas) => {
 export const trainingPlanCourseSave = (datas) => {
   return $post("/api/admin/obe/culture/program/course/save", datas);
   return $post("/api/admin/obe/culture/program/course/save", datas);
 };
 };
-
 // 培养方案管理-课程支撑毕业要求达成矩阵 ------------------->
 // 培养方案管理-课程支撑毕业要求达成矩阵 ------------------->
 export const trainingPlanCourseMatrixDetail = (datas) => {
 export const trainingPlanCourseMatrixDetail = (datas) => {
   return $postParam("/api/admin/obe/culture/program/course/matrix/get", datas);
   return $postParam("/api/admin/obe/culture/program/course/matrix/get", datas);
@@ -98,6 +94,17 @@ export const deleteCourseOutline = (id) => {
 export const updateCourseOutline = (datas) => {
 export const updateCourseOutline = (datas) => {
   return $post("/api/admin/obe/course_outline/save", datas);
   return $post("/api/admin/obe/course_outline/save", datas);
 };
 };
+export const selectableTrainingPlanList = () => {
+  return $postParam(
+    "/api/admin/obe/course_outline/can_choose_culture_program",
+    {}
+  );
+};
+export const selectableTrainingPlanCourseList = (cultureProgramId) => {
+  return $postParam("/api/admin/obe/course_outline/can_choose_course", {
+    cultureProgramId,
+  });
+};
 // 课程大纲管理-课程目标 ------------------->
 // 课程大纲管理-课程目标 ------------------->
 export const courseOutlineTargetListPage = (datas) => {
 export const courseOutlineTargetListPage = (datas) => {
   return $postParam("/api/admin/obe/course_target/list", datas);
   return $postParam("/api/admin/obe/course_target/list", datas);
@@ -136,7 +143,7 @@ export const courseOutlineTargetRequirementKnowledge = (datas) => {
 //   return $post("/api/admin/basic/professional/save", datas);
 //   return $post("/api/admin/basic/professional/save", datas);
 // };
 // };
 
 
-// 课程考核方式和内容 ------------------->
+// 课程考核设置 ------------------->
 export const courseExamineListPage = (datas) => {
 export const courseExamineListPage = (datas) => {
   return $postParam(
   return $postParam(
     "/api/admin/obe/course_outline/assessment_setting_page",
     "/api/admin/obe/course_outline/assessment_setting_page",
@@ -148,8 +155,7 @@ export const deleteCourseExamine = (id) => {
     id,
     id,
   });
   });
 };
 };
-
-// 课程考核方式和内容-评价方式 ------------------->
+// 课程考核设置-评价方式 ------------------->
 export const courseExamineEvaluationListPage = (datas) => {
 export const courseExamineEvaluationListPage = (datas) => {
   return $postParam("/api/admin/obe/course_evaluation/list", datas);
   return $postParam("/api/admin/obe/course_evaluation/list", datas);
 };
 };
@@ -165,8 +171,7 @@ export const courseExamineWeightSettingStatus = (datas) => {
     datas
     datas
   );
   );
 };
 };
-
-// 课程考核方式和内容-评价方式 ------------------->
+// 课程考核设置-权重设置 ------------------->
 export const courseExamineWeightDetail = (datas) => {
 export const courseExamineWeightDetail = (datas) => {
   return $postParam("/api/admin/obe/course_weight/find", datas);
   return $postParam("/api/admin/obe/course_weight/find", datas);
 };
 };
@@ -174,6 +179,7 @@ export const courseExamineWeightSave = (datas) => {
   return $post("/api/admin/obe/course_weight/save", datas);
   return $post("/api/admin/obe/course_weight/save", datas);
 };
 };
 
 
+// 统计相关页面 =====================>
 // 毕业要求达成度统计 ------------------->
 // 毕业要求达成度统计 ------------------->
 export const requirementStatisticsListPage = (datas) => {
 export const requirementStatisticsListPage = (datas) => {
   return $postParam("/api/admin/obe/course_outline/page", datas);
   return $postParam("/api/admin/obe/course_outline/page", datas);
@@ -184,3 +190,33 @@ export const requirementStatisticsRadar = (id) => {
 export const requirementStatisticsDetail = (id) => {
 export const requirementStatisticsDetail = (id) => {
   return $postParam("/api/admin/obe/course_outline/delete", { id });
   return $postParam("/api/admin/obe/course_outline/delete", { id });
 };
 };
+
+// 课程目标达成度统计 ------------------->
+export const targetStatisticsListPage = (datas) => {
+  return $postParam("/api/admin/course/degree/report/list", datas);
+};
+export const targetStatisticsDetail = (datas) => {
+  return $postParam("/api/admin/course/degree/report/view", datas);
+};
+export const targetStatisticsSave = (datas) => {
+  return $post("/api/admin/course/degree/report/save", datas);
+};
+export const targetStatisticsReport = (datas) => {
+  return $postParam("/api/admin/course/degree/report/export", datas, {
+    responseType: "blob",
+  });
+};
+// 报告数据发生变化
+export const targetStatisticsChangeCheck = (datas) => {
+  return $postParam("/api/admin/course/degree/report/change", datas);
+};
+// 学生毕业要求达成度 ------------------->
+export const studentTargetListPage = (datas) => {
+  return $postParam("/api/admin/course/degree/report/list", datas);
+};
+export const studentTargetDetail = (datas) => {
+  return $postParam("/api/admin/course/degree/report/view", datas);
+};
+export const studentSemesterTargetList = (datas) => {
+  return $postParam("/api/admin/course/degree/report/view", datas);
+};

+ 31 - 14
src/modules/target/components/course-outline/ModifyCourseOutline.vue

@@ -23,12 +23,22 @@
         ></el-input>
         ></el-input>
       </el-form-item>
       </el-form-item>
       <el-form-item prop="cultureProgramId" label="所属培养方案:">
       <el-form-item prop="cultureProgramId" label="所属培养方案:">
-        <training-plan-select
+        <el-select
           v-model="modalForm.cultureProgramId"
           v-model="modalForm.cultureProgramId"
-          placeholder="培养方案"
+          placeholder="请选择"
+          filterable
+          clearable
           class="width-full"
           class="width-full"
           @change="trainingPlanChange"
           @change="trainingPlanChange"
-        ></training-plan-select>
+        >
+          <el-option
+            v-for="item in trainingPlans"
+            :key="item.id"
+            :value="item.id"
+            :label="item.name"
+          >
+          </el-option>
+        </el-select>
       </el-form-item>
       </el-form-item>
       <el-form-item prop="semesterId" label="修读学期:">
       <el-form-item prop="semesterId" label="修读学期:">
         <semester-select
         <semester-select
@@ -97,8 +107,11 @@
 </template>
 </template>
 
 
 <script>
 <script>
-import { updateCourseOutline } from "../../api";
-import { conditionTrainingPlanCourseList } from "../../../../modules/base/api";
+import {
+  updateCourseOutline,
+  selectableTrainingPlanList,
+  selectableTrainingPlanCourseList,
+} from "../../api";
 
 
 import { EVALUATION_MODE, COURSE_TYPE } from "@/constants/enumerate";
 import { EVALUATION_MODE, COURSE_TYPE } from "@/constants/enumerate";
 
 
@@ -109,7 +122,6 @@ const initModalForm = {
   evaluationMode: "",
   evaluationMode: "",
   courseType: "",
   courseType: "",
   cultureProgramId: "",
   cultureProgramId: "",
-  professionalId: "",
   semesterId: "",
   semesterId: "",
 };
 };
 
 
@@ -131,6 +143,7 @@ export default {
       COURSE_TYPE,
       COURSE_TYPE,
       modalForm: { ...initModalForm },
       modalForm: { ...initModalForm },
       courses: [],
       courses: [],
+      trainingPlans: [],
       rules: {
       rules: {
         courseOutlineName: [
         courseOutlineName: [
           { required: true, message: "请输入课程大纲名称", trigger: "change" },
           { required: true, message: "请输入课程大纲名称", trigger: "change" },
@@ -170,6 +183,7 @@ export default {
     visibleChange() {
     visibleChange() {
       this.modalForm = this.$objAssign(initModalForm, this.instance);
       this.modalForm = this.$objAssign(initModalForm, this.instance);
       this.modalForm.courseOutlineName = this.instance.outlineName || "";
       this.modalForm.courseOutlineName = this.instance.outlineName || "";
+      this.getTrainingPlans();
       this.getCourses();
       this.getCourses();
 
 
       this.$nextTick(() => {
       this.$nextTick(() => {
@@ -182,18 +196,21 @@ export default {
     open() {
     open() {
       this.modalIsShow = true;
       this.modalIsShow = true;
     },
     },
+    async getTrainingPlans() {
+      this.trainingPlans = [];
+      const res = await selectableTrainingPlanList();
+      this.trainingPlans = res || [];
+    },
     async getCourses() {
     async getCourses() {
       this.courses = [];
       this.courses = [];
-      if (!this.modalForm.professionalId || !this.modalForm.cultureProgramId)
-        return;
-      const res = await conditionTrainingPlanCourseList({
-        professionalId: this.modalForm.professionalId,
-        cultureProgramId: this.modalForm.cultureProgramId,
-      });
+      if (!this.modalForm.cultureProgramId) return;
+      const res = await selectableTrainingPlanCourseList(
+        this.modalForm.cultureProgramId
+      );
       this.courses = res || [];
       this.courses = res || [];
     },
     },
-    trainingPlanChange(val) {
-      this.modalForm.professionalId = val?.professionalId;
+    trainingPlanChange() {
+      this.modalForm.courseId = "";
       this.getCourses();
       this.getCourses();
     },
     },
     async submit() {
     async submit() {

+ 41 - 2
src/modules/target/components/requirement-statistics/RequirementStatisticsRadar.vue

@@ -2,7 +2,9 @@
   <div class="part-box part-box-pad">
   <div class="part-box part-box-pad">
     <el-row type="flex" :gutter="20">
     <el-row type="flex" :gutter="20">
       <el-col :span="12">
       <el-col :span="12">
-        <v-chart v-if="chartOption" :option="chartOption"></v-chart>
+        <div class="chart-box" style="height: 500px">
+          <v-chart v-if="chartOption" :option="chartOption"></v-chart>
+        </div>
       </el-col>
       </el-col>
       <el-col :span="12">
       <el-col :span="12">
         <el-table :data="dataList">
         <el-table :data="dataList">
@@ -73,7 +75,44 @@ export default {
       this.dataList = res || [];
       this.dataList = res || [];
       this.updateChartOption();
       this.updateChartOption();
     },
     },
-    updateChartOption() {},
+    updateChartOption() {
+      const option = {
+        color: ["#556dff", "#f59a23"],
+        legend: {
+          top: 0,
+          data: ["预期值", "实际值"],
+          itemWidth: 12,
+          itemHeight: 4,
+          itemGap: 22,
+          left: 40,
+        },
+        radar: {
+          shape: "circle",
+          indicator: this.dataList.map((item) => {
+            return {
+              name: item.name,
+              max: 1,
+            };
+          }),
+        },
+        series: [
+          {
+            type: "radar",
+            data: [
+              {
+                value: this.dataList.map((item) => item.predict),
+                name: "预期值",
+              },
+              {
+                value: this.dataList.map((item) => item.val),
+                name: "实际值",
+              },
+            ],
+          },
+        ],
+      };
+      return option;
+    },
   },
   },
 };
 };
 </script>
 </script>

+ 192 - 0
src/modules/target/components/student-target/DetailStudentTarget.vue

@@ -0,0 +1,192 @@
+<template>
+  <el-dialog
+    class="page-dialog"
+    :visible.sync="modalIsShow"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    fullscreen
+    @close="closeHandle"
+  >
+    <div slot="title">{{ rowData.name }}毕业要求达成情况</div>
+
+    <div class="part-box part-box-pad">
+      <div class="part-title">
+        <h2>个人与本院对比情况</h2>
+        <div class="chart-box" style="height: 400px">
+          <v-chart v-if="chartOption" :option="chartOption"></v-chart>
+        </div>
+      </div>
+    </div>
+
+    <div class="part-box part-box-pad">
+      <div class="part-title">
+        <h2>个人毕业要求达成度情况</h2>
+      </div>
+
+      <el-form ref="FilterForm" label-position="left" label-width="85px" inline>
+        <el-form-item label-width="0px">
+          <semester-select v-model="filter.semesterId"></semester-select>
+        </el-form-item>
+        <el-form-item label-width="0px">
+          <el-button type="primary" @click="search">查询</el-button>
+        </el-form-item>
+      </el-form>
+
+      <el-table ref="TableList" :data="dataList">
+        <el-table-column prop="name" label="课程名称"> </el-table-column>
+        <el-table-column prop="name" label="所属学期"> </el-table-column>
+        <el-table-column prop="name" label="期末成绩"> </el-table-column>
+        <el-table-column
+          v-for="(column, cindex) in columns"
+          :key="cindex"
+          :label="column"
+        >
+          <template slot-scope="scope">
+            {{ scope.row.requirements[cindex] }}
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <div slot="footer"></div>
+  </el-dialog>
+</template>
+
+<script>
+import { studentTargetDetail, studentSemesterTargetList } from "../../api";
+
+export default {
+  name: "detail-requirement-statistics",
+  props: {
+    rowData: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      dataList: [],
+      chartOption: null,
+      filter: { semesterId: "" },
+      columns: [],
+      tableData: [],
+    };
+  },
+  methods: {
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async getDetail() {
+      const res = await studentTargetDetail({
+        cultureProgramId: this.rowData.id,
+      });
+      this.dataList = res || [];
+      this.updateChartOption();
+    },
+    updateChartOption() {
+      const options = {
+        color: ["#556dff", "#f59a23"],
+        grid: {
+          left: 40,
+          top: 40,
+          right: 80,
+          bottom: 50,
+          containLabel: true,
+        },
+        legend: {
+          top: 0,
+          data: ["个人达成情况", "专业达成情况"],
+          itemWidth: 12,
+          itemHeight: 4,
+          itemGap: 22,
+          left: 40,
+        },
+        xAxis: {
+          type: "category",
+          name: "课程目标",
+          nameTextStyle: {
+            color: "#363D59",
+          },
+          data: this.courseTargetList.map((item) => item.targetName),
+          axisLabel: {
+            color: "#6F7482",
+            interval: 0,
+            fontSize: 12,
+            margin: 12,
+          },
+          axisLine: {
+            show: true,
+            lineStyle: {
+              color: "#EFF0F5",
+            },
+          },
+          splitLine: {
+            show: false,
+          },
+          axisTick: {
+            show: false,
+          },
+          axisPointer: {
+            type: "shadow",
+          },
+        },
+        yAxis: {
+          type: "value",
+          name: "达成值",
+          min: 0,
+          max: 1,
+          interval: 0.1,
+          nameTextStyle: {
+            color: "#363D59",
+          },
+          axisLabel: {
+            color: "#6F7482",
+          },
+          axisLine: {
+            lineStyle: {
+              color: "#EFF0F5",
+            },
+          },
+          splitLine: {
+            lineStyle: {
+              color: "#EFF0F5",
+            },
+          },
+        },
+        series: [
+          {
+            name: "个人达成情况",
+            type: "bar",
+            barWidth: 20,
+            data: this.courseTargetList.map((item) => item.evaluationValue),
+          },
+          {
+            name: "专业达成情况",
+            type: "bar",
+            barWidth: 20,
+            data: this.courseTargetList.map((item) => item.evaluationValue),
+          },
+        ],
+      };
+      return options;
+    },
+    async search() {
+      if (!this.filter.semesterId) return;
+
+      const res = await studentSemesterTargetList(this.filter);
+      this.tableData = res || [];
+
+      this.columns = !this.tableData[0]
+        ? []
+        : this.tableData[0].requirements.map((item) => item.name);
+    },
+  },
+};
+</script>

+ 899 - 0
src/modules/target/components/target-statistics/DetailTargetStatistics.vue

@@ -0,0 +1,899 @@
+<template>
+  <el-dialog
+    class="page-dialog"
+    :visible.sync="modalIsShow"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    :show-close="false"
+    append-to-body
+    fullscreen
+    @opened="initData"
+  >
+    <div slot="title" class="box-justify">
+      <div>
+        <span>{{ course.courseName }}({{ course.courseCode }})</span>
+      </div>
+      <div>
+        <!-- <el-button type="primary" :loading="downloading" @click="toSave"
+          >保存报告</el-button
+        > -->
+        <el-button type="primary" :loading="downloading" @click="toExport"
+          >导出报告</el-button
+        >
+        <el-button type="danger" @click="cancel">退出</el-button>
+      </div>
+    </div>
+    <div v-if="modalIsShow">
+      <!-- base info -->
+      <div class="page-head">
+        <h2>
+          <span>课程基本情况</span>
+        </h2>
+      </div>
+      <div class="part-box part-box-pad">
+        <table class="table table-tiny">
+          <colgroup>
+            <col width="100" />
+            <col />
+            <col width="100" />
+            <col />
+            <col width="100" />
+            <col />
+            <col width="100" />
+            <col />
+          </colgroup>
+          <tr>
+            <td class="td-bg">课程编码</td>
+            <td>{{ commonInfo.courseCode }}</td>
+            <td class="td-bg">课程名称</td>
+            <td>{{ commonInfo.courseName }}</td>
+            <td class="td-bg">课程类别</td>
+            <td>
+              <el-input
+                v-model="courseBasicInfo.courseType"
+                placeholder="请填写课程类别"
+                size="small"
+                :maxlength="30"
+              ></el-input>
+            </td>
+            <td class="td-bg">学分</td>
+            <td>
+              <el-input-number
+                v-model="courseBasicInfo.credit"
+                placeholder="请录入学分"
+                size="small"
+                :min="0"
+                :max="999"
+                :controls="false"
+                style="width: 100px"
+              ></el-input-number>
+            </td>
+          </tr>
+          <tr>
+            <td class="td-bg">考核方式</td>
+            <td>
+              <el-input
+                v-model="courseBasicInfo.evaluationMode"
+                placeholder="请填写考核方式"
+                size="small"
+                :maxlength="30"
+              ></el-input>
+            </td>
+            <td class="td-bg">开课时间</td>
+            <td>{{ courseBasicInfo.openTime }}</td>
+            <td class="td-bg">授课对象</td>
+            <td>{{ courseBasicInfo.teachingObject }}</td>
+            <td class="td-bg">学时</td>
+            <td>
+              <el-input-number
+                v-model="courseBasicInfo.period"
+                placeholder="请录入学时"
+                :min="0"
+                :max="999"
+                :controls="false"
+                size="small"
+                style="width: 100px"
+              ></el-input-number>
+            </td>
+          </tr>
+          <tr>
+            <td class="td-bg">选课人数</td>
+            <td>
+              <el-input-number
+                v-model="courseBasicInfo.selectionCount"
+                placeholder="请录入选课人数"
+                :min="0"
+                :max="999"
+                :controls="false"
+                size="small"
+                style="width: 125px"
+              ></el-input-number>
+            </td>
+            <td class="td-bg">参评人数</td>
+            <td>{{ courseBasicInfo.participantCount }}</td>
+            <td class="td-bg">课程目标达成度期望值</td>
+            <td colspan="3">
+              <el-select
+                v-model="courseBasicInfo.courseDegree"
+                size="small"
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="item in expectancyList"
+                  :key="item"
+                  :value="item"
+                  :label="item"
+                ></el-option>
+              </el-select>
+            </td>
+          </tr>
+          <tr>
+            <td class="td-bg">任课老师</td>
+            <td>{{ courseBasicInfo.teacher }}</td>
+            <td class="td-bg">评价负责人</td>
+            <td>
+              <el-input
+                v-model="courseBasicInfo.director"
+                placeholder="请填写评价负责人姓名"
+                size="small"
+                :maxlength="30"
+              ></el-input>
+            </td>
+            <td class="td-bg">评价参与人</td>
+            <td colspan="3">
+              <el-input
+                v-model="courseBasicInfo.participant"
+                placeholder="请填写评价人姓名"
+                size="small"
+                :maxlength="30"
+              ></el-input>
+            </td>
+          </tr>
+        </table>
+      </div>
+
+      <div class="page-head">
+        <h2>
+          <span>课程目标考核分布</span>
+          <el-popover
+            popper-class="el-popper-dark"
+            placement="right-start"
+            width="500"
+            trigger="hover"
+          >
+            <i class="el-icon-question" slot="reference"></i>
+            <div>
+              <p>
+                课程目标分布会自动按题库系统里试卷下试题标记的属性内容进行显示
+              </p>
+            </div>
+          </el-popover>
+        </h2>
+      </div>
+      <div class="part-box part-box-pad">
+        <el-table :data="questionInfoTable">
+          <el-table-column
+            label="试题号"
+            prop="name"
+            width="140"
+            align="center"
+            fixed="left"
+          ></el-table-column>
+          <el-table-column
+            label="合计"
+            prop="totalScore"
+            width="100"
+            align="center"
+            fixed="left"
+          ></el-table-column>
+          <el-table-column
+            v-for="struct in paperStructs"
+            :key="struct.mainNumber"
+            :label="struct.mainNumber"
+            align="center"
+          >
+            <el-table-column
+              v-for="subNumber in struct.subNumbers"
+              :key="subNumber"
+              :label="subNumber"
+              :prop="`${struct.mainNumber}_${subNumber}`"
+              align="center"
+            >
+            </el-table-column>
+          </el-table-column>
+        </el-table>
+        <div class="chart-box" style="height: 300px; margin-top: 20px">
+          <v-chart
+            v-if="questionInfoChartOption"
+            :option="questionInfoChartOption"
+          ></v-chart>
+        </div>
+      </div>
+
+      <div class="page-head">
+        <h2>
+          <span>课程目标达成评价结果</span>
+          <el-popover
+            popper-class="el-popper-dark"
+            placement="right-start"
+            width="500"
+            trigger="hover"
+          >
+            <i class="el-icon-question" slot="reference"></i>
+            <div>
+              <p>1.评价依据:填写通过考核什么内容实现课程目标的评价;</p>
+              <p>2.目标分值:对应考核环节的满分;</p>
+              <p>
+                3.权重:对应考核环节在对应的课程目标中的权重,目标下各项权重相加等于1;
+              </p>
+              <p>4.实际平均分:为参与评价的学生在该环节的平均分;</p>
+              <p>5.目标达成评价值:∑(实际平均分/目标分值*权重);</p>
+              <p>6.整体课程目标达成评价值:为课程分目标达成评价值的最小值。</p>
+            </div>
+          </el-popover>
+        </h2>
+      </div>
+      <div class="part-box part-box-pad">
+        <h4 class="part-title">课程考核成绩评价结果</h4>
+
+        <table class="table">
+          <tr class="td-bg">
+            <th>课程目标</th>
+            <th>评价依据</th>
+            <th>评价环节</th>
+            <th>权重</th>
+            <th>目标分值</th>
+            <th>实际平均分</th>
+            <th>目标达成评价值</th>
+          </tr>
+
+          <template v-for="item in courseTargetList">
+            <tr
+              v-for="(evaluation, eindex) in item.evaluationList"
+              :key="`${item.targetId}-${eindex}`"
+            >
+              <td
+                v-if="!eindex"
+                :rowspan="item.evaluationList.length"
+                style="width: 240px"
+              >
+                {{ item.targetName }}
+              </td>
+              <td v-if="!eindex" :rowspan="item.evaluationList.length">
+                <p
+                  v-for="edesc in item.graduationRequirementPoint.split('\n')"
+                  :key="edesc"
+                >
+                  {{ edesc }}
+                </p>
+              </td>
+              <td style="width: 140px">{{ evaluation.evaluation }}</td>
+
+              <td
+                v-if="!eindex"
+                style="width: 80px"
+                :rowspan="item.evaluationList.length - 1"
+              >
+                {{ item.normalTargetWeight }}
+              </td>
+              <td
+                v-else-if="eindex === item.evaluationList.length - 1"
+                style="width: 80px"
+              >
+                {{ evaluation.targetWeight }}
+              </td>
+
+              <td style="width: 100px">{{ evaluation.targetScore }}</td>
+              <td style="width: 100px">{{ evaluation.targetAvgScore }}</td>
+              <td
+                v-if="!eindex"
+                :rowspan="item.evaluationList.length"
+                style="width: 140px"
+              >
+                {{ item.evaluationValue }}
+              </td>
+            </tr>
+          </template>
+          <tr>
+            <td colspan="6">课程总目标</td>
+            <td>{{ courseTargetValue }}</td>
+          </tr>
+        </table>
+
+        <div class="chart-box" style="height: 400px; margin-top: 20px">
+          <v-chart
+            v-if="courseTargetListChartOption"
+            :option="courseTargetListChartOption"
+          ></v-chart>
+        </div>
+
+        <h4 class="part-title">课程考核成绩评价明细结果</h4>
+
+        <el-table
+          :data="studentScoreTable"
+          :span-method="studentScoreSpanMethod"
+        >
+          <el-table-column
+            label="姓名"
+            prop="name"
+            min-width="120"
+            align="center"
+            fixed="left"
+          ></el-table-column>
+          <el-table-column
+            label="学号"
+            prop="studentCode"
+            width="100"
+            align="center"
+            fixed="left"
+          ></el-table-column>
+          <el-table-column
+            v-for="(target, tindex) in courseTargets"
+            :key="tindex"
+            :label="target.targetName"
+            align="center"
+          >
+            <el-table-column width="140" align="center">
+              <template slot="header">
+                期末考试({{ target.finalWeight | percentFilter }})
+              </template>
+              <el-table-column
+                label="期末成绩"
+                :prop="`${target.targetId}-final`"
+                width="140"
+                align="center"
+              >
+              </el-table-column>
+            </el-table-column>
+            <el-table-column min-width="120" align="center">
+              <template slot="header">
+                平时考试({{ target.usualWeight | percentFilter }})
+              </template>
+              <el-table-column
+                v-for="work in target.usualWorks"
+                :key="`${target.targetId}${work}`"
+                :label="work"
+                :prop="`${target.targetId}-usual-${work}`"
+                min-width="100"
+                align="center"
+              >
+              </el-table-column>
+            </el-table-column>
+          </el-table-column>
+          <el-table-column
+            label="学生成绩"
+            prop="score"
+            align="center"
+            width="100"
+          ></el-table-column>
+        </el-table>
+      </div>
+    </div>
+
+    <div slot="footer"></div>
+  </el-dialog>
+</template>
+
+<script>
+// import { reportData } from "./data";
+import {
+  targetStatisticsDetail,
+  targetStatisticsReport,
+  targetStatisticsSave,
+  targetStatisticsChangeCheck,
+} from "../api";
+import { downloadByApi } from "@/plugins/download";
+import { calcSum } from "@/plugins/utils";
+
+export default {
+  name: "detail-target-statistics",
+  props: {
+    course: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  filters: {
+    percentFilter(val) {
+      const num = val || 0;
+      const perc = num % 10 ? 2 : 1;
+      return ((num || 0) / 100).toFixed(perc);
+    },
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      expectancyList: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
+      commonInfo: {},
+      courseBasicInfo: {},
+      paperStructs: [],
+      questionInfoTable: [],
+      courseTargetList: [],
+      courseTargetValue: 0.67,
+      questionInfoChartOption: null,
+      courseTargetListChartOption: null,
+
+      courseTargets: [],
+      targetColumnCounts: [],
+      studentScoreTable: [],
+      downloading: false,
+    };
+  },
+  methods: {
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    resetData() {
+      this.commonInfo = {};
+      this.courseBasicInfo = {};
+      this.paperStructs = [];
+      this.questionInfoTable = [];
+      this.courseTargetList = [];
+      this.courseTargetValue = 0.67;
+      this.questionInfoChartOption = null;
+      this.courseTargetListChartOption = null;
+      this.courseTargets = [];
+      this.targetColumnCounts = [];
+      this.studentScoreTable = [];
+      this.downloading = false;
+    },
+    buildData(data) {
+      this.commonInfo = data.commonInfo;
+      this.courseBasicInfo = data.courseBasicInfo;
+
+      this.courseTargetValue =
+        data.courseEvaluationResultInfo.targetEvaluationSumValue;
+      this.courseTargetList = data.courseEvaluationResultInfo.targetList.map(
+        (target) => {
+          target.normalTargetWeight = calcSum(
+            target.evaluationList.slice(0, -1).map((elem) => elem.targetWeight)
+          );
+          return target;
+        }
+      );
+
+      const {
+        courseEvaluationSpreadInfo: { questionInfo, scoreList },
+      } = data;
+      this.parsePaperStructs(questionInfo);
+      this.parseQuestionInfoTable(questionInfo);
+      this.questionInfoChartOption = this.getQuestionInfoChartOption(scoreList);
+      this.courseTargetListChartOption = this.getCourseTargetListChartOption();
+
+      let examStudentList =
+        data.courseEvaluationResultDetailInfo.examStudentList;
+      examStudentList.pop();
+      examStudentList.splice(examStudentList.length - 2, 1);
+
+      this.parseCourseTargets(examStudentList);
+      this.parseStudentScoreTable(examStudentList);
+    },
+    async initData() {
+      this.resetData();
+
+      await this.checkChange();
+      const data = await targetStatisticsDetail({
+        examId: this.course.examId,
+        courseCode: this.course.courseCode,
+        teachCourseId: this.course.teachCourseId,
+      });
+      this.buildData(data);
+    },
+    async checkChange() {
+      const res = await targetStatisticsChangeCheck({
+        examId: this.course.examId,
+        courseCode: this.course.courseCode,
+        teachCourseId: this.course.teachCourseId,
+        report: true,
+      });
+
+      if (res.courseTargetChange) {
+        this.$notify.warning("课程目标与已保存不一致,请重新设置权重!");
+      }
+      if (res.evaluationChange) {
+        this.$notify.warning(
+          "评价方式与已保存不一致,请重新设置权重及导入新的平时成绩!"
+        );
+      }
+      if (res.targetScoreChange) {
+        this.$notify.warning(res.targetScoreChangeStr);
+      }
+    },
+    parsePaperStructs(questionInfo) {
+      if (!questionInfo || !questionInfo.length) return;
+
+      const structMap = {};
+      questionInfo.forEach((item) => {
+        if (!structMap[item.mainNumber]) {
+          structMap[item.mainNumber] = [];
+        }
+        structMap[item.mainNumber].push(item.subNumber + "");
+      });
+      this.paperStructs = Object.keys(structMap).map((mainNumber) => {
+        return {
+          mainNumber,
+          subNumbers: structMap[mainNumber],
+        };
+      });
+    },
+    parseQuestionInfoTable(questionInfo) {
+      const targetMap = {};
+      const tData = {
+        name: "目标分值",
+        totalScore: calcSum(questionInfo.map((item) => item.score || 0)),
+      };
+      questionInfo.forEach((question) => {
+        const qno = `${question.mainNumber}_${question.subNumber}`;
+        tData[qno] = question.score;
+
+        question.targetList.forEach((target) => {
+          if (!targetMap[target.targetId]) {
+            targetMap[target.targetId] = {
+              name: target.targetName,
+              totalScore: 0,
+            };
+          }
+
+          if (target.dimensionList.length) {
+            targetMap[target.targetId][qno] = question.score;
+            targetMap[target.targetId].totalScore += question.score;
+          } else {
+            targetMap[target.targetId][qno] = "";
+          }
+        });
+      });
+
+      this.questionInfoTable = [tData, ...Object.values(targetMap)];
+    },
+    getQuestionInfoChartOption(scoreList) {
+      const scoreRange = scoreList.scoreRange.map((item) => {
+        return {
+          name: `${item.minScore}~${item.maxScore}`,
+          value: item.rate,
+        };
+      });
+      scoreRange.unshift({
+        name: "不及格",
+        value: scoreList.failRate,
+      });
+
+      let options = {
+        color: ["#556dff", "#3A5AE5"],
+        title: {
+          text: "成绩分布图",
+          left: "center",
+        },
+        grid: {
+          left: 40,
+          top: 60,
+          right: 80,
+          bottom: 40,
+          containLabel: true,
+        },
+        xAxis: {
+          type: "category",
+          name: "成绩(分)",
+          nameTextStyle: {
+            color: "#363D59",
+          },
+          data: scoreRange.map((item) => item.name),
+          axisLabel: {
+            color: "#6F7482",
+            interval: 0,
+            fontSize: 12,
+            margin: 12,
+          },
+          axisLine: {
+            show: true,
+            lineStyle: {
+              color: "#EFF0F5",
+            },
+          },
+          splitLine: {
+            show: false,
+          },
+          axisTick: {
+            show: false,
+          },
+          axisPointer: {
+            type: "shadow",
+          },
+        },
+        yAxis: {
+          type: "value",
+          name: "百分比(%)",
+          nameTextStyle: {
+            color: "#363D59",
+          },
+          axisLabel: {
+            color: "#6F7482",
+          },
+          axisLine: {
+            lineStyle: {
+              color: "#EFF0F5",
+            },
+          },
+          splitLine: {
+            lineStyle: {
+              color: "#EFF0F5",
+            },
+          },
+        },
+        series: [
+          {
+            name: "占比",
+            type: "bar",
+            barWidth: 60,
+            data: scoreRange.map((item) => item.value),
+            label: {
+              show: true,
+              formatter: "{c}%",
+              position: "top",
+            },
+          },
+        ],
+      };
+      return options;
+    },
+    getCourseTargetListChartOption() {
+      let options = {
+        color: ["#556dff", "#f59a23"],
+        title: {
+          text: "课程目标达成评价值",
+          left: "center",
+        },
+        grid: {
+          left: 40,
+          top: 40,
+          right: 80,
+          bottom: 30,
+          containLabel: true,
+        },
+        legend: {
+          top: 0,
+          data: ["期望值", "达成值"],
+          itemWidth: 12,
+          itemHeight: 4,
+          itemGap: 22,
+          right: 40,
+        },
+        xAxis: {
+          type: "category",
+          name: "课程目标",
+          nameTextStyle: {
+            color: "#363D59",
+          },
+          data: this.courseTargetList.map((item) => item.targetName),
+          axisLabel: {
+            color: "#6F7482",
+            interval: 0,
+            fontSize: 12,
+            margin: 12,
+          },
+          axisLine: {
+            show: true,
+            lineStyle: {
+              color: "#EFF0F5",
+            },
+          },
+          splitLine: {
+            show: false,
+          },
+          axisTick: {
+            show: false,
+          },
+          axisPointer: {
+            type: "shadow",
+          },
+        },
+        yAxis: {
+          type: "value",
+          name: "期望值",
+          min: 0,
+          max: 1,
+          interval: 0.1,
+          nameTextStyle: {
+            color: "#363D59",
+          },
+          axisLabel: {
+            color: "#6F7482",
+          },
+          axisLine: {
+            lineStyle: {
+              color: "#EFF0F5",
+            },
+          },
+          splitLine: {
+            lineStyle: {
+              color: "#EFF0F5",
+            },
+          },
+        },
+        series: [
+          {
+            name: "达成值",
+            type: "bar",
+            barWidth: 60,
+            data: this.courseTargetList.map((item) => item.evaluationValue),
+            label: {
+              show: true,
+              position: "top",
+            },
+          },
+          {
+            name: "期望值",
+            type: "line",
+            symbol: "none",
+            data: this.courseTargetList.map(
+              (item) => this.courseBasicInfo.courseDegree || 0
+            ),
+          },
+        ],
+      };
+      return options;
+    },
+    parseCourseTargets(examStudentList) {
+      if (!examStudentList || !examStudentList.length) return;
+
+      const targetList = examStudentList[0].targetList;
+      const targetVals = {};
+      this.courseTargetList.forEach((target) => {
+        targetVals[target.targetId] = target.evaluationValue;
+      });
+
+      let tColumnCounts = [];
+      this.courseTargets = targetList.map((target) => {
+        const ntarget = {
+          targetId: target.targetId,
+          targetName: target.targetName,
+          finalWeight: target.finalScore.targetWeight,
+          usualWeight: target.usualScore.targetWeight,
+          evaluationValue: targetVals[target.targetId],
+        };
+        ntarget.finalDimensions = ["期末成绩"];
+        ntarget.usualWorks = target.usualScore.scoreList.map(
+          (item) => item.evaluation
+        );
+        tColumnCounts.push(
+          ntarget.finalDimensions.length + ntarget.usualWorks.length
+        );
+        return ntarget;
+      });
+
+      let tCount = 0;
+      this.targetColumnCounts = tColumnCounts.map((item) => {
+        tCount += item;
+        return tCount;
+      });
+    },
+    parseStudentScoreTable(examStudentList) {
+      const lastIndex = examStudentList.length - 1;
+      const studentScoreTable = examStudentList.map((student, sindex) => {
+        const nitem = {
+          name: student.name,
+          studentCode: student.studentCode,
+          score: student.score,
+        };
+
+        const finalScoreKey =
+          lastIndex === sindex ? "matrixAvgScore" : "targetScoreSum";
+        const workScoreKey = lastIndex === sindex ? "matrixAvgScore" : "score";
+
+        student.targetList.forEach((target, index) => {
+          nitem[`${target.targetId}-final`] = target.finalScore[finalScoreKey];
+
+          target.usualScore.scoreList.forEach((work) => {
+            nitem[`${target.targetId}-usual-${work.evaluation}`] =
+              work[workScoreKey];
+          });
+        });
+        return nitem;
+      });
+
+      const targetData = {
+        name: "课程目标达成度",
+        score: "",
+      };
+      this.courseTargets.forEach((target) => {
+        targetData[`${target.targetId}-final`] = target.evaluationValue;
+      });
+      studentScoreTable.push(targetData);
+
+      const fTarget = this.courseTargets[0];
+      studentScoreTable.push({
+        name: "课程达成度",
+        [`${fTarget.targetId}-final`]: this.courseTargetValue,
+      });
+      this.studentScoreTable = studentScoreTable;
+    },
+    studentScoreSpanMethod({ row, column, rowIndex, columnIndex }) {
+      const lineCount = this.studentScoreTable.length - 1;
+      const maxTargetColumnCount = this.targetColumnCounts.slice(-1)[0];
+      if (rowIndex === lineCount) {
+        if (columnIndex === 0) {
+          return [1, 2];
+        } else if (columnIndex === 2) {
+          return [1, maxTargetColumnCount + 1];
+        } else {
+          return [0, 0];
+        }
+      }
+      if (rowIndex === lineCount - 1) {
+        const colsMap = {};
+        const targetColumns = [0, ...this.targetColumnCounts.slice(0, -1)];
+        targetColumns.map((item, index) => {
+          colsMap[item] = this.targetColumnCounts[index] - item;
+        });
+        if (columnIndex === 0) {
+          return [1, 2];
+        } else {
+          if (targetColumns.includes(columnIndex - 2)) {
+            return [1, colsMap[columnIndex - 2]];
+          } else if (columnIndex < maxTargetColumnCount + 2) {
+            return [0, 0];
+          } else {
+            return [1, 1];
+          }
+        }
+      }
+      if (rowIndex === lineCount - 2) {
+        if (columnIndex === 0) {
+          return [1, 2];
+        } else if (columnIndex === 1) {
+          return [0, 0];
+        } else {
+          return [1, 1];
+        }
+      }
+    },
+    async toSave() {
+      if (this.downloading) return;
+      this.downloading = true;
+
+      const res = await targetStatisticsSave({
+        examId: this.course.examId,
+        courseName: this.course.courseName,
+        courseCode: this.course.courseCode,
+        teachCourseId: this.course.teachCourseId,
+        ...this.courseBasicInfo,
+      }).catch(() => {});
+      this.downloading = false;
+
+      if (!res) return;
+      this.$message.success("保存成功!");
+      this.initData();
+    },
+    async toExport() {
+      if (this.downloading) return;
+      this.downloading = true;
+
+      const res = await downloadByApi(() => {
+        const datas = {
+          examId: this.course.examId,
+          courseCode: this.course.courseCode,
+          teachCourseId: this.course.teachCourseId,
+        };
+        return targetStatisticsReport(datas);
+      }).catch((e) => {
+        this.$message.error(e || "下载失败,请重新尝试!");
+      });
+      this.downloading = false;
+
+      if (!res) return;
+      this.$message.success("下载成功!");
+    },
+  },
+};
+</script>
+
+<style scoped>
+.td-bg {
+  color: #8b8fa1;
+  font-weight: 500;
+}
+</style>

+ 122 - 0
src/modules/target/views/StudentTarget.vue

@@ -0,0 +1,122 @@
+<template>
+  <div class="student-target">
+    <div class="part-box part-box-filter part-box-flex">
+      <el-form ref="FilterForm" label-position="left" label-width="85px" inline>
+        <template v-if="checkPrivilege('condition', 'condition')">
+          <secp-select
+            v-model="filter"
+            defaultSelectExam
+            @exam-default="search"
+          ></secp-select>
+        </template>
+        <el-form-item label-width="0px">
+          <el-button
+            v-if="checkPrivilege('button', 'select')"
+            type="primary"
+            @click="search"
+            >查询</el-button
+          >
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="part-box part-box-pad">
+      <el-table ref="TableList" :data="dataList">
+        <el-table-column
+          type="index"
+          label="序号"
+          width="70"
+          :index="indexMethod"
+        ></el-table-column>
+        <el-table-column prop="name" label="姓名"> </el-table-column>
+        <el-table-column prop="code" label="学号"> </el-table-column>
+        <el-table-column prop="college" label="学院"> </el-table-column>
+        <el-table-column prop="professionalName" label="专业">
+        </el-table-column>
+        <el-table-column
+          class-name="action-column"
+          label="操作"
+          width="140"
+          fixed="right"
+        >
+          <template slot-scope="scope">
+            <el-button
+              v-if="checkPrivilege('link', 'View')"
+              class="btn-primary"
+              type="text"
+              @click="toDetail(scope.row)"
+              >查看详情</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="part-page">
+        <el-pagination
+          background
+          layout="total, sizes, prev, pager, next, jumper"
+          :pager-count="5"
+          :current-page="current"
+          :total="total"
+          :page-size="size"
+          @current-change="toPage"
+          @size-change="pageSizeChange"
+        >
+        </el-pagination>
+      </div>
+    </div>
+    <!-- DetailStudentTarget -->
+    <detail-student-target
+      ref="DetailStudentTarget"
+      :row-data="curRow"
+    ></detail-student-target>
+  </div>
+</template>
+
+<script>
+import { studentTargetListPage } from "../api";
+import DetailStudentTarget from "../components/student-target/DetailStudentTarget.vue";
+
+export default {
+  name: "student-target",
+  components: { DetailStudentTarget },
+  data() {
+    return {
+      filter: {
+        semesterId: "",
+        examId: "",
+        courseCode: "",
+      },
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      dataList: [],
+      curRow: {},
+    };
+  },
+  methods: {
+    async getList() {
+      if (!this.checkPrivilege("list", "list")) return;
+
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size,
+      };
+      const data = await studentTargetListPage(datas);
+      this.dataList = data.records;
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    search() {
+      this.toPage(1);
+    },
+    toDetail(row) {
+      this.curRow = row;
+      this.$refs.DetailStudentTarget.open();
+    },
+  },
+};
+</script>

+ 133 - 0
src/modules/target/views/TargetStatistics.vue

@@ -0,0 +1,133 @@
+<template>
+  <div class="target-statistics">
+    <div class="part-box part-box-filter part-box-flex">
+      <el-form ref="FilterForm" label-position="left" label-width="85px" inline>
+        <template v-if="checkPrivilege('condition', 'condition')">
+          <secp-select
+            v-model="filter"
+            defaultSelectExam
+            @exam-default="search"
+          ></secp-select>
+        </template>
+        <el-form-item label-width="0px">
+          <el-button
+            v-if="checkPrivilege('button', 'select')"
+            type="primary"
+            @click="search"
+            >查询</el-button
+          >
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="part-box part-box-pad">
+      <el-table ref="TableList" :data="dataList">
+        <el-table-column
+          type="index"
+          label="序号"
+          width="70"
+          :index="indexMethod"
+        ></el-table-column>
+        <el-table-column label="课程(代码)">
+          <template slot-scope="scope">
+            {{ scope.row.courseName | defaultFieldFilter }}({{
+              scope.row.courseCode | defaultFieldFilter
+            }})
+          </template>
+        </el-table-column>
+        <el-table-column prop="semesterName" label="修读学期">
+        </el-table-column>
+        <el-table-column prop="cultureProgramName" label="所属培养方案">
+        </el-table-column>
+        <el-table-column prop="userName" label="创建人">
+          <span slot-scope="scope">
+            {{ scope.row.userName }}({{ scope.row.userLoginName }})
+          </span>
+        </el-table-column>
+        <el-table-column
+          class-name="action-column"
+          label="操作"
+          width="140"
+          fixed="right"
+        >
+          <template slot-scope="scope">
+            <el-button
+              v-if="checkPrivilege('link', 'View')"
+              class="btn-primary"
+              type="text"
+              @click="toDetail(scope.row)"
+              >查看详情</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="part-page">
+        <el-pagination
+          background
+          layout="total, sizes, prev, pager, next, jumper"
+          :pager-count="5"
+          :current-page="current"
+          :total="total"
+          :page-size="size"
+          @current-change="toPage"
+          @size-change="pageSizeChange"
+        >
+        </el-pagination>
+      </div>
+    </div>
+    <!-- DetailTargetStatistics -->
+    <detail-target-statistics
+      ref="DetailTargetStatistics"
+      :course="curRow"
+    ></detail-target-statistics>
+  </div>
+</template>
+
+<script>
+import { targetStatisticsListPage } from "../api";
+import DetailTargetStatistics from "../components/target-statistics/DetailTargetStatistics.vue";
+
+export default {
+  name: "target-statistics",
+  components: { DetailTargetStatistics },
+  data() {
+    return {
+      filter: {
+        semesterId: "",
+        examId: "",
+        courseCode: "",
+      },
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      dataList: [],
+      curRow: {},
+    };
+  },
+  methods: {
+    async getList() {
+      if (!this.checkPrivilege("list", "list")) return;
+
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size,
+      };
+      const data = await targetStatisticsListPage(datas);
+      this.dataList = data.records;
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    search() {
+      this.toPage(1);
+    },
+    toDetail(row) {
+      this.curRow = row;
+      this.$refs.DetailTargetStatistics.open();
+    },
+  },
+};
+</script>