3 Achegas f73c937abf ... 848647ef45

Autor SHA1 Mensaxe Data
  画安 848647ef45 首页左右侧边栏添加深蓝渐变背景蒙层 hai 1 semana
  画安 e1e0699cad 优化 CrossingDetailPanel 布局,饼图区域不受禁用蒙层影响 hai 2 semanas
  画安 5dfdebbb45 修复勤务路线地图标记点击与任务列表弹窗数据不一致的问题 hai 2 semanas

+ 56 - 9
src/components/TongzhouTrafficMap.vue

@@ -89,6 +89,7 @@ export default {
       markerById: {}, // ID → marker 索引,加速 focusById 查找
       startEndMarkers: [], // 起点/终点 marker 引用,缩放时需重建
       subAreaOverlays: [], // 子区蒙层覆盖物
+      _subAreaParams: null, // 子区绘制参数,缩放时重绘用
     };
   },
   mounted() {
@@ -319,6 +320,8 @@ export default {
           if (!this.isComponentDestroyed) {
             this.currentZoomSize = this.getDotSizeByZoom();
             this.rebuildStartEndMarkers();
+            this.rebuildDotMarkers();
+            this.rebuildSubAreaText();
           }
         });
       } catch (err) {
@@ -348,11 +351,11 @@ export default {
           { start: [116.66325, 39.9171], end: [116.6833, 39.9171], color: "#13C373", trunkId: 'trunk_6', trunkName: '张台路与湖亦路路口' },
         ],
         "勤务路线": [
-          { start: [116.6445, 39.8980], end: [116.6850, 39.8980], color: "#BC301D", dutyState: 'pending', taskId: 1 },
-          { start: [116.6850, 39.8980], end: [116.7250, 39.8980], color: "#BC301D", dutyState: 'active', progress: 0.45, taskId: 3 },
-          { start: [116.7250, 39.8980], end: [116.7650, 39.8980], color: "#BC301D", dutyState: 'done', taskId: 2 },
-          { start: [116.6445, 39.9080], end: [116.7050, 39.9080], color: "#BC301D", dutyState: 'done', taskId: 4 },
-          { start: [116.6445, 39.8880], end: [116.7050, 39.8880], color: "#BC301D", dutyState: 'pending', taskId: 5 }
+          { start: [116.6400, 39.9270], end: [116.7000, 39.9270], color: "#BC301D", dutyState: 'pending', taskId: 1 },
+          { start: [116.6400, 39.9120], end: [116.7000, 39.9120], color: "#BC301D", dutyState: 'active', progress: 0.45, taskId: 3 },
+          { start: [116.6700, 39.8970], end: [116.7300, 39.8970], color: "#BC301D", dutyState: 'done', taskId: 2 },
+          { start: [116.6400, 39.8820], end: [116.7000, 39.8820], color: "#BC301D", dutyState: 'done', taskId: 4 },
+          { start: [116.6400, 39.8670], end: [116.7000, 39.8670], color: "#BC301D", dutyState: 'pending', taskId: 5 }
         ]
       };
 
@@ -966,6 +969,31 @@ export default {
       }
     },
 
