Browse Source

富文本拼音效果下的图片位置

zq 5 ngày trước cách đây
mục cha
commit
17cb4d4878

+ 57 - 7
src/components/PinyinText.vue

@@ -43,15 +43,21 @@
           </span>
         </span>
         <!-- 图片块 -->
-        <img
+        <div
           v-else-if="block.type === 'image'"
           :key="'image-' + index"
-          :src="block.src"
-          :alt="block.alt"
-          :width="block.width"
-          :height="block.height"
-          class="inline-image"
-        />
+          :style="block.containerStyle"
+          class="image-container"
+        >
+          <img
+            :src="block.src"
+            :alt="block.alt"
+            :width="block.width"
+            :height="block.height"
+            :style="block.imageStyle"
+            class="inline-image"
+          />
+        </div>
         <!-- 换行符 -->
         <br v-else-if="block.type === 'newline'" :key="'newline-' + index" />
       </template>
@@ -240,6 +246,38 @@ export default {
             const altMatch = attrs.match(/alt=["']([^"']*)["']/i);
             const widthMatch = attrs.match(/width=["']?(\d+)["']?/i);
             const heightMatch = attrs.match(/height=["']?(\d+)["']?/i);
+            const styleMatch = attrs.match(/style=["']([^"']*)["']/i);
+
+            // 获取父容器样式(从 tagStack 中累积)
+            const containerStyleObj = {};
+            tagStack.forEach((tagItem) => {
+              if (tagItem.style) {
+                tagItem.style.split(';').forEach((rule) => {
+                  if (rule.trim()) {
+                    const [prop, value] = rule.split(':').map((s) => s.trim());
+                    if (prop && value) {
+                      const camelProp = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
+                      containerStyleObj[camelProp] = value;
+                    }
+                  }
+                });
+              }
+            });
+
+            // 解析图片自身样式
+            let imageStyleObj = {};
+            if (styleMatch) {
+              const styleStr = styleMatch[1];
+              styleStr.split(';').forEach((rule) => {
+                if (rule.trim()) {
+                  const [prop, value] = rule.split(':').map((s) => s.trim());
+                  if (prop && value) {
+                    const camelProp = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
+                    imageStyleObj[camelProp] = value;
+                  }
+                }
+              });
+            }
 
             blocks.push({
               type: 'image',
@@ -247,6 +285,8 @@ export default {
               alt: altMatch ? altMatch[1] : '',
               width: widthMatch ? widthMatch[1] : null,
               height: heightMatch ? heightMatch[1] : null,
+              containerStyle: containerStyleObj,
+              imageStyle: imageStyleObj,
             });
           }
         } else if (item.is_style === 'true' || item.is_style === true) {
@@ -481,6 +521,16 @@ export default {
       display: inline-flex;
       flex-wrap: wrap;
     }
+
+    .image-container {
+      display: block;
+
+      .inline-image {
+        display: inline-block;
+        max-width: 100%;
+        height: auto;
+      }
+    }
   }
 
   .pinyin-paragraph {

+ 82 - 29
src/views/book/courseware/create/components/base/rich_text/RichText.vue

@@ -2,13 +2,21 @@
   <ModuleBase ref="base" :type="data.type">
     <template #content>
       <div :style="{ width: data.note_list.length > 0 ? '' : '100%' }">
-        <RichText ref="richText" v-model="data.content" :is-view-note="true"
-          :is-title="isEnable(data.property.is_title)" :is-view-pinyin="isEnable(data.property.view_pinyin)"
-          :font-size="data?.unified_attrib?.font_size" :font-family="data?.unified_attrib?.font" placeholder="输入内容"
-          @view-explanatory-note="viewExplanatoryNote" @selectContentSetMemo="selectContentSetMemo"
+        <RichText
+          ref="richText"
+          v-model="data.content"
+          :is-view-note="true"
+          :is-title="isEnable(data.property.is_title)"
+          :is-view-pinyin="isEnable(data.property.view_pinyin)"
+          :font-size="data?.unified_attrib?.font_size"
+          :font-family="data?.unified_attrib?.font"
+          placeholder="输入内容"
+          @view-explanatory-note="viewExplanatoryNote"
+          @selectContentSetMemo="selectContentSetMemo"
           @createParsedTextInfoPinyin="createParsedTextInfoPinyin"
           @createParsedTextStyleForTitle="createParsedTextStyleForTitle"
-          @compareAnnotationAndSave="compareAnnotationAndSave" />
+          @compareAnnotationAndSave="compareAnnotationAndSave"
+        />
 
         <!-- 生成音频 -->
         <template v-if="data.property.is_enable_voice === 'true'">
@@ -17,13 +25,21 @@
           </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(data.content)">
+              <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(data.content)"
+              >
                 <SvgIcon icon-class="voiceprint-line" class="record" />
-                <span class="auto-btn">{{ data.audio_file_id ? '已生成' : '生成音频' }}</span>{{ data.audio_file_id ? '成功' : ''
-                }}
+                <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>
@@ -32,31 +48,68 @@
 
         <el-button class="btn" @click="openMultilingual">多语言</el-button>
 
-        <MultilingualFill :visible.sync="multilingualVisible" :text="data.content" :translations="data.multilingual"
-          @SubmitTranslation="handleMultilingualTranslation" />
+        <MultilingualFill
+          :visible.sync="multilingualVisible"
+          :text="data.content"
+          :translations="data.multilingual"
+          @SubmitTranslation="handleMultilingualTranslation"
+        />
 
-        <el-button v-show="isEnable(data.property.view_pinyin)" style="margin-left: 10px" class="btn"
-          @click.native="showWordFlag = true">分词校对</el-button>
+        <el-button
+          v-show="isEnable(data.property.view_pinyin)"
+          style="margin-left: 10px"
+          class="btn"
+          @click.native="showWordFlag = true"
+          >分词校对</el-button
+        >
 
-        <el-dialog v-if="showWordFlag" :visible.sync="showWordFlag" :show-close="true" :close-on-click-modal="true"
-          :modal-append-to-body="true" :append-to-body="true" :lock-scroll="true" width="80%" class="practiceBox">
+        <el-dialog
+          v-if="showWordFlag"
+          :visible.sync="showWordFlag"
+          :show-close="true"
+          :close-on-click-modal="true"
+          :modal-append-to-body="true"
+          :append-to-body="true"
+          :lock-scroll="true"
+          width="80%"
+          class="practiceBox"
+        >
           <CheckWord :data="wordData" @saveWord="saveWord" />
         </el-dialog>
 
-        <el-button v-show="isEnable(data.property.view_pinyin)" style="margin-left: 10px" class="btn"
-          @click.native="generatPinyin">生成拼音</el-button>
+        <el-button
+          v-show="isEnable(data.property.view_pinyin)"
+          style="margin-left: 10px"
+          class="btn"
+          @click.native="generatPinyin"
+          >生成拼音</el-button
+        >
 
         <el-divider v-if="isEnable(data.property.view_pinyin)" content-position="left">拼音效果</el-divider>
-        <PinyinText v-if="isEnable(data.property.view_pinyin)" :id="richId + '_pinyin_text'" ref="PinyinText"
-          :paragraph-list="data.paragraph_list" :rich-text-list="data.rich_text_list"
-          :paragraph-version="paragraphVersion" :pinyin-position="data.property.pinyin_position"
+        <PinyinText
+          v-if="isEnable(data.property.view_pinyin)"
+          :id="richId + '_pinyin_text'"
+          ref="PinyinText"
+          :paragraph-list="data.paragraph_list"
+          :rich-text-list="data.rich_text_list"
+          :paragraph-version="paragraphVersion"
+          :pinyin-position="data.property.pinyin_position"
           :pinyin-overall-position="data.property.pinyin_overall_position"
-          :pinyin-size="data?.unified_attrib?.pinyin_size" :font-size="data?.unified_attrib?.font_size"
-          :font-family="data?.unified_attrib?.font" :component-type="data.type"
-          :pinyin-padding="data.property.pinyin_padding" @fillCorrectPinyin="fillCorrectPinyin" />
+          :pinyin-size="data?.unified_attrib?.pinyin_size"
+          :font-size="data?.unified_attrib?.font_size"
+          :font-family="data?.unified_attrib?.font"
+          :component-type="data.type"
+          :pinyin-padding="data.property.pinyin_padding"
+          @fillCorrectPinyin="fillCorrectPinyin"
+        />
       </div>
-      <ExplanatoryNoteDialog ref="explanatoryNote" :open.sync="isViewExplanatoryNoteDialog" :init-data="oldRichData"
-        @confirm="confirmExplanatoryNote" @cancel="cancelExplanatoryNote" />
+      <ExplanatoryNoteDialog
+        ref="explanatoryNote"
+        :open.sync="isViewExplanatoryNoteDialog"
+        :init-data="oldRichData"
+        @confirm="confirmExplanatoryNote"
+        @cancel="cancelExplanatoryNote"
+      />
     </template>
   </ModuleBase>
 </template>
@@ -163,7 +216,7 @@ export default {
             this.data.audio_file_id = file_id;
           }
         })
-        .catch(() => { });
+        .catch(() => {});
     },
 
     async saveWord(saveArr) {
@@ -181,7 +234,7 @@ export default {
       let isCreate =
         this.data.paragraph_list.length <= 0 ||
         this.data.paragraph_list_parameter.is_first_sentence_first_hz_pinyin_first_char_upper_case !==
-        property.is_first_sentence_first_hz_pinyin_first_char_upper_case;
+          property.is_first_sentence_first_hz_pinyin_first_char_upper_case;
       if (text && isCreate) {
         this.data.paragraph_list_parameter.text = text;
         this.data.paragraph_list_parameter.is_first_sentence_first_hz_pinyin_first_char_upper_case =

+ 41 - 12
src/views/book/courseware/create/components/base/rich_text/RichTextSetting.vue

@@ -19,14 +19,24 @@
         <el-switch v-model="property.view_pinyin" active-value="true" inactive-value="false" />
       </el-form-item>
       <el-form-item label="拼音位置">
-        <el-radio v-for="{ value, label } in pinyinPositionList" :key="value" v-model="property.pinyin_position"
-          :label="value" :disabled="!isEnable(property.view_pinyin)">
+        <el-radio
+          v-for="{ value, label } in pinyinPositionList"
+          :key="value"
+          v-model="property.pinyin_position"
+          :label="value"
+          :disabled="!isEnable(property.view_pinyin)"
+        >
           {{ label }}
         </el-radio>
       </el-form-item>
       <el-form-item label="对齐样式">
-        <el-radio v-for="{ value, label } in pinyinOverallPositionList" :key="value"
-          v-model="property.pinyin_overall_position" :label="value" :disabled="!isEnable(property.view_pinyin)">
+        <el-radio
+          v-for="{ value, label } in pinyinOverallPositionList"
+          :key="value"
+          v-model="property.pinyin_overall_position"
+          :label="value"
+          :disabled="!isEnable(property.view_pinyin)"
+        >
           {{ label }}
         </el-radio>
       </el-form-item>
@@ -42,8 +52,12 @@
       <template v-if="property.audio_generation_method === 'auto'">
         <el-form-item label="音色">
           <el-select v-model="property.voice_type" placeholder="请选择">
-            <el-option v-for="{ voice_type, name } in voice_type_list" :key="voice_type" :label="name"
-              :value="voice_type" />
+            <el-option
+              v-for="{ voice_type, name } in voice_type_list"
+              :key="voice_type"
+              :label="name"
+              :value="voice_type"
+            />
           </el-select>
         </el-form-item>
         <el-form-item label="风格情感">
@@ -59,8 +73,12 @@
       </template>
 
       <el-form-item label="边距">
-        <el-input v-model="property.pinyin_padding" :disabled="!isEnable(property.view_pinyin)"
-          placeholder="例:10px 20px 30px 40px" style="width: 95%" />
+        <el-input
+          v-model="property.pinyin_padding"
+          :disabled="!isEnable(property.view_pinyin)"
+          placeholder="例:10px 20px 30px 40px"
+          style="width: 95%"
+        />
       </el-form-item>
       <el-form-item label="">
         <div style="font-size: 12px; line-height: 1.5; color: #999">
@@ -68,8 +86,13 @@
         </div>
       </el-form-item>
       <el-form-item label="">
-        <el-checkbox v-model="property.is_first_sentence_first_hz_pinyin_first_char_upper_case"
-          :disabled="!isEnable(property.view_pinyin)" true-label="true" false-label="false">句首大写</el-checkbox>
+        <el-checkbox
+          v-model="property.is_first_sentence_first_hz_pinyin_first_char_upper_case"
+          :disabled="!isEnable(property.view_pinyin)"
+          true-label="true"
+          false-label="false"
+          >句首大写</el-checkbox
+        >
       </el-form-item>
     </el-form>
   </div>
@@ -77,7 +100,13 @@
 
 <script>
 import SettingMixin from '@/views/book/courseware/create/components/common/SettingMixin';
-import { isEnable, pinyinPositionList, pinyinOverallPositionList, audioGenerationMethodList,speedRatioList } from '@/views/book/courseware/data/common';
+import {
+  isEnable,
+  pinyinPositionList,
+  pinyinOverallPositionList,
+  audioGenerationMethodList,
+  speedRatioList,
+} from '@/views/book/courseware/data/common';
 import { getRichTextProperty } from '@/views/book/courseware/data/richText';
 import { GetTextToAudioConfParamList } from '@/api/app';
 
@@ -107,7 +136,7 @@ export default {
     this.getTextToAudioConfParamList();
   },
   methods: {
-     // 得到文本转音频的配置参数列表
+    // 得到文本转音频的配置参数列表
     getTextToAudioConfParamList() {
       GetTextToAudioConfParamList()
         .then(({ status, voice_type_list, emotion_list }) => {

+ 29 - 14
src/views/book/courseware/preview/components/rich_text/RichTextPreview.vue

@@ -5,31 +5,46 @@
 
     <div class="main">
       <div ref="leftDiv" :style="{ width: data.note_list?.length > 0 ? '' : '100%' }">
-        <PinyinText v-if="isEnable(data.property.view_pinyin)" :is-enable-voice="data.property.is_enable_voice"
-          :audio-file-id="data.audio_file_id" :unified-attrib="data.unified_attrib"
-          :paragraph-list="data.paragraph_list" :rich-text-list="data.rich_text_list"
+        <PinyinText
+          v-if="isEnable(data.property.view_pinyin)"
+          :is-enable-voice="data.property.is_enable_voice"
+          :audio-file-id="data.audio_file_id"
+          :unified-attrib="data.unified_attrib"
+          :paragraph-list="data.paragraph_list"
+          :rich-text-list="data.rich_text_list"
           :pinyin-position="data.property.pinyin_position"
           :pinyin-overall-position="data.property.pinyin_overall_position"
-          :pinyin-size="data?.unified_attrib?.pinyin_size" :font-size="data?.unified_attrib?.font_size"
-          :font-family="data?.unified_attrib?.font" :pinyin-padding="data.property.pinyin_padding"
-          :is-preview="isPreview" />
+          :pinyin-size="data?.unified_attrib?.pinyin_size"
+          :font-size="data?.unified_attrib?.font_size"
+          :font-family="data?.unified_attrib?.font"
+          :pinyin-padding="data.property.pinyin_padding"
+          :is-preview="isPreview"
+        />
         <div v-else>
-          <AudioPlay v-if="isEnable(data.property.is_enable_voice)" :file-id="data.audio_file_id" :theme-color="data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : ''
-            " />
+          <AudioPlay
+            v-if="isEnable(data.property.is_enable_voice)"
+            :file-id="data.audio_file_id"
+            :theme-color="data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : ''"
+          />
           <span class="rich-text" @click="handleRichFillClick" v-html="convertText(sanitizeHTML(data.content))"></span>
-
         </div>
-
       </div>
 
-
       <div v-show="showLang" class="lang">
-        {{data.multilingual?.find((item) => item.type === getLang())?.translation}}
+        {{ data.multilingual?.find((item) => item.type === getLang())?.translation }}
       </div>
     </div>
 
-    <el-dialog ref="optimizedDialog" title="" :visible.sync="noteDialogVisible" width="680px" :style="dialogStyle"
-      :close-on-click-modal="false" destroy-on-close @close="noteDialogVisible = false">
+    <el-dialog
+      ref="optimizedDialog"
+      title=""
+      :visible.sync="noteDialogVisible"
+      width="680px"
+      :style="dialogStyle"
+      :close-on-click-modal="false"
+      destroy-on-close
+      @close="noteDialogVisible = false"
+    >
       <span v-html="sanitizeHTML(selectedNote)"></span>
     </el-dialog>
   </div>