|  | @@ -0,0 +1,300 @@
 | 
	
		
			
				|  |  | +<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="输入题干" />
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        <UploadAudio
 | 
	
		
			
				|  |  | +          v-show="isEnable(data.property.is_enable_listening)"
 | 
	
		
			
				|  |  | +          :file-id="data.file_id_list?.[0]"
 | 
	
		
			
				|  |  | +          @upload="upload"
 | 
	
		
			
				|  |  | +          @deleteFile="deleteFile"
 | 
	
		
			
				|  |  | +        />
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        <el-input
 | 
	
		
			
				|  |  | +          v-show="isEnable(data.property.is_enable_description)"
 | 
	
		
			
				|  |  | +          v-model="data.description"
 | 
	
		
			
				|  |  | +          rows="3"
 | 
	
		
			
				|  |  | +          resize="none"
 | 
	
		
			
				|  |  | +          type="textarea"
 | 
	
		
			
				|  |  | +          placeholder="输入描述"
 | 
	
		
			
				|  |  | +        />
 | 
	
		
			
				|  |  | +      </div>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      <div class="content">
 | 
	
		
			
				|  |  | +        <RichText
 | 
	
		
			
				|  |  | +          ref="modelEssay"
 | 
	
		
			
				|  |  | +          v-model="data.article"
 | 
	
		
			
				|  |  | +          :is-fill="true"
 | 
	
		
			
				|  |  | +          :toolbar="false"
 | 
	
		
			
				|  |  | +          :wordlimit-num="false"
 | 
	
		
			
				|  |  | +          placeholder="输入文段"
 | 
	
		
			
				|  |  | +          @showContentmenu="showContentmenu"
 | 
	
		
			
				|  |  | +          @hideContentmenu="hideContentmenu"
 | 
	
		
			
				|  |  | +        />
 | 
	
		
			
				|  |  | +        <div v-show="isShow" ref="contentmenu" :style="contentmenu" class="contentmenu">
 | 
	
		
			
				|  |  | +          <SvgIcon icon-class="slice" size="16" @click="setFill" />
 | 
	
		
			
				|  |  | +          <span class="button" @click="setFill">设为填空</span>
 | 
	
		
			
				|  |  | +          <span class="line"></span>
 | 
	
		
			
				|  |  | +          <SvgIcon icon-class="close-circle" size="16" @click="deleteFill" />
 | 
	
		
			
				|  |  | +          <span class="button" @click="deleteFill">删除填空</span>
 | 
	
		
			
				|  |  | +        </div>
 | 
	
		
			
				|  |  | +        <el-button @click="identifyText">识别</el-button>
 | 
	
		
			
				|  |  | +        <div v-if="data.answer.answer_list.length > 0" class="correct-answer">
 | 
	
		
			
				|  |  | +          <div class="subtitle">正确答案</div>
 | 
	
		
			
				|  |  | +          <el-input
 | 
	
		
			
				|  |  | +            v-for="(item, i) in data.answer.answer_list.filter(({ type }) => type === 'any_one')"
 | 
	
		
			
				|  |  | +            :key="item.mark"
 | 
	
		
			
				|  |  | +            v-model="item.value"
 | 
	
		
			
				|  |  | +            @blur="handleTone(item.value, i)"
 | 
	
		
			
				|  |  | +          >
 | 
	
		
			
				|  |  | +            <span slot="prefix">{{ i + 1 }}.</span>
 | 
	
		
			
				|  |  | +          </el-input>
 | 
	
		
			
				|  |  | +        </div>
 | 
	
		
			
				|  |  | +      </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 switchOption"
 | 
	
		
			
				|  |  | +            :key="value"
 | 
	
		
			
				|  |  | +            v-model="data.property.is_enable_listening"
 | 
	
		
			
				|  |  | +            :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 v-model="data.property.score" type="number" />
 | 
	
		
			
				|  |  | +        </el-form-item>
 | 
	
		
			
				|  |  | +      </el-form>
 | 
	
		
			
				|  |  | +    </template>
 | 
	
		
			
				|  |  | +  </QuestionBase>
 | 
	
		
			
				|  |  | +</template>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +<script>
 | 
	
		
			
				|  |  | +import UploadAudio from '../common/UploadAudio.vue';
 | 
	
		
			
				|  |  | +import QuestionMixin from '../common/QuestionMixin.js';
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import { getRandomNumber } from '@/utils';
 | 
	
		
			
				|  |  | +import { addTone } from '@/views/exercise_questions/data/common';
 | 
	
		
			
				|  |  | +import { fillData, handleToneValue } from '@/views/exercise_questions/data/listenFill';
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +export default {
 | 
	
		
			
				|  |  | +  name: 'ListenFillQuestion',
 | 
	
		
			
				|  |  | +  components: {
 | 
	
		
			
				|  |  | +    UploadAudio,
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +  mixins: [QuestionMixin],
 | 
	
		
			
				|  |  | +  data() {
 | 
	
		
			
				|  |  | +    return {
 | 
	
		
			
				|  |  | +      isShow: false,
 | 
	
		
			
				|  |  | +      contentmenu: {
 | 
	
		
			
				|  |  | +        top: 0,
 | 
	
		
			
				|  |  | +        left: 0,
 | 
	
		
			
				|  |  | +      },
 | 
	
		
			
				|  |  | +      data: JSON.parse(JSON.stringify(fillData)),
 | 
	
		
			
				|  |  | +    };
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +  created() {
 | 
	
		
			
				|  |  | +    window.addEventListener('click', this.hideContentmenu);
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +  beforeDestroy() {
 | 
	
		
			
				|  |  | +    window.removeEventListener('click', this.hideContentmenu);
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +  methods: {
 | 
	
		
			
				|  |  | +    // 识别文本
 | 
	
		
			
				|  |  | +    identifyText() {
 | 
	
		
			
				|  |  | +      this.data.model_essay = [];
 | 
	
		
			
				|  |  | +      this.data.answer.answer_list = [];
 | 
	
		
			
				|  |  | +      this.data.article
 | 
	
		
			
				|  |  | +        .split(/<p>(.*?)<\/p>/gi)
 | 
	
		
			
				|  |  | +        .filter((item) => item)
 | 
	
		
			
				|  |  | +        .forEach((item) => {
 | 
	
		
			
				|  |  | +          if (item.charCodeAt() === 10) return;
 | 
	
		
			
				|  |  | +          // 匹配 span 标签和三个以上的_,并将它们组成数组
 | 
	
		
			
				|  |  | +          let str = item.replace(/<span.*?>(.*?)<\/span>|([_]{3,})/gi, '###$1$2###');
 | 
	
		
			
				|  |  | +          this.data.model_essay.push(this.splitRichText(str));
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    // 分割富文本
 | 
	
		
			
				|  |  | +    splitRichText(str) {
 | 
	
		
			
				|  |  | +      let _str = str;
 | 
	
		
			
				|  |  | +      let start = 0;
 | 
	
		
			
				|  |  | +      let index = 0;
 | 
	
		
			
				|  |  | +      let arr = [];
 | 
	
		
			
				|  |  | +      let matchNum = 0;
 | 
	
		
			
				|  |  | +      while (index !== -1) {
 | 
	
		
			
				|  |  | +        index = _str.indexOf('###', start);
 | 
	
		
			
				|  |  | +        if (index === -1) break;
 | 
	
		
			
				|  |  | +        matchNum += 1;
 | 
	
		
			
				|  |  | +        arr.push({ content: _str.slice(start, index), type: 'text' });
 | 
	
		
			
				|  |  | +        if (matchNum % 2 === 0 && arr.length > 0) {
 | 
	
		
			
				|  |  | +          arr[arr.length - 1].type = 'input';
 | 
	
		
			
				|  |  | +          let mark = getRandomNumber();
 | 
	
		
			
				|  |  | +          arr[arr.length - 1].mark = mark;
 | 
	
		
			
				|  |  | +          let content = arr[arr.length - 1].content;
 | 
	
		
			
				|  |  | +          // 设置答案数组
 | 
	
		
			
				|  |  | +          let isUnderline = /^_{3,}$/.test(content);
 | 
	
		
			
				|  |  | +          this.data.answer.answer_list.push({
 | 
	
		
			
				|  |  | +            value: isUnderline ? '' : content,
 | 
	
		
			
				|  |  | +            mark,
 | 
	
		
			
				|  |  | +            type: isUnderline ? 'any_one' : 'only_one',
 | 
	
		
			
				|  |  | +          });
 | 
	
		
			
				|  |  | +          // 将 content 设置为空,为预览准备
 | 
	
		
			
				|  |  | +          arr[arr.length - 1].content = '';
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        start = index + 3;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      let last = _str.slice(start);
 | 
	
		
			
				|  |  | +      if (last) {
 | 
	
		
			
				|  |  | +        arr.push({ content: last, type: 'text' });
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      return arr;
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    // 设置填空
 | 
	
		
			
				|  |  | +    setFill() {
 | 
	
		
			
				|  |  | +      this.$refs.modelEssay.setContent();
 | 
	
		
			
				|  |  | +      this.hideContentmenu();
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    // 删除填空
 | 
	
		
			
				|  |  | +    deleteFill() {
 | 
	
		
			
				|  |  | +      this.$refs.modelEssay.deleteContent();
 | 
	
		
			
				|  |  | +      this.hideContentmenu();
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    hideContentmenu() {
 | 
	
		
			
				|  |  | +      this.isShow = false;
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    showContentmenu({ pixelsFromLeft, pixelsFromTop }) {
 | 
	
		
			
				|  |  | +      this.isShow = true;
 | 
	
		
			
				|  |  | +      this.contentmenu = {
 | 
	
		
			
				|  |  | +        left: `${pixelsFromLeft + 14}px`,
 | 
	
		
			
				|  |  | +        top: `${pixelsFromTop - 18}px`,
 | 
	
		
			
				|  |  | +      };
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    handleTone(value, i) {
 | 
	
		
			
				|  |  | +      if (!/^[a-zA-Z0-9\s]+$/.test(value)) return;
 | 
	
		
			
				|  |  | +      this.data.answer.answer_list[i].value = value
 | 
	
		
			
				|  |  | +        .trim()
 | 
	
		
			
				|  |  | +        .split(/\s+/)
 | 
	
		
			
				|  |  | +        .map((item) => {
 | 
	
		
			
				|  |  | +          return handleToneValue(item);
 | 
	
		
			
				|  |  | +        })
 | 
	
		
			
				|  |  | +        .map((item) =>
 | 
	
		
			
				|  |  | +          item.map(({ number, con }) => (number && con ? addTone(Number(number), con) : number || con || '')),
 | 
	
		
			
				|  |  | +        )
 | 
	
		
			
				|  |  | +        .filter((item) => item.length > 0)
 | 
	
		
			
				|  |  | +        .join(' ');
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +</script>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +<style lang="scss" scoped>
 | 
	
		
			
				|  |  | +.content {
 | 
	
		
			
				|  |  | +  position: relative;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  .el-button {
 | 
	
		
			
				|  |  | +    margin-top: 8px;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  .correct-answer {
 | 
	
		
			
				|  |  | +    .subtitle {
 | 
	
		
			
				|  |  | +      margin: 8px 0;
 | 
	
		
			
				|  |  | +      font-size: 14px;
 | 
	
		
			
				|  |  | +      color: #4e5969;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    .el-input {
 | 
	
		
			
				|  |  | +      width: 180px;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      :deep &__prefix {
 | 
	
		
			
				|  |  | +        display: flex;
 | 
	
		
			
				|  |  | +        align-items: center;
 | 
	
		
			
				|  |  | +        color: $text-color;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      + .el-input {
 | 
	
		
			
				|  |  | +        margin-left: 8px;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  .contentmenu {
 | 
	
		
			
				|  |  | +    position: absolute;
 | 
	
		
			
				|  |  | +    z-index: 999;
 | 
	
		
			
				|  |  | +    display: flex;
 | 
	
		
			
				|  |  | +    column-gap: 4px;
 | 
	
		
			
				|  |  | +    align-items: center;
 | 
	
		
			
				|  |  | +    padding: 4px 8px;
 | 
	
		
			
				|  |  | +    font-size: 14px;
 | 
	
		
			
				|  |  | +    background-color: #fff;
 | 
	
		
			
				|  |  | +    border-radius: 2px;
 | 
	
		
			
				|  |  | +    box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 10%);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    .svg-icon,
 | 
	
		
			
				|  |  | +    .button {
 | 
	
		
			
				|  |  | +      cursor: pointer;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    .line {
 | 
	
		
			
				|  |  | +      min-height: 16px;
 | 
	
		
			
				|  |  | +      margin: 0 4px;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +</style>
 |