VideoInteractionPreview.vue 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. <!-- eslint-disable vue/no-v-html -->
  2. <template>
  3. <div class="imageText-preview" :style="[getAreaStyle(), getComponentStyle()]">
  4. <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
  5. <div v-if="data.video_list.length > 0" class="interaction-box">
  6. <!-- <video
  7. id="interaction-preview-video"
  8. :src="data.video_list[0].file_url"
  9. width="100%"
  10. height="400"
  11. controls
  12. controlsList="nodownload"
  13. @timeupdate="handleTimeUpdate"
  14. ></video> -->
  15. <video
  16. id="interaction-preview-video"
  17. ref="videoPlayer"
  18. :src="data.video_list[0].file_url"
  19. width="100%"
  20. height="400"
  21. controls
  22. class="video-js vjs-default-skin vjs-big-play-centered"
  23. ></video>
  24. </div>
  25. <!-- v-if="Object.keys(this.userAnswer).length === data.file_info_list.length" -->
  26. <el-button
  27. v-if="data.property.feed_back === 'total'"
  28. type="primary"
  29. :style="{
  30. background:
  31. data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '#165dff',
  32. borderColor:
  33. data.unified_attrib && data.unified_attrib.topic_color ? data.unified_attrib.topic_color : '#165dff',
  34. marginTop: '5px',
  35. }"
  36. @click="lookReport"
  37. >{{ convertText('查看答题报告') }}</el-button
  38. >
  39. <AnswerCorrect
  40. :answer-correct="data?.answer_correct"
  41. :visible.sync="visibleAnswerCorrect"
  42. @closeAnswerCorrect="closeAnswerCorrect"
  43. />
  44. <AnswerAnalysis
  45. :visible.sync="visibleAnswerAnalysis"
  46. :answer-list="data.answer_list"
  47. :analysis-list="data.analysis_list"
  48. @closeAnswerAnalysis="closeAnswerAnalysis"
  49. />
  50. <el-dialog
  51. v-if="visible"
  52. :visible.sync="visible"
  53. :show-close="false"
  54. :close-on-click-modal="true"
  55. :modal-append-to-body="true"
  56. :append-to-body="true"
  57. :lock-scroll="true"
  58. width="1040px"
  59. >
  60. <ExercisePreview
  61. :exercise_id="exercise_id"
  62. :feed_back="data.property.feed_back"
  63. @handleCancle="handleClose"
  64. @submitAdd="submitAdd"
  65. />
  66. <!-- <iframe v-if="visible" :src="newpath" width="100%" :height="iframeHeight" frameborder="0"></iframe> -->
  67. </el-dialog>
  68. <el-dialog
  69. v-if="reportFlag"
  70. :visible.sync="reportFlag"
  71. :show-close="true"
  72. :close-on-click-modal="true"
  73. :modal-append-to-body="true"
  74. :append-to-body="true"
  75. :lock-scroll="true"
  76. :width="isMobile ? '100%' : '1040px'"
  77. @close="handleClose"
  78. >
  79. <Report
  80. :report-result="reportResult"
  81. :exercise-list="exerciseList"
  82. :user-answer="userAnswer"
  83. :file-list="data.file_info_list"
  84. @handleCancle="handleClose"
  85. />
  86. </el-dialog>
  87. </div>
  88. </template>
  89. <script>
  90. import ExercisePreview from './ExercisePreview.vue';
  91. import Report from './Report.vue';
  92. import PreviewMixin from '../common/PreviewMixin';
  93. import { getVideoInteractionData } from '@/views/book/courseware/data/videoInteraction';
  94. import { getConfig } from '@/utils/auth';
  95. import 'videojs-markers/dist/videojs.markers.min.css';
  96. import videojs from 'video.js';
  97. import 'video.js/dist/video-js.css';
  98. import 'videojs-markers';
  99. export default {
  100. name: 'VideoInteractionPreview',
  101. components: { ExercisePreview, Report },
  102. mixins: [PreviewMixin],
  103. props: {
  104. isMobile: {
  105. type: Boolean,
  106. default: false,
  107. },
  108. },
  109. data() {
  110. return {
  111. data: getVideoInteractionData(),
  112. video_info: null,
  113. file_preview_url: getConfig() ? getConfig().doc_preview_service_address : '',
  114. visible: false,
  115. newpath: '',
  116. iframeHeight: `${window.innerHeight - 100}px`,
  117. first: '',
  118. exercise_id: '',
  119. userAnswer: {}, // 用户答题答案
  120. exerciseList: {}, // 题目列表
  121. reportFlag: false,
  122. reportResult: {
  123. total: 0,
  124. right: 0,
  125. error: 0,
  126. rightRate: 0,
  127. },
  128. player: null,
  129. };
  130. },
  131. watch: {
  132. isJudgingRightWrong(val) {
  133. if (!val) return;
  134. this.userAnswer = this.answer.answer_list[0];
  135. },
  136. },
  137. created() {
  138. this.initData();
  139. },
  140. mounted() {},
  141. beforeDestroy() {
  142. if (this.player) {
  143. this.player.dispose();
  144. }
  145. },
  146. methods: {
  147. initData() {
  148. this.data.file_info_list = this.data.file_info_list.sort((a, b) => Number(a.currentTime) - Number(b.currentTime));
  149. if (!this.isJudgingRightWrong) {
  150. this.answer.answer_list[0] = this.userAnswer;
  151. }
  152. if (this.data.video_list.length > 0) {
  153. let _this = this;
  154. this.$nextTick(() => {
  155. _this.handleCreatPlayer();
  156. });
  157. }
  158. },
  159. // 初始化player
  160. handleCreatPlayer() {
  161. let _this = this;
  162. let options = {
  163. autoplay: false, // 自动播放
  164. height: 500,
  165. width: 1000,
  166. controls: true, // 用户可以与之交互的控件
  167. loop: true, // 视频一结束就重新开始
  168. muted: false, // 默认情况下将使所有音频静音
  169. playsinline: true,
  170. webkitPlaysinline: true,
  171. // aspectRatio:"16:9",//显示比率
  172. playbackRates: [0.5, 1, 1.5, 2],
  173. fullscreen: {
  174. options: { navigationUI: 'hide' },
  175. },
  176. sources: [
  177. {
  178. src: this.data.video_list[0].file_url,
  179. type: 'video/mp4',
  180. },
  181. ],
  182. };
  183. this.player = videojs(this.$refs.videoPlayer, options, function onPlayerReady() {
  184. // console.log('onPlayerReady');
  185. });
  186. // 设置标点
  187. _this.handleMarkers();
  188. // 获取当前播放时间
  189. this.player.on('timeupdate', () => {
  190. this.handleTimeUpdate(this.player.currentTime());
  191. // console.log(this.currentTime());
  192. });
  193. },
  194. // 设置标点
  195. handleMarkers() {
  196. let markers = [];
  197. this.data.file_info_list.forEach((item) => {
  198. markers.push({
  199. time: item.currentTime,
  200. text: item.file_name ?? item.name,
  201. });
  202. });
  203. this.player.markers({
  204. // 不显示鼠标悬浮标记提示文字
  205. markerTip: {
  206. display: true,
  207. },
  208. markerStyle: {
  209. width: '4px',
  210. 'background-color': 'red',
  211. 'border-radius': '50%',
  212. },
  213. markers,
  214. });
  215. },
  216. handleTimeUpdate(currentTime) {
  217. this.data.file_info_list.forEach((item) => {
  218. if (
  219. Number(item.currentTime) > Number(currentTime) - 0.17 &&
  220. Number(item.currentTime) < Number(currentTime) + 0.17 &&
  221. item.id !== this.first
  222. ) {
  223. this.first = JSON.parse(JSON.stringify(item.id));
  224. this.player.pause();
  225. this.exercise_id = JSON.parse(JSON.stringify(item.id));
  226. this.visible = true;
  227. // GetFileURLMap({ file_id_list: [item.file_id] }).then(({ url_map }) => {
  228. // this.newpath = `${this.file_preview_url}onlinePreview?url=${Base64.encode(url_map[item.file_id])}`;
  229. // this.visible = true;
  230. // });
  231. }
  232. });
  233. },
  234. handleClose() {
  235. this.reportFlag = false;
  236. this.player.play();
  237. this.first = JSON.parse(JSON.stringify(this.exercise_id));
  238. setTimeout(() => {
  239. this.first = '';
  240. }, 1000);
  241. },
  242. submitAdd(id, answer, content, flag) {
  243. this.first = JSON.parse(JSON.stringify(this.exercise_id));
  244. setTimeout(() => {
  245. this.first = '';
  246. }, 1000);
  247. this.$set(this.userAnswer, id, answer);
  248. this.$set(this.exerciseList, id, content);
  249. if (flag || this.data.property.feed_back === 'total') {
  250. this.visible = false;
  251. this.player.play();
  252. }
  253. },
  254. lookReport() {
  255. this.player.pause();
  256. let num = 0;
  257. let total = this.data.file_info_list.length;
  258. Object.keys(this.userAnswer).forEach((key) => {
  259. if (this.userAnswer[key].is_right) {
  260. num += 1;
  261. }
  262. });
  263. this.reportResult = {
  264. total,
  265. right: num,
  266. error: total - num,
  267. rightRate: `${((num / total) * 100).toFixed(0)}%`,
  268. };
  269. this.reportFlag = true;
  270. },
  271. },
  272. };
  273. </script>
  274. <style lang="scss" scoped>
  275. @use '@/styles/mixin.scss' as *;
  276. video:full-screen {
  277. :fullscreen::-webkit-media-controls-fullscreen-button {
  278. display: none;
  279. }
  280. }
  281. video::-webkit-media-controls-fullscreen-button {
  282. display: none;
  283. }
  284. </style>