|
@@ -65,7 +65,16 @@
|
|
|
class="stage-item-wrapper">
|
|
class="stage-item-wrapper">
|
|
|
<div class="phase-box" :class="{ 'is-active': item.value === currentStage }"
|
|
<div class="phase-box" :class="{ 'is-active': item.value === currentStage }"
|
|
|
@click="onStageClick(item.value)">
|
|
@click="onStageClick(item.value)">
|
|
|
- <img :src="item.img" alt="stage" class="phase-image" />
|
|
|
|
|
|
|
+ <PhaseDiagram
|
|
|
|
|
+ v-if="item.icons && item.icons.length"
|
|
|
|
|
+ :icons="item.icons"
|
|
|
|
|
+ :no="item.no || item.value"
|
|
|
|
|
+ :arrow-color="item.arrowColor"
|
|
|
|
|
+ :arrow-colors="item.arrowColors || {}"
|
|
|
|
|
+ :bg-color="item.bgColor"
|
|
|
|
|
+ :number-color="item.numberColor"
|
|
|
|
|
+ />
|
|
|
|
|
+ <img v-else :src="item.img" alt="stage" class="phase-image" />
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="bottom-controls">
|
|
<div class="bottom-controls">
|
|
@@ -186,6 +195,7 @@ import SignalTimingChart from '@/components/ui/SignalTimingChart.vue';
|
|
|
import IntersectionMapVideos from '@/components/ui/IntersectionMapVideos.vue';
|
|
import IntersectionMapVideos from '@/components/ui/IntersectionMapVideos.vue';
|
|
|
import DropdownSelect from '@/components/ui/DropdownSelect.vue';
|
|
import DropdownSelect from '@/components/ui/DropdownSelect.vue';
|
|
|
import PlanDonutChart from '@/components/ui/PlanDonutChart.vue';
|
|
import PlanDonutChart from '@/components/ui/PlanDonutChart.vue';
|
|
|
|
|
+import PhaseDiagram from '@/components/ui/PhaseDiagram.vue';
|
|
|
|
|
|
|
|
import { apiGetCrossingDetailData, apiSaveCrossingTempScheme, apiSaveCrossingScheme } from '@/api';
|
|
import { apiGetCrossingDetailData, apiSaveCrossingTempScheme, apiSaveCrossingScheme } from '@/api';
|
|
|
|
|
|
|
@@ -195,7 +205,8 @@ export default {
|
|
|
SignalTimingChart,
|
|
SignalTimingChart,
|
|
|
IntersectionMapVideos,
|
|
IntersectionMapVideos,
|
|
|
DropdownSelect,
|
|
DropdownSelect,
|
|
|
- PlanDonutChart
|
|
|
|
|
|
|
+ PlanDonutChart,
|
|
|
|
|
+ PhaseDiagram,
|
|
|
},
|
|
},
|
|
|
props: {
|
|
props: {
|
|
|
preloadedData: { type: Object, default: null },
|
|
preloadedData: { type: Object, default: null },
|
|
@@ -468,6 +479,15 @@ export default {
|
|
|
this.applyData(data);
|
|
this.applyData(data);
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
+ // 给每个 stage 派生 icons (PhaseDiagram 渲染用):
|
|
|
|
|
+ // 优先用后端直接给的 icons[], 否则从 direction 字符串拆 token。
|
|
|
|
|
+ // 缺失时 PhaseDiagram 不渲染, 模板内 fallback 到旧的 <img :src="item.img">。
|
|
|
|
|
+ _withIcons(stages) {
|
|
|
|
|
+ return (stages || []).map(item => ({
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ icons: item.icons || (item.direction || '').split(',').filter(Boolean),
|
|
|
|
|
+ }));
|
|
|
|
|
+ },
|
|
|
applyData(data) {
|
|
applyData(data) {
|
|
|
this.currentRoute = data.currentRoute || {};
|
|
this.currentRoute = data.currentRoute || {};
|
|
|
this.intersectionData = data.intersectionData || {};
|
|
this.intersectionData = data.intersectionData || {};
|
|
@@ -476,7 +496,7 @@ export default {
|
|
|
this.currentSec = data.currentTime || 0;
|
|
this.currentSec = data.currentTime || 0;
|
|
|
this.phaseDiff = data.phaseDiff || 0;
|
|
this.phaseDiff = data.phaseDiff || 0;
|
|
|
this.coordTime = data.coordTime || 0;
|
|
this.coordTime = data.coordTime || 0;
|
|
|
- this.currentStageList = data.stageList || [];
|
|
|
|
|
|
|
+ this.currentStageList = this._withIcons(data.stageList);
|
|
|
// 双相位图字段:后端没返回时为 null,UI 自动退化为单图模式
|
|
// 双相位图字段:后端没返回时为 null,UI 自动退化为单图模式
|
|
|
this.thisCycle = data.thisCycle || null;
|
|
this.thisCycle = data.thisCycle || null;
|
|
|
this.lastCycle = data.lastCycle || null;
|
|
this.lastCycle = data.lastCycle || null;
|
|
@@ -595,7 +615,7 @@ export default {
|
|
|
console.warn('[CrossingDetailPanel] saveTempScheme failed:', e);
|
|
console.warn('[CrossingDetailPanel] saveTempScheme failed:', e);
|
|
|
}
|
|
}
|
|
|
// 本地写回(不阻塞 UI)
|
|
// 本地写回(不阻塞 UI)
|
|
|
- if (Array.isArray(payload.stages)) this.currentStageList = payload.stages;
|
|
|
|
|
|
|
+ if (Array.isArray(payload.stages)) this.currentStageList = this._withIcons(payload.stages);
|
|
|
const tr = payload.timeRange || {};
|
|
const tr = payload.timeRange || {};
|
|
|
if (tr.startDate !== undefined) this.startDate = tr.startDate;
|
|
if (tr.startDate !== undefined) this.startDate = tr.startDate;
|
|
|
if (tr.startTime !== undefined) this.startTime = tr.startTime;
|
|
if (tr.startTime !== undefined) this.startTime = tr.startTime;
|
|
@@ -614,7 +634,7 @@ export default {
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
console.warn('[CrossingDetailPanel] saveScheme failed:', e);
|
|
console.warn('[CrossingDetailPanel] saveScheme failed:', e);
|
|
|
}
|
|
}
|
|
|
- if (Array.isArray(payload.stages)) this.currentStageList = payload.stages;
|
|
|
|
|
|
|
+ if (Array.isArray(payload.stages)) this.currentStageList = this._withIcons(payload.stages);
|
|
|
this.buildDonutFromPhaseData();
|
|
this.buildDonutFromPhaseData();
|
|
|
this.$emit('confirm', { method: this.currentMethod, scheme: this.currentScheme, stages: this.currentStageList });
|
|
this.$emit('confirm', { method: this.currentMethod, scheme: this.currentScheme, stages: this.currentStageList });
|
|
|
},
|
|
},
|