引入echarts依赖

npm install echarts
npm install echarts-gl

定义一个使用3d地图的组件

<template>
  <div class="echarts"/>
</template>

<script>
import {watchEffect} from 'vue'
import * as echarts from 'echarts'
import debounce from 'lodash/debounce'
import {addListener, removeListener} from 'resize-detector'

const INIT_TRIGGERS = ['theme', 'initOptions', 'autoResize']
const REWATCH_TRIGGERS = ['manualUpdate', 'watchShallow']

export default {
  props: {
    option: {
      type: Object,
      default: () => {
      },
    },
    initOptions: {
      type: Object,
      default: () => {
      },
    },
    group: {
      type: String,
      default: '',
    },
    autoResize: {
      type: Boolean,
      default: true,
    },
    watchShallow: {
      type: Boolean,
      default: false,
    },
    manualUpdate: {
      type: Boolean,
      default: false,
    },
    proCity: {
      type: String,
      default: "",
    },
    level: {
      type: String,
      default: "province",
    }
  },
  setup() {
    watchEffect(() => {
    })
    return {
      lastArea: 0,
    }
  },
  watch: {
    proCity() {
      this.initOptionsWatcher()
      this.refresh()
    }
  },
  created() {
    this.initOptionsWatcher()
    INIT_TRIGGERS.forEach((prop) => {
      this.$watch(
        prop,
        () => {
          this.refresh()
        },
        {deep: true}
      )
    })
    REWATCH_TRIGGERS.forEach((prop) => {
      this.$watch(prop, () => {
        this.initOptionsWatcher()
        this.refresh()
      })
    })
  },
  mounted() {
    if (this.option) {
      this.init()
    }
  },
  activated() {
    if (this.autoResize) {
      this.chart && this.chart.resize()
    }
  },
  unmounted() {
    if (this.chart) {
      this.destroy()
    }
  },
  methods: {
    mergeOptions(option, notMerge, lazyUpdate) {
      if (this.manualUpdate) {
        this.manualOptions = option
      }
      if (!this.chart) {
        this.init(option)
      } else {
        this.delegateMethod('setOption', option, notMerge, lazyUpdate)
      }
    },
    appendData(params) {
      this.delegateMethod('appendData', params)
    },
    resize(option) {
      this.delegateMethod('resize', option)
    },
    dispatchAction(payload) {
      this.delegateMethod('dispatchAction', payload)
    },
    convertToPixel(finder, value) {
      return this.delegateMethod('convertToPixel', finder, value)
    },
    convertFromPixel(finder, value) {
      return this.delegateMethod('convertFromPixel', finder, value)
    },
    containPixel(finder, value) {
      return this.delegateMethod('containPixel', finder, value)
    },
    showLoading(type, option) {
      this.delegateMethod('showLoading', type, option)
    },
    hideLoading() {
      this.delegateMethod('hideLoading')
    },
    getDataURL(option) {
      return this.delegateMethod('getDataURL', option)
    },
    getConnectedDataURL(option) {
      return this.delegateMethod('getConnectedDataURL', option)
    },
    clear() {
      this.delegateMethod('clear')
    },
    dispose() {
      this.delegateMethod('dispose')
    },
    delegateMethod(name, ...args) {
      if (!this.chart) {
        this.init()
      }
      return this.chart[name](...args)
    },
    delegateGet(methodName) {
      if (!this.chart) {
        this.init()
      }
      return this.chart[methodName]()
    },
    getArea() {
      return this.$el.offsetWidth * this.$el.offsetHeight
    },
    init(option) {
      if (this.chart) {
        return
      }
      const chart = echarts.init(this.$el, this.initOptions)
      chart.showLoading();
      if (this.group) {
        chart.group = this.group
      }
      let geoJson = require(`@/geo/${this.level}/${this.proCity}.json`)
      echarts.registerMap('china', geoJson);
      chart.hideLoading();
      chart.setOption(option || this.manualOptions || this.option || {}, true)
      if (this.autoResize) {
        this.lastArea = this.getArea()
        this.__resizeHandler = debounce(
          () => {
            if (this.lastArea === 0) {
              this.mergeOptions({}, true)
              this.resize()
              this.mergeOptions(this.option || this.manualOptions || {}, true)
            } else {
              this.resize()
            }
            this.lastArea = this.getArea()
          },
          100,
          {leading: true}
        )
        addListener(this.$el, this.__resizeHandler)
      }
      Object.defineProperties(this, {
        width: {
          configurable: true,
          get: () => {
            return this.delegateGet('getWidth')
          },
        },
        height: {
          configurable: true,
          get: () => {
            return this.delegateGet('getHeight')
          },
        },
        isDisposed: {
          configurable: true,
          get: () => {
            return !!this.delegateGet('isDisposed')
          },
        },
        computedOptions: {
          configurable: true,
          get: () => {
            return this.delegateGet('getOption')
          },
        },
      })
      this.chart = chart
    },
    initOptionsWatcher() {
      if (this.__unwatchOptions) {
        this.__unwatchOptions()
        this.__unwatchOptions = null
      }
      if (!this.manualUpdate) {
        this.__unwatchOptions = this.$watch(
          'option',
          (val, oldVal) => {
            if (!this.chart && val) {
              this.init()
            } else {
              this.chart.setOption(val, val !== oldVal)
            }
          },
          {deep: !this.watchShallow}
        )
      }
    },
    destroy() {
      if (this.autoResize) {
        removeListener(this.$el, this.__resizeHandler)
      }
      this.dispose()
      this.chart = null
    },
    refresh() {
      if (this.chart) {
        this.destroy()
        this.init()
      }
    },
  },
  connect(group) {
    if (typeof group !== 'string') {
      group = group.map((chart) => chart.chart)
    }
    echarts.connect(group)
  },
  disconnect(group) {
    echarts.disConnect(group)
  },
  getMap(mapName) {
    return echarts.getMap(mapName)
  },
  registerMap(mapName, geoJSON, specialAreas) {
    echarts.registerMap(mapName, geoJSON, specialAreas)
  },
  graphic: echarts.graphic,
}
</script>
<style lang="scss">
</style>

