ChooseTonePreview.vue 15 KB

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