+    rebuildDotMarkers() {
+      if (!this.map || this.dotMarkers.length === 0) return;
+      const size = this.currentZoomSize;
+      const textDisplay = size >= 14 ? 'flex' : 'none';
+      const textSize = `${Math.max(10, size - 4)}px`;
+      for (const entry of this.dotMarkers) {
+        const { marker, config, isPassed } = entry;
+        if (isPassed) {
+          marker.setContent(`
+            <div class="pure-light-node" style="width: ${size}px; height: ${size}px; background: #5A5A5A; border: 1.5px solid rgba(255,255,255,0.25); box-sizing: border-box; display: flex; justify-content: center; align-items: center; color: #fff; border-radius: 50%; cursor: pointer; opacity: 0.55;">
+              <span style="display: ${textDisplay}; font-weight: bold; font-size: ${textSize};">勤</span>
+            </div>
+          `);
+        } else {
+          const displayText = config.name ? config.name.charAt(0) : '';
+          marker.setContent(`
+            <div class="pure-light-node route-node" style="width: ${size}px; height: ${size}px; background: ${config.color || '#999'}; box-shadow: none; border: none; box-sizing: border-box; display: flex; justify-content: center; align-items: center; color: #fff; border-radius: 50%; cursor: pointer;">
+              <span style="display: ${textDisplay}; font-weight: bold; font-size: ${textSize};">${displayText}</span>
+            </div>
+          `);
+        }
+      }
+    },
+
+
     /**
      * 创建交通信号灯标记
      * @param {Array<number>|Object} position - 位置坐标
@@ -1001,7 +1029,7 @@ export default {
           `;
         } else if (isPassed) {
           markerContent = `
-            <div class="pure-light-node" style="width: var(--dot-size); height: var(--dot-size); background: #5A5A5A; border: 1.5px solid rgba(255,255,255,0.25); box-sizing: content-box; display: flex; justify-content: center; align-items: center; color: #888; border-radius: 50%; cursor: pointer; padding: var(--dot-padding); opacity: 0.55;">
+            <div class="pure-light-node" style="width: var(--dot-size); height: var(--dot-size); background: #5A5A5A; border: 1.5px solid rgba(255,255,255,0.25); box-sizing: border-box; display: flex; justify-content: center; align-items: center; color: #fff; border-radius: 50%; cursor: pointer; opacity: 0.55;">
               <span style="display: var(--text-display); font-weight: bold; font-size: var(--text-size);">勤</span>
             </div>
           `;
@@ -1045,6 +1073,10 @@ export default {
         if (isStartEnd) {
           this.startEndMarkers.push({ marker, position: [lng, lat], config, type });
         }
+        // 记录勤务路线上的圆点 marker(passed + normal route),缩放时需重建
+        if (!isStartEnd && isRoute) {
+          this.dotMarkers.push({ marker, config, type, isPassed });
+        }
 
         marker.on('click', (e) => {
           if (this.isComponentDestroyed) return;
@@ -1395,6 +1427,7 @@ export default {
     drawSubAreaCircle(leaves, label) {
       if (!this.isMapReady() || !leaves || leaves.length === 0) return;
 
+      this._subAreaParams = { leaves, label };
       this.clearSubAreaOverlays();
 
       const pts = leaves.map(n => [Number(n.lng), Number(n.lat)]);
@@ -1436,10 +1469,16 @@ export default {
         const bottomRight = this.map.lngLatToContainer([Math.max(...lngs), Math.min(...lats)]);
         const pixelW = Math.abs(bottomRight.x - topLeft.x);
         const pixelH = Math.abs(bottomRight.y - topLeft.y);
-        // 字号以横向宽度为基准,按文字长度均分,确保不超出区域
+        // 字号同时受宽度和高度约束,取较小值确保不超出区域
         const charCount = label.length || 1;
-        const fontSize = Math.min(Math.max(12, Math.round(pixelW / (charCount + 1))), 48);
+        const sizeByW = Math.round(pixelW * 0.8 / (charCount + 1));
+        const sizeByH = Math.round(pixelH * 0.6);
+        const fontSize = Math.min(Math.max(10, Math.min(sizeByW, sizeByH)), 48);
 
+        // 缩放过小时隐藏文字
+        if (fontSize < 10) {
+          // 不绘制文字
+        } else {
         const text = new this.AMap.Text({
           text: label,
           position: [cx, cy],
@@ -1452,13 +1491,14 @@ export default {
             'font-size': `${fontSize}px`,
             'font-weight': 'bold',
             'padding': '0',
-            'letter-spacing': '2px',
+            'letter-spacing': fontSize >= 16 ? '2px' : '0px',
             'white-space': 'nowrap',
             'pointer-events': 'none',
           },
         });
         this.subAreaOverlays.push(text);
         this.map.add(text);
+        }
       }
     },
 
@@ -1501,6 +1541,13 @@ export default {
     /**
      * 清除子区圆形蒙层
      */
