|
|
@@ -25,16 +25,25 @@
|
|
|
<span v-else :key="j" v-html="convertText(sanitizeHTML(li.content))"></span>
|
|
|
</template>
|
|
|
<template v-if="li.type === 'input'">
|
|
|
+ <!-- 输入填空 -->
|
|
|
<template v-if="data.property.fill_type === fillTypeList[0].value">
|
|
|
<el-input
|
|
|
:key="j"
|
|
|
- v-model="li.content"
|
|
|
+ :ref="`input-${li.mark}`"
|
|
|
+ v-model="li.input"
|
|
|
:disabled="disabled"
|
|
|
- :class="[data.property.fill_font, ...computedAnswerClass(li.mark)]"
|
|
|
- :style="[{ width: Math.max(data.property.input_default_width, li.content.length * 21.3) + 'px' }]"
|
|
|
+ :class="[...computedAnswerClass(li.mark)]"
|
|
|
+ :style="[
|
|
|
+ {
|
|
|
+ fontFamily: data.property.fill_font,
|
|
|
+ width: (inputWidthMap[li.mark] || data.property.input_default_width) + 'px',
|
|
|
+ },
|
|
|
+ ]"
|
|
|
+ @input="handleInput(li.input, li.mark)"
|
|
|
/>
|
|
|
</template>
|
|
|
|
|
|
+ <!-- 选词填空 -->
|
|
|
<template v-else-if="data.property.fill_type === fillTypeList[1].value">
|
|
|
<el-popover :key="j" placement="top" trigger="click">
|
|
|
<div class="word-list">
|
|
|
@@ -48,17 +57,16 @@
|
|
|
</span>
|
|
|
</div>
|
|
|
|
|
|
- <el-input
|
|
|
+ <span
|
|
|
slot="reference"
|
|
|
- v-model="li.content"
|
|
|
- :readonly="true"
|
|
|
- :class="[data.property.fill_font, ...computedAnswerClass(li.mark)]"
|
|
|
- class="pinyin"
|
|
|
- :style="[{ width: Math.max(data.property.input_default_width, li.content.length * 21.3) + 'px' }]"
|
|
|
- />
|
|
|
+ class="select-content"
|
|
|
+ :style="[{ minWidth: data.property.input_default_width + 'px' }]"
|
|
|
+ v-html="sanitizeHTML(li.input)"
|
|
|
+ ></span>
|
|
|
</el-popover>
|
|
|
</template>
|
|
|
|
|
|
+ <!-- 手写填空 -->
|
|
|
<template v-else-if="data.property.fill_type === fillTypeList[2].value">
|
|
|
<span :key="j" class="write-click" @click="handleWriteClick(li.mark)">
|
|
|
<img
|
|
|
@@ -70,6 +78,7 @@
|
|
|
</span>
|
|
|
</template>
|
|
|
|
|
|
+ <!-- 语音填空 -->
|
|
|
<template v-else-if="data.property.fill_type === fillTypeList[3].value">
|
|
|
<SoundRecordBox
|
|
|
ref="record"
|
|
|
@@ -145,14 +154,7 @@ import SoundRecord from '../../common/SoundRecord.vue';
|
|
|
import SoundRecordBox from '@/views/book/courseware/preview/components/record_input/SoundRecord.vue';
|
|
|
import WriteDialog from './components/WriteDialog.vue';
|
|
|
|
|
|
-import {
|
|
|
- getFillData,
|
|
|
- fillFontList,
|
|
|
- fillTypeList,
|
|
|
- arrangeTypeList,
|
|
|
- audioPositionList,
|
|
|
-} from '@/views/book/courseware/data/fill';
|
|
|
-import { getPlainText } from '@/utils/common';
|
|
|
+import { getFillData, fillTypeList, arrangeTypeList, audioPositionList } from '@/views/book/courseware/data/fill';
|
|
|
|
|
|
export default {
|
|
|
name: 'FillPreview',
|
|
|
@@ -168,16 +170,13 @@ export default {
|
|
|
data: getFillData(),
|
|
|
fillTypeList,
|
|
|
modelEssay: [],
|
|
|
+ inputWidthMap: {},
|
|
|
selectedWordList: [], // 用于存储选中的词汇
|
|
|
writeVisible: false,
|
|
|
writeMark: '',
|
|
|
};
|
|
|
},
|
|
|
computed: {
|
|
|
- fontFamily() {
|
|
|
- const fontItem = fillFontList.find(({ value }) => this.data.property.fill_font === value);
|
|
|
- return fontItem ? fontItem.font : '';
|
|
|
- },
|
|
|
isShowAnswer() {
|
|
|
return (
|
|
|
(Array.isArray(this.data.answer_list) && this.data.answer_list.length > 0) ||
|
|
|
@@ -218,10 +217,10 @@ export default {
|
|
|
this.answer.answer_list = list
|
|
|
.map((item) => {
|
|
|
return item
|
|
|
- .map(({ type, content, audio_answer_list, mark }) => {
|
|
|
+ .map(({ type, input, audio_answer_list, mark }) => {
|
|
|
if (type === 'input') {
|
|
|
return {
|
|
|
- value: content,
|
|
|
+ value: input,
|
|
|
mark,
|
|
|
audio_answer_list,
|
|
|
write_base64: '',
|
|
|
@@ -242,7 +241,7 @@ export default {
|
|
|
this.modelEssay.forEach((item) => {
|
|
|
item.forEach((li) => {
|
|
|
if (li.mark === mark) {
|
|
|
- li.content = value;
|
|
|
+ li.input = value;
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
@@ -282,7 +281,7 @@ export default {
|
|
|
handleSelectWord(content, mark, li) {
|
|
|
if (!content || !mark || !li) return;
|
|
|
|
|
|
- li.content = getPlainText(content);
|
|
|
+ li.input = content;
|
|
|
this.selectedWordList.push(mark);
|
|
|
},
|
|
|
/**
|
|
|
@@ -308,32 +307,63 @@ export default {
|
|
|
const isFront = this.data.property.audio_position === audioPositionList[0].value;
|
|
|
const isEnableVoice = this.data.property.is_enable_voice_answer === 'true';
|
|
|
const isHasAudio = this.data.audio_file_id.length > 0;
|
|
|
- let _list = [
|
|
|
+ let areaList = [
|
|
|
{ name: 'audio', value: '24px' },
|
|
|
{ name: 'fill', value: '1fr' },
|
|
|
];
|
|
|
|
|
|
if (!isHasAudio) {
|
|
|
- _list[0].value = '0px';
|
|
|
+ areaList.shift();
|
|
|
}
|
|
|
|
|
|
- if (!isFront) {
|
|
|
- _list = _list.reverse();
|
|
|
+ if (!isFront && isHasAudio) {
|
|
|
+ areaList = areaList.reverse();
|
|
|
}
|
|
|
+
|
|
|
let gridArea = '';
|
|
|
let gridTemplateRows = '';
|
|
|
let gridTemplateColumns = '';
|
|
|
+
|
|
|
if (isRow) {
|
|
|
- gridArea = `"${_list[0].name} ${_list[1].name}${isEnableVoice ? ' record' : ''}" ${this.showLang ? ' "lang lang lang"' : ''}`;
|
|
|
- gridTemplateRows = `auto ${this.showLang ? 'auto' : ''}`;
|
|
|
- gridTemplateColumns = `${_list[0].value} ${_list[1].value}${isEnableVoice ? ' 160px' : ''}`;
|
|
|
+ const rowAreas = areaList.map(({ name }) => name);
|
|
|
+ const rowCols = areaList.map(({ value }) => value);
|
|
|
+ const templateRows = ['auto'];
|
|
|
+
|
|
|
+ if (isEnableVoice) {
|
|
|
+ rowAreas.push('record');
|
|
|
+ rowCols.push('160px');
|
|
|
+ }
|
|
|
+
|
|
|
+ const areaRows = [`"${rowAreas.join(' ')}"`];
|
|
|
+
|
|
|
+ if (this.showLang) {
|
|
|
+ areaRows.push(`"${Array(rowAreas.length).fill('lang').join(' ')}"`);
|
|
|
+ templateRows.push('auto');
|
|
|
+ }
|
|
|
+
|
|
|
+ gridArea = areaRows.join(' ');
|
|
|
+ gridTemplateRows = templateRows.join(' ');
|
|
|
+ gridTemplateColumns = rowCols.join(' ');
|
|
|
} else {
|
|
|
- gridArea = `"${_list[0].name}" "${_list[1].name}" ${isEnableVoice ? `" record" ` : ''} ${this.showLang ? ' "lang"' : ''}`;
|
|
|
- gridTemplateRows = `${_list[0].value} ${_list[1].value} ${isEnableVoice ? ' 32px' : ''} ${this.showLang ? 'auto' : ''}`;
|
|
|
+ const areaRows = areaList.map(({ name }) => `"${name}"`);
|
|
|
+ const templateRows = areaList.map(({ value }) => value);
|
|
|
+
|
|
|
+ if (isEnableVoice) {
|
|
|
+ areaRows.push('"record"');
|
|
|
+ templateRows.push('32px');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.showLang) {
|
|
|
+ areaRows.push('"lang"');
|
|
|
+ templateRows.push('auto');
|
|
|
+ }
|
|
|
+
|
|
|
+ gridArea = areaRows.join(' ');
|
|
|
+ gridTemplateRows = templateRows.join(' ');
|
|
|
gridTemplateColumns = '1fr';
|
|
|
}
|
|
|
|
|
|
- let style = {
|
|
|
+ return {
|
|
|
'grid-auto-flow': isRow ? 'column' : 'row',
|
|
|
'column-gap': isRow && isHasAudio ? '16px' : undefined,
|
|
|
'row-gap': isRow || !isHasAudio ? undefined : '8px',
|
|
|
@@ -341,7 +371,6 @@ export default {
|
|
|
'grid-template-rows': gridTemplateRows,
|
|
|
'grid-template-columns': gridTemplateColumns,
|
|
|
};
|
|
|
- return style;
|
|
|
},
|
|
|
/**
|
|
|
* 计算答题对错选项字体颜色
|
|
|
@@ -383,7 +412,7 @@ export default {
|
|
|
this.modelEssay.forEach((item) => {
|
|
|
item.forEach((li) => {
|
|
|
if (li.type === 'input') {
|
|
|
- li.content = '';
|
|
|
+ li.input = '';
|
|
|
li.write_base64 = '';
|
|
|
}
|
|
|
});
|
|
|
@@ -416,6 +445,50 @@ export default {
|
|
|
|
|
|
return noTextContentData;
|
|
|
},
|
|
|
+ /**
|
|
|
+ * 处理输入框内容变化
|
|
|
+ * @description 根据输入框值计算所需长度,动态调整输入框宽度,输入框宽度不小于默认宽度,且不超过组件最大宽度 1000px,需要考虑输入框前面的宽度,保证输入框不会超出组件范围
|
|
|
+ * @param {String} value 输入框当前值
|
|
|
+ * @param {String} mark 选项标识
|
|
|
+ */
|
|
|
+ handleInput(value, mark) {
|
|
|
+ if (!mark) return;
|
|
|
+
|
|
|
+ const text = `${value || ''}`;
|
|
|
+ const defaultWidth = Number(this.data?.property?.input_default_width) || 120;
|
|
|
+ const fontSize = 16;
|
|
|
+ const fontFamily = this.data?.property?.fill_font || 'arial';
|
|
|
+
|
|
|
+ const canvas = document.createElement('canvas');
|
|
|
+ const context = canvas.getContext('2d');
|
|
|
+ let textWidth = 0;
|
|
|
+
|
|
|
+ if (context) {
|
|
|
+ context.font = `${fontSize}pt ${fontFamily}`;
|
|
|
+ textWidth = context.measureText(text || ' ').width;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 额外留出输入框内边距和边框余量,避免刚好卡边。
|
|
|
+ const contentWidth = Math.ceil(textWidth + 24);
|
|
|
+
|
|
|
+ const inputRef = this.$refs[`input-${mark}`];
|
|
|
+ const inputVm = Array.isArray(inputRef) ? inputRef[0] : inputRef;
|
|
|
+ const inputEl = inputVm?.$el;
|
|
|
+ const containerEl = this.$el;
|
|
|
+
|
|
|
+ let availableWidth = 1000;
|
|
|
+ if (inputEl && containerEl) {
|
|
|
+ const inputRect = inputEl.getBoundingClientRect();
|
|
|
+ const containerRect = containerEl.getBoundingClientRect();
|
|
|
+ const rightLimit = containerRect.left + Math.min(containerRect.width, 1000);
|
|
|
+ availableWidth = Math.floor(rightLimit - inputRect.left - 12);
|
|
|
+ }
|
|
|
+
|
|
|
+ const maxWidth = Math.max(40, availableWidth);
|
|
|
+ const nextWidth = Math.min(Math.max(contentWidth, defaultWidth), maxWidth);
|
|
|
+
|
|
|
+ this.$set(this.inputWidthMap, mark, nextWidth);
|
|
|
+ },
|
|
|
},
|
|
|
};
|
|
|
</script>
|
|
|
@@ -428,6 +501,7 @@ export default {
|
|
|
|
|
|
.main {
|
|
|
display: grid;
|
|
|
+ gap: 10px;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
@@ -465,6 +539,19 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ .select-content {
|
|
|
+ display: inline-block;
|
|
|
+ height: 32px;
|
|
|
+ margin: 0 10px;
|
|
|
+ vertical-align: bottom;
|
|
|
+ cursor: pointer;
|
|
|
+ border-bottom: 1px solid $font-color;
|
|
|
+
|
|
|
+ :deep p {
|
|
|
+ margin: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
.el-input {
|
|
|
display: inline-flex;
|
|
|
align-items: center;
|