Просмотр исходного кода

feat: 做题时添加评分和收藏

ChenYangfan 6 лет назад
Родитель
Сommit
91d59e80fd

+ 12 - 4
src/components/Basic/CStars.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="c-stars">
     <c-icon class="stars-icon" @mouseenter.native="handleMouseEnter(i)" @mouseleave.native="handleMouseLeave"
-            :size="26" v-for="i in level" :key="i" theme="orange" @click.native="handleClick(i)">
+            :size="26" v-for="i in level" :key="i" :theme="theme" @click.native="handleClick(i)">
       {{isInStarRange(i) ? 'star' : 'star_border'}}
     </c-icon>
   </div>
@@ -23,13 +23,22 @@ export default {
       type: Number,
       default: 5
     },
-    disabled: Boolean
+    disabled: Boolean,
+    theme: {
+      type: String,
+      default: 'orange'
+    }
   },
   data () {
     return {
       hoverStars: this.stars
     }
   },
+  watch: {
+    stars (newValue) {
+      this.hoverStars = newValue
+    }
+  },
   methods: {
     isInStarRange (i) {
       return i <= this.hoverStars
@@ -53,14 +62,13 @@ export default {
   cursor: pointer;
   transition: .1s ease-out;
   &:hover {
-    transform: scale(1.35);
+    transform: scale(1.2);
   }
 }
 .c-stars {
   height: 26px;
   transition: .15s ease-out;
   &:hover {
-    transform: scale(1.2);
   }
 }
 </style>

+ 6 - 0
src/components/Basic/Ripple.vue

@@ -66,6 +66,12 @@ $opacity: 1;
   }
 }
 
