vue3+leaflet案例:告警系统GIS一张图(附源码下载)

发布于:2025-08-15 ⋅ 阅读:(12) ⋅ 点赞:(0)

基于vue3+leaflet的告警系统GIS一张图案例,覆盖功能点包括底图切换、告警事件模拟统计、模拟实时告警点移动信息、模拟告警管理以及数据诊断等,适合学习leaflet与前端框架结合开发webgis开发可视化项目。

demo源码运行环境以及配置

运行环境:依赖Node安装环境,demo本地Node版本:推荐v18+。

运行工具:vscode或者其他工具。

前端项目配置方式:下载demo源码,vscode打开,然后顺序执行以下命令:
(1)下载demo环境依赖包命令:npm install
(2)启动demo命令:npm run dev
(3)打包demo命令: npm run build

技术栈

Vue 3.2.39

Vite 2.5.10

leaflet 1.9.4

示例效果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
核心源码

import { onMounted, onUnmounted, nextTick, ref, reactive } from "vue";
import L from "leaflet";
import * as echarts from "echarts";
import config from "../config";
import { getPointAlongLine, getDistanceBy2Points } from "../utils/geoUtils";
// import { useRouter } from "vue-router";
// import { ElMessage } from 'element-plus';
// const router = useRouter();
const isShowAlarmSystem = ref(false);
const isShowDataSystem = ref(false);
const isShowLeftMenu = ref(true);
// 告警事件统计图表对象
let myChartEvent = null;
// 诊断数据统计图表对象
let dataChartEvent = null;
// 按照日期切换统计图表
const radio = ref(1);
const alarmEventTal = ref({
  emergency: 100,
  important: 85,
  general: 200,
  prompt: 96
})
// 统计图表数据源-月
let echartsDataMonth = {
  "xAxis": [
    "雷击",
    "外力破坏",
    "覆冰",
    "温度异常"
  ],
  "yAxis": [
    "154",
    "125",
    "188",
    "178",
  ]
};
// 统计图表数据源-周
let echartsDataWeek = {
  "xAxis": [
    "雷击",
    "外力破坏",
    "覆冰",
    "温度异常"
  ],
  "yAxis": [
    "64",
    "75",
    "58",
    "88",
  ]
};
// 统计图表数据源-天
let echartsDataDay = {
  "xAxis": [
    "雷击",
    "外力破坏",
    "覆冰",
    "温度异常"
  ],
  "yAxis": [
    "34",
    "45",
    "48",
    "58",
  ]
};
// 数据诊断图表数据源
let timeXData = [
  '0.00', '4.09', '8.17', '12.26', '16.35', '20.43', '24.52', '28.60', '32.69', '36.78', '40.86', '44.95', '49.04', '53.12', '57.21', '61.30', '65.38', '69.47', '73.55', '77.64', '81.73', '85.81',
  '89.90', '93.99', '98.07', '102.16', '106.25', '110.33', '114.42', '118.50', '122.59', '126.68', '130.76', '134.85', '138.94', '143.02', '147.11', '151.20', '155.28', '159.37', '163.45', '167.54', '171.63', '175.71', '179.80', '183.89',
  '187.97', '192.06', '196.15', '200.23', '204.32', '208.40', '212.49', '216.58', '220.66', '224.75', '228.84', '232.92', '237.01', '241.10', '245.18', '249.27', '253.35', '257.44', '261.53', '265.61', '269.70', '273.79', '277.87', '281.96'
];
let timeYData = [
  13.996026, 25.939534, 24.403596, 24.156721, 22.940715, 20.067240, 12.103183, 11.610609, 10.647344, 11.052902, 12.320346, 12.305370, 19.471151, 18.599405, 28.122542, 28.465133, 28.932984, 29.017473, 38.485951, 38.036698, 38.076452, 47.790099, 47.922228, 37.787734,
  37.816976, 37.440732, 27.890553, 27.412264, 27.708192, 27.559679, 27.617277, 17.692056, 17.692056, 17.692056, 17.368156, 27.278625, 27.414416, 37.849099, 17.414209, 16.960530, 16.960530, 17.411365, 17.665097, 27.495684, 27.103351, 27.506305, 27.238517, 27.154806, 27.296673, 17.110990, 17.104874, 17.445841,
  17.249234, 17.576882, 18.122866, 17.470368, 17.534492, 17.575769, 7.834490, 7.624720, 7.938114, 7.337769, 7.783019, 7.611189, 7.107963, 7.622926, 8.084923, 7.655780, 7.464569, 7.790213
]
const formData = ref({
  id: '',
  data_type: '',
  channel_number: '',
})
// 数据诊断面板
let dataDialogVisible = ref(false);
// 告警管理面板
let alarmDialogVisible = ref(false);
const formAlarmData = reactive({
  id: '',
  alarmType: '',
  siteName: '',
  timeDate: '',
  shortcuts: [
    {
      text: '最近一周',
      value: () => {
        const end = new Date()
        const start = new Date()
        start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
        return [start, end]
      },
    },
    {
      text: '最近一个月',
      value: () => {
        const end = new Date()
        const start = new Date()
        start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
        return [start, end]
      },
    },
    {
      text: '最近3个月',
      value: () => {
        const end = new Date()
        const start = new Date()
        start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
        return [start, end]
      },
    },
  ]
})
// 告警管理的实时告警信息表
const tableData = [
  {
    eventCode: '1',
    siteName: 'xx桩',
    mileageLocation: 'xx桩+0.34km',
    lon: 114.320941,
    lat: 34.799099,
    opticalCableDistance: '3.34km',
    peak: '50℃',
    alarmType: '高温异常',
    alarmLevel: '红色预警',
    alarmTime: '2025-03-31 14:00:00',
    startPoint: 912.3,
    sampleStep: 2.1,
    pointCount: 490,
    pointInfo: [{ postion: 2, Info: 40 },
    { postion: 4, Info: 44 }]
  }
]
// 轨迹点模拟数据
const pointsSource = [
  [25.29132248, 99.34718605],
  [25.28893705, 99.34728116]
]
// 实时报警点数据
// const alarmPoint = [25.29054437177747, 99.34721946716309];
let alarmMarker = null;
let map = null; // 地图对象
let geojsonLayer = null; // 框选绘制图层对象
let alarmTimer = null; // 报警点更新定时器
let currentDistance = 0; // 当前距离累加器
onMounted(() => {
  nextTick(() => {
    // 初始化地图对象
    initMap();
    // 初始化轨迹点及线
    initPoints2Line(pointsSource);
    // 首页统计图表初始化
    initCharts(echartsDataMonth);
  });
});
// 组件卸载时清除定时器
onUnmounted(() => {
  if (alarmTimer) {
    clearInterval(alarmTimer);
    alarmTimer = null;
  }
});
// 初始化地图
const initMap = () => {
  // 创建地图对象
  map = L.map("map", {
    attributionControl: false,
    zoomControl: false,
    // maxZoom: 20,
  }).setView(config.mapInitParams.center, config.mapInitParams.zoom);
  map.on('click', function (e) {
    const latlng = e.latlng; // 获取点击位置的经纬度对象
    console.log("经度: " + latlng.lng + ", 纬度: " + latlng.lat);
  });
  //创建底图切换数据源
  const baseLayer1 = L.tileLayer(config.baseMaps[0].Url); //OSM街道图
  const baseLayer2 = L.tileLayer(config.baseMaps[1].Url); //ArcGIS影像图
  const baseLayer4 = L.layerGroup([L.tileLayer(config.baseMaps[3].Url, {
    subdomains: ["0", "1", "2", "3", "4", "5", "6", "7"],
  }), L.tileLayer(config.baseMaps[4].Url, {
    subdomains: ["0", "1", "2", "3", "4", "5", "6", "7"],
  })]); //天地图街道图
  const baseLayer5 = L.layerGroup([L.tileLayer(config.baseMaps[5].Url, {
    subdomains: ["0", "1", "2", "3", "4", "5", "6", "7"],
  }), L.tileLayer(config.baseMaps[6].Url, {
    subdomains: ["0", "1", "2", "3", "4", "5", "6", "7"],
  })]); //天地图影像图
  const baseLayer8 = L.tileLayer(config.baseMaps[9].Url, {
    subdomains: ["1", "2", "3", "4"],
  }); //高德街道图
  const baseLayer9 = L.tileLayer(config.baseMaps[10].Url, {
    subdomains: ["1", "2", "3", "4"],
  }); //高德影像图
  // 构建图层标题及图例
  const getTitle = (text, borderColor, fillColor, isBorderDashed) => {
    return `<i style='display:inline-block;border:${isBorderDashed ? "dashed" : "solid"
      } 2px ${borderColor};background:${fillColor};width:20px;height:20px;position:relative;top:4px;'></i>
  <span style='padding-left:1px;margin-top: 2px;position: relative;top:1px;'>${text}</span>`;
  };
  // 构建图片形式的标题及图例
  const getImageTitle = (text, imgUrl, size) => {
    return `<div style='display:inline-block;width:${size}px;height:${size}px;position:relative;top:4px;'><img src='${imgUrl}' style='height:${size}px;'/></div>
  <span style='padding-left:1px;margin-top: 2px;position: relative;top:1px;'>${text}</span>`;
  };
  map.addLayer(baseLayer2); //地图默认加载的底图
  // map.addLayer(labelPointLayer); //地图默认加载的叠加图层
  const baseLayers = {
    [getImageTitle(`OSM街道图`, `./img/OSMVector.png`, 35)]: baseLayer1,
    [getImageTitle(`ArcGIS影像图`, `./img/arcgisImage.png`, 35)]: baseLayer2,
    [getImageTitle(`天地图街道图`, `./img/tdtVector.png`, 35)]: baseLayer4,
    [getImageTitle(`天地图影像图`, `./img/tdtImage.png`, 35)]: baseLayer5,
    [getImageTitle(`高德街道图`, `./img/gaodeVector.png`, 35)]: baseLayer8,
    [getImageTitle(`高德街道图`, `./img/gaodeImage.png`, 35)]: baseLayer9,
  };
  //底图切换控件
  // const OHTER_SPOT_COLOR = "#006fff";
  // const SPOT_FILL_COLOR = "rgba(0,0,255,0.15)";
  // 专题图层
  const overlayMaps = {
    // [getImageTitle(`充电站`, `./img/labelPointMarker.png`, 20)]:
    //   labelPointLayer,
    // [getImageTitle("热力图", `./img/heatmap.png`, 25)]: heatmapLayer,
  };
  //底图切换控件
  L.control.layers(baseLayers, overlayMaps, { position: 'bottomright' }).addTo(map);
};
// 自定义图标
// const customIcon = L.icon({
//   iconUrl: './public/img/labelPointMarker.png', // 图标图片路径
//   iconSize: [30, 30], // 图标大小(宽, 高)
// });
const getCustomIcon = (iconUrl, iconSize) => {
  return L.icon({
    iconUrl: iconUrl,
    iconSize: iconSize,
    iconAnchor: [iconSize[0] / 2, iconSize[1]]
  });
};
// 创建轨迹点及点连线
const initPoints2Line = (points) => {
  if (points.length > 0) {
    // 创建起点和终点
    for (let i = 0; i < points.length; i++) {
      let marker = L.marker(
        new L.LatLng(
          points[i][0],
          points[i][1]
        ),
        {
          properties: {},
          icon: getCustomIcon(`./public/img/${i === 0 ? 'starPoint.png' : 'endPoint.png'}`, [42, 42]) // marker点图标
        }
      ).addTo(map);
    }
    // 起点终点连线
    const polyline = L.polyline(points,
      {
        // color: '#1f9653',
        weight: 6
      }).addTo(map);
    // 模拟实时报警点更新
    const totalDistance = getDistanceBy2Points(pointsSource[0], pointsSource[1]);
    currentDistance = 0; // 重置当前距离
    // 清除可能存在的旧定时器
    if (alarmTimer) {
      clearInterval(alarmTimer);
    }
    refreshAlarmPoint(currentDistance + 10);
    // 设置定时器,每5秒执行一次,距离累加5米
    alarmTimer = setInterval(() => {
      currentDistance += 10; // 每次距离累加5米
      // 如果超过总距离,重置为起点
      if (currentDistance > totalDistance) {
        currentDistance = 0;
      }
      refreshAlarmPoint(currentDistance);
    }, 3000); // 3秒执行一次
  }
};
// 刷新绘制实时报警点
const refreshAlarmPoint = (distance) => {
  const alarmPoint = getPointAlongLine(pointsSource[0], pointsSource[1], distance);
  if (alarmPoint) {
    if (alarmMarker) {
      alarmMarker.remove();
    }
    alarmMarker = L.marker(alarmPoint, {
      icon: L.divIcon({
        className: 'animated-gif-icon',
        iconSize: [40, 40] // 设置图标大小以匹配 GIF 的大小
      })
    }).addTo(map);
    // alarmMarker.on('click', function () {
    //   const popup = L.popup()
    //     .setLatLng(alarmMarker.getLatLng())
    //     .setContent('我是一个弹窗')
    //     .openOn(map);
    // });
    const properties = tableData[0];
    map.closePopup();
    openMapPopup(properties);
  }
}
// 构造地图弹窗内容
const getPopupContent = (properties) => {
  return `预警点所属标桩名称:${properties.siteName}</br></br>预警类型:${properties.alarmType}</br></br>预警级别:${properties.alarmLevel}</br></br>
  预警时间:${properties.alarmTime}</br></br>坐标:${properties.lon},${properties.lat}`;
};
// 搜索结果列表点击定位地图弹窗
const openMapPopup = (properties) => {
  const elements = getPopupContent(properties);
  // const latlng = L.latLng(properties.lat, properties.lon);
  const latlng = alarmMarker.getLatLng();
  map.openPopup(elements, latlng);
  // map.setView(latlng, 15);
}
// 复位
const resetFun = () => {
  map.setView(config.mapInitParams.center, config.mapInitParams.zoom);
}
// 首页 月 周 天切换
const handleChange = (value) => {
  if (value === 1) {
    alarmEventTal.value = {
      emergency: 100,
      important: 85,
      general: 200,
      prompt: 96
    }
    initCharts(echartsDataMonth);
  } else if (value === 2) {
    alarmEventTal.value = {
      emergency: 70,
      important: 65,
      general: 150,
      prompt: 56
    }
    initCharts(echartsDataWeek);
  } else if (value === 3) {
    alarmEventTal.value = {
      emergency: 40,
      important: 45,
      general: 100,
      prompt: 36
    }
    initCharts(echartsDataDay);
  }
}
// 首页统计图表初始化
const initCharts = (echartsData) => {
  // 基于准备好的dom,初始化echarts实例
  const myChartDom = document.getElementById('echartsId');
  if (myChartDom) {
    myChartEvent?.dispose();
  }
  myChartEvent = echarts.init(myChartDom);
  const option = {
    title: {
      text: '告警事件统计分析',
      left: 'center',
      // left: '1%',
      top: '3%',
      textStyle: {
        color: '#fff',
        fontSize: '16',
      }
    },
    tooltip: {
      trigger: 'axis',
      axisPointer: {
        type: 'shadow'
      },
      backgroundColor: 'rgba(0, 0, 0, 0.35)', // 提示框背景颜色
      textStyle: {
        color: '#fff', // 提示框文本颜色
        fontSize: 14, // 文本字体大小
        // fontFamily: 'Arial' // 文本字体
      },
      // borderColor: '#000', // 设置Tooltip边框颜色为黑色
      borderWidth: 0, // 设置Tooltip边框宽度为2像素
    },
    grid: {
      left: '3%',
      top: '12%',
      right: '3%',
      bottom: '3%',
      containLabel: true
    },
    xAxis: [{
      type: 'category',
      data: echartsData.xAxis,
      axisLine: {
        show: true,
        lineStyle: {
          color: "rgba(255,255,255,.1)",
          width: 1,
          type: "solid"
        },
      },
      axisTick: {
        show: false,
      },
      axisLabel: {
        interval: 0,
        // rotate:50,
        show: true,
        splitNumber: 15,
        textStyle: {
          color: "rgba(255,255,255,.6)",
          fontSize: '12',
        },
      },
    }],
    yAxis: [{
      type: 'value',
      axisLabel: {
        //formatter: '{value} %'
        show: true,
        textStyle: {
          color: "rgba(255,255,255,.6)",
          fontSize: '12',
        },
      },
      axisTick: {
        show: false,
      },
      axisLine: {
        show: true,
        lineStyle: {
          color: "rgba(255,255,255,.1 )",
          width: 1,
          type: "solid"
        },
      },
      splitLine: {
        lineStyle: {
          color: "rgba(255,255,255,.1)",
        }
      }
    }],
    series: [
      {
        type: 'bar',
        data: echartsData.yAxis,
        barWidth: '35%', //柱子宽度
        itemStyle: {
          normal: {
            color: '#2f89cf',
            opacity: 1,
            barBorderRadius: 5,
          }
        },
        tooltip: {
          valueFormatter: function (value) {
            return value + ' 个';
          }
        }
      }
    ]
  };
  // 使用刚指定的配置项和数据显示图表。
  myChartEvent.setOption(option);
  window.addEventListener("resize", function () {
    myChartEvent?.resize();
  });
}
// 数据诊断统计图表初始化
const initDataCharts = () => {
  // prettier-ignore
  // timeData = timeData.map(function (str) {
  //   return str.replace('2009/', '');
  // });
  const option = {
    // title: {
    //   text: '温度',
    //   left: 'center'
    // },
    tooltip: {
      trigger: 'axis',
      axisPointer: {
        animation: false
      },
      formatter: function (params) {
        // console.log('params:', params);
        let result = params[0].name + ' 米' + '<br/>';
        for (let i = 0; i < params.length; i++) {
          result += params[i].marker + params[i].seriesName + ': ' + params[i].value + ' ℃<br/>';
        }
        return result;
      },
    },
    // legend: {
    //   data: ['温度'],
    //   left: 10
    // },
    // toolbox: {
    //   feature: {
    //     dataZoom: {
    //       yAxisIndex: 'none'
    //     },
    //     restore: {},
    //     saveAsImage: {}
    //   }
    // },
    axisPointer: {
      link: [
        {
          xAxisIndex: 'all'
        }
      ]
    },
    dataZoom: [
      {
        show: true,
        realtime: true,
        textStyle: {
          color: '#fff' // 设置字体颜色为红色
        },
        start: 30,
        end: 70,
        xAxisIndex: [0, 1]
      },
      {
        type: 'inside',
        textStyle: {
          color: '#fff' // 设置字体颜色为红色
        },
        realtime: true,
        start: 30,
        end: 70,
        xAxisIndex: [0, 1]
      }
    ],
    grid: [
      {
        left: 60,
        right: 50,
        height: '35%'
      },
      {
        left: 60,
        right: 50,
        top: '55%',
        height: '35%'
      }
    ],
    xAxis: [
      {
        type: 'category',
        name: '米',
        nameTextStyle: {//y轴上方单位的颜色
          color: '#fff'
        },
        boundaryGap: false,
        axisLine: { onZero: true },
        data: timeXData,
        axisLabel: {
          // interval: 0,
          // rotate:50,
          show: true,
          // splitNumber: 15,
          textStyle: {
            color: "rgba(255,255,255,1)",
            fontSize: '12',
          },
        },
      }
    ],
    yAxis: [
      {
        name: '温度(℃)',
        type: 'value',
        max: 60,
        nameTextStyle: {//y轴上方单位的颜色
          color: '#fff'
        },
        axisLabel: {
          //formatter: '{value} %'
          show: true,
          textStyle: {
            color: "rgba(255,255,255,1)",
            fontSize: '12',
          },
        }
      }
    ],
    series: [
      {
        name: '温度',
        type: 'line',
        symbolSize: 8,
        // prettier-ignore
        data: timeYData
      }
    ]
  };
  nextTick(() => {
    // 基于准备好的dom,初始化echarts实例
    const myChartDom = document.getElementById('echartsIdData');
    if (myChartDom) {
      dataChartEvent?.dispose();
    }
    dataChartEvent = echarts.init(myChartDom);
    // 使用刚指定的配置项和数据显示图表。
    dataChartEvent.setOption(option);
    window.addEventListener("resize", function () {
      dataChartEvent?.resize();
    });
  })
}
// 告警系统面板
const openAlarmDialog = () => {
  // reSetStatus();
  isShowDataSystem.value = false;
  dataDialogVisible.value = false;
  isShowAlarmSystem.value = !isShowAlarmSystem.value;
  alarmDialogVisible.value = !alarmDialogVisible.value;
  isShowAlarmSystem.value ? isShowLeftMenu.value = false : isShowLeftMenu.value = true;
  // isShowLeftMenu.value = false;
}
// 数据诊断面板
const openDataDialog = () => {
  // reSetStatus();
  isShowAlarmSystem.value = false;
  alarmDialogVisible.value = false;
  isShowDataSystem.value = !isShowDataSystem.value;
  dataDialogVisible.value = !dataDialogVisible.value;
  isShowDataSystem.value ? isShowLeftMenu.value = false : isShowLeftMenu.value = true;
  // isShowLeftMenu.value = false;
  if (dataDialogVisible.value) {
    nextTick(() => {
      // 数据诊断统计图表初始化
      initDataCharts();
    })
  }
}

网站公告

今日签到

点亮在社区的每一天
去签到