Parcourir la source

基础写作和看图写作

natasha il y a 1 an
Parent
commit
cab5b77954

+ 3 - 0
src/views/exercise_questions/create/components/create.vue

@@ -60,6 +60,7 @@ import ListenJudgeQuestion from './exercises/ListenJudgeQuestion.vue';
 import ListenFillQuestion from './exercises/ListenFillQuestion.vue';
 import WordCardQuestion from './exercises/WordCardQuestion.vue';
 import AnswerQuestion from './exercises/AnswerQuestion.vue';
+import WritePictureQuestion from './exercises/WritePictureQuestion.vue';
 
 export default {
   name: 'CreateMain',
@@ -83,6 +84,7 @@ export default {
     ListenFillQuestion,
     WordCardQuestion,
     AnswerQuestion,
+    WritePictureQuestion,
   },
   provide() {
     return {
@@ -124,6 +126,7 @@ export default {
         listen_fill: ListenFillQuestion,
         word_card: WordCardQuestion,
         answer_question: AnswerQuestion,
+        write_picture: WritePictureQuestion,
       },
     };
   },

+ 279 - 0
src/views/exercise_questions/create/components/exercises/WritePictureQuestion.vue

@@ -0,0 +1,279 @@
+<template>
+  <QuestionBase>
+    <template #content>
+      <div class="stem">
+        <el-input
+          v-if="data.property.stem_type === stemTypeList[0].value"
+          v-model="data.stem"
+          rows="3"
+          resize="none"
+          type="textarea"
+          placeholder="输入题干"
+        />
+
+        <RichText v-if="data.property.stem_type === stemTypeList[1].value" v-model="data.stem" placeholder="输入题干" />
+      </div>
+
+      <div class="content">
+        <div v-for="(item, index) in data.option_list" :key="index" class="content-item">
+          <div v-if="pic_list[item.picture_file_id]" class="content-pic">
+            <div class="item-left">
+              <el-image
+                style="width: 72px; height: 72px"
+                :src="pic_list[item.picture_file_id]"
+                :preview-src-list="[pic_list[item.picture_file_id]]"
+                fit="contain"
+              />
+              <button class="delete-btn" @click="delectOptions(index, item.picture_file_id)">
+                <i class="el-icon-delete"></i>删除
+              </button>
+            </div>
+            <div class="item-right">
+              <div class="item-rich">
+                <label class="">图片标题</label>
+                <RichText v-model="item.picture_title" placeholder="输入图片标题" />
+              </div>
+              <div class="item-rich">
+                <label class="">图片信息</label>
+                <RichText v-model="item.picture_info" placeholder="输入图片信息" />
+              </div>
+            </div>
+          </div>
+          <template v-if="isEnable(data.property.is_enable_description)">
+            <label class="title-little">题目要求:</label>
+            <el-input v-model="item.description" rows="3" resize="none" type="textarea" placeholder="输入题目要求" />
+          </template>
+          <label class="title-little">阅读材料:</label>
+          <RichText v-model="item.article" placeholder="输入阅读材料" />
+          <template v-if="isEnable(data.property.is_enable_sample_text)">
+            <el-divider class="write-divider" />
+            <label class="title-little">范文:</label>
+            <RichText v-model="item.sample_text" placeholder="输入范文" :wordlimit-num="5000" />
+            <p class="tips">多篇范文之间使用分割线(---)</p>
+          </template>
+        </div>
+        <UploadDrag @fileUploadSuccess="fileUploadSuccess" :limit="999" ref="uploadDrag"></UploadDrag>
+      </div>
+    </template>
+
+    <template #property>
+      <el-form :model="data.property">
+        <el-form-item label="题干">
+          <el-radio
+            v-for="{ value, label } in stemTypeList"
+            :key="value"
+            v-model="data.property.stem_type"
+            :label="value"
+          >
+            {{ label }}
+          </el-radio>
+        </el-form-item>
+        <el-form-item label="题号">
+          <el-input v-model="data.property.question_number" />
+        </el-form-item>
+        <el-form-item label-width="45px">
+          <el-radio
+            v-for="{ value, label } in questionNumberTypeList"
+            :key="value"
+            v-model="data.other.question_number_type"
+            :label="value"
+          >
+            {{ label }}
+          </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_description"
+            :label="value"
+          >
+            {{ label }}
+          </el-radio>
+        </el-form-item>
+        <el-form-item label="分值">
+          <el-radio
+            v-for="{ value, label } in scoreTypeList"
+            :key="value"
+            v-model="data.property.score_type"
+            :label="value"
+          >
+            {{ label }}
+          </el-radio>
+        </el-form-item>
+        <el-form-item label-width="45px">
+          <el-input-number
+            v-model="data.property.score"
+            :min="0"
+            :step="data.property.score_type === scoreTypeList[0].value ? 1 : 0.1"
+          />
+        </el-form-item>
+        <el-form-item label="词数">
+          <el-input-number :min="1" :step="10" v-model="data.property.word_num" class="word-num-input" :precision="0" />
+        </el-form-item>
+        <el-form-item label="范文">
+          <el-radio
+            v-for="{ value, label } in switchOption"
+            :key="value"
+            v-model="data.property.is_enable_sample_text"
+            :label="value"
+          >
+            {{ label }}
+          </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_upload_accessory"
+            :label="value"
+          >
+            {{ label }}
+          </el-radio>
+        </el-form-item>
+      </el-form>
+    </template>
+  </QuestionBase>
+</template>
+
+<script>
+import QuestionMixin from '../common/QuestionMixin.js';
+
+import { writePictrueData, getOption } from '@/views/exercise_questions/data/writePicture';
+import { GetFileStoreInfo } from '@/api/app';
+import UploadDrag from '../common/UploadDrag.vue';
+
+export default {
+  name: 'TalkPicture',
+  mixins: [QuestionMixin],
+  components: { UploadDrag },
+  data() {
+    return {
+      data: JSON.parse(JSON.stringify(writePictrueData)),
+      pic_list: {},
+      is_first: true,
+    };
+  },
+  watch: {
+    'data.file_id_list': {
+      handler() {
+        if (this.is_first) {
+          this.handleData();
+        }
+      },
+      deep: true,
+    },
+  },
+  created() {},
+  mounted() {},
+  methods: {
+    // 初始化数据
+    handleData() {
+      this.data.file_id_list.forEach((item) => {
+        GetFileStoreInfo({ file_id: item }).then(({ file_id, file_url }) => {
+          this.$set(this.pic_list, file_id, file_url);
+        });
+      });
+      this.is_first = false;
+    },
+    // 删除
+    delectOptions(i, id) {
+      this.$confirm('是否删除该条全部信息?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(() => {
+          delete this.pic_list[id];
+          this.data.file_id_list.splice(this.data.file_id_list.indexOf(id), 1);
+          this.data.option_list.splice(i, 1);
+          this.$refs.uploadDrag.clearFiles();
+        })
+        .catch(() => {});
+    },
+    fileUploadSuccess(file_id, file_url) {
+      this.data.file_id_list.push(file_id);
+      this.data.option_list.push(getOption());
+      this.data.option_list[this.data.option_list.length - 1].picture_file_id = file_id;
+      this.$set(this.pic_list, file_id, file_url);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.content {
+  :deep .el-upload {
+    width: 100%;
+
+    &-dragger {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      width: 100%;
+      height: 90px;
+      font-size: 14px;
+
+      :first-child {
+        color: #000;
+      }
+
+      :last-child {
+        color: $text-color;
+      }
+    }
+  }
+
+  .content-item {
+    .content-pic {
+      display: flex;
+      column-gap: 8px;
+      margin-bottom: 24px;
+    }
+  }
+
+  .delete-btn {
+    width: 100%;
+    padding: 5px 8px;
+    font-size: 14px;
+    line-height: 22px;
+    color: #f53f3f;
+    background: #fff4f4;
+    border: none;
+    border-radius: 4px;
+
+    .el-icon-delete {
+      margin-right: 8px;
+    }
+
+    &:hover {
+      color: #f53f3f;
+    }
+
+    &:focus {
+      outline: none;
+    }
+  }
+
+  .item-left {
+    width: 72px;
+  }
+
+  .item-right {
+    flex: 1;
+
+    .item-rich {
+      display: flex;
+
+      > label {
+        flex-shrink: 0;
+        width: 64px;
+        font-size: 14px;
+        line-height: 32px;
+        color: #4e5969;
+      }
+    }
+  }
+}
+</style>

+ 0 - 10
src/views/exercise_questions/create/components/exercises/WriteQuestion.vue

@@ -100,16 +100,6 @@
             {{ label }}
           </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_voice_answer"
-            :label="value"
-          >
-            {{ label }}
-          </el-radio>
-        </el-form-item>
         <el-form-item label="上传附件">
           <el-radio
             v-for="{ value, label } in switchOption"

+ 3 - 0
src/views/exercise_questions/create/index.vue

@@ -88,6 +88,7 @@ import ListenFillPreview from '../preview/ListenFillPreview.vue';
 import ListenJudgePreview from '../preview/ListenJudgePreview.vue';
 import WordCardPreview from '../preview/WordCardPreview.vue';
 import AnswerQuestionPreview from '../preview/AnswerQuestionPreview.vue';
+import WritePictruePreview from '../preview/WritePictruePreview.vue';
 
 export default {
   name: 'CreateExercise',
@@ -111,6 +112,7 @@ export default {
     ListenJudgePreview,
     WordCardPreview,
     AnswerQuestionPreview,
+    WritePictruePreview,
   },
   provide() {
     return {
@@ -152,6 +154,7 @@ export default {
         listen_judge: ListenJudgePreview,
         word_card: WordCardPreview,
         answer_question: AnswerQuestionPreview,
+        write_picture: WritePictruePreview,
       },
     };
   },

+ 8 - 4
src/views/exercise_questions/data/common.js

@@ -43,12 +43,16 @@ export const questionTypeOption = [
     ],
   },
   {
-    value: 'read',
-    label: '阅读题',
+    value: 'writing',
+    label: '写作题',
+    children: [
+      { label: '基础写作', value: 'write' },
+      { label: '看图写作', value: 'write_picture' },
+    ],
   },
   {
-    value: 'write',
-    label: '写作题',
+    value: 'read',
+    label: '阅读题',
   },
 ];
 

+ 0 - 1
src/views/exercise_questions/data/write.js

@@ -17,7 +17,6 @@ export const writeData = {
     score_type: scoreTypeList[0].value, // 分值类型
     word_num: 50, // 词数
     is_enable_sample_text: 'true', // 范文开启
-    is_enable_voice_answer: 'true', // 语音作答
     is_enable_upload_accessory: 'true', // 上传附件
   },
   // 其他属性

+ 36 - 0
src/views/exercise_questions/data/writePicture.js

@@ -0,0 +1,36 @@
+import { stemTypeList, scoreTypeList, questionNumberTypeList, switchOption } from './common';
+import { getRandomNumber } from '@/utils/index';
+export function getOption() {
+  return {
+    picture_title: '',
+    picture_info: '',
+    reference_answer: '',
+    picture_file_id: '',
+    mark: getRandomNumber(),
+    description: '',
+    article: '',
+    sample_text: '',
+  };
+}
+export const writePictrueData = {
+  type: 'write_picture', // 题型
+  stem: '', // 题干
+  option_list: [], // 选项
+  file_id_list: [],
+  answer: { score: 0, score_type: scoreTypeList[0].value }, // 答案
+  // 题型属性
+  property: {
+    stem_type: stemTypeList[0].value, // 题干类型
+    question_number: '1', // 题号
+    score: 1, // 分值
+    is_enable_description: switchOption[1].value, // 描述
+    score_type: scoreTypeList[0].value, // 分值类型
+    word_num: 50, // 词数
+    is_enable_sample_text: 'true', // 范文开启
+    is_enable_upload_accessory: 'true', // 上传附件
+  },
+  // 其他属性
+  other: {
+    question_number_type: questionNumberTypeList[0].value, // 题号类型
+  },
+};

+ 247 - 0
src/views/exercise_questions/preview/WritePictruePreview.vue

@@ -0,0 +1,247 @@
+<!-- eslint-disable vue/no-v-html -->
+<template>
+  <div class="writepicture-preview">
+    <div class="stem">
+      <span class="question-number">{{ data.property.question_number }}.</span>
+      <span v-html="sanitizeHTML(data.stem)"></span>
+    </div>
+    <div class="article-content" v-html="sanitizeHTML(data.option_list[active_index].article)"></div>
+    <div v-if="isEnable(data.property.is_enable_description)" class="description">
+      {{ data.option_list[active_index].description }}
+    </div>
+    <div class="content">
+      <div class="content-left">
+        <el-carousel
+          type="card"
+          height="276px"
+          :autoplay="false"
+          indicator-position="none"
+          arrow="never"
+          @change="changeImg"
+        >
+          <el-carousel-item v-for="(item, index) in data.option_list" :key="index">
+            <el-image
+              v-if="pic_list[item.picture_file_id]"
+              style="width: 370px; height: 276px"
+              :src="pic_list[item.picture_file_id]"
+              fit="contain"
+            />
+          </el-carousel-item>
+        </el-carousel>
+        <h3 class="pic-title" v-html="sanitizeHTML(data.option_list[active_index].picture_title)"></h3>
+        <p class="pic-info" v-html="sanitizeHTML(data.option_list[active_index].picture_info)"></p>
+      </div>
+      <div class="content-right">
+        <el-input
+          v-model="answer_list[active_index].value"
+          rows="12"
+          resize="none"
+          type="textarea"
+          placeholder="请输入"
+          :maxlength="data.property.word_num"
+          show-word-limit
+          @input="handleInput"
+        />
+      </div>
+    </div>
+    <template v-if="isEnable(data.property.is_enable_upload_accessory)">
+      <!-- 上传附件 -->
+      <UploadFiles
+        :fille-number="999"
+        file-type-name="文件"
+        :upload-type="'*'"
+        :file-id-list="answer_list[active_index].accessory_file_id"
+        upload-title="上传附件:"
+        @upload="handleUpload"
+        @deleteFile="handleDelete"
+      />
+    </template>
+    <template v-if="isEnable(data.property.is_enable_sample_text)">
+      <el-divider content-position="center"
+        ><span
+          :class="['sample-text', show_sample_text ? 'sample-show' : 'sample-hide']"
+          @click="show_sample_text = !show_sample_text"
+          >{{ show_sample_text ? '隐藏范文' : '查看范文' }}</span
+        ></el-divider
+      >
+      <div
+        v-if="show_sample_text"
+        class="article-content"
+        v-html="sanitizeHTML(answer_list[active_index].sample_text)"
+      ></div>
+    </template>
+  </div>
+</template>
+
+<script>
+import PreviewMixin from './components/PreviewMixin';
+import { GetFileStoreInfo } from '@/api/app';
+import SoundRecordPreview from './components/common/SoundRecordPreview.vue';
+import UploadFiles from './components/common/UploadFiles.vue';
+
+export default {
+  name: 'TalkPictruePreview',
+  components: {
+    SoundRecordPreview,
+    UploadFiles,
+  },
+  mixins: [PreviewMixin],
+  data() {
+    return {
+      show_sample_text: false,
+      pic_list: {},
+      active_index: 0,
+      answer_list: [],
+    };
+  },
+  created() {
+    this.handleData();
+  },
+  methods: {
+    // 初始化数据
+    handleData() {
+      this.answer_list = [];
+      this.pic_list = {};
+      this.active_index = 0;
+      this.data.file_id_list.forEach((item) => {
+        GetFileStoreInfo({ file_id: item }).then(({ file_id, file_url }) => {
+          this.$set(this.pic_list, file_id, file_url);
+        });
+      });
+      this.data.option_list.forEach((item) => {
+        let obj = {
+          mark: item.mark,
+          value: '',
+          audio_file_id: '',
+          accessory_file_id: [], // 上传文件列表
+        };
+        this.answer_list.push(obj);
+      });
+    },
+    changeImg(index) {
+      this.active_index = index;
+    },
+    // 文件上传成功
+    handleUpload(fileId) {
+      this.user_answer[this.active_index].accessory_file_id.push(fileId);
+    },
+    // 删除文件
+    handleDelete(fileId) {
+      this.user_answer[this.active_index].accessory_file_id.splice(
+        this.user_answer.accessory_file_id.indexOf(fileId),
+        1,
+      );
+    },
+    handleInput(value) {
+      if (value.length >= this.data.property.word_num) {
+        this.$message.warning(`字数达到${value.length}字!`);
+      }
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+@use '@/styles/mixin.scss' as *;
+
+.writepicture-preview {
+  @include preview;
+
+  :deep p {
+    margin: 0;
+  }
+
+  .content {
+    display: flex;
+    column-gap: 24px;
+
+    &-left {
+      flex-shrink: 0;
+      width: 478px;
+
+      :deep .el-carousel__item--card {
+        width: 77%;
+        margin-left: -13.5%;
+      }
+
+      .el-image {
+        opacity: 0.2;
+      }
+
+      .el-carousel__item--card.is-active {
+        .el-image {
+          background: #fff;
+          opacity: 1;
+        }
+      }
+
+      .pic-title {
+        margin: 8px 0 4px;
+        font-size: 12px;
+        font-weight: 600;
+        line-height: 20px;
+        color: #000;
+        word-break: break-word;
+      }
+
+      .pic-info {
+        margin: 0;
+        font-size: 12px;
+        font-weight: 400;
+        line-height: 20px;
+        color: #000;
+        word-break: break-word;
+      }
+    }
+
+    &-right {
+      flex: 1;
+
+      .el-textarea {
+        height: 276px;
+        margin-bottom: 16px;
+      }
+    }
+  }
+
+  .reference-box {
+    padding: 12px;
+    background: #f9f8f9;
+
+    .reference-title {
+      margin: 0 0 10px;
+      font-size: 14px;
+      font-weight: 400;
+      line-height: 32px;
+      color: #4e5969;
+    }
+  }
+
+  .el-divider--horizontal {
+    margin: 12px 0;
+  }
+
+  .sample-text {
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 30px;
+    color: #000;
+    cursor: pointer;
+
+    &.sample-show {
+      color: $light-main-color;
+    }
+  }
+
+  .article-content {
+    :deep p {
+      margin: 0;
+    }
+  }
+
+  :deep .el-textarea .el-input__count {
+    bottom: 15px;
+    background-color: #f2f3f5;
+  }
+}
+</style>

+ 1 - 7
src/views/exercise_questions/preview/WritePreview.vue

@@ -16,10 +16,7 @@
       show-word-limit
       @input="handleInput"
     />
-    <template v-if="isEnable(data.property.is_enable_voice_answer)">
-      <!-- 语音作答 -->
-      <SoundRecordPreview :wav-blob.sync="user_answer.voice_file_id" />
-    </template>
+
     <template v-if="isEnable(data.property.is_enable_upload_accessory)">
       <!-- 上传附件 -->
       <UploadFiles
@@ -46,14 +43,12 @@
 </template>
 
 <script>
-import SoundRecordPreview from './components/common/SoundRecordPreview.vue';
 import PreviewMixin from './components/PreviewMixin';
 import UploadFiles from './components/common/UploadFiles.vue';
 
 export default {
   name: 'WritePreview',
   components: {
-    SoundRecordPreview,
     UploadFiles,
   },
   mixins: [PreviewMixin],
@@ -62,7 +57,6 @@ export default {
       show_sample_text: false,
       user_answer: {
         text: '', // 用户文章
-        voice_file_id: '', // 录音内容
         accessory_file_id: [], // 上传文件列表
       },
     };