Selaa lähdekoodia

清理: 删除 8 个孤儿组件 + 同步引用

通过路由→视图→组件的 BFS 依赖追溯,确认下列组件没有任何引用路径,
且无 dialogManager 字符串调用 / headerComponent 字符串引用:

- src/components/HelloWorld.vue        (脚手架残留)
- src/components/WaveCanvas.vue        (未集成的背景动画)
- src/components/MapPlaceholder.vue    (已被真实地图替代)
- src/components/ui/AlarmPopup.vue     (从未被 import 或字符串调用)
- src/components/ui/DeviceStatusDonutChart.vue
- src/components/ui/DeviceStatusPanel.vue (已被 DeviceStatusTabs 替代)
- src/components/ui/MapLegend.vue      (与 TongzhouTrafficMap 内部 CSS 同名但无关)
- src/components/ui/WeatherWidget.vue  (Home.vue 导入但模板未使用)

同步清理引用:
- DashboardLayout.vue 删 DeviceStatusPanel import + 注册
- Home.vue 删 WeatherWidget import + 注册
- api/index.js + mock/api.js 删 apiGetMapLegendConfig 定义与实现

合计 -1101 行 0 行新增。build 验证通过。
画安 1 kuukausi sitten
vanhempi
commit
ab98d65b6f

+ 0 - 3
src/api/index.js

@@ -121,6 +121,3 @@ export const apiGetDetectorMonitorData = (id) =>
 
 export const apiGetOverviewTopCharts = () =>
   http.get('/overview/top-charts')
-
-export const apiGetMapLegendConfig = () =>
-  http.get('/map/legend-config')

+ 0 - 58
src/components/HelloWorld.vue

@@ -1,58 +0,0 @@
-<template>
-  <div class="hello">
-    <h1>{{ msg }}</h1>
-    <p>
-      For a guide and recipes on how to configure / customize this project,<br>
-      check out the
-      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
-    </p>
-    <h3>Installed CLI Plugins</h3>
-    <ul>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
-    </ul>
-    <h3>Essential Links</h3>
-    <ul>
-      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
-      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
-      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
-      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
-      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
-    </ul>
-    <h3>Ecosystem</h3>
-    <ul>
-      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
-      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
-      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
-      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
-      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
-    </ul>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'HelloWorld',
-  props: {
-    msg: String
-  }
-}
-</script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped>
-h3 {
-  margin: 40px 0 0;
-}
-ul {
-  list-style-type: none;
-  padding: 0;
-}
-li {
-  display: inline-block;
-  margin: 0 10px;
-}
-a {
-  color: #42b983;
-}
-</style>

+ 0 - 105
src/components/MapPlaceholder.vue

@@ -1,105 +0,0 @@
-<template>
-  <div class="map">
-    <div class="grid"></div>
-    <svg class="roads" viewBox="0 0 1000 600" preserveAspectRatio="none">
-      <path d="M40,520 C240,420 360,440 520,320 C680,210 820,230 960,120"
-            stroke="rgba(80,210,255,0.7)" stroke-width="6" fill="none" />
-      <path d="M100,160 C220,240 300,280 420,360 C560,450 740,520 920,560"
-            stroke="rgba(70,255,160,0.55)" stroke-width="6" fill="none" />
-      <path d="M520,40 L520,560"
-            stroke="rgba(110,190,255,0.45)" stroke-width="5" fill="none" />
-    </svg>
-
-    <div class="pt"
-         v-for="p in points"
-         :key="p.id"
-         :class="p.status"
-         :style="ptStyle(p)"
-         @mouseenter="hover=p"
-         @mouseleave="hover=null"
-    >
-      <div class="pulse" v-if="p.status==='alarm'"></div>
-    </div>
-
-    <div class="tip" v-if="hover" :style="tipStyle(hover)">
-      <div class="t1">{{ hover.name }}</div>
-      <div class="t2">{{ hover.id }} · {{ hover.status }}</div>
-    </div>
-  </div>
-</template>
-
-<script>
-export default {
-  name: "MapPlaceholder",
-  props: { points: { type: Array, default: () => [] } },
-  data(){ return { hover: null }; },
-  methods:{
-    // 将经纬度映射到占位图坐标(演示:做线性映射,让分布“看起来像在一张地图上”)
-    ptStyle(p){
-      const lngMin=116.12, lngMax=116.32;
-      const latMin=39.84, latMax=39.96;
-      const x = ((p.lng - lngMin) / (lngMax - lngMin)) * 100;
-      const y = (1 - (p.lat - latMin) / (latMax - latMin)) * 100;
-      return { left: `${x}%`, top: `${y}%` };
-    },
-    tipStyle(p){
-      return { ...this.ptStyle(p), transform: "translate(12px, -18px)" };
-    }
-  }
-};
-</script>
-
-<style scoped>
-.map{
-  position:relative;
-  width:100%; height:100%;
-  border-radius: 16px;
-  overflow:hidden;
-  background: radial-gradient(circle at 50% 40%, rgba(20,80,160,0.22), rgba(7,14,35,0.92) 70%);
-  border: 1px solid rgba(60,180,255,0.18);
-}
-.grid{
-  position:absolute; inset:0;
-  background-image:
-    linear-gradient(rgba(60,180,255,0.08) 1px, transparent 1px),
-    linear-gradient(90deg, rgba(60,180,255,0.08) 1px, transparent 1px);
-  background-size: 40px 40px;
-  opacity: 0.55;
-}
-.roads{ position:absolute; inset:0; opacity: 0.75; }
-
-.pt{
-  position:absolute;
-  width: 8px; height: 8px;
-  border-radius: 50%;
-  transform: translate(-50%, -50%);
-  box-shadow: 0 0 12px rgba(43,220,255,0.25);
-}
-.pt.normal{ background: rgba(60,220,255,0.85); }
-.pt.busy{ background: rgba(70,255,160,0.85); }
-.pt.alarm{ background: rgba(255,90,90,0.9); box-shadow: 0 0 16px rgba(255,90,90,0.35); }
-.pulse{
-  position:absolute; left:50%; top:50%;
-  width: 8px; height: 8px; border-radius:50%;
-  transform: translate(-50%,-50%);
-  border: 2px solid rgba(255,90,90,0.65);
-  animation: pulse 1.6s ease-out infinite;
-}
-@keyframes pulse{
-  0%{ width:8px; height:8px; opacity: 0.9; }
-  100%{ width:46px; height:46px; opacity: 0; }
-}
-.tip{
-  position:absolute;
-  min-width: 140px;
-  padding: 10px 12px;
-  background: rgba(6,18,55,0.70);
-  border: 1px solid rgba(60,180,255,0.22);
-  border-radius: 10px;
-  color: rgba(235,255,255,0.92);
-  pointer-events:none;
-  backdrop-filter: blur(6px);
-}
-.t1{ font-weight: 700; font-size: 13px; }
-.t2{ margin-top: 4px; font-size: 12px; opacity: 0.75; }
-</style>

