Bootstrap

vue实现平面图标记点位可拖拽可删除(可自定义标记图标)

需求:

1.不限制背景图的大小做overflower,标记点位获取相对位置xy,可拖动修改标点位置,可删除标点
2.关联设备后做自定义标记图标

图例:

在这里插入图片描述

代码:

1.html

<el-dialog title="设置设备" :visible.sync="imgMark" append-to-body id="plan">
  <div class="plan-img">
    <img class="bg" :src="bgSrc" @click.stop="setPoint" />
    <div v-for="(item, index) in markerArr" 
         :key="index" 
         class="block" 
         :style="{ left: item.x, top: item.y, transform: 'translate(-50%, -100%)' }"
         @drag="dragenter($event, item)"
         draggable="true">
        <img :src="markSrc" class="mark-icon"  @click.stop="editPoit(index,item)" />
        <div class="button-group" v-if="item.showModal">
          <span @click="associative(index)">关联设备</span>
          <span @click="deletePoit(index)">删除点位</span>
        </div>
     </div>
   </div>
   <div slot="footer" class="dialog-footer">
     <el-button type="primary" @click="submitMark" class="button-minwidth">确定</el-button>
     <el-button @click="imgMark = false" class="button-minwidth">取消</el-button>
   </div>
</el-dialog>

2.js

<script>
import bgSrc from '@/assets/images/bg.png'
import markSrc from '@/assets/images/img-mark.png'

export default {
  data() {
    return {
      //平面图标记点位
      imgMark:false,
      bgSrc,
      markSrc,
      markerArr:[],
    };
  },
  created() {
    window.addEventListener("resize", this.getImgRect)
  },
  beforeDestroy() {
    window.removeEventListener("resize", this.getImgRect)
  },
  methods: {
    //设置设备
    handleSetDev(row){
      this.imgMark = true
      this.getImgRect()
    },
    //绘制标记
    setPoint(e){
      e = e || window.event;
      let x = e.offsetX || e.layerX;
      let y = e.offsetY || e.layerY;
      let obj = {x: x + 'px',y: y + 'px',showModal:false}
      this.markerArr.push(obj);
    },
    //获取相对位置
    getImgRect() {
      setTimeout(() => {
        // 元素点是相对 背景图定位的    拖拽点获得的做标是相对屏幕的距离 
        this.imgRect = document.querySelector(".plan-img .bg").getBoundingClientRect()
      }, 30);
    },
    //移动位置
    dragenter(e, item) {
      const {x, y} = this.imgRect
      if(e.clientX !==0 && e.clientY !==0) {
        item.x = `${e.clientX - x}px`
        item.y = `${e.clientY - y}px`
      }   
    },
    //编辑标记
    editPoit(index,item){
      if(item.showModal==false){
        item.showModal=true
      }else {
        item.showModal=false
      }
    },
    //关联设备
    associative(index){
    },
    //提交关联设备
    submitDev(){
      this.spaceDev=false
    },
    //删除标记
    deletePoit(index){
      this.markerArr.splice(index, 1);
    },
    //提交标记
    submitMark(){
      this.imgMark=false
    },
  }
};
</script>

3.css

#plan{
  ::v-deep .el-dialog{
    height: 65%;
  }
  ::v-deep .el-dialog__body{
    overflow: hidden !important;
  }
  .plan-img{
    width: 100%;
    height: 100%;
    overflow: auto;
    margin-bottom: 20px;
    position: relative;
    .bg{
      position: relative;  
      top:0px;  
      left:0px;
    }
    .block{
      cursor: pointer;
      z-index: 29;
      position:absolute;
      display: flex;
      flex-direction: row;
      width: 24px;
      height: 30px;
      .mark-icon{
        position: absolute;
        top: 0;
        left: 0;
        width: 24px;
        height: 30px;
        z-index: 30;
      }
      .button-group{
        position: absolute;
        left: 34px;
        top: 0px;
        display: flex;
        flex-direction: column;
        background: #ffffff;
        z-index: 40;
        span{
          padding: 10px 44px 10px 20px;
          white-space: nowrap;
        }
        span:first-child{
          border-bottom: 1px solid #DCDFE6;
        }
      }
    }
  }
}

上图用到的图片放在下面了

标点图:在这里插入图片描述
背景图:

在这里插入图片描述

切换自定义图标:

1.图例
在这里插入图片描述

这里我就不写具体功能实现js代码了,实现的思路是:
1.打开弹窗时必须要把markerArr置空并调用后台获取标点详情接口获取markerArr数据,下面组合数据可以参考
2.在标点选择关联的数据提交后,改变当前对象isDev属性为true切换图标样式并展示当前对象icon,下面HTML和CSS代码可以对比上文参考
3.如果当前对象的isDev已经为true,也可以切换另一条数据绑定Id进行重新绑定存入markerArr,这一步需要后台提供筛选数据的接口为基础,最后按后台保存接口参数要求筛选组合markerArr属性数据提交保存

