380 lines
16 KiB
HTML
380 lines
16 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>全国采集数量分布地图生成器</title>
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.3/echarts.min.js"></script>
|
||
<!-- 引入中国地图数据 -->
|
||
<script src="https://fastly.jsdelivr.net/npm/echarts@4.9.0/map/js/china.js"></script>
|
||
<style>
|
||
#map-container {
|
||
height: calc(100vh - 120px);
|
||
min-height: 500px;
|
||
}
|
||
.custom-card {
|
||
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||
border-radius: 12px;
|
||
background: white;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="bg-slate-50 text-slate-900 font-sans">
|
||
|
||
<header class="bg-blue-600 text-white p-4 shadow-md">
|
||
<div class="container mx-auto flex justify-between items-center">
|
||
<h1 class="text-xl font-bold flex items-center gap-2">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||
全国采集数量分布地图
|
||
</h1>
|
||
<p class="text-sm opacity-80">支持批量导入与独立导出</p>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="container mx-auto p-4 lg:p-6 grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||
|
||
<!-- 左侧控制面板 -->
|
||
<div class="lg:col-span-1 space-y-4">
|
||
<div class="custom-card p-4 flex flex-col h-full">
|
||
<h2 class="font-semibold mb-3 flex items-center gap-2 text-slate-700">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 1 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||
数据管理
|
||
</h2>
|
||
<p class="text-xs text-slate-500 mb-2">每行格式:地区 数量</p>
|
||
<textarea
|
||
id="data-input"
|
||
class="w-full flex-grow p-3 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none resize-none font-mono mb-4"
|
||
placeholder="输入数据..."
|
||
></textarea>
|
||
|
||
<div class="space-y-3">
|
||
<div>
|
||
<label class="text-xs font-medium text-slate-500 mb-1 block">导出文件名</label>
|
||
<input
|
||
id="export-filename"
|
||
type="text"
|
||
class="w-full p-2 border border-slate-200 rounded-md text-sm outline-none focus:ring-2 focus:ring-blue-500"
|
||
placeholder="如:1月采集分布图"
|
||
>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-1 gap-2">
|
||
<button
|
||
onclick="updateChart()"
|
||
class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 rounded-lg transition-all shadow-sm active:scale-95 flex items-center justify-center gap-2"
|
||
>
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
|
||
实时刷新
|
||
</button>
|
||
<button
|
||
onclick="exportHTML()"
|
||
class="w-full bg-emerald-600 hover:bg-emerald-700 text-white font-medium py-2 rounded-lg transition-all shadow-sm active:scale-95 flex items-center justify-center gap-2"
|
||
>
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||
导出纯净地图
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="custom-card p-4">
|
||
<h3 class="text-sm font-semibold mb-2 text-slate-700">数据统计</h3>
|
||
<div class="space-y-2">
|
||
<div class="flex justify-between text-sm">
|
||
<span class="text-slate-500">地区总数:</span>
|
||
<span id="stat-count" class="font-bold">0</span>
|
||
</div>
|
||
<div class="flex justify-between text-sm">
|
||
<span class="text-slate-500">采集总量:</span>
|
||
<span id="stat-total" class="font-bold text-blue-600">0</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧地图展示 -->
|
||
<div class="lg:col-span-3">
|
||
<div class="custom-card p-4 relative overflow-hidden">
|
||
<div id="map-container" class="w-full"></div>
|
||
<div id="no-data" class="absolute inset-0 flex items-center justify-center bg-white/80 hidden z-10">
|
||
<p class="text-slate-400 italic">请在左侧输入有效数据以生成地图</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</main>
|
||
|
||
<script>
|
||
// 初始数据
|
||
const initialData = `大连市 158
|
||
湖南省 1777
|
||
新疆维吾尔 83
|
||
青海省 37
|
||
陕西省 1841
|
||
河北省 2714
|
||
青岛市 409
|
||
广东省 6726
|
||
福建省 1722
|
||
浙江省 3031
|
||
黑龙江省 500
|
||
云南省 934
|
||
深圳市 2332
|
||
江苏省 4712
|
||
甘肃省 786
|
||
江西省 1174
|
||
广西省 872
|
||
吉林省 399
|
||
内蒙古 701
|
||
厦门市 431
|
||
四川省 4560
|
||
河南省 2779
|
||
西藏 20
|
||
山西省 926
|
||
山东省 3229
|
||
安徽省 2307
|
||
海南省 384
|
||
宁夏 46
|
||
重庆市 838
|
||
上海市 1621
|
||
湖北省 1786
|
||
贵州省 1129
|
||
辽宁省 554
|
||
宁波市 567
|
||
天津市 452
|
||
北京市 1666`;
|
||
|
||
const cityToProvince = {
|
||
'大连市': '辽宁', '大连': '辽宁',
|
||
'青岛市': '山东', '青岛': '山东',
|
||
'深圳市': '广东', '深圳': '广东',
|
||
'厦门市': '福建', '厦门': '福建',
|
||
'宁波市': '浙江', '宁波': '浙江'
|
||
};
|
||
|
||
let myChart = null;
|
||
|
||
function init() {
|
||
document.getElementById('data-input').value = initialData;
|
||
myChart = echarts.init(document.getElementById('map-container'));
|
||
updateChart();
|
||
|
||
window.addEventListener('resize', () => {
|
||
myChart && myChart.resize();
|
||
});
|
||
}
|
||
|
||
function parseData(text) {
|
||
const lines = text.trim().split('\n');
|
||
const dataMap = {};
|
||
let total = 0;
|
||
|
||
lines.forEach(line => {
|
||
const parts = line.trim().split(/[\s\t,]+/);
|
||
if (parts.length >= 2) {
|
||
const rawName = parts[0];
|
||
const value = parseFloat(parts[1]);
|
||
if (isNaN(value)) return;
|
||
|
||
total += value;
|
||
let cleanName = rawName.replace(/(省|市|自治区|维吾尔|壮族|回族)/g, '');
|
||
if (cityToProvince[rawName]) {
|
||
cleanName = cityToProvince[rawName];
|
||
}
|
||
|
||
if (dataMap[cleanName]) {
|
||
dataMap[cleanName] += value;
|
||
} else {
|
||
dataMap[cleanName] = value;
|
||
}
|
||
}
|
||
});
|
||
|
||
return {
|
||
list: Object.keys(dataMap).map(name => ({ name, value: dataMap[name] })),
|
||
total: total,
|
||
count: Object.keys(dataMap).length
|
||
};
|
||
}
|
||
|
||
function updateChart() {
|
||
const inputText = document.getElementById('data-input').value;
|
||
const parsed = parseData(inputText);
|
||
|
||
if (parsed.list.length === 0) {
|
||
document.getElementById('no-data').classList.remove('hidden');
|
||
return;
|
||
} else {
|
||
document.getElementById('no-data').classList.add('hidden');
|
||
}
|
||
|
||
document.getElementById('stat-count').innerText = parsed.count;
|
||
document.getElementById('stat-total').innerText = parsed.total.toLocaleString();
|
||
|
||
const maxVal = Math.max(...parsed.list.map(i => i.value), 100);
|
||
|
||
const option = getChartOption(parsed, maxVal);
|
||
myChart.setOption(option);
|
||
}
|
||
|
||
function getChartOption(parsed, maxVal) {
|
||
return {
|
||
backgroundColor: '#ffffff',
|
||
title: {
|
||
text: '全国采集数量分布地图',
|
||
left: 'center',
|
||
top: 20,
|
||
textStyle: { color: '#1e293b', fontSize: 24, fontWeight: 'bold' }
|
||
},
|
||
tooltip: {
|
||
trigger: 'item',
|
||
formatter: function(params) {
|
||
if (isNaN(params.value)) return params.name + ': 无数据';
|
||
return `${params.name}<br/>采集数量: <span style="font-weight:bold;color:#2563eb">${params.value.toLocaleString()}</span>`;
|
||
},
|
||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||
borderRadius: 8,
|
||
padding: 10,
|
||
textStyle: { color: '#334155' }
|
||
},
|
||
visualMap: {
|
||
min: 0,
|
||
max: maxVal,
|
||
left: 40,
|
||
bottom: 40,
|
||
text: ['高', '低'],
|
||
calculable: true,
|
||
inRange: {
|
||
color: ['#f0f9ff', '#bae6fd', '#38bdf8', '#0284c7', '#075985']
|
||
}
|
||
},
|
||
geo: {
|
||
map: 'china',
|
||
roam: true,
|
||
zoom: 1.1,
|
||
label: {
|
||
show: true,
|
||
fontSize: 11,
|
||
lineHeight: 15,
|
||
color: '#334155',
|
||
fontWeight: 'bold',
|
||
textBorderColor: '#ffffff',
|
||
textBorderWidth: 2,
|
||
formatter: function(params) {
|
||
if (params.name === '香港' || params.name === '澳门') return '';
|
||
const item = parsed.list.find(i => i.name === params.name);
|
||
return item && item.value > 0 ? `${params.name}\n${item.value}` : params.name;
|
||
}
|
||
},
|
||
itemStyle: {
|
||
borderColor: 'rgba(255,255,255,0.6)',
|
||
borderWidth: 1,
|
||
areaColor: '#f8fafc'
|
||
},
|
||
emphasis: {
|
||
itemStyle: { areaColor: '#fbbf24' },
|
||
label: { color: '#000' }
|
||
},
|
||
regions: [
|
||
{ name: '香港', label: { show: false } },
|
||
{ name: '澳门', label: { show: false } }
|
||
]
|
||
},
|
||
series: [
|
||
{
|
||
type: 'map',
|
||
geoIndex: 0,
|
||
data: parsed.list
|
||
}
|
||
]
|
||
};
|
||
}
|
||
|
||
// 导出功能:生成仅含地图的纯净 HTML
|
||
function exportHTML() {
|
||
const currentDataText = document.getElementById('data-input').value;
|
||
const customFilename = document.getElementById('export-filename').value.trim();
|
||
const parsed = parseData(currentDataText);
|
||
const maxVal = Math.max(...parsed.list.map(i => i.value), 100);
|
||
|
||
// 构建纯净版模板
|
||
const cleanTemplate = `<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>全国采集数量分布地图</title>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.3/echarts.min.js"><\/script>
|
||
<script src="https://fastly.jsdelivr.net/npm/echarts@4.9.0/map/js/china.js"><\/script>
|
||
<style>
|
||
body, html { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #fff; }
|
||
#map { width: 100vw; height: 100vh; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="map"></div>
|
||
<script>
|
||
const parsedData = ${JSON.stringify(parsed)};
|
||
const maxVal = ${maxVal};
|
||
|
||
const myChart = echarts.init(document.getElementById('map'));
|
||
const option = {
|
||
backgroundColor: '#ffffff',
|
||
title: {
|
||
text: '全国采集数量分布地图',
|
||
left: 'center',
|
||
top: 30,
|
||
textStyle: { color: '#1e293b', fontSize: 26, fontWeight: 'bold' }
|
||
},
|
||
tooltip: {
|
||
trigger: 'item',
|
||
formatter: function(params) {
|
||
if (isNaN(params.value)) return params.name + ': 无数据';
|
||
return params.name + '<br/>采集数量: ' + params.value.toLocaleString();
|
||
}
|
||
},
|
||
visualMap: {
|
||
min: 0, max: maxVal, left: 50, bottom: 50, text: ['高', '低'], calculable: true,
|
||
inRange: { color: ['#f0f9ff', '#bae6fd', '#38bdf8', '#0284c7', '#075985'] }
|
||
},
|
||
geo: {
|
||
map: 'china', roam: true, zoom: 1.1,
|
||
label: {
|
||
show: true, fontSize: 12, lineHeight: 16, color: '#334155', fontWeight: 'bold',
|
||
textBorderColor: '#ffffff', textBorderWidth: 2,
|
||
formatter: function(params) {
|
||
if (params.name === '香港' || params.name === '澳门') return '';
|
||
const item = parsedData.list.find(i => i.name === params.name);
|
||
return item && item.value > 0 ? params.name + '\\n' + item.value : params.name;
|
||
}
|
||
},
|
||
itemStyle: { borderColor: 'rgba(255,255,255,0.6)', borderWidth: 1, areaColor: '#f8fafc' },
|
||
emphasis: { itemStyle: { areaColor: '#fbbf24' } },
|
||
regions: [{ name: '香港', label: { show: false } }, { name: '澳门', label: { show: false } }]
|
||
},
|
||
series: [{ type: 'map', geoIndex: 0, data: parsedData.list }]
|
||
};
|
||
myChart.setOption(option);
|
||
window.addEventListener('resize', () => myChart.resize());
|
||
<\/script>
|
||
</body>
|
||
</html>`;
|
||
|
||
const blob = new Blob([cleanTemplate], { type: 'text/html' });
|
||
const a = document.createElement('a');
|
||
const date = new Date().toISOString().slice(0,10);
|
||
|
||
// 使用自定义名称或默认名称
|
||
const finalFilename = customFilename ? `${customFilename}.html` : `全国采集地图_纯净版_${date}.html`;
|
||
|
||
a.href = URL.createObjectURL(blob);
|
||
a.download = finalFilename;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
}
|
||
|
||
window.onload = init;
|
||
</script>
|
||
</body>
|
||
</html> |