+ 0 - 257
src/components/WaveCanvas.vue

@@ -1,257 +0,0 @@
-<template>
-  <canvas ref="waveCanvas" class="wave-canvas"></canvas>
-</template>
-
-<script>
-export default {
-  name: "WaveCanvas",
-  data() {
-    return {
-      canvas: null,
-      ctx: null,
-      animationId: null,
-      time: 0,
-      waves: [],
-      particles: []
-    };
-  },
-  mounted() {
-    this.initCanvas();
-    this.initWaves();
-    this.initParticles();
-    this.animate();
-    window.addEventListener("resize", this.handleResize);
-  },
-  beforeDestroy() {
-    if (this.animationId) {
-      cancelAnimationFrame(this.animationId);
-    }
-    window.removeEventListener("resize", this.handleResize);
-  },
-  methods: {
-    initCanvas() {
-      this.canvas = this.$refs.waveCanvas;
-      this.ctx = this.canvas.getContext("2d");
-      this.setCanvasSize();
-    },
-    setCanvasSize() {
-      const parent = this.canvas.parentElement;
-      this.canvas.width = parent.clientWidth;
-      this.canvas.height = parent.clientHeight;
-    },
-    handleResize() {
-      this.setCanvasSize();
-      this.initParticles();
-    },
-    initWaves() {
-      // 创建多层波浪参数
-      this.waves = [
-        {
-          amplitude: 30,
-          frequency: 0.01,
-          speed: 0.02,
-          yOffset: 0.7,
-          color: "rgba(64, 156, 255, 0.4)",
-          lineWidth: 2
-        },
-        {
-          amplitude: 25,
-          frequency: 0.015,
-          speed: 0.025,
-          yOffset: 0.75,
-          color: "rgba(100, 181, 246, 0.3)",
-          lineWidth: 1.5
-        },
-        {
-          amplitude: 20,
-          frequency: 0.008,
-          speed: 0.015,
-          yOffset: 0.8,
-          color: "rgba(144, 202, 249, 0.25)",
-          lineWidth: 1
-        },
-        {
-          amplitude: 35,
-          frequency: 0.012,
-          speed: 0.018,
-          yOffset: 0.65,
-          color: "rgba(64, 156, 255, 0.2)",
-          lineWidth: 1.5
-        }
-      ];
-    },
-    initParticles() {
-      this.particles = [];
-      const width = this.canvas.width;
-      const height = this.canvas.height;
-      
-      // 创建点阵粒子
-      const cols = Math.floor(width / 15);
-      const rows = Math.floor(height / 15);
-      
-      for (let i = 0; i < cols; i++) {
-        for (let j = 0; j < rows; j++) {
-          if (Math.random() > 0.6) {
-            this.particles.push({
-              x: i * 15 + Math.random() * 10,
-              y: j * 15 + Math.random() * 10,
-              baseY: j * 15,
-              size: Math.random() * 1.5 + 0.5,
-              opacity: Math.random() * 0.5 + 0.2,
-              speed: Math.random() * 0.5 + 0.2,
-              phase: Math.random() * Math.PI * 2
-            });
-          }
-        }
-      }
-    },
-    drawWave(wave, index) {
-      const ctx = this.ctx;
-      const width = this.canvas.width;
-      const height = this.canvas.height;
-      const baseY = height * wave.yOffset;
-      
-      ctx.beginPath();
-      ctx.strokeStyle = wave.color;
-      ctx.lineWidth = wave.lineWidth;
-      
-      // 绘制波浪线
-      for (let x = 0; x <= width; x += 2) {
-        const y = baseY + 
-          Math.sin(x * wave.frequency + this.time * wave.speed + index) * wave.amplitude +
-          Math.sin(x * wave.frequency * 2 + this.time * wave.speed * 1.5) * (wave.amplitude * 0.5);
-        
-        if (x === 0) {
-          ctx.moveTo(x, y);
-        } else {
-          ctx.lineTo(x, y);
-        }
-      }
-      
-      ctx.stroke();
-      
-      // 填充波浪下方渐变
-      ctx.lineTo(width, height);
-      ctx.lineTo(0, height);
-      ctx.closePath();
-      
-      const gradient = ctx.createLinearGradient(0, baseY - wave.amplitude, 0, height);
-      gradient.addColorStop(0, wave.color.replace(/[\d.]+\)$/, "0.1)"));
-      gradient.addColorStop(0.5, wave.color.replace(/[\d.]+\)$/, "0.05)"));
-      gradient.addColorStop(1, "transparent");
-      
-      ctx.fillStyle = gradient;
-      ctx.fill();
-    },
-    drawParticles() {
-      const ctx = this.ctx;
-      const width = this.canvas.width;
-      const height = this.canvas.height;
-      
-      this.particles.forEach(particle => {
-        // 粒子随波浪波动
-        const waveY = Math.sin(particle.x * 0.01 + this.time * 0.02) * 20 +
-                      Math.sin(particle.x * 0.02 + this.time * 0.03) * 10;
-        
-        const y = particle.baseY + waveY * particle.speed;
-        
-        // 只在底部区域显示粒子
-        if (y > height * 0.5) {
-          const opacity = particle.opacity * (1 - (y - height * 0.5) / (height * 0.5));
-          
-          ctx.beginPath();
-          ctx.arc(particle.x, y, particle.size, 0, Math.PI * 2);
-          ctx.fillStyle = `rgba(100, 200, 255, ${Math.max(0, opacity)})`;
-          ctx.fill();
-          
-          // 添加发光效果
-          if (opacity > 0.3) {
-            ctx.beginPath();
-            ctx.arc(particle.x, y, particle.size * 3, 0, Math.PI * 2);
-            ctx.fillStyle = `rgba(100, 200, 255, ${opacity * 0.2})`;
-            ctx.fill();
-          }
-        }
-      });
-    },
-    drawGrid() {
-      const ctx = this.ctx;
-      const width = this.canvas.width;
-      const height = this.canvas.height;
-      
-      // 绘制淡淡的网格线
-      ctx.strokeStyle = "rgba(64, 156, 255, 0.03)";
-      ctx.lineWidth = 0.5;
-      
-      const gridSize = 30;
-      
-      for (let x = 0; x <= width; x += gridSize) {
-        ctx.beginPath();
-        ctx.moveTo(x, height * 0.5);
-        ctx.lineTo(x, height);
-        ctx.stroke();
-      }
-      
-      for (let y = Math.floor(height * 0.5); y <= height; y += gridSize) {
-        ctx.beginPath();
-        ctx.moveTo(0, y);
-        ctx.lineTo(width, y);
-        ctx.stroke();
-      }
-    },
-    drawGlow() {
-      const ctx = this.ctx;
-      const width = this.canvas.width;
-      const height = this.canvas.height;
-      
-      // 底部发光效果
-      const gradient = ctx.createLinearGradient(0, height - 150, 0, height);
-      gradient.addColorStop(0, "transparent");
-      gradient.addColorStop(0.5, "rgba(64, 156, 255, 0.1)");
-      gradient.addColorStop(1, "rgba(64, 156, 255, 0.25)");
-      
-      ctx.fillStyle = gradient;
-      ctx.fillRect(0, height - 150, width, 150);
-    },
-    animate() {
-      const ctx = this.ctx;
-      const width = this.canvas.width;
-      const height = this.canvas.height;
-      
-      // 清空画布
-      ctx.clearRect(0, 0, width, height);
-      
-      // 绘制网格
-      this.drawGrid();
-      
-      // 绘制波浪
-      this.waves.forEach((wave, index) => {
-        this.drawWave(wave, index);
-      });
-      
-      // 绘制粒子
-      this.drawParticles();
-      
-      // 绘制发光效果
-      this.drawGlow();
-      
-      // 更新时间
-      this.time += 1;
-      
-      // 继续动画
-      this.animationId = requestAnimationFrame(this.animate.bind(this));
-    }
-  }
-};
-</script>
-
-<style scoped>
-.wave-canvas {
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  pointer-events: none;
-}
-</style>

