ZZJ
2021-12-03 1706494087a9f8fd22f691d279e9ef7756cd316d
src/pages/internetEquipment/module/mapIndex.vue
@@ -1,19 +1,102 @@
<template>
  <div id="map-index">
    <div class="control">
      <a href="#" title="定位" @click="aClick()">
      <div class="location icon iconfont" @click="location">&#xe74e;</div>
      </a>
      <a href="#" title="放大" @click="aClick()">
      <div class="zoom-in icon iconfont" @click="zoomIn">&#xeb89;</div>
      </a>
      <a href="#" title="缩小" @click="aClick()">
      <div class="zoom-out icon iconfont" @click="zoomOut">&#xe758;</div>
      </a>
    </div>
    <a href="#" title="新增/编辑" @click="aClick()">
    <div class="range icon iconfont" @click="drawPolygon">&#xe773;</div>
    </a>
    <el-input v-model="nodeId" placeholder="请输入内容"></el-input>
    <div class="save" v-if="showBtn" @click="savePoly">保存</div>
    <div class="del" v-if="polygonInfo" @click="delPoly">删除</div>
    <div class="cancel" v-if="showBtn" @click="resetMap">取消</div>
    <span id="polygonInfo" v-show="polygonInfo">{{ polygonInfo }}</span>
    <img
      v-if="myNode && !isHidden"
      src="/images/map/Group 594.png"
      alt=""
      class="video_icon"
      @click="hiddenVideo()"
    />
    <a v-else href="#" title="实时监控" @click="aClick()">
      <img
        src="/images/map/监控.png"
        alt=""
        class="video_icon"
        @click="showVideo()"
      />
    </a>
    <div class="video_box" v-if="myNode" :class="{ hidden: isHidden }">
      <div class="title">
        <div class="left">
          <span class="ball"></span>
          <span class="left_info">实时监控</span>
        </div>
        <div class="right">设备编码: {{ myNode.values_.id }}</div>
      </div>
      <video controls="controls" loop="loop">
        <source src="/images/map/video_20210924_101628.mp4" type="video/mp4" />
      </video>
    </div>
    <div class="helmet_box" v-show="myNode">
      <div class="icon_close iconfont" @click="close">&#xe729;</div>
      <div class="sn" v-if="myNode">{{ myNode.values_.lat.id }}</div>
      <div class="location" v-if="myNode">经度: {{ myNode.values_.lat }}</div>
      <div class="location" v-if="myNode">纬度: {{ myNode.values_.lng }}</div>
      <div class="info" v-if="myNode">电量: {{ myNode.values_.battery }}</div>
      <div class="button">
        <img
          src="/images/InternetData/视频.png"
          alt=""
          v-if="!isSending"
          @click="sendVoice()"
        />
        <img src="/images/InternetData/视频_点击.png" alt="" v-if="isSending" />
        <img
          src="/images/InternetData/语音.png"
          alt=""
          v-if="isTele == false"
          @click="isTele = true"
        />
        <img
          src="/images/InternetData/语音_点击.png"
          alt=""
          v-if="isTele == true"
          @click="isTele = false"
        />
      </div>
    </div>
    <telephoneBox :tele="myNode" v-if="isTele" @close="isTele = false" />
    <div class="mask" v-if="isTele"></div>
  </div>
