餐饮油烟智能监测与监管一体化平台
riku
2026-03-16 0488cc32d225a28289ba6c70a2a297f347cacdad
src/utils/map/marks.js
@@ -2,82 +2,400 @@
 * 高德地图点标记绘制相关
 */
import { map, AMap } from './index';
import { useToolboxStore } from '@/stores/toolbox';
import { map, AMap } from './index'
import { useToolboxStore } from '@/stores/toolbox'
import util from './util'
import * as echarts from 'echarts'
const toolboxStore = useToolboxStore();
const toolboxStore = useToolboxStore()
var _massMarks = undefined;
var _massMarks = undefined
export default {
// 环信码等级和对应颜色
const ringCodeLevelColors = [
  '#52c41a', // 绿色
  '#faad14', // 黄色
  '#f5222d', // 红色
]
  /**
   * 绘制海量点标记
   * @param fDatas 完整监测数据
   * @param _factor 当前展示的监测因子对象
 * @param {Array} shops 店铺对象数组
 * @param {Object} shops[].shop 店铺基本信息
 * @param {string} shops[].shop.name 店铺名称
 * @param {string} shops[].shop.address 店铺地址
 * @param {number} shops[].shop.latitude 纬度
 * @param {number} shops[].shop.longitude 经度
 * @param {string} shops[].shop.ringCodeLevel 最新环信码等级
 * @param {string} shops[].shop.ringCodePublishTime 最新环信码发布时间
 * @param {Array} shops[].recentData 近1小时的监测数据
 * @param {string} shops[].recentData[].sampleTime 数据采样时间
 * @param {number} shops[].recentData[].oilSmokeConcentration 油烟浓度
 * @param {number} shops[].recentData[].purifierCurrent 净化器电流
 * @param {number} shops[].recentData[].fanCurrent 风机电流
   */
  drawMassMarks(fDatas, _factor, onClick) {
    if (!toolboxStore.dataMarkerStatus) {
      return;
function drawMassMarks(shops) {
  // 配置样式
  const massMarksStyle = ringCodeLevelColors.map((color, index) => ({
    url: createCustomMarker(color),
    size: new AMap.Size(20, 20),
    anchor: new AMap.Pixel(10, 10),
  }))
  // 准备海量点数据
  const massMarksData = shops.map((shop, index) => {
    // 根据环信码等级获取颜色
    const color = getColorByRingCodeLevel(shop.shop.ringCodeLevel)
    return {
      id: index,
      name: shop.shop.name,
      lnglat: [shop.shop.longitude, shop.shop.latitude],
      style: shop.shop.ringCodeLevel, // 样式索引,对应 massMarksStyle
      extData: shop, // 存储完整的店铺信息
    }
    this.clearMassMarks();
    const lnglats = fDatas.lnglats_GD;
    var data = [];
    for (let i = 0; i < lnglats.length; i++) {
      data.push({
        lnglat: lnglats[i], //点标记位置
        name: `${fDatas.times[i]}<br/>${_factor.factorName}: ${_factor.datas[i].factorData} μg/m³`,
        id: i
      });
  })
  // 清除现有的海量点标记
  if (window.massMarks) {
    window.massMarks.clear()
    }
    // 创建样式对象
    var styleObject = {
      url: 'https://a.amap.com/jsapi_demos/static/images/mass1.png',
      // url: './asset/mipmap/ic_up_white.png', // 图标地址
  // 创建新的海量点标记
  window.massMarks = new AMap.MassMarks(massMarksData, {
    zIndex: 111,
    cursor: 'pointer',
    style: massMarksStyle,
  })
      size: new AMap.Size(11, 11), // 图标大小
      anchor: new AMap.Pixel(5, 5) // 图标显示位置偏移量,基准点为图标左上角
    };
    var massMarks = new AMap.MassMarks(data, {
      zIndex: 5, // 海量点图层叠加的顺序
      zooms: [15, 18], // 在指定地图缩放级别范围内展示海量点图层
      style: styleObject // 设置样式对象
    });
    massMarks.on('click', (event) => {
      const i = event.data.id;
      // 3. 自定义点击事件
      onClick(i);
    });
  // 添加点击事件
  window.massMarks.on('click', function (e) {
    const shop = e.data.extData
    showShopInfoWindow(shop)
  })
    var marker = new AMap.Marker({
      content: ' ',
      map: map,
    offset: new AMap.Pixel(13, 12),
  })
      offset: new AMap.Pixel(13, 12)
    });
    var timeout;
    massMarks.on('mouseover', (e) => {
  var timeout
  window.massMarks.on('mouseover', (e) => {
      if (timeout) {
        clearTimeout(timeout);
      clearTimeout(timeout)
      }
      marker.setPosition(e.data.lnglat);
      marker.setLabel({ content: e.data.name });
      map.add(marker);
    marker.setPosition(e.data.lnglat)
    marker.setLabel({ content: e.data.name })
    map.add(marker)
      timeout = setTimeout(() => {
        map.remove(marker);
      }, 2000);
    });
    _massMarks = massMarks;
    map.add(massMarks);
  },
  clearMassMarks() {
    if (_massMarks) {
      map.remove(_massMarks);
      _massMarks = undefined;
      map.remove(marker)
    }, 2000)
  })
  // 添加到地图
  window.massMarks.setMap(map)
  util.setBound(massMarksData.map((item) => item.lnglat))
    }
/**
 * 根据环信码等级获取颜色
 * @param {string} level 环信码等级
 * @returns {string} 颜色值
 */
function getColorByRingCodeLevel(level) {
  switch (level + '') {
    case '0':
      return '#52c41a' // 绿色
    case '1':
      return '#faad14' // 黄色
    case '2':
      return '#f5222d' // 红色
    default:
      return '#8c8c8c' // 灰色
  }
}
/**
 * 根据环信码等级获取中文
 * @param {string} level 环信码等级
 * @returns {string} 中文表示
 */
function getRingCodeLevelText(level) {
  switch (level + '') {
    case '0':
      return '绿色'
    case '1':
      return '黄色'
    case '2':
      return '红色'
    default:
      return '未知'
  }
}
/**
 * 创建自定义标记
 * @param {string} color 标记颜色
 * @returns {string} 标记的SVG URL
 */
function createCustomMarker(color) {
  const svg = `
    <svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
      <circle cx="10" cy="10" r="8" fill="${color}" stroke="white" stroke-width="2"/>
      <circle cx="10" cy="10" r="3" fill="white"/>
    </svg>
  `
  return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)))
}
/**
 * 显示店铺信息窗口
 * @param {Object} shop 店铺对象
 */
