前言
- 需求:在平台中能够展示用户所在地区的行政区域图,点击每一个下级区域后显示其相关信息。
- 技术:vue3、vite、echarts
准备
- 安装echarts
npm install echarts - 安装echarts-gl
npm install echarts-gl - 获取行政区域下所有下级地区的边框经纬度数据。JSON数据格式参考:datav.aliyun.com/portal/scho…
实现
首先,我们要做的是先去根据数据区绘制3d区域地图,给地图设置交互效果。
交互效果
F市下的行政区域有A区、B区、C区、D区、E区,设定最多可以选中1个区域。用户开始选中A区后再选择B区,这时候选中区域数量已经超过最大数量,所以会先取消上次选中的区域(A区),在选中B区。
绘制地图
◇ 配置地图options
在utils下创建setOptions.js文件。文件内容如下:
export async function set3dMapOptions_backstage(opts) {
let { mapData, regions, color, formatterCallback } = Object.assign(
{
color: "#0fd2aa",//地图区域颜色
regions: [],//自定义某区域颜色
formatterCallback: null,//划入区域后的回调
},
opts || {})
//地图根据地区进行个性化配置
const mapStatic = await getMapDate();
let bottoms = mapStatic?.bottom;
let lefts = mapStatic?.left;
let distances = mapStatic?.zoomDistance;
let alphas = mapStatic?.rotationAngle;
let options = {
series: [
{
type: "map3D",
map: "zhejiang",
name: "3D地图",
data: regions.length != 0 ? regions : mapData.features.map((region, index) => ({
name: region.properties.name,
itemStyle: {
color: colors[index % 5],
}
})),
label: {
show: true, //是否显示高亮
distance: 0,
textStyle: {
color: '#000',
fontSize: 14,
}
},
light: {
// 配置光照
main: {
intensity: 1, // 光照强度
shadow: true, // 是否开启阴影
alpha: 50, // 俯视角度
beta: 50 // 水平角度
}
},
bottom: bottoms, //地图距离底部
left: lefts, //地图距离左侧
itemStyle: {
color: color,//regions优先级高于color
opacity: 1,
borderWidth: 1,
borderColor: '#a88c98'
},
viewControl: {
rotateSensitivity: 1,
zoomSensitivity: 1,
panSensitivity: 1,
distance: distances, // 缩放距离
alpha: alphas // 旋转角度
},
emphasis: {
disabled: true,
label: {
show: true,
distance: 0,
textStyle: {
color: '#fff',
},
formatter: (params) => {
if (formatterCallback) formatterCallback(params.name);
return params.name;
},
},
itemStyle: {
color: "#40a1fd", // 高亮时地图板块颜色改变
},
},
},
],
};
return options;
}
◇ 绘制行政区域图
创建echarts3DAreaMap.vue文件,文件内容如下:
为地图准备一个定义了宽高的容器。
<div class="echarts3DAreaMap">
<div class="map-chart" id="mapEchart" style="height: 100%; width: 100%"></div>
</div>
初始化echarts实例,根据options生成一个3d地图。
<script setup>
import { ref, onMounted, watch } from "vue";
import * as echarts from "echarts";
import "echarts-gl";
const props = defineProps({
//地图配置项
options: Object,
//地图数据
mapData: Object,
//最大选择区域数量
maxClick: {
type: Number,
default: 1,
},
//地图是否可点击
clickable: {
type: Boolean,
default: true,
},
});
const emit = defineEmits(["areaCancelSelect", "areaSelect"]);
let myChart = null; //chart节点对象
/**初始化3d地图*/
function chartMapInit() {
myChart = echarts.init(document.getElementById("mapEchart"));
echarts.registerMap("zhejiang", props.mapData);
myChart.hideLoading();
myChart.setOption(props.options);
//绑定点击事件
myChart.on('click', (e) => {
myChartClick(e.data.name, e)
})
}
/**选中的区域名称列表[string]*/
let selectAreaList = [];
/**
* 处理图表点击事件的函数
* @param {string} name - 被点击区域的名称
* @param {Object} e - 点击事件对象
*/
function myChartClick(name, e) {
if (props.clickable) {
let index = selectAreaList.findIndex((item) => item == name);
if (index == -1) {
areaSelect(name, e);
} else {
areaCancelSelect(index, name);
}
} else {
emit("areaSelect");
}
}
/**
* 区域选择函数
* @param {string} name - 要选择的区域的名称
* @param {Event} e - 触发的事件对象
*/
function areaSelect(name, e) {
if (selectAreaList.length < props.maxClick || props.maxClick == 0) {
selectAreaList.push(name);
setStyle_active(name, e)
} else if (selectAreaList.length >= props.maxClickAreaNum) {
areaCancelSelect(0, selectAreaList[0])
selectAreaList.push(name);
setStyle_active(name, e)
}
}
let seriesData = [...props.options.series[0].data]
let oldStyle = {
name: null,
itemStyle: {
color: "#40a1fd",
},
height: 3,
};
let newObj = {
name: null,
label: {
textStyle: {
color: '#fff',
},
},
itemStyle: {
color: "#40a1fd",
},
height: 5,
};
/**
* 设置区域选中颜色函数
* @param {string} name - 要选择的区域的名称
* @param {Event} e - 触发的事件对象
*/
function setStyle_active(name, e) {
newObj.name = name
let obj = JSON.parse(JSON.stringify(newObj))
let i = seriesData.findIndex(
(item) => item.name == name,
);
if (i != -1) {
oldStyle = { ...props.options.series[0].data[i] }
seriesData[i] = obj
} else {
oldStyle = { name: name, itemStyle: {}, height: 3 }
seriesData.push(obj)
}
props.options.series[0].data = JSON.parse(JSON.stringify(seriesData))
myChart.setOption(props.options);
emit("areaSelect", e, selectAreaList);
}
/**
* 区域取消选中
* 当用户取消选择某个区域时调用此函数
* @param {number} index - 被取消选中的区域在选中区域列表中的索引
* @param {string} areaName - 被取消选中的区域的名称
*/
function areaCancelSelect(index, areaName) {
selectAreaList.splice(index, 1);
cancelSelectAfter(areaName)
}
/**
* 设置区域取消选中颜色函数
* @param {string} areaName - 区域的名称
*/
function setStyle_active(name, e) {
let i = seriesData.findIndex(
(item) => item.name == areaName,
);
let { name, itemStyle, height } = seriesData[i];
oldStyle.name = areaName
let obj = JSON.parse(JSON.stringify(oldStyle))
seriesData[i] = obj
props.options.series[0].data = JSON.parse(JSON.stringify(seriesData))
myChart.setOption(props.options);
emit("areaCancelSelect", name, props.options);
}
/**窗口变化时修改地图大小*/
window.addEventListener("resize", () => {
myChart.resize();
});
onMounted(() => {
chartMapInit();
});
defineExpose({
myChartClick,
});
</script>
引入地图字组件。
获取完地图边框数据后,需要将边框数据以及鼠标移入区域后的回调作为参数调用set3dMapOptions_backstage方法,拿到返回的地图配置项并展示地图。
<template>
<div class="regionalComparison-map">
<Echarts3DAreaMap ref="echarts3DAreaMapRef" v-if="isShowMap" :mapData="mapData" :options="mapOptions" :maxClickAreaNum="2" @areaCancelSelect="areaCancelSelect" @areaSelect="areaSelect"></Echarts3DAreaMap>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import {
set3dMapOptions_backstage,
} from "@/utils/setOptions";
/**是否显示地图子组件*/
const isShowMap = ref(false);
/**地图子组件ref*/
const echarts3DAreaMapRef = ref(null);
/**地图边框数据*/
let mapData = ref(null);
/**获取完地图边框数据后,需要将边框数据以及鼠标移入区域后的回调作为参数调用set3dMapOptions_backstage方法,拿到返回的地图配置项并展示地图*/
/**区域选中*/
function areaSelect(data){
}
/**区域取消选中*/
function areaCancelSelect(name){
}
/**鼠标移入区域后的回到函数*/
function labelformatter(name) {
}
</script>
通过上面的代码,已经实现了地图的绘制和交互,下面就是怎么实现区域选中后,在用户点击的位置上显示区域信息。这里将通过css样式来实现这一功能。
◇ 区域信息
首先,创建一个展示区域信息的子组件areaInfo.vue,内容如下:
<template>
<div class="detail-model" :style="styles">内容</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
const props = defineProps({
styles: {
type: Object,
default: {},
},
contentStyles: {
type: Object,
default: {},
},
});
</script>
<style lang="scss" scoped>
.detail-model {
width: 550px;
height: 600px;
position: absolute;
display: flex;
z-index: 999;
.content {
width: 80%;
background: url("@/assets/screen/mapDialogBg.png") no-repeat;
background-size: 100% 100%;
}
.line-box {
width: 110px;
height: 100%;
.children {
position: absolute;
bottom: 0;
}
.line1 {
height: 1px;
width: 155px;
background-color: #fb7293;
transform-origin: 0% 0%;
transform: rotate(-45deg);
}
}
}
</style>
父组件中引入区域信息子组件,并在根据offsetX、offsetY这两个数值来设置区域信息子组件得样式,内容如下:
<template>
<AreaFarmerDetail v-if="isShowDialog" :styles="styles" :contentStyles="contentStyles" @closeModel="closeDetailModel"></AreaFarmerDetail>
</template>
<script setup>
import { ref } from "vue";
function areaSelect(e, data) {
isShowDialog.value=true
styles.value.left = e.event.offsetX + 30 + "px";
styles.value.top = e.event.offsetY - 490 + "px";
//解决区域信息因遮盖导致显示不全问题
if (e.event.offsetY < 430) {
contentStyles.value.top = 600 - e.event.offsetY + 'px';
} else {
contentStyles.value.top = 0 + 'px';
}
}
</style>
到此就结束了,下面是功能的效果图: