DialoguePreview.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. <!-- eslint-disable vue/no-v-html -->
  2. <template>
  3. <div class="dialogue-preview">
  4. <div class="stem">
  5. <span class="question-number" :style="{ fontSize: data.property.stem_question_number_font_size }">
  6. {{ questionNumberEndIsBracket(data.property.question_number) }}
  7. </span>
  8. <span v-html="sanitizeHTML(data.stem)"></span>
  9. </div>
  10. <div
  11. v-if="isEnable(data.property.is_enable_description)"
  12. class="description rich-text"
  13. v-html="sanitizeHTML(data.description)"
  14. ></div>
  15. <div class="content">
  16. <div
  17. v-for="(item, i) in optionList"
  18. :key="i"
  19. class="option-list"
  20. :style="{ flexDirection: item.type === 'input' ? 'row-reverse' : 'row' }"
  21. >
  22. <span
  23. class="avatar"
  24. :style="{
  25. backgroundColor: data.property.role_list?.find(({ mark }) => mark === item.role).color,
  26. }"
  27. >
  28. {{ data.property.role_list?.find(({ mark }) => mark === item.role).name }}
  29. </span>
  30. <div v-if="item.type === 'text'" class="text-wrapper">
  31. <div class="text">{{ item.text }}</div>
  32. </div>
  33. <div v-else-if="item.type === 'image'" class="image">
  34. <img :src="file_map_list[item.file_id]" />
  35. </div>
  36. <div v-else-if="item.type === 'audio'" class="audio">
  37. <AudioPlay
  38. :file-id="item.file_id"
  39. :show-slider="true"
  40. :show-progress="false"
  41. :background-color="data.property.role_list?.find(({ mark }) => mark === item.role).color"
  42. />
  43. </div>
  44. <div v-else-if="item.type === 'input'">
  45. <SoundRecordPreview :wav-blob.sync="item.file_id" :disabled="disabled" type="small" />
  46. </div>
  47. </div>
  48. </div>
  49. <div v-if="isEnable(data.property.is_enable_reference_answer) && isShowRightAnswer" class="reference-box">
  50. <h5 class="reference-title">参考答案</h5>
  51. <span class="reference-answer rich-text" v-html="sanitizeHTML(data.reference_answer)"></span>
  52. </div>
  53. <div v-if="isEnable(data.property.is_enable_analysis) && isShowRightAnswer" class="analysis">
  54. <span class="analysis-title">解析</span>
  55. <div class="analysis-content" v-html="sanitizeHTML(data.analysis)"></div>
  56. </div>
  57. </div>
  58. </template>
  59. <script>
  60. import PreviewMixin from './components/PreviewMixin';
  61. import SoundRecordPreview from './components/common/SoundRecordPreview.vue';
  62. import { GetFileURLMap } from '@/api/app';
  63. export default {
  64. name: 'DialoguePreview',
  65. components: {
  66. SoundRecordPreview,
  67. },
  68. mixins: [PreviewMixin],
  69. data() {
  70. return {
  71. optionList: [],
  72. file_map_list: [],
  73. inputFocus: false,
  74. focusPostion: {
  75. i: -1,
  76. j: -1,
  77. },
  78. };
  79. },
  80. watch: {
  81. 'data.option_list': {
  82. handler(val) {
  83. let list = JSON.parse(JSON.stringify(val));
  84. let file_id_list = [];
  85. list.forEach(({ type, file_id }) => {
  86. if (type === 'image' || type === 'audio') {
  87. file_id_list.push(file_id);
  88. }
  89. });
  90. GetFileURLMap({ file_id_list }).then(({ url_map }) => {
  91. this.file_map_list = url_map;
  92. });
  93. this.optionList = list;
  94. },
  95. deep: true,
  96. immediate: true,
  97. },
  98. optionList: {
  99. handler(val) {
  100. this.answer.answer_list = [];
  101. val.forEach(({ type, mark, file_id }) => {
  102. if (type === 'input') {
  103. if (file_id.length <= 0) return;
  104. this.answer.answer_list.push({
  105. audio_file_id: file_id,
  106. mark,
  107. });
  108. return;
  109. }
  110. });
  111. },
  112. deep: true,
  113. immediate: true,
  114. },
  115. isJudgingRightWrong(val) {
  116. if (!val) return;
  117. this.answer.answer_list.forEach(({ mark, audio_file_id }) => {
  118. let findOption = this.optionList.find((item) => item.mark === mark);
  119. findOption.file_id = audio_file_id;
  120. });
  121. },
  122. },
  123. methods: {},
  124. };
  125. </script>
  126. <style lang="scss" scoped>
  127. @use '@/styles/mixin.scss' as *;
  128. .dialogue-preview {
  129. @include preview;
  130. .description {
  131. display: flex;
  132. flex-wrap: wrap;
  133. gap: 4px 8px;
  134. &-item {
  135. padding: 4px 8px;
  136. cursor: pointer;
  137. background-color: #e9edf7;
  138. border-radius: 4px;
  139. &:hover,
  140. &:active {
  141. color: $light-main-color;
  142. }
  143. }
  144. }
  145. .content {
  146. display: flex;
  147. flex-direction: column;
  148. row-gap: 16px;
  149. padding: 24px;
  150. background-color: $fill-color;
  151. border-radius: 16px;
  152. .option-list {
  153. display: flex;
  154. column-gap: 8px;
  155. .avatar {
  156. display: flex;
  157. align-items: center;
  158. justify-content: center;
  159. width: 40px;
  160. min-width: 40px;
  161. height: 40px;
  162. min-height: 40px;
  163. margin-right: 8px;
  164. font-size: 12px;
  165. color: #fff;
  166. border-radius: 50%;
  167. }
  168. .text-wrapper {
  169. display: flex;
  170. flex-direction: column;
  171. row-gap: 8px;
  172. align-items: flex-start;
  173. .text {
  174. padding: 8px 12px;
  175. font-size: 16px;
  176. word-break: break-all;
  177. background-color: #fff;
  178. border-radius: 8px;
  179. .el-input {
  180. display: inline-flex;
  181. align-items: center;
  182. width: 120px;
  183. margin: 0 2px;
  184. &.right {
  185. :deep input.el-input__inner {
  186. color: $right-color;
  187. }
  188. }
  189. &.wrong {
  190. :deep input.el-input__inner {
  191. color: $error-color;
  192. }
  193. }
  194. & + .right-answer {
  195. position: relative;
  196. left: -4px;
  197. display: inline-block;
  198. height: 32px;
  199. font-size: 16px;
  200. line-height: 28px;
  201. vertical-align: bottom;
  202. border-bottom: 1px solid $font-color;
  203. }
  204. :deep input.el-input__inner {
  205. padding: 0;
  206. font-size: 16px;
  207. color: $font-color;
  208. text-align: center;
  209. background-color: #fff;
  210. border-width: 0;
  211. border-bottom: 1px solid $font-color;
  212. border-radius: 0;
  213. }
  214. }
  215. }
  216. }
  217. .sound-record-preview {
  218. padding: 4px;
  219. background-color: #fff;
  220. border-radius: 40px;
  221. :deep .sound-item .sound-item-span {
  222. color: #1d1d1d;
  223. background-color: #fff;
  224. }
  225. :deep .sound-item-luyin .sound-item-span {
  226. color: #fff;
  227. background-color: $light-main-color;
  228. }
  229. }
  230. .image img {
  231. border-radius: 8px;
  232. }
  233. }
  234. }
  235. }
  236. </style>