新增点击省份,查看省份地图

This commit is contained in:
DelLevin-Home
2026-01-28 00:44:51 +08:00
parent 4484646f48
commit 0c62a7982e
10 changed files with 1242 additions and 210 deletions

View File

@@ -149,7 +149,9 @@
<!-- 历史足迹 (地图) -->
<div id="footprint-content" class="tab-content">
<div class="content-section">
<h2 data-i18n="footprint_map_h2">历史足迹</h2>
</div>
</div>
<!-- 闲言碎语内容 -->
@@ -184,6 +186,16 @@
Levin </span>
</div>
</div>
<div id="modalOverlay"
style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); z-index: 1000;cursor: pointer; ">
<div id="modalContent"
style="
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
background-color: white; padding: 20px; border-radius: 5px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); text-align: center; cursor: default; ">
<p>博主是个宅男,还没去过哦~</p>
</div>
</div>
<script>
// ========== 语言切换逻辑 ==========
const translations = {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,220 @@
/**
* ECharts 地图足迹模块 (使用本地 GeoJSON)
*/
const MapFootprintModule = (() => {
let myChart = null; // ECharts 实例
const MAP_NAME = 'china_custom'; // 为自定义地图起一个名字
const visitedPlaces = [
{ name: "济南市", value: [117.024961, 36.682788] },
{ name: "青岛市", value: [120.384423, 36.065918] },
{ name: "淄博市", value: [118.055915, 36.813547] },
{ name: "潍坊市", value: [119.162775, 36.705759] },
{ name: "北京市", value: [116.407395, 39.904211] },
{ name: "杭州市", value: [120.153576, 30.287459] },
{ name: "苏州市", value: [120.585316, 31.298886] },
{ name: "扬州市", value: [119.421003, 32.393159] },
{ name: "保定市", value: [115.482331, 38.867657] },
{ name: "石家庄市", value: [114.514891, 38.042309] },
{ name: "衡水市", value: [115.665996, 37.739574] },
{ name: "洛阳市", value: [112.454174, 34.618139] },
{ name: "南京市", value: [118.796877, 32.060255] },
{ name: "无锡市", value: [120.311987, 31.490920] },
{ name: "沧州市", value: [116.838581, 38.308094] },
{ name: "镇江市", value: [119.452753, 32.204402] },
];
/**
* 加载 GeoJSON 地图数据并注册
*/
const loadAndRegisterMap = async () => {
try {
// console.log('开始加载 GeoJSON 地图数据...');
const response = await fetch('./static/js/echarts/china.geojson'); // 确保路径正确
if (!response.ok) {
throw new Error(`加载 GeoJSON 失败: ${response.status} ${response.statusText}`);
}
const geoJsonData = await response.json();
// 注册自定义地图
echarts.registerMap(MAP_NAME, geoJsonData);
// console.log('GeoJSON 地图数据加载并注册成功。');
// 地图注册成功后,初始化图表
const container = document.getElementById(getMapContainerId());
if (container) {
initEChartsMap(container);
} else {
console.error('地图容器 DOM 元素未找到,无法初始化图表。');
}
} catch (error) {
console.error('加载或注册 GeoJSON 地图时出错:', error);
// 可以选择在内容区域显示错误信息
const contentDiv = document.getElementById('footprint-content'); // 假设足迹内容区域ID是这个
if (contentDiv) {
contentDiv.innerHTML = '<p>加载地图数据失败,请检查网络连接或稍后重试。</p>';
}
}
};
/**
* 初始化 ECharts 地图
* @param {HTMLElement} container - 图表容器DOM元素
*/
const initEChartsMap = (container) => {
if (!container) {
console.error('地图容器未找到');
return;
}
// 初始化 ECharts 实例
myChart = echarts.init(container);
// ECharts 配置项 (使用注册的地图名称)
const option = {
title: {
text: '',
subtext: '读万卷书,不如行万里路。见多才会识广~',
left: 'center',
textStyle: {
fontSize: 18
},
subtextStyle: {
fontSize: 12
}
},
tooltip: {
trigger: 'item',
formatter: function(params) {
if (params.value && params.value.length >= 2) {
return params.name + '<br/>经度: ' + params.value[0].toFixed(4) + '<br/>纬度: ' + params.value[1].toFixed(4);
}
return params.name; // 如果没有坐标信息,只显示地名
}
},
toolbox: {
show: false,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
dataView: { readOnly: false },
restore: {},
saveAsImage: {}
}
},
visualMap: {
show: false, // 是否显示 visualMap 组件,这里设为 false因为我们用 symbolSize 控制点大小
min: 0,
max: 100,
left: 'left',
top: 'bottom',
text: ['High', 'Low'], // 文本,默认为数值文本
calculable: true
},
geo: {
map: MAP_NAME, // 使用注册的自定义地图名称
roam: false, // 是否开启鼠标缩放和平移漫游
zoom: 1.1, // 当前视角的缩放比例
center: [104.06, 30],
emphasis: { // 高亮状态下的样式
itemStyle: {
areaColor: '#f0f0f0', // 高亮时省份的颜色
borderColor: '#409EFF', // 高亮时省份的边框颜色
borderWidth: 1
}
},
itemStyle: { // 普通状态下的样式
areaColor: '#eef2ff', // 未选中时省份的颜色
borderColor: '#333' // 未选中时省份的边框颜色
}
},
series: [
{
name: '足迹',
type: 'scatter', // 使用散点图表示标记点
coordinateSystem: 'geo', // 指定坐标系为地理坐标系
data: visitedPlaces, // 使用我们的数据
symbol: 'pin', // 使用内置符号 'pin'
symbolSize: 18, // 标记点的大小
itemStyle: { // 普通状态下的样式
color: '#FF6B6B', // 设置图标颜色
borderColor: '#fff', // 设置图标边框颜色
borderWidth: 1, // 设置图标边框宽度
},
emphasis: { // 高亮状态
itemStyle: {
color: '#dd4444' // 高亮时点的颜色
}
},
tooltip: {
formatter: function(params) {
// 这里可以更精细地控制 tooltip 内容
return ` ${params.data.name}<br/>坐标: [ ${params.data.value[0].toFixed(4)}, ${params.data.value[1].toFixed(4)}]`;
}
}
}
]
};
// 设置配置项并渲染图表
myChart.setOption(option);
// 监听窗口大小变化,自动适配
window.addEventListener('resize', function () {
if (myChart) {
myChart.resize();
}
});
};
/**
* 获取地图容器ID
* @returns {string}
*/
const getMapContainerId = () => {
return 'echarts-map-container'; // 定义一个唯一的ID
};
/**
* 渲染 ECharts 地图内容到指定容器
* @param {HTMLElement} contentDiv - 标签页的内容容器
*/
const renderMap = (contentDiv) => {
// 清空内容区域
// contentDiv.innerHTML = '';
// 创建地图容器
const mapDiv = document.createElement('div');
mapDiv.id = getMapContainerId();
mapDiv.style.width = '100%';
mapDiv.style.height = '600px';
mapDiv.style.marginTop = '20px';
contentDiv.appendChild(mapDiv);
// 加载 GeoJSON 并初始化地图
// 使用 setTimeout 确保 DOM 元素已渲染后再加载地图数据
setTimeout(() => {
loadAndRegisterMap();
}, 100); // 延迟一点确保渲染完成
};
/**
* 销毁 ECharts 实例 (可选,用于性能优化或切换时清理)
*/
const destroyMap = () => {
if (myChart) {
myChart.dispose(); // 销毁实例,释放资源
myChart = null;
console.log('ECharts 地图已销毁');
}
};
// 返回公共方法
return {
renderMap,
destroyMap // 导出销毁方法,如果需要的话
};
})();

