openlayers加载类封装

本地图的加载类包含矢量底图切换、图上量算、经纬度显示、以及图层控制、流动线等

html代码:

<template>
    <div id="ol_map">
    </div>
</template>

<script>
import xyMap from '@/map/init'
export default {
  data() {
    return {
      mapInstance: null,
    };
  },
  components:{
  },
  mounted() {
    this.initMap();
  },
  methods: {
    initMap() {
      this.mapInstance = new xyMap({
        targetEle: 'ol_map',
        center: [104.043622,30.672401],
        zoom: 17,
        minZoom: 1,
        maxZoom: 18,
        toolBar: true,
        layerControl: true,
        vueEle: this,
      });
      var data = [
        {
          id: '12311231',
          pipeCoordinatePoints: '[[104.043847,30.67543],[104.044188,30.675453],[104.044242,30.675748],[104.045113,30.675585],[104.045113,30.675585],[104.047979,30.675243],[104.047979,30.675243]]',

        }
      ]
      this.mapInstance.renderPipeLine(data);
    },
  },
  beforeUnmount() {
    if (this.mapInstance && this.mapInstance.mapObj) {
      this.mapInstance.mapObj.setTarget(undefined);
    }
  }
};
</script>

<style>
</style>

直接附上js源码

import 'ol/ol.css';
import Map from 'ol/Map';
import View from 'ol/View';
import OSM from 'ol/source/OSM';
import XYZ from 'ol/source/XYZ';
import Overlay from 'ol/Overlay';
import Feature from 'ol/Feature';
import { unByKey } from 'ol/Observable';
import { transform } from 'ol/proj';
import { createStringXY } from 'ol/coordinate';
import { altKeyOnly, click, pointerMove } from 'ol/events/condition.js';
import VectorSource from 'ol/source/Vector';
import { getArea, getLength } from 'ol/sphere';
import { LineString, Point, Polygon } from 'ol/geom';
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer';
import { Circle as CircleStyle, Fill, Icon, Stroke, Style } from 'ol/style';
import { Draw } from 'ol/interaction';
import { fromLonLat } from 'ol/proj';
import Select from 'ol/interaction/Select.js';
import {
  ScaleLine,
  MousePosition,
  Attribution,
  OverviewMap,
  defaults as defaultControls,
} from 'ol/control';
import xymap from '@/utils/mapUtil';
export default class map {
  /*
    *  @targetEle {Str} 元素类名选择器
       @center    {Arr} 经纬度
       @zoom      {Num} 地图缩放
       @minZoom   {Num} 地图最小的缩放
       @maxZoom   {Num} 地图最大的缩放值
    * */
  constructor({ targetEle, center, zoom, minZoom, maxZoom, toolBar, layerControl, vueEle }) {
    this.customLayersArr = []; // 先初始化为空数组
    this.index = 0;
    this.zoom = zoom; //地图的缩放
    this.toolBar = toolBar;
    this.layerControl = layerControl;
    this.position = null;
    this.draw = null; //绘画对象
    this.mapObj = null; //地图对象
    this.markerLayer = null; //marker图层对象
    this.pipelineLayer = null; //管线图层对象
    this.infoWindows = []; //信息窗体
    this.helpTooltip = null;
    this.moveMarker = null;
    this.vector = null; //矢量对象
    this.source = null; //地图加载源
    this.points = null; //地图的经纬度
    this.sketch = null; //绘画的草图
    this.center = center; //地图的经纬度
    this.measureTooltip = null; //
    this.animating = false; //监听轨迹回放进度
    this.lineString = null; //绘制成线的对象
    this.minZoom = minZoom; //地图最小缩放值
    this.maxZoom = maxZoom; //地图最大缩放值
    this.targetEle = targetEle; //地图呈现的目标元素
    this.measureTooltipElement = null; //测量时的提示对象
    this.helpTooltipElement = null;
    this.isMeasuring = false;
    this.select = null;
    this.vueEle = vueEle; //vue组件上下文this
    this.init();
  }

  /*地图初始化*/
  init() {
    this.createMapInstance();
    this.createLayers();
    this.showPopup();
    this.initHight();
    if (this.toolBar) {
      this.loadToolbar();
    }
    if (this.layerControl) {
      this.loadLayerControl();
    }
    this.searchMap();
  }

  selectStyle(feature) {
    const selected = new Style({
      fill: new Fill({
        color: 'rgba(245, 108, 108, 1)',
      }),
      stroke: new Stroke({
        color: 'rgba(227, 53, 19, 0.9)',
        width: 4,
      }),
    });
    const color = feature.get('COLOR') || '#eeeeee';
    selected.getFill().setColor(color);
    return selected;
  }
  initHight() {
    const _this = this;
    this.highlightLayer = null; // 初始化为null

    this.select = new Select({
      condition: click,
      layers: [this.markerLayer, this.pipelineLayer],
      style: null,
      hitTolerance: 10,
      filter: feature => {
        const params = feature.get('params');
        return params && Object.keys(params).length > 0;
      },
    });

    this.mapObj.addInteraction(this.select);

    this.select.on('select', e => {
      if (e.selected.length > 0) {
        const feature = e.selected[0];
        if (feature.get('layerType') == 'marker_layer') {
          var state = feature.values_?.params?.状态 == '报警' ? true : false;
          this.focusFeature(e, state);
        } else {
          this.focusLine(e);
        }
      }
    });
  }
  focusFeature(feature, state) {
    const targetFeature = feature.selected ? feature.selected[0] : feature;

    if (!targetFeature) return;

    // 清除现有高亮
    if (this.highlightLayer) {
      this.highlightLayer.getSource().clear();
      this.mapObj.removeLayer(this.highlightLayer); // 移除旧图层
    }

    // 总是重新创建高亮图层,确保样式正确
    this.highlightLayer = new VectorLayer({
      source: new VectorSource(),
      zIndex: 10,
      isHightLayer: true,
      updateWhileInteracting: false,
      renderMode: 'vector',
      style: new Style({
        image: new Icon({
          src: '/assets/images/focus.png',
          scale: 0.9,
        }),
      }),
    });
    this.mapObj.addLayer(this.highlightLayer);

    // 添加高亮特征
    const highlight = new Feature({
      geometry: targetFeature.getGeometry().clone(),
    });
    this.highlightLayer.getSource().addFeature(highlight);
  }

  focusLine(feature) {
    // 清除现有高亮
    if (this.highlightLayer) {
      this.highlightLayer.getSource().clear();
      this.mapObj.removeLayer(this.highlightLayer);
    }

    // 创建新的高亮图层
    this.highlightLayer = new VectorLayer({
      source: new VectorSource(),
      isHightLayer: true,
      updateWhileInteracting: false,
      renderMode: 'vector',
      zIndex: 10,
    });
    this.mapObj.addLayer(this.highlightLayer);

    const lineStyle = new Style({
      stroke: new Stroke({
        color: '#F5F5DC',
        width: 16,
        lineDash: [5, 5],
      }),
    });

    const fea = feature.selected[0];
    const coor = JSON.parse(fea.values_.coordinates);
    const transformedCoords = coor.map(coord => fromLonLat(coord));

    const pipeLine = new LineString(transformedCoords);
    const pipeFeature = new Feature(pipeLine);

    // 设置样式
    pipeFeature.setStyle(lineStyle);

    // 将特征添加到高亮图层的源
    this.highlightLayer.getSource().addFeature(pipeFeature);
  }

  createMapInstance() {
    const positionElement = document.createElement('div');
    positionElement.className = 'mouse-position';
    document.getElementById(this.targetEle).appendChild(positionElement);

    this.mapObj = new Map({
      target: this.targetEle,
      layers: [], // 初始为空,后面通过createLayers添加
      view: this.getView(),
      controls: defaultControls({
        attribution: true,
      }).extend([
        new Attribution(), // 属性控件
        new ScaleLine({
          // 比例尺控件
          units: 'metric',
        }),
        new MousePosition({
          // 鼠标位置控件
          target: positionElement,
          projection: 'EPSG:4326', //指定输出坐标系
          coordinateFormat: createStringXY(4),
          className: 'custom-mouse-position',
          undefinedHTML: '&nbsp;',
        }),
      ]),
    });
  }

  showPopup() {
    var _this = this;
    // 弹窗元素
    const container = document.createElement('div');
    container.id = 'popup';
    container.className = 'ol-popup';

    const closer = document.createElement('a');
    closer.id = 'popup-closer';
    closer.className = 'ol-popup-closer';
    container.appendChild(closer);

    const content = document.createElement('div');
    content.id = 'popup-content';
    container.appendChild(content);

    // 关闭弹窗
    closer.onclick = () => (container.style.display = 'none');

    // 添加弹窗到地图
    const popupOverlay = new Overlay({
      element: container,
      offset: [15, 0],
      positioning: 'center-left',
      autoPanAnimation: { duration: 250 },
    });
    this.mapObj.addOverlay(popupOverlay);

    // 点击事件处理
    this.mapObj.on('singleclick', evt => {
      if (this.isMeasuring) return;
      // 重置弹窗
      content.innerHTML = '';
      container.style.display = 'none';

      // 获取所有点击到的要素(排除高亮图层)
      const features = this.mapObj.getFeaturesAtPixel(evt.pixel, {
        layerFilter: layer => {
          // 只检查矢量图层且不是高亮图层
          return layer instanceof VectorLayer && !layer.get('isHightLayer');
        },
        hitTolerance: 10,
      });
      if (features.length > 0) {
        const feature = features[0];
        if (
          feature.getProperties('values').layerType &&
          feature.getProperties('values').layerType !== 'pipe_line_layer'
        ) {
          const obj = feature.get('params');
          if (obj) {
            const contentDiv = document.createElement('div');
            for (const key in obj) {
              if (obj.hasOwnProperty(key)) {
                const p = document.createElement('p');
                const label = document.createElement('label');
                label.textContent = `${key}:`;
                const span = document.createElement('span');
                span.textContent = obj[key];
                // 状态颜色高亮
                if (key === '状态') {
                  span.style.color =
                    obj[key] === '在线'
                      ? 'green'
                      : obj[key] === '离线'
                      ? 'gray'
                      : obj[key] === '报警'
                      ? 'red'
                      : '';
                  span.style.fontWeight = 'bold';
                }

                p.appendChild(label);
                p.appendChild(span);
                contentDiv.appendChild(p);
              }
            }
            const historyP = document.createElement('p');
            historyP.className = 'history_p';
            historyP.textContent = '历史数据⨠';
            historyP.onclick = function () {
              var id = feature.id_;
              _this.vueEle.deviceId = id;
              _this.vueEle.getHistoryDec();
            };
            contentDiv.appendChild(historyP);

            content.appendChild(contentDiv);
            popupOverlay.setPosition(evt.coordinate);
            container.style.display = 'block';
          }
        } else {
          const feature = features[0];
          this.vueEle.pipelineId = feature.id_;
          this.vueEle.deviceVisible = true;
        }
      } else {
        //清除事件
        document.querySelector('.search-result').style.display = 'none';
        if (this.highlightLayer) {
          this.highlightLayer.getSource().clear();
        }
      }
    });
  }

  /*
   * 为地图设置视图center,zoom,minZoom,maxZoom
   * */
  getView() {
    let view = new View({
      center: transform(this.center, 'EPSG:4326', 'EPSG:3857'),
      zoom: this.zoom,
      minZoom: this.minZoom,
      maxZoom: this.maxZoom,
    });
    return view;
  }

  /* 创建图层 */
  createLayers() {
    this.createBaseLayers();
    this.createOverlayLayers();
    this.customLayersArr.forEach(item => {
      this.mapObj.addLayer(item.layer);
    });
  }

  /* 创建基础图层 */
  createBaseLayers() {
    this.markerLayer = new VectorLayer({
      source: new VectorSource({ features: [] }),
      visible: true,
      zIndex: 110,
    });

    this.pipelineLayer = new VectorLayer({
      source: new VectorSource(),
      zIndex: 100,
    });
  }

  /* 创建覆盖图层 */
  createOverlayLayers() {
    this.customLayersArr = [
      this.createTianDiTuLayer('tianditu_img', '影像地图', 'img_w'),
      this.createTianDiTuLayer('tianditu_cia', '影像注记', 'cia_w'),
      {
        id: 'pipe_line_layer',
        mapType: 'pipe_line_layer',
        redress: '0',
        showText: '管线',
        layer: this.pipelineLayer,
      },
    ];
  }
  createTianDiTuLayer(mapType, showText, layerType) {
    return {
      id: mapType,
      mapType,
      redress: '0',
      showText,
      layer: this.setTianDiTuSource(
        `https://t0.tianditu.gov.cn/DataServer?T=${layerType}_w&x={x}&y={y}&l={z}&tk=3c147dbe940e0c4464f2b582bd92fde0`,
        true
      ),
    };
  }

  setTianDiTuSource(url, visible) {
    const subDomains = ['t0', 't1', 't2', 't3', 't4', 't5', 't6', 't7'];
    return new TileLayer({
      visible,
      zIndex: 1,
      source: new XYZ({
        url: url,
        tileUrlFunction: tileCoord => {
          const z = tileCoord[0];
          const x = tileCoord[1];
          const y = -tileCoord[2] - 1;
          const subDomain = subDomains[(x + y) % subDomains.length];

          return url
            .replace('{subDomain}', subDomain)
            .replace('{col}', x)
            .replace('{row}', y)
            .replace('{level}', z)
        },
        crossOrigin: 'anonymous', // 添加跨域属性
      }),
    });
  }