function showShopInfoWindow(shop) {
  // 准备信息窗口内容
  // const content = `
  //   <div style="padding: 10px; max-width: 300px;">
  //     <h3 style="margin: 0 0 10px 0; color: #333;">${shop.shop.name}</h3>
  //     <div style="font-size: 14px; line-height: 1.5;">
  //       <p><strong>地址:</strong>${shop.shop.address}</p>
  //       <p><strong>环信码等级:</strong><span style="color: ${getColorByRingCodeLevel(shop.shop.ringCodeLevel)}">${shop.shop.ringCodeLevel}</span></p>
  //       <p><strong>环信码发布时间:</strong>${shop.shop.ringCodePublishTime}</p>
  //       <h4 style="margin: 10px 0 5px 0; color: #666;">近1小时监测数据</h4>
  //       <div style="max-height: 150px; overflow-y: auto;">
  //         ${shop.recentData
  //           .map(
  //             (item) => `
  //           <div style="padding: 5px; border-bottom: 1px solid #f0f0f0;">
  //             <p><strong>采样时间:</strong>${item.sampleTime}</p>
  //             <p><strong>油烟浓度:</strong>${item.oilSmokeConcentration} mg/m³</p>
  //             <p><strong>净化器电流:</strong>${item.purifierCurrent} A</p>
  //             <p><strong>风机电流:</strong>${item.fanCurrent} A</p>
  //           </div>
  //         `,
  //           )
  //           .join('')}
  //       </div>
  //     </div>
  //   </div>
  // `
  const content = `
    <div style="padding: 10px; width: 400px;">
      <h3 style="margin: 0 0 10px 0; color: #333;">${shop.shop.name}</h3>
      <div style="font-size: 14px; line-height: 1.5;">
        <p><strong>地址:</strong>${shop.shop.address}</p>
        <p><strong>环信码等级:</strong><span style="color: ${getColorByRingCodeLevel(shop.shop.ringCodeLevel)}">${getRingCodeLevelText(shop.shop.ringCodeLevel)}</span></p>
        <p><strong>环信码发布时间:</strong>${shop.shop.ringCodePublishTime}</p>
        <h4 style="margin: 10px 0 5px 0; color: #666;">近1小时监测数据</h4>
        <div id="infowindowChartContainer" style="width: 100%; height: 250px;"></div>
      </div>
    </div>
  `
  // 清除现有的信息窗口
  if (window.infoWindow) {
    window.infoWindow.close()
  }
  // 创建新的信息窗口
  window.infoWindow = new AMap.InfoWindow({
    content: content,
    size: new AMap.Size(400, 400),
    offset: new AMap.Pixel(-24, -80),
  })
  // 打开信息窗口
  window.infoWindow.open(map, [shop.shop.longitude, shop.shop.latitude])
  // 等待信息窗口加载完成后初始化图表
  setTimeout(() => {
    const chartdom = document.getElementById('infowindowChartContainer')
    if (chartdom) {
      initChart(chartdom, shop.recentData)
    }
  }, 100)
}
/**
 * 初始化监测数据图表
 * @param {Array} data 监测数据
 */
function initChart(chartdom, data) {
  // 准备图表数据
  const times = data.map((item) => item.sampleTime)
  const oilSmokeData = data.map((item) => item.oilSmokeConcentration)
  const purifierData = data.map((item) => item.purifierCurrent)
  const fanData = data.map((item) => item.fanCurrent)
  // 初始化图表
  const chart = echarts.init(chartdom)
  // 图表配置
  const option = {
    tooltip: {
      trigger: 'axis',
      axisPointer: {
        type: 'cross',
        label: {
          backgroundColor: '#6a7985',
  },
      },
    },
    legend: {
      data: ['油烟浓度', '净化器电流', '风机电流'],
      top: 0,
    },
    grid: {
      left: '3%',
      right: '4%',
      bottom: '3%',
      containLabel: true,
    },
    xAxis: [
      {
        type: 'category',
        boundaryGap: false,
        data: times.map((time) => time.split(' ')[1]),
        axisLabel: {
          rotate: 0,
          fontSize: 10,
        },
      },
    ],
    yAxis: [
      {
        type: 'value',
        name: '油烟浓度 (mg/m³)',
        position: 'left',
        left: '30%',
      },
      {
        type: 'value',
        name: '电流 (A)',
        position: 'right',
      },
    ],
    series: [
      {
        name: '油烟浓度',
        type: 'line',
        data: oilSmokeData,
        yAxisIndex: 0,
        smooth: true,
      },
      {
        name: '净化器电流',
        type: 'line',
        data: purifierData,
        yAxisIndex: 1,
        smooth: true,
      },
      {
        name: '风机电流',
        type: 'line',
        data: fanData,
        yAxisIndex: 1,
        smooth: true,
      },
    ],
  }
  // 应用配置
  chart.setOption(option)
  // 响应式处理
  window.addEventListener('resize', function () {
    chart.resize()
  })
}
/**
 * 清除海量点标记
 */
function clearMassMarks() {
  if (window.massMarks) {
    window.massMarks.clear()
    window.massMarks.setMap(null)
    window.massMarks = null
  }
  if (window.infoWindow) {
    window.infoWindow.close()
    window.infoWindow = null
  }
}
export default {
  drawMassMarks,
  clearMassMarks,
  // /**
  //  * 绘制海量点标记
  //  * @param fDatas 完整监测数据
  //  * @param _factor 当前展示的监测因子对象
  //  */
  // drawMassMarks(fDatas, _factor, onClick) {
  //   if (!toolboxStore.dataMarkerStatus) {
  //     return;
  //   }
  //   this.clearMassMarks();
  //   const lnglats = fDatas.lnglats_GD;
  //   var data = [];
  //   for (let i = 0; i < lnglats.length; i++) {
  //     data.push({
  //       lnglat: lnglats[i], //点标记位置
  //       name: `${fDatas.times[i]}<br/>${_factor.factorName}: ${_factor.datas[i].factorData} μg/m³`,
  //       id: i
  //     });
  //   }
  //   // 创建样式对象
  //   var styleObject = {
  //     url: 'https://a.amap.com/jsapi_demos/static/images/mass1.png',
  //     // url: './asset/mipmap/ic_up_white.png', // 图标地址
  //     size: new AMap.Size(11, 11), // 图标大小
  //     anchor: new AMap.Pixel(5, 5) // 图标显示位置偏移量,基准点为图标左上角
  //   };
  //   var massMarks = new AMap.MassMarks(data, {
  //     zIndex: 5, // 海量点图层叠加的顺序
  //     zooms: [15, 18], // 在指定地图缩放级别范围内展示海量点图层
  //     style: styleObject // 设置样式对象
  //   });
  //   massMarks.on('click', (event) => {
  //     const i = event.data.id;
  //     // 3. 自定义点击事件
  //     onClick(i);
  //   });
  //   var marker = new AMap.Marker({
  //     content: ' ',
  //     map: map,
  //     offset: new AMap.Pixel(13, 12)
  //   });
  //   var timeout;
  //   massMarks.on('mouseover', (e) => {
  //     if (timeout) {
  //       clearTimeout(timeout);
  //     }
  //     marker.setPosition(e.data.lnglat);
  //     marker.setLabel({ content: e.data.name });
  //     map.add(marker);
  //     timeout = setTimeout(() => {
  //       map.remove(marker);
  //     }, 2000);
  //   });
  //   _massMarks = massMarks;
  //   map.add(massMarks);
  // },
  // clearMassMarks() {
  //   if (_massMarks) {
  //     map.remove(_massMarks)
  //     _massMarks = undefined
  //   }
  // },
  /**
   * 创建标记点
@@ -93,14 +411,14 @@
      // 开启标注避让,默认为开启,v1.4.15 新增属性
      collision: collision,
      // 开启标注淡入动画,默认为开启,v1.4.15 新增属性
      animation: true
    });
      animation: true,
    })
    map.add(layer);
    map.add(layer)
    // var markers = [];
    for (var i = 0; i < dataList.length; i++) {
      const data = dataList[i];
      const data = dataList[i]
      var curData = {
        name: data.name,
        position: [data.longitude, data.latitude],
@@ -115,7 +433,7 @@
          size: [30, 30],
          anchor: 'bottom-center',
          angel: 0,
          retina: true
          retina: true,
        },
        text: {
          content: showTxt ? data.name : '',
@@ -127,22 +445,22 @@
            fillColor: '#fff',
            strokeColor: '#333',
            strokeWidth: 0,
            backgroundColor: '#122b54a9'
            backgroundColor: '#122b54a9',
          },
        },
          }
        }
      };
      curData.extData = {
        index: i
      };
        index: i,
      }
      var labelMarker = new AMap.LabelMarker(curData);
      var labelMarker = new AMap.LabelMarker(curData)
      // markers.push(labelMarker);
      layer.add(labelMarker);
      layer.add(labelMarker)
    }
    return layer;
    return layer
  },
  createMarker({ position, img, title, content, label = '', extData }) {
@@ -151,8 +469,8 @@
      size: new AMap.Size(30, 30), //图标尺寸
      image: img, //Icon 的图像
      // imageOffset: new AMap.Pixel(-9, -3), //图像相对展示区域的偏移量,适于雪碧图等
      imageSize: new AMap.Size(30, 30) //根据所设置的大小拉伸或压缩图片
    });
      imageSize: new AMap.Size(30, 30), //根据所设置的大小拉伸或压缩图片
    })
    const marker = new AMap.Marker({
      position: position,
      // offset: new AMap.Pixel(-13, -30),
@@ -161,11 +479,11 @@
      title: title,
      label: {
        content: label,
        direction: 'bottom'
        direction: 'bottom',
      },
      extData
    });
      extData,
    })
    // map.add(marker);
    return marker;
    return marker
  },
  }
};