+.orange {
+  .ripple {
+    background: $theme-orange-transparent;
+  }
+}
+
 .green {
   .ripple {
     background: $theme-green-transparent;

+ 31 - 0
src/server/question.js

@@ -94,3 +94,34 @@ export function rateQuestion ({ questionId, rate }) {
   return axios
     .post(useQuestionServer(`/${questionId}/rate`), data)
 }
+
+// 获取收藏的题目
+export function getCollectedQuestions ({ key = '', page = 1, size = 10, sort = '' }) {
+  const params = generateSearchParams({ key, page, sort, size })
+  return axios
+    .get(useQuestionServer('/collected'), { params })
+    .then(res => res.data)
+}
+
+// 获取评价的题目
+export function getRatedQuestions ({ key = '', page = 1, size = 10, sort = '' }) {
+  const params = generateSearchParams({ key, page, sort, size })
+  return axios
+    .get(useQuestionServer('/rated'), { params })
+    .then(res => res.data)
+}
+
+// 获取完成的题目
+export function getCompletedQuestions ({ key = '', page = 1, size = 10, sort = '' }) {
+  const params = generateSearchParams({ key, page, sort, size })
+  return axios
+    .get(useQuestionServer('/completed'), { params })
+    .then(res => res.data)
+}
+
+// 取得与题目的交互信息
+export function getInteractiveList ({ questionIdJoinByComma }) {
+  return axios
+    .get(useQuestionServer('/interactive'), { params: { questionId: questionIdJoinByComma } })
+    .then(res => res.data)
+}

+ 29 - 2
src/views/quiz/StudentDoQuiz.vue

@@ -10,8 +10,11 @@
         <div>上次得分:{{!isEmpty(answersInfo.score) ? answersInfo.score : '无提交历史'}}</div>
         <div>最近提交:{{!isEmpty(answersInfo.submitAt) ? momentFromNow(answersInfo.submitAt) : '无提交历史'}}</div>
       </div>
+      <div v-if="answersInfo.submitNumber && answersInfo.score !== 100" class="wrong mb-12 wrong-tip">本标志为上次提交错题</div>
       <template v-for="(question, index) in questions">
         <do-quiz-question-card @choice="handleChoice" :key="question.id" :question-prop="question"
+                               :marked-prop="interactiveMap ? interactiveMap[question.id].marked : false"
+                               :rate-prop="interactiveMap ? interactiveMap[question.id].rate : 0"
                                :index="index"></do-quiz-question-card>
       </template>
       <div class="mt-24">
@@ -26,7 +29,7 @@ import { createDefaultQuiz } from '@/views/quiz/utils/default'
 import QuestionKind from '@/enums/question-kind'
 import CButton from '@/components/Basic/CButton'
 import { getOneQuiz, getOneQuizStudentAnswer, submitQuizStudentAnswer } from '@/server/quiz'
-import { getQuestionsByQuiz } from '@/server/question'
+import { getInteractiveList, getQuestionsByQuiz } from '@/server/question'
 import momentFromNow from '@/utils/moment-from-now'
 import { isEmpty } from '@/utils/types'
 import DoQuizQuestionCard from '@/views/quiz/components/DoQuizQuestionCard'
@@ -47,6 +50,7 @@ export default {
         score: null,
         submitAt: null
       },
+      interactiveMap: null,
 
       // 做题起止时间相关,时间为了方便加减采用毫秒表示
       lastDate: new Date().valueOf(),
@@ -94,11 +98,25 @@ export default {
         }).catch(err => {
           this.$setErrorMessage(err.response.data.msg)
         })
+        // 获取评分和是否收藏
+        getInteractiveList({
+          questionIdJoinByComma: this.questions.map(_ => _.id).join(',')
+        }).then(res => {
+          const tempMap = {}
+          res.forEach(item => {
+            tempMap[item.questionId] = { marked: item.collection, rate: item.rate }
+          })
+          this.interactiveMap = tempMap
+        }).catch(err => this.$setErrorMessage(err.response.data.msg))
       }).catch(err => this.$setErrorMessage(err.response.data.msg))
     },
     handleFormSubmit () {
       const { quizId } = this.$route.params
-      this.questionPointer !== -1 && this.pushRecord({ questionId: this.questionPointer, startAt: this.lastDate, endAt: this.choiceDate })
+      this.questionPointer !== -1 && this.pushRecord({
+        questionId: this.questionPointer,
+        startAt: this.lastDate,
+        endAt: this.choiceDate
+      })
       const answers = {}
       let uncompleted = false
       this.questions.map(question => {
@@ -189,7 +207,16 @@ export default {
 .do-quiz-question-card:nth-child(2n) {
   background-color: #f7f7f7;
 }
+
 .do-quiz-question-card:nth-child(2n + 1) {
   background-color: #fff;
 }
+
+.wrong {
+  border-left: 6px solid $theme-red;
+}
+
+.wrong-tip {
+  padding-left: 12px;
+}
 </style>

+ 5 - 2
src/views/quiz/StudentQuestionTable.vue

@@ -44,24 +44,27 @@ export default {
 
 @media (min-width: 500px) {
   .questions-router-card {
+    direction: rtl;
     height: calc(100vh - 192px);
     border-radius: 16px;
     background-color: white;
-    box-shadow: 3px 6px 6px rgba(0, 0, 0, .03), -2px -2px 2px rgba(255, 255, 255, .6);
+    box-shadow: 3px 6px 6px rgba(0, 0, 0, .03);
 
     display: flex;
 
     .actions {
+      direction: ltr;
       display: flex;
       flex-direction: column;
       justify-content: space-around;
       align-items: center;
       width: 60px;
       background-color: #e0efff;
-      border-radius: 16px 0 0 16px;
+      border-radius: 0 16px 16px 0;
     }
 
     .views {
+      direction: ltr;
       flex: 1;
       padding: 16px 24px;
       overflow: auto;

+ 45 - 24
src/views/quiz/components/DoQuizQuestionCard.vue

@@ -1,7 +1,7 @@
 <template>
-  <div class="do-quiz-question-card">
-    <div class="quiz-list-item"
-         :class="question.studentAnswer ? question.studentAnswer.correct === false ? 'wrong' : '' : ''">
+  <div class="do-quiz-question-card"
+       :class="question.studentAnswer ? question.studentAnswer.correct === false ? 'wrong' : '' : ''">
+    <div class="quiz-list-item">
       <div class="quiz-list-item__index">{{ index + 1 }}.</div>
       <div>
         <markdown-section class="quiz-list-item-title" :raw="question.stem"/>
@@ -25,8 +25,10 @@
       </div>
     </div>
     <div class="action-bar">
-      <c-stars :level="5" v-model="star"></c-stars>
-      <icon-button no-bold :left="12" theme="green">bookmark_border</icon-button>
+      <c-stars title="评分" theme="orange" :level="5" v-model="rate"></c-stars>
+      <icon-button no-bold :left="12" theme="orange" title="收藏 / 取消收藏" @click="marked = !marked">
+        {{marked ? 'bookmark' : 'bookmark_border'}}
+      </icon-button>
     </div>
   </div>
 </template>
@@ -37,27 +39,55 @@ import MarkdownSection from '@/views/quiz/components/MarkdownSection'
 import QuestionKind from '@/enums/question-kind'
 import CStars from '@/components/Basic/CStars'
 import IconButton from '@/components/Basic/IconButton'
+import { rateQuestion, starQuestion } from '@/server/question'
 
 // fixme: 通过几层reference传值可能会造成一点困惑 - 咕咕咕
 export default {
   components: { IconButton, CStars, CIcon, MarkdownSection },
   props: {
     questionProp: Object,
-    index: Number
-  },
-  created () {
-    this.question = this.questionProp
-    this.$watch('questionProp', newValue => {
-      this.question = newValue
-    })
+    index: Number,
+    rateProp: {
+      type: Number,
+      default: 0
+    },
+    markedProp: Boolean
   },
+
   data () {
     return {
       question: null,
-      star: 0,
+      rate: this.rateProp,
+      marked: this.markedProp,
       QuestionKind
     }
   },
+  watch: {
+    markedProp (newValue) {
+      this.marked = newValue
+    },
+    rateProp (newValue) {
+      this.rate = newValue
+    },
+    rate (newValue) {
+      if (newValue !== 0 && newValue !== this.rateProp) {
+        rateQuestion({ questionId: this.question.id, rate: newValue })
+          .catch(err => this.$setErrorMessage(err.response.data.msg))
+      }
+    },
+    marked (newValue) {
+      if (newValue !== 0 && newValue !== this.markedProp) {
+        starQuestion({ questionId: this.question.id, collect: newValue })
+          .catch(err => this.$setErrorMessage(err.response.data.msg))
+      }
+    }
+  },
+  created () {
+    this.question = this.questionProp
+    this.$watch('questionProp', newValue => {
+      this.question = newValue
+    })
+  },
   methods: {
     emitChoice () {
       this.$emit('choice', { questionId: this.question.id })
@@ -88,19 +118,10 @@ export default {
   }
 }
 
-.wrong {
-  .quiz-list-item__index {
-    color: $theme-red;
-  }
-
-  .quiz-list-item-title {
-    color: $theme-red;
-  }
-}
-
 .do-quiz-question-card {
-  overflow: hidden;
+  border-left: 6px solid $card-background;
 }
+
 .action-bar {
   opacity: 0.4;
   display: flex;