map+echart+vue 大数据多次渲染卡顿问题

尤其是地图组件比较庞大,再导入大批量数据最容易造成卡顿的出现。

尤其是渲染次数多,交互多的情况。

所以每次重新加载数据一定要先销毁之前的。

不然越越卡直到卡死

父组件:

   <div class="mapchart">
           <amap :key="mapkey" ref='mymap'></amap>
      </div>

  

 chosethismaptab(ind){ // 每次重新交互接受新数据
      this.tabind = ind
      this.$refs.mymap.destr() // 每次重新加载 通知子组件先销毁再渲染
      this.mapkey+=1
    },

子组件

 destr(){
      this.myChart.dispose();
    },

这样就不会再卡顿了,不管怎么交互,点击多少次重新渲染都没问题了。

卡顿容易造成渲染等待越来越长:

不卡顿状态:

就算下钻再深也没关系

 完整代码:

<!-- 大区服务 -->
<template>
  <div class="main">
     <headbreak style="background: #fff" :titlearr="titlearr"></headbreak>
     <div style="display:none;">
      <el-button @click="toPage('1')">30分钟及时预约率</el-button>
    <el-button @click="toPage('2')">预约准时上门率</el-button>
    <el-button @click="toPage('3')">TAT平均服务完成时长</el-button>
    <el-button @click="toPage('4')">投诉7天解决率</el-button>
    <el-button @click="toPage('5')">一次修复率</el-button>
    <el-button @click="toPage('6')">2天维修达成率</el-button>
    <el-button @click="toPage('7')">N+1投诉解决方案提交率</el-button>
     </div>
    <!-- 搜索栏 -->
    <div style="width:100%;display:flex;margin:10px;">
      <!-- <amap></amap> -->
    <el-button @click="qucklysearch('近3天')">近3天</el-button>
    <el-button @click="qucklysearch('近7天')">近7天</el-button>
    <el-button @click="qucklysearch('近30天')">近30天</el-button>
    <el-button @click="qucklysearch('本周')">本周</el-button>
    <el-button @click="qucklysearch('本月')">本月</el-button>
    <el-button @click="qucklysearch('自定义')">自定义</el-button>
    <div style="width:300px;margin-left:10px;">
    <mytimer :timerrange.sync = "timerangedate"></mytimer> 
    </div>
    <span>
      <el-select v-model="pinleitype">
        <el-option value="1">1</el-option>
        <el-option value="2">2</el-option>
      </el-select>
    </span>
    <span>
      <el-button v-popover:popover2>默认 <i class="el-icon-arrow-down"></i></el-button>
      <el-popover
        ref="popover2"
        placement="bottom"
        title="标题"
        width="200"
        trigger="click"
        content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。">
        <p>
          <span>洗衣机品类近七日数据</span>
          <span>首选项</span>
          <span><i class="el-icon-delete"></i></span>
        </p>
        <p>21212112</p>
        <p>21212112</p>
      </el-popover>
    </span>
    </div>
    <!-- 折现图表看板 -->
    <div class="boardbox1">
      <ul>
        <li>
           <cardyuyuelv></cardyuyuelv>
        </li>
        <li>
           <cardyuyuelv id="chart_category_2" 
           title="预约准时上门率"
           wanchenglv="80%"
           ></cardyuyuelv>
        </li>
        <li>
           <cardyuyuelv id="chart_category_3" title="改约率"
            wanchenglv="10%"
           ></cardyuyuelv>
        </li>
        <li>
           <cardyuyuelv id="chart_category_4" title="TAT平均服务完成时长"
            wanchenglv="1天3小时"
           ></cardyuyuelv>
        </li>
      </ul>
    </div>
    <!-- 地图 -->
    <div class="mapbox">
      <ul>
        <li  @click="chosethismaptab(1)">
          <p>换件维修TAT</p>
          <p :class="this.tabind == 1 ? 'active' : ''">1天7小时</p>
        </li>
        <li  @click="chosethismaptab(2)">
          <p>不换件维修TAT</p>
          <p :class="this.tabind == 2 ? 'active' : ''">1天7小时</p>
        </li>
         <li  @click="chosethismaptab(3)">
          <p>快速安装TAT</p>
          <p :class="this.tabind == 3 ? 'active' : ''">1天7小时</p>
        </li>
          <li  @click="chosethismaptab(4)">
          <p>鉴定TAT</p>
          <p :class="this.tabind == 4 ? 'active' : ''">1天7小时</p>
        </li>
         <li @click="chosethismaptab(5)">
          <p>非上门服务TAT</p>
          <p :class="this.tabind == 5 ? 'active' : ''">1天7小时</p>
        </li>
      </ul>
      <div class="mapcontent">
         <div class="mapchart">
           <amap :key="mapkey" ref='mymap'></amap>
      </div>
      <div class="zhibiao">
        <p>
          <span></span>
          <span>2小时</span>
          <span>36%</span>
        </p>
         <p>
          <span></span>
          <span>1~3天</span>
          <span>24%</span>
        </p>
         <p>
          <span></span>
          <span>4~7天</span>
          <span>20%</span>
        </p>
         <p>
          <span></span>
          <span>7天以上</span>
          <span>20%</span>
        </p>
      </div>
      <div class="maptabs">
        <el-tabs style="margin-top:20px;height:400px;" type="border-card">
          <el-tab-pane label="大区排名">
            <p @click="fnchosethislist">
              <span>1</span>
              <span>所属大区</span>
              <span>1天4小时</span>
            </p>
            <p>
              <span>2</span>
              <span>所属大区</span>
              <span>1天4小时</span>
            </p>
          </el-tab-pane>
          <el-tab-pane label="门店排名">
             <p>
              <span>1</span>
              <span>所属门店</span>
              <span>1天4小时</span>
            </p>
            <p>
              <span>2</span>
              <span>所属门店</span>
              <span>1天4小时</span>
            </p>
          </el-tab-pane>
        </el-tabs>
      </div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      timerangedate:"",
      pinleitype:"",
      titlearr:["数据看板","大区服务"],
      tabind:1,
      mapkey:1,
    }
  },
  components:{
    headbreak: () => import("../partsManagement/components/headbreak.vue"),
    amap:()=>import('./charts/mapchart.vue'),
    mytimer: ()=>import('../settlementMgt/components/mypickerange.vue'),
    cardyuyuelv: ()=>import('./components/cardyuyuelv.vue')
    
  },
  methods: {
    fnchosethislist(){
    },
    chosethismaptab(ind){ // 每次重新交互接受新数据
      this.tabind = ind
      this.$refs.mymap.destr() // 每次重新加载 通知子组件先销毁再渲染
      this.mapkey+=1
    },
    toPage(val) {
      switch (val) {
        case'1':
          this.$router.push({ name:'timelyRatw'});
          break;
        case '2':
          this.$router.push({ name:'punctualRate'});
          break;
        case '3':
          this.$router.push({ name:'avgTime'});
          break;
        case '4':
          this.$router.push({ name:'resolutionRate'});
          break;
        case '5':
          this.$router.push({ name:'repairRate'});
          break;
        case '6':
          this.$router.push({ name:'maintenanceRate'});
          break;
        case '7':
          this.$router.push({ name:'reductionRate'});
          break;
      }

      return type;
    },
    qucklysearch(timer){
      console.log(timer)
    },
  }
}
</script>
<style lang="less" scoped>
.main{
  width:99.8%;
  // background:pink;
}
.mapbox{
  margin:10px;
  ul{
    display: flex;justify-content:space-between;cursor:pointer;border:1px solid #ccc;
    li{
      p{position:relative;bottom:-11px;}
       p:nth-child(1){
         font-size:16px;
       }
      p:nth-child(2){
        font-size:30px;
        &.active{
          border-bottom:2px solid blue;
        }
      }
    }
  }
  .mapcontent{
    border:1px solid #ccc;padding-bottom:20px;
    display: flex;justify-content:flex-start;
    .mapchart{width:50%;
      height:430px;
      background:#fff;
    }
    .zhibiao{width:12%;
    position:relative;top:300px;
    >p{
      color:rgb(57, 53, 53);font-size:14px;
      span:nth-child(1){display: inline-block;width:10px;height:10px;border-radius:10px;background:rgb(45, 151, 44);}
      span:nth-child(2){margin-left:10px;}
      span:nth-child(3){margin-left:10px;}
    }
    }
    .maptabs{width:38%;padding:10px;
      p{
        cursor:pointer;
        span:nth-child(1){display: inline-block;width:20px;height:20px;border-radius:10px;background:red;text-align: center;}
        span:nth-child(2){margin-left:30px;font-size:20px;}
        span:nth-child(3){margin-left:30px;font-size:20px;}
      }
    }
  }
}
  .boardbox1{
    ul{
      display: flex;justify-content:space-between;
      li{
        width:25%;
        // border:1px solid #ccc;
        // box-shadow: 2px 4px 8px #ccc;
      }
    }
  }
