Browse Source

Merge branch 'master' of http://60.205.254.193:3000/GCLS/eep_page

dsy 1 week ago
parent
commit
a81f9fd6e2

+ 0 - 6
src/components/RichText.vue

@@ -174,12 +174,6 @@ export default {
               isRendered = true;
               window.MathJax.typesetPromise([editor.getBody()]);
             }
-            if (e.target.classList.contains('rich-fill')) {
-              const noteId = e.target.getAttribute('data-annotation-id');
-              that.$emit('selectNote', noteId);
-            } else {
-              that.$emit('selectNote', '');
-            }
           });
 
           // 添加 MathJax 按钮

+ 0 - 54
src/views/book/courseware/create/components/base/rich_text/RichText.vue

@@ -13,7 +13,6 @@
           @selectContentSetMemo="selectContentSetMemo"
           @crateParsedTextInfoPinyin="crateParsedTextInfoPinyin"
           @compareAnnotationAndSave="compareAnnotationAndSave"
-          @selectNote="selectNote"
         />
         <el-divider v-if="isEnable(data.property.view_pinyin)" content-position="left">拼音效果</el-divider>
         <PinyinText
@@ -25,17 +24,6 @@
           @fillCorrectPinyin="fillCorrectPinyin"
         />
       </div>
-      <div v-if="data.note_list.length > 0" class="note-list">
-        <span>注释</span>
-        <ul>
-          <li v-for="note in data.note_list" :key="note.id">
-            <p :style="{ 'background-color': selectedNoteId == note.id ? '#FFF2C9' : '#CCC' }">
-              {{ note.selectText }}
-            </p>
-            <span v-html="sanitizeHTML(note.note)"></span>
-          </li>
-        </ul>
-      </div>
       <ExplanatoryNoteDialog
         ref="explanatoryNote"
         :open.sync="isViewExplanatoryNoteDialog"
@@ -69,7 +57,6 @@ export default {
       richId: '',
       isViewExplanatoryNoteDialog: false,
       oldRichData: {},
-      selectedNoteId: '',
     };
   },
   watch: {
@@ -150,9 +137,6 @@ export default {
         this.oldRichData = {};
       }
     },
-    selectNote(id) {
-      this.selectedNoteId = id;
-    },
     // 点击弹窗确认-保存
     confirmExplanatoryNote(text) {
       console.log(text);
@@ -201,41 +185,3 @@ export default {
   },
 };
 </script>
-<style lang="scss" scoped>
-:deep .module-content {
-  display: flex;
-  column-gap: 10px;
-
-  .note-list {
-    display: flex;
-    flex-direction: column;
-    width: 20%;
-
-    > span {
-      font-weight: bold;
-    }
-
-    li {
-      margin-top: 6px;
-      border-radius: 8px;
-      box-shadow: 1px 3px 2px rgba(0, 0, 0, 10%);
-
-      p {
-        padding: 4px;
-        margin: 0;
-        background-color: #ccc;
-        border-radius: 8px 8px 0 0;
-      }
-
-      span {
-        display: block;
-        padding: 8px;
-      }
-    }
-
-    p {
-      text-align: center;
-    }
-  }
-}
-</style>

+ 8 - 8
src/views/book/courseware/data/bookType.js

@@ -247,14 +247,14 @@ export const bookTypeOption = [
         set: ArticleSetting,
         preview: ArticlePreview,
       },
