dsy hai 3 semanas
pai
achega
e14753ba83
Modificáronse 19 ficheiros con 1268 adicións e 727 borrados
  1. 184 578
      package-lock.json
  2. 17 19
      package.json
  3. 4 0
      src/views/book/courseware/preview/CoursewarePreview.vue
  4. 260 0
      src/views/book/courseware/preview/components/article/NormalModelChs.vue
  5. 174 1
      src/views/book/courseware/preview/components/article/PhraseModelChs.vue
  6. 179 0
      src/views/book/courseware/preview/components/article/Practicechs.vue
  7. 128 2
      src/views/book/courseware/preview/components/article/Voicefullscreen.vue
  8. 110 0
      src/views/book/courseware/preview/components/article/WordModelChs.vue
  9. 5 1
      src/views/book/courseware/preview/components/article/index.vue
  10. 1 1
      src/views/book/courseware/preview/components/character/CharacterPreview.vue
  11. 1 0
      src/views/book/courseware/preview/components/common/PreviewMixin.js
  12. 1 1
      src/views/book/courseware/preview/components/dialogue_article/PhraseModelChs.vue
  13. 6 5
      src/views/book/courseware/preview/components/dialogue_article/Practicechs.vue
  14. 5 1
      src/views/book/courseware/preview/components/dialogue_article/index.vue
  15. 9 18
      src/views/book/courseware/preview/components/drawing/DrawingPreview.vue
  16. 179 96
      src/views/book/courseware/preview/components/fill/FillPreview.vue
  17. 2 2
      src/views/book/courseware/preview/components/table/TablePreview.vue
  18. 2 1
      src/views/book/courseware/preview/components/voice_matrix/VoiceMatrixPreview.vue
  19. 1 1
      src/views/book/courseware/preview/components/write_base/WriteBasePreview.vue

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 184 - 578
package-lock.json


+ 17 - 19
package.json

@@ -22,14 +22,12 @@
   "dependencies": {
     "@tinymce/tinymce-vue": "^3.2.8",
     "7zip-bin": "^5.2.0",
-    "adm-zip": "^0.5.16",
     "axios": "^1.7.2",
     "cnchar": "^3.2.6",
     "core-js": "^3.47.0",
     "dompurify": "^3.1.5",
-    "electron-store": "^11.0.2",
     "element-ui": "^2.15.14",
-    "hanzi-writer": "^3.7.0",
+    "hanzi-writer": "^3.1.0",
     "js-audio-recorder": "^1.0.7",
     "mathjax": "^3.2.2",
     "md5": "^2.3.0",
@@ -52,27 +50,27 @@
     "web-highlighter": "^0.7.4"
   },
   "devDependencies": {
-    "@babel/core": "^7.24.7",
-    "@babel/eslint-parser": "^7.24.7",
+    "@babel/core": "^7.28.5",
+    "@babel/eslint-parser": "^7.28.5",
     "@electron/fuses": "^2.1.3",
-    "@rushstack/eslint-patch": "^1.10.3",
-    "@types/md5": "^2.3.5",
-    "@vue/cli-plugin-babel": "~5.0.8",
-    "@vue/cli-plugin-eslint": "~5.0.8",
-    "@vue/cli-service": "~5.0.8",
+    "@rushstack/eslint-patch": "^1.15.0",
+    "@types/md5": "^2.3.6",
+    "@vue/cli-plugin-babel": "~5.0.9",
+    "@vue/cli-plugin-eslint": "~5.0.9",
+    "@vue/cli-service": "~5.0.9",
     "@vue/eslint-config-prettier": "^9.0.0",
     "@vue/preload-webpack-plugin": "^2.0.0",
     "compression-webpack-plugin": "^6.1.2",
     "electron": "43.0.0",
-    "electron-builder": "^26.15.3",
-    "eslint": "^8.57.0",
-    "eslint-plugin-prettier": "^5.1.3",
-    "eslint-plugin-vue": "^9.26.0",
-    "nodemon": "^3.1.3",
-    "patch-package": "^8.0.0",
-    "postcss-html": "^1.7.0",
-    "prettier": "^3.3.2",
-    "sass": "^1.77.6",
+    "electron-builder": "^24.13.3",
+    "eslint": "^8.57.1",
+    "eslint-plugin-prettier": "^5.5.4",
+    "eslint-plugin-vue": "^9.33.0",
+    "nodemon": "^3.1.11",
+    "patch-package": "^8.0.1",
+    "postcss-html": "^1.8.0",
+    "prettier": "^3.7.3",
+    "sass": "^1.94.2",
     "sass-loader": "^14.2.1",
     "stylelint": "^15.11.0",
     "stylelint-config-recess-order": "^4.6.0",

+ 4 - 0
src/views/book/courseware/preview/CoursewarePreview.vue

@@ -1187,6 +1187,10 @@ export default {
       display: grid;
       gap: $component-spacing;
 
+      // 与 PreviewEdit 保持一致:列被行高拉伸时,auto 行按内容紧凑排列,
+      // 避免富文本等自动高度组件被 align-content 默认的 stretch 撑高
+      align-content: start;
+
       .active {
         box-shadow: 0 0 6px 1px $main-hover-color;
       }

+ 260 - 0
src/views/book/courseware/preview/components/article/NormalModelChs.vue

@@ -1353,6 +1353,266 @@
                               }}</span
                             >
                           </span>
