Bläddra i källkod

Merge branch 'feature-quiz' into develop

# Conflicts:
#	src/views/quiz/QuizView.vue
#	src/views/student/components/StudentDoQuiz.vue
#	src/views/student/components/StudentQuizDetail.vue
#	src/views/student/tabs/StudentQuiz.vue
ChenYangfan 6 år sedan
förälder
incheckning
3630be11b8

+ 104 - 0
src/components/Cards/QuizCard.vue

@@ -0,0 +1,104 @@
+<template>
+  <router-link tag="div" class="quiz-card" :to="navPath">
+    <div class="quiz-card-title">
+      <c-icon theme="green" :left="-2" :right="6" :size="18">edit</c-icon>
+      {{quiz.name || '暂无课程名称'}}
+    </div>
+    <show-transition-small>
+      <div v-if="!hideCourseName" class="quiz-card-row">{{ quiz.course && quiz.course.name }}</div>
+    </show-transition-small>
+    <show-transition-small>
+      <div v-if="!hideSlideName" class="quiz-card-row">{{ quiz.slide && quiz.slide.name }}</div>
+    </show-transition-small>
+    <show-transition-small>
+      <div v-if="!hideQuizTime" class="quiz-card-row">测试时间:{{ SlideStateTranslation[quiz.quizTime] }}</div>
+    </show-transition-small>
+    <show-transition-small>
+      <div v-if="!hideQuizState" class="quiz-card-row">测试状态:{{ QuizStateNaming[quiz.state] }}</div>
+    </show-transition-small>
+  </router-link>
+</template>
+
+<script>
+import CIcon from '@/components/Basic/CIcon'
+import ShowTransitionSmall from '@/animations/ShowTransitionSmall'
+import SlideStateTranslation from '@/server/enums/slide-state-translation'
+import { QuizStateNaming } from '@/server/enums/quiz-state'
+
+export default {
+  name: 'QuizCard',
+  components: { CIcon, ShowTransitionSmall },
+  props: {
+    clickHandler: {
+      type: Function,
+      default: () => {}
+    },
+    quiz: {
+      type: Object,
+      default: () => ({})
+    },
+    navPath: {
+      type: Object,
+      default: () => ({})
+    },
+    hideQuizTime: {
+      type: Boolean,
+      default: false
+    },
+    hideQuizState: {
+      type: Boolean,
+      default: false
+    },
+    hideCourseName: {
+      type: Boolean,
+      default: false
+    },
+    hideSlideName: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      SlideStateTranslation,
+      QuizStateNaming
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  @import '~@/style/theme.scss';
+
+  .quiz-card {
+    margin: 12px 0;
+    padding: 8px 8px 8px 16px;
+    // border-left: 4px solid $input-background-color;
+    transition: .2s;
+    cursor: pointer;
+    border-radius: 12px;
+    background-color: $card-background;
+
+    &:hover {
+      background: darken($card-background, 5);
+      // border-left: 4px solid $theme-blue;
+    }
+  }
+
+  .quiz-card-title {
+    font-size: 16px;
+    display: flex;
+    align-items: center;
+    position: relative;
+    line-height: 1.8;
+  }
+
+  .quiz-card-row {
+    font-size: 14px;
+    color: #6a7a8a;
+  }
+
+  .c-icon {
+    transform: scale(1.3);
+  }
+</style>

+ 12 - 40
src/views/quiz/QuizView.vue

@@ -1,22 +1,11 @@
 <template>
-  <div class="quiz-view">
+  <tab-side-component>
     <back-title>{{slideInfo ? slideInfo.name + '的所有测验' : "加载中..."}}</back-title>
-    <div v-if="quizList === null" class="no-content-warning">
-      加载中...
-    </div>
-    <div v-for="item in quizList" :key="item.id" class="quiz-card">
-      <div class="quiz-info">
-        <div>{{ item.name }}</div>
-        <div>测试时间:{{ SlideStateTranslation[item.quizTime] }}</div>
-        <div>测试状态:{{ QuizStateNaming[item.state] }}</div>
-      </div>
-      <div class="quiz-action">
-        <router-link :to="{ name: 'student-quiz-detail', params: { quizId: item.id }}">
-          <c-button theme="green" icon="more">详情</c-button>
-        </router-link>
-      </div>
-    </div>
-  </div>
+    <quiz-card
+      v-for="item in quizList" :key="item.id"
+      :nav-path="{ name: 'student-quiz-detail', params: { quizId: item.id } }"
+      :quiz="item" hide-quiz-state hide-quiz-time />
+  </tab-side-component>
 </template>
 
 <script>
@@ -24,13 +13,13 @@ import BackTitle from '@/components/Basic/BackTitle'
 import SlideStateTranslation from '@/server/enums/slide-state-translation'
 import { QuizStateNaming } from '@/server/enums/quiz-state'
 import { getQuizList } from '@/server/quiz'
+import TabSideComponent from '@/components/Layouts/TabSideComponent'
+import QuizCard from '@/components/Cards/QuizCard'
 import { getSlideInfo } from '@/server/slide'
-import CButton from '@/components/Basic/CButton'
-
 export default {
   name: 'QuizView',
-  components: { CButton, BackTitle },
-  data () {
+  components: { QuizCard, TabSideComponent, BackTitle },
+  data() {
     return {
       quizList: null,
       slideInfo: null,
@@ -61,26 +50,9 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.quiz-view {
+.questions {
   margin: 0 auto;
-  max-width: 575px;
+  max-width: 1100px;
   padding: 64px 24px;
 }
-
-.quiz-card {
-  margin: 24px 0;
-  padding: 8px 12px;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  border: 1px solid #cadaea;
-  border-radius: 6px;
-
-  .quiz-info {
-    margin: 8px 0;
-  }
-  .quiz-action {
-
-  }
-}
 </style>

+ 2 - 3
src/views/quiz/components/ChoiceQuestionDetail.vue

@@ -1,6 +1,5 @@
 <template>
   <div>
-    <div v-if="!isEmpty(value.pass)">{{ value.pass ? '回答正确' : '回答错误' }}</div>
     <editor-section title="题干">
       <markdown-section :raw="value.stem"/>
     </editor-section>
@@ -17,10 +16,10 @@
     <editor-section v-if="!isEmpty(value.studentAnswer) && !fold" title="你的答案">
       {{ value.studentAnswer }}
     </editor-section>
-    <editor-section title="正确答案" v-if="!fold">
+    <editor-section title="正确答案" v-if="!fold && !isEmpty(value.answer)">
       {{ value.answer }}
     </editor-section>
-    <editor-section title="解析" v-if="!fold">
+    <editor-section title="解析" v-if="!fold && !isEmpty(value.analysis)">
       <markdown-section :raw="value.analysis"/>
     </editor-section>
   </div>

+ 1 - 1
src/views/quiz/components/EditorSection.vue

@@ -19,7 +19,7 @@ export default {
 
 <style lang="scss" scoped>
 h3 {
-  font-size: 18px;
+  font-size: 14px;
   color: #5a6a7a;
 }
 </style>

+ 1 - 1
src/views/quiz/components/MarkdownSection.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="markdown-section editor" v-html="markdownToHTML(raw)" />
+  <div class="markdown-section editor" v-html="markdownToHTML(raw || '')" />
 </template>
 
 <script>

+ 24 - 4
src/views/quiz/components/QuizDetailFactory.vue

@@ -1,9 +1,14 @@
 <template>
   <div>
+    <div v-if="quizDetail.state === QuizState.closed" class="quiz-info">测试得分:{{ Math.floor(100 * (passQuestionNums / totalQuestionNums)) }} 分</div>
     <div class="quiz-info">测试时间:{{ SlideStateTranslation[quizDetail.quizTime] }}</div>
     <div class="quiz-info">测试状态:{{ QuizStateNaming[quizDetail.state] }}</div>
     <div class="mt-32 mb-32" :key="item.id" v-for="(item, index) in quizDetail.questions">
-      <div class="index">题号:{{ index + 1 }}</div>
+      <div class="index">题号:{{ index + 1 }}
+        <c-icon v-if="!isEmpty(item.pass)"
+                :theme="item.pass ? 'green' : 'red'">
+          {{ item.pass ? 'check' : 'close' }}
+        </c-icon></div>
       <question-detail-factory :question="item" />
     </div>
   </div>
@@ -13,11 +18,13 @@
 import { createDefaultQuiz } from '@/views/quiz/utils/default'
 import SlideStateTranslation from '@/server/enums/slide-state-translation'
 import QuestionKind from '@/server/enums/question-kind'
-import { QuizStateNaming } from '@/server/enums/quiz-state'
+import { QuizStateNaming, QuizState } from '@/server/enums/quiz-state'
 import QuestionDetailFactory from '@/views/quiz/components/QuestionDetailFactory'
+import { isEmpty } from '@/utils/types'
+import CIcon from '@/components/Basic/CIcon'
 
 export default {
-  components: { QuestionDetailFactory },
+  components: { CIcon, QuestionDetailFactory },
   props: {
     quizDetail: {
       type: Object,
@@ -29,15 +36,28 @@ export default {
 
       SlideStateTranslation,
       QuestionKind,
+      QuizState,
       QuizStateNaming
     }
+  },
+  computed: {
+    totalQuestionNums() {
+      return this.quizDetail?.questions?.length ?? 0
+    },
+    passQuestionNums() {
+      return this.quizDetail?.questions?.filter(item => item.pass).length
+    }
+  },
+  methods: {
+    isEmpty
   }
 }
 </script>
 <style lang="scss" scoped>
 @import "~@/style/theme.scss";
 .quiz-info {
-  margin: 12px 0;
+  margin: 8px 0;
+  color: #7a8a9a;
 }
 .index {
   color: $theme-blue;

+ 2 - 3
src/views/quiz/components/TrueFalseQuestionDetail.vue

@@ -1,6 +1,5 @@
 <template>
   <div>
-    <div v-if="!isEmpty(value.pass)">{{ value.pass ? "回答正确" : "回答错误" }}</div>
     <editor-section title="题干">
       <markdown-section :raw="value.stem" />
     </editor-section>
@@ -8,11 +7,11 @@
       <span v-if="value.studentAnswer" >√</span>
       <span v-else >×</span>
     </editor-section>
-    <editor-section v-if="!fold" title="正确答案">
+    <editor-section v-if="!fold && !isEmpty(value.answer)" title="正确答案">
       <span v-if="value.answer" >√</span>
       <span v-else >×</span>
     </editor-section>
-    <editor-section title="解析" v-if="!fold">
+    <editor-section title="解析" v-if="!fold && !isEmpty(value.analysis)">
       <markdown-section :raw="value.analysis" />
     </editor-section>
   </div>

+ 48 - 56
src/views/student/components/StudentDoQuiz.vue

@@ -1,30 +1,33 @@
 <template>
-  <div class="student-do-quiz">
-    <back-title>
-      {{ quiz.name }}
-    </back-title>
+  <div class="do-quiz">
+    <back-title>{{ quiz.name }}</back-title>
     <form @submit.prevent="handleFormSubmit">
-      <div class="question-card" :key="question.id" v-for="(question, index) in quiz.questions">
-        <div class="index">题号:{{ index + 1 }}</div>
-        <markdown-section :raw="question.stem"/>
-        <div v-if="question.kind === QuestionKind.choice">
-          <label :key="index" v-for="(item, index) in question.options" class="choice-to-select-answer">
-            <input v-model="question.studentAnswer" type="radio" :value="index"/>
-            <markdown-section style="display: inline" :raw="item"/>
-          </label>
-        </div>
-        <div v-else-if="question.kind === QuestionKind.trueOrFalse">
-          <label>
-            <input v-model="question.studentAnswer" type="radio" :value="true"/>
-            √
-          </label>
-          <label>
-            <input v-model="question.studentAnswer" type="radio" :value="false"/>
-            ×
-          </label>
+      <c-button class="mb-16" theme="green" block>提交</c-button>
+      <div class="quiz-list mb-16" :key="question.id" v-for="(question, index) in quiz.questions">
+        <div class="quiz-list__index">{{ index + 1 }}.</div>
+        <div>
+          <markdown-section :raw="question.stem" />
+          <div v-if="question.kind === QuestionKind.choice">
+            <label class="option" :key="index" v-for="(item, index) in question.options">
+              <input v-model="question.studentAnswer" type="radio" :value="index" />
+              {{ index }}.
+              <markdown-section style="display: inline" :raw="item" />
+            </label>
+          </div>
+          <div v-else-if="question.kind === QuestionKind.trueOrFalse">
+            <label class="option">
+              <input v-model="question.studentAnswer" type="radio" :value="true" />
+              √
+            </label>
+            <label class="option">
+              <input v-model="question.studentAnswer" type="radio" :value="false" />
+              ×
+            </label>
+          </div>
         </div>
+
       </div>
-      <c-button icon="check" class="mb-24" theme="green">确认提交</c-button>
+      <c-button class="mb-16" theme="green" block>提交</c-button>
     </form>
   </div>
 </template>
@@ -39,7 +42,7 @@ import CButton from '@/components/Basic/CButton'
 export default {
   name: 'StudentDoQuiz',
   components: { CButton, MarkdownSection, BackTitle },
-  data () {
+  data() {
     return {
       quiz: createDefaultQuiz(),
       hasSubmit: false,
@@ -56,17 +59,17 @@ export default {
       next(false)
     }
   },
-  mounted () {
+  mounted() {
     this.fetchQuizDetail()
   },
   methods: {
-    fetchQuizDetail () {
+    fetchQuizDetail() {
       const { quizId } = this.$route.params
       getQuizById(quizId)
         .then(res => (this.quiz = res))
         .catch(err => this.$setErrorMessage(err.response.data.msg))
     },
-    handleFormSubmit () {
+    handleFormSubmit() {
       const { quizId } = this.$route.params
       submitQuiz({ quizId, answers: this.quiz.questions })
         .then(() => {
@@ -83,37 +86,26 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-@import "~@/style/theme.scss";
-
-.back-title {
-  display: flex;
-  align-items: center;
-}
+  .do-quiz {
+    margin: 0 auto;
+    max-width: 1100px;
+    padding: 64px 24px;
+  }
+  .quiz-list{
+    display: flex;
 
-.student-do-quiz {
-  max-width: 800px;
-  margin: 0 auto;
-  padding: 32px 24px;
-}
+    &__index{
+      margin-right: 16px;
+    }
 
-.choice-to-select-answer {
-  display: flex;
-  align-items: center;
-  margin: 8px 0;
+    .option{
+      display: flex;
+      align-items: center;
+      margin-top: 8px;
 
-  input {
-    margin-right: 8px;
+      input{
+        margin-right: 4px;
+      }
+    }
   }
-}
-
-.question-card {
-  margin: 32px 0;
-}
-
-.index {
-  color: $theme-blue;
-  padding: 6px 8px;
-  margin: 8px -8px;
-  background-color: $theme-blue-transparent;
-}
 </style>

+ 2 - 6
src/views/student/components/StudentQuizDetail.vue

@@ -1,12 +1,8 @@
 <template>
   <tab-side-component>
     <back-title>{{quiz.name}}</back-title>
-    <div v-if="quiz.state === QuizState.closed">
-      <div>正确:{{passQuestionNums}} 题</div>
-      <div>总共:{{totalQuestionNums}} 题</div>
-    </div>
-    <router-link v-if="quiz.state === QuizState.ongoing" :to="`${$route.path}/do-quiz`">
-      <c-button theme="green" standout block>做题</c-button>
+    <router-link v-if="quiz.state === QuizState.ongoing" :to="{ name: 'student-do-quiz', params: { quizId: quiz.id}}">
+      <c-button class="mb-16" theme="green" icon="edit" block>做题</c-button>
     </router-link>
     <quiz-detail-factory :quiz-detail="quiz" />
   </tab-side-component>

+ 18 - 93
src/views/student/tabs/StudentQuiz.vue

@@ -1,63 +1,19 @@
 <template>
   <div class="tab complex-tab">
     <div class="left">
-      <h1 class="title">课程测验
-        <c-tag theme="green">学生</c-tag>
-      </h1>
-      <div class="quiz-show-type-bar mb-16">
-        <c-button @click="showOngoingQuizzes = true" :right="6" block :theme="showOngoingQuizzes ? 'green' : 'normal'">
-          正在进行
-        </c-button>
-        <c-button @click="showOngoingQuizzes = false" :left="6" block :theme="showOngoingQuizzes ? 'normal' : 'green'">
-          已结束
-        </c-button>
-      </div>
-      <div v-if="showOngoingQuizzes">
-        <div class="sub-title">
-          待完成的测验
-          <c-button :theme="showMore ? 'blue' : 'normal'" :left="8" text
-                    :icon="showMore ? 'unfold_less' : 'unfold_more'"
-                    @click="showMore = !showMore">
-            {{showMore ? '收起' : '展开'}}
-          </c-button>
-        </div>
-        <div class="no-content-warning" v-if="ongoingQuizzes === null">加载中...</div>
-        <div class="quiz-link" :key="quiz.id" v-for="quiz in ongoingQuizzes">
-          <router-link :to="`/student/quiz/${quiz.id}`">
-            <div class="quiz-link--title">{{ quiz.name }}</div>
-            <show-transition-small>
-              <div v-if="showMore" class="quiz-link--info">对应课程:{{ quiz.course && quiz.course.name }}</div>
-            </show-transition-small>
-            <show-transition-small>
-              <div v-if="showMore" class="quiz-link--info">对应课件:{{ quiz.slide && quiz.slide.name }}</div>
-            </show-transition-small>
-          </router-link>
-        </div>
-      </div>
-      <div v-if="!showOngoingQuizzes">
-        <div class="sub-title">
-          已完成的测验
-          <c-button :theme="showMore ? 'blue' : 'normal'" :left="8" text
-                    :icon="showMore ? 'unfold_less' : 'unfold_more'"
-                    @click="showMore = !showMore">
-            {{showMore ? '收起' : '展开'}}
-          </c-button>
-        </div>
-        <div class="no-content-warning" v-if="closedQuizzes === null">加载中...</div>
-        <div :key="quiz.id" class="quiz-link" v-for="quiz in closedQuizzes">
-          <router-link :to="`/student/quiz/${quiz.id}`">
-            <div class="quiz-link--title">{{ quiz.name }}</div>
-            <show-transition-small>
-              <div v-if="showMore" class="quiz-link--info">对应课程:{{ quiz.course && quiz.course.name }}</div>
-            </show-transition-small>
-            <show-transition-small>
-              <div v-if="showMore" class="quiz-link--info">对应课件:{{ quiz.slide && quiz.slide.name }}</div>
-            </show-transition-small>
-          </router-link>
-        </div>
-      </div>
+      <h1 class="title">课程测验 <c-tag theme="green">学生</c-tag></h1>
+      <div class="sub-title">待完成的测验</div>
+      <quiz-card
+        :key="quiz.id" v-for="quiz in ongoingQuizzes"
+        :nav-path="{ name: 'student-quiz-detail', params: { quizId: quiz.id } }"
+        :quiz="quiz" hide-quiz-state hide-quiz-time />
+      <div class="sub-title">已完成的测验 & 结果</div>
+      <quiz-card
+        :key="quiz.id" v-for="quiz in closedQuizzes"
+        :nav-path="{ name: 'student-quiz-detail', params: { quizId: quiz.id } }"
+        :quiz="quiz" hide-quiz-state hide-quiz-time />
     </div>
-    <router-view/>
+    <router-view />
   </div>
 </template>
 
@@ -65,20 +21,17 @@
 import { getQuizList } from '@/server/quiz'
 import { QuizState } from '@/server/enums/quiz-state'
 import CTag from '@/components/Basic/CTag'
-import CButton from '@/components/Basic/CButton'
-import ShowTransitionSmall from '@/animations/ShowTransitionSmall'
+import QuizCard from '@/components/Cards/QuizCard'
 
 export default {
-  components: { ShowTransitionSmall, CButton, CTag },
-  data () {
+  components: { QuizCard, CTag },
+  data() {
     return {
-      ongoingQuizzes: null,
-      closedQuizzes: null,
-      showOngoingQuizzes: true,
-      showMore: false
+      ongoingQuizzes: [],
+      closedQuizzes: []
     }
   },
-  mounted () {
+  mounted() {
     getQuizList({ state: QuizState.ongoing })
       .then(res => (this.ongoingQuizzes = res.quizzes))
       .catch(err => this.$setErrorMessage(err.response.data.msg))
@@ -92,32 +45,4 @@ export default {
 <style lang="scss" scoped>
 @import "~@/style/theme.scss";
 @import "~@/views/shared/tab-common";
-
-.quiz-show-type-bar {
-  display: flex;
-}
-.sub-title {
-  display: flex;
-  align-items: center;
-}
-.quiz-link {
-  padding: 2px 8px;
-  border: 1px solid #cadaea;
-  border-radius: 6px;
-  margin: 12px 0;
-
-  &--title {
-    margin: 6px 0;
-  }
-
-  &--info {
-    font-size: 12px;
-    line-height: 16px;
-    color: #7a8a9a;
-  }
-
-  &:hover {
-    background-color: $theme-background-grey;
-  }
-}
 </style>