vue2 地图热力图 -下钻省市县 三级及地图资源文件

实现:

   1、全国地图热力-下钻省市县热力图

   2、请求接口加载热力值

   3、解决地图资源文件旧(如县升级市导致)更新资源文件

   4、Datav数据可视化平台使用

 

 

效果演示:

 1、全国地图

image

 

2、省市

image

 

3、区县

image

 

三级下钻,示例

资源存放

public/json 存放地图json文件,可以直接下载放到public/json目录下

 

 

 

注意

   1、两处红色注释区域为测试,实际红色注册区域上下文 注释打开

   2、存在public/json文件过旧,是一定存在的,如果有最新直接替换。新旧文件取值不同,直接下载地图文件,覆盖原有的json文件即可,不要改动代码

          adcode: feature.id || feature.properties.adcode, // 兼容 旧文件与新文件地图 json文件

 3、阿里云 地图资源下载

 4、如果其它异常,需要额外处理 

 

 

vue完整代码,可以直接运行,echarts 需要下载echarts5

<template>
  <div class="map-wrapper">
    <el-card>
      <div slot="header" class="clearfix">专项检直分/村行疑点业务统计-07</div>
      <!-- 返回按钮(层级>1时显示) -->
      <button v-if="level > 1" @click="goBack" class="back-btn">
        ← 返回{{ level === 2 ? "全国" : level === 3 ? "省份" : "城市" }}
      </button>
      <!-- 地图容器 -->
      <div id="map" ref="mapContainer" class="map-container"></div>

      <!-- 当前层级提示 -->
      <!-- <div class="level-tip">{{ currentLevelName }}</div> -->
    </el-card>
  </div>
</template>

<script>
// import * as echarts from "echarts";
import * as echarts from "echarts5";

export default {
  name: "EchartsMapDrill",
  data() {
    return {
      mapJson: "",
      mapInstance: null, // ECharts 实例
      level: 1, // 当前层级:1=全国(省),2=省(市),3=市(县)
      currentAdcode: "100000", // 当前区域 adcode(全国默认 100000)
      history: [
        // 层级历史(用于返回)
        { level: 1, adcode: "100000", name: "中国" },
      ],
      currentLevelName: "中国(省级地图)", // 当前层级名称
      currentHeatData: [], //当前层级的热力原点数据
      // { name: '浙江', lng: 120.1536, lat: 30.2875, value: 90, code: '330000' },
      // testData: [
      //   { name: "杭州", lng: 120.1551, lat: 30.2741, value: 80 }, // 正确GCJ-02坐标
      //   { name: "宁波", lng: 121.5497, lat: 29.8683, value: 60 },
      // ],
      heatDataMap: {
        // 全国层级热力数据(省级)
        // { name: '浙江', lng: 120.1536, lat: 30.2875, value: 90, code: '330000' },
        100000: [
          { name: "浙江", lng: 120.153576, lat: 30.287459, value: 90 },
          { name: "广东", lng: 113.264434, lat: 23.129113, value: 95 },
          { name: "江苏", lng: 118.767413, lat: 32.045897, value: 88 },
          { name: "山东", lng: 117.000923, lat: 36.675807, value: 82 },
        ],
      },
    };
  },
  mounted() {
    // 测试使用
    this.initMap()
    // 地图
    this.mapInstance = echarts.init(this.$refs.mapContainer);
    // 监听窗口 resize 实现自适应
    window.addEventListener("resize", () => this.mapInstance?.resize());
  },
  beforeDestroy() {
    // 销毁实例,避免内存泄漏
    this.mapInstance?.dispose();
    window.removeEventListener("resize", () => this.mapInstance?.resize());
  },
  methods: {
    //第1步:
    reqApi(){
      // sendReq({code:100000}).then(res=>{
        // 接口请求成功,获取当前热力信息,比如首次是全国100000,成功后存入值为:
        // this.heatDataMap[this.currentAdcode] = res.data
        this.heatDataMap[this.currentAdcode] =  [{name:'北京',code:121212,value:22}]
        // 成功后,渲染地图数据
          this.initMap()
      // })
      
    },
    // 初始化地图
    initMap() {
      // 加载当前层级地图和热力数据
      // this.currentHeatData = this.heatDataMap[this.currentAdcode] || [];
      this.getHotValue()

      this.loadMapData(this.currentAdcode); // 加载当前层级地图数据
    },
    // 获取热点值
    getHotValue() {
      this.currentHeatData = this.heatDataMap[this.currentAdcode] || [];
    },

    // 加载地图 JSON 数据(核心方法)
    async loadMapData(adcode) {
      try {
        // 1. 请求地图数据(public/map/目录下)
        const fileName = adcode === "100000" ? "china" : adcode;
        const res = await fetch(`/json/${fileName}.json`);

        if (!res.ok) throw new Error(`数据加载失败(状态码:${res.status})`);
        const mapJson = await res.json();

        console.log("mapJson", mapJson);
        this.mapJson = mapJson;

        // 2. 注册地图(ECharts 需先注册才能渲染)
        echarts.registerMap(adcode, mapJson);

        // 获取 热力值数据
        const apidata = this.currentHeatData
        // 3. 格式化地图数据(提取 name 和 adcode,用于点击下钻)
        // const mapData = mapJson.features.map(feature => {
        //   for(let i =0; i< apidata.length; i++){
        //     if([feature.id, feature.properties.adcode].includes(apidata[i].code)){
        //       return{
        //           name: feature.properties.name,
        //           adcode: feature.id || feature.properties.adcode, // 兼容 旧文件与新文件地图 json文件
        //           // 可选:添加自定义数据(如人口、GDP,用于后续可视化)
        //           value: apidata[i].score // 占位,可替换为实际数据
        //       }
        //     }
        //   }
        // });

        // 如果测试直接打开注释,把上边注释掉预览效果
         const mapData = mapJson.features.map((feature) => ({
          name: feature.properties.name,
          adcode: feature.id || feature.properties.adcode,
          value: Math.floor(Math.random() *100) // 占位,可替换为实际数据
        }));

        // 4. 配置地图 option
        const option = this.getMapOption(adcode, mapData, mapJson);

        // 5. 渲染地图
        this.mapInstance.setOption(option, true); // true 避免重复合并配置

        // 6. 绑定点击事件(实现下钻)
        this.bindClickEvent();
      } catch (err) {
        console.error("地图加载失败:", err.message);
        alert(`地图加载失败:${err.message}`);
      }
    },

    // 地图配置项(根据层级动态调整)
    getMapOption(adcode, mapData, mapJson) {
      // 提取当前区域名称(如“浙江省”“杭州市”)
      const areaName = this.history[this.history.length - 1].name;

      return {
        title: {
          //  title: { text: `${areaName}热力地图`, left: 'center', textStyle: { fontSize: 18 } },
          text: areaName,
          left: "center",
          textStyle: { fontSize: 18, fontWeight: 600 },
        },
        tooltip: {
          trigger: "item",
          // formatter: (params) =>
          //   `${params.name}<br/>行政代码:${params.data.adcode}`,
          formatter: (params) => {
            // 区分地图区域和热力原点的tooltip
            if (params.seriesType === "map") {
              return `${params.name}<br/>行政代码:${params.data.adcode}`;
            } else if (params.seriesType === "scatter") {
              return `${params.name}<br/>经纬度:${params.value[0].toFixed(
                2
              )}, ${params.value[1].toFixed(2)}<br/>热力值:${params.value[2]}`;
            }
          },
        },
        // geo: [
        //   {
        //     map: adcode, // 关联已注册的地图
        //     roam: true,
        //     //  data: mapData, // 格式化后的地图数据
        //     label: {
        //       show: false,
        //       fontSize: this.getLabelFontSize(),
        //     },
        //     itemStyle: { areaColor: "#ececec" },
        //     label: false,
        //   },
        // ],
        // map 系列:绑定 geo 组件,不独立渲染地图
        visualMap:{
          min:0,
          max:200,
          calculable:true,
          text:['',''],
          left:'left',
          top:'bottom',
          inRange:{
            color:['#f5f5f5','#fd9b02','#e20508']
          }
          
        },
        series: [
          {
            name: this.getSeriesName(), // 系列名称(根据层级动态变化)
            type: "map",
            mapType: adcode,
            // map: adcode, // 对应注册的地图名称(adcode)
            roam: true, // 禁止缩放平移(按需开启)
            label: {
              show: false,
              fontSize: this.getLabelFontSize(), // 按层级调整字体大小(避免重叠)
            },
            // geoIndex: 0,
            data: mapData, // 格式化后的地图数据
            itemStyle: {
              areaColor: '#f5f5f5', // 默认区域颜色
              // borderColor: '#ffffff', // 边界颜色
              // borderWidth: 1.5 // 边界宽度
            },

            emphasis: {
              // 鼠标 hover 高亮样式
              itemStyle: { areaColor: "#ffae00" },
              // itemStyle:false,
              label: false,
              // label: { color: '#fff', fontWeight: 'bold', fontSize: this.getLabelFontSize() + 1 }
            },
          },
          // {
          //   name: "热力点",
          //   type: "scatter",
          //   coordinateSystem: "geo", // 绑定地图坐标系
          //   geoIndex: 0, // 关联第1个geo系列(即map系列)
          //   // this.formatHeatData(this.currentHeatData),
          //   data: this.formatHeatData(this.currentHeatData), // 格式化热力数据
          //   symbol: "circle", // 原点形状(circle/rect/triangle等)
          //   symbolSize: (value) => {
          //     // 热力值越大,原点越大(10~30px动态调整)
          //     return 1 + (value[2] / 100) * 10;
          //   },
          //   itemStyle: {
          //     color: "#f17b1b", // 固定红色(支持十六进制、RGB、颜色名)
          //     opacity: 0.8, // 透明度(0~1,避免遮挡地图)
          //     shadowBlur: 16, // 阴影增强视觉效果
          //     shadowColor: "rgba(255, 77, 79, 0.5)",
          //   },
          //   emphasis: {
          //     symbolSize: (value) => 20 + (value[2] / 100) * 20, // hover时放大
          //     itemStyle: { opacity: 1 },
          //   },
          // },
        ],
      };
    },

    // 格式化热力数据:ECharts scatter 需传入 [lng, lat, value] 格式
    formatHeatData(heatData) {
      const formatted = heatData.map((item) => ({
        name: item.name,
        value: [item.lng, item.lat, item.value],
      }));
      console.log("格式化后的热力数据:", formatted); // 新增打印
      return formatted;
    },

    // 根据层级获取系列名称(如“省份”“城市”“区县”)
    getSeriesName() {
      switch (this.level) {
        case 1:
          return "省份";
        case 2:
          return "城市";
        case 3:
          return "区县";
        default:
          return "区域";
      }
    },

    // 根据层级调整标签字体大小(层级越深,字体越小)
    getLabelFontSize() {
      switch (this.level) {
        case 1:
          return 12; // 全国(省):字体稍大
        case 2:
          return 10; // 省(市):字体中等
        case 3:
          return 8; // 市(县):字体偏小(避免区县名称重叠)
        default:
          return 10;
      }
    },

    // 绑定地图点击事件(实现下钻)
    bindClickEvent() {
      this.mapInstance.off("click"); // 先解绑旧事件,避免重复触发

      this.mapInstance.on("click", (params) => {
        console.log("params", params);
        const { adcode, name } = params.data;
        const nextLevel = this.level + 1;

        // 限制最大层级(3级:市→县,县之后不再下钻)
        if (nextLevel > 3) {
          alert(`已到达最细层级(${name}),无更下级地图数据`);
          return;
        }

        console.log("namename", name);

       this.currentAdcode = adcode;

        // 更新状态:层级、当前 adcode、历史记录、层级名称
        this.level = nextLevel;

        this.history.push({
          level: nextLevel,
          adcode: this.currentAdcode,
          name,
        });
        this.currentLevelName = `${name}(${
          nextLevel === 2 ? "市级" : "县级"
        }地图)`;

        // 加载下一级地图数据 --测试
        this.getHotValue();
        this.loadMapData(this.currentAdcode);

        // 加载下一级地图数据 --测试


        // 正式请求
        // this.currentHeatData = this.heatDataMap[this.currentAdcode] || [];
        // if(!this.currentHeatData || !this.currentHeatData.length){
        //    this.reqApi()
        // }else{
        //   this.loadMapData(this.currentAdcode);
        // }
      });
    },

    // 返回上一级
    goBack() {
      if (this.history.length <= 1) return; // 已到全国层级,无法返回

      // 移除当前层级历史
      this.history.pop();
      // 获取上一级状态
      const prev = this.history[this.history.length - 1];
      this.level = prev.level;
      this.currentAdcode = prev.adcode;
      this.currentLevelName = `${prev.name}(${
        prev.level === 1 ? "省级" : "市级"
      }地图)`;

      this.getHotValue();
      // 加载上一级地图数据
      this.loadMapData(prev.adcode);
    },
  },
};
</script>

