IntersectionSignalMonitoring.vue 6.0 KB

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