+ 0 - 124
src/components/ui/AlarmPopup.vue

@@ -1,124 +0,0 @@
-<template>
-  <div class="map-alarm-popup">
-    <div class="popup-header">
-      <div class="status-icon warning">
-        <i class="el-icon-warning-outline"></i>
-      </div>
-      <span class="title-text">{{ title }}</span>
-    </div>
-
-    <div class="popup-content">
-      <div class="info-row">
-        <span class="label">路口:</span>
-        <span class="value">{{ intersection }}</span>
-      </div>
-      <div class="info-row">
-        <span class="label">发生时间:</span>
-        <span class="value">{{ time }}</span>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'AlarmPopup',
-  props: {
-    title: {
-      type: String,
-      default: '降级黄闪'
-    },
-    intersection: {
-      type: String,
-      default: '北京路与南京路'
-    },
-    time: {
-      type: String,
-      default: '2026.1.23.12:00'
-    },
-    // 可选:用于控制不同状态的颜色 (warning:黄, error:红, normal:绿)
-    type: {
-      type: String,
-      default: 'warning'
-    }
-  }
-};
-</script>
-
-<style scoped>
-/* ================= 整体卡片样式 ================= */
-.map-alarm-popup {
-  width: max-content; /* 宽度根据内容自适应 */
-  min-width: 240px;
-  /* 完美的暗黑透明背景 + 毛玻璃,高度还原图片 */
-  background: rgba(15, 20, 35, 0.85); 
-  backdrop-filter: blur(8px);
-  -webkit-backdrop-filter: blur(8px);
-  border: 1px solid rgba(255, 255, 255, 0.1); /* 极细边缘光 */
-  border-radius: 8px; /* 圆角 */
-  padding: 16px 20px;
-  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
-  pointer-events: auto; /* 允许鼠标点击交互 */
-}
-
-/* ================= 头部标题区 ================= */
-.popup-header {
-  display: flex;
-  align-items: center;
-  margin-bottom: 12px;
-}
-
-/* 状态图标容器 */
-.status-icon {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  width: 24px;
-  height: 24px;
-  border-radius: 50%;
-  margin-right: 10px;
-  color: #fff;
-  font-size: 14px;
-}
-
-/* 警告状态(黄色) */
-.status-icon.warning {
-  background-color: #e6a23c; 
-  box-shadow: 0 0 8px rgba(230, 162, 60, 0.6);
-}
-
-/* 异常状态(红色,预留) */
-.status-icon.error {
-  background-color: #f56c6c;
-  box-shadow: 0 0 8px rgba(245, 108, 108, 0.6);
-}
-
-.title-text {
-  font-size: 16px;
-  font-weight: bold;
-  color: #ffffff;
-  letter-spacing: 1px;
-}
-
-/* ================= 内容信息区 ================= */
-.popup-content {
-  display: flex;
-  flex-direction: column;
-  gap: 8px; /* 行间距 */
-}
-
-.info-row {
-  font-size: 14px;
-  display: flex;
-  align-items: center;
-  white-space: nowrap; /* 防止文字换行 */
-}
-
-.label {
-  color: #a0a5b0; /* 标签文字用灰白色区分层级 */
-}
-
-.value {
-  color: #ffffff; /* 核心数据纯白高亮 */
-}
-</style>