</template>
<script>
import { getHelemtData, getZones, createZones } from "@/api/helemt";
import {
  getHelemtData,
  getZones,
  createZones,
  delZones,
  sendAudio,
} from "@/api/helemt";
import telephoneBox from "../components/telephoneBox.vue";
import "ol/ol.css";
import Feature from "ol/Feature";
@@ -28,6 +111,9 @@
import Draw from "ol/interaction/Draw";
import { Modify, Snap } from "ol/interaction";
import Polygon from "ol/geom/Polygon";
import Select from "ol/interaction/Select";
import Overlay from "ol/Overlay";
import { pointerMove } from "ol/events/condition";
let myMap = {};
let myVectorSource = {};
@@ -41,24 +127,20 @@
});
let myDraw = {};
let myModify = {};
let myPolygon = {};
let mySelect = {};
let overlay = {};
let overlay2 = {};
let voiceText = "";
export default {
  created() {
    this.getNodeData();
  },
  data() {
    return {
      isSending: false,
      isTele: false,
      isHidden: false,
      showBtn: false,
      nodeArr: [
        { data: [116.06157, 39.66157], id: 1, color: "绿" },
        { data: [116.06247, 39.66247], id: 2, color: "绿" },
        { data: [116.06337, 39.66337], id: 3, color: "绿" },
        { data: [116.06467, 39.66437], id: 4, color: "绿" },
        { data: [116.06517, 39.66517], id: 5, color: "红" },
        { data: [116.06627, 39.66627], id: 6, color: "红" },
        { data: [116.06787, 39.66787], id: 7, color: "红" },
        { data: [116.06897, 39.66897], id: 8, color: "红" },
      ],
      nodeArr: [],
      nodeId: "",
      iconArr: [],
      rangeArr: [],
@@ -68,14 +150,21 @@
      polyFeature: [],
      drawStore: [],
      modifyStore: [],
      polygonInfo: "",
      nodeFeature: [],
      myNode: null,
      commentContent: "",
    };
  },
  mounted() {
    this.initMap();
  },
  components: {
    telephoneBox,
  },
  methods: {
    async getNodeData() {},
    async initMap() {
      const that = this;
      // 获取节点
      const res = await getHelemtData();
      this.nodeArr = [];
@@ -84,6 +173,7 @@
          data: [item.lng, item.lat],
          id: item.device_sn,
          color: item.is_out_bound == 0 ? "绿" : "红",
          battery: item.battery,
        });
      });
@@ -97,7 +187,6 @@
          return obj;
        });
      }
      console.log(this.polygonArr);
      // 设置地图中心
      this.center = this.nodeArr[0].data;