</style>
<template>
  <div>
    <div id="container">
      <div class="back" @click="backfn">返 回</div>
      <div :id="root" class="rootCls"></div>
    </div>
  </div>
</template>
<script>
import AMapLoader from "@amap/amap-jsapi-loader";
import * as echarts from "echarts";
var geoJson = {
  features: [],
};
var parentInfo = [
  {
    cityName: "全国",
    level: "china",
    code: 100000,
  },
];
var timeTitle = ["2022"];
export default {
  name: "",
  props: {
    root:{
      type:String,
      default:'root'
    }
  },
  data() {
    return {
      myChart:null,
    };
  },
  components: {},
  methods: {
    // 渲染地图echarts
    initEchartMap(mapData, sum, pointData) {
      //这里做个切换,全国的时候才显示南海诸岛  只有当注册的名字为china的时候才会显示南海诸岛
      if (parentInfo.length === 1) {
        echarts.registerMap("china", geoJson); //注册
      } else {
        echarts.registerMap("map", geoJson); //注册
      }
      var option = {
        timeline: {
          show:false, // 底部时间线关闭
          data: timeTitle,
          axisType: "category",
          autoPlay: true,
          playInterval: 3000,
          left: "10%",
          right: "10%",
          bottom: "3%",
          width: "80%",
          label: {
            normal: {
              textStyle: {
                color: "rgb(179, 239, 255)",
              },
            },
            emphasis: {
              textStyle: {
                color: "#fff",
              },
            },
          },
          symbolSize: 10,
          lineStyle: {
            color: "#8df4f4",
          },
          checkpointStyle: {
            borderColor: "#8df4f4",
            color: "#53D9FF",
            borderWidth: 2,
          },
          controlStyle: {
            showNextBtn: true,
            showPrevBtn: true,
            normal: {
              color: "#53D9FF",
              borderColor: "#53D9FF",
            },
            emphasis: {
              color: "rgb(58,115,192)",
              borderColor: "rgb(58,115,192)",
            },
          },
        },
        baseOption: {
          animation: true,
          animationDuration: 1000,
          animationEasing: "cubicInOut",
          animationDurationUpdate: 1000,
          animationEasingUpdate: "cubicInOut",
          tooltip: {
            trigger: "item",
          },
          toolbox: {
            show:false, // 转为table 展示 关闭
            feature: {
              restore: {
                show: false,
              },
              dataView: {
                optionToContent: function (opt) {
                  let series = opt.series[0].data; //折线图数据
                  let tdHeads =
                    '<th  style="padding: 0 20px">所在地区</th><th style="padding: 0 20px">销售额</th>'; //表头
                  let tdBodys = ""; //数据
                  let table = `<table border="1" style="margin-left:20px;border-collapse:collapse;font-size:14px;text-align:left;"><tbody><tr>${tdHeads} </tr>`;
                  for (let i = 0; i < series.length; i++) {
                    table += `<tr>
                              <td style="padding: 0 50px">${series[i].name}</td>
                              <td style="padding: 0 50px">${series[
                                i
                              ].value.toFixed(2)}万</td>
                              </tr>`;
                  }
                  table += "</tbody></table>";
                  return table;
                },
              },
              saveAsImage: {
                show:false, // 下载地图功能
                name:
                  parentInfo[parentInfo.length - 1].cityName + "销售额统计图",
              },
              dataZoom: {
                show: false,
              },
              magicType: {
                show: false,
              },
            },
            iconStyle: {
              normal: {
                borderColor: "#1990DA",
              },
            },
            top: 15,
            right: 35,
          },
          geo: {
            map: parentInfo.length === 1 ? "china" : "map",
            zoom: 1.2, // 初始化大小
            top:50,
            roam: true,
            tooltip: {
              trigger: "item",
              formatter: (p) => {
                let val = p.value[2];
                if (window.isNaN(val)) {
                  val = 0;
                }
                let txtCon =
                  "<div style='text-align:left'>" +
                  p.name +
                  ":<br />销售额:" +
                  val.toFixed(2) +
                  "万</div>";
                return txtCon;
              },
            },
            label: {
              normal: {
                show: true,
                color: "rgb(249, 249, 249)", //省份标签字体颜色
                formatter: (p) => {
                  switch (
                    p.name // 地图上的标名
                  ) {
                    case "内蒙古自治区":
                      p.name = "内蒙古";
                      break;
                    case "西藏自治区":
                      p.name = "西藏";
                      break;
                    case "新疆维吾尔自治区":
                      p.name = "新疆";
                      break;
                    case "宁夏回族自治区":
                      p.name = "宁夏";
                      break;
                    case "广西壮族自治区":
                      p.name = "广西";
                      break;
                    case "香港特别行政区":
                      p.name = "香港";
                      break;
                    case "澳门特别行政区":
                      p.name = "澳门";
                      break;
                    default:
                      break;
                  }
                  return p.name;
                },
              },
              emphasis: {
                show: true,
                color: "#f75a00",
              },
            },
            itemStyle: {
              normal: {
                areaColor: "#24CFF4",
                borderColor: "#53D9FF",
                borderWidth: 1.3,
                shadowBlur: 15,
                shadowColor: "rgb(58,115,192)",
                shadowOffsetX: 7,
                shadowOffsetY: 6,
              },
              emphasis: {
                areaColor: "#8dd7fc",
                borderWidth: 1.6,
                shadowBlur: 25,
              },
            },
          },
        },
        options: [],
      };
      timeTitle.forEach((item) => {
        var min = mapData[item][mapData[item].length - 1].value;
        var max = mapData[item][0].value;
        if (mapData[item].length === 1) {
          min = 0;
        }
        option.options.push({
          // backgroundColor: "#012248",// 背景色
          title: [ // 头部标题
            // {
            //   left: "center",
            //   top: 10,
            //   text:
            //     item +
            //     "年" +
            //     parentInfo[parentInfo.length - 1].cityName +
            //     "销售额统计图(可点击)",
            //   textStyle: { 
            //     color: "red",
            //     fontSize: 12,
            //   },
            // },
            // {
            //   text: "年度销售总额:" + sum[item].toFixed(2) + "万",
            //   left: "center",
            //   top: "6.5%",
            //   textStyle: {
            //     color: "#FFAC50",
            //     fontSize: 26,
            //   },
            // },
          ],
          visualMap: {
            min: min,
            max: max,
            left: "-13%", // 隐掉左下脚本条
            bottom: "5%",
            calculable: true,
            seriesIndex: [0],
            inRange: {
              color: ["#24CFF4", "#2E98CA", "#1E62AC"], // 地图颜色配置 -原色
              // color: ["#010824", "#010824", "#010824"], // 地图颜色配置
            },
            textStyle: {
              color: "#24CFF4",
            },
          },
          series: [
            {
              name: item + "销售额度",
              type: "map",
              geoIndex: 0,
              map: parentInfo.length === 1 ? "china" : "map",
              roam: true,
              zoom: 1.3,
              tooltip: {
                trigger: "item",
                formatter: (p) => {
                  let val = p.value;
                  if (p.name == "南海诸岛") return;
                  if (window.isNaN(val)) {
                    val = 0;
                  }
                  let txtCon =
                    "<div style='text-align:left'>" +
                    p.name +
                    ":<br />销售额:" +
                    val.toFixed(2) +
                    "万</div>";
                  return txtCon;
                },
              },
              label: {
                normal: {
                  show: false,
                },
                emphasis: {
                  show: false,
                },
              },
              data: mapData[item],
            },
            {
              name: "散点",
              type: "effectScatter",
              coordinateSystem: "geo",
              rippleEffect: {
                // brushType: "fill",
                brushType: 'stroke'
              },
              showEffectOn: 'render', //'emphasis' 高亮(hover)的时候显示特效
              hoverAnimation: true,//图形上的文本标签,可用于说明图形的一些数据信息,比如值,名称等
              itemStyle: {
                normal: {
                  //  color: 'purple',
                  color: "#F4E925", // 地图散点配置色 - 原色
                  // color: "#6034fd", // 地图散点配置色
                  shadowBlur: 10,
                  shadowColor: "#333",
                },
              },
              data: pointData[item],
              // symbolSize: 8,
              symbolSize: function (val) {
                let value = val[1];
                if (value == max) {
                  return 11;
                }
                return 10;
              },
              showEffectOn: "render", //加载完毕显示特效
            },
          ],
        });
      });
      console.log(this.root,'---')
      var myChart = echarts.init(document.getElementById(this.root));
      this.myChart = echarts.init(document.getElementById(this.root));
      myChart.setOption(option, true); // 第二个参数尤其重要,如果不设置地图一点击就会跑偏甚至跑出content,设置后会原始坐标上渲染显示
      //点击前解绑,防止点击事件触发多次
      myChart.off("click");
      myChart.on("click", this.echartsMapClick);
    },
    // 监听echarts 的点击事件
    echartsMapClick(params) {
      if (!params.data) {
        return;
      } else {
        //如果当前是最后一级,那就直接return
        if (parentInfo[parentInfo.length - 1].code == params.data.cityCode) {
          return;
        }
        let data = params.data;
        parentInfo.push({
          cityName: data.name,
          level: data.level,
          code: data.cityCode,
        });
        this.getGeoJson(data.cityCode);
      }
    },
    // 获取地图数据
    getMapData() {
      let mapData = {},
        pointData = {},
        sum = {};
      for (let i = 0; i < timeTitle.length; i++) {
        mapData[timeTitle[i]] = [];
        pointData[timeTitle[i]] = [];
        sum[timeTitle[i]] = 0;
        for (let j = 0; j < geoJson.features.length; j++) {
          let value = 1;
          mapData[timeTitle[i]].push({
            name: geoJson.features[j].properties.name,
            value: value,
            level: geoJson.features[j].properties.level,
            cityCode: geoJson.features[j].properties.adcode,
          });
          pointData[timeTitle[i]].push({
            name: geoJson.features[j].properties.name,
            value: [
              geoJson.features[j].properties.center[0],
              geoJson.features[j].properties.center[1],
              value,
            ],
            cityCode: geoJson.features[j].properties.adcode,
          });
          sum[timeTitle[i]] += value;
        }
        mapData[timeTitle[i]] = mapData[timeTitle[i]].sort(function (a, b) {
          return b.value - a.value;
        });
      }
      this.initEchartMap(mapData, sum, pointData);
    },
    // 获取地图初始化json数据
    getGeoJson(adcode) {
      // console.log(this) // vue
      let that = this;
      console.log(adcode, AMapUI, "------");
      AMapUI.loadUI(["geo/DistrictExplorer"], (DistrictExplorer) => {
        var districtExplorer = new DistrictExplorer();
        districtExplorer.loadAreaNode(adcode, function (error, areaNode) {
          if (error) {
            console.error(error);
            return;
          }
          let Json = areaNode.getSubFeatures();
          if (Json.length > 0) {
            geoJson.features = Json;
          } else if (Json.length === 0) {
            geoJson.features = geoJson.features.filter(
              (item) => item.properties.adcode == adcode
            );
            if (geoJson.features.length === 0) return;
          }
          // console.log(this) // DistrictExplorer
          that.getMapData();
        });
      });
    },
    // 返回上一级
    backfn() {
      if (parentInfo.length === 1) {
        return;
      }
      parentInfo.pop();
      this.getGeoJson(parentInfo[parentInfo.length - 1].code);
    },
    // 初始化地图 必须
    initAMap() {
      AMapLoader.load({
        key: mykey,
        version: "2.0",
        plugins: ["AMap.ToolBar", "AMap.Driving"],
        AMapUI: {
          version: "1.1",
          plugins: [],
        },
        Loca: {
          version: "2.0",
        },
      })
        .then((AMap) => {
          // 注释的内容是高德官网渲染其它地图的组件 测试用例 这里不再演示
          // this.map = new AMap.Map("container", {
          //   viewMode: "3D",
          //   zoom: 5,
          //   zooms: [2, 22],
          //   center: [105.602725, 37.076636],
          // });
          // this.my_init(); // 高德测试用例用来调用高德ui组件
          this.getGeoJson(100000);
        })
        .catch((e) => {
          console.log(e);
        });
    },
    my_init() {
      // 高德测试用例用来调用高德ui组件
      // initAMapUI(); //这里调用initAMapUI前先初始化
      // //其他逻辑
      // AMapUI.loadUI(
      //   [
      //     "overlay/SimpleMarker", //SimpleMarker
      //     "overlay/SimpleInfoWindow", //SimpleInfoWindow
      //   ],
      //   (SimpleMarker, SimpleInfoWindow) => {
      //     //....引用加载的UI....
      //     console.log(SimpleMarker, SimpleInfoWindow);
      //     this.initPage(SimpleMarker);
      //   }
      // );
    },
    destr(){
      console.log('dispose')
      this.myChart.dispose();
    },
    //初始化到页面
    initPage(SimpleMarker) {
      //创建SimpleMarker实例
      new SimpleMarker({
        //前景文字
        iconLabel: "",
        //图标主题
        iconTheme: "default",
        //背景图标样式
        iconStyle: "pink",
        //...其他Marker选项...,不包括content
        map: this.map,
        position: [117.405285, 39.904989],
      });
      //创建SimpleMarker实例
      new SimpleMarker({
        //前景文字
        iconLabel: {
          innerHTML: "<i>B</i>", //设置文字内容
          style: {
            color: "#fff", //设置文字颜色
          },
        },
        //图标主题
        iconTheme: "fresh",
        //背景图标样式
        iconStyle: "black",
        //...其他Marker选项...,不包括content
        map: this.map,
        position: [110.315285, 39.924989],
      });
    },
  },
  mounted() {
    // this.getGeoJson(100000);
    this.initAMap();
  },
};
</script>
<style scoped lang="less">
.rootCls {
  width: 600px;
  height: 430px;
  // border: 1px solid rgb(161, 153, 153);
  position: fixed;
  top: 0px;
  z-index: 10;
}
.back {
  text-align: center;
  width: 60px;
  height: 30px;
  line-height: 30px;
  border: 1px solid #fff;
  border-radius: 4px;
  position: absolute;
  left: 20px;
  top: 30px;
  color: rgb(32, 43, 46);
  font-size: 16px;
  cursor: pointer;
  z-index: 100000;
}
#container {
  padding: 0px;
  margin: 0px;
  // width: 100%;
  // height: 100%;
  position: absolute;
  z-index: 9;
}
</style>

 

posted @ 2022-05-05 17:39  少哨兵  阅读(986)  评论(0编辑  收藏  举报