DialoguePreview.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. <!-- eslint-disable vue/no-v-html -->
  2. <template>
  3. <div class="dialogue-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="content">
  14. <div v-for="(item, i) in optionList" :key="i" class="option-list">
  15. <span
  16. class="avatar"
  17. :style="{ backgroundColor: data.property.role_list.find(({ mark }) => mark === item.role).color }"
  18. >
  19. {{ data.property.role_list.find(({ mark }) => mark === item.role).name }}
  20. </span>
  21. <div v-if="item.type === 'text'" class="text-wrapper">
  22. <div class="text">
  23. <template v-for="(li, j) in item.content_list">
  24. <span v-if="li.type === 'text'" :key="j">{{ li.content }}</span>
  25. <template v-else-if="li.type === 'input'">
  26. <el-input
  27. :key="j"
  28. v-model="li.content"
  29. :disabled="disabled"
  30. :class="[...computedAnswerClass(li.mark, li.content)]"
  31. :style="[{ width: Math.max(80, li.content.length * 16) + 'px' }]"
  32. />
  33. <span
  34. v-show="computedAnswerText(li.mark, li.content).length > 0"
  35. :key="`answer-${j}`"
  36. class="right-answer"
  37. >
  38. {{ computedAnswerText(li.mark, li.content) }}
  39. </span>
  40. </template>
  41. </template>
  42. </div>
  43. <SoundRecordPreview
  44. v-if="
  45. isEnable(data.property.is_enable_voice_answer) && item.content_list.some(({ type }) => type === 'input')
  46. "
  47. :wav-blob.sync="item.file_id"
  48. :disabled="disabled"
  49. type="small"
  50. />
  51. </div>
  52. <div v-else-if="item.type === 'image'" class="image">
  53. <img :src="file_map_list[item.file_id]" />
  54. </div>
  55. <div v-else-if="item.type === 'audio'" class="audio">
  56. <AudioPlay
  57. :file-id="item.file_id"
  58. :show-slider="true"
  59. :show-progress="false"
  60. :background-color="data.property.role_list.find(({ mark }) => mark === item.role).color"
  61. />
  62. </div>
  63. </div>
  64. </div>
  65. </div>
  66. </template>
  67. <script>
  68. import PreviewMixin from './components/PreviewMixin';
  69. import SoundRecordPreview from './components/common/SoundRecordPreview.vue';
  70. import { GetFileURLMap } from '@/api/app';
  71. export default {
  72. name: 'DialoguePreview',
  73. components: {
  74. SoundRecordPreview,
  75. },
  76. mixins: [PreviewMixin],
  77. data() {
  78. return {
  79. optionList: [],
  80. file_map_list: [],
  81. };
  82. },
  83. watch: {
  84. 'data.option_list': {
  85. handler(val) {
  86. let list = JSON.parse(JSON.stringify(val));
  87. let file_id_list = [];
  88. list.forEach(({ type, file_id }) => {
  89. if (type === 'image' || type === 'audio') {
  90. file_id_list.push(file_id);
  91. }
  92. });
  93. GetFileURLMap({ file_id_list }).then(({ url_map }) => {
  94. this.file_map_list = url_map;
  95. });
  96. this.optionList = list;
  97. },
  98. deep: true,
  99. immediate: true,
  100. },
  101. optionList: {
  102. handler(val) {
  103. this.answer.answer_list = [];
  104. val.forEach(({ type, content_list, mark, file_id }) => {
  105. if (type !== 'text') return;
  106. let list = content_list
  107. .map(({ type, mark, content }) => {
  108. if (type !== 'input') return;
  109. return {
  110. mark,
  111. content,
  112. };
  113. })
  114. .filter((item) => item);
  115. if (list.length <= 0) return;
  116. this.answer.answer_list.push({
  117. content_list: list,
  118. file_id,
  119. mark,
  120. });
  121. });
  122. },
  123. deep: true,
  124. immediate: true,
  125. },
  126. isJudgingRightWrong(val) {
  127. if (!val) return;
  128. this.answer.answer_list.forEach(({ mark, file_id, content_list }) => {
  129. let find = this.optionList.find((item) => item.mark === mark);
  130. find.file_id = file_id;
  131. find.content_list.forEach((item) => {
  132. if (item.type === 'text') return;
  133. let find = content_list.find((li) => li.mark === item.mark);
  134. item.content = find.content;
  135. });
  136. });
  137. },
  138. },
  139. methods: {
  140. /**
  141. * 计算答题对错选项字体颜色
  142. * @param {string} mark 选项标识
  143. * @param {string} content 选项内容
  144. */
  145. computedAnswerClass(mark, content) {
  146. if (!this.isJudgingRightWrong && !this.isShowRightAnswer) {
  147. return '';
  148. }
  149. let rightValue = this.data.answer.answer_list.find((item) => item.mark === mark)?.value || '';
  150. let classList = [];
  151. let isRight = rightValue === content;
  152. if (this.isJudgingRightWrong) {
  153. isRight ? classList.push('right') : classList.push('wrong');
  154. }
  155. if (this.isShowRightAnswer && !isRight) {
  156. classList.push('show-right-answer');
  157. }
  158. return classList;
  159. },
  160. computedAnswerText(mark, content) {
  161. if (!this.isShowRightAnswer) return '';
  162. if (content.length === 0) return '';
  163. let find = this.data.answer.answer_list.find((item) => item.mark === mark);
  164. if (find.value === content) return '';
  165. if (!find) return '';
  166. return `(${find.value})`;
  167. },
  168. },
  169. };
  170. </script>
  171. <style lang="scss" scoped>
  172. @use '@/styles/mixin.scss' as *;
  173. .dialogue-preview {
  174. @include preview;
  175. .content {
  176. display: flex;
  177. flex-direction: column;
  178. row-gap: 16px;
  179. padding: 24px;
  180. background-color: $fill-color;
  181. border-radius: 16px;
  182. .option-list {
  183. display: flex;
  184. column-gap: 8px;
  185. .avatar {
  186. display: flex;
  187. align-items: center;
  188. justify-content: center;
  189. width: 40px;
  190. min-width: 40px;
  191. height: 40px;
  192. min-height: 40px;
  193. margin-right: 8px;
  194. font-size: 12px;
  195. color: #fff;
  196. border-radius: 50%;
  197. }
  198. .text-wrapper {
  199. display: flex;
  200. flex-direction: column;
  201. row-gap: 8px;
  202. align-items: flex-start;
  203. .text {
  204. padding: 8px 12px;
  205. font-size: 16px;
  206. word-break: break-all;
  207. background-color: #fff;
  208. border-radius: 8px;
  209. .el-input {
  210. display: inline-flex;
  211. align-items: center;
  212. width: 120px;
  213. margin: 0 2px;
  214. &.right {
  215. :deep input.el-input__inner {
  216. color: $right-color;
  217. }
  218. }
  219. &.wrong {
  220. :deep input.el-input__inner {
  221. color: $error-color;
  222. }
  223. }
  224. & + .right-answer {
  225. position: relative;
  226. left: -4px;
  227. display: inline-block;
  228. height: 32px;
  229. font-size: 16px;
  230. line-height: 28px;
  231. vertical-align: bottom;
  232. border-bottom: 1px solid $font-color;
  233. }
  234. :deep input.el-input__inner {
  235. padding: 0;
  236. font-size: 16px;
  237. color: $font-color;
  238. text-align: center;
  239. background-color: #fff;
  240. border-width: 0;
  241. border-bottom: 1px solid $font-color;
  242. border-radius: 0;
  243. }
  244. }
  245. }
  246. .sound-record-preview {
  247. padding: 4px;
  248. background-color: #fff;
  249. border-radius: 40px;
  250. :deep .sound-item .sound-item-span {
  251. color: #1d1d1d;
  252. background-color: #fff;
  253. }
  254. :deep .sound-item-luyin .sound-item-span {
  255. color: #fff;
  256. background-color: $light-main-color;
  257. }
  258. }
  259. }
  260. .image img {
  261. border-radius: 8px;
  262. }
  263. }
  264. }
  265. }
  266. </style>