Parcourir la source

feat: 创建测试界面

ChenYangfan il y a 6 ans
Parent
commit
2b98f45303

+ 3 - 1
src/components/Basic/CInput.vue

@@ -1,6 +1,7 @@
 <template>
   <component
     class="c-input"
+    :required="required"
     :is="textarea ? 'textarea' : 'input'"
     :type="type ? type : 'text'"
     :placeholder="placeholder"
@@ -21,7 +22,8 @@ export default {
     placeholder: String,
     textarea: Boolean,
     type: String,
-    maxlength: Number
+    maxlength: Number,
+    required: Boolean
   }
 }
 </script>

+ 1 - 1
src/components/Basic/IconButton.vue

@@ -65,7 +65,7 @@ export default {
 
 .icon-button {
   position: relative;
-  display: flex;
+  display: inline-flex;
   align-items: center;
   justify-content: center;
   padding: 8px;

+ 1 - 1
src/components/Editor.vue

@@ -85,5 +85,5 @@ export default {
 </script>
 
 <style lang="scss">
-@import '~@/style/quiz-editor.scss';
+@import '~@/style/editor.scss';
 </style>

+ 4 - 2
src/style/quiz-editor.scss → src/style/editor.scss

@@ -1,13 +1,15 @@
+@import "~@/style/theme.scss";
 .editor {
-  font-family: 'Inter';
+  font-family: 'Inter', sans-serif;
   font-size: 16px;
-  color: black;
+  color: #3a4a5a;
   line-height: 1.5;
 
   &__content {
 
     strong {
       font-weight: bold;
+      color: darken($theme-blue, 10);
     }
 
     em {

+ 8 - 6
src/views/quiz/CreateQuiz.vue

@@ -1,18 +1,16 @@
 <template>
-  <div>
-    <back-title>创建测试</back-title>
+  <div class="create-quiz">
     <quiz-editor @submit="handleSubmit" />
   </div>
 </template>
 
 <script>
-import BackTitle from '@/components/Basic/BackTitle'
 import { createQuiz } from '@/server/quiz'
 import QuizEditor from '@/views/quiz/components/QuizEditor'
 
 export default {
   name: 'CreateQuiz',
-  components: { QuizEditor, BackTitle },
+  components: { QuizEditor },
   methods: {
     handleSubmit(quizData) {
       const { slideId } = this.$route.params
@@ -27,6 +25,10 @@ export default {
 }
 </script>
 
-<style scoped>
-
+<style lang="scss" scoped>
+.create-quiz {
+  margin: 0 auto;
+  max-width: 1100px;
+  padding: 32px 24px;
+}
 </style>

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

@@ -27,7 +27,7 @@ export default {
 <style lang="scss" scoped>
 .questions {
   margin: 0 auto;
-  max-width: 900px;
+  max-width: 1100px;
   padding: 64px 24px;
   min-height: 90vh;
   align-items: center;

+ 14 - 12
src/views/quiz/components/ChoiceQuestionDetail.vue

@@ -1,27 +1,27 @@
 <template>
   <div>
-    <div v-if="!isEmpty(value.pass)">{{ value.pass ? "回答正确" : "回答错误" }}</div>
+    <div v-if="!isEmpty(value.pass)">{{ value.pass ? '回答正确' : '回答错误' }}</div>
     <editor-section title="题干">
-      <markdown-section :raw="value.stem" />
+      <markdown-section :raw="value.stem"/>
     </editor-section>
-    <editor-section title="选项">
+    <editor-section title="选项" v-if="!fold">
       <section
         v-for="(item, index) in value.options"
         :key="index"
         class="choice"
       >
-        <div class="choice__index">{{ index }}.</div>
-        <markdown-section :raw="item" />
+        <div class="choice__index">{{ index }}</div>
+        <markdown-section :raw="item"/>
       </section>
     </editor-section>
-    <editor-section v-if="!isEmpty(value.studentAnswer)" title="你的答案">
+    <editor-section v-if="!isEmpty(value.studentAnswer) && !fold" title="你的答案">
       {{ value.studentAnswer }}
     </editor-section>
-    <editor-section title="正确答案">
+    <editor-section title="正确答案" v-if="!fold">
       {{ value.answer }}
     </editor-section>
-    <editor-section title="解析">
-      <markdown-section :raw="value.analysis" />
+    <editor-section title="解析" v-if="!fold">
+      <markdown-section :raw="value.analysis"/>
     </editor-section>
   </div>
 </template>
@@ -40,6 +40,10 @@ export default {
     value: {
       type: Object,
       default: createDefaultChoiceQuestion
+    },
+    fold: {
+      type: Boolean,
+      default: false
     }
   },
   methods: {
@@ -50,7 +54,5 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-  .choice {
-    display: flex;
-  }
+@import "~@/views/quiz/style/question-detail.scss";
 </style>

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

@@ -1,6 +1,6 @@
 <template>
   <section class="editor-section">
-    <h3 class="mb-8">{{ title  }}</h3>
+    <h3 class="mb-8">{{ title  }}</h3>
     <slot />
   </section>
 </template>

+ 37 - 21
src/views/quiz/components/QuestionCard.vue

@@ -1,18 +1,23 @@
 <template>
   <div class="question-card">
     <div class="question-card__header mb-8">
-      <span>
-        <span class="question-id">{{ questionId }}</span>
+      <h3>
+        <icon-button :size="32" :left="-6" theme="red" v-if="selectable" title="点击选择 / 取消选择题目"
+                     @click="$emit('toggle-select', questionId)" :no-bold="selected">
+          {{selected ? 'check_box' : 'check_box_outline_blank'}}
+        </icon-button>
+        <span class="question-id">编号:{{ questionId }}</span>
         <c-tag theme="green">{{ QuestionKind.naming[kind] }}</c-tag>
-      </span>
-      <span>
-        <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="{ name: 'edit-question', params: { questionId }}">修改</router-link>
-        <router-link class="action" style="margin-left: 16px;" :to="{ name: 'question-detail', params: { questionId }}">详情</router-link>
-      </span>
+      </h3>
+      <div class="spacer"></div>
+      <div class="actions">
+        <router-link :to="{ name: 'edit-question', params: { questionId }}">
+          <icon-button theme="green" :size="32">edit</icon-button>
+        </router-link>
+        <router-link :to="{ name: 'question-detail', params: { questionId }}">
+          <icon-button no-bold theme="blue" :size="32">more_horiz</icon-button>
+        </router-link>
+      </div>
     </div>
     <markdown-section :raw="stem"/>
   </div>
@@ -22,10 +27,11 @@
 import * as QuestionKind from '@/server/enums/question-kind'
 import MarkdownSection from '@/views/quiz/components/MarkdownSection'
 import CTag from '@/components/Basic/CTag'
+import IconButton from '@/components/Basic/IconButton'
 
 export default {
   name: 'QuestionCard',
-  components: { CTag, MarkdownSection },
+  components: { IconButton, CTag, MarkdownSection },
   props: {
     stem: {
       type: String,
@@ -57,21 +63,31 @@ export default {
 <style lang="scss" scoped>
 @import "~@/style/theme.scss";
 
-.question-id {
-  font-weight: bold;
+.question-card {
+  padding: 12px;
+  position: relative;
 }
 
 .question-card__header {
   display: flex;
-  align-items: center;
-  justify-content: space-between;
-}
 
-.action {
-  color: $theme-blue;
+  h3 {
+    display: flex;
+    align-items: center;
+  }
 
-  &:hover {
-    color: darken($theme-blue, 10);
+  .spacer {
+    flex: 1;
   }
 }
+
+.c-button {
+  margin-left: 8px;
+  padding: 2px 6px;
+}
+
+.question-id {
+  font-size: 14px;
+  color: #7a8a9a;
+}
 </style>

+ 6 - 2
src/views/quiz/components/QuestionDetailFactory.vue

@@ -1,6 +1,6 @@
 <template>
-  <choice-question-detail v-if="question.kind === QuestionKind.choice" :value="question" />
-  <true-false-question-detail v-else-if="question.kind === QuestionKind.trueOrFalse" :value="question" />
+  <choice-question-detail :fold="fold" v-if="question.kind === QuestionKind.choice" :value="question" />
+  <true-false-question-detail :fold="fold" v-else-if="question.kind === QuestionKind.trueOrFalse" :value="question" />
 </template>
 
 <script>
@@ -15,6 +15,10 @@ export default {
     question: {
       type: Object,
       required: true
+    },
+    fold: {
+      type: Boolean,
+      default: false
     }
   },
   data() {

+ 12 - 5
src/views/quiz/components/QuestionTable.vue

@@ -1,7 +1,9 @@
 <template>
   <div class="question-table">
     <search-bar class="mb-16" v-model="inputStem" placeholder="题干搜索" @input="debounceFetchQuestions"/>
-    <div>
+    <pagination class="mt-16 mb-32" :page="page.number" :size="page.size" :total-elements="page.totalElements"
+                @change="fetchQuestionList"></pagination>
+    <div class="question-card-container">
       <question-card
         v-for="item in questionList"
         :key="item.id"
@@ -13,8 +15,6 @@
         @toggle-select="handleToggleSelectQuestion(item)"
       />
     </div>
-    <pagination class="mt-16" :page="page.number" :size="page.size" :total-elements="page.totalElements"
-                @change="fetchQuestionList"></pagination>
   </div>
 </template>
 
@@ -87,8 +87,15 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+.question-card-container {
+  column-width: 300px;
+  column-gap: 24px;
+}
+
 .question-card {
-  padding: 16px 0;
-  border-bottom: 1px solid rgba(12, 36, 124, 0.12);
+  margin-bottom: 24px;
+  break-inside: avoid;
+  border-radius: 6px;
+  border: 1px solid #cadaea;
 }
 </style>

+ 110 - 26
src/views/quiz/components/QuizEditor.vue

@@ -1,25 +1,34 @@
 <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 class="quiz-editor">
+    <div>
+      <back-title>创建测试</back-title>
+      <div class="quiz-form">
+        <div class="sub-title mb-16">测试详情</div>
+        <div class="input-title mb-8">
+          测试名称:
+        </div>
+        <c-input class="mb-24" v-model="quiz.name" placeholder="请输入测试名称"></c-input>
+        <div class="input-title mb-8">
+          开放测试所需课件状态:
+        </div>
+        <c-select disabled-display="[ 未选择 ]" v-model="quiz.quizTime" class="mb-24" :list="selectItems"></c-select>
+        <c-button theme="green" icon="check" block @click="handleSubmit">创建测试</c-button>
+      </div>
+      <div class="selected-question-card mt-24 mb-24" :key="question.id" v-for="(question, index) in selectedQuestions">
+        <div class="selected-question-card-header">
+          <div>题号:<span>{{ index + 1 }}</span></div>
+          <div class="spacer"></div>
+          <c-button @click="moveIndexQuestionUp(index)" text theme="blue" icon="arrow_upward">上移</c-button>
+          <c-button @click="moveIndexQuestionUp(index + 1)" :right="-8" text theme="green" icon="arrow_downward">下移</c-button>
+        </div>
+        <question-detail-factory :question="question"/>
+      </div>
     </div>
-    <question-table v-model="selectedQuestions" selectable />
-    <button>提交</button>
-  </form>
+    <div>
+      <div class="sub-title">题库</div>
+      <question-table v-model="selectedQuestions" selectable/>
+    </div>
+  </div>
 </template>
 
 <script>
@@ -28,40 +37,54 @@ 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'
+import CInput from '@/components/Basic/CInput'
+import CButton from '@/components/Basic/CButton'
+import CSelect from '@/components/Basic/CSelect'
+import BackTitle from '@/components/Basic/BackTitle'
 
 export default {
   name: 'QuizEditor',
-  components: { QuestionDetailFactory, QuestionTable },
+  components: { BackTitle, CSelect, CButton, CInput, QuestionDetailFactory, QuestionTable },
   props: {
     initQuiz: {
       type: Object,
       default: () => ({})
     }
   },
-  data() {
+  data () {
     return {
       quiz: { name: '', quizTime: '' },
       selectedQuestions: [],
+      unfoldList: {},
 
       QuestionKindNaming,
       QuizStateNaming,
       SlideStateTranslation
     }
   },
+  computed: {
+    selectItems () {
+      return Object.keys(this.SlideStateTranslation).map(key => ({
+        value: key,
+        display: this.SlideStateTranslation[key]
+      }))
+    }
+  },
   watch: {
     initQuiz: {
       immediate: true,
-      handler({ id, name, quizTime, questions = [] } = {}) {
+      handler ({ id, name, quizTime, questions = [] } = {}) {
         this.quiz = { id, name, quizTime }
         this.selectedQuestions = questions
       }
     }
   },
   methods: {
-    handleSubmit() {
+    handleSubmit () {
+      this.$setInfoMessage('正在上传测试内容')
       this.$emit('submit', { ...this.quiz, questions: this.selectedQuestions.map(item => item.id) })
     },
-    moveIndexQuestionUp(index) {
+    moveIndexQuestionUp (index) {
       if (index < this.selectedQuestions.length && index >= 1) {
         const nextQuestions = [...this.selectedQuestions]
         const before = nextQuestions[index - 1]
@@ -74,6 +97,67 @@ export default {
 }
 </script>
 
-<style scoped>
+<style lang="scss" scoped>
+@import "~@/style/theme.scss";
+
+.input-title {
+  font-size: 18px;
+  color: #5a6a7a;
+}
+
+.quiz-editor {
+  display: flex;
+  width: 100%;
+  margin: 0 -24px;
+  align-items: start;
+
+  & > div {
+    margin: 0 24px;
+    flex: 1;
+  }
+
+  .quiz-form {
+    box-sizing: border-box;
+    border: 1px solid #cadaea;
+    border-radius: 6px;
+    padding: 16px;
+  }
+}
+
+.selected-question-card {
+  border: 1px solid #cadaea;
+  border-radius: 6px;
+  padding: 8px 12px 12px 12px;
+  font-size: 12px;
 
+  .spacer {
+    flex: 1;
+  }
+
+  &-header {
+    font-size: 16px;
+    display: flex;
+    align-items: center;
+    padding: 8px 12px;
+    background-color: $theme-background-grey-darker;
+    margin: -8px -12px 8px -12px;
+  }
+}
+
+@media (max-width: 900px) {
+  .quiz-editor {
+    display: block;
+    margin: 0 auto;
+
+    & > div {
+      margin: unset;
+    }
+
+    & > div:last-child {
+      /*border-top: 1px #cadaea solid;*/
+      padding-top: 16px;
+      margin-top: 24px;
+    }
+  }
+}
 </style>

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

@@ -4,15 +4,15 @@
     <editor-section title="题干">
       <markdown-section :raw="value.stem" />
     </editor-section>
-    <editor-section v-if="!isEmpty(value.studentAnswer)" title="你的答案">
+    <editor-section v-if="!isEmpty(value.studentAnswer) && !fold" title="你的答案">
       <span v-if="value.studentAnswer" >√</span>
       <span v-else >×</span>
     </editor-section>
-    <editor-section title="正确答案">
+    <editor-section v-if="!fold" title="正确答案">
       <span v-if="value.answer" >√</span>
       <span v-else >×</span>
     </editor-section>
-    <editor-section title="解析">
+    <editor-section title="解析" v-if="!fold">
       <markdown-section :raw="value.analysis" />
     </editor-section>
   </div>
@@ -32,6 +32,10 @@ export default {
     value: {
       type: Object,
       default: createDefaultChoiceQuestion
+    },
+    fold: {
+      type: Boolean,
+      default: false
     }
   },
   methods: {

+ 16 - 0
src/views/quiz/style/question-detail.scss

@@ -0,0 +1,16 @@
+.choice {
+  display: flex;
+  align-items: center;
+
+  &__index {
+    margin-right: 4px;
+    &::after {
+      content: '. ';
+      margin-left: 2px;
+    }
+  }
+}
+
+.editor-section {
+  margin: 12px 0 24px 0;
+}