Browse Source

fix: Zhurui 修改coderExamDetail页面

Leonezhurui 3 năm trước cách đây
mục cha
commit
12927982e9

+ 1 - 0
package.json

@@ -13,6 +13,7 @@
     "babel-plugin-import": "^1.13.3",
     "clipboard": "^2.0.11",
     "core-js": "^3.6.5",
+    "dayjs": "^1.11.7",
     "echarts": "^4.8.0",
     "element-plus": "^1.0.2-beta.71",
     "marked": "^4.2.3",

+ 2 - 2
src/components/PageHeader.vue

@@ -4,9 +4,9 @@
       <slot name="breadcrumb" />
     </div>
     <div class="header-line">
-      <h2 class="title">
+      <div class="title">
         <slot />
-      </h2>
+      </div>
       <div class="actions">
         <slot name="actions" />
       </div>

+ 388 - 154
src/views/ExamPage/ExamDetail.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="exam-container">
-    <el-card>
+    <el-card style="background: none; box-shadow:none;border: none">
       <template #header>
         <page-header>
           <template #actions>
@@ -9,192 +9,270 @@
             </el-button>
             <el-button
                 v-if="isStudentGroup && !exam.joined"
-                type="primary"
                 @click="dialog.join = true"
             >
               参加考试
             </el-button>
             <el-button
                 v-if="(isStudentGroup && exam.joined) || (isTeacherGroup)"
+                style="background-color: #1767C0;border-color:#1767C0;color:white;min-width: 100px;"
                 @click="jumpToCoderOnlineExam"
             >
               在线自测
             </el-button>
-            <el-button
-                v-if="isTeacherGroup || isAdmin || isAssistantGroup"
-                @click="checkStudentGrade"
-            >
-              查看学生成绩
-            </el-button>
+<!--            <el-button-->
+<!--                v-if="isTeacherGroup || isAdmin || isAssistantGroup"-->
+<!--                style="background-color:white;border-color:white;color:#606266;min-width: 125px;box-shadow: #868484 0 2px 5px .1px"-->
+<!--                @click="checkStudentGrade"-->
+<!--            >-->
+<!--              查看学生成绩-->
+<!--            </el-button>-->
             <el-button
                 v-if="isTeacherGroup || isAdmin"
                 disabled
+                style="background-color:white;color:#606266;min-width: 100px;"
                 @click="goToDetect()"
             >
               查看查重结果
             </el-button>
             <el-button
                 v-if="isTeacherGroup || isAdmin"
-                variant="danger"
+                style="background-color:#F46C6C;border-color:#F46C6C;color:white;min-width: 100px;"
                 @click="dialog.delete = true"
             >
               删除考试
             </el-button>
           </template>
-          {{ exam.name }}
+          <span style="font-size: 36px;font-family: 'Inter',sans-serif">{{ exam.name }}</span>
         </page-header>
       </template>
 