-      {
-        value: 'dialogue_article',
-        label: '对话课文',
-        icon: '',
-        component: DialogueArticlePage,
-        set: DialogueArticleSetting,
-        preview: DialogueArticlePreview,
-      },
+      // {
+      //   value: 'dialogue_article',
+      //   label: '对话课文',
+      //   icon: '',
+      //   component: DialogueArticlePage,
+      //   set: DialogueArticleSetting,
+      //   preview: DialogueArticlePreview,
+      // },
       {
         value: 'select',
         label: '选择组件',

+ 616 - 21
src/views/book/courseware/preview/components/character/CharacterPreview.vue

@@ -4,23 +4,264 @@
     <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
 
     <div class="main">
-      <div v-for="(item, index) in data.content_list" :key="index" class="content-box">
-        <div class="main-top" :style="{ width: item.hz_strokes_list.length * 64 + 'px' }">
-          <AudioPlay v-if="isEnable(data.property.is_enable_voice)" :file-id="item.audio_file_id" theme-color="gray" />
-          <span v-if="isEnable(data.property.is_enable_pinyin)" class="pinyin">{{ item.pinyin }}</span>
+      <template v-if="data.property.model === 'write'">
+        <div
+          class="item-box"
+          :class="[!item.is_margin ? 'item-box-write' : '']"
+          v-for="(item, index) in data.option_list"
+          :key="index"
+        >
+          <div
+            class="number-box"
+            :style="{
+              marginTop: isEnable(data.property.is_enable_pinyin) ? '30px' : '',
+            }"
+          >
+            <span class="number">{{ index + 1 }}</span>
+          </div>
+          <div class="pinyin-en" :class="[item.is_example ? 'item-example' : '']">
+            <div v-if="isEnable(data.property.is_enable_pinyin) && item.is_common_pinyin" class="pinyin">
+              {{ item.pinyin }}
+            </div>
+            <div class="items-flex">
+              <div class="items" v-for="(items, indexs) in item.content_list" :key="indexs">
+                <div v-if="isEnable(data.property.is_enable_pinyin) && !item.is_common_pinyin" class="pinyin">
+                  {{ items.pinyin }}
+                </div>
+                <div class="items-content">
+                  <template v-if="items && items.type === 'img'">
+                    <el-image
+                      class="items-image"
+                      v-if="items.file_list[0]"
+                      :src="items.file_list[0].file_url"
+                      fit="contain"
+                    ></el-image>
+                  </template>
+                  <template v-else-if="items && items.type === 'lian'">
+                    <span class="items-lian">{{ items.con }}</span>
+                  </template>
+                  <Strockplayredline
+                    v-if="items && items.type === 'hanzi'"
+                    :Book_text="items.con"
+                    :playStorkes="isEnable(data.property.is_enable_play_structure)"
+                    :curItem="
+                      isEnable(data.property.is_enable_high_strokes)
+                        ? data.property.model === 'input'
+                          ? items.high_strokes
+                          : userAnswer[index].item[indexs]
+                        : null
+                    "
+                    :type="data.property.model === 'input' ? 'newWord-template-input' : data.type"
+                    :targetDiv="'newWordTemplate' + items.con + index + indexs"
+                    :hz_json="items.hz_info[0].hzDetail.hz_json"
+                    class="hanzi-storck"
+                    :class="[
+                      !item.is_margin && item.content_list.length > 1 && indexs == 0 ? 'leftBorderRadius' : '',
+
+                      !item.is_margin && item.content_list.length > 1 && indexs == item.content_list.length - 1
+                        ? 'rightBorderRadius'
+                        : '',
+                      !item.is_margin &&
+                      item.content_list.length > 1 &&
+                      indexs != item.content_list.length - 1 &&
+                      indexs != 0
+                        ? 'NoborderRadius'
+                        : '',
+                      !item.is_margin && item.content_list.length > 1 && indexs != item.content_list.length - 1
+                        ? 'NoborderRight'
+                        : '',
+                    ]"
+                  />
+
+                  <div
+                    :class="[
+                      'strockplay-newWord',
+                      !item.is_margin && item.content_list.length > 1 && indexs == 0 ? 'leftBorderRadius' : '',
+
+                      !item.is_margin && item.content_list.length > 1 && indexs == item.content_list.length - 1
+                        ? 'rightBorderRadius'
+                        : '',
+                      !item.is_margin &&
+                      item.content_list.length > 1 &&
+                      indexs != item.content_list.length - 1 &&
+                      indexs != 0
+                        ? 'NoborderRadius'
+                        : '',
+                      !item.is_margin && item.content_list.length > 1 && indexs != item.content_list.length - 1
+                        ? 'NoborderRight'
+                        : '',
+                    ]"
+                    v-if="items && items.type === 'write'"
+                    @click="
+                      freeWrite(
+                        userAnswer[index][indexs].imgArr ? userAnswer[index][indexs].imgArr : null,
+                        index,
+                        indexs,
+                      )
+                    "
+                  >
+                    <SvgIcon icon-class="hanzi-writer-bg" class="character-target-bg" />
+                    <img
+                      v-if="
+                        !play_status &&
+                        userAnswer[index][indexs].imgArr &&
+                        userAnswer[index][indexs].imgArr.strokes_image
+                      "
+                      class="hanzi-writer-img"
+                      :src="userAnswer[index][indexs].imgArr.strokes_image"
+                      alt=""
+                    />
+                  </div>
+                </div>
+              </div>
+            </div>
+            <div class="en-common">{{ item.shiyi }}</div>
+          </div>
+        </div>
+      </template>
+      <template v-else>
+        <div :class="['words-box']">
+          <div v-for="(item, index) in data.option_list" :key="index" :class="['words-item']">
+            <div class="words-top">
+              <div class="words-left" :style="{}">
+                <AudioPlay :file-id="item.audio_file_id" theme-color="gray" />
+                <span class="pinyin">{{ item.pinyin }}</span>
+              </div>
+            </div>
+            <div class="card-box">
+              <!-- 描红 -->
+              <template v-for="(items, indexs) in item.content_list">
+                <Strockplayredline
+                  :key="'miao' + indexs"
+                  :Book_text="items.con"
+                  :playStorkes="isEnable(data.property.is_enable_stroke)"
+                  :curItem="null"
+                  :targetDiv="'newWordTemplate' + items.con + index + indexs"
+                  :hz_json="items.hz_info[0].hzDetail.hz_json"
+                  class="hanzi-storck"
+                  :class="['strock-chinese', 'border-right-none']"
+                />
+              </template>
+
+              <div
+                v-for="(itemI, indexI) in item.miao_arr"
+                :key="indexI + index"
+                style="display: flex"
+                @click="miaoStorkes(index, indexI, item.mark, item.content, itemI.strokes)"
+              >
+                <Strockplayredlines
+                  v-if="
+                    userAnswer[index].strokes_content_list[indexI] &&
+                    userAnswer[index].strokes_content_list[indexI].flag === 'true'
+                  "
+                  :play-storkes="false"
+                  :book-text="item.content"
+                  :target-div="'write-praT' + Math.random().toString(36).substring(2, 10)"
+                  :book-strokes="itemI.strokes"
+                  :class="['strock-chinese']"
+                />
+                <Strockplayredlines
+                  v-else
+                  :play-storkes="false"
+                  :book-text="item.content"
+                  :target-div="'write-praT' + Math.random().toString(36).substring(2, 10)"
+                  :book-strokes="itemI.strokes"
+                  :stroke-color="'#ddd'"
+                  :class="['strock-chinese']"
+                />
+              </div>
+              <!-- 书写 -->
+              <div v-for="(items, indexs) in item.imgArr" :key="'write' + indexs" class="con-box">
+                <div
+                  :class="['strockplay-newWord']"
+                  @click="
+                    freeWrite(
+                      userAnswer[index].strokes_content_list[indexs].imgArr
+                        ? userAnswer[index].strokes_content_list[indexs].imgArr
+                        : null,
+                      index,
+                      indexs,
+                    )
+                  "
+                >
+                  <SvgIcon icon-class="hanzi-writer-bg" class="character-target-bg" />
+                  <img
+                    v-if="
+                      !play_status &&
+                      items &&
+                      userAnswer[index].strokes_content_list[indexs].imgArr &&
+                      userAnswer[index].strokes_content_list[indexs].imgArr.strokes_image
+                    "
+                    class="hanzi-writer-img"
+                    :src="userAnswer[index].strokes_content_list[indexs].imgArr.strokes_image"
+                    alt=""
+                  />
+                </div>
+              </div>
+            </div>
+            <div class="words-bottom" v-if="item.shiyi">
+              {{ item.shiyi }}
+            </div>
+          </div>
         </div>
