| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779 |
- <!-- eslint-disable vue/no-v-html -->
- <template>
- <div class="select-preview" :style="[getAreaStyle(), getComponentStyle()]">
- <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
- <div class="main" :style="getMainStyle()">
- <AudioFill
- v-if="data.audio_file_id.length > 0"
- :color="data.unified_attrib?.topic_color"
- :file-id="data.audio_file_id"
- />
- <div class="fill-wrapper">
- <div class="fill-content">
- <template v-for="(group, gi) in groupedModelEssay">
- <span :key="`group-${gi}`" :style="groupStyle(group)">
- <template v-for="(li, i) in group.items">
- <template v-if="li.type === 'text'">
- <PinyinText
- :key="`text-${group.indexes[i]}`"
- class="content"
- :paragraph-list="li.paragraph_list"
- :rich-text-list="li.rich_text_list"
- :pinyin-position="data.property.pinyin_position"
- :body-styles="pinyinBodyStyles"
- :is-show-pinyin="isEnable(data.property.view_pinyin)"
- :is-preview="true"
- />
- </template>
- <template v-if="li.type === 'input'">
- <!-- 输入填空 -->
- <template v-if="data.property.fill_type === fillTypeList[0].value">
- <el-input
- :key="`input-${group.indexes[i]}`"
- :ref="`input-${li.mark}`"
- v-model="li.input"
- :disabled="disabled"
- :class="[...computedAnswerClass(li.mark)]"
- :style="[
- {
- '--fill-font-family': data.property.fill_font,
- width: (inputWidthMap[li.mark] || data.property.input_default_width) + 'px',
- marginBottom: resolveFillControlMargin(li),
- ...getFillControlAlignmentStyle(li),
- },
- ]"
- @input="handleInput(li.input, li.mark)"
- />
- </template>
- <!-- 选词填空 -->
- <template v-else-if="data.property.fill_type === fillTypeList[1].value">
- <el-popover :key="`popover-${group.indexes[i]}`" placement="top" trigger="click">
- <div class="word-list">
- <span
- v-for="{ content, mark } in data.word_list"
- :key="mark"
- class="word-item"
- @click="handleSelectWord(content, mark, li)"
- v-html="sanitizeHTML(content)"
- >
- </span>
- </div>
- <span
- slot="reference"
- class="select-content"
- :style="[
- { minWidth: data.property.input_default_width + 'px' },
- { marginBottom: resolveFillControlMargin(li) },
- ]"
- v-html="sanitizeHTML(li.input)"
- ></span>
- </el-popover>
- </template>
- <!-- 手写填空 -->
- <template v-else-if="data.property.fill_type === fillTypeList[2].value">
- <span
- :key="`write-${group.indexes[i]}`"
- class="write-click"
- :style="{ marginBottom: resolveFillControlMargin(li) }"
- @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">
- <SoundRecordBox
- ref="record"
- :key="`record-${group.indexes[i]}`"
- type="mini"
- :many-times="false"
- class="record-box fill"
- :attrib="data.unified_attrib"
- :answer-record-list="data.audio_answer_list"
- :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
- @handleWav="handleMiniWav($event, li.mark)"
- />
- </template>
- </template>
- </template>
- </span>
- </template>
- </div>
- </div>
- <SoundRecord
- v-if="isEnable(data.property.is_enable_voice_answer)"
- ref="record"
- type="pro"
- :class="['record-box', 'sound-record-wrapper', isHorizontal ? 'horizontal' : 'vertical']"
- :attrib="data.unified_attrib"
- :answer-record-list="data.record_list"
- :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
- @handleWav="handleWav"
- />
- <div v-if="showLang" class="lang">
- {{ data.multilingual.find((item) => item.type === getLang())?.translation }}
- </div>
- <PreviewOperation
- :is-show-retry="isHasFill"
- :is-show-answer="isHasFill && isShowAnswerFill"
- :is-show-judge-correct="isShowJudgeCorrect"
- @showAnswerAnalysis="showAnswerAnalysis"
- @judgeCorrect="judgeCorrect"
- @retry="retry"
- />
- </div>
- <WriteDialog :visible.sync="writeVisible" @confirm="handleWriteConfirm" />
- <AnswerCorrect
- :visible.sync="visibleAnswerCorrect"
- :is-check-correct="isCheckCorrect"
- @closeAnswerCorrect="closeAnswerCorrect"
- />
- <AnswerAnalysis
- :visible.sync="visibleAnswerAnalysis"
- :answer-list="data.answer_list"
- :analysis-list="data.analysis_list"
- @closeAnswerAnalysis="closeAnswerAnalysis"
- >
- <div slot="right-answer" class="fill-wrapper" :style="{ '--fill-font-family': data.property.fill_font }">
- <div class="fill-content">
- <template v-for="(group, gi) in groupedModelEssay">
- <span :key="`answer-group-${gi}`" :style="groupStyle(group)">
- <template v-for="(li, i) in group.items">
- <template v-if="li.type === 'text'">
- <PinyinText
- :key="`answer-text-${group.indexes[i]}`"
- :is-show-pinyin="isEnable(data.property.view_pinyin)"
- class="content"
- :paragraph-list="li.paragraph_list"
- :rich-text-list="li.rich_text_list"
- :body-styles="pinyinBodyStyles"
- :pinyin-position="data.property.pinyin_position"
- :is-preview="true"
- />
- </template>
- <template v-if="li.type === 'input'">
- <span
- v-show="computedAnswerText(li.mark).length > 0"
- :key="`answer-${group.indexes[i]}`"
- class="right-answer"
- :style="{ marginBottom: resolveFillControlMargin(li) }"
- >
- {{ computedAnswerText(li.mark) }}
- </span>
- </template>
- </template>
- </span>
- </template>
- </div>
- </div>
- </AnswerAnalysis>
- </div>
- </template>
- <script>
- 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';
- import {
- getFillData,
- fillTypeList,
- arrangeTypeList,
- audioPositionList,
- groupModelEssay,
- } from '@/views/book/courseware/data/fill';
- export default {
- name: 'FillPreview',
- components: {
- AudioFill,
- SoundRecord,
- SoundRecordBox,
- WriteDialog,
- },
- mixins: [PreviewMixin],
- data() {
- return {
- data: getFillData(),
- fillTypeList,
- modelEssay: [],
- inputWidthMap: {},
- selectedWordList: [], // 用于存储选中的词汇
- writeVisible: false,
- writeMark: '',
- };
- },
- computed: {
- pinyinBodyStyles() {
- const unifiedAttrib = this.data?.unified_attrib || {};
- return {
- fontFamily: unifiedAttrib.font || undefined,
- fontSize: unifiedAttrib.font_size || undefined,
- color: unifiedAttrib.text_color || undefined,
- };
- },
- isHasFill() {
- return this.data.model_essay.some((li) => li.type === 'input');
- },
- isHorizontal() {
- return this.data.property.arrange_type === arrangeTypeList[0].value;
- },
- // 是否有固定答案(data.answer.answer_list 中存在任意一项 value 非空)
- hasFixedAnswer() {
- return (this.data.answer?.answer_list || []).some((item) => item?.value);
- },
- // 是否显示判断对错按钮:有固定答案时才显示
- isShowJudgeCorrect() {
- return this.hasFixedAnswer;
- },
- // 是否显示查看答案按钮:有固定答案或参考答案(answer_list)有数据时显示
- isShowAnswerFill() {
- return this.hasFixedAnswer || (this.data.answer_list || []).length > 0;
- },
- /**
- * 按「段落 + 对齐方式」对 modelEssay 分组,便于整体应用 text-align,
- * 让同一段内的文字块与填空连续排列(可换行),而不是每块独占一行;
- * 段落之间必须断开,否则相邻的同对齐方式段落会共用同一个块级容器(同一个文本流),
- * 被排成一行(例如连续的多个居中段落)。
- * @returns {Array} [{ align, items, indexes }]
- */
- groupedModelEssay() {
- return groupModelEssay(this.modelEssay);
- },
- },
- watch: {
- 'data.model_essay': {
- handler(list) {
- if (!list || !Array.isArray(list)) return;
- this.modelEssay = JSON.parse(JSON.stringify(list));
- this.syncAnswerList(list);
- },
- deep: true,
- immediate: true,
- },
- modelEssay: {
- handler(list) {
- if (!list || !Array.isArray(list)) return;
- this.syncAnswerList(list);
- },
- deep: true,
- immediate: true,
- },
- isJudgingRightWrong(val) {
- if (!val) return;
- this.answer.answer_list.forEach(({ mark, value }) => {
- const li = this.modelEssay.find((item) => item.mark === mark);
- if (li) {
- li.input = value;
- }
- });
- this.handleWav(this.answer.record_list);
- },
- 'data.record_list'(val) {
- this.answer.record_list = val;
- },
- 'answer.record_list': {
- handler(val) {
- this.data.record_list = val;
- },
- immediate: true,
- },
- },
- methods: {
- /**
- * 分组容器样式:有对齐时应用块级与对齐,无对齐时保持行内流式。
- * @param {Object} group 分组对象
- * @returns {Object} 样式对象
- */
- groupStyle(group) {
- if (!group || !group.align) return {};
- return {
- display: 'block',
- textAlign: group.align,
- };
- },
- /**
- * 计算填空控件需要补偿的 margin-bottom。
- * 词级 margin-bottom 会撑高行盒,控件 vertical-align: bottom 会贴到被撑高的行盒底部,
- * 因此给控件加上与段落一致的 margin-bottom,使其边框底部回到文本底部;
- * 无 margin 时返回空字符串,保持原有显示不变。
- * @param {Object} li 填空块对象
- * @returns {String} 补偿值,如 '28pt',无则空字符串
- */
- resolveFillControlMargin(li) {
- const tagItem = (li?.rich_text_list || []).find((item) =>
- /^<(p|div|h[1-6]|li|blockquote)\b[^>]*>/i.test(String(item?.text || '')),
- );
- const raw = String(tagItem?.text || '').match(/margin-bottom\s*:\s*([^;"']+)/i);
- if (!raw) return '';
- const match = raw[1].trim().match(/^([\d.]+)\s*(px|pt|em)?$/i);
- if (!match) return '';
- const amount = parseFloat(match[1]);
- const unit = (match[2] || 'px').toLowerCase();
- // px/pt 为绝对单位,可直接使用;em 相对词级字号,换算为绝对 pt 后再补偿
- if (unit !== 'em') return raw[1].trim();
- const fontSize = String(this.pinyinBodyStyles?.fontSize || '16pt');
- const fsMatch = fontSize.match(/^([\d.]+)\s*(px|pt|em)?$/i);
- if (!fsMatch) return raw[1].trim();
- const fsAmount = parseFloat(fsMatch[1]);
- const fsUnit = (fsMatch[2] || 'pt').toLowerCase();
- if (fsUnit === 'em') return `${amount * fsAmount}em`;
- if (fsUnit === 'pt') return `${amount * fsAmount}pt`;
- return `${amount * fsAmount}px`;
- },
- getFillControlAlignmentStyle(li) {
- const tagItem = (li?.rich_text_list || []).find((item) =>
- /^<(p|div|h[1-6]|li|blockquote)\b[^>]*>/i.test(String(item?.text || '')),
- );
- const lineHeight = String(tagItem?.text || '').match(/line-height\s*:\s*([^;"']+)/i);
- if (!lineHeight || !/^\d*\.?\d+$/.test(lineHeight[1].trim())) return {};
- const ratio = Number(lineHeight[1].trim());
- if (ratio < 0 || ratio >= 1.5) return {};
- return {
- marginBottom: '',
- position: 'relative',
- top: `${1.5 - ratio}em`,
- };
- },
- handleWav(data) {
- this.data.record_list = data;
- },
- syncAnswerList(list) {
- this.answer.answer_list = list
- .map(({ type, input, audio_answer_list, mark }) => {
- if (type === 'input') {
- return {
- value: input,
- mark,
- audio_answer_list,
- write_base64: '',
- };
- }
- return null;
- })
- .filter((item) => item);
- },
- /**
- * 处理小音频录音
- * @param {Object} data 音频数据
- * @param {String} mark 选项标识
- */
- handleMiniWav(data, mark) {
- if (!data || !mark) return;
- const li = this.modelEssay.find((item) => item?.mark === mark);
- if (li) {
- this.$set(li, 'audio_answer_list', data);
- }
- },
- /**
- * 处理选中词汇
- * @param {String} content 选中的词汇内容
- * @param {String} mark 选项标识
- * @param {Object} li 当前输入框对象
- */
- handleSelectWord(content, mark, li) {
- if (!content || !mark || !li) return;
- li.input = content;
- this.selectedWordList.push(mark);
- },
- /**
- * 处理书写区确认
- * @param {String} data 书写区数据
- */
- handleWriteConfirm(data) {
- if (!data) return;
- const li = this.modelEssay.find((item) => item?.mark === this.writeMark);
- if (li) {
- this.$set(li, 'write_base64', data);
- }
- },
- 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;
- const isEnableVoice = this.data.property.is_enable_voice_answer === 'true';
- const isHasAudio = this.data.audio_file_id.length > 0;
- let areaList = [
- { name: 'audio', value: '24px' },
- { name: 'fill', value: '1fr' },
- ];
- if (!isHasAudio) {
- areaList.shift();
- }
- if (!isFront && isHasAudio) {
- areaList = areaList.reverse();
- }
- let gridArea = '';
- let gridTemplateRows = '';
- let gridTemplateColumns = '';
- if (isRow) {
- 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 {
- const areaRows = areaList.map(({ name }) => `"${name}"`);
- const templateRows = areaList.map(({ value }) => value);
- if (isEnableVoice) {
- areaRows.push('"record"');
- templateRows.push('56px');
- }
- if (this.showLang) {
- areaRows.push('"lang"');
- templateRows.push('auto');
- }
- gridArea = areaRows.join(' ');
- gridTemplateRows = templateRows.join(' ');
- gridTemplateColumns = '1fr';
- }
- return {
- 'grid-auto-flow': isRow ? 'column' : 'row',
- 'column-gap': isRow && isHasAudio ? '16px' : undefined,
- 'row-gap': isRow || !isHasAudio ? undefined : '8px',
- 'grid-template-areas': gridArea,
- 'grid-template-rows': gridTemplateRows,
- 'grid-template-columns': gridTemplateColumns,
- };
- },
- /**
- * 计算答题对错选项字体颜色
- * @param {string} mark 选项标识
- */
- computedAnswerClass(mark) {
- if (!this.isJudgingRightWrong && !this.isShowRightAnswer) {
- return '';
- }
- let selectOption = this.answer.answer_list.find((item) => item.mark === mark);
- let answerOption = this.data.answer.answer_list.find((item) => item.mark === mark);
- if (!selectOption || !answerOption) return '';
- let selectValue = selectOption.value;
- let answerValue = answerOption.value;
- let answerType = answerOption.type;
- let classList = [];
- let isRight =
- answerType === 'only_one' ? selectValue === answerValue : answerValue.split('/').includes(selectValue);
- if (this.isJudgingRightWrong) {
- isRight ? classList.push('right') : classList.push('wrong');
- }
- if (this.isShowRightAnswer && !isRight) {
- classList.push('show-right-answer');
- }
- return classList;
- },
- /**
- * 计算正确答案文本
- * @param {string} mark 选项标识
- */
- computedAnswerText(mark) {
- let answerOption = this.data.answer.answer_list.find((item) => item.mark === mark);
- if (!answerOption) return '';
- let answerValue = answerOption.value;
- return `${answerValue}`;
- },
- // 重做
- retry() {
- this.modelEssay.forEach((li) => {
- if (li.type === 'input') {
- li.input = '';
- li.write_base64 = '';
- li.audio_answer_list = [];
- }
- });
- this.selectedWordList = [];
- this.isJudgingRightWrong = false;
- this.isShowRightAnswer = false;
- this.handleWav([]);
- },
- /**
- * 获取无文本内容的数据结构,用于保存为个人模板时的样式模板
- */
- getNoTextContentData() {
- let noTextContentData = JSON.parse(JSON.stringify(this.data));
- const resetFieldMap = {
- model_essay: [],
- content: '',
- audio_file_id: '',
- file_list: [],
- multilingual: [],
- analysis_list: [],
- answer_list: [],
- record_list: [],
- };
- Object.assign(noTextContentData, resetFieldMap);
- if (noTextContentData.answer) {
- noTextContentData.answer.answer_list = [];
- noTextContentData.answer.reference_answer = '';
- }
- 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>
- <style lang="scss" scoped>
- @use '@/styles/mixin.scss' as *;
- .select-preview {
- @include preview-base;
- .main {
- display: grid;
- gap: 10px;
- align-items: center;
- }
- .fill-wrapper {
- grid-area: fill;
- align-self: start;
- font-size: 16pt;
- :deep .pinyin-area .rich-text-container,
- :deep .pinyin-area .pinyin-paragraph,
- :deep .pinyin-area .pinyin-sentence {
- display: inline;
- }
- .fill-content {
- width: calc(100% - 82px);
- margin: 0;
- :deep .el-input__inner {
- background-color: transparent !important;
- }
- }
- .content {
- display: inline;
- }
- .record-box.fill {
- display: inline-flex;
- align-items: center;
- background-color: #fff;
- border-bottom: 1px solid $font-color;
- :deep(.playBack) {
- width: 20px;
- height: 20px;
- }
- }
- .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%;
- }
- }
- .select-content {
- // 选中的词贴到控件底部:文字底部与正文文字底部对齐(下边框即填空横线)
- display: inline-flex;
- align-items: flex-end;
- 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;
- width: 120px;
- margin: 0 10px;
- margin-bottom: -2px;
- vertical-align: bottom;
- &.pinyin :deep input.el-input__inner {
- font-family: 'PINYIN-B', sans-serif;
- }
- &.chinese :deep input.el-input__inner {
- font-family: 'arial', sans-serif;
- }
- &.english :deep input.el-input__inner {
- font-family: 'arial', sans-serif;
- }
- &.right {
- :deep input.el-input__inner {
- color: $right-color;
- }
- }
- &.wrong {
- :deep input.el-input__inner {
- color: $error-color;
- }
- }
- :deep .el-input__inner {
- // 1em 行盒:输入文字基线与正文文字对齐,下边框即填空横线
- height: 1em;
- padding: 0;
- font-family: var(--fill-font-family, 'arial'), sans-serif;
- font-size: 16pt;
- line-height: 1em;
- color: $font-color;
- text-align: center;
- background-color: #fff;
- border-width: 0;
- border-bottom: 1px solid $font-color;
- border-radius: 0;
- }
- }
- .right-answer {
- // 盒模型/行高与填空输入框一致:文字底部贴正文文字底部,下边框即填空横线
- // 字体与学生输入保持一致(--fill-font-family 由模板传入)
- position: relative;
- box-sizing: border-box;
- display: inline-block;
- height: 1em;
- margin: 0 4px;
- font-family: var(--fill-font-family, 'arial'), sans-serif;
- line-height: 1em;
- vertical-align: bottom;
- border-bottom: 1px solid $font-color;
- }
- }
- .record-box {
- padding: 6px 12px;
- vertical-align: bottom;
- background-color: $fill-color;
- :deep .record-time {
- width: 100px;
- }
- }
- .sound-record-wrapper {
- width: 200px;
- max-width: 200px;
- &.vertical {
- margin: 24px 0 0 16px;
- }
- }
- }
- </style>
- <style lang="scss" scoped>
- .word-list {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- align-items: center;
- .word-item {
- cursor: pointer;
- }
- }
- </style>
|