| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- <template>
- <div class="map-wrapper" @click.stop>
- <div ref="mapContainer" class="map-container"></div>
- </div>
- </template>
- <script setup>
- import { ref, onMounted } from 'vue';
- import L from 'leaflet';
- import 'leaflet/dist/leaflet.css';
- const mapContainer = ref(null);
- onMounted(() => {
- // 初始化地图(强制确保容器可用)
- if (!mapContainer.value) {
- console.error('❌ 地图容器未找到!');
- return;
- }
- // 定义位置格式化函数(处理"广东省韶关市"前缀)
- const formatLocation = (fullLocation) => {
- if (!fullLocation) return '未知位置'; // 处理空值
- // 移除前缀并清理空格
- const processed = fullLocation.replace(/^(广东省)?韶关市/, '').trim();
- // 处理移除后为空的情况
- return processed || '未知位置';
- };
- const map = L.map(mapContainer.value, {
- center: [25, 114], // 韶关大致中心 前大往下,后大往左
- zoom: 8.5,
- minZoom: 8.3,
- });
- // 区县颜色映射(与GeoJSON的properties.name严格匹配)
- const districtColorMap = {
- "武江区": "#FF6B6B",
- "浈江区": "#4ECDC4",
- "曲江区": "#FFD166",
- "始兴县": "#A0DAA9",
- "仁化县": "#6A0572",
- "翁源县": "#1A535C",
- "乳源瑶族自治县": "#FF9F1C",
- "新丰县": "#87CEEB",
- "乐昌市": "#118AB2",
- "南雄市": "#06D6A0",
- };
- // 加载区县边界(带完整错误处理)
- fetch('/data/韶关市各区县边界图.geojson')
- .then(res => {
- if (!res.ok) throw new Error(`区县边界加载失败:${res.status}`);
- return res.json();
- })
- .then(geojson => {
- console.log('✅ 区县边界数据加载完成,要素数:', geojson.features.length);
-
- L.geoJSON(geojson, {
- style: (feature) => {
- const districtName = feature.properties.name; // 匹配GeoJSON的name字段
- const color = districtColorMap[districtName] || '#cccccc';
- return {
- fillColor: color,
- fillOpacity: 0.7,
- color: '#333333', // 边界颜色
- weight: 2, // 边界宽度
- };
- },
- }).addTo(map);
- // 加载水系图(新增,带样式和错误处理)
- fetch('/data/韶关市河流水系图.geojson')
- .then(res => {
- if (!res.ok) throw new Error(`水系图加载失败:${res.status}`);
- return res.json();
- })
- .then(waterGeojson => {
- console.log('✅ 水系图数据加载完成,要素数:', waterGeojson.features.length);
-
- L.geoJSON(waterGeojson, {
- style: {
- color: '#0066CC', // 水系颜色
- weight: 2, // 线条宽度
- opacity: 0.8, // 透明度
- },
- }).addTo(map);
- // ========================
- // 修复核心:加载采样+检测数据(单接口)
- // ========================
- fetch('http://localhost:8000/api/vector/export/all?table_name=water_sampling_data')
- .then(res => {
- if (!res.ok) throw new Error(`数据加载失败:HTTP ${res.status}`);
- return res.json();
- })
- .then(geoJSONData => { // geoJSONData 是 FeatureCollection 结构
- console.log('✅ 采样数据加载完成,要素数:', geoJSONData.features.length);
-
- let markerCount = 0;
- geoJSONData.features.forEach((feature, idx) => {
- const props = feature.properties; // 单个要素的完整属性(采样+检测)
- try {
- // 智能提取经纬度字段(支持多种可能的字段名)
- const latField = ['latitude', 'lat', 'Latitude', 'Lat'].find(key => props[key] !== undefined);
- const lngField = ['longitude', 'lng', 'Longitude', 'Lng'].find(key => props[key] !== undefined);
-
- if (!latField || !lngField) {
- console.error(`❌ 未找到经纬度字段(第${idx}条):`, props);
- return;
- }
-
- // 清理并转换经纬度(处理特殊字符和逗号)
- const cleanLat = String(props[latField]).replace(/[^\d.-]/g, '');
- const cleanLng = String(props[lngField]).replace(/[^\d.-]/g, '');
-
- // 强制四舍五入到6位小数(避免精度问题)
- const lat = parseFloat(parseFloat(cleanLat).toFixed(6));
- const lng = parseFloat(parseFloat(cleanLng).toFixed(6));
-
- // 范围校验(扩大范围10%,兼容边界值)
- if (isNaN(lat) || isNaN(lng) || lat < 22.7 || lat > 25.5 || lng < 112.7 || lng > 115.3) {
- console.warn(`❌ 坐标超出合理范围(第${idx}条):`, lat, lng, props);
- return;
- }
-
- // 创建标记点(使用 L.circleMarker 而非 L.marker)
- const marker = L.circleMarker([lat, lng], {
- radius: 4, // 增大圆点半径,确保可见
- color: '#FF3333', // 边框颜色(红色)
- fillColor: '#FF3333', // 填充颜色(红色)
- fillOpacity: 0.9, // 填充透明度(接近不透明)
- weight: 2, // 边框宽度(加粗)
- zIndexOffset: 1000, // 提高层级,确保在所有图层之上
- }).addTo(map);
- // 弹窗内容:直接从 props 提取检测数据(匹配实际字段名)
- marker.bindPopup(`
- <div class="popup-container">
- <h3 class="popup-title">${formatLocation(props.sampling_location)}</h3>
- <div class="popup-divider"></div> <!-- 分隔线 -->
- <table class="popup-table">
- <thead>
- <tr>
- <th>检测项</th>
- <th>数值</th>
- </tr>
- </thead>
- <tbody>
- <tr><td>Ph</td><td>${props.ph_value || '未知'}</td></tr>
- <tr><td>铬(Cr)(ug/L)</td><td>${props.cr_concentration.toFixed(6)|| '未知'}</td></tr>
- <tr><td>砷(As)(ug/L)</td><td>${props.as_concentration.toFixed(6) || '未知'}</td></tr>
- <tr><td>镉(Cd)(ug/L)</td><td>${props.cd_concentration.toFixed(6) || '未知'}</td></tr>
- <tr><td>汞(Hg)(ug/L)</td><td>${props.hg_concentration.toFixed(6) || '未知'}</td></tr>
- <tr><td>铅(Pb)(ug/L)</td><td>${props.pb_concentration.toFixed(6) || '未知'}</td></tr>
- </tbody>
- </table>
- </div>
- `);
-
- markerCount++;
- } catch (err) {
- console.error(`❌ 处理采样点失败(第${idx}条):`, err);
- }
- });
- console.log(`✅ 成功创建 ${markerCount} 个标记点`);
- })
- .catch(err => {
- console.error('❌ 采样/检测数据加载失败:', err);
- alert('数据接口错误:' + err.message);
- });
- // ========================
- // 水系图加载完成后的逻辑结束
- // ========================
- })
- .catch(err => {
- console.error('❌ 水系图加载失败:', err);
- alert('水系图加载错误:' + err.message);
- });
- })
- .catch(err => {
- console.error('❌ 区县边界加载失败:', err);
- alert('区县边界加载错误:' + err.message);
- });
- });
- </script>
- <style scoped>
- .map-wrapper {
- width:100%;
- height: 100%;
- position: relative;
- z-index: 100;
- }
- .map-container {
- width: 100% !important;
- height: 500px !important;
- }
- /* 标题和分隔线 */
- ::v-deep .popup-title {
- text-align: center; /* 居中 */
- font-size: 16px; /* 减小字号 */
- font-weight: 700; /* 加粗 */
- color: #0066CC; /* 蓝色,匹配设计 */
- margin: 0 0 4px; /* 间距调整 */
- border-bottom: 2px solid #0066CC; /* 底部横线 */
- padding-bottom: 4px; /* 横线与文字间距 */
- }
- ::v-deep .popup-divider {
- height: 1px; /* 横线高度 */
- background: #0066CC; /* 横线颜色 */
- margin: 6px 0; /* 上下间距 */
- }
- /* 表格样式 */
- ::v-deep .popup-table {
- width: 100%; /* 占满容器 */
- border-collapse: collapse;/* 合并边框 */
- margin-top: 12px; /* 与段落间距 */
- }
- ::v-deep .popup-table th,
- ::v-deep .popup-table td {
- border: 1px solid #CCCCCC;/* 单元格边框 */
- padding: 4px 6px; /* 内边距 */
- text-align: center; /* 内容居中 */
- font-size: 12px; /* 字号调整 */
- }
- ::v-deep .popup-table th {
- background: #F5F5F5; /* 表头背景色 */
- font-weight: 600; /* 表头加粗 */
- }
- /* 美化弹窗(完整层级穿透) */
- ::v-deep .leaflet-popup-content-wrapper {
- padding: 0 !important;
- border-radius: 12px !important;
- box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important;
- }
- ::v-deep .leaflet-popup-content {
- margin: 0 !important;
- width: auto !important;
- max-width: 220px !important;
- }
- ::v-deep .popup-container {
- min-width: 180px;
- max-width: 220px;
- padding: 10px;/**内边距 */
- font-family: "Microsoft YaHei", sans-serif;
- }
- ::v-deep .popup-content p {
- margin: 6px 0;
- font-size: 15px;
- color: #666;
- line-height: 1.6;
- }
- ::v-deep .popup-content strong {
- color: #FF3333; /* 与标记点颜色呼应 */
- font-weight: 600;
- }
- /* 可选:隐藏弹窗箭头,更像卡片 */
- ::v-deep .leaflet-popup-tip {
- display: none;
- }
- /* 临时调试:确保标记点可见 */
- ::v-deep .leaflet-marker-icon {
- display: none !important; /* 隐藏默认标记图标 */
- }
- ::v-deep .leaflet-circle-marker {
- stroke-width: 2px !important;
- }
- </style>
|