|
@@ -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>
|