+                          <span
+                            v-if="
+                              item.wordsList[pIndex + 2] &&
+                              item.wordsList[pIndex + 2].chs &&
+                              (chsFhList.indexOf(item.wordsList[pIndex + 2].chs) > -1 ||
+                                NumberList.indexOf(item.wordsList[pIndex + 2].chs) > -1) &&
+                              item.wordsList[pIndex + 3] &&
+                              item.wordsList[pIndex + 3].chs &&
+                              (chsFhList.indexOf(item.wordsList[pIndex + 3].chs) > -1 ||
+                                NumberList.indexOf(item.wordsList[pIndex + 3].chs) > -1)
+                            "
+                            class="NNPE-words-box"
+                          >
+                            <span
+                              v-if="curQue.property.pinyin_position == 'top' && config.isShowPY && item.dhaspinyin"
+                              :class="[
+                                'NNPE-pinyin',
+                                noFont.indexOf(item.wordsList[pIndex + 3].pinyin) > -1 ? 'noFont' : '',
+                              ]"
+                              style="text-align: left"
+                              :style="{
+                                fontSize: attrib && attrib.pinyin_size ? attrib.pinyin_size : '14px',
+                                height:
+                                  attrib && attrib.pinyin_size
+                                    ? attrib.pinyin_size.replace('pt', '') * 1.5 + 'pt'
+                                    : '22px',
+                              }"
+                              @click.stop="
+                                viewNotes(
+                                  $event,
+                                  item.wordsList[pIndex + 3].pinyin,
+                                  item.timeList && item.timeList[pItem.sentIndex] && item.timeList[pItem.sentIndex].bg,
+                                  item.timeList && item.timeList[pItem.sentIndex] && item.timeList[pItem.sentIndex].ed,
+                                  pItem.articleSentIndex,
+                                  pItem,
+                                )
+                              "
+                              >{{
+                                NumberList.indexOf(item.wordsList[pIndex + 3].pinyin) == -1
+                                  ? item.wordsList[pIndex + 3].pinyin
+                                  : ''
+                              }}</span
+                            >
+                            <el-tooltip
+                              v-if="item.wordsList[pIndex + 3].matchNotesObj.con"
+                              popper-class="article-bubble"
+                              effect="light"
+                              placement="bottom-start"
+                            >
+                              <div
+                                slot="content"
+                                :style="{
+                                  color: attrib && attrib.text_color ? attrib.text_color : '#de4444',
+                                  fontSize: attrib && attrib.font_size ? attrib.font_size : '',
+                                }"
+                                v-html="item.wordsList[pIndex + 3].matchNotesObj.con"
+                              ></div>
+                              <span
+                                class="NNPE-chs"
+                                style="text-align: left"
+                                :class="[
+                                  item.timeList &&
+                                  item.timeList[pItem.sentIndex] &&
+                                  curTime >= item.timeList[pItem.sentIndex].bg &&
+                                  curTime <= item.timeList[pItem.sentIndex].ed &&
+                                  curTime
+                                    ? 'active'
+                                    : '',
+                                  pItem.paraIndex == paraIndex && pItem.sentIndex == sentIndex ? 'overActive' : '',
+                                  pItem.chstimeList &&
+                                  pItem.chstimeList[pItem.leg - 1] &&
+                                  curTime >= pItem.chstimeList[pItem.leg - 1].wordBg &&
+                                  curQue.wordTime &&
+                                  curTime <= item.timeList[pItem.sentIndex].ed
+                                    ? 'wordActive'
+                                    : '',
+                                ]"
+                                :style="{
+                                  fontFamily: item.wordsList[pIndex + 3].config.fontFamily,
+                                  textDecoration: item.wordsList[pIndex + 3].config.textDecoration,
+                                  borderBottom:
+                                    item.wordsList[pIndex + 3].config.border === 'dotted' ? '1px dotted' : '',
+                                  fontWeight: item.wordsList[pIndex + 3].config.fontWeight,
+                                  height:
+                                    attrib && attrib.font_size
+                                      ? attrib.font_size.replace('pt', '') * 1.4 + 'pt'
+                                      : '28px',
+                                  fontSize: attrib && attrib.font_size ? attrib.font_size : '20px',
+                                  lineHeight:
+                                    attrib && attrib.font_size
+                                      ? attrib.font_size.replace('pt', '') * 1.4 + 'pt'
+                                      : '28px',
+                                  width: item.wordsList[pIndex + 3].chs.trim() === '' ? '6px' : '',
+                                  backgroundColor:
+                                    item.timeList &&
+                                    item.timeList[pItem.sentIndex] &&
+                                    curTime >= item.timeList[pItem.sentIndex].bg &&
+                                    curTime <= item.timeList[pItem.sentIndex].ed &&
+                                    curTime &&
+                                    attrib
+                                      ? attrib.assist_color
+                                      : '',
+                                  color:
+                                    pItem.chstimeList &&
+                                    pItem.chstimeList[pItem.leg - 1] &&
+                                    curTime >= pItem.chstimeList[pItem.leg - 1].wordBg &&
+                                    curQue.wordTime &&
+                                    curTime <= item.timeList[pItem.sentIndex].ed &&
+                                    attrib
+                                      ? attrib.topic_color
+                                      : item.wordsList[pIndex + 3].matchNotesObj.con &&
+                                          item.wordsList[pIndex + 3].matchNotesObj.notesColor
+                                        ? item.wordsList[pIndex + 3].matchNotesObj.notesColor
+                                        : item.wordsList[pIndex + 3].config.color,
+                                }"
+                                @click.stop="
+                                  viewNotes(
+                                    $event,
+                                    item.wordsList[pIndex + 3].chs,
+                                    item.timeList &&
+                                      item.timeList[pItem.sentIndex] &&
+                                      item.timeList[pItem.sentIndex].bg,
+                                    item.timeList &&
+                                      item.timeList[pItem.sentIndex] &&
+                                      item.timeList[pItem.sentIndex].ed,
+                                    pItem.articleSentIndex,
+                                    pItem,
+                                  )
+                                "
+                                ><span>{{ convertText(item.wordsList[pIndex + 3].chs) }}</span
+                                ><img
+                                  v-if="
+                                    item.wordsList[pIndex + 3].img &&
+                                    item.wordsList[pIndex + 3].img.length > 0 &&
+                                    item.wordsList[pIndex + 3].imgPosition === 'after'
+                                  "
+                                  :src="item.wordsList[pIndex + 3].img[0].file_url"
+                                  :style="{
+                                    height: attrib && attrib.font_size ? attrib.font_size : '20px',
+                                  }"
+                              /></span>
+                            </el-tooltip>
+                            <template v-else>
+                              <span
+                                class="NNPE-chs"
+                                style="text-align: left"
+                                :class="[
+                                  item.timeList &&
+                                  item.timeList[pItem.sentIndex] &&
+                                  curTime >= item.timeList[pItem.sentIndex].bg &&
+                                  curTime <= item.timeList[pItem.sentIndex].ed &&
+                                  curTime
+                                    ? 'active'
+                                    : '',
+                                  pItem.paraIndex == paraIndex && pItem.sentIndex == sentIndex ? 'overActive' : '',
+                                  pItem.chstimeList &&
+                                  pItem.chstimeList[pItem.leg - 1] &&
+                                  curTime >= pItem.chstimeList[pItem.leg - 1].wordBg &&
+                                  curQue.wordTime &&
+                                  curTime <= item.timeList[pItem.sentIndex].ed
+                                    ? 'wordActive'
+                                    : '',
+                                ]"
+                                :style="{
+                                  fontFamily: item.wordsList[pIndex + 3].config.fontFamily,
+                                  textDecoration: item.wordsList[pIndex + 3].config.textDecoration,
+                                  borderBottom:
+                                    item.wordsList[pIndex + 3].config.border === 'dotted' ? '1px dotted' : '',
+                                  fontWeight: item.wordsList[pIndex + 3].config.fontWeight,
+                                  height:
+                                    attrib && attrib.font_size
+                                      ? attrib.font_size.replace('pt', '') * 1.4 + 'pt'
+                                      : '28px',
+                                  fontSize: attrib && attrib.font_size ? attrib.font_size : '20px',
+                                  lineHeight:
+                                    attrib && attrib.font_size
+                                      ? attrib.font_size.replace('pt', '') * 1.4 + 'pt'
+                                      : '28px',
+                                  width: item.wordsList[pIndex + 3].chs.trim() === '' ? '6px' : '',
+                                  backgroundColor:
+                                    item.timeList &&
+                                    item.timeList[pItem.sentIndex] &&
+                                    curTime >= item.timeList[pItem.sentIndex].bg &&
+                                    curTime <= item.timeList[pItem.sentIndex].ed &&
+                                    curTime &&
+                                    attrib
+                                      ? attrib.assist_color
+                                      : '',
+                                  color:
+                                    pItem.chstimeList &&
+                                    pItem.chstimeList[pItem.leg - 1] &&
+                                    curTime >= pItem.chstimeList[pItem.leg - 1].wordBg &&
+                                    curQue.wordTime &&
+                                    curTime <= item.timeList[pItem.sentIndex].ed &&
+                                    attrib
+                                      ? attrib.topic_color
+                                      : item.wordsList[pIndex + 3].matchNotesObj.con &&
+                                          item.wordsList[pIndex + 3].matchNotesObj.notesColor
+                                        ? item.wordsList[pIndex + 3].matchNotesObj.notesColor
+                                        : item.wordsList[pIndex + 3].config.color,
+                                }"
+                                @click.stop="
+                                  viewNotes(
+                                    $event,
+                                    item.wordsList[pIndex + 3].chs,
+                                    item.timeList &&
+                                      item.timeList[pItem.sentIndex] &&
+                                      item.timeList[pItem.sentIndex].bg,
+                                    item.timeList &&
+                                      item.timeList[pItem.sentIndex] &&
+                                      item.timeList[pItem.sentIndex].ed,
+                                    pItem.articleSentIndex,
+                                    pItem,
+                                  )
+                                "
+                                ><span>{{ convertText(item.wordsList[pIndex + 3].chs) }}</span
+                                ><img
+                                  v-if="
+                                    item.wordsList[pIndex + 3].img &&
+                                    item.wordsList[pIndex + 3].img.length > 0 &&
+                                    item.wordsList[pIndex + 3].imgPosition === 'after'
+                                  "
+                                  :src="item.wordsList[pIndex + 3].img[0].file_url"
+                                  :style="{
+                                    height: attrib && attrib.font_size ? attrib.font_size : '20px',
+                                  }"
+                              /></span>
+                            </template>
+
+                            <span
+                              v-if="curQue.property.pinyin_position == 'bottom' && config.isShowPY && item.dhaspinyin"
+                              :class="[
+                                'NNPE-pinyin',
+                                noFont.indexOf(item.wordsList[pIndex + 3].pinyin) > -1 ? 'noFont' : '',
+                              ]"
+                              style="text-align: left"
+                              :style="{
+                                fontSize: attrib && attrib.pinyin_size ? attrib.pinyin_size : '14px',
+                                height:
+                                  attrib && attrib.pinyin_size
+                                    ? attrib.pinyin_size.replace('pt', '') * 1.5 + 'pt'
+                                    : '22px',
+                              }"
+                              @click.stop="
+                                viewNotes(
+                                  $event,
+                                  item.wordsList[pIndex + 3].pinyin,
+                                  item.timeList && item.timeList[pItem.sentIndex] && item.timeList[pItem.sentIndex].bg,
+                                  item.timeList && item.timeList[pItem.sentIndex] && item.timeList[pItem.sentIndex].ed,
+                                  pItem.articleSentIndex,
+                                  pItem,
+                                )
+                              "
+                              >{{
+                                NumberList.indexOf(item.wordsList[pIndex + 3].pinyin) == -1
+                                  ? item.wordsList[pIndex + 3].pinyin
+                                  : ''
+                              }}</span
+                            >
+                          </span>
                         </template>
                         <template v-else>
                           <span class="NNPE-words-box">