2.代码

(1)组合数据

let arr=res.data
  if(arr.length>0){
    arr.forEach(item=>{
      let obj={
        deptId:item.deptId,
        xcoordinate:item.xcoordinate?item.xcoordinate+'px':'',
        ycoordinate:item.ycoordinate?item.ycoordinate+'px':'',
        showModal:false,
        isDev:true,
        Id:item.Id,
        devName:item.devName,
        icon:item.icon
      }
      this.markerArr.push(obj)
    })
}

(2)html

<el-dialog title="设置设备" :visible.sync="imgMark" append-to-body id="plan">
  <div class="plan-img">
    <img class="bg" :src="bgSrc" @click.stop="setPoint" />
      <div v-for="(item, index) in markerArr" 
           :key="index" 
           :class="item.isDev==true?'blocks':'block'" 
           :style="{ left: item.xcoordinate, top: item.ycoordinate, transform: 'translate(-50%, -100%)' }"
           @drag="dragenter($event, item)"
           draggable="true">
        <img v-if="item.isDev==false" :src="markSrc" class="mark-icon"  @click.stop="editPoit(index,item)" /> -->
        <div v-if="item.isDev==true" class="marker-outer" @click.stop="editPoit(index,item)">
          <div class="marker-inner">
            <img :src="item.icon"/>
          </div>
        </div>
        <div class="button-group" v-if="item.showModal">
          <span @click="associative(index)">关联设备</span>
          <span @click="deletePoit(index)">删除点位</span>
        </div>
      </div>
    </div>
    <div slot="footer" class="dialog-footer">
      <el-button type="primary" @click="submitMark" class="button-minwidth">{{$t('Base.confirms')}}</el-button>
      <el-button @click="imgMark = false" class="button-minwidth">{{$t('Base.cancels')}}</el-button>
    </div>
  </el-dialog>

(3)css

.blocks{
  cursor: pointer;
  z-index: 29;
  position:absolute;
  display: flex;
  flex-direction: row;
  width: 60px;
  height: 80px;
  .marker-outer{
    position: absolute;
    top: 0;
    left: 0;
    z-index: 30;
    display: inline-block;
    width: 66px;
    height: 66px;
    border-radius: 50%;
    background: #ffffff;
    text-align: center;
    box-shadow: 0px 0px 2px 3px #0095FF;
    .marker-inner{
      display: inline-block;
      width: 43px;
      height: 43px;
      border-radius: 50%;
      background: #fff;
      color: #111;
      position: absolute;
      top: 10px;
      left: 10px;
      z-index: 31;
      img{
        width: 100%;
        height: 100%;
      }
   }
 }

最近产品提了个做边界限制的需求,所以需要根据图标显示对按钮编辑框的显示位置进行动态变更

<div class="button-group" 
     v-if="item.showModal"
     :class="[
       toLeftBottom?'button-lb':'',
       toLeftTop?'button-lt':'',
       toRight?'buttom-r':'',
       toRightBottom?'buttom-rb':'',
       toRightTop?'buttom-rt':''
       ]" >
  <span @click="associative(index)">关联设备</span>
  <span @click="deletePoit(index)">删除点位</span>
</div>
data(){
  return{
    toLeftTop:false,  //左上
    toLeftBottom:false,  //左下
    toRight:false,
    toRightTop:false,  //右上
    toRightBottom:false,  //右下
  }
},
methods:{
  //编辑标记
    editPoit(index,item){
      this.markerArr.forEach(res=>{
        if(res!==item){
          res.showModal=false
        }else{
          if(item.showModal==false){
            item.showModal=true
          }else {
            item.showModal=false
          }
        }
      })
      let x=parseInt(item.xcoordinate)
      let y=parseInt(item.ycoordinate)
      let width=parseInt(this.imgWidth)
      let height=parseInt(this.imgHeight)
      
      this.toRight=false
      this.toLeftBottom=false
      this.toRightTop=false
      this.toLeftTop=false
      this.toRightBottom=false

      if(x>=width-100){
        this.toRight=true
        if(y<=50){
          this.toRightBottom=true
        }else if(y>=height-50){
          this.toRightTop=true
        }
      }else if(x<=20){
        if(y<=20){
          this.toLeftBottom=true
        }else if(y>=height-50){
          this.toLeftTop=true
        }
      }
    },
}
.button-lb{top: 80px;}
.button-lt{top:5px;}
.buttom-r{left: -130px;}
.buttom-rb{top: 80px;}
.buttom-rt{top: -40px;}

.button-lb{top: 30px;}
.button-lt{top: -40px;}
.buttom-r{left: -130px;}
.buttom-rb{top: 30px;}
.buttom-rt{top: -40px;}
;