| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- <template>
- <div class="container" ref="Container">
- <div class="intersection" ref="intersectionBox">
- <video class="video-1" :src="video1" :style="videoStyles.v1" autoplay muted loop></video>
- <video class="video-2" :src="video2" :style="videoStyles.v2" autoplay muted loop></video>
- <video class="video-3" :src="video1" :style="videoStyles.v3" autoplay muted loop></video>
- <video class="video-4" :src="video2" :style="videoStyles.v4" autoplay muted loop></video>
- <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/SignalTimingChart.vue';
- import IntersectionMap from '@/components/ui/IntersectionMap.vue';
- import { fetchSignalTimingData, getIntersectionData } from '@/mock/data';
- import video1 from '@/assets/videos/video1.mp4';
- import video2 from '@/assets/videos/video2.mp4';
- export default {
- name: "IntersectionSignalMonitoring",
- components: {
- SignalTimingChart,
- IntersectionMap
- },
- 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;
- const signalRes = await fetchSignalTimingData(this.nodeData.id);
- this.signalTimingData = signalRes.data;
- this.intersectionData = await getIntersectionData(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>
|