+ 174 - 1
src/views/book/courseware/preview/components/article/PhraseModelChs.vue

@@ -943,6 +943,179 @@
                               }}</span
                             >
                           </span>
+                          <span
+                            v-if="
+                              item.wordsList[pIndex + 2] &&
+                              item.wordsList[pIndex + 2].chs &&
+                              (chsFhList.indexOf(item.wordsList[pIndex + 2].chs) > -1 ||
+                                NumberList.indexOf(item.wordsList[pIndex + 2].chs) > -1) &&
+                              item.wordsList[pIndex + 3] &&
+                              item.wordsList[pIndex + 3].chs &&
+                              (chsFhList.indexOf(item.wordsList[pIndex + 3].chs) > -1 ||
+                                NumberList.indexOf(item.wordsList[pIndex + 3].chs) > -1)
+                            "
+                            class="NNPE-words-box"
+                          >
+                            <span
+                              v-if="curQue.property.pinyin_position == 'top' && config.isShowPY && item.dhaspinyin"
+                              :class="[
+                                'NNPE-pinyin',
+                                noFont.indexOf(item.wordsList[pIndex + 1].pinyin) > -1 ? 'noFont' : '',
+                              ]"
+                              style="text-align: left"
+                              :style="{
+                                fontSize: attrib && attrib.pinyin_size ? attrib.pinyin_size : '14px',
+                                height:
+                                  attrib && attrib.pinyin_size
+                                    ? attrib.pinyin_size.replace('pt', '') * 1.5 + 'pt'
+                                    : '22px',
+                              }"
+                              @click.stop="
+                                viewNotes($event, item.wordsList[pIndex + 3].pinyin, '', item.wordsList[pIndex + 3])
+                              "
+                              >{{
+                                NumberList.indexOf(item.wordsList[pIndex + 3].pinyin) == -1
+                                  ? item.wordsList[pIndex + 3].pinyin
+                                  : ''
+                              }}</span
+                            >
+                            <el-tooltip
+                              v-if="item.wordsList[pIndex + 3].matchNotesObj.con"
+                              popper-class="article-bubble"
+                              effect="light"
+                              placement="bottom-start"
+                            >
+                              <div
+                                slot="content"
+                                :style="{
+                                  color: attrib && attrib.text_color ? attrib.text_color : '#de4444',
+                                  fontSize: attrib && attrib.font_size ? attrib.font_size : '',
+                                }"
+                                v-html="item.wordsList[pIndex + 3].matchNotesObj.con"
+                              ></div>
+                              <span
+                                class="NNPE-chs"
+                                style="text-align: left"
+                                :style="{
+                                  fontFamily: item.wordsList[pIndex + 3].config.fontFamily,
+                                  textDecoration: item.wordsList[pIndex + 3].config.textDecoration,
+                                  borderBottom:
+                                    item.wordsList[pIndex + 3].config.border === 'dotted' ? '1px dotted' : '',
+                                  fontWeight: item.wordsList[pIndex + 3].config.fontWeight,
+                                  color:
+                                    item.wordsList[pIndex + 3].matchNotesObj.con &&
+                                    item.wordsList[pIndex + 3].matchNotesObj.notesColor
+                                      ? item.wordsList[pIndex + 3].matchNotesObj.notesColor
+                                      : item.wordsList[pIndex + 3].config.color,
+                                  height:
+                                    attrib && attrib.font_size
+                                      ? attrib.font_size.replace('pt', '') * 1.4 + 'pt'
+                                      : '28px',
+                                  fontSize: attrib && attrib.font_size ? attrib.font_size : '20px',
+                                  lineHeight:
+                                    attrib && attrib.font_size
+                                      ? attrib.font_size.replace('pt', '') * 1.4 + 'pt'
+                                      : '28px',
+                                  width: item.wordsList[pIndex + 3].chs.trim() === '' ? '6px' : '',
+                                }"
+                                @click.stop="
+                                  viewNotes(
+                                    $event,
+                                    item.wordsList[pIndex + 3].words
+                                      ? item.wordsList[pIndex + 3].words
+                                      : item.wordsList[pIndex + 3].chs,
+                                    '',
+                                    item.wordsList[pIndex + 3],
+                                  )
+                                "
+                                >{{ convertText(item.wordsList[pIndex + 3].chs)
+                                }}<img
+                                  v-if="
+                                    item.wordsList[pIndex + 3].img &&
+                                    item.wordsList[pIndex + 3].img.length > 0 &&
+                                    item.wordsList[pIndex + 3].imgPosition === 'after'
+                                  "
+                                  :src="item.wordsList[pIndex + 3].img[0].file_url"
+                                  :style="{
+                                    height: attrib && attrib.font_size ? attrib.font_size : '20px',
+                                  }"
+                              /></span>
+                            </el-tooltip>
+                            <template v-else>
+                              <span
+                                class="NNPE-chs"
+                                style="text-align: left"
+                                :style="{
+                                  fontFamily: item.wordsList[pIndex + 3].config.fontFamily,
+                                  textDecoration: item.wordsList[pIndex + 3].config.textDecoration,
+                                  borderBottom:
+                                    item.wordsList[pIndex + 3].config.border === 'dotted' ? '1px dotted' : '',
+                                  fontWeight: item.wordsList[pIndex + 3].config.fontWeight,
+                                  color:
+                                    item.wordsList[pIndex + 3].matchNotesObj.con &&
+                                    item.wordsList[pIndex + 3].matchNotesObj.notesColor
+                                      ? item.wordsList[pIndex + 3].matchNotesObj.notesColor
+                                      : item.wordsList[pIndex + 3].config.color,
+                                  height:
+                                    attrib && attrib.font_size
+                                      ? attrib.font_size.replace('pt', '') * 1.4 + 'pt'
+                                      : '28px',
+                                  fontSize: attrib && attrib.font_size ? attrib.font_size : '20px',
+                                  lineHeight:
+                                    attrib && attrib.font_size
+                                      ? attrib.font_size.replace('pt', '') * 1.4 + 'pt'
+                                      : '28px',
+
+                                  width: item.wordsList[pIndex + 3].chs.trim() === '' ? '6px' : '',
+                                }"
+                                @click.stop="
+                                  viewNotes(
+                                    $event,
+                                    item.wordsList[pIndex + 3].words
+                                      ? item.wordsList[pIndex + 3].words
+                                      : item.wordsList[pIndex + 3].chs,
+                                    '',
+                                    item.wordsList[pIndex + 3],
+                                  )
+                                "
+                                >{{ convertText(item.wordsList[pIndex + 3].chs)
+                                }}<img
+                                  v-if="
+                                    item.wordsList[pIndex + 3].img &&
+                                    item.wordsList[pIndex + 3].img.length > 0 &&
+                                    item.wordsList[pIndex + 3].imgPosition === 'after'
+                                  "
+                                  :src="item.wordsList[pIndex + 3].img[0].file_url"
+                                  :style="{
+                                    height: attrib && attrib.font_size ? attrib.font_size : '20px',
+                                  }"
+                              /></span>
+                            </template>
+
+                            <span
+                              v-if="curQue.property.pinyin_position == 'bottom' && config.isShowPY && item.dhaspinyin"
+                              :class="[
+                                'NNPE-pinyin',
+                                noFont.indexOf(item.wordsList[pIndex + 3].pinyin) > -1 ? 'noFont' : '',
+                              ]"
+                              style="text-align: left"
+                              :style="{
+                                fontSize: attrib && attrib.pinyin_size ? attrib.pinyin_size : '14px',
+                                height:
+                                  attrib && attrib.pinyin_size
+                                    ? attrib.pinyin_size.replace('pt', '') * 1.5 + 'pt'
+                                    : '22px',
+                              }"
+                              @click.stop="
+                                viewNotes($event, item.wordsList[pIndex + 3].pinyin, '', item.wordsList[pIndex + 3])
+                              "
+                              >{{
+                                NumberList.indexOf(item.wordsList[pIndex + 3].pinyin) == -1
+                                  ? item.wordsList[pIndex + 3].pinyin
+                                  : ''
+                              }}</span
+                            >
+                          </span>
                         </template>
                         <template v-else>
                           <span class="NNPE-words-box">
