创建JSON地图

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<style>
*{box-sizing: border-box;}
body{margin:0;padding:10px;font-size:14px;}
.top-bar{
  display:flex;
  gap:8px;
  margin-bottom:10px;
  align-items:center;
  flex-wrap:wrap;
}
.top-bar button,.top-bar input{
  padding:6px 10px;
  cursor:pointer;
}
.map-wrap{
  position:relative;
  width:1900px;
  height:900px;
  border:2px solid #666;
  overflow:hidden;
}
#bgImg{
  position:absolute;
  left:0;top:0;
  width:1900px;height:900px;
  object-fit:cover;
  display:block;
}
.heat-layer{
  position:absolute;left:0;top:0;width:100%;height:100%;
  z-index:2;pointer-events:none;display:none;
}
.heat-block{
  position:absolute;
  filter: blur(12px);
  border-radius:6px;
}
.zone-wrap{
  position:absolute;
  border:2px solid #999;
  background:rgba(150,150,150,0.05);
  z-index:3;
}
.zone-title{
  position:absolute;top:-22px;left:0;
  font-weight:bold;white-space:nowrap;
}
.factory-hot{
  position:absolute;
  border:2px dashed #0066ff;
  background:rgba(0,102,255,0.08);
  cursor:pointer;
  transition:all 0.2s;
}
.factory-hot:hover{background:rgba(0,102,255,0.2);}
.summary-tag{
  position:absolute;
  transform:translate(-50%,-50%);
  padding:4px 10px;border-radius:8px;
  color:#fff;font-weight:bold;white-space:nowrap;
  box-shadow:0 2px 8px rgba(0,0,0,0.15);
  cursor:pointer;
}
.sensor-hot{
  position:absolute;
  width:32px;height:32px;
  transform:translate(-50%,-50%);
  border-radius:50%;
  background:rgba(0,0,0,0.1);
  cursor:pointer;
  transition:all 0.2s;
}
.sensor-hot:hover{transform:translate(-50%,-50%) scale(1.2);}
.sensor-dot{
  width:12px;height:12px;border-radius:50%;
  margin:10px;
}
.drag-rect{
  position:absolute;
  border:1px dashed #f00;
  background:rgba(255,0,0,0.1);
  pointer-events:none;
  z-index:999;
}
.edit-modal{
  position:fixed;
  top:50%;left:50%;
  transform:translate(-50%,-50%);
  background:#fff;
  border:1px solid #ccc;
  padding:15px;
  box-shadow:0 0 20px #0003;
  z-index:9999;
  display:none;
}
.edit-modal input{
  width:280px;
  margin:6px 0;
  padding:5px;
  display:block;
}
.detail-popup{
  position:absolute;
  background:#fff;border:1px solid #ddd;
  padding:12px;box-shadow:0 3px 15px rgba(0,0,0,0.2);
  z-index:99;display:none;min-width:220px;
}
.del-btn{
  margin-top:8px;
  color:#fff;
  background:#f44336;
  border:none;
  padding:4px 8px;
  cursor:pointer;
}
</style>
</head>
<body>
<div class="top-bar">
  <input type="file" id="uploadImg" accept="image/*">
  <button id="drawRectFactory">矩形厂房(拖拽)</button>
  <button id="drawSensor">绘制传感器点位</button>
  <button id="stopDraw">停止绘制</button>
  <button id="toggleHeat">开关热力图层</button>
  <button id="toggleSensor">展开/收起点位</button>
  <button id="exportJson">导出配置JSON</button>
  <button id="importJson">导入配置JSON</button>
  <input type="file" id="jsonFile" accept=".json" style="display:none">
</div>

<div class="map-wrap" id="mapWrap">
  <img id="bgImg">
  <div class="heat-layer" id="heatLayer"></div>
  <div class="detail-popup" id="detailPopup"></div>
  <div class="drag-rect" id="dragRect"></div>
</div>

<div class="edit-modal" id="editModal">
  <h4>编辑配置信息</h4>
  <label>图层层级 z(数字越大越上层)</label>
  <input type="number" id="editZ" value="5">
  <label>名称</label>
  <input type="text" id="editName">
  <label>自定义参数doing</label>
  <input type="text" id="editDoing">
  <label>温度(仅传感器生效)</label>
  <input type="number" id="editTemp">
  <button id="saveEdit">保存</button>
  <button id="cancelEdit">取消</button>
</div>

