Jelajahi Sumber

通话弹窗拖动

zhangjie 3 tahun lalu
induk
melakukan
426525df8f

+ 21 - 16
src/features/invigilation/ExamInvigilation/ExamInvigilation.vue

@@ -235,12 +235,13 @@
     <div class="invigilation-list">
       <h3>实时监控台</h3>
       <invigilation-student
+        ref="InvigilationStudent"
         v-for="item in students"
         :key="item.examStudentId"
-        ref="InvigilationStudent"
         :data="item"
         show-type="patrol"
-        @muted="videoMuted"
+        :max-retry-count="1"
+        @muted-change="videoAllMuted"
       ></invigilation-student>
     </div>
   </div>
@@ -257,10 +258,12 @@ import {
   examInvigilationWarnMessage,
 } from "@/api/invigilation";
 import { mapState } from "vuex";
+import timeMixin from "@/mixins/timeMixin";
 
 export default {
   name: "exam-invigilation",
   components: { EchartRender, InvigilationStudent },
+  mixins: [timeMixin],
   data() {
     return {
       monitorCount: {
@@ -288,7 +291,6 @@ export default {
         dataList: [],
         type: "light",
       },
-      setT: null,
       loopRunning: false,
       students: [],
       messages: [],
@@ -311,8 +313,8 @@ export default {
   },
   methods: {
     async initData() {
-      if (this.setT) clearTimeout(this.setT);
       if (!this.loopRunning) return;
+      this.clearSetTs();
 
       const fetchAll = [
         this.getCount(),
@@ -326,7 +328,7 @@ export default {
       await this.getVideoList();
       await this.getMessageList();
 
-      this.setT = setTimeout(() => {
+      this.addSetTime(() => {
         this.initData();
       }, 10 * 1000);
     },
@@ -415,17 +417,15 @@ export default {
       this.typeWarningData.type = colorType;
       this.warningTrendData.type = colorType;
     },
-    videoMuted(muted) {
-      if (!muted) {
-        this.$refs.InvigilationStudent.forEach((refInst) => {
-          refInst.mutedPlayer(true);
-        });
-      }
+    videoAllMuted() {
+      this.$refs.InvigilationStudent.forEach((refInst) => {
+        refInst.mutedPlayer(true);
+      });
     },
   },
   beforeDestroy() {
     this.loopRunning = false;
-    if (this.setT) clearTimeout(this.setT);
+    this.clearSetTs();
   },
 };
 </script>
@@ -460,9 +460,7 @@ export default {
   right: 0;
   width: 280px;
   height: 100%;
-  background: #fff;
   border-radius: 6px;
-  padding: 20px;
   z-index: 9;
   overflow-y: auto;
   overflow-x: hidden;
@@ -473,6 +471,9 @@ export default {
     line-height: 25px;
     margin-bottom: 20px;
   }
+  .invigilation-student {
+    background-color: #fff;
+  }
 }
 .invigilation-analysis {
   min-width: 940px;
@@ -584,19 +585,23 @@ export default {
       color: #737aae;
     }
   }
+
   .invigilation-analysis {
     padding: 30px;
     background: #25294a;
   }
   .invigilation-list {
-    background: #202442;
+    background: #25294a;
     border-radius: 0;
+    padding-top: 30px;
+    padding-right: 20px;
+
     .invigilation-student {
       background: #2d325a;
       border: 1px solid #2d325a;
     }
     .student-info {
-      .student-time {
+      &-time {
         background: #353c70;
       }
       > p {

+ 5 - 7
src/features/invigilation/OnlinePatrol/PatrolExamDetail.vue

@@ -235,7 +235,7 @@
           ref="InvigilationStudent"
           :data="item"
           show-type="patrol"
-          @muted="videoMuted"
+          @muted-change="videoAllMuted"
         ></invigilation-student>
       </div>
     </div>
@@ -454,12 +454,10 @@ export default {
         params: { examRecordId: row.examRecordId },
       });
     },
-    videoMuted(muted) {
-      if (!muted) {
-        this.$refs.InvigilationStudent.forEach((refInst) => {
-          refInst.mutedPlayer(true);
-        });
-      }
+    videoAllMuted() {
+      this.$refs.InvigilationStudent.forEach((refInst) => {
+        refInst.mutedPlayer(true);
+      });
     },
     goBack() {
       window.history.go(-1);

+ 0 - 14
src/features/invigilation/OnlinePatrol/PatrolWarningDetail.vue

@@ -111,18 +111,6 @@
               <div class="student-video-container">
                 <div class="student-video-tips">{{ item.name }}</div>
                 <flv-media :ref="item.ref" :live-url="item.liveUrl"></flv-media>
-                <!-- <div
-                  v-if="item.liveUrl"
-                  class="student-video-muted"
-                  @click="videoMute(item)"
-                >
-                  <i
-                    :class="[
-                      'icon',
-                      item.muted ? 'icon-audio' : 'icon-audio-act',
-                    ]"
-                  ></i>
-                </div> -->
               </div>
             </div>
           </div>
@@ -254,7 +242,6 @@ export default {
           ? `${domain}/live/${item.liveUrl.toLowerCase()}.flv`
           : "";
         item.name = sourceNames[item.source];
-        item.muted = true;
         item.ref = snakeToHump(item.source) + "Video";
         records[item.source] = item;
       });
@@ -263,7 +250,6 @@ export default {
         return (
           records[source] || {
             liveUrl: null,
-            muted: true,
             source,
             name: sourceNames[source],
             ref: snakeToHump(source) + "Video",

+ 5 - 7
src/features/invigilation/RealtimeMonitoring/RealtimeMonitoring.vue

@@ -281,7 +281,7 @@
         <invigilation-student
           ref="InvigilationStudent"
           :data="item"
-          @muted="videoMuted"
+          @muted-change="videoAllMuted"
         ></invigilation-student>
       </div>
     </div>
@@ -626,12 +626,10 @@ export default {
         params: { examRecordId: row.examRecordId },
       });
     },
-    videoMuted(muted) {
-      if (!muted) {
-        this.$refs.InvigilationStudent.forEach((refInst) => {
-          refInst.mutedPlayer(true);
-        });
-      }
+    videoAllMuted() {
+      this.$refs.InvigilationStudent.forEach((refInst) => {
+        refInst.mutedPlayer(true);
+      });
     },
   },
   beforeDestroy() {

+ 25 - 31
src/features/invigilation/RealtimeMonitoring/WarningDetail.vue

@@ -243,25 +243,20 @@
     ></simple-image-preview>
 
     <!-- 通话弹出层 -->
-    <el-dialog
-      custom-class="communication-dialog"
-      :visible.sync="dialogVisible"
-      width="600px"
-      :show-close="false"
-      :close-on-press-escape="false"
-      :close-on-click-modal="false"
-      append-to-body
-      fullscreen
+    <div
+      v-if="dialogVisible"
+      class="communication-dialog"
+      v-move-ele.prevent.stop
     >
-      <!-- TODO:拖动 -->
       <div class="communication-box" v-show="!isWaiting">
         <div class="communication-host" id="communication-host"></div>
         <div class="communication-guest" id="communication-guest"></div>
-        <div class="communication-action">
-          <el-button round type="danger" @click="hangup">结束通话</el-button>
+        <div class="communication-action" @mousedown.stop>
+          <el-button round type="danger" @click.stop="hangup"
+            >结束通话</el-button
+          >
         </div>
         <div class="communication-info">
-          <!-- <span>当前网络状态良好</span> -->
           <span>持续时长:<second-timer ref="SecondTimer"></second-timer></span>
         </div>
       </div>
@@ -276,12 +271,11 @@
         <p class="communication-wait-username">
           {{ detailInfo.examStudentName }}
         </p>
-        <div class="communication-wait-action">
+        <div class="communication-wait-action" @mousedown.stop>
           <el-button round type="danger" @click="hangup">取消通话</el-button>
         </div>
       </div>
-      <span slot="footer" class="dialog-footer"> </span>
-    </el-dialog>
+    </div>
   </div>
 </template>
 
@@ -311,6 +305,7 @@ import {
   objTypeOf,
   snakeToHump,
 } from "@/utils/utils";
+import MoveEle from "@/plugins/move-ele";
 import { mapState } from "vuex";
 
 const domEmpty = (dom) => {
@@ -329,6 +324,7 @@ export default {
     SimpleImagePreview,
     SecondTimer,
   },
+  directives: { MoveEle },
   data() {
     return {
       examRecordId: this.$route.params.examRecordId,
@@ -341,12 +337,11 @@ export default {
       viewVideoReady: false,
       holding: false,
       // communication
-      popoverVisible: false,
       userMonitor: {},
       client: null,
       localStream: null,
       dialogVisible: false,
-      isWaiting: true,
+      isWaiting: false,
       subscribeSetTs: [],
       loopRunning: false,
       loopSetTs: [],
@@ -457,7 +452,6 @@ export default {
           ? `${domain}/live/${item.liveUrl.toLowerCase()}.flv`
           : "";
         item.name = sourceNames[item.source];
-        item.muted = true;
         item.ref = snakeToHump(item.source) + "Video";
         records[item.source] = item;
       });
@@ -466,7 +460,6 @@ export default {
         return (
           records[source] || {
             liveUrl: null,
-            muted: true,
             source,
             name: sourceNames[source],
             ref: snakeToHump(source) + "Video",
@@ -723,7 +716,6 @@ export default {
       }
 
       this.dialogVisible = true;
-      this.popoverVisible = false;
       this.holding = false;
       // 添加远程用户视频发布监听
       this.client.on("stream-added", (event) => {
@@ -829,16 +821,6 @@ export default {
       this.clearSubscribeSetTs();
       this.$refs.SecondTimer.end();
 
-      if (this.autoAnswerInfo) {
-        // 结束学生的通话
-        await communicationOver({
-          recordId: this.examRecordId,
-          source: this.autoAnswerInfo.source,
-        }).catch(() => {
-          console.log("结束通话状态异常!");
-        });
-      }
-
       // 取消发布本地视频
       await this.client.unpublish(this.localStream).catch((error) => {
         console.log("取消发布本地视频失败!", error);
@@ -863,6 +845,18 @@ export default {
       this.dialogVisible = false;
       this.isWaiting = true;
       // this.initSubscribeVideo();
+
+      if (this.autoAnswerInfo) {
+        // 结束学生的通话
+        await communicationOver({
+          recordId: this.examRecordId,
+          source: this.autoAnswerInfo.source,
+        }).catch(() => {
+          console.log("结束通话状态异常!");
+        });
+
+        this.goBack();
+      }
     },
     initSubscribeVideo() {
       this.viewVideoReady = true;

+ 7 - 2
src/features/invigilation/common/FlvMedia.vue

@@ -39,12 +39,15 @@ export default {
     liveUrl: {
       type: String,
     },
+    maxRetryCount: {
+      type: Number,
+      default: 3,
+    },
   },
   data() {
     return {
       flvPlayer: null,
       retryCount: 0,
-      maxRetryCount: 3,
       loading: false,
       result: {
         error: false,
@@ -85,6 +88,7 @@ export default {
       this.flvPlayer.on(flvjs.Events.ERROR, this.playError);
       this.flvPlayer.on(flvjs.Events.METADATA_ARRIVED, () => {
         this.loading = false;
+        this.retryCount = 0;
       });
     },
     playError(errorType) {
@@ -145,8 +149,9 @@ export default {
     },
     videoMuted() {
       if (!this.flvPlayer) return;
+      const muted = this.flvPlayer.muted;
       this.$emit("muted-change");
-      this.flvPlayer.muted = !this.flvPlayer.muted;
+      this.flvPlayer.muted = !muted;
     },
     reloadVideo() {
       this.result = {

+ 10 - 30
src/features/invigilation/common/InvigilationStudent.vue

@@ -4,7 +4,8 @@
       <flv-media
         ref="ThirdViewVideo"
         :live-url="data.liveUrl"
-        v-if="data.liveUrl"
+        :max-retry-count="maxRetryCount"
+        @muted-change="mutedChange"
       ></flv-media>
     </div>
     <div class="student-info">
@@ -14,9 +15,6 @@
           ><i class="icon icon-net-break"></i>
         </h6>
         <div class="student-info-tips">
-          <div class="student-info-audio" @click="toMuted">
-            <i :class="['icon', muted ? 'icon-audio' : 'icon-audio-act']"></i>
-          </div>
           <div class="student-info-time">
             <i
               :class="['icon', isNetbreak ? 'icon-clock-danger' : 'icon-clock']"
@@ -66,6 +64,10 @@ export default {
       type: String,
       default: "invigilation",
     },
+    maxRetryCount: {
+      type: Number,
+      default: 3,
+    },
   },
   data() {
     return {
@@ -101,20 +103,15 @@ export default {
       }
     },
     toLargeView() {
-      this.muted = true;
-      this.$emit("muted", false);
+      this.$emit("muted-change");
       this.$refs.ThirdViewVideo.destroyPlayer();
       this.$refs.InvigilationStudentMediaDialog.open();
     },
-    toMuted() {
-      // console.log(this.muted);
-      const nextMuted = !this.muted;
-      this.$emit("muted", nextMuted);
-      const res = this.mutedPlayer(nextMuted);
-      if (res) this.muted = nextMuted;
+    mutedChange() {
+      this.$emit("muted-change");
     },
     mutedPlayer(muted) {
-      return this.$refs.ThirdViewVideo.mutedPlayer(muted);
+      this.$refs.ThirdViewVideo.mutedPlayer(muted);
     },
     dialogClose() {
       this.$refs.ThirdViewVideo.reloadVideo();
@@ -199,23 +196,6 @@ export default {
     align-items: center;
     margin-bottom: 5px;
   }
-  &-audio {
-    display: inline-block;
-    vertical-align: middle;
-    width: 24px;
-    height: 24px;
-    text-align: center;
-    line-height: 24px;
-    margin-right: 5px;
-    cursor: pointer;
-    &:hover {
-      transform: scale(1.05, 1.05);
-    }
-
-    .icon {
-      margin-top: -2px;
-    }
-  }
   &-time {
     display: inline-block;
     vertical-align: middle;

+ 1 - 5
src/features/invigilation/common/InvigilationStudentMediaDialog.vue

@@ -11,11 +11,7 @@
   >
     <div class="media-close" @click="cancel"><i class="el-icon-close"></i></div>
     <div class="media-video">
-      <flv-media
-        ref="ThirdViewVideo"
-        :live-url="data.liveUrl"
-        v-if="data.liveUrl"
-      ></flv-media>
+      <flv-media ref="ThirdViewVideo" :live-url="data.liveUrl"></flv-media>
     </div>
     <div class="media-info" slot="footer">
       <p>{{ data.name }}</p>

+ 69 - 0
src/plugins/move-ele.js

@@ -0,0 +1,69 @@
+export default {
+  inserted(el, { value, modifiers }) {
+    let [_x, _y] = [0, 0];
+    // 当前拖动事务开始前元素的left,top
+    let [oleft, otop] = [0, 0];
+    // 元素移动后的left,top
+    let [left, top] = [0, 0];
+    let isDrag = false;
+
+    let moveHandle = function (e) {
+      isDrag = true;
+      if (modifiers.prevent) {
+        e.preventDefault();
+      }
+      if (modifiers.stop) {
+        e.stopPropagation();
+      }
+      const mLeft = e.pageX - _x;
+      const mTop = e.pageY - _y;
+      left = oleft + mLeft;
+      top = otop + mTop;
+
+      // console.log(left, top);
+
+      if (value && value.mouseMove) {
+        value.mouseMove({ left, top, mLeft, mTop });
+      } else {
+        el.style.left = left + "px";
+        el.style.top = top + "px";
+      }
+    };
+
+    let upHandle = function (e) {
+      if (modifiers.prevent) {
+        e.preventDefault();
+      }
+      if (modifiers.stop) {
+        e.stopPropagation();
+      }
+      oleft = left;
+      otop = top;
+
+      if (value && value.mouseUp) value.mouseUp({ left, top });
+      if (value && value.click && !isDrag) value.click(e);
+
+      document.removeEventListener("mousemove", moveHandle);
+      document.removeEventListener("mouseup", upHandle);
+
+      isDrag = false;
+    };
+
+    el.addEventListener("mousedown", function (e) {
+      if (modifiers.prevent) {
+        e.preventDefault();
+      }
+      if (modifiers.stop) {
+        e.stopPropagation();
+      }
+      _x = e.pageX;
+      _y = e.pageY;
+      oleft = el.offsetLeft;
+      otop = el.offsetTop;
+      if (value && value.mouseDown) value.mouseDown({ oleft, otop }, e);
+
+      document.addEventListener("mousemove", moveHandle);
+      document.addEventListener("mouseup", upHandle);
+    });
+  },
+};

+ 26 - 27
src/styles/base.scss

@@ -722,18 +722,22 @@ body {
 
 // communication-dialog
 .communication-dialog {
-  &.el-dialog {
-    background-color: transparent;
-  }
+  position: fixed;
+  overflow: hidden;
+  width: 500px;
+  height: 720px;
+  background: #000;
+  right: auto;
+  bottom: auto;
+  left: 40px;
+  top: 120px;
+  border-radius: 10px;
+  z-index: 9999;
+  box-shadow: 0 0 20px #333;
 
   .communication-box {
-    width: 600px;
-    height: 720px;
-    background: #000;
-    margin: 0 auto;
     position: relative;
-    border-radius: 6px;
-    overflow: hidden;
+    height: 100%;
     video {
       display: block;
       width: 100%;
@@ -752,24 +756,23 @@ body {
       top: 10px;
       right: 10px;
       z-index: 99;
-      background: #000;
+      background: rgb(58, 58, 58);
     }
     .communication-action {
       position: absolute;
-      width: 100%;
-      bottom: 40px;
-      left: 0;
+      left: 50%;
+      bottom: 10px;
       z-index: 99;
-      text-align: center;
+      transform: translateX(-50%);
     }
     .communication-info {
       position: absolute;
-      width: 100%;
-      bottom: 0;
-      height: 40px;
-      line-height: 40px;
-      padding: 0 20px;
+      right: 10px;
+      bottom: 10px;
+      height: 30px;
+      line-height: 30px;
       color: #fff;
+      z-index: 99;
 
       > span {
         display: block;
@@ -783,14 +786,10 @@ body {
   }
 
   .communication-wait {
-    width: 460px;
-    height: 511px;
-    background: #fff;
-    margin: 100px auto 0;
     position: relative;
-    border-radius: 6px;
+    height: 100%;
     text-align: center;
-    padding: 40px;
+    padding: 150px 40px 40px;
 
     &-tips {
       font-size: 16px;
@@ -814,9 +813,9 @@ body {
     &-username {
       font-size: 18px;
       font-weight: 600;
-      color: rgba(32, 43, 75, 1);
+      color: #fff;
       line-height: 25px;
-      margin-bottom: 100px;
+      margin-bottom: 180px;
     }
   }
   // element