ChooseTonePreview.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. <!-- eslint-disable vue/no-v-html -->
  2. <template>
  3. <div v-if="show_preview" class="choosetone-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
  9. v-if="isEnable(data.property.is_enable_description)"
  10. class="description rich-text"
  11. v-html="sanitizeHTML(data.description)"
  12. ></div>
  13. <div class="option-list">
  14. <li v-for="(item, i) in data.option_list" :key="i" :class="['option-item']">
  15. <span>{{ computeOptionMethods[data.option_number_show_mode](i) }} </span>
  16. <AudioPlay v-if="item.audio_file_id" :file-id="item.audio_file_id" :show-slider="true" />
  17. <div
  18. class="option-content"
  19. :class="[isJudgingRightWrong ? (con_preview[i].all_right ? 'all-right' : 'has-error') : '']"
  20. >
  21. <span v-if="item.content_hz" class="items-hz">{{ item.content_hz }}</span>
  22. <template v-if="data.property.answer_mode === 'select'">
  23. <span
  24. v-for="(itemc, indexc) in con_preview[i].item_con"
  25. :key="indexc"
  26. :class="[
  27. 'item-con',
  28. active_index_str === i + '-' + indexc ? 'active' : '',
  29. isJudgingRightWrong && !con_preview[i].user_answer[indexc].is_right ? 'error' : '',
  30. ]"
  31. @click="
  32. con_preview[i].item_active_index = indexc;
  33. active_index_str = i + '-' + indexc;
  34. "
  35. >
  36. {{ itemc }}
  37. </span>
  38. </template>
  39. <template v-else>
  40. <span v-for="(itemc, indexc) in con_preview[i].item_con" :key="indexc" class="items-box">
  41. <span
  42. v-for="(itemi, indexi) in itemc"
  43. :key="indexi"
  44. :class="[
  45. 'items-con',
  46. active_index_str === i + '-' + indexc + '-' + indexi ? 'active' : '',
  47. isJudgingRightWrong &&
  48. !con_preview[i].user_answer[indexc].is_right &&
  49. con_preview[i].user_answer[indexc].select_index_submit === indexi
  50. ? 'error'
  51. : '',
  52. isJudgingRightWrong &&
  53. !con_preview[i].user_answer[indexc].is_right &&
  54. con_preview[i].user_answer[indexc].right_index === indexi
  55. ? 'right'
  56. : '',
  57. ]"
  58. @click="handleSelectItemTone(i, indexc, indexi, con_preview[i].item_con_yuan[indexc][indexi])"
  59. >{{ itemi }}</span
  60. >
  61. </span>
  62. </template>
  63. </div>
  64. <span
  65. v-for="({ img, value }, j) in toneList"
  66. :key="j"
  67. :class="[
  68. 'tone',
  69. data.property.answer_mode === 'select' &&
  70. con_preview[i].user_answer[con_preview[i].item_active_index] &&
  71. con_preview[i].user_answer[con_preview[i].item_active_index].select_tone === value
  72. ? 'active'
  73. : data.property.answer_mode === 'label' &&
  74. con_preview[i].user_answer[con_preview[i].item_active_index] &&
  75. con_preview[i].user_answer[con_preview[i].item_active_index].select_tone === value &&
  76. con_preview[i].user_answer[con_preview[i].item_active_index].select_letter === active_letter &&
  77. select_item_index === i
  78. ? 'active'
  79. : '',
  80. (isJudgingRightWrong &&
  81. con_preview[i].user_answer[con_preview[i].item_active_index].right_answer === value &&
  82. con_preview[i].user_answer[con_preview[i].item_active_index].select_index_submit !==
  83. con_preview[i].user_answer[con_preview[i].item_active_index].right_answer &&
  84. data.property.answer_mode === 'select') ||
  85. (isJudgingRightWrong &&
  86. data.property.answer_mode === 'label' &&
  87. con_preview[i].user_answer[con_preview[i].item_active_index].right_answer === value &&
  88. con_preview[i].user_answer[con_preview[i].item_active_index].right_index ===
  89. con_preview[i].user_answer[con_preview[i].item_active_index].select_index &&
  90. select_item_index === i)
  91. ? 'right'
  92. : '',
  93. ]"
  94. @click="chooseTone(con_preview[i], value, i)"
  95. >
  96. <SvgIcon :icon-class="img" />
  97. </span>
  98. </li>
  99. </div>
  100. <div class="answer-tips-box">
  101. <img v-if="show_tips" class="answer-tips" src="../../../assets/select-tone-tips.png" />
  102. <span :class="['tips-btn', show_tips ? 'tips-btn-active' : '']" @click="show_tips = !show_tips">
  103. <img src="../../../assets/tips-icon.png" />
  104. </span>
  105. </div>
  106. </div>
  107. </template>
  108. <script>
  109. import { computeOptionMethods } from '@/views/exercise_questions/data/common';
  110. import PreviewMixin from './components/PreviewMixin';
  111. export default {
  112. name: 'ChooseTonePreview',
  113. mixins: [PreviewMixin],
  114. data() {
  115. return {
  116. computeOptionMethods,
  117. toneList: [
  118. { value: '1', label: '一声', img: 'first-tone' },
  119. { value: '2', label: '二声', img: 'second-tone' },
  120. { value: '3', label: '三声', img: 'third-tone' },
  121. { value: '4', label: '四声', img: 'fourth-tone' },
  122. // { value: '0', label: '轻声', img: 'neutral-tone' },
  123. ],
  124. con_preview: [],
  125. tone_data: [
  126. ['ā', 'á', 'ǎ', 'à', 'a'],
  127. ['ō', 'ó', 'ǒ', 'ò', 'o'],
  128. ['ē', 'é', 'ě', 'è', 'e'],
  129. ['ī', 'í', 'ǐ', 'ì', 'i'],
  130. ['ū', 'ú', 'ǔ', 'ù', 'u'],
  131. ['ǖ', 'ǘ', 'ǚ', 'ǜ', 'ü'],
  132. ['ǖ', 'ǘ', 'ǚ', 'ǜ', 'ü'],
  133. ['Ā', 'Á', 'Â', 'À', 'A'],
  134. ['Ō', 'Ó', 'Ô', 'Ò', 'O'],
  135. ['Ē', 'É', 'Ê', 'È', 'E'],
  136. ['Ī', 'Í', 'Î', 'Ì', 'I'],
  137. ['Ū', 'Ú', 'Û', 'Ù', 'U'],
  138. ],
  139. final_con: '',
  140. active_index_str: '', // 高亮索引的字符串
  141. active_letter: '', // 选中字母的值
  142. active_letter_index: 0, // 选择字母索引
  143. select_item_index: 0, // 小题索引
  144. show_tips: false, // 是否显示答题提示
  145. show_preview: false,
  146. };
  147. },
  148. watch: {
  149. data: {
  150. handler(val) {
  151. if (!val || this.data.type !== 'choose_tone') return;
  152. this.handleData();
  153. },
  154. deep: true,
  155. immediate: true,
  156. },
  157. isJudgingRightWrong: {
  158. handler(val) {
  159. if (!val) return;
  160. this.judgeRight();
  161. },
  162. immediate: true,
  163. },
  164. },
  165. created() {
  166. // this.handleData();
  167. },
  168. methods: {
  169. chooseTone(item, value, i) {
  170. if (this.disabled) return;
  171. if ((!this.active_letter || this.select_item_index !== i) && this.data.property.answer_mode === 'label') return;
  172. item.user_answer[item.item_active_index].select_tone = value;
  173. if (this.data.property.answer_mode === 'label') {
  174. item.user_answer[item.item_active_index].select_letter = this.active_letter;
  175. this.active_index_str = `${i}-${item.item_active_index}-${this.active_letter_index}`;
  176. this.handleReplaceTone(this.active_letter + value);
  177. setTimeout(() => {
  178. let new_con = item.item_con_yuan[item.item_active_index].split(this.active_letter);
  179. item.item_con[item.item_active_index] = new_con[0] + this.final_con + new_con[1];
  180. this.$forceUpdate();
  181. this.answer.answer_list[i].value[item.item_active_index] =
  182. new_con[0] + this.active_letter + value + new_con[1];
  183. }, 100);
  184. } else {
  185. this.active_index_str = `${i}-${item.item_active_index}`;
  186. this.handleReplaceTone(item.item_con_yuan[item.item_active_index] + value);
  187. setTimeout(() => {
  188. item.item_con[item.item_active_index] = this.final_con;
  189. this.$forceUpdate();
  190. }, 100);
  191. this.answer.answer_list[i].value[item.item_active_index] = value;
  192. }
  193. },
  194. // 处理数据
  195. handleData() {
  196. this.con_preview = [];
  197. this.show_preview = false;
  198. if (!this.isJudgingRightWrong) {
  199. this.answer.answer_list = [];
  200. }
  201. this.data.option_list.forEach((item) => {
  202. let con_arr = JSON.parse(JSON.stringify(item.content_view));
  203. let user_answer = [];
  204. let user_submit = []; // 用户提交答案
  205. con_arr.forEach((items) => {
  206. user_answer.push({
  207. select_tone: null,
  208. select_letter: '',
  209. select_index: '',
  210. });
  211. user_submit.push(this.data.property.answer_mode === 'label' ? items : '');
  212. });
  213. let obj = {
  214. item_con: con_arr,
  215. item_con_yuan: JSON.parse(JSON.stringify(con_arr)),
  216. mark: item.mark,
  217. user_answer,
  218. item_active_index: 0,
  219. active_letter: '',
  220. };
  221. if (!this.isJudgingRightWrong) {
  222. let obj = {
  223. mark: item.mark,
  224. value: user_submit,
  225. };
  226. this.answer.answer_list.push(obj);
  227. }
  228. this.con_preview.push(obj);
  229. });
  230. this.show_preview = true;
  231. },
  232. handleReplaceTone(e, arr, index, resArr) {
  233. this.$nextTick(() => {
  234. let value = e;
  235. this.resArr = [];
  236. if (value) {
  237. let reg = /\s+/g;
  238. let valueArr = value.split(reg);
  239. valueArr.forEach((item) => {
  240. this.handleValue(item, resArr);
  241. });
  242. let str = '';
  243. setTimeout(() => {
  244. if (resArr) {
  245. resArr.forEach((item) => {
  246. str += ' ';
  247. item.forEach((sItem) => {
  248. if (sItem.number && sItem.con) {
  249. let number = Number(sItem.number);
  250. let con = sItem.con;
  251. let word = this.addTone(number, con);
  252. str += word;
  253. } else if (sItem.number) {
  254. str += sItem.number;
  255. } else if (sItem.con) {
  256. str += ` ${sItem.con} `;
  257. }
  258. });
  259. });
  260. if (this.data.property.answer_mode === 'label') {
  261. let number_index = e.search(/0|1|2|3|4/) + 1;
  262. arr[index] = str.trim() + (number_index === 0 ? '' : e.substring(number_index));
  263. } else {
  264. arr[index] = str.trim();
  265. }
  266. } else {
  267. this.resArr.forEach((item) => {
  268. str += ' ';
  269. item.forEach((sItem) => {
  270. if (sItem.number && sItem.con) {
  271. let number = Number(sItem.number);
  272. let con = sItem.con;
  273. let word = this.addTone(number, con);
  274. str += word;
  275. } else if (sItem.number) {
  276. str += sItem.number;
  277. } else if (sItem.con) {
  278. str += ` ${sItem.con} `;
  279. }
  280. });
  281. });
  282. this.final_con = str.trim();
  283. }
  284. }, 10);
  285. }
  286. });
  287. },
  288. handleValue(valItem, resArr) {
  289. let reg = /\d/;
  290. let reg2 = /[A-Za-zü]+\d/g;
  291. let numList = [];
  292. let valArr = valItem.split('');
  293. if (reg2.test(valItem)) {
  294. for (let i = 0; i < valArr.length; i++) {
  295. let item = valItem[i];
  296. if (reg.test(item)) {
  297. let numIndex = numList.length === 0 ? 0 : numList[numList.length - 1].index;
  298. let con = valItem.substring(numIndex, i);
  299. con = con.replace(/\d/g, '');
  300. let obj = {
  301. index: i,
  302. number: item,
  303. con,
  304. isTran: true,
  305. };
  306. numList.push(obj);
  307. }
  308. }
  309. } else {
  310. numList = [];
  311. }
  312. if (resArr) {
  313. if (numList.length === 0) {
  314. resArr.push([{ con: valItem }]);
  315. } else {
  316. resArr.push(numList);
  317. }
  318. } else if (numList.length === 0) {
  319. this.resArr.push([{ con: valItem }]);
  320. } else {
  321. this.resArr.push(numList);
  322. }
  323. },
  324. addTone(number, con) {
  325. let zmList = ['a', 'o', 'e', 'i', 'u', 'v', 'ü', 'A', 'O', 'E', 'I', 'U'];
  326. let cons = con;
  327. if (number) {
  328. for (let i = 0; i < zmList.length; i++) {
  329. let zm = zmList[i];
  330. if (con.indexOf(zm) > -1) {
  331. let zm2 = this.tone_data[i][number - 1];
  332. if (con.indexOf('iu') > -1) {
  333. zm2 = this.tone_data[4][number - 1];
  334. cons = con.replace('u', zm2);
  335. } else if (con.indexOf('ui') > -1) {
  336. zm2 = this.tone_data[3][number - 1];
  337. cons = con.replace('i', zm2);
  338. } else if (
  339. con.indexOf('yv') > -1 ||
  340. con.indexOf('jv') > -1 ||
  341. con.indexOf('qv') > -1 ||
  342. con.indexOf('xv') > -1
  343. ) {
  344. zm2 = this.tone_data[4][number - 1];
  345. cons = con.replace('v', zm2);
  346. } else {
  347. cons = con.replace(zm, zm2);
  348. }
  349. break;
  350. }
  351. }
  352. }
  353. return cons;
  354. },
  355. handleSelectItemTone(i, indexc, indexi, itemi) {
  356. this.con_preview[i].item_active_index = indexc;
  357. this.con_preview[i].user_answer[indexc].select_index = indexi;
  358. this.active_index_str = `${i}-${indexc}-${indexi}`;
  359. this.active_letter = itemi;
  360. this.active_letter_index = indexi;
  361. this.select_item_index = i;
  362. },
  363. // 判断对错
  364. judgeRight() {
  365. this.con_preview = [];
  366. this.show_preview = false;
  367. this.data.option_list.forEach((item, index) => {
  368. let con_arr = JSON.parse(JSON.stringify(item.content_view));
  369. let user_answer = [];
  370. let user_select = [];
  371. let user_res_arr = [];
  372. con_arr.forEach((items, indexs) => {
  373. user_answer.push({
  374. select_tone: this.answer.answer_list[index].value[indexs],
  375. select_letter: '',
  376. select_index: '',
  377. is_right:
  378. this.answer.answer_list[index].value[indexs] === this.data.answer.answer_list[index].value[indexs],
  379. right_answer: this.data.answer.answer_list[index].value[indexs],
  380. });
  381. user_res_arr.push([]);
  382. user_select.push('');
  383. if (this.data.property.answer_mode === 'label') {
  384. this.handleReplaceTone(
  385. this.answer.answer_list[index].value[indexs],
  386. user_select,
  387. indexs,
  388. user_res_arr[indexs],
  389. );
  390. if (
  391. this.answer.answer_list[index].value[indexs].match(/\d+/g) &&
  392. this.answer.answer_list[index].value[indexs].match(/\d+/g).length > 0
  393. ) {
  394. user_answer[indexs].select_tone = this.answer.answer_list[index].value[indexs].match(/\d+/g)[0];
  395. let letter_number = this.answer.answer_list[index].value[indexs].match(/\d+/g)[0];
  396. let letter_index = this.answer.answer_list[index].value[indexs].indexOf(letter_number) - 1;
  397. user_answer[indexs].select_letter = this.answer.answer_list[index].value[indexs].substring(
  398. letter_index,
  399. letter_index + 1,
  400. );
  401. user_answer[indexs].select_index_submit = letter_index;
  402. } else {
  403. user_select[indexs] = items;
  404. }
  405. user_answer[indexs].right_answer = this.data.answer.answer_list[index].value[indexs].match(/0|1|2|3|4/)
  406. ? this.data.answer.answer_list[index].value[indexs].match(/0|1|2|3|4/)[0]
  407. : '';
  408. user_answer[indexs].right_index = this.data.answer.answer_list[index].value[indexs].search(/0|1|2|3|4/) - 1;
  409. } else {
  410. this.handleReplaceTone(
  411. items + this.answer.answer_list[index].value[indexs],
  412. user_select,
  413. indexs,
  414. user_res_arr[indexs],
  415. );
  416. }
  417. });
  418. let obj = {
  419. item_con: user_select,
  420. item_con_yuan: JSON.parse(JSON.stringify(con_arr)),
  421. mark: item.mark,
  422. user_answer,
  423. item_active_index: 0,
  424. active_letter: '',
  425. user_res_arr,
  426. all_right:
  427. JSON.stringify(this.answer.answer_list[index].value) ===
  428. JSON.stringify(this.data.answer.answer_list[index].value),
  429. };
  430. this.con_preview.push(obj);
  431. });
  432. setTimeout(() => {
  433. this.show_preview = true;
  434. }, 100);
  435. },
  436. },
  437. };
  438. </script>
  439. <style lang="scss" scoped>
  440. @use '@/styles/mixin.scss' as *;
  441. .choosetone-preview {
  442. @include preview;
  443. position: relative;
  444. min-height: 450px;
  445. .option-list {
  446. .option-item {
  447. display: flex;
  448. column-gap: 16px;
  449. align-items: center;
  450. width: 100%;
  451. margin-bottom: 16px;
  452. .option-content {
  453. padding: 10px 22px;
  454. color: #706f78;
  455. background-color: $content-color;
  456. border: 1px solid $content-color;
  457. border-radius: 40px;
  458. &.all-right {
  459. background-color: $right-bc-color;
  460. border-color: $right-bc-color;
  461. }
  462. &.has-error {
  463. border-color: $error-color;
  464. }
  465. }
  466. .items-hz {
  467. margin-right: 4px;
  468. font-size: 16px;
  469. font-weight: 500;
  470. line-height: 24px;
  471. color: #000;
  472. }
  473. .item-con,
  474. .items-con {
  475. font-family: 'League';
  476. font-weight: 500;
  477. color: #000;
  478. cursor: pointer;
  479. &.right {
  480. color: $right-color;
  481. }
  482. &.error {
  483. color: $error-color;
  484. }
  485. &.active {
  486. color: #2f6fec;
  487. }
  488. }
  489. .items-box {
  490. margin-right: 3px;
  491. }
  492. .tone {
  493. width: 32px;
  494. height: 32px;
  495. padding: 8px;
  496. font-size: 0;
  497. color: #9f9f9f;
  498. text-align: center;
  499. cursor: pointer;
  500. &.right {
  501. color: $right-color;
  502. background-color: $right-bc-color;
  503. border-radius: 16px;
  504. }
  505. &.active {
  506. color: #2f6fec;
  507. background: #dfe9fd;
  508. border-radius: 16px;
  509. }
  510. }
  511. }
  512. }
  513. .answer-tips-box {
  514. position: absolute;
  515. top: 24px;
  516. right: 24px;
  517. display: flex;
  518. .answer-tips {
  519. width: 307px;
  520. margin-top: 32px;
  521. }
  522. .tips-btn {
  523. width: 34px;
  524. height: 34px;
  525. padding: 5px;
  526. font-size: 0;
  527. background: #fff5e3;
  528. border: 1px solid rgba(0, 0, 0, 8%);
  529. border-radius: 8px;
  530. &-active {
  531. background: #e3d5b3;
  532. }
  533. img {
  534. width: 22px;
  535. height: 22px;
  536. cursor: pointer;
  537. }
  538. }
  539. }
  540. }
  541. </style>