532 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Vue
		
	
	
	
			
		
		
	
	
			532 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Vue
		
	
	
	
<template>
 | 
						||
  <div class="HomeMap">
 | 
						||
    <div class="zoom-level" @click="soundAndMenu">
 | 
						||
      <img :src="zoomLevelImage" alt="" />
 | 
						||
    </div>
 | 
						||
    <div class="zoom-control">刻度尺:{{ zoomControl }}</div>
 | 
						||
    <div class="btn-container" v-if="closeBtnVisible">
 | 
						||
      <el-button @click="handleClickClose()">返回</el-button>
 | 
						||
    </div>
 | 
						||
    <div id="map" ref="olMap" class="map-container"></div>
 | 
						||
    <div class="pointingNorth">
 | 
						||
      <img src="@/assets/img/user.png" alt="" @click="userOpen" />
 | 
						||
      <img src="@/assets/img/setup.png" alt="" @click="setupOpen" />
 | 
						||
      <img src="@/assets/img/query.png" alt="" @click="queryOpen" />
 | 
						||
    </div>
 | 
						||
    <div class="configuration">
 | 
						||
      <div class="refresh" @click="refreshClick">
 | 
						||
        <img src="@/assets/img/refresh.png" alt="" />
 | 
						||
      </div>
 | 
						||
      <div class="refresh" @click="positionClick">
 | 
						||
        <img src="@/assets/img/icon_postions.png" alt="" />
 | 
						||
      </div>
 | 
						||
      <div class="refresh" @click="modelClick">
 | 
						||
        <img src="@/assets/img/icon_model.png" alt="" />
 | 
						||
      </div>
 | 
						||
    </div>
 | 
						||
    <el-dialog
 | 
						||
      title="设置"
 | 
						||
      :visible.sync="dialogVisible"
 | 
						||
      width="50%"
 | 
						||
      :before-close="handleClose"
 | 
						||
      append-to-body
 | 
						||
      class="custom-class"
 | 
						||
    >
 | 
						||
      <div class="block">
 | 
						||
        <span class="demonstration">预警声音设置</span>
 | 
						||
        <el-slider v-model="value2"></el-slider>
 | 
						||
      </div>
 | 
						||
      <span slot="footer" class="dialog-footer">
 | 
						||
        <el-button @click="dialogVisible = false">取 消</el-button>
 | 
						||
        <el-button type="primary" @click="determine"> 确 定 </el-button>
 | 
						||
      </span>
 | 
						||
    </el-dialog>
 | 
						||
    <el-dialog
 | 
						||
      title="查询"
 | 
						||
      :visible.sync="dialogQueryVisible"
 | 
						||
      width="90%"
 | 
						||
      :before-close="handleClose"
 | 
						||
      append-to-body
 | 
						||
      class="custom-table"
 | 
						||
    >
 | 
						||
      <AlertDialog />
 | 
						||
    </el-dialog>
 | 
						||
  </div>
 | 
						||
</template>
 | 
						||
 | 
						||
<script>
 | 
						||
import { mapGetters } from "vuex";
 | 
						||
import Map from "ol/Map";
 | 
						||
import View from "ol/View";
 | 
						||
import TileLayer from "ol/layer/Tile";
 | 
						||
import { fromLonLat } from "ol/proj";
 | 
						||
import XYZ from "ol/source/XYZ";
 | 
						||
import { Feature, Overlay } from "ol";
 | 
						||
import { Point, MultiPolygon } from "ol/geom";
 | 
						||
import { Vector as VectorLayer } from "ol/layer";
 | 
						||
import { Vector as VectorSource } from "ol/source";
 | 
						||
import { Style, Fill, Stroke, Circle } from "ol/style";
 | 
						||
import GeoJSON from "ol/format/GeoJSON";
 | 
						||
import LayerGroup from "ol/layer/Group";
 | 
						||
import AlertDialog from "../menuData/AlertDialog.vue";
 | 
						||
import { devPositionList } from "@/api/position.js";
 | 
						||
 | 
						||