+ 0 - 255
src/components/ui/DeviceStatusDonutChart.vue

@@ -1,255 +0,0 @@
-<template>
-    <div class="donut-chart-wrapper">
-        <div class="echarts-wrapper">
-            <div class="echarts-container" ref="chartRef"></div>
-            
-            <div class="center-text-overlay" v-if="activeFaults.length > 1">
-                <span class="main-number" :style="{ fontSize: safePx2echarts(20) + 'px' }">
-                    {{ totalFaults }}
-                </span>
-            </div>
-        </div>
-
-        <div class="custom-legend">
-            <div class="legend-item" v-for="(item, index) in chartData" :key="index">
-                <span class="legend-name" :style="{ fontSize: safePx2echarts(12) + 'px' }">{{ item.name }}</span>
-                <span class="legend-color-box" :style="{ backgroundColor: item.color }"></span>
-            </div>
-        </div>
-    </div>
-</template>
-
-<script>
-import * as echarts from 'echarts';
-import echartsResize, { px2echarts } from '@/mixins/echartsResize.js';
-
-export default {
-    name: 'DeviceStatusDonutChart',
-    mixins: [echartsResize],
-    props: {
-        // 父组件传入的数据格式应为: [{name: '正常', value: 0, color: '#...'}, {name: '红灯故障', value: 2, color: '#...'}]
-        chartData: {
-            type: Array,
-            required: true,
-            default: () => []
-        }
-    },
-    data() {
-        return {
-            chartInstance: null
-        };
-    },
-    computed: {
-        // 过滤出真正的“故障”数据(名称不包含正常,且值大于0)
-        activeFaults() {
-            return this.chartData.filter(item => 
-                item.name.indexOf('正常') === -1 && Number(item.value) > 0
-            );
-        },
-        // 计算故障总数(用于图3中心显示)
-        totalFaults() {
-            return this.activeFaults.reduce((sum, item) => sum + Number(item.value), 0);
-        }
-    },
-    watch: {
-        chartData: {
-            deep: true,
-            handler() {
-                this.$nextTick(() => {
-                    if (this.chartInstance) {
-                        this.chartInstance.resize();
-                    }
-                    this.updateChart();
-                });
-            }
-        }
-    },
-    mounted() {
-        this.$nextTick(() => {
-            this.initChart();
-        });
-    },
-    beforeDestroy() {
-        if (this.resizeObserver) this.resizeObserver.disconnect();
-        if (this.chartInstance) this.chartInstance.dispose();
-    },
-    methods: {
-        safePx2echarts(val) {
-            return typeof px2echarts === 'function' ? px2echarts(val) : val;
-        },
-
-        initChart() {
-            if (!this.$refs.chartRef) return;
-            this.chartInstance = echarts.init(this.$refs.chartRef);
-            this.updateChart();
-        },
-
-        updateChart() {
-            if (!this.chartInstance) return;
-            this.chartInstance.clear();
-
-            const chartWidth = this.chartInstance.getWidth();
-            let seriesData = [];
-
-            if (this.activeFaults.length === 0) {
-                const normalItem = this.chartData.find(item => item.name.indexOf('正常') !== -1);
-                const normalColor = normalItem ? normalItem.color : '#9FE051'; 
-                seriesData = [{
-                    name: '故障',
-                    value: 1,
-                    actualValue: 0, 
-                    itemStyle: { color: normalColor },
-                    label: { color: normalColor },
-                    labelLine: { lineStyle: { color: normalColor } }
-                }];
-            } else {
-                seriesData = this.activeFaults.map(item => ({
-                    name: item.name,
-                    value: item.value,
-                    itemStyle: { color: item.color },
-                    label: { color: item.color },
-                    labelLine: { lineStyle: { color: item.color } }
-                }));
-            }
-
-            const option = {
-                series: [
-                    {
-                        type: 'pie',
-                        // 【优化1】圆环再缩小一点点,给边缘文字留出绝对安全的距离
-                        radius: ['40%', '55%'], 
-                        center: ['50%', '50%'],
-                        startAngle: 210,
-                        avoidLabelOverlap: true,
-                        label: {
-                            show: true,
-                            position: 'outside',
-                            // 【优化2】文字靠近圆环一点,防止撞墙
-                            distance: this.safePx2echarts(5), 
-                            formatter: (params) => {
-                                const displayValue = params.data.actualValue !== undefined ? params.data.actualValue : params.value;
-                                return `{name|${params.name}}\n{val|${displayValue}}`;
-                            },
-                            rich: {
-                                name: {
-                                    fontSize: this.safePx2echarts(12),
-                                    color: 'inherit',
-                                    fontWeight: 'bold',
-                                    padding: [0, 0, this.safePx2echarts(4), 0]
-                                    // 【核心修复】删掉了 width 和 overflow,让 ECharts 自然渲染
-                                },
-                                val: {
-                                    fontSize: this.safePx2echarts(12),
-                                    color: 'inherit',
-                                    fontWeight: 'bold',
-                                    padding: [this.safePx2echarts(4), 0, 0, 0]
-                                    // 【核心修复】删掉了 width 和 overflow
-                                }
-                            }
-                        },
-                        labelLine: {
-                            show: true,
-                            // 【优化3】斜线改短,将文字往中心拉
-                            length: this.safePx2echarts(8), 
-                            length2: this.safePx2echarts(15) 
-                        },
-                        labelLayout: (params) => {
-                            const isLeft = params.labelRect.x < chartWidth / 2;
-                            const points = params.labelLinePoints;
-                            
-                            if (points) {
-                                points[2][0] = isLeft
-                                    ? params.labelRect.x
-                                    : params.labelRect.x + params.labelRect.width;
-                                    
-                                points[1][1] = points[2][1] = params.labelRect.y + params.labelRect.height / 2;
-                            }
-
-                            return {
-                                labelLinePoints: points
-                            };
-                        },
-                        data: seriesData,
-                        animationType: 'expansion',
-                        animationEasing: 'elasticOut',
-                        animationDuration: 2000, 
-                        animationDelay: 200,
-                    }
-                ]
-            };
-
-            this.chartInstance.setOption(option, true);
-        }
-    }
-}
-</script>
-
-<style scoped>
-.donut-chart-wrapper {
-    display: flex;
-    align-items: stretch;
-    justify-content: space-between;
-    width: 100%;
-    height: 100%;
-}
-
-/* 左侧饼图容器 */
-.echarts-wrapper {
-    position: relative;
-    width: 65%;
-    flex: 1;
-    min-height: 0;
-}
-
-.echarts-container {
-    width: 100%;
-    height: 100%;
-    min-height: 0;
-}
-
-/* 饼图中心数字 */
-.center-text-overlay {
-    position: absolute;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-    pointer-events: none; /* 防止遮挡鼠标悬停饼图的事件 */
-    z-index: 10;
-}
-
-.main-number {
-    color: #8392b4;
-    font-weight: bold;
-    font-family: Arial, sans-serif;
-    line-height: 1;
-}
-
-/* 右侧图例 */
-.custom-legend {
-    display: flex;
-    flex-direction: column;
-    justify-content: center;
-    gap: 12px;
-    width: 35%; /* 占据右边 35% 空间 */
-    padding-right: 10px;
-}
-
-.legend-item {
-    display: flex;
-    align-items: center;
-    justify-content: flex-end; /* 靠右对齐 */
-    gap: 10px;
-}
-
-.legend-name {
-    color: #00d2ff; /* 对应图片中的青色文字 */
-    font-weight: 400;
-}
-
-.legend-color-box {
-    width: 24px;   /* 宽方块 */
-    height: 12px;
-    border-radius: 3px;
-    flex-shrink: 0;
-}
-</style>

