dusenyao 1 rok pred
rodič
commit
0e70305b70

+ 61 - 0
src/layouts/answer/header/index.vue

@@ -0,0 +1,61 @@
+<template>
+  <header class="header">
+    <el-image class="logo" :src="$store.state.app.config.logo_image_url" />
+    <div v-if="!token" class="selectLoginOrRegistration">
+      <span>登录</span>
+    </div>
+    <!-- 用户头像和用户名 -->
+    <el-dropdown v-else trigger="click" class="user">
+      <span class="el-dropdown-link">
+        <img
+          class="avatar"
+          :src="token.image_url ? token.image_url : require('@/assets/header/avatar-default.png')"
+          alt="head portrait"
+        />
+        <span class="real_name">{{ token.user_real_name }}</span>
+      </span>
+      <el-dropdown-menu slot="dropdown" class="user-menu">
+        <el-dropdown-item @click.native="logout">
+          <img :src="require('@/assets/header/exit.png')" /><span>退出登录</span>
+        </el-dropdown-item>
+      </el-dropdown-menu>
+    </el-dropdown>
+  </header>
+</template>
+
+<script>
+import { getToken } from '@/utils/auth';
+
+export default {
+  name: 'LayoutAnswerHeader',
+  data() {
+    const token = getToken();
+
+    return {
+      token,
+    };
+  },
+  methods: {
+    logout() {
+      this.$store.dispatch('user/signOut');
+      window.location.href = '/';
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.header {
+  position: sticky;
+  top: 0;
+  left: 0;
+  display: flex;
+  column-gap: 24px;
+  align-items: center;
+  height: $header-h;
+  padding: 0 24px;
+  overflow: hidden;
+  background-color: #fff;
+  border-bottom: 1px solid #ebebeb;
+}
+</style>

+ 34 - 0
src/layouts/answer/index.vue

@@ -0,0 +1,34 @@
+<template>
+  <div class="answer">
+    <LayoutHeader />
+    <div class="answer-container">
+      <router-view />
+    </div>
+  </div>
+</template>
+
+<script>
+import LayoutHeader from './header/index.vue';
+
+export default {
+  name: 'LayoutAnswer',
+  components: {
+    LayoutHeader,
+  },
+  data() {
+    return {};
+  },
+  methods: {},
+};
+</script>
+
+<style lang="scss" scoped>
+.answer {
+  display: flex;
+  flex-direction: column;
+
+  &-container {
+    overflow: auto;
+  }
+}
+</style>

+ 1 - 1
src/layouts/default/header/index.vue

@@ -14,7 +14,7 @@
     </ul>
 
     <div v-if="!token" class="selectLoginOrRegistration">
-      <span @click="cutLoginReg">登录</span>
+      <span>登录</span>
     </div>
     <!-- 用户头像和用户名 -->
     <el-dropdown v-else trigger="click" class="user">

+ 18 - 1
src/router/modules/exercise.js

@@ -1,4 +1,5 @@
 import DEFAULT from '@/layouts/default';
+import ANSWER from '@/layouts/answer';
 
 /**
  * 练习管理创建
@@ -15,4 +16,20 @@ const ExerciseCreatePage = {
   ],
 };
 
-export default [ExerciseCreatePage];
+/**
+ * 练习题答题
+ */
+const AnswerPage = {
+  path: '/answer',
+  component: ANSWER,
+  redirect: 'Answer',
+  children: [
+    {
+      path: '/answer',
+      name: 'Answer',
+      component: () => import('@/views/exercise_questions/answer/index.vue'),
+    },
+  ],
+};
+
+export default [ExerciseCreatePage, AnswerPage];

+ 15 - 0
src/views/exercise_questions/answer/index.vue

@@ -0,0 +1,15 @@
+<template>
+  <div></div>
+</template>
+
+<script>
+export default {
+  name: 'AnswerPage',
+  data() {
+    return {};
+  },
+  methods: {},
+};
+</script>
+
+<style lang="scss" scoped></style>

+ 1 - 1
src/views/exercise_questions/create/components/exercises/FillQuestion.vue

@@ -26,7 +26,7 @@
           rows="3"
           resize="none"
           type="textarea"
-          placeholder="输入描述"
+          placeholder="输入填空内容"
         />
       </div>
 

+ 1 - 1
src/views/exercise_questions/create/components/exercises/ListenFillQuestion.vue

@@ -26,7 +26,7 @@
           rows="3"
           resize="none"
           type="textarea"
-          placeholder="输入描述"
+          placeholder="输入填空内容"
         />
       </div>
 

+ 0 - 19
src/views/exercise_questions/create/components/exercises/ReadAloudQuestion.vue

@@ -28,14 +28,6 @@
           @upload="upload"
           @deleteFile="deleteFile"
         />
-
-        <el-input
-          v-if="isEnable(data.property.is_enable_reference_answer)"
-          v-model="data.reference_answer"
-          type="textarea"
-          rows="3"
-          placeholder="输入参考答案"
-        />
       </div>
     </template>
 
@@ -87,17 +79,6 @@
           </el-radio>
         </el-form-item>
 
-        <el-form-item label="参考答案">
-          <el-radio
-            v-for="{ value, label } in switchOption"
-            :key="value"
-            v-model="data.property.is_enable_reference_answer"
-            :label="value"
-          >
-            {{ label }}
-          </el-radio>
-        </el-form-item>
-
         <el-form-item label="分值">
           <el-radio
             v-for="{ value, label } in scoreTypeList"

+ 0 - 2
src/views/exercise_questions/data/readAloud.js

@@ -5,7 +5,6 @@ export const readAloudData = {
   type: 'read_aloud', // 题型
   stem: '', // 题干
   description: '', // 描述
-  reference_answer: '', // 参考答案
   file_id_list: [], // 文件 id 列表
   answer: {
     score: 0,
@@ -17,7 +16,6 @@ export const readAloudData = {
     question_number: '1', // 题号
     is_enable_listening: switchOption[0].value, // 是否开启听力
     is_enable_description: switchOption[0].value, // 是否启用描述
-    is_enable_reference_answer: switchOption[0].value, // 是否开启参考答案
     score: 1, // 分值
     score_type: scoreTypeList[0].value, // 分值类型
   },

+ 13 - 4
src/views/exercise_questions/preview/FillPreview.vue

@@ -17,13 +17,14 @@
     />
 
     <div class="fill-wrapper">
-      <p v-for="(item, i) in data.model_essay" :key="i">
+      <p v-for="(item, i) in modelEssay" :key="i">
         <template v-for="(li, j) in item">
           <span v-if="li.type === 'text'" :key="j" v-html="sanitizeHTML(li.content)"></span>
           <el-input
             v-if="li.type === 'input'"
             :key="j"
             v-model="li.content"
+            :style="{ width: Math.max(80, li.content.length * 16) + 'px' }"
             @focus="handleInputFocus(i, j)"
             @blur="handleTone(li.content, i, j)"
           />
@@ -45,6 +46,7 @@ export default {
   data() {
     return {
       inputFocus: false,
+      modelEssay: [],
       focusPostion: {
         i: -1,
         j: -1,
@@ -60,6 +62,14 @@ export default {
     'data.model_essay': {
       handler(val) {
         if (!val) return;
+        this.modelEssay = JSON.parse(JSON.stringify(val));
+      },
+      deep: true,
+      immediate: true,
+    },
+    modelEssay: {
+      handler(val) {
+        if (!val) return;
         this.answer.answer_list = val
           .map((item) => {
             return item
@@ -104,15 +114,14 @@ export default {
       };
     },
     selectedDescription(text) {
-      console.log(2);
       if (!this.inputFocus) return;
       const { i, j } = this.focusPostion;
-      this.data.model_essay[i][j].content = text;
+      this.modelEssay[i][j].content = text;
       this.inputFocus = false;
     },
     handleTone(value, i, j) {
       if (!/^[a-zA-Z0-9\s]+$/.test(value)) return;
-      this.data.model_essay[i][j].content = value
+      this.modelEssay[i][j].content = value
         .trim()
         .split(/\s+/)
         .map((item) => {

+ 80 - 5
src/views/exercise_questions/preview/ListenFillPreview.vue

@@ -5,7 +5,11 @@
       <span class="question-number">{{ data.property.question_number }}.</span>
       <span v-html="sanitizeHTML(data.stem)"></span>
     </div>
-    <div v-if="isEnable(data.property.is_enable_description)" class="description">{{ data.description }}</div>
+    <div v-if="isEnable(data.property.is_enable_description)" class="description">
+      <span v-for="(text, i) in descriptionList" :key="i" class="description-item" @click="selectedDescription(text)">
+        {{ text }}
+      </span>
+    </div>
 
     <AudioPlay
       v-if="isEnable(data.property.is_enable_listening) && data.file_id_list.length > 0"
@@ -13,10 +17,17 @@
     />
 
     <div class="fill-wrapper">
-      <p v-for="(item, i) in data.model_essay" :key="i">
+      <p v-for="(item, i) in modelEssay" :key="i">
         <template v-for="(li, j) in item">
           <span v-if="li.type === 'text'" :key="j" v-html="sanitizeHTML(li.content)"></span>
-          <el-input v-if="li.type === 'input'" :key="j" v-model="li.content" @blur="handleTone(li.content, i, j)" />
+          <el-input
+            v-if="li.type === 'input'"
+            :key="j"
+            v-model="li.content"
+            :style="{ width: Math.max(80, li.content.length * 16) + 'px' }"
+            @focus="handleInputFocus(i, j)"
+            @blur="handleTone(li.content, i, j)"
+          />
         </template>
       </p>
     </div>
@@ -33,12 +44,32 @@ export default {
   name: 'ListenFillPreview',
   mixins: [PreviewMixin],
   data() {
-    return {};
+    return {
+      inputFocus: false,
+      modelEssay: [],
+      focusPostion: {
+        i: -1,
+        j: -1,
+      },
+    };
+  },
+  computed: {
+    descriptionList() {
+      return this.data.description.split(/\s+/);
+    },
   },
   watch: {
     'data.model_essay': {
       handler(val) {
         if (!val) return;
+        this.modelEssay = JSON.parse(JSON.stringify(val));
+      },
+      deep: true,
+      immediate: true,
+    },
+    modelEssay: {
+      handler(val) {
+        if (!val) return;
         this.answer.answer_list = val
           .map((item) => {
             return item
@@ -58,10 +89,39 @@ export default {
       immediate: true,
     },
   },
+  created() {
+    document.addEventListener('click', this.handleBlur);
+    document.addEventListener('keydown', this.handleBlurTab);
+  },
+  beforeDestroy() {
+    document.removeEventListener('click', this.handleBlur);
+    document.removeEventListener('keydown', this.handleBlurTab);
+  },
   methods: {
+    handleBlur(e) {
+      if (e.target.tagName === 'INPUT') return;
+      this.inputFocus = false;
+    },
+    handleBlurTab(e) {
+      if (e.keyCode !== 9 || e.key !== 'Tab') return;
+      this.inputFocus = false;
+    },
+    handleInputFocus(i, j) {
+      this.inputFocus = true;
+      this.focusPostion = {
+        i,
+        j,
+      };
+    },
+    selectedDescription(text) {
+      if (!this.inputFocus) return;
+      const { i, j } = this.focusPostion;
+      this.modelEssay[i][j].content = text;
+      this.inputFocus = false;
+    },
     handleTone(value, i, j) {
       if (!/^[a-zA-Z0-9\s]+$/.test(value)) return;
-      this.data.model_essay[i][j].content = value
+      this.modelEssay[i][j].content = value
         .trim()
         .split(/\s+/)
         .map((item) => {
@@ -83,6 +143,21 @@ export default {
 .fill-preview {
   @include preview;
 
+  .description {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 4px 24px;
+
+    &-item {
+      cursor: pointer;
+
+      &:hover,
+      &:active {
+        color: $light-main-color;
+      }
+    }
+  }
+
   .fill-wrapper {
     .el-input {
       width: 120px;

+ 1 - 2
src/views/exercise_questions/preview/ReadAloudPreview.vue

@@ -13,7 +13,6 @@
       :file-id="data.file_id_list[0]"
     />
 
-    <el-input v-model="answer.answer_list[0].text" type="textarea" :autosize="{ minRows: 6, maxRows: 36 }" />
     <SoundRecordPreview :wav-blob.sync="answer.answer_list[0].voice_file_id" />
   </div>
 </template>
@@ -29,7 +28,7 @@ export default {
   },
   mixins: [PreviewMixin],
   created() {
-    this.$set(this.answer.answer_list, 0, { text: '', voice_file_id: '' });
+    this.$set(this.answer.answer_list, 0, { voice_file_id: '' });
   },
   methods: {},
 };