Просмотр исходного кода

十字路口新增simple图标模式,简化各方向箭头显示

  1. apiGetCrossingDetailData接口新增iconMode参数,支持传递图标模式
  2. mockAdapter解析iconMode参数并透传给mock API
  3. mock数据层_makeIntersectionConfig和_makePhaseData支持iconMode参数,
     simple模式下各方向仅显示2个箭头(北/东:左转+直行,南/西:掉头+直行)
  4. simple模式相位图标P2改为北左转+南掉头,P4改为东左转+西掉头
  5. CrossingDetailPanel组件新增iconMode prop,请求数据时传递该参数
  6. 总览小弹窗(CrossingPanel)默认使用simple模式获取路口和相位数据
  7. 状态监控、干线协调、特勤监控页面的路口弹窗统一使用simple模式
  8. CrossingMultiView多路口分屏视图也使用simple模式
画安 недель назад: 2
Родитель
Сommit
f81d0ddbca

+ 2 - 2
src/api/index.js

@@ -101,8 +101,8 @@ export const apiGetSpecialTaskMonitorData = (id) =>
 export const apiGetCrossingPanelData = (id) =>
   http.get(`/crossing/panel/${id}`)
 
-export const apiGetCrossingDetailData = (id) =>
-  http.get(`/crossing/detail/${id}`)
+export const apiGetCrossingDetailData = (id, { iconMode } = {}) =>
+  http.get(`/crossing/detail/${id}`, { params: iconMode ? { iconMode } : undefined })
 
 export const apiGetCrossingTopCharts = () =>
   http.get('/crossing/top-charts')

+ 3 - 2
src/components/ui/CrossingDetailPanel.vue

