FillPreview.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <!-- eslint-disable vue/no-v-html -->
  2. <template>
  3. <div class="select-preview" :style="getAreaStyle()">
  4. <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
  5. <div class="main" :style="getMainStyle()">
  6. <AudioFill :file-id="data.audio_file_id" />
  7. <div class="fill-wrapper">
  8. <p v-for="(item, i) in data.model_essay" :key="i">
  9. <template v-for="(li, j) in item">
  10. <span v-if="li.type === 'text'" :key="j" v-html="sanitizeHTML(li.content)"></span>
  11. <template v-if="li.type === 'input'">
  12. <el-input
  13. :key="j"
  14. v-model="li.content"
  15. :disabled="disabled"
  16. :class="[data.property.fill_font, ...computedAnswerClass(li.mark)]"
  17. :style="[{ width: Math.max(80, li.content.length * 21.3) + 'px' }]"
  18. />
  19. <span v-show="computedAnswerText(li.mark).length > 0" :key="`answer-${j}`" class="right-answer">
  20. {{ computedAnswerText(li.mark) }}
  21. </span>
  22. </template>
  23. </template>
  24. </p>
  25. </div>
  26. <SoundRecord
  27. v-if="isEnable(data.property.is_enable_voice_answer)"
  28. ref="record"
  29. type="normal"
  30. class="record-box"
  31. :answer-record-list="data.record_list"
  32. :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
  33. @handleWav="handleWav"
  34. />
  35. </div>
  36. </div>
  37. </template>
  38. <script>
  39. import { getFillData, fillFontList, arrangeTypeList, audioPositionList } from '@/views/book/courseware/data/fill';
  40. import PreviewMixin from '../common/PreviewMixin';
  41. import AudioFill from './components/AudioFillPlay.vue';
  42. import SoundRecord from '../../common/SoundRecord.vue';
  43. export default {
  44. name: 'FillPreview',
  45. components: {
  46. AudioFill,
  47. SoundRecord,
  48. },
  49. mixins: [PreviewMixin],
  50. data() {
  51. return {
  52. data: getFillData(),
  53. };
  54. },
  55. computed: {
  56. fontFamily() {
  57. return fillFontList.find(({ value }) => this.data.property.fill_font === value).font;
  58. },
  59. },
  60. watch: {
  61. 'data.model_essay': {
  62. handler(list) {
  63. this.answer.answer_list = list
  64. .map((item) => {
  65. return item
  66. .map(({ type, content, mark }) => {
  67. if (type === 'input') {
  68. return {
  69. value: content,
  70. mark,
  71. };
  72. }
  73. })
  74. .filter((item) => item);
  75. })
  76. .flat();
  77. },
  78. deep: true,
  79. },
  80. isJudgingRightWrong(val) {
  81. if (!val) return;
  82. this.answer.answer_list.forEach(({ mark, value }) => {
  83. this.data.model_essay.forEach((item) => {
  84. item.forEach((li) => {
  85. if (li.mark === mark) {
  86. li.content = value;
  87. }
  88. });
  89. });
  90. });
  91. this.handleWav(this.answer.record_list);
  92. },
  93. 'data.record_list'(val) {
  94. this.answer.record_list = val;
  95. },
  96. },
  97. created() {
  98. this.answer.answer_list = this.data.model_essay
  99. .map((item) => {
  100. return item
  101. .map(({ type, content, mark }) => {
  102. if (type === 'input') {
  103. return {
  104. value: content,
  105. mark,
  106. };
  107. }
  108. })
  109. .filter((item) => item);
  110. })
  111. .flat();
  112. },
  113. methods: {
  114. handleWav(data) {
  115. this.data.record_list = data;
  116. },
  117. getMainStyle() {
  118. const isRow = this.data.property.arrange_type === arrangeTypeList[0].value;
  119. const isFront = this.data.property.audio_position === audioPositionList[0].value;
  120. const isEnableVoice = this.data.property.is_enable_voice_answer === 'true';
  121. let _list = [
  122. { name: 'audio', value: '24px' },
  123. { name: 'fill', value: '1fr' },
  124. ];
  125. if (!isFront) {
  126. _list = _list.reverse();
  127. }
  128. let grid = isRow
  129. ? `"${_list[0].name} ${_list[1].name}${isEnableVoice ? ' record' : ''}" auto / ${_list[0].value} ${_list[1].value}${isEnableVoice ? ' 160px' : ''}`
  130. : `"${_list[0].name}" ${_list[0].value} "${_list[1].name}" ${_list[1].value}${isEnableVoice ? `" record" 32px ` : ''} / 1fr`;
  131. let style = {
  132. 'grid-auto-flow': isRow ? 'column' : 'row',
  133. 'column-gap': isRow ? '16px' : undefined,
  134. 'row-gap': isRow ? undefined : '8px',
  135. grid,
  136. };
  137. return style;
  138. },
  139. /**
  140. * 计算答题对错选项字体颜色
  141. * @param {string} mark 选项标识
  142. */
  143. computedAnswerClass(mark) {
  144. if (!this.isJudgingRightWrong && !this.isShowRightAnswer) {
  145. return '';
  146. }
  147. let selectOption = this.answer.answer_list.find((item) => item.mark === mark);
  148. let answerOption = this.data.answer.answer_list.find((item) => item.mark === mark);
  149. if (!selectOption) return '';
  150. let selectValue = selectOption.value;
  151. let answerValue = answerOption.value;
  152. let classList = [];
  153. let isRight = selectValue === answerValue;
  154. if (this.isJudgingRightWrong) {
  155. isRight ? classList.push('right') : classList.push('wrong');
  156. }
  157. if (this.isShowRightAnswer && !isRight) {
  158. classList.push('show-right-answer');
  159. }
  160. return classList;
  161. },
  162. /**
  163. * 计算正确答案文本
  164. * @param {string} mark 选项标识
  165. */
  166. computedAnswerText(mark) {
  167. if (!this.isShowRightAnswer) return '';
  168. let selectOption = this.answer.answer_list.find((item) => item.mark === mark);
  169. let answerOption = this.data.answer.answer_list.find((item) => item.mark === mark);
  170. if (!selectOption) return '';
  171. let selectValue = selectOption.value;
  172. let answerValue = answerOption.value;
  173. let isRight = selectValue === answerValue;
  174. if (isRight) return '';
  175. return `(${answerValue})`;
  176. },
  177. },
  178. };
  179. </script>
  180. <style lang="scss" scoped>
  181. @use '@/styles/mixin.scss' as *;
  182. .select-preview {
  183. @include preview-base;
  184. .main {
  185. display: grid;
  186. align-items: center;
  187. }
  188. .fill-wrapper {
  189. grid-area: fill;
  190. font-size: 16pt;
  191. p {
  192. margin: 0;
  193. }
  194. .el-input {
  195. display: inline-flex;
  196. align-items: center;
  197. width: 120px;
  198. margin: 0 2px;
  199. &.pinyin :deep input.el-input__inner {
  200. font-family: 'PINYIN-B', sans-serif;
  201. }
  202. &.chinese :deep input.el-input__inner {
  203. font-family: 'arial', sans-serif;
  204. }
  205. &.english :deep input.el-input__inner {
  206. font-family: 'arial', sans-serif;
  207. }
  208. &.right {
  209. :deep input.el-input__inner {
  210. color: $right-color;
  211. }
  212. }
  213. &.wrong {
  214. :deep input.el-input__inner {
  215. color: $error-color;
  216. }
  217. }
  218. & + .right-answer {
  219. position: relative;
  220. left: -4px;
  221. display: inline-block;
  222. height: 32px;
  223. line-height: 28px;
  224. vertical-align: bottom;
  225. border-bottom: 1px solid $font-color;
  226. }
  227. :deep input.el-input__inner {
  228. padding: 0;
  229. font-size: 16pt;
  230. color: $font-color;
  231. text-align: center;
  232. background-color: #fff;
  233. border-width: 0;
  234. border-bottom: 1px solid $font-color;
  235. border-radius: 0;
  236. }
  237. }
  238. }
  239. .record-box {
  240. padding: 6px 12px;
  241. background-color: $fill-color;
  242. :deep .record-time {
  243. width: 100px;
  244. }
  245. }
  246. }
  247. </style>