1082 lines
29 KiB
Vue
1082 lines
29 KiB
Vue
<template>
|
||
<div class="card__content-box">
|
||
<!-- 滚动表格区域 -->
|
||
<div class="table">
|
||
<!-- 表头容器 - 固定显示 -->
|
||
<div class="table__header-container">
|
||
<div class="table__item table__header">
|
||
<div>序号</div>
|
||
<div>预警名称</div>
|
||
<div>预警点位</div>
|
||
<div>预警摄像</div>
|
||
<div>发布时间</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 内容滚动区域 -->
|
||
<div class="table__body-container">
|
||
<div class="scroll" v-if="tableData.length > 0">
|
||
<vue3-seamless-scroll :step="0.5" :hover="true" :list="tableData">
|
||
<div
|
||
class="table__item"
|
||
v-for="(item, index) in tableData"
|
||
:key="index"
|
||
@click="handleRowClick(item)"
|
||
>
|
||
<div class="scroll_table item_num">{{ index + 1 }}</div>
|
||
|
||
<!-- 1. 预警名称添加Tooltip -->
|
||
<el-tooltip
|
||
effect="dark"
|
||
placement="top"
|
||
:content="item.warningSigns"
|
||
teleported
|
||
popper-class="table-tooltip"
|
||
>
|
||
<div class="scroll_table item_courtName tooltip">
|
||
{{ item.warningSigns }}
|
||
</div>
|
||
</el-tooltip>
|
||
|
||
<!-- 2. 预警点位添加Tooltip -->
|
||
<el-tooltip
|
||
effect="dark"
|
||
placement="top"
|
||
:content="item.pointName"
|
||
teleported
|
||
popper-class="table-tooltip"
|
||
>
|
||
<div class="scroll_table item_punisherName tooltip">
|
||
{{ item.pointName }}
|
||
</div>
|
||
</el-tooltip>
|
||
|
||
<!-- 3. 预警摄像添加Tooltip -->
|
||
<el-tooltip
|
||
effect="dark"
|
||
placement="top"
|
||
:content="item.cameraName"
|
||
teleported
|
||
popper-class="table-tooltip"
|
||
>
|
||
<div class="scroll_table item_punisherName tooltip">
|
||
{{ item.cameraName }}
|
||
</div>
|
||
</el-tooltip>
|
||
|
||
<!-- 4. 发布时间添加Tooltip -->
|
||
<el-tooltip
|
||
effect="dark"
|
||
placement="top"
|
||
:content="item.releaseTime"
|
||
teleported
|
||
popper-class="table-tooltip"
|
||
>
|
||
<div class="scroll_table item_courtName tooltip">
|
||
{{ item.releaseTime }}
|
||
</div>
|
||
</el-tooltip>
|
||
</div>
|
||
</vue3-seamless-scroll>
|
||
</div>
|
||
|
||
<div v-else class="nocontent">
|
||
<el-empty :image-size="100" description="暂无数据" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 处置状态弹窗 -->
|
||
<LargeScreenModal
|
||
v-model:open="chargeOpen"
|
||
:title="modalTitle"
|
||
>
|
||
<div class="tableWrap largeScreen">
|
||
<el-descriptions
|
||
class="largeScreen__descriptions"
|
||
:column="2"
|
||
size="large"
|
||
border
|
||
>
|
||
<template v-for="item in formColumns">
|
||
<el-descriptions-item :label="item.label">{{
|
||
formInfo[item.key] || "-"
|
||
}}</el-descriptions-item>
|
||
</template>
|
||
</el-descriptions>
|
||
</div>
|
||
|
||
<div class="custom-timeline">
|
||
<div
|
||
v-for="(item, index) in timelineData"
|
||
:key="index"
|
||
class="timeline-item"
|
||
>
|
||
<div class="timeline-dot"></div>
|
||
<div class="timeline-content">
|
||
<div class="timeline-time">处理时间: {{ item.createTime }}</div>
|
||
<div class="timeline-desc">
|
||
<p>状态:{{ getStatusLabel(item.disposalStatus) }}</p>
|
||
<p>措施:{{ item.disposalMeasures || "-" }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</LargeScreenModal>
|
||
|
||
<!-- 预警详情与热力图弹窗 -->
|
||
<LargeScreenModal
|
||
v-model:open="warningModalOpen"
|
||
title="预警详情与热力图"
|
||
:width="styleUtil.px2vw(1200)"
|
||
:height="styleUtil.px2vh(800)"
|
||
@close="handleClose"
|
||
>
|
||
<div class="titles">{{ titlesTitle }}</div>
|
||
<!-- 详情区域 -->
|
||
<div class="detail-section">
|
||
<el-descriptions
|
||
class="largeScreen__descriptions"
|
||
:column="2"
|
||
size="large"
|
||
border
|
||
>
|
||
<template #extra>
|
||
</template>
|
||
<template v-for="item in warningFormColumns">
|
||
<!-- <el-descriptions-item v-if="item.label == '监控/外呼'" :label="item.label"> -->
|
||
<el-descriptions-item v-if="item.label == '监控'" :label="item.label">
|
||
<el-button type="primary" size="default" @click="lookJK">
|
||
查看监控
|
||
</el-button>
|
||
<!-- <el-button type="primary" size="default" @click="CALL">
|
||
外呼
|
||
</el-button> -->
|
||
</el-descriptions-item>
|
||
<el-descriptions-item v-else :label="item.label">{{
|
||
warningDetail[item.key] || "-"
|
||
}}</el-descriptions-item>
|
||
</template>
|
||
</el-descriptions>
|
||
</div>
|
||
|
||
<!-- 地图区域 -->
|
||
<div class="map-section">
|
||
<div id="warningMap" ref="mapContainer"></div>
|
||
|
||
<!-- 加载状态 -->
|
||
<div v-if="isLoading" class="map-loading">
|
||
<el-icon class="is-loading"><Loading /></el-icon>加载中...
|
||
</div>
|
||
|
||
<!-- 热力图图例 -->
|
||
<div class="map-legend" v-if="showLegend && showHeatmap">
|
||
<h4>客流热力等级</h4>
|
||
<div class="legend-gradient"></div>
|
||
<div class="legend-labels">
|
||
<span>低</span><span>中</span><span>高</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</LargeScreenModal>
|
||
|
||
<!-- 视频弹窗 -->
|
||
<LargeScreenModal
|
||
v-model:open="chargeOpenJK"
|
||
title="监控视频"
|
||
:width="styleUtil.px2vw(1200)"
|
||
:height="styleUtil.px2vh(800)"
|
||
@close="handleVideoClose"
|
||
>
|
||
<div class="tableWrap largeScreen">
|
||
<VideoMonitor
|
||
:video-url="currentVideoUrl"
|
||
width="100%"
|
||
height="100%"
|
||
style="margin-left: 40px"
|
||
/>
|
||
</div>
|
||
</LargeScreenModal>
|
||
<!-- 外呼的弹窗 -->
|
||
<LargeScreenModal
|
||
v-model:open="callDialog"
|
||
title="外呼"
|
||
:width="styleUtil.px2vw(400)"
|
||
:height="styleUtil.px2vh(500)"
|
||
@close="handleVideoClose2"
|
||
>
|
||
<el-row>
|
||
<el-col :span="24">
|
||
<div class="callDemo">
|
||
<Call ref="CallRef" :listRows="callForm" width="100%" height="400px" />
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
</LargeScreenModal>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import {
|
||
ref,
|
||
reactive,
|
||
onMounted,
|
||
getCurrentInstance,
|
||
nextTick,
|
||
watch,
|
||
} from "vue";
|
||
import {
|
||
getPoints,
|
||
} from "@/api/points";
|
||
import Call from "@/components/call.vue";
|
||
|
||
import styleUtil from "@/utils/styleUtils";
|
||
import LargeScreenModal from "../largeScreenModal.vue";
|
||
import { Vue3SeamlessScroll } from "vue3-seamless-scroll";
|
||
import L from "leaflet";
|
||
import "leaflet/dist/leaflet.css";
|
||
import "leaflet.heat/dist/leaflet-heat.js"; // 与主地图一致的热力图库
|
||
import { ElIcon, ElEmpty, ElMessage, ElSlider } from "element-plus";
|
||
import { Loading } from "@element-plus/icons-vue";
|
||
import _ from "lodash";
|
||
import VideoMonitor from "../../../../components/VideoMonitor.vue";
|
||
import moment from "moment";
|
||
|
||
// 接口导入 - 与主地图使用相同的接口
|
||
import {
|
||
getchuzhiList,
|
||
getchuzhiListDetail,
|
||
getchuzhiListLiucheng,
|
||
getYJRLi,
|
||
getYJRLiDetalis,
|
||
getYJRLiJK,
|
||
} from "@/api/map.js";
|
||
|
||
// 图标 - 与主地图一致
|
||
import warningIcon from "@/assets/images/yujing2.png";
|
||
|
||
// 常量定义 - 与主地图完全一致
|
||
const CHINA_BOUNDS = {
|
||
minLat: 18.1,
|
||
maxLat: 53.5,
|
||
minLng: 73.6,
|
||
maxLng: 135.1,
|
||
};
|
||
const DEFAULT_COORDS = [34.22, 108.96]; // 西安
|
||
const DEFAULT_ZOOM = 11;
|
||
const PARAM_ZOOM = 16;
|
||
|
||
const { proxy } = getCurrentInstance();
|
||
const emit = defineEmits(["update-map-params"]);
|
||
const { disposal_status } = proxy.useDict("disposal_status");
|
||
|
||
// 表格数据
|
||
const tableData = ref([]);
|
||
|
||
// 处置弹窗相关
|
||
const chargeOpen = ref(false);
|
||
const modalTitle = ref("处置状态查看");
|
||
const formInfo = ref({});
|
||
const timelineData = ref([]);
|
||
const formColumns = ref([
|
||
{ label: "预警唯一标识符", key: "warningSigns" },
|
||
{ label: "发布单位名称", key: "unitName" },
|
||
{ label: "发布单位编码", key: "unitCode" },
|
||
{ label: "发布时间", key: "releaseTime" },
|
||
{ label: "经度", key: "lng" },
|
||
{ label: "纬度", key: "lat" },
|
||
{ label: "可能波及范围", key: "scopeImpact" },
|
||
{ label: "异常数据值", key: "abnormalData" },
|
||
{ label: "预计持续时间", key: "expectedDuration" },
|
||
//{ label: "监控/外呼", key: "expectedDuration" },
|
||
{ label: "监控", key: "expectedDuration" },
|
||
]);
|
||
|
||
// 预警详情弹窗相关
|
||
const warningModalOpen = ref(false);
|
||
const warningDetail = ref({});
|
||
const currentRow = ref(null);
|
||
const isLoading = ref(false);
|
||
const showLegend = ref(false);
|
||
const showNoData = ref(false);
|
||
const showHeatmap = ref(true);
|
||
const showMarker = ref(true);
|
||
|
||
// 热力图参数 - 与主地图完全一致
|
||
const heatRadius = ref(50); // 热力图半径
|
||
const heatBlur = ref(30); // 热力图模糊度
|
||
const heatMaxZoom = ref(10); // 最大缩放级别
|
||
const heatMaxOpacity = ref(0.8); // 最大透明度
|
||
|
||
// 地图相关 - 与主地图保持一致的图层结构
|
||
const mapContainer = ref(null);
|
||
const mapInstance = ref(null);
|
||
const markerObj = ref(null);
|
||
const heatmapLayer = ref(null); // 热力图专用图层
|
||
const markersLayer = ref(null); // 标记专用图层
|
||
let currentHeatmapLayer = null; // 当前热力图层实例
|
||
const filePathS = ref("");
|
||
const chargeOpenJK = ref(false);
|
||
const currentVideoUrl = ref("");
|
||
// 预警详情表格配置
|
||
const warningFormColumns = ref([
|
||
{ label: "预警名称", key: "warningSigns" },
|
||
{ label: "预警点位", key: "pointName" },
|
||
{ label: "预警摄像", key: "cameraName" },
|
||
{ label: "发布时间", key: "releaseTime" },
|
||
{ label: "经度", key: "lng" },
|
||
{ label: "纬度", key: "lat" },
|
||
{ label: "可能波及范围", key: "scopeImpact" },
|
||
{ label: "异常数据值", key: "abnormalData" },
|
||
{ label: "预计持续时间", key: "expectedDuration" },
|
||
// { label: "监控/外呼", key: "expectedDuration" }
|
||
{ label: "监控", key: "expectedDuration" }
|
||
]);
|
||
const titlesTitle = ref('')
|
||
const currentWarningId = ref('')
|
||
// 行点击事件 - 与主地图逻辑一致
|
||
const handleRowClick = async (row) => {
|
||
console.log(row);
|
||
currentWarningId.value = row.unitCode;
|
||
filePathS.value = row.filePath;
|
||
currentRow.value = row;
|
||
isLoading.value = true;
|
||
warningModalOpen.value = true;
|
||
showNoData.value = false;
|
||
showHeatmap.value = true;
|
||
showMarker.value = true;
|
||
|
||
try {
|
||
// 解析经纬度
|
||
const lat = Number(row.lat);
|
||
const lng = Number(row.lng);
|
||
const hasValidCoord = !isNaN(lat) && !isNaN(lng) && isInChina(lat, lng);
|
||
|
||
if (hasValidCoord) {
|
||
emit("update-map-params", {
|
||
latitude: lat.toFixed(6),
|
||
longitude: lng.toFixed(6),
|
||
monitoringTypeName: "warning",
|
||
});
|
||
} else {
|
||
ElMessage.warning("经纬度无效,使用默认位置");
|
||
}
|
||
|
||
await nextTick();
|
||
let startTime = moment(row.releaseTime).format("YYYY-MM-DD");
|
||
|
||
// 并行加载数据 - 与主地图使用相同的接口
|
||
const [detailRes, heatRes] = await Promise.all([
|
||
getYJRLiDetalis(row.id).catch((err) => ({ code: 500, data: null })),
|
||
getYJRLi({
|
||
srcIndex: row.filePath || row.id,
|
||
startTime: startTime ? `${startTime} 00:00:00` : "",
|
||
endTime: row.releaseTime ? `${row.releaseTime}` : "",
|
||
}).catch((err) => ({ code: 500, data: null })),
|
||
]);
|
||
console.log(heatRes, "热力");
|
||
|
||
// 处理详情数据
|
||
warningDetail.value = detailRes.code === 200 ? detailRes.data || {} : {};
|
||
console.log(warningDetail.value,'warningDetailwarningDetail');
|
||
const index = warningDetail.value.warningSigns.indexOf('秒');
|
||
titlesTitle.value = index !== -1 ? warningDetail.value.warningSigns.slice(index + 1) : '';
|
||
// 初始化地图(与主地图配置一致)
|
||
await initMap(row);
|
||
|
||
// 处理热力图数据 - 与主地图逻辑一致
|
||
if (heatRes.code === 200 && heatRes.data && heatRes.data.length) {
|
||
renderHeatmap(heatRes.data); // 使用与主地图相同的热力图渲染
|
||
showLegend.value = true;
|
||
showNoData.value = false;
|
||
|
||
// 调整地图视图到数据区域 - 与主地图逻辑一致
|
||
const bounds = L.latLngBounds(
|
||
heatRes.data.map((item) => [
|
||
Number(item.latitude || item.lat),
|
||
Number(item.longitude || item.lng),
|
||
])
|
||
);
|
||
|
||
if (bounds.isValid()) {
|
||
mapInstance.value.fitBounds(bounds, {
|
||
padding: [80, 80], // 可选:增加边距,让视图更宽松
|
||
maxZoom: 14, // 核心属性:限制最大缩放级别
|
||
});
|
||
}
|
||
} else {
|
||
showNoData.value = true;
|
||
showLegend.value = false;
|
||
ElMessage.info("未获取到热力图数据");
|
||
}
|
||
} catch (error) {
|
||
console.error("加载失败:", error);
|
||
ElMessage.error("加载数据失败,请重试");
|
||
await initMap(currentRow.value);
|
||
} finally {
|
||
isLoading.value = false;
|
||
}
|
||
};
|
||
|
||
// 初始化地图 - 与主地图配置完全一致
|
||
const initMap = async (row) => {
|
||
const lat = Number(row.lat) || DEFAULT_COORDS[0];
|
||
const lng = Number(row.lng) || DEFAULT_COORDS[1];
|
||
|
||
await nextTick();
|
||
|
||
if (!mapContainer.value) {
|
||
console.error("地图容器不存在");
|
||
return;
|
||
}
|
||
|
||
// 设置容器尺寸
|
||
mapContainer.value.style.width = "100%";
|
||
mapContainer.value.style.height = "100%";
|
||
|
||
// 创建瓦片地图
|
||
L.CRS.EPSG4490 = L.extend({},
|
||
L.CRS.EPSG4326,
|
||
{
|
||
code: 'EPSG:4490',
|
||
scale: function (zoom) { return 256 * Math.pow(2, zoom - 1); }
|
||
}
|
||
);
|
||
|
||
// 创建地图实例(与主地图参数一致)
|
||
if (!mapInstance.value) {
|
||
mapInstance.value = L.map(mapContainer.value, {
|
||
center: [lat, lng],
|
||
zoom: 12,
|
||
maxZoom: 16, // 可选:限制手动缩放的最大级别
|
||
minZoom: 8,
|
||
zoomControl: false,
|
||
attributionControl: false,
|
||
dragging: true,
|
||
touchZoom: true,
|
||
scrollWheelZoom: true,
|
||
doubleClickZoom: true,
|
||
// 配置瓦片地图
|
||
crs: L.CRS.EPSG4490,
|
||
});
|
||
|
||
// 添加与主地图相同的底图
|
||
L.tileLayer(
|
||
// "https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
|
||
"https://10.22.245.209:18888/kgis/rest/services/GETileWGS20240425/MapServer/tile/{z}/{y}/{x}",
|
||
{ subdomains: ["1", "2", "3", "4"], maxZoom: 20 }
|
||
).addTo(mapInstance.value);
|
||
|
||
// // 添加与主地图相同的底图
|
||
// const tileLayer1 = L.tileLayer(
|
||
// "https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
|
||
// { subdomains: ["1", "2", "3", "4"] }
|
||
// ).addTo(mapInstance.value);
|
||
|
||
// const tileLayer2 = L.tileLayer(
|
||
// "https://webst0{s}.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1",
|
||
// { subdomains: ["1", "2", "3", "4"], pane: "markerPane" }
|
||
// ).addTo(mapInstance.value);
|
||
|
||
// 添加相同的缩放控件
|
||
L.control.zoom({ position: "topright" }).addTo(mapInstance.value);
|
||
|
||
// 创建图层(与主地图图层结构一致)
|
||
heatmapLayer.value = L.layerGroup().addTo(mapInstance.value);
|
||
markersLayer.value = L.layerGroup().addTo(mapInstance.value);
|
||
|
||
// 保持与主地图相同的图层顺序
|
||
heatmapLayer.value.setZIndex(300);
|
||
markersLayer.value.setZIndex(400);
|
||
|
||
// 监听地图事件 - 与主地图一致
|
||
mapInstance.value.on(
|
||
"zoomend moveend",
|
||
_.debounce(() => {
|
||
if (mapInstance.value) {
|
||
mapInstance.value.invalidateSize();
|
||
}
|
||
}, 100)
|
||
);
|
||
} else {
|
||
mapInstance.value.setView([lat, lng], PARAM_ZOOM);
|
||
clearHeatmap();
|
||
}
|
||
|
||
// 添加预警标记 - 与主地图标记样式一致
|
||
if (row) {
|
||
addWarningMarker(lat, lng, row);
|
||
}
|
||
};
|
||
|
||
// 添加预警标记 - 与主地图标记逻辑一致
|
||
const addWarningMarker = (lat, lng, row) => {
|
||
if (markerObj.value) {
|
||
markersLayer.value.removeLayer(markerObj.value);
|
||
}
|
||
|
||
// 创建与主地图一致的图标
|
||
const icon = L.icon({
|
||
iconUrl: warningIcon,
|
||
iconSize: [32, 32],
|
||
iconAnchor: [16, 32],
|
||
popupAnchor: [0, -32],
|
||
});
|
||
|
||
markerObj.value = L.marker([lat, lng], {
|
||
icon: icon,
|
||
zIndexOffset: 1000, // 确保标记在热力图上方
|
||
});
|
||
|
||
// 绑定与主地图风格一致的弹窗内容
|
||
markerObj.value.bindPopup(`
|
||
<b>${row.warningSigns || "预警信息"}</b><br>
|
||
点位: ${row.pointName || "未知"}<br>
|
||
时间: ${row.releaseTime || "未知"}
|
||
`);
|
||
|
||
markersLayer.value.addLayer(markerObj.value);
|
||
markerObj.value.openPopup();
|
||
|
||
if (!showMarker.value) {
|
||
markersLayer.value.removeLayer(markerObj.value);
|
||
}
|
||
};
|
||
|
||
// 修改后的renderHeatmap函数,只显示一个圆圈
|
||
const renderHeatmap = (heatData) => {
|
||
// 清除现有热力图
|
||
clearHeatmap();
|
||
|
||
if (!mapInstance.value || !heatData || !heatData.length) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 获取第一条数据
|
||
const firstItem = heatData[0];
|
||
if (!firstItem) {
|
||
showNoData.value = true;
|
||
ElMessage.warning("没有有效的热力图数据");
|
||
return;
|
||
}
|
||
|
||
// 提取并验证经纬度和强度值
|
||
const lat = Number(
|
||
firstItem.latitude || firstItem.lat || firstItem.LAT || firstItem.Latitude
|
||
);
|
||
const lng = Number(
|
||
firstItem.longitude ||
|
||
firstItem.lng ||
|
||
firstItem.LNG ||
|
||
firstItem.Longitude
|
||
);
|
||
|
||
// 计算强度值
|
||
const enterValue = Number(
|
||
firstItem.enter || firstItem.count || firstItem.value || 50
|
||
);
|
||
|
||
let intensity;
|
||
if (enterValue <= 0) {
|
||
intensity = 30;
|
||
} else {
|
||
if (enterValue < 10000) {
|
||
intensity = 50;
|
||
} else if (enterValue < 100000) {
|
||
intensity = 70;
|
||
} else {
|
||
intensity = 100;
|
||
}
|
||
}
|
||
|
||
// 有效性检查
|
||
if (isNaN(lat) || isNaN(lng)) {
|
||
showNoData.value = true;
|
||
ElMessage.warning(`经纬度无效: lat=${lat}, lng=${lng}`);
|
||
console.error("无效的经纬度数据:", firstItem);
|
||
return;
|
||
}
|
||
|
||
if (!isInChina(lat, lng)) {
|
||
showNoData.value = true;
|
||
ElMessage.warning(`经纬度不在有效范围内: lat=${lat}, lng=${lng}`);
|
||
console.error("经纬度超出范围:", firstItem);
|
||
return;
|
||
}
|
||
|
||
// 只使用单个点
|
||
const points = [
|
||
[lat, lng, intensity], // 仅保留中心点位
|
||
];
|
||
|
||
// 确保热力图容器图层已添加到地图
|
||
if (!mapInstance.value.hasLayer(heatmapLayer.value)) {
|
||
mapInstance.value.addLayer(heatmapLayer.value);
|
||
}
|
||
|
||
// 创建热力图图层
|
||
currentHeatmapLayer = L.heatLayer(points, {
|
||
radius: 60, // 可以根据需要调整半径大小
|
||
blur: 40, // 可以根据需要调整模糊度
|
||
maxZoom: heatMaxZoom.value,
|
||
maxOpacity: 0.9,
|
||
gradient: {
|
||
0.1: "#FFEDA0",
|
||
0.3: "#FED976",
|
||
0.5: "#FEB24C",
|
||
0.7: "#FD8D3C",
|
||
0.9: "#FC4E2A",
|
||
1: "#E31A1C",
|
||
},
|
||
});
|
||
|
||
// 添加热力图到容器图层
|
||
heatmapLayer.value.addLayer(currentHeatmapLayer);
|
||
|
||
// 确保标记层在热力图上方
|
||
if (markersLayer.value) {
|
||
mapInstance.value.removeLayer(markersLayer.value);
|
||
mapInstance.value.addLayer(markersLayer.value);
|
||
}
|
||
|
||
// 显示图例
|
||
showLegend.value = true;
|
||
showNoData.value = false;
|
||
|
||
console.log(`成功渲染热力图,经纬度: (${lat}, ${lng}),强度: ${intensity}`);
|
||
} catch (error) {
|
||
console.error("热力图渲染失败:", error);
|
||
ElMessage.error(`热力图渲染失败: ${error.message}`);
|
||
}
|
||
};
|
||
|
||
// 更新热力图设置
|
||
const updateHeatmapSettings = () => {
|
||
if (currentRow.value && currentHeatmapLayer) {
|
||
clearHeatmap();
|
||
// 重新获取数据并渲染
|
||
getYJRLi({
|
||
srcIndex: currentRow.value.filePath || currentRow.value.id,
|
||
startTime: currentRow.value.releaseTime
|
||
? `${currentRow.value.releaseTime} 00:00:00`
|
||
: "",
|
||
endTime: currentRow.value.releaseTime
|
||
? `${currentRow.value.releaseTime} 23:59:59`
|
||
: "",
|
||
}).then((res) => {
|
||
if (res.code === 200 && res.data && res.data.length) {
|
||
renderHeatmap(res.data);
|
||
}
|
||
});
|
||
}
|
||
};
|
||
|
||
// 切换热力图显示 - 与主地图逻辑一致
|
||
const toggleHeatmap = () => {
|
||
showHeatmap.value = !showHeatmap.value;
|
||
|
||
if (showHeatmap.value) {
|
||
mapInstance.value.addLayer(heatmapLayer.value);
|
||
showLegend.value = true;
|
||
} else {
|
||
mapInstance.value.removeLayer(heatmapLayer.value);
|
||
showLegend.value = false;
|
||
}
|
||
};
|
||
|
||
// 切换标记显示
|
||
const toggleMarker = () => {
|
||
showMarker.value = !showMarker.value;
|
||
|
||
if (showMarker.value && markerObj.value) {
|
||
markersLayer.value.addLayer(markerObj.value);
|
||
} else if (markerObj.value) {
|
||
markersLayer.value.removeLayer(markerObj.value);
|
||
}
|
||
};
|
||
|
||
// 清除热力图 - 与主地图逻辑一致
|
||
const clearHeatmap = () => {
|
||
if (
|
||
currentHeatmapLayer &&
|
||
heatmapLayer.value &&
|
||
heatmapLayer.value.hasLayer(currentHeatmapLayer)
|
||
) {
|
||
heatmapLayer.value.removeLayer(currentHeatmapLayer);
|
||
}
|
||
currentHeatmapLayer = null;
|
||
};
|
||
|
||
// 查看监控
|
||
const lookJK = () => {
|
||
let params = {
|
||
typeinfo: filePathS.value,
|
||
};
|
||
getYJRLiJK(params).then((res) => {
|
||
console.log(res, "监控返回");
|
||
if (res.code === 200 && res.msg) {
|
||
currentVideoUrl.value = res.msg;
|
||
chargeOpenJK.value = true;
|
||
} else {
|
||
proxy.$modal.msgWarning("无监控数据");
|
||
}
|
||
});
|
||
};
|
||
const callDialog = ref(false);
|
||
const callForm = ref({});
|
||
// 外呼
|
||
const CALL = () => {
|
||
resetCall();
|
||
getPoints(currentWarningId.value).then((response) => {
|
||
console.log(response,'rq');
|
||
callForm.value = {
|
||
phone: response.data.dutyPhone
|
||
}
|
||
});
|
||
callDialog.value = true;
|
||
}
|
||
// 重置外呼内容
|
||
function resetCall() {
|
||
callDialog.value = false;
|
||
callForm.value = {};
|
||
}
|
||
// 关闭弹窗
|
||
const handleVideoClose = () => {
|
||
chargeOpenJK.value = false;
|
||
currentVideoUrl.value = "";
|
||
};
|
||
|
||
// 关闭弹窗清理 - 与主地图一致
|
||
const handleClose = () => {
|
||
if (mapInstance.value) {
|
||
mapInstance.value.off();
|
||
mapInstance.value.remove();
|
||
mapInstance.value = null;
|
||
}
|
||
warningModalOpen.value = false;
|
||
clearHeatmap();
|
||
showLegend.value = false;
|
||
};
|
||
|
||
// 查看处置详情
|
||
const handleLook = (row) => {
|
||
getchuzhiListDetail(row.warningId).then((res) => {
|
||
formInfo.value = res.data || {};
|
||
});
|
||
|
||
getchuzhiListLiucheng({ handleId: row.id }).then((res) => {
|
||
timelineData.value = res.rows || [];
|
||
chargeOpen.value = true;
|
||
});
|
||
};
|
||
|
||
// 检查是否在中国范围内 - 与主地图一致
|
||
const isInChina = (lat, lng) => {
|
||
return (
|
||
lat >= CHINA_BOUNDS.minLat &&
|
||
lat <= CHINA_BOUNDS.maxLat &&
|
||
lng >= CHINA_BOUNDS.minLng &&
|
||
lng <= CHINA_BOUNDS.maxLng
|
||
);
|
||
};
|
||
|
||
const getStatusLabel = (value) => {
|
||
const found = disposal_status.value.find((item) => item.value === value);
|
||
return found ? found.label : "未知状态";
|
||
};
|
||
|
||
// 加载表格数据
|
||
const loadTableData = () => {
|
||
getchuzhiList({ warningStatus: 1 })
|
||
.then((res) => {
|
||
tableData.value = res.rows || [];
|
||
})
|
||
.catch((err) => {
|
||
console.error("加载表格数据失败:", err);
|
||
ElMessage.error("加载表格数据失败");
|
||
});
|
||
};
|
||
|
||
// 初始化 - 修复Leaflet图标路径问题与主地图一致
|
||
onMounted(() => {
|
||
loadTableData();
|
||
|
||
// 修复Leaflet默认图标路径问题
|
||
if (!L.Icon.Default.prototype._getIconUrl) {
|
||
delete L.Icon.Default.prototype._getIconUrl;
|
||
L.Icon.Default.mergeOptions({
|
||
iconRetinaUrl: require("leaflet/dist/images/marker-icon-2x.png"),
|
||
iconUrl: require("leaflet/dist/images/marker-icon.png"),
|
||
shadowUrl: require("leaflet/dist/images/marker-shadow.png"),
|
||
});
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@use "@/assets/styles/computed.scss" as calculate;
|
||
$tableColumnLength: 4;
|
||
$table__title__height: calculate.vh(45px);
|
||
$table__item__height: calculate.vh(35px);
|
||
.titles {
|
||
color: #fff;
|
||
text-align: center;
|
||
font-size: calculate.px2font(20px);
|
||
}
|
||
// 基础样式
|
||
.nocontent {
|
||
width: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
&:deep(.el-empty) {
|
||
padding: calculate.vh(50px) 0;
|
||
.el-empty__image {
|
||
width: calculate.vw(100px) !important;
|
||
}
|
||
.el-empty__description {
|
||
margin-top: calculate.vh(40px);
|
||
p {
|
||
font-size: calculate.px2font(20px);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.callDemo {
|
||
&:deep(#demo) {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 10px;
|
||
|
||
.el-button {
|
||
max-width: 100px;
|
||
}
|
||
}
|
||
}
|
||
// 弹窗样式
|
||
.detail-section {
|
||
height: 50%;
|
||
padding: 10px;
|
||
overflow: auto;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.map-controls {
|
||
height: 5%;
|
||
padding: 5px 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15px;
|
||
box-sizing: border-box;
|
||
|
||
:deep(.el-slider) {
|
||
width: 150px;
|
||
}
|
||
}
|
||
|
||
.map-section {
|
||
height: calculate.vh(500px);
|
||
position: relative;
|
||
padding: calculate.vh(10px);
|
||
box-sizing: border-box;
|
||
margin-top: calculate.vh(10px);
|
||
}
|
||
|
||
#warningMap {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 4px;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
// 地图状态样式 - 与主地图一致
|
||
.map-loading,
|
||
.no-data {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
z-index: 1001;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.map-loading {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
color: #555;
|
||
}
|
||
|
||
// 热力图图例 - 与主地图完全一致
|
||
.map-legend {
|
||
position: absolute;
|
||
bottom: 30px;
|
||
right: 20px;
|
||
z-index: 1000;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
padding: 12px;
|
||
border-radius: 8px;
|
||
color: #333;
|
||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||
border: 1px solid #eee;
|
||
}
|
||
|
||
.map-legend h4 {
|
||
margin: 0 0 8px 0;
|
||
font-size: 14px;
|
||
color: #333;
|
||
}
|
||
|
||
.legend-gradient {
|
||
height: 20px;
|
||
width: 180px;
|
||
background: linear-gradient(
|
||
to right,
|
||
#ffeda0,
|
||
/* 低 */ #fed976,
|
||
#feb24c,
|
||
#fd8d3c,
|
||
#fc4e2a,
|
||
#e31a1c /* 高 */
|
||
);
|
||
margin: 5px 0;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.legend-labels {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 12px;
|
||
color: #555;
|
||
}
|
||
|
||
.card__content-box {
|
||
.table {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
margin-top: 2vh;
|
||
// 表头容器 - 固定不滚动
|
||
&__header-container {
|
||
flex-shrink: 0; // 不缩小
|
||
border-bottom: 1px solid #888; // 增加分隔线
|
||
}
|
||
|
||
// 内容容器 - 可滚动
|
||
&__body-container {
|
||
flex-grow: 1; // 占满剩余空间
|
||
overflow-y: auto; // 允许垂直滚动
|
||
// 添加滚动条样式(可选)
|
||
&::-webkit-scrollbar {
|
||
width: 0;
|
||
}
|
||
&::-webkit-scrollbar-thumb {
|
||
background-color: rgba(255, 255, 255, 0.3);
|
||
border-radius: 3px;
|
||
}
|
||
}
|
||
|
||
&__item {
|
||
line-height: $table__item__height;
|
||
display: grid;
|
||
grid-template-columns: 2fr repeat(4, minmax(0, 3fr));
|
||
color: #fff;
|
||
text-align: center;
|
||
font-size: calculate.px2font(14px);
|
||
font-weight: bold;
|
||
border-radius: calculate.px2font(2px);
|
||
padding: calculate.vh(7px) 0;
|
||
cursor: pointer;
|
||
border-bottom: 10px solid #0b2342; // 红色分隔线(若只需纯间距可删除此句)
|
||
// background-color: #29689e;
|
||
}
|
||
|
||
&__item:nth-child(2n) {
|
||
// background-color: #3c93d2;
|
||
}
|
||
|
||
&__item:hover {
|
||
box-shadow: inset 0 0 0 calculate.px2font(3px) #266fff;
|
||
}
|
||
|
||
// 表头样式
|
||
&__header {
|
||
font-weight: bold;
|
||
border-bottom: none;
|
||
}
|
||
}
|
||
}
|
||
// 时间线样式
|
||
.custom-timeline {
|
||
position: relative;
|
||
padding-left: 20px;
|
||
margin-top: 4%;
|
||
|
||
.timeline-item {
|
||
position: relative;
|
||
padding-bottom: 20px;
|
||
|
||
&:not(:last-child)::after {
|
||
content: "";
|
||
position: absolute;
|
||
left: -2px;
|
||
top: 16px;
|
||
height: calc(100% - 16px);
|
||
width: 2px;
|
||
background: #e4e7ed;
|
||
}
|
||
}
|
||
|
||
.timeline-dot {
|
||
position: absolute;
|
||
left: -8px;
|
||
top: 4px;
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
background: #409eff;
|
||
}
|
||
|
||
.timeline-content {
|
||
padding-left: 20px;
|
||
color: #fff;
|
||
}
|
||
}
|
||
|
||
.tooltip {
|
||
width: 98%;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
// 图层层级控制 - 与主地图完全一致
|
||
:deep(.leaflet-map-pane) {
|
||
z-index: 2 !important;
|
||
}
|
||
:deep(.leaflet-tile-pane) {
|
||
z-index: 2 !important;
|
||
} /* 底图 */
|
||
:deep(.leaflet-overlay-pane) {
|
||
z-index: 3 !important;
|
||
} /* 热力图在底图上方 */
|
||
:deep(.leaflet-marker-pane) {
|
||
z-index: 4 !important;
|
||
} /* 标记在热力图上方 */
|
||
:deep(.leaflet-popup-pane) {
|
||
z-index: 5 !important;
|
||
} /* 弹窗在最上方 */
|
||
.largeScreen__descriptions {
|
||
:deep(.el-button){
|
||
background: #0479fe;
|
||
}
|
||
}
|
||
</style>
|