-      <paper>
-        <el-descriptions>
-          <el-descriptions-item
-              label="编号:"
-              label-class-name="label-bold"
-          >
-            {{ exam.id }}
-          </el-descriptions-item>
-          <el-descriptions-item
-              label="创建者:"
-              label-class-name="label-bold"
-          >
-            {{ exam.creator ? exam.creator.username : '' }}
-          </el-descriptions-item>
-          <el-descriptions-item
-              label="创建时间:"
-              label-class-name="label-bold"
-          >
-            {{ formatTimestamp(exam.created_at) }}
-          </el-descriptions-item>
-          <el-descriptions-item
-              v-if="(isStudentGroup || isTeacherGroup) && exam.joined"
-              :label="`${isTeacherGroup ? '自测' : ''}成绩`"
-              label-class-name="label-bold"
-          >
-            {{ result.score }}
-          </el-descriptions-item>
-          <el-descriptions-item
-              label="状态:"
-              label-class-name="label-bold"
-          >
-            <el-tag size="mini" :type="statusTagType">
-              {{ statusTagContent }}
-            </el-tag>
-          </el-descriptions-item>
-          <el-descriptions-item
-              label="起止时间:"
-              :span="2"
-              label-class-name="label-bold"
-          >
-            {{ formatTimestamp(exam.startAt) }} 至 {{ formatTimestamp(exam.endAt) }}
-            <a
-                v-if="isTeacherGroup || isAdmin"
-                class="changeable"
-                @click="dialog.update = true"
-            >
-              修改
-            </a>
-          </el-descriptions-item>
-          <el-descriptions-item
-              v-if="(isStudentGroup && exam.joined) || isTeacherGroup"
-              label="试卷地址:"
-              :span="3"
-              label-class-name="label-bold"
+      <el-descriptions>
+        <el-descriptions-item
+            label="编号:"
+            label-class-name="label-bold"
+            min-width="200px"
+        >
+          {{ exam.id }}
+        </el-descriptions-item>
+        <el-descriptions-item
+            label="创建者:"
+            label-class-name="label-bold"
+            min-width="300px"
+        >
+          {{ exam.creator ? exam.creator.username : '' }}
+        </el-descriptions-item>
+        <el-descriptions-item
+            label="状态:"
+            label-class-name="label-bold"
+        >
+          <el-tag size="small" :type="statusTagType" round>
+            {{ statusTagContent }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item
+            v-if="(isStudentGroup || isTeacherGroup) && exam.joined"
+            :label="`${isTeacherGroup ? '自测' : ''}成绩:`"
+            label-class-name="label-bold"
+        >
+          {{ result.score }}
+        </el-descriptions-item>
+        <el-descriptions-item
+            label="创建时间:"
+            label-class-name="label-bold"
+        >
+          {{ formatTimestamp(exam.created_at) }}
+        </el-descriptions-item>
+        <el-descriptions-item
+            label="起止时间:"
+            :span="2"
+            label-class-name="label-bold"
+        >
+          {{ formatTimestamp(exam.startAt) }} 至 {{ formatTimestamp(exam.endAt) }}
+          <a
+              v-if="isTeacherGroup || isAdmin"
+              class="changeable"
+              @click="dialog.update = true"
           >
-          <span id="exam-url">
-          {{ exam.url ? exam.url : '不可用' }}
+            修改
+          </a>
+        </el-descriptions-item>
+        <el-descriptions-item
+            v-if="(isStudentGroup && exam.joined) || isTeacherGroup"
+            label="试卷地址:"
+            :span="3"
+            label-class-name="label-bold"
+        >
+        <span id="exam-url">
+        {{ exam.url ? exam.url : '不可用' }}
         </span>
-            <a
-                v-if="exam.url"
-                id="copy-btn"
-                class="changeable"
-                data-clipboard-target="#exam-url"
-                @click="copyAddress"
-            >
-              复制
-            </a>
-          </el-descriptions-item>
-          <el-descriptions-item
-              v-if="isTeacherGroup || isAdmin"
-              label="是否允许学生查看控制台:"
-              :span="2"
-              label-class-name="label-bold"
+          <a
+              v-if="exam.url"
+              id="copy-btn"
+              class="changeable"
+              data-clipboard-target="#exam-url"
+              @click="copyAddress"
           >
-            <span>{{ isConsoleOutputShownForStu ? "允许" : "不允许" }}</span>
-            <a class="changeable" @click="dialog.console = true">
-              修改
-            </a>
-          </el-descriptions-item>
-        </el-descriptions>
-      </paper>
+            复制
+          </a>
+        </el-descriptions-item>
+        <el-descriptions-item
+            v-if="isTeacherGroup || isAdmin"
+            label="是否允许学生查看控制台:"
+            :span="2"
+            label-class-name="label-bold"
+        >
+          <span>{{ isConsoleOutputShownForStu ? "允许" : "不允许" }}</span>
+          <a class="changeable" @click="dialog.console = true">
+            修改
+          </a>
+        </el-descriptions-item>
+      </el-descriptions>
 
-      <el-tabs
-          class="top-divide exam-detail-tabs"
-          v-model="tabValue"
-      >
-        <el-tab-pane v-if="exam.joined" label="提交记录" name="commits">
-          <el-table v-if="exam.joined" :data="result.commits">
-            <el-table-column prop="id" label="#" width="80" />
-            <el-table-column label="提交时间">
-              <template #default="scope">
-                <span>{{ formatTimestamp(scope.row.created_at) }}</span>
-              </template>
-            </el-table-column>
-            <el-table-column label="状态" width="180">
-              <template #default="scope">
+<!--      题目列表-->
+      <el-card class="son-card" style="background: none; box-shadow:none;border: none; margin-top: 15px;margin-bottom: 30px">
+        <template #header>
+          <span style="color:black;font-size: 24px;font-family: 'Inter',sans-serif">题目列表</span>
+        </template>
+
+        <el-collapse accordion>
+          <el-collapse-item
+              v-for="problem in exam.problems"
+              :key="problem.id"
+              :name="problem.id"
+          >
+            <template #title>
+              <span style="margin-left: 20px;font-size: 16px;">{{ problem.name }}</span>
+              <el-tag size="small" round style="margin-left: 15px">
+                {{ problemTypeToTagContent(problem.type) }}
+              </el-tag>
+            </template>
+            <div style="margin-left: 40px">{{ problem.description }}</div>
+            <div style="margin-left: 30px;margin-top: 10px">
+              <a
+                  v-if="isTeacherGroup || isAdmin"
+                  style="color: #409EFF;cursor: pointer;"
+                  @click="jumpToProblemDetail(problem.id)">
+                预览
+              </a>
+            </div>
+          </el-collapse-item>
+        </el-collapse>
+      </el-card>
+
+<!--      提交记录-->
+      <el-card class="son-card" style="background: none; box-shadow:none;border: none;margin-bottom: 30px">
+        <template #header>
+          <span style="color:black;font-size: 24px;font-family: 'Inter',sans-serif">提交记录</span>
+        </template>
+
+        <el-table v-if="exam.joined" :data="result.commits" style="background: none; box-shadow:none;border: none">
+          <el-table-column prop="id" label="#" width="200" />
+          <el-table-column label="提交时间">
+            <template #default="scope">
+              <span>{{ formatTimestamp(scope.row.created_at) }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="状态" width="200">
+            <template #default="scope">
               <span>
                 {{ scope.row.finished ? '运行结束' : '等待或运行中' }}
               </span>
-              </template>
-            </el-table-column>
-            <el-table-column prop="score" label="成绩" width="120" />
-            <el-table-column label="操作" width="120">
-              <template #default="scope">
-                <el-button
-                    type="primary"
-                    size="mini"
-                    @click="
+            </template>
+          </el-table-column>
+          <el-table-column prop="score" label="成绩" width="150" />
+          <el-table-column label="操作" width="150">
+            <template #default="scope">
+              <el-button
+                  type="primary"
+                  size="mini"
+                  @click="
                     $router.push(
                     {
                       path: `/exam/${$route.params.examId}/commit/${scope.row.id}`
                     })
                 "
-                >
-                  详情
-                </el-button>
+              >
+                详情
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-card>
+
+<!--      结果列表-->
+      <el-card class="son-card" style="background: none; box-shadow:none;border: none;margin-bottom: 30px" v-if="isTeacherGroup || isAdmin || isAssistantGroup">
+        <template #header>
+          <page-header>
+            <template #actions>
+              <el-button
+                  @click="download"
+                  style="background-color: #1767C0;border-color:#1767C0;color:white;min-width: 100px;">
+                下载成绩单
+              </el-button>
+              <el-button
+                  @click="downloadDetailed"
+                  style="background-color: #1767C0;border-color:#1767C0;color:white;min-width: 100px;">
+                下载详细成绩单
+              </el-button>
+            </template>
+            <span style="color:black;font-size: 24px;font-family: 'Inter',sans-serif;font-weight: normal;margin-top: 0;margin-bottom: 0">结果列表</span>
+          </page-header>
+        </template>
+        <div style="margin-left: 20px">
+          <echarts style="width: 100%" :options="chartOptions" />
+          <el-descriptions>
+            <el-descriptions-item label="总计人数:" label-class-name="label-bold">
+              {{ totalStudents }}
+            </el-descriptions-item>
+            <el-descriptions-item label="总计平均成绩:" label-class-name="label-bold">
+              {{ totalAverage }}
+            </el-descriptions-item>
+            <el-descriptions-item label="有效人数:" label-class-name="label-bold">
+              {{ committedStudents }}
+            </el-descriptions-item>
+            <el-descriptions-item label="有效平均成绩:" label-class-name="label-bold">
+              {{ committedAverage }}
+            </el-descriptions-item>
+            <el-descriptions-item label="满分人数:" label-class-name="label-bold">
+              {{ fullScoreStudents }}
+            </el-descriptions-item>
+            <el-descriptions-item label="满分比例:" label-class-name="label-bold">
+              {{ fullScoreRatio }}
+            </el-descriptions-item>
+            <el-descriptions-item label="及格人数:" label-class-name="label-bold">
+              {{ passedStudents }}
+            </el-descriptions-item>
+            <el-descriptions-item label="及格比例:" label-class-name="label-bold">
+              {{ passedRatio }}
+            </el-descriptions-item>
+          </el-descriptions>
+          <el-table :data="resultList" style="background: none; box-shadow:none;border: none">
+            <el-table-column prop="id" label="#" width="200" />
+            <el-table-column label="用户名">
+              <template #default="scope">
+                <span>{{ scope.row.owner.username }}</span>
               </template>
             </el-table-column>
-          </el-table>
-        </el-tab-pane>
-        <el-tab-pane
-            v-if="exam.problems && exam.problems.length"
-            class="problems-tab-pane"
-            label="题目列表"
-            name="problems"
-        >
-          <el-collapse accordion>
-            <el-collapse-item
-                v-for="problem in exam.problems"
-                :key="problem.id"
-                :name="problem.id"
-            >
-              <template #title>
-                {{ problem.name }}
-                <el-tag class="problem-tag">
-                  {{ problemTypeToTagContent(problem.type) }}
-                </el-tag>
+            <el-table-column label="最后提交时间">
+              <template #default="scope">
+                <span v-if="!scope.row.lastCommitAt">没有提交记录</span>
+                <span v-else>{{ formatTimestamp(scope.row.lastCommitAt) }}</span>
               </template>
-              <div style="display: inline;margin-right: 40px">{{ problem.description }}</div>
-              <el-button
-                  v-if="isTeacherGroup || isAdmin"
-                  @click="jumpToProblemDetail(problem.id)"
-                  style="float: right;margin-right: 40px"
-                  size="small"
-              >预览题目</el-button>
-            </el-collapse-item>
-          </el-collapse>
-        </el-tab-pane>
-      </el-tabs>
+            </el-table-column>
+            <el-table-column prop="score" label="成绩" width="200" />
+            <el-table-column label="操作" width="200">
+              <template #default="scope">
+                <el-button type="text" @click="jumpToCodemirror" disabled>源码</el-button>
+                |
+                <el-button type="text" @click="jumpToResultDetail(scope.row.id)">详情</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </el-card>
     </el-card>
+
     <el-dialog title="确认删除考试吗?" v-model="dialog.delete">
       <el-alert
           style="margin-bottom: 20px"
@@ -288,16 +366,16 @@
 <script>
 import {examStatusToTagContent, examStatusToTagType, formatTimestamp, problemTypeToTagContent} from "@/utils/coder-utils";
 import {mapGetters} from "vuex";
-import Paper from "@/views/ProblemPage/components/Paper";
 import PageHeader from "../../components/PageHeader";
+import Echarts from "@/views/ExamPage/ResultPage/Echarts.vue";
 import {ElMessage} from "element-plus";
 import Clipboard from "clipboard";
 
 export default {
   name: "ExamDetail",
   components: {
-    Paper,
     PageHeader,
+    Echarts
   },
   props: {
   },
@@ -323,6 +401,42 @@ export default {
       },
 
       range: [],
+
+      //结果列表
+      resultList: [],
+      chartOptions: {
+        tooltip: {
+          show: true
+        },
+        xAxis: {
+          data: [
+            "未提交",
+            "0-9",
+            "10-19",
+            "20-29",
+            "30-39",
+            "40-49",
+            "50-59",
+            "60-69",
+            "70-79",
+            "80-89",
+            "90-99",
+            "100"
+          ]
+        },
+        yAxis: {
+          name: "人数"
+        },
+        series: [
+          {
+            type: "bar",
+            data: [],
+            tooltip: {
+              formatter: "{c}人"
+            }
+          }
+        ]
+      }
     };
   },
   computed: {
@@ -347,6 +461,51 @@ export default {
     },
     tabValue() {
       return this.exam.joined? "commits": "problems";
+    },
+    //结果列表的统计数据
+    summary() {
+      return this.resultList
+          .map((result) => result.score)
+          .reduce((a = 0, b = 0) => a + b, 0) ?? 0;
+    },
+    totalStudents() {
+      return this.resultList.length;
+    },
+    totalAverage() {
+      return (this.summary / Math.max(this.totalStudents, 1)).toFixed(2);
+    },
+    committedStudents() { //该同学有提交历史就算有效提交
+      return this.resultList
+          .map(result => result.lastCommitAt ? 1 : 0)
+          .reduce((a, b) => a + b, 0);
+    },
+    committedAverage() { //注意:无效提交成绩为0
+      return (this.summary / Math.max(this.committedStudents, 1)).toFixed(2);
+    },
+    fullScoreStudents() {
+      return this.resultList
+          .map(result => result.score === 100)
+          .reduce((a, b) => a + b, 0);
+    },
+    fullScoreRatio() {
+      return (
+          (
+              (this.fullScoreStudents * 100) /
+              Math.max(this.committedStudents, 1)
+          ).toFixed(2) + "%"
+      );
+    },
+    passedStudents() { //
+      return this.resultList
+          .map(result => result?.score ?? 0 >= 60)
+          .reduce((a, b) => a + b, 0);
+    },
+    passedRatio() {
+      return (
+          (
+              this.passedStudents / Math.max(this.committedStudents, 1)
+          ).toFixed(2) + "%"
+      );
     }
   },
   mounted() {
@@ -355,11 +514,10 @@ export default {
   methods: {
     /**
      * 获取当前考试信息
-     */
-    fetchExamDetail() {
+     */ async fetchExamDetail() {
       this.loading.refresh = true;
-      const { examId } = this.$route.params;
-      this.$apis.getCoderExamByExamId(examId)
+      const {examId} = this.$route.params;
+      await this.$apis.getCoderExamByExamId(examId)
           .then(_res => {
             this.exam = _res.data.res;
             if (!this.isAdmin && this.exam.joined) {
@@ -368,14 +526,30 @@ export default {
           }).catch(_err => {
             ElMessage.error("当前考试不存在!跳转到管理页面!");
             this.$router.push("/manage");
-      });
+          });
       if (this.isTeacherGroup) {
-        this.$apis.getExamIsShownForStu(examId)
+        await this.$apis.getExamIsShownForStu(examId)
             .then(_res => {
               this.isConsoleOutputShownForStu = _res.data.res ?? false;
               this.loading.refresh = false;
             }).catch(_err => {
-              ElMessage.error(_err);
+          ElMessage.error(_err);
+        });
+        await this.$apis.getCoderResultByExamId(examId)
+            .then(_res => {
+              this.resultList = _res.data.res ?? [];
+              console.log(this.resultList);
+              //下面是两步操作
+              //1 按照分数段分类
+              //2 根据分数段,获取每个分数段的人数(返回数组)
+              this.chartOptions.series[0].data = this.resultList.map(
+                  result => (result.lastCommitAt ? Math.floor((result.score ?? 0) / 10) : -1) + 1
+              ).reduce((a, b) => {
+                a[b]++;
+                return a;
+              }, new Array(12).fill(0));
+            }).catch(_err => {
+          ElMessage.error(_err);
         });
       }
       this.loading.refresh = false;
@@ -498,6 +672,24 @@ export default {
         this.dialog.join = false;
       });
     },
+    //TODO 下载考试成绩的认证过程,信息安全问题
+    //下载考试成绩单
+    download() {
+      const { examId } = this.$route.params;
+      window.open(`https://coder.seec.seecoder.cn/api/file/result/${examId}`);
+    },
+    //下载考试详细成绩单
+    downloadDetailed() {
+      const { examId } = this.$nuxt.$route.params;
+      window.open(`https://coder.seec.seecoder.cn/api/file/result/${examId}/detailed`);
+    },
+    //查看提交代码
+    jumpToCodemirror() {
+    },
+    //跳转到具体结果信息页
+    jumpToResultDetail(resultId) {
+      this.$router.push(`${this.$route.path}/result/${resultId}`);
+    }
   }
 };
 </script>
@@ -508,8 +700,50 @@ export default {
   min-height: calc(100vh - 100px);
 }
 .changeable {
-  color: blue;
+  color: #409EFF;
   cursor: pointer;
+  margin-left: 20px;
 }
 
+::v-deep .el-card__header  {
+  border-bottom: 1px solid black;
+}
+
+::v-deep .son-card .el-card__header {
+  border-bottom: none;
+  padding-bottom: 0;
+}
+
+::v-deep .el-descriptions__body {
+  font-size: 16px;
+  background-color: #F8F9FB;  //样式穿透,覆盖原本的样式
+}
+
+::v-deep .label-bold {
+  font-weight: 500;
+  font-size: 16px;
+}
+
+::v-deep .el-tag {
+  border-radius: 15px;
+}
+
+::v-deep .el-collapse-item__content {
+  padding-bottom: 15px;
+}
+
+//::v-deep .el-table th{
+//  color: #606266;
+//  font-family: 'Roboto', sans-serif;
+//  font-size: 16px;
+//  background-color: #F8F9FB;
+//}
+//::v-deep .el-table__row{
+//  background-color: #F8F9FB;
+//}
+
+//::v-deep .el-table__header {
+//  color: black;
+//  border-bottom: 1px solid #afaaaa;
+//}
 </style>

+ 6 - 0
src/views/ProblemPage/ProblemDetail.vue

@@ -28,6 +28,9 @@
               删除题目
             </el-button>
           </template>
+          <el-button @click="getBack">
+            <el-icon><Back /></el-icon>
+          </el-button>
           {{ problem.name }}
         </page-header>
       </template>
@@ -353,6 +356,9 @@ export default {
     onDialogUpdate(path) {
       this.selectedFile = path;
       this.dialog.update = true;
+    },
+    getBack() {
+      this.$router.go(-1);
     }
   }
 };

+ 1 - 1
src/views/TaskPage/TaskPage.vue

@@ -458,4 +458,4 @@ export default {
   grid-template-columns: 1fr 1fr 1fr 1fr;
   grid-gap: 10px;
 }
-</style>
+</style>