-        <div class="strock-chinese-box">
-          <Strockplayredline
-            v-for="(items, indexs) in item.hz_strokes_list"
-            :key="indexs"
-            :play-storkes="isEnable(data.property.is_enable_stroke)"
-            :book-text="item.con"
-            :target-div="'pre' + item.con + indexs"
-            :book-strokes="items.strokes"
-            :class="['strock-chinese', indexs !== item.hz_strokes_list.length - 1 ? 'border-right-none' : '']"
-            :bg-type="data.property.frame_type"
-            :frame-color="data.property.frame_color"
+      </template>
+      <div v-if="if_free_show" class="practiceBox practice-box-strock">
+        <FreewriteLettle
+          ref="freePaint"
+          :current-tree-i-d="'1234456'"
+          :current-hz="current_hz"
+          :curren-hz-data="current_hz_data"
+          :row-index="active_index"
+          :col-index="active_col_index"
+          :disabled="disabled"
+          :show-play="isEnable(data.property.is_enable_play_back)"
+          @closeIfFreeShow="closeIfFreeShow"
+          @changePraShow="changePraShow"
+          @changeCurQue="changeCurQue"
+          @deleteWriteRecord="deleteWriteRecord"
+        />
+      </div>
+      <div v-if="if_miao_show" class="practiceBox practice-box-strock">
+        <div class="miao-box">
+          <i class="el-icon-close close-icon" @click="if_miao_show = false"></i>
+          <Strockplayredlines
+            v-if="
+              (userAnswer[active_index].strokes_content_list[active_col_index].flag &&
+                userAnswer[active_index].strokes_content_list[active_col_index].flag === 'true') ||
+              disabled
+            "
+            :play-storkes="false"
+            :book-text="current_hz"
+            :target-div="'write-praT' + current_hz + active_col_index + Math.random().toString(36).substring(2, 10)"
+            :book-strokes="current_hz_data"
+            :stroke-color="
+              disabled &&
+              (!userAnswer[active_index].strokes_content_list[active_col_index].flag ||
+                (userAnswer[active_index].strokes_content_list[active_col_index].flag &&
+                  userAnswer[active_index].strokes_content_list[active_col_index].flag === 'false'))
+                ? '#ddd'
+                : ''
+            "
+          />
+          <Strockred
+            ref="strockRed"
+            :class="[
+              'strock-red',
+              userAnswer[active_index].strokes_content_list[active_col_index].flag &&
+              userAnswer[active_index].strokes_content_list[active_col_index].flag === 'true'
+                ? 'strock-red-zindex'
+                : '',
+            ]"
+            :book-text="current_hz"
+            :hanzi-color="hanzi_color"
+            :target-div="'write-praT' + current_hz + active_col_index + Math.random().toString(36).substring(2, 10)"
+            :book-strokes="current_hz_data"
+            :is-answer.sync="userAnswer[active_index].strokes_content_list[active_col_index].flag"
+            :show-error-tip="isEnable(data.property.is_enable_error)"
           />
