|
|
@@ -3,8 +3,34 @@
|
|
|
<div class="intersection-video-wrap">
|
|
|
<IntersectionMapVideos :mapData="intersectionData" />
|
|
|
</div>
|
|
|
- <div class="signal-timing-wrap">
|
|
|
- <SignalTimingChart :cycleLength="cycleLength" :currentTime="currentSec" :phaseData="mockPhaseData" :showScanLine="dataReady" :autoScan="dataReady" @scan-tick="onScanTick" />
|
|
|
+ <div class="signal-timing-wrap" :class="{ 'is-dual': isDual }">
|
|
|
+ <template v-if="isDual">
|
|
|
+ <div class="timing-row timing-row-live">
|
|
|
+ <div class="row-label">
|
|
|
+ 本周期 实时<span v-if="thisCycle"> · {{ thisCycle.schemeName }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="row-chart">
|
|
|
+ <SignalTimingChart :cycleLength="thisCycle.cycleLength" :currentTime="currentSec"
|
|
|
+ :phaseData="thisCycle.phaseData" :showScanLine="dataReady" :showScanLineLabel="dataReady"
|
|
|
+ :clipToActive="true" :compactScanLine="true" :autoScan="dataReady"
|
|
|
+ @scan-tick="onScanTick" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="timing-row timing-row-last">
|
|
|
+ <div class="row-label">
|
|
|
+ 上周期 方案
|
|
|
+ <span v-if="lastCycle"> · 实际 {{ lastCycle.actualDuration }}s / 计划 {{ lastCycle.cycleLength }}s</span>
|
|
|
+ </div>
|
|
|
+ <div class="row-chart">
|
|
|
+ <SignalTimingChart v-if="lastCycle" :cycleLength="lastCycle.cycleLength" :currentTime="0"
|
|
|
+ :phaseData="lastCycle.phaseData" :showScanLine="false" :showScanLineLabel="false" />
|
|
|
+ <div v-else class="empty-placeholder">暂无上周期数据</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <SignalTimingChart v-else :cycleLength="cycleLength" :currentTime="currentSec"
|
|
|
+ :phaseData="mockPhaseData" :showScanLine="dataReady" :autoScan="dataReady"
|
|
|
+ @scan-tick="onScanTick" />
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
@@ -31,23 +57,48 @@ export default {
|
|
|
intersectionData: {},
|
|
|
currentRoute: {},
|
|
|
cycleLength: 140,
|
|
|
- currentSec: 15,
|
|
|
- mockPhaseData: []
|
|
|
+ currentSec: 0,
|
|
|
+ mockPhaseData: [],
|
|
|
+ thisCycle: null,
|
|
|
+ lastCycle: null,
|
|
|
}
|
|
|
},
|
|
|
- async mounted() {
|
|
|
- const nodeId = this.$attrs.id || this.id;
|
|
|
- const data = await apiGetCrossingPanelData(nodeId);
|
|
|
- if (data) {
|
|
|
- this.currentRoute = data.currentRoute || {};
|
|
|
- this.intersectionData = data.intersectionData || {};
|
|
|
- this.mockPhaseData = data.phaseData || [];
|
|
|
- if (data.cycleLength) this.cycleLength = data.cycleLength;
|
|
|
- if (data.currentTime !== undefined) this.currentSec = data.currentTime;
|
|
|
- this.$nextTick(() => { this.dataReady = true; });
|
|
|
- }
|
|
|
+ computed: {
|
|
|
+ isDual() {
|
|
|
+ return !!(this.thisCycle && this.thisCycle.phaseData && this.thisCycle.phaseData.length);
|
|
|
+ },
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.initScaleObserver();
|
|
|
+ this.loadData();
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ if (this._ro) this._ro.disconnect();
|
|
|
},
|
|
|
methods: {
|
|
|
+ initScaleObserver() {
|
|
|
+ const ro = new ResizeObserver(entries => {
|
|
|
+ const { width } = entries[0].contentRect;
|
|
|
+ const s = Math.min(width / 260, 1);
|
|
|
+ this.$el.style.setProperty('--s', s);
|
|
|
+ });
|
|
|
+ ro.observe(this.$el);
|
|
|
+ this._ro = ro;
|
|
|
+ },
|
|
|
+ async loadData() {
|
|
|
+ const nodeId = this.$attrs.id || this.id;
|
|
|
+ const data = await apiGetCrossingPanelData(nodeId);
|
|
|
+ if (data) {
|
|
|
+ this.currentRoute = data.currentRoute || {};
|
|
|
+ this.intersectionData = data.intersectionData || {};
|
|
|
+ this.mockPhaseData = data.phaseData || [];
|
|
|
+ if (data.cycleLength) this.cycleLength = data.cycleLength;
|
|
|
+ if (data.currentTime !== undefined) this.currentSec = data.currentTime;
|
|
|
+ this.thisCycle = data.thisCycle || null;
|
|
|
+ this.lastCycle = data.lastCycle || null;
|
|
|
+ this.$nextTick(() => { this.dataReady = true; });
|
|
|
+ }
|
|
|
+ },
|
|
|
onScanTick(activeTime) {
|
|
|
if (!this.mockPhaseData || this.mockPhaseData.length === 0) return;
|
|
|
const phase = this.mockPhaseData.find(p => p[0] === 0 && activeTime >= p[1] && activeTime < p[2]);
|
|
|
@@ -98,10 +149,11 @@ export default {
|
|
|
|
|
|
<style scoped>
|
|
|
.crossing-panel {
|
|
|
+ --s: 1;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
width: 100%;
|
|
|
- height: 100%;
|
|
|
+ height: 100%;
|
|
|
min-height: 0;
|
|
|
}
|
|
|
|
|
|
@@ -112,11 +164,10 @@ export default {
|
|
|
}
|
|
|
|
|
|
.signal-timing-wrap {
|
|
|
- flex: 1;
|
|
|
+ flex: 0 0 auto;
|
|
|
min-height: 0;
|
|
|
- --s: 1;
|
|
|
+ height: clamp(56px, calc(var(--s) * 80px), 80px);
|
|
|
width: 100%;
|
|
|
- height: 80px;
|
|
|
min-width: 0;
|
|
|
background-color: transparent;
|
|
|
box-sizing: border-box;
|
|
|
@@ -124,7 +175,47 @@ export default {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
overflow: hidden;
|
|
|
- padding: calc(var(--s) * 10px) 0 0 0;
|
|
|
+ padding: clamp(3px, calc(var(--s) * 10px), 10px) 0 0 0;
|
|
|
+}
|
|
|
+
|
|
|
+.signal-timing-wrap.is-dual {
|
|
|
+ height: clamp(90px, calc(var(--s) * 140px), 140px);
|
|
|
+}
|
|
|
+
|
|
|
+.timing-row {
|
|
|
+ flex: 1 1 0;
|
|
|
+ min-height: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.row-label {
|
|
|
+ flex: 0 0 auto;
|
|
|
+ font-size: clamp(8px, calc(var(--s) * 11px), 11px);
|
|
|
+ color: #9ca3af;
|
|
|
+ padding: 0 4px;
|
|
|
+ line-height: 1;
|
|
|
+ margin-bottom: 2px;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+}
|
|
|
+
|
|
|
+.row-chart {
|
|
|
+ flex: 1 1 0;
|
|
|
+ min-height: 0;
|
|
|
+ display: flex;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-placeholder {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #6b7280;
|
|
|
+ font-size: clamp(10px, calc(var(--s) * 12px), 12px);
|
|
|
}
|
|
|
|
|
|
.header {
|