<style scoped>
.map-wrapper {
  position: relative;
  width: 100%;
  height: 100%;
  box-sizing: border-box;
}

.back-btn {
  position: absolute;
  top: 70px;
  left: 30px;
  padding: 8px 16px;
  background: #409eff;
  color: #fff;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  z-index: 10;
  font-size: 14px;
  transition: background 0.3s;
}

.back-btn:hover {
  background: #269aff;
}

.map-container {
  width: 100%;
  height: 330px;
  /* height: 100%; */
  /* height: calc(100% - 10px); */
}

.level-tip {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 14px;
  color: #666;
  background: rgba(255, 255, 255, 0.8);
  padding: 4px 12px;
  border-radius: 16px;
}

.heat-legend {
  position: absolute;
  bottom: 20px;
  right: 20px;
  background: rgba(255, 255, 255, 0.8);
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 12px;
  color: #666;
}
.legend-item {
  display: inline-block;
  width: 20px;
  height: 8px;
  margin: 0 4px;
  border-radius: 4px;
}
.legend-item.low {
  background: #e0f7fa;
}
.legend-item.mid {
  background: #4dd0e1;
}
.legend-item.high {
  background: #006064;
}
.el-card{
  /* display: flex;
  flex-direction: column; */
}

::v-deep .el-card__body {
  /* flex: 1;   */
  /* overflow: hidden;  */
  padding: 0 !important; 
}

.el-card__body{
  padding: 0 !important;
}
</style>

 

posted on 2025-12-22 11:06  Mc525  阅读(3)  评论(0)    收藏  举报

导航