1565 lines
44 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>
<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>
<!-- <TongnanCenterCardDialog
ref="tongnanCenterCardDialog"
:visible.sync="visible"
:value="value"
:z-index="zIndex"
:width="width"
></TongnanCenterCardDialog> -->
<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="{}"
:visible="true"
@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 TongnanCenterCardDialog from '../Dialog/tongnanCenterCardDialog.vue';
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 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';
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) => {
if (newVal !== oldVal) {
await getAffectedRoadSectionData();
}
},
{ immediate: true }
);
// 定义 emits
const emit = defineEmits([
'districtClick',
'openTongnanTeam',
'openResponseSituation',
'openTongnanResponsible',
'riskPointStatsChange',
'update:roadvalArr',
'openHazardPointSituation',
]);
// 当前选中的区县
const selectedDistrict = ref(null);
let selectedLayer = null;
// 隧道信息弹窗
const tunnelDialogVisible = ref(false);
const tunnelDialogData = ref({});
// 地图信息弹窗
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 => {
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';
console.log(item);
// 使用 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,
});
// 添加点击事件到 marker
marker.on('click', () => {
console.log('centerInfoCard clicked, county:', countyName);
// 调用处理函数
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;
// 在地图上显示区县卡片
showCountyCardsOnMap(data.dataList);
};
// 关闭中心信息卡片弹窗
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);
if (res.code === '00000' && res.data) {
// 统计各区县预警数量
const warningStats = countWarningsByCounty(res.data);
console.log('区县预警统计:', warningStats);
affectedCountyData.value = warningStats;
// getAffectedProjectData();
// getAffectedTunnelData();
// getAffectedBridgeData();
// getAffectedRoadSectionData();
// getEmergencyForceData();
loadMapData();
}
} catch (error) {
console.error('获取受影响对象数据失败:', error);
}
};
const affectedBridgeData = ref([]);
// 获取受影响桥梁数据
const getAffectedBridgeData = async () => {
try {
const timeParams = getTimeParams();
const res = await request({
url: '/snow-ops-platform/map/bridge',
method: 'GET',
params: timeParams,
});
if (res.data) {
res.data.forEach(item => {
item.COORDINATE_POINT = [item.GL1_QLJD, item.GL1_QLWD];
// if (
// Number(item.GL1_JSZKPJDM) > 3 ||
// item.GL1_AKJFLLX == '大桥' ||
// item.GL1_AKJFLLX == '特大桥'
// ) {
// }
affectedBridgeData.value.push(item);
});
}
console.log('受影响桥梁数据:', affectedBridgeData.value);
addProjectMarkers(affectedBridgeData.value, bridgeIcon, 'bridge');
} catch (error) {
console.error('获取受影响桥梁数据失败:', error);
return [];
}
};
const tunnelInfoDialogRef = ref([]);
// 获取受影响隧道数据
const getAffectedTunnelData = async () => {
try {
const timeParams = getTimeParams();
const res = await request({
url: '/snow-ops-platform/map/tunnel',
method: 'GET',
params: timeParams,
});
if (res.code === '00000' && res.data) {
res.data.forEach(item => {
item.COORDINATE_POINT = [Number(item.GL1_SDJD2), Number(item.GL1_SDWD2)];
// if (Number(item.GL1_PDDJ) > 3 || item.GL1_SDLX == '特长隧道') {
// }
tunnelInfoDialogRef.value.push(item);
});
console.log('受影响隧道数据:', tunnelInfoDialogRef.value);
addProjectMarkers(tunnelInfoDialogRef.value, tunnelIcon2, 'tunnel');
}
return [];
} catch (error) {
console.error('获取受影响隧道数据失败:', error);
return [];
}
};
// 获取受影响项目数据
const affectedProjectData = ref([]);
const getAffectedProjectData = async () => {
try {
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 parsedData = res.data.map(item => {
const newItem = { ...item };
if (item.COORDINATE_POINT) {
console.log('原始坐标:', item.COORDINATE_POINT);
newItem.COORDINATE_POINT = item.COORDINATE_POINT.substring(
6,
item.COORDINATE_POINT.length - 1
)
.split(' ')
.reverse();
console.log('解析后坐标:', newItem.COORDINATE_POINT);
}
return newItem;
});
affectedProjectData.value = parsedData;
// 在地图上添加项目标记
console.log('开始添加项目标记...');
addProjectMarkers(parsedData, 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 () => {
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 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) {
res.data.forEach(item => {
// item.COORDINATE_POINT = JSON.parse(item.STARTPOINT);
item.COORDINATE_POINT = [item.GL1_LON, item.GL1_LAT];
});
affectedRoadSectionData.value = res.data;
// 在地图上添加项目标记
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(affectedRoadSectionData.value, img, 'road');
}
return [];
} catch (error) {
console.error('获取受影响路段数据失败:', error);
return [];
}
};
const emergencyForceData = ref([]);
// 获取应急力量数据
const getEmergencyForceData = async () => {
try {
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 riskPointData = ref([]);
// 风险点统计数据
const riskPointStats = ref({
重大路外隐患点: 0,
重大路内隐患点: 0,
较大路外隐患点: 0,
较大路内隐患点: 0,
一般路内隐患点: 0,
一般路外隐患点: 0,
风险点总数: 0,
});
// 获取风险点数据
const getRiskPointData = async (riskLevel, item) => {
try {
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,
});
// 清除旧的风险点标记,避免重复渲染
clearProjectMarkers();
addProjectMarkers(riskPointData.value, iconUrl, 'riskPoint');
// 获取风险点统计数据
} else {
console.warn('没有获取到风险点数据或返回码错误:', res);
}
return res.data || [];
} catch (error) {
console.error('获取风险点数据失败:', error);
return [];
}
};
let projectMarkers = []; // 存储项目标记
// 清除项目标记
const clearProjectMarkers = () => {
projectMarkers.forEach(marker => {
if (mapInstance) {
mapInstance.removeLayer(marker);
}
});
clearCountyCardMarkers();
projectMarkers = [];
// 关闭所有弹窗
tunnelDialogVisible.value = false;
mapInfoDialogVisible.value = false;
centerCardVisible.value = false;
};
// 根据数据类型获取对应的图标
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') => {
console.log(
'addProjectMarkers 被调用, mapInstance:',
!!mapInstance,
'数据条数:',
data?.length,
'类型:',
type
);
if (!mapInstance) {
console.warn('mapInstance 未初始化,无法添加标记');
return;
}
if (!data || data.length === 0) {
console.warn('没有数据,无法添加标记');
return;
}
// // 清除之前的标记
// clearProjectMarkers();
// 遍历数据添加标记
data.forEach(item => {
if (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,
});
// 点击 marker 打开对应类型的信息弹窗
marker.on('click', () => {
if (type === 'riskPoint') {
// 风险点类型,触发父组件打开涉灾隐患点情况弹窗
emit('openHazardPointSituation', item);
} else {
openMapInfoDialog(type, item);
}
});
marker.addTo(mapInstance);
projectMarkers.push(marker);
} else {
console.warn('无效的坐标:', item.COORDINATE_POINT, item);
}
} else {
console.warn('缺少坐标数据:', item);
}
});
console.log(`已添加 ${projectMarkers.length} 个项目标记`);
};
// 确定主要预警颜色(出现最多的预警等级)
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,
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);
console.log('已合并渝北区和江北区为两江新区');
}
};
// 计算区县中心点
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 });
// 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();
break;
case '隧道':
await getAffectedTunnelData();
break;
case '边坡':
break;
case '桥梁':
await getAffectedBridgeData();
break;
case '路段':
await getAffectedRoadSectionData();
break;
case '队伍':
await getEmergencyForceData();
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);
// 先重新加载受影响区县数据
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);
console.log('定位到区县:', countyName, '简化后:', targetName);
// 查找对应的区县图层
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;
console.log('已定位到区县:', countyName, '中心点:', center);
} 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>