CharacterPreview.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. <!-- eslint-disable vue/no-v-html -->
  2. <template>
  3. <div class="character-preview" :style="getAreaStyle()">
  4. <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
  5. <div class="main">
  6. <template v-if="data.property.model === 'write'">
  7. <div
  8. class="item-box"
  9. :class="[!item.is_margin ? 'item-box-write' : '']"
  10. v-for="(item, index) in data.option_list"
  11. :key="index"
  12. >
  13. <div
  14. class="number-box"
  15. :style="{
  16. marginTop: isEnable(data.property.is_enable_pinyin) ? '30px' : '',
  17. }"
  18. >
  19. <span class="number">{{ index + 1 }}</span>
  20. </div>
  21. <div class="pinyin-en" :class="[item.is_example ? 'item-example' : '']">
  22. <div v-if="isEnable(data.property.is_enable_pinyin) && item.is_common_pinyin" class="pinyin">
  23. {{ item.pinyin }}
  24. </div>
  25. <div class="items-flex">
  26. <div class="items" v-for="(items, indexs) in item.content_list" :key="indexs">
  27. <div v-if="isEnable(data.property.is_enable_pinyin) && !item.is_common_pinyin" class="pinyin">
  28. {{ items.pinyin }}
  29. </div>
  30. <div class="items-content">
  31. <template v-if="items && items.type === 'img'">
  32. <el-image
  33. class="items-image"
  34. v-if="items.file_list[0]"
  35. :src="items.file_list[0].file_url"
  36. fit="contain"
  37. ></el-image>
  38. </template>
  39. <template v-else-if="items && items.type === 'lian'">
  40. <span class="items-lian">{{ items.con }}</span>
  41. </template>
  42. <Strockplayredline
  43. v-if="items && items.type === 'hanzi'"
  44. :Book_text="items.con"
  45. :playStorkes="isEnable(data.property.is_enable_play_structure)"
  46. :curItem="
  47. isEnable(data.property.is_enable_high_strokes)
  48. ? data.property.model === 'input'
  49. ? items.high_strokes
  50. : userAnswer[index].item[indexs]
  51. : null
  52. "
  53. :type="data.property.model === 'input' ? 'newWord-template-input' : data.type"
  54. :targetDiv="'newWordTemplate' + items.con + index + indexs"
  55. :hz_json="items.hz_info[0].hzDetail.hz_json"
  56. class="hanzi-storck"
  57. :class="[
  58. !item.is_margin && item.content_list.length > 1 && indexs == 0 ? 'leftBorderRadius' : '',
  59. !item.is_margin && item.content_list.length > 1 && indexs == item.content_list.length - 1
  60. ? 'rightBorderRadius'
  61. : '',
  62. !item.is_margin &&
  63. item.content_list.length > 1 &&
  64. indexs != item.content_list.length - 1 &&
  65. indexs != 0
  66. ? 'NoborderRadius'
  67. : '',
  68. !item.is_margin && item.content_list.length > 1 && indexs != item.content_list.length - 1
  69. ? 'NoborderRight'
  70. : '',
  71. ]"
  72. />
  73. <div
  74. :class="[
  75. 'strockplay-newWord',
  76. !item.is_margin && item.content_list.length > 1 && indexs == 0 ? 'leftBorderRadius' : '',
  77. !item.is_margin && item.content_list.length > 1 && indexs == item.content_list.length - 1
  78. ? 'rightBorderRadius'
  79. : '',
  80. !item.is_margin &&
  81. item.content_list.length > 1 &&
  82. indexs != item.content_list.length - 1 &&
  83. indexs != 0
  84. ? 'NoborderRadius'
  85. : '',
  86. !item.is_margin && item.content_list.length > 1 && indexs != item.content_list.length - 1
  87. ? 'NoborderRight'
  88. : '',
  89. ]"
  90. v-if="items && items.type === 'write'"
  91. @click="
  92. freeWrite(
  93. userAnswer[index][indexs].imgArr ? userAnswer[index][indexs].imgArr : null,
  94. index,
  95. indexs,
  96. )
  97. "
  98. >
  99. <SvgIcon icon-class="hanzi-writer-bg" class="character-target-bg" />
  100. <img
  101. v-if="
  102. !play_status &&
  103. userAnswer[index][indexs].imgArr &&
  104. userAnswer[index][indexs].imgArr.strokes_image
  105. "
  106. class="hanzi-writer-img"
  107. :src="userAnswer[index][indexs].imgArr.strokes_image"
  108. alt=""
  109. />
  110. </div>
  111. </div>
  112. </div>
  113. </div>
  114. <div class="en-common">{{ item.shiyi }}</div>
  115. </div>
  116. </div>
  117. </template>
  118. <template v-else>
  119. <div :class="['words-box']">
  120. <div v-for="(item, index) in data.option_list" :key="index" :class="['words-item']">
  121. <div class="words-top">
  122. <div class="words-left" :style="{}">
  123. <AudioPlay :file-id="item.audio_file_id" theme-color="gray" />
  124. <span class="pinyin">{{ item.pinyin }}</span>
  125. </div>
  126. </div>
  127. <div class="card-box">
  128. <!-- 描红 -->
  129. <template v-for="(items, indexs) in item.content_list">
  130. <Strockplayredline
  131. :key="'miao' + indexs"
  132. :Book_text="items.con"
  133. :playStorkes="isEnable(data.property.is_enable_stroke)"
  134. :curItem="null"
  135. :targetDiv="'newWordTemplate' + items.con + index + indexs"
  136. :hz_json="items.hz_info[0].hzDetail.hz_json"
  137. class="hanzi-storck"
  138. :class="['strock-chinese', 'border-right-none']"
  139. />
  140. </template>
  141. <div
  142. v-for="(itemI, indexI) in item.miao_arr"
  143. :key="indexI + index"
  144. style="display: flex"
  145. @click="miaoStorkes(index, indexI, item.mark, item.content, itemI.strokes)"
  146. >
  147. <Strockplayredlines
  148. v-if="
  149. userAnswer[index].strokes_content_list[indexI] &&
  150. userAnswer[index].strokes_content_list[indexI].flag === 'true'
  151. "
  152. :play-storkes="false"
  153. :book-text="item.content"
  154. :target-div="'write-praT' + Math.random().toString(36).substring(2, 10)"
  155. :book-strokes="itemI.strokes"
  156. :class="['strock-chinese']"
  157. />
  158. <Strockplayredlines
  159. v-else
  160. :play-storkes="false"
  161. :book-text="item.content"
  162. :target-div="'write-praT' + Math.random().toString(36).substring(2, 10)"
  163. :book-strokes="itemI.strokes"
  164. :stroke-color="'#ddd'"
  165. :class="['strock-chinese']"
  166. />
  167. </div>
  168. <!-- 书写 -->
  169. <div v-for="(items, indexs) in item.imgArr" :key="'write' + indexs" class="con-box">
  170. <div
  171. :class="['strockplay-newWord']"
  172. @click="
  173. freeWrite(
  174. userAnswer[index].strokes_content_list[indexs].imgArr
  175. ? userAnswer[index].strokes_content_list[indexs].imgArr
  176. : null,
  177. index,
  178. indexs,
  179. )
  180. "
  181. >
  182. <SvgIcon icon-class="hanzi-writer-bg" class="character-target-bg" />
  183. <img
  184. v-if="
  185. !play_status &&
  186. items &&
  187. userAnswer[index].strokes_content_list[indexs].imgArr &&
  188. userAnswer[index].strokes_content_list[indexs].imgArr.strokes_image
  189. "
  190. class="hanzi-writer-img"
  191. :src="userAnswer[index].strokes_content_list[indexs].imgArr.strokes_image"
  192. alt=""
  193. />
  194. </div>
  195. </div>
  196. </div>
  197. <div class="words-bottom" v-if="item.shiyi">
  198. {{ item.shiyi }}
  199. </div>
  200. </div>
  201. </div>
  202. </template>
  203. <div v-if="if_free_show" class="practiceBox practice-box-strock">
  204. <FreewriteLettle
  205. ref="freePaint"
  206. :current-tree-i-d="'1234456'"
  207. :current-hz="current_hz"
  208. :curren-hz-data="current_hz_data"
  209. :row-index="active_index"
  210. :col-index="active_col_index"
  211. :disabled="disabled"
  212. :show-play="isEnable(data.property.is_enable_play_back)"
  213. @closeIfFreeShow="closeIfFreeShow"
  214. @changePraShow="changePraShow"
  215. @changeCurQue="changeCurQue"
  216. @deleteWriteRecord="deleteWriteRecord"
  217. />
  218. </div>
  219. <div v-if="if_miao_show" class="practiceBox practice-box-strock">
  220. <div class="miao-box">
  221. <i class="el-icon-close close-icon" @click="if_miao_show = false"></i>
  222. <Strockplayredlines
  223. v-if="
  224. (userAnswer[active_index].strokes_content_list[active_col_index].flag &&
  225. userAnswer[active_index].strokes_content_list[active_col_index].flag === 'true') ||
  226. disabled
  227. "
  228. :play-storkes="false"
  229. :book-text="current_hz"
  230. :target-div="'write-praT' + current_hz + active_col_index + Math.random().toString(36).substring(2, 10)"
  231. :book-strokes="current_hz_data"
  232. :stroke-color="
  233. disabled &&
  234. (!userAnswer[active_index].strokes_content_list[active_col_index].flag ||
  235. (userAnswer[active_index].strokes_content_list[active_col_index].flag &&
  236. userAnswer[active_index].strokes_content_list[active_col_index].flag === 'false'))
  237. ? '#ddd'
  238. : ''
  239. "
  240. />
  241. <Strockred
  242. ref="strockRed"
  243. :class="[
  244. 'strock-red',
  245. userAnswer[active_index].strokes_content_list[active_col_index].flag &&
  246. userAnswer[active_index].strokes_content_list[active_col_index].flag === 'true'
  247. ? 'strock-red-zindex'
  248. : '',
  249. ]"
  250. :book-text="current_hz"
  251. :hanzi-color="hanzi_color"
  252. :target-div="'write-praT' + current_hz + active_col_index + Math.random().toString(36).substring(2, 10)"
  253. :book-strokes="current_hz_data"
  254. :is-answer.sync="userAnswer[active_index].strokes_content_list[active_col_index].flag"
  255. :show-error-tip="isEnable(data.property.is_enable_error)"
  256. />
  257. <div v-if="!disabled" :class="['reset-box']" @click="resetHanzi">
  258. <SvgIcon icon-class="reset" class="reset-btn" />
  259. </div>
  260. </div>
  261. </div>
  262. </div>
  263. </div>
  264. </template>
  265. <script>
  266. import { getCharacterData } from '@/views/book/courseware/data/character';
  267. import PreviewMixin from '../common/PreviewMixin';
  268. import AudioPlay from '../character_base/components/AudioPlay.vue';
  269. import Strockplayredline from '../newWord_template/components/Strockplayredline.vue';
  270. import Strockplayredlines from '../character_base/components/Strockplayredline.vue';
  271. import Strockred from '../character_base/components/Strockred.vue';
  272. import FreewriteLettle from '../character_base/components/FreewriteLettle.vue';
  273. export default {
  274. name: 'CharacterPreview',
  275. components: {
  276. AudioPlay,
  277. Strockplayredline,
  278. Strockred,
  279. FreewriteLettle,
  280. Strockplayredlines,
  281. },
  282. mixins: [PreviewMixin],
  283. data() {
  284. return {
  285. data: getCharacterData(),
  286. userAnswer: [],
  287. hanzi_color: '#404040', // 描红汉字底色
  288. writer_number_yuan: 15,
  289. writer_number: null, // 书写个数
  290. miao_number: null, // 描红个数
  291. if_free_show: false,
  292. free_img: [],
  293. active_index: null,
  294. active_col_index: null,
  295. current_hz: '', // 当前汉字
  296. current_hz_data: null, // 当前汉字数据
  297. play_status: false, // 播放状态
  298. active_mark: '',
  299. option_list: [],
  300. show_preview: false,
  301. miao_arr: [],
  302. if_miao_show: false, // 描红模块
  303. };
  304. },
  305. computed: {},
  306. watch: {
  307. 'data.option_list': {
  308. handler(val) {
  309. if (val) {
  310. this.handleData();
  311. }
  312. },
  313. deep: true,
  314. immediate: true,
  315. },
  316. },
  317. created() {},
  318. methods: {
  319. handleData() {
  320. console.log(this.data.option_list);
  321. let answer_list = [];
  322. this.data.option_list.forEach((item, index) => {
  323. if (this.data.property.model === 'write') {
  324. let arr = [];
  325. item.content_list.forEach((items) => {
  326. let obj = null;
  327. if (items.type === 'write') {
  328. obj = {
  329. imgArr: null,
  330. };
  331. }
  332. arr.push(obj);
  333. });
  334. answer_list.push(arr);
  335. } else {
  336. let miao_arr = [];
  337. let arr = [];
  338. if (item.content.trim()) {
  339. for (let i = 0; i < this.data.property.write_number; i++) {
  340. item.content_list.forEach((items) => {
  341. arr.push({ imgArr: null, flag: null });
  342. });
  343. }
  344. for (let i = 0; i < this.data.property.miao_number; i++) {
  345. item.content_list.forEach((items) => {
  346. miao_arr.push({
  347. strokes: items && items.hz_info && items.hz_info[0] ? items.hz_info[0].hzDetail.hz_json : null,
  348. length: item.content_list.length,
  349. });
  350. });
  351. }
  352. item.imgArr = arr;
  353. item.miao_arr = miao_arr;
  354. // if (this.isJudgingRightWrong) {
  355. // item.imgArr = this.userAnswer.find(
  356. // (items) => items.mark === item.mark,
  357. // )?.strokes_content_list;
  358. // }
  359. }
  360. let obj = {
  361. // mark: item.mark,
  362. strokes_content_list: arr,
  363. miao_arr: miao_arr,
  364. };
  365. // if (!this.isJudgingRightWrong) {
  366. // this.userAnswer.push(obj);
  367. // }
  368. answer_list.push(obj);
  369. }
  370. });
  371. this.userAnswer = answer_list;
  372. },
  373. changePraShow() {
  374. this.if_free_show = false;
  375. },
  376. closeIfFreeShow(data, rowIndex, colIndex, mark) {
  377. // this.option_list[rowIndex].imgArr[colIndex] = JSON.stringify(data);
  378. this.if_free_show = false;
  379. this.freeWrite(data, rowIndex, colIndex);
  380. this.$forceUpdate();
  381. },
  382. freeWrite(imgUrl, index, indexs) {
  383. this.if_free_show = true;
  384. this.active_index = index;
  385. this.active_col_index = indexs;
  386. this.current_hz = this.data.option_list[index].content_list[indexs].con;
  387. this.current_hz_data = imgUrl;
  388. },
  389. // 删除记录
  390. deleteWriteRecord(rowIndex, colIndex) {
  391. this.$set(this.option_list[rowIndex].imgArr, colIndex, JSON.stringify({}));
  392. this.changeCurQue(null, rowIndex, colIndex);
  393. this.current_hz_data = null;
  394. this.active_mark = '';
  395. this.$forceUpdate();
  396. },
  397. changeCurQue(answer, rowIndex, colIndex) {
  398. if (answer) {
  399. if (this.data.property.model === 'write') {
  400. this.userAnswer[rowIndex][colIndex].imgArr = answer;
  401. } else {
  402. this.userAnswer[rowIndex].strokes_content_list[colIndex].imgArr = answer;
  403. }
  404. this.$forceUpdate();
  405. }
  406. },
  407. // 点击描红模块
  408. miaoStorkes(index, indexs, mark, content, storkes) {
  409. this.if_miao_show = true;
  410. this.active_index = index;
  411. this.active_col_index = indexs;
  412. this.current_hz = content;
  413. this.current_hz_data = storkes;
  414. },
  415. // 保存描红
  416. saveComplete(flag) {
  417. this.answer.answer_list[this.active_index].strokes_content_list[this.active_col_index] = flag;
  418. },
  419. resetHanzi() {
  420. this.$refs.strockRed.resetHanzi();
  421. },
  422. updateColor(color) {
  423. this.writer.updateColor('strokeColor', color);
  424. this.writer.updateColor('drawingColor', color);
  425. },
  426. },
  427. };
  428. </script>
  429. <style lang="scss" scoped>
  430. @use '@/styles/mixin.scss' as *;
  431. .character-preview {
  432. @include preview-base;
  433. display: block;
  434. .main {
  435. display: flex;
  436. flex-wrap: wrap;
  437. column-gap: 16px;
  438. justify-content: start;
  439. }
  440. .content-box {
  441. width: max-content;
  442. }
  443. .audio-wrapper-nobg {
  444. height: 16px;
  445. :deep .audio-play {
  446. width: 16px;
  447. height: 16px;
  448. color: #000;
  449. background-color: initial;
  450. }
  451. :deep .audio-play.not-url {
  452. color: #a1a1a1;
  453. }
  454. :deep .voice-play {
  455. width: 16px;
  456. height: 16px;
  457. }
  458. }
  459. .main-top {
  460. display: flex;
  461. column-gap: 4px;
  462. align-items: center;
  463. justify-content: center;
  464. }
  465. .pinyin {
  466. font-family: 'League';
  467. font-size: 12px;
  468. font-weight: 500;
  469. color: #000;
  470. }
  471. .strock-chinese-box {
  472. display: flex;
  473. flex-wrap: wrap;
  474. margin: 8px 0;
  475. }
  476. .strockplay-newWord {
  477. position: relative;
  478. box-sizing: border-box;
  479. flex-shrink: 0;
  480. width: 80px;
  481. height: 80px;
  482. border: 2px solid #346cda;
  483. border-radius: 8px;
  484. .character-target-bg,
  485. .hanzi-writer-img {
  486. position: absolute;
  487. top: 0;
  488. left: 0;
  489. width: 100%;
  490. height: 100%;
  491. color: #dedede;
  492. }
  493. .hanzi-writer-img {
  494. z-index: 1;
  495. }
  496. }
  497. .border-right-none {
  498. border-right: none;
  499. }
  500. .item-box {
  501. display: flex;
  502. gap: 5px;
  503. }
  504. .number-box {
  505. display: flex;
  506. align-items: center;
  507. height: 80px;
  508. margin-right: 16px;
  509. .number {
  510. display: block;
  511. width: 24px;
  512. height: 24px;
  513. font-family: 'arial';
  514. font-size: 14px;
  515. font-weight: bold;
  516. line-height: 24px;
  517. color: #fff;
  518. text-align: center;
  519. background: #346cda;
  520. border-radius: 100%;
  521. }
  522. }
  523. .items {
  524. font-size: 0;
  525. }
  526. .pinyin {
  527. height: 30px;
  528. min-height: 16px;
  529. font-family: 'League';
  530. font-size: 20px;
  531. font-weight: 400;
  532. color: rgba(0, 0, 0, 50%);
  533. text-align: center;
  534. }
  535. .items-image,
  536. .hanzi-storck {
  537. width: 80px;
  538. height: 80px;
  539. overflow: hidden;
  540. border: 2px solid #346cda;
  541. border-radius: 8px;
  542. }
  543. .items-lian {
  544. display: block;
  545. height: 80px;
  546. padding: 0 10px;
  547. font-size: 34px;
  548. line-height: 80px;
  549. color: #346cda;
  550. }
  551. .items-flex {
  552. display: flex;
  553. gap: 5px;
  554. }
  555. .item-box-write {
  556. .items-flex {
  557. gap: 0;
  558. }
  559. .NoborderRadius {
  560. border-radius: 0;
  561. }
  562. .rightBorderRadius {
  563. border-radius: 0;
  564. border-top-right-radius: 8px;
  565. border-bottom-right-radius: 8px;
  566. }
  567. .leftBorderRadius {
  568. border-radius: 0;
  569. border-top-left-radius: 8px;
  570. border-bottom-left-radius: 8px;
  571. }
  572. .NoborderRight {
  573. border-right: none;
  574. }
  575. }
  576. .pinyin-common {
  577. margin-bottom: 5px;
  578. :deep .edit-div {
  579. font-family: 'League';
  580. }
  581. }
  582. .en-common {
  583. margin-top: 8px;
  584. // text-align: center;
  585. word-break: break-word;
  586. }
  587. .practiceBox {
  588. position: fixed;
  589. top: 0;
  590. left: 0;
  591. z-index: 101;
  592. box-sizing: border-box;
  593. width: 100%;
  594. height: 100vh;
  595. overflow: hidden;
  596. overflow-y: auto;
  597. background: rgba(0, 0, 0, 19%);
  598. &.practice-box-strock {
  599. display: flex;
  600. align-items: center;
  601. justify-content: center;
  602. padding-top: 0;
  603. }
  604. .miao-box {
  605. position: relative;
  606. width: 332px;
  607. height: 360px;
  608. padding: 30px 16px;
  609. margin: 0 auto;
  610. background: #f3f3f3;
  611. border-radius: 8px;
  612. box-shadow: 0 4px 16px rgba(0, 0, 0, 15%);
  613. .close-icon {
  614. position: absolute;
  615. top: 0;
  616. right: 8px;
  617. z-index: 2;
  618. width: 32px;
  619. height: 32px;
  620. padding: 8px;
  621. cursor: pointer;
  622. }
  623. .strockredBox,
  624. .strockplay-redInner {
  625. width: 300px;
  626. height: 300px;
  627. margin: 0 auto;
  628. }
  629. .strock-red {
  630. position: absolute;
  631. top: 30px;
  632. left: 16px;
  633. &-zindex {
  634. z-index: -1;
  635. }
  636. }
  637. .reset-box {
  638. position: absolute;
  639. right: 22px;
  640. bottom: 22px;
  641. z-index: 100;
  642. display: flex;
  643. align-items: center;
  644. justify-content: center;
  645. width: 11px;
  646. height: 11px;
  647. color: $text-color;
  648. cursor: pointer;
  649. &:hover {
  650. color: #000;
  651. }
  652. }
  653. }
  654. }
  655. .card-box {
  656. display: flex;
  657. flex-flow: wrap;
  658. gap: 5px;
  659. }
  660. .words-left {
  661. display: flex;
  662. gap: 10px;
  663. align-items: center;
  664. margin-bottom: 5px;
  665. }
  666. :deep .strockplay-redInner {
  667. width: 80px;
  668. height: 80px;
  669. border: 2px solid #346cda !important;
  670. border-radius: 8px;
  671. }
  672. .words-item {
  673. margin-bottom: 10px;
  674. }
  675. .words-bottom {
  676. margin-top: 3px;
  677. word-break: break-word;
  678. }
  679. }
  680. </style>