|
@@ -0,0 +1,378 @@
|
|
|
+<template>
|
|
|
+ <QuestionBase>
|
|
|
+ <template #content>
|
|
|
+ <div class="stem">
|
|
|
+ <RichText v-model="data.stem" :font-size="18" placeholder="输入题干" />
|
|
|
+
|
|
|
+ <RichText
|
|
|
+ v-if="isEnable(data.property.is_enable_description)"
|
|
|
+ v-model="data.description"
|
|
|
+ placeholder="输入提示"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="content">
|
|
|
+ <label class="title-little">题目:</label>
|
|
|
+ <ul>
|
|
|
+ <li v-for="(item, i) in data.option_list" :key="i" class="content-item">
|
|
|
+ <span class="question-number" title="双击切换序号类型" @dblclick="changeOptionType(data)">
|
|
|
+ {{ computedQuestionNumber(i, data.option_number_show_mode) }}
|
|
|
+ </span>
|
|
|
+ <el-input v-model="item.content" :placeholder="'输入汉字或词汇'" />
|
|
|
+ <el-input
|
|
|
+ v-model="item.pinyin"
|
|
|
+ :placeholder="'拼音间用空格隔开'"
|
|
|
+ @blur="handleSplitPy(item)"
|
|
|
+ @change="changePinyin(item)"
|
|
|
+ />
|
|
|
+ <UploadAudio
|
|
|
+ v-if="data.other.audio_generation_method === 'upload'"
|
|
|
+ :key="item.audio_file_id || i"
|
|
|
+ :file-id="item.audio_file_id"
|
|
|
+ :item-index="i"
|
|
|
+ :show-upload="!item.audio_file_id"
|
|
|
+ @upload="uploads"
|
|
|
+ @deleteFile="deleteFiles"
|
|
|
+ />
|
|
|
+ <div v-else-if="data.other.audio_generation_method === 'auto'" class="auto-matically">
|
|
|
+ <AudioPlay v-if="item.audio_file_id" :file-id="item.audio_file_id" theme-color="gray" />
|
|
|
+ <span
|
|
|
+ v-loading="loading_list[i] ? loading_list[i].loading : false"
|
|
|
+ class="auto-btn"
|
|
|
+ @click="handleMatically(item, i)"
|
|
|
+ >{{ item.audio_file_id ? '已生成' : '自动生成' }}</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <SoundRecord v-else :wav-blob.sync="item.audio_file_id" />
|
|
|
+ <SvgIcon icon-class="delete" class="delete pointer" @click="deleteOption(i, item.audio_file_id)" />
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ <div class="footer">
|
|
|
+ <span class="add-option" @click="addOption">
|
|
|
+ <SvgIcon icon-class="add-circle" size="14" /> <span>增加汉字</span>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template #property>
|
|
|
+ <el-form :model="data.property">
|
|
|
+ <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 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-number
|
|
|
+ v-model="data.property.score"
|
|
|
+ :min="0"
|
|
|
+ :step="data.property.score_type === scoreTypeList[0].value ? 1 : 0.1"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="音频">
|
|
|
+ <el-radio
|
|
|
+ v-for="{ value, label } in audioGenerationMethodList"
|
|
|
+ :key="value"
|
|
|
+ v-model="data.other.audio_generation_method"
|
|
|
+ :label="value"
|
|
|
+ >
|
|
|
+ {{ label }}
|
|
|
+ </el-radio>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </template>
|
|
|
+ </QuestionBase>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import QuestionMixin from '../common/QuestionMixin.js';
|
|
|
+import UploadAudio from '../common/UploadAudio.vue';
|
|
|
+import SoundRecord from '../common/SoundRecord.vue';
|
|
|
+import { GetStaticResources } from '@/api/app';
|
|
|
+import { changeOptionType, handleInputNumber, addTone } from '@/views/exercise_questions/data/common';
|
|
|
+import { getRandomNumber } from '@/utils/index';
|
|
|
+
|
|
|
+import { wordDictationData, audioGenerationMethodList, getOption } from '@/views/exercise_questions/data/wordDictation';
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'WordDictationQuestion',
|
|
|
+ components: {
|
|
|
+ UploadAudio,
|
|
|
+ SoundRecord,
|
|
|
+ },
|
|
|
+ mixins: [QuestionMixin],
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ audioGenerationMethodList,
|
|
|
+ data: JSON.parse(JSON.stringify(wordDictationData)),
|
|
|
+ changeOptionType,
|
|
|
+ handleInputNumber,
|
|
|
+ loading_list: [
|
|
|
+ {
|
|
|
+ loading: false,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ loading: false,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ loading: false,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ matically_pinyin_obj: {}, // 存放转成声调的拼音
|
|
|
+ };
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ 'data.option_list.length': {
|
|
|
+ handler(val) {
|
|
|
+ this.loading_list = [];
|
|
|
+ for (let i = 0; i < val; i++) {
|
|
|
+ let obj = {
|
|
|
+ loading: false,
|
|
|
+ };
|
|
|
+ this.loading_list.push(obj);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ deep: true,
|
|
|
+ immediate: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ mounted() {},
|
|
|
+ methods: {
|
|
|
+ addOption() {
|
|
|
+ this.data.option_list.push(getOption());
|
|
|
+ this.loading_list.push({
|
|
|
+ loading: false,
|
|
|
+ });
|
|
|
+ },
|
|
|
+ uploads(file_id, index) {
|
|
|
+ this.data.option_list[index].audio_file_id = file_id;
|
|
|
+ this.data.file_id_list.push(file_id);
|
|
|
+ },
|
|
|
+ deleteFiles(file_id, itemIndex) {
|
|
|
+ this.data.option_list[itemIndex].audio_file_id = '';
|
|
|
+ this.data.file_id_list.splice(this.data.file_id_list.indexOf(file_id), 1);
|
|
|
+ },
|
|
|
+ // 删除小题
|
|
|
+ deleteOption(i, file_id) {
|
|
|
+ this.$confirm('是否删除?', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ })
|
|
|
+ .then(() => {
|
|
|
+ this.data.option_list.splice(i, 1);
|
|
|
+ this.data.file_id_list.splice(this.data.file_id_list.indexOf(file_id), 1);
|
|
|
+ this.loading_list.splice(i, 1);
|
|
|
+ })
|
|
|
+ .catch(() => {});
|
|
|
+ },
|
|
|
+ // 自动生成音频
|
|
|
+ handleMatically(item, i) {
|
|
|
+ if (item.pinyin.trim()) {
|
|
|
+ this.loading_list[i].loading = true;
|
|
|
+ let MethodName = 'tool-PinyinToVoiceFile';
|
|
|
+ let data = {
|
|
|
+ pinyin: item.pinyin.trim().split(' ').join(','),
|
|
|
+ };
|
|
|
+ GetStaticResources(MethodName, data)
|
|
|
+ .then((res) => {
|
|
|
+ this.loading_list[i].loading = false;
|
|
|
+ if (res.status === 1) {
|
|
|
+ item.audio_file_id = res.file_id;
|
|
|
+ this.data.file_id_list.push(res.file_id);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ this.loading_list[i].loading = false;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 切割拼音
|
|
|
+ handleSplitPy(item) {
|
|
|
+ let index = item.pinyin.search(/0|1|2|3|4/);
|
|
|
+ if (index > -1) {
|
|
|
+ this.handleItemPinyin(item.pinyin, item.mark);
|
|
|
+ setTimeout(() => {
|
|
|
+ item.pinyin = this.matically_pinyin_obj[item.mark];
|
|
|
+ }, 100);
|
|
|
+ }
|
|
|
+ let pinyin_list = item.pinyin.trim().split(' ');
|
|
|
+ item.pinyin_item_list = [];
|
|
|
+ pinyin_list.forEach((itemp) => {
|
|
|
+ let obj = {
|
|
|
+ pinyin_item: itemp,
|
|
|
+ };
|
|
|
+ item.pinyin_item_list.push(obj);
|
|
|
+ });
|
|
|
+ },
|
|
|
+ handleReplaceTone(value, mark) {
|
|
|
+ if (!value) return;
|
|
|
+ value.split(/\s+/).forEach((item) => {
|
|
|
+ this.handleValue(item);
|
|
|
+ });
|
|
|
+ this.matically_pinyin_obj[mark] = this.res_arr
|
|
|
+ .map((item) =>
|
|
|
+ item.map(({ number, con }) => (number && con ? addTone(Number(number), con) : number || con || '')),
|
|
|
+ )
|
|
|
+ .filter((item) => item.length > 0)
|
|
|
+ .join(' ');
|
|
|
+ },
|
|
|
+ handleValue(valItem) {
|
|
|
+ let numList = [];
|
|
|
+ if (/[A-Za-zü]+\d/g.test(valItem)) {
|
|
|
+ valItem.split('').forEach((item, i) => {
|
|
|
+ if (/\d/.test(item)) {
|
|
|
+ let con = valItem.replace(/\d/g, '');
|
|
|
+ numList.push({
|
|
|
+ index: i,
|
|
|
+ number: item,
|
|
|
+ con,
|
|
|
+ isTran: true,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ numList = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ this.res_arr.push(numList.length === 0 ? [{ con: valItem }] : numList);
|
|
|
+ },
|
|
|
+ handleItemPinyin(content, mark) {
|
|
|
+ let content_arr = content.trim().split(' ');
|
|
|
+ this.res_arr = [];
|
|
|
+ this.$set(this.matically_pinyin_obj, mark, []);
|
|
|
+ content_arr.forEach((items, index) => {
|
|
|
+ let items_trim = items.trim();
|
|
|
+ if (items_trim) {
|
|
|
+ this.handleReplaceTone(items_trim, mark);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 修改拼音
|
|
|
+ changePinyin(item) {
|
|
|
+ if (this.data.other.audio_generation_method === 'auto') {
|
|
|
+ item.audio_file_id = '';
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 智能识别
|
|
|
+ * @param {String} text 识别数据
|
|
|
+ */
|
|
|
+ recognition(text) {
|
|
|
+ let arr = text
|
|
|
+ .split(/[\r\n]/)
|
|
|
+ .map((item) => item.trim())
|
|
|
+ .filter((item) => item);
|
|
|
+
|
|
|
+ if (arr.length > 0) {
|
|
|
+ this.data.stem = arr[0];
|
|
|
+ this.data.option_list = [];
|
|
|
+ arr.slice(1).map((content, index) => {
|
|
|
+ let content_item = content.split('&&');
|
|
|
+ this.data.option_list.push({
|
|
|
+ content: content_item[0] ? content_item[0] : '',
|
|
|
+ mark: getRandomNumber(),
|
|
|
+ audio_file_id: '',
|
|
|
+ pinyin: content_item[1] ? content_item[1] : '',
|
|
|
+ pinyin_item_list: [],
|
|
|
+ });
|
|
|
+ this.handleSplitPy(this.data.option_list[index]);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+
|
|
|
+ .content-item {
|
|
|
+ .upload-wrapper {
|
|
|
+ margin-top: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep .file-name {
|
|
|
+ width: 205px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .auto-matically {
|
|
|
+ display: flex;
|
|
|
+ flex-shrink: 0;
|
|
|
+ align-items: center;
|
|
|
+ width: 233px;
|
|
|
+ padding: 5px 12px;
|
|
|
+ background-color: $fill-color;
|
|
|
+ border-radius: 2px;
|
|
|
+
|
|
|
+ .audio-wrapper {
|
|
|
+ margin-right: 12px;
|
|
|
+
|
|
|
+ :deep .audio-play {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ color: #000;
|
|
|
+ background-color: initial;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep .audio-play.not-url {
|
|
|
+ color: #a1a1a1;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep .voice-play {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .auto-btn {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: 22px;
|
|
|
+ color: #1d2129;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .delete {
|
|
|
+ flex-shrink: 0;
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|