+ 0 - 101
src/components/ui/DeviceStatusPanel.vue

@@ -1,101 +0,0 @@
-<template>
-  <div class="device-status-panel">
-    <TechTabs v-model="onlineStatusActiveTab" type="segmented"   @tab-click="handleTabClick">
-      <TechTabPane label="信号机" name="signalMachine" style="height: 100%;">
-        <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
-          :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" customHeight="133px"/>
-      </TechTabPane>
-      <TechTabPane label="检测器" name="detector">
-        <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
-          :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" customHeight="133px"/>
-      </TechTabPane>
-      <TechTabPane label="红路灯" name="trafficLight">
-        <DynamicDonutChart v-if="onlineStatusDisplayData" :chartData="onlineStatusDisplayData.chartData"
-          :centerTitle="onlineStatusDisplayData.centerTitle" :centerSubTitle="onlineStatusDisplayData.centerSubTitle" customHeight="133px"/>
-      </TechTabPane>
-    </TechTabs>
-  </div>
-</template>
-
-<script>
-import TechTabs from '@/components/ui/TechTabs.vue';
-import TechTabPane from '@/components/ui/TechTabPane.vue';
-import DynamicDonutChart from '@/components/ui/DynamicDonutChart.vue';
-
-
-const mockDeviceData = {
-  'signalMachine': {
-    centerTitle: '98%',
-    centerSubTitle: '980/1000',
-    chartData: [
-      { name: '在线', value: 980, color: '#32F6F8' },
-      { name: '离线', value: 20, color: '#E4D552' }
-    ]
-  },
-  'detector': {
-    centerTitle: '85%',
-    centerSubTitle: '425/500',
-    chartData: [
-      { name: '正常', value: 425, color: '#32F6F8' },
-      { name: '故障', value: 50, color: '#faad14' },
-    ]
-  },
-  'trafficLight': {
-    centerTitle: '99%',
-    centerSubTitle: '1188/1200',
-    chartData: [
-      { name: '正常发光', value: 1188, color: '#32F6F8' },
-      { name: '灯组损坏', value: 12, color: '#ff4d4f' }
-    ]
-  }
-};
-
-export default {
-  name: 'DeviceStatusPanel',
-  components: {
-    TechTabs,
-    TechTabPane,
-    DynamicDonutChart
-  },
-  data() {
-    return {
-      // 在线状态面板
-      onlineStatusActiveTab: 'detector',
-      onlineStatusDisplayData: mockDeviceData['detector'],
-    };
-  },
-  computed: {
-    // 获取当前激活 Tab 的数据
-    currentData() {
-      return this.tabs[this.activeIndex].data;
-    }
-  },
-  mounted() {
-    
-  },
-  beforeDestroy() {
-    
-  },
-  methods: {
-    // 监听 Tab 切换事件
-    handleTabClick(selectedTabName) {
-      console.log('用户切换了设备类型:', selectedTabName);
-
-      // 从 mock 字典中取出对应的数据并赋值,图表会自动响应式更新!
-      if (mockDeviceData[selectedTabName]) {
-        this.onlineStatusDisplayData = mockDeviceData[selectedTabName];
-      }
-    },
-  }
-};
-</script>
-
-<style scoped>
-.device-status-panel {
-  width: 100%;
-  height: 100%;
-  display: flex;
-  flex-direction: column;
-  /* box-sizing: border-box; */
-}
-</style>

+ 0 - 111
src/components/ui/MapLegend.vue

@@ -1,111 +0,0 @@
-<template>
-  <div class="map-legend">
-    <div class="legend-header">图例</div>
-
-    <div class="legend-list">
-      <div class="legend-item" v-for="(item, index) in legendData" :key="index">
-        <div class="legend-icon">
-          <img :src="item.iconUrl" />
-        </div>
-
-        <span class="legend-label">{{ item.label }}</span>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'MapLegend',
-  data() {
-    return {
-      // 完全按照图片内容配置的数据字典
-      legendData: [
-        { label: '中心计划', iconUrl: require('@/assets/images/icon_zhong.png') },
-        { label: '干线协调', iconUrl: require('@/assets/images/icon_ganxian.png') },
-        { label: '勤务路线', iconUrl: require('@/assets/images/icon_qin.png') },
-        { label: '定周期控制', iconUrl: require('@/assets/images/icon_ding.png') },
-        { label: '感应控制', iconUrl: require('@/assets/images/icon_gan.png') },
-        { label: '自适应控制', iconUrl: require('@/assets/images/icon_zi.png') },
-        { label: '手动控制', iconUrl: require('@/assets/images/icon_shou.png') },
-        { label: '特殊控制', iconUrl: require('@/assets/images/icon_te.png') },
-        { label: '离线', iconUrl: require('@/assets/images/icon_lixian.png') },
-        { label: '降级', iconUrl: require('@/assets/images/icon_jiangji.png') },
-        { label: '故障', iconUrl: require('@/assets/images/icon_guzhang.png') }
-      ]
-    };
-  }
-};
-</script>
-
-<style scoped>
-/* ================== 容器主面板 ================== */
-.map-legend {
-  width: 130px;
-  background: radial-gradient(circle at 20% 0%, rgba(40,120,200,0.2) 0%, rgba(20,60,130,0.4) 70%);
-  box-shadow: inset 0px 0px 0.625rem 0px rgba(88, 146, 255, 0.4), inset 1.25rem 0px 1.875rem -0.625rem rgba(88, 146, 255, 0.15);
-  border: 1px solid rgba(255, 255, 255, 0.15);
-  border-radius: 7px;
-  backdrop-filter: blur(8px);
-  -webkit-backdrop-filter: blur(8px);
-  /* 底部阴影让它浮起来 */
-  overflow: hidden;
-  user-select: none;
-}
-
-/* ================== 头部标题 ================== */
-.legend-header {
-  color: #ffffff;
-  font-size: 14px;
-  font-weight: 600;
-  padding: 12px 16px;
-  /* background: linear-gradient(180deg, rgba(24, 144, 255, 0.1) 0%, rgba(24, 144, 255, 0.3) 100%); */
-  /* box-shadow: inset 0 0 8px rgba(24, 144, 255, 0.2); */
-  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
-  letter-spacing: 1px;
-  background: rgba(119,161,255,0.23);
-}
-
-/* ================== 列表区域 ================== */
-.legend-list {
-  padding: 12px 8px;
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-  /* 控制每一行之间的间距 */
-}
-
-.legend-item {
-  display: flex;
-  align-items: center;
-}
-
-/* ================== 左侧图标统一底座 ================== */
-.legend-icon {
-  width: 20px;
-  height: 20px;
-  border-radius: 50%;
-  /* 正圆形 */
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  margin-right: 8px;
-  /* 和右侧文字的间距 */
-  box-shadow: 0 0 6px rgba(0, 0, 0, 0.3) inset;
-  /* 内阴影增加立体感 */
-}
-
-/* 单字图标样式 */
-.legend-icon img {
-  width: 20px;
-  height: 20px;
-}
-
-/* ================== 右侧文字标签 ================== */
-.legend-label {
-  /* 提取图片中极其特殊的淡薄荷绿文字颜色 */
-  color: #6CFFD2;
-  font-size: 14px;
-  letter-spacing: 1px;
-}
-</style>

