Explorar el Código

Merge branch 'feature-quiz' into develop

ChenYangfan hace 6 años
padre
commit
2aa88f29a2

+ 5 - 1
src/components/Editor.vue

@@ -43,9 +43,13 @@ export default {
     }
   },
   watch: {
-    initContent(val) {
+    initContent: {
+      immediate: true,
+      handler(val) {
         // eslint-disable-next-line no-unused-expressions
         this.editor?.setContent(markdownToHTML(val))
+        this.$emit('update-markdown', val)
+      }
     }
   },
   mounted() {

+ 15 - 4
src/router/index.js

@@ -40,6 +40,11 @@ const routes = [{
   path: '/quiz-detail/:quizId',
   name: 'quiz-detail',
   component: () => import('../views/quiz/QuizDetail.vue')
+}, {
+  meta: { roles: ['teacher'] },
+  path: '/quiz-detail/:quizId/edit',
+  name: 'edit-quiz',
+  component: () => import('../views/quiz/EditQuiz.vue')
 }, {
   meta: { roles: ['student'] },
   path: '/quiz/:slideId',
@@ -55,11 +60,21 @@ const routes = [{
   path: '/questions/:questionId',
   name: 'question-detail',
   component: () => import('../views/quiz/QuestionDetail.vue')
+}, {
+  meta: { roles: ['teacher'] },
+  path: '/questions/:questionId/edit',
+  name: 'edit-question',
+  component: () => import('../views/quiz/EditQuestion.vue')
 }, {
   meta: { roles: ['teacher'] },
   path: '/create-question',
   name: 'create-question',
   component: () => import('../views/quiz/CreateQuestion.vue')
+}, {
+  meta: { roles: ['student'] },
+  path: '/student/quiz/:quizId/do-quiz',
+  name: 'student-do-quiz',
+  component: () => import(/* webpackChunkName: "student" */ '../views/student/components/StudentDoQuiz.vue')
 }, {
   meta: { roles: ['teacher'] },
   path: '/teacher',
@@ -132,10 +147,6 @@ const routes = [{
       path: ':quizId',
       name: 'student-quiz-detail',
       component: () => import(/* webpackChunkName: "student" */ '../views/student/components/StudentQuizDetail.vue')
-    }, {
-      path: ':quizId/do-quiz',
-      name: 'student-do-quiz',
-      component: () => import(/* webpackChunkName: "student" */ '../views/student/components/StudentDoQuiz.vue')
     }]
   }, {
     path: 'courses',

+ 1 - 1
src/server/question.js

@@ -27,7 +27,7 @@ export function createQuestion(questionBody) {
 
 export function updateQuestion({ questionId, questionBody }) {
   return axios
-    .post(useQuestionServer(`/${questionId}`), questionBody)
+    .put(useQuestionServer(`/${questionId}`), questionBody)
     .then(res => res.data)
 }
 

+ 1 - 1
src/server/quiz.js

@@ -41,7 +41,7 @@ export function updateQuiz({
 }) {
   const data = { id, name, quizTime, slideId, questions }
   return axios
-    .post(useQuizServer(`/${id}`), data)
+    .put(useQuizServer(`/${id}`), data)
     .then(res => res.data)
 }
 

+ 6 - 6
src/views/quiz/CreateQuestion.vue

@@ -2,8 +2,7 @@
   <div class="create-question">
     <back-title>创建题目</back-title>
     <question-kind-bar v-model="currentKind" />
-    <choice-question-editor v-if="currentKind === QuestionKind.choice" @submit="create($event, QuestionKind.choice)" />
-    <true-false-question-editor v-else @submit="create($event, QuestionKind.trueOrFalse)" />
+    <question-editor-factory :kind="currentKind" @submit="create($event, currentKind)" />
   </div>
 </template>
 
@@ -11,13 +10,14 @@
 import BackTitle from '@/components/Basic/BackTitle'
 import QuestionKindBar from '@/components/QuestionKindBar'
 import QuestionKind from '@/server/enums/question-kind'
-import ChoiceQuestionEditor from '@/views/quiz/components/ChoiceQuestionEditor'
-import TrueFalseQuestionEditor from '@/views/quiz/components/TrueFalseQuestionEditor'
 import { createQuestion } from '@/server/question'
+import QuestionEditorFactory from '@/views/quiz/components/QuestionEditorFactory'
+
 export default {
-  components: { TrueFalseQuestionEditor, ChoiceQuestionEditor, QuestionKindBar, BackTitle },
+  name: 'CreateQuestion',
+  components: { QuestionEditorFactory, QuestionKindBar, BackTitle },
   data() {
-    return { currentKind: QuestionKind.choice, QuestionKind }
+    return { currentKind: QuestionKind.choice }
   },
   methods: {
     create(questionBody, kind) {

+ 7 - 49
src/views/quiz/CreateQuiz.vue

@@ -1,69 +1,27 @@
 <template>
   <div>
-    <back-title>测试详情</back-title>
-    <form @submit.prevent="handleSubmit">
-      <label>
-        测试名称:
-        <input v-model="quiz.name" required placeholder="请输入测试名称" />
-      </label>
-      <label>
-        测试进行时课件的状态(测试时间):
-        <select v-model="quiz.quizTime" required >
-          <option value="">请选择测试时间</option>
-          <option :key="index" v-for="(item, index) in SlideStateTranslation" :value="index">{{ item }}</option>
-        </select>
-      </label>
-      <div class="mb-16" :key="question.id" v-for="(question, index) in selectedQuestions">
-        <div>{{ index + 1 }}.</div>
-        <a @click="moveIndexQuestionUp(index)">向上移动</a>
-        <a @click="moveIndexQuestionUp(index + 1)">向下移动</a>
-        <question-detail-factory :question="question" />
-      </div>
-      <question-table v-model="selectedQuestions" selectable />
-      <button>创建</button>
-    </form>
+    <back-title>创建测试</back-title>
+    <quiz-editor @submit="handleSubmit" />
   </div>
 </template>
 
 <script>
-import { naming as QuestionKindNaming } from '@/server/enums/question-kind'
-import { QuizStateNaming } from '@/server/enums/quiz-state'
-import SlideStateTranslation from '@/server/enums/slide-state-translation'
 import BackTitle from '@/components/Basic/BackTitle'
-import QuestionTable from '@/views/quiz/components/QuestionTable'
-import QuestionDetailFactory from '@/views/quiz/components/QuestionDetailFactory'
 import { createQuiz } from '@/server/quiz'
+import QuizEditor from '@/views/quiz/components/QuizEditor'
 
 export default {
   name: 'CreateQuiz',
-  components: { QuestionDetailFactory, QuestionTable, BackTitle },
-  data() {
-    return {
-      quiz: { name: '', quizTime: '', slideId: this.$route.params.slideId },
-      selectedQuestions: [],
-
-      QuestionKindNaming,
-      QuizStateNaming,
-      SlideStateTranslation
-    }
-  },
+  components: { QuizEditor, BackTitle },
   methods: {
-    handleSubmit() {
-      createQuiz({ ...this.quiz, questions: this.selectedQuestions.map(item => item.id) })
+    handleSubmit(quizData) {
+      const { slideId } = this.$route.params
+      createQuiz({ ...quizData, slideId })
         .then(() => this.$setSuccessMessage('创建成功'))
         .then(() => this.$router.go(-1))
         .catch(err =>
           this.$setErrorMessage(err.response.data.msg)
         )
-    },
-    moveIndexQuestionUp(index) {
-      if (index < this.selectedQuestions.length && index >= 1) {
-        const nextQuestions = [...this.selectedQuestions]
-        const before = nextQuestions[index - 1]
-        nextQuestions[index - 1] = nextQuestions[index]
-        nextQuestions[index] = before
-        this.selectedQuestions = nextQuestions
-      }
     }
   }
 }

+ 51 - 0
src/views/quiz/EditQuestion.vue

@@ -0,0 +1,51 @@
+<template>
+  <div class="edit-question">
+    <back-title>修改题目</back-title>
+    <question-editor-factory :kind="question.kind" :init-value="question" @submit="edit($event)" />
+  </div>
+</template>
+
+<script>
+import BackTitle from '@/components/Basic/BackTitle'
+import QuestionKind from '@/server/enums/question-kind'
+import { getQuestionById, updateQuestion } from '@/server/question'
+import QuestionEditorFactory from '@/views/quiz/components/QuestionEditorFactory'
+
+export default {
+  name: 'EditQuestion',
+  components: { QuestionEditorFactory, BackTitle },
+  data() {
+    return {
+      currentKind: QuestionKind.choice,
+      question: {}
+    }
+  },
+  mounted() {
+    this.fetchQuestionDetail()
+  },
+  methods: {
+    fetchQuestionDetail() {
+      const { questionId } = this.$route.params
+      getQuestionById(questionId)
+        .then(res => (this.question = res))
+        .catch(err => this.$setErrorMessage(err.response.data.msg))
+    },
+    edit(questionBody) {
+      updateQuestion({ questionId: questionBody.id, questionBody })
+        .then(() => this.$setSuccessMessage('修改成功'))
+        .then(() => this.$router.go(-1))
+        .catch(err =>
+          this.$setErrorMessage(err.response.data.msg)
+        )
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+  .edit-question {
+    margin: 0 auto;
+    max-width: 1100px;
+    padding: 64px 24px;
+  }
+</style>

+ 47 - 0
src/views/quiz/EditQuiz.vue

@@ -0,0 +1,47 @@
+<template>
+  <div>
+    <back-title>修改测试</back-title>
+    <quiz-editor :init-quiz="initQuiz" @submit="handleSubmit" />
+  </div>
+</template>
+
+<script>
+import BackTitle from '@/components/Basic/BackTitle'
+import { getQuizById, updateQuiz } from '@/server/quiz'
+import QuizEditor from '@/views/quiz/components/QuizEditor'
+
+export default {
+  name: 'EditQuiz',
+  components: { QuizEditor, BackTitle },
+  data() {
+    return {
+      initQuiz: {}
+    }
+  },
+  mounted() {
+    this.fetchQuizDetail()
+  },
+  methods: {
+    fetchQuizDetail() {
+      const { quizId } = this.$route.params
+      getQuizById(quizId)
+        .then(res => (this.initQuiz = res))
+        .catch(err =>
+          this.$setErrorMessage(err.response.data.msg)
+        )
+    },
+    handleSubmit(quizData) {
+      updateQuiz({ ...quizData, slideId: this.initQuiz?.slide?.id })
+        .then(() => this.$setSuccessMessage('创建成功'))
+        .then(() => this.$router.go(-1))
+        .catch(err =>
+          this.$setErrorMessage(err.response.data.msg)
+        )
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 1 - 0
src/views/quiz/QuestionDetail.vue

@@ -12,6 +12,7 @@ import BackTitle from '@/components/Basic/BackTitle'
 import QuestionDetailFactory from '@/views/quiz/components/QuestionDetailFactory'
 
 export default {
+  name: 'QuestionDetail',
   components: { QuestionDetailFactory, BackTitle },
   data() {
     return {

+ 1 - 0
src/views/quiz/Questions.vue

@@ -11,6 +11,7 @@
 import BackTitle from '@/components/Basic/BackTitle'
 import QuestionTable from '@/views/quiz/components/QuestionTable'
 export default {
+  name: 'Questions',
   components: { QuestionTable, BackTitle }
 }
 </script>

+ 1 - 0
src/views/quiz/QuizDetail.vue

@@ -15,6 +15,7 @@ import QuizResult from '@/views/quiz/components/QuizResult'
 import { QuizState } from '@/server/enums/quiz-state'
 
 export default {
+  name: 'QuizDetail',
   components: { QuizResult, QuizDetailFactory, BackTitle },
   data() {
     return {

+ 4 - 1
src/views/quiz/QuizView.vue

@@ -5,7 +5,9 @@
       <div>{{ item.name }}</div>
       <div>测试时间:{{ SlideStateTranslation[item.quizTime] }}</div>
       <div>测试状态:{{ QuizStateNaming[item.state] }}</div>
-      <router-link :to="`/student/quiz/${item.id}`"><button>详情</button></router-link>
+      <router-link :to="{ name: 'student-quiz-detail', params: { quizId: item.id }}">
+        <button>详情</button>
+      </router-link>
     </div>
   </tab-side-component>
 </template>
@@ -17,6 +19,7 @@ import { QuizStateNaming } from '@/server/enums/quiz-state'
 import { getQuizList } from '@/server/quiz'
 import TabSideComponent from '@/components/Layouts/TabSideComponent'
 export default {
+  name: 'QuizView',
   components: { TabSideComponent, BackTitle },
   data() {
     return {

+ 15 - 2
src/views/quiz/TeacherQuizSetting.vue

@@ -1,13 +1,20 @@
 <template>
   <div class="teacher-quiz-setting">
     <back-title>具体课件的测试列表</back-title>
-    <router-link :to="`${$route.path}/create-quiz`"><button>新增测试</button></router-link>
+    <router-link :to="{ name:'teacher-quiz-create', params: { slideId: this.slideId }}">
+      <button>新增测试</button>
+    </router-link>
     <router-link to="/questions"><button>题库管理</button></router-link>
     <div v-for="item in quizList" :key="item.id">
       <div>{{ item.name }}</div>
       <div>测试时间:{{ SlideStateTranslation[item.quizTime] }}</div>
       <div>测试状态:{{ QuizStateNaming[item.state] }}</div>
-      <router-link :to="`/quiz-detail/${item.id}`"><button>详情</button></router-link>
+      <router-link :to="{ name: 'quiz-detail', params: { quizId: item.id }}">
+        <button>详情</button>
+      </router-link>
+      <router-link :to="{ name: 'edit-quiz', params: { quizId: item.id }}">
+        <button>修改</button>
+      </router-link>
     </div>
   </div>
 </template>
@@ -19,6 +26,7 @@ import SlideStateTranslation from '@/server/enums/slide-state-translation'
 import { QuizStateNaming } from '@/server/enums/quiz-state'
 
 export default {
+  name: 'TeacherQuizSetting',
   components: { BackTitle },
   data() {
     return {
@@ -28,6 +36,11 @@ export default {
       QuizStateNaming
     }
   },
+  computed: {
+    slideId() {
+      return this.$route.params.slideId
+    }
+  },
   mounted() {
     this.fetchQuiz()
   },

+ 12 - 2
src/views/quiz/components/ChoiceQuestionEditor.vue

@@ -41,7 +41,7 @@
     </editor-section>
     <editor-section title="解析">
       <editor
-        :init-content="value.analysis"
+        :init-content="initValue.analysis"
         @update-markdown="value.analysis = $event"
       />
     </editor-section>
@@ -80,7 +80,17 @@ export default {
   watch: {
     value: {
       deep: true,
-      handler: nextValue => this.$emit('input', nextValue)
+      handler(nextValue) {
+        return this.$emit('input', nextValue)
+      }
+    },
+    initValue: {
+      deep: true,
+      immediate: true,
+      handler(nextInitValue) {
+        this.value.options = nextInitValue.options
+        this.value.answer = nextInitValue.answer
+      }
     }
   },
   methods: {

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

@@ -9,7 +9,8 @@
         <a v-if="selectable" class="action" style="cursor: pointer" @click="$emit('toggle-select', questionId)">
           {{ selected ? "取消选择" : "选择" }}
         </a>
-        <router-link class="action" style="margin-left: 16px;" :to="`/questions/${questionId}`">详情</router-link>
+        <router-link class="action" style="margin-left: 16px;" :to="{ name: 'edit-question', params: { questionId }}">修改</router-link>
+        <router-link class="action" style="margin-left: 16px;" :to="{ name: 'question-detail', params: { questionId }}">详情</router-link>
       </span>
     </div>
     <markdown-section :raw="stem" />
@@ -29,7 +30,7 @@ export default {
       default: ''
     },
     questionId: {
-      type: Number,
+      type: String,
       required: true
     },
     kind: {

+ 37 - 0
src/views/quiz/components/QuestionEditorFactory.vue

@@ -0,0 +1,37 @@
+<template>
+  <choice-question-editor v-if="kind === QuestionKind.choice" :initValue="initValue" @submit="handleSubmit" />
+  <true-false-question-editor v-else-if="kind === QuestionKind.trueOrFalse" :initValue="initValue" @submit="handleSubmit" />
+</template>
+
+<script>
+import QuestionKind from '@/server/enums/question-kind'
+import ChoiceQuestionEditor from '@/views/quiz/components/ChoiceQuestionEditor'
+import TrueFalseQuestionEditor from '@/views/quiz/components/TrueFalseQuestionEditor'
+
+export default {
+  name: 'QuestionEditorFactory',
+  components: { TrueFalseQuestionEditor, ChoiceQuestionEditor },
+  props: {
+    initValue: {
+      type: Object,
+      default: () => ({})
+    },
+    kind: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    return { QuestionKind }
+  },
+  methods: {
+    handleSubmit(event) {
+      this.$emit('submit', { ...this.initValue, ...event })
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

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

@@ -1,7 +1,7 @@
 <template>
   <div>
     <input class="mb-16" v-model="inputStem" placeholder="题干搜索" @input="debounceFetchQuestions" />
-    <router-link to="/create-question">
+    <router-link :to="{ name: 'create-question' }">
       <button>
         创建题目
       </button>

+ 79 - 0
src/views/quiz/components/QuizEditor.vue

@@ -0,0 +1,79 @@
+<template>
+  <form @submit.prevent="handleSubmit">
+    <label>
+      测试名称:
+      <input v-model="quiz.name" required placeholder="请输入测试名称" />
+    </label>
+    <label>
+      测试进行时课件的状态(测试时间):
+      <select v-model="quiz.quizTime" required >
+        <option value="" disabled selected hidden>请选择测试时间</option>
+        <option :key="index" v-for="(item, index) in SlideStateTranslation" :value="index">{{ item }}</option>
+      </select>
+    </label>
+    <div class="mb-16" :key="question.id" v-for="(question, index) in selectedQuestions">
+      <div>{{ index + 1 }}.</div>
+      <a @click="moveIndexQuestionUp(index)">向上移动</a>
+      <a @click="moveIndexQuestionUp(index + 1)">向下移动</a>
+      <question-detail-factory :question="question" />
+    </div>
+    <question-table v-model="selectedQuestions" selectable />
+    <button>提交</button>
+  </form>
+</template>
+
+<script>
+import { naming as QuestionKindNaming } from '@/server/enums/question-kind'
+import { QuizStateNaming } from '@/server/enums/quiz-state'
+import SlideStateTranslation from '@/server/enums/slide-state-translation'
+import QuestionTable from '@/views/quiz/components/QuestionTable'
+import QuestionDetailFactory from '@/views/quiz/components/QuestionDetailFactory'
+
+export default {
+  name: 'QuizEditor',
+  components: { QuestionDetailFactory, QuestionTable },
+  props: {
+    initQuiz: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data() {
+    return {
+      quiz: { name: '', quizTime: '' },
+      selectedQuestions: [],
+
+      QuestionKindNaming,
+      QuizStateNaming,
+      SlideStateTranslation
+    }
+  },
+  watch: {
+    initQuiz: {
+      immediate: true,
+      handler({ id, name, quizTime, questions = [] } = {}) {
+        this.quiz = { id, name, quizTime }
+        this.selectedQuestions = questions
+      }
+    }
+  },
+  methods: {
+    handleSubmit() {
+      this.$emit('submit', { ...this.quiz, questions: this.selectedQuestions.map(item => item.id) })
+    },
+    moveIndexQuestionUp(index) {
+      if (index < this.selectedQuestions.length && index >= 1) {
+        const nextQuestions = [...this.selectedQuestions]
+        const before = nextQuestions[index - 1]
+        nextQuestions[index - 1] = nextQuestions[index]
+        nextQuestions[index] = before
+        this.selectedQuestions = nextQuestions
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 11 - 3
src/views/quiz/components/TrueFalseQuestionEditor.vue

@@ -55,14 +55,22 @@ export default {
   watch: {
     value: {
       deep: true,
-      handler: nextValue => this.$emit('input', nextValue)
+      handler(nextValue) {
+        return this.$emit('input', nextValue)
+      }
+    },
+    initValue: {
+      deep: true,
+      immediate: true,
+      handler(nextInitValue) {
+        this.value.answer = nextInitValue.answer
+      }
     }
   },
   methods: {
     handleSubmit() {
-      const { stem, options, answer, analysis } = this.value
+      const { stem, answer, analysis } = this.value
       if (!stem) this.$setErrorMessage('请输入题干')
-      else if (!options?.A) this.$setErrorMessage('请输入选项')
       else if (!answer) this.$setErrorMessage('请选择答案')
       else if (!analysis) this.$setErrorMessage('请输入解析')
       else this.$emit('submit', this.value)

+ 4 - 4
src/views/student/components/StudentDoQuiz.vue

@@ -1,5 +1,5 @@
 <template>
-  <tab-side-component>
+  <div>
     <back-title>{{ quiz.name }}</back-title>
     <form @submit.prevent="handleFormSubmit">
       <button>提交</button>
@@ -25,10 +25,9 @@
       </div>
       <button>提交</button>
     </form>
-  </tab-side-component>
+  </div>
 </template>
 <script>
-import TabSideComponent from '@/components/Layouts/TabSideComponent'
 import BackTitle from '@/components/Basic/BackTitle'
 import { getQuizById, submitQuiz } from '@/server/quiz'
 import { createDefaultQuiz } from '@/views/quiz/utils/default'
@@ -36,7 +35,8 @@ import QuestionKind from '@/server/enums/question-kind'
 import MarkdownSection from '@/views/quiz/components/MarkdownSection'
 
 export default {
-  components: { MarkdownSection, BackTitle, TabSideComponent },
+  name: 'StudentDoQuiz',
+  components: { MarkdownSection, BackTitle },
   data() {
     return {
       quiz: createDefaultQuiz(),

+ 1 - 0
src/views/student/components/StudentQuizDetail.vue

@@ -21,6 +21,7 @@ import { QuizState } from '@/server/enums/quiz-state'
 import { createDefaultQuiz } from '@/views/quiz/utils/default'
 
 export default {
+  name: 'StudentQuizDetail',
   components: { QuizDetailFactory, TabSideComponent, BackTitle },
   data() {
     return {