ChinesePreview.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. <!-- eslint-disable vue/no-v-html -->
  2. <template>
  3. <div v-if="show_preview" class="chinese-preview">
  4. <div class="stem">
  5. <span class="question-number">{{ data.property.question_number }}.</span>
  6. <span v-html="sanitizeHTML(data.stem)"></span>
  7. </div>
  8. <div v-if="isEnable(data.property.is_enable_description)" class="description">{{ data.description }}</div>
  9. <!-- 笔画学习 -->
  10. <div :class="['words-box', 'words-box-' + data.property.learn_type]">
  11. <div v-for="(item, index) in option_list" :key="index" :class="['words-item']">
  12. <template
  13. v-if="item.content && item.content.trim() && item.hz_strokes_list[0] && item.hz_strokes_list[0].strokes"
  14. >
  15. <div
  16. v-if="data.property.learn_type !== 'dictation'"
  17. class="words-top"
  18. :style="{
  19. width:
  20. 64 *
  21. ((writer_number + 1) * item.hz_strokes_list.length > writer_number_yuan
  22. ? writer_number_yuan
  23. : (writer_number + 1) * item.hz_strokes_list.length) +
  24. 'px',
  25. }"
  26. >
  27. <div class="words-left" :style="{ width: '64' * item.hz_strokes_list.length + 'px' }">
  28. <AudioPlay :file-id="item.audio_file_id" theme-color="gray" />
  29. <span class="pinyin">{{ item.pinyin }}</span>
  30. </div>
  31. <p class="words-right">{{ item.definition }}</p>
  32. <p class="words-right">{{ item.collocation }}</p>
  33. </div>
  34. <div v-if="data.property.learn_type === 'paint'" class="card-box">
  35. <!-- 描红 -->
  36. <template v-for="(items, indexs) in item.hz_strokes_list">
  37. <Strockplayredline
  38. :key="indexs"
  39. :play-storkes="true"
  40. :book-text="item.content"
  41. :target-div="'pre' + item.content + index + indexs"
  42. :book-strokes="items.strokes"
  43. :class="['strock-chinese', 'border-right-none']"
  44. />
  45. </template>
  46. <div v-for="itemI in writer_number" :key="itemI + data.property.learn_type + index" style="display: flex">
  47. <Strockred
  48. v-for="(items, indexs) in item.hz_strokes_list"
  49. :key="indexs"
  50. :book-text="item.content"
  51. :hanzi-color="hanzi_color"
  52. :reset="true"
  53. :target-div="'write-praT' + item.content + itemI + Math.random().toString(36).substring(2, 10)"
  54. :book-strokes="items.strokes"
  55. :class="[
  56. 'strock-chinese',
  57. ((item.hz_strokes_list.length * (itemI + 1) + indexs - 1) % writer_number_yuan !== 0 &&
  58. itemI !== writer_number) ||
  59. (itemI === writer_number && indexs !== item.hz_strokes_list.length - 1)
  60. ? 'border-right-none'
  61. : '',
  62. ]"
  63. />
  64. </div>
  65. </div>
  66. <div v-else-if="data.property.learn_type === 'write'" class="card-box">
  67. <!-- 书写 -->
  68. <template v-for="(items, indexs) in item.hz_strokes_list">
  69. <Strockplayredline
  70. :key="'write' + indexs"
  71. :play-storkes="true"
  72. :book-text="item.content"
  73. :target-div="'pre' + item.content + index + indexs"
  74. :book-strokes="items.strokes"
  75. :class="['strock-chinese', indexs !== item.hz_strokes_list.length - 1 ? 'border-right-none' : '']"
  76. />
  77. </template>
  78. <div v-for="(items, indexs) in item.imgArr" :key="indexs" class="con-box">
  79. <div
  80. :class="['strockplay-newWord', (indexs + 1) % writer_number_yuan !== 0 ? 'border-left-none' : '']"
  81. @click="freeWrite(items ? JSON.parse(items) : null, index, indexs, item.mark)"
  82. >
  83. <SvgIcon icon-class="hanzi-writer-bg" class="character-target-bg" />
  84. <img
  85. v-if="!play_status && items && JSON.parse(items).strokes_image"
  86. class="hanzi-writer-img"
  87. :src="JSON.parse(items).strokes_image"
  88. alt=""
  89. />
  90. </div>
  91. </div>
  92. </div>
  93. </template>
  94. <div v-if="data.property.learn_type === 'dictation'" class="card-box">
  95. <div class="words-info">
  96. <span>{{ computeOptionMethods[data.option_number_show_mode](index) }} </span>
  97. <span class="pinyin">{{ item.pinyin }}</span>
  98. <AudioPlay :file-id="item.audio_file_id" theme-color="white" />
  99. </div>
  100. <div class="words-dic-box">
  101. <div v-for="(itemc, indexc) in item.imgArr" :key="indexc" class="words-dic-item">
  102. <span class="pinyin">{{ item.pinyin_arr[indexc].pinyin_item }}</span>
  103. <div
  104. :class="['strockplay-newWord']"
  105. @click="freeWrite(itemc ? JSON.parse(itemc) : itemc, index, indexc, item.mark)"
  106. >
  107. <SvgIcon icon-class="hanzi-writer-bg" class="character-target-bg" />
  108. <img
  109. v-if="!play_status && itemc && JSON.parse(itemc).strokes_image"
  110. class="hanzi-writer-img"
  111. :src="JSON.parse(itemc).strokes_image"
  112. alt=""
  113. />
  114. </div>
  115. </div>
  116. </div>
  117. </div>
  118. </div>
  119. </div>
  120. <div v-if="if_free_show" class="practiceBox practice-box-strock">
  121. <FreewriteLettle
  122. ref="freePaint"
  123. :current-tree-i-d="'1234456'"
  124. :current-hz="current_hz"
  125. :curren-hz-data="current_hz_data"
  126. :row-index="active_index"
  127. :col-index="active_col_index"
  128. :disabled="disabled"
  129. @closeIfFreeShow="closeIfFreeShow"
  130. @changePraShow="changePraShow"
  131. @changeCurQue="changeCurQue"
  132. @deleteWriteRecord="deleteWriteRecord"
  133. />
  134. </div>
  135. </div>
  136. </template>
  137. <script>
  138. import { computeOptionMethods } from '@/views/exercise_questions/data/common';
  139. import PreviewMixin from './components/PreviewMixin';
  140. import Strockplayredline from './components/common/Strockplayredline.vue';
  141. import Strockred from './components/common/Strockred.vue';
  142. import FreewriteLettle from './components/common/FreewriteLettle.vue';
  143. export default {
  144. name: 'ChinesePreview',
  145. components: {
  146. Strockplayredline,
  147. Strockred,
  148. FreewriteLettle,
  149. },
  150. mixins: [PreviewMixin],
  151. data() {
  152. return {
  153. computeOptionMethods,
  154. hanzi_color: '#404040', // 描红汉字底色
  155. writer_number_yuan: 19,
  156. writer_number: null, // 书写个数
  157. if_free_show: false,
  158. free_img: [],
  159. active_index: null,
  160. active_col_index: null,
  161. current_hz: '', // 当前汉字
  162. current_hz_data: null, // 当前汉字数据
  163. play_status: false, // 播放状态
  164. hz_data: [
  165. '你',
  166. '最',
  167. '近',
  168. '怎',
  169. '么',
  170. '样',
  171. '我',
  172. '很',
  173. '好',
  174. '再',
  175. '见',
  176. '吃',
  177. '饭',
  178. '天',
  179. '启',
  180. '扫',
  181. '描',
  182. '以',
  183. '平',
  184. '太',
  185. '效',
  186. '国',
  187. '是',
  188. '称',
  189. '需',
  190. '值',
  191. '复',
  192. '包',
  193. '头',
  194. '条',
  195. '够',
  196. '关',
  197. '放',
  198. '发',
  199. '补',
  200. '快',
  201. '素',
  202. ],
  203. active_mark: '',
  204. option_list: [],
  205. show_preview: false,
  206. };
  207. },
  208. watch: {
  209. 'data.property.learn_type': {
  210. handler(val, oldVal) {
  211. if (val !== oldVal) {
  212. this.handleData();
  213. }
  214. },
  215. deep: true,
  216. immediate: true,
  217. },
  218. data: {
  219. handler(val) {
  220. if (!val || this.data.type !== 'chinese') return;
  221. this.handleData();
  222. },
  223. deep: true,
  224. immediate: true,
  225. },
  226. isJudgingRightWrong: {
  227. handler(val) {
  228. if (!val) return;
  229. this.handleData();
  230. },
  231. immediate: true,
  232. },
  233. },
  234. created() {
  235. // this.handleData();
  236. },
  237. mounted() {},
  238. methods: {
  239. // 初始化数据
  240. handleData() {
  241. if (!this.isJudgingRightWrong) {
  242. this.answer.answer_list = [];
  243. }
  244. this.show_preview = false;
  245. this.writer_number = this.data.property.tian_number ? Number(this.data.property.tian_number) : 8;
  246. let option_list = JSON.parse(JSON.stringify(this.data)).option_list;
  247. option_list.forEach((item, index) => {
  248. let arr = [];
  249. if (this.data.property.learn_type === 'dictation') {
  250. item.pinyin_arr = [];
  251. let pinyin_arr = item.pinyin.trim().split(' ');
  252. pinyin_arr.forEach((itemp) => {
  253. let obj = {
  254. pinyin_item: itemp,
  255. };
  256. arr.push(null);
  257. item.pinyin_arr.push(obj);
  258. });
  259. if (this.isJudgingRightWrong) {
  260. item.imgArr = this.answer.answer_list[index].strokes_content_list;
  261. } else {
  262. item.imgArr = arr;
  263. }
  264. } else if (item.content.trim()) {
  265. for (let i = 0; i < this.writer_number; i++) {
  266. item.hz_strokes_list.forEach(() => {
  267. arr.push(null);
  268. });
  269. }
  270. if (this.isJudgingRightWrong) {
  271. item.imgArr = this.answer.answer_list[index].strokes_content_list;
  272. } else {
  273. item.imgArr = arr;
  274. }
  275. }
  276. let obj = {
  277. mark: item.mark,
  278. strokes_content_list: arr,
  279. };
  280. if (!this.isJudgingRightWrong) {
  281. this.answer.answer_list.push(obj);
  282. }
  283. });
  284. this.option_list = option_list;
  285. this.show_preview = true;
  286. if (document.getElementsByClassName('preview-content').length > 0) {
  287. this.writer_number_yuan = Math.floor(
  288. (document.getElementsByClassName('preview-content')[0].clientWidth - 128) / 64,
  289. );
  290. }
  291. },
  292. changePraShow() {
  293. this.if_free_show = false;
  294. },
  295. closeIfFreeShow(data, rowIndex, colIndex, mark) {
  296. this.option_list[rowIndex].imgArr[colIndex] = JSON.stringify(data);
  297. this.if_free_show = false;
  298. this.freeWrite(data, rowIndex, colIndex, mark);
  299. this.$forceUpdate();
  300. },
  301. freeWrite(imgUrl, index, indexs, mark) {
  302. this.if_free_show = true;
  303. this.active_index = index;
  304. this.active_col_index = indexs;
  305. this.active_mark = mark;
  306. if (this.data.property.learn_type === 'dictation') {
  307. this.current_hz = this.hz_data[index];
  308. } else {
  309. this.current_hz = this.option_list[index].content;
  310. }
  311. this.current_hz_data = imgUrl;
  312. },
  313. // 删除记录
  314. deleteWriteRecord(rowIndex, colIndex) {
  315. this.$set(this.option_list[rowIndex].imgArr, colIndex, JSON.stringify({}));
  316. this.changeCurQue(null, colIndex, this.active_mark);
  317. this.current_hz_data = null;
  318. this.active_mark = '';
  319. this.$forceUpdate();
  320. },
  321. changeCurQue(answer, colIndex, mark) {
  322. if (answer) {
  323. let write_model = [];
  324. this.answer.answer_list.forEach((itema) => {
  325. if (itema.mark === mark) {
  326. write_model = itema.strokes_content_list;
  327. }
  328. });
  329. write_model[colIndex] = JSON.stringify(answer);
  330. }
  331. },
  332. },
  333. };
  334. </script>
  335. <style lang="scss" scoped>
  336. @use '@/styles/mixin.scss' as *;
  337. .chinese-preview {
  338. @include preview;
  339. .words-box {
  340. .words-item {
  341. // display: flex;
  342. // flex-wrap: wrap;
  343. min-width: 64px;
  344. margin-bottom: 24px;
  345. }
  346. .words-top {
  347. display: flex;
  348. width: 100%;
  349. min-height: 30px;
  350. border: 1px solid #e81b1b;
  351. .words-left {
  352. box-sizing: border-box;
  353. display: flex;
  354. column-gap: 4px;
  355. align-items: center;
  356. justify-content: center;
  357. width: 64px;
  358. margin-right: 12px;
  359. border-right: 1px solid #e81b1b;
  360. }
  361. .words-right {
  362. padding: 8px 4px;
  363. margin: 0;
  364. font-size: 14px;
  365. line-height: 14px;
  366. color: #000;
  367. }
  368. }
  369. .audio-wrapper {
  370. height: 16px;
  371. :deep .audio-play {
  372. width: 16px;
  373. height: 16px;
  374. color: #000;
  375. background-color: initial;
  376. }
  377. :deep .audio-play.not-url {
  378. color: #a1a1a1;
  379. }
  380. :deep .voice-play {
  381. width: 16px;
  382. height: 16px;
  383. }
  384. }
  385. .words-info {
  386. display: flex;
  387. column-gap: 4px;
  388. align-items: center;
  389. justify-content: center;
  390. width: max-content;
  391. padding: 5px 16px;
  392. margin: 0 auto 8px;
  393. font-size: 14px;
  394. line-height: 22px;
  395. color: #fff;
  396. background: #165dff;
  397. border-radius: 20px;
  398. .pinyin {
  399. font-size: 14px;
  400. color: #fff;
  401. }
  402. .audio-wrapper {
  403. :deep .audio-play {
  404. color: #fff;
  405. }
  406. :deep .audio-play.not-url {
  407. color: #a1a1a1;
  408. }
  409. }
  410. }
  411. .card-box {
  412. display: flex;
  413. flex-wrap: wrap;
  414. }
  415. .pinyin {
  416. font-family: 'League';
  417. font-size: 12px;
  418. font-weight: 500;
  419. color: #000;
  420. }
  421. .strock-chinese {
  422. border: 1px solid #e81b1b;
  423. border-top: none;
  424. }
  425. .strockplay-newWord {
  426. position: relative;
  427. box-sizing: border-box;
  428. flex-shrink: 0;
  429. width: 64px;
  430. height: 64px;
  431. border: 1px solid #e81b1b;
  432. border-top: none;
  433. .character-target-bg,
  434. .hanzi-writer-img {
  435. position: absolute;
  436. top: 0;
  437. left: 0;
  438. width: 100%;
  439. height: 100%;
  440. color: #dedede;
  441. }
  442. .hanzi-writer-img {
  443. z-index: 1;
  444. }
  445. }
  446. .border-left-none {
  447. border-left: none;
  448. }
  449. .border-right-none {
  450. border-right: none;
  451. }
  452. &-learn {
  453. display: flex;
  454. column-gap: 24px;
  455. .words-item {
  456. display: block;
  457. }
  458. }
  459. }
  460. .words-box-dictation {
  461. display: flex;
  462. flex-wrap: wrap;
  463. column-gap: 24px;
  464. .card-box {
  465. display: block;
  466. }
  467. .words-dic-box {
  468. display: flex;
  469. column-gap: 6px;
  470. width: max-content;
  471. margin: 0 auto;
  472. }
  473. .words-dic-item {
  474. text-align: center;
  475. .pinyin {
  476. line-height: 30px;
  477. }
  478. }
  479. }
  480. .words-dic-item {
  481. .strockplay-newWord {
  482. border-top: 1px solid #e81b1b;
  483. }
  484. }
  485. .practiceBox {
  486. position: fixed;
  487. top: 0;
  488. left: 0;
  489. z-index: 101;
  490. box-sizing: border-box;
  491. width: 100%;
  492. height: 100vh;
  493. overflow: hidden;
  494. overflow-y: auto;
  495. background: rgba(0, 0, 0, 19%);
  496. &.practice-box-strock {
  497. display: flex;
  498. align-items: center;
  499. justify-content: center;
  500. padding-top: 0;
  501. }
  502. }
  503. }
  504. </style>