+ 0 - 56
src/components/ui/WeatherWidget.vue

@@ -1,56 +0,0 @@
-<template>
-  <div class="weather-widget">
-    <span class="text">
-      <span class="icon">{{ icon }}</span>{{ text }}
-    </span>
-    <span class="temp">{{ temperature }}</span>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'WeatherWidget',
-  props: {
-    icon: {
-      type: String,
-      default: '☀️'
-    },
-    text: {
-      type: String,
-      default: '晴'
-    },
-    temperature: {
-      type: String,
-      default: '32/17℃'
-    }
-  }
-}
-</script>
-
-<style scoped>
-.weather-widget {
-  display: flex;
-  align-items: center;
-  gap: 12px;
-  color: #ffffff;
-  font-family: var(--font-family, sans-serif);
-}
-
-.icon {
-  font-size: 22px;
-}
-
-.text {
-  font-size: 24px;
-  line-height: 26px;
-  font-weight: 500;
-  letter-spacing: 2px;
-}
-
-.temp {
-  font-size: 16px;
-  line-height: 24px;
-  font-family: PingFangSC, PingFang SC;
-  color: rgba(255,255,255,0.8);
-}
-</style>

+ 0 - 2
src/layouts/DashboardLayout.vue

@@ -92,7 +92,6 @@ import FullscreenToggle from '@/components/ui/FullscreenToggle.vue';
 import HomeButton from '@/components/ui/HomeButton.vue';
 
 // 注册所有可能在弹窗中使用的内容组件
