<template>
|
<!-- 地图容器 -->
|
<div class="map-container" :class="{ loading: isLoading }">
|
<!-- echarts地图渲染区域 -->
|
<div class="chart" ref="myMap" />
|
<!-- 新增加载提示 -->
|
<div v-if="isLoading" class="loading-tip">地图加载中...</div>
|
</div>
|
</template>
|
|
<script>
|
// 导入防抖函数
|
import _ from 'lodash';
|
|
// 导入地图相关的背景和图标资源
|
import mapBg from "@/assets/map/mapBg.png"; // 地图背景图片
|
import tooltipBg from "@/assets/map/tooltipBg.png"; // 提示框背景图片
|
import tooltipBlue from "@/assets/map/tooltipBlue.png"; // 蓝色标记图标
|
import tooltipOrange from "@/assets/map/tooltipOrange.png"; // 橙色标记图标
|
|
// 定义地图常量配置
|
const MAP_CONSTANTS = {
|
// 阴影相关配置
|
SHADOW: {
|
COLOR: "#00eaff",
|
BLUR: 25,
|
OFFSET: 0
|
},
|
// 标记点相关配置
|
MARKER: {
|
SIZE: [20, 30],
|
OFFSET: [0, -15],
|
Z_INDEX: 20,
|
COLORS: {
|
BLUE: '#00eaff',
|
ORANGE: '#ff8e3a'
|
}
|
},
|
// 提示框相关配置
|
TOOLTIP: {
|
OFFSET: [-100, -160],
|
WIDTH: '211px',
|
FONT_FAMILY: 'pangmenzhengdao'
|
},
|
// 地图图层配置
|
LAYERS: {
|
1: { // 最底层
|
top: "15%",
|
bottom: "10%",
|
borderColor: "#00A0F5",
|
borderWidth: 1
|
},
|
2: { // 中间层
|
top: "16%",
|
bottom: "9%",
|
borderColor: "#0C93C5",
|
borderWidth: 1
|
},
|
3: { // 最顶层
|
top: "17%",
|
bottom: "8%",
|
borderColor: "#0A5C83",
|
borderWidth: 2
|
}
|
}
|
};
|
|
export default {
|
name: 'Map',
|
// 接收父组件传递的数据
|
props: {
|
data: {
|
type: Object,
|
default: () => ({}),
|
// 新增:数据格式验证
|
validator(value) {
|
if (!value.mapResponses) return true;
|
return Array.isArray(value.mapResponses) &&
|
value.mapResponses.every(item =>
|
item.street &&
|
typeof item.householdCount === 'number' &&
|
typeof item.personCount === 'number'
|
);
|
}
|
}
|
},
|
data() {
|
return {
|
mapBg: null, // 地图背景图片实例
|
mapPoints: [], // 存储处理后的地图标记点数据
|
chart: null, // echarts实例
|
isLoading: false, // 新增:加载状态标志
|
errorMessage: '' // 新增:错误信息
|
}
|
},
|
|
// 新增:计算属性
|
computed: {
|
// 检查数据是否有效
|
hasValidData() {
|
return this.data && this.data.mapResponses && this.data.mapResponses.length > 0;
|
}
|
},
|
|
mounted() {
|
// 组件挂载后初始化地图
|
this.initEventListeners(); // 新增:初始化事件监听
|
this.$nextTick(() => {
|
if (this.hasValidData) {
|
this.initMap();
|
}
|
});
|
},
|
methods: {
|
// 新增:初始化事件监听器
|
initEventListeners() {
|
window.addEventListener('resize', this.handleResize);
|
// 新增:错误监听
|
window.addEventListener('error', this.handleGlobalError, true);
|
},
|
|
// 新增:全局错误处理
|
handleGlobalError(event) {
|
if (event.target.tagName === 'SCRIPT') {
|
this.handleError(new Error('地图资源加载失败'), '资源加载');
|
}
|
},
|
|
// 统一的错误处理方法
|
handleError(error, context) {
|
console.error(`地图组件错误 - ${context}:`, error);
|
this.errorMessage = `加载失败: ${error.message}`;
|
this.isLoading = false;
|
// 可以添加错误上报逻辑
|
},
|
|
// 处理窗口大小变化,使用防抖优化
|
handleResize: _.debounce(function () {
|
if (this.chart) {
|
this.chart.resize();
|
}
|
}, 300),
|
|
// 处理地图数据,将原始数据转换为echarts可用的格式
|
processMapData() {
|
if (!this.data.mapResponses) return;
|
|
// 获取地图JSON数据
|
const mapJson = require("@/utils/map/chongzhou.json");
|
|
// 遍历处理每个地点数据
|
this.mapPoints = this.data.mapResponses.map(point => {
|
// 在地图JSON中查找对应的地理信息
|
const feature = mapJson.features.find(f => f.properties.name === point.street);
|
|
if (!feature) {
|
console.warn(`未找到街道: ${point.street} 的坐标`);
|
return null;
|
}
|
|
// 判断是街道还是乡镇,用于显示不同的图标
|
const isStreet = point.street.includes('街道');
|
|
// 获取中心点坐标
|
let center;
|
if (feature.properties.center) {
|
// 优先使用预设的中心点坐标
|
center = feature.properties.center;
|
} else if (feature.geometry && feature.geometry.coordinates) {
|
// 否则根据地理数据计算中心点
|
if (feature.geometry.type === 'Polygon') {
|
// 处理单个多边形
|
const coordinates = feature.geometry.coordinates[0];
|
center = this.calculateCenter(coordinates);
|
} else if (feature.geometry.type === 'MultiPolygon') {
|
// 处理多个多边形
|
const coordinates = feature.geometry.coordinates[0][0];
|
center = this.calculateCenter(coordinates);
|
}
|
}
|
|
if (!center) {
|
console.warn(`无法获取 ${point.street} 的中心点坐标`);
|
return null;
|
}
|
|
// 返回处理后的标记点数据
|
return {
|
name: point.street,
|
value: center,
|
type: isStreet ? 'orange' : 'blue', // 街道使用橙色,乡镇使用蓝色
|
symbolSize: 32,
|
householdCount: point.householdCount, // 安置户数
|
personCount: point.personCount // 安置人数
|
};
|
}).filter(Boolean); // 过滤掉无效的数据
|
},
|
|
// 计算多边形的中心点坐标
|
calculateCenter(coordinates) {
|
if (!coordinates || coordinates.length === 0) return null;
|
|
// 计算所有顶点的平均值作为中心点
|
let sumX = 0;
|
let sumY = 0;
|
let count = coordinates.length;
|
|
coordinates.forEach(coord => {
|
sumX += coord[0];
|
sumY += coord[1];
|
});
|
|
return [sumX / count, sumY / count];
|
},
|
|
// 新增:检查地图实例状态
|
checkChartStatus() {
|
if (!this.chart) {
|
throw new Error('地图实例未初始化');
|
}
|
return true;
|
},
|
|
// 初始化地图方法优化
|
initMap: _.debounce(async function () {
|
try {
|
if (!this.$refs.myMap) {
|
throw new Error('地图DOM元素未准备好');
|
}
|
|
this.isLoading = true;
|
this.errorMessage = '';
|
|
// 新增:等待资源加载
|
await this.loadMapResources();
|
|
this.processMapData();
|
|
if (this.chart) {
|
this.chart.dispose();
|
}
|
|
this.chart = this.$echarts.init(this.$refs.myMap);
|
this.chart.showLoading();
|
|
const mapJson = require("@/utils/map/chongzhou.json");
|
this.$echarts.registerMap("myMap", mapJson);
|
|
// 设置配置项
|
this.chart.setOption(this.getMapOption());
|
this.chart.hideLoading();
|
|
this.isLoading = false;
|
} catch (error) {
|
this.handleError(error, '初始化地图');
|
}
|
}, 300),
|
|
// 新增:加载地图资源
|
async loadMapResources() {
|
try {
|
const mapImage = new Image();
|
mapImage.src = mapBg;
|
await new Promise((resolve, reject) => {
|
mapImage.onload = resolve;
|
mapImage.onerror = reject;
|
});
|
this.mapBg = mapImage;
|
} catch (error) {
|
throw new Error('地图背景图片加载失败');
|
}
|
},
|
|
// 生成标记点系列的配置
|
getMarkerSeries(type) {
|
return {
|
name: type === "blue" ? "蓝色标记" : "橙色标记",
|
type: "scatter",
|
coordinateSystem: "geo",
|
geoIndex: 0,
|
data: this.mapPoints.filter(point => point.type === type),
|
symbol: `image://${type === "blue" ? tooltipBlue : tooltipOrange}`,
|
symbolSize: MAP_CONSTANTS.MARKER.SIZE,
|
symbolOffset: MAP_CONSTANTS.MARKER.OFFSET,
|
z: MAP_CONSTANTS.MARKER.Z_INDEX,
|
|
label: {
|
show: false // 禁用标签显示
|
},
|
|
// 修复tooltip配置
|
tooltip: {
|
show: true,
|
trigger: 'item',
|
backgroundColor: 'transparent',
|
borderWidth: 0,
|
padding: 0,
|
className: 'map-tooltip',
|
position: function (point) {
|
// 固定偏移量
|
return [point[0] - 100, point[1] - 160];
|
},
|
formatter: function (params) {
|
const { name, data } = params;
|
return `
|
<div style="
|
background: url(${tooltipBg}) no-repeat center center;
|
background-size: 100% 100%;
|
width: 211px;
|
padding: 20px 0;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
">
|
<div style="color: #fff; font-size: 16px; margin-bottom: 16px; font-weight: bold;">${name}</div>
|
<div style="display: flex; justify-content: space-around; width: 100%; padding: 0 21px;">
|
<div style="text-align: center;">
|
<div style="color: #66ffff; font-size: 20px; font-family: 'pangmenzhengdao'; margin-bottom: 8px;">${data.householdCount}</div>
|
<div style="color: #fff; font-size: 12px;">安置户数(户)</div>
|
</div>
|
<div style="text-align: center;">
|
<div style="color: #66ffff; font-size: 20px; font-family: 'pangmenzhengdao'; margin-bottom: 8px;">${data.personCount}</div>
|
<div style="color: #fff; font-size: 12px;">安置人数(人)</div>
|
</div>
|
</div>
|
</div>
|
`;
|
}
|
},
|
|
emphasis: {
|
scale: true,
|
scaleSize: 1.2,
|
itemStyle: {
|
shadowBlur: 10,
|
shadowColor: type === "blue" ? MAP_CONSTANTS.SHADOW.COLOR : '#ff8e3a'
|
}
|
}
|
};
|
},
|
|
// 生成地图图层的配置
|
// 参数 index: 图层索引(1-3),用于创建三层叠加的立体效果
|
getGeoLayers(index) {
|
// 定义每层的具体配置
|
const layerConfig = {
|
1: { // 最底层
|
top: "15%", // 距顶部距离
|
bottom: "10%", // 距底部距离
|
borderColor: "#00A0F5", // 边框颜色
|
borderWidth: 1 // 边框宽度
|
},
|
2: { // 中间层
|
top: "16%",
|
bottom: "9%",
|
borderColor: "#0C93C5",
|
borderWidth: 1
|
},
|
3: { // 最顶层
|
top: "17%",
|
bottom: "8%",
|
borderColor: "#0A5C83",
|
borderWidth: 2
|
}
|
};
|
|
const config = layerConfig[index];
|
return {
|
map: "myMap", // 使用注册的地图名称
|
aspectScale: 1, // 地图长宽比
|
zoom: 1, // 缩放级别
|
roam: false, // 是否开启鼠标缩放和平移漫游
|
top: config.top, // 距容器顶部距离
|
bottom: config.bottom, // 距容器底部距离
|
z: index, // 图层层级,值越大越靠前
|
|
// 地图区域的样式设置
|
itemStyle: {
|
areaColor: "transparent", // 区域填充色为透明
|
borderColor: config.borderColor, // 边框颜色
|
borderWidth: config.borderWidth, // 边框宽度
|
// 仅在最顶层添加阴影效果
|
...(index === 3 ? {
|
shadowColor: "#00eaff", // 阴影颜色
|
shadowBlur: 25, // 阴影模糊大小
|
shadowOffsetX: 0, // 阴影水平偏移
|
shadowOffsetY: 0, // 阴影垂直偏移
|
opacity: 1 // 不透明度
|
} : {})
|
},
|
|
// 高亮状态配置
|
emphasis: {
|
disabled: true // 禁用高亮效果
|
},
|
|
// 选中状态配置
|
select: {
|
disabled: true // 禁用选中状态
|
},
|
|
// 提示框配置
|
tooltip: {
|
show: false // 禁用提示框
|
},
|
|
silent: true // 禁用鼠标事件响应
|
};
|
},
|
|
// 新增:获取地图完整配置项
|
getMapOption() {
|
return {
|
// 关闭默认的tooltip
|
tooltip: {
|
show: false
|
},
|
// geo组件配置,用于地图的三层叠加效果
|
geo: [
|
this.getGeoLayers(1), // 最底层
|
this.getGeoLayers(2), // 中间层
|
this.getGeoLayers(3) // 最顶层
|
],
|
// 系列列表
|
series: [
|
{
|
type: "map", // 图表类型为地图
|
name: "地图", // 系列名称
|
selectedMode: false, // 禁用选中模式
|
aspectScale: 1, // 地图长宽比
|
zoom: 1, // 地图缩放比例
|
roam: false, // 禁用地图平移缩放
|
regionHeight: 2, // 三维地图的高度
|
map: "myMap", // 使用注册的地图名称
|
z: 10, // 图层的层级
|
top: "14%", // 距离容器顶部的距离
|
bottom: "11%", // 距离容器底部的距离
|
|
// 地图区域的样式
|
itemStyle: {
|
areaColor: { // 区域填充样式
|
type: 'pattern', // 使用图案填充
|
image: mapBg, // 背景图片
|
repeat: 'no-repeat', // 图片不重复
|
imageSize: '100%', // 图片大小
|
patternSize: [815, 534], // 图案大小
|
patternPosition: [0, 0] // 图案位置
|
},
|
borderColor: "#324D6B", // 边框颜色
|
borderWidth: 2, // 边框宽度
|
},
|
|
// 高亮状态的配置
|
emphasis: {
|
disabled: false, // 启用高亮效果
|
label: {
|
show: true, // 显示标签
|
color: "#fff", // 文字颜色
|
fontSize: 10, // 文字大小
|
fontWeight: "normal", // 文字粗细
|
opacity: 1 // 不透明度
|
},
|
itemStyle: {
|
areaColor: { // 区域填充,保持与正常状态相同的背景
|
type: 'pattern',
|
image: mapBg,
|
repeat: 'no-repeat',
|
imageSize: '100%',
|
patternSize: [815, 534],
|
patternPosition: [0, 0]
|
},
|
borderColor: MAP_CONSTANTS.SHADOW.COLOR, // 高亮时的边框颜色
|
borderWidth: 2, // 高亮时的边框宽度
|
shadowColor: 'rgba(0, 234, 255, 0.8)', // 阴影颜色,添加透明度
|
shadowBlur: 40, // 增大阴影模糊半径
|
shadowOffsetX: 0, // 保持阴影水平偏移为0
|
shadowOffsetY: 0, // 保持阴影垂直偏移为0
|
glow: {
|
width: 20, // 发光宽度
|
color: 'rgba(0, 234, 255, 0.2)' // 发光颜色
|
},
|
opacity: 0.9 // 略微降低不透明度,增强光晕效果
|
}
|
},
|
|
// 选中状态配置
|
select: {
|
disabled: true // 禁用选中状态
|
},
|
|
// 标签配置
|
label: {
|
show: true, // 显示标签
|
color: "#fff", // 文字颜色
|
fontSize: 10, // 文字大小
|
fontWeight: "normal", // 文字粗细
|
opacity: 1 // 不透明度
|
},
|
|
tooltip: {
|
show: false // 禁用该系列的tooltip
|
},
|
silent: false // 允许鼠标事件
|
},
|
// 添加蓝色和橙色标记点系列
|
this.getMarkerSeries("blue"), // 蓝色标记点系列
|
this.getMarkerSeries("orange") // 橙色标记点系列
|
]
|
};
|
},
|
},
|
watch: {
|
// 监听数据变化,更新地图
|
data: {
|
handler(newVal) {
|
if (this.hasValidData) {
|
this.$nextTick(() => {
|
this.initMap();
|
});
|
}
|
},
|
immediate: false // 不立即执行,等待mounted
|
}
|
},
|
beforeDestroy() {
|
// 组件销毁前清理echarts实例
|
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('error', this.handleGlobalError, true);
|
if (this.chart) {
|
this.chart.dispose();
|
this.chart = null;
|
}
|
}
|
}
|
</script>
|
|
<style lang="less" scoped>
|
/* 地图容器样式 */
|
.map-container {
|
width: 815px;
|
height: 534px;
|
position: relative;
|
|
// 加载状态样式
|
&.loading {
|
&::after {
|
content: '';
|
position: absolute;
|
top: 0;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background: rgba(0, 0, 51, 0.6);
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
}
|
}
|
|
// 新增:加载提示样式
|
.loading-tip {
|
position: absolute;
|
top: 50%;
|
left: 50%;
|
transform: translate(-50%, -50%);
|
color: #fff;
|
font-size: 14px;
|
z-index: 10;
|
}
|
|
// 新增:错误提示样式
|
.error-message {
|
position: absolute;
|
top: 50%;
|
left: 50%;
|
transform: translate(-50%, -50%);
|
color: #ff4d4f;
|
font-size: 14px;
|
z-index: 10;
|
}
|
}
|
|
/* 图表容器样式 */
|
.chart {
|
width: 100%;
|
height: 100%;
|
transition: all 0.3s ease;
|
}
|
</style>
|