|
@@ -0,0 +1,225 @@
|
|
|
+<template>
|
|
|
+ <ModuleBase :type="data.type">
|
|
|
+ <template #content>
|
|
|
+ <!-- eslint-disable max-len -->
|
|
|
+ <div class="fill-wrapper">
|
|
|
+ <RichText
|
|
|
+ v-model="data.content"
|
|
|
+ toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
|
|
|
+ :wordlimit-num="false"
|
|
|
+ />
|
|
|
+ <div v-if="data.audio_file_id">
|
|
|
+ <SoundRecord :wav-blob.sync="data.audio_file_id" />
|
|
|
+ </div>
|
|
|
+ <template v-else>
|
|
|
+ <div :class="['upload-audio-play']">
|
|
|
+ <UploadAudio
|
|
|
+ v-if="data.property.audio_generation_method === 'upload'"
|
|
|
+ :file-id="data.audio_file_id"
|
|
|
+ :show-upload="!data.audio_file_id"
|
|
|
+ @upload="uploads"
|
|
|
+ @deleteFile="deleteFiles"
|
|
|
+ />
|
|
|
+ <div v-else-if="data.property.audio_generation_method === 'auto'" class="auto-matic" @click="handleMatic">
|
|
|
+ <SvgIcon icon-class="voiceprint-line" class="record" />
|
|
|
+ <span class="auto-btn">{{ data.audio_file_id ? '已生成' : '生成音频' }}</span
|
|
|
+ >{{ data.audio_file_id ? '成功' : '' }}
|
|
|
+ </div>
|
|
|
+ <SoundRecord v-else :wav-blob.sync="data.audio_file_id" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </ModuleBase>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import ModuleMixin from '../../common/ModuleMixin';
|
|
|
+import SoundRecord from '@/views/book/courseware/create/components/question/fill/components/SoundRecord.vue';
|
|
|
+import UploadAudio from '@/views/book/courseware/create/components/question/fill/components/UploadAudio.vue';
|
|
|
+
|
|
|
+import { getPinyinBaseData } from '@/views/book/courseware/data/pinyinBase';
|
|
|
+import { addTone, handleToneValue } from '@/views/book/courseware/data/common';
|
|
|
+import { getRandomNumber } from '@/utils';
|
|
|
+import { GetStaticResources } from '@/api/app';
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'PinyinBasePage',
|
|
|
+ components: {
|
|
|
+ SoundRecord,
|
|
|
+ UploadAudio,
|
|
|
+ },
|
|
|
+ mixins: [ModuleMixin],
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ data: getPinyinBaseData(),
|
|
|
+ };
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // 识别文本
|
|
|
+ identifyText() {
|
|
|
+ this.data.model_essay = [];
|
|
|
+ this.data.answer.answer_list = [];
|
|
|
+
|
|
|
+ this.data.content
|
|
|
+ .split(/<(p|div)[^>]*>(.*?)<\/(p|div)>/g)
|
|
|
+ .filter((s) => s && !s.match(/^(p|div)$/))
|
|
|
+ .forEach((item) => {
|
|
|
+ if (item.charCodeAt() === 10) return;
|
|
|
+ let str = item
|
|
|
+ // 去除所有的 font-size 样式
|
|
|
+ .replace(/font-size:\s*\d+(\.\d+)?px;/gi, '')
|
|
|
+ // 匹配 class 名为 rich-fill 的 span 标签和三个以上的_,并将它们组成数组
|
|
|
+ .replace(/<span class="rich-fill".*?>(.*?)<\/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;
|
|
|
+ },
|
|
|
+ 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(' ');
|
|
|
+ },
|
|
|
+ uploads(file_id) {
|
|
|
+ this.data.audio_file_id = file_id;
|
|
|
+ },
|
|
|
+ deleteFiles() {
|
|
|
+ this.data.audio_file_id = '';
|
|
|
+ },
|
|
|
+ // 自动生成音频
|
|
|
+ handleMatic() {
|
|
|
+ GetStaticResources('tool-TextToVoiceFile', {
|
|
|
+ text: this.data.content.replace(/<[^>]+>/g, ''),
|
|
|
+ })
|
|
|
+ .then(({ status, file_id }) => {
|
|
|
+ if (status === 1) {
|
|
|
+ this.data.audio_file_id = file_id;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch(() => {});
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.fill-wrapper {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ row-gap: 16px;
|
|
|
+ align-items: flex-start;
|
|
|
+
|
|
|
+ :deep .rich-wrapper {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tips {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+
|
|
|
+ .auto-matic,
|
|
|
+ .upload-audio-play {
|
|
|
+ :deep .upload-wrapper {
|
|
|
+ margin-top: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .audio-wrapper {
|
|
|
+ :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-matic {
|
|
|
+ display: flex;
|
|
|
+ flex-shrink: 0;
|
|
|
+ column-gap: 12px;
|
|
|
+ align-items: center;
|
|
|
+ width: 200px;
|
|
|
+ padding: 5px 12px;
|
|
|
+ background-color: $fill-color;
|
|
|
+ border-radius: 2px;
|
|
|
+
|
|
|
+ .auto-btn {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: 22px;
|
|
|
+ color: #1d2129;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .correct-answer {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ .el-input {
|
|
|
+ width: 180px;
|
|
|
+
|
|
|
+ :deep &__prefix {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ color: $text-color;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|