Преглед на файлове

feat: 教师查看学生回答情况统计界面

ChenYangfan преди 6 години
родител
ревизия
85fbcca939
променени са 8 файла, в които са добавени 153 реда и са изтрити 12 реда
  1. 1 0
      package.json
  2. 1 1
      src/components/Cards/AnswerCard.vue
  3. 3 1
      src/config/server-info.js
  4. 3 0
      src/main.js
  5. 7 0
      src/server/quiz.js
  6. 14 10
      src/views/quiz/QuizDetail.vue
  7. 99 0
      src/views/quiz/components/QuizStatistic.vue
  8. 25 0
      yarn.lock

+ 1 - 0
package.json

@@ -11,6 +11,7 @@
   "dependencies": {
     "core-js": "^3.4.3",
     "highlight.js": "^9.17.1",
+    "laue": "^0.2.1",
     "marked": "^0.8.0",
     "moment": "^2.24.0",
     "register-service-worker": "^1.6.2",

+ 1 - 1
src/components/Cards/AnswerCard.vue

@@ -26,7 +26,7 @@ export default {
 
 .answer-card {
   margin: 12px 0;
-  padding: 8px 12px;
+  padding: 6px 8px;
   border-radius: 4px;
   cursor: pointer;
   transition: .3s;

+ 3 - 1
src/config/server-info.js

@@ -4,7 +4,9 @@
 
 // const host = 'http://106.14.178.184:8080/seec_helper'
 // const host = 'http://192.168.3.70:18080'
-const host = process.env.NODE_ENV === 'development' ? 'http://localhost:8080' : ''
+const host = process.env.NODE_ENV === 'development' ? 'http://helper.seecoder.cn' : ''
+
+// const host = process.env.NODE_ENV === 'development' ? 'http://localhost:8080' : ''
 const wsHost = process.env.NODE_ENV === 'development' ? 'ws://localhost:8080' : 'ws://' + location.host
 
 const adminUrl = host + '/api/admin'

+ 3 - 0
src/main.js

@@ -7,11 +7,14 @@ import uuid from 'uuid'
 import store from './store'
 import { SET_MESSAGE } from '@/store/types/message-types'
 import moment from '@/plugins/moment'
+import { Laue } from 'laue'
 
 Vue.config.productionTip = false
 Vue.prototype.$uuid = uuid
 Vue.prototype.$moment = moment
 
+Vue.use(Laue)
+
 Vue.prototype.$setMessage = function ({ message, type = 'info' }) {
   this.$store.commit(SET_MESSAGE, {
     message: message,

+ 7 - 0
src/server/quiz.js

@@ -90,3 +90,10 @@ export function getOneQuizStudentAnswer ({ quizId }) {
     .get(useQuizServer(`/${quizId}/student-answer`))
     .then(res => res.data)
 }
+
+// 获取测试作答的统计信息
+export function getQuizStatistic ({ quizId }) {
+  return axios
+    .get(useQuizServer(`/${quizId}/statistic`))
+    .then(res => res.data)
+}

+ 14 - 10
src/views/quiz/QuizDetail.vue

@@ -5,11 +5,13 @@
         {{ quizDetail.name }}
         <c-button class="delete-button" :left="12" text theme="red" @click="handleDelete" icon="close">删除测试</c-button>
       </back-title>
-      <div class="sub-title mb-16">学生提交结果列表</div>
+      <quiz-statistic :quiz-id="$route.params.quizId"></quiz-statistic>
       <search-bar v-model="searchKey"></search-bar>
       <pagination @change="handlePageChange" v-show="result && result.length > 0" :page="page" :size="size"
                   :total-elements="totalElements"></pagination>
-      <div class="answer-card-container" v-if="quizDetail.state === QuizState.closed || quizDetail.state === QuizState.ongoing">
+      <div class="no-content-warning" v-if="result === null">加载中...</div>
+      <div class="answer-card-container"
+           v-else-if="quizDetail.state === QuizState.closed || quizDetail.state === QuizState.ongoing">
         <answer-card @click="handleSelectAnswer(answer)" v-for="answer of result" :key="answer.studentId"
                      :answer="answer"></answer-card>
       </div>
@@ -33,19 +35,21 @@ import CButton from '@/components/Basic/CButton'
 import Pagination from '@/components/Pagination'
 import SearchBar from '@/components/Basic/SearchBar'
 import debounce from '@/utils/debounce'
+import QuizStatistic from '@/views/quiz/components/QuizStatistic'
 
 export default {
   name: 'QuizDetail',
-  components: { SearchBar, Pagination, CButton, AnswerCard, QuizDetailFactory, BackTitle },
+  components: { QuizStatistic, SearchBar, Pagination, CButton, AnswerCard, QuizDetailFactory, BackTitle },
   data () {
     return {
       page: 1,
-      size: 12,
+      size: 9,
       totalElements: 1,
       searchKey: '',
 
       quizDetail: createDefaultQuiz(),
-      result: [],
+      result: null,
+
       showStudentName: '',
       showScore: '',
       showSubmitAt: '',
@@ -112,7 +116,7 @@ export default {
 .quiz-detail {
   max-width: 1100px;
   margin: 0 auto;
-  padding: 64px 24px;
+  padding: 32px 24px;
 }
 
 .delete-button {
@@ -120,7 +124,7 @@ export default {
 }
 
 .answer-card-container {
-  margin: 32px 0;
+  margin: 12px 0 0 0 ;
   column-width: 150px;
   column-gap: 12px;
 
@@ -145,11 +149,11 @@ export default {
     flex: 1;
   }
   .quiz {
-    margin: -32px 0;
+    margin: -16px 0;
     width: 100px;
-    padding: 32px 24px 32px 12px;
+    padding: 16px 24px 16px 12px;
     flex: 1;
-    max-height: calc(100vh - 128px);
+    max-height: calc(100vh - 64px);
     overflow: auto;
   }
 }

+ 99 - 0
src/views/quiz/components/QuizStatistic.vue

@@ -0,0 +1,99 @@
+<template>
+  <div class="quiz-statistic">
+    <div>
+      <la-cartesian :data="scoreValues" v-if="statistic" :width="250" :height="180">
+        <la-bar dot prop="pv"></la-bar>
+        <la-x-axis gridline dashed :tick-size="5" prop="name" :font-size="10"></la-x-axis>
+        <la-y-axis gridline dashed :font-size="10"></la-y-axis>
+      </la-cartesian>
+      分数分布
+    </div>
+    <div>
+      <la-polar fill-container v-if="statistic" :data="submitTimesValues"  :width="180" :height="180">
+        <la-pie show-label label-prop="name" show-value :font-size="12" prop="pv"></la-pie>
+      </la-polar>
+      提交次数分布
+    </div>
+  </div>
+</template>
+
+<script>
+import { getQuizStatistic } from '@/server/quiz'
+
+export default {
+  props: ['quizId'],
+  created () {
+    this.getStatistic()
+  },
+  data () {
+    return {
+      statistic: null
+    }
+  },
+  watch: {
+    quizId () {
+      this.getStatistic()
+    }
+  },
+  computed: {
+    scoreValues () {
+      const scores = [
+        { name: '', num: 0 },
+        { name: '<60', num: 0 },
+        { name: '60-79.9', num: 0 },
+        { name: '80-99.9', num: 0 },
+        { name: '100', num: 0 }
+      ]
+      Object.keys(this.statistic.scores).sort((a, b) => {
+        return parseFloat(a) - parseFloat(b)
+      }).map(key => {
+        const score = this.statistic.scores[key]
+        const keyFloat = parseFloat(key)
+        switch (true) {
+          case keyFloat <= 60:
+            scores[1].num += score
+            break
+          case keyFloat <= 80:
+            scores[2].num += score
+            break
+          case keyFloat < 100:
+            scores[3].num += score
+            break
+          case Math.abs(keyFloat - 100) < 0.1:
+            scores[4].num += score
+            break
+        }
+      })
+      return scores.map(({ name, num }) => ({ name, pv: num }))
+    },
+    submitTimesValues () {
+      return Object.keys(this.statistic.submitNums).sort((a, b) => {
+        return parseFloat(a) - parseFloat(b)
+      }).map(key => ({ name: key, pv: parseInt(this.statistic.submitNums[key]) }))
+    }
+  },
+  methods: {
+    getStatistic () {
+      getQuizStatistic({ quizId: this.quizId })
+        .then(res => (this.statistic = res))
+        .catch(err => this.$setErrorMessage(err.response.data.msg))
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import "~@/style/theme.scss";
+
+.quiz-statistic {
+  padding: 12px 0;
+  margin-bottom: 16px;
+  border-radius: 4px;
+  border: 1px solid darken($card-background, 5);
+  display: flex;
+  justify-content: center;
+  > div {
+    text-align: center;
+  }
+}
+</style>

+ 25 - 0
yarn.lock

@@ -3480,6 +3480,23 @@ cz-conventional-changelog@^3.0.2:
   optionalDependencies:
     "@commitlint/load" ">6.1.1"
 
+d3-array@^1.2.1:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f"
+  integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==
+
+d3-path@1:
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf"
+  integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
+
+d3-shape@^1.2.0:
+  version "1.3.7"
+  resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7"
+  integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==
+  dependencies:
+    d3-path "1"
+
 dashdash@^1.12.0:
   version "1.14.1"
   resolved "https://registry.npm.taobao.org/dashdash/download/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@@ -6781,6 +6798,14 @@ koa@^2.11.0:
     type-is "^1.6.16"
     vary "^1.1.2"
 
+laue@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/laue/-/laue-0.2.1.tgz#5bd19c7c0315902e3b4297a8040c3218160b2e6e"
+  integrity sha512-BDKpX/Vzy6e4ZtoFDrYkY94Pr6wzyXTMtQzCtEUBeH7jbOlTM9wvPDPoR7it7otycDMpDF9g2E3LpnitnzlF0Q==
+  dependencies:
+    d3-array "^1.2.1"
+    d3-shape "^1.2.0"
+
 launch-editor-middleware@^2.2.1:
   version "2.2.1"
   resolved "https://registry.npm.taobao.org/launch-editor-middleware/download/launch-editor-middleware-2.2.1.tgz#e14b07e6c7154b0a4b86a0fd345784e45804c157"