+          <div v-if="!disabled" :class="['reset-box']" @click="resetHanzi">
+            <SvgIcon icon-class="reset" class="reset-btn" />
+          </div>
         </div>
       </div>
     </div>
@@ -32,24 +273,167 @@ import { getCharacterData } from '@/views/book/courseware/data/character';
 
 import PreviewMixin from '../common/PreviewMixin';
 import AudioPlay from '../character_base/components/AudioPlay.vue';
-import Strockplayredline from '../character_base/components/Strockplayredline.vue';
+import Strockplayredline from '../newWord_template/components/Strockplayredline.vue';
+import Strockplayredlines from '../character_base/components/Strockplayredline.vue';
+import Strockred from '../character_base/components/Strockred.vue';
+import FreewriteLettle from '../character_base/components/FreewriteLettle.vue';
 
 export default {
   name: 'CharacterPreview',
   components: {
     AudioPlay,
     Strockplayredline,
+    Strockred,
+    FreewriteLettle,
+    Strockplayredlines,
   },
   mixins: [PreviewMixin],
   data() {
     return {
       data: getCharacterData(),
+      userAnswer: [],
+      hanzi_color: '#404040', // 描红汉字底色
+      writer_number_yuan: 15,
+      writer_number: null, // 书写个数
+      miao_number: null, // 描红个数
+      if_free_show: false,
+      free_img: [],
+      active_index: null,
+      active_col_index: null,
+      current_hz: '', // 当前汉字
+      current_hz_data: null, // 当前汉字数据
+      play_status: false, // 播放状态
+      active_mark: '',
+      option_list: [],
+      show_preview: false,
+      miao_arr: [],
+      if_miao_show: false, // 描红模块
     };
   },
   computed: {},
-  watch: {},
+  watch: {
+    'data.option_list': {
+      handler(val) {
+        if (val) {
+          this.handleData();
+        }
+      },
+      deep: true,
+      immediate: true,
+    },
+  },
   created() {},
-  methods: {},
+  methods: {
+    handleData() {
+      console.log(this.data.option_list);
+      let answer_list = [];
+      this.data.option_list.forEach((item, index) => {
+        if (this.data.property.model === 'write') {
+          let arr = [];
+          item.content_list.forEach((items) => {
+            let obj = null;
+            if (items.type === 'write') {
+              obj = {
+                imgArr: null,
+              };
+            }
+            arr.push(obj);
+          });
+          answer_list.push(arr);
+        } else {
+          let miao_arr = [];
+          let arr = [];
+          if (item.content.trim()) {
+            for (let i = 0; i < this.data.property.write_number; i++) {
+              item.content_list.forEach((items) => {
+                arr.push({ imgArr: null, flag: null });
+              });
+            }
+            for (let i = 0; i < this.data.property.miao_number; i++) {
+              item.content_list.forEach((items) => {
+                miao_arr.push({
+                  strokes: items && items.hz_info && items.hz_info[0] ? items.hz_info[0].hzDetail.hz_json : null,
+                  length: item.content_list.length,
+                });
+              });
+            }
+            item.imgArr = arr;
+            item.miao_arr = miao_arr;
+            // if (this.isJudgingRightWrong) {
+            //   item.imgArr = this.userAnswer.find(
+            //     (items) => items.mark === item.mark,
+            //   )?.strokes_content_list;
+            // }
+          }
+          let obj = {
+            // mark: item.mark,
+            strokes_content_list: arr,
+            miao_arr: miao_arr,
+          };
+          // if (!this.isJudgingRightWrong) {
+          //   this.userAnswer.push(obj);
+          // }
+          answer_list.push(obj);
+        }
+      });
+      this.userAnswer = answer_list;
+    },
+    changePraShow() {
+      this.if_free_show = false;
+    },
+    closeIfFreeShow(data, rowIndex, colIndex, mark) {
+      // this.option_list[rowIndex].imgArr[colIndex] = JSON.stringify(data);
+      this.if_free_show = false;
+      this.freeWrite(data, rowIndex, colIndex);
+      this.$forceUpdate();
+    },
+    freeWrite(imgUrl, index, indexs) {
+      this.if_free_show = true;
+      this.active_index = index;
+      this.active_col_index = indexs;
+
+      this.current_hz = this.data.option_list[index].content_list[indexs].con;
+      this.current_hz_data = imgUrl;
+    },
+    // 删除记录
+    deleteWriteRecord(rowIndex, colIndex) {
+      this.$set(this.option_list[rowIndex].imgArr, colIndex, JSON.stringify({}));
+      this.changeCurQue(null, rowIndex, colIndex);
+      this.current_hz_data = null;
+      this.active_mark = '';
+      this.$forceUpdate();
+    },
+    changeCurQue(answer, rowIndex, colIndex) {
+      if (answer) {
+        if (this.data.property.model === 'write') {
+          this.userAnswer[rowIndex][colIndex].imgArr = answer;
+        } else {
+          this.userAnswer[rowIndex].strokes_content_list[colIndex].imgArr = answer;
+        }
+
+        this.$forceUpdate();
+      }
+    },
+    // 点击描红模块
+    miaoStorkes(index, indexs, mark, content, storkes) {
+      this.if_miao_show = true;
+      this.active_index = index;
+      this.active_col_index = indexs;
+      this.current_hz = content;
+      this.current_hz_data = storkes;
+    },
+    // 保存描红
+    saveComplete(flag) {
+      this.answer.answer_list[this.active_index].strokes_content_list[this.active_col_index] = flag;
+    },
+    resetHanzi() {
+      this.$refs.strockRed.resetHanzi();
+    },
+    updateColor(color) {
+      this.writer.updateColor('strokeColor', color);
+      this.writer.updateColor('drawingColor', color);
+    },
+  },
 };
 </script>
 