export default {
 | 
						||
  name: "HomeMap",
 | 
						||
  components: {
 | 
						||
    AlertDialog
 | 
						||
  },
 | 
						||
  data() {
 | 
						||
    return {
 | 
						||
      map: null,
 | 
						||
      zoomLevel: null,
 | 
						||
      isZoomedIn: true,
 | 
						||
      isZoomedOut: false,
 | 
						||
      showPermissionPrompt: false,
 | 
						||
      dialogVisible: false,
 | 
						||
      value2: 0,
 | 
						||
      currentBaseMap: "gaode",
 | 
						||
      dialogQueryVisible: false,
 | 
						||
      closeBtnVisible: false,
 | 
						||
      zoomControl: 0,
 | 
						||
      toggleMap: false
 | 
						||
    };
 | 
						||
  },
 | 
						||
  created() {
 | 
						||
    // 清空控制台(可选)
 | 
						||
    // console.clear();
 | 
						||
  },
 | 
						||
  mounted() {
 | 
						||
    this.initMap();
 | 
						||
  },
 | 
						||
  computed: {
 | 
						||
    ...mapGetters(["statePage", "cityCode", "checkFlag"]),
 | 
						||
    zoomLevelImage() {
 | 
						||
      return this.isZoomedIn
 | 
						||
        ? require("@/assets/img/sound.png")
 | 
						||
        : require("@/assets/img/mute.png");
 | 
						||
    }
 | 
						||
  },
 | 
						||
  watch: {
 | 
						||
    checkFlag: {
 | 
						||
      handler(newVal) {
 | 
						||
        this.closeBtnVisible = newVal;
 | 
						||
        this.dialogQueryVisible = false;
 | 
						||
      },
 | 
						||
      deep: true
 | 
						||
    }
 | 
						||
  },
 | 
						||
  methods: {
 | 
						||
    handleClickClose() {
 | 
						||
      this.$store.commit("SET_CHECKFLAG", false);
 | 
						||
      this.$store.commit("SET_CLEARTRAJECTORY", true);
 | 
						||
    },
 | 
						||
    initPositon() {
 | 
						||
      let positionIds = {
 | 
						||
        pageNum: 1,
 | 
						||
        pageSize: 10000
 | 
						||
      };
 | 
						||
      devPositionList(positionIds).then((devRes) => {
 | 
						||
        if (devRes.code === 0) {
 | 
						||
          let positionRes = localStorage.getItem("PositionIds");
 | 
						||
          this.positionList = devRes.data.items;
 | 
						||
          const matchedList = this.positionList.filter((item) =>
 | 
						||
            positionRes.includes(item.id)
 | 
						||
          );
 | 
						||
          this.loadGeoJson(matchedList);
 | 
						||
        }
 | 
						||
      });
 | 
						||
    },
 | 
						||
 | 
						||
    // 加载 GeoJSON 数据并绘制
 | 
						||
    loadGeoJson(value) {
 | 
						||
      // 清空已有图层
 | 
						||
      this.clearLayer();
 | 
						||
 | 
						||
      // 创建矢量源
 | 
						||
      const source = new VectorSource();
 | 
						||
 | 
						||
      // 解析 GeoJSON 数据
 | 
						||
      const geojsonFormat = new GeoJSON();
 | 
						||
      value.forEach((item) => {
 | 
						||
        // 解析 regionJson 为 OpenLayers 特征
 | 
						||
        const feature = geojsonFormat.readFeature(JSON.parse(item.regionJson), {
 | 
						||
          dataProjection: "EPSG:4326", // 输入数据为 WGS84
 | 
						||
          featureProjection: "EPSG:3857" // 转换为地图投影
 | 
						||
        });
 | 
						||
 | 
						||
        // 设置多边形样式:透明蓝色填充,蓝色边框
 | 
						||
        feature.setStyle(
 | 
						||
          new Style({
 | 
						||
            fill: new Fill({
 | 
						||
              color: "rgba(0, 0, 255, 0.3)" // 透明蓝色填充
 | 
						||
            }),
 | 
						||
            stroke: new Stroke({
 | 
						||
              color: "#0000FF", // 蓝色边框
 | 
						||
              width: 2
 | 
						||
            })
 | 
						||
          })
 | 
						||
        );
 | 
						||
 | 
						||
        // 添加多边形到源
 | 
						||
        source.addFeature(feature);
 | 
						||
 | 
						||
        // 添加中心点
 | 
						||
        const centerFeature = new Feature({
 | 
						||
          geometry: new Point(fromLonLat([item.lon, item.lat]))
 | 
						||
        });
 | 
						||
 | 
						||
        // 设置中心点样式:红色圆点
 | 
						||
        centerFeature.setStyle(
 | 
						||
          new Style({
 | 
						||
            image: new Circle({
 | 
						||
              radius: 6,
 | 
						||
              fill: new Fill({
 | 
						||
                color: "#FF0000" // 红色填充
 | 
						||
              }),
 | 
						||
              stroke: new Stroke({
 | 
						||
                color: "#FFFFFF", // 白色边框
 | 
						||
                width: 1
 | 
						||
              })
 | 
						||
            })
 | 
						||
          })
 | 
						||
        );
 | 
						||
 | 
						||
        // 添加中心点到源
 | 
						||
        source.addFeature(centerFeature);
 | 
						||
      });
 | 
						||
 | 
						||
      // 创建矢量图层
 | 
						||
      this.vectorLayer = new VectorLayer({
 | 
						||
        source: source,
 | 
						||
        zIndex: 10 // 设置图层优先级
 | 
						||
      });
 | 
						||
 | 
						||
      // 添加到父组件的地图
 | 
						||
      window.olMap.addLayer(this.vectorLayer);
 | 
						||
      // 跳转到 GeoJSON 数据区域
 | 
						||
      if (source.getFeatures().length > 0) {
 | 
						||
        const extent = source.getExtent(); // 获取所有特征的边界框
 | 
						||
        window.olMap.getView().fit(extent, {
 | 
						||
          padding: [50, 50, 50, 50], // 四周留白(像素)
 | 
						||
          maxZoom: 15, // 最大缩放级别
 | 
						||
          duration: 500 // 动画持续时间(毫秒)
 | 
						||
        });
 | 
						||
      }
 | 
						||
    },
 | 
						||
    // 清空图层
 | 
						||
    clearLayer() {
 | 
						||
      if (this.vectorLayer) {
 | 
						||
        window.olMap.removeLayer(this.vectorLayer);
 | 
						||
        this.vectorLayer = null;
 | 
						||
      }
 | 
						||
    },
 | 
						||
    refreshClick() {
 | 
						||
      // 刷新页面
 | 
						||
      window.location.reload();
 | 
						||
    },
 | 
						||
    positionClick() {
 | 
						||
      console.log("位置点击");
 | 
						||
      this.$store.commit("SET_POSITIONPOINT", true);
 | 
						||
    },
 | 
						||
    modelClick() {
 | 
						||
      // 切换底图
 | 
						||
      this.toggleMap = !this.toggleMap;
 | 
						||
      window.olMap.getLayers().forEach((layer) => {
 | 
						||
        if (layer.get("title") === "白色底图组") {
 | 
						||
          layer.setVisible(this.toggleMap);
 | 
						||
        } else if (layer.get("title") === "暗色底图组") {
 | 
						||
          layer.setVisible(!this.toggleMap);
 | 
						||
        }
 | 
						||
      });
 | 
						||
    },
 | 
						||
    soundAndMenu() {
 | 
						||
      this.isZoomedIn = !this.isZoomedIn;
 | 
						||
      this.$store.commit("SET_ISZOOMEDIN", this.isZoomedIn);
 | 
						||
    },
 | 
						||
    initMap() {
 | 
						||
      // 初始化 OpenLayers 地图
 | 
						||
      this.map = new Map({
 | 
						||
        target: "map", // 替换为 this.$refs.olMap 如果在 Vue 中
 | 
						||
        layers: [
 | 
						||
          // 白色底图 + 默认注记
 | 
						||
          new LayerGroup({
 | 
						||
            title: "白色底图组",
 | 
						||
            layers: [
 | 
						||
              this.createTiandituLayer("vec", "天地图矢量底图"), // 白色底图
 | 
						||
              this.createTiandituLayer("cia", "天地图默认注记") // 默认注记
 | 
						||
            ],
 | 
						||
            visible: true // 默认显示白色底图
 | 
						||
          }),
 | 
						||
          // 暗色底图 + 白色注记
 | 
						||
          new LayerGroup({
 | 
						||
            title: "暗色底图组",
 | 
						||
            layers: [
 | 
						||
              this.createTiandituLayer(
 | 
						||
                "vec",
 | 
						||
                "天地图暗色底图",
 | 
						||
                "grayscale(98%) invert(100%) sepia(20%) hue-rotate(180deg) saturate(1600%) brightness(80%) contrast(90%)"
 | 
						||
              ), // 暗色底图
 | 
						||
              this.createTiandituLayer(
 | 
						||
                "cia",
 | 
						||
                "天地图白色注记",
 | 
						||
                "grayscale(100%) brightness(150%) contrast(120%)"
 | 
						||
              ) // 白色注记
 | 
						||
            ],
 | 
						||
            visible: false // 默认隐藏暗色底图
 | 
						||
          })
 | 
						||
        ],
 | 
						||
        view: new View({
 | 
						||
          projection: "EPSG:3857",
 | 
						||
          center: fromLonLat([117.337103, 39.040924]), // 经纬度转投影坐标
 | 
						||
          zoom: 10,
 | 
						||
          minZoom: 0,
 | 
						||
          maxZoom: 18,
 | 
						||
          constrainRotation: false
 | 
						||
        })
 | 
						||
      });
 | 
						||
 | 
						||
      // 保存地图实例到全局(与 Mars3D 的 window.marsMap 类似)
 | 
						||
      window.olMap = this.map;
 | 
						||
 | 
						||
      // 处理地图加载完成后的逻辑
 | 
						||
      this.onMapLoad(this.map);
 | 
						||
 | 
						||
      // 绑定点击事件
 | 
						||
      this.map.on("click", this.mapClickHandler);
 | 
						||
 | 
						||
      // 绑定缩放/移动结束事件
 | 
						||
      this.map.getView().on("change:resolution", this.mapZoomHandler);
 | 
						||
      this.map.getView().on("change:center", this.mapZoomHandler);
 | 
						||
    },
 | 
						||
    mapClickHandler(event) {
 | 
						||
      // 处理地图点击事件
 | 
						||
      console.log("地图点击:", event);
 | 
						||
    },
 | 
						||
    mapZoomHandler() {
 | 
						||
      // 获取当前缩放等级
 | 
						||
      this.zoomLevel = Math.round(this.map.getView().getZoom());
 | 
						||
    },
 | 
						||
    createTiandituLayer(layerType, title, filter = null) {
 | 
						||
      let tiandituKey = "a78289a00ac2e57e0e7abe1d8560d644";
 | 
						||
      return new TileLayer({
 | 
						||
        visible: true,
 | 
						||
        name: title,
 | 
						||
        source: new XYZ({
 | 
						||
          url: `http://t{0-7}.tianditu.gov.cn/${layerType}_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=${layerType}&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tiandituKey}`,
 | 
						||
          tileLoadFunction: filter
 | 
						||
            ? (imageTile, src) => {
 | 
						||
                const img = new Image();
 | 
						||
                img.setAttribute("crossOrigin", "anonymous");
 | 
						||
                img.onload = function () {
 | 
						||
                  const canvas = document.createElement("canvas");
 | 
						||
                  const w = img.width;
 | 
						||
                  const h = img.height;
 | 
						||
                  canvas.width = w;
 | 
						||
                  canvas.height = h;
 | 
						||
                  const context = canvas.getContext("2d");
 | 
						||
                  context.filter = filter;
 | 
						||
                  context.drawImage(img, 0, 0, w, h, 0, 0, w, h);
 | 
						||
                  imageTile.getImage().src = canvas.toDataURL("image/png");
 | 
						||
                };
 | 
						||
                img.src = src;
 | 
						||
              }
 | 
						||
            : undefined
 | 
						||
        }),
 | 
						||
        title: title
 | 
						||
      });
 | 
						||
    },
 | 
						||
    onMapLoad(map) {
 | 
						||
      setTimeout(() => {
 | 
						||
        this.initPositon();
 | 
						||
      }, 2000);
 | 
						||
      // 地图加载完成后的处理
 | 
						||
      const camera = window.mapConfig || {};
 | 
						||
      if (camera.isCamera) {
 | 
						||
        // 设置视角约束
 | 
						||
        map
 | 
						||
          .getView()
 | 
						||
          .setCenter(
 | 
						||
            fromLonLat([camera.cameraLon || 0, camera.cameraLat || 0])
 | 
						||
          );
 | 
						||
        map.getView().setMinZoom(3); // 最小缩放等级
 | 
						||
        map.getView().setMaxZoom(18); // 最大缩放等级
 | 
						||
      }
 | 
						||
      // 监听缩放级别变化
 | 
						||
      const view = map.getView();
 | 
						||
      this.zoomControl = view.getZoom();
 | 
						||
      view.on("change:resolution", () => {
 | 
						||
        this.zoomControl = Math.round(view.getZoom());
 | 
						||
        console.log(this.zoomControl, "缩放级别已更改");
 | 
						||
      });
 | 
						||
    },
 | 
						||
    handleClose() {
 | 
						||
      this.dialogVisible = false;
 | 
						||
      this.dialogQueryVisible = false;
 | 
						||
    },
 | 
						||
    userOpen() {
 | 
						||
      this.$confirm("是否退出登录?", "提示", {
 | 
						||
        confirmButtonText: "确定",
 | 
						||
        cancelButtonText: "取消",
 | 
						||
        type: "warning"
 | 
						||
      })
 | 
						||
        .then(() => {
 | 
						||
          this.$router.push({ name: "login" });
 | 
						||
          localStorage.removeItem("setToken");
 | 
						||
          localStorage.removeItem("expires");
 | 
						||
          localStorage.removeItem("userId");
 | 
						||
          localStorage.removeItem("isAdmin");
 | 
						||
          localStorage.removeItem("PositionIds");
 | 
						||
        })
 | 
						||
        .catch(() => {
 | 
						||
          this.$message({
 | 
						||
            type: "info",
 | 
						||
            message: "已取消退出登录"
 | 
						||
          });
 | 
						||
        });
 | 
						||
    },
 | 
						||
    setupOpen() {
 | 
						||
      this.dialogVisible = true;
 | 
						||
      const media = this.$refs.uavAudio;
 | 
						||
      const savedVolume = localStorage.getItem("soundValue");
 | 
						||
      if (savedVolume !== null) {
 | 
						||
        this.value2 = Number(savedVolume);
 | 
						||
      } else {
 | 
						||
        this.value2 = media.volume * 100;
 | 
						||
      }
 | 
						||
      localStorage.setItem("soundValue", this.value2);
 | 
						||
    },
 | 
						||
    determine() {
 | 
						||
      // 确定时保存音量到 localStorage
 | 
						||
      localStorage.setItem("soundValue", this.value2);
 | 
						||
      this.$store.commit("SET_SILDERVALUE", this.value2);
 | 
						||
      this.dialogVisible = false; // 可选:关闭弹窗
 | 
						||
    },
 | 
						||
    queryOpen() {
 | 
						||
      this.dialogQueryVisible = true;
 | 
						||
    },
 | 
						||
    headdenForm(value, type) {
 | 
						||
      let params = {};
 | 
						||
      if (type === "search") {
 | 
						||
        params = JSON.parse(JSON.stringify(this.$refs.myForm.ruleForm));
 | 
						||
        if (params.dateRange && params.dateRange.length === 2) {
 | 
						||
          params.strartDate = moment(params.dateRange[0]).format("YYYY-MM-DD");
 | 
						||
          params.endDate = moment(params.dateRange[1]).format("YYYY-MM-DD");
 | 
						||
        }
 | 
						||
        this.$delete(params, "dateRange");
 | 
						||
        params.pageNum = this.paginationParam.currentPage;
 | 
						||
        params.pageSize = this.paginationParam.size;
 | 
						||
        alarmList(params).then((res) => {
 | 
						||
          if (res.code === 0) {
 | 
						||
            this.tableData = res.data.items.map((item) => {
 | 
						||
              const { isWhitelist, duration, frequency, ...rest } = item;
 | 
						||
              return {
 | 
						||
                ...rest,
 | 
						||
                isattacked: item.isattacked ? "是" : "否",
 | 
						||
                duration: String(item.duration),
 | 
						||
                frequency: String(item.frequency)
 | 
						||
              };
 | 
						||
            });
 | 
						||
            this.paginationParam.total = res.data.total;
 | 
						||
          }
 | 
						||
        });
 | 
						||
      }
 | 
						||
    },
 | 
						||
    handleSizeChange(value) {
 | 
						||
      console.log(value);
 | 
						||
      this.paginationParam.size = value;
 | 
						||
      this.headdenForm({}, "search");
 | 
						||
    },
 | 
						||
    handlePageChange(value) {
 | 
						||
      console.log(value);
 | 
						||
      this.paginationParam.currentPage = value;
 | 
						||
      this.headdenForm({}, "search");
 | 
						||
    }
 | 
						||
  }
 | 
						||
};
 | 
						||