View File

@@ -0,0 +1,407 @@
/**
* ECharts 地图足迹模块 (支持钻取功能 - 点击省份查看下一级, 点击按钮返回)
*/
const MapFootprintModule = (() => {
let myChart = null; // ECharts 实例
let currentMapLevel = 'country'; // 当前地图层级 ('country', 'province')
let currentProvinceCode = null; // 当前显示的省份编码 (如果有的话)
// --- 配置 ---
const COUNTRY_MAP_NAME = 'china_custom';
const COUNTRY_GEOJSON_PATH = './static/js/echarts/china.geojson';
const PROVINCE_GEOJSON_BASE_PATH = './static/js/echarts/province/'; // 假设省份 GeoJSON 存放在这个目录
// --- 数据 ---
// 注意:这里的坐标可能需要根据你实际访问的城市进行微调
const visitedPlaces = [
{ name: "济南市", value: [117.024961, 36.682788], province: "山东省" },
{ name: "青岛市", value: [120.384423, 36.065918], province: "山东省" },
{ name: "淄博市", value: [118.055915, 36.813547], province: "山东省" },
{ name: "潍坊市", value: [119.162775, 36.705759], province: "山东省" },
{ name: "北京市", value: [116.407395, 39.904211], province: "北京" },
{ name: "杭州市", value: [120.153576, 30.287459], province: "浙江" },
{ name: "苏州市", value: [120.585316, 31.298886], province: "江苏" },
{ name: "扬州市", value: [119.421003, 32.393159], province: "江苏" },
{ name: "保定市", value: [115.482331, 38.867657], province: "河北" },
{ name: "石家庄市", value: [114.514891, 38.042309], province: "河北" },
{ name: "衡水市", value: [115.665996, 37.739574], province: "河北" },
{ name: "洛阳市", value: [112.454174, 34.618139], province: "河南" },
{ name: "南京市", value: [118.796877, 32.060255], province: "江苏" },
{ name: "无锡市", value: [120.311987, 31.490920], province: "江苏" },
{ name: "沧州市", value: [116.838581, 38.308094], province: "河北" },
{ name: "镇江市", value: [119.452753, 32.204402], province: "江苏" },
];
// 省份名称到其 GeoJSON 文件名的映射 (需要根据你的文件名调整)
const provinceNameToGeoFile = {
"山东省": "shandong.json",
// 你可以继续添加其他省份的映射,例如:
// "江苏省": "jiangsu.json",
// "河北省": "hebei.json",
// ...
};
// --- 工具函数 ---
/**
* 根据省份中文名获取其对应的 GeoJSON 文件完整路径
* @param {string} provinceName - 省份中文名
* @returns {string|null} - 文件路径或 null
*/
const getProvinceGeoPath = (provinceName) => {
const fileName = provinceNameToGeoFile[provinceName];
if (fileName) {
return ` ${PROVINCE_GEOJSON_BASE_PATH}${fileName}`;
}
console.warn(`未找到省份 " ${provinceName}" 对应的 GeoJSON 文件。`);
return null;
};
/**
* 根据地图层级和可选的省份编码生成 ECharts 配置
* @param {string} level - 地图层级 ('country' or 'province')
* @param {string} [provinceCode] - 省份编码 (当 level='province' 时使用)
* @returns {Object} - ECharts 配置对象
*/
const getEChartsOption = (level, provinceCode = null) => {
let geoMapName, roamSetting, zoomLevel, centerCoord, titleText;
let filteredVisitedPlaces = []; // 用于过滤足迹点
if (level === 'country') {
geoMapName = COUNTRY_MAP_NAME;
roamSetting = false; // 启用缩放和平移
zoomLevel = 1.1;
centerCoord = [104.06, 30]; // 中国中心
titleText = '';
filteredVisitedPlaces = visitedPlaces; // 显示所有足迹
} else if (level === 'province' && provinceCode) {
geoMapName = `province_ ${provinceCode}`; // 为省份地图创建唯一名称
roamSetting = false; // 省份地图也启用缩放平移
zoomLevel = 1.0; // 可以根据需要调整
centerCoord = [118.5, 36.5]; // 示例:山东中心
titleText = ` ${provinceCode} - 我的足迹`;
// 过滤出属于当前省份的足迹点
filteredVisitedPlaces = visitedPlaces.filter(place => place.province === provinceCode);
} else {
console.error("无效的地图层级或省份代码");
return {}; // 返回空配置
}
return {
title: {
text: titleText,
// subtext: level === 'country' ? '点击省份进入详情' : '点击下方返回按钮返回全国地图',
subtext: '读万卷书,不如行万里路。见多才会识广~',
left: 'center',
textStyle: {
fontSize: 18
},
subtextStyle: {
fontSize: 12
}
},
tooltip: {
trigger: 'item',
formatter: function(params) {
if (params.componentType === 'series' && params.seriesType === 'scatter') {
// 足迹点的 tooltip
if (params.value && params.value.length >= 2) {
return params.name + '<br/>经度: ' + params.value[0].toFixed(4) + '<br/>纬度: ' + params.value[1].toFixed(4);
}
return params.name;
} else if (params.componentType === 'geo') {
// 地图区域的 tooltip
return params.name;
}
return params.name;
}
},
geo: {
map: geoMapName,
roam: roamSetting,
zoom: zoomLevel,
center: centerCoord,
emphasis: {
itemStyle: {
areaColor: '#f0f0f0',
borderColor: '#409EFF',
borderWidth: 1
}
},
itemStyle: {
areaColor: '#eef2ff',
borderColor: '#333'
}
},
series: [
{
name: '足迹',
type: 'scatter',
coordinateSystem: 'geo',
data: filteredVisitedPlaces,
symbol: 'pin',
symbolSize: 18,
itemStyle: {
color: '#FF6B6B',
borderColor: '#fff',
borderWidth: 1,
},
emphasis: {
itemStyle: {
color: '#dd4444'
}
},
tooltip: {
formatter: function(params) {
return ` ${params.data.name}<br/>坐标: [ ${params.data.value[0].toFixed(4)}, ${params.data.value[1].toFixed(4)}]`;
}
}
}
]
};
};
/**
* 加载并注册指定路径的 GeoJSON 地图数据
* @param {string} path - GeoJSON 文件路径
* @param {string} mapName - 注册到 ECharts 的地图名称
* @returns {Promise<boolean>} - 成功或失败
*/
const loadAndRegisterSingleMap = async (path, mapName) => {
try {
console.log(`正在加载 GeoJSON: ${path}`);
const response = await fetch(path);
if (!response.ok) {
throw new Error(`加载 GeoJSON 失败: ${response.status} ${response.statusText}`);
}
const geoJsonData = await response.json();
// 注册地图
echarts.registerMap(mapName, geoJsonData);
console.log(`GeoJSON 地图数据 ${mapName} 加载并注册成功。`);
return true;
} catch (error) {
console.error('加载或注册 GeoJSON 地图时出错:', error);
return false;
}
};
/**
* 切换到指定的地图层级和区域
* @param {string} targetLevel - 目标层级 ('country' or 'province')
* @param {string} [targetProvinceCode] - 目标省份编码 (当 targetLevel='province' 时使用)
*/
const switchMap = async (targetLevel, targetProvinceCode = null) => {
if (!myChart) {
console.error("ECharts 实例不存在,无法切换地图。");
return;
}
let mapNameToUse, geoPathToLoad;
if (targetLevel === 'country') {
mapNameToUse = COUNTRY_MAP_NAME;
geoPathToLoad = COUNTRY_GEOJSON_PATH;
currentMapLevel = 'country';
currentProvinceCode = null;
} else if (targetLevel === 'province' && targetProvinceCode) {
mapNameToUse = `province_ ${targetProvinceCode}`;
const path = getProvinceGeoPath(targetProvinceCode);
if (!path) {
console.error(`无法切换到省份 ${targetProvinceCode}: 找不到对应的 GeoJSON 文件。`);
return;
}
geoPathToLoad = path;
currentMapLevel = 'province';
currentProvinceCode = targetProvinceCode;
} else {
console.error("无效的目标层级或省份代码。");
return;
}
// 尝试加载并注册目标地图数据
const loadSuccess = await loadAndRegisterSingleMap(geoPathToLoad, mapNameToUse);
if (!loadSuccess) {
console.error(`切换地图失败:无法加载 ${geoPathToLoad}`);
return;
}
// 生成新配置并应用
const newOption = getEChartsOption(targetLevel, targetProvinceCode);
myChart.setOption(newOption, true); // 使用 notMerge=true 确保完全替换
// 更新返回按钮的可见性
updateBackButtonVisibility();
};
// --- 新增函数:管理返回按钮 ---
/**
* 获取返回按钮的DOM元素
* @returns {HTMLElement|null}
*/
const getBackButtonElement = () => {
// 假设按钮ID是固定的可以根据需要调整
return document.getElementById('map-back-button');
};
/**
* 创建返回按钮并将其添加到图表容器中
* @param {HTMLElement} chartContainer - ECharts 图表容器
*/
const createBackButton = (chartContainer) => {
const button = document.createElement('button');
button.id = 'map-back-button';
button.textContent = '⬅返回全国地图';
button.style.position = 'absolute';
button.style.top = '10px';
button.style.left = '10px';
button.style.zIndex = 1000; // 确保按钮在图表之上
button.style.padding = '2px 5px'; // 减少内边距以更接近纯文本
button.style.fontSize = '14px'; // 根据需要调整字体大小
button.style.cursor = 'pointer';
button.style.border = 'none'; // 去掉边框
button.style.backgroundColor = 'transparent'; // 设置背景透明
button.style.display = 'none'; // 初始隐藏
button.onclick = () => {
console.log("点击了返回按钮。");
switchMap('country');
};
chartContainer.appendChild(button);
};
/**
* 根据当前地图层级更新返回按钮的可见性
*/
const updateBackButtonVisibility = () => {
const button = getBackButtonElement();
if (!button) {
console.warn("返回按钮 DOM 元素未找到,无法更新其可见性。");
return;
}
// 当前在省份地图时显示按钮,否则隐藏
button.style.display = (currentMapLevel === 'province') ? 'block' : 'none';
};
// --- /新增函数 ---
/**
* 初始化 ECharts 地图 (初始加载国家地图)
* @param {HTMLElement} container - 图表容器DOM元素
*/
const initEChartsMap = async (container) => {
if (!container) {
console.error('地图容器未找到');
return;
}
// 初始化 ECharts 实例
myChart = echarts.init(container);
// 创建并添加返回按钮
createBackButton(container);
// 首先加载并注册国家地图
const countryLoadSuccess = await loadAndRegisterSingleMap(COUNTRY_GEOJSON_PATH, COUNTRY_MAP_NAME);
if (!countryLoadSuccess) {
console.error("初始化失败:无法加载国家 GeoJSON。");
return; // 或者显示错误信息给用户
}
// 应用初始配置 (国家地图)
const initialOption = getEChartsOption('country');
myChart.setOption(initialOption);
// --- 添加事件监听 ---
// 监听地图区域点击事件
myChart.on('click', 'geo', function (params) {
if (currentMapLevel === 'country') {
// 当前是国家地图,点击省份则进入
const clickedProvinceName = params.name;
console.log(`点击了省份: ${clickedProvinceName}`);
// 尝试获取省份对应的 GeoJSON 文件名
const fileName = Object.keys(provinceNameToGeoFile).find(key => key === clickedProvinceName);
console.log(fileName)
if (fileName) {
const provinceCode = fileName; // 这里假设文件名就是省份简称
console.log(`即将切换到省份: ${provinceCode}`);
switchMap('province', provinceCode);
} else {
console.log(`未配置省份 " ${clickedProvinceName}" 的详细地图。`);
}
}
// 如果在省份地图上点击,可以有其他逻辑,比如不处理
});
// 监听窗口大小变化,自动适配
window.addEventListener('resize', function () {
if (myChart) {
myChart.resize();
}
});
};
/**
* 获取地图容器ID
* @returns {string}
*/
const getMapContainerId = () => {
return 'echarts-map-container';
};
/**
* 渲染 ECharts 地图内容到指定容器
* @param {HTMLElement} contentDiv - 标签页的内容容器
*/
const renderMap = (contentDiv) => {
// 清空内容区域
// contentDiv.innerHTML = '';
// 创建地图容器
const mapDiv = document.createElement('div');
mapDiv.id = getMapContainerId();
mapDiv.style.width = '100%';
mapDiv.style.height = '600px';
mapDiv.style.marginTop = '20px';
mapDiv.style.position = 'relative'; // 为绝对定位的按钮提供参考
contentDiv.appendChild(mapDiv);
// 加载 GeoJSON 并初始化地图
setTimeout(async () => {
const container = document.getElementById(getMapContainerId());
if (container) {
await initEChartsMap(container); // 使用 await 确保初始化完成
} else {
console.error('地图容器 DOM 元素未找到,无法初始化图表。');
}
}, 100);
};
/**
* 销毁 ECharts 实例 (可选,用于性能优化或切换时清理)
*/
const destroyMap = () => {
if (myChart) {
myChart.dispose();
myChart = null;
console.log('ECharts 地图已销毁');
// 可以考虑移除返回按钮
const button = getBackButtonElement();
if (button && button.parentNode) {
button.parentNode.removeChild(button);
}
}
};
// 返回公共方法
return {
renderMap,
destroyMap
};
})();

