|
@@ -6,7 +6,7 @@
|
|
|
<div class="main" :style="getMainStyle()">
|
|
|
<AudioFill :file-id="data.audio_file_id" />
|
|
|
<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>
|
|
|
<template v-if="li.type === 'input'">
|
|
@@ -23,15 +23,20 @@
|
|
|
<template v-else-if="data.property.fill_type === fillTypeList[1].value">
|
|
|
<el-popover :key="j" placement="top" trigger="click">
|
|
|
<div class="word-list">
|
|
|
- <span v-for="(word, index) in wordList" :key="index" class="word-item" @click="li.content = word">
|
|
|
- {{ word }}
|
|
|
+ <span
|
|
|
+ v-for="{ content, mark } in data.word_list"
|
|
|
+ :key="mark"
|
|
|
+ class="word-item"
|
|
|
+ @click="handleSelectWord(content, mark, li)"
|
|
|
+ >
|
|
|
+ {{ content }}
|
|
|
</span>
|
|
|
</div>
|
|
|
|
|
|
<el-input
|
|
|
slot="reference"
|
|
|
v-model="li.content"
|
|
|
- :disabled="true"
|
|
|
+ :readonly="true"
|
|
|
:class="[data.property.fill_font, ...computedAnswerClass(li.mark)]"
|
|
|
class="pinyin"
|
|
|
:style="[{ width: Math.max(80, li.content.length * 21.3) + 'px' }]"
|
|
@@ -39,12 +44,30 @@
|
|
|
</el-popover>
|
|
|
</template>
|
|
|
|
|
|
- <template v-else-if="data.property.fill_type === fillTypeList[2].value"></template>
|
|
|
+ <template v-else-if="data.property.fill_type === fillTypeList[2].value">
|
|
|
+ <span :key="j" class="write-click" @click="handleWriteClick(li.mark)">
|
|
|
+ <img
|
|
|
+ v-show="li.write_base64"
|
|
|
+ style="background-color: #f4f4f4"
|
|
|
+ :src="li.write_base64"
|
|
|
+ alt="write-show"
|
|
|
+ />
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
|
|
|
<template v-else-if="data.property.fill_type === fillTypeList[3].value">
|
|
|
- <!-- TODO -->
|
|
|
- <!-- <SoundRecord :key="j" :wav-blob.sync="li.audio_file_id" /> -->
|
|
|
+ <SoundRecordBox
|
|
|
+ ref="record"
|
|
|
+ :key="j"
|
|
|
+ type="mini"
|
|
|
+ :many-times="false"
|
|
|
+ class="record-box"
|
|
|
+ :answer-record-list="data.audio_answer_list"
|
|
|
+ :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
|
|
|
+ @handleWav="handleMiniWav($event, li.mark)"
|
|
|
+ />
|
|
|
</template>
|
|
|
+
|
|
|
<span v-show="computedAnswerText(li.mark).length > 0" :key="`answer-${j}`" class="right-answer">
|
|
|
{{ computedAnswerText(li.mark) }}
|
|
|
</span>
|
|
@@ -62,6 +85,8 @@
|
|
|
@handleWav="handleWav"
|
|
|
/>
|
|
|
</div>
|
|
|
+
|
|
|
+ <WriteDialog :visible.sync="writeVisible" @confirm="handleWriteConfirm" />
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
@@ -77,19 +102,26 @@ import {
|
|
|
import PreviewMixin from '../common/PreviewMixin';
|
|
|
import AudioFill from './components/AudioFillPlay.vue';
|
|
|
import SoundRecord from '../../common/SoundRecord.vue';
|
|
|
+import SoundRecordBox from '@/views/book/courseware/preview/components/record_input/SoundRecord.vue';
|
|
|
+import WriteDialog from './components/WriteDialog.vue';
|
|
|
|
|
|
export default {
|
|
|
name: 'FillPreview',
|
|
|
components: {
|
|
|
AudioFill,
|
|
|
SoundRecord,
|
|
|
+ SoundRecordBox,
|
|
|
+ WriteDialog,
|
|
|
},
|
|
|
mixins: [PreviewMixin],
|
|
|
data() {
|
|
|
return {
|
|
|
data: getFillData(),
|
|
|
fillTypeList,
|
|
|
- wordList: [],
|
|
|
+ modelEssay: [],
|
|
|
+ selectedWordList: [], // 用于存储选中的词汇
|
|
|
+ writeVisible: false,
|
|
|
+ writeMark: '',
|
|
|
};
|
|
|
},
|
|
|
computed: {
|
|
@@ -100,15 +132,19 @@ export default {
|
|
|
watch: {
|
|
|
'data.model_essay': {
|
|
|
handler(list) {
|
|
|
+ if (!list || !Array.isArray(list)) return;
|
|
|
+
|
|
|
+ this.modelEssay = JSON.parse(JSON.stringify(list));
|
|
|
this.answer.answer_list = list
|
|
|
.map((item) => {
|
|
|
return item
|
|
|
- .map(({ type, content, mark }) => {
|
|
|
+ .map(({ type, content, audio_answer_list, mark }) => {
|
|
|
if (type === 'input') {
|
|
|
return {
|
|
|
value: content,
|
|
|
- audio_file_id: '',
|
|
|
mark,
|
|
|
+ audio_answer_list,
|
|
|
+ write_base64: '',
|
|
|
};
|
|
|
}
|
|
|
})
|
|
@@ -117,22 +153,13 @@ export default {
|
|
|
.flat();
|
|
|
},
|
|
|
deep: true,
|
|
|
- },
|
|
|
- 'data.vocabulary': {
|
|
|
- handler(val) {
|
|
|
- if (!val) return;
|
|
|
- this.wordList = val
|
|
|
- .split(/[\n\r]+/)
|
|
|
- .map((item) => item.split(' ').filter((s) => s))
|
|
|
- .flat();
|
|
|
- },
|
|
|
immediate: true,
|
|
|
},
|
|
|
isJudgingRightWrong(val) {
|
|
|
if (!val) return;
|
|
|
|
|
|
this.answer.answer_list.forEach(({ mark, value }) => {
|
|
|
- this.data.model_essay.forEach((item) => {
|
|
|
+ this.modelEssay.forEach((item) => {
|
|
|
item.forEach((li) => {
|
|
|
if (li.mark === mark) {
|
|
|
li.content = value;
|
|
@@ -147,26 +174,55 @@ export default {
|
|
|
this.answer.record_list = val;
|
|
|
},
|
|
|
},
|
|
|
- created() {
|
|
|
- this.answer.answer_list = this.data.model_essay
|
|
|
- .map((item) => {
|
|
|
- return item
|
|
|
- .map(({ type, content, mark }) => {
|
|
|
- if (type === 'input') {
|
|
|
- return {
|
|
|
- value: content,
|
|
|
- mark,
|
|
|
- };
|
|
|
- }
|
|
|
- })
|
|
|
- .filter((item) => item);
|
|
|
- })
|
|
|
- .flat();
|
|
|
- },
|
|
|
methods: {
|
|
|
handleWav(data) {
|
|
|
this.data.record_list = data;
|
|
|
},
|
|
|
+ /**
|
|
|
+ * 处理小音频录音
|
|
|
+ * @param {Object} data 音频数据
|
|
|
+ * @param {String} mark 选项标识
|
|
|
+ */
|
|
|
+ handleMiniWav(data, mark) {
|
|
|
+ if (!data || !mark) return;
|
|
|
+ for (const item of this.modelEssay) {
|
|
|
+ const li = item.find((li) => li?.mark === mark);
|
|
|
+ if (li) {
|
|
|
+ this.$set(li, 'audio_answer_list', data);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 处理选中词汇
|
|
|
+ * @param {String} content 选中的词汇内容
|
|
|
+ * @param {String} mark 选项标识
|
|
|
+ * @param {Object} li 当前输入框对象
|
|
|
+ */
|
|
|
+ handleSelectWord(content, mark, li) {
|
|
|
+ if (!content || !mark || !li) return;
|
|
|
+
|
|
|
+ li.content = content;
|
|
|
+ this.selectedWordList.push(mark);
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 处理书写区确认
|
|
|
+ * @param {String} data 书写区数据
|
|
|
+ */
|
|
|
+ handleWriteConfirm(data) {
|
|
|
+ if (!data) return;
|
|
|
+ for (const item of this.modelEssay) {
|
|
|
+ const li = item.find((li) => li?.mark === this.writeMark);
|
|
|
+ if (li) {
|
|
|
+ this.$set(li, 'write_base64', data);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ handleWriteClick(mark) {
|
|
|
+ this.writeVisible = true;
|
|
|
+ this.writeMark = mark;
|
|
|
+ },
|
|
|
getMainStyle() {
|
|
|
const isRow = this.data.property.arrange_type === arrangeTypeList[0].value;
|
|
|
const isFront = this.data.property.audio_position === audioPositionList[0].value;
|
|
@@ -253,6 +309,28 @@ export default {
|
|
|
margin: 0;
|
|
|
}
|
|
|
|
|
|
+ .record-box {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ background-color: #fff;
|
|
|
+ border-bottom: 1px solid $font-color;
|
|
|
+ }
|
|
|
+
|
|
|
+ .write-click {
|
|
|
+ display: inline-block;
|
|
|
+ width: 104px;
|
|
|
+ height: 32px;
|
|
|
+ padding: 0 4px;
|
|
|
+ vertical-align: bottom;
|
|
|
+ cursor: pointer;
|
|
|
+ border-bottom: 1px solid $font-color;
|
|
|
+
|
|
|
+ img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
.el-input {
|
|
|
display: inline-flex;
|
|
|
align-items: center;
|
|
@@ -316,3 +394,16 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
</style>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.word-list {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ .word-item {
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|