@@ -150,7 +150,8 @@ export default {
         PlanDonutChart
     },
     props: {
-        preloadedData: { type: Object, default: null }
+        preloadedData: { type: Object, default: null },
+        iconMode: { type: String, default: 'simple' }  // 'default' | 'simple'
     },
     data() {
         return {
@@ -278,7 +279,7 @@ export default {
         },
         async loadData() {
             const nodeId = this.$attrs.id || this.id;
-            const data = await apiGetCrossingDetailData(nodeId);
+            const data = await apiGetCrossingDetailData(nodeId, { iconMode: this.iconMode });
             if (data) {
                 this.applyData(data);
             }

+ 1 - 1
src/components/ui/CrossingMultiView.vue

@@ -156,7 +156,7 @@ export default {
             if (needLoad.length === 0) return;
             const version = ++this.rebuildVersion;
             const results = await Promise.all(
-                needLoad.map(item => apiGetCrossingDetailData(item.id).catch(() => null))
+                needLoad.map(item => apiGetCrossingDetailData(item.id, { iconMode: 'simple' }).catch(() => null))
             );
             if (version !== this.rebuildVersion) return;
             results.forEach((data, i) => {

+ 42 - 22
src/mock/api.js

@@ -110,7 +110,7 @@ function _camerasToArmTypes(cameras) {
   return result
 }
 
-function _makeIntersectionConfig(id, name, { fixedNsGreen } = {}) {
+function _makeIntersectionConfig(id, name, { fixedNsGreen, iconMode = 'default' } = {}) {
   const phases = ['南北直行', '东西直行', '北单放', '东单放']
   const nsGreen = fixedNsGreen !== undefined ? fixedNsGreen : false
   const countdown = 10 + Math.floor(Math.random() * 50)
@@ -119,10 +119,21 @@ function _makeIntersectionConfig(id, name, { fixedNsGreen } = {}) {
   const cameras = _makeCameras(id, name, seed)
   const armCamTypes = _camerasToArmTypes(cameras)
 
-  const lanePresets = [
-    ['U', 'L', 'S', 'R'], ['U', 'L', 'S', 'R'],
-    ['U', 'L', 'S', 'R'], ['U', 'L', 'S', 'R'],
-  ]
+  const lanesPresetMap = {
+    default: {
+      N: ['U', 'L', 'S', 'R'],
+      S: ['U', 'L', 'S', 'R'],
+      E: ['U', 'L', 'S', 'R'],
+      W: ['U', 'L', 'S', 'R'],
+    },
+    simple: {
+      N: ['L', 'S', null, null],  // 北:左转+直行
+      S: ['U', 'S', null, null],  // 南:掉头+直行
+      E: ['L', 'S', null, null],  // 东:掉头+直行
+      W: ['U', 'S', null, null],  // 西:左转+直行
+    },
+  }
+  const lanesPreset = lanesPresetMap[iconMode] || lanesPresetMap.default
 
   return {
     status: _getDeviceStatus(id),
@@ -131,10 +142,10 @@ function _makeIntersectionConfig(id, name, { fixedNsGreen } = {}) {
       ew: { phaseName: phases[1], time: countdown, isGreen: !nsGreen },
     },
     armsConfig: {
-      N: { cameraType: armCamTypes.N, lanes: lanePresets[0] },
-      S: { cameraType: armCamTypes.S, lanes: lanePresets[1] },
-      E: { cameraType: armCamTypes.E, lanes: lanePresets[2] },
-      W: { cameraType: armCamTypes.W, lanes: lanePresets[3] },
+      N: { cameraType: armCamTypes.N, lanes: lanesPreset.N },
+      S: { cameraType: armCamTypes.S, lanes: lanesPreset.S },
+      E: { cameraType: armCamTypes.E, lanes: lanesPreset.E },
+      W: { cameraType: armCamTypes.W, lanes: lanesPreset.W },
     },
     cameras,
   }
@@ -145,9 +156,9 @@ function _makeIntersectionConfig(id, name, { fixedNsGreen } = {}) {
  * @param {number} cycleLength 周期总时长
  * @param {boolean} isTwoRows 是否生成上下双排 8 相位 (默认 true)
  */
-function _makePhaseData(cycleLength = 140, isTwoRows = true) {
+function _makePhaseData(cycleLength = 140, isTwoRows = true, iconMode = 'default') {
   const n = 4; // 4个阶段 (S1-S4)
-  const stageTime = Math.floor(cycleLength / n); 
+  const stageTime = Math.floor(cycleLength / n);
   const pd = [];
 
   // ==========================================
@@ -155,12 +166,21 @@ function _makePhaseData(cycleLength = 140, isTwoRows = true) {
   // 前端组件会按逗号切割并分别放到对角位置
   // ==========================================
   // 固定4个阶段的图标和方向:P1南北直行、P2南北左转、P3东西直行、P4东西左转
-  const phaseConfig = [
-    { icon: 'STRAIGHT_DOWN,STRAIGHT_UP', direction: 'ns' },       // P1: 南北直行
-    { icon: 'TURN_DOWN_LEFT,TURN_UP_LEFT', direction: 'ns' },     // P2: 南北左转
-    { icon: 'STRAIGHT_LEFT,STRAIGHT_RIGHT', direction: 'ew' },    // P3: 东西直行
-    { icon: 'TURN_LEFT_DOWN,TURN_RIGHT_UP', direction: 'ew' },    // P4: 东西左转
-  ];
+  const phaseConfigMap = {
+    default: [
+      { icon: 'STRAIGHT_DOWN,STRAIGHT_UP', direction: 'ns' },       // P1: 南北直行
+      { icon: 'TURN_DOWN_LEFT,TURN_UP_LEFT', direction: 'ns' },     // P2: 南北左转
+      { icon: 'STRAIGHT_LEFT,STRAIGHT_RIGHT', direction: 'ew' },    // P3: 东西直行
+      { icon: 'TURN_LEFT_DOWN,TURN_RIGHT_UP', direction: 'ew' },    // P4: 东西左转
+    ],
+    simple: [
+      { icon: 'STRAIGHT_DOWN,STRAIGHT_UP', direction: 'ns' },              // P1: 南北直行
+      { icon: 'TURN_DOWN_LEFT,TURN_UP_LEFT_UTURN', direction: 'ns' },      // P2: 北左转+南掉头
+      { icon: 'STRAIGHT_LEFT,STRAIGHT_RIGHT', direction: 'ew' },           // P3: 东西直行
+      { icon: 'TURN_LEFT_DOWN,TURN_RIGHT_UP_UTURN', direction: 'ew' },      // P4: 东左转+西掉头
+    ],
+  };
+  const phaseConfig = phaseConfigMap[iconMode] || phaseConfigMap.default;
 
   let t = 0;
   for (let i = 0; i < n; i++) {
@@ -786,7 +806,7 @@ export async function apiGetSpecialTaskMonitorData(id, { fixedNsGreen } = {}) {
 export async function apiGetCrossingPanelData(id) {
   await delay(300)
   const point = DB.points.find(p => p.id === id)
-  const config = DB.intersectionConfigs[id] || _makeIntersectionConfig(id, null, { fixedNsGreen: false })
+  const config = DB.intersectionConfigs[id] || _makeIntersectionConfig(id, null, { fixedNsGreen: false, iconMode: 'simple' })
   const seed = id ? id.charCodeAt(id.length - 1) : 0
   // 确保 config 有 status
   if (!config.status) {
@@ -795,7 +815,7 @@ export async function apiGetCrossingPanelData(id) {
 
   const preset = DB.signalTimings[id]
   const cycleLength = preset ? preset.data.cycleLength : [100, 120, 130, 140, 150, 160][Math.abs(seed) % 6]
-  const phaseData = preset ? preset.data.phaseData : _makePhaseData(cycleLength, false)
+  const phaseData = preset ? preset.data.phaseData : _makePhaseData(cycleLength, false, 'simple')
   const currentTime = Math.floor(Date.now() / 1000) % cycleLength
 
   return ok({
@@ -814,10 +834,10 @@ export async function apiGetCrossingPanelData(id) {
 /**
  * GET /api/crossing/detail/:id — CrossingDetailPanel 弹窗
  */
-export async function apiGetCrossingDetailData(id) {
+export async function apiGetCrossingDetailData(id, { iconMode = 'default' } = {}) {
   await delay(350)
   const point = DB.points.find(p => p.id === id)
-  const config = DB.intersectionConfigs[id] || _makeIntersectionConfig(id, null, { fixedNsGreen: false })
+  const config = DB.intersectionConfigs[id] || _makeIntersectionConfig(id, null, { fixedNsGreen: false, iconMode })
 
   // 用 id 的全部字符生成稳定 seed(加权位置避免 charCode 总和碰撞)
   const seed = id ? Array.from(id).reduce((s, c, i) => s + c.charCodeAt(0) * (i + 1), 0) : 0
@@ -830,7 +850,7 @@ export async function apiGetCrossingDetailData(id) {
   // 从真实阶段数据推导周期和相位
   const preset = DB.signalTimings[id]
   const cycleLength = preset ? preset.data.cycleLength : [100, 120, 130, 140, 150, 160][seed % 6]
-  const phaseData = preset ? preset.data.phaseData : _makePhaseData(cycleLength || 140, false)
+  const phaseData = preset ? preset.data.phaseData : _makePhaseData(cycleLength || 140, false, iconMode)
 
   // 从相位数据中提取阶段列表(优先上轨道绿灯相位,单轨道时取 track 0,最多4个)
   const hasTrack1 = phaseData.some(p => p[0] === 1)

+ 2 - 1
src/mock/mockAdapter.js

@@ -191,7 +191,8 @@ mock.onGet(/\/crossing\/panel\/(.+)/).reply(async (config) => {
 
 mock.onGet(/\/crossing\/detail\/(.+)/).reply(async (config) => {
   const id = config.url.match(/\/crossing\/detail\/(.+)/)[1]
-  const res = await mockApi.apiGetCrossingDetailData(id)
+  const iconMode = (config.params && config.params.iconMode) || 'default'
+  const res = await mockApi.apiGetCrossingDetailData(id, { iconMode })
   return [200, res]
 })
 

+ 1 - 1
src/views/SpecialSituationMonitoring.vue

@@ -442,7 +442,7 @@ export default {
         // 单个路口详情弹窗(总览双击展开等场景使用)
         async showCrossingDetailDialogs(nodeData) {
             console.log('显示路口详情弹窗组', nodeData.id, nodeData.label);
-            const detailData = await apiGetCrossingDetailData(nodeData.id);
+            const detailData = await apiGetCrossingDetailData(nodeData.id, { iconMode: 'simple' });
             const dialogId = 'crossing_detail' + nodeData.id;
             this.$refs.layout.openDialog({
                 id: dialogId,

+ 2 - 2
src/views/StatusMonitoring.vue

@@ -392,7 +392,7 @@ export default {
             console.log('路口多选', nodeData.id, nodeData.label);
 
             // 0. 离线检查
-            const detailData = await apiGetCrossingDetailData(nodeData.id);
+            const detailData = await apiGetCrossingDetailData(nodeData.id, { iconMode: 'simple' });
             if (detailData?.intersectionData?.status !== '在线') {
                 this.$msg({
                     title: '提示',
@@ -459,7 +459,7 @@ export default {
         // 单个路口详情弹窗(总览双击展开等场景使用)
         async showCrossingDetailDialogs(nodeData) {
             console.log('显示路口详情弹窗组', nodeData.id, nodeData.label);
-            const detailData = await apiGetCrossingDetailData(nodeData.id);
+            const detailData = await apiGetCrossingDetailData(nodeData.id, { iconMode: 'simple' });
             if (detailData?.intersectionData?.status !== '在线') {
                 this.$msg({
                     title: '提示',

+ 1 - 1
src/views/TrunkCoordination.vue

@@ -442,7 +442,7 @@ export default {
         // 单个路口详情弹窗(总览双击展开等场景使用)
         async showCrossingDetailDialogs(nodeData) {
             console.log('显示路口详情弹窗组', nodeData.id, nodeData.label);
-            const detailData = await apiGetCrossingDetailData(nodeData.id);
+            const detailData = await apiGetCrossingDetailData(nodeData.id, { iconMode: 'simple' });
             const dialogId = 'crossing_detail' + nodeData.id;
             this.$refs.layout.openDialog({
                 id: dialogId,