@@ -131,6 +220,114 @@
        console.log(item);
      })
    }) */
      window.addEventListener("resize", function () {
        map.updateSize();
      });
      const select = new Select({
        filter: (feature, layer) => {
          if (feature.values_ && feature.values_.type == "node") {
            this.myNode = feature;
            return true;
          } else if (feature.type && feature.type == "polygon") {
            myPolygon = feature;
            this.myNode = null;
            return true;
          } else {
            this.myNode = null;
            return false;
          }
        },
        style: (feature) => {
          if (feature.values_ && feature.values_.type == "node") {
            return new Style({
              image: new Icon({
                size: [32, 32],
                src: `/images/map/安全帽-${feature.values_.color}.png`,
              }),
              zIndex: 1,
            });
          } else if (feature.type && feature.type == "polygon") {
            return new Style({
              fill: new Fill({
                color: "rgba(0, 0, 255, 0.1)",
              }),
              stroke: new Stroke({
                color: "skyblue",
                width: 3,
              }),
            });
          }
        },
      });
      const select2 = new Select({
        condition: pointerMove,
        filter: (feature, layer) => {
          if (feature.values_ && feature.values_.type == "node") {
            return true;
          } else {
            return false;
          }
        },
        style: (feature) => {
          if (feature.values_ && feature.values_.type == "node") {
            return new Style({
              image: new Icon({
                size: [32, 32],
                src: `/images/map/安全帽-${feature.values_.color}.png`,
              }),
              zIndex: 1,
            });
          }
        },
      });
      const info = document.querySelector("#polygonInfo");
      map.addInteraction(select);
      map.addInteraction(select2);
      mySelect = select;
      select.on("select", function (e) {
        e.stopPropagation();
        overlay2.setPosition(undefined);
        if (e.selected.length == 0) {
          that.polygonInfo = "";
          that.nodeId = "";
          that.myNode = null;
          return false;
        }
        if (e.selected[0].values_.type == "node") {
          that.isHidden = false;
          that.polygonInfo = "";
          that.nodeId = e.selected[0].values_.id;
          return false;
        }
        if (myPolygon.polygonName) {
          that.polygonInfo = myPolygon.polygonName;
        } else {
          that.polygonInfo = "新区域";
        }
        overlay.setPosition(e.mapBrowserEvent.coordinate);
        return false;
      });
      select2.on("select", function (e) {
        if (
          e.selected.length &&
          e.selected[0].values_.type == "node" &&
          that.myNode &&
          that.myNode.values_.id == e.selected[0].values_.id
        ) {
          overlay2.setPosition(e.mapBrowserEvent.coordinate);
        } else {
        }
      });
    },
    initNode([x, y], color) {
      const iconFeature = new Feature({
@@ -147,12 +344,19 @@
      iconFeature.setStyle(iconStyle);
      this.iconArr.push(iconFeature);
      iconFeature.set("color", `${color}`);
      iconFeature.set("lat", `${x}`);
      iconFeature.set("lng", `${y}`);
      return iconFeature;
    },
    initAllNode() {
      this.nodeArr.forEach((item) => {
        const node = this.initNode(item.data, item.color);
        node.set("id", item.id);
        node.set("type", "node");
        node.set("battery", item.battery);
        this.nodeFeature.push(node);
        myVectorSource.addFeature(node);
      });
    },
@@ -161,6 +365,8 @@
      this.polygonArr.forEach((item) => {
        const feature = new Feature({ geometry: new Polygon([item.data]) });
        feature.id = item.id;
        feature.type = "polygon";
        feature.polygonName = item.name;
        this.polyFeature.push(feature);
      });
    },
@@ -179,6 +385,19 @@
      });
    },
    initBottomMap(vectorLayer) {
      const info = document.querySelector("#polygonInfo");
      const helmet_box = document.querySelector(".helmet_box");
      overlay2 = new Overlay({
        element: helmet_box,
        autoPan: true,
        autoPanAnimation: {
          duration: 250,
        },
      });
      overlay = new Overlay({
        element: info,
      });
      return new Map({
        target: "map-index",
        layers: [baseLayer, vectorLayer],
@@ -186,6 +405,7 @@
          center: transform(this.center, "EPSG:4326", "EPSG:3857"),
          zoom: this.zoom,
        }),
        overlays: [overlay, overlay2],
      });
    },
    zoomIn() {
@@ -204,6 +424,8 @@
      view.setCenter(transform(this.center, "EPSG:4326", "EPSG:3857"));
    },
    drawPolygon() {
      this.resetMap();
      mySelect.setActive(false);
      this.showBtn = true;
      const draw = new Draw({
        source: myVectorSource,
@@ -232,6 +454,7 @@
        }
        event.feature.id = id;
        event.feature.type = "polygon";
        this.drawStore.push({
          id,
          data: event.feature.getGeometry().getCoordinates()[0],
@@ -240,7 +463,6 @@
      const modify = new Modify({ source: myVectorSource });
      modify.addEventListener("modifyend", (event) => {
        console.log(event.features);
        const id = event.features.array_[0].id;
        const data = event.features.array_[0].getGeometry().getCoordinates()[0];
        this.modifyStore.push({ id, data });
@@ -250,14 +472,14 @@
      myMap.addInteraction(draw);
    },
    resetMap() {
      this.polygonInfo = "";
      mySelect.setActive(true);
      this.initPolygonArr();
      console.log(this.polyFeature);
      const vectorSource = new VectorSource({
        features: this.polyFeature,
      });
      /* if(this.polygonArr.length>0){
      this.initPolygonArr()
      console.log(this.polyFeature);
      vectorSource.addFeature(this.polyFeature[0])
      } */
      myVectorSource = vectorSource;
@@ -272,6 +494,7 @@
      this.modifyStore = [];
    },
    async savePoly() {
      mySelect.setActive(true);
      myMap.removeInteraction(myDraw);
      myMap.removeInteraction(myModify);
      this.showBtn = false;
@@ -291,7 +514,6 @@
      }
      const arrData = this.polygonArr.map((item) => {
        let name = item.name ? item.name : "";
        console.log(item);
        let data = item.data.map((arr) => {
          arr = transform([arr[0], arr[1]], "EPSG:3857", "EPSG:4326");
          return arr.join(",");
@@ -303,8 +525,181 @@
          id: +item.id,
        };
      });
      const res = await createZones({ dots_arr: arrData });
      console.log(res);
      await createZones({ dots_arr: arrData });
      const res = await getHelemtData();
      this.nodeArr = [];
      res.data.items.forEach((item) => {
        this.nodeArr.push({
          data: [item.lng, item.lat],
          id: item.device_sn,
          color: item.is_out_bound == 0 ? "绿" : "红",
        });
      });
      this.nodeFeature.forEach((item) => {
        myVectorSource.removeFeature(item);
      });
      this.initNode();
      this.initAllNode();
    },
    delPoly() {
      this.$confirm("此操作将删除该区域, 是否继续?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(async () => {
          myVectorSource.removeFeature(myPolygon);
          const res = await delZones({ id: myPolygon.id });
          if (res.status == 200) {
            this.$message({
              type: "success",
              message: "删除成功!",
            });
            this.polygonInfo = "";
            const res2 = await getZones();
            if (res2.data && res2.data.items && res2.data.items.length > 0) {
              this.polygonArr = res2.data.items.map((obj) => {
                obj.data = obj.dots.map((item) => {
                  return transform(
                    [item[0], item[1]],
                    "EPSG:4326",
                    "EPSG:3857"
                  );
                });
                return obj;
              });
            }
          } else {
            this.$message({
              type: "info",
              message: "删除失败",
            });
          }
        })
        .catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除",
          });
        });
    },
    showVideo() {
      if (!this.myNode) {
        return false;
      }
      this.isHidden = false;
    },
    hiddenVideo() {
      document.querySelector("video").pause();
      this.isHidden = true;
    },
    close() {
      overlay2.setPosition(undefined);
    },
    onCommentInputChange() {
      let value = document.querySelector("#commentContent").value;
      voiceText = value;
      let cont = 20 - value.length;
      document.querySelector(
        "#comment_info"
      ).innerHTML = `还可输入${cont}个字符`;
    },
    sendVoice() {
      const _this = this;
      this.isSending = true;
      const h = this.$createElement;
      this.$msgbox({
        message: h(
          "div",
          {
            attrs: {
              class: "el-textarea",
            },
          },
          [
            h(
              "div",
              {
                attrs: {
                  class: "el-title",
                },
              },
              [
                h(
                  "span",
                  {
                    attrs: {
                      class: "icon iconfont",
                    },
                  },
                  "\ue7cc"
                ),
                h("span", null, "发送语音"),
              ]
            ),
            h("textarea", {
              attrs: {
                placeholder: "请输入语音命令",
                maxlength: "20",
                class: "el-textarea__inner",
                autocomplete: "off",
                rows: 3,
                id: "commentContent",
              },
              value: this.commentContent,
              on: { input: this.onCommentInputChange },
            }),
            h(
              "div",
              { attrs: { class: "info", id: "comment_info" } },
              "还可输入20个字符"
            ),
          ]
        ),
        showCancelButton: true,
        confirmButtonText: "确定",
        confirmButtonClass: "hele_btn_save",
        cancelButtonClass: "hele_btn_cancel",
        cancelButtonText: "取消",
        beforeClose: (action, instance, done) => {
          document.querySelector("#commentContent").value = "";
          document.querySelector(
            "#comment_info"
          ).innerHTML = `还可输入20个字符`;
          _this.isSending = false;
          done();
        },
      })
        .then((action) => {
          if (action == "confirm") {
            if (!voiceText) {
              this.$message({
                message: "指令不能为空",
                type: "warning",
              });
              _this.isSending = false;
              return;
            }
            sendAudio(_this.myNode.values_.id, voiceText).then((res) => {
              this.$message({
                message: "指令发送成功",
                type: "success",
              });
              _this.isSending = false;
            });
            _this.isSending = false;
          } else {
            _this.isSending = false;
          }
        })
        .catch(() => {
          _this.isSending = false;
        });
    },
    aClick() {
      return false;
    },
  },
  watch: {
@@ -326,14 +721,12 @@
        if (res.data.items && res.data.items.length > 0) {
          res.data.items.forEach((obj) => {
            this.nodeArr.forEach((item) => {
              if ((item.id = obj.device_sn)) {
              if (item.id == obj.device_sn) {
                arr.push(item);
              }
            });
          });
        }
        console.log(this.rangeArr);
        if (this.rangeArr.length > 0) {
          this.rangeArr.forEach((item) => {
@@ -362,6 +755,7 @@
            rangeFeature.setStyle(iconStyle);
            myVectorSource.addFeature(rangeFeature);
            rangeFeature.set("type", "range");
            this.rangeArr.push(rangeFeature);
          });
        }
@@ -372,12 +766,106 @@
</script>
<style scoped lang="scss">
a {
  background-color: transparent;
  color: #337ab7;
  text-decoration: none;
}
a:active a:hover {
  outline: 0;
}
#map-index {
  position: relative;
  margin: 20px 0;
  width: 1170px;
  width: 100%;
  height: 396px;
  border-radius: 15px;
  .helmet_box {
    width: 205px;
    height: 172px;
    padding: 10px 10px 20px 15px;
    font-size: 12px;
    background: rgba(255, 255, 255, 0.7);
    backdrop-filter: blur(4px);
    border-radius: 15px;
    text-align: left;
    .icon_close {
      text-align: right;
      cursor: pointer;
    }
    .sn {
      color: #f54336;
    }
    .location {
      margin: 10px 0;
    }
    .button {
      margin-top: 10px;
      text-align: center;
      img {
        cursor: pointer;
        width: 22px;
        &:first-child {
          margin-right: 48px;
        }
      }
    }
  }
  .video_box {
    box-sizing: border-box;
    overflow: hidden;
    position: absolute;
    z-index: 2;
    top: 45px;
    right: 25px;
    width: 483px;
    height: 306px;
    background: rgba(241, 250, 246, 0.6);
    backdrop-filter: blur(4px);
    /* Note: backdrop-filter has minimal browser support */
    border-radius: 15px;
    border: 1px solid rgb(17, 170, 102);
    transition: all linear 0.5s;
    &.hidden {
      width: 0;
      border: none;
    }
    .title {
      margin: 15px 40px 0 20px;
      width: 420px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      color: #4f4f4f;
      font-size: 12px;
      .left {
        .ball {
          display: inline-block;
          width: 0;
          height: 0;
          border: 4px solid #f54336;
          border-radius: 2px;
          margin-right: 10px;
        }
      }
    }
    video {
      margin: 8px 40px 0 20px;
      width: 424px;
      height: 246px;
      object-fit: fill;
    }
  }
  .control {
    position: absolute;
    display: flex;
@@ -464,6 +952,84 @@
    line-height: 36px;
    cursor: pointer;
  }
  .del {
    position: absolute;
    z-index: 3;
    top: 15px;
    right: 113px;
    width: 87px;
    height: 35px;
    background: rgb(245, 108, 108);
    border: 1px solid rgb(245, 108, 108);
    border-radius: 8px;
    color: #fff;
    font-size: 12px;
    line-height: 36px;
    cursor: pointer;
  }
  .video_icon {
    position: absolute;
    z-index: 3;
    top: 179px;
    right: 10px;
    width: 37px;
  }
  #polygonInfo {
    padding: 10px;
    border: 1px solid;
    color: black;
    background-color: #fff;
    z-index: 3;
    width: 100px;
  }
  .mask {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    text-align: center;
    z-index: 4;
    background-color: black;
    opacity: 0.5;
  }
}
.el-textarea {
  height: 150px;
  padding: 0 15px;
  .el-title {
    margin-top: 20px;
    margin-bottom: 15px;
    color: #4f4f4f;
    font-size: 16px;
    font-weight: 700;
    .icon {
      font-weight: normal;
      font-size: 19px;
    }
  }
  .el-textarea__inner {
    width: 357px;
    &:focus {
      border: 1px solid #11aa66;
    }
  }
  .info {
    font-size: 12px;
    color: #828282;
    text-align: right;
    padding-right: 38px;
  }
}
</style>