+    rebuildSubAreaText() {
+      if (!this._subAreaParams) return;
+      const { leaves, label } = this._subAreaParams;
+      // 保留参数,重绘整个子区(包含字号重新计算)
+      this.drawSubAreaCircle(leaves, label);
+    },
+
     clearSubAreaOverlays() {
       if (this.map && this.subAreaOverlays.length > 0) {
         try { this.map.remove(this.subAreaOverlays); } catch (e) { void e; }

+ 81 - 80
src/components/ui/CrossingDetailPanel.vue

@@ -32,97 +32,98 @@
                         </div>
                     </div>
 
-                    <div class="form-interactive-area" :class="{ 'is-disabled': !isManualMode }">
-                        <div class="control-method-content">
-                            <SegmentedRadio v-model="currentMethod" :options="controlMethodOptions" size="auto" />
-                        </div>
-
-                        <div class="control-scheme" :class="{ 'is-disabled': isSchemeDisabled }">
-                            <div class="control-label-wrap">
-                                <span class="control-label">控制方案</span>
-                                <DropdownSelect v-model="currentScheme" :options="schemeOptions" size="auto" />
+                    <div class="form-interactive-area">
+                        <div class="form-editable-area" :class="{ 'is-disabled': !isManualMode }">
+                            <div class="control-method-content">
+                                <SegmentedRadio v-model="currentMethod" :options="controlMethodOptions" size="auto" />
                             </div>
 
-                            <div class="time-form-bar" v-if="currentMethod === 'temp'">
-
-                                <el-date-picker v-model="startDate" type="date" placeholder="选择日期"
-                                    value-format="yyyy-MM-dd" size="small" :append-to-body="true"
-                                    :popper-options="{ boundariesPadding: 0, gpuAcceleration: false }">
-                                </el-date-picker>
-
-                                <el-time-picker v-model="startTime" placeholder="选择时间"
-                                    value-format="HH:mm:ss" size="small" :append-to-body="true"
-                                    :popper-options="{ boundariesPadding: 0, gpuAcceleration: false }">
-                                </el-time-picker>
-
-                                <el-date-picker v-model="endDate" type="date" placeholder="选择日期"
-                                    value-format="yyyy-MM-dd" size="small" :append-to-body="true"
-                                    :popper-options="{ boundariesPadding: 0, gpuAcceleration: false }">
-                                </el-date-picker>
-
-                                <el-time-picker v-model="endTime" placeholder="选择时间"
-                                    value-format="HH:mm:ss" size="small" :append-to-body="true"
-                                    :popper-options="{ boundariesPadding: 0, gpuAcceleration: false }">
-                                </el-time-picker>
-
-                                <div class="form-item-labeled">
-                                    <span class="form-label">时长</span>
-                                    <el-select v-model="duration" placeholder="请选择时长" size="small"
-                                        :popper-append-to-body="true">
-                                        <el-option v-for="d in durationOptions" :key="d" :label="d"
-                                            :value="d"></el-option>
-                                    </el-select>
+                            <div class="control-scheme" :class="{ 'is-disabled': isSchemeDisabled }">
+                                <div class="control-label-wrap">
+                                    <span class="control-label">控制方案</span>
+                                    <DropdownSelect v-model="currentScheme" :options="schemeOptions" size="auto" />
                                 </div>
 
-                                <div class="form-item-labeled">
-                                    <span class="form-label">周期</span>
-                                    <el-select v-model="period" placeholder="请选择周期" size="small"
-                                        :popper-append-to-body="true">
-                                        <el-option v-for="p in 8" :key="p" :label="'周期' + p"
-                                            :value="p"></el-option>
-                                    </el-select>
-                                </div>
+                                <div class="time-form-bar" v-if="currentMethod === 'temp'">
+
+                                    <el-date-picker v-model="startDate" type="date" placeholder="选择日期"
+                                        value-format="yyyy-MM-dd" size="small" :append-to-body="true"
+                                        :popper-options="{ boundariesPadding: 0, gpuAcceleration: false }">
+                                    </el-date-picker>
+
+                                    <el-time-picker v-model="startTime" placeholder="选择时间"
+                                        value-format="HH:mm:ss" size="small" :append-to-body="true"
+                                        :popper-options="{ boundariesPadding: 0, gpuAcceleration: false }">
+                                    </el-time-picker>
+
+                                    <el-date-picker v-model="endDate" type="date" placeholder="选择日期"
+                                        value-format="yyyy-MM-dd" size="small" :append-to-body="true"
+                                        :popper-options="{ boundariesPadding: 0, gpuAcceleration: false }">
+                                    </el-date-picker>
+
+                                    <el-time-picker v-model="endTime" placeholder="选择时间"
+                                        value-format="HH:mm:ss" size="small" :append-to-body="true"
+                                        :popper-options="{ boundariesPadding: 0, gpuAcceleration: false }">
+                                    </el-time-picker>
+
+                                    <div class="form-item-labeled">
+                                        <span class="form-label">时长</span>
+                                        <el-select v-model="duration" placeholder="请选择时长" size="small"
+                                            :popper-append-to-body="true">
+                                            <el-option v-for="d in durationOptions" :key="d" :label="d"
+                                                :value="d"></el-option>
+                                        </el-select>
+                                    </div>
 
-                            </div>
+                                    <div class="form-item-labeled">
+                                        <span class="form-label">周期</span>
+                                        <el-select v-model="period" placeholder="请选择周期" size="small"
+                                            :popper-append-to-body="true">
+                                            <el-option v-for="p in 8" :key="p" :label="'周期' + p"
+                                                :value="p"></el-option>
+                                        </el-select>
+                                    </div>
 
-                            <div class="current-stage">
-                                <div class="current-stage-warp">
-                                    <div class="current-stage-label">当前阶段:</div>
-                                    <div v-for="(item, index) in currentStageList" :key="index"
-                                        class="stage-item-wrapper">
-                                        <div class="phase-box" :class="{ 'is-active': item.value === currentStage }"
-                                            @click="onStageClick(item.value)">
-                                            <img :src="item.img" alt="stage" class="phase-image" />
-                                        </div>
+                                </div>
 
-                                        <div class="bottom-controls">
-                                            <div class="input-unit-wrapper">
-                                                <input type="number" v-model.number="item.time" class="stage-input"
-                                                    :disabled="!canEditStage"
-                                                    :title="canEditStage ? '修改阶段时间' : '当前控制方式不可修改'" />
-                                                <span class="unit">s</span>
+                                <div class="current-stage">
+                                    <div class="current-stage-warp">
+                                        <div class="current-stage-label">当前阶段:</div>
+                                        <div v-for="(item, index) in currentStageList" :key="index"
+                                            class="stage-item-wrapper">
+                                            <div class="phase-box" :class="{ 'is-active': item.value === currentStage }"
+                                                @click="onStageClick(item.value)">
+                                                <img :src="item.img" alt="stage" class="phase-image" />
+                                            </div>
+
+                                            <div class="bottom-controls">
+                                                <div class="input-unit-wrapper">
+                                                    <input type="number" v-model.number="item.time" class="stage-input"
+                                                        :disabled="!canEditStage"
+                                                        :title="canEditStage ? '修改阶段时间' : '当前控制方式不可修改'" />
+                                                    <span class="unit">s</span>
+                                                </div>
+                                                <span class="percent">{{ stagePercent(item.time) }}</span>
                                             </div>
-                                            <span class="percent">{{ stagePercent(item.time) }}</span>
                                         </div>
                                     </div>
                                 </div>
                             </div>
+                        </div>
 
-                            <!-- 方案圆饼图 -->
-                            <div class="donut-row">
-                                <div class="donut-item">
-                                    <div class="donut-title">实时方案(执行方案3)</div>
-                                    <PlanDonutChart :chartData="realtimeDonutData"
-                                        :centerValue="String(realtimeRemaining)" centerLabel="剩余时长" :showTotal="true"
-                                        :totalValue="cycleLength" :scale="panelScale" />
-                                </div>
-                                <div class="donut-item">
-                                    <div class="donut-title">下周期方案</div>
-                                    <PlanDonutChart :chartData="nextCycleDonutData" :centerValue="String(cycleLength)"
-                                        centerLabel="总时长" :showTotal="false" :scale="panelScale" />
-                                </div>
+                        <!-- 方案圆饼图 -->
+                        <div class="donut-row">
+                            <div class="donut-item">
+                                <div class="donut-title">实时方案(执行方案3)</div>
+                                <PlanDonutChart :chartData="realtimeDonutData"
+                                    :centerValue="String(realtimeRemaining)" centerLabel="剩余时长" :showTotal="true"
+                                    :totalValue="cycleLength" :scale="panelScale" />
+                            </div>
+                            <div class="donut-item">
+                                <div class="donut-title">下周期方案</div>
+                                <PlanDonutChart :chartData="nextCycleDonutData" :centerValue="String(cycleLength)"
+                                    centerLabel="总时长" :showTotal="false" :scale="panelScale" />
                             </div>
-
                         </div>
                     </div>
 
@@ -975,7 +976,7 @@ export default {
     overflow-x: hidden;
 }
 
-.form-interactive-area.is-disabled {
+.form-editable-area.is-disabled {
     opacity: 0.6;
     pointer-events: none;
 }
@@ -1090,8 +1091,8 @@ export default {
 }
 
 .donut-title {
-    font-size: clamp(11px, calc(var(--s) * 13px), 14px);
-    color: #a0aec0;
+    font-size: clamp(12px, calc(var(--s) * 22px), 28px);
+    color: #ffffff;
     margin-bottom: 4px;
 }
 

+ 1 - 0
src/layouts/DashboardLayout.vue

@@ -299,6 +299,7 @@ export default {
     height: 100%;
     display: flex;
     flex-direction: column;
+    background: linear-gradient(180deg, rgba(10, 25, 60, 0.75) 0%, rgba(5, 15, 40, 0.85) 100%);
 }
 
 /* --- 中心区域保持事件穿透,让出地图的拖拽控制权 --- */

+ 1 - 1
src/views/StatusMonitoring.vue

@@ -36,7 +36,7 @@
                     </TechTabPane>
                     <TechTabPane label="路口" name="crossing" class="menu-scroll-view" :loading="menuData.length === 0">
                         <MenuItem theme="tech" v-for="item in menuData" :key="item.id" :node="item" :level="0"
-                            @node-click="handleMenuClick" />
+                            @node-click="handleMenuClick" @folder-click="handleFolderClick" />
                     </TechTabPane>
                     <TechTabPane label="干线" name="trunkLine" class="menu-scroll-view" :loading="trunkLineMenuData.length === 0">
                         <MenuItem v-for="item in trunkLineMenuData" :key="item.id" :node="item" :level="0"