@@ -2064,7 +2237,7 @@ export default {
             words = wItem.chs;
             endIndex = startIndex + 1;
           }
-        } else if (item[0] === wItem.chs && sentence.indexOf(item) > -1) {
+        } else if (item.substring(0, wItem.chs.length) === wItem.chs && sentence.indexOf(item) > -1) {
           let index = null;
           let chsStr = '';
           for (let i = startIndex; i < sItem.length + 1; i++) {

+ 179 - 0
src/views/book/courseware/preview/components/article/Practicechs.vue

@@ -624,6 +624,119 @@
                           >
                         </template>
                       </span>
+                      <span
+                        v-if="
+                          item.sentArr[pIndex + 2] &&
+                          item.sentArr[pIndex + 2].chs &&
+                          chsFhList.indexOf(item.sentArr[pIndex + 2].chs) > -1 &&
+                          item.sentArr[pIndex + 3] &&
+                          item.sentArr[pIndex + 3].chs &&
+                          chsFhList.indexOf(item.sentArr[pIndex + 3].chs) > -1
+                        "
+                        class="NNPE-words-box"
+                      >
+                        <template v-if="curQue.property.pinyin_position == 'top'">
+                          <span
+                            v-if="config.isShowPY"
+                            :class="[
+                              'NNPE-pinyin',
+                              sentIndex == index ? 'wordBlank' : '',
+                              noFont.indexOf(item.sentArr[pIndex + 3].pinyin) > -1 ? 'noFont' : '',
+                            ]"
+                            style="text-align: left"
+                            :style="{
+                              fontSize: attrib && attrib.pinyin_size ? attrib.pinyin_size : '14px',
+                              height:
+                                attrib && attrib.pinyin_size
+                                  ? attrib.pinyin_size.replace('pt', '') * 1.5 + 'pt'
+                                  : '22px',
+                            }"
+                            >{{
+                              NumberList.indexOf(item.sentArr[pIndex + 3].pinyin) == -1
+                                ? item.sentArr[pIndex + 3].pinyin
+                                : ''
+                            }}</span
+                          >
+                        </template>
+                        <span
+                          class="NNPE-chs"
+                          style="text-align: left"
+                          :class="[sentIndex == index ? 'wordBlank' : '']"
+                        >
+                          <span
+                            :class="[
+                              pItem.timeList[pItem.leg - 1] &&
+                              curQue.wordTime &&
+                              curTime >= pItem.timeList[pItem.leg - 1].wordBg &&
+                              curTime <= curQue.wordTime[index].ed
+                                ? 'active'
+                                : '',
+                              sentIndex == index ? 'wordBlank' : '',
+                            ]"
+                            :style="{
+                              fontFamily: item.sentArr[pIndex + 3].config.fontFamily,
+                              textDecoration: item.sentArr[pIndex + 3].config.textDecoration,
+                              borderBottom: item.sentArr[pIndex + 3].config.border === 'dotted' ? '1px dotted' : '',
+                              fontWeight: item.sentArr[pIndex + 3].config.fontWeight,
+                              height:
+                                attrib && attrib.font_size ? attrib.font_size.replace('pt', '') * 1.4 + 'pt' : '28px',
+                              fontSize: attrib && attrib.font_size ? attrib.font_size : '20px',
+                              lineHeight:
+                                attrib && attrib.font_size ? attrib.font_size.replace('pt', '') * 1.4 + 'pt' : '28px',
+                              display: 'inline-block',
+                              width: item.sentArr[pIndex + 3].chs.trim() === '' ? '6px' : '',
+                              color:
+                                pItem.timeList[pItem.leg - 1] &&
+                                curQue.wordTime &&
+                                curTime >= pItem.timeList[pItem.leg - 1].wordBg &&
+                                curTime <= curQue.wordTime[index].ed &&
+                                attrib
+                                  ? attrib.topic_color
+                                  : item.sentArr[pIndex + 3].config.color,
+                            }"
+                          >
+                            {{
+                              NumberList.indexOf(item.sentArr[pIndex + 3].pinyin) == -1
+                                ? convertText(item.sentArr[pIndex + 3].chs)
+                                : ''
+                            }}</span
+                          >
+                          <img
+                            v-if="
+                              item.sentArr[pIndex + 3].img &&
+                              item.sentArr[pIndex + 3].img.length > 0 &&
+                              item.sentArr[pIndex + 3].imgPosition === 'after'
+                            "
+                            :src="item.sentArr[pIndex + 3].img[0].file_url"
+                            :style="{
+                              height: attrib && attrib.font_size ? attrib.font_size : '20px',
+                            }"
+                          />
+                        </span>
+                        <template v-if="curQue.property.pinyin_position == 'bottom'">
+                          <span
+                            v-if="config.isShowPY"
+                            :class="[
+                              'NNPE-pinyin',
+                              sentIndex == index ? 'wordBlank' : '',
+                              noFont.indexOf(item.sentArr[pIndex + 3].pinyin) > -1 ? 'noFont' : '',
+                            ]"
+                            style="text-align: left"
+                            :style="{
+                              fontSize: attrib && attrib.pinyin_size ? attrib.pinyin_size : '14px',
+                              height:
+                                attrib && attrib.pinyin_size
+                                  ? attrib.pinyin_size.replace('pt', '') * 1.5 + 'pt'
+                                  : '22px',
+                            }"
+                            >{{
+                              NumberList.indexOf(item.sentArr[pIndex + 3].pinyin) == -1
+                                ? item.sentArr[pIndex + 3].pinyin
+                                : ''
+                            }}</span
+                          >
+                        </template>
+                      </span>
                     </template>
                     <template v-else>
                       <span class="NNPE-words-box">
