CharacterBasePreview.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. <!-- eslint-disable vue/no-v-html -->
  2. <template>
  3. <div v-if="show_preview" class="character-preview" :style="getAreaStyle()">
  4. <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
  5. <div class="main">
  6. <div class="main-top" :style="{ width: data.hz_strokes_list.length * 64 + 'px' }">
  7. <AudioPlay
  8. v-if="isEnable(data.property.is_enable_voice)"
  9. :file-id="data.audio_file_id"
  10. :theme-color="data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : ''"
  11. />
  12. <span v-if="isEnable(data.property.view_pinyin)" class="pinyin">{{ data.pinyin }}</span>
  13. </div>
  14. <div class="strock-chinese-box">
  15. <template v-if="data.property.fun_type === 'show'">
  16. <Strockplayredline
  17. v-for="(items, indexs) in data.hz_strokes_list"
  18. :key="indexs"
  19. :play-storkes="true"
  20. :book-text="data.content"
  21. :target-div="'pre' + data.content + indexs"
  22. :book-strokes="items.strokes"
  23. :class="['strock-chinese', indexs !== data.hz_strokes_list.length - 1 ? 'border-right-none' : '']"
  24. :bg-type="data.property.frame_type"
  25. :play-color="data.property.frame_color"
  26. />
  27. </template>
  28. <template v-else>
  29. <template v-if="isEnable(data.property.is_enable_miao)">
  30. <div
  31. v-for="(itemI, indexI) in miao_arr[0]"
  32. :key="indexI + data.property.fun_type + 0"
  33. style="display: flex"
  34. @click="miaoStorkes(0, indexI, data.mark, data.content, itemI.strokes)"
  35. >
  36. <Strockplayredline
  37. v-if="data.imgArr[indexI] && data.imgArr[indexI] === 'true'"
  38. :play-storkes="false"
  39. :book-text="data.content"
  40. :target-div="'write-praT' + data.content + itemI + Math.random().toString(36).substring(2, 10)"
  41. :book-strokes="itemI.strokes"
  42. :class="['strock-chinese', indexI !== miao_arr[0].length - 1 ? 'border-right-none' : '']"
  43. />
  44. <Strockplayredline
  45. v-else
  46. :play-storkes="false"
  47. :book-text="data.content"
  48. :target-div="'write-praT' + data.content + itemI + Math.random().toString(36).substring(2, 10)"
  49. :book-strokes="itemI.strokes"
  50. :stroke-color="'#ddd'"
  51. :class="['strock-chinese', indexI !== miao_arr[0].length - 1 ? 'border-right-none' : '']"
  52. />
  53. </div>
  54. </template>
  55. <template v-else>
  56. <div v-for="(items, indexs) in data.imgArr" :key="indexs" class="con-box">
  57. <div
  58. :class="['strockplay-newWord', indexs !== data.imgArr.length - 1 ? 'border-right-none' : '']"
  59. :style="{ borderColor: data.property.frame_color }"
  60. @click="freeWrite(items ? JSON.parse(items) : null, 0, indexs, data.mark)"
  61. >
  62. <SvgIcon icon-class="hanzi-writer-bg" class="character-target-bg" />
  63. <img
  64. v-if="!play_status && items && JSON.parse(items).strokes_image"
  65. class="hanzi-writer-img"
  66. :src="JSON.parse(items).strokes_image"
  67. alt=""
  68. />
  69. </div>
  70. </div>
  71. </template>
  72. <!-- <Strockred
  73. v-if="data.answer.answer_list[0]"
  74. :class="[
  75. 'strock-red',
  76. data.answer.answer_list[0].strokes_content_list[active_col_index] &&
  77. data.answer.answer_list[0].strokes_content_list[active_col_index] === 'true'
  78. ? 'strock-red-zindex'
  79. : '',
  80. ]"
  81. :book-text="current_hz"
  82. :hanzi-color="hanzi_color"
  83. :target-div="'write-praT' + current_hz + active_col_index + Math.random().toString(36).substring(2, 10)"
  84. :book-strokes="current_hz_data"
  85. :is-answer.sync="data.answer.answer_list[0].strokes_content_list[active_col_index]"
  86. ref="strockRed"
  87. /> -->
  88. </template>
  89. </div>
  90. <p v-if="data.definition" class="words-right">{{ data.definition }}</p>
  91. <template v-if="isEnable(data.property.is_enable_voice_answer)">
  92. <SoundRecord
  93. ref="record"
  94. type="normal"
  95. class="record-box"
  96. :answer-record-list="data.record_list"
  97. :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
  98. @handleWav="handleWav"
  99. />
  100. </template>
  101. </div>
  102. <div v-if="if_free_show" class="practiceBox practice-box-strock">
  103. <FreewriteLettle
  104. ref="freePaint"
  105. :current-tree-i-d="'1234456'"
  106. :current-hz="current_hz"
  107. :curren-hz-data="current_hz_data"
  108. :row-index="0"
  109. :col-index="active_col_index"
  110. :show-play="isEnable(data.property.is_enable_play_back)"
  111. :disabled="disabled"
  112. @closeIfFreeShow="closeIfFreeShow"
  113. @changePraShow="changePraShow"
  114. @changeCurQue="changeCurQue"
  115. @deleteWriteRecord="deleteWriteRecord"
  116. />
  117. </div>
  118. <div v-if="if_miao_show" class="practiceBox practice-box-strock">
  119. <div class="miao-box">
  120. <i class="el-icon-close close-icon" @click="if_miao_show = false"></i>
  121. <Strockplayredline
  122. v-if="
  123. (data.answer.answer_list[0].strokes_content_list[active_col_index] &&
  124. data.answer.answer_list[0].strokes_content_list[active_col_index] === 'true') ||
  125. disabled
  126. "
  127. :play-storkes="false"
  128. :book-text="current_hz"
  129. :target-div="'write-praT' + current_hz + active_col_index + Math.random().toString(36).substring(2, 10)"
  130. :book-strokes="current_hz_data"
  131. :stroke-color="
  132. disabled &&
  133. (!data.answer.answer_list[0].strokes_content_list[active_col_index] ||
  134. (data.answer.answer_list[0].strokes_content_list[active_col_index] &&
  135. data.answer.answer_list[0].strokes_content_list[active_col_index] === 'false'))
  136. ? '#ddd'
  137. : ''
  138. "
  139. />
  140. <Strockred
  141. ref="strockRed"
  142. :class="[
  143. 'strock-red',
  144. data.answer.answer_list[0].strokes_content_list[active_col_index] &&
  145. data.answer.answer_list[0].strokes_content_list[active_col_index] === 'true'
  146. ? 'strock-red-zindex'
  147. : '',
  148. ]"
  149. :book-text="current_hz"
  150. :hanzi-color="hanzi_color"
  151. :target-div="'write-praT' + current_hz + active_col_index + Math.random().toString(36).substring(2, 10)"
  152. :book-strokes="current_hz_data"
  153. :is-answer.sync="data.answer.answer_list[0].strokes_content_list[active_col_index]"
  154. :show-error-tip="isEnable(data.property.is_enable_error)"
  155. />
  156. <div v-if="!disabled" :class="['reset-box']" @click="resetHanzi">
  157. <SvgIcon icon-class="reset" class="reset-btn" />
  158. </div>
  159. </div>
  160. </div>
  161. </div>
  162. </template>
  163. <script>
  164. import { getCharacterBaseData } from '@/views/book/courseware/data/characterBase';
  165. import PreviewMixin from '../common/PreviewMixin';
  166. import AudioPlay from './components/AudioPlay.vue';
  167. import Strockplayredline from './components/Strockplayredline.vue';
  168. import Strockred from './components/Strockred.vue';
  169. import SoundRecord from '../../common/SoundRecord.vue';
  170. import FreewriteLettle from './components/FreewriteLettle.vue';
  171. export default {
  172. name: 'CharacterBasePreview',
  173. components: {
  174. AudioPlay,
  175. Strockplayredline,
  176. SoundRecord,
  177. Strockred,
  178. FreewriteLettle,
  179. },
  180. mixins: [PreviewMixin],
  181. inject: {
  182. bookStatus: {
  183. default: 'online',
  184. },
  185. },
  186. data() {
  187. return {
  188. data: getCharacterBaseData(),
  189. if_free_show: false,
  190. active_col_index: null,
  191. current_hz: '', // 当前汉字
  192. current_hz_data: null, // 当前汉字数据
  193. play_status: false, // 播放状态
  194. active_mark: '',
  195. option_list: [],
  196. show_preview: false,
  197. miao_arr: [],
  198. if_miao_show: false, // 描红模块
  199. hanzi_color: '#404040', // 描红汉字底色
  200. };
  201. },
  202. computed: {},
  203. watch: {
  204. 'data.record_list'(val) {
  205. this.data.record_list = val;
  206. },
  207. 'data.content': {
  208. handler(val) {
  209. if (val) {
  210. this.handleData();
  211. }
  212. },
  213. deep: true,
  214. immediate: true,
  215. },
  216. isJudgingRightWrong: {
  217. handler(val) {
  218. if (!val) return;
  219. this.handleData();
  220. },
  221. immediate: true,
  222. },
  223. },
  224. created() {
  225. // if (this.bookStatus === 'offline') {
  226. // this.$message.warning('播放笔画动画功能只有在线教材可以支持,请使用在线教材');
  227. // }
  228. },
  229. methods: {
  230. handleWav(data) {
  231. this.data.record_list = data;
  232. },
  233. handleData() {
  234. this.show_preview = false;
  235. if (!this.isJudgingRightWrong) {
  236. this.data.answer.answer_list = [];
  237. }
  238. this.miao_arr = [];
  239. this.miao_arr.push([]);
  240. let arr = [];
  241. this.data.hz_strokes_list.forEach((items) => {
  242. if (this.data.property.fun_type === 'write' && this.isEnable(this.data.property.is_enable_miao)) {
  243. this.miao_arr[0].push({
  244. strokes: items && items.strokes ? items.strokes : null,
  245. length: this.data.hz_strokes_list.length,
  246. });
  247. }
  248. arr.push(null);
  249. });
  250. this.data.imgArr = arr;
  251. if (this.isJudgingRightWrong) {
  252. this.data.imgArr = this.data.answer.answer_list[0].strokes_content_list;
  253. }
  254. let obj = {
  255. mark: this.data.mark,
  256. strokes_content_list: arr,
  257. };
  258. if (!this.isJudgingRightWrong) {
  259. this.data.answer.answer_list.push(obj);
  260. }
  261. this.show_preview = true;
  262. },
  263. changePraShow() {
  264. this.if_free_show = false;
  265. },
  266. closeIfFreeShow(data, rowIndex, colIndex, mark) {
  267. this.data.imgArr[colIndex] = JSON.stringify(data);
  268. this.if_free_show = false;
  269. this.freeWrite(data, rowIndex, colIndex, mark);
  270. this.$forceUpdate();
  271. },
  272. freeWrite(imgUrl, index, indexs, mark) {
  273. this.if_free_show = true;
  274. this.active_col_index = indexs;
  275. this.active_mark = mark;
  276. this.current_hz = this.data.content;
  277. this.current_hz_data = imgUrl;
  278. },
  279. // 删除记录
  280. deleteWriteRecord(rowIndex, colIndex) {
  281. this.$set(this.data.imgArr, colIndex, JSON.stringify({}));
  282. this.changeCurQue(null, colIndex, this.active_mark);
  283. this.current_hz_data = null;
  284. this.active_mark = '';
  285. this.$forceUpdate();
  286. },
  287. changeCurQue(answer, colIndex, mark) {
  288. if (answer) {
  289. let write_model = [];
  290. this.answer.answer_list.forEach((itema) => {
  291. if (itema.mark === mark) {
  292. write_model = itema.strokes_content_list;
  293. }
  294. });
  295. write_model[colIndex] = JSON.stringify(answer);
  296. }
  297. },
  298. // 点击描红模块
  299. miaoStorkes(index, indexs, mark, content, storkes) {
  300. this.if_miao_show = true;
  301. this.active_col_index = indexs;
  302. this.active_mark = mark;
  303. this.current_hz = content;
  304. this.current_hz_data = storkes;
  305. },
  306. // 保存描红
  307. saveComplete(flag) {
  308. this.answer.answer_list[0].strokes_content_list[this.active_col_index] = flag;
  309. },
  310. resetHanzi() {
  311. this.$refs.strockRed.resetHanzi();
  312. },
  313. updateColor(color) {
  314. this.writer.updateColor('strokeColor', color);
  315. this.writer.updateColor('drawingColor', color);
  316. },
  317. },
  318. };
  319. </script>
  320. <style lang="scss" scoped>
  321. @use '@/styles/mixin.scss' as *;
  322. .character-preview {
  323. @include preview-base;
  324. .audio-wrapper-nobg {
  325. height: 16px;
  326. :deep .audio-play {
  327. width: 16px;
  328. height: 16px;
  329. color: #000;
  330. background-color: initial;
  331. }
  332. :deep .audio-play.not-url {
  333. color: #a1a1a1;
  334. }
  335. :deep .voice-play {
  336. width: 16px;
  337. height: 16px;
  338. }
  339. }
  340. .main-top {
  341. display: flex;
  342. column-gap: 4px;
  343. align-items: center;
  344. justify-content: center;
  345. }
  346. .pinyin {
  347. font-family: 'League';
  348. font-size: 12px;
  349. font-weight: 500;
  350. color: #000;
  351. }
  352. .words-right {
  353. padding: 8px 4px;
  354. margin: 0;
  355. font-size: 14px;
  356. line-height: 14px;
  357. color: #000;
  358. }
  359. .strock-chinese-box {
  360. display: flex;
  361. flex-wrap: wrap;
  362. margin: 8px 0;
  363. }
  364. .record-box {
  365. padding: 7px 12px;
  366. margin-top: 8px;
  367. background: #f2f3f5;
  368. border-radius: 2px;
  369. }
  370. .strockplay-newWord {
  371. position: relative;
  372. box-sizing: border-box;
  373. flex-shrink: 0;
  374. width: 64px;
  375. height: 64px;
  376. border: 1px solid #e81b1b;
  377. .character-target-bg,
  378. .hanzi-writer-img {
  379. position: absolute;
  380. top: 0;
  381. left: 0;
  382. width: 100%;
  383. height: 100%;
  384. color: #dedede;
  385. }
  386. .hanzi-writer-img {
  387. z-index: 1;
  388. }
  389. }
  390. .border-right-none {
  391. border-right: none;
  392. }
  393. .practiceBox {
  394. position: fixed;
  395. top: 0;
  396. left: 0;
  397. z-index: 101;
  398. box-sizing: border-box;
  399. width: 100%;
  400. height: 100vh;
  401. overflow: hidden;
  402. overflow-y: auto;
  403. background: rgba(0, 0, 0, 19%);
  404. &.practice-box-strock {
  405. display: flex;
  406. align-items: center;
  407. justify-content: center;
  408. padding-top: 0;
  409. }
  410. .miao-box {
  411. position: relative;
  412. width: 332px;
  413. height: 360px;
  414. padding: 30px 16px;
  415. margin: 0 auto;
  416. background: #f3f3f3;
  417. border-radius: 8px;
  418. box-shadow: 0 4px 16px rgba(0, 0, 0, 15%);
  419. .close-icon {
  420. position: absolute;
  421. top: 0;
  422. right: 8px;
  423. z-index: 2;
  424. width: 32px;
  425. height: 32px;
  426. padding: 8px;
  427. cursor: pointer;
  428. }
  429. .strockredBox,
  430. .strockplay-redInner {
  431. width: 300px;
  432. height: 300px;
  433. margin: 0 auto;
  434. }
  435. .strock-red {
  436. position: absolute;
  437. top: 30px;
  438. left: 16px;
  439. &-zindex {
  440. z-index: -1;
  441. }
  442. }
  443. .reset-box {
  444. position: absolute;
  445. right: 22px;
  446. bottom: 22px;
  447. z-index: 100;
  448. display: flex;
  449. align-items: center;
  450. justify-content: center;
  451. width: 11px;
  452. height: 11px;
  453. color: $text-color;
  454. cursor: pointer;
  455. &:hover {
  456. color: #000;
  457. }
  458. }
  459. }
  460. }
  461. }
  462. </style>