@@ -116,9 +500,10 @@ export default {
     position: relative;
     box-sizing: border-box;
     flex-shrink: 0;
-    width: 64px;
-    height: 64px;
-    border: 1px solid #e81b1b;
+    width: 80px;
+    height: 80px;
+    border: 2px solid #346cda;
+    border-radius: 8px;
 
     .character-target-bg,
     .hanzi-writer-img {
@@ -138,5 +523,215 @@ export default {
   .border-right-none {
     border-right: none;
   }
+
+  .item-box {
+    display: flex;
+    gap: 5px;
+  }
+
+  .number-box {
+    display: flex;
+    align-items: center;
+    height: 80px;
+    margin-right: 16px;
+
+    .number {
+      display: block;
+      width: 24px;
+      height: 24px;
+      font-family: 'arial';
+      font-size: 14px;
+      font-weight: bold;
+      line-height: 24px;
+      color: #fff;
+      text-align: center;
+      background: #346cda;
+      border-radius: 100%;
+    }
+  }
+
+  .items {
+    font-size: 0;
+  }
+
+  .pinyin {
+    height: 30px;
+    min-height: 16px;
+    font-family: 'League';
+    font-size: 20px;
+    font-weight: 400;
+    color: rgba(0, 0, 0, 50%);
+    text-align: center;
+  }
+
+  .items-image,
+  .hanzi-storck {
+    width: 80px;
+    height: 80px;
+    overflow: hidden;
+    border: 2px solid #346cda;
+    border-radius: 8px;
+  }
+
+  .items-lian {
+    display: block;
+    height: 80px;
+    padding: 0 10px;
+    font-size: 34px;
+    line-height: 80px;
+    color: #346cda;
+  }
+
+  .items-flex {
+    display: flex;
+    gap: 5px;
+  }
+
+  .item-box-write {
+    .items-flex {
+      gap: 0;
+    }
+
+    .NoborderRadius {
+      border-radius: 0;
+    }
+
+    .rightBorderRadius {
+      border-radius: 0;
+      border-top-right-radius: 8px;
+      border-bottom-right-radius: 8px;
+    }
+
+    .leftBorderRadius {
+      border-radius: 0;
+      border-top-left-radius: 8px;
+      border-bottom-left-radius: 8px;
+    }
+
+    .NoborderRight {
+      border-right: none;
+    }
+  }
+
+  .pinyin-common {
+    margin-bottom: 5px;
+
+    :deep .edit-div {
+      font-family: 'League';
+    }
+  }
+
+  .en-common {
+    margin-top: 8px;
+
+    // text-align: center;
+    word-break: break-word;
+  }
+
+  .practiceBox {
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 101;
+    box-sizing: border-box;
+    width: 100%;
+    height: 100vh;
+    overflow: hidden;
+    overflow-y: auto;
+    background: rgba(0, 0, 0, 19%);
+
+    &.practice-box-strock {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      padding-top: 0;
+    }
+
+    .miao-box {
+      position: relative;
+      width: 332px;
+      height: 360px;
+      padding: 30px 16px;
+      margin: 0 auto;
+      background: #f3f3f3;
+      border-radius: 8px;
+      box-shadow: 0 4px 16px rgba(0, 0, 0, 15%);
+
+      .close-icon {
+        position: absolute;
+        top: 0;
+        right: 8px;
+        z-index: 2;
+        width: 32px;
+        height: 32px;
+        padding: 8px;
+        cursor: pointer;
+      }
+
+      .strockredBox,
+      .strockplay-redInner {
+        width: 300px;
+        height: 300px;
+        margin: 0 auto;
+      }
+
+      .strock-red {
+        position: absolute;
+        top: 30px;
+        left: 16px;
+
+        &-zindex {
+          z-index: -1;
+        }
+      }
+
+      .reset-box {
+        position: absolute;
+        right: 22px;
+        bottom: 22px;
+        z-index: 100;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 11px;
+        height: 11px;
+        color: $text-color;
+        cursor: pointer;
+
+        &:hover {
+          color: #000;
+        }
+      }
+    }
+  }
+
+  .card-box {
+    display: flex;
+    flex-flow: wrap;
+    gap: 5px;
+  }
+
+  .words-left {
+    display: flex;
+    gap: 10px;
+    align-items: center;
+    margin-bottom: 5px;
+  }
+
+  :deep .strockplay-redInner {
+    width: 80px;
+    height: 80px;
+    border: 2px solid #346cda !important;
+    border-radius: 8px;
+  }
+
+  .words-item {
+    margin-bottom: 10px;
+  }
+
+  .words-bottom {
+    margin-top: 3px;
+    word-break: break-word;
+  }
 }
 </style>

+ 1 - 1
src/views/book/courseware/preview/components/character_base/components/FreewriteLettle.vue

@@ -200,7 +200,7 @@ export default {
           strokes_content: JSON.stringify(this.$refs.esign.history),
           strokes_image: write_img,
         };
-        this.$emit('changeCurQue', answer, this.colIndex);
+        this.$emit('changeCurQue', answer, this.rowIndex, this.colIndex);
         let obj = {
           hz: this.currentHz,
           strokes_content: JSON.stringify(this.$refs.esign.history),

+ 78 - 70
src/views/book/courseware/preview/components/rich_text/RichTextPreview.vue

@@ -12,18 +12,20 @@
         />
         <span v-else class="rich-text" @click="handleRichFillClick" v-html="sanitizeHTML(data.content)"></span>
       </div>
-      <div v-if="data.note_list.length > 0" ref="rightDiv" class="note-list" :style="{ height: divHeight + 'px' }">
-        <span>注释</span>
-        <ul>
-          <li v-for="note in data.note_list" :key="note.id" :ref="note.id">
-            <p :style="{ 'background-color': selectedNoteId == note.id ? '#FFF2C9' : '#CCC' }">
-              {{ note.selectText }}
-            </p>
-            <span v-html="sanitizeHTML(note.note)"></span>
-          </li>
-        </ul>
-      </div>
     </div>
+
+    <el-dialog
+      title=""
+      :visible.sync="noteDialogVisible"
+      width="680px"
+      ref="optimizedDialog"
+      :style="dialogStyle"
+      :close-on-click-modal="false"
+      destroy-on-close
+      @close="noteDialogVisible = false"
+    >
+      <span v-html="selectedNote"></span>
+    </el-dialog>
   </div>
 </template>
 
@@ -41,10 +43,17 @@ export default {
     return {
       isEnable,
       data: getRichTextData(),
-      selectedNoteId: '',
       isPreview: true,
       divHeight: 'auto',
       observer: null,
+      noteDialogVisible: false,
+      selectedNote: '',
+      dialogStyle: {
+        position: 'fixed',
+        top: '0',
+        left: '0',
+        margin: '0',
+      },
     };
   },
   mounted() {
@@ -62,24 +71,61 @@ export default {
       const richFillElement = event.target.closest('.rich-fill');
       if (richFillElement) {
         // 处理点击事件
-        this.selectedNoteId = richFillElement.dataset.annotationId;
-
-        const sectionRef = this.$refs[`${this.selectedNoteId}`][0];
-        const rightDiv = this.$refs.rightDiv;
-
-        // 计算滚动位置
-        const rightDivTop = rightDiv.getBoundingClientRect().top;
-        const sectionTop = sectionRef.getBoundingClientRect().top;
-        const scrollPosition = sectionTop - rightDivTop + rightDiv.scrollTop;
-
-        // 平滑滚动
-        rightDiv.scrollTo({
-          top: scrollPosition,
-          behavior: 'smooth',
-        });
+        let selectedNoteId = richFillElement.dataset.annotationId;
+        if (this.data.note_list.some((p) => p.id === selectedNoteId)) {
+          this.noteDialogVisible = true;
+          this.selectedNote = this.data.note_list.find((p) => p.id === selectedNoteId).note;
+        }
       } else {
-        this.selectedNoteId = '';
+        this.selectedNote = '';
+        this.noteDialogVisible = false;
       }
+      this.$nextTick(() => {
+        const dialogElement = this.$refs.optimizedDialog;
+        // 确保对话框DOM已渲染
+        if (!dialogElement) {
+          return;
+        }
+        // 获取对话框内容区域的DOM元素
+        const dialogContent = dialogElement.$el.querySelector('.el-dialog');
+        if (!dialogContent) {
+          return;
+        }
+        const dialogRect = dialogContent.getBoundingClientRect();
+        const dialogWidth = dialogRect.width;
+        const dialogHeight = dialogRect.height;
+        const padding = 10; // 安全边距
+
+        const clickX = event.clientX;
+        const clickY = event.clientY;
+
+        const windowWidth = window.innerWidth;
+        const windowHeight = window.innerHeight;
+
+        // 水平定位 - 中心对齐
+        let left = clickX - dialogWidth / 2;
+        // 边界检查
+        left = Math.max(padding, Math.min(left, windowWidth - dialogWidth - padding));
+
+        // 垂直定位 - 点击位置作为下边界中心
+        let top = clickY - dialogHeight;
+        // 上方空间不足时,改为向下展开
+        if (top < padding) {
+          top = clickY + padding;
+          // 如果向下展开会超出屏幕,则贴底部显示
+          if (top + dialogHeight > windowHeight - padding) {
+            top = windowHeight - dialogHeight - padding;
+          }
+        }
+
+        this.dialogStyle = {
+          position: 'fixed',
+          top: `${top - 20}px`,
+          left: `${left}px`,
+          margin: '0',
+          transform: 'none',
+        };
+      });
     },
     updateHeight() {
       this.$nextTick(() => {
@@ -101,48 +147,10 @@ export default {
 .describe-preview {
   @include preview-base;
 
-  .main {
-    display: flex;
-    column-gap: 10px;
-    justify-content: space-between;
-
-    .note-list {
-      display: flex;
-      flex-direction: column;
-      width: 20%;
-      min-height: 150px;
-      overflow: auto;
-
-      > span {
-        font-weight: bold;
-      }
-
-      li {
-        margin-top: 6px;
-        border-radius: 8px;
-        box-shadow: 1px 3px 2px rgba(0, 0, 0, 10%);
-
-        p {
-          padding: 4px;
-          margin: 0;
-          background-color: #ccc;
-          border-radius: 8px 8px 0 0;
-        }
-
-        span {
-          display: block;
-          padding: 8px;
-
-          :deep p {
-            margin: 0 !important;
-          }
-        }
-      }
-
-      p {
-        text-align: center;
-      }
-    }
+  :deep .el-dialog {
+    position: fixed;
+    margin: 0 !important;
+    transition: all 0.2s; /* 添加平滑过渡效果 */
   }
 }
 </style>