chinese-question.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. <template>
  2. <view class="chinese-area" v-model="questionData">
  3. <view class="question_title">
  4. <text class="question_number">
  5. {{ questionNumberEndIsBracket(questionData.property.question_number) }}
  6. </text>
  7. <text class="question_stem" v-html="sanitizeHTML(questionData.stem)"
  8. :ref="'richText-1-1'+questionData.question_id"
  9. @longpress="previewByRichTextImg(-1,-1,questionData.question_id)"></text>
  10. </view>
  11. <view class="description"
  12. v-if="isEnable(questionData.property.is_enable_description)&&questionData.description.length > 0"
  13. v-html="sanitizeHTML(questionData.description)" :ref="'richText-2-2'+questionData.question_id"
  14. @longpress="previewByRichTextImg(-2,-2,questionData.question_id)">
  15. </view>
  16. <scroll-view v-if="isShow" class="words-box" scroll-x :scroll-into-view="tabCurrent">
  17. <view v-for="(item, index) in questionData.option_list" :key="index" @click="swichMenu(index)"
  18. :id="'tabNum'+index" :class="['words-item',currentTab===index ? 'menu-topic-active' : '']">
  19. <!-- 听写汉字 -->
  20. <template v-if="questionData.property.learn_type === 'dictation'">
  21. <view class="dictation-box">
  22. <text>{{computedQuestionNumber(index,questionData.option_number_show_mode)}}</text>
  23. <text>{{item.pinyin}}</text>
  24. <AudioPlay v-if="item.audio_file_id" :file-id="item.audio_file_id" />
  25. </view>
  26. </template>
  27. <template v-else>
  28. <!-- 描红和书写 -->
  29. <view class="strock-chinese-area">
  30. <Strockplayredline v-for="(hz,indexHZ) in item.hz_strokes_list" :class="['strock-chinese']"
  31. :play-storkes="true" :book-text="hz.content" :book-strokes="hz.strokes" strokeColor="#000000"
  32. :target-view="'stem_'+questionData.question_id+'_' + item.mark+'_'+indexHZ" />
  33. </view>
  34. </template>
  35. </view>
  36. </scroll-view>
  37. <view class="title-split"></view>
  38. <!-- 内容 -->
  39. <swiper v-if="isShowC" class="swiper-box" :current="currentTab" disable-touch>
  40. <swiper-item class="swiper-topic-list" v-for="(item, index) in questionData.option_list" :key="index">
  41. <view class="swiper-topic-area">
  42. <!-- 描红 -->
  43. <template v-if="questionData.property.learn_type === 'paint'">
  44. <view class="paint-content-box" v-for="(num,indexT) in questionData.property.tian_number" :key="indexT">
  45. <view class="paint-content-area" :id="questionData.question_id+'_'+item.mark"
  46. v-for="(hz,indexHZ) in item.hz_strokes_list" :key="indexHZ"
  47. @click="showStrockRedPop(questionData.question_id+'_'+item.mark,item.mark,indexHZ+indexT*item.hz_strokes_list.length,item.pinyin,hz.content,hz.strokes,item.audio_file_id)">
  48. <Strockplayredline :class="['strock-chinese']" :play-storkes="false" :book-text="hz.content"
  49. :targetView="'con_'+questionData.question_id+'_'+item.mark+Number(indexHZ+indexT*item.hz_strokes_list.length)"
  50. :book-strokes="hz.strokes" :bjIconSize="72"
  51. :strokeColor="strokeColorList[item.mark+Number(indexHZ+indexT*item.hz_strokes_list.length)]" />
  52. </view>
  53. </view>
  54. </template>
  55. <!-- 听写汉字 -->
  56. <template v-if="questionData.property.learn_type === 'dictation'">
  57. <view class="dictation-content-box" v-for="(itemI,indexI) in item.pinyin_item_list" :key="indexI">
  58. <text class="pinyin-text">{{itemI.pinyin_item}}</text>
  59. <view class="hanzi-writer-area"
  60. @click="showHanziWritePop('dictation',questionData.question_id+'_'+item.mark+indexI,item.mark,item.pinyin,indexI)">
  61. <SvgIcon class="character-target-bg" icon-class="hanzi-writer-bg" :size="72" />
  62. <img class="hanzi-writer-img" :ref="'con_'+questionData.question_id+'_'+item.mark+indexI"
  63. :src="imgList[item.mark+indexI]" />
  64. </view>
  65. </view>
  66. </template>
  67. <!-- 书写 -->
  68. <template v-if="questionData.property.learn_type === 'write'">
  69. <view class="write-content-box" v-for="(num,indexT) in questionData.property.tian_number" :key="indexT">
  70. <view class="write-content-area" v-for="(itemI,indexI) in item.pinyin_item_list" :key="indexI"
  71. @click="showHanziWritePop('write',questionData.question_id+'_'+item.mark,item.mark,item.pinyin,indexI+indexT*item.pinyin_item_list.length)">
  72. <SvgIcon icon-class="hanzi-writer-bg" class="character-target-bg" :size="72" />
  73. <img class="hanzi-writer-img"
  74. :ref="'con_'+questionData.question_id+'_'+item.mark+Number(indexI+indexT*item.pinyin_item_list.length)"
  75. :src="imgList[item.mark+Number(indexI+indexT*item.pinyin_item_list.length)]" />
  76. </view>
  77. </view>
  78. </template>
  79. </view>
  80. </swiper-item>
  81. </swiper>
  82. <uni-popup ref="parentPopup" :mask-click="true">
  83. <view class="popup-box">
  84. <template v-if="questionData.property.learn_type === 'paint'">
  85. <Strockplayredline :class="['strock-chinese']" :play-storkes="false" :book-text="hz_content"
  86. :targetView="'black_'+canvas_id+answer_index" :book-strokes="hz_strokes" :bjIconSize="300"
  87. strokeColor="#404040" ref="strockBlack" />
  88. <Strockred :class="['strockred-box']" :mark="item_mark" :book-text="hz_content" :book-strokes="hz_strokes"
  89. :pinyin="pinyin_item" :reset="true" :audioFileId="py_audio_file_id" :target-view="'pop_'+canvas_id"
  90. :answerIndex="answer_index" :userAnswerList="returnUserAnswerList" @saveStrock="saveStrock"
  91. :hanzi-color="hanzi_color" ref="strockRed" />
  92. </template>
  93. <template v-else>
  94. <FreeWrite :class="['con-box']" :canvasId="'pop_'+canvas_id" :mark="item_mark" :answerIndex="answer_index"
  95. @saveStrock="saveStrock" :userAnswerList="returnUserAnswerList" :pinyin="pinyin_item"
  96. :disabled="answer_control[questionData.question_id].isReadOnly" />
  97. </template>
  98. </view>
  99. </uni-popup>
  100. </view>
  101. </template>
  102. <script>
  103. import {
  104. questionData,
  105. sanitizeHTML,
  106. isEnable,
  107. computedQuestionNumber,
  108. answer_control,
  109. } from '@/pages/answer_question/common/data/common.js';
  110. import {
  111. GetFileStoreInfo,
  112. GetStaticResources,
  113. GetFileURLMap
  114. } from '@/api/api.js';
  115. import Strockplayredline from '@/components/Strockplayredline/Strockplayredline.vue';
  116. import Strockred from '@/components/Strockred/Strockred.vue';
  117. import FreeWrite from '@/components/FreeWrite/FreeWrite.vue';
  118. import AudioPlay from '@/components/AudioPlay/AudioPlay.vue';
  119. import AnswerControlMixin from '@/pages/answer_question/common/data/AnswerControlMixin.js';
  120. export default {
  121. name: "chinese-question",
  122. mixins: [AnswerControlMixin],
  123. components: {
  124. Strockplayredline,
  125. Strockred,
  126. FreeWrite,
  127. AudioPlay
  128. },
  129. props: {
  130. questionData: questionData,
  131. },
  132. data() {
  133. return {
  134. isEnable,
  135. sanitizeHTML,
  136. computedQuestionNumber,
  137. answer_control,
  138. currentTab: 0, //当前标签
  139. prevTab: 0, //上一次点击的标签
  140. tabCurrent: 'tabNum0', //当前标签对应页
  141. currentPager: 0, //当前书写汉字格
  142. hanzi_color: '#404040', // 描红汉字底色
  143. answer_list: {
  144. write_model: {},
  145. }, // 用户答题数据
  146. isShow: true,
  147. isShowC: true,
  148. userAnswerList: [],
  149. returnUserAnswerList: [], //用
  150. strokesList: {}, //用于临时存储笔画
  151. imgList: [],
  152. canvas_id: '',
  153. item_mark: '',
  154. pinyin_item: '',
  155. answer_index: 0,
  156. hasPlay: false,
  157. hz_content: '',
  158. hz_strokes: '',
  159. py_audio_file_id: '',
  160. strokeColorList: [],
  161. };
  162. },
  163. watch: {
  164. 'questionData.question_id': {
  165. handler(val, oldVal) {
  166. var that = this;
  167. that.commonComputedAnswerControl(val);
  168. that.isShow = false;
  169. that.isShowC = false;
  170. that.currentTab = 0;
  171. that.prevTab = 0;
  172. that.currentPager = 0;
  173. that.tabCurrent = 'tabNum0';
  174. that.userAnswerList = [];
  175. that.imgList = [];
  176. that.strokesList = {};
  177. if (val !== oldVal) {
  178. var cb = function() {
  179. that.handleData();
  180. };
  181. that.setUserAnswer(cb);
  182. }
  183. },
  184. immediate: true,
  185. deep: true,
  186. },
  187. },
  188. mounted() {
  189. uni.$on('_closePopup', this.closePopup);
  190. },
  191. onUnload() {
  192. uni.$off('_closePopup', this.closePopup);
  193. },
  194. methods: {
  195. // 初始化数据
  196. handleData() {
  197. var that = this;
  198. setTimeout(function() {
  199. that.isShow = true;
  200. }, 0)
  201. setTimeout(function() {
  202. that.isShowC = true;
  203. }, 10)
  204. var answer_list = [];
  205. },
  206. //切换汉字
  207. swichMenu(id) {
  208. this.currentPager = 0;
  209. this.currentTab = id;
  210. this.tabCurrent = 'tabNum' + id;
  211. },
  212. //切换当前汉字田字格数
  213. prevIndex(i) {
  214. if (i === 0) return;
  215. this.currentPager--;
  216. },
  217. nextIndex(i, total) {
  218. if (i === total - 1) return;
  219. this.currentPager++;
  220. },
  221. saveStrock(mark, data, img, answerIndex) {
  222. if (!mark) return;
  223. var en = this.questionData.option_list.find(p => p.mark == mark);
  224. if (en == null) return;
  225. var learn_type = this.questionData.property.learn_type;
  226. var oldAnswer = this.userAnswerList.find(p => p.mark == mark);
  227. var hasAnswer = false;
  228. if (!oldAnswer) {
  229. oldAnswer = {
  230. mark: mark,
  231. strokes_content_list: []
  232. };
  233. var answerCount = 0;
  234. if (learn_type == 'dictation') {
  235. answerCount = en.pinyin_item_list.length;
  236. } else {
  237. answerCount = this.questionData.property.tian_number * en.pinyin_item_list.length;
  238. }
  239. for (var i = 0; i < answerCount; i++) {
  240. oldAnswer.strokes_content_list[i] = learn_type == 'paint' ? 'false' : '';
  241. }
  242. } else {
  243. hasAnswer = true;
  244. oldAnswer.strokes_content_list[answerIndex] = '';
  245. }
  246. if (data) {
  247. if (learn_type == 'paint') {
  248. oldAnswer.strokes_content_list[answerIndex] = data;
  249. } else {
  250. var curHZ = this.strokesList[mark] || {};
  251. var curList = curHZ[answerIndex] || [];
  252. curList.push(data);
  253. curHZ[answerIndex] = curList;
  254. this.strokesList[mark] = curHZ;
  255. var strokes_content = {
  256. hz: en.hz_strokes_list[answerIndex % en.hz_strokes_list.length].hz,
  257. strokes_content: JSON.stringify(curList[0]),
  258. strokes_image: img
  259. };
  260. oldAnswer.strokes_content_list[answerIndex] = JSON.stringify(strokes_content);
  261. }
  262. if (!hasAnswer)
  263. this.userAnswerList.push(oldAnswer);
  264. }
  265. this.returnUserAnswerList = this.userAnswerList;
  266. var questionId = this.questionData.question_id;
  267. this.questionData.user_answer[questionId].isEdit = this.userAnswerList.length > 0;
  268. this.questionData.user_answer[questionId].answer_list = this.userAnswerList;
  269. const id = 'con_' + this.questionData.question_id + '_' + mark + answerIndex;
  270. if (learn_type == 'paint') {
  271. var _dom = document.getElementById(id);
  272. if (isEnable(data)) {
  273. _dom.style.opacity = 1;
  274. } else {
  275. _dom.style.opacity = 0.2;
  276. }
  277. }
  278. const element = this.$refs[id];
  279. if (!element) return;
  280. if (img) {
  281. this.$forceUpdate();
  282. this.$refs.parentPopup.close();
  283. this.imgList[mark + answerIndex] = img;
  284. element[0].setAttribute('src', img);
  285. element[0].style = "display:block";
  286. } else {
  287. element[0].setAttribute('src', '');
  288. element[0].style = "display:none";
  289. }
  290. // console.log('保存的', this.imgList);
  291. },
  292. setUserAnswer: function(cb) {
  293. // console.log('设置的', this.imgList);
  294. var that = this;
  295. var callback = function() {
  296. var userAnswer = [];
  297. var questionId = that.questionData.question_id;
  298. var _ua = that.questionData.user_answer[questionId];
  299. if (_ua && _ua.answer_list && _ua.answer_list.length > 0)
  300. userAnswer = _ua.answer_list;
  301. that.returnUserAnswerList = userAnswer;
  302. userAnswer.forEach(p => {
  303. if (p.strokes_content_list) {
  304. p.strokes_content_list.forEach((item, i) => {
  305. if (item == '' || null == item) return;
  306. var learn_type = that.questionData.property.learn_type;
  307. if ('paint' == learn_type) {
  308. if (isEnable(item)) {
  309. const id = 'con_' + questionId + '_' + p.mark + i;
  310. setTimeout(function() {
  311. var _dom = document.getElementById(id);
  312. _dom.style.opacity = 1;
  313. }, 1000);
  314. }
  315. } else {
  316. that.imgList[p.mark + i] = JSON.parse(item).strokes_image;
  317. }
  318. })
  319. }
  320. });
  321. if (cb && typeof cb === 'function') {
  322. cb();
  323. }
  324. }
  325. this.$emit("getUserAnswer", this.questionData.question_id, callback);
  326. },
  327. //书写汉字弹窗
  328. showHanziWritePop(type, canvas_id, item_mark, pinyin_item, answer_index) {
  329. this.$refs.parentPopup.open('center');
  330. this.answer_index = answer_index;
  331. if ('write' == type) {
  332. canvas_id = canvas_id + this.answer_index;
  333. }
  334. this.canvas_id = canvas_id;
  335. this.item_mark = item_mark;
  336. this.pinyin_item = pinyin_item;
  337. },
  338. //描红汉字弹窗
  339. showStrockRedPop(canvas_id, item_mark, answer_index, pinyin_item, hz_content, hz_strokes, py_audio_file_id) {
  340. if (this.answer_control[this.questionData.question_id].isReadOnly) return;
  341. var that = this;
  342. this.$refs.parentPopup.open('center');
  343. this.canvas_id = canvas_id + answer_index;
  344. this.item_mark = item_mark;
  345. this.answer_index = answer_index;
  346. this.pinyin_item = pinyin_item;
  347. this.hz_content = hz_content;
  348. this.hz_strokes = hz_strokes;
  349. this.py_audio_file_id = py_audio_file_id;
  350. this.returnUserAnswerList.forEach(p => {
  351. if (p.mark == item_mark) {
  352. if (isEnable(p.strokes_content_list[answer_index])) {
  353. setTimeout(function() {
  354. that.$refs.strockRed.$el.children[0].children[1].style.zIndex = -1;
  355. that.$refs.strockBlack.$el.style.zIndex = 1;
  356. }, 10);
  357. }
  358. }
  359. })
  360. },
  361. //关闭弹窗
  362. closePopup() {
  363. this.$refs.parentPopup.close();
  364. },
  365. },
  366. }
  367. </script>
  368. <style lang="scss" scoped>
  369. .chinese-area {
  370. .words-box {
  371. margin: 32rpx 0;
  372. /deep/ .uni-scroll-view-content {
  373. display: flex;
  374. column-gap: 32rpx;
  375. }
  376. .words-item {
  377. opacity: 0.2;
  378. .dictation-box {
  379. white-space: nowrap;
  380. color: #ffffff;
  381. background-color: $uni-color-main;
  382. display: flex;
  383. align-items: center;
  384. column-gap: 10rpx;
  385. padding: 16rpx 32rpx;
  386. border-radius: 40rpx;
  387. }
  388. .strock-chinese-area {
  389. display: flex;
  390. .strock-chinese {
  391. border: 1px solid #e81b1b;
  392. border-right: none;
  393. }
  394. .strock-chinese:last-child {
  395. border-right: 1px solid #e81b1b;
  396. }
  397. }
  398. }
  399. .menu-topic-active {
  400. opacity: 1;
  401. }
  402. }
  403. .title-split {
  404. height: 20rpx;
  405. position: relative;
  406. width: 109.5%;
  407. left: -5%;
  408. background: $uni-bg-color-grey;
  409. box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1) inset;
  410. }
  411. .swiper-box {
  412. margin-top: 32rpx;
  413. width: 100%;
  414. padding: 4rpx;
  415. .swiper-topic-area {
  416. width: 100%;
  417. display: flex;
  418. column-gap: 16rpx;
  419. row-gap: 16rpx;
  420. flex-wrap: wrap;
  421. .paint-content-box {
  422. display: flex;
  423. align-items: center;
  424. column-gap: 16rpx;
  425. row-gap: 16rpx;
  426. .paint-content-area {
  427. width: 72px;
  428. height: 72px;
  429. display: flex;
  430. align-items: center;
  431. justify-content: center;
  432. border: 1px solid #E81B1B;
  433. /deep/ .character-target-div {
  434. opacity: 0.2;
  435. }
  436. }
  437. }
  438. .write-content-box {
  439. display: flex;
  440. align-items: center;
  441. column-gap: 16rpx;
  442. row-gap: 16rpx;
  443. .write-content-area {
  444. position: relative;
  445. .character-target-bg {
  446. // color: #3F3F3F;
  447. border: 1px solid #E81B1B;
  448. }
  449. .hanzi-writer-img {
  450. width: 72px;
  451. height: 72px;
  452. position: absolute;
  453. top: 1px;
  454. left: 1px;
  455. border: 0;
  456. }
  457. .hanzi-writer-img {
  458. z-index: 1;
  459. }
  460. }
  461. }
  462. .dictation-content-box {
  463. display: flex;
  464. flex-direction: column;
  465. flex-wrap: wrap;
  466. align-items: center;
  467. row-gap: 16rpx;
  468. column-gap: 16rpx;
  469. .hanzi-writer-area {
  470. position: relative;
  471. .character-target-bg {
  472. // color: #DEDEDE;
  473. border: 1px solid #E81B1B;
  474. }
  475. .hanzi-writer-img {
  476. width: 72px;
  477. height: 72px;
  478. position: absolute;
  479. top: 1px;
  480. left: 1px;
  481. border: 0;
  482. }
  483. .hanzi-writer-img {
  484. z-index: 1;
  485. }
  486. }
  487. .play-handwriting-btn {
  488. width: 72px;
  489. height: 26px;
  490. background-color: #F6F6F6;
  491. border-radius: 6rpx;
  492. font-size: 24rpx;
  493. display: flex;
  494. align-items: center;
  495. justify-content: center;
  496. column-gap: 16rpx;
  497. }
  498. }
  499. }
  500. }
  501. .popup-box {
  502. padding: 24rpx;
  503. border-radius: 16rpx;
  504. background-color: #ffffff;
  505. .close-box {
  506. display: flex;
  507. justify-content: right;
  508. }
  509. .strock-chinese {
  510. position: absolute;
  511. top: 114rpx;
  512. z-index: -1;
  513. /deep/.character-target-bg {
  514. border: 4px solid #E81B1B;
  515. }
  516. /deep/.character-target-div {
  517. top: 8rpx;
  518. left: 8rpx;
  519. }
  520. }
  521. .strockred-box {
  522. position: relative;
  523. }
  524. }
  525. }
  526. .audio-wrapper {
  527. margin: 0;
  528. /deep/ .audio-play {
  529. width: 16px !important;
  530. height: 16px !important;
  531. }
  532. /deep/ .voice-play {
  533. width: 16px !important;
  534. height: 16px !important;
  535. }
  536. }
  537. </style>
  538. <style>
  539. /deep/ uni-swiper {
  540. /* min-height: calc(100vh - 570rpx) !important; */
  541. height: 100vh !important;
  542. }
  543. </style>