Files
all-personal-resume/个人简历_文旅/echarts-background-map.js

612 lines
26 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ECharts世界地图背景版 - 无飞行路线
class BackgroundWorldMap {
constructor() {
this.chart = null;
this.mapData = null;
this.visitedCountries = [
'中国', '日本', '韩国', '泰国', '新加坡', '马来西亚', '印度尼西亚',
'菲律宾', '越南', '印度', '阿联酋', '土耳其', '埃及', '南非',
'美国', '加拿大', '墨西哥', '巴西', '阿根廷', '英国', '法国',
'德国', '意大利', '西班牙', '荷兰', '比利时', '瑞士', '奥地利',
'捷克', '波兰', '俄罗斯', '澳大利亚', '新西兰', '冰岛', '挪威',
'瑞典', '芬兰', '丹麦', '葡萄牙', '希腊', '摩洛哥', '肯尼亚'
];
this.initializeMap();
}
async initializeMap() {
const container = document.getElementById('hero-map-background');
if (!container) {
console.error('地图容器不存在');
return;
}
if (typeof echarts === 'undefined') {
console.error('ECharts库未加载');
return;
}
try {
// 加载地图数据
const response = await fetch('worldZH.json');
if (!response.ok) {
throw new Error('地图数据加载失败');
}
this.mapData = await response.json();
// 修改地图数据,确保所有区域都有统一的样式
if (this.mapData.features) {
this.mapData.features.forEach(feature => {
if (!feature.properties) {
feature.properties = {};
}
// 给所有地区设置统一的属性值
feature.properties.value = 50; // 统一的值配合visualMap使用
});
}
echarts.registerMap('world', this.mapData);
// 初始化图表
this.initChart(container);
} catch (error) {
console.error('地图初始化失败:', error);
}
}
initChart(container) {
// 检查容器是否已经有图表实例
const existingInstance = echarts.getInstanceByDom(container);
if (existingInstance) {
console.log('容器已有图表实例,先销毁');
existingInstance.dispose();
}
// 确保容器有正确的尺寸
const rect = container.getBoundingClientRect();
// 创建ECharts实例明确指定尺寸
this.chart = echarts.init(container, null, {
renderer: 'canvas',
width: rect.width || window.innerWidth,
height: rect.height || window.innerHeight
});
// 立即调整大小以适应容器
setTimeout(() => {
this.chart.resize();
// 重新设置中心点以确保居中
this.adjustMapCenter();
}, 100);
// 所有国家现在都使用统一的绿色样式
// 主要城市点 - 简化版本
const cityPoints = [
{ name: '北京', value: [116.4074, 39.9042, 100] },
{ name: '东京', value: [139.6917, 35.6895, 95] },
{ name: '巴黎', value: [2.3522, 48.8566, 92] },
{ name: '纽约', value: [-74.0060, 40.7128, 95] },
{ name: '伦敦', value: [-0.1276, 51.5074, 90] },
{ name: '悉尼', value: [151.2093, -33.8688, 90] },
{ name: '迪拜', value: [55.2708, 25.2048, 85] },
{ name: '新加坡', value: [103.8198, 1.3521, 88] },
{ name: '巴厘岛', value: [115.1889, -8.4095, 100] }, // 当前位置
{ name: '罗马', value: [12.4964, 41.9028, 94] },
{ name: '马德里', value: [-3.7038, 40.4168, 86] },
{ name: '柏林', value: [13.4050, 52.5200, 88] },
{ name: '莫斯科', value: [37.6173, 55.7558, 76] },
{ name: '开罗', value: [31.2357, 30.0444, 72] },
{ name: '里约热内卢', value: [-43.1729, -22.9068, 82] }
];
// 配置项 - 简洁的背景风格
const option = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(10, 14, 39, 0.95)',
borderColor: 'rgba(0, 180, 216, 0.5)',
borderWidth: 1,
textStyle: {
color: '#90e0ef',
fontSize: 12
},
formatter: function(params) {
if (params.seriesType === 'scatter' || params.seriesType === 'effectScatter') {
return `<div style="padding: 5px;">
<strong style="color: #00b4d8;">${params.name}</strong><br/>
<span style="color: #ff6b35;">访问指数:${params.value[2] || 100}%</span>
</div>`;
} else if (params.componentType === 'geo') {
return `<div style="padding: 5px;">
<strong style="color: #52b788;">${params.name}</strong><br/>
<span style="color: #90e0ef;">点击查看详情</span>
</div>`;
}
return params.name;
}
},
geo: {
map: 'world',
roam: false, // 禁用缩放和拖拽
center: [0, 0], // 世界地图标准中心
zoom: 1.2, // 适度放大
silent: false, // 允许交互
left: 'center', // 水平居中
top: 'center', // 垂直居中
aspectScale: 0.75, // 调整宽高比以补偿投影变形
itemStyle: {
normal: {
areaColor: 'rgba(82, 183, 136, 0.35)', // 统一绿色高亮
borderColor: 'rgba(82, 183, 136, 0.3)',
borderWidth: 0.8
}
},
emphasis: {
disabled: false,
itemStyle: {
areaColor: 'rgba(82, 183, 136, 0.5)',
borderColor: 'rgba(82, 183, 136, 0.8)',
borderWidth: 2,
shadowBlur: 10,
shadowColor: 'rgba(82, 183, 136, 0.5)'
},
label: {
show: true,
color: '#52b788',
fontSize: 14,
fontWeight: 'bold'
}
},
regions: [] // 清空所有特殊区域配置
},
series: [
// 城市散点 - 更简洁
{
name: '城市',
type: 'scatter',
coordinateSystem: 'geo',
data: cityPoints,
symbolSize: function(val) {
return Math.max(val[2] / 12, 4); // 更小的点
},
itemStyle: {
color: function(params) {
const value = params.value[2];
// 使用更柔和的颜色
if (value >= 90) return 'rgba(255, 107, 53, 0.6)';
if (value >= 80) return 'rgba(244, 162, 97, 0.6)';
return 'rgba(0, 180, 216, 0.6)';
},
shadowBlur: 5,
shadowColor: 'rgba(0, 180, 216, 0.3)'
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowColor: 'rgba(255, 107, 53, 0.5)'
},
label: {
show: true,
formatter: '{b}',
position: 'top',
color: '#fff',
fontSize: 12
}
},
silent: false // 允许交互
},
// 当前位置脉冲效果
{
name: '当前位置',
type: 'effectScatter',
coordinateSystem: 'geo',
data: [{
name: '巴厘岛',
value: [115.1889, -8.4095]
}],
symbolSize: 15,
rippleEffect: {
period: 4,
scale: 2.5,
brushType: 'fill'
},
itemStyle: {
color: 'rgba(255, 107, 53, 0.7)',
shadowBlur: 10,
shadowColor: 'rgba(255, 107, 53, 0.5)'
},
silent: true // 不响应鼠标事件
}
]
};
// 应用配置
this.chart.setOption(option);
// 响应式
window.addEventListener('resize', () => {
if (this.chart) {
this.chart.resize();
}
});
// 美化的点击交互
this.chart.on('click', 'series.scatter', (params) => {
this.showCityInfo(params);
});
// 点击地图区域显示国家信息
this.chart.on('click', 'geo', (params) => {
if (params.name) {
this.showCountryInfo(params);
}
});
}
showCityInfo(params) {
// 移除已存在的弹窗
const existingModal = document.querySelector('.map-info-modal');
if (existingModal) {
existingModal.remove();
}
const modal = document.createElement('div');
modal.className = 'map-info-modal';
modal.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: linear-gradient(135deg, rgba(10, 14, 39, 0.98) 0%, rgba(38, 70, 83, 0.95) 100%);
border: 2px solid rgba(0, 180, 216, 0.5);
border-radius: 20px;
padding: 30px;
z-index: 10000;
color: white;
min-width: 320px;
max-width: 400px;
box-shadow:
0 20px 60px rgba(0, 0, 0, 0.5),
0 0 100px rgba(0, 180, 216, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
animation: modalFadeIn 0.3s ease;
`;
const value = params.value[2] || 0;
const cityData = this.getCityData(params.name);
modal.innerHTML = `
<style>
@keyframes modalFadeIn {
from { opacity: 0; transform: translate(-50%, -45%) scale(0.95); }
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
.modal-close {
position: absolute;
top: 15px;
right: 15px;
width: 30px;
height: 30px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
font-size: 18px;
}
.modal-close:hover {
background: rgba(255, 107, 53, 0.8);
border-color: #ff6b35;
transform: rotate(90deg);
}
</style>
<button class="modal-close" onclick="this.parentElement.remove()">×</button>
<div style="text-align: center;">
<h3 style="
font-size: 28px;
margin-bottom: 15px;
background: linear-gradient(135deg, #00b4d8, #90e0ef);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: bold;
">${params.name}</h3>
<div style="
font-size: 60px;
margin: 20px 0;
filter: drop-shadow(0 0 20px rgba(0, 180, 216, 0.5));
">${cityData.emoji}</div>
<p style="
color: #90e0ef;
font-size: 14px;
margin-bottom: 20px;
letter-spacing: 1px;
">${cityData.country}</p>
<div style="
background: rgba(0, 180, 216, 0.1);
border: 1px solid rgba(0, 180, 216, 0.3);
border-radius: 15px;
padding: 15px;
margin: 20px 0;
">
<p style="color: #52b788; font-size: 16px; margin-bottom: 10px;">🌟 访问指数</p>
<div style="
font-size: 36px;
font-weight: bold;
background: linear-gradient(135deg, #ff6b35, #f4a261);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
">${value}%</div>
</div>
<p style="
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
line-height: 1.6;
margin-top: 15px;
">${cityData.description}</p>
</div>
`;
document.body.appendChild(modal);
// 点击背景关闭
setTimeout(() => {
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.remove();
}
});
}, 100);
}
showCountryInfo(params) {
// 移除已存在的弹窗
const existingModal = document.querySelector('.map-info-modal');
if (existingModal) {
existingModal.remove();
}
const modal = document.createElement('div');
modal.className = 'map-info-modal';
modal.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: linear-gradient(135deg, rgba(10, 14, 39, 0.98) 0%, rgba(38, 70, 83, 0.95) 100%);
border: 2px solid rgba(82, 183, 136, 0.5);
border-radius: 20px;
padding: 30px;
z-index: 10000;
color: white;
min-width: 320px;
max-width: 400px;
box-shadow:
0 20px 60px rgba(0, 0, 0, 0.5),
0 0 100px rgba(82, 183, 136, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
animation: modalFadeIn 0.3s ease;
`;
const countryData = this.getCountryData(params.name);
const isVisited = this.visitedCountries.includes(params.name);
modal.innerHTML = `
<style>
@keyframes modalFadeIn {
from { opacity: 0; transform: translate(-50%, -45%) scale(0.95); }
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
.modal-close {
position: absolute;
top: 15px;
right: 15px;
width: 30px;
height: 30px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
font-size: 18px;
}
.modal-close:hover {
background: rgba(82, 183, 136, 0.8);
border-color: #52b788;
transform: rotate(90deg);
}
</style>
<button class="modal-close" onclick="this.parentElement.remove()">×</button>
<div style="text-align: center;">
<h3 style="
font-size: 28px;
margin-bottom: 15px;
background: linear-gradient(135deg, #52b788, #90e0ef);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: bold;
">${params.name}</h3>
<div style="
font-size: 60px;
margin: 20px 0;
filter: drop-shadow(0 0 20px rgba(82, 183, 136, 0.5));
">${countryData.flag}</div>
<div style="
display: inline-block;
padding: 8px 20px;
background: ${isVisited ? 'rgba(82, 183, 136, 0.2)' : 'rgba(255, 255, 255, 0.1)'};
border: 1px solid ${isVisited ? '#52b788' : 'rgba(255, 255, 255, 0.3)'};
border-radius: 20px;
margin: 15px 0;
font-size: 14px;
color: ${isVisited ? '#52b788' : 'rgba(255, 255, 255, 0.6)'};
">
${isVisited ? '✓ 已访问' : '◯ 未访问'}
</div>
${isVisited ? `
<div style="
background: rgba(0, 180, 216, 0.1);
border: 1px solid rgba(0, 180, 216, 0.3);
border-radius: 15px;
padding: 15px;
margin: 20px 0;
">
<p style="color: #90e0ef; font-size: 14px; margin-bottom: 8px;">访问记录</p>
<p style="color: white; font-size: 16px;">${countryData.visitInfo}</p>
</div>
` : ''}
<p style="
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
line-height: 1.6;
margin-top: 15px;
">${countryData.description}</p>
</div>
`;
document.body.appendChild(modal);
// 点击背景关闭
setTimeout(() => {
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.remove();
}
});
}, 100);
}
getCityData(cityName) {
const cityDatabase = {
'北京': { emoji: '🏛️', country: '中国', description: '千年古都,现代化国际大都市' },
'东京': { emoji: '🗼', country: '日本', description: '繁华都市,传统与现代的完美融合' },
'巴黎': { emoji: '🗼', country: '法国', description: '浪漫之都,艺术与时尚的天堂' },
'纽约': { emoji: '🗽', country: '美国', description: '世界之都,梦想开始的地方' },
'伦敦': { emoji: '🎡', country: '英国', description: '雾都传奇,历史与现代交织' },
'悉尼': { emoji: '🌉', country: '澳大利亚', description: '海港城市,阳光与海滩的乐园' },
'迪拜': { emoji: '🏙️', country: '阿联酋', description: '沙漠奇迹,奢华与创新的象征' },
'新加坡': { emoji: '🦁', country: '新加坡', description: '花园城市,多元文化的交汇点' },
'巴厘岛': { emoji: '🏝️', country: '印度尼西亚', description: '诸神之岛,心灵净化的圣地' },
'罗马': { emoji: '🏛️', country: '意大利', description: '永恒之城,历史在每个角落' },
'马德里': { emoji: '🎭', country: '西班牙', description: '热情之都,弗拉明戈的故乡' },
'柏林': { emoji: '🏰', country: '德国', description: '历史见证,艺术与创意的中心' },
'莫斯科': { emoji: '🏰', country: '俄罗斯', description: '红场传说,东西方文化的桥梁' },
'开罗': { emoji: '🔺', country: '埃及', description: '金字塔之城,古文明的守护者' },
'里约热内卢': { emoji: '🏖️', country: '巴西', description: '狂欢之都,桑巴与海滩的天堂' }
};
return cityDatabase[cityName] || { emoji: '📍', country: '未知', description: '等待探索的神秘之地' };
}
getCountryData(countryName) {
const countryDatabase = {
'中国': { flag: '🇨🇳', visitInfo: '2次访问 · 30天', description: '五千年文明,山河壮丽,美食天堂' },
'日本': { flag: '🇯🇵', visitInfo: '3次访问 · 21天', description: '樱花之国,匠人精神,科技与传统并存' },
'美国': { flag: '🇺🇸', visitInfo: '2次访问 · 15天', description: '自由之地,多元文化,创新的摇篮' },
'法国': { flag: '🇫🇷', visitInfo: '1次访问 · 10天', description: '浪漫国度,美食美酒,艺术殿堂' },
'英国': { flag: '🇬🇧', visitInfo: '1次访问 · 7天', description: '绅士之国,皇家传统,现代金融中心' },
'澳大利亚': { flag: '🇦🇺', visitInfo: '1次访问 · 14天', description: '袋鼠之国,自然奇观,冲浪天堂' },
'新加坡': { flag: '🇸🇬', visitInfo: '2次访问 · 5天', description: '狮城,花园城市,美食汇聚' },
'印度尼西亚': { flag: '🇮🇩', visitInfo: '1次访问 · 10天', description: '千岛之国,热带风情,文化多样' },
'意大利': { flag: '🇮🇹', visitInfo: '1次访问 · 12天', description: '艺术之国,美食故乡,历史瑰宝' },
'西班牙': { flag: '🇪🇸', visitInfo: '1次访问 · 8天', description: '热情国度,弗拉明戈,阳光海岸' },
'德国': { flag: '🇩🇪', visitInfo: '1次访问 · 6天', description: '严谨之国,啤酒文化,工业强国' },
'俄罗斯': { flag: '🇷🇺', visitInfo: '1次访问 · 9天', description: '战斗民族,文学艺术,广袤国土' },
'埃及': { flag: '🇪🇬', visitInfo: '1次访问 · 7天', description: '法老之国,金字塔,尼罗河文明' },
'巴西': { flag: '🇧🇷', visitInfo: '1次访问 · 10天', description: '桑巴王国,亚马逊雨林,足球圣地' }
};
return countryDatabase[countryName] || {
flag: '🏳️',
visitInfo: '',
description: '尚未探索的神秘土地,等待下一次冒险'
};
}
adjustMapCenter() {
// 动态调整地图中心以适应容器
const containerWidth = this.chart.getWidth();
const containerHeight = this.chart.getHeight();
const aspectRatio = containerWidth / containerHeight;
// 根据容器宽高比调整中心点
let centerX = -100;
let centerY = 20;
// 如果容器更宽,稍微向左偏移
if (aspectRatio > 1.5) {
centerX = 160;
}
// 更新地图中心,保持原有的颜色配置
this.chart.setOption({
geo: {
center: [centerX, centerY],
itemStyle: {
normal: {
areaColor: 'rgba(82, 183, 136, 0.35)', // 保持统一绿色
borderColor: 'rgba(82, 183, 136, 0.3)',
borderWidth: 0.8
}
}
}
});
}
destroy() {
if (this.chart) {
this.chart.dispose();
this.chart = null;
}
}
}
// 初始化背景地图 - 确保只创建一次
let mapInitialized = false;
function initializeMap() {
// 防止重复初始化
if (mapInitialized || window.backgroundMap) {
console.log('地图已经初始化,跳过');
return;
}
const container = document.getElementById('hero-map-background');
if (!container) {
console.warn('地图容器不存在');
return;
}
// 清空容器内容
container.innerHTML = '';
// 标记为已初始化
mapInitialized = true;
// 创建地图实例
try {
window.backgroundMap = new BackgroundWorldMap();
console.log('地图初始化成功');
} catch (error) {
console.error('地图初始化失败:', error);
mapInitialized = false;
}
}
// DOM加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
// 延迟执行以确保所有资源加载完成
setTimeout(initializeMap, 200);
});