<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>零碳园区GIS地图展示原型 (V4.1 - 已修复)</title>
<!-- 字体与图标库 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
<!-- Mapbox GL JS 库 -->
<link href="https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.js"></script>
<style>
/* 1. 整体设计规范 */
:root {
--bg-color: #0A1931;
--primary-color: #27E1C1;
--secondary-color: #00FFAB;
--accent-color: #409EFF;
--text-color: #E0E6F1;
--text-muted-color: #A9C1D9;
--warn-color: #FFC107;
--danger-color: #F44336;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Noto Sans SC', sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
overflow: hidden;
}
/* 2. 界面总体布局 */
.main-container {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
}
#map {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* 3.1 中央地图视区元素 */
.info-label {
position: absolute;
width: 160px;
height: 70px;
background-color: rgba(10, 25, 49, 0.8);
border-left: 3px solid var(--primary-color);
padding: 8px 12px;
cursor: pointer;
backdrop-filter: blur(5px);
transition: all 0.3s ease;
}
.info-label:hover {
transform: scale(1.05);
background-color: rgba(10, 25, 49, 1);
}
.info-label .title {
font-size: 14px;
font-weight: 500;
color: var(--text-color);
}
.info-label .data {
font-size: 22px;
font-weight: 700;
color: var(--secondary-color);
}
.info-label .unit {
font-size: 12px;
color: var(--text-muted-color);
margin-left: 4px;
}
.custom-marker {
width: 20px;
height: 20px;
background-color: var(--primary-color);
border-radius: 50%;
border: 2px solid white;
box-shadow: 0 0 10px var(--primary-color);
cursor: pointer;
}
/* 3.2 & 3.3 左右侧图标入口区 */
.icon-bar {
position: absolute;
top: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
gap: 20px;
z-index: 10;
}
.left-bar { left: 20px; }
.right-bar { right: 20px; }
.icon-btn {
position: relative;
width: 56px;
height: 56px;
background-color: rgba(10, 30, 60, 0.7);
border: 1px solid var(--accent-color);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
color: var(--text-muted-color);
cursor: pointer;
transition: all 0.3s ease;
}
.icon-btn:hover {
background-color: var(--accent-color);
color: white;
transform: scale(1.1);
}
.layer-toggle {
position: absolute;
top: 2px;
right: 2px;
width: 18px;
height: 18px;
background-color: rgba(0,0,0,0.5);
color: var(--text-muted-color);
border-radius: 50%;
font-size: 10px;
display: flex;
justify-content: center;
align-items: center;
}
.layer-toggle.active {
color: var(--secondary-color);
}
/* 3.4 弹出式卡片 */
.side-card {
position: absolute;
top: 60px; /* 预留顶部空间 */
width: 450px;
height: 960px; /* 1080 - 60*2 */
background: linear-gradient(to bottom, rgba(10, 25, 49, 0.95), rgba(10, 25, 49, 0.85));
backdrop-filter: blur(10px);
border: 1px solid var(--accent-color);
border-radius: 8px;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
z-index: 20;
display: flex;
flex-direction: column;
transition: transform 0.5s ease-in-out, opacity 0.5s ease-in-out;
opacity: 0;
pointer-events: none;
}
.side-card.left {
left: 86px; /* 20px margin + 64px icon bar */
transform: translateX(-110%);
}
.side-card.right {
right: 86px;
transform: translateX(110%);
}
.side-card.show {
transform: translateX(0);
opacity: 1;
pointer-events: auto;
}
.card-header {
padding: 15px 20px;
border-bottom: 1px solid rgba(64, 158, 255, 0.3);
display: flex;
align-items: center;
gap: 15px;
flex-shrink: 0;
}
.card-header h3 {
font-size: 20px;
color: white;
flex-grow: 1;
}
.card-close-btn {
font-size: 20px;
color: var(--text-muted-color);
cursor: pointer;
}
.card-close-btn:hover {
color: white;
}
.card-body {
padding: 20px;
flex-grow: 1;
overflow-y: auto;
}
/* 自定义滚动条 */
.card-body::-webkit-scrollbar { width: 6px; }
.card-body::-webkit-scrollbar-track { background: transparent; }
.card-body::-webkit-scrollbar-thumb { background: var(--accent-color); border-radius: 3px; }
/* 列表卡片内容 */
.search-stats {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.search-stats input {
background-color: rgba(0,0,0,0.3);
border: 1px solid var(--accent-color);
border-radius: 4px;
color: white;
padding: 8px 12px;
width: 60%;
}
.search-stats .stats {
font-size: 14px;
color: var(--text-muted-color);
}
.item-list .list-item {
background-color: rgba(64, 158, 255, 0.1);
border-radius: 4px;
padding: 15px;
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 15px;
cursor: pointer;
transition: all 0.3s ease;
}
.item-list .list-item:hover {
background-color: rgba(64, 158, 255, 0.2);
border-left: 3px solid var(--primary-color);
}
.list-item .status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: var(--secondary-color);
}
.list-item .item-info {
flex-grow: 1;
}
.item-info .item-title { font-size: 16px; font-weight: 500; color: white; }
.item-info .item-subtitle { font-size: 12px; color: var(--text-muted-color); }
.list-item .item-data { font-size: 18px; font-weight: 700; color: var(--accent-color); }
/* 详情卡片内容 */
.detail-tabs {
display: flex;
gap: 1px;
background-color: rgba(64, 158, 255, 0.3);
margin-bottom: 20px;
}
.detail-tabs .tab {
flex-grow: 1;
padding: 12px;
text-align: center;
background-color: rgba(10, 25, 49, 0.8);
cursor: pointer;
transition: all 0.3s ease;
}
.detail-tabs .tab.active, .detail-tabs .tab:hover {
background-color: var(--accent-color);
color: white;
}
.tab-content { display: none; }
.tab-content.active { display: block; }
.key-metrics {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.metric-card {
background-color: rgba(64, 158, 255, 0.1);
padding: 20px;
border-radius: 4px;
text-align: center;
}
.metric-card .label { font-size: 14px; color: var(--text-muted-color); margin-bottom: 10px; }
.metric-card .value { font-size: 32px; font-weight: 700; color: var(--primary-color); }
.metric-card .unit { font-size: 14px; margin-left: 5px; }
.param-list .param-item {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid rgba(64, 158, 255, 0.2);
}
.param-list .param-label { color: var(--text-muted-color); }
.param-list .param-value { color: white; font-weight: 500; }
</style>
</head>
<body>
<div class="main-container">
<!-- 3.1 中央地图视区 -->
<div id="map"></div>
<!-- 3.1.2 常驻信息标签 (示例) -->
<div id="info-label-1" class="info-label">
<div class="title">华虹半导体</div>
<div class="data">1,234 <span class="unit">kW</span></div>
</div>
<div id="info-label-2" class="info-label">
<div class="title">园区1号储能站</div>
<div class="data">-5.2 <span class="unit">MW</span></div>
</div>
<!-- 3.2 左侧功能入口区 -->
<div class="icon-bar left-bar">
<div class="icon-btn" title="碳排分析"><i class="fa-solid fa-smog"></i></div>
<div class="icon-btn" title="能耗分析"><i class="fa-solid fa-chart-line"></i></div>
<div class="icon-btn" title="指标评价"><i class="fa-solid fa-bullseye"></i></div>
<div class="icon-btn" title="告警中心"><i class="fa-solid fa-bell"></i></div>
</div>
<!-- 3.3 右侧图层控制区 -->
<div class="icon-bar right-bar">
<div class="icon-btn" id="btn-enterprise-list" title="企业列表">
<i class="fa-solid fa-building"></i>
<div class="layer-toggle active" id="toggle-enterprise-layer"><i class="fa-solid fa-eye"></i></div>
</div>
<div class="icon-btn" id="btn-pv-list" title="光伏列表">
<i class="fa-solid fa-solar-panel"></i>
<div class="layer-toggle active" id="toggle-pv-layer"><i class="fa-solid fa-eye"></i></div>
</div>
<div class="icon-btn" id="btn-storage-list" title="储能列表">
<i class="fa-solid fa-car-battery"></i>
<div class="layer-toggle active" id="toggle-storage-layer"><i class="fa-solid fa-eye"></i></div>
</div>
</div>
<!-- 3.2.2 详细信息卡片 (左侧) -->
<div id="detail-card" class="side-card left">
<div class="card-header">
<i class="fa-solid fa-car-battery fa-lg"></i>
<h3 id="detail-title">园区1号储能站</h3>
<i class="fa-solid fa-xmark card-close-btn" onclick="closeCard('detail-card')"></i>
</div>
<div class="card-body">
<div class="key-metrics">
<div class="metric-card">
<div class="label">荷电状态(SOC)</div>
<div class="value">88.5<span class="unit">%</span></div>
</div>
<div class="metric-card">
<div class="label">实时功率</div>
<div class="value">-5.2<span class="unit">MW</span></div>
</div>
</div>
<div class="detail-tabs">
<div class="tab active">实时状态</div>
<div class="tab">基础档案</div>
<div class="tab">历史数据</div>
</div>
<div class="tab-content active">
<div class="param-list">
<div class="param-item"><span class="param-label">健康状态(SOH)</span><span class="param-value">98.2 %</span></div>
<div class="param-item"><span class="param-label">电池簇平均温度</span><span class="param-value">28.5 ℃</span></div>
<div class="param-item"><span class="param-label">总电压 / 总电流</span><span class="param-value">750 V / -693 A</span></div>
<div class="param-item"><span class="param-label">今日累计充电量</span><span class="param-value">12.5 MWh</span></div>
<div class="param-item"><span class="param-label">今日累计放电量</span><span class="param-value">8.2 MWh</span></div>
</div>
</div>
</div>
</div>
<!-- 3.3.2 分类设施列表卡片 (右侧) -->
<div id="list-card" class="side-card right">
<div class="card-header">
<i class="fa-solid fa-car-battery fa-lg"></i>
<h3 id="list-title">储能设施列表</h3>
<i class="fa-solid fa-xmark card-close-btn" onclick="closeCard('list-card')"></i>
</div>
<div class="card-body">
<div class="search-stats">
<input type="text" placeholder="请输入设施名称">
<div class="stats">共 5 座,总容量 30MWh</div>
</div>
<div class="item-list">
<div class="list-item" onclick="openDetailCard()">
<div class="status-dot"></div>
<div class="item-info">
<div class="item-title">园区1号储能站</div>
<div class="item-subtitle">状态: 充电中 | SOC: 88.5%</div>
</div>
<div class="item-data">-5.2 MW</div>
</div>
<div class="list-item">
<div class="status-dot" style="background-color: var(--primary-color);"></div>
<div class="item-info">
<div class="item-title">华虹半导体用户侧储能</div>
<div class="item-subtitle">状态: 放电中 | SOC: 65.1%</div>
</div>
<div class="item-data">+2.1 MW</div>
</div>
<!-- 更多列表项... -->
</div>
</div>
</div>
</div>
<script>
// --- 初始化地图 ---
// ===================================================================
// VITAL: 请将 'YOUR_MAPBOX_ACCESS_TOKEN' 替换为您自己的Mapbox访问令牌
// ===================================================================
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/dark-v11', // 深色科技风格
center: [120.73, 31.27], // 模拟苏州工业园区位置
zoom: 13,
pitch: 45,
bearing: -17.6,
});
map.on('load', () => {
// 在地图上添加模拟的点位和常驻标签
const marker1 = new mapboxgl.Marker({ element: document.createElement('div'), className: 'custom-marker' })
.setLngLat([120.74, 31.28])
.addTo(map);
const label1 = document.getElementById('info-label-1');
const markerLabel1 = new mapboxgl.Marker({ element: label1, anchor: 'bottom-left', offset: [15, 0] })
.setLngLat([120.74, 31.28])
.addTo(map);
const label2 = document.getElementById('info-label-2');
const markerLabel2 = new mapboxgl.Marker({ element: label2, anchor: 'bottom-left', offset: [15, 0] })
.setLngLat([120.72, 31.26])
.addTo(map);
// 点击地图点位打开详情卡片
marker1.getElement().addEventListener('click', () => {
openDetailCard('华虹半导体详情');
});
});
// --- 交互逻辑 ---
// 打开/关闭卡片
function openCard(cardId) {
// 关闭所有可能打开的卡片,确保每次只显示一个
document.querySelectorAll('.side-card').forEach(card => card.classList.remove('show'));
// 打开指定的卡片
document.getElementById(cardId).classList.add('show');
}
function closeCard(cardId) {
document.getElementById(cardId).classList.remove('show');
}
function openDetailCard(title = '园区1号储能站') {
document.getElementById('detail-title').innerText = title;
openCard('detail-card');
}
// 右侧图层控制图标交互
document.getElementById('btn-storage-list').addEventListener('click', (event) => {
// 点击眼睛角标
if (event.target.closest('.layer-toggle')) {
event.stopPropagation(); // 阻止事件冒泡到父元素
const toggle = event.target.closest('.layer-toggle');
toggle.classList.toggle('active');
const icon = toggle.querySelector('i');
if (toggle.classList.contains('active')) {
icon.className = 'fa-solid fa-eye';
console.log('储能图层已显示');
} else {
icon.className = 'fa-solid fa-eye-slash';
console.log('储能图层已隐藏');
}
} else { // 点击图标本身
document.getElementById('list-title').innerText = "储能设施列表";
openCard('list-card');
}
});
// 模拟其他右侧按钮
document.getElementById('btn-enterprise-list').addEventListener('click', (e) => {
if (e.target.closest('.layer-toggle')) { e.stopPropagation(); return; }
document.getElementById('list-title').innerText = "企业列表";
openCard('list-card');
});
document.getElementById('btn-pv-list').addEventListener('click', (e) => {
if (e.target.closest('.layer-toggle')) { e.stopPropagation(); return; }
document.getElementById('list-title').innerText = "光伏设施列表";
openCard('list-card');
});
</script>
</body>
</html>
index.html
style.css
index.js
index.html