hejianhao
2025-03-24 4223c358f1ad9ffa4fcbc8fb0e350dc5bf342b51
src/components/datascreen/map.vue
@@ -1,216 +1,589 @@
<template>
    <div class="map-container">
    <!-- 地图容器 -->
    <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 "echarts-gl";
export default {
    name: 'Map',
    data() {
        return {
            mapBg: null // 地图背景图片
// 导入防抖函数
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.initMapBg();
        this.initMap();
        // 组件挂载后初始化地图
        this.initEventListeners(); // 新增:初始化事件监听
        this.$nextTick(() => {
            if (this.hasValidData) {
                this.initMap();
            }
        });
    },
    methods: {
        // 初始化地图背景图片
        initMapBg() {
            // 创建一个 canvas 来生成背景图案
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            canvas.width = 8;
            canvas.height = 8;
            // 设置渐变
            const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
            gradient.addColorStop(0, 'rgba(0,24,106,0.8)');
            gradient.addColorStop(1, 'rgba(0,24,106,0.9)');
            // 绘制背景
            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            // 添加网格效果
            ctx.strokeStyle = 'rgba(0,234,255,0.1)';
            ctx.lineWidth = 0.5;
            ctx.beginPath();
            ctx.moveTo(0, canvas.height / 2);
            ctx.lineTo(canvas.width, canvas.height / 2);
            ctx.moveTo(canvas.width / 2, 0);
            ctx.lineTo(canvas.width / 2, canvas.height);
            ctx.stroke();
            // 保存为base64图片
            this.mapBg = canvas.toDataURL();
        // 新增:初始化事件监听器
        initEventListeners() {
            window.addEventListener('resize', this.handleResize);
            // 新增:错误监听
            window.addEventListener('error', this.handleGlobalError, true);
        },
        initMap() {
            // json地图数据-需要根据需求下载引入对应名称文件
            let mapJson = require("@/utils/map/chongzhou.json");
            let myChart = this.$echarts.init(this.$refs.myMap);
            myChart.showLoading();
            myChart.hideLoading();
            this.$echarts.registerMap("myMap", mapJson);
            let option = {
                tooltip: {
                    trigger: "none", // 关闭提示框
        // 新增:全局错误处理
        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  // 禁用标签显示
                },
                // 设置多层级地图实现立体效果
                geo: [
                    {
                        map: "myMap", // 使用注册的地图名称
                        aspectScale: 1, // 地图长宽比
                        zoom: 1, // 地图缩放比例
                        top: "15%", // 距离容器顶部距离
                        bottom: "10%", // 距离容器底部距离
                        roam: false, // 禁用地图平移缩放
                        z: 5, // 图层层级,数值大的在上层
                        itemStyle: {
                            areaColor: "transparent", // 区域透明
                            borderColor: "#00eaff", // 边框颜色
                            borderWidth: 1 // 边框宽度
                        },
                        select: {
                            disabled: true // 禁用选中状态
                        },
                        tooltip: {
                            show: false, // 关闭提示框
                        },
                    },
                    {
                        map: "myMap", // 第二层地图
                        aspectScale: 1,
                        zoom: 1,
                        top: "16%", // 比第一层低一点
                        bottom: "9%",
                        roam: false,
                        z: 4, // 层级比第一层低
                        itemStyle: {
                            areaColor: "transparent",
                            borderColor: "#00eaff",
                            borderWidth: 1
                        },
                        select: {
                            disabled: true
                        },
                        tooltip: {
                            show: false,
                        },
                    },
                    {
                        map: "myMap", // 第三层地图
                        aspectScale: 1,
                        zoom: 1,
                        top: "17%", // 最底层
                        bottom: "8%",
                        roam: false,
                        z: 3, // 最底层层级
                        itemStyle: {
                            areaColor: "transparent",
                            borderColor: "#00eaff",
                            borderWidth: 1
                        },
                        select: {
                            disabled: true
                        },
                        tooltip: {
                            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 20px;">
                                    <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%",
                        viewControl: { // 视角控制
                            distance: 115, // 视角距离主体的距离
                            alpha: 40, // 视角绕 x 轴,即上下旋转的角度
                            rotateSensitivity: [0, 0], // 禁用旋转
                            beta: 0, // 视角绕 y 轴,即左右旋转的角度
                        },
                        label: { // 标签样式
                            normal: {
                                show: true, // 显示标签
                                textStyle: {
                                    color: "#fff", // 标签文字颜色
                                    fontSize: 16, // 文字大小
                                    fontWeight: "normal", // 文字粗细
                                    textShadowColor: "rgba(0,234,255,0.8)", // 文字阴影颜色
                                    textShadowBlur: 10, // 文字阴影模糊
                                    opacity: 1, // 文字透明度
                                },
                        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]   // 图案位置
                            },
                            emphasis: {
                                color: "#fff", // 悬浮时文字颜色
                            },
                            borderColor: "#324D6B",  // 边框颜色
                            borderWidth: 2,          // 边框宽度
                        },
                        itemStyle: { // 主图层样式
                            areaColor: {
                                image: this.mapBg, // 使用背景图片
                                repeat: 'repeat' // 平铺模式
                            },
                            borderColor: "#00eaff", // 边框颜色
                            borderWidth: 2, // 边框宽度
                            borderType: 'solid',
                            shadowColor: "#00eaff", // 阴影颜色
                            shadowOffsetX: 0,
                            shadowOffsetY: 0,
                            shadowBlur: 20,
                            opacity: 1
                        },
                        // 高亮状态的配置
                        emphasis: {
                            disabled: true // 禁用悬浮效果
                            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
                            disabled: true    // 禁用选中状态
                        },
                        light: { // 光照相关的设置
                            main: { // 主光源
                                color: "#00eaff", // 光照颜色
                                intensity: 1.2, // 增加光照强度
                                shadow: true, // 是否显示阴影
                                shadowQuality: "high", // 阴影质量
                                alpha: 40,
                                beta: 0,
                            },
                            ambient: { // 环境光
                                color: "#00eaff", // 环境光颜色
                                intensity: 0.3, // 减弱环境光以突出主光源
                            },
                        // 标签配置
                        label: {
                            show: true,       // 显示标签
                            color: "#fff",    // 文字颜色
                            fontSize: 10,     // 文字大小
                            fontWeight: "normal", // 文字粗细
                            opacity: 1        // 不透明度
                        },
                        tooltip: {
                            show: false      // 禁用该系列的tooltip
                        },
                        silent: false        // 允许鼠标事件
                    },
                ],
                    // 添加蓝色和橙色标记点系列
                    this.getMarkerSeries("blue"),    // 蓝色标记点系列
                    this.getMarkerSeries("orange")   // 橙色标记点系列
                ]
            };
            myChart.setOption(option);
        },
    },
    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 scoped>
<style lang="less" scoped>
/* 地图容器样式 */
.map-container {
    position: relative;
    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>