-import DeviceStatusPanel from '@/components/ui/DeviceStatusPanel.vue';
 import SecurityRoutePanel from '@/components/ui/SecurityRoutePanel.vue';
 import IntersectionMapVideos from '@/components/ui/IntersectionMapVideos.vue';
 import TrafficTimeSpace from '@/components/ui/TrafficTimeSpace.vue';
@@ -127,7 +126,6 @@ export default {
         UserProfile,
         FullscreenToggle,
         HomeButton,
-        DeviceStatusPanel,
         SecurityRoutePanel,
         IntersectionMapVideos,
         TrafficTimeSpace,

+ 0 - 27
src/mock/api.js

@@ -1446,33 +1446,6 @@ export async function apiGetOverviewTopCharts() {
 }
 
 /**
- * GET /api/map/legend-config — 地图标注线路配置
- */
-export async function apiGetMapLegendConfig() {
-  await delay(150)
-  const tzPoints = DB.points.filter(p => p.node && p.node.includes('通州'))
-  const lngs = tzPoints.map(p => p.lng), lats = tzPoints.map(p => p.lat)
-  const [minLng, maxLng] = [Math.min(...lngs), Math.max(...lngs)]
-  const [minLat, maxLat] = [Math.min(...lats), Math.max(...lats)]
-  const midLat = (minLat + maxLat) / 2
-  const hStep = (maxLat - minLat) / 5, vStep = (maxLng - minLng) / 8
-
-  return ok([
-    { name: '中心计划',   start: [minLng, midLat + hStep],     end: [maxLng, midLat + hStep],     color: '#004CDE' },
-    { name: '干线协调',   start: [minLng, midLat],             end: [maxLng, midLat],             color: '#13C373' },
-    { name: '勤务路线',   start: [minLng, midLat - hStep],     end: [maxLng, midLat - hStep],     color: '#BC301D' },
-    { name: '定周期控制', start: [minLng + vStep, maxLat],     end: [minLng + vStep, minLat],     color: '#3296FA' },
-    { name: '感应控制',   start: [minLng + vStep * 2, maxLat], end: [minLng + vStep * 2, minLat], color: '#FF864C' },
-    { name: '自适应控制', start: [minLng + vStep * 3, maxLat], end: [minLng + vStep * 3, minLat], color: '#9F6EFE' },
-    { name: '手动控制',   start: [minLng + vStep * 4, maxLat], end: [minLng + vStep * 4, minLat], color: '#EB9F36' },
-    { name: '特殊控制',   start: [minLng + vStep * 5, maxLat], end: [minLng + vStep * 5, minLat], color: '#A26218' },
-    { name: '离线',       start: [minLng, maxLat - hStep * 0.3], end: [maxLng, maxLat - hStep * 0.3], color: '#7A7A7A' },
-    { name: '降级',       start: [minLng, minLat + hStep * 0.3], end: [maxLng, minLat + hStep * 0.3], color: '#D9C13B' },
-    { name: '故障',       start: [maxLng - vStep * 0.5, maxLat], end: [maxLng - vStep * 0.5, minLat], color: '#FF3938' },
-  ])
-}
-
-/**
  * GET /api/devices/fault-status
  * 设备故障统计(匹配 DeviceStatusTabs 组件的 statusData 格式)
  * keys: signalMachineStatus, detectorStatus, trafficLightStatus

+ 0 - 2
src/views/Home.vue

@@ -112,7 +112,6 @@
 
 <script>
 import DashboardLayout from '@/layouts/DashboardLayout.vue';
-import WeatherWidget from '@/components/ui/WeatherWidget.vue';
 import DateTimeWidget from '@/components/ui/DateTimeWidget.vue';
 import PanelContainer from '@/components/ui/PanelContainer.vue';
 import TickDonutChart from '@/components/ui/TickDonutChart.vue';
@@ -128,7 +127,6 @@ export default {
   name: "HomePage",
   components: {
     DashboardLayout,
-    WeatherWidget,
     DateTimeWidget,
     PanelContainer,
     TickDonutChart,