@@ -976,6 +1089,8 @@ export default {
       mathNum: Math.random().toString(36).substr(2),
       ed: undefined,
       refresh: true,
+      highWords: null,
+      newWordList: [],
       multilingualTextList: {},
     };
   },
@@ -1149,6 +1264,7 @@ export default {
       let wordTimeList = curQue.wordTime;
       let dhaspinyin = false; // 每段是否有拼音
       let dhaspinyinArr = [];
+      this.handleNewword();
       curQue.detail.forEach((dItem, dIndex) => {
         dhaspinyin = false;
         dItem.wordsList.forEach((sItem, sIndex) => {
@@ -1158,7 +1274,21 @@ export default {
             let startIndex = wIndex === 0 ? 0 : sentArr[wIndex - 1].startIndex + sentArr[wIndex - 1].chs.length;
             let endIndex = wIndex === 0 ? wItem.chs.length : sentArr[wIndex - 1].endIndex + wItem.chs.length;
             // this.judgePad(sItem, wItem, wIndex);
+            let sentence = dItem.sentences[sIndex];
             this.mergeWordSymbol(wItem);
+            let words = '';
+            if (this.newWordList.length > 0) {
+              if (!this.highWords) {
+                this.findLightWord(wItem, wIndex, sentence, sItem);
+                words = this.highWords ? this.highWords.words : '';
+              } else if (wIndex > this.highWords.endIndex - 1) {
+                this.highWords = null;
+                this.findLightWord(wItem, wIndex, sentence, sItem);
+                words = this.highWords ? this.highWords.words : '';
+              } else {
+                words = this.highWords ? this.highWords.words : '';
+              }
+            }
             let obj = {
               paraIndex: dIndex, // 段落索引
               sentIndex: sIndex, // 在段落中句子索引
@@ -1176,6 +1306,7 @@ export default {
               startIndex,
               endIndex,
               leg: wItem.chs.length,
+              words,
               timeList: [],
               config: {
                 fontFamily: wItem.fontFamily,
@@ -1239,6 +1370,54 @@ export default {
         wItem.isShow = true;
       }
     },
+    findLightWord(wItem, startIndex, sentence, sItem) {
+      let endIndex = 0;
+      let words = '';
+      this.newWordList.forEach((item) => {
+        if (item.length === 1) {
+          if (item === wItem.chs && !wItem.banLight) {
+            words = wItem.chs;
+            endIndex = startIndex + 1;
+          }
+        } else if (item.substring(0, wItem.chs.length) === wItem.chs && sentence.indexOf(item) > -1) {
+          let index = null;
+          let chsStr = '';
+          for (let i = startIndex; i < sItem.length + 1; i++) {
+            index = i;
+            if (chsStr.length === item.length) {
+              break;
+            } else {
+              chsStr += sItem[i] ? sItem[i].chs : '';
+            }
+          }
+          if (chsStr === item && !wItem.banLight) {
+            words = item;
+            endIndex = index;
+          }
+        } else if (wItem.new_word && wItem.new_word === item && !wItem.banLight) {
+          words = item;
+          endIndex = startIndex + 1;
+        }
+      });
+      if (words) {
+        this.highWords = { words, endIndex };
+      } else {
+        this.highWords = null;
+      }
+    },
+    handleNewword() {
+      let NewWordList = [];
+      this.NNPENewWordList.forEach((wItem) => {
+        // item.forEach((wItem) => {
+        if (wItem.new_word) {
+          NewWordList.push(wItem.new_word);
+        } else if (wItem.detail && wItem.detail.sentence) {
+          NewWordList.push(wItem.detail.sentence);
+        }
+        // });
+      });
+      this.newWordList = JSON.parse(JSON.stringify(NewWordList));
+    },
 
     // 判断是否有padding
     judgePad(sItem, wItem, curIndex) {

+ 128 - 2
src/views/book/courseware/preview/components/article/Voicefullscreen.vue

@@ -109,7 +109,7 @@
                               : '',
                           textDecoration:
                             item[pIndex - 2].config && item[pIndex - 2].config.textDecoration
-                              ? item[pIndex + 2].config.textDecoration
+                              ? item[pIndex - 2].config.textDecoration
                               : '',
                           borderBottom:
                             item[pIndex - 2].config && item[pIndex - 2].config.border === 'dotted' ? '1px dotted' : '',
@@ -551,6 +551,115 @@
                         >
                       </template>
                     </span>
+                    <span
+                      v-if="
+                        item[pIndex + 2] &&
+                        item[pIndex + 2].chs &&
+                        chsFhList.indexOf(item[pIndex + 2].chs) > -1 &&
+                        item[pIndex + 3] &&
+                        item[pIndex + 3].chs &&
+                        chsFhList.indexOf(item[pIndex + 3].chs) > -1
+                      "
+                      class="NNPE-words-box"
+                    >
+                      <template v-if="curQue.property.pinyin_position == 'top'">
+                        <span
+                          v-if="config.isShowPY"
+                          :class="[
+                            'NNPE-pinyin',
+                            noFont.indexOf(item[pIndex + 3].pinyin) > -1 ? 'noFont' : '',
+                            bgIndex == 1 ? 'font-white' : '',
+                          ]"
+                          :style="{
+                            fontSize: pySize + 'px',
+                            textAlign: left,
+                            height: pySize * 1.25 + 'px',
+                          }"
+                          >{{ NumberList.indexOf(item[pIndex + 3].pinyin) == -1 ? item[pIndex + 3].pinyin : '' }}</span
+                        >
+                      </template>
+                      <span
+                        :class="['NNPE-chs', curQue.property.pinyin_position == 'top' ? 'bottom' : '']"
+                        :style="{
+                          fontSize: hzSize + 'px',
+                          textAlign: left,
+                          height: hzSize * 1.17 + 'px',
+                        }"
+                      >
+                        <span
+                          :class="[
+                            pItem.timeList[pItem.leg - 1] &&
+                            curTime >= pItem.timeList[pItem.leg - 1].wordBg &&
+                            curQue.wordTime &&
+                            curQue.wordTime[curSentIndex] &&
+                            curTime <= curQue.wordTime[curSentIndex].ed
+                              ? bgIndex == 0
+                                ? 'active'
+                                : 'active-yellow'
+                              : '',
+                            bgIndex == 1 ? 'font-white' : '',
+                            newWordList.indexOf(item[pIndex + 3].chs) > -1 && !isPlaying ? 'newActive' : '',
+                            item[pIndex + 3].words && !isPlaying ? 'newActive' : '',
+                          ]"
+                          :style="{
+                            fontSize: hzSize + 'px',
+                            fontFamily:
+                              item[pIndex + 3].config && item[pIndex + 3].config.fontFamily
+                                ? item[pIndex + 3].config.fontFamily
+                                : '',
+                            textDecoration:
+                              item[pIndex + 3].config && item[pIndex + 3].config.textDecoration
+                                ? item[pIndex + 3].config.textDecoration
+                                : '',
+                            borderBottom:
+                              item[pIndex + 3].config && item[pIndex + 3].config.border === 'dotted'
+                                ? '1px dotted'
+                                : '',
+                            fontWeight:
+                              item[pIndex + 3].config && item[pIndex + 3].config.fontWeight
+                                ? item[pIndex + 3].config.fontWeight
+                                : '',
+                            color:
+                              item[pIndex + 3].config && item[pIndex + 3].config.color
+                                ? item[pIndex + 3].config.color
+                                : '',
+                            height: hzSize * 1.17 + 'px',
+                            display: 'inline-block',
+                            width: item[pIndex + 3].chs.trim() === '' ? '10px' : '',
+                          }"
+                          >{{ convertText(item[pIndex + 3].chs) }}</span
+                        >
+                        <img
+                          v-if="
+                            item[pIndex + 3].img &&
+                            item[pIndex + 3].img.length > 0 &&
+                            item[pIndex + 3].imgPosition === 'after'
+                          "
+                          :src="item[pIndex + 3].img[0].file_url"
+                          :style="{
+                            width: hzSize + 'px',
+                            height: hzSize + 'px',
+                          }"
+                        />
+                      </span>
+                      <template v-if="curQue.property.pinyin_position == 'bottom'">
+                        <span
+                          v-if="config.isShowPY"
+                          :class="[
+                            'NNPE-pinyin',
+                            noFont.indexOf(item[pIndex + 3].pinyin) > -1 ? 'noFont' : '',
+                            bgIndex == 1 ? 'font-white' : '',
+                            'bottom',
+                          ]"
+                          :style="{
+                            fontSize: pySize + 'px',
+                            textAlign: left,
+                            height: pySize * 1.25 + 'px',
+                          }"
+                          >{{ NumberList.indexOf(item[pIndex + 3].pinyin) == -1 ? item[pIndex + 3].pinyin : '' }}</span
+                        >
+                      </template>
+                    </span>
                   </template>
                   <template v-else>
                     <span class="NNPE-words-box">
@@ -1348,12 +1457,17 @@ export default {
       this.pauseAudio();
       this.isPlaying = false;
       let item = JSON.parse(JSON.stringify(this.sentList[this.curSentIndex]));
+
       if (item.sentArr) {
         this.item = item.sentArr;
         this.enwords = item.enwords;
       } else {
         this.item = item;
       }
+      this.item.forEach((wItem, wIndex) => {
+        this.mergeWordSymbol(wItem);
+        wItem.isShow = wItem.isShow;
+      });
       this.sentList.forEach((item) => {
         this.isCollArr.push(false);
       });
@@ -1373,6 +1487,18 @@ export default {
       }
       // this.checkCollStatus();
     },
+    // 词和标点合一起
+    mergeWordSymbol(wItem) {
+      if (
+        this.chsFhList.indexOf(wItem.chs) > -1 ||
+        this.NumberList.indexOf(wItem.chs) > -1 ||
+        this.firstFhlist.indexOf(wItem.chs) > -1
+      ) {
+        wItem.isShow = false;
+      } else {
+        wItem.isShow = true;
+      }
+    },
     pauseAudio() {
       let audio = document.getElementsByTagName('audio');
       if (audio && audio.length > 0 && window.location.href.indexOf('GCLS-Learn') === -1 && audio.forEach) {
@@ -1478,7 +1604,7 @@ export default {
             }
           });
         }
