浏览代码

视频播放交互优化与测试视频数据更新

  1. XgVideoPlayer 视频交互优化
     - 默认不自动播放
     - 点击播放,再点击暂停
     - 自定义播放图标遮罩层,暂停时显示居中播放按钮
     - 禁用 xgplayer 原生 start 插件避免图标重叠

  2. 测试视频数据更新
     - 新增 video3.mp4、video4.mp4 导入
     - 四个角落视频固定分配:左上video1、右上video2、左下video3、右下video4
画安 3 周之前
父节点
当前提交
577ec06dec

二进制
src/assets/videos/video1.mp4


二进制
src/assets/videos/video2.mp4


二进制
src/assets/videos/video3.mp4


二进制
src/assets/videos/video4.mp4


+ 0 - 191
src/components/IntersectionSignalMonitoring.vue

@@ -1,191 +0,0 @@
-<template>
-    <div class="container" ref="Container">
-        <div class="intersection" ref="intersectionBox">
-            <div class="video-1" :style="videoStyles.v1"><XgVideoPlayer :src="video1" /></div>
-            <div class="video-2" :style="videoStyles.v2"><XgVideoPlayer :src="video2" /></div>
-            <div class="video-3" :style="videoStyles.v3"><XgVideoPlayer :src="video1" /></div>
-            <div class="video-4" :style="videoStyles.v4"><XgVideoPlayer :src="video2" /></div>
-
-            <IntersectionMap :mapData="intersectionData" />
-        </div>
-        <div class="signaltiming" ref="Signaltiming">
-            <SignalTimingChart 
-                :loading="loading" 
-                :cycle-length="signalTimingData.cycleLength"
-                :current-time="signalTimingData.currentTime" 
-                :phase-data="signalTimingData.phaseData" />
-        </div>
-    </div>
-</template>
-
-<script>
-
-import SignalTimingChart from '@/components/ui/SignalTimingChart.vue';
-import IntersectionMap from '@/components/ui/IntersectionMap.vue';
-import { apiGetSignalTiming, apiGetIntersectionData } from '@/api';
-import video1 from '@/assets/videos/video1.mp4';
-import video2 from '@/assets/videos/video2.mp4';
-import XgVideoPlayer from '@/components/ui/XgVideoPlayer.vue';
-
-export default {
-    name: "IntersectionSignalMonitoring",
-    components: {
-        SignalTimingChart,
-        IntersectionMap,
-        XgVideoPlayer
-    },
-    props: {
-        nodeData: {
-            type: Object,
-            default: () => ({})
-        }
-    },
-    data() {
-        return {
-            signalTimingData: {},
-            loading: false,
-            intersectionData: {},
-            timer: null,
-            mapWidth: 600,
-            mapHeight: 600,
-            video1,
-            video2,
-            videoStyles: { v1: {}, v2: {}, v3: {}, v4: {} }
-        };
-    },
-    computed: {
-
-    },
-    created() {
-    },
-    async mounted() {
-        this.measureIntersectionBox();
-
-        this._roaPending = false;
-        this._resizeObserver = new ResizeObserver(() => {
-            if (!this._roaPending) {
-                this._roaPending = true;
-                requestAnimationFrame(() => {
-                    this._roaPending = false;
-                    this.measureIntersectionBox();
-                });
-            }
-        });
-        this._resizeObserver.observe(this.$refs.Container);
-
-        this.loading = true;
-        this.signalTimingData = await apiGetSignalTiming(this.nodeData.id);
-        this.intersectionData = await apiGetIntersectionData(this.nodeData.id) || {};
-        this.loading = false;
-
-        this.startSimulationTimer();
-    },
-    beforeDestroy() {
-    if (this._resizeObserver) this._resizeObserver.disconnect();
-    if (this.timer) clearInterval(this.timer);
-  },
-  methods: {
-    measureIntersectionBox() {
-      const container = this.$refs.Container;
-      const signaltiming = this.$refs.Signaltiming;
-      const box = this.$refs.intersectionBox;
-      if (!container) return;
-      // 容器总高度 - padding(上下各15) - gap(12) - signaltiming高度(300) = intersection可用高度
-      const containerH = container.clientHeight;
-      const containerW = container.clientWidth;
-      const signalH = signaltiming ? signaltiming.clientHeight : 300;
-      const padding = 30; // 上下 padding 各 15px
-      const gap = 12;
-      this.mapWidth = containerW - padding;
-      this.mapHeight = containerH - padding - gap - signalH;
-
-      // 根据 Konva 画布的缩放比例动态计算视频位置
-      if (box) {
-        const boxW = box.clientWidth;
-        const boxH = box.clientHeight;
-        const designSize = 900; // IntersectionMap 设计尺寸
-        const scale = Math.min(boxW / designSize, boxH / designSize);
-
-        // Konva 画布实际尺寸
-        const canvasH = designSize * scale;
-
-        // 视频尺寸(固定高度280,宽度按16:9比例计算)
-        const vh = Math.round(280 * scale);
-        const vw = Math.round(280 * 16 / 9 * scale);
-        // 马路半幅宽(缩放后)
-        const halfRoadScaled = Math.round(160 * scale);
-        // 水平偏移 = box宽度/2 - 视频宽度 - 马路半幅宽
-        const hOffset = Math.round(boxW / 2 - vw - halfRoadScaled - 3); // 3px微调
-        // 垂直偏移 = 画布顶部在容器中的偏移
-        const vOffset = Math.round((boxH - canvasH) / 2);
-
-        const size = { width: vw + 'px', height: vh + 'px' };
-        this.videoStyles = {
-          v1: { ...size, left: hOffset + 'px', top: vOffset + 'px' },
-          v2: { ...size, right: hOffset + 'px', top: vOffset + 'px' },
-          v3: { ...size, left: hOffset + 'px', bottom: vOffset + 'px' },
-          v4: { ...size, right: hOffset + 'px', bottom: vOffset + 'px' }
-        };
-      }
-    },
-    startSimulationTimer() {
-      this.timer = setInterval(() => {
-        // 创建数据的副本以触发 Vue 的响应式更新
-        let newData = JSON.parse(JSON.stringify(this.intersectionData));
-        
-        let ns = newData.signals.ns;
-        let ew = newData.signals.ew;
-
-        // 南北向倒计时
-        ns.time--;
-        if (ns.time < 0) {
-          ns.time = 38;
-          ns.isGreen = !ns.isGreen; // 切换红绿状态
-        }
-
-        // 东西向倒计时
-        ew.time--;
-        if (ew.time < 0) {
-          ew.time = 38;
-          ew.isGreen = !ew.isGreen; // 切换红绿状态
-        }
-
-        // 将新数据赋值回去
-        this.intersectionData = newData;
-      }, 1000);
-    }
-  }
-};
-</script>
-
-<style scoped>
-.container {
-    width: 100%;
-    height: 100%;
-    background-color: #212842;
-    padding: 15px;
-    overflow: hidden;
-    display: flex;
-    flex-direction: column;
-    box-sizing: border-box;
-}
-.container .intersection {
-    flex: 1;
-    min-height: 0;
-    width: 100%;
-    position: relative;
-}
-.container .intersection video {
-    position: absolute;
-    z-index: 10;
-    object-fit: fill;
-}
-.container .signaltiming {
-    margin-top: 12px;
-    width: 100%;
-    min-width: 0;
-    height: 180px;
-    flex-shrink: 0;
-    overflow: hidden;
-}
-</style>

