From 2059579bd0f6dc761510d3912c8866d164fb88f7 Mon Sep 17 00:00:00 2001 From: nightdays Date: Tue, 18 Nov 2025 17:53:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=86=B0=E9=9B=AA=E9=98=BB=E6=96=AD?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E5=A4=A7=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/cockpit/api/commonHttp.js | 12 + .../assets/legendTool/气象预警icon定位.png | Bin 0 -> 3167 bytes .../cockpit/components/CockpitLayout.vue | 30 +- .../ImageMarkTooltip/ImageMarkTooltip.vue | 333 ++++++++++++++++++ .../ImageMarkTooltip/blockEvent.vue | 11 + .../components/ImageMarkTooltip/index.js | 78 ++++ .../components/ImageMarkTooltip/riskRoad.vue | 10 + .../ImageMarkTooltip/serviceFacility.vue | 24 ++ .../ImageMarkTooltip/weatherAlert.vue | 11 + .../cockpit/components/LegendToolbar.vue | 31 +- .../cockpit/composables/ImageMarkData.js | 99 ++++++ .../views/cockpit/composables/useMapBase.js | 37 ++ .../cockpit/composables/useMapImageMark.js | 177 ++++++++++ 13 files changed, 843 insertions(+), 10 deletions(-) create mode 100644 packages/screen/src/views/cockpit/api/commonHttp.js create mode 100644 packages/screen/src/views/cockpit/assets/legendTool/气象预警icon定位.png create mode 100644 packages/screen/src/views/cockpit/components/ImageMarkTooltip/ImageMarkTooltip.vue create mode 100644 packages/screen/src/views/cockpit/components/ImageMarkTooltip/blockEvent.vue create mode 100644 packages/screen/src/views/cockpit/components/ImageMarkTooltip/index.js create mode 100644 packages/screen/src/views/cockpit/components/ImageMarkTooltip/riskRoad.vue create mode 100644 packages/screen/src/views/cockpit/components/ImageMarkTooltip/serviceFacility.vue create mode 100644 packages/screen/src/views/cockpit/components/ImageMarkTooltip/weatherAlert.vue create mode 100644 packages/screen/src/views/cockpit/composables/ImageMarkData.js create mode 100644 packages/screen/src/views/cockpit/composables/useMapBase.js create mode 100644 packages/screen/src/views/cockpit/composables/useMapImageMark.js diff --git a/packages/screen/src/views/cockpit/api/commonHttp.js b/packages/screen/src/views/cockpit/api/commonHttp.js new file mode 100644 index 0000000..3c4ec8c --- /dev/null +++ b/packages/screen/src/views/cockpit/api/commonHttp.js @@ -0,0 +1,12 @@ +import { request } from '@shared/utils/request' + +// 获取业务基础地图 +export function getBusinessBaseMapLayer() { + return request({ + url: '/snow-ops-platform/dataDirectory/queryCatalog', + method: 'GET', + params: { + pcatalog: 'DDT' + } + }) +} \ No newline at end of file diff --git a/packages/screen/src/views/cockpit/assets/legendTool/气象预警icon定位.png b/packages/screen/src/views/cockpit/assets/legendTool/气象预警icon定位.png new file mode 100644 index 0000000000000000000000000000000000000000..165d3626b74db333673e991cc5f0d281a3ff44f9 GIT binary patch literal 3167 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 - + + + diff --git a/packages/screen/src/views/cockpit/components/ImageMarkTooltip/blockEvent.vue b/packages/screen/src/views/cockpit/components/ImageMarkTooltip/blockEvent.vue new file mode 100644 index 0000000..2aa8a1e --- /dev/null +++ b/packages/screen/src/views/cockpit/components/ImageMarkTooltip/blockEvent.vue @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/packages/screen/src/views/cockpit/components/ImageMarkTooltip/index.js b/packages/screen/src/views/cockpit/components/ImageMarkTooltip/index.js new file mode 100644 index 0000000..ab102aa --- /dev/null +++ b/packages/screen/src/views/cockpit/components/ImageMarkTooltip/index.js @@ -0,0 +1,78 @@ +import { createVNode, render } from 'vue' +import ImageMarkTooltip from './ImageMarkTooltip.vue' + +class ImageMarkTooltipUI { + constructor() { + this.instance = null + this.container = null + this.entity = null + this.root = null + } + + // 显示 tooltip + show(options = {}) { + // 销毁之前的实例 + this.close() + + // 创建容器 + this.container = document.createElement('div') + this.container.className = 'tooltip-service-container' + this.root = document.querySelector('.cockpit-main') + this.root.appendChild(this.container) + + this.entity = options.entity + + // 创建 VNode + const vnode = createVNode(ImageMarkTooltip, { + visible: true, + position: options.position || { x: 0, y: 0 }, + data: options.data || {}, + loading: options.loading || false, + error: options.error || '', + onClose: () => { + this.close() + } + }) + + // 渲染到容器 + render(vnode, this.container) + this.instance = vnode.component + return this.instance + } + + updatePosition(position) { + if (this.instance) { + this.instance.props.position = position + this.instance.update() + } + } + + // 更新 tooltip 内容 + update(options) { + if (this.instance) { + this.instance.props = { + ...this.instance.props, + ...options + } + } + } + + // 关闭 tooltip + close() { + if (this.container) { + render(null, this.container) + this.root.removeChild(this.container) + this.container = null + this.instance = null + } + } +} + +// 创建单例实例 +const instance = new ImageMarkTooltipUI() + +export const CommonTooltip = instance + +export const newImageMarkTooltip = () => { + return new ImageMarkTooltipUI() +} \ No newline at end of file diff --git a/packages/screen/src/views/cockpit/components/ImageMarkTooltip/riskRoad.vue b/packages/screen/src/views/cockpit/components/ImageMarkTooltip/riskRoad.vue new file mode 100644 index 0000000..0dde344 --- /dev/null +++ b/packages/screen/src/views/cockpit/components/ImageMarkTooltip/riskRoad.vue @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/packages/screen/src/views/cockpit/components/ImageMarkTooltip/serviceFacility.vue b/packages/screen/src/views/cockpit/components/ImageMarkTooltip/serviceFacility.vue new file mode 100644 index 0000000..c8cd7e7 --- /dev/null +++ b/packages/screen/src/views/cockpit/components/ImageMarkTooltip/serviceFacility.vue @@ -0,0 +1,24 @@ + + + \ No newline at end of file diff --git a/packages/screen/src/views/cockpit/components/ImageMarkTooltip/weatherAlert.vue b/packages/screen/src/views/cockpit/components/ImageMarkTooltip/weatherAlert.vue new file mode 100644 index 0000000..2aa8a1e --- /dev/null +++ b/packages/screen/src/views/cockpit/components/ImageMarkTooltip/weatherAlert.vue @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/packages/screen/src/views/cockpit/components/LegendToolbar.vue b/packages/screen/src/views/cockpit/components/LegendToolbar.vue index da66089..26bd9b7 100644 --- a/packages/screen/src/views/cockpit/components/LegendToolbar.vue +++ b/packages/screen/src/views/cockpit/components/LegendToolbar.vue @@ -76,6 +76,7 @@ import blockEventMarkerIcon from '../assets/legendTool/阻断事件icon定位.pn import emergencyForceIcon from '../assets/legendTool/应急力量icon.png' import emergencyForceMarkerIcon from '../assets/legendTool/应急力量icon定位.png' import weatherAlertIcon from '../assets/legendTool/气象预警icon.png' +import weatherAlertMarkerIcon from '../assets/legendTool/气象预警icon定位.png' // 默认图例项配置(包含普通图标和地图定位图标) const defaultLegendItems = [ @@ -84,12 +85,12 @@ const defaultLegendItems = [ { key: 'trafficMonitor', label: '交调', icon: trafficMonitorIcon, markerIcon: trafficMonitorMarkerIcon }, { key: 'bridge', label: '桥梁', icon: bridgeIcon, markerIcon: bridgeMarkerIcon }, { key: 'tunnel', label: '隧洞', icon: tunnelIcon, markerIcon: tunnelMarkerIcon }, - { key: 'serviceFacility', label: '服务设施', icon: serviceFacilityIcon, markerIcon: serviceFacilityMarkerIcon }, - { key: 'riskRoad', label: '风险路段', icon: riskRoadIcon, markerIcon: riskRoadMarkerIcon }, + { key: 'serviceFacility', label: '养护站', icon: serviceFacilityIcon, markerIcon: serviceFacilityMarkerIcon }, + { key: 'riskRoad', label: '高海拔路段', icon: riskRoadIcon, markerIcon: riskRoadMarkerIcon }, { key: 'hazardPoint', label: '涉灾隐患点', icon: hazardPointIcon, markerIcon: hazardPointMarkerIcon }, { key: 'blockEvent', label: '阻断事件', icon: blockEventIcon, markerIcon: blockEventMarkerIcon }, { key: 'emergencyForce', label: '应急力量', icon: emergencyForceIcon, markerIcon: emergencyForceMarkerIcon }, - { key: 'weatherAlert', label: '气象预警', icon: weatherAlertIcon, markerIcon: weatherAlertIcon } + { key: 'weatherAlert', label: '气象预警', icon: weatherAlertIcon, markerIcon: weatherAlertMarkerIcon } ] // 定义 props @@ -103,6 +104,14 @@ const props = defineProps({ default: null }, + /** + * 根据key显示对应图例 + */ + legendKeys: { + type: Array, + default: null + }, + /** * Marker 数据配置(可选) * 如果提供,将在事件中传递完整的 marker 数据 @@ -135,10 +144,10 @@ const props = defineProps({ * 兼容旧版:标记点切换回调函数 * @deprecated 推荐使用 @marker-toggle 事件 */ - onMarkerToggle: { - type: Function, - default: null - } + // onMarkerToggle: { + // type: Function, + // default: null + // } }) // 定义 emits @@ -146,6 +155,13 @@ const emit = defineEmits(['marker-toggle', 'clear', 'update:modelValue', 'collap // 计算图例项配置(支持自定义) const computedLegendItems = computed(() => { + if(props.legendKeys && props.legendKeys.length > 0) { + return props.legendKeys.map(key => { + const item = defaultLegendItems.find(item => item.key === key) + return item + }) + } + return props.legendItems && props.legendItems.length > 0 ? props.legendItems : defaultLegendItems @@ -260,7 +276,6 @@ const handleItemClick = (key) => { // 构建并发送事件 const payload = buildPayload(key, newIsActive) emit('marker-toggle', payload) - // 兼容旧版 props 回调 if (props.onMarkerToggle) { props.onMarkerToggle( diff --git a/packages/screen/src/views/cockpit/composables/ImageMarkData.js b/packages/screen/src/views/cockpit/composables/ImageMarkData.js new file mode 100644 index 0000000..128c613 --- /dev/null +++ b/packages/screen/src/views/cockpit/composables/ImageMarkData.js @@ -0,0 +1,99 @@ +import { newImageMarkTooltip } from '../components/ImageMarkTooltip' + +// 专门用于绘制图片的api数据类 +export default class ImageMarkData { + key = null + markerIcon = null + api = null + // 提示框实例,当鼠标移入或者点击图例时,需要获取到该实例 + tooltip = null + onResponse = null + cacheData = null + expiresAt = null + abortController = null + /** + * 60秒内重复点击使用缓存数据,减少服务器压力 + */ + cacheTime = 60 * 1000 + + constructor({ api, tooltip, onResponse }) { + this.api = api + this.response = onResponse + // 初始化提示框 + this.tooltip = tooltip || newImageMarkTooltip() + } + + getCache = () => { + // 检查缓存是否有效 + if ( + this.cacheData?.length && + this.expiresAt > Date.now() + ) { + return this.cacheData + } + } + + request = async (params) => { + // 首先查看缓存是否可用 + const cacheData = this.getCache() + if (cacheData) return cacheData + + const controller = this.createAbortController() + const config = {} + if (controller) config.signal = controller.signal + let res = null + try { + res = await this.api(params, config) + } catch (error) { + console.error('请求失败: ', error) + } finally { + let data = null + if (this.response) data = this.response(res) + else data = this.commonOnResponse(res) + + this.cacheData = data + this.expiresAt = Date.now() + this.cacheTime + + return data + } + } + + createAbortController = () => { + if (typeof AbortController === 'undefined') { + console.warn('当前环境不支持 AbortController') + return null + } + return new AbortController() + } + + cancelRequest = () => { + if (this.abortController) { + try { + this.abortController.abort() + } catch (error) { + this.console.warn('取消请求失败', error) + } finally { + this.abortController = null + } + } + } + + // 通用的响应处理 + commonOnResponse = (res) => { + if (res?.success) { + res.data = res.data.slice(0, 2) + const dataList = res.data.map((item) => { + item.mapData = { + id: this.key + '-' + item.rid, + layerId: this.key, + position: [item.jd, item.wd, 0], + image: this.markerIcon + } + return item + }) + this.data = dataList + return dataList + } + return [] + } +} \ No newline at end of file diff --git a/packages/screen/src/views/cockpit/composables/useMapBase.js b/packages/screen/src/views/cockpit/composables/useMapBase.js new file mode 100644 index 0000000..2983faf --- /dev/null +++ b/packages/screen/src/views/cockpit/composables/useMapBase.js @@ -0,0 +1,37 @@ +import { getBusinessBaseMapLayer } from '@/views/cockpit/api/commonHttp.js' + +// 当前页面的最基础地图服务 +// 主要是加载地图底图 +export const useMapBase = (mapStore) => { + + const loadBusinessBaseMapLayer = async () => { + const layerService = mapStore.services().layer + const res = await getBusinessBaseMapLayer() + const data = [...res] + mapStore.baseMapGroups = data + for (const item of data) { + const layers = mapStore.getBaseMapLayersForGroup(item.Attribute?.rid || item.Rid) + for (const layerConfig of layers) { + const layer = { + id: layerConfig.id, + type: layerConfig.type, + url: layerConfig.url, + meta: layerConfig.meta, + } + await layerService.addLayer(layer) + } + } + + + } + + const loadBaseData = () => { + setTimeout(() => { + loadBusinessBaseMapLayer() + }, 0) + } + + return { + loadBaseData + } +} \ No newline at end of file diff --git a/packages/screen/src/views/cockpit/composables/useMapImageMark.js b/packages/screen/src/views/cockpit/composables/useMapImageMark.js new file mode 100644 index 0000000..e3f3912 --- /dev/null +++ b/packages/screen/src/views/cockpit/composables/useMapImageMark.js @@ -0,0 +1,177 @@ +import { fetchEmergencyForceList } from '@/views/cockpit/api/emergencyForce' + +import ImageMarkData from './ImageMarkData' +import * as Cesium from 'cesium' + +/** + * 当前业务下的地图服务 + * 主要是涉及需要调用后端服务获得地图数据 + * */ + +export const useMapImageMark = (mapStore) => { + + // mapStore准备就绪进行初始化 + mapStore.onReady(() => init()) + + // 接口服务映射 + const imageMarkMap = { + serviceFacility: new ImageMarkData({ + api: fetchEmergencyForceList, + }), + riskRoad: new ImageMarkData({ + api: fetchEmergencyForceList, + }), + blockEvent: new ImageMarkData({ + api: fetchEmergencyForceList, + }), + weatherAlert: new ImageMarkData({ + api: fetchEmergencyForceList, + }) + } + + let entityService + let viewer + let handler + + const init = () => { + entityService = mapStore.services().entity + viewer = mapStore.getViewer() + + // 事件注册 + handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas); + + // 点击事件 + handler.setInputAction(function (event) { + const pickedFeature = viewer.scene.pick(event.position); + if (pickedFeature?.id) { + const entity = pickedFeature.id + const data = entity.properties.originalData.getValue() + const imageMarkData = imageMarkMap[data.mapData.layerId] + imageMarkData.tooltip.show({ + data, + entity, + position: getScreenPosition(entity) + }) + } + + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + + // 帧渲染函数,也就是显示器的每一帧都会执行 + // 地图拖动与放大的监控,需要更新各个tooltips的位置, 通过消息来通知 + viewer.scene.postRender.addEventListener(() => { + for(const key in imageMarkMap) { + const imageMarkData = imageMarkMap[key] + const entity = imageMarkData.tooltip.entity + if(!entity) continue + imageMarkData.tooltip.updatePosition(getScreenPosition(entity)) + } + }) + } + + + + /** + * 绘制各个地图标,例如点击LengendItem的某一项,就会显示对应的图标列表 + */ + const drawImageEntities = async (dataList) => { + for (const item of dataList) { + const mapData = item.mapData + entityService.addBillboard({ + id: mapData.id, + layerId: mapData.layerId, + position: mapData.position, + image: mapData.image, + width: mapData.width || 44, + height: mapData.height || 44, + clampToGround: true, + properties: { originalData: item } + }) + } + } + + /* + * 清除某个图层里的实体 + */ + const clearLayerEntity = (layerId) => { + entityService.clearLayerEntities(layerId) + const imageMarkData = imageMarkMap[layerId] + if (!imageMarkData) return + imageMarkData.tooltip.close() + } + + /** + * 根据图标名称请求后台服务,动态加载图标列表 + */ + const loadDynamicMark = async ({ key, markerIcon, params }) => { + const imageMarkData = imageMarkMap[key] + if (!imageMarkData) return + imageMarkData.markerIcon = markerIcon + imageMarkData.key = key + const dataList = await imageMarkData.request(params) + + drawImageEntities(dataList) + } + + /** + * 显示/隐藏地图标 + */ + const toggleMark = async ({ key, active, markerIcon, params }) => { + const imageMarkData = imageMarkMap[key] + + if (!active) { + clearLayerEntity(key) + if (imageMarkData) imageMarkData.cancelRequest() + return + } + + loadDynamicMark({ key, markerIcon, params }) + } + + /** + * 获得该实体位于屏幕的位置,用于html元素进行定位使用 + */ + const getScreenPosition = (entity) => { + + try { + const position = entity.position?.getValue(Cesium.JulianDate.now()) + if (!position) return + + // 处理 clampToGround 实体 + // 对于使用 CLAMP_TO_GROUND 的 billboard,需要获取实际的地形高度 + let clampedPosition = position + + // 转换为地理坐标 + const cartographic = Cesium.Cartographic.fromCartesian(position) + + // 尝试获取地形高度 + if (viewer.scene.globe) { + const height = viewer.scene.globe.getHeight(cartographic) + + // 如果成功获取地形高度,使用地形高度重建位置 + if (Cesium.defined(height)) { + cartographic.height = height + clampedPosition = Cesium.Cartographic.toCartesian(cartographic) + } else { + // 备用方案:尝试使用 clampToHeight + const clampedCartesian = viewer.scene.clampToHeight(position) + if (clampedCartesian) { + clampedPosition = clampedCartesian + } + } + } + + // 使用修正后的位置进行屏幕坐标转换 + const screenPosition = viewer.scene.cartesianToCanvasCoordinates(clampedPosition) + if (screenPosition) { + return screenPosition + } + } catch (error) { + console.error('更新 Tooltip 位置失败:', error) + } + } + + return { + toggleMark + } + +} \ No newline at end of file