Vue3 中国地图

效果图:

html 复制代码
<template>
  <div style="width: 100%; height: 100%; background-color: #000">
    <div style="width: 200px">
      <el-select v-model="mapRegion.adcode" placeholder="请选择">
        <el-option
          v-for="item in mapRegionOptions"
          :key="item.adcode"
          :label="item.name"
          :value="item.adcode"
        >
        </el-option>
      </el-select>
    </div>
    <v-chart ref="vChartRef" :option="option"> </v-chart>
  </div>
</template>

<script setup lang="ts">
import { watch, ref, nextTick, computed, reactive } from "vue";
import VChart from "vue-echarts";
import { use, registerMap } from "echarts/core";
import { EffectScatterChart, MapChart } from "echarts/charts";
import { CanvasRenderer } from "echarts/renderers";
import mapJsonWithoutHainanIsLands from "./mapWithoutHainanIsLands/mapWithoutHainanIsLands.json";
import {
  DatasetComponent,
  GridComponent,
  TooltipComponent,
  GeoComponent,
  VisualMapComponent,
} from "echarts/components";
import cloneDeep from "lodash/cloneDeep";
import { setOption } from "./chart";
import mapChinaJson from "./mapGeojson/china.json";

use([
  MapChart,
  DatasetComponent,
  CanvasRenderer,
  GridComponent,
  TooltipComponent,
  GeoComponent,
  EffectScatterChart,
  VisualMapComponent,
]);

/**    下拉框    **/
const mapRegionOptions = ref([
  {
    adcode: "china",
    name: "中国",
  },
]);

const mapRegion = computed(() => {
  return option.mapRegion;
});

const initMapRegionOptions = () => {
  mapChinaJson.features.forEach((element: any) => {
    if (element.properties.name) {
      mapRegionOptions.value.push({ ...element.properties });
    }
  });
};
initMapRegionOptions();

/**    地图    **/
const vChartRef = ref<typeof VChart>();
//动态获取json注册地图
const getGeojson = (regionId: string) => {
  return new Promise<boolean>((resolve) => {
    import(`./mapGeojson/${regionId}.json`).then((data) => {
      registerMap(regionId, { geoJSON: data.default as any, specialAreas: {} });
      resolve(true);
    });
  });
};

// 数据源
const chartData = ref({
  point: [
    {
      name: "北京",
      value: [116.405285, 39.904989, 200],
    },
    {
      name: "郑州",
      value: [113.665412, 34.757975, 888],
    },
    {
      name: "青海",
      value: [101.778916, 36.623178, 666],
    },
    {
      name: "宁夏回族自治区",
      value: [106.278179, 38.46637, 66],
    },
    {
      name: "哈尔滨市",
      value: [126.642464, 45.756967, 101],
    },
  ],
  map: [
    {
      name: "北京市",
      value: 666,
    },
    {
      name: "河北省",
      value: 98,
    },
    {
      name: "江苏省",
      value: 300,
    },
    {
      name: "福建省",
      value: 1199,
    },
    {
      name: "山东省",
      value: 86,
    },
    {
      name: "河南省",
      value: 850,
    },
    {
      name: "湖北省",
      value: 84,
    },
    {
      name: "广西壮族自治区",
      value: 81,
    },
    {
      name: "海南省",
      value: 900,
    },
    {
      name: "青海省",
      value: 800,
    },
    {
      name: "新疆维吾尔自治区",
      value: 7,
    },
  ],
  pieces: [
    {
      gte: 1000,
      label: ">1000",
    },
    {
      gte: 600,
      lte: 999,
      label: "600-999",
    },
    {
      gte: 200,
      lte: 599,
      label: "200-599",
    },
    {
      gte: 50,
      lte: 199,
      label: "49-199",
    },
    {
      gte: 10,
      lte: 49,
      label: "10-49",
    },
    {
      lte: 9,
      label: "<9",
    },
  ],
});

const seriesItem1 = reactive({
  type: "effectScatter",
  coordinateSystem: "geo",
  symbolSize: 4,
  legendHoverLink: true,
  showEffectOn: "render",
  rippleEffect: {
    scale: 6,
    color: "#FFFFFF",
    brushType: "fill",
  },
  tooltip: {
    show: true,
    backgroundColor: "rgba(0,0,0,.6)",
    borderColor: "rgba(147, 235, 248, .8)",
    textStyle: {
      color: "#FFF",
    },
  },
  label: {
    formatter: "{b}",
    fontSize: 11,
    offset: [0, 2],
    position: "bottom",
    textBorderColor: "#fff",
    textShadowColor: "#000",
    textShadowBlur: 10,
    textBorderWidth: 0,
    color: "#FFFFFF",
    show: true,
  },
  itemStyle: {
    color: "#FFFFFF",
    borderColor: "rgba(225,255,255,2)",
    borderWidth: 4,
    shadowColor: "#E1FFFF",
    shadowBlur: 10,
  },
  data: [],
});

