|
@@ -1,17 +1,28 @@
|
|
|
<template>
|
|
|
<div class="map-page">
|
|
|
+ <!-- 新增加载提示 -->
|
|
|
+ <div v-if="isLoading" class="loading-overlay">
|
|
|
+ <div class="loading-spinner"></div>
|
|
|
+ <p>地图数据加载中...</p>
|
|
|
+ </div>
|
|
|
+ <!-- 新增工具栏容器 -->
|
|
|
+ <div class="map-toolbar">
|
|
|
+ <RegionSelector
|
|
|
+ class="compact-region-selector"
|
|
|
+ ref="regionSelector"
|
|
|
+ @region-change="handleRegionChange" />
|
|
|
+
|
|
|
+ </div>
|
|
|
<div ref="mapContainer"
|
|
|
- class="map-container"
|
|
|
- ></div>
|
|
|
+ class="map-container"></div>
|
|
|
<div v-if="error" class="error">{{ error }}</div>
|
|
|
- <!-- 覆盖层控制 -->
|
|
|
- <!-- <div class="control-panel">
|
|
|
- <label>
|
|
|
- <input type="checkbox" v-model="state.showOverlay" @change="toggleOverlay" />
|
|
|
- 显示土壤类型覆盖
|
|
|
- </label>
|
|
|
- </div> -->
|
|
|
+
|
|
|
<div class="control-panel">
|
|
|
+ <div class="basemap-toggle">
|
|
|
+ <button @click="toggleBaseLayer" :class="{ active: isBaseLayer }">
|
|
|
+ {{ isBaseLayer ? '纯净地图' : '腾讯地图' }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
<label>
|
|
|
<input type="checkbox" v-model="state.showSoilTypes" @change="toggleSoilTypeLayer" />
|
|
|
显示韶关市评估单元
|
|
@@ -27,14 +38,48 @@
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ <div class="map-legend" :class="{ active: isShowLegend }">
|
|
|
+ <div class="legend-controls">
|
|
|
+ <button @click="switchLegendType('cdRisk')">Cd风险</button>
|
|
|
+ <button @click="switchLegendType('safetyQ')">安全指数Q</button>
|
|
|
+ </div>
|
|
|
+ <div class="legend-header">
|
|
|
+ <h1>图例</h1>
|
|
|
+ </div>
|
|
|
+ <div class="legend-header">
|
|
|
+ <h4>{{ currentLegendTitle }}</h4>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Cd风险等级图例 -->
|
|
|
+ <div v-if="currentLegend === 'cdRisk'" class="legend-section">
|
|
|
+ <div class="legend-scale">
|
|
|
+ <div class="scale-item" v-for="(item, index) in cdRiskLegend" :key="index">
|
|
|
+ <div class="color-box" :style="{ backgroundColor: item.color }"></div>
|
|
|
+ <span>{{ item.label }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 安全指数Q图例 -->
|
|
|
+ <div v-if="currentLegend === 'safetyQ'" class="legend-section">
|
|
|
+ <div class="legend-scale">
|
|
|
+ <div class="scale-item" v-for="(item, index) in safetyQLegend" :key="index">
|
|
|
+ <div class="color-box" :style="{ backgroundColor: item.color }"></div>
|
|
|
+ <span>{{ item.label }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="legend-source">
|
|
|
+ <p>注:Q = 阈值/污染物含量</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
|
|
|
-import axios from 'axios'
|
|
|
-import markerIcon from '@/assets/dot.png'
|
|
|
import html2canvas from 'html2canvas'
|
|
|
+import RegionSelector from '@/components/RegionSelector.vue'
|
|
|
|
|
|
const isExporting = ref(false)
|
|
|
const isMapReady = ref(false)
|
|
@@ -61,20 +106,160 @@ const state = reactive({
|
|
|
let soilTypeLayer = null
|
|
|
let geoJSONLayer;
|
|
|
let currentInfoWindow = null;
|
|
|
-let surveyDataLayer = ref(null);
|
|
|
-let multiPolygon;
|
|
|
+const surveyDataLayer = ref(null); // 保持响应式引用
|
|
|
+let multiPolygon;
|
|
|
+const districtLayers = ref(new Map()) // 存储区县图层
|
|
|
+const combinedSurveyFeatures = ref([]); // 存储原始数据
|
|
|
+const currentSurveyFilter = ref([]); // 当前选中区域
|
|
|
+const isLoading = ref(false)
|
|
|
|
|
|
const categoryColors = { // 分类颜色配置
|
|
|
'优先保护类': '#00C853', // 绿色
|
|
|
'安全利用类': '#FFD600', // 黄色
|
|
|
- '严格管控类': '#D50000' // 红色
|
|
|
+ '严格管控类': '#D50000', // 红色
|
|
|
+ '其他': '#CCCCCC', // 灰色
|
|
|
+ '农产品样品': '#4CAF50', // 绿色
|
|
|
+ '土壤样品': '#2196F3' // 蓝色
|
|
|
};
|
|
|
|
|
|
+const isShowLegend = ref(true)
|
|
|
+const currentLegend = ref('cdRisk') // 默认显示Cd风险图例
|
|
|
+
|
|
|
+/// 图例配置数据
|
|
|
+const cdRiskLegend = reactive([
|
|
|
+ { label: '无风险', color: '#00C853' },
|
|
|
+ { label: '中低风险', color: '#FFD600' },
|
|
|
+ { label: '高风险', color: '#D50000' }
|
|
|
+])
|
|
|
+
|
|
|
+const safetyQLegend = reactive([
|
|
|
+ { label: 'Q < 1', color: '#00C853' },
|
|
|
+ { label: '1 < Q < 5', color: '#FFD600' },
|
|
|
+ { label: 'Q > 5', color: '#D50000' }
|
|
|
+])
|
|
|
+
|
|
|
+// 图例标题映射
|
|
|
+const legendTitles = {
|
|
|
+ cdRisk: 'Cd污染风险等级',
|
|
|
+ safetyQ: '安全生产指数Q'
|
|
|
+}
|
|
|
+
|
|
|
+const currentLegendTitle = computed(() => legendTitles[currentLegend.value])
|
|
|
+
|
|
|
+// 切换图例显示
|
|
|
+const toggleLegend = () => {
|
|
|
+ isShowLegend.value = !isShowLegend.value
|
|
|
+}
|
|
|
+
|
|
|
+// 切换图例类型
|
|
|
+const switchLegendType = (type) => {
|
|
|
+ currentLegend.value = type
|
|
|
+}
|
|
|
+
|
|
|
const tMapConfig = reactive({
|
|
|
key: import.meta.env.VITE_TMAP_KEY, // 请替换为你的开发者密钥
|
|
|
geocoderURL: 'https://apis.map.qq.com/ws/geocoder/v1/'
|
|
|
})
|
|
|
|
|
|
+const isBaseLayer = ref(false)
|
|
|
+const layerVisibility = reactive({
|
|
|
+ province: false,
|
|
|
+ city: false,
|
|
|
+ county: false
|
|
|
+})
|
|
|
+const currentZoom = ref(12)
|
|
|
+
|
|
|
+// 预加载所有GeoJSON图层
|
|
|
+const geoLayers = reactive({
|
|
|
+ province: null,
|
|
|
+ city: null,
|
|
|
+ county: null
|
|
|
+})
|
|
|
+
|
|
|
+const initBaseLayers = async () => {
|
|
|
+ try {
|
|
|
+ // 按层级加载GeoJSON(需替换实际路径)
|
|
|
+ isLoading.value = true;
|
|
|
+ geoLayers.province = await loadAndCreateLayer('/data/省.geojson', 'province')
|
|
|
+ geoLayers.city = await loadAndCreateLayer('/data/市.geojson', 'city')
|
|
|
+ geoLayers.county = await loadAndCreateLayer('/data/县.geojson', 'county')
|
|
|
+
|
|
|
+ // 初始化默认状态
|
|
|
+ updateLayerVisibility()
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载地理数据失败:', error)
|
|
|
+ error.value = '地理数据加载失败'
|
|
|
+ } finally {
|
|
|
+ isLoading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 创建带样式的图层
|
|
|
+const loadAndCreateLayer = async (url, type) => {
|
|
|
+ const geoData = await loadGeoJSON(url)
|
|
|
+ return new TMap.value.vector.GeoJSONLayer({
|
|
|
+ map: map,
|
|
|
+ data: geoData,
|
|
|
+ zIndex: 5,
|
|
|
+ polygonStyle: new TMap.value.PolygonStyle({
|
|
|
+ color: 'rgba(242, 241, 237, 1)',
|
|
|
+ borderColor: '#000000',
|
|
|
+ borderWidth: 1
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 智能切换核心方法
|
|
|
+const toggleBaseLayer = () => {
|
|
|
+ isBaseLayer.value = !isBaseLayer.value
|
|
|
+ // 新增地图样式切换逻辑
|
|
|
+ if (map) {
|
|
|
+ map.setMapStyleId(isBaseLayer.value ? '1' : '0')
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isBaseLayer.value) {
|
|
|
+ map.on('zoom', handleZoomChange)
|
|
|
+ updateLayerVisibility()
|
|
|
+ } else {
|
|
|
+ map.off('zoom', handleZoomChange)
|
|
|
+ hideAllLayers()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 缩放事件处理
|
|
|
+const handleZoomChange = () => {
|
|
|
+ currentZoom.value = map.getZoom()
|
|
|
+ updateLayerVisibility()
|
|
|
+}
|
|
|
+
|
|
|
+// 图层可见性逻辑
|
|
|
+const updateLayerVisibility = () => {
|
|
|
+ const zoom = currentZoom.value
|
|
|
+ const rules = [
|
|
|
+ { min: 0, max: 5, types: ['province'] },
|
|
|
+ { min: 5, max: 10, types: ['city'] },
|
|
|
+ { min: 10, max: 20, types: ['county'] }
|
|
|
+ ]
|
|
|
+
|
|
|
+ rules.forEach(rule => {
|
|
|
+ const isActive = zoom >= rule.min && zoom <= rule.max
|
|
|
+ rule.types.forEach(type => {
|
|
|
+ layerVisibility[type] = isActive && isBaseLayer.value
|
|
|
+ geoLayers[type]?.setVisible(isActive && isBaseLayer.value)
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+// 清理方法
|
|
|
+const hideAllLayers = () => {
|
|
|
+ Object.values(geoLayers).forEach(layer => {
|
|
|
+ if (layer) layer.setVisible(false)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
|
|
|
const loadSDK = () => {
|
|
|
return new Promise((resolve, reject) => {
|
|
@@ -182,6 +367,7 @@ const initData = () => {
|
|
|
// 初始化地图
|
|
|
const initMap = async () => {
|
|
|
try {
|
|
|
+ isLoading.value = true
|
|
|
await loadSDK()
|
|
|
|
|
|
map = new TMap.value.Map(mapContainer.value, {
|
|
@@ -204,9 +390,10 @@ const initMap = async () => {
|
|
|
// map: map,
|
|
|
// styles: { default: defaultStyle }
|
|
|
// })
|
|
|
- const geojsonData = await loadGeoJSON('/data/单元格.geojson');
|
|
|
+ const geojsonData = await loadGeoJSON('https://soilgd.com:8000/api/vector/export/all?table_name=unit_ceil');
|
|
|
initMapWithGeoJSON(geojsonData, map);
|
|
|
await initSurveyDataLayer(map);
|
|
|
+ filterSurveyDataLayer(currentSurveyFilter.value)
|
|
|
// 绑定点击事件
|
|
|
// map.on('click', handleMapClick)
|
|
|
// markersLayer.on('click', handleMarkerClick)
|
|
@@ -219,6 +406,8 @@ const initMap = async () => {
|
|
|
updateMarkers()
|
|
|
} catch (err) {
|
|
|
error.value = err.message
|
|
|
+ } finally {
|
|
|
+ isLoading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -489,6 +678,129 @@ async function loadGeoJSON(url) {
|
|
|
return await response.json();
|
|
|
}
|
|
|
|
|
|
+const handleRegionChange = async (districtNames) => {
|
|
|
+ isLoading.value = true;
|
|
|
+ console.log('收到区域变更:', districtNames)
|
|
|
+ currentSurveyFilter.value = districtNames;
|
|
|
+
|
|
|
+
|
|
|
+ // // 删除已取消选择的图层
|
|
|
+ // Array.from(districtLayers.value.keys()).forEach(name => {
|
|
|
+ // if (!districtNames.includes(name)) {
|
|
|
+ // const layer = districtLayers.value.get(name)
|
|
|
+ // layer.setMap(null) // 正确销毁图层
|
|
|
+ // districtLayers.value.delete(name)
|
|
|
+ // }
|
|
|
+ // })
|
|
|
+
|
|
|
+ // // 添加新选择的图层
|
|
|
+ // await Promise.all(districtNames.map(async name => {
|
|
|
+ // if (!districtLayers.value.has(name)) {
|
|
|
+ // try {
|
|
|
+ // const geoData = await loadGeoJSON(`/data/${name}.geojson`)
|
|
|
+
|
|
|
+ // // 创建独立图层实例
|
|
|
+ // const layer = new TMap.value.vector.GeoJSONLayer({
|
|
|
+ // map: map, // 确保传入当前地图实例
|
|
|
+ // data: geoData,
|
|
|
+ // zIndex: 3,
|
|
|
+ // styles: {
|
|
|
+ // // 按腾讯地图规范定义样式
|
|
|
+ // polygonStyle: new TMap.value.PolygonStyle({
|
|
|
+ // color: randomRGBA(0.3),
|
|
|
+ // borderColor: '#FF0000',
|
|
|
+ // borderWidth: 2
|
|
|
+ // })
|
|
|
+ // }
|
|
|
+ // })
|
|
|
+
|
|
|
+ // districtLayers.value.set(name, layer)
|
|
|
+ // } catch (error) {
|
|
|
+ // console.error(`加载【${name}】边界失败:`, error)
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }))
|
|
|
+ filterSurveyDataLayer(districtNames);
|
|
|
+ isLoading.value = false;
|
|
|
+}
|
|
|
+
|
|
|
+const filterSurveyDataLayer = (selectedRegions) => {
|
|
|
+ // ===== 1. 销毁旧图层 ===== [1,3](@ref)
|
|
|
+ if (surveyDataLayer.value) {
|
|
|
+ surveyDataLayer.value.setMap(null); // 从地图解除关联
|
|
|
+ surveyDataLayer.value.destroy(); // 释放内存资源
|
|
|
+ surveyDataLayer.value = null; // 清除引用
|
|
|
+ }
|
|
|
+
|
|
|
+ const mergedCategoryColors = {
|
|
|
+ ...categoryColors,
|
|
|
+ };
|
|
|
+
|
|
|
+ // 创建样式(包含默认分类)
|
|
|
+ const pointStyles = Object.keys(mergedCategoryColors).map(category => ({
|
|
|
+ id: category,
|
|
|
+ style: new TMap.value.MarkerStyle({
|
|
|
+ width: 12,
|
|
|
+ height: 12,
|
|
|
+ anchor: { x: 6, y: 6 },
|
|
|
+ src: createColoredCircle(mergedCategoryColors[category])
|
|
|
+ })
|
|
|
+ }));
|
|
|
+ const layerId = `survey-layer-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
|
+ surveyDataLayer.value = new TMap.value.MultiMarker({
|
|
|
+ id: layerId,
|
|
|
+ map: map,
|
|
|
+ styles: Object.assign({}, ...pointStyles.map(s => ({ [s.id]: s.style }))),
|
|
|
+ geometries: [] // 初始空数据
|
|
|
+ });
|
|
|
+ if (!surveyDataLayer?.value) {
|
|
|
+ throw new Error("调查数据图层未初始化")
|
|
|
+ }
|
|
|
+ if (!combinedSurveyFeatures?.value) {
|
|
|
+ throw new Error("调查数据未加载")
|
|
|
+ }
|
|
|
+ console.groupCollapsed("[区域过滤] 调试信息");
|
|
|
+ console.log("🔄 收到过滤请求,当前选中区域:", selectedRegions);
|
|
|
+ console.log("📦 原始数据总量:", combinedSurveyFeatures.value.length);
|
|
|
+ console.log(combinedSurveyFeatures.value);
|
|
|
+
|
|
|
+ const filtered = selectedRegions.length === 0
|
|
|
+ ? combinedSurveyFeatures.value
|
|
|
+ : combinedSurveyFeatures.value.filter(feature => {
|
|
|
+ const xmc = feature.properties.XMC || '';
|
|
|
+ return selectedRegions.some(region => xmc.includes(region));
|
|
|
+ });
|
|
|
+ console.log("✅ 过滤后数据量:", filtered.length);
|
|
|
+ console.log("🔍 示例过滤后数据:", filtered.slice(0,3).map(f => ({
|
|
|
+ id: f.properties.ID || f.properties.OBJECTID,
|
|
|
+ XMC: f.properties.XMC,
|
|
|
+ CMC: f.properties.CMC,
|
|
|
+ H_XTFX: f.properties.H_XTFX,
|
|
|
+ })));
|
|
|
+ console.groupEnd();
|
|
|
+
|
|
|
+ try {
|
|
|
+ surveyDataLayer.value.setGeometries(filtered.map(feature => ({
|
|
|
+ id: feature.properties.ID || feature.properties.OBJECTID,
|
|
|
+ styleId: feature.properties.H_XTFX || feature.properties.h_xtfx || '其他',
|
|
|
+ position: new TMap.value.LatLng(
|
|
|
+ feature.geometry.coordinates[1],
|
|
|
+ feature.geometry.coordinates[0]
|
|
|
+ ),
|
|
|
+ properties: {
|
|
|
+ ...feature.properties,
|
|
|
+ H_XTFX: feature.properties.H_XTFX || '其他'
|
|
|
+ }
|
|
|
+
|
|
|
+ })));
|
|
|
+ console.log("🗺️ 图层更新成功");
|
|
|
+ } catch (e) {
|
|
|
+ console.error("[图层操作异常]", e);
|
|
|
+ error.value = `地图更新失败: ${e.message}`;
|
|
|
+ setTimeout(() => error.value = null, 5000);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
function initMapWithGeoJSON(geojsonData, map) {
|
|
|
// 销毁旧图层
|
|
|
if (geoJSONLayer) {
|
|
@@ -499,7 +811,7 @@ function initMapWithGeoJSON(geojsonData, map) {
|
|
|
geoJSONLayer = new TMap.value.vector.GeoJSONLayer({
|
|
|
map: map,
|
|
|
data: geojsonData,
|
|
|
- zIndex: 1,
|
|
|
+ zIndex: 10,
|
|
|
polygonStyle: new TMap.value.PolygonStyle({ // 必须用 PolygonStyle 类实例
|
|
|
color: 'rgba(255, 0, 0, 0.25)',
|
|
|
showBorder: true,
|
|
@@ -514,7 +826,7 @@ function initMapWithGeoJSON(geojsonData, map) {
|
|
|
// 高亮选中图层
|
|
|
const highlightLayer = new TMap.value.MultiPolygon({
|
|
|
map,
|
|
|
- zIndex: 2,
|
|
|
+ zIndex: 20,
|
|
|
styles: {
|
|
|
highlight: new TMap.value.PolygonStyle({ // 注意要改为 PolygonStyle
|
|
|
color: 'rgba(0, 123, 255, 0.5)', // 半透明蓝色填充
|
|
@@ -547,40 +859,56 @@ function initMapWithGeoJSON(geojsonData, map) {
|
|
|
// 加载调查数据并初始化图层
|
|
|
const initSurveyDataLayer = async (map) => {
|
|
|
try {
|
|
|
- // 加载GeoJSON数据
|
|
|
- const surveyData = await loadGeoJSON('/data/调查数据.geojson');
|
|
|
+ isLoading.value = true
|
|
|
+ const geoJsonFiles = [
|
|
|
+ 'https://soilgd.com:8000/api/vector/export/all?table_name=surveydata',
|
|
|
+ '/data/河池土壤样品.geojson',
|
|
|
+ '/data/河池农产品样品.geojson',
|
|
|
+ ];
|
|
|
+
|
|
|
+ const surveyDataArray = await Promise.all(geoJsonFiles.map(loadGeoJSON));
|
|
|
+ const features = surveyDataArray.flatMap(geoData => geoData.features);
|
|
|
|
|
|
- // 创建分类样式
|
|
|
- const pointStyles = Object.keys(categoryColors).map(category => ({
|
|
|
+ // 保存原始数据用于过滤
|
|
|
+ combinedSurveyFeatures.value = features;
|
|
|
+
|
|
|
+ // 合并颜色配置(添加默认分类)
|
|
|
+ const mergedCategoryColors = {
|
|
|
+ ...categoryColors,
|
|
|
+ };
|
|
|
+
|
|
|
+ // 创建样式(包含默认分类)
|
|
|
+ const pointStyles = Object.keys(mergedCategoryColors).map(category => ({
|
|
|
id: category,
|
|
|
style: new TMap.value.MarkerStyle({
|
|
|
width: 12,
|
|
|
height: 12,
|
|
|
anchor: { x: 6, y: 6 },
|
|
|
- src: createColoredCircle(categoryColors[category]) // 生成圆形图标
|
|
|
+ src: createColoredCircle(mergedCategoryColors[category])
|
|
|
})
|
|
|
}));
|
|
|
|
|
|
- // 初始化图层
|
|
|
- surveyDataLayer = new TMap.value.MultiMarker({
|
|
|
+ // 初始化图层(处理缺失属性)
|
|
|
+ surveyDataLayer.value = new TMap.value.MultiMarker({
|
|
|
map: map,
|
|
|
styles: Object.assign({}, ...pointStyles.map(s => ({ [s.id]: s.style }))),
|
|
|
- geometries: surveyData.features.map(feature => ({
|
|
|
- id: feature.properties.ID,
|
|
|
- styleId: feature.properties.H_XTFX,
|
|
|
+ geometries: combinedSurveyFeatures.value.map(feature => ({
|
|
|
+ id: feature.properties.ID || feature.properties.OBJECTID,
|
|
|
+ styleId: feature.properties.H_XTFX || feature.properties.h_xtfx || '其他', // 设置默认值
|
|
|
position: new TMap.value.LatLng(
|
|
|
- feature.geometry.coordinates[1],
|
|
|
+ feature.geometry.coordinates[1],
|
|
|
feature.geometry.coordinates[0]
|
|
|
),
|
|
|
properties: {
|
|
|
...feature.properties,
|
|
|
-
|
|
|
+ // 强制添加H_XTFX字段保证数据一致性
|
|
|
+ H_XTFX: feature.properties.H_XTFX|| '其他'
|
|
|
}
|
|
|
}))
|
|
|
});
|
|
|
|
|
|
// 添加点击事件
|
|
|
- surveyDataLayer.on('click', (event) => {
|
|
|
+ surveyDataLayer.value.on('click', (event) => {
|
|
|
const prop = event.geometry.properties;
|
|
|
if (currentInfoWindow) currentInfoWindow.close();
|
|
|
currentInfoWindow = new TMap.value.InfoWindow({
|
|
@@ -595,7 +923,14 @@ const initSurveyDataLayer = async (map) => {
|
|
|
});
|
|
|
});
|
|
|
} catch (error) {
|
|
|
- console.error('调查数据加载失败:', error);
|
|
|
+ console.error("调查数据加载失败:", error);
|
|
|
+ // 添加详细错误日志
|
|
|
+ console.groupCollapsed("[错误详情]");
|
|
|
+ console.error("错误对象:", error);
|
|
|
+ console.trace("调用堆栈");
|
|
|
+ console.groupEnd();
|
|
|
+ } finally {
|
|
|
+ isLoading.value = false
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -636,7 +971,7 @@ const createColoredCircle = (color) => {
|
|
|
return;
|
|
|
}
|
|
|
if (surveyDataLayer) {
|
|
|
- surveyDataLayer.setVisible(state.showSurveyData);
|
|
|
+ surveyDataLayer.value.setVisible(state.showSurveyData);
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -664,6 +999,7 @@ onMounted(async () => {
|
|
|
await loadSDK()
|
|
|
initData()
|
|
|
await initMap()
|
|
|
+ await initBaseLayers()
|
|
|
} catch (err) {
|
|
|
error.value = err.message
|
|
|
}
|
|
@@ -679,41 +1015,96 @@ onBeforeUnmount(() => {
|
|
|
infoWindow.value.close()
|
|
|
infoWindow.value = null
|
|
|
}
|
|
|
- if (farmlandLayer) {
|
|
|
- farmlandLayer.destroy();
|
|
|
- farmlandLayer = null;
|
|
|
- }
|
|
|
- if (bboxLayer) {
|
|
|
- bboxLayer.destroy();
|
|
|
- bboxLayer = null;
|
|
|
- }
|
|
|
if (soilTypeLayer) {
|
|
|
soilTypeLayer.destroy();
|
|
|
soilTypeLayer = null;
|
|
|
}
|
|
|
- if (surveyDataLayer) {
|
|
|
- surveyDataLayer.destroy();
|
|
|
- surveyDataLayer = null;
|
|
|
+ if (surveyDataLayer.value) {
|
|
|
+ surveyDataLayer.value.setMap(null);
|
|
|
+ surveyDataLayer.value.destroy();
|
|
|
}
|
|
|
+ map.off('zoom', handleZoomChange)
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
+.basemap-toggle {
|
|
|
+ margin-top: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.basemap-toggle button {
|
|
|
+ padding: 8px 16px;
|
|
|
+ background: #3876ff;
|
|
|
+ color: white;
|
|
|
+ border: none;
|
|
|
+ border-radius: 20px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.basemap-toggle button:hover {
|
|
|
+ background: #2b5dc5;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/* 图层过渡动画 */
|
|
|
+.tmap-geojson-layer {
|
|
|
+ transition: opacity 0.3s ease, visibility 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.map-toolbar {
|
|
|
+ position: relative; /* 确保层级上下文 */
|
|
|
+ z-index: 1000; /* 低于子组件下拉菜单的z-index */
|
|
|
+ padding: 12px;
|
|
|
+ background: rgba(255, 255, 255, 0.9);
|
|
|
+ backdrop-filter: blur(5px);
|
|
|
+}
|
|
|
+
|
|
|
+.compact-region-selector {
|
|
|
+ width: 800px;
|
|
|
+ max-width: 100%;
|
|
|
+
|
|
|
+ /* 重置可能影响子组件的样式 */
|
|
|
+ .selection-container {
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .select-group {
|
|
|
+ min-width: 180px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 移动端适配 */
|
|
|
+ @media (max-width: 768px) {
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ .selection-container {
|
|
|
+ flex-wrap: wrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .select-group {
|
|
|
+ flex: 1 1 30%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
.map-page {
|
|
|
position: relative;
|
|
|
width: 100vw;
|
|
|
height: 100vh;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
}
|
|
|
|
|
|
.map-container {
|
|
|
- width: 100%;
|
|
|
- height: 100vh !important;
|
|
|
- min-height: 600px;
|
|
|
+ flex: 1;
|
|
|
+ height: calc(100vh - 48px); /* 对应新的工具栏高度 */
|
|
|
+ position: relative;
|
|
|
+ background: #f5f5f7;
|
|
|
}
|
|
|
-
|
|
|
.control-panel {
|
|
|
position: fixed;
|
|
|
- top: 24px;
|
|
|
+ top: 80px; /* 下移避开工具栏 */
|
|
|
right: 24px;
|
|
|
background: rgba(255, 255, 255, 0.95);
|
|
|
padding: 16px;
|
|
@@ -794,19 +1185,34 @@ onBeforeUnmount(() => {
|
|
|
box-shadow: 0 4px 12px rgba(56, 118, 255, 0.3);
|
|
|
}
|
|
|
|
|
|
-/* 新增加载动画 */
|
|
|
-@keyframes spin {
|
|
|
- 0% { transform: rotate(0deg); }
|
|
|
- 100% { transform: rotate(360deg); }
|
|
|
+/* 新增加载提示样式 */
|
|
|
+.loading-overlay {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: rgba(255, 255, 255, 0.8);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ z-index: 9999;
|
|
|
}
|
|
|
|
|
|
.loading-spinner {
|
|
|
- width: 18px;
|
|
|
- height: 18px;
|
|
|
- border: 2px solid rgba(255, 255, 255, 0.3);
|
|
|
- border-top-color: white;
|
|
|
+ width: 50px;
|
|
|
+ height: 50px;
|
|
|
+ border: 5px solid #f3f3f3;
|
|
|
+ border-top: 5px solid #3876ff;
|
|
|
border-radius: 50%;
|
|
|
- animation: spin 0.8s linear infinite;
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes spin {
|
|
|
+ 0% { transform: rotate(0deg); }
|
|
|
+ 100% { transform: rotate(360deg); }
|
|
|
}
|
|
|
|
|
|
/* 响应式调整 */
|
|
@@ -910,10 +1316,118 @@ onBeforeUnmount(() => {
|
|
|
.point-info h3[data-category="优先保护类"] { --category-color: #00C853; }
|
|
|
.point-info h3[data-category="安全利用类"] { --category-color: #FFD600; }
|
|
|
.point-info h3[data-category="严格管控类"] { --category-color: #D50000; }
|
|
|
+.point-info h3[data-category="其他"] { --category-color: #CCCCCC; }
|
|
|
+.point-info h3[data-category="土壤样品"] { --category-color: #2196F3; }
|
|
|
+.point-info h3[data-category="农产品样品"] { --category-color: #4CAF50; }
|
|
|
.highlight-status {
|
|
|
padding: 8px;
|
|
|
background: rgba(0, 255, 0, 0.1);
|
|
|
border-left: 3px solid #00FF00;
|
|
|
margin-top: 12px;
|
|
|
}
|
|
|
+.map-legend {
|
|
|
+ position: fixed;
|
|
|
+ left: 20px;
|
|
|
+ bottom: 80px;
|
|
|
+ z-index: 1000;
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 15px;
|
|
|
+ backdrop-filter: blur(8px);
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ width: 220px;
|
|
|
+}
|
|
|
+
|
|
|
+.map-legend.active {
|
|
|
+ bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.legend-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.close-btn {
|
|
|
+ background: none;
|
|
|
+ border: none;
|
|
|
+ font-size: 20px;
|
|
|
+ cursor: pointer;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+
|
|
|
+.legend-section {
|
|
|
+ max-height: 180px;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.legend-scale {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column; /* 改为纵向排列 */
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.scale-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.color-box {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ border-radius: 3px;
|
|
|
+ border: 1px solid #ccc;
|
|
|
+}
|
|
|
+
|
|
|
+.legend-source {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #666;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式调整 */
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .map-legend {
|
|
|
+ width: 180px;
|
|
|
+ bottom: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .scale-item {
|
|
|
+ flex-direction: column;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+}
|
|
|
+.legend-controls {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ position: relative;
|
|
|
+ z-index: 1001; /* 确保在图例面板之上 */
|
|
|
+}
|
|
|
+
|
|
|
+.legend-controls button {
|
|
|
+ padding: 8px 16px;
|
|
|
+ background: #3876ff;
|
|
|
+ color: white;
|
|
|
+ border: none;
|
|
|
+ border-radius: 20px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.legend-controls button:hover{
|
|
|
+ background: #2b5dc5;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式调整 */
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .legend-btn {
|
|
|
+ padding: 8px 16px;
|
|
|
+ font-size: 13px;
|
|
|
+ border-radius: 20px;
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|