+ 1 - 3
src/components/ui/VideoMonitorBox.vue

@@ -2,9 +2,7 @@
   <div class="video-box">
     <template v-if="videoSrc">
       <XgVideoPlayer :src="videoSrc" />
-      <div class="close-btn" title="关闭视频" @click="handleClose">
-        ×
-      </div>
+      <div class="close-btn" title="关闭视频" @click="handleClose">✕</div>
     </template>
     
     <template v-else>

+ 36 - 7
src/components/ui/XgVideoPlayer.vue

@@ -1,5 +1,9 @@
 <template>
-    <div class="xg-video-player" ref="container"></div>
+    <div class="xg-video-player" ref="container">
+        <div v-if="paused" class="play-overlay" @click.stop="play">
+            <svg class="play-icon" viewBox="0 0 48 48" fill="none"><circle cx="24" cy="24" r="23" fill="rgba(0,0,0,0.5)" stroke="rgba(255,255,255,0.8)" stroke-width="2"/><polygon points="18,14 36,24 18,34" fill="rgba(255,255,255,0.9)"/></svg>
+        </div>
+    </div>
 </template>
 
 <script>
@@ -16,7 +20,7 @@ export default {
         },
         autoplay: {
             type: Boolean,
-            default: true
+            default: false
         },
         loop: {
             type: Boolean,
@@ -50,7 +54,8 @@ export default {
     },
     data() {
         return {
-            player: null
+            player: null,
+            paused: true
         };
     },
     computed: {
@@ -118,7 +123,6 @@ export default {
                 fluid: true,
 
                 autoplay: this.autoplay,
-                autoplayMuted: this.muted,
                 loop: this.loop,
                 volume: this.muted ? 0 : 0.6,
 
@@ -126,15 +130,16 @@ export default {
                 videoFillMode: this.fillMode,
                 isLive: this.live,
 
-                closeVideoClick: true,
+                closeVideoClick: false,
                 closeVideoDblclick: true,
                 enableContextmenu: false,
                 keyShortcut: false,
                 cssFullscreen: false,
+                ignores: ['start'],
             });
 
-            this.player.on('play', () => this.$emit('play'));
-            this.player.on('pause', () => this.$emit('pause'));
+            this.player.on('play', () => { this.paused = false; this.$emit('play'); });
+            this.player.on('pause', () => { this.paused = true; this.$emit('pause'); });
             this.player.on('ended', () => this.$emit('ended'));
             this.player.on('error', (err) => this.$emit('error', err));
         },
@@ -176,6 +181,29 @@ export default {
 .xg-video-player {
     width: 100%;
     height: 100%;
+    position: relative;
+}
+
+.play-overlay {
+    position: absolute;
+    inset: 0;
+    z-index: 20;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+}
+
+.play-icon {
+    width: 40px;
+    height: 40px;
+    opacity: 0.85;
+    transition: opacity 0.2s, transform 0.2s;
+}
+
+.play-overlay:hover .play-icon {
+    opacity: 1;
+    transform: scale(1.1);
 }
 
 .xg-video-player >>> .xgplayer {
@@ -185,4 +213,5 @@ export default {
     width: 100% !important;
     height: 100% !important;
 }
+
 </style>

+ 5 - 3
src/mock/api.js

@@ -18,12 +18,14 @@ import mockData from './mock_data.json'
 
 import video1 from '@/assets/videos/video1.mp4'
 import video2 from '@/assets/videos/video2.mp4'
+import video3 from '@/assets/videos/video3.mp4'
+import video4 from '@/assets/videos/video4.mp4'
 import arrow1 from '@/assets/images/arrow_1.png'
 import arrow2 from '@/assets/images/arrow_2.png'
 import arrow3 from '@/assets/images/arrow_3.png'
 import arrow4 from '@/assets/images/arrow_4.png'
 
-const VIDEOS = [video1, video2]
+const VIDEOS = [video1, video2, video3, video4]
 const ARROWS = [arrow1, arrow2, arrow3, arrow4]
 
 function pickVideo(i) { return VIDEOS[i % VIDEOS.length] }
@@ -199,8 +201,8 @@ function _makePhaseData(cycleLength = 140, isTwoRows = true) {
   return pd;
 }
 
-function _makeCornerVideos(seed = 0) {
-  return { nw: pickVideo(seed), ne: pickVideo(seed + 1), sw: pickVideo(seed + 1), se: pickVideo(seed) }
+function _makeCornerVideos() {
+  return { nw: video1, ne: video2, sw: video3, se: video4 }
 }
 
 function _makeStageList() {