pyt
2025-03-26 03cd344a15bf63bf7968dc77a026c77c78c304f4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
<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>