WordCardPreview.vue 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <!-- eslint-disable vue/no-v-html -->
  2. <template>
  3. <div class="word-card-preview">
  4. <div class="stem">
  5. <span class="question-number" :style="{ fontSize: data.property.stem_question_number_font_size }">
  6. {{ questionNumberEndIsBracket(data.property.question_number) }}
  7. </span>
  8. <span v-html="sanitizeHTML(data.stem)"></span>
  9. </div>
  10. <div
  11. v-if="isEnable(data.property.is_enable_description)"
  12. class="description rich-text"
  13. v-html="sanitizeHTML(data.description)"
  14. ></div>
  15. <!-- 笔画学习 -->
  16. <div :class="['words-box']">
  17. <el-image
  18. v-if="
  19. option_list[active_index] &&
  20. option_list[active_index].picture_file_id &&
  21. pic_list[option_list[active_index].picture_file_id]
  22. "
  23. :src="pic_list[option_list[active_index].picture_file_id]"
  24. fit="contain"
  25. />
  26. <div class="words-right">
  27. <template v-for="(item, index) in option_list">
  28. <div v-if="index === active_index" :key="index" class="strock-box">
  29. <div v-if="item.audio_file_id || item.pinyin" class="pinyin-box">
  30. <AudioPlay v-if="item.audio_file_id" :file-id="item.audio_file_id" theme-color="white" />
  31. <span class="pinyin">{{ item.pinyin }}</span>
  32. </div>
  33. <div v-if="item.hz_strokes_list && item.hz_strokes_list.length > 0" class="strock-left">
  34. <template v-for="(items, indexs) in item.hz_strokes_list">
  35. <Strockplayredline
  36. v-if="items"
  37. :key="indexs"
  38. :play-storkes="true"
  39. :book-text="items.hz"
  40. :target-div="'pre' + items.hz + indexs + active_index"
  41. :book-strokes="items.strokes"
  42. :class="['strock-chinese', indexs !== item.hz_strokes_list.length - 1 ? 'border-right-none' : '']"
  43. />
  44. </template>
  45. </div>
  46. </div>
  47. </template>
  48. <el-divider />
  49. <div v-if="option_list[active_index]" class="content-box">
  50. <span v-if="option_list[active_index].definition_preview.length > 0" class="tips">释义:</span>
  51. <p v-for="(itemd, indexd) in option_list[active_index].definition_preview" :key="indexd" class="definition">
  52. {{ itemd }}
  53. </p>
  54. <span v-if="option_list[active_index].collocation" class="tips">搭配:</span>
  55. <p v-if="option_list[active_index].collocation" class="definition">
  56. {{ option_list[active_index].collocation }}
  57. </p>
  58. <span v-if="option_list[active_index].example_sentence.length > 0" class="tips">例句:</span>
  59. <template v-for="(iteme, indexe) in option_list[active_index].example_sentence">
  60. <p v-if="iteme.trim()" :key="indexe + iteme.trim()" class="example-sentence">
  61. <span>{{ computeOptionMethods[data.option_number_show_mode](indexe) }} </span>
  62. <span>{{ iteme }}</span>
  63. </p>
  64. </template>
  65. </div>
  66. <el-divider />
  67. <div v-if="answer.answer_list[active_index]" class="sound-box">
  68. <SoundRecordPreview
  69. :wav-blob.sync="answer.answer_list[active_index].audio_file_id"
  70. :type="'small'"
  71. :disabled="disabled"
  72. />
  73. </div>
  74. </div>
  75. <div v-if="option_list.length > 1" :class="['words-item']">
  76. <label
  77. v-for="(item, index) in option_list"
  78. :key="index"
  79. :class="[active_index === index ? 'active' : '']"
  80. @click="active_index = index"
  81. >{{ item.content }}</label
  82. >
  83. </div>
  84. </div>
  85. </div>
  86. </template>
  87. <script>
  88. import { computeOptionMethods } from '@/views/exercise_questions/data/common';
  89. import PreviewMixin from './components/PreviewMixin';
  90. import { GetFileStoreInfo } from '@/api/app';
  91. import SoundRecordPreview from './components/common/SoundRecordPreview.vue';
  92. import Strockplayredline from './components/common/Strockplayredline.vue';
  93. export default {
  94. name: 'WordCardPreview',
  95. components: { SoundRecordPreview, Strockplayredline },
  96. mixins: [PreviewMixin],
  97. data() {
  98. return {
  99. computeOptionMethods,
  100. hanzi_color: '#404040', // 描红汉字底色
  101. pic_list: {},
  102. active_index: 0,
  103. option_list: [],
  104. };
  105. },
  106. watch: {
  107. data: {
  108. handler(val) {
  109. if (!val || this.data.type !== 'word_card') return;
  110. this.handleData();
  111. },
  112. deep: true,
  113. immediate: true,
  114. },
  115. },
  116. created() {
  117. // this.handleData();
  118. },
  119. mounted() {},
  120. methods: {
  121. // 初始化数据
  122. handleData() {
  123. this.pic_list = {};
  124. this.data.file_id_list.forEach((item) => {
  125. GetFileStoreInfo({ file_id: item }).then(({ file_id, file_url }) => {
  126. this.$set(this.pic_list, file_id, file_url);
  127. });
  128. });
  129. let option_list = JSON.parse(JSON.stringify(this.data.option_list));
  130. option_list.forEach((item) => {
  131. let obj = {
  132. mark: item.mark,
  133. audio_file_id: '',
  134. };
  135. item.definition_preview = item.definition.split('\n');
  136. if (!this.isJudgingRightWrong) {
  137. this.answer.answer_list.push(obj);
  138. }
  139. });
  140. this.option_list = option_list;
  141. },
  142. },
  143. };
  144. </script>
  145. <style lang="scss" scoped>
  146. @use '@/styles/mixin.scss' as *;
  147. .word-card-preview {
  148. @include preview;
  149. .words-box {
  150. display: flex;
  151. column-gap: 24px;
  152. .el-image {
  153. flex-shrink: 0;
  154. width: 346px;
  155. height: 346px;
  156. }
  157. .words-right {
  158. flex: 1;
  159. }
  160. .words-item {
  161. width: 120px;
  162. label {
  163. display: block;
  164. padding: 8px 16px;
  165. margin-bottom: 4px;
  166. font-size: 18px;
  167. font-weight: 400;
  168. line-height: 26px;
  169. color: rgba(0, 0, 0, 30%);
  170. cursor: pointer;
  171. border-radius: 20px;
  172. &.active {
  173. color: #fff;
  174. background: rgba(48, 110, 255, 100%);
  175. }
  176. }
  177. }
  178. .pinyin {
  179. font-family: 'League';
  180. font-size: 16px;
  181. font-weight: 500;
  182. color: #fff;
  183. }
  184. .strock-chinese {
  185. width: 96px;
  186. height: 96px;
  187. border: 1px solid #e81b1b;
  188. :deep .strock-play-box {
  189. width: 16px;
  190. height: 16px;
  191. }
  192. }
  193. .border-right-none {
  194. border-right: none;
  195. }
  196. }
  197. .strock-left {
  198. display: flex;
  199. }
  200. .pinyin-box {
  201. display: flex;
  202. gap: 4px;
  203. align-items: center;
  204. width: max-content;
  205. padding: 4px 8px;
  206. margin-bottom: 10px;
  207. background: rgba(47, 111, 236, 100%);
  208. border-radius: 40px;
  209. :deep .audio-play {
  210. width: auto;
  211. height: 24px;
  212. background: none;
  213. }
  214. }
  215. .definition {
  216. margin: 0 0 8px;
  217. font-size: 16px;
  218. line-height: 24px;
  219. color: #000;
  220. }
  221. .tips {
  222. display: block;
  223. margin-bottom: 8px;
  224. font-size: 16px;
  225. font-weight: 400;
  226. line-height: 24px;
  227. color: rgba(0, 0, 0, 40%);
  228. }
  229. .example-sentence {
  230. margin: 0;
  231. font-size: 16px;
  232. line-height: 24px;
  233. color: #000;
  234. }
  235. .sound-box {
  236. width: max-content;
  237. padding: 8px;
  238. background: $content-color;
  239. border-radius: 40px;
  240. }
  241. .el-divider {
  242. margin: 16px 0;
  243. }
  244. }
  245. </style>