View File

@@ -1,229 +1,616 @@
// ./static/js/map-footprint.js
/**
* ECharts 地图足迹模块 (使用本地 GeoJSON)
* ECharts 地图足迹模块 (支持钻取功能 - 点击省份查看下一级, 点击按钮返回)
*/
const MapFootprintModule = (() => {
let myChart = null; // ECharts 实例
const MAP_NAME = 'china_custom'; // 为自定义地图起一个名字
let myChart = null; // ECharts 实例
let currentMapLevel = "country"; // 当前地图层级 ('country', 'province')
let currentProvinceCode = null; // 当前显示的省份编码 (如果有的话)
const visitedPlaces = [
{ name: "济南市", value: [117.024961, 36.682788] },
{ name: "青岛市", value: [120.384423, 36.065918] },
{ name: "淄博市", value: [118.055915, 36.813547] },
{ name: "潍坊市", value: [119.162775, 36.705759] },
{ name: "北京市", value: [116.407395, 39.904211] },
{ name: "杭州市", value: [120.153576, 30.287459] },
{ name: "苏州市", value: [120.585316, 31.298886] },
{ name: "扬州市", value: [119.421003, 32.393159] },
{ name: "保定市", value: [115.482331, 38.867657] },
{ name: "石家庄市", value: [114.514891, 38.042309] },
{ name: "衡水市", value: [115.665996, 37.739574] },
{ name: "洛阳市", value: [112.454174, 34.618139] },
{ name: "南京市", value: [118.796877, 32.060255] },
{ name: "无锡市", value: [120.311987, 31.490920] },
{ name: "沧州市", value: [116.838581, 38.308094] },
{ name: "镇江市", value: [119.452753, 32.204402] },
];
// --- 配置 ---
const COUNTRY_MAP_NAME = "china_custom";
const COUNTRY_GEOJSON_PATH = "./static/js/echarts/china.geojson";
const PROVINCE_GEOJSON_BASE_PATH = "./static/js/echarts/province/"; // 假设省份 GeoJSON 存放在这个目录
/**
* 加载 GeoJSON 地图数据并注册
*/
const loadAndRegisterMap = async () => {
try {
// console.log('开始加载 GeoJSON 地图数据...');
const response = await fetch('./static/js/echarts/china.geojson'); // 确保路径正确
if (!response.ok) {
throw new Error(`加载 GeoJSON 失败: ${response.status} ${response.statusText}`);
// --- 数据 ---
// 注意:这里的坐标可能需要根据你实际访问的城市进行微调
const visitedPlaces = [
{ name: "济南市", value: [117.024961, 36.682788], province: "山东省" },
{ name: "青岛市", value: [120.384423, 36.065918], province: "山东省" },
{ name: "淄博市", value: [118.055915, 36.813547], province: "山东省" },
{ name: "潍坊市", value: [119.162775, 36.705759], province: "山东省" },
{ name: "保定市", value: [115.482331, 38.867657], province: "河北省" },
{ name: "衡水市", value: [115.665996, 37.739574], province: "河北省" },
{ name: "沧州市", value: [116.838581, 38.308094], province: "河北省" },
{ name: "石家庄市", value: [114.514891, 38.042309], province: "河北省" },
{ name: "张家口市", value: [114.886714, 40.811943], province: "河北省" },
{ name: "洛阳市", value: [112.454174, 34.618139], province: "河南省" },
{ name: "昌平区", value: [116.235904, 40.218086], province: "北京市" },
{ name: "杭州市", value: [120.153576, 30.287459], province: "浙江省" },
{ name: "苏州市", value: [120.585316, 31.298886], province: "江苏省" },
{ name: "扬州市", value: [119.421003, 32.393159], province: "江苏省" },
{ name: "镇江市", value: [119.452753, 32.204402], province: "江苏省" },
{ name: "南京市", value: [118.796877, 32.060255], province: "江苏省" },
{ name: "无锡市", value: [120.311987, 31.49092], province: "江苏省" },
];
// 省份名称到其 GeoJSON 文件名的映射 (需要根据你的文件名调整)
const provinceNameToGeoFile = {
北京市: "北京市.geojson",
山东省: "山东省.geojson",
河北省: "河北省.geojson",
河南省: "河南省.geojson",
浙江省: "浙江省.geojson",
江苏省: "江苏省.geojson",
};
// --- 工具函数 ---
/**
* 根据省份中文名获取其对应的 GeoJSON 文件完整路径
* @param {string} provinceName - 省份中文名
* @returns {string|null} - 文件路径或 null
*/
const getProvinceGeoPath = (provinceName) => {
const fileName = provinceNameToGeoFile[provinceName];
if (fileName) {
return ` ${PROVINCE_GEOJSON_BASE_PATH}${fileName}`;
}
console.warn(`未找到省份 " ${provinceName}" 对应的 GeoJSON 文件。`);
return null;
};
/**
* 根据地图层级和可选的省份编码生成 ECharts 配置
* @param {string} level - 地图层级 ('country' or 'province')
* @param {string} [provinceCode] - 省份编码 (当 level='province' 时使用)
* @returns {Object} - ECharts 配置对象
*/
const getEChartsOption = (level, provinceCode = null) => {
let geoMapName, roamSetting, zoomLevel, centerCoord, titleText;
let filteredVisitedPlaces = []; // 用于过滤足迹点
let seriesConfig = []; // 存储所有 series
let visualMapConfig = null; // 存储 visualMap 配置 (仅省份需要)
if (level === "country") {
geoMapName = COUNTRY_MAP_NAME;
roamSetting = false; // 启用缩放和平移
zoomLevel = 1.1;
centerCoord = [104.06, 32]; // 中国中心
titleText = "";
filteredVisitedPlaces = visitedPlaces; // 显示所有足迹
// 国家地图:保持原有的 geo 和 scatter 配置
seriesConfig = [
{
name: "足迹",
type: "scatter",
coordinateSystem: "geo",
data: filteredVisitedPlaces,
symbol: "pin",
symbolSize: 18,
itemStyle: {
color: "#FF6B6B",
borderColor: "#fff",
borderWidth: 1,
},
emphasis: {
itemStyle: {
color: "#dd4444",
},
},
tooltip: {
formatter: function (params) {
return ` ${params.data.name}<br/>坐标: [ ${params.data.value[0].toFixed(4)}, ${params.data.value[1].toFixed(4)}]`;
},
},
},
];
} else if (level === "province" && provinceCode) {
geoMapName = `province_ ${provinceCode}`; // 为省份地图创建唯一名称
roamSetting = false; // 省份地图也启用缩放平移
zoomLevel = 1.0; // 可以根据需要调整
// 可以根据省份动态设置中心点,这里暂时固定
// centerCoord = [118.5, 36.5]; // 示例:山东中心
// 从 visitedPlaces 中找一个城市作为中心点,如果没有,则使用默认
const firstPlaceInProvince = visitedPlaces.find(
(place) => place.province === provinceCode,
);
centerCoord = firstPlaceInProvince
? firstPlaceInProvince.value
: [118.5, 36.5];
if (provinceCode == "山东省") {
centerCoord = [118.5, 36.5];
} else if (provinceCode == "河北省") {
centerCoord = [115.482331, 40];
} else if (provinceCode == "浙江省") {
centerCoord = [120.153576, 29.4];
} else if (provinceCode == "江苏省") {
centerCoord = [119, 32.9];
} else if (provinceCode == "河南省") {
centerCoord = [114, 34];
} else if (provinceCode == "北京市") {
centerCoord = [116.407395, 40.3];
}
console.log(firstPlaceInProvince.value);
titleText = ` ${provinceCode} - “印迹”`;
// 过滤出属于当前省份的足迹点
filteredVisitedPlaces = visitedPlaces.filter(
(place) => place.province === provinceCode,
);
// --- 为省份地图准备高亮数据 ---
// 提取当前省份内访问过的城市名称
const visitedCitiesInProvince = filteredVisitedPlaces.map(
(place) => place.name,
);
// 构造 map 系列的数据,访问过的城市 value 设为 1未访问的可以设为 0
// 为了实现高亮,我们只需要列出访问过的城市即可,未访问的城市会使用默认颜色
const provinceMapData = visitedCitiesInProvince.map((cityName) => ({
name: cityName, // GeoJSON 中区域的名称必须与此 name 匹配
value: 1, // 值为 1 表示访问过,用于 visualMap 区分
}));
// --- 配置 visualMap ---
visualMapConfig = {
show: false, // 通常不显示 visualMap 组件条
min: 0, // 最小值
max: 1, // 最大值
inRange: {
// 当 data 中 item.value 为 1 时,使用的颜色
color: ["#FF6B6B"], // 高亮颜色 (访问过的城市)
},
// outOfRange: {
// // 当 value 超出范围或未定义时,使用的颜色
// // 此颜色由 series.itemStyle.areaColor 控制
// },
calculable: true,
};
// --- 创建省份地图系列 (用于高亮城市) ---
// 关键修改:将 map 系列的 clickHandler 禁用,让 geo 组件处理点击
const provinceMapSeries = {
// name: "访问过的城市",
type: "map",
map: geoMapName, // 指向当前省份的 GeoJSON
roam: roamSetting,
zoom: zoomLevel,
center: centerCoord,
silent: true, // 设置为 true使 map 系列不响应鼠标事件,避免干扰 geo 的点击返回
label: {
show: true, // 可以选择是否显示城市名称标签
color: "#000", // 标签颜色
fontSize: 10,
},
emphasis: {
label: {
// 悬停时标签效果
show: true,
fontWeight: "bold",
},
itemStyle: {
// 悬停时区域效果
opacity: 0.8, // 降低透明度
// areaColor: '#ffcccc', // 也可以设置悬停颜色,但会受 visualMap 影响
},
},
// 数据驱动颜色
data: provinceMapData,
// 默认样式(未在 data 中定义的区域将使用此样式)
itemStyle: {
areaColor: "#eef2ff", // 未访问区域的默认颜色
borderColor: "#333",
borderWidth: 0.5,
},
};
// 将省份地图系列和 geo 组件添加到配置中
seriesConfig = [provinceMapSeries];
// geo 配置也需要包含省份 geo
// 注意:在返回全国地图时,需要重新设置 geo 为国家地图
// 这个 geo 组件会覆盖全局的 geo 配置
// 我们需要将 geo 配置移动到外面处理
} else {
console.error("无效的地图层级或省份代码");
return {}; // 返回空配置
}
// 构建最终的 option
const option = {
title: {
text: titleText,
subtext: "读万卷书,不如行万里路。见多才会识广~",
left: "center",
textStyle: {
fontSize: 18,
},
subtextStyle: {
fontSize: 12,
},
},
tooltip: {
trigger: "item",
formatter: function (params) {
if (params.seriesType === "scatter" && level === "country") {
// 国家地图的足迹点 tooltip
if (params.value && params.value.length >= 2) {
return (
params.name +
"<br/>经度: " +
params.value[0].toFixed(4) +
"<br/>纬度: " +
params.value[1].toFixed(4)
);
}
const geoJsonData = await response.json();
// 注册自定义地图
echarts.registerMap(MAP_NAME, geoJsonData);
// console.log('GeoJSON 地图数据加载并注册成功。');
// 地图注册成功后,初始化图表
const container = document.getElementById(getMapContainerId());
if (container) {
initEChartsMap(container);
} else {
console.error('地图容器 DOM 元素未找到,无法初始化图表。');
}
} catch (error) {
console.error('加载或注册 GeoJSON 地图时出错:', error);
// 可以选择在内容区域显示错误信息
const contentDiv = document.getElementById('footprint-content'); // 假设足迹内容区域ID是这个
if (contentDiv) {
contentDiv.innerHTML = '<p>加载地图数据失败,请检查网络连接或稍后重试。</p>';
}
}
};
/**
* 初始化 ECharts 地图
* @param {HTMLElement} container - 图表容器DOM元素
*/
const initEChartsMap = (container) => {
if (!container) {
console.error('地图容器未找到');
return;
}
// 初始化 ECharts 实例
myChart = echarts.init(container);
// ECharts 配置项 (使用注册的地图名称)
const option = {
title: {
text: '',
subtext: '读万卷书,不如行万里路。见多才会识广~',
left: 'center',
textStyle: {
fontSize: 18
return params.name;
} else if (params.seriesType === "map" && level === "province") {
// 省份地图的区域 tooltip
// params.value 是 data 中对应的 value
const status = params.value === 1 ? "访问过" : "未访问";
return `区域: ${params.name}<br/>状态: ${status}`;
} else if (
params.componentType === "geo" &&
level === "province" &&
params.componentSubType === "map"
) {
// 省份地图的 geo 区域 tooltip (如果需要)
return `点击返回全国: ${params.name}`;
}
return params.name;
},
},
// geo 组件配置
geo:
level === "country"
? {
map: geoMapName,
roam: roamSetting,
zoom: zoomLevel,
center: centerCoord,
emphasis: {
itemStyle: {
areaColor: "#f0f0f0", // geo 强调样式,当没有 series 时生效
borderColor: "#409EFF",
borderWidth: 1,
},
subtextStyle: {
fontSize: 12
}
},
tooltip: {
trigger: 'item',
formatter: function(params) {
if (params.value && params.value.length >= 2) {
return params.name + '<br/>经度: ' + params.value[0].toFixed(4) + '<br/>纬度: ' + params.value[1].toFixed(4);
}
return params.name; // 如果没有坐标信息,只显示地名
}
},
toolbox: {
show: false,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
dataView: { readOnly: false },
restore: {},
saveAsImage: {}
}
},
visualMap: {
show: false, // 是否显示 visualMap 组件,这里设为 false因为我们用 symbolSize 控制点大小
min: 0,
max: 100,
left: 'left',
top: 'bottom',
text: ['High', 'Low'], // 文本,默认为数值文本
calculable: true
},
geo: {
map: MAP_NAME, // 使用注册的自定义地图名称
roam: false, // 是否开启鼠标缩放和平移漫游
zoom: 1.1, // 当前视角的缩放比例
center: [104.06, 30],
emphasis: { // 高亮状态下的样式
itemStyle: {
areaColor: '#f0f0f0', // 高亮时省份的颜色
borderColor: '#409EFF', // 高亮时省份的边框颜色
borderWidth: 1
}
},
itemStyle: { // 普通状态下的样式
areaColor: '#eef2ff', // 未选中时省份的颜色
borderColor: '#333' // 未选中时省份的边框颜色
}
},
series: [
{
name: '足迹',
type: 'scatter', // 使用散点图表示标记点
coordinateSystem: 'geo', // 指定坐标系为地理坐标系
data: visitedPlaces, // 使用我们的数据
symbol: 'pin', // 使用内置符号 'pin'
symbolSize: 18, // 标记点的大小
itemStyle: { // 普通状态下的样式
color: '#FF6B6B', // 设置图标颜色
borderColor: '#fff', // 设置图标边框颜色
borderWidth: 1, // 设置图标边框宽度
},
emphasis: { // 高亮状态
itemStyle: {
color: '#dd4444' // 高亮时点的颜色
}
},
tooltip: {
formatter: function(params) {
// 这里可以更精细地控制 tooltip 内容
return ` ${params.data.name}<br/>坐标: [ ${params.data.value[0].toFixed(4)}, ${params.data.value[1].toFixed(4)}]`;
}
}
}
]
};
// 设置配置项并渲染图表
myChart.setOption(option);
// 监听窗口大小变化,自动适配
window.addEventListener('resize', function () {
if (myChart) {
myChart.resize();
},
itemStyle: {
areaColor: "#eef2ff", // geo 默认样式,当没有 series 时生效
borderColor: "#333",
},
}
});
: {
// 省份地图的 geo 配置
id: "province_geo_for_clicks", // 给 geo 组件一个 ID 便于识别
map: geoMapName,
roam: roamSetting,
zoom: zoomLevel,
center: centerCoord,
silent: false, // 设置为 false允许 geo 组件响应点击
// 隐藏 geo 组件的视觉表现,因为高亮由 series 控制
itemStyle: {
areaColor: "transparent", // 透明填充
borderColor: "transparent", // 透明边框
},
emphasis: {
itemStyle: {
areaColor: "rgba(64, 158, 255, 0.2)", // 可选:点击时的视觉反馈
borderColor: "transparent",
},
},
},
series: seriesConfig, // 使用构建好的 series 配置
};
/**
* 获取地图容器ID
* @returns {string}
*/
const getMapContainerId = () => {
return 'echarts-map-container'; // 定义一个唯一的ID
// 如果当前是省份地图,添加 visualMap 配置
if (visualMapConfig) {
option.visualMap = visualMapConfig;
}
return option;
};
/**
* 加载并注册指定路径的 GeoJSON 地图数据
* @param {string} path - GeoJSON 文件路径
* @param {string} mapName - 注册到 ECharts 的地图名称
* @returns {Promise<boolean>} - 成功或失败
*/
const loadAndRegisterSingleMap = async (path, mapName) => {
try {
console.log(`正在加载 GeoJSON: ${path}`);
const response = await fetch(path);
if (!response.ok) {
throw new Error(
`加载 GeoJSON 失败: ${response.status} ${response.statusText}`,
);
}
const geoJsonData = await response.json();
// 注册地图
echarts.registerMap(mapName, geoJsonData);
console.log(`GeoJSON 地图数据 ${mapName} 加载并注册成功。`);
return true;
} catch (error) {
console.error("加载或注册 GeoJSON 地图时出错:", error);
return false;
}
};
/**
* 切换到指定的地图层级和区域
* @param {string} targetLevel - 目标层级 ('country' or 'province')
* @param {string} [targetProvinceCode] - 目标省份编码 (当 targetLevel='province' 时使用)
*/
const switchMap = async (targetLevel, targetProvinceCode = null) => {
if (!myChart) {
console.error("ECharts 实例不存在,无法切换地图。");
return;
}
let mapNameToUse, geoPathToLoad;
if (targetLevel === "country") {
mapNameToUse = COUNTRY_MAP_NAME;
geoPathToLoad = COUNTRY_GEOJSON_PATH;
currentMapLevel = "country";
currentProvinceCode = null;
} else if (targetLevel === "province" && targetProvinceCode) {
mapNameToUse = `province_ ${targetProvinceCode}`;
const path = getProvinceGeoPath(targetProvinceCode);
if (!path) {
console.error(
`无法切换到省份 ${targetProvinceCode}: 找不到对应的 GeoJSON 文件。`,
);
return;
}
geoPathToLoad = path;
currentMapLevel = "province";
currentProvinceCode = targetProvinceCode;
} else {
console.error("无效的目标层级或省份代码。");
return;
}
// 尝试加载并注册目标地图数据
const loadSuccess = await loadAndRegisterSingleMap(
geoPathToLoad,
mapNameToUse,
);
if (!loadSuccess) {
console.error(`切换地图失败:无法加载 ${geoPathToLoad}`);
return;
}
// 生成新配置并应用
const newOption = getEChartsOption(targetLevel, targetProvinceCode);
myChart.setOption(newOption, true); // 使用 notMerge=true 确保完全替换
// 更新返回按钮的可见性
updateBackButtonVisibility();
};
// --- 新增函数:管理返回按钮 ---
/**
* 获取返回按钮的DOM元素
* @returns {HTMLElement|null}
*/
const getBackButtonElement = () => {
// 假设按钮ID是固定的可以根据需要调整
return document.getElementById("map-back-button");
};
/**
* 创建返回按钮并将其添加到图表容器中
* @param {HTMLElement} chartContainer - ECharts 图表容器
*/
const createBackButton = (chartContainer) => {
const button = document.createElement("button");
button.id = "map-back-button";
button.textContent = "⬅返回全国地图";
button.style.position = "absolute";
button.style.top = "10px";
button.style.left = "10px";
button.style.zIndex = 1000; // 确保按钮在图表之上
button.style.padding = "2px 5px"; // 减少内边距以更接近纯文本
button.style.fontSize = "14px"; // 根据需要调整字体大小
button.style.cursor = "pointer";
button.style.border = "none"; // 去掉边框
button.style.backgroundColor = "transparent"; // 设置背景透明
button.style.display = "none"; // 初始隐藏
button.onclick = () => {
console.log("点击了返回按钮。");
switchMap("country");
};
/**
* 渲染 ECharts 地图内容到指定容器
* @param {HTMLElement} contentDiv - 标签页的内容容器
*/
const renderMap = (contentDiv) => {
// 清空内容区域
contentDiv.innerHTML = '';
chartContainer.appendChild(button);
};
// 创建标题
const titleElement = document.createElement('h2');
titleElement.setAttribute('data-i18n', 'footprint_map_h2'); // 假设你在语言文件中有这个键
titleElement.textContent = '历史足迹'; // 默认文本
contentDiv.appendChild(titleElement);
/**
* 根据当前地图层级更新返回按钮的可见性
*/
const updateBackButtonVisibility = () => {
const button = getBackButtonElement();
if (!button) {
console.warn("返回按钮 DOM 元素未找到,无法更新其可见性。");
return;
}
// 当前在省份地图时显示按钮,否则隐藏
button.style.display = currentMapLevel === "province" ? "block" : "none";
};
// 创建地图容器
const mapDiv = document.createElement('div');
mapDiv.id = getMapContainerId();
mapDiv.style.width = '100%';
mapDiv.style.height = '600px'; // 设置地图高度,可以根据需要调整
mapDiv.style.marginTop = '20px'; // 可选:添加上边距
// --- /新增函数 ---
/**
* 初始化 ECharts 地图 (初始加载国家地图)
* @param {HTMLElement} container - 图表容器DOM元素
*/
const initEChartsMap = async (container) => {
if (!container) {
console.error("地图容器未找到");
return;
}
contentDiv.appendChild(mapDiv);
// 初始化 ECharts 实例
myChart = echarts.init(container);
// 加载 GeoJSON 并初始化地图
// 使用 setTimeout 确保 DOM 元素已渲染后再加载地图数据
setTimeout(() => {
loadAndRegisterMap();
}, 100); // 延迟一点确保渲染完成
};
// 创建并添加返回按钮
createBackButton(container);
/**
* 销毁 ECharts 实例 (可选,用于性能优化或切换时清理)
*/
const destroyMap = () => {
if (myChart) {
myChart.dispose(); // 销毁实例,释放资源
myChart = null;
console.log('ECharts 地图已销毁');
// 首先加载并注册国家地图
const countryLoadSuccess = await loadAndRegisterSingleMap(
COUNTRY_GEOJSON_PATH,
COUNTRY_MAP_NAME,
);
if (!countryLoadSuccess) {
console.error("初始化失败:无法加载国家 GeoJSON。");
return; // 或者显示错误信息给用户
}
// 应用初始配置 (国家地图)
const initialOption = getEChartsOption("country");
myChart.setOption(initialOption);
// --- 添加事件监听 ---
// 监听 geo 组件的点击事件
// 关键修改:现在 geo 组件在省份地图时也存在,并且不静默
myChart.on("click", "geo", function (params) {
console.log("Geo component clicked:", params.componentId, params.name); // Debug log
if (currentMapLevel === "country") {
// 当前是国家地图,点击省份则进入
const clickedProvinceName = params.name;
console.log(`点击了省份: ${clickedProvinceName}`);
// 尝试获取省份对应的 GeoJSON 文件名
const fileName = Object.keys(provinceNameToGeoFile).find(
(key) => key === clickedProvinceName,
);
console.log(fileName);
if (fileName) {
const provinceCode = fileName; // 这里假设文件名就是省份简称
// console.log(`即将切换到省份: ${provinceCode}`);
switchMap("province", provinceCode);
} else {
showModal();
console.log(`未配置省份 " ${clickedProvinceName}" 的详细地图。`);
}
};
} else if (
currentMapLevel === "province" &&
params.componentId === "province_geo_for_clicks"
) {
// 当前是省份地图,且点击的是省份 geo 组件,则返回全国地图
console.log("点击省份 geo 组件,返回全国地图");
switchMap("country");
}
// 如果在省份地图上点击,但不是省份 geo 组件(理论上不会发生,因为 series 是 silent 的),可以有其他逻辑,比如不处理
});
// 返回公共方法
return {
renderMap,
destroyMap // 导出销毁方法,如果需要的话
};
// 监听窗口大小变化,自动适配
window.addEventListener("resize", function () {
if (myChart) {
myChart.resize();
}
});
};
})();
/**
* 获取地图容器ID
* @returns {string}
*/
const getMapContainerId = () => {
return "echarts-map-container";
};
/**
* 渲染 ECharts 地图内容到指定容器
* @param {HTMLElement} contentDiv - 标签页的内容容器
*/
const renderMap = (contentDiv) => {
// 清空内容区域
// contentDiv.innerHTML = '';
// 创建地图容器
const mapDiv = document.createElement("div");
mapDiv.id = getMapContainerId();
mapDiv.style.width = "100%";
mapDiv.style.height = "600px";
mapDiv.style.marginTop = "20px";
mapDiv.style.position = "relative"; // 为绝对定位的按钮提供参考
contentDiv.appendChild(mapDiv);
// 加载 GeoJSON 并初始化地图
setTimeout(async () => {
const container = document.getElementById(getMapContainerId());
if (container) {
await initEChartsMap(container); // 使用 await 确保初始化完成
} else {
console.error("地图容器 DOM 元素未找到,无法初始化图表。");
}
}, 100);
};
/**
* 销毁 ECharts 实例 (可选,用于性能优化或切换时清理)
*/
const destroyMap = () => {
if (myChart) {
myChart.dispose();
myChart = null;
console.log("ECharts 地图已销毁");
// 可以考虑移除返回按钮
const button = getBackButtonElement();
if (button && button.parentNode) {
button.parentNode.removeChild(button);
}
}
};
// 返回公共方法
return {
renderMap,
destroyMap,
};
})();
function showModal() {
document.getElementById("modalOverlay").style.display = "block";
}
const closeModal = () => {
document.getElementById('modalOverlay').style.display = 'none';
};
// --- 页面加载完成后执行 ---
document.addEventListener('DOMContentLoaded', function() {
// 获取遮罩层和内容区域元素
const modalOverlay = document.getElementById('modalOverlay');
const modalContent = document.getElementById('modalContent');
// 定义点击遮罩层的处理函数
function handleOverlayClick(event) {
// 检查点击的目标是否是遮罩层本身(而不是它的子元素,比如里面的 p 或 button
if (event.target === modalOverlay) {
closeModal(); // 如果是,就关闭弹窗
}
}
// 为遮罩层添加点击事件监听器
modalOverlay.addEventListener('click', handleOverlayClick);
// --- 示例:如何显示弹窗 (供参考) ---
// window.showModal = function() {
// modalOverlay.style.display = 'block';
// // 注意:如果每次都显示,事件监听器只需要添加一次,或者在这里检查是否已存在
// }
});