  createPolygonMap() {
    // 确保只初始化一次
    if (!this.source) {
      this.source = new VectorSource({ wrapX: false });
      this.vector = new VectorLayer({
        source: this.source,
        zIndex: 100,
        style: new Style({
          // 添加默认样式确保图形可见
          fill: new Fill({ color: 'rgba(255, 0, 0, 0.2)' }),
          stroke: new Stroke({ color: 'red', width: 2 }),
        }),
      });
      this.mapObj.addLayer(this.vector);
    }
    this.addInteraction('Polygon'); // 改为绘制多边形
  }
  addInteraction(drawType) {
    // 清除现有交互
    if (this.draw) {
      this.mapObj.removeInteraction(this.draw);
      this.draw = null;
    }

    // 初始化存储
    this.measureTooltips = this.measureTooltips || [];
    this.currentListeners = this.currentListeners || [];

    // 创建新的绘制交互
    this.draw = new Draw({
      source: this.source,
      type: drawType,
      style: new Style({
        fill: new Fill({ color: 'rgba(255, 204, 51, 0.2)' }),
        stroke: new Stroke({
          color: 'rgba(255, 204, 51)',
          width: 2,
          lineDash: [10, 10],
        }),
        image: new CircleStyle({
          radius: 5,
          stroke: new Stroke({ color: 'rgba(0, 0, 0, 0.7)', lineDash: [10, 10] }),
          fill: new Fill({ color: 'rgba(255, 255, 255, 0.2)' }),
        }),
      }),
      condition: event => {
        // 阻止与地图拖拽的冲突
        return !event.originalEvent.ctrlKey && !event.originalEvent.shiftKey;
      },
    });

    // 添加交互
    this.mapObj.addInteraction(this.draw);

    // 绘制开始事件
    this.draw.on('drawstart', evt => {
      this.sketch = evt.feature;

      // 创建新的tooltip
      const tooltipElement = document.createElement('div');
      tooltipElement.className = 'ol-tooltip ol-tooltip-measure';
      tooltipElement.style.whiteSpace = 'nowrap';
      tooltipElement.style.padding = '4px';
      const tooltip = new Overlay({
        element: tooltipElement,
        offset: [0, -15],
        positioning: 'bottom-center',
        stopEvent: false, // 允许tooltip接收事件
      });
      this.mapObj.addOverlay(tooltip);
      this.measureTooltips.push(tooltip);

      // 几何变化监听
      const listener = this.sketch.getGeometry().on('change', evt => {
        const geom = evt.target;
        let output, coord;

        if (geom instanceof Polygon) {
          output = this.formatArea(geom);
          coord = geom.getInteriorPoint().getCoordinates();
        } else if (geom instanceof LineString) {
          output = this.formatLength(geom);
          coord = geom.getLastCoordinate();
        }

        tooltipElement.innerHTML = output;
        tooltip.setPosition(coord);
      });

      this.currentListeners.push({
        feature: this.sketch,
        listener: listener,
      });
    });

    // 绘制结束事件
    this.draw.on('drawend', evt => {
      const feature = evt.feature;
      const lastTooltip = this.measureTooltips[this.measureTooltips.length - 1];

      if (lastTooltip) {
        lastTooltip.getElement().className = 'ol-tooltip ol-tooltip-static';
        lastTooltip.setOffset([-10, -10]);
      }

      // 设置最终样式
      feature.setStyle(
        new Style({
          fill: new Fill({ color: 'rgba(255, 204, 51, 0.2)' }),
          stroke: new Stroke({
            color: 'rgba(255, 204, 51)',
            width: 2,
            lineDash: [10, 10],
          }),
        })
      );

      this.sketch = null;
    });

    // 处理指针移动
    this.mapObj.on('pointermove', this.pointerMoveHandler);
    this.mapObj.on('pointerout', this.mouseoutHandler);
  }
  pointerMoveHandler = evt => {
    if (evt.dragging || !this.helpTooltip) return;

    let helpMsg = '单击开始绘制';
    if (this.sketch) {
      const geom = this.sketch.getGeometry();
      if (geom instanceof Polygon) {
        helpMsg = '点击继续绘制多边形';
      } else if (geom instanceof LineString) {
        helpMsg = '点击继续绘制线条';
      }
    }

    this.helpTooltipElement.innerHTML = helpMsg;
    this.helpTooltip.setPosition(evt.coordinate);
    this.helpTooltipElement.classList.remove('hidden');
  };

  clearAllMeasurements() {
    // 清除绘制交互
    if (this.draw) {
      this.mapObj.removeInteraction(this.draw);
      this.draw = null;
    }

    // 清除所有监听器
    this.currentListeners.forEach(item => {
      unByKey(item.listener);
    });
    this.currentListeners = [];

    // 清除所有tooltip
    this.measureTooltips.forEach(tooltip => {
      this.mapObj.removeOverlay(tooltip);
    });
    this.measureTooltips = [];

    // 清除帮助提示
    if (this.helpTooltip) {
      this.helpTooltipElement.classList.add('hidden');
    }

    // 清除源数据
    if (this.source) {
      this.source.clear();
    }

    // 重置测量状态
    this.isMeasuring = false; // 确保测量状态被重置
    this.sketch = null; // 确保草图被重置

    // 重新添加点击事件监听器
    this.mapObj.on('singleclick', evt => {
      if (this.isMeasuring) return; // 量算时跳过弹窗逻辑
      const content = document.getElementById('popup-content');
      content.innerHTML = '';
      const features = this.mapObj.getFeaturesAtPixel(evt.pixel);
      if (features.length > 0) {
        const feature = features[0];
        const obj = feature.getProperties('values').params;
        const contentDiv = document.createElement('div');
        for (const key in obj) {
          if (obj.hasOwnProperty(key)) {
            const p = document.createElement('p');
            const label = document.createElement('label');
            label.textContent = `${key}:`;
            const span = document.createElement('span');
            span.textContent = obj[key];
            p.appendChild(label);
            p.appendChild(span);
            contentDiv.appendChild(p);
          }
        }
        content.appendChild(contentDiv);
        this.mapObj.getOverlayById('popup').setPosition(evt.coordinate);
        document.getElementById('popup').style.display = 'block';
      } else {
        document.getElementById('popup').style.display = 'none';
      }
    });
  }
  /*鼠标按下的操作*/
  mouseoutHandler() {
    this.helpTooltipElement.classList.add('hidden');
  }

  drawPolygon(drawType) {
    this.isMeasuring = true;
    // 确保基础结构存在
    if (!this.source) {
      this.createPolygonMap();
    }

    // 移除旧的交互,保留已有的图形
    if (this.draw) {
      this.mapObj.removeInteraction(this.draw);
    }

    this.addInteraction(drawType || 'LineString'); // 默认为线
  }

  /*输出格式几何图形的结果*/
  formatArea(polygon) {
    let area = getArea(polygon);
    let output = '';
    output = Math.round(area * 100) / 100 + ' ' + 'm<sup>2</sup>';
    return output;
  }

  /*输出绘制线性结果*/
  formatLength(line) {
    let length = getLength(line, { projection: 'EPSG:3857' });
    let output = '';
    output = Math.round(length * 100) / 100 + ' m';
    return output;
  }
  createPopupMap() {
    this.init();
    this.markerLayer = new VectorLayer({
      source: new VectorSource({
        features: [],
      }),
    });
    this.mapObj.addLayer(this.markerLayer);
    this.markerLayer.setVisible(true);
  }

