matching-question.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. <template>
  2. <!-- 连线题 -->
  3. <view class="matching-area" v-model="questionData">
  4. <view class="question_title">
  5. <text class="question_number">
  6. {{ questionNumberEndIsBracket(questionData.property.question_number) }}
  7. </text>
  8. <text class="question_stem" v-html="sanitizeHTML(questionData.stem)"
  9. :ref="'richText-1-1'+questionData.question_id"
  10. @longpress="previewByRichTextImg(-1,-1,questionData.question_id)"></text>
  11. </view>
  12. <view class="description"
  13. v-if="isEnable(questionData.property.is_enable_description)&&questionData.description.length > 0"
  14. v-html="sanitizeHTML(questionData.description)" :ref="'richText-2-2'+questionData.question_id"
  15. @longpress="previewByRichTextImg(-2,-2,questionData.question_id)">
  16. </view>
  17. <view ref="list" class="option-box">
  18. <view class="option-row" cellpadding="40" v-for="(item,i) in optionList" :key="i">
  19. <view class="option-serial">
  20. <text class="serial-number" :style="{'font-size':questionData.property.option_question_number_font_size}">
  21. {{ isEnable(isEnableManualModify)?(item[0].custom_number?item[0].custom_number+'.':''):computedQuestionNumber(i,questionData.option_number_show_mode) }}
  22. </text>
  23. </view>
  24. <view v-for="({content,mark}, j) in item" :key="mark" :ref="'itemView'+i+j"
  25. :class="['item-wrapper', `item-${mark}`, computedAnswerClass(mark)]"
  26. @click="handleClickConnection($event, i, j, mark)">
  27. <text v-html="sanitizeHTML(content)" class="content" :ref="'richText'+i+j+questionData.question_id"
  28. @longpress="previewByRichTextImg(i,j,questionData.question_id)"></text>
  29. </view>
  30. </view>
  31. </view>
  32. <view v-if="answer_control[questionData.question_id].isViewRightAnswer" :class=[circulateAnswerList()]>
  33. <uni-title type="h1" title="正确答案:" style="margin-bottom:32rpx;"></uni-title>
  34. <view ref="answer-list" class="option-box">
  35. <view v-for="(item, i) in optionList" :key="i" class="option-row">
  36. <view v-for="{ content, mark } in item" :key="mark" :class="['item-wrapper', `answer-item-${mark}`]">
  37. <text class="content" v-html="sanitizeHTML(content)"></text>
  38. </view>
  39. </view>
  40. </view>
  41. </view>
  42. <view class="reference" v-if="isViewAnalysis&&answer_control[questionData.question_id].isViewRightAnswer">
  43. <text class="reference-title">解析</text>
  44. <text class="reference-answer" v-html="sanitizeHTML(questionData.analysis)"
  45. :ref="'richText-3-3'+questionData.question_id"
  46. @longpress="previewByRichTextImg(-3,-3,questionData.question_id)">
  47. </text>
  48. </view>
  49. </view>
  50. </template>
  51. <script>
  52. let landscapeOb;
  53. import {
  54. questionData,
  55. svgNS,
  56. sanitizeHTML,
  57. isEnable,
  58. answer_control,
  59. computedQuestionNumber
  60. } from '@/pages/answer_question/common/data/common.js';
  61. import AnswerControlMixin from '@/pages/answer_question/common/data/AnswerControlMixin.js';
  62. export default {
  63. name: "matching-question",
  64. mixins: [AnswerControlMixin],
  65. props: {
  66. questionData: questionData,
  67. isEnableManualModify: {
  68. type: String,
  69. default: 'false',
  70. },
  71. },
  72. data() {
  73. return {
  74. sanitizeHTML,
  75. isEnable,
  76. answer_control,
  77. computedQuestionNumber,
  78. answerList: [], // 答案列表
  79. curConnectionPoint: {
  80. i: -1,
  81. j: -1,
  82. mark: ''
  83. }, // 当前连线点
  84. mediaQueryOb: null,
  85. lineColorArr: ['#E92E2E', '#1F79F6', '#FA56BB', '#FE9244', '#17B58F', '#FFD85A', '#52C52A', '#000000'],
  86. };
  87. },
  88. created() {
  89. // console.log('进入连线', this.questionData);
  90. },
  91. computed: {
  92. optionList() {
  93. this.setUserAnswer();
  94. var that = this;
  95. var first_option_list = {};
  96. var cunList = [];
  97. var svg_html = "";
  98. var storageKey = that.questionData.answer_record_id + '_' + that.questionData.share_record_id;
  99. uni.getStorage({
  100. key: storageKey,
  101. success: function(res) {
  102. first_option_list = res.data;
  103. var currentData = first_option_list[that.questionData.question_id] || {};
  104. cunList = currentData.option_list;
  105. }
  106. })
  107. if (cunList && cunList.length > 0)
  108. return cunList;
  109. // 生成一个与选项列表相同的二维数组,但没有内容
  110. let arr = Array.from(Array(this.questionData.option_list.length), () => Array(this.questionData.option_list[0]
  111. .length).fill({}));
  112. for (let i = 0; i < this.questionData.property.column_number; i++) {
  113. let rowNumList = Array.from(Array(this.questionData.option_list.length).keys()); // 行数列表
  114. for (let j = 0; j < this.questionData.option_list.length; j++) {
  115. if (!this.questionData.option_list[j][i].lineColor && i == 1) {
  116. this.questionData.option_list[j][i].lineColor = this.lineColorArr[j];
  117. }
  118. let random = Math.floor(Math.random() * rowNumList.length); // 随机行数
  119. let item = this.questionData.option_list[j][i]; // 当前选项
  120. arr[rowNumList[random]][i] = item; // 将当前选项添加到随机行数的列中
  121. rowNumList.splice(random, 1); // 删除已经添加过选项的行数
  122. }
  123. }
  124. first_option_list[that.questionData.question_id] = {
  125. option_list: arr
  126. };
  127. //第一次进入题目,将随机选项存到缓存,uni获取缓存为异步,需注意
  128. uni.setStorage({
  129. key: storageKey,
  130. data: first_option_list
  131. });
  132. return arr;
  133. },
  134. isViewAnalysis: function() {
  135. return isEnable(this.questionData.property.is_enable_analysis);
  136. }
  137. },
  138. watch: {
  139. 'questionData.question_id': {
  140. handler(val) {
  141. this.commonComputedAnswerControl(val);
  142. if (!this.questionData.parentType && this.questionData.parentType != 'read')
  143. this.clearLine();
  144. },
  145. immediate: true,
  146. deep: true
  147. },
  148. optionList: {
  149. handler() {
  150. let list = this.optionList.map((item) => {
  151. return item.map(({
  152. mark
  153. }) => {
  154. return {
  155. mark,
  156. preMark: '',
  157. nextMark: ''
  158. };
  159. });
  160. });
  161. this.$set(this, 'answerList', list);
  162. },
  163. immediate: true,
  164. },
  165. answerList: {
  166. handler(list) {
  167. let _userAnswerList = this.questionData.user_answer[this.questionData.question_id].answer_list;
  168. let arr = [];
  169. let column_number = this.questionData.property.column_number;
  170. // 从前往后遍历,如果有 nextMark,就往后找,直到没有 nextMark
  171. // 如果第一个选项的 nextMark 为空,则整个行都为空,等待后面的 preArr 填充
  172. list.forEach((item, i) => {
  173. let {
  174. mark,
  175. nextMark
  176. } = item[0];
  177. arr[i] = nextMark ? [mark, nextMark] : new Array(column_number).fill('');
  178. let _nextMark = nextMark;
  179. while (_nextMark) {
  180. let fMark = this.findMark(list, _nextMark, 'next');
  181. if (column_number > arr[i].length) {
  182. arr[i].push(fMark);
  183. }
  184. _nextMark = fMark;
  185. }
  186. });
  187. let emptyStringArray = []; // 无数据的列的下标数组
  188. arr.forEach((item, i) => {
  189. item.every((li) => li.length <= 0) ? emptyStringArray.push(i) : '';
  190. });
  191. if (column_number === 2) {
  192. _userAnswerList = arr;
  193. // this.saveUserAnswer();
  194. return;
  195. }
  196. // 从后向前遍历,如果有 preMark,就往前找,直到没有 preMark,并过滤掉无数据的列
  197. let preArr = [];
  198. list.forEach((item) => {
  199. let {
  200. mark,
  201. preMark
  202. } = item[column_number - 1];
  203. let _arr = new Array(column_number).fill('');
  204. let back = column_number - 1;
  205. let _preMark = preMark;
  206. while (_preMark) {
  207. _arr[back] = mark;
  208. back -= 1;
  209. _arr[back] = _preMark;
  210. back -= 1;
  211. let fMark = this.findMark(list, _preMark, 'pre');
  212. if (back >= 0 && fMark) {
  213. _arr[back] = fMark;
  214. }
  215. _preMark = fMark;
  216. }
  217. if (_arr.every((li) => li.length <= 0)) return;
  218. preArr.push(_arr);
  219. });
  220. // 将 preArr 中的数据填充到 arr 无数据的位置中
  221. if (preArr.length > 0 && emptyStringArray.length > 0) {
  222. preArr.forEach((item, i) => {
  223. arr[emptyStringArray[i]] = item;
  224. });
  225. }
  226. _userAnswerList = arr;
  227. //this.saveUserAnswer();
  228. },
  229. deep: true,
  230. },
  231. },
  232. mounted() {
  233. document.addEventListener('click', this.handleEventConnection);
  234. this.testMediaQueryObserver();
  235. this.landscapeObserver();
  236. },
  237. beforeDestroy() {
  238. document.removeEventListener('click', this.handleEventConnection);
  239. },
  240. methods: {
  241. testMediaQueryObserver() {
  242. this.mediaQueryOb = uni.createMediaQueryObserver(this)
  243. this.mediaQueryOb.observe({
  244. minWidth: 750, //页面最小宽度 375px
  245. // maxWidth: 1200 //页面宽度最大 500px
  246. }, matches => {
  247. this.findMarkCreateLine(this.questionData.user_answer[this.questionData.question_id].answer_list, false);
  248. // var isViewRightAnswer = this.commonComputedAnswerControl(this.questionData.question_id).isViewRightAnswer;
  249. // this.findMarkCreateLine(this.questionData.answer.answer_list, isViewRightAnswer);
  250. })
  251. },
  252. landscapeObserver() {
  253. landscapeOb = uni.createMediaQueryObserver(this);
  254. landscapeOb.observe({
  255. orientation: 'landscape' //屏幕方向为纵向
  256. }, matches => {
  257. this.findMarkCreateLine(this.questionData.user_answer[this.questionData.question_id].answer_list, false);
  258. // var isViewRightAnswer = this.commonComputedAnswerControl(this.questionData.question_id).isViewRightAnswer;
  259. // this.findMarkCreateLine(this.questionData.answer.answer_list, isViewRightAnswer);
  260. })
  261. },
  262. destroyed() {
  263. this.mediaQueryOb.disconnect(); //组件销毁时停止监听
  264. landscapeOb.disconnect();
  265. },
  266. //重置连线点
  267. resetCurConnectionPoint() {
  268. this.curConnectionPoint = {
  269. i: -1,
  270. j: -1,
  271. mark: ''
  272. };
  273. },
  274. /**
  275. * 当点击的不是连线点时,清除所有连线点的选中状态
  276. * @param {PointerEvent} e
  277. */
  278. handleEventConnection(e) {
  279. let currentNode = e.target;
  280. while (currentNode !== null) {
  281. if (currentNode.classList && currentNode.classList.contains('item-wrapper')) {
  282. break;
  283. }
  284. currentNode = currentNode.parentNode;
  285. }
  286. if (currentNode) return;
  287. Array.from(document.getElementsByClassName('item-wrapper')).forEach((item) => {
  288. item.classList.remove('focus');
  289. });
  290. this.resetCurConnectionPoint();
  291. },
  292. /**
  293. * 处理点击连线
  294. * @param {PointerEvent} e 事件对象
  295. * @param {Number} i 选项列表索引
  296. * @param {Number} j 选项索引
  297. * @param {String} mark 选项标识
  298. */
  299. handleClickConnection(e, i, j, mark) {
  300. if (this.answer_control[this.questionData.question_id].isReadOnly) return;
  301. let {
  302. i: curI,
  303. j: curJ,
  304. mark: curMark
  305. } = this.curConnectionPoint;
  306. // 获取 item-wrapper 元素
  307. let _ref = 'itemView' + i + j;
  308. let currentNode = this.$refs[_ref][0].$el;
  309. while (currentNode !== null) {
  310. if (currentNode.classList && currentNode.classList.contains('item-wrapper')) {
  311. break;
  312. }
  313. currentNode = currentNode.parentNode;
  314. }
  315. // 如果当前连线点不存在或就是当前连线点,则设置当前连线点
  316. if ((curI === -1 && curJ === -1) || mark === curMark) {
  317. this.curConnectionPoint = {
  318. i,
  319. j,
  320. mark
  321. };
  322. currentNode.classList.add('focus');
  323. return;
  324. }
  325. // 如果当前连线点存在,清除所有连线点的选中状态
  326. Array.from(document.querySelectorAll('.item-wrapper')).forEach((item) => {
  327. item.classList.remove('focus');
  328. });
  329. // 如果当前连线点与上一个连线点不在相邻的位置,则设置点击的连接点为当前连线点
  330. if (Math.abs(curJ - j) > 1 || mark === curMark || (curJ === j && curI !== i)) {
  331. this.curConnectionPoint = {
  332. i,
  333. j,
  334. mark
  335. };
  336. currentNode.classList.add('focus');
  337. return;
  338. }
  339. this.changeConnectionList(mark, j);
  340. // 如果当前连线点与上一个连线点在相邻的位置,则创建连接线
  341. this.createLine(mark);
  342. },
  343. // 循环答案列表
  344. circulateAnswerList() {
  345. var isViewRightAnswer = this.commonComputedAnswerControl(this.questionData.question_id).isViewRightAnswer;
  346. let answer_list = isViewRightAnswer ?
  347. this.questionData.answer.answer_list :
  348. this.questionData.user_answer[this.questionData.question_id].answer_list;
  349. var has = true;
  350. if (this.$refs['answer-list']) {
  351. if (this.$refs['answer-list'].$el.childNodes.length >= this.optionList.length * 2)
  352. has = false;
  353. }
  354. if (has)
  355. this.findMarkCreateLine(answer_list, true);
  356. },
  357. /**
  358. * 创建连接线
  359. * @param {String} mark 选项标识
  360. */
  361. createLine(mark) {
  362. var cur = this.commonComputedAnswerControl(this.questionData.question_id);
  363. let {
  364. offsetWidth,
  365. offsetLeft,
  366. offsetTop,
  367. offsetHeight
  368. } = document.getElementsByClassName(
  369. `item-${mark}`
  370. )[0];
  371. // `${cur.isViewRightAnswer ? 'answer-' : ''}item-${mark}`,
  372. const {
  373. curOffsetWidth,
  374. curOffsetLeft,
  375. curOffsetTop,
  376. curOffsetHeight,
  377. curMark
  378. } = this.computedCurConnectionPoint();
  379. let top = Math.min(offsetTop + offsetHeight / 2, curOffsetTop + curOffsetHeight);
  380. let left = Math.min(offsetLeft + offsetWidth, curOffsetLeft + curOffsetWidth);
  381. let width = Math.abs(
  382. offsetLeft > curOffsetLeft ?
  383. curOffsetLeft - offsetLeft + offsetWidth :
  384. offsetLeft - curOffsetLeft + curOffsetWidth,
  385. );
  386. let height = Math.abs((offsetTop + offsetHeight / 2) - (curOffsetTop + curOffsetHeight));
  387. // 判断是左上还是右下
  388. let size = offsetLeft > curOffsetLeft ? offsetTop > curOffsetTop : offsetTop < curOffsetTop;
  389. // 创建一个空的SVG元素
  390. let svg = document.createElementNS(svgNS, 'svg');
  391. svg.setAttribute(
  392. 'style',
  393. `position:absolute; width: 57px; height: ${Math.max(8, height)}px; top: ${top}px; left: ${left-1}px;`,
  394. );
  395. svg.classList.add('connection-line', `svg-${mark}-${curMark}`); // 添加类名
  396. // 向SVG元素添加 path 元素
  397. let path = document.createElementNS(svgNS, 'path');
  398. path.setAttribute('d', `M ${size ? 0 : 57} 0 L ${size ? 57 : 0} ${height}`); // 设置路径数据
  399. this.setPathAttr(path, mark, curMark);
  400. svg.appendChild(path);
  401. // this.$refs[cur.isViewRightAnswer ? 'answer-list' : 'list'].$el.appendChild(svg); // 将SVG元素插入到文档中
  402. this.$refs.list.$el.appendChild(svg);
  403. // 清除当前连线点
  404. this.resetCurConnectionPoint();
  405. //连线成功,记录答案
  406. this.saveUserAnswer();
  407. },
  408. //创建连线
  409. createLine_new(preMark, mark, predom, curdom, isStandardAnswer) {
  410. var preName = isStandardAnswer ? 'answer-' : '';
  411. if (!curdom) return;
  412. let {
  413. offsetWidth,
  414. offsetLeft,
  415. offsetTop,
  416. offsetHeight
  417. } = curdom;
  418. let curOffsetWidth = predom.offsetWidth;
  419. let curOffsetHeight = predom.offsetHeight;
  420. let curOffsetLeft = predom.offsetLeft;
  421. let curOffsetTop = predom.offsetTop;
  422. let top = Math.min(offsetTop + offsetHeight / 2, curOffsetTop + curOffsetHeight / 2);
  423. let left = Math.min(offsetLeft + offsetWidth, curOffsetLeft + curOffsetWidth);
  424. let width = Math.abs(
  425. offsetLeft > curOffsetLeft ?
  426. curOffsetLeft - offsetLeft + offsetWidth :
  427. offsetLeft - curOffsetLeft + curOffsetWidth,
  428. );
  429. let height = Math.abs((offsetTop + offsetHeight / 2) - (curOffsetTop + curOffsetHeight / 2));
  430. // 判断是左上还是右下
  431. let size = offsetLeft > curOffsetLeft ? offsetTop > curOffsetTop : offsetTop < curOffsetTop;
  432. // 创建一个空的SVG元素position:absolute定位后需根据元素自身定位计算,目前间隔固定为56PX
  433. let svg = document.createElementNS(svgNS, 'svg');
  434. svg.setAttribute(
  435. 'style',
  436. `position:absolute; width: 57px; height: ${Math.max(8, height)}px; top: ${top}px; left: ${left-1}px;`,
  437. );
  438. svg.classList.add('connection-line', `svg-${mark}-${preMark}`); // 添加类名
  439. // 向SVG元素添加 path 元素
  440. let path = document.createElementNS(svgNS, 'path');
  441. path.setAttribute('d', `M ${size ? 0 : 57} 0 L ${size ? 57 : 0} ${height}`); // 设置路径数据
  442. this.setPathAttr(path, mark, preMark);
  443. svg.appendChild(path);
  444. this.$refs[preName + 'list'].$el.appendChild(svg); // 将SVG元素插入到文档中
  445. },
  446. // 设置 path 公用属性
  447. setPathAttr(path, mark, preMark) {
  448. var color = '#306eff';
  449. var data = this.optionList.find(p => p[1].mark == mark || p[1].mark == preMark);
  450. if (data && data[1].lineColor)
  451. color = data[1].lineColor
  452. path.setAttribute('stroke-width', '2'); // 设置线条宽度
  453. path.setAttribute('stroke-linecap', 'round'); // 设置线段的两端样式
  454. path.setAttribute('stroke', color); // 设置填充颜色
  455. path.setAttribute('transform', `translate(0, 2)`); // 设置偏移量
  456. },
  457. /**
  458. * 计算当前连线点的位置
  459. * @param {Boolean} isShowRightAnswer 是否是显示正确答案
  460. */
  461. computedCurConnectionPoint() {
  462. var cur = this.commonComputedAnswerControl(this.questionData.question_id);
  463. if (cur.isReadOnly) return;
  464. var isViewRightAnswer = cur.isViewRightAnswer;
  465. const {
  466. mark
  467. } = this.curConnectionPoint;
  468. // let dom = document.getElementsByClassName(`${isViewRightAnswer ? 'answer-' : ''}item-${mark}`)[0];
  469. let dom = document.getElementsByClassName(`item-${mark}`)[0];
  470. return {
  471. curOffsetWidth: dom.offsetWidth,
  472. curOffsetLeft: dom.offsetLeft,
  473. curOffsetTop: dom.offsetTop,
  474. curOffsetHeight: dom.offsetHeight / 2,
  475. curMark: mark,
  476. };
  477. },
  478. // 清除所有连接线
  479. clearLine() {
  480. document.querySelectorAll('svg.connection-line').forEach((item) => {
  481. item.remove();
  482. });
  483. },
  484. /**
  485. * 修改连接列表
  486. * @param {String} mark 选项标识
  487. */
  488. changeConnectionList(mark, j) {
  489. const {
  490. mark: curMark,
  491. j: curJ
  492. } = this.curConnectionPoint;
  493. this.changeAnswerList(curMark, mark, curJ < j);
  494. this.changeAnswerList(mark, curMark, curJ > j);
  495. },
  496. getOption(arr, key, val) {
  497. if (!arr || arr.length == 0) return [];
  498. var en = arr.find(p => p[key] == val);
  499. return en;
  500. },
  501. /**
  502. * 根据 mark 查找 nextMark 或 preMark
  503. * @param {Array} list 答案列表
  504. * @param {String} mark 标记
  505. * @param {'pre'|'next'} type 类型
  506. * @returns {String} 返回 nextMark 或 preMark
  507. */
  508. findMark(list, mark, type) {
  509. let fMark = '';
  510. list.find((item) => {
  511. return item.find((li) => {
  512. if (mark === li.mark) {
  513. fMark = type === 'pre' ? li.preMark : li.nextMark;
  514. return true;
  515. }
  516. });
  517. });
  518. return fMark;
  519. },
  520. /**
  521. * 改变答案列表
  522. * @param {String} curMark 当前选项标识
  523. * @param {String} mark 选项标识
  524. */
  525. changeAnswerList(curMark, mark, isPre) {
  526. let oldPointer = {
  527. mark: '',
  528. position: ''
  529. };
  530. // 找到当前选项,修改 preMark 或 nextMark
  531. this.answerList.find((item) =>
  532. item.find((li) => {
  533. if (li.mark === curMark) {
  534. if (isPre) {
  535. if (li.nextMark) {
  536. oldPointer = {
  537. mark: li.nextMark,
  538. position: 'next'
  539. };
  540. }
  541. li.nextMark = mark;
  542. } else {
  543. if (li.preMark) {
  544. oldPointer = {
  545. mark: li.preMark,
  546. position: 'pre'
  547. };
  548. }
  549. li.preMark = mark;
  550. }
  551. return true;
  552. }
  553. }),
  554. );
  555. // 如果当前选项有 preMark 或 nextMark,则清除原来的连线
  556. if (!oldPointer.mark) return;
  557. document.querySelector(`svg.connection-line.svg-${curMark}-${oldPointer.mark}`)?.remove();
  558. document.querySelector(`svg.connection-line.svg-${oldPointer.mark}-${curMark}`)?.remove();
  559. // 找到原来的选项,清除 preMark 或 nextMark
  560. this.answerList.find((item) =>
  561. item.find((li) => {
  562. if (li.mark === oldPointer.mark) {
  563. if (oldPointer.position === 'pre') {
  564. li.nextMark = '';
  565. } else {
  566. li.preMark = '';
  567. }
  568. return true;
  569. }
  570. }),
  571. );
  572. },
  573. computedAnswerClass(mark) {
  574. var cur = this.commonComputedAnswerControl(this.questionData.question_id);
  575. if (!cur.isJudgeAnswer) return '';
  576. let answer = this.questionData.answer.answer_list.find((item) => {
  577. return item.some((li) => li === mark);
  578. });
  579. let user_answer = this.questionData.user_answer[this.questionData.question_id].answer_list;
  580. return this.is2DArrayContains1DArray(user_answer, answer) ? 'right' : 'wrong';
  581. },
  582. is2DArrayContains1DArray(arr2D, arr1D) {
  583. for (let i = 0; i < arr2D.length; i++) {
  584. const currentArray = arr2D[i];
  585. if (currentArray.length !== arr1D.length) {
  586. continue;
  587. }
  588. let found = true;
  589. for (let j = 0; j < currentArray.length; j++) {
  590. if (currentArray[j] !== arr1D[j]) {
  591. found = false;
  592. break;
  593. }
  594. }
  595. if (found) {
  596. return true;
  597. }
  598. }
  599. return false;
  600. },
  601. //切换题目保存答案
  602. saveUserAnswer() {
  603. //保存答案给后台
  604. var that = this;
  605. var questionId = this.questionData.question_id;
  606. var answer_list = this.questionData.answer.answer_list || [];
  607. var userAnswer = [];
  608. var is_fill_answer = false;
  609. answer_list.forEach(p => {
  610. var a1 = "";
  611. var a2 = p[1];
  612. var a3 = "";
  613. that.answerList.map(x => {
  614. var en = that.getOption(x, 'mark', a2);
  615. if (en) {
  616. a1 = en.preMark;
  617. a3 = en.nextMark;
  618. is_fill_answer = is_fill_answer || (a1 || a3);
  619. return false;
  620. }
  621. })
  622. if (p.length == 3)
  623. userAnswer.push([a1, a2, a3])
  624. else
  625. userAnswer.push([a1, a2])
  626. })
  627. this.questionData.user_answer[questionId].isEdit = true;
  628. this.questionData.user_answer[questionId].is_fill_answer = is_fill_answer;
  629. this.questionData.user_answer[questionId].content = JSON.stringify(userAnswer);
  630. this.questionData.user_answer[questionId].answer_list = userAnswer;
  631. this.$emit("setSubAnswer", this.questionData.user_answer[questionId], questionId);
  632. },
  633. //回显用户答案
  634. setUserAnswer: function() {
  635. var that = this;
  636. var callback = function() {
  637. var userAnswer = [];
  638. var questionId = that.questionData.question_id;
  639. var _ua = that.questionData.user_answer[questionId];
  640. if (_ua && _ua.answer_list && _ua.answer_list.length > 0)
  641. userAnswer = _ua.answer_list;
  642. userAnswer = userAnswer.filter(p => p.length > 1);
  643. that.findMarkCreateLine(userAnswer, false);
  644. that.circulateAnswerList();
  645. };
  646. if (that.questionData.isSubSub) {
  647. that.$nextTick(() => {
  648. callback();
  649. })
  650. } else {
  651. this.$emit("getUserAnswer", this.questionData.question_id, callback);
  652. }
  653. },
  654. /**
  655. * @param {String} 标识集合
  656. * @param {Boolean} 是否标准答案
  657. */
  658. findMarkCreateLine(markList, isStandardAnswer) {
  659. var that = this;
  660. var itemPre = isStandardAnswer ? 'answer-item-' : 'item-';
  661. this.$nextTick(() => {
  662. markList.forEach(p => {
  663. let a0 = p[0];
  664. let a1 = p[1];
  665. let a2 = p[2];
  666. if (!a1) return false;
  667. if (a0) {
  668. let preId = itemPre + a0;
  669. let nextId = itemPre + a1;
  670. if (!isStandardAnswer) {
  671. that.changeAnswerList(a1, a0, false);
  672. that.changeAnswerList(a0, a1, true);
  673. }
  674. var predom = document.getElementsByClassName(preId)[0];
  675. var curdom = document.getElementsByClassName(nextId)[0];
  676. that.createLine_new(a0, a1, predom, curdom, isStandardAnswer);
  677. }
  678. if (a2) {
  679. let preId = itemPre + a1;
  680. let nextId = itemPre + a2;
  681. if (!isStandardAnswer) {
  682. that.changeAnswerList(a2, a1, false);
  683. that.changeAnswerList(a1, a2, true);
  684. }
  685. var predom = document.getElementsByClassName(preId)[0];
  686. var curdom = document.getElementsByClassName(nextId)[0];
  687. that.createLine_new(a1, a2, predom, curdom, isStandardAnswer);
  688. }
  689. })
  690. setTimeout(function() {
  691. that.$forceUpdate();
  692. }, 10);
  693. })
  694. },
  695. }
  696. }
  697. </script>
  698. <style lang="scss" scoped>
  699. .matching-area {
  700. .option-box {
  701. position: relative;
  702. display: flex;
  703. flex-direction: column;
  704. row-gap: 32rpx;
  705. margin-bottom: 32rpx;
  706. /deep/ img {
  707. width: auto;
  708. height: auto;
  709. min-width: 100px;
  710. max-width: 100%;
  711. max-height: 100%;
  712. }
  713. .option-row {
  714. display: flex;
  715. column-gap: 56px;
  716. .option-serial {
  717. width: 16px;
  718. margin-right: -46px;
  719. }
  720. .item-wrapper {
  721. display: flex;
  722. flex: 1;
  723. align-items: center;
  724. column-gap: 16rpx;
  725. padding: 8px 30px 8px 8px;
  726. border-radius: 16rpx;
  727. background-color: $uni-bg-color-grey;
  728. .content {
  729. flex: 1;
  730. word-break: break-all;
  731. font-size: $font-size-serial;
  732. }
  733. &.focus {
  734. background-color: #dcdbdd;
  735. }
  736. &.right {
  737. background-color: $right-bc-color;
  738. border: 1px solid $right-bc-color;
  739. }
  740. &.wrong {
  741. border: 1px solid $error-color;
  742. }
  743. }
  744. }
  745. }
  746. .reference {
  747. margin: 32rpx 0;
  748. background-color: #f9f8f9;
  749. padding: 24rpx;
  750. .reference-title {
  751. display: block;
  752. line-height: 64rpx;
  753. color: #4E5969;
  754. font-size: 28rpx;
  755. }
  756. .reference-answer {
  757. color: #1D2129;
  758. line-height: 48rpx;
  759. font-size: 14pt;
  760. }
  761. }
  762. }
  763. </style>