找到相关的地图选择器,如阿里的

或者使用我个人的cdn上的数据,前缀为https://cdn.allbs.cn/pluginsSrc/geo/

image-20221008155711475

示例,默认前缀+上方显示的几个json文件则为世界或者全国的边界数据。前缀+province+当前省的地址码则为省份相关的边界数据。前缀+city+当前市的地址码则为市级相关的边界数据。

  • 全国地图边界数据,则url为https://cdn.allbs.cn/pluginsSrc/geo/china.json

  • 江苏省数据url则为https://cdn.allbs.cn/pluginsSrc/geo/province/320000.json

  • 南京市数据url则为https://cdn.allbs.cn/pluginsSrc/geo/city/320100.json

下载地图相关的json数据,province中保存的是省一级的数据,city中保存的是市一级的数据

image-20221008154546403

实际使用(下面的两种示例不是根据省市区分的,只是两种写法,里面包含一些echarts-gl的用法的区别)

省份

<template>
	<geo-chart
            class="jp-chart e-h-600"
            :init-options="initOptions"
            :option="mapOption"
            ref="mapChartRef"
            :pro-city="proCity"
            :level="level"
          />
</template>
<script>
import GeoChart from "@/extra/GeoChart";
export default {
  name: "index",
  props: {
    proCity: {
      type: String,
      default: ''
    },
    proCityName: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      pieChartOption: {},
      hxtUseChartOption: {},
      phChartOption: {},
      mapOption: {},
      level: 'province',
      geoJson: {},
      dqData: [
        {'name': '南京市', 'value': 66},
        {'name': '镇江市', 'value': 77},
        {'name': '宿迁市', 'value': 22},
        {'name': '盐城市', 'value': 45},
        {'name': '苏州市', 'value': 50},
      ]
    }
  },
  components: {GeoChart},
  mounted: function () {
  },
  watch: {
    proCity() {
      this.initMapGeo();
    }
  },
  created() {
    this.initMapGeo();
  },
  computed: {
  },
  methods: {
    initMapGeo() {
      let geoJson = require(`@/geo/${this.level}/${this.proCity}.json`)
      const provinceCenter = new Map()
      geoJson.features.forEach((province) => {
        provinceCenter.set(province.properties.name, province.properties.cp)
      })
      const barData = this.dqData.map((item) => {
        return {
          name: item.name,
          value: [...provinceCenter.get(item.name), item.value]
        }
      })
      this.mapOption = {
        title: {
          text: this.proCityName,
          x: 'left',
          top: "20",
          textStyle: {
            color: '#000',
            fontSize: 24
          }
        },
        tooltip: {
          show: true,
          formatter: (params) => {
            return "" + params.name + "接入情况<br/>" + "企业数: " + params.value[2] + "家<br/>";
          },
        },
        geo3D: {
          map: 'china',
          itemStyle: {
            color: '#4887f6',
            areaColor: '#48D9FF',
            opacity: 1,
            borderWidth: 0.8,
            borderColor: '#ffffff'
          },
          label: {                // 标签的相关设置
            show: true,                 // (地图上的城市名称)是否显示标签 [ default: false ]
            textStyle: {                // 标签的字体样式
              color: '#fff',                  // 地图初始化区域字体颜色
              fontSize: 12,                    // 字体大小
              opacity: 1,                     // 字体透明度
              backgroundColor: 'rgba(0,23,11,0)',     // 字体背景色
              fontWeight: 'bold'
            },
          },
          light: { //光照阴影
            main: {
              color: '#fff', //光照颜色
              intensity: 1.2, //光照强度
              //shadowQuality: 'high', //阴影亮度
              shadow: true, //是否显示阴影
              alpha: 35,
              beta: 10

            },
            ambient: {
              intensity: 0.3
            }
          },
          tooltip: { //提示框组件。
            alwaysShowContent: true,
            hoverAnimation: true,
            trigger: 'item', //触发类型 散点图
            enterable: true, //鼠标是否可进入提示框
            transitionDuration: 1, //提示框移动动画过渡时间
            triggerOn: 'click',
            borderWidth: '1px',
            borderRadius: '4',
            borderColor: 'rgb(255,0,0)',
            textStyle: {
              color: 'rgb(255,0,0)'
            },
            padding: [5, 10]
          },
        },
        series: {
          type: 'bar3D',
          coordinateSystem: 'geo3D',
          itemStyle: {
            color: '#ff0202'
          },
          // 倒角尺寸
          bevelSize: 0.5,
          bevelSmoothness: 20,
          data: barData,
          minHeight: 0.2,
          barSize: 2,
          emphasis: {
            label: {
              show: true,
              formatter: (param) => {
                return param.name + ' : ' + param.value[2] + '家'
              },
              distance: 1,
              textStyle: {
                fontWeight: 'bold',
                fontSize: 18,
                color: '#ff0202'
              }
            },
          },
          animation: true,
          animationDurationUpdate: 2000
        }
      }
    },
  }
}
</script>