-      } else if (this.newWordList.indexOf(item.chs) > -1) {
+      } else if (this.newWordList.indexOf(item.chs) > -1 || this.newWordList.indexOf(item.words) > -1) {
         if (this.oldHz !== item.chs) {
           this.isShow = false;
 

+ 110 - 0
src/views/book/courseware/preview/components/article/WordModelChs.vue

@@ -590,6 +590,116 @@
                               }}</span
                             >
                           </span>
+                          <span
+                            v-if="
+                              item.wordsList[pIndex + 2] &&
+                              item.wordsList[pIndex + 2].chs &&
+                              chsFhList.indexOf(item.wordsList[pIndex + 2].chs) > -1 &&
+                              item.wordsList[pIndex + 3] &&
+                              item.wordsList[pIndex + 3].chs &&
+                              chsFhList.indexOf(item.wordsList[pIndex + 3].chs) > -1
+                            "
+                            class="NNPE-words-box"
+                            @click="showWordDetail($event, item.wordsList[pIndex + 3])"
+                          >
+                            <span
+                              v-if="curQue.property.pinyin_position == 'top' && config.isShowPY && item.dhaspinyin"
+                              :class="[
+                                'NNPE-pinyin',
+                                noFont.indexOf(item.wordsList[pIndex + 3].pinyin) > -1 ? 'noFont' : '',
+                              ]"
+                              style="text-align: left"
+                              :style="{
+                                fontSize: attrib && attrib.pinyin_size ? attrib.pinyin_size : '14px',
+                                height:
+                                  attrib && attrib.pinyin_size
+                                    ? attrib.pinyin_size.replace('pt', '') * 1.5 + 'pt'
+                                    : '22px',
+                              }"
+                              >{{
+                                NumberList.indexOf(item.wordsList[pIndex + 3].pinyin) == -1
+                                  ? item.wordsList[pIndex + 3].pinyin
+                                  : ''
+                              }}</span
+                            >
+                            <span
+                              class="NNPE-chs"
+                              style="text-align: left"
+                              :class="[
+                                item.timeList &&
+                                item.timeList[pItem.sentIndex] &&
+                                curTime >= item.timeList[pItem.sentIndex].bg &&
+                                curTime <= item.timeList[pItem.sentIndex].ed &&
+                                curTime
+                                  ? 'active'
+                                  : '',
+                                pItem.paraIndex == paraIndex && pItem.sentIndex == sentIndex ? 'overActive' : '',
+                                pItem.chstimeList &&
+                                pItem.chstimeList[pItem.leg - 1] &&
+                                curTime >= pItem.chstimeList[pItem.leg - 1].wordBg &&
+                                curQue.wordTime &&
+                                curTime <= item.timeList[pItem.sentIndex].ed
+                                  ? 'wordActive'
+                                  : '',
+                              ]"
+                              :style="{
+                                fontFamily: item.wordsList[pIndex + 3].config.fontFamily,
+                                textDecoration: item.wordsList[pIndex + 3].config.textDecoration,
+                                borderBottom: item.wordsList[pIndex + 3].config.border === 'dotted' ? '1px dotted' : '',
+                                fontWeight: item.wordsList[pIndex + 3].config.fontWeight,
+                                color: item.wordsList[pIndex + 3].config.color,
+                                height:
+                                  attrib && attrib.font_size ? attrib.font_size.replace('pt', '') * 1.4 + 'pt' : '28px',
+                                fontSize: attrib && attrib.font_size ? attrib.font_size : '20px',
+                                lineHeight:
+                                  attrib && attrib.font_size ? attrib.font_size.replace('pt', '') * 1.4 + 'pt' : '28px',
+                                backgroundColor:
+                                  item.timeList &&
+                                  item.timeList[pItem.sentIndex] &&
+                                  curTime >= item.timeList[pItem.sentIndex].bg &&
+                                  curTime <= item.timeList[pItem.sentIndex].ed &&
+                                  curTime &&
+                                  attrib
+                                    ? attrib.assist_color
+                                    : '',
+                                width: item.wordsList[pIndex + 3].chs.trim() === '' ? '6px' : '',
+                              }"
+                              >{{
+                                NumberList.indexOf(item.wordsList[pIndex + 3].pinyin) == -1
+                                  ? convertText(item.wordsList[pIndex + 3].chs)
+                                  : ''
+                              }}<img
+                                v-if="
+                                  item.wordsList[pIndex + 3].img &&
+                                  item.wordsList[pIndex + 3].img.length > 0 &&
+                                  item.wordsList[pIndex + 3].imgPosition === 'after'
+                                "
+                                :src="item.wordsList[pIndex + 3].img[0].file_url"
+                                :style="{
+                                  height: attrib && attrib.font_size ? attrib.font_size : '20px',
+                                }"
+                            /></span>
+                            <span
+                              v-if="curQue.property.pinyin_position == 'bottom' && config.isShowPY && item.dhaspinyin"
+                              :class="[
+                                'NNPE-pinyin',
+                                noFont.indexOf(item.wordsList[pIndex + 3].pinyin) > -1 ? 'noFont' : '',
+                              ]"
+                              style="text-align: left"
+                              :style="{
+                                fontSize: attrib && attrib.pinyin_size ? attrib.pinyin_size : '14px',
+                                height:
+                                  attrib && attrib.pinyin_size
+                                    ? attrib.pinyin_size.replace('pt', '') * 1.5 + 'pt'
+                                    : '22px',
+                              }"
+                              >{{
+                                NumberList.indexOf(item.wordsList[pIndex + 3].pinyin) == -1
+                                  ? item.wordsList[pIndex + 3].pinyin
+                                  : ''
+                              }}</span
+                            >
+                          </span>
                         </template>
                         <template v-else>
                           <span class="NNPE-words-box" @click="showWordDetail($event, pItem)">

