1805 lines
54 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<!-- 检查URL参数中是否有Map=dev如果有则使用本地地图数据否则使用阿里云地图数据 -->
<div class="chongqing-map-container">
<div ref="mapContainer" class="map-container"></div>
<div v-if="loading" class="loading-overlay">
<div class="loading-spinner"></div>
<span class="loading-text">地图加载中...</span>
</div>
<div v-if="error" class="error-overlay">
<span class="error-text">{{ error }}</span>
<button class="retry-btn" @click="loadMapData">重试</button>
</div>
<mapInfoDialog
v-model:visible="mapInfoDialogVisible"
:type="mapInfoDialogType"
:data="mapInfoDialogData"
/>
<centerInfoCard
:visible="centerCardVisible"
:title="centerCardTitle"
:dataList="centerCardDataList"
@close="closeCenterCard"
@itemClick="handleCenterCardItemClick"
@click="handleCenterCardClick"
/>
<hazardPointSituationDialog
v-model:visible="hazardPointSituationDialogVisible"
:data="{}"
@close="closeHazardPointSituationDialog"
/>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch, defineExpose, h, render } from 'vue';
import axios from 'axios';
import { request } from '@/utils/request';
import projectIcon from '../../../assets/MaMap_img/项目@2x.png';
import bridgeIcon from '../../../assets/MaMap_img/桥梁icon@2x.png';
import tunnelIcon from '../../../assets/MaMap_img/蓝色@2x1.png';
import tunnelIcon2 from '../../../assets/MaMap_img/隧洞icon@2x.png';
import rescueTeamIcon from '../../../assets/MaMap_img/队伍icon@2x.png';
import engineeringIconIcon from '../../../assets/MaMap_img/危大工程icon@2x.png';
import hazardIconIcon1 from '../../../assets/MaMap_img/一般路内隐患点@2x.png';
import hazardIconIcon2 from '../../../assets/MaMap_img/一般路外隐患点@2x.png';
import hazardIconIcon3 from '../../../assets/MaMap_img/较大路内隐患点@2x.png';
import hazardIconIcon4 from '../../../assets/MaMap_img/较大路外隐患点@2x.png';
import hazardIconIcon5 from '../../../assets/MaMap_img/重大路内隐患点@2x.png';
import hazardIconIcon6 from '../../../assets/MaMap_img/重大路外隐患点@2x.png';
import tunnelLineIcon3 from '../../../assets/MaMap_img/高风险路段@2x.png';
import tunnelLineIcon2 from '../../../assets/MaMap_img/较高风险路段@2x.png';
import tunnelLineIcon1 from '../../../assets/MaMap_img/中风险路段@2x.png';
import tunnelLineIcon from '../../../assets/MaMap_img/线路icon定位@2x.png';
import mapInfoDialog from '../Dialog/mapInfoDialog.vue';
import centerInfoCard from '../Dialog/centerInfoCard.vue';
import hazardPointSituationDialog from '../Dialog/hazardPointSituationDialog.vue';
import I from '../../../../dist/cesium/Workers/upsampleVerticesFromCesium3DTilesTerrain';
const mapContainer = ref(null);
const loading = ref(false);
const error = ref(null);
let mapInstance = null;
let geoJsonLayer = null;
const props = defineProps({
activeitem: {
type: Object,
default: () => {},
},
dateRange: {
type: Array,
default: () => [],
},
roadItem: {
// 路段项 数据 主要是选中的路段等级
type: Object,
default: () => {
return {
label: '高风险',
};
},
},
});
watch(
() => props.roadItem,
async (newVal, oldVal) => {
console.log('newVal', newVal)
getAffectedRoadSectionData(false);
},
{ immediate: true }
);
// 定义 emits
const emit = defineEmits([
'districtClick',
'openTongnanTeam',
'openResponseSituation',
'openTongnanResponsible',
'riskPointStatsChange',
'update:roadvalArr',
'openHazardPointSituation',
'openRoadSectionSituation',
]);
// 当前选中的区县
const selectedDistrict = ref(null);
let selectedLayer = null;
// 地图信息弹窗
const mapInfoDialogVisible = ref(false);
const mapInfoDialogType = ref('project');
const mapInfoDialogData = ref({});
// 中心信息卡片弹窗
const centerCardVisible = ref(false);
const centerCardTitle = ref('调度统计');
const centerCardDataList = ref([]);
// 地图上显示的区县卡片标记
let countyCardMarkers = [];
// 打开地图信息弹窗
const openMapInfoDialog = (type, data) => {
mapInfoDialogType.value = type;
mapInfoDialogData.value = data;
mapInfoDialogVisible.value = true;
};
// 清除地图上的区县卡片标记
const clearCountyCardMarkers = () => {
countyCardMarkers.forEach(marker => {
// 清理 Vue 组件
if (marker._vueContainer) {
render(null, marker._vueContainer);
}
if (mapInstance) {
mapInstance.removeLayer(marker);
}
});
countyCardMarkers = [];
};
// 在地图上显示区县卡片
const showCountyCardsOnMap = dataList => {
if (!mapInstance || !geoJsonLayer) {
console.warn('地图未初始化,无法显示区县卡片');
return;
}
// 清除之前的卡片标记
clearCountyCardMarkers();
if (!dataList || dataList.length === 0) {
return;
}
// 简化区县名称
const simplifyName = name => {
return name
.replace('土家族苗族自治县', '')
.replace('苗族土家族自治县', '')
.replace('自治县', '')
.replace('区', '')
.replace('县', '');
};
// 遍历数据列表,为每个区县创建卡片
dataList.forEach(item => {
// if (j.countyName === item.countyName) {
// item = j;
// }
const countyName = item.countyName || item.name;
if (!countyName) return;
const targetName = simplifyName(countyName);
// 查找对应的区县图层
let targetLayer = null;
geoJsonLayer.eachLayer(layer => {
const layerName = layer.feature?.properties?.name || '';
if (simplifyName(layerName) === targetName) {
targetLayer = layer;
}
});
if (targetLayer) {
// 获取区县的中心点
const bounds = targetLayer.getBounds();
const center = bounds.getCenter();
// 创建一个容器元素用于挂载 Vue 组件
const container = document.createElement('div');
container.className = 'county-card-wrapper';
// 使用 Vue 的 h 函数创建组件虚拟节点
const vnode = h(centerInfoCard, {
visible: true,
title: countyName,
dataList: [item],
item: item,
onClose: () => {
closeCenterCard();
},
onItemClick: clickedItem => {
handleCenterCardItemClick(clickedItem);
},
});
// 渲染组件到容器
render(vnode, container);
// 创建自定义图标,使用渲染后的 HTML
const customIcon = window.L.divIcon({
className: 'county-card-icon',
html: container.innerHTML,
iconSize: [140, 40],
iconAnchor: [110, 60],
});
// 创建标记
const marker = window.L.marker(center, {
icon: customIcon,
interactive: true,
zIndexOffset: 2000, // 提升区县卡片层级,确保在最上层
});
// 添加点击事件到 marker
marker.on('click', () => {
// 调用处理函数
handleCenterCardItemClick(item);
// 移动地图到该位置
// mapInstance.setView(center, 10);
// 触发点击事件
emit('districtClick', {
name: countyName,
data: item,
type: item.type,
});
});
marker.addTo(mapInstance);
countyCardMarkers.push(marker);
// 保存容器引用以便后续清理
marker._vueContainer = container;
}
});
// 调整地图视角以显示所有卡片
// if (countyCardMarkers.length > 0) {
// const group = new window.L.featureGroup(countyCardMarkers);
// mapInstance.fitBounds(group.getBounds().pad(0.2));
// }
};
// 获取公路类型文本
const getRoadTypeText = roadType => {
const roadTypeMap = {
national: '国省道',
rural: '农村公路',
};
return roadTypeMap[roadType] || roadType;
};
// 打开中心信息卡片弹窗
const openCenterCard = data => {
centerCardDataList.value = data.dataList || [];
centerCardTitle.value = data.title || '调度统计';
centerCardVisible.value = true;
let affectedItems = [];
// 遍历数据列表,只有影响区域的区县才添加到受影响列表中
data.dataList.forEach(item => {
if (item.countyName && (item.countyName.includes('渝北') || item.countyName.includes('江北'))) {
item.countyName = '两江新区';
}
affectedCountyData.value.sortedList.forEach(j => {
if (item.countyName.includes(j.name)) {
affectedItems.push(item);
}
});
});
// 在地图上显示区县卡片
showCountyCardsOnMap(affectedItems);
};
// 关闭中心信息卡片弹窗
const closeCenterCard = () => {
centerCardVisible.value = false;
clearCountyCardMarkers();
};
// 处理中心卡片项点击
const handleCenterCardItemClick = item => {
console.log('点击了卡片项:', item);
// 根据区县名称定位地图
if (item.countyName || item.name) {
const countyName = item.countyName || item.name;
locateToDistrict(countyName);
}
};
// 打开隧道信息弹窗(兼容旧代码)
const openTunnelDialog = data => {
openMapInfoDialog('tunnel', data);
};
// 格式化日期时间为接口所需格式
const formatDateTime = date => {
if (!date) return '';
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const hours = String(d.getHours()).padStart(2, '0');
const minutes = String(d.getMinutes()).padStart(2, '0');
const seconds = String(d.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
// 获取时间参数
const getTimeParams = type => {
let start = '';
let end = '';
if (props.dateRange && props.dateRange.length === 2) {
start = formatDateTime(props.dateRange[0]) || '';
end = formatDateTime(props.dateRange[1]) || '';
}
return {
start: start,
end: end,
};
};
// 受影响对象数据
const affectedCountyData = ref({
byName: {},
sortedList: [],
});
// 获取受影响对象数据
const getAffectedCountyData = async () => {
try {
const timeParams = getTimeParams();
const res = await request({
url: '/snow-ops-platform/weather-warning/affected-county',
method: 'GET',
params: timeParams,
});
console.log('受影响对象数据:', res);
clearProjectMarkers();
if (res.code === '00000' && res.data) {
// 统计各区县预警数量
const warningStats = countWarningsByCounty(res.data);
console.log('区县预警统计:', warningStats);
affectedCountyData.value = warningStats;
getAffectedProjectData(true); // 获取受影响项目数据
getAffectedTunnelData(true); // 获取受影响隧道数据
getAffectedBridgeData(true); // 获取受影响桥梁数据
getDangerProjectData(true); // 获取危大工程数据
// 获取受影响路段数据
for (let index = 0; index < 5; index++) {
console.log(index);
if (index == 0) {
props.roadItem.label = '高风险路段';
} else if (index == 1) {
props.roadItem.label = '较高风险路段';
} else if (index == 2) {
props.roadItem.label = '中风险路段';
} else if (index == 3) {
props.roadItem.label = '低风险路段';
} else if (index == 4) {
props.roadItem.label = '';
}
if (props.roadItem.label) {
getAffectedRoadSectionData(true);
}
}
// getEmergencyForceData();
loadMapData(); // 加载地图数据
}
} catch (error) {
console.error('获取受影响对象数据失败:', error);
}
};
/**
* 通用数据过滤方法 - 根据区县筛选数据并设置坐标
* @param {Array} dataList - 原始数据列表
* @param {boolean} flag - 是否只筛选受影响区县的数据
* @param {Object} options - 配置选项
* @param {string} options.countyField - 区县字段名
* @param {string} options.countyMatchField - 匹配区县时使用的字段名如与countyField不同
* @param {Function} options.coordinateParser - 坐标解析函数接收item返回[lng, lat]
* @returns {Array} 过滤后的数据
*/
const filterDataByCounty = (dataList, flag, options) => {
const { countyField = 'COUNTY', countyMatchField, coordinateParser } = options;
const matchField = countyMatchField || countyField;
const filteredData = [];
dataList.forEach(item => {
// 区县名称处理:江北/渝北 -> 两江新区
const countyValue = item[countyField];
if (countyValue && (countyValue.includes('江北') || countyValue.includes('渝北'))) {
item[countyField] = '两江新区';
}
const matchValue = item[matchField];
if (!flag) {
// 不过滤,直接添加所有数据
item.COORDINATE_POINT = coordinateParser(item);
filteredData.push(item);
} else {
// 根据受影响区县列表进行过滤
const isMatched = affectedCountyData.value.sortedList.some(j => {
return matchValue && j.name && matchValue.includes(j.name);
});
if (isMatched) {
item.COORDINATE_POINT = coordinateParser(item);
filteredData.push(item);
}
}
});
return filteredData;
};
const affectedBridgeData = ref([]);
// 获取受影响桥梁数据
const getAffectedBridgeData = async flag => {
try {
// 检查桥梁标记是否已存在,如果存在则不加载接口
if (hasProjectMarkersByType('bridge')) {
console.log('类型为 bridge 的标记已存在,跳过接口调用');
return [];
}
const timeParams = getTimeParams();
const res = await request({
url: '/snow-ops-platform/map/bridge',
method: 'GET',
params: timeParams,
});
if (res.data) {
// 使用通用过滤方法处理数据
const filteredData = filterDataByCounty(res.data, flag, {
countyField: 'COUNTY',
countyMatchField: 'GL1_QXMC',
coordinateParser: item => [item.GL1_QLJD, item.GL1_QLWD],
});
affectedBridgeData.value = filteredData;
console.log('受影响桥梁数据:', affectedBridgeData.value);
addProjectMarkers(affectedBridgeData.value, bridgeIcon, 'bridge');
}
} catch (error) {
console.error('获取受影响桥梁数据失败:', error);
return [];
}
};
const tunnelInfoDialogRef = ref([]);
// 获取受影响隧道数据
const getAffectedTunnelData = async flag => {
try {
// 检查隧道标记是否已存在,如果存在则不加载接口
if (hasProjectMarkersByType('tunnel')) {
console.log('类型为 tunnel 的标记已存在,跳过接口调用');
return [];
}
const timeParams = getTimeParams();
const res = await request({
url: '/snow-ops-platform/map/tunnel',
method: 'GET',
params: timeParams,
});
if (res.code === '00000' && res.data) {
// 使用通用过滤方法处理数据
const filteredData = filterDataByCounty(res.data, flag, {
countyField: 'COUNTY',
countyMatchField: 'GL1_QXMC',
coordinateParser: item => [Number(item.GL1_SDJD2), Number(item.GL1_SDWD2)],
});
tunnelInfoDialogRef.value = filteredData;
console.log('受影响隧道数据:', tunnelInfoDialogRef.value);
addProjectMarkers(tunnelInfoDialogRef.value, tunnelIcon2, 'tunnel');
}
return [];
} catch (error) {
console.error('获取受影响隧道数据失败:', error);
return [];
}
};
// 获取受影响项目数据
const affectedProjectData = ref([]);
const getAffectedProjectData = async flag => {
try {
// 检查项目标记是否已存在,如果存在则不加载接口
if (hasProjectMarkersByType('project')) {
console.log('类型为 project 的标记已存在,跳过接口调用');
return [];
}
const timeParams = getTimeParams();
const res = await request({
url: '/snow-ops-platform/weather-warning/affected-object/project',
method: 'GET',
params: timeParams,
});
console.log('受影响项目数据:', res);
if (res.code === '00000' && res.data) {
console.log('项目数据条数:', res.data.length);
// 预处理:解析坐标格式
const preprocessedData = res.data.map(item => {
const newItem = { ...item };
if (item.COORDINATE_POINT) {
console.log('原始坐标:', item.COORDINATE_POINT);
newItem.COORDINATE_POINT_PARSED = item.COORDINATE_POINT.substring(
6,
item.COORDINATE_POINT.length - 1
)
.split(' ')
.reverse();
console.log('解析后坐标:', newItem.COORDINATE_POINT_PARSED);
}
return newItem;
});
// 使用通用过滤方法处理数据
const filteredData = filterDataByCounty(preprocessedData, flag, {
countyField: 'COUNTY',
coordinateParser: item => item.COORDINATE_POINT_PARSED || [],
});
affectedProjectData.value = filteredData;
// 在地图上添加项目标记
console.log('开始添加项目标记...');
addProjectMarkers(filteredData, projectIcon, 'project');
} else {
console.warn('没有获取到项目数据或返回码错误:', res);
}
return res.data || [];
} catch (error) {
console.error('获取受影响项目数据失败:', error);
return [];
}
};
// 获取受影响路段数据
const affectedRoadSectionData = ref([]);
const roadvalArr = ref([
{ label: '高风险', value: 0, show: false },
{ label: '较高风险', value: 0, show: false },
{ label: '中风险', value: 0, show: false },
{ label: '低风险', value: 0, show: false },
]);
const getAffectedRoadSectionData = async flag => {
try {
console.log('受影响路段数据:', props.roadItem);
const { start, end } = getTimeParams();
let riskLevel = '';
if (props.roadItem.label == '高风险路段') {
riskLevel = '高风险';
} else if (props.roadItem.label == '较高风险路段') {
riskLevel = '较高风险';
} else if (props.roadItem.label == '中风险路段') {
riskLevel = '中风险';
} else if (props.roadItem.label == '低风险路段') {
riskLevel = '低风险';
}
// 检查该等级的路段标记是否已存在,如果存在则不加载接口
const typeKey = `road-${riskLevel}`;
if (hasProjectMarkersByType(typeKey)) {
console.log(`类型为 ${typeKey} 的标记已存在,跳过接口调用`);
return [];
}
const res = await request({
// url: '/snow-ops-platform/weather-warning/affected-object/road-section',
url: '/snow-ops-platform/risk-point/road-section',
method: 'GET',
params: {
riskLevel: riskLevel,
isWithinRedLine: '是',
start: start,
end: end,
},
});
console.log('受影响路段数据:', res);
if (res.code === '00000' && res.data) {
// 使用通用过滤方法处理数据
const filteredData = filterDataByCounty(res.data, flag, {
countyField: 'COUNTY',
countyMatchField: 'GL1_QXMC',
coordinateParser: item => [item.GL1_LON, item.GL1_LAT],
});
affectedRoadSectionData.value = filteredData;
// 在地图上添加项目标记
console.log('受影响路段数据:', affectedRoadSectionData.value);
let img = tunnelLineIcon;
if (riskLevel == '高风险') {
img = tunnelLineIcon3;
roadvalArr.value[0] = {
label: '高风险',
value: affectedRoadSectionData.value.length,
show: true,
};
} else if (riskLevel == '较高风险') {
img = tunnelLineIcon2;
roadvalArr.value[1] = {
label: '较高风险',
value: affectedRoadSectionData.value.length,
show: true,
};
} else if (riskLevel == '中风险') {
img = tunnelLineIcon1;
roadvalArr.value[2] = {
label: '中风险',
value: affectedRoadSectionData.value.length,
show: true,
};
} else if (riskLevel == '低风险') {
img = tunnelLineIcon;
roadvalArr.value[3] = {
label: '低风险',
value: affectedRoadSectionData.value.length,
show: true,
};
}
emit('update:roadvalArr ==== 风险点总数', roadvalArr.value);
// 给数据添加风险等级字段,确保 addProjectMarkers 能正确识别类型键
const dataWithRiskLevel = affectedRoadSectionData.value.map(item => ({
...item,
riskLevel: riskLevel
}));
addProjectMarkers(dataWithRiskLevel, img, 'road');
}
return [];
} catch (error) {
console.error('获取受影响路段数据失败:', error);
return [];
}
};
const emergencyForceData = ref([]);
// 获取应急力量数据
const getEmergencyForceData = async () => {
try {
// 检查应急力量标记是否已存在,如果存在则不加载接口
if (hasProjectMarkersByType('emergency')) {
console.log('类型为 emergency 的标记已存在,跳过接口调用');
return [];
}
const timeParams = getTimeParams();
const res = await request({
url: '/snow-ops-platform/yhYjll/listForcesAndMaterials',
method: 'GET',
params: timeParams,
});
console.log('应急力量数据:', res);
if (res.code === '00000' && res.data) {
// 解析坐标数据
res.data.forEach(item => {
item.COORDINATE_POINT = [item.lng, item.lat];
console.log('解析后坐标:', item.COORDINATE_POINT);
});
emergencyForceData.value = res.data;
// 在地图上添加应急力量标记
console.log('开始添加应急力量标记...', res.data);
addProjectMarkers(res.data, rescueTeamIcon, 'emergency');
} else {
console.warn('没有获取到应急力量数据或返回码错误:', res);
}
return res.data || [];
} catch (error) {
console.error('获取应急力量数据失败:', error);
return [];
}
};
const dangerProjectData = ref([]);
// 获取危大工程数据
const getDangerProjectData = async flag => {
try {
// 检查危大工程标记是否已存在,如果存在则不加载接口
if (hasProjectMarkersByType('engineering')) {
console.log('类型为 engineering 的标记已存在,跳过接口调用');
return [];
}
const res = await request({
url: '/snow-ops-platform/dangerProjectInfo/listAll',
method: 'GET',
});
console.log('危大工程数据:', res);
if (res.code === '00000' && res.data) {
// 使用通用过滤方法处理数据
const filteredData = filterDataByCounty(res.data, flag, {
countyField: 'districtName',
coordinateParser: item => [item.longitude, item.latitude],
});
dangerProjectData.value = filteredData;
console.log('过滤后危大工程数据:', filteredData);
// 在地图上添加危大工程标记
console.log('开始添加危大工程标记...', filteredData);
addProjectMarkers(filteredData, engineeringIconIcon, 'engineering');
} else {
console.warn('没有获取到危大工程数据或返回码错误:', res);
}
return res.data || [];
} catch (error) {
console.error('获取危大工程数据失败:', error);
return [];
}
};
// 风险点数据
const riskPointData = ref([]);
// 风险点统计数据
const riskPointStats = ref({
重大路外隐患点: 0,
重大路内隐患点: 0,
较大路外隐患点: 0,
较大路内隐患点: 0,
一般路内隐患点: 0,
一般路外隐患点: 0,
风险点总数: 0,
});
// 获取风险点数据
const getRiskPointData = async (riskLevel, item) => {
try {
// 根据风险等级和在红线内外确定类型键
let riskTypeKey = 'riskPoint';
if (riskLevel) {
if (riskLevel.includes('重大') && item.isWithinRedLine === '是') {
riskTypeKey = 'riskPoint-重大路内';
} else if (riskLevel.includes('重大') && item.isWithinRedLine === '否') {
riskTypeKey = 'riskPoint-重大路外';
} else if (riskLevel.includes('较大') && item.isWithinRedLine === '是') {
riskTypeKey = 'riskPoint-较大路内';
} else if (riskLevel.includes('较大') && item.isWithinRedLine === '否') {
riskTypeKey = 'riskPoint-较大路外';
} else if (riskLevel.includes('一般') && item.isWithinRedLine === '是') {
riskTypeKey = 'riskPoint-一般路内';
} else if (riskLevel.includes('一般') && item.isWithinRedLine === '否') {
riskTypeKey = 'riskPoint-一般路外';
}
}
// 检查该类型的风险点标记是否已存在,如果存在则不加载接口
if (hasProjectMarkersByType(riskTypeKey)) {
console.log(`类型为 ${riskTypeKey} 的标记已存在,跳过接口调用`);
return [];
}
const res = await request({
url: '/snow-ops-platform/risk-point',
method: 'GET',
params: {
riskLevel: riskLevel || '',
isWithinRedLine: item.isWithinRedLine,
},
});
console.log('风险点数据:', res);
if (res.code === '00000' && res.data) {
// 解析坐标数据
res.data.forEach(item => {
item.COORDINATE_POINT = [item.GL1_LON, item.GL1_LAT];
});
riskPointData.value = res.data;
// 在地图上添加风险点标记
console.log('开始添加风险点标记...', res.data);
// 根据风险等级选择不同的图标
let iconUrl = hazardIconIcon1; // 默认使用一般路内隐患点图标
if (riskLevel) {
if (riskLevel.includes('重大') && item.isWithinRedLine === '是') {
iconUrl = hazardIconIcon5; // 重大路内隐患点
} else if (riskLevel.includes('重大') && item.isWithinRedLine === '否') {
iconUrl = hazardIconIcon6; // 重大路外隐患点
} else if (riskLevel.includes('较大') && item.isWithinRedLine === '是') {
iconUrl = hazardIconIcon3; // 较大路内隐患点
} else if (riskLevel.includes('较大') && item.isWithinRedLine === '否') {
iconUrl = hazardIconIcon4; // 较大路外隐患点
} else if (riskLevel.includes('一般') && item.isWithinRedLine === '是') {
iconUrl = hazardIconIcon1; // 一般路内隐患点
} else if (riskLevel.includes('一般') && item.isWithinRedLine === '否') {
iconUrl = hazardIconIcon2; // 一般路外隐患点
}
}
emit('riskPointStatsChange', {
riskLevel: riskLevel.substring(0, 2),
isWithinRedLine: item.isWithinRedLine,
value: riskPointData.value.length,
});
// 给数据添加风险等级和在红线内外字段,确保 addProjectMarkers 能正确识别类型键
const dataWithRiskInfo = riskPointData.value.map(riskItem => ({
...riskItem,
riskLevel: riskLevel,
isWithinRedLine: item.isWithinRedLine
}));
addProjectMarkers(dataWithRiskInfo, iconUrl, 'riskPoint');
// 获取风险点统计数据
} else {
console.warn('没有获取到风险点数据或返回码错误:', res);
}
return res.data || [];
} catch (error) {
console.error('获取风险点数据失败:', error);
return [];
}
};
// 按类型存储项目标记,避免同类型重复刷新
// 格式: { 'road-高风险': [...], 'road-较高风险': [...], 'riskPoint-高风险': [...], ... }
let projectMarkersMap = new Map();
// 清除所有项目标记
const clearProjectMarkers = () => {
projectMarkersMap.forEach((markers, type) => {
markers.forEach(marker => {
if (mapInstance) {
mapInstance.removeLayer(marker);
}
});
});
projectMarkersMap.clear();
clearCountyCardMarkers();
// 关闭所有弹窗
mapInfoDialogVisible.value = false;
centerCardVisible.value = false;
};
// 清除指定类型的项目标记
const clearProjectMarkersByType = (typeKey) => {
const markers = projectMarkersMap.get(typeKey);
if (markers) {
markers.forEach(marker => {
if (mapInstance) {
mapInstance.removeLayer(marker);
}
});
projectMarkersMap.delete(typeKey);
console.log(`已清除类型为 ${typeKey} 的标记,共 ${markers.length}`);
}
};
// 检查指定类型的项目标记是否已存在
const hasProjectMarkersByType = (typeKey) => {
const markers = projectMarkersMap.get(typeKey);
return markers && markers.length > 0;
};
// 根据数据类型获取对应的图标
const getIconByType = (item, type) => {
// 如果是风险点类型,根据风险等级和是否在红线内返回对应图标
if (type === 'riskPoint' || item.riskLevel) {
const riskLevel = item.riskLevel || '';
const isWithinRedLine = item.isWithinRedLine || item.iswithinredline || '';
if (riskLevel.includes('重大')) {
return isWithinRedLine === '是' ? hazardIconIcon5 : hazardIconIcon6;
} else if (riskLevel.includes('较大')) {
return isWithinRedLine === '是' ? hazardIconIcon3 : hazardIconIcon4;
} else if (riskLevel.includes('一般')) {
return isWithinRedLine === '是' ? hazardIconIcon1 : hazardIconIcon2;
}
}
// 如果是路段类型,根据风险等级返回对应图标
if (type === 'road' || item.riskLevel) {
const riskLevel = item.riskLevel || '';
if (riskLevel.includes('高风险')) {
return tunnelLineIcon3;
} else if (riskLevel.includes('较高风险')) {
return tunnelLineIcon2;
} else if (riskLevel.includes('中风险')) {
return tunnelLineIcon1;
} else if (riskLevel.includes('低风险')) {
return tunnelLineIcon;
}
}
// 默认返回传入的图标或项目图标
return projectIcon;
};
// 在地图上添加项目标记
const addProjectMarkers = (data, iconUrl, type = 'project') => {
if (!mapInstance) {
console.warn('mapInstance 未初始化,无法添加标记');
return;
}
if (!data || data.length === 0) {
console.warn('没有数据,无法添加标记');
return;
}
// 根据类型和等级生成类型键,用于区分不同类型的标记
// 例如: 'road-高风险', 'road-较高风险', 'riskPoint-高风险', 'project', 'tunnel', 'bridge'
let typeKey = type;
// 对于路段类型,从数据中解析风险等级
if (type === 'road' && data.length > 0) {
const riskLevel = data[0].GL1_FXDJ || data[0].riskLevel || '';
if (riskLevel) {
typeKey = `road-${riskLevel}`;
}
}
// 对于风险点类型,从数据中解析风险等级和在红线内外
if (type === 'riskPoint' && data.length > 0) {
const riskLevel = data[0].GL1_FXDJ || data[0].riskLevel || '';
const isWithinRedLine = data[0].GL1_SFHXN || data[0].isWithinRedLine || '';
if (riskLevel) {
// 根据风险等级和在红线内外确定类型键
if (riskLevel.includes('重大') && isWithinRedLine === '是') {
typeKey = 'riskPoint-重大路内';
} else if (riskLevel.includes('重大') && isWithinRedLine === '否') {
typeKey = 'riskPoint-重大路外';
} else if (riskLevel.includes('较大') && isWithinRedLine === '是') {
typeKey = 'riskPoint-较大路内';
} else if (riskLevel.includes('较大') && isWithinRedLine === '否') {
typeKey = 'riskPoint-较大路外';
} else if (riskLevel.includes('一般') && isWithinRedLine === '是') {
typeKey = 'riskPoint-一般路内';
} else if (riskLevel.includes('一般') && isWithinRedLine === '否') {
typeKey = 'riskPoint-一般路外';
} else {
typeKey = `riskPoint-${riskLevel}`;
}
}
}
// 如果该类型的标记已存在,直接返回,避免重复渲染
if (hasProjectMarkersByType(typeKey)) {
console.log(`类型为 ${typeKey} 的标记已存在,跳过添加`);
return;
}
// 存储新添加的标记
const newMarkers = [];
// 遍历数据添加标记
data.forEach(item => {
if (item && item.COORDINATE_POINT && item.COORDINATE_POINT.length == 2) {
// COORDINATE_POINT 格式: [经度, 纬度]
// Leaflet 需要: [纬度, 经度]
const lng = item.COORDINATE_POINT[0];
const lat = item.COORDINATE_POINT[1];
const latNum = parseFloat(lat);
const lngNum = parseFloat(lng);
if (!isNaN(latNum) && !isNaN(lngNum)) {
// 根据数据类型获取对应的图标
const currentIconUrl = iconUrl || getIconByType(item, type);
// 创建自定义图标
const projectIconObj = window.L.icon({
iconUrl: currentIconUrl,
iconSize: [35, 35],
iconAnchor: [10, 10],
popupAnchor: [0, -10],
});
const marker = window.L.marker([latNum, lngNum], {
icon: projectIconObj,
zIndexOffset: 1000, // 提升图标层级,确保在地图其他元素之上
});
// 点击 marker 打开对应类型的信息弹窗
marker.on('click', () => {
if (type === 'riskPoint') {
// 风险点类型,触发父组件打开涉灾隐患点情况弹窗
emit('openHazardPointSituation', item);
} else if (type === 'road') {
// 路段类型,触发父组件打开路段情况弹窗
emit('openRoadSectionSituation', item);
} else {
openMapInfoDialog(type, item);
}
});
marker.addTo(mapInstance);
newMarkers.push(marker);
} else {
console.warn('无效的坐标:', item.COORDINATE_POINT, item);
}
} else {
console.warn('缺少坐标数据:', item);
}
});
// 将新标记存储到对应类型中
projectMarkersMap.set(typeKey, newMarkers);
console.log(`已添加 ${newMarkers.length} 个类型为 ${typeKey} 的标记`);
};
// 确定主要预警颜色(出现最多的预警等级)
const getMainWarningColor = levels => {
if (!levels || Object.keys(levels).length === 0) return '#1890ff'; // 默认蓝色
// 预警等级优先级:红色 > 橙色 > 黄色 > 蓝色
const priority = ['红色', '橙色', '黄色', '蓝色'];
// 按数量排序
const sortedLevels = Object.entries(levels)
.map(([level, count]) => ({ level, count }))
.sort((a, b) => b.count - a.count);
// 如果有多个等级数量相同,按优先级排序
const maxCount = sortedLevels[0].count;
const maxLevels = sortedLevels.filter(item => item.count === maxCount);
if (maxLevels.length === 1) {
return getColorByLevel(maxLevels[0].level);
} else {
// 按优先级选择
for (const level of priority) {
if (maxLevels.some(item => item.level === level)) {
return getColorByLevel(level);
}
}
return getColorByLevel(maxLevels[0].level);
}
};
// 统计各区县的预警数量(按预警等级区分)
const countWarningsByCounty = data => {
if (!data || !Array.isArray(data)) return {};
const stats = {};
data.forEach(item => {
// 根据实际数据结构调整字段名
const rawCountyName = item.countyName || item.name || item.qxmc || item.gl1Qxmc;
const riskLevel = item.riskLevel || item.level || item.warningLevel || item.gl1Yjdj;
// 简化区县名称
const countyName = simplifyDistrictName(rawCountyName);
if (countyName) {
if (!stats[countyName]) {
stats[countyName] = {
total: 0,
levels: {},
};
}
// 统计总数
stats[countyName].total += 1;
// 按预警等级统计
const level = riskLevel || '未知';
if (stats[countyName].levels[level]) {
stats[countyName].levels[level] += 1;
} else {
stats[countyName].levels[level] = 1;
}
}
});
// 合并渝北区和江北区为两江新区
if (stats['渝北区'] || stats['江北区']) {
const yubeiData = stats['渝北区'] || {
total: 0,
levels: {},
};
const jiangbeiData = stats['江北区'] || {
total: 0,
levels: {},
};
// 合并总数
const total = yubeiData.total + jiangbeiData.total;
// 合并各等级预警数量
const levels = { ...yubeiData.levels };
Object.entries(jiangbeiData.levels).forEach(([level, count]) => {
if (levels[level]) {
levels[level] += count;
} else {
levels[level] = count;
}
});
// 创建两江新区数据
stats['两江新区'] = {
total,
levels,
};
// 删除渝北区和江北区
delete stats['渝北区'];
delete stats['江北区'];
}
// 转换为数组格式并按总数排序(数量从大到小)
const sortedList = Object.entries(stats)
.map(([name, data]) => ({
name: name.substring(0, 2),
total: data.total,
levels: data.levels,
}))
.sort((a, b) => b.total - a.total);
return {
byName: stats, // 对象格式:{ 区县名: { total: 总数, levels: { 等级: 数量 } } }
sortedList: sortedList, // 数组格式:[{ name: 区县名, total: 总数, levels: { 等级: 数量 } }]
};
};
// 重庆地图 GeoJSON API 地址 - 使用最新版本
const GEOJSON_URL = 'https://geo.datav.aliyun.com/areas_v3/bound/500000_full.json';
// 加载地图数据
const loadMapData = async () => {
loading.value = true;
error.value = null;
try {
// 初始化地图
// 检查URL参数中是否有Map=dev
const urlParams = new URLSearchParams(window.location.search);
const isDev = urlParams.get('Map') === 'dev';
let geoJsonData;
if (isDev) {
// 本地测试用
const response = await fetch(GEOJSON_URL);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
geoJsonData = await response.json();
} else {
// 部署用
const response = await axios.get('/aliyun-geo/bound/500000_full.json');
geoJsonData = response.data;
if (!geoJsonData) {
throw new Error('地图数据为空');
}
}
// // 处理行政区划变更:渝北区和江北区合并为两江新区
processDistrictMerge(geoJsonData);
initMap(geoJsonData);
} catch (err) {
console.error('加载地图数据失败:', err);
error.value = '地图数据加载失败,请检查网络连接';
} finally {
loading.value = false;
}
};
// 处理行政区划合并
const processDistrictMerge = geoJsonData => {
if (!geoJsonData || !geoJsonData.features) return;
// 查找渝北区和江北区的特征
const yubeiIndex = geoJsonData.features.findIndex(
f => f.properties && f.properties.name === '渝北区'
);
const jiangbeiIndex = geoJsonData.features.findIndex(
f => f.properties && f.properties.name === '江北区'
);
// 如果两个区都存在,合并为两江新区
if (yubeiIndex !== -1 && jiangbeiIndex !== -1) {
const yubeiFeature = geoJsonData.features[yubeiIndex];
const jiangbeiFeature = geoJsonData.features[jiangbeiIndex];
// 创建两江新区的特征
const liangjiangFeature = JSON.parse(JSON.stringify(yubeiFeature));
liangjiangFeature.properties.name = '两江新区';
liangjiangFeature.properties.adcode = '500006'; // 使用新的行政区划代码
// 如果是 MultiPolygon合并两个区的坐标
if (
yubeiFeature.geometry.type === 'MultiPolygon' &&
jiangbeiFeature.geometry.type === 'MultiPolygon'
) {
liangjiangFeature.geometry.coordinates = [
...yubeiFeature.geometry.coordinates,
...jiangbeiFeature.geometry.coordinates,
];
} else if (
yubeiFeature.geometry.type === 'Polygon' &&
jiangbeiFeature.geometry.type === 'Polygon'
) {
liangjiangFeature.geometry.type = 'MultiPolygon';
liangjiangFeature.geometry.coordinates = [
[yubeiFeature.geometry.coordinates],
[jiangbeiFeature.geometry.coordinates],
];
}
// 移除原来的两个区
geoJsonData.features.splice(Math.max(yubeiIndex, jiangbeiIndex), 1);
geoJsonData.features.splice(Math.min(yubeiIndex, jiangbeiIndex), 1);
// 添加合并后的两江新区
geoJsonData.features.push(liangjiangFeature);
}
};
// 计算区县中心点
const getCentroid = coordinates => {
let sumLat = 0;
let sumLng = 0;
let count = 0;
const processCoordinates = coords => {
if (typeof coords[0] === 'number') {
// 单个坐标点 [lng, lat]
sumLng += coords[0];
sumLat += coords[1];
count++;
} else if (Array.isArray(coords[0])) {
// 坐标数组
coords.forEach(processCoordinates);
}
};
processCoordinates(coordinates);
return count > 0 ? [sumLat / count, sumLng / count] : null;
};
// 简化区县名称
const simplifyDistrictName = name => {
const nameMap = {
酉阳土家族苗族自治县: '酉阳县',
秀山土家族苗族自治县: '秀山县',
彭水苗族土家族自治县: '彭水县',
石柱土家族自治县: '石柱县',
};
return nameMap[name] || name;
};
// 根据预警等级获取颜色
const getColorByLevel = level => {
const colorMap = {
红色预警: '#912210',
橙色预警: '#BA6527',
黄色预警: '#A47109',
蓝色预警: '#185A91',
未知: '#1890ff',
};
return colorMap[level] || '#1890ff';
};
// 初始化地图
const initMap = geoJsonData => {
if (!mapContainer.value) return;
try {
// 清除旧地图实例
if (mapInstance) {
mapInstance.remove();
}
// 创建地图实例
mapInstance = new window.L.Map(mapContainer.value, {
center: [29.563, 106.551], // 重庆中心坐标
zoom: 8,
minZoom: 6,
maxZoom: 18,
zoomControl: false,
attributionControl: false,
});
// 添加瓦片图层 - 使用深色样式
// const tileLayer = new window.L.TileLayer(
// "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
// {
// attribution:
// '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
// subdomains: "abcd",
// maxZoom: 19,
// },
// );
// mapInstance.addLayer(tileLayer);
// 添加 GeoJSON 图层
geoJsonLayer = new window.L.GeoJSON(geoJsonData, {
style: feature => {
// const districtName = feature.properties.name;
const districtName = simplifyDistrictName(feature.properties.name);
let fillColor = '#132C44'; // 默认深蓝色背景
let fillOpacity = 0.4; // 默认透明度更高(更透明)
// 如果有预警统计数据,应用主要预警颜色
if (
affectedCountyData.value &&
affectedCountyData.value.byName &&
affectedCountyData.value.byName[districtName]
) {
const districtData = affectedCountyData.value.byName[districtName];
// console.log(districtData.levels);
fillColor = getMainWarningColor(districtData.levels);
fillOpacity = 1; // 有预警时稍微不透明一些
}
return {
fillColor: fillColor,
weight: 0.5,
opacity: 0.6,
color: '#00d4ff',
fillOpacity: fillOpacity,
};
},
onEachFeature: (feature, layer) => {
if (feature.properties && feature.properties.name) {
// 区县点击事件
// layer.on("click", (e) => {
// // 清除之前选中的样式
// if (selectedLayer) {
// selectedLayer.setStyle({
// fillColor: "#1890ff",
// fillOpacity: 0.15,
// weight: 1.5,
// color: "#40a9ff",
// });
// }
// // 设置当前选中样式
// layer.setStyle({
// fillColor: "#ff4d4f",
// fillOpacity: 0.6,
// weight: 2.5,
// color: "#ff7875",
// });
// selectedLayer = layer;
// selectedDistrict.value = feature.properties.name;
// // 触发事件
// emit("districtClick", {
// name: feature.properties.name,
// feature: feature,
// latlng: e.latlng,
// });
// // 平滑移动到点击位置
// mapInstance.panTo(e.latlng, { animate: true, duration: 0.5 });
// });
// 鼠标悬停效果
// layer.on("mouseover", () => {
// layer.setStyle({
// fillColor: "#1890ff",
// fillOpacity: 0.4,
// weight: 2,
// color: "#69c0ff",
// });
// });
// layer.on("mouseout", () => {
// layer.setStyle({
// fillColor: "#1890ff",
// fillOpacity: 0.15,
// weight: 1.5,
// color: "#40a9ff",
// });
// });
// 添加 popup
// layer.bindPopup(`<div class="map-popup">
// <strong>${feature.properties.name}</strong>
// </div>`);
}
},
});
mapInstance.addLayer(geoJsonLayer);
// 添加区县标签
geoJsonData.features.forEach(feature => {
if (feature.properties && feature.properties.name) {
let centroid;
if (feature.geometry.type === 'Polygon') {
centroid = getCentroid(feature.geometry.coordinates);
} else if (feature.geometry.type === 'MultiPolygon') {
// 取第一个多边形的中心
centroid = getCentroid(feature.geometry.coordinates[0]);
}
if (centroid) {
const displayName = simplifyDistrictName(feature.properties.name);
const label = window.L.divIcon({
className: 'district-label',
html: `<div class="label-content">${displayName}</div>`,
iconSize: [80, 30],
iconAnchor: [40, 15],
});
const marker = window.L.marker(centroid, {
icon: label,
zIndexOffset: 500, // 提升标签层级
});
// marker.on("click", (e) => {
// // 清除之前选中的样式
// if (selectedLayer) {
// selectedLayer.setStyle({
// fillColor: "#1E3A8A",
// fillOpacity: 0.3,
// weight: 2,
// });
// }
// // 找到对应的区县图层并设置样式
// geoJsonLayer.eachLayer((layer) => {
// if (
// layer.feature &&
// layer.feature.properties.name === feature.properties.name
// ) {
// layer.setStyle({
// fillColor: "#ff4d4f",
// fillOpacity: 0.6,
// weight: 2.5,
// color: "#ff7875",
// });
// selectedLayer = layer;
// selectedDistrict.value = feature.properties.name;
// }
// });
// // 触发事件
// emit("districtClick", {
// name: feature.properties.name,
// feature: feature,
// latlng: e.latlng,
// });
// // 平滑移动到点击位置
// mapInstance.panTo(e.latlng, { animate: true, duration: 0.5 });
// });
mapInstance.addLayer(marker);
}
}
});
// 调整视图以适应重庆边界
mapInstance.fitBounds(geoJsonLayer.getBounds());
} catch (err) {
console.error('初始化地图失败:', err);
error.value = '地图初始化失败';
}
};
// 监听 activeitem 变化
watch(
() => props.activeitem,
async newVal => {
console.log('activeitem 变化:', newVal);
switch (newVal.label) {
case '涉灾隐患点':
// 点击涉灾隐患点时,获取所有风险点数据
await getRiskPointData();
break;
case '项目':
await getAffectedProjectData(false);
break;
case '隧道':
await getAffectedTunnelData(false);
break;
case '边坡':
break;
case '桥梁':
await getAffectedBridgeData(false);
break;
case '路段':
await getAffectedRoadSectionData(false);
break;
case '队伍':
await getEmergencyForceData();
break;
case '危大工程':
await getDangerProjectData(false);
break;
default:
break;
}
},
{ immediate: false }
);
// 处理隐患点点击事件
const handleHazardItemClick = async item => {
console.log('处理隐患点点击:', item);
// 根据隐患点类型映射风险等级
const riskLevelMap = {
重大路内隐患点: '重大隐患',
重大路外隐患点: '重大隐患',
较大路内隐患点: '较大隐患',
较大路外隐患点: '较大隐患',
一般路内隐患点: '一般隐患',
一般路外隐患点: '一般隐患',
};
const riskLevel = riskLevelMap[item.label] || '';
await getRiskPointData(riskLevel, item);
};
// 监听 dateRange 变化,重新加载数据
watch(
() => props.dateRange,
async (newVal, oldVal) => {
console.log('dateRange 变化:', newVal, oldVal);
// 先重新加载受影响区县数据
clearProjectMarkers(); // 清除项目标记
await getAffectedCountyData();
},
{ deep: true }
);
// 组件挂载时加载地图
onMounted(() => {
// 获取受影响对象数据
getAffectedCountyData();
// 检查 Leaflet 是否已加载
if (typeof window.L === 'undefined') {
// 动态加载 Leaflet CSS 和 JS
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
link.integrity = 'sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=';
link.crossOrigin = '';
document.head.appendChild(link);
const script = document.createElement('script');
script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
script.integrity = 'sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=';
script.crossOrigin = '';
script.onload = loadMapData;
document.head.appendChild(script);
} else {
loadMapData();
}
});
// 组件卸载时清理资源
onUnmounted(() => {
if (mapInstance) {
mapInstance.remove();
mapInstance = null;
}
});
// 根据区县名称定位地图
const locateToDistrict = countyName => {
if (!mapInstance || !geoJsonLayer) {
console.warn('地图未初始化,无法定位');
return;
}
// 简化区县名称
const simplifyName = name => {
return name
.replace('土家族苗族自治县', '')
.replace('苗族土家族自治县', '')
.replace('自治县', '')
.replace('区', '')
.replace('县', '');
};
const targetName = simplifyName(countyName);
// 查找对应的区县图层
let targetLayer = null;
geoJsonLayer.eachLayer(layer => {
const layerName = layer.feature?.properties?.name || '';
if (simplifyName(layerName) === targetName) {
targetLayer = layer;
}
});
if (targetLayer) {
// 获取区县的中心点
const bounds = targetLayer.getBounds();
const center = bounds.getCenter();
// 移动地图到该中心点并放大
// mapInstance.setView(center, 10);
// 高亮显示该区县
if (selectedLayer) {
geoJsonLayer.resetStyle(selectedLayer);
}
// targetLayer.setStyle({
// fillColor: "#ff7a00",
// fillOpacity: 0.6,
// weight: 3,
// color: "#ff4d4f",
// });
selectedLayer = targetLayer;
} else {
console.warn('未找到区县:', countyName);
}
};
// 暴露方法给父组件调用
defineExpose({
getEmergencyForceData,
clearProjectMarkers,
openCenterCard,
locateToDistrict,
handleHazardItemClick,
getRiskPointData,
});
</script>
<style lang="scss">
// 区县标签样式(需要全局样式,因为 Leaflet 动态添加元素)
.district-label {
background: transparent !important;
border: none !important;
.label-content {
background: transparent;
color: #fff;
padding: vw(4) vw(8);
border-radius: vw(2);
font-weight: 500;
text-align: center;
white-space: nowrap;
text-shadow:
0 1px 3px rgba(0, 0, 0, 0.8),
0 0 2px rgba(0, 0, 0, 0.5);
letter-spacing: 0.5px;
cursor: pointer;
width: fit-content;
transition: all 0.3s;
&:hover {
color: #40a9ff;
text-shadow: 0 0 10px rgba(64, 169, 255, 0.8);
transform: scale(1.05);
}
}
}
</style>
<style lang="scss" scoped>
// 视频屏幕自适应 - 基于视口宽度动态调整
@function vw($px) {
@return calc($px / 1920 * 100vw);
}
.chongqing-map-container {
width: 100%;
height: 100%;
position: relative;
}
.map-container {
width: 100%;
height: 100%;
}
.loading-overlay,
.error-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.7);
z-index: 1000;
}
.loading-spinner {
width: vw(40);
height: vw(40);
border: vw(4) solid #3b82f6;
border-top: vw(4) solid transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: vw(10);
}
.loading-text {
color: #fff;
font-size: vw(14);
}
.error-text {
color: #ff6b6b;
font-size: vw(14);
margin-bottom: vw(10);
}
.retry-btn {
background: #3b82f6;
color: white;
border: none;
padding: vw(8) vw(16);
border-radius: vw(4);
cursor: pointer;
font-size: vw(12);
&:hover {
background: #2563eb;
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
// Leaflet 地图样式覆盖
:deep(.leaflet-container) {
background: #0f1c2e !important;
.leaflet-popup {
left: -20px !important;
}
.leaflet-popup-content-wrapper {
background: rgba(24, 144, 255, 0.95);
border-radius: vw(4);
padding: vw(6) vw(12);
min-width: vw(80);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
.map-popup {
color: #fff;
font-size: vw(12);
text-align: center;
margin: 0;
padding: 0;
font-weight: 500;
}
}
.leaflet-popup-tip {
background: rgba(24, 144, 255, 0.95);
}
.leaflet-popup-content {
width: max-content !important;
margin: vw(10) 10px vw(10) 0;
line-height: 1.3;
}
}
// 区县高亮样式
:deep(.leaflet-interactive) {
transition: all 0.3s;
cursor: pointer;
}
.project-popup {
width: vw(120);
height: vw(20);
color: #fff;
font-size: vw(12);
text-align: center;
margin: 0;
padding: 15px;
font-weight: 500;
}
// 区县卡片样式
:deep(.county-card-icon) {
background: transparent !important;
border: none !important;
// .center-info-card-container {
// width: 100%;
// min-width: 120px;
// }
.center-info-card {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
cursor: pointer;
transition: all 0.3s;
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(24, 144, 255, 0.5);
filter: brightness(1.1);
}
}
}
</style>