<script>
$(function(){
  const $mapWrap = $("#mapWrap");
  const $bgImg = $("#bgImg");
  const $heatLayer = $("#heatLayer");
  const $popup = $("#detailPopup");
  const $dragRect = $("#dragRect");
  const $editModal = $("#editModal");

  // 全局配置:厂房、传感器完全分开存储
  let parkConfig = {
    zones:[]
  };

  // 绘制模式:rectFactory / sensor / null
  let drawMode = null;
  let isDrag = false;
  let dragStart = {x:0,y:0};
  let editTarget = null;
  // 删除标记:记录当前要删除的分区、下标、类型
  let delInfo = {zoneIdx: -1, type: "", idx: -1};

  let heatShow = false;
  let sensorShow = false;

  // 1.上传底图(仅前端预览,不存入JSON)
  $("#uploadImg").on("change",function(e){
    const file = e.target.files[0];
    if(!file)return;
    const reader = new FileReader();
    reader.onload = function(res){
      $bgImg.attr("src",res.target.result);
      renderAll();
    }
    reader.readAsDataURL(file);
  })

  // 切换绘制模式
  $("#drawRectFactory").click(()=>drawMode="rectFactory");
  $("#drawSensor").click(()=>drawMode="sensor");
  $("#stopDraw").click(()=>drawMode=null);

  // 矩形拖拽绘制厂房
  $mapWrap.mousedown(function(e){
    if(drawMode !== "rectFactory") return;
    const rect = $mapWrap[0].getBoundingClientRect();
    dragStart.x = e.clientX - rect.left;
    dragStart.y = e.clientY - rect.top;
    isDrag = true;
    $dragRect.css({left:dragStart.x,top:dragStart.y,width:0,height:0});
  })
  $mapWrap.mousemove(function(e){
    if(!isDrag || drawMode !== "rectFactory") return;
    const rect = $mapWrap[0].getBoundingClientRect();
    const curX = e.clientX - rect.left;
    const curY = e.clientY - rect.top;
    const w = Math.abs(curX - dragStart.x);
    const h = Math.abs(curY - dragStart.y);
    const l = Math.min(curX, dragStart.x);
    const t = Math.min(curY, dragStart.y);
    $dragRect.css({left:l,top:t,width:w,height:h});
  })
  $mapWrap.mouseup(function(e){
    if(drawMode !== "rectFactory" || !isDrag){
      $popup.hide();
      return;
    }
    isDrag = false;
    const x1 = parseInt($dragRect.css("left"));
    const y1 = parseInt($dragRect.css("top"));
    const x2 = x1 + parseInt($dragRect.css("width"));
    const y2 = y1 + parseInt($dragRect.css("height"));
    $dragRect.css({width:0,height:0});
    if(x2-x1 < 20 || y2-y1 <20)return;
    editTarget = {
      type:"rectFactory",
      data:{x1,y1,x2,y2,z:5,name:"矩形厂房",doing:""}
    };
    showEditModal();
  })

  // 点击画布绘制传感器点位
  $mapWrap.click(function(e){
    if(drawMode === "sensor"){
      const rect = $mapWrap[0].getBoundingClientRect();
      const x = parseInt(e.clientX - rect.left);
      const y = parseInt(e.clientY - rect.top);
      editTarget = {
        type:"sensor",
        data:{x,y,z:8,name:"传感器",doing:"",temp:25}
      };
      showEditModal();
      return;
    }
    if(!drawMode)$popup.hide();
  })

  // 弹出编辑框
  function showEditModal(){
    $("#editZ").val(editTarget.data.z||5);
    $("#editName").val(editTarget.data.name);
    $("#editDoing").val(editTarget.data.doing);
    $("#editTemp").val(editTarget.data.temp||25);
    $editModal.show();
  }
  $("#saveEdit").click(function(){
    const z = Number($("#editZ").val()) || 5;
    const name = $("#editName").val();
    const doing = $("#editDoing").val();
    const temp = parseFloat($("#editTemp").val())||25;
    editTarget.data.z = z;
    editTarget.data.name = name;
    editTarget.data.doing = doing;
    if(editTarget.type==="sensor") editTarget.data.temp = temp;

    // 初始化默认分区
    if(parkConfig.zones.length ===0){
      parkConfig.zones.push({
        zoneName:"默认园区分区",
        zoneX1:0,zoneY1:0,zoneX2:1920,zoneY2:1080,
        z:3,
        factories:[],
        sensors:[] // 传感器独立数组,与厂房完全分离
      })
    }
    const zone = parkConfig.zones[0];
    if(editTarget.type==="rectFactory"){
      zone.factories.push(editTarget.data);
    }else{
      zone.sensors.push(editTarget.data);
    }
    $editModal.hide();
    renderAll();
  })
  $("#cancelEdit").click(()=>$editModal.hide());

  // 删除元素
  $popup.on("click",".del-btn",function(){
    const zone = parkConfig.zones[delInfo.zoneIdx];
    if(delInfo.type === "factory"){
      zone.factories.splice(delInfo.idx,1);
    }else if(delInfo.type === "sensor"){
      zone.sensors.splice(delInfo.idx,1);
    }
    $popup.hide();
    renderAll();
  })

  // 温度配色工具
  function getTempColor(temp){
    if(temp<20)return "#2196F3";
    else if(temp>30)return "#f44336";
    return "#4CAF50";
  }

  function clearRender(){
    $(".zone-wrap,.factory-hot,.summary-tag,.sensor-hot,.heat-block").remove();
  }

  // 全局渲染
  function renderAll(){
    clearRender();
    parkConfig.zones.forEach((zone,zoneIdx)=>{
      const zw = zone.zoneX2 - zone.zoneX1;
      const zh = zone.zoneY2 - zone.zoneY1;
      // 分区层级z
      let $zone = $(`<div class="zone-wrap"><div class="zone-title">${zone.zoneName}</div></div>`)
        .css({
          left:zone.zoneX1,
          top:zone.zoneY1,
          width:zw,
          height:zh,
          zIndex: zone.z || 3
        });
      $mapWrap.append($zone);

      // ========== 渲染厂房区域 ==========
      zone.factories.forEach((fac,facIdx)=>{
        const tw = fac.x2 - fac.x1;
        const th = fac.y2 - fac.y1;
        const cx = fac.x1 + tw/2;
        const cy = fac.y1 + th/2;
        // 厂房层级z
        let $fac = $(`<div class="factory-hot"></div>`)
          .css({
            left:fac.x1,
            top:fac.y1,
            width:tw,
            height:th,
            zIndex: fac.z || 5
          });
        let $tag = $(`<div class="summary-tag">${fac.name} ${fac.doing}</div>`)
          .css({
            left:cx,
            top:cy,
            background:"#0066ff",
            zIndex: (fac.z || 5) + 1
          });
        // 点击弹窗带删除按钮
        $fac.add($tag).click(function(e){
          e.stopPropagation();
          delInfo = {zoneIdx, type:"factory", idx:facIdx};
          let html = `<h4>${fac.name} 备注:${fac.doing} | 层级z:${fac.z}</h4>`;
          html += `<button class="del-btn">删除此厂房</button>`;
          $popup.html(html).css({left:e.pageX,top:e.pageY}).show();
        })
        $mapWrap.append($fac).append($tag);

        // 热力色块
        let $heat = $(`<div class="heat-block"></div>`)
          .css({
            left:fac.x1,
            top:fac.y1,
            width:tw,
            height:th,
            background:"#0066ff",
            opacity:0.28
          })
        $heatLayer.append($heat);
      })

      // ========== 渲染独立传感器(单独数组,和厂房分离) ==========
      zone.sensors.forEach((sen,senIdx)=>{
        let c = getTempColor(sen.temp);
        let $sen = $(`<div class="sensor-hot"><div class="sensor-dot" style="background:${c}"></div></div>`)
          .css({
            left:sen.x,
            top:sen.y,
            display:sensorShow?"block":"none",
            zIndex: sen.z || 8
          })
          .click(function(e){
            e.stopPropagation();
            delInfo = {zoneIdx, type:"sensor", idx:senIdx};
            let html = `
              <h4>${sen.name}</h4>
              <p>层级z:${sen.z}</p>
              <p>doing:${sen.doing}</p>
              <p>温度:${sen.temp}℃</p>
              <button class="del-btn">删除此传感器</button>
            `;
            $popup.html(html).css({left:e.pageX,top:e.pageY}).show();
          })
        $mapWrap.append($sen);
      })
    })
  }

  // 热力图层开关
  $("#toggleHeat").click(function(){
    heatShow = !heatShow;
    $heatLayer.css("display",heatShow?"block":"none");
    $(this).text(heatShow?"关闭热力图层":"开启热力图层");
  })
  // 传感器显示开关
  $("#toggleSensor").click(function(){
    sensorShow = !sensorShow;
    $(".sensor-hot").css("display",sensorShow?"block":"none");
    $(this).text(sensorShow?"收起点位":"展开点位");
  })

  // 导出JSON:不含图片,厂房、传感器分开放
  $("#exportJson").click(function(){
    const output = JSON.parse(JSON.stringify(parkConfig));
    const jsonStr = JSON.stringify(output,null,2);
    const blob = new Blob([jsonStr],{type:"application/json"});
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = "园区地图配置.json";
    a.click();
    URL.revokeObjectURL(url);
  })
  // 导入JSON
  $("#importJson").click(()=>$("#jsonFile").click());
  $("#jsonFile").on("change",function(e){
    const file = e.target.files[0];
    if(!file)return;
    const reader = new FileReader();
    reader.onload = function(res){
      try{
        parkConfig = JSON.parse(res.target.result);
        renderAll();
      }catch(err){
        alert("JSON格式错误");
      }
    }
    reader.readAsText(file);
  })

  renderAll();
})
</script>
</body>
</html>
此条目发表在None分类目录。将固定链接加入收藏夹。

发表回复