</script>
 | 
						||
 | 
						||
<style scoped lang="scss">
 | 
						||
.HomeMap {
 | 
						||
  width: 100%;
 | 
						||
  height: 100%;
 | 
						||
  position: relative;
 | 
						||
 | 
						||
  .zoom-level {
 | 
						||
    position: absolute;
 | 
						||
    top: 60px;
 | 
						||
    right: 40px;
 | 
						||
    z-index: 1000;
 | 
						||
    color: #fff;
 | 
						||
    img {
 | 
						||
      width: 48px;
 | 
						||
      height: 48px;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  .zoom-control {
 | 
						||
    position: absolute;
 | 
						||
    left: 60px;
 | 
						||
    bottom: 30px;
 | 
						||
    z-index: 1000;
 | 
						||
    color: #fff;
 | 
						||
    img {
 | 
						||
      width: 48px;
 | 
						||
      height: 48px;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  .btn-container {
 | 
						||
    position: absolute;
 | 
						||
    top: 70px;
 | 
						||
    right: 100px;
 | 
						||
    z-index: 1000;
 | 
						||
    color: #fff;
 | 
						||
  }
 | 
						||
  .configuration {
 | 
						||
    position: absolute;
 | 
						||
    bottom: 32px;
 | 
						||
    right: 40px;
 | 
						||
    z-index: 1000;
 | 
						||
    color: #fff;
 | 
						||
    width: 50px;
 | 
						||
    height: 200px;
 | 
						||
    background-color: rgba(209, 242, 255, 0.9);
 | 
						||
    display: flex;
 | 
						||
    flex-wrap: wrap;
 | 
						||
    align-items: center;
 | 
						||
    justify-content: space-between;
 | 
						||
    .refresh {
 | 
						||
      width: 100%;
 | 
						||
      height: 30%;
 | 
						||
      line-height: 60px;
 | 
						||
      img {
 | 
						||
        width: 32px;
 | 
						||
        height: 32px;
 | 
						||
        margin-top: 30%;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  .map-container {
 | 
						||
    width: 100%;
 | 
						||
    height: 100%;
 | 
						||
  }
 | 
						||
 | 
						||
  .pointingNorth {
 | 
						||
    width: 236px;
 | 
						||
    height: 52px;
 | 
						||
    position: absolute;
 | 
						||
    bottom: 37px;
 | 
						||
    left: 50%;
 | 
						||
    transform: translateX(-50%);
 | 
						||
    z-index: 1000;
 | 
						||
    display: flex;
 | 
						||
    align-items: center;
 | 
						||
    justify-content: space-between;
 | 
						||
    img {
 | 
						||
      width: 52px;
 | 
						||
      height: 52px;
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
</style>
 |