<template>
<vab-chart
           class="jp-chart e-h-350"
           :init-options="initOptions"
           :option="phChartOption"
           ref="phChartRef"
           theme="vab-echarts-theme"
           />
</template>

<script>
import GeoChart from "@/extra/GeoChart";

export default {
  name: "index",
  props: {
    proCity: {
      type: String,
      default: ''
    },
    proCityName: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      initOptions: {
        renderer: 'svg',
      },
      mapOption: {},
      level: 'city',
    }
  },
  components: {GeoChart},
  mounted: function () {
  },
  created() {
    this.initMapGeo();
  },
  computed: {},
  watch: {
    proCity() {
      this.initMapGeo();
    }
  },
  methods: {
    initMapGeo() {
      this.mapOption = {
        // backgroundColor: "#220392",
        title: {
          text: this.proCityName,
          x: 'left',
          top: "20",
          textStyle: {
            color: '#000',
            fontSize: 24
          }
        },
        tooltip: {
          show: true,
        },
        geo3D: {
          map: 'china',
          // roam: true,
          itemStyle: {
            color: '#4887f6',
            areaColor: '#48D9FF',
            opacity: 1,
            borderWidth: 0.8,
            borderColor: '#ffffff'
          },
          label: {                // 标签的相关设置
            show: true,                 // (地图上的城市名称)是否显示标签 [ default: false ]
            textStyle: {                // 标签的字体样式
              color: '#ffffff',                  // 地图初始化区域字体颜色
              fontSize: 12,                    // 字体大小
              opacity: 1,                     // 字体透明度
              backgroundColor: 'rgba(0,23,11,0)',     // 字体背景色
              fontWeight: 'bold'
            },
          },
          //shading: 'lambert',
          light: { //光照阴影
            main: {
              color: '#fff', //光照颜色
              intensity: 1.2, //光照强度
              //shadowQuality: 'high', //阴影亮度
              shadow: true, //是否显示阴影
              alpha: 35,
              beta: 10

            },
            ambient: {
              intensity: 0.3
            }
          },

        },
        series: [
          {
            //配置路径
            type: 'lines3D',
            coordinateSystem: 'geo3D',
            polyline: 'true',
            blendMode: 'lighter',
            zlevel: 102,
            effect: {
              show: true,
              trailWidth: 3,
              trailOpacity: 0.5,
              trailLength: 0.2,
              constantSpeed: 5
            },
            label: {                // 标签的相关设置
              show: true,                 // (地图上的城市名称)是否显示标签 [ default: false ]
              //distance: 50,               // 标签距离图形的距离,在三维的散点图中这个距离是屏幕空间的像素值,其它图中这个距离是相对的三维距离
              //formatter:,               // 标签内容格式器
              textStyle: {                // 标签的字体样式
                color: '#000',                  // 地图初始化区域字体颜色
                fontSize: 8,                    // 字体大小
                opacity: 1,                     // 字体透明度
                backgroundColor: 'rgba(0,23,11,0)'      // 字体背景色
              },
            },
            lineStyle: {
              color: '#FFB728',
              opacity: 0.8,
              width: 1.5
            },
            data: [
              {
                coords: [[113.149649, 22.617641], [112.88089, 22.583612]],
                // 数据值
                value: 100,
                // 数据名
                name: '测试一',
                // 线条样式
                lineStyle: {},
              }, {
                coords: [[112.316858, 22.186088], [112.88089, 22.583612]],
                // 数据值
                value: 100,
                // 数据名
                name: '测试二',
                // 线条样式
                lineStyle: {}
              }
            ]
          },
        ]
      }
    }
  }
}
</script>

<style scoped>
</style>

效果