const seriesItem2 = reactive({
  name: "区域",
  type: "map",
  map: "china",
  data: [],
  selectedMode: false,
  zoom: 1,
  geoIndex: 1,
  tooltip: {
    show: true,
    backgroundColor: "#00000060",
    borderColor: "rgba(147, 235, 248, 0.8)",
    textStyle: {
      color: "#FFFFFF",
      fontSize: 12,
    },
  },
  label: {
    show: false,
    color: "#FFFFFF",
    fontSize: 12,
  },
  emphasis: {
    disabled: false,
    label: {
      color: "#FFFFFF",
      fontSize: 12,
    },
    itemStyle: {
      areaColor: "#389BB7",
      shadowColor: "#389BB7",
      borderWidth: 1,
    },
  },
  itemStyle: {
    borderColor: "#93EBF8",
    borderWidth: 1,
    areaColor: {
      type: "radial",
      x: 0.5,
      y: 0.5,
      r: 0.8,
      colorStops: [
        {
          offset: 0,
          color: "#93ebf800", // 0% 处的颜色
        },
        {
          offset: 1,
          color: "#93ebf820", // 100% 处的颜色
        },
      ],
      globalCoord: false,
    },
    shadowColor: "#80D9F842",
    shadowOffsetX: -2,
    shadowOffsetY: 2,
    shadowBlur: 10,
  },
});
const getSeries = (values: typeof chartData.value) => {
  let series: any = [];
  if (!values) return [];
  // 不在这里修改 option,而是返回配置
  let item1: any = cloneDeep(seriesItem1);
  item1.data = values.point;
  let item2: any = cloneDeep(seriesItem2);
  item2.data = values.map;
  series.push(item1, item2);
  return series;
};
const updateSeries = () => {
  option.series = getSeries(chartData.value);
  // 如果需要更新 visualMap.pieces,也可以在这里处理
  option.visualMap.pieces = chartData.value.pieces;
};

const option = reactive({
  mapRegion: {
    adcode: "china",
    showHainanIsLands: true,
    enter: false,
    backSize: 20,
    backColor: "#ffffff",
  },
  tooltip: {
    show: true,
    trigger: "item",
  },
  visualMap: {
    show: true,
    orient: "vertical",
    pieces: [
      { gte: 1000, label: ">1000" },
      { gte: 600, lte: 999, label: "600-999" },
      { gte: 200, lte: 599, label: "200-599" },
      { gte: 50, lte: 199, label: "49-199" },
      { gte: 10, lte: 49, label: "10-49" },
      { lte: 9, label: "<9" },
    ],
    inRange: {
      color: ["#c3d7df", "#5cb3cc", "#8abcd1", "#66a9c9", "#2f90b9", "#1781b5"],
    },
    textStyle: {
      color: "#fff",
    },
  },
  geo: {
    show: false,
    type: "map",
    roam: false,
    map: "china",
    selectedMode: false,
    zoom: 1,
  },
  series: getSeries(chartData.value),
});

//异步时先注册空的 保证初始化不报错
registerMap(`${option.mapRegion.adcode}`, {
  geoJSON: {} as any,
  specialAreas: {},
});

// 进行更换初始化地图 如果为china 单独处理
const registerMapInitAsync = async () => {
  await nextTick();
  const adCode = `${option.mapRegion.adcode}`;
  if (adCode !== "china") {
    await getGeojson(adCode);
  } else {
    await hainanLandsHandle(option.mapRegion.showHainanIsLands);
  }
  vEchartsSetOption();
};
registerMapInitAsync();

// 手动触发渲染
const vEchartsSetOption = () => {
  setOption(vChartRef.value, option);
};

// 处理海南群岛
const hainanLandsHandle = async (newData: boolean) => {
  if (newData) {
    await getGeojson("china");
  } else {
    registerMap("china", {
      geoJSON: mapJsonWithoutHainanIsLands as any,
      specialAreas: {},
    });
  }
};

// 切换地图
const checkOrMap = async (newData: string) => {
  await getGeojson(newData);
  option.geo.map = newData;
  option.series.forEach((item: any) => {
    if (item.type === "map") item.map = newData;
  });
  vEchartsSetOption();
};

//监听是否显示南海群岛
watch(
  () => option.mapRegion.showHainanIsLands,
  async (newData) => {
    try {
      await hainanLandsHandle(newData);
      vEchartsSetOption();
    } catch (error: any) {
      throw new Error(error);
    }
  },
  {
    deep: false,
  }
);

//监听地图展示区域发生变化
watch(
  () => `${option.mapRegion.adcode}`,
  async (newData) => {
    try {
      checkOrMap(newData);
    } catch (error: any) {
      throw Error(error);
    }
  },
  {
    deep: false,
  }
);

// 监听 chartData 变化并更新图表
watch(
  () => chartData.value,
  async () => {
    updateSeries();
    await nextTick();
    vEchartsSetOption();
  },
  { deep: true }
);
</script>
TypeScript 复制代码
import type VChart from 'vue-echarts'

// 定义包含chart属性的类型
type ChartInstance = {
  chart: echarts.ECharts | null
} & (typeof VChart | undefined)

// 解决ts报错问题
type ChartOptionFun = <D>(instance: ChartInstance, data: D) => void

/**
 * * 配置公共 setOption 方法
 * @param instance
 * @param data
 */
export const setOption: ChartOptionFun = <D>(instance: ChartInstance, data: D) => {
  if (!instance?.chart) return
  const option = instance.chart.getOption()
  if (option) {
    option.apiData = null
  }
  instance.chart.setOption(data)
}
相关推荐
四谎真好看20 分钟前
JavaWeb 学习笔记(Day02)之Vue
笔记·学习·vue·学习笔记·javaweb
Sapphire~21 分钟前
Vue3-04 自定义组件Person
vue
林恒smileZAZ21 分钟前
【Vue3】我用 Vue 封装了个 ECharts Hooks
前端·vue.js·echarts
沐墨染1 小时前
大型数据分析组件前端实践:多维度检索与实时交互设计
前端·elementui·数据挖掘·数据分析·vue·交互
@AfeiyuO15 小时前
Vue3 高德地图
vue·echarts
sanra12315 小时前
前端定位相关技巧
前端·vue
Sun_小杰杰哇16 小时前
Dayjs常用操作使用
开发语言·前端·javascript·typescript·vue·reactjs·anti-design-vue
小林攻城狮20 小时前
echarts 参考线实现数据项之间的差异值标注
前端·echarts
座山雕~1 天前
VUE 3
vue