+ 5 - 1
src/views/book/courseware/preview/components/article/index.vue

@@ -348,6 +348,9 @@ export default {
     'data.content': {
       handler(val) {
         if (val) {
+          this.showPhrases = false;
+          this.showPractice = false;
+          this.showWord = false;
           this.handleData();
           let _this = this;
           if (!this.isJudgingRightWrong) {
@@ -629,6 +632,7 @@ export default {
             : [],
         );
       this.NNPEAnnotationList = this.data.notes_list && this.data.notes_list.option ? this.data.notes_list.option : [];
+      this.handleNewword();
       let resArr = [];
       let sentArrTotal = [];
       let timeArr = [];
@@ -739,7 +743,7 @@ export default {
             words = wItem.chs;
             endIndex = startIndex + 1;
           }
-        } else if (item[0] === wItem.chs && sentence.indexOf(item) > -1) {
+        } else if (item.substring(0, wItem.chs.length) === wItem.chs && sentence.indexOf(item) > -1) {
           let index = null;
           let chsStr = '';
           for (let i = startIndex; i < sItem.length + 1; i++) {

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

@@ -77,7 +77,7 @@
                   <Strockplayredline
                     v-if="items && items.type === 'hanzi'"
                     :Book_text="items.con"
-                    :play-storkes="isEnable(data.property.is_enable_play_structure)"
+                    :play-storkes="isEnable(data.property.is_enable_stroke)"
                     :cur-item="
                       isEnable(data.property.is_enable_high_strokes)
                         ? data.property.model === 'input'

+ 1 - 0
src/views/book/courseware/preview/components/common/PreviewMixin.js

@@ -159,6 +159,7 @@ const mixin = {
         if (!newVal) return;
         this.componentBackground = newVal;
       },
+      immediate: true,
     },
   },
   components: {

+ 1 - 1
src/views/book/courseware/preview/components/dialogue_article/PhraseModelChs.vue

@@ -1615,7 +1615,7 @@ export default {
             words = wItem.chs;
             endIndex = startIndex + 1;
           }
-        } else if (item[0] === wItem.chs && sentence.indexOf(item) > -1) {
+        } else if (item.substring(0, wItem.chs.length) === wItem.chs && sentence.indexOf(item) > -1) {
           let index = null;
           let chsStr = '';
           for (let i = startIndex; i < sItem.length + 1; i++) {

+ 6 - 5
src/views/book/courseware/preview/components/dialogue_article/Practicechs.vue

@@ -119,7 +119,7 @@
                   :type="curQue.property.role_img_type"
                 />
               </template>
-              <div v-else style="width: 36px; height: 36px"></div>
+              <div v-else style="width: 44px; height: 44px"></div>
               <div class="sentence-box">
                 <template v-if="item.sentArr[0].sentIndex == 0">
                   <!-- <div class="roleDetail" v-if="item.roleDetail.detail && item.roleDetail.detail.wordsList.length > 0">
@@ -1165,7 +1165,7 @@ export default {
     },
     pauseAudio() {
       let audio = document.getElementsByTagName('audio');
-      if (audio && audio.length > 0 && window.location.href.indexOf('GCLS-Learn') === -1) {
+      if (audio && audio.length > 0 && window.location.href.indexOf('GCLS-Learn') === -1 && audio.forEach) {
         audio.forEach((item) => {
           item.pause();
         });
@@ -1173,7 +1173,7 @@ export default {
     },
     pauseVideo() {
       let video = document.getElementsByTagName('video');
-      if (video && video.length > 0 && window.location.href.indexOf('GCLS-Learn') === -1) {
+      if (video && video.length > 0 && window.location.href.indexOf('GCLS-Learn') === -1 && video.forEach) {
         video.forEach((item) => {
           item.pause();
         });
@@ -1255,6 +1255,7 @@ export default {
       let curQue = JSON.parse(JSON.stringify(this.curQue));
       let wordTimeList = curQue.wordTime;
       let dhaspinyin = false; // 每段是否有拼音
+      this.handleNewword();
       curQue.detail.forEach((dItem, dIndex) => {
         dhaspinyin = false;
         let roleDetail = this.getRole(dItem);
@@ -1382,7 +1383,7 @@ export default {
             words = wItem.chs;
             endIndex = startIndex + 1;
           }
-        } else if (item[0] === wItem.chs && sentence.indexOf(item) > -1) {
+        } else if (item.substring(0, wItem.chs.length) === wItem.chs && sentence.indexOf(item) > -1) {
           let index = null;
           let chsStr = '';
           for (let i = startIndex; i < sItem.length + 1; i++) {
@@ -1752,7 +1753,7 @@ export default {
     display: flex;
     align-items: center;
     justify-content: space-between;
-    padding: 0 10px 8px 68px;
+    padding: 0 10px 8px 76px;
 
     &-inner {
       display: flex;

+ 5 - 1
src/views/book/courseware/preview/components/dialogue_article/index.vue

@@ -343,6 +343,9 @@ export default {
     'data.detail.length': {
       handler(val) {
         if (val) {
+          this.showPhrases = false;
+          this.showPractice = false;
+          this.showWord = false;
           this.handleData();
           let _this = this;
           _this.$nextTick(() => {
@@ -628,6 +631,7 @@ export default {
             : [],
         );
       this.NNPEAnnotationList = this.data.notes_list && this.data.notes_list.option ? this.data.notes_list.option : [];
+      this.handleNewword();
       let resArr = [];
       let sentArrTotal = [];
       let timeArr = [];
@@ -737,7 +741,7 @@ export default {
             words = wItem.chs;
             endIndex = startIndex + 1;
           }
-        } else if (item[0] === wItem.chs && sentence.indexOf(item) > -1) {
+        } else if (item.substring(0, wItem.chs.length) === wItem.chs && sentence.indexOf(item) > -1) {
           let index = null;
           let chsStr = '';
           for (let i = startIndex; i < sItem.length + 1; i++) {

+ 9 - 18
src/views/book/courseware/preview/components/drawing/DrawingPreview.vue

@@ -6,10 +6,10 @@
     <div
       class="img-box"
       :style="{
-        background: image_url ? '' : '#DCDFE6',
+        background: image_url ? '' : '#f7f8fa',
         width: isMobile ? '100%' : data.image_width + 'px',
         height: isMobile ? mobileHeight + 'px' : data.image_height + 'px',
-        border: '1px dotted #DCDFE6',
+        border: '1px dotted #f7f8fa',
       }"
     >
       <div v-if="image_url" class="img-set" :style="{ top: `${data.imgData.top}px`, left: `${data.imgData.left}px` }">
@@ -82,7 +82,9 @@
           @click="selSize(2)"
           :style="{ cursor: disabled ? 'not-allowed' : 'pointer' }"
         />
-        <div class="save" @click="save" :style="{ cursor: disabled ? 'not-allowed' : 'pointer' }">保 存</div>
+        <div class="save" @click="save" :style="{ cursor: disabled ? 'not-allowed' : 'pointer' }">
+          <SvgIcon icon-class="saves" size="24" title="保存" />
+        </div>
       </div>
     </div>
   </div>
@@ -187,7 +189,7 @@ export default {
     eraser() {
       this.penIndex = null;
       this.options = {
-        penColor: '#DCDFE6',
+        penColor: '#f7f8fa',
         minWidth: 3,
         maxWidth: 3,
       };
@@ -271,7 +273,7 @@ export default {
     / 2px auto 2px;
 
   img {
-    object-fit: cover;
+    object-fit: contain;
   }
 }
 
@@ -289,22 +291,11 @@ export default {
 
   .save {
     box-sizing: border-box;
-    width: 65px;
-    height: 32px;
-    margin-left: 16px;
-    font-size: 16px;
-    font-weight: 400;
-    line-height: 32px;
+    height: 24px;
+    margin-left: 8px;
     color: #000;
     text-align: center;
     cursor: pointer;
-    background: rgba(0, 0, 0, 5%);
-    border: 1px solid rgba(0, 0, 0, 10%);
-    border-radius: 6px;
-  }
-
-  .save:hover {
-    background: rgba(0, 0, 0, 25%);
   }
 
   > img {

+ 179 - 96
src/views/book/courseware/preview/components/fill/FillPreview.vue

@@ -11,88 +11,101 @@
       />
       <div class="fill-wrapper">
         <div class="fill-content">
-          <template v-for="(li, i) in modelEssay">
-            <template v-if="li.type === 'text'">
-              <PinyinText
-                :key="`text-${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-${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',
-                    },
-                  ]"
-                  @input="handleInput(li.input, li.mark)"
-                />
-              </template>
-
-              <!-- 选词填空 -->
-              <template v-else-if="data.property.fill_type === fillTypeList[1].value">
-                <el-popover :key="`popover-${i}`" placement="top" trigger="click">
-                  <div class="word-list">
+          <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),
+                        },
+                      ]"
+                      @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
-                      v-for="{ content, mark } in data.word_list"
-                      :key="mark"
-                      class="word-item"
-                      @click="handleSelectWord(content, mark, li)"
-                      v-html="sanitizeHTML(content)"
+                      :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>
-                  </div>
-
-                  <span
-                    slot="reference"
-                    class="select-content"
-                    :style="[{ minWidth: data.property.input_default_width + 'px' }]"
-                    v-html="sanitizeHTML(li.input)"
-                  ></span>
-                </el-popover>
+                  </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>
-
-              <!-- 手写填空 -->
-              <template v-else-if="data.property.fill_type === fillTypeList[2].value">
-                <span :key="`write-${i}`" class="write-click" @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-${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>
+            </span>
           </template>
         </div>
       </div>
@@ -133,24 +146,33 @@
     >
       <div slot="right-answer" class="fill-wrapper">
         <div class="fill-content">
-          <template v-for="(li, i) in modelEssay">
-            <template v-if="li.type === 'text'">
-              <PinyinText
-                :key="`answer-text-${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-${i}`" class="right-answer">
-                {{ computedAnswerText(li.mark) }}
-              </span>
-            </template>
+          <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>
@@ -214,6 +236,25 @@ export default {
     isShowAnswerFill() {
       return this.hasFixedAnswer || (this.data.answer_list || []).length > 0;
     },
+    /**
+     * 按段落对齐方式对 modelEssay 分组,便于整体应用 text-align,
+     * 让同一段内的文字块与填空连续排列(可换行),而不是每块独占一行。
+     * @returns {Array} [{ align, items, indexes }]
+     */
+    groupedModelEssay() {
+      const groups = [];
+      (this.modelEssay || []).forEach((item, index) => {
+        const align = item.align || '';
+        const last = groups[groups.length - 1];
+        if (last && last.align === align) {
+          last.items.push(item);
+          last.indexes.push(index);
+        } else {
+          groups.push({ align, items: [item], indexes: [index] });
+        }
+      });
+      return groups;
+    },
   },
   watch: {
     'data.model_essay': {
@@ -258,6 +299,47 @@ export default {
     },
   },
   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`;
+    },
     handleWav(data) {
       this.data.record_list = data;
     },
@@ -427,6 +509,7 @@ export default {
         if (li.type === 'input') {
           li.input = '';
           li.write_base64 = '';
+          li.audio_answer_list = [];
         }
       });
       this.selectedWordList = [];

+ 2 - 2
src/views/book/courseware/preview/components/table/TablePreview.vue

@@ -365,8 +365,8 @@
                 <td
                   v-if="col.is_show_cell"
                   :key="col.mark"
-                  :colspan="col.colspan"
-                  :rowspan="col.rowspan"
+                  :colspan="col.cell.colspan"
+                  :rowspan="col.cell.rowspan"
                   :style="{
                     borderTop: i === 0 ? '1px solid ' + data.property.border_color : '',
                     borderBottom: '1px solid ' + data.property.border_color,

+ 2 - 1
src/views/book/courseware/preview/components/voice_matrix/VoiceMatrixPreview.vue

@@ -38,7 +38,7 @@
       <!-- 语音矩阵 -->
       <div
         class="voice-matrix-container"
-        :style="{ height: isEnable(data.property.is_enable_record) ? 'calc(100% - 135px)' : 'auto' }"
+        :style="{ height: isEnable(data.property.is_enable_record) ? 'calc(100% - 115px)' : 'auto' }"
       >
         <div
           v-if="data.option_list.length > 0"
@@ -185,6 +185,7 @@
           type="promax"
           class="luyin-box"
           :answer-record-list="data.record_list"
+          :select-data="selectData"
           :attrib="data.unified_attrib"
           @getWavblob="getWavblob"
           @getSelectData="getSelectData"

+ 1 - 1
src/views/book/courseware/preview/components/write_base/WriteBasePreview.vue

@@ -27,7 +27,7 @@
         />
         <div class="btn-box">
           <SvgIcon icon-class="clear" size="24" title="清除" @click="handleReset" />
-          <SvgIcon icon-class="camera" size="24" title="保存" @click="handleGenerate" />
+          <SvgIcon icon-class="saves" size="24" title="保存" @click="handleGenerate" />
         </div>
       </div>
       <!-- <template v-if="data.write_base64[0]">

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio