CharacterStructurePreview.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. <!-- eslint-disable vue/no-v-html -->
  2. <template>
  3. <div class="structure-preview" :style="[getAreaStyle(), getComponentStyle()]">
  4. <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
  5. <div class="main">
  6. <div
  7. class="option"
  8. :style="{
  9. background: data.unified_attrib && data.unified_attrib.assist_color ? data.unified_attrib.assist_color : '',
  10. }"
  11. >
  12. <draggable
  13. v-model="SortArr"
  14. animation="300"
  15. group="site"
  16. class="content-box"
  17. :disabled="disabled"
  18. @start="onStart($event)"
  19. @end="onEnd($event)"
  20. @choose="choose($event)"
  21. >
  22. <transition-group>
  23. <div
  24. v-for="(item, i) in data.structure_select_list"
  25. :id="item.id"
  26. :key="'op' + i"
  27. class="option_one"
  28. :class="['option_one-' + data.property.frame_size]"
  29. :style="{
  30. background:
  31. data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '',
  32. }"
  33. >
  34. <img
  35. :src="item.type === 'local' ? require('@/assets/structure/' + item.value) : item.value"
  36. class="small-img"
  37. />
  38. </div>
  39. </transition-group>
  40. </draggable>
  41. </div>
  42. <div class="one-box">
  43. <div
  44. v-for="(items, row) in data.option_list"
  45. :key="'row' + row"
  46. class="one"
  47. :class="[!items.pinyin ? 'one_nopy' : '']"
  48. :style="{ marginRight: (row + 1) % 3 == 0 ? '' : '16px' }"
  49. >
  50. <div
  51. class="number"
  52. :style="{
  53. background: data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '',
  54. }"
  55. >
  56. {{ row + 1 }}
  57. </div>
  58. <div class="hzpinyin">
  59. <div
  60. v-if="isEnable(data.property.view_pinyin)"
  61. class="pinyin"
  62. :style="{
  63. fontSize: data.unified_attrib && data.unified_attrib.pinyin_size ? data.unified_attrib.pinyin_size : '',
  64. }"
  65. >
  66. {{ items.pinyin }}
  67. </div>
  68. <template v-if="items.hz_info.length > 0">
  69. <!-- @click="writeWord(conItem, items.pinyin)" -->
  70. <div
  71. class="strockplay-newWord"
  72. :class="['frame-size-' + data.property.frame_size]"
  73. :style="{
  74. borderColor:
  75. data.unified_attrib && data.unified_attrib.topic_color
  76. ? data.unified_attrib.topic_color
  77. : '#346CDA',
  78. }"
  79. >
  80. <Strockplay
  81. class-name="adult-strockplay"
  82. :Book_text="items.hz_info[0].con"
  83. :play-storkes="true"
  84. :stroke-play-color="
  85. data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '#346CDA'
  86. "
  87. :stroke-color="'#000000'"
  88. :paly-width="'18px'"
  89. :BoxbgType="'0'"
  90. :cur-item="items.hz_info[0].hzDetail.hz_json"
  91. :target-div="'writeTops-item-' + '-' + items.hz_info[0].con + row"
  92. class="writeTop-item"
  93. :style="{
  94. borderColor:
  95. data.unified_attrib && data.unified_attrib.topic_color
  96. ? data.unified_attrib.topic_color
  97. : '#346CDA',
  98. }"
  99. />
  100. </div>
  101. </template>
  102. </div>
  103. <div class="image">
  104. <img src="@/assets/drag-arrows.png" alt="" />
  105. </div>
  106. <div class="answer" :class="['answer-' + data.property.frame_size]">
  107. <!-- :disabled="TaskModel == 'ANSWER' ? true : items.is_example ? true : false" -->
  108. <draggable
  109. v-model="answer.answer_list[row].answer_list"
  110. animation="300"
  111. group="site"
  112. :disabled="items.is_example"
  113. class="content-box content-boxs"
  114. :class="['content-boxs-' + data.property.frame_size]"
  115. :move="onMove"
  116. >
  117. <transition-group>
  118. <div
  119. v-for="(answer, i) in answer.answer_list[row].answer_list"
  120. :key="'op' + i"
  121. class="option_one"
  122. :class="[
  123. items.is_example ? 'option_one_example' : classNameJudge(items, row),
  124. 'option_one-' + data.property.frame_size,
  125. ]"
  126. :index="'form' + i"
  127. :style="{
  128. borderColor:
  129. data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '',
  130. }"
  131. >
  132. <img
  133. v-if="answer.img_url || answer.value"
  134. :src="
  135. answer.img_url
  136. ? answer.img_url
  137. : answer.type === 'local'
  138. ? require('@/assets/structure/' + answer.value)
  139. : answer.value
  140. "
  141. alt=""
  142. />
  143. </div>
  144. <div
  145. v-if="answer.answer_list[row].answer_list.length == 0"
  146. :key="row"
  147. class="option_one"
  148. :class="[
  149. isJudgingRightWrong && items.answer ? 'wrong' : '',
  150. 'option_one-' + data.property.frame_size,
  151. ]"
  152. :style="{
  153. borderColor:
  154. data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '',
  155. }"
  156. ></div>
  157. </transition-group>
  158. </draggable>
  159. </div>
  160. </div>
  161. </div>
  162. <PreviewOperation @showAnswerAnalysis="showAnswerAnalysis" @judgeCorrect="judgeCorrect" @retry="retry" />
  163. <AnswerCorrect
  164. :answer-correct="data?.answer_correct"
  165. :visible.sync="visibleAnswerCorrect"
  166. :is-check-correct="isCheckCorrect"
  167. @closeAnswerCorrect="closeAnswerCorrect"
  168. />
  169. <AnswerAnalysis
  170. :visible.sync="visibleAnswerAnalysis"
  171. :answer-list="data.answer_list"
  172. :analysis-list="data.analysis_list"
  173. @closeAnswerAnalysis="closeAnswerAnalysis"
  174. >
  175. <div slot="right-answer">
  176. <div class="one-box">
  177. <div
  178. v-for="(items, row) in data.option_list"
  179. :key="'row' + row"
  180. class="one"
  181. :class="[!items.pinyin ? 'one_nopy' : '']"
  182. :style="{ marginRight: (row + 1) % 3 == 0 ? '' : '16px' }"
  183. >
  184. <div
  185. class="number"
  186. :style="{
  187. background:
  188. data.unified_attrib && data.unified_attrib.topic_color
  189. ? data.unified_attrib.topic_color
  190. : '#346CDA',
  191. }"
  192. >
  193. {{ row + 1 }}
  194. </div>
  195. <div class="hzpinyin">
  196. <div v-if="isEnable(data.property.view_pinyin)" class="pinyin">
  197. {{ items.pinyin }}
  198. </div>
  199. <template v-if="items.hz_info.length > 0">
  200. <div
  201. class="strockplay-newWord"
  202. :class="['frame-size-' + data.property.frame_size]"
  203. :style="{
  204. borderColor:
  205. data.unified_attrib && data.unified_attrib.topic_color
  206. ? data.unified_attrib.topic_color
  207. : '#346CDA',
  208. }"
  209. >
  210. <Strockplay
  211. class-name="adult-strockplay"
  212. :Book_text="items.hz_info[0].con"
  213. :play-storkes="true"
  214. :stroke-play-color="
  215. data.unified_attrib && data.unified_attrib.topic_color
  216. ? data.unified_attrib.topic_color
  217. : '#346CDA'
  218. "
  219. :stroke-color="'#000000'"
  220. :paly-width="'18px'"
  221. :BoxbgType="'0'"
  222. :cur-item="items.hz_info[0].hzDetail.hz_json"
  223. :target-div="'writeTops-item-right' + '-' + items.hz_info[0].con"
  224. class="writeTop-item"
  225. :style="{
  226. borderColor:
  227. data.unified_attrib && data.unified_attrib.topic_color
  228. ? data.unified_attrib.topic_color
  229. : '#346CDA',
  230. }"
  231. />
  232. </div>
  233. </template>
  234. </div>
  235. <div class="image">
  236. <img src="@/assets/drag-arrows.png" alt="" />
  237. </div>
  238. <div class="answer">
  239. <div
  240. class="option_one"
  241. :class="[items.is_example ? 'option_one_example' : '', 'option_one-' + data.property.frame_size]"
  242. :style="{
  243. borderColor:
  244. data.unified_attrib && data.unified_attrib.topic_color
  245. ? data.unified_attrib.topic_color
  246. : '#346CDA',
  247. }"
  248. >
  249. <img
  250. v-if="items.answer"
  251. :src="
  252. items.answer.length > 3
  253. ? data.file_list.find((p) => p.file_id === items.answer).file_url
  254. : require('@/assets/structure/structure-' + items.answer + '.png')
  255. "
  256. alt=""
  257. />
  258. </div>
  259. </div>
  260. </div>
  261. </div>
  262. </div>
  263. </AnswerAnalysis>
  264. </div>
  265. </div>
  266. </template>
  267. <script>
  268. import { getCharacterStructureData } from '@/views/book/courseware/data/characterStructure';
  269. import PreviewMixin from '../common/PreviewMixin';
  270. import draggable from 'vuedraggable';
  271. import Strockplay from '../new_word/components/Strockplay.vue';
  272. import { getRandomNumber } from '@/utils';
  273. export default {
  274. name: 'CharacterStructurePreview',
  275. components: { draggable, Strockplay },
  276. mixins: [PreviewMixin],
  277. data() {
  278. return {
  279. data: getCharacterStructureData(),
  280. dragData: [],
  281. SortArr: [],
  282. single: [],
  283. drag: false,
  284. currentId: null,
  285. isPraShow: false,
  286. curData: null,
  287. userErrorList: [],
  288. correctArr: [],
  289. };
  290. },
  291. // 计算属性 类似于data概念
  292. computed: {},
  293. watch: {
  294. 'data.option_list': {
  295. handler(val) {
  296. if (val) {
  297. this.handleData();
  298. }
  299. },
  300. deep: true,
  301. immediate: true,
  302. },
  303. },
  304. methods: {
  305. onMove(e) {
  306. return false;
  307. },
  308. choose(e) {
  309. let index = e.item.outerHTML.indexOf('id');
  310. let formIndex = e.item.outerHTML[index + 4];
  311. let formIndex2 = e.item.outerHTML[index + 5];
  312. if (!isNaN(Number(formIndex))) {
  313. this.currentId = Number(formIndex);
  314. }
  315. if (!isNaN(Number(formIndex2))) {
  316. this.currentId = (String(this.currentId) + formIndex2) * 1;
  317. }
  318. },
  319. // 开始拖拽事件
  320. onStart() {
  321. this.drag = true;
  322. this.dragData = JSON.parse(JSON.stringify(this.SortArr));
  323. },
  324. // 拖拽结束事件
  325. onEnd(e) {
  326. this.drag = false;
  327. let formIndex = Number(e.to.firstChild.attributes[1].value[4]);
  328. if (`${formIndex}` !== 'NaN') {
  329. if (this.answer.answer_list[formIndex].answer_list.length > 1) {
  330. let arr = [];
  331. this.data.option_list.forEach((item) => {
  332. if (item.id === this.currentId) {
  333. arr.push(item);
  334. }
  335. });
  336. this.answer.answer_list[formIndex].answer_list = JSON.parse(JSON.stringify(arr));
  337. }
  338. }
  339. this.SortArr = JSON.parse(JSON.stringify(this.dragData));
  340. },
  341. handleData() {
  342. this.single = [];
  343. this.data.structure_select_list.forEach((item) => {
  344. let items = this.data.file_list.find((p) => p.file_id === item.file_id);
  345. if (items) {
  346. item.value = items.file_url;
  347. }
  348. });
  349. this.data.option_list.forEach((items) => {
  350. if (items.is_example) {
  351. let obj = {
  352. id: items.answer,
  353. img_list: [],
  354. img_url:
  355. items.answer.length > 3
  356. ? this.data.structure_select_list.find((p) => p.file_id === items.answer)
  357. ? this.data.structure_select_list.find((p) => p.file_id === items.answer).value
  358. : ''
  359. : require(`@/assets/structure/structure-${items.answer}.png`),
  360. userAnswerJudge: 'example',
  361. };
  362. this.single.push({
  363. answer_list: [obj],
  364. });
  365. } else {
  366. this.single.push({
  367. answer_list: [],
  368. });
  369. }
  370. });
  371. this.data.structure_select_list.forEach((item, i) => {
  372. item.id = i;
  373. this.SortArr.push(item);
  374. });
  375. if (!this.isJudgingRightWrong) {
  376. this.$set(this.answer, 'answer_list', this.single);
  377. }
  378. },
  379. classNameJudge(item, index) {
  380. let classname = '';
  381. if (!this.isJudgingRightWrong && !this.isShowRightAnswer) {
  382. return '';
  383. }
  384. if (item.answer && this.isJudgingRightWrong) {
  385. if (item.answer === this.answer.answer_list[index].answer_list[0].file_id) {
  386. classname = 'right';
  387. } else {
  388. classname = 'wrong';
  389. }
  390. }
  391. return classname;
  392. },
  393. // 重做
  394. retry() {
  395. this.isJudgingRightWrong = false;
  396. this.isShowRightAnswer = false;
  397. this.handleData([]);
  398. },
  399. /**
  400. * 获取无文本内容的数据结构,用于保存为个人模板时的样式模板
  401. */
  402. getNoTextContentData() {
  403. let noTextContentData = JSON.parse(JSON.stringify(this.data));
  404. const resetFieldMap = {
  405. analysis_list: [],
  406. answer_list: [],
  407. structure_select_id: [], // 存放已选选项id
  408. structure_select_list: [],
  409. image_list: [],
  410. image_id_list: [],
  411. file_list: [],
  412. file_id_list: [],
  413. };
  414. Object.assign(noTextContentData, resetFieldMap);
  415. noTextContentData.option_list.forEach((item) => {
  416. item.content = '';
  417. item.pinyin = '';
  418. item.mark = getRandomNumber();
  419. item.hz_info = [];
  420. item.is_example = false;
  421. item.answer = '';
  422. });
  423. if (noTextContentData.answer) {
  424. noTextContentData.answer.answer_list = [];
  425. noTextContentData.answer.reference_answer = '';
  426. }
  427. return noTextContentData;
  428. },
  429. },
  430. };
  431. </script>
  432. <style lang="scss" scoped>
  433. @use '@/styles/mixin.scss' as *;
  434. .structure-preview {
  435. .content-box {
  436. span {
  437. display: flex;
  438. flex-wrap: wrap;
  439. }
  440. }
  441. .content-boxs {
  442. height: 80px;
  443. overflow: hidden;
  444. &-middle {
  445. height: 70px;
  446. }
  447. &-small {
  448. height: 60px;
  449. }
  450. }
  451. .option {
  452. display: flex;
  453. flex-wrap: wrap;
  454. padding: 10px 15px;
  455. background: #deebff;
  456. border-radius: 4px;
  457. .option_one {
  458. display: flex;
  459. align-items: center;
  460. justify-content: center;
  461. width: 72px;
  462. height: 72px;
  463. margin: 10px 15px;
  464. overflow: hidden;
  465. cursor: pointer;
  466. background: #9dcaff;
  467. border-radius: 4px;
  468. &-middle {
  469. width: 62px;
  470. height: 62px;
  471. }
  472. &-small {
  473. width: 52px;
  474. height: 52px;
  475. }
  476. img {
  477. max-width: 100%;
  478. max-height: 100%;
  479. // position: relative;
  480. // top: 2px;
  481. opacity: 0.8;
  482. }
  483. }
  484. }
  485. .strockplay-newWord {
  486. position: relative;
  487. box-sizing: border-box;
  488. width: 80px;
  489. height: 80px;
  490. // padding: 5px;
  491. overflow: hidden;
  492. // background: #fff;
  493. background-size: cover;
  494. border: 2px solid #346cda;
  495. border-radius: 4px;
  496. }
  497. .frame-size-middle {
  498. width: 70px !important;
  499. height: 70px !important;
  500. }
  501. .frame-size-small {
  502. width: 60px !important;
  503. height: 60px !important;
  504. }
  505. .one {
  506. display: flex;
  507. align-items: center;
  508. margin: 35px 0 32px;
  509. &.one_nopy {
  510. margin: 16px 0;
  511. }
  512. .number {
  513. display: block;
  514. width: 24px;
  515. height: 24px;
  516. margin-right: 16px;
  517. font-family: 'arial';
  518. font-size: 14px;
  519. font-weight: bold;
  520. line-height: 24px;
  521. color: #fff;
  522. text-align: center;
  523. background: #346cda;
  524. border-radius: 100%;
  525. }
  526. .hzpinyin {
  527. position: relative;
  528. .pinyin {
  529. position: absolute;
  530. top: -28px;
  531. width: 100%;
  532. font-family: 'League';
  533. font-size: 16px;
  534. font-weight: 400;
  535. color: rgba($color: #000, $alpha: 50%);
  536. text-align: center;
  537. }
  538. }
  539. .image {
  540. img {
  541. width: 15px;
  542. margin: 0 9px;
  543. }
  544. }
  545. .answer {
  546. width: 80px;
  547. height: 80px;
  548. &-middle {
  549. width: 70px;
  550. height: 70px;
  551. }
  552. &-small {
  553. width: 60px;
  554. height: 60px;
  555. }
  556. .option_one {
  557. display: flex;
  558. align-items: center;
  559. justify-content: center;
  560. width: 80px;
  561. height: 80px;
  562. overflow: hidden;
  563. border: 2px solid #346cda;
  564. border-radius: 4px;
  565. &-middle {
  566. width: 70px;
  567. height: 70px;
  568. }
  569. &-small {
  570. width: 60px;
  571. height: 60px;
  572. }
  573. &.right {
  574. background: #e9f7f2 !important;
  575. border-color: $right-color !important;
  576. }
  577. &.wrong {
  578. border-color: $error-color !important;
  579. }
  580. img {
  581. max-width: 100%;
  582. max-height: 100%;
  583. // position: relative;
  584. // left: -2px;
  585. opacity: 0.8;
  586. }
  587. }
  588. }
  589. .answer:hover {
  590. // @include background_color("theme_color");
  591. }
  592. }
  593. .one-box {
  594. display: flex;
  595. flex-flow: wrap;
  596. column-gap: 16px;
  597. padding: 20px 0;
  598. }
  599. }
  600. </style>