| | |
| | | * @Author: shuishen 1109946754@qq.com |
| | | * @Date: 2024-11-09 15:41:35 |
| | | * @LastEditors: shuishen 1109946754@qq.com |
| | | * @LastEditTime: 2024-11-20 17:16:17 |
| | | * @LastEditTime: 2025-09-11 22:59:25 |
| | | * @FilePath: \bigScreen\src\views\pd\components\dynamicPanel.vue |
| | | * @Description: |
| | | * |
| | | * Copyright (c) 2024 by shuishen, All Rights Reserved. |
| | | --> |
| | | <script setup> |
| | | import 'element-plus/theme-chalk/dark/css-vars.css' |
| | | |
| | | import { ElMessage, ElNotification } from 'element-plus' |
| | | import qyfw from "@/assets/json/qyfw" |
| | | import EventBus from 'utils/bus' |
| | | import qysgwslx from "@/assets/json/qysgwslx" |
| | | import disposeDataJson from "@/assets/json/dispose" |
| | | const { VITE_APP_BASE } = import.meta.env |
| | | import { computed, onBeforeUnmount, onUnmounted, watch } from 'vue' |
| | | import { spatialAnalysisPoint } from "@/api/space/space" |
| | | import { useMap } from 'store/map' |
| | | const mapStore = useMap() |
| | | import { usePointStore } from 'store/usepoint' |
| | | |
| | | const showPanel = defineModel('showPanel') |
| | | const emit = defineEmits(['closePanel']) |
| | | import { onUnmounted } from 'vue' |
| | | |
| | | const panelData = reactive({ |
| | | data: [ |
| | | { |
| | | key: 1, |
| | | title: '典型案例1', |
| | | content: '南片区松节油储罐区泄漏事故: 南片区某企业松节油储罐泄漏,如不采取措施,将造成污染事故,影响到赣江下游水质。', |
| | | data: [ |
| | | { |
| | | type: 'demo', |
| | | key: 1, |
| | | title: '典型案例', |
| | | content: '化工园区罐区泄漏事故: 化工园区某企业储罐泄漏,如不采取措施,将造成污染事故,影响到长江下游水质。', |
| | | }, |
| | | |
| | | eventProfile: '2024年11月11日11时,南片区江西殷莱特化工有限公司松节油储罐泄漏,如不采取措施,将造成污染事故,影响到赣江下游水质。', |
| | | specificPollutantPosition: { |
| | | lng: 115.10175740, |
| | | lat: 27.27356477 |
| | | }, |
| | | specificPollutant: '松节油,无色至淡黄色油状液体,具有松香气味。 熔点:无 沸点:154~170℃相对密度(水=1):0.85~0.87 饱和蒸气压(kPa):2.67(51.4℃)溶解性:不溶于水,溶于乙醇、氯仿、醚等多数有机溶剂。', |
| | | |
| | | transitPointPosition: { |
| | | lng: 115.10231042, |
| | | lat: 27.27352320 |
| | | }, |
| | | |
| | | wastewaterTreatmentPosition: { |
| | | lng: 115.10814596, |
| | | lat: 27.27257473 |
| | | }, |
| | | wastewaterTreatment: '事故应急结束后,应急指挥部组织专家评估组指导企业对受污染的废水进入废水处理中站进行处理,对公共应急池的事故废水进行水质监测,分析水质达标情况,并研判处理处置方式,在确保安全的情况下转转输至绿源污水处理厂处理。' |
| | | }, |
| | | { |
| | | key: 2, |
| | | title: '典型案例2', |
| | | content: '北片区碱类腐蚀性物质泄漏事故: 北片区某企业(位于吉兴路以北)在生产过程中操作失误,大量含碱废水泄漏,如不采取措施,将造成污染事故,影响到赣江下游水质。', |
| | | |
| | | eventProfile: '2024年10月15日14时,北片区江西宏润香料有限公司在生产过程中操作失误,大量含碱废水泄漏,如不采取措施,将造成污染事故,影响到赣江下游水质。', |
| | | |
| | | specificPollutantPosition: { |
| | | lng: 115.10446199, |
| | | lat: 27.28453867 |
| | | }, |
| | | specificPollutant: '液碱,无色透明液体。是重要的化工基础原料,用途极广。化学工业用于制造甲酸、草酸、硼砂、苯酚、氰化钠及肥皂、合成脂肪酸、合成洗涤剂等,呈碱性。', |
| | | |
| | | transitPointPosition: { |
| | | lng: 115.10508747, |
| | | lat: 27.28393479 |
| | | }, |
| | | |
| | | wastewaterTreatmentPosition: { |
| | | lng: 115.10814596, |
| | | lat: 27.27257473 |
| | | }, |
| | | wastewaterTreatment: '将拦截废水从应急池中通过北区污水提升泵站转移至绿源污水处理厂进行处置,同时在排口下游展开应急监测,确保废水没有进入周边水系。' |
| | | }, |
| | | { |
| | | key: 3, |
| | | title: '典型案例3', |
| | | content: '危险化学品运输事故: 化工集中区北区(吉兴路以南)某企业的危化品运输车辆金工大道危化品运专用运输道上发生侧翻,大量盐酸流至路面,如不采取措施,将造成污染事故,影响到赣江下游水质。', |
| | | |
| | | eventProfile: '2024年11月04日18时,化工集中区北区江西迷斯婕生物科技有限公司的危化品运输车辆金工大道危化品运专用运输道上发生侧翻,大量盐酸流至路面,如不采取措施,将造成污染事故,影响到赣江下游水质。', |
| | | |
| | | specificPollutantPosition: { |
| | | lng: 115.10392960, |
| | | lat: 27.28660330 |
| | | }, |
| | | specificPollutant: '盐酸是无色液体(工业用盐酸会因有杂质三价铁盐而略显黄色),为氯化氢的水溶液,具有刺激性气味。', |
| | | |
| | | transitPointPosition: { |
| | | lng: 115.10468990, |
| | | lat: 27.28756147 |
| | | }, |
| | | |
| | | wastewaterTreatmentPosition: { |
| | | lng: 115.10814596, |
| | | lat: 27.27257473 |
| | | }, |
| | | wastewaterTreatment: '对转输至企业应急空间的事故废水进行水质监测,分析水质达标情况,并研判处理处置方式,在确保安全的情况下加药处理或者排入企业废水处理站处理' |
| | | }, |
| | | ] |
| | | { |
| | | type: 'custom', |
| | | key: 2, |
| | | title: '自定义案例', |
| | | content: '自定义风险源:化工园区某企业发生化学原料危害品泄漏,如不采取措施,将造成污染事故,影响到长江下游水质', |
| | | }, |
| | | ] |
| | | }) |
| | | |
| | | let isSpeaking = false |
| | | const dynamicVectorLayer = new DC.VectorLayer('dynamicVectorLayer').addTo(window.$viewer) |
| | | const dynamicHtmlLayer = new DC.HtmlLayer('dynamicHtmlLayer').addTo(window.$viewer) |
| | | let dynamicVectorLayer = new DC.VectorLayer('dynamicVectorLayer').addTo(window.$viewer) |
| | | let customVectorLayer = new DC.HtmlLayer('customVectorLayer').addTo(window.$viewer) |
| | | let qyWsLayer = new DC.VectorLayer('qyWsLayer').addTo(window.$viewer) |
| | | |
| | | const schemeStart = (item) => { |
| | | emit('closePanel') |
| | | clearAll() |
| | | let center = DC.Position.fromObject(item.specificPollutantPosition) |
| | | let circle = new DC.Circle(center, 20) |
| | | circle.setStyle({ |
| | | material: new DC.CircleWaveMaterialProperty({ |
| | | color: DC.Color.RED.withAlpha(0.9), |
| | | speed: 20, |
| | | count: 5, |
| | | gradient: 0.5 |
| | | qysgwslx.features.forEach(item => { |
| | | let polyline = new DC.Polyline(item.geometry.coordinates.join(';')) |
| | | polyline.setStyle({ |
| | | width: 5, |
| | | material: new DC.PolylineTrailMaterialProperty({ |
| | | color: DC.Color.ALICEBLUE, |
| | | speed: 10 |
| | | }), |
| | | clampToGround: true |
| | | }) |
| | | }) |
| | | dynamicVectorLayer.addOverlay(circle) |
| | | |
| | | let divContent = new DC.DivIcon( |
| | | new DC.Position(item.specificPollutantPosition.lng, item.specificPollutantPosition.lat, 70), |
| | | `<div class="dynamic-event-profile-map-popup"> |
| | | <div class="content-wrap"> |
| | | <div class="title">事件概况</div> |
| | | <div class="content"> |
| | | <p>${item.eventProfile}</p> |
| | | <p>特征污染物:${item.specificPollutant}</p> |
| | | </div> |
| | | </div> |
| | | <div class="arrow"></div> |
| | | </div>` |
| | | ) |
| | | dynamicHtmlLayer.addOverlay(divContent) |
| | | |
| | | window.$viewer.flyToPosition(new DC.Position(item.specificPollutantPosition.lng, item.specificPollutantPosition.lat, 400, 0, -90, 0), () => { |
| | | const utterance = new SpeechSynthesisUtterance(`事件概况:${item.eventProfile}特征污染物:${item.specificPollutant}`) |
| | | |
| | | isSpeaking = true |
| | | |
| | | utterance.onend = () => { |
| | | isSpeaking = false |
| | | |
| | | // 执行对应的事件函数 |
| | | window.$viewer.flyToPosition(new DC.Position(item.transitPointPosition.lng, item.transitPointPosition.lat, 400, 0, -90, 0), () => { |
| | | |
| | | let centerT = DC.Position.fromObject(item.transitPointPosition) |
| | | let circleT = new DC.Circle(centerT, 10) |
| | | circleT.setStyle({ |
| | | material: new DC.CircleWaveMaterialProperty({ |
| | | color: DC.Color.fromRandom(), |
| | | speed: 20, |
| | | count: 5, |
| | | gradient: 0.5 |
| | | }) |
| | | }) |
| | | dynamicVectorLayer.addOverlay(circleT) |
| | | |
| | | const utteranceTow = new SpeechSynthesisUtterance('') |
| | | |
| | | isSpeaking = true |
| | | |
| | | utteranceTow.onend = () => { |
| | | isSpeaking = false |
| | | |
| | | window.$viewer.flyToPosition(new DC.Position(item.wastewaterTreatmentPosition.lng, item.wastewaterTreatmentPosition.lat, 400, 0, -90, 0), () => { |
| | | let centerThree = DC.Position.fromObject(item.wastewaterTreatmentPosition) |
| | | let circleThree = new DC.Circle(centerThree, 30) |
| | | circleThree.setStyle({ |
| | | classificationType: 0, |
| | | material: new DC.CircleSpiralMaterialProperty({ |
| | | color: DC.Color.BLUE.withAlpha(0.2), |
| | | speed: 8.0 |
| | | }) |
| | | }) |
| | | dynamicVectorLayer.addOverlay(circleThree) |
| | | |
| | | let divIconT = new DC.DivIcon( |
| | | new DC.Position(item.wastewaterTreatmentPosition.lng, item.wastewaterTreatmentPosition.lat, 70), |
| | | `<div class="dynamic-map-popup"> |
| | | <div class="content-wrap"> |
| | | <div class="title">绿源进水口</div> |
| | | <div class="content"> |
| | | <p>${item.wastewaterTreatment}</p> |
| | | </div> |
| | | </div> |
| | | <div class="arrow"></div> |
| | | </div>` |
| | | ) |
| | | dynamicHtmlLayer.addOverlay(divIconT) |
| | | |
| | | const utteranceThree = new SpeechSynthesisUtterance(item.wastewaterTreatment) |
| | | |
| | | isSpeaking = true |
| | | |
| | | utteranceThree.onend = () => { |
| | | isSpeaking = false |
| | | } |
| | | |
| | | window.speechSynthesis.speak(utteranceThree) |
| | | }, 3) |
| | | } |
| | | |
| | | window.speechSynthesis.speak(utteranceTow) |
| | | }, 3) |
| | | } |
| | | |
| | | window.speechSynthesis.speak(utterance) |
| | | }, 3) |
| | | } |
| | | qyWsLayer.addOverlay(polyline) |
| | | }) |
| | | |
| | | const clearAll = () => { |
| | | if (isSpeaking) { |
| | | window.speechSynthesis.cancel() |
| | | } |
| | | EventBus.emit('restHandleDelChange', `3-2`) |
| | | EventBus.emit('restHandleDelChange', `3-4`) |
| | | |
| | | dynamicVectorLayer.clear() |
| | | dynamicHtmlLayer.clear() |
| | | modelData.disposeData.forEach(i => { |
| | | 'selectedNum' in i && (i.selectedNum = 0) |
| | | 'selectedID' in i && (i.selectedID = []) |
| | | 'show' in i && (i.show = false) |
| | | }) |
| | | customData.forEach(j => j.disposeData.forEach(i => { |
| | | 'selectedNum' in i && (i.selectedNum = 0) |
| | | 'selectedID' in i && (i.selectedID = []) |
| | | 'show' in i && (i.show = false) |
| | | })) |
| | | |
| | | mapStore.setStartCustomWx(false) |
| | | mapStore.setNeedSelectID([]) |
| | | customVectorLayer.clear() |
| | | dynamicVectorLayer.clear() |
| | | contentData.value = null |
| | | isShowPdContent.value = false |
| | | curContentInd.value = 0 |
| | | } |
| | | |
| | | onUnmounted(() => { |
| | | clearAll() |
| | | let message = null |
| | | |
| | | window.$viewer && window.$viewer.removeLayer(dynamicVectorLayer) |
| | | window.$viewer && window.$viewer.removeLayer(dynamicHtmlLayer) |
| | | const schemeStart = (item, type) => { |
| | | emit('closePanel') |
| | | clearAll() |
| | | |
| | | if (type === 1) { |
| | | contentData.value = modelData |
| | | contentData.value.disposeData[0].show = true |
| | | curContentInd.value = 0 |
| | | isShowPdContent.value = true |
| | | |
| | | let oneCenter = DC.Position.fromObject({ |
| | | lng: contentData.value?.incidentSiteLng, |
| | | lat: contentData.value?.incidentSiteLat, |
| | | alt: contentData.value?.incidentSiteAlt, |
| | | }) |
| | | let oneCircle = new DC.Circle(oneCenter, 20) |
| | | oneCircle.setStyle({ |
| | | material: new DC.CircleWaveMaterialProperty({ |
| | | color: DC.Color.RED.withAlpha(0.9), |
| | | speed: 20, |
| | | count: 5, |
| | | gradient: 0.5 |
| | | }) |
| | | }) |
| | | dynamicVectorLayer.addOverlay(oneCircle) |
| | | } else { |
| | | customData.forEach(item => { |
| | | let iconEl = ` |
| | | <div class="map-icon"> |
| | | <img src="${item.backgroundIcon}"> |
| | | </div> |
| | | ` |
| | | let divIcon = new DC.DivIcon( |
| | | new DC.Position(item.incidentSiteLng, item.incidentSiteLat, item.incidentSiteAlt || 64), |
| | | `<div class="public-map-popup fxy-larger"> |
| | | ${iconEl} |
| | | </div>` |
| | | |
| | | ) |
| | | |
| | | divIcon.attrParams = item |
| | | |
| | | divIcon.on(DC.MouseEventType.CLICK, (e) => { |
| | | contentData.value = e.overlay.attrParams |
| | | contentData.value.disposeData[0].show = true |
| | | curContentInd.value = 0 |
| | | isShowPdContent.value = true |
| | | |
| | | customVectorLayer.clear() |
| | | message?.close() |
| | | message = null |
| | | }) |
| | | |
| | | customVectorLayer.addOverlay(divIcon) |
| | | }) |
| | | |
| | | message = ElNotification({ |
| | | title: '提示', |
| | | showClose: true, |
| | | message: '请选择事发地。', |
| | | type: 'warning', |
| | | duration: 0, |
| | | offset: 100, |
| | | }) |
| | | } |
| | | } |
| | | |
| | | const isShowPdContent = ref(false) |
| | | |
| | | const contentData = ref(null) |
| | | const curContentInd = ref(0) |
| | | |
| | | const isShowNextBtn = computed(() => { |
| | | if (contentData.value === null) return false |
| | | return curContentInd.value < contentData.value?.disposeData.length - 1 |
| | | }) |
| | | |
| | | const isDisabledNextBtn = computed(() => { |
| | | if (contentData.value === null) return false |
| | | const curItem = contentData.value?.disposeData[curContentInd.value] |
| | | return curItem.selectedNum < curItem.isSelectNum |
| | | }) |
| | | |
| | | const isShowSelectBtn = computed(() => { |
| | | if (contentData.value === null) return false |
| | | |
| | | const isShow = contentData.value?.disposeData[curContentInd.value]?.isNeedHandle |
| | | |
| | | mapStore.setStartCustomWx(isShow) |
| | | return isShow |
| | | }) |
| | | |
| | | const isShowSelectText = computed(() => { |
| | | if (contentData.value === null) return '' |
| | | |
| | | return contentData.value?.disposeData[curContentInd.value]?.buttonText |
| | | }) |
| | | |
| | | const cancelText = computed(() => { |
| | | if (contentData.value === null) return '' |
| | | |
| | | if (contentData.value?.disposeData.length - 1 === curContentInd.value) { |
| | | return '结束模拟' |
| | | } else { |
| | | return '取消模拟' |
| | | } |
| | | }) |
| | | |
| | | const wxEntityClick = (e) => { |
| | | if (contentData.value === null) return '' |
| | | |
| | | const clickId = e.overlay.attrParams.id |
| | | |
| | | let flag = contentData.value?.disposeData[curContentInd.value]?.needSelectID.some(i => i == clickId) |
| | | let isSelectedCur = contentData.value?.disposeData[curContentInd.value]?.selectedID.some(i => i == clickId) |
| | | |
| | | if (isSelectedCur) { |
| | | ElMessage({ |
| | | message: '已选择当前设施请重新选择。', |
| | | type: 'warning', |
| | | }) |
| | | |
| | | return |
| | | } |
| | | |
| | | if (flag) { |
| | | contentData.value.disposeData[curContentInd.value].selectedID.push(clickId) |
| | | |
| | | contentData.value.disposeData[curContentInd.value].selectedNum += 1 |
| | | |
| | | if (isDisabledNextBtn.value) { |
| | | ElMessage({ |
| | | message: '选择正确,请继续选择下一个。', |
| | | type: 'success', |
| | | }) |
| | | } else { |
| | | ElMessage({ |
| | | message: '选择正确。', |
| | | type: 'success', |
| | | }) |
| | | } |
| | | } else { |
| | | ElMessage({ |
| | | message: '选择错误,请重新选择。', |
| | | type: 'warning', |
| | | }) |
| | | } |
| | | } |
| | | |
| | | EventBus.on('wxEntityClick', wxEntityClick) |
| | | |
| | | const modelData = { |
| | | title: '典型情景模拟', |
| | | incidentSiteLng: 115.6129562, |
| | | incidentSiteLat: 29.80862305, |
| | | incidentSiteAlt: 8, |
| | | |
| | | disposeData: [ |
| | | { |
| | | label: '事故概况', |
| | | content: '2024年X月X日X时,某企业二甲苯储罐泄漏。', |
| | | isNeedHandle: false, |
| | | show: false, |
| | | }, |
| | | { |
| | | levelTitle: '企业级应急处置', |
| | | content: '企业关闭前置阀门,切断泄漏源;根据泄漏情况,向罐内适量注水,抬高液位,形成水垫层,缓解险情,配合堵漏。', |
| | | isNeedHandle: false, |
| | | show: false, |
| | | }, |
| | | { |
| | | useLayerID: '3-2', |
| | | content: '启动应急池的使用,将泄漏的化学原料引导至应急池中。', |
| | | isNeedHandle: true, |
| | | buttonText: '选择应急池', |
| | | isSelectNum: 1, |
| | | selectedNum: 0, |
| | | needSelectID: ['1875105484274749441'], |
| | | selectedID: [], |
| | | show: false, |
| | | }, |
| | | { |
| | | levelTitle: '企间级应急处置', |
| | | useLayerID: '3-2', |
| | | content: '若该公司应急池容量不足,可通过移动泵或槽罐车将事故废水转移至临近企业事故应急池或污水处理厂事故应急池。', |
| | | isNeedHandle: true, |
| | | buttonText: '选择应急池', |
| | | isSelectNum: 1, |
| | | selectedNum: 0, |
| | | needSelectID: ['1875105469779234817'], |
| | | selectedID: [], |
| | | show: false, |
| | | }, |
| | | { |
| | | levelTitle: '园区级应急处置', |
| | | useLayerID: '3-4', |
| | | content: '立即关闭园区排洪渠下游闸坝,拦截污染水体。', |
| | | isNeedHandle: true, |
| | | buttonText: '选择闸坝', |
| | | isSelectNum: 1, |
| | | selectedNum: 0, |
| | | needSelectID: ['1875105459276697601'], |
| | | selectedID: [], |
| | | show: false, |
| | | }, |
| | | |
| | | { |
| | | useLayerID: '3-4', |
| | | content: '立即关闭园区上游闸坝,拦截清水,减轻应急压力。', |
| | | isNeedHandle: true, |
| | | buttonText: '选择闸坝', |
| | | isSelectNum: 1, |
| | | selectedNum: 0, |
| | | needSelectID: ['1922914799476486145'], |
| | | selectedID: [], |
| | | show: false, |
| | | }, |
| | | |
| | | { |
| | | useLayerID: '3-2', |
| | | content: '开启园区事故应急池将拦截的污水转输至池内。', |
| | | isNeedHandle: true, |
| | | buttonText: '选择应急池', |
| | | isSelectNum: 1, |
| | | selectedNum: 0, |
| | | needSelectID: ['1922914788973948929'], |
| | | selectedID: [], |
| | | show: false, |
| | | }, |
| | | |
| | | { |
| | | content: '将拦截废水从应急池中转移至污水处理厂进行处置,同时在排口下游展开应急监测,确保废水没有进入周边水系。', |
| | | isNeedHandle: false, |
| | | show: false, |
| | | } |
| | | ] |
| | | } |
| | | |
| | | const customData = [ |
| | | { |
| | | title: '自定义情景模拟', |
| | | incidentSiteLng: 115.61393792, |
| | | incidentSiteLat: 29.79786228, |
| | | incidentSiteAlt: 11.98, |
| | | backgroundIcon: VITE_APP_BASE + 'img/mapicon/sfd.png', |
| | | disposeData: [ |
| | | { |
| | | label: '事故概况', |
| | | content: '2025年X月X日X时,危化品运输车辆沿理文路前往危化品停车场时,途经危化品停车场桥梁时,为躲避行人发生侧翻,大量盐酸流入园区南排洪渠中,如不采取措施,将造成污染事故,影响到赤湖水质,进而可能影响到下游长江水质。', |
| | | isNeedHandle: false, |
| | | show: false, |
| | | }, |
| | | { |
| | | content: '就近调集园区企业应急物资库,收集到氢氧化钠1吨(预计用时30分钟)', |
| | | isNeedHandle: false, |
| | | show: false, |
| | | }, |
| | | { |
| | | content: '立即在危化品停车场桥上游使用沙袋或挖机进行临时筑坝,拦截上游来水', |
| | | isNeedHandle: false, |
| | | show: false, |
| | | }, |
| | | { |
| | | content: '在荣联环保桥下游使用沙袋或挖机进行临时筑坝,杜绝污染团流入赤湖,在两道临时闸坝之间形成的蓄污空间内投药处置', |
| | | isNeedHandle: false, |
| | | show: false, |
| | | }, |
| | | |
| | | { |
| | | useLayerID: '3-4', |
| | | content: '处理完成后打开上游临时闸坝释放清水。', |
| | | isNeedHandle: true, |
| | | buttonText: '选择闸坝', |
| | | isSelectNum: 2, |
| | | selectedNum: 0, |
| | | needSelectID: ['1922914802630602754', '1922914799476486145'], |
| | | selectedID: [], |
| | | show: false, |
| | | }, |
| | | |
| | | { |
| | | useLayerID: '3-4', |
| | | content: '稀释后打开下游临时闸坝排放处理后的污水。', |
| | | isNeedHandle: true, |
| | | buttonText: '选择闸坝', |
| | | isSelectNum: 2, |
| | | selectedNum: 0, |
| | | needSelectID: ['1922914776902742017', '1922914784108556289'], |
| | | selectedID: [], |
| | | show: false, |
| | | }, |
| | | |
| | | { |
| | | useLayerID: '3-2', |
| | | content: '开启园区事故应急池将拦截的污水转输至池内。', |
| | | isNeedHandle: true, |
| | | buttonText: '选择应急池', |
| | | isSelectNum: 1, |
| | | selectedNum: 0, |
| | | needSelectID: ['1922914788973948929'], |
| | | selectedID: [], |
| | | show: false, |
| | | }, |
| | | |
| | | { |
| | | content: '将拦截废水从应急池中转移至污水处理厂进行处置,同时在排口下游展开应急监测,确保废水没有进入周边水系。', |
| | | isNeedHandle: false, |
| | | show: false, |
| | | } |
| | | ] |
| | | }, |
| | | |
| | | { |
| | | title: '自定义情景模拟', |
| | | incidentSiteLng: 115.61264639, |
| | | incidentSiteLat: 29.81692806, |
| | | incidentSiteAlt: 19, |
| | | backgroundIcon: VITE_APP_BASE + 'img/mapicon/sfd.png', |
| | | disposeData: [ |
| | | { |
| | | label: '事故概况', |
| | | content: '2025年X月X日X时,某企业物料运输管道发生泄漏。', |
| | | isNeedHandle: false, |
| | | show: false, |
| | | }, |
| | | { |
| | | levelTitle: '企业级应急处置', |
| | | content: '企业通知供应企业停泵,关闭进入公司厂界的盐酸管道阀门;根据泄漏情况,用沙土筑成临时围堰,将盐酸圈在围堰内,然后用氢氧化钠/液碱进行中和处理。用泡沫或其它有效覆盖物进行覆盖,对泄漏区域用雾状水喷淋稀释抑制其挥发,防止泄漏盐酸挥发殃及周围建筑物及人员。', |
| | | isNeedHandle: false, |
| | | show: false, |
| | | }, |
| | | { |
| | | useLayerID: '3-2', |
| | | content: '启动应急池的使用,喷淋水将泄漏的化学原料导入厂区事故应急池。', |
| | | isNeedHandle: true, |
| | | buttonText: '选择应急池', |
| | | isSelectNum: 1, |
| | | selectedNum: 0, |
| | | needSelectID: ['1875105480151748609'], |
| | | selectedID: [], |
| | | show: false, |
| | | }, |
| | | { |
| | | levelTitle: '园区级应急处置', |
| | | useLayerID: '3-4', |
| | | content: '立即关闭园区排洪渠下游闸坝,拦截污染水体。', |
| | | isNeedHandle: true, |
| | | buttonText: '选择闸坝', |
| | | isSelectNum: 1, |
| | | selectedNum: 0, |
| | | needSelectID: ['1875105459276697601'], |
| | | selectedID: [], |
| | | show: false, |
| | | }, |
| | | |
| | | { |
| | | useLayerID: '3-4', |
| | | content: '立即关闭园区上游闸坝,拦截清水,减轻应急压力。', |
| | | isNeedHandle: true, |
| | | buttonText: '选择闸坝', |
| | | isSelectNum: 1, |
| | | selectedNum: 0, |
| | | needSelectID: ['1922914799476486145'], |
| | | selectedID: [], |
| | | show: false, |
| | | }, |
| | | |
| | | { |
| | | useLayerID: '3-2', |
| | | content: '开启园区事故应急池将拦截的污水转输至池内。', |
| | | isNeedHandle: true, |
| | | buttonText: '选择应急池', |
| | | isSelectNum: 1, |
| | | selectedNum: 0, |
| | | needSelectID: ['1922914788973948929'], |
| | | selectedID: [], |
| | | show: false, |
| | | }, |
| | | |
| | | { |
| | | content: '将拦截废水从应急池中转移至污水处理厂进行处置,同时在排口下游展开应急监测,确保废水没有进入周边水系。', |
| | | isNeedHandle: false, |
| | | show: false, |
| | | } |
| | | ] |
| | | } |
| | | ] |
| | | |
| | | let curLoadLayerId = null |
| | | |
| | | const nextStep = () => { |
| | | if (curLoadLayerId) { |
| | | EventBus.emit('restHandleDelChange', curLoadLayerId) |
| | | } |
| | | |
| | | mapStore.setStartCustomWx(false) |
| | | curContentInd.value += 1 |
| | | contentData.value.disposeData[curContentInd.value].show = true |
| | | |
| | | if ('useLayerID' in contentData.value.disposeData[curContentInd.value]) { |
| | | curLoadLayerId = contentData.value.disposeData[curContentInd.value].useLayerID |
| | | EventBus.emit('restHandleCheckChange', contentData.value.disposeData[curContentInd.value].useLayerID) |
| | | } else { |
| | | curLoadLayerId = null |
| | | } |
| | | |
| | | if ('needSelectID' in contentData.value.disposeData[curContentInd.value]) { |
| | | mapStore.setNeedSelectID(contentData.value.disposeData[curContentInd.value].needSelectID) |
| | | } else { |
| | | mapStore.setNeedSelectID([]) |
| | | } |
| | | } |
| | | |
| | | const cancelFlowDispose = () => { |
| | | clearAll() |
| | | } |
| | | |
| | | watch([isShowSelectBtn, isDisabledNextBtn], ([newData, newDisabledData]) => { |
| | | window.$viewer.tooltip.enable = newData && newDisabledData |
| | | }) |
| | | |
| | | const mouseToolTip = (e) => { |
| | | if (isShowSelectBtn.value) { |
| | | window.$viewer.tooltip.showAt(e.windowPosition, isShowSelectText.value) |
| | | } |
| | | } |
| | | |
| | | window.$viewer.on(DC.MouseEventType.MOUSE_MOVE, mouseToolTip) |
| | | |
| | | onBeforeUnmount(() => { |
| | | clearAll() |
| | | window.$viewer.off(DC.MouseEventType.MOUSE_MOVE, mouseToolTip) |
| | | |
| | | |
| | | dynamicVectorLayer && window.$viewer && window.$viewer.removeLayer(dynamicVectorLayer) |
| | | customVectorLayer && window.$viewer && window.$viewer.removeLayer(customVectorLayer) |
| | | qyWsLayer && window.$viewer && window.$viewer.removeLayer(qyWsLayer) |
| | | |
| | | dynamicVectorLayer = null |
| | | customVectorLayer = null |
| | | qyWsLayer = null |
| | | |
| | | EventBus.off('wxEntityClick', wxEntityClick) |
| | | }) |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="panel-container no-select"> |
| | | <el-carousel :autoplay="false" :interval="4000" type="card" height="360px"> |
| | | <el-carousel-item v-for="item, index in panelData.data" :key="index"> |
| | | <template #> |
| | | <div class="content-box flex f-d-c"> |
| | | <div> |
| | | <div v-show="showPanel" class="panel-container no-select"> |
| | | <el-carousel :autoplay="false" :interval="4000" type="card" height="360px"> |
| | | <el-carousel-item v-for="item, index in panelData.data" :key="index"> |
| | | <template #> |
| | | <div class="content-box flex f-d-c"> |
| | | <div class="title"> |
| | | {{ item.title }} |
| | | </div> |
| | | <div class="content h0 flex-1"> |
| | | <div class="bg-box"> |
| | | |
| | | </div> |
| | | |
| | | <div class="describe-box"> |
| | | {{ item.content }} |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="footer-btn" v-show="item.type == 'demo'"> |
| | | <div class="start-btn" @click="schemeStart(item, 1)"> |
| | | 开始模拟 |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="footer-btn" v-show="item.type != 'demo'"> |
| | | <div class="start-btn" @click="schemeStart(item, 2)"> |
| | | 自定义风险源 |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </el-carousel-item> |
| | | </el-carousel> |
| | | </div> |
| | | |
| | | <div v-show="isShowPdContent" class="right-container"> |
| | | <div class="title"> |
| | | {{ item.title }} |
| | | {{ contentData?.title }} |
| | | </div> |
| | | <div class="content h0 flex-1"> |
| | | <div class="bg-box"> |
| | | |
| | | </div> |
| | | |
| | | <div class="describe-box"> |
| | | {{ item.content }} |
| | | </div> |
| | | <div class="content"> |
| | | <div v-show="item.show" v-for="item, ind in contentData?.disposeData" :key="ind"> |
| | | <div v-if="item.levelTitle" class="level-title"> |
| | | {{ item.levelTitle }} |
| | | </div> |
| | | <div :class="{ 'text-orange': ind === curContentInd }"> |
| | | {{ item.content }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="footer-btn"> |
| | | <div class="start-btn" @click="schemeStart(item)"> |
| | | 开始模拟 |
| | | </div> |
| | | <el-button @click="cancelFlowDispose">{{ cancelText }}</el-button> |
| | | <el-button :disabled="isDisabledNextBtn" type="primary" @click="nextStep" |
| | | v-show="isShowNextBtn">下一步</el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </el-carousel-item> |
| | | </el-carousel> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped lang="scss"> |
| | | .panel-container { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | margin: auto; |
| | | width: 960px; |
| | | height: 440px; |
| | | pointer-events: all; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | margin: auto; |
| | | width: 960px; |
| | | height: 440px; |
| | | pointer-events: all; |
| | | |
| | | ::v-deep(.el-carousel) { |
| | | .el-carousel__item { |
| | | background: #2E4274; |
| | | ::v-deep(.el-carousel) { |
| | | .el-carousel__item { |
| | | background: #2E4274; |
| | | |
| | | .content-box { |
| | | padding: 10px 0; |
| | | width: 100%; |
| | | height: 100%; |
| | | color: #fff; |
| | | .content-box { |
| | | padding: 10px 0; |
| | | width: 100%; |
| | | height: 100%; |
| | | color: #fff; |
| | | |
| | | .title { |
| | | padding-left: 40px; |
| | | height: 80px; |
| | | line-height: 80px; |
| | | font-size: 44px; |
| | | font-weight: bold; |
| | | font-style: initial; |
| | | background: #3a7bd5; |
| | | /* fallback for old browsers */ |
| | | background: -webkit-linear-gradient(to right, #3a7bd5, rgba(255, 255, 255, 0.1)); |
| | | /* Chrome 10-25, Safari 5.1-6 */ |
| | | background: linear-gradient(to right, #3a7bd5, rgba(255, 255, 255, 0.1)); |
| | | /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ |
| | | .title { |
| | | padding-left: 40px; |
| | | height: 80px; |
| | | line-height: 80px; |
| | | font-size: 44px; |
| | | font-weight: bold; |
| | | font-style: initial; |
| | | background: #3a7bd5; |
| | | /* fallback for old browsers */ |
| | | background: -webkit-linear-gradient(to right, #3a7bd5, rgba(255, 255, 255, 0.1)); |
| | | /* Chrome 10-25, Safari 5.1-6 */ |
| | | background: linear-gradient(to right, #3a7bd5, rgba(255, 255, 255, 0.1)); |
| | | /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ |
| | | } |
| | | |
| | | .content { |
| | | margin-top: 20px; |
| | | padding: 0 20px; |
| | | |
| | | .describe-box { |
| | | line-height: 36px; |
| | | } |
| | | } |
| | | |
| | | .footer-btn { |
| | | margin-bottom: 40px; |
| | | height: 40px; |
| | | line-height: 40px; |
| | | text-align: center; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .content { |
| | | margin-top: 20px; |
| | | padding: 0 20px; |
| | | |
| | | .describe-box { |
| | | line-height: 36px; |
| | | } |
| | | } |
| | | |
| | | .footer-btn { |
| | | margin-bottom: 40px; |
| | | height: 40px; |
| | | line-height: 40px; |
| | | text-align: center; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-select { |
| | | user-select: none; |
| | | /* Standard syntax */ |
| | | -webkit-user-select: none; |
| | | /* Safari/Chrome */ |
| | | -moz-user-select: none; |
| | | /* Firefox */ |
| | | -ms-user-select: none; |
| | | /* Internet Explorer/Edge */ |
| | | user-select: none; |
| | | /* Standard syntax */ |
| | | -webkit-user-select: none; |
| | | /* Safari/Chrome */ |
| | | -moz-user-select: none; |
| | | /* Firefox */ |
| | | -ms-user-select: none; |
| | | /* Internet Explorer/Edge */ |
| | | } |
| | | |
| | | .start-btn, |
| | | .start-btn:focus, |
| | | .start-btn:hover { |
| | | display: inline-block; |
| | | position: relative; |
| | | min-width: 96px; |
| | | border: none; |
| | | border-radius: 8px; |
| | | background: #491cc5; |
| | | /* fallback for old browsers */ |
| | | background: -webkit-linear-gradient(to bottom, #491cc5, #5f80f5); |
| | | /* Chrome 10-25, Safari 5.1-6 */ |
| | | background: linear-gradient(to bottom, #491cc5, #5f80f5); |
| | | /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ |
| | | padding: 0 10px; |
| | | display: inline-block; |
| | | position: relative; |
| | | min-width: 96px; |
| | | border: none; |
| | | border-radius: 8px; |
| | | background: #491cc5; |
| | | /* fallback for old browsers */ |
| | | background: -webkit-linear-gradient(to bottom, #491cc5, #5f80f5); |
| | | /* Chrome 10-25, Safari 5.1-6 */ |
| | | background: linear-gradient(to bottom, #491cc5, #5f80f5); |
| | | /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ |
| | | |
| | | color: #fff; |
| | | font-size: 1rem; |
| | | font-weight: bold; |
| | | text-align: center; |
| | | text-decoration: none; |
| | | text-transform: uppercase; |
| | | box-shadow: 0 0 10px rgba(255, 255, 255, 0.5); |
| | | transition: transform ease-in 0.1s, box-shadow ease-in 0.25s; |
| | | transform: skewX(8deg); |
| | | color: #fff; |
| | | font-size: 1rem; |
| | | font-weight: bold; |
| | | text-align: center; |
| | | text-decoration: none; |
| | | text-transform: uppercase; |
| | | box-shadow: 0 0 10px rgba(255, 255, 255, 0.5); |
| | | transition: transform ease-in 0.1s, box-shadow ease-in 0.25s; |
| | | transform: skewX(8deg); |
| | | } |
| | | |
| | | .start-btn:before, |
| | | .start-btn:after { |
| | | content: ""; |
| | | display: block; |
| | | position: absolute; |
| | | width: 140%; |
| | | height: 100%; |
| | | left: -20%; |
| | | z-index: -1000; |
| | | transition: all ease-in-out 0.5s; |
| | | background-repeat: no-repeat; |
| | | content: ""; |
| | | display: block; |
| | | position: absolute; |
| | | width: 140%; |
| | | height: 100%; |
| | | left: -20%; |
| | | z-index: -1000; |
| | | transition: all ease-in-out 0.5s; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .start-btn:before { |
| | | display: none; |
| | | top: -75%; |
| | | background-image: radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, transparent 20%, #3E56B2 20%, transparent 30%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, transparent 10%, #3E56B2 15%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%); |
| | | background-size: 10% 10%, 20% 20%, 15% 15%, 20% 20%, 18% 18%, 10% 10%, 15% 15%, 10% 10%, 18% 18%; |
| | | display: none; |
| | | top: -75%; |
| | | background-image: radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, transparent 20%, #3E56B2 20%, transparent 30%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, transparent 10%, #3E56B2 15%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%); |
| | | background-size: 10% 10%, 20% 20%, 15% 15%, 20% 20%, 18% 18%, 10% 10%, 15% 15%, 10% 10%, 18% 18%; |
| | | } |
| | | |
| | | .start-btn:after { |
| | | display: none; |
| | | bottom: -75%; |
| | | background-image: radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, transparent 10%, #3E56B2 15%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%); |
| | | background-size: 15% 15%, 20% 20%, 18% 18%, 20% 20%, 15% 15%, 10% 10%, 20% 20%; |
| | | display: none; |
| | | bottom: -75%; |
| | | background-image: radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, transparent 10%, #3E56B2 15%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%), radial-gradient(circle, #3E56B2 20%, transparent 20%); |
| | | background-size: 15% 15%, 20% 20%, 18% 18%, 20% 20%, 15% 15%, 10% 10%, 20% 20%; |
| | | } |
| | | |
| | | .start-btn:active { |
| | | transform: scale(0.9); |
| | | background-color: #5e7ae9; |
| | | box-shadow: 0 2px 25px rgba(94, 122, 233, .2); |
| | | transform: scale(0.9); |
| | | background-color: #5e7ae9; |
| | | box-shadow: 0 2px 25px rgba(94, 122, 233, .2); |
| | | } |
| | | |
| | | .start-btn:active:before { |
| | | display: block; |
| | | animation: anim-top ease-in-out 0.75s forwards; |
| | | display: block; |
| | | animation: anim-top ease-in-out 0.75s forwards; |
| | | } |
| | | |
| | | .start-btn:active:after { |
| | | display: block; |
| | | animation: anim-bottom ease-in-out 0.75s forwards; |
| | | display: block; |
| | | animation: anim-bottom ease-in-out 0.75s forwards; |
| | | } |
| | | |
| | | @keyframes anim-top { |
| | | 0% { |
| | | background-position: 5% 90%, 10% 90%, 10% 90%, 15% 90%, 25% 90%, 25% 90%, 40% 90%, 55% 90%, 70% 90%; |
| | | } |
| | | 0% { |
| | | background-position: 5% 90%, 10% 90%, 10% 90%, 15% 90%, 25% 90%, 25% 90%, 40% 90%, 55% 90%, 70% 90%; |
| | | } |
| | | |
| | | 50% { |
| | | background-position: 0% 80%, 0% 20%, 10% 40%, 20% 0%, 30% 30%, 22% 50%, 50% 50%, 65% 20%, 90% 30%; |
| | | } |
| | | 50% { |
| | | background-position: 0% 80%, 0% 20%, 10% 40%, 20% 0%, 30% 30%, 22% 50%, 50% 50%, 65% 20%, 90% 30%; |
| | | } |
| | | |
| | | 100% { |
| | | background-position: 0% 70%, 0% 10%, 10% 30%, 20% -10%, 30% 20%, 22% 40%, 50% 40%, 65% 10%, 90% 20%; |
| | | background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%; |
| | | } |
| | | 100% { |
| | | background-position: 0% 70%, 0% 10%, 10% 30%, 20% -10%, 30% 20%, 22% 40%, 50% 40%, 65% 10%, 90% 20%; |
| | | background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%; |
| | | } |
| | | } |
| | | |
| | | @keyframes anim-bottom { |
| | | 0% { |
| | | background-position: 10% -10%, 30% 10%, 55% -10%, 70% -10%, 85% -10%, 70% -10%, 70% 0%; |
| | | } |
| | | 0% { |
| | | background-position: 10% -10%, 30% 10%, 55% -10%, 70% -10%, 85% -10%, 70% -10%, 70% 0%; |
| | | } |
| | | |
| | | 50% { |
| | | background-position: 0% 80%, 20% 80%, 45% 60%, 60% 100%, 75% 70%, 95% 60%, 105% 0%; |
| | | } |
| | | 50% { |
| | | background-position: 0% 80%, 20% 80%, 45% 60%, 60% 100%, 75% 70%, 95% 60%, 105% 0%; |
| | | } |
| | | |
| | | 100% { |
| | | background-position: 0% 90%, 20% 90%, 45% 70%, 60% 110%, 75% 80%, 95% 70%, 110% 10%; |
| | | background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%; |
| | | } |
| | | 100% { |
| | | background-position: 0% 90%, 20% 90%, 45% 70%, 60% 110%, 75% 80%, 95% 70%, 110% 10%; |
| | | background-size: 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%, 0% 0%; |
| | | } |
| | | } |
| | | |
| | | .right-container { |
| | | padding: 10px; |
| | | display: flex; |
| | | color: #fff; |
| | | |
| | | user-select: none; |
| | | /* 禁止文本选择 */ |
| | | -webkit-user-select: none; |
| | | /* Safari 兼容 */ |
| | | -moz-user-select: none; |
| | | /* Firefox 兼容 */ |
| | | -ms-user-select: none; |
| | | /* IE10+/Edge 兼容 */ |
| | | |
| | | .title { |
| | | line-height: 40px; |
| | | } |
| | | |
| | | .content { |
| | | margin: 10px 0; |
| | | flex: 1; |
| | | |
| | | &>div { |
| | | text-indent: 2em; |
| | | line-height: 32px; |
| | | } |
| | | |
| | | .level-title { |
| | | text-indent: 0; |
| | | } |
| | | |
| | | .text-orange { |
| | | color: orange; |
| | | } |
| | | } |
| | | |
| | | .footer-btn { |
| | | display: flex; |
| | | flex-direction: row; |
| | | justify-content: center; |
| | | |
| | | ::v-deep(.el-button) { |
| | | width: 96px; |
| | | } |
| | | } |
| | | } |
| | | </style> |