2026-04-13 14:54:48 +08:00

1380 lines
37 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>
<tunnelInfoDialog
v-model:visible="tunnelDialogVisible"
:data="tunnelDialogData"
/>
<mapInfoDialog
v-model:visible="mapInfoDialogVisible"
:type="mapInfoDialogType"
:data="mapInfoDialogData"
/>
<centerInfoCard
:visible="centerCardVisible"
:title="centerCardTitle"
:dataList="centerCardDataList"
@close="closeCenterCard"
@itemClick="handleCenterCardItemClick"
@click="handleCenterCardClick"
/>
</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 tunnelLineIcon from "../../../assets/MaMap_img/线路icon定位@2x.png";
import tunnelIcon2 from "../../../assets/MaMap_img/隧洞icon@2x.png";
import rescueTeamIcon from "../../../assets/MaMap_img/队伍icon@2x.png";
import tunnelInfoDialog from "../Dialog/tunnelInfoDialog.vue";
import mapInfoDialog from "../Dialog/mapInfoDialog.vue";
import centerInfoCard from "../Dialog/centerInfoCard.vue";
const mapContainer = ref(null);
const loading = ref(false);
const error = ref(null);
let mapInstance = null;
let geoJsonLayer = null;
const props = defineProps({
activeIndex: {
type: Number,
default: -1,
},
dateRange: {
type: Array,
default: () => [],
},
});
// 定义 emits
const emit = defineEmits([
"districtClick",
"openTongnanTeam",
"openResponseSituation",
"openTongnanResponsible",
]);
// 当前选中的区县
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";
// 使用 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: [150, 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 = () => {
if (props.dateRange && props.dateRange.length === 2) {
return {
start: formatDateTime(props.dateRange[0]),
end: formatDateTime(props.dateRange[1]),
};
}
// 默认时间范围当月1号到当前时间
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, "0");
const day = String(now.getDate()).padStart(2, "0");
const hours = String(now.getHours()).padStart(2, "0");
const minutes = String(now.getMinutes()).padStart(2, "0");
const seconds = String(now.getSeconds()).padStart(2, "0");
return {
start: `${year}-${month}-01 00:00:00`,
end: `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`,
};
};
// 受影响对象数据
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/weather-warning/affected-object/bridge",
method: "GET",
params: timeParams,
});
if (res.code === "00000" && res.data) {
res.data.forEach((item) => {
item.COORDINATE_POINT = [item.GL1_QLJD, item.GL1_QLWD];
});
}
affectedBridgeData.value = res.data;
addProjectMarkers(res.data, bridgeIcon, "bridge");
} catch (error) {
console.error("获取受影响桥梁数据失败:", error);
return [];
}
};
const affectedTunnelData = ref([]);
const tunnelInfoDialogRef = ref(null);
// 获取受影响隧道数据
const getAffectedTunnelData = async () => {
try {
const timeParams = getTimeParams();
const res = await request({
url: "/snow-ops-platform/weather-warning/affected-object/tunnel",
method: "GET",
params: timeParams,
});
console.log("受影响隧道数据:", res);
if (res.code === "00000" && res.data) {
res.data.forEach((item) => {
item.COORDINATE_POINT = [
Number(item.GL1_SDJD2),
Number(item.GL1_SDWD2),
];
});
tunnelInfoDialogRef.value = res.data;
console.log("受影响隧道数据:", res.data);
addProjectMarkers(res.data, 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 getAffectedRoadSectionData = async () => {
try {
const timeParams = getTimeParams();
const res = await request({
url: "/snow-ops-platform/weather-warning/affected-object/road-section",
method: "GET",
params: timeParams,
});
console.log("受影响路段数据:", res);
if (res.code === "00000" && res.data) {
res.data.forEach((item) => {
item.COORDINATE_POINT = JSON.parse(item.STARTPOINT);
});
affectedRoadSectionData.value = res.data;
// 在地图上添加项目标记
console.log("开始添加项目标记...");
addProjectMarkers(affectedRoadSectionData.value, tunnelLineIcon, "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/list",
method: "GET",
params: timeParams,
});
console.log("应急力量数据:", res);
if (res.code === "00000" && res.data) {
// 解析坐标数据
res.data.forEach((item) => {
if (item.gl1Lx == 1 || item.gl1Lx == 2) {
item.COORDINATE_POINT = [item.gl1Lng, item.gl1Lat];
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 [];
}
};
let projectMarkers = []; // 存储项目标记
// 清除项目标记
const clearProjectMarkers = () => {
projectMarkers.forEach((marker) => {
if (mapInstance) {
mapInstance.removeLayer(marker);
}
});
clearCountyCardMarkers();
projectMarkers = [];
// 关闭所有弹窗
tunnelDialogVisible.value = false;
mapInfoDialogVisible.value = false;
centerCardVisible.value = false;
};
// 在地图上添加项目标记
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();
// 创建自定义图标
const projectIconObj = window.L.icon({
iconUrl: iconUrl,
iconSize: [35, 35],
iconAnchor: [10, 10],
popupAnchor: [0, -10],
});
// 遍历数据添加标记
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 marker = window.L.marker([latNum, lngNum], {
icon: projectIconObj,
});
// 点击 marker 打开对应类型的信息弹窗
marker.on("click", () => {
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 = "地图初始化失败";
}
};
// 监听 activeIndex 变化
watch(
() => props.activeIndex,
async (newVal) => {
console.log("activeIndex 变化:", newVal);
switch (newVal) {
case 0:
await getAffectedProjectData();
break;
case 1:
await getAffectedTunnelData();
break;
case 3:
await getAffectedBridgeData();
break;
case 4:
await getAffectedRoadSectionData();
break;
case 5:
await getEmergencyForceData();
break;
default:
break;
}
},
{ immediate: false },
);
// 监听 dateRange 变化,重新加载数据
watch(
() => props.dateRange,
async (newVal) => {
console.log("dateRange 变化:", newVal);
if (newVal && newVal.length === 2) {
// 先重新加载受影响区县数据
await getAffectedCountyData();
// 根据当前 activeIndex 重新加载对应数据
switch (props.activeIndex) {
case 0:
await getAffectedProjectData();
break;
case 1:
await getAffectedTunnelData();
break;
case 3:
await getAffectedBridgeData();
break;
case 4:
await getEmergencyForceData();
break;
default:
break;
}
}
},
{ 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,
});
</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: 150px;
}
.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>