feat: 冰雪阻断大屏新增聚焦功能
This commit is contained in:
parent
0802468c75
commit
09dc7a6119
@ -9,17 +9,18 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "^3.5.18",
|
|
||||||
"vue-router": "^4.6.3",
|
|
||||||
"pinia": "^3.0.3",
|
|
||||||
"element-plus": "^2.11.5",
|
|
||||||
"@element-plus/icons-vue": "^2.3.2",
|
"@element-plus/icons-vue": "^2.3.2",
|
||||||
"echarts": "^6.0.0",
|
"@h5/shared": "workspace:*",
|
||||||
"vue-echarts": "^8.0.1",
|
"@turf/turf": "^7.3.0",
|
||||||
"cesium": "^1.135.0",
|
|
||||||
"axios": "^1.13.2",
|
|
||||||
"@vueuse/core": "^14.0.0",
|
"@vueuse/core": "^14.0.0",
|
||||||
"@h5/shared": "workspace:*"
|
"axios": "^1.13.2",
|
||||||
|
"cesium": "^1.135.0",
|
||||||
|
"echarts": "^6.0.0",
|
||||||
|
"element-plus": "^2.11.5",
|
||||||
|
"pinia": "^3.0.3",
|
||||||
|
"vue": "^3.5.18",
|
||||||
|
"vue-echarts": "^8.0.1",
|
||||||
|
"vue-router": "^4.6.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
|
|||||||
@ -433,12 +433,19 @@ const renderEmergencyForcePoints = async (entityService, points, markerIcon) =>
|
|||||||
* 应急资源表格行被点击时触发
|
* 应急资源表格行被点击时触发
|
||||||
*/
|
*/
|
||||||
const handleEmergencyResourceClick = (resource) => {
|
const handleEmergencyResourceClick = (resource) => {
|
||||||
|
let focusKey = null
|
||||||
|
|
||||||
// 根据区县名称,过滤养护站
|
// 根据区县名称,过滤养护站
|
||||||
const filterList = filterMark.value.serviceFacility
|
const filterList = filterMark.value.serviceFacility
|
||||||
const index = filterList.findIndex((item) => item == resource.qxmc)
|
const index = filterList.findIndex((item) => item == resource.qxmc)
|
||||||
// 实现第一次点击表示选中,第二次点击表示取消选中
|
// 实现第一次点击表示选中,第二次点击表示取消选中
|
||||||
if (index > -1) filterList.splice(index, 1)
|
if (index > -1) {
|
||||||
else filterList.push(resource.qxmc)
|
filterList.splice(index, 1)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
filterList.push(resource.qxmc)
|
||||||
|
focusKey = resource.qxmc
|
||||||
|
}
|
||||||
|
|
||||||
// 当点击了某个区县,需要将养护站的legend也点亮
|
// 当点击了某个区县,需要将养护站的legend也点亮
|
||||||
const legendIndex = legendToolActiveItem.value.indexOf('serviceFacility')
|
const legendIndex = legendToolActiveItem.value.indexOf('serviceFacility')
|
||||||
@ -451,9 +458,16 @@ const handleEmergencyResourceClick = (resource) => {
|
|||||||
legendToolActiveItem.value.splice(legendIndex, 1)
|
legendToolActiveItem.value.splice(legendIndex, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
mapImageMarkHook.filterYHZMark("serviceFacility", (item) => {
|
// 调用图例hook来进行数据的请求与图例的绘制,第一个回调函数是过滤点位,第二个是聚焦点位
|
||||||
return filterList.find((filterItem) => filterItem == item.qxmc)
|
mapImageMarkHook.filterYHZMark(
|
||||||
})
|
"serviceFacility",
|
||||||
|
(item) => {
|
||||||
|
return filterList.find((filterItem) => filterItem == item.qxmc)
|
||||||
|
},
|
||||||
|
(item) => {
|
||||||
|
return item.qxmc == focusKey
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,391 +0,0 @@
|
|||||||
<template>
|
|
||||||
<transition name="fade">
|
|
||||||
<div v-if="visible && position" class="common-tooltip" :style="{
|
|
||||||
left: `${position.x}px`,
|
|
||||||
top: `${position.y - 20}px`
|
|
||||||
}">
|
|
||||||
<span class="title">{{ title }}</span>
|
|
||||||
<!-- 关闭按钮 -->
|
|
||||||
<button class="close-button" type="button" aria-label="关闭" @click="handleClose">
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
|
||||||
<div class="tooltip-content" v-if="!loading && detail">
|
|
||||||
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">名称:</span>
|
|
||||||
<span class="value">{{ detail.mc }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">所属区县:</span>
|
|
||||||
<span class="value">{{ detail.qxmc }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">应急设备:</span>
|
|
||||||
<span class="value">{{ detail.sbsl }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">应急物资:</span>
|
|
||||||
<span class="value">{{ detail.wzsl }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="label">应急人员:</span>
|
|
||||||
<span class="value">{{ detail.rysl }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 如果没有数据 -->
|
|
||||||
<div v-if="!hasData" class="no-data">
|
|
||||||
暂无详细信息
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 加载状态 -->
|
|
||||||
<div v-if="loading" class="loading-overlay">
|
|
||||||
<div class="loading-spinner"></div>
|
|
||||||
<span>加载中...</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 错误状态 -->
|
|
||||||
<div v-if="error" class="error-message">
|
|
||||||
{{ error }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { computed, ref } from 'vue'
|
|
||||||
import { getYHZDetail } from '@/views/cockpit/api/commonHttp.js'
|
|
||||||
/**
|
|
||||||
* 应急力量详情提示框组件
|
|
||||||
* 使用 HTML Overlay 方式显示在地图标记点上方
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ==================== Props ====================
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
/**
|
|
||||||
* 是否显示提示框
|
|
||||||
*/
|
|
||||||
visible: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 提示框位置(屏幕坐标)
|
|
||||||
* @type {{ x: number, y: number }}
|
|
||||||
*/
|
|
||||||
position: {
|
|
||||||
type: Object,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 详情数据
|
|
||||||
* @type {{ qxmc?: string, yjllpz?: string, wzQtwz?: string }}
|
|
||||||
*/
|
|
||||||
data: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 错误信息
|
|
||||||
*/
|
|
||||||
error: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// ==================== State ====================
|
|
||||||
|
|
||||||
const title = ref('养护站')
|
|
||||||
const detail = ref(null)
|
|
||||||
const loading = ref(true)
|
|
||||||
|
|
||||||
// ==================== Emits ====================
|
|
||||||
|
|
||||||
const emit = defineEmits(['close'])
|
|
||||||
|
|
||||||
// ==================== Computed ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否有有效数据
|
|
||||||
*/
|
|
||||||
const hasData = computed(() => {
|
|
||||||
return !!props.data
|
|
||||||
})
|
|
||||||
|
|
||||||
// ==================== Methods ====================
|
|
||||||
// 这个方法会在index.js中被执行
|
|
||||||
const init = () => {
|
|
||||||
getDetail()
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDetail = async () => {
|
|
||||||
loading.value = true
|
|
||||||
const res = await getYHZDetail({
|
|
||||||
id: props.data.id
|
|
||||||
})
|
|
||||||
if (res.success) {
|
|
||||||
detail.value = res.data
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理关闭按钮点击
|
|
||||||
*/
|
|
||||||
const handleClose = () => {
|
|
||||||
emit('close')
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
init
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@use '@/styles/mixins.scss' as *;
|
|
||||||
|
|
||||||
.flex {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.common-tooltip {
|
|
||||||
// CSS 变量:控制背景图片尺寸和内边距
|
|
||||||
// 注意:修改图片资源时,需要同步更新这些高度值以匹配实际 PNG 尺寸
|
|
||||||
--tooltip-top-height: 68px;
|
|
||||||
--tooltip-bottom-height: 68px;
|
|
||||||
--tooltip-side-padding: 24px;
|
|
||||||
--tooltip-body-padding: 16px;
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
z-index: 9999;
|
|
||||||
pointer-events: auto;
|
|
||||||
|
|
||||||
// 居中并置于标记点上方
|
|
||||||
transform: translate(-50%, calc(-100% - 20px));
|
|
||||||
|
|
||||||
min-width: 200px;
|
|
||||||
width: 300px;
|
|
||||||
|
|
||||||
// 上下 padding 需要容纳顶部和底部图片 + 内容间距
|
|
||||||
// 左右 padding 保持一致
|
|
||||||
padding: calc(var(--tooltip-top-height)) var(--tooltip-side-padding) calc(var(--tooltip-bottom-height));
|
|
||||||
|
|
||||||
// 三段式可拉伸背景:顶部固定高度、中部可拉伸、底部固定高度
|
|
||||||
background-image:
|
|
||||||
url('@/views/cockpit/assets/emergencyForceTooltip/top.png'),
|
|
||||||
url('@/views/cockpit/assets/emergencyForceTooltip/bottom.png'),
|
|
||||||
url('@/views/cockpit/assets/emergencyForceTooltip/middle.png');
|
|
||||||
|
|
||||||
// 顶部/底部/中部都不重复,中部通过 background-size 拉伸填充
|
|
||||||
background-repeat: no-repeat, no-repeat, no-repeat;
|
|
||||||
|
|
||||||
// 背景定位:顶部居中对齐顶边,底部居中对齐底边
|
|
||||||
// 中部略向上偏移 1px,结合高度 +2px 在上下各覆盖 1px 避免子像素渲染缝隙
|
|
||||||
background-position:
|
|
||||||
center top,
|
|
||||||
center bottom,
|
|
||||||
center calc(var(--tooltip-top-height) - 1px);
|
|
||||||
|
|
||||||
// 背景尺寸:顶部/底部高度固定
|
|
||||||
// 中部填充剩余空间并增加 2px,在上下各多覆盖约 1px 避免间隙
|
|
||||||
background-size:
|
|
||||||
100% var(--tooltip-top-height),
|
|
||||||
100% var(--tooltip-bottom-height),
|
|
||||||
100% calc(100% - var(--tooltip-top-height) - var(--tooltip-bottom-height) + 2px);
|
|
||||||
|
|
||||||
// 移除原有的 border 和 border-radius,使用图片背景
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
// 保留毛玻璃效果和阴影以增强视觉层次
|
|
||||||
// backdrop-filter: blur(12px);
|
|
||||||
// box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
|
|
||||||
|
|
||||||
color: #fff;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.6;
|
|
||||||
|
|
||||||
// 小箭头(指向标记点)
|
|
||||||
// &::after {
|
|
||||||
// content: '';
|
|
||||||
// position: absolute;
|
|
||||||
// bottom: -10px;
|
|
||||||
// left: 50%;
|
|
||||||
// transform: translateX(-50%);
|
|
||||||
// width: 0;
|
|
||||||
// height: 0;
|
|
||||||
// border-left: 10px solid transparent;
|
|
||||||
// border-right: 10px solid transparent;
|
|
||||||
// border-top: 10px solid rgba(71, 186, 255, 0.4);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
white-space: nowrap;
|
|
||||||
position: absolute;
|
|
||||||
top: 12px;
|
|
||||||
left: 20px;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
font-family: PingFangSC, PingFang SC;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #FFFFFF;
|
|
||||||
line-height: 22px;
|
|
||||||
text-shadow: 0px 0px 20px #079DFF;
|
|
||||||
text-align: right;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭按钮 - 仅作为透明点击热区,视觉由背景图中的关闭图标承载
|
|
||||||
.close-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 18px;
|
|
||||||
right: 2px;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
// 透明化所有视觉元素,仅保留点击功能
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
color: transparent;
|
|
||||||
font-size: 0;
|
|
||||||
line-height: 1;
|
|
||||||
box-shadow: none;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
// 为键盘用户提供可见的焦点轮廓(仅在键盘导航时显示)
|
|
||||||
&:focus-visible {
|
|
||||||
outline: 2px solid rgba(255, 255, 255, 0.8);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 内容区域
|
|
||||||
.tooltip-content {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1.5fr 1fr;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
.info-item:first-child {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// padding-right: 1.5rem; // 为关闭按钮留出空间
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb {
|
|
||||||
margin-bottom: 0.5rem
|
|
||||||
}
|
|
||||||
|
|
||||||
// 信息项
|
|
||||||
.info-item {
|
|
||||||
|
|
||||||
.label {
|
|
||||||
white-space: nowrap;
|
|
||||||
color: rgba(255, 255, 255, 0.7);
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
font-family: SourceHanSansCN, SourceHanSansCN;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #14FFF6;
|
|
||||||
line-height: 21px;
|
|
||||||
text-align: left;
|
|
||||||
font-style: normal;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 无数据提示
|
|
||||||
.no-data {
|
|
||||||
color: rgba(255, 255, 255, 0.5);
|
|
||||||
text-align: center;
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载状态
|
|
||||||
.loading-overlay {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
|
|
||||||
// 使用半透明背景,让背景图片纹理仍然可见
|
|
||||||
background: rgba(0, 0, 0, 1);
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
|
|
||||||
span {
|
|
||||||
color: rgba(255, 255, 255, 0.8);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载动画
|
|
||||||
.loading-spinner {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
border: 3px solid rgba(71, 186, 255, 0.2);
|
|
||||||
border-top-color: rgba(71, 186, 255, 1);
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 0.8s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 错误信息
|
|
||||||
.error-message {
|
|
||||||
color: #ff6b6b;
|
|
||||||
font-size: 13px;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
background: rgba(255, 107, 107, 0.1);
|
|
||||||
border: 1px solid rgba(255, 107, 107, 0.3);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 淡入淡出动画
|
|
||||||
.fade-enter-active,
|
|
||||||
.fade-leave-active {
|
|
||||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-enter-from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translate(-50%, calc(-100% - 10px));
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translate(-50%, calc(-100% - 30px));
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -3,6 +3,7 @@ import { getAllYHZList } from '@/views/cockpit/api/commonHttp'
|
|||||||
|
|
||||||
import ImageMarkData from './ImageMarkData'
|
import ImageMarkData from './ImageMarkData'
|
||||||
import * as Cesium from 'cesium'
|
import * as Cesium from 'cesium'
|
||||||
|
import * as turf from '@turf/turf'
|
||||||
|
|
||||||
|
|
||||||
import serviceFacilityMarkerIcon from '../assets/legendTool/服务设施icon定位.png'
|
import serviceFacilityMarkerIcon from '../assets/legendTool/服务设施icon定位.png'
|
||||||
@ -134,6 +135,28 @@ export const useMapImageMark = (mapStore) => {
|
|||||||
imageMarkData.tooltip.close()
|
imageMarkData.tooltip.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聚焦到图片实例的中心点
|
||||||
|
*/
|
||||||
|
const focusOnMarkers = (dataList) => {
|
||||||
|
if (!dataList || dataList.length === 0) return
|
||||||
|
const positions = dataList.map((item) => {
|
||||||
|
return [item.mapData.position[0], item.mapData.position[1]]
|
||||||
|
})
|
||||||
|
const features = turf.points(positions)
|
||||||
|
const center = turf.center(features)
|
||||||
|
const position = center.geometry.coordinates
|
||||||
|
mapStore.viewer.camera.setView({
|
||||||
|
destination: Cesium.Cartesian3.fromDegrees(position[0], position[1], 160000),
|
||||||
|
orientation: {
|
||||||
|
heading: Cesium.Math.toRadians(0), // 方向角
|
||||||
|
pitch: Cesium.Math.toRadians(-90), // 俯仰角(-90 为垂直向下看)
|
||||||
|
roll: 0 // 翻滚角
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据图标名称请求后台服务,动态加载图标列表
|
* 根据图标名称请求后台服务,动态加载图标列表
|
||||||
*/
|
*/
|
||||||
@ -165,7 +188,7 @@ export const useMapImageMark = (mapStore) => {
|
|||||||
/**
|
/**
|
||||||
* 过滤养护站图标
|
* 过滤养护站图标
|
||||||
*/
|
*/
|
||||||
const filterYHZMark = async (key, filterFn) => {
|
const filterYHZMark = async (key, filterFn, focusFn) => {
|
||||||
const imageMarkData = imageMarkMap[key]
|
const imageMarkData = imageMarkMap[key]
|
||||||
if (!imageMarkData) return
|
if (!imageMarkData) return
|
||||||
// 取消上一次请求
|
// 取消上一次请求
|
||||||
@ -175,7 +198,15 @@ export const useMapImageMark = (mapStore) => {
|
|||||||
return filterFn(item)
|
return filterFn(item)
|
||||||
})
|
})
|
||||||
clearLayerEntity(key)
|
clearLayerEntity(key)
|
||||||
|
// 绘制过滤出来的点
|
||||||
drawImageEntities(dataList)
|
drawImageEntities(dataList)
|
||||||
|
// 聚焦聚焦函数过滤出来的点
|
||||||
|
const focusDataList = dataList.filter((item)=>{
|
||||||
|
return focusFn(item)
|
||||||
|
})
|
||||||
|
if(focusDataList) {
|
||||||
|
focusOnMarkers(focusDataList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user