2091 lines
64 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"
/>
<hazardPointSituationDialog :data="{}" />
</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) // 获取危大工程数据
let item = { isWithinRedLine: '', label: '' }
for (let index = 0; index < 6; index++) {
if (index == 0) {
item = {
isWithinRedLine: '是',
label: '重大路内隐患点',
}
} else if (index == 1) {
item = {
isWithinRedLine: '否',
label: '重大路外隐患点',
}
} else if (index == 2) {
item = {
isWithinRedLine: '是',
label: '较大路内隐患点',
}
} else if (index == 3) {
item = {
isWithinRedLine: '否',
label: '较大路外隐患点',
}
} else if (index == 4) {
item = {
isWithinRedLine: '是',
label: '一般路内隐患点',
}
} else if (index == 5) {
item = {
isWithinRedLine: '是',
label: '一般路外隐患点',
}
} else if (index == 6) {
item = {
isWithinRedLine: '',
label: '',
}
}
if (item.label) {
handleHazardItemClick(item, true) // 获取风险点数据,第一次加载传入 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 = []
// 提取区县名称(去掉前缀和后缀)
const extractCountyName = (value) => {
if (!value) return ''
// 去掉"重庆市"前缀
let name = value.replace(/^重庆市/, '')
// 去掉"区"、"县"后缀
name = name.replace(/(区|县)$/, '')
return name
}
dataList.forEach((item) => {
// 区县名称处理:江北/渝北 -> 两江新区
const countyValue = item[countyField]
if (countyValue && (countyValue.includes('江北') || countyValue.includes('渝北'))) {
item[countyField] = '两江新区'
}
const matchValue = item[matchField]
// 提取纯净的区县名称用于匹配
const extractedCountyName = extractCountyName(matchValue)
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) || extractedCountyName === 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/map/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)
// 解析格式: "106.44E,29.62N" -> [经度, 纬度]
const coordStr = item.COORDINATE_POINT.trim()
// 按逗号分割
const parts = coordStr.split(',')
if (parts.length === 2) {
// 去掉 E/N 后缀并转换为数字
const lng = parseFloat(parts[0].replace(/E$/i, '').trim())
const lat = parseFloat(parts[1].replace(/N$/i, '').trim())
if (!isNaN(lng) && !isNaN(lat)) {
newItem.COORDINATE_POINT_PARSED = [lng, lat]
}
}
console.log('解析后坐标:', newItem.COORDINATE_POINT_PARSED)
}
return newItem
})
// 使用通用过滤方法处理数据
const filteredData = filterDataByCounty(preprocessedData, flag, {
countyField: 'ADMINISTRATIVE_REGION',
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 getRiskPointData = async (riskLevel, item, flag) => {
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} 的标记已存在,删除旧图标并重新加载`)
clearProjectMarkersByType(riskTypeKey)
}
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) {
// 解析坐标数据
const filteredData = filterDataByCounty(res.data, flag, {
countyField: 'DistrictRICT_NAME',
countyMatchField: 'GL1_QXMC',
coordinateParser: (item) => [item.GL1_LON, item.GL1_LAT],
})
filteredData.forEach((item) => {
item.COORDINATE_POINT = [item.GL1_LON, item.GL1_LAT]
})
riskPointData.value = filteredData
// 在地图上添加风险点标记
console.log('开始添加风险点标记...', filteredData)
// 根据风险等级选择不同的图标
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 地址 - 根据 URL 参数动态判断
const getGeoJsonUrl = () => {
const urlParams = new URLSearchParams(window.location.search)
const mapParam = urlParams.get('Map')
if (mapParam === 'dev') {
return 'https://geo.datav.aliyun.com/areas_v3/bound/500000_full.json'
}
return 'http://58.144.223.142:11501/aliyun-geo/bound/500000_full.json'
}
const GEOJSON_URL = getGeoJsonUrl()
// 天地图配置缓存
let tiandituConfig = null
// 获取天地图配置
const getTiandituConfig = async () => {
// 如果已有缓存,直接返回
if (tiandituConfig) {
return tiandituConfig
}
try {
const res = await request({
url: '/snow-ops-platform/dataDirectory/queryCatalog',
method: 'GET',
params: {
pcatalog: 'TDT',
},
})
if (res.code === '00000' && res.data && res.data.length > 0) {
// 查找天地图配置
const tiandituItem = res.data.find((item) => item.Name === '天地图' || item.Name?.includes('天地图'))
if (tiandituItem && tiandituItem.Children && tiandituItem.Children.length > 0) {
// 查找影像底图
const imgLayer = tiandituItem.Children.find((child) => child.Name?.includes('影像') || child.Name?.includes('底图'))
// 查找行政区划图层
const boundaryLayer = tiandituItem.Children.find((child) => child.Name?.includes('行政区划') || child.Name?.includes('边界'))
const config = {}
if (imgLayer && imgLayer.Attribute && imgLayer.Attribute.servicePath) {
config.imgLayer = {
name: imgLayer.Name,
servicePath: imgLayer.Attribute.servicePath,
}
}
if (boundaryLayer && boundaryLayer.Attribute && boundaryLayer.Attribute.servicePath) {
config.boundaryLayer = {
name: boundaryLayer.Name,
servicePath: boundaryLayer.Attribute.servicePath,
}
}
if (Object.keys(config).length > 0) {
tiandituConfig = config
console.log('获取天地图配置成功:', tiandituConfig)
return tiandituConfig
}
}
}
console.warn('未找到天地图配置,使用默认配置')
return null
} catch (error) {
console.error('获取天地图配置失败:', error)
return null
}
}
// 加载地图数据
const loadMapData = async () => {
loading.value = true
error.value = null
try {
// 初始化地图 - 使用天地图配置
await initMap()
} 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 getCountyWarningData = (countyName) => {
if (!affectedCountyData.value || !affectedCountyData.value.byName) {
return null
}
// 简化区县名称进行匹配
const simplifiedName = simplifyDistrictName(countyName)
const shortName = simplifiedName.replace(/区$/, '').replace(/县$/, '')
// 尝试直接匹配
if (affectedCountyData.value.byName[simplifiedName]) {
return affectedCountyData.value.byName[simplifiedName]
}
// 尝试匹配短名称(去掉"区"、"县"后缀)
for (const [name, data] of Object.entries(affectedCountyData.value.byName)) {
const dataShortName = name.replace(/区$/, '').replace(/县$/, '')
if (dataShortName === shortName) {
return data
}
}
// 特殊处理:两江新区
if (shortName === '两江' || countyName === '两江新区') {
return affectedCountyData.value.byName['两江新区']
}
return null
}
// 根据预警数据获取区县填充颜色
const getCountyFillColor = (countyName) => {
const warningData = getCountyWarningData(countyName)
if (!warningData || !warningData.levels) {
return 'rgb(50, 108, 112, 0.6)' // 默认绿色90%透明度
}
const color = getMainWarningColor(warningData.levels)
// 将颜色转换为半透明填充色使用E6约90%透明度)
return color
}
// 根据预警数据获取区县边框颜色
const getCountyStrokeColor = (countyName) => {
return '#ffffff' // 默认白色
const warningData = getCountyWarningData(countyName)
if (!warningData || !warningData.levels) {
return '' // 默认白色
}
return getMainWarningColor(warningData.levels)
}
// 加载重庆区县 GeoJSON 数据并着色
const loadDistrictGeoJson = async () => {
try {
// 清除已有的 GeoJSON 图层
if (geoJsonLayer) {
mapInstance.removeLayer(geoJsonLayer)
geoJsonLayer = null
}
// 清除已有的区县标签
if (window.districtLabelMarkers) {
window.districtLabelMarkers.forEach((marker) => {
if (mapInstance) {
mapInstance.removeLayer(marker)
}
})
}
window.districtLabelMarkers = []
// 获取 GeoJSON 数据
const response = await fetch(GEOJSON_URL)
if (!response.ok) {
throw new Error(`获取 GeoJSON 数据失败: ${response.status}`)
}
const geoJsonData = await response.json()
console.log('重庆 GeoJSON 数据:', geoJsonData)
// 处理行政区划合并(渝北+江北=两江新区)
processDistrictMerge(geoJsonData)
// 创建 GeoJSON 图层
geoJsonLayer = window.L.geoJson(geoJsonData, {
style: (feature) => {
const countyName = feature.properties?.name || ''
const fillColor = getCountyFillColor(countyName)
const strokeColor = getCountyStrokeColor(countyName)
return {
fillColor: fillColor,
weight: 1,
opacity: 1,
color: strokeColor, // 边框颜色
dashArray: '',
fillOpacity: 0.75, // 增加填充透明度,使颜色更明显
}
},
onEachFeature: (feature, layer) => {
const countyName = feature.properties?.name || ''
const warningData = getCountyWarningData(countyName)
// 获取区县中心点并添加标签
const bounds = layer.getBounds()
const center = bounds.getCenter()
// 简化区县名称显示,添加尾缀
const simplifyDisplayName = (name) => {
const nameMap = {
彭水苗族土家族自治县: '彭水县',
酉阳土家族苗族自治县: '酉阳县',
秀山土家族苗族自治县: '秀山县',
石柱土家族自治县: '石柱县',
}
// 先检查是否需要特殊处理
if (nameMap[name]) {
return nameMap[name]
}
// 如果已经有"区"或"县"后缀,直接返回
if (name.endsWith('区') || name.endsWith('县')) {
return name
}
// 根据名称判断添加"区"还是"县"
// 重庆主城9区渝中、大渡口、江北、沙坪坝、九龙坡、南岸、北碚、渝北、巴南
const districtList = ['渝中', '大渡口', '江北', '沙坪坝', '九龙坡', '南岸', '北碚', '渝北', '巴南', '两江新区']
if (districtList.includes(name)) {
return name + '区'
}
// 其他默认添加"县"
return name + '县'
}
const displayName = simplifyDisplayName(countyName)
// 创建区县名称标签
const labelIcon = window.L.divIcon({
className: 'district-name-label',
html: `<div class="district-name-text">${displayName}</div>`,
iconSize: [80, 20],
iconAnchor: [40, 10],
})
const labelMarker = window.L.marker(center, {
icon: labelIcon,
interactive: false,
zIndexOffset: 300,
})
labelMarker.addTo(mapInstance)
window.districtLabelMarkers.push(labelMarker)
// 获取当前区县的边框颜色
const strokeColor = getCountyStrokeColor(countyName)
// 添加交互事件
layer.on({
mouseover: (e) => {
const targetLayer = e.target
// 悬浮时高亮边框,使用当前区县的边框颜色,加粗边框
targetLayer.setStyle({
weight: 4,
color: strokeColor,
fillOpacity: 0.85, // 悬浮时填充更明显的颜色
})
// 将当前图层置于最上层,避免被相邻区县遮挡
targetLayer.bringToFront()
},
mouseout: (e) => {
const targetLayer = e.target
// 恢复原始样式
geoJsonLayer.resetStyle(targetLayer)
},
click: (e) => {
const targetLayer = e.target
const bounds = targetLayer.getBounds()
mapInstance.fitBounds(bounds, { padding: [50, 50] })
// 触发区县点击事件
emit('districtClick', {
name: countyName,
data: warningData || { total: 0, levels: {} },
})
},
})
},
})
// 添加图层到地图
geoJsonLayer.addTo(mapInstance)
console.log('已添加区县 GeoJSON 图层')
// 将 GeoJSON 图层置于底图之上,其他标记之下
geoJsonLayer.setZIndex(100)
} catch (err) {
console.error('加载区县 GeoJSON 数据失败:', err)
}
}
// 根据预警等级获取颜色
const getColorByLevel = (level) => {
const colorMap = {
红色预警: '#912210',
橙色预警: '#BA6527',
黄色预警: '#A47109',
蓝色预警: '#185A91',
未知: '#1890ff',
}
return colorMap[level] || '#1890ff'
}
// 初始化地图
const initMap = async () => {
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 tdtConfig = await getTiandituConfig()
// 添加天地图影像底图
if (tdtConfig && tdtConfig.imgLayer && tdtConfig.imgLayer.servicePath) {
let servicePath = tdtConfig.imgLayer.servicePath
// 确保包含 tk 参数(天地图密钥)
if (!servicePath.includes('tk=')) {
// 使用默认的测试密钥,生产环境应该替换为正式密钥
servicePath += '&tk=b78ed71126d03ee82ce658731344a897'
}
// 天地图 URL 格式为 https://t{s}.tianditu.gov.cn/...
// subdomains 应该配置为 ['0', '1', ...] 以生成 t0, t1 等子域名
const tileLayer = new window.L.TileLayer(servicePath, {
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
maxZoom: 18,
attribution: '天地图',
})
mapInstance.addLayer(tileLayer)
console.log('已添加天地图影像底图:', tdtConfig.imgLayer.name)
} else {
console.warn('未获取到天地图影像底图配置,使用备用底图')
// 使用备用底图
const backupLayer = new window.L.TileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
subdomains: ['a', 'b', 'c'],
maxZoom: 19,
attribution: 'OpenStreetMap',
})
mapInstance.addLayer(backupLayer)
}
// 添加天地图行政区划图层(边界线)
if (tdtConfig && tdtConfig.boundaryLayer && tdtConfig.boundaryLayer.servicePath) {
let boundaryPath = tdtConfig.boundaryLayer.servicePath
// 确保包含 tk 参数
if (!boundaryPath.includes('tk=')) {
boundaryPath += '&tk=b78ed71126d03ee82ce658731344a897'
}
const boundaryLayer = new window.L.TileLayer(boundaryPath, {
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
maxZoom: 18,
attribution: '天地图行政区划',
})
mapInstance.addLayer(boundaryLayer)
console.log('已添加天地图行政区划图层:', tdtConfig.boundaryLayer.name)
} else {
console.warn('未获取到天地图行政区划图层配置')
}
// 加载重庆区县 GeoJSON 并着色
await loadDistrictGeoJson()
// 根据区县统计数据添加区县标记(使用固定坐标)
const districtCenters = {
渝中: [29.563, 106.571],
江北: [29.575, 106.573],
南岸: [29.523, 106.613],
九龙坡: [29.523, 106.513],
沙坪坝: [29.543, 106.453],
大渡口: [29.488, 106.483],
北碚: [29.803, 106.403],
渝北: [29.713, 106.623],
巴南: [29.383, 106.523],
万州: [30.803, 108.403],
涪陵: [29.703, 107.393],
永川: [29.353, 105.893],
合川: [29.973, 106.273],
江津: [29.283, 106.253],
南川: [29.153, 107.093],
綦江: [29.023, 106.653],
大足: [29.703, 105.713],
璧山: [29.593, 106.223],
铜梁: [29.843, 106.053],
潼南: [30.183, 105.833],
荣昌: [29.403, 105.593],
开州: [31.163, 108.393],
梁平: [30.673, 107.803],
武隆: [29.323, 107.753],
城口: [31.943, 108.663],
丰都: [29.863, 107.723],
垫江: [30.323, 107.333],
忠县: [30.303, 108.033],
云阳: [30.933, 108.693],
奉节: [31.023, 109.463],
巫山: [31.073, 109.873],
巫溪: [31.403, 109.633],
石柱: [30.003, 108.113],
秀山: [28.453, 108.993],
酉阳: [28.843, 108.773],
彭水: [29.293, 108.173],
两江: [29.723, 106.583],
}
// 根据统计数据添加区县标签已在loadDistrictGeoJson中添加此处注释避免重复
// if (affectedCountyData.value && affectedCountyData.value.byName) {
// Object.entries(affectedCountyData.value.byName).forEach(([districtName, data]) => {
// const center = districtCenters[districtName]
// if (center) {
// const displayName = districtName
// 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(center, {
// icon: label,
// interactive: false,
// zIndexOffset: 500,
// })
// marker.addTo(mapInstance)
// }
// })
// }
// 调整视图以适应重庆边界
const chongqingBounds = [
[28.0, 105.0], // 西南角
[32.0, 110.0], // 东北角
]
mapInstance.fitBounds(chongqingBounds)
} 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, flag) => {
console.log('处理隐患点点击:', item)
// 根据隐患点类型映射风险等级
const riskLevelMap = {
重大路内隐患点: '重大隐患',
重大路外隐患点: '重大隐患',
较大路内隐患点: '较大隐患',
较大路外隐患点: '较大隐患',
一般路内隐患点: '一般隐患',
一般路外隐患点: '一般隐患',
}
const riskLevel = riskLevelMap[item.label] || ''
await getRiskPointData(riskLevel, item, flag)
}
const flag = ref(true)
// 监听 dateRange 变化,重新加载数据
watch(
() => props.dateRange,
async (newVal, oldVal) => {
console.log('dateRange 变化:', newVal, oldVal)
// 先重新加载受影响区县数据
await clearProjectMarkers() // 清除项目标记
await getAffectedCountyData()
},
{ deep: true },
)
// 监听 affectedCountyData 变化,重新加载区县 GeoJSON 着色
watch(
() => affectedCountyData.value,
async (newVal) => {
console.log('affectedCountyData 变化,重新加载区县着色:', newVal)
if (mapInstance && newVal && newVal.byName) {
await loadDistrictGeoJson()
}
},
{ 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);
}
}
}
// 区县名称标签样式
.district-name-label {
background: transparent !important;
border: none !important;
.district-name-text {
color: #fff;
font-size: 12px;
font-weight: 600;
text-align: center;
white-space: nowrap;
text-shadow:
0 1px 3px rgba(0, 0, 0, 0.9),
0 0 4px rgba(0, 0, 0, 0.7);
letter-spacing: 1px;
pointer-events: none;
}
}
// 区县弹窗样式
:deep(.county-popup) {
.leaflet-popup-content-wrapper {
background: rgba(0, 20, 40, 0.95);
border: 1px solid rgba(64, 169, 255, 0.5);
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
}
.leaflet-popup-content {
margin: 0;
color: #fff;
font-size: 13px;
line-height: 1.5;
}
.leaflet-popup-tip {
background: rgba(0, 20, 40, 0.95);
border: 1px solid rgba(64, 169, 255, 0.5);
}
.leaflet-popup-close-button {
color: #fff;
font-size: 18px;
padding: 4px;
transition: color 0.3s;
&:hover {
color: #40a9ff;
}
}
}
</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>