  /*创建一个经纬度(坐标点)*/
  getPoint(point) {
    return new Point(point).transform('EPSG:4326', 'EPSG:3857');
  }
  createMarker(id, point, url, info) {
    // 创建要素
    const marker = new Feature({
      geometry: this.getPoint(point),
    });

    // 创建 canvas 元素用于渲染 GIF
    const canvas = document.createElement('canvas');
    const size = 30; // 根据需要调整大小
    canvas.width = size;
    canvas.height = size;
    const context = canvas.getContext('2d');

    // 使用 gifler 加载 GIF
    gifler(url)
      .frames(canvas, (ctx, frame) => {
        // 清除上一帧
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // 绘制圆形裁剪区域
        ctx.save(); // 保存当前状态
        ctx.beginPath();
        ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2, true); // 创建圆形
        ctx.clip(); // 裁剪
        ctx.drawImage(frame.buffer, 0, 0, size, size); // 绘制当前帧
        ctx.restore(); // 恢复状态

        // 更新标记样式
        marker.setStyle(
          new Style({
            image: new Icon({
              img: canvas,
              imgSize: [canvas.width, canvas.height],
              scale: 1,
            }),
          })
        );
      })
      .catch(err => {
        // 使用静态图像作为后备
        marker.setStyle(
          new Style({
            image: new Icon({
              src: url, // 使用静态图像作为后备
              scale: 1,
            }),
          })
        );
      });

    // 设置标记属性
    marker.setId(id);
    marker.set('layerType', 'marker_layer'); // 添加图层类型标识
    marker.set('params', info);

    // 添加到 markerLayer
    this.markerLayer.getSource().addFeature(marker);
    return marker;
  }

  renderPipeLine(item) {
    if (!item || !item.length) return;

    // 清除之前的管线
    this.pipelineLayer.getSource().clear();

    // 存储所有箭头特征和动画状态
    const arrowAnimations = [];

    item.forEach((item, index) => {
      if (item.pipeCoordinatePoints) {
        const poiArr = JSON.parse(item.pipeCoordinatePoints);
        var transformedPipeCoords = poiArr.map(coord => fromLonLat(coord));

        const pipeLine = new LineString(transformedPipeCoords);
        const pipeFeature = new Feature(pipeLine);

        pipeFeature.setId(`${item.id}`);
        pipeFeature.set('params', { 管线名称: '测试管线', 状态: '在用', 管线编号: 'S6802' });
        pipeFeature.set('layerType', 'pipe_line_layer');
        pipeFeature.set('coordinates', item.pipeCoordinatePoints);

        // 基础管线样式
        const lineStyle = new Style({
          stroke: new Stroke({
            color: '#FFA500',
            width: 8,
          }),
        });

        // 添加基础管线特征
        pipeFeature.setStyle(lineStyle);
        this.pipelineLayer.getSource().addFeature(pipeFeature);

        // 创建多个箭头特征并存储动画状态
        const numberOfArrows = 5; // 设置箭头数量
        const distanceBetweenArrows = 1 / (numberOfArrows + 1); // 计算箭头之间的间距

        for (let i = 0; i < numberOfArrows; i++) {
          const progress = (i + 1) * distanceBetweenArrows; // 计算每个箭头的初始进度
          const arrowFeature = new Feature({
            geometry: new Point(pipeLine.getCoordinateAt(progress)),
          });

          // 存储动画所需信息
          arrowAnimations.push({
            pipeLine: pipeLine,
            arrowFeature: arrowFeature,
            progress: progress,
            speed: 0.0004, // 控制动画速度
          });

          // 添加箭头特征到图层
          this.pipelineLayer.getSource().addFeature(arrowFeature);
        }
      }
    });
    // 如果没有管线,直接返回
    if (arrowAnimations.length === 0) return;
    // 预计算所有线段的旋转角度
    arrowAnimations.forEach(animation => {
      const segments = [];
      animation.pipeLine.forEachSegment((start, end) => {
        const dx = end[0] - start[0];
        const dy = end[1] - start[1];
        segments.push({
          start: start,
          end: end,
          rotation: Math.atan2(dy, dx),
        });
      });
      animation.segments = segments;
    });

    // 动画函数
    const animateArrows = () => {
      arrowAnimations.forEach(animation => {
        // 更新进度,确保箭头在管线上循环
        animation.progress += animation.speed;
        if (animation.progress >= 1) {
          animation.progress = 0; // 重新开始
        }

        // 获取当前坐标
        const currentCoord = animation.pipeLine.getCoordinateAt(animation.progress);
        animation.arrowFeature.getGeometry().setCoordinates(currentCoord);

        // 计算当前点的旋转角度
        let arrowRotation = 0;
        for (const segment of animation.segments) {
          if (isPointOnSegment(currentCoord, segment.start, segment.end)) {
            arrowRotation = segment.rotation;
            break;
          }
        }

        // 更新箭头样式
        animation.arrowFeature.setStyle(
          new Style({
            image: new Icon({
              src: '/assets/images/arrow.png',
              anchor: [0.45, 0.5],
              scale: 0.8,
              rotateWithView: true,
              rotation: -arrowRotation,
            }),
          })
        );
      });

      // 继续动画
      requestAnimationFrame(animateArrows);
    };

    // 开始动画
    animateArrows();

    // 判断点是否在线段上的辅助函数
    function isPointOnSegment(point, segmentStart, segmentEnd, tolerance = 0.1) {
      const crossProduct =
        (point[1] - segmentStart[1]) * (segmentEnd[0] - segmentStart[0]) -
        (point[0] - segmentStart[0]) * (segmentEnd[1] - segmentStart[1]);

      if (Math.abs(crossProduct) > tolerance) return false;

      const dotProduct =
        (point[0] - segmentStart[0]) * (segmentEnd[0] - segmentStart[0]) +
        (point[1] - segmentStart[1]) * (segmentEnd[1] - segmentStart[1]);

      if (dotProduct < 0) return false;

      const squaredLength =
        Math.pow(segmentEnd[0] - segmentStart[0], 2) + Math.pow(segmentEnd[1] - segmentStart[1], 2);

      return dotProduct <= squaredLength;
    }
  }
  /*移除所有marker*/
  removeAllMarker() {
    this.markerLayer.getSource().clear();
  }

  loadToolbar() {
    var _this = this;
    var container = document.createElement('div');
    container.id = 'ol-toolbar';
    container.className = 'ol-toolbar';
    document.getElementById(this.targetEle).appendChild(container);
    var panel = document.createElement('div');
    panel.className = 'ol-toolbar-panel';
    container.appendChild(panel);
    var switcher = document.createElement('div');
    switcher.className = 'ol-toolbar-switch';
    switcher.title = '工具栏';
    switcher.innerHTML = '<i></i>';
    container.appendChild(switcher);

    switcher.addEventListener('click', function (e) {
      if (panel.style.display == 'block') {
        panel.style.display = 'none';
        xymap.util.addClass(switcher, 'active');
      } else {
        panel.style.display = 'block';
        xymap.util.removeClass(switcher, 'active');
      }
    });
    var measure = document.createElement('div');
    measure.className = 'toolbar-item toolbar-measure';
    var measure_switcher = document.createElement('a');
    measure_switcher.innerHTML =
      '<span class="item-icon icon-tool"></span><span class="item-text">测量</span><em class="icon-switch"></em>';
    measure.appendChild(measure_switcher);
    panel.appendChild(measure);
    var measure_ul = document.createElement('ul');
    measure_ul.className = 'measure-panel';
    measure_ul.style.display = 'none';
    var distance = document.createElement('li');
    distance.innerHTML =
      '<span class="item-icon icon-distance"></span><span class="item-text">距离</span>';
    measure_ul.appendChild(distance);
    var area = document.createElement('li');
    area.innerHTML = '<span class="item-icon icon-area"></span><span class="item-text">面积</span>';
    measure_ul.appendChild(area);
    var clear = document.createElement('li');
    clear.innerHTML =
      '<span class="item-icon icon-clear"></span><span class="item-text">清空</span>';
    measure_ul.appendChild(clear);
    measure.appendChild(measure_ul);

    measure_switcher.addEventListener('click', function (e) {
      if (measure_ul.style.display == 'block') {
        measure_ul.style.display = 'none';
        xymap.util.removeClass(measure_switcher, 'active');
      } else {
        measure_ul.style.display = 'block';
        xymap.util.addClass(measure_switcher, 'active');
      }
    });
    distance.addEventListener('click', function (e) {
      measure_ul.style.display = 'none';
      xymap.util.removeClass(measure_switcher, 'active');
      _this.drawPolygon('LineString');
    });
    area.addEventListener('click', function (e) {
      measure_ul.style.display = 'none';
      xymap.util.removeClass(measure_switcher, 'active');
      _this.drawPolygon('Polygon');
    });
    clear.addEventListener('click', function (e) {
      measure_ul.style.display = 'none';
      xymap.util.removeClass(measure_switcher, 'active');
      //这里需要移除所有图上绘制
      if (_this.source) {
        _this.source.clear(); // 清除所有要素
        _this.clearAllMeasurements();
      }

      // 移除绘制交互
      if (_this.draw) {
        _this.mapObj.removeInteraction(_this.draw);
        _this.draw = null;
        _this.isMeasuring = false;
      }
    });
    //地图切换
    var view = document.createElement('div');
    view.className = 'toolbar-item toolbar-xyz';
    var view_switcher = document.createElement('a');
    var switcher_icon = document.createElement('span');
    switcher_icon.className = 'item-icon icon-map-road';
    view_switcher.appendChild(switcher_icon);
    var switcher_text = document.createElement('span');
    switcher_text.className = 'item-text';
    switcher_text.innerHTML = '影像';
    view_switcher.appendChild(switcher_text);
    view.appendChild(view_switcher);
    panel.appendChild(view);
    view_switcher.addEventListener('click', function (e) {
      var text = switcher_text.innerHTML;
      if (text == '影像') {
        // 切换到矢量地图
        xymap.util.removeClass(switcher_icon, 'icon-map-road');
        xymap.util.addClass(switcher_icon, 'icon-map-img');
        switcher_text.innerHTML = '地图';
        _this.switchToVectorMap(); // 调用切换方法
      } else {
        // 切换到影像地图
        xymap.util.removeClass(switcher_icon, 'icon-map-img');
        xymap.util.addClass(switcher_icon, 'icon-map-road');
        switcher_text.innerHTML = '影像';
        _this.switchToImageMap(); // 调用切换方法
      }

      // 更新图层控制(确保只显示当前地图类型)
      if (_this.layerControl) {
        _this.updateLayerControl();
      }
    });
  }

  updateLayerControl() {
    // 移除旧的图层控制
    const layerControl = document.getElementById('ol-layer-container');
    if (layerControl) {
      layerControl.remove();
    }
    // 重新加载图层控制(此时 customLayersArr 已经更新)
    this.loadLayerControl();
  }

  switchToImageMap() {
    // 1. 移除矢量地图和矢量注记
    this.removeLayers(['tianditu_vec', 'tianditu_cva']);

    // 2. 创建新图层
    const imgLayer = this.createTianDiTuLayer('tianditu_img', '影像地图', 'img_w');
    const ciaLayer = this.createTianDiTuLayer('tianditu_cia', '影像注记', 'cia_w');

    // 3. 添加图层到地图
    this.mapObj.addLayer(imgLayer.layer);
    this.mapObj.addLayer(ciaLayer.layer);

    // 4. 更新customLayersArr
    this.customLayersArr.push(imgLayer, ciaLayer);

    // 5. 更新图层控制面板
    this.updateLayerControl();
  }

  switchToVectorMap() {
    // 1. 移除影像地图和影像注记
    this.removeLayers(['tianditu_img', 'tianditu_cia']);

    // 2. 创建新图层
    const vecLayer = this.createTianDiTuLayer('tianditu_vec', '矢量地图', 'vec_w');
    const cvaLayer = this.createTianDiTuLayer('tianditu_cva', '矢量注记', 'cva_w');

    // 3. 添加图层到地图
    this.mapObj.addLayer(vecLayer.layer);
    this.mapObj.addLayer(cvaLayer.layer);

    // 4. 更新customLayersArr
    this.customLayersArr.push(vecLayer, cvaLayer);

    // 5. 更新图层控制面板
    this.updateLayerControl();
  }

  // 移除指定图层
  removeLayers(layerTypes) {
    layerTypes.forEach(type => {
      const layerObj = this.customLayersArr.find(layer => layer.mapType === type);
      if (layerObj) {
        this.mapObj.removeLayer(layerObj.layer);

        const index = this.customLayersArr.indexOf(layerObj);
        if (index > -1) {
          this.customLayersArr.splice(index, 1);
        }
      }
    });
  }

  toggleLayerVisibility(layerId, visible) {
    const layer = this.customLayersArr.find(item => item.mapType === layerId);
    if (layer) {
      layer.layer.setVisible(visible);
    }
  }

  loadLayerControl(params = {}) {
    if (params == false) {
      return;
    }
    params = params || {};
    if (params.visible == false) {
      const layerControl = document.getElementById('ol-layer-container');
      if (layerControl) {
        layerControl.remove();
      }
      return;
    }

    // 创建容器(保持原有HTML结构)
    const container = document.createElement('div');
    container.id = 'ol-layer-container';
    container.className = 'ol-layer-container';
    document.getElementById(this.targetEle).appendChild(container);

    // 创建切换按钮
    const switcher = document.createElement('div');
    switcher.className = 'ol-layer-switch';
    switcher.title = '图层控制';
    switcher.innerHTML = '<i></i>';
    container.appendChild(switcher);

    // 创建面板
    const panel = document.createElement('div');
    panel.className = 'ol-layer-panel';
    panel.style.display = 'none';
    container.appendChild(panel);

    // 切换显示/隐藏
    switcher.addEventListener('click', function (e) {
      if (panel.style.display == 'block') {
        panel.style.display = 'none';
        switcher.classList.remove('active');
      } else {
        panel.style.display = 'block';
        switcher.classList.add('active');
      }
    });

    // 修改点:在添加图层前先排序(按zIndex降序)
    this.customLayersArr.sort((a, b) => {
      const zIndexA = a.layer.getZIndex() || 0;
      const zIndexB = b.layer.getZIndex() || 0;
      return zIndexA - zIndexB; // 数值大的在上
    });

    // 添加图层控制项(保持原有方法)
    this.addLayerControl();
  }

  addLayerControl(layerIds = []) {
    const panel = document.querySelector('.ol-layer-panel');
    if (!panel) return;

    // 清空现有内容
    panel.innerHTML = '';

    // 如果没有指定layerIds,则使用所有自定义图层
    if (layerIds.length === 0) {
      layerIds = this.customLayersArr.map(layer => layer.mapType);
    }

    // 添加图层控制项
    this.customLayersArr
      .slice()
      .reverse()
      .forEach(item => {
        if (layerIds.includes(item.mapType)) {
          const layerItem = document.createElement('div');
          layerItem.className = 'ol-layer-item';

          const layerName = document.createElement('span');
          layerName.className = 'layer-item-name';
          layerName.textContent = item.showText;

          const checkbox = document.createElement('span');
          checkbox.className = item.layer.getVisible() ? 'check' : 'uncheck';
          checkbox.dataset.layer = item.mapType;

          layerItem.appendChild(layerName);
          layerItem.appendChild(checkbox);
          panel.appendChild(layerItem);

          // 添加事件监听
          checkbox.addEventListener('click', e => {
            const statusDom = e.target;
            const layerId = statusDom.dataset.layer;
            const layer = this.customLayersArr.find(l => l.mapType === layerId)?.layer;

            if (layer) {
              if (statusDom.classList.contains('check')) {
                statusDom.classList.remove('check');
                statusDom.classList.add('uncheck');
                layer.setVisible(false);
              } else {
                statusDom.classList.remove('uncheck');
                statusDom.classList.add('check');
                layer.setVisible(true);
              }
            }
          });
        }
      });
  }
  loadingSpinner = function () {
    var spinner = document.createElement('div'),
      container1 = document.createElement('div'),
      container2 = document.createElement('div'),
      container3 = document.createElement('div'),
      circle11 = document.createElement('div'),
      circle12 = document.createElement('div'),
      circle13 = document.createElement('div'),
      circle14 = document.createElement('div'),
      circle21 = document.createElement('div'),
      circle22 = document.createElement('div'),
      circle23 = document.createElement('div'),
      circle24 = document.createElement('div'),
      circle31 = document.createElement('div'),
      circle32 = document.createElement('div'),
      circle33 = document.createElement('div'),
      circle34 = document.createElement('div');

    spinner.classList.add('xy-spinner');
    container1.classList.add('spinner-container');
    container1.classList.add('container1');
    container2.classList.add('spinner-container');
    container2.classList.add('container2');
    container3.classList.add('spinner-container');
    container3.classList.add('container3');
    circle11.classList.add('circle1');
    circle12.classList.add('circle2');
    circle13.classList.add('circle3');
    circle14.classList.add('circle4');
    circle21.classList.add('circle1');
    circle22.classList.add('circle2');
    circle23.classList.add('circle3');
    circle24.classList.add('circle4');
    circle31.classList.add('circle1');
    circle32.classList.add('circle2');
    circle33.classList.add('circle3');
    circle34.classList.add('circle4');

    container1.appendChild(circle11);
    container1.appendChild(circle12);
    container1.appendChild(circle13);
    container1.appendChild(circle14);
    container2.appendChild(circle21);
    container2.appendChild(circle22);
    container2.appendChild(circle23);
    container2.appendChild(circle24);
    container3.appendChild(circle31);
    container3.appendChild(circle32);
    container3.appendChild(circle33);
    container3.appendChild(circle34);
    spinner.appendChild(container1);
    spinner.appendChild(container2);
    spinner.appendChild(container3);

    return spinner;
  };

  searchMap() {
    var _this = this;
    var container = document.createElement('div'),
      searchResult = document.createElement('div'),
      spinner = _this.loadingSpinner(),
      searchUl,
      emptyDiv = document.createElement('div'),
      layuiInputBlock = document.createElement('div'),
      layerInput = document.createElement('input'),
      layerIcon = document.createElement('i'),
      searchButton = document.createElement('div');

    searchButton.className = 'searchBtn';
    searchButton.innerHTML = '查询';
    container.id = 'search-contain';
    container.className = 'search-contain';
    searchResult.classList.add('search-result');

    emptyDiv.classList.add('empty');
    emptyDiv.innerHTML = '无查询数据';
    layuiInputBlock.classList.add('layui-input-block');
    layerInput.classList.add('layui-input');
    layerInput.setAttribute('type', 'text');
    layerInput.setAttribute('name', 'search');
    layerInput.setAttribute('autocomplete', 'off');
    layerInput.setAttribute('placeholder', ' 搜索');

    layuiInputBlock.appendChild(layerInput);
    layuiInputBlock.appendChild(layerIcon);
    searchResult.appendChild(emptyDiv);
    searchResult.appendChild(spinner);
    container.appendChild(layuiInputBlock);
    container.appendChild(searchButton);
    container.appendChild(searchResult);

    searchButton.onclick = function (e) {
      var value = layerInput.value;
      searchResult.style.display = 'block';
      emptyDiv.style.display = 'none';
      searchUl = document.createElement('ul');
      searchUl.classList.add('search-ul');

      // queryMap(value).then(res => {
      //   if (res.data.code == 200) {
      //     var data = res.data.data;
      //     data.forEach(item => {
      //       var searchLi = document.createElement('li'),
      //         foundCnt = document.createElement('a'),
      //         foundValue = document.createElement('i'),
      //         foundLayerName = document.createElement('em');

      //       foundCnt.classList.add('found-contain');
      //       foundValue.classList.add('found-value');
      //       foundLayerName.classList.add('found-layer-name');
      //       foundValue.innerHTML = item.rtuName + '  ';
      //       foundLayerName.innerHTML = item.rtuNumber;
      //       foundCnt.appendChild(foundValue);
      //       foundCnt.appendChild(foundLayerName);
      //       searchLi.appendChild(foundCnt);
      //       searchUl.appendChild(searchLi);

      //       foundCnt.onclick = function () {
      //         _this.setCenter(item.lon, item.lat);

      //         // 找到对应的feature
      //         const features = _this.markerLayer.getSource().getFeatures();
      //         const fea = features.find(ele => ele.id_ == item.id);

      //         if (fea) {
      //           var state = fea.values_.params.状态 == '报警' ? true : false;
      //           // 直接调用focusFeature并手动触发弹窗
      //           _this.focusFeature(fea, state);
      //         }
      //       };
      //     });
      //     spinner.style.display = 'none';
      //     searchResult.appendChild(searchUl);
      //   }
      // });
    };
    document.getElementById(this.targetEle).appendChild(container);
  }
  setCenter(lon, lat) {
    this.mapObj.getView().animate({
      center: transform([lon, lat], 'EPSG:4326', 'EPSG:3857'),
      zoom: 17,
      duration: 1000,
    });
  }
}

css

#ol_map {
    position: relative;
    width: 98.8%;
    height: 99%; /* 确保地图容器有高度 */
  }
  
  .ol-tooltip {
    position: absolute;
    left: 50px;
    background: rgba(0, 0, 0, 0.7);
    color: white;
    padding: 4px;
    border-radius: 4px;
    font-size: 12px;
  }
  .ol-tooltip-measure {
    opacity: 1;
    font-weight: bold;
  }
  /*--------工具栏样式开始--------*/
  .ol-toolbar {
      display: flex;
      z-index: 10;
      position: absolute;
      top: 10px;
      right:35px;
      height: 34px;
      background: #fff;
      box-shadow: 1px 2px 1px rgb(0 0 0 / 15%);
  }
  
  .ol-toolbar-switch {
      height: 16px;
      padding: 9px 12px;
  }
  
  .ol-toolbar-switch i {
      background: url(/assets/images/icon.png);
      background-position: -312px -264px;
      width: 12px;
      height: 16px;
      display: inline-block;
      transform: rotate(-0deg);
      -ms-transform: rotate(-0deg);
      -moz-transform: rotate(-0deg);
      -webkit-transform: rotate(-0deg);
      -o-transform: rotate(-0deg);
      -webkit-transition: .3s;
      transition: .3s;
  }
  
  .ol-toolbar-switch.active i {
      transform: rotate(180deg);
      -ms-transform: rotate(180deg);
      -moz-transform: rotate(180deg);
      -webkit-transform: rotate(180deg);
      -o-transform: rotate(180deg);
  }
  
  .toolbar-item {
      position: relative;
      float: left;
      cursor: pointer;
      padding: 0 15px;
  }
  
  .toolbar-item .item-icon {
      display: inline-block;
      background-image: url(/assets/images/icon.png);
      background-repeat: no-repeat;
      width: 16px;
      height: 16px;
      float: left;
      margin-right: 6px;
      margin-top: 9px;
  }
  
  .toolbar-item .item-text {
      float: left;
      font-size: 12px;
      font-style: normal;
      color: #333;
      height: 34px;
      line-height: 34px;
      display: inline-block;
  }
  
  .toolbar-item > .active .item-text {
      color: #2f87eb;
  }
  
  .toolbar-item .item-icon.icon-tool {
      background-position: -84px 0;
  }
  
  .toolbar-item > .active .icon-tool {
      background-position: -187px 0;
  }
  
  .toolbar-item .icon-switch {
      width: 11px;
      height: 6px;
      background-image: url(/assets/images/icon.png);
      background-repeat: no-repeat;
      background-position: -339px -222px;
      -webkit-transition: .3s;
      transition: .3s;
      margin-left: 5px;
      float: left;
      margin-top: 13px;
  }
  
  .toolbar-item > .active .icon-switch {
      -webkit-transform: rotate(180deg);
      transform: rotate(180deg);
  }
  
  .toolbar-item ul.measure-panel {
      position: absolute;
      width: 65px;
      background: #fff;
      top: 19px;
      left: 0px;
      list-style: none;
      box-shadow: 1px 2px 1px rgb(0 0 0 / 15%);
      padding: 0;
  }
  
  .toolbar-item ul li {
      height: 34px;
      line-height: 34px;
      padding: 0 7px 0 8px;
  }
  
  .toolbar-item ul li:not(:last-child) {
      border-bottom: 1px solid #ddd;
  }
  
  .toolbar-item .item-icon.icon-distance {
      background-position: -295px 0;
  }
  
  .toolbar-item .item-icon.icon-area {
      background-position: -324px 0;
  }
  
  .toolbar-item .item-icon.icon-clear {
      background-position: -138px 0;
  }
  
  .toolbar-item li:hover .item-text {
      color: #2f87eb;
  }
  
  .toolbar-item li:hover .icon-distance {
      background-position: -434px 0;
  }
  
  .toolbar-item li:hover .icon-area {
      background-position: -463px 0;
  }
  
  .toolbar-item li:hover .icon-clear {
      background-position: -241px 0;
  }
  
  .toolbar-item .item-icon.icon-map-img {
      background-position: -558px -218px;
  }
  
  .toolbar-item .item-icon.icon-map-road {
      background-position: -582px -218px;
  }
  
   /*--------图层控制样式开始--------*/
  .ol-layer-container {
      position: absolute;
      left: 25px;
      bottom: 40px;
      background: #fff;
      box-shadow: 3px 3px 6px rgb(0 0 0 / 30%);
      z-index: 9;
  }
  
  .ol-layer-container .ol-layer-switch {
      position: relative;
      z-index: 8;
      box-shadow: 0 0 0 0 #fff;
      cursor: pointer;
      height: 26px;
  }
  
  .ol-layer-container .ol-layer-switch i {
      background: url(/assets/images/icon.png);
      background-position: -49px -265px;
      width: 16px;
      height: 16px;
      display: inline-block;
      margin: 5px;
  }
  
  .ol-layer-container > .active i {
      background-position: -49px -288px;
  }
  
  .ol-layer-panel {
      position: absolute;
      width: 200px;
      z-index: 9;
      left: 32px;
      bottom: 0;
      background: #fff;
      border-radius: 3px;
      font-size: 13px;
      box-shadow: 1px 2px 1px rgb(0 0 0 / 15%);
  }
  
  .ol-layer-panel .ol-layer-item {
      height: 36px;
      line-height: 36px;
      border-top: 1px solid #eee;
      display: flex;
      align-items: center;
      position: relative;
      margin: 0 10px;
  }
  
  .ol-layer-item:first-child {
      border-top: 0px;
      margin-top: 10px;
  }
  
  .ol-layer-item:last-child {
      border-bottom: 0px;
      margin-bottom: 10px;
  }
  
  .ol-layer-item .layer-item-name {
      cursor: pointer;
  }
  
  .ol-layer-item .check, .ol-layer-item .uncheck {
      float: right;
      display: block;
      width: 14px;
      height: 14px;
      cursor: pointer;
      position: absolute;
      right: 0;
      margin-top: 0px;
  }
  
  .ol-layer-item .check {
      background: url(/assets/images/icon.png) -342px -53px no-repeat;
  }
  
  .ol-layer-item .uncheck {
      background: url(/assets/images/icon.png) -318px -53px no-repeat;
  }

  .ol-popup {
      position: absolute;
      background-color: white;
      -webkit-filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
      filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
      padding: 15px;
      border-radius: 10px;
      border: 1px solid #cccccc;
      bottom: 18px;
      left: -60px;
      min-width: 300px;
  }
  
  .ol-popup:after, .ol-popup:before {
      top: 100%;
      border: solid transparent;
      content: " ";
      height: 0;
      width: 0;
      position: absolute;
      pointer-events: none;
  }
  
  .ol-popup:after {
      border-top-color: white;
      border-width: 10px;
      left: 48px;
      margin-left: -10px;
  }
  
  .ol-popup:before {
      border-top-color: #cccccc;
      border-width: 11px;
      left: 48px;
      margin-left: -11px;
  }
  
  .ol-popup-closer {
      text-decoration: none;
      position: absolute;
      top: 2px;
      right: 8px;
  }
  
  .ol-popup-closer:after {
      content: "×";
      font-weight: bold;
      font-size: 24px; /* 调整图标大小 */
      color: rgb(107, 107, 107);
      position: relative;
      top: -2px; /* 调整位置 */
  }
  
  .ol-popup-more {
      text-align: right;
      color: #1E9FFF;
      cursor: pointer;
  }
  
  .ol-popup-road {
      color: #1E9FFF;
      cursor: pointer;
  }
  #popup-content{
    font-size: 14px;
    font-weight: 500;
  }
  #popup-content div p span{
    padding-left: 5px;
  }
  #popup-content div p{
    margin: 5px;
  }

  .custom-mouse-position{
    position: absolute;
    bottom:  15px;
    left: 45%;
    z-index: 999;
    background-color: rgba(255, 255, 255, 0.1);
    min-width: 180px;
    text-align: center;
    color: #fff;
    border-radius: 10px;
  }

  #search-contain{
    position: absolute;
    left: 50px;
    top: 10px;
    width: 270px;
    height: 28px;
    z-index: 999;
}
  
  .search-contain .layui-input-block {
    margin-top: 5px;
    margin-left: 5px;
    font-size: 12px;
  }

  .search-contain .searchBtn{
    float: right;
    text-align: center;
    margin-right: 5px;
    height: 26px;
    padding: 2px 10px 0 10px;
    background:#1E9FFF;
    color: #fff;
    border-radius: 2px;
    line-height: 26px;
    cursor: pointer;
    font-size: 14px;
  }
  
  .search-contain .search-result {
    position: relative;
    display: none;
    width: 300px;
    max-height: 300px;
    top: 5px;
    background-color: rgba(255, 255, 255, 1);
    border-radius: 5px;
    overflow-y: scroll;
  }
  
  .search-contain .search-ul {
    padding: 0 0 0 15px;
    overflow-y: auto;
    overflow-x: hidden;
    font-size: 0;
  }
  .search-ul li{
    list-style-type: none;
    position: relative;
    color: black;
    font-size: 13px;
  }

  .search-ul li::before {
    content: ''; /* 必须设置content属性 */
    position: absolute;
    left: 0; /* 图标在左侧 */
    top: 50%; /* 垂直居中 */
    transform: translateY(-50%); /* 垂直居中 */
    width: 18px; /* 图标宽度 */
    height: 18px; /* 图标高度 */
    background-image: url('/assets/images/icon_location.png'); /* 图标路径 */
    background-size: cover; /* 确保图标覆盖整个区域 */
}
  
  .search-contain .found-contain {
    display: block;
    height: 35px;
    line-height: 35px;
    padding-right: 10px;
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: pointer;
  }
  
  .search-contain .found-value {
    font-style: normal;
    color: #666;
    position: relative;
    z-index: 1;
    padding-top: 1px;
    padding-left: 20px;
  }
  
  .search-contain .found-value.active {
    color: #29baf1;
  }
  
  .search-contain .found-layer-name {
    font-style: normal;
    color: #999;
    float: right;
    margin-right: 10px;
  }
  
  .search-contain .empty {
    padding: 30px;
    text-align: center;
    color: #8e8e8e;
  }
  
  .search-contain .layui-input {
    float: left;
    width: 200px;
    height: 26px;
    background-color: rgba(255, 255, 255, 1);
    border: none 0;
    padding-left: 5px;
  }
  
  
  /******************************加载spinner*********************************/
  .xy-spinner {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    margin: auto;
    width: 30px;
    height: 30px;
  }
  
  .xy-spinner .spinner-container {
    position: absolute;
    width: 100%;
    height: 100%;
  }
  
  .xy-spinner .container2 {
    -webkit-transform: rotateZ(45deg);
    transform: rotateZ(45deg);
  }
  
  .xy-spinner .container3 {
    -webkit-transform: rotateZ(90deg);
    transform: rotateZ(90deg);
  }
  
  .xy-spinner .container1>div,
  .xy-spinner .container2>div,
  .xy-spinner .container3>div {
    width: 6px;
    height: 6px;
    background-color: #49a9ee;
  
    border-radius: 100%;
    position: absolute;
    -webkit-animation: bouncedelay 1.2s infinite ease-in-out;
    animation: bouncedelay 1.2s infinite ease-in-out;
    -webkit-animation-fill-mode: both;
    animation-fill-mode: both;
  }
  
  .xy-spinner .circle1 {
    top: 0;
    left: 0;
  }
  
  .xy-spinner .circle2 {
    top: 0;
    right: 0;
  }
  
  .xy-spinner .circle3 {
    right: 0;
    bottom: 0;
  }
  
  .xy-spinner .circle4 {
    left: 0;
    bottom: 0;
  }
  
  .xy-spinner .container2 .circle1 {
    -webkit-animation-delay: -1.1s;
    animation-delay: -1.1s;
  }
  
  .xy-spinner .container3 .circle1 {
    -webkit-animation-delay: -1s;
    animation-delay: -1s;
  }
  
  .xy-spinner .container1 .circle2 {
    -webkit-animation-delay: -0.9s;
    animation-delay: -0.9s;
  }
  
  .xy-spinner .container2 .circle2 {
    -webkit-animation-delay: -0.8s;
    animation-delay: -0.8s;
  }
  
  .xy-spinner .container3 .circle2 {
    -webkit-animation-delay: -0.7s;
    animation-delay: -0.7s;
  }
  
  .xy-spinner .container1 .circle3 {
    -webkit-animation-delay: -0.6s;
    animation-delay: -0.6s;
  }
  
  .xy-spinner .container2 .circle3 {
    -webkit-animation-delay: -0.5s;
    animation-delay: -0.5s;
  }
  
  .xy-spinner .container3 .circle3 {
    -webkit-animation-delay: -0.4s;
    animation-delay: -0.4s;
  }
  
  .xy-spinner .container1 .circle4 {
    -webkit-animation-delay: -0.3s;
    animation-delay: -0.3s;
  }
  
  .xy-spinner .container2 .circle4 {
    -webkit-animation-delay: -0.2s;
    animation-delay: -0.2s;
  }
  
  .xy-spinner .container3 .circle4 {
    -webkit-animation-delay: -0.1s;
    animation-delay: -0.1s;
  }
  
  @keyframes bouncedelay {
  
    0%,
    80%,
    100% {
      transform: scale(0);
      -webkit-transform: scale(0);
    }
  
    40% {
      transform: scale(1);
      -webkit-transform: scale(1);
    }
  }
  
  @-webkit-keyframes bouncedelay {
  
    0%,
    80%,
    100% {
      transform: scale(0);
      -webkit-transform: scale(0);
    }
  
    40% {
      transform: scale(1);
      -webkit-transform: scale(1);
    }
  }
  
  .mapScreen .layer_nav {
    background-color: rgba(6, 54, 87, 0.6);
  }
  
  .mapScreen .esri-widget--button {
    background-color: rgba(6, 54, 87, 0.6);
    color: rgba(255, 255, 255, 1);
  }
  
  .mapScreen .xyol_layer_ctrl_layers {
    background-color: rgba(6, 54, 87, 0.6);
  }
  
  .mapScreen .xyol_layer_ctrl_layers .xyol_layer_ctrl_layer {
    background-color: transparent;
    color: rgb(255, 255, 255);
  }
  
  .mapScreen .xyol_sublayer_ul .xyol_sublayer_li {
    background-color: transparent;
  }
  
  .mapScreen .xyol_layer_ctrl_layer {
    border-bottom: 1px solid rgba(215, 216, 216, 0.1);
  }
  
  .mapScreen .xyol_layer_ctrl_layers .xyol_layer_ctrl_layer:first-child {
    border-top: 0px;
  }
  
  .mapScreen .xyol_layer_ctrl_layers .xyol_layer_ctrl_layer:last-child {
    border-bottom: 0px;
  }
  
  .mapScreen .xyol_sublayer_li .check,
  .mapScreen .xyol_layer_ctrl_layer .check {
    background: url('/assets/images/icon.png') #235077 -364px -53px no-repeat;
  }

  .history_p{
    float: right;
    color: rgb(30, 159, 255);
    cursor: pointer;
    font-weight: bold;
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值