IntersectionSignalMonitoring.vue 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. <template>
  2. <div class="container" ref="Container">
  3. <div class="intersection" ref="intersectionBox">
  4. <div class="video-1" :style="videoStyles.v1"><XgVideoPlayer :src="video1" /></div>
  5. <div class="video-2" :style="videoStyles.v2"><XgVideoPlayer :src="video2" /></div>
  6. <div class="video-3" :style="videoStyles.v3"><XgVideoPlayer :src="video1" /></div>
  7. <div class="video-4" :style="videoStyles.v4"><XgVideoPlayer :src="video2" /></div>
  8. <IntersectionMap :mapData="intersectionData" />
  9. </div>
  10. <div class="signaltiming" ref="Signaltiming">
  11. <SignalTimingChart
  12. :loading="loading"
  13. :cycle-length="signalTimingData.cycleLength"
  14. :current-time="signalTimingData.currentTime"
  15. :phase-data="signalTimingData.phaseData" />
  16. </div>
  17. </div>
  18. </template>
  19. <script>
  20. import SignalTimingChart from '@/components/ui/SignalTimingChart.vue';
  21. import IntersectionMap from '@/components/ui/IntersectionMap.vue';
  22. import { apiGetSignalTiming, apiGetIntersectionData } from '@/api';
  23. import video1 from '@/assets/videos/video1.mp4';
  24. import video2 from '@/assets/videos/video2.mp4';
  25. import XgVideoPlayer from '@/components/ui/XgVideoPlayer.vue';
  26. export default {
  27. name: "IntersectionSignalMonitoring",
  28. components: {
  29. SignalTimingChart,
  30. IntersectionMap,
  31. XgVideoPlayer
  32. },
  33. props: {
  34. nodeData: {
  35. type: Object,
  36. default: () => ({})
  37. }
  38. },
  39. data() {
  40. return {
  41. signalTimingData: {},
  42. loading: false,
  43. intersectionData: {},
  44. timer: null,
  45. mapWidth: 600,
  46. mapHeight: 600,
  47. video1,
  48. video2,
  49. videoStyles: { v1: {}, v2: {}, v3: {}, v4: {} }
  50. };
  51. },
  52. computed: {
  53. },
  54. created() {
  55. },
  56. async mounted() {
  57. this.measureIntersectionBox();
  58. this._roaPending = false;
  59. this._resizeObserver = new ResizeObserver(() => {
  60. if (!this._roaPending) {
  61. this._roaPending = true;
  62. requestAnimationFrame(() => {
  63. this._roaPending = false;
  64. this.measureIntersectionBox();
  65. });
  66. }
  67. });
  68. this._resizeObserver.observe(this.$refs.Container);
  69. this.loading = true;
  70. this.signalTimingData = await apiGetSignalTiming(this.nodeData.id);
  71. this.intersectionData = await apiGetIntersectionData(this.nodeData.id) || {};
  72. this.loading = false;
  73. this.startSimulationTimer();
  74. },
  75. beforeDestroy() {
  76. if (this._resizeObserver) this._resizeObserver.disconnect();
  77. if (this.timer) clearInterval(this.timer);
  78. },
  79. methods: {
  80. measureIntersectionBox() {
  81. const container = this.$refs.Container;
  82. const signaltiming = this.$refs.Signaltiming;
  83. const box = this.$refs.intersectionBox;
  84. if (!container) return;
  85. // 容器总高度 - padding(上下各15) - gap(12) - signaltiming高度(300) = intersection可用高度
  86. const containerH = container.clientHeight;
  87. const containerW = container.clientWidth;
  88. const signalH = signaltiming ? signaltiming.clientHeight : 300;
  89. const padding = 30; // 上下 padding 各 15px
  90. const gap = 12;
  91. this.mapWidth = containerW - padding;
  92. this.mapHeight = containerH - padding - gap - signalH;
  93. // 根据 Konva 画布的缩放比例动态计算视频位置
  94. if (box) {
  95. const boxW = box.clientWidth;
  96. const boxH = box.clientHeight;
  97. const designSize = 900; // IntersectionMap 设计尺寸
  98. const scale = Math.min(boxW / designSize, boxH / designSize);
  99. // Konva 画布实际尺寸
  100. const canvasH = designSize * scale;
  101. // 视频尺寸(固定高度280,宽度按16:9比例计算)
  102. const vh = Math.round(280 * scale);
  103. const vw = Math.round(280 * 16 / 9 * scale);
  104. // 马路半幅宽(缩放后)
  105. const halfRoadScaled = Math.round(160 * scale);
  106. // 水平偏移 = box宽度/2 - 视频宽度 - 马路半幅宽
  107. const hOffset = Math.round(boxW / 2 - vw - halfRoadScaled - 3); // 3px微调
  108. // 垂直偏移 = 画布顶部在容器中的偏移
  109. const vOffset = Math.round((boxH - canvasH) / 2);
  110. const size = { width: vw + 'px', height: vh + 'px' };
  111. this.videoStyles = {
  112. v1: { ...size, left: hOffset + 'px', top: vOffset + 'px' },
  113. v2: { ...size, right: hOffset + 'px', top: vOffset + 'px' },
  114. v3: { ...size, left: hOffset + 'px', bottom: vOffset + 'px' },
  115. v4: { ...size, right: hOffset + 'px', bottom: vOffset + 'px' }
  116. };
  117. }
  118. },
  119. startSimulationTimer() {
  120. this.timer = setInterval(() => {
  121. // 创建数据的副本以触发 Vue 的响应式更新
  122. let newData = JSON.parse(JSON.stringify(this.intersectionData));
  123. let ns = newData.signals.ns;
  124. let ew = newData.signals.ew;
  125. // 南北向倒计时
  126. ns.time--;
  127. if (ns.time < 0) {
  128. ns.time = 38;
  129. ns.isGreen = !ns.isGreen; // 切换红绿状态
  130. }
  131. // 东西向倒计时
  132. ew.time--;
  133. if (ew.time < 0) {
  134. ew.time = 38;
  135. ew.isGreen = !ew.isGreen; // 切换红绿状态
  136. }
  137. // 将新数据赋值回去
  138. this.intersectionData = newData;
  139. }, 1000);
  140. }
  141. }
  142. };
  143. </script>
  144. <style scoped>
  145. .container {
  146. width: 100%;
  147. height: 100%;
  148. background-color: #212842;
  149. padding: 15px;
  150. overflow: hidden;
  151. display: flex;
  152. flex-direction: column;
  153. box-sizing: border-box;
  154. }
  155. .container .intersection {
  156. flex: 1;
  157. min-height: 0;
  158. width: 100%;
  159. position: relative;
  160. }
  161. .container .intersection video {
  162. position: absolute;
  163. z-index: 10;
  164. object-fit: fill;
  165. }
  166. .container .signaltiming {
  167. margin-top: 12px;
  168. width: 100%;
  169. min-width: 0;
  170. height: 180px;
  171. flex-shrink: 0;
  172. overflow: hidden;
  173. }
  174. </style>