Merge remote-tracking branch 'origin/master'
17 files modified
3 files added
| | |
| | | VITE_APP_AIRLINE_URL = https://wrj.shuixiongit.com/minio/cloud-bucket |
| | | |
| | | #系统运维 |
| | | VITE_APP_ADMIN_URL = 'https://wrj.shuixiongit.com/manage' |
| | | VITE_APP_ADMIN_URL = 'https://wrj.shuixiongit.com/manage' |
| | |
| | | # ws地址 |
| | | VITE_APP_WS_API_URL = wss://wrj.shuixiongit.com/drone-wss/api/v1/ws |
| | | # 管理后台地址 |
| | | VITE_APP_ADMIN_URL = ' https://aisky.org.cn/manage' |
| | | VITE_APP_ADMIN_URL = 'https://aisky.org.cn/manage' |
| | |
| | | VITE_APP_WS_API_URL = wss://wrj.shuixiongit.com/drone-wss/api/v1/ws |
| | | # 航线文件地址 |
| | | VITE_APP_AIRLINE_URL = https://wrj.shuixiongit.com/minio/cloud-bucket |
| | | |
| | | #系统运维 |
| | | VITE_APP_ADMIN_URL = 'https://wrj.shuixiongit.com/manage' |
| | |
| | | </div> |
| | | </template> |
| | | <script setup> |
| | | import { ElImage } from 'element-plus' |
| | | import { ElImage, ElIcon } from 'element-plus' |
| | | |
| | | // src="@/assets/images/home/useEventOperate/eventErr.png" |
| | | import { Close } from '@element-plus/icons-vue' |
| | |
| | | loading.value = true |
| | | const res = await getEventDetails({ id: props.data.eventId }) |
| | | info.value = res.data.data |
| | | |
| | | info.value.url = import.meta.env.VITE_APP_AIRLINE_URL + info.value.url |
| | | info.value.url = disposeUrl(info.value) |
| | | loading.value = false |
| | | }) |
| | | |
| | | const disposeUrl = ({ url, media_type }) => { |
| | | if (media_type == 'PHOTO_TYPE') { |
| | | const parts = url.split('.') |
| | | parts[parts.length - 2] += '_small' // 在最后一个点前插入 _small |
| | | |
| | | return import.meta.env.VITE_APP_AIRLINE_URL + parts.join('.') |
| | | } |
| | | |
| | | return import.meta.env.VITE_APP_AIRLINE_URL + url |
| | | } |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | |
| | | * @Author: shuishen 1109946754@qq.com |
| | | * @Date: 2025-04-15 22:41:40 |
| | | * @LastEditors: shuishen 1109946754@qq.com |
| | | * @LastEditTime: 2025-04-16 19:02:39 |
| | | * @LastEditTime: 2025-04-17 19:43:36 |
| | | * @FilePath: \command-center-dashboard\src\hooks\components\useMapHandlerClick.js |
| | | * @Description: |
| | | * |
| | | * Copyright (c) 2025 by shuishen, All Rights Reserved. |
| | | */ |
| | | import * as Cesium from 'cesium' |
| | | import { render } from 'vue' |
| | | import { useStore } from 'vuex' |
| | | |
| | | import DevicePopUpBox from '@/hooks/components/DevicePopUpBox.vue' |
| | | import EventPopUpBox from '@/hooks/components/EventPopUpBox.vue' |
| | | |
| | | /** |
| | | * |
| | | * @param {Object} options - 配置选项 |
| | | */ |
| | | export function useMapHandlerClick (viewer, options = { |
| | | type: 'single-drone-event', |
| | | customDom: EventPopUpBox |
| | | }) { |
| | | const { type } = options |
| | | export function useMapHandlerClick (options = {}) { |
| | | const { |
| | | popupType = 'event-popup', |
| | | eventType = 'single-drone-event', |
| | | styleTransform = 'translate(-50%,-110%)', |
| | | getViewer |
| | | } = options |
| | | |
| | | const currentClickEntity = ref(null) |
| | | const store = useStore() |
| | | |
| | | const popupData = { |
| | | 'event-popup': EventPopUpBox, |
| | | 'device-popup': DevicePopUpBox |
| | | } |
| | | |
| | | const MapPopUpBox = popupData[popupType] |
| | | |
| | | let viewer = null |
| | | let handler = null |
| | | let currentClickEntity = null |
| | | |
| | | // 查找特定类型的实体 |
| | | const findEntityByType = (entities, type) => { |
| | | return entities.find(entity => |
| | | entity?.properties?.customData?._value?.data?.type === type |
| | | ) |
| | | let types = [] |
| | | |
| | | Array.isArray(type) ? types = type : types = [type] |
| | | |
| | | return types.reduce((pre, curType) => { |
| | | let entity = entities.find(entity => entity?.properties?.customData?._value?.data?.type === curType) |
| | | |
| | | return entity ? (pre.push({ |
| | | type: curType, |
| | | entity |
| | | }), pre) : pre |
| | | }, []) |
| | | } |
| | | |
| | | const publicEvent = (entity) => { |
| | | viewer.scene.postRender.addEventListener(labelBoxRender) |
| | | } |
| | | |
| | | const typeEvent = { |
| | | 'deviceAggregation': publicEvent, |
| | | 'single-drone-event': publicEvent, |
| | | 'event': publicEvent, |
| | | 'device': (entity) => { |
| | | const device = entity.properties.customData._value.data |
| | | store.commit('setSingleUavHome', device) |
| | | } |
| | | } |
| | | |
| | | // 左键单机事件 |
| | |
| | | let clickedEntities = viewer?.scene.drillPick(click.position).map(item => item.id) |
| | | if (!clickedEntities.length) return |
| | | |
| | | currentClickEntity.value = findEntityByType(clickedEntities, type) |
| | | let curClick = findEntityByType(clickedEntities, eventType) |
| | | |
| | | removeLabel() |
| | | |
| | | if (currentClickEntity.value) { |
| | | viewer?.scene.postRender.addEventListener(labelBoxRender) |
| | | if (curClick.length > 0 && typeEvent[curClick[0].type]) { |
| | | currentClickEntity = curClick[0].entity |
| | | |
| | | typeEvent[curClick[0].type](currentClickEntity) |
| | | } |
| | | } |
| | | |
| | | // 事件初始化 |
| | | const handlerInit = () => { |
| | | !viewer && (viewer = getViewer()) |
| | | |
| | | if (handler) return |
| | | |
| | | handler = new Cesium.ScreenSpaceEventHandler(viewer?.scene.canvas) |
| | |
| | | |
| | | // 获取弹框box |
| | | const getLabelDom = data => { |
| | | const vNode = h(customDom, { data, removeLabel }) |
| | | const vNode = h(MapPopUpBox, { data, removeLabel }) |
| | | const tooltipContainer = document.createElement('div') |
| | | tooltipContainer.id = 'mapPopUpBox' |
| | | tooltipContainer.style.position = 'absolute' |
| | |
| | | |
| | | // 弹框位置刷新 |
| | | const labelBoxRender = () => { |
| | | if (!currentEntity) return |
| | | if (!currentClickEntity) return |
| | | let dom = document.querySelector('#mapPopUpBox') |
| | | if (!dom) { |
| | | dom = getLabelDom(currentEntity.properties.customData._value.data) |
| | | dom = getLabelDom(currentClickEntity.properties.customData._value.data) |
| | | } |
| | | const screenPosition = viewer?.scene.cartesianToCanvasCoordinates(currentEntity?.position?._value) |
| | | const screenPosition = viewer?.scene.cartesianToCanvasCoordinates(currentClickEntity?.position?._value) |
| | | if (screenPosition) { |
| | | dom.style.left = `${screenPosition.x}px` |
| | | dom.style.top = `${screenPosition.y}px` |
| | |
| | | removeDom() |
| | | } |
| | | |
| | | // 自动清理 |
| | | onUnmounted(() => { |
| | | const removeAll = () => { |
| | | removeHandler() |
| | | removeLabel() |
| | | } |
| | | |
| | | // 自动清理 |
| | | onUnmounted(() => { |
| | | removeAll() |
| | | }) |
| | | |
| | | return { |
| | | currentClickEntity, |
| | | handlerInit, |
| | | removeHandler |
| | | removeAll, |
| | | removeLabel |
| | | } |
| | | } |
| | |
| | | import eventAggregationImg from '@/assets/images/home/useUavHome/eventAggregationImg.png' |
| | | import uavImg from '@/assets/images/home/useUavHome/uavImg.png' |
| | | |
| | | // 事件图标 |
| | | import eventSingle from '@/assets/images/home/useEventOperate/eventSingle.png' |
| | | import { getEventImage } from '@/utils/stateToImageMap/event' |
| | | import { getDroneStatusImage } from '@/utils/stateToImageMap/drone' |
| | | |
| | | import eventPending from '@/assets/images/home/useEventOperate/eventPending.png' // 待处理 0 |
| | | import eventWaitAudit from '@/assets/images/home/useEventOperate/eventWaitAudit.png' // 待审核 2 |
| | | import eventProcessing from '@/assets/images/home/useEventOperate/eventProcessing.png' // 处理中 3 |
| | | import eventCompleted from '@/assets/images/home/useEventOperate/eventCompleted.png' // 已完成 4 |
| | | import eventClosed from '@/assets/images/home/useEventOperate/eventClosed.png' // 已完结 5 |
| | | |
| | | import DevicePopUpBox from '@/hooks/components/DevicePopUpBox.vue' |
| | | import EventPopUpBox from '@/hooks/components/EventPopUpBox.vue' |
| | | |
| | | |
| | | // 机巢图标 |
| | | import endingImg from '@/assets/images/aiNowFly/ending.png' |
| | | import endingHighImg from '@/assets/images/aiNowFly/ending-high.png' |
| | | |
| | | import { render } from 'vue' |
| | | import { useStore } from 'vuex' |
| | | import { getCenterPoint } from '@/utils/cesium/mapUtil' |
| | | import cesiumOperation from '@/utils/cesium-tsa' |
| | | import { getDeviceRegion, getDeviceRegionCount, getEventDetails, getMapEvents } from '@/api/home/aggregation' |
| | | import { PolyGradientMaterial } from '@/utils/cesium/Material' |
| | | import { start } from 'nprogress' |
| | | |
| | | // hook |
| | | import { useMapHandlerClick } from '@/hooks/components/useMapHandlerClick' |
| | | |
| | | /** |
| | | * 机巢聚合功能 |
| | | */ |
| | |
| | | const { flyTo } = cesiumOperation() |
| | | |
| | | const mergeImg = type === 'device' ? aggregationImg : eventAggregationImg |
| | | const MapPopUpBox = type === 'device' ? DevicePopUpBox : EventPopUpBox |
| | | const styleTransform = type === 'device' ? 'translateY(-50%)' : 'translate(-50%,-110%)' |
| | | const eventType = type === 'device' ? ['deviceAggregation', 'device'] : 'event' |
| | | |
| | | const { |
| | | handlerInit: mapHandlerInit, |
| | | removeAll: removeMapHandlerAll, |
| | | removeLabel: removeMapLabel |
| | | } = useMapHandlerClick({ |
| | | popupType: `${type}-popup`, |
| | | eventType: eventType, |
| | | styleTransform, |
| | | getViewer () { |
| | | return viewer |
| | | } |
| | | }) |
| | | |
| | | let scalingJudgment = [ |
| | | { name: '县', splashedList: [], gJson: null, show: false, outline: {}, value: [0, 48651], height: 31753 }, |
| | |
| | | ] |
| | | let viewer = null |
| | | let active = null |
| | | let handler = null |
| | | let currentEntity = null |
| | | |
| | | const store = useStore() |
| | | const userAreaCode = computed(() => store.state.user.userInfo.detail.areaCode) |
| | |
| | | if (active === item.name) return |
| | | active = item.name |
| | | removeEntities() |
| | | removeLabel() |
| | | removeMapLabel() |
| | | renderOutline(item) |
| | | if (!item.gJson && !item.splashedList?.length) return |
| | | item.gJson ? aggregation(item) : splashed(item) |
| | |
| | | |
| | | needFly = true |
| | | if (!viewer) return |
| | | handlerInit() |
| | | mapHandlerInit() |
| | | |
| | | viewer.scene.postRender.removeEventListener(determineScaling) |
| | | |
| | |
| | | //散点机巢 |
| | | function splashed (row) { |
| | | row.splashedList.forEach((item, index) => { |
| | | const eventImage = { |
| | | 0: eventPending, |
| | | 2: eventWaitAudit, |
| | | 3: eventProcessing, |
| | | 4: eventCompleted, |
| | | 5: eventClosed |
| | | } |
| | | |
| | | const image = type === 'device' ? (item.status === "OFFLINE" ? endingHighImg : endingImg) : eventImage[item.status] || eventSingle |
| | | const image = type === 'device' ? getDroneStatusImage(item.status) : getEventImage(item.status) |
| | | |
| | | viewer.entities.add({ |
| | | id: `aggregation-splashed-${index}`, |
| | |
| | | }) |
| | | } |
| | | |
| | | // 获取弹框box |
| | | const getLabelDom = data => { |
| | | const vNode = h(MapPopUpBox, { data, removeLabel }) |
| | | const tooltipContainer = document.createElement('div') |
| | | tooltipContainer.id = 'mapPopUpBox' |
| | | tooltipContainer.style.position = 'absolute' |
| | | tooltipContainer.style.transform = styleTransform |
| | | tooltipContainer.style.pointerEvents = 'none' |
| | | document.querySelector('.page-index').append(tooltipContainer) |
| | | render(vNode, tooltipContainer) |
| | | return tooltipContainer |
| | | } |
| | | |
| | | // 弹框位置刷新 |
| | | const labelBoxRender = () => { |
| | | if (!currentEntity) return |
| | | let dom = document.querySelector('#mapPopUpBox') |
| | | if (!dom) { |
| | | dom = getLabelDom(currentEntity.properties.customData._value.data) |
| | | } |
| | | const screenPosition = viewer.scene.cartesianToCanvasCoordinates(currentEntity?.position?._value) |
| | | if (screenPosition) { |
| | | dom.style.left = `${screenPosition.x}px` |
| | | dom.style.top = `${screenPosition.y}px` |
| | | dom.style.display = 'block' |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据条件获取项 |
| | |
| | | */ |
| | | const findTypeItem = (arr, condition) => { |
| | | return arr.find(item => condition(item)) |
| | | } |
| | | |
| | | // 左键单机事件 |
| | | const singleMachineEvent = async click => { |
| | | let clickTargets = viewer.scene.drillPick(click.position).map(item => item.id) |
| | | if (!clickTargets.length) return |
| | | |
| | | let deviceAggregationFind = findTypeItem( |
| | | clickTargets, |
| | | item => item?.properties?.customData?._value?.data?.type === 'deviceAggregation' |
| | | ) |
| | | let deviceFind = findTypeItem(clickTargets, item => item?.properties?.customData?._value?.data?.type === 'device') |
| | | // "event" |
| | | let eventFind = findTypeItem(clickTargets, item => item?.properties?.customData?._value?.data?.type === 'event') |
| | | // let eventFind = findTypeItem(clickTargets, (item) => item?.properties?.customData?._value?.data?.type === 'eventAggregation') |
| | | currentEntity = deviceAggregationFind || deviceFind || eventFind |
| | | |
| | | if (!currentEntity) return |
| | | if (!currentEntity?.position?._value) return |
| | | // 一定要移除 |
| | | removeLabel() |
| | | if (deviceAggregationFind || eventFind) { |
| | | viewer.scene.postRender.addEventListener(labelBoxRender) |
| | | } |
| | | if (deviceFind) { |
| | | const device = deviceFind.properties.customData._value.data |
| | | store.commit('setSingleUavHome', device) |
| | | } |
| | | } |
| | | |
| | | const removeDom = () => { |
| | | const dom = document.querySelector('#mapPopUpBox') |
| | | if (dom && dom.parentNode) { |
| | | dom.parentNode.removeChild(dom) |
| | | } |
| | | } |
| | | |
| | | // 移除 点 和 gjson 实体 |
| | |
| | | item.includes('aggregation-') && viewer.entities.removeById(item) |
| | | }) |
| | | } |
| | | // 移除弹框标签 |
| | | const removeLabel = () => { |
| | | viewer?.scene.postRender.removeEventListener(labelBoxRender) |
| | | removeDom() |
| | | } |
| | | |
| | | |
| | | // 移除所有监听事件,变量置空 |
| | | const removeAll = () => { |
| | |
| | | const clearMapEntity = () => { |
| | | if (!viewer) return |
| | | removeEntities() |
| | | removeLabel() |
| | | removeMapHandlerAll() |
| | | |
| | | // viewer.camera.moveEnd.removeEventListener(determineScaling); |
| | | viewer.scene.postRender.removeEventListener(determineScaling) |
| | | handler?.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK) |
| | | handler?.destroy() |
| | | |
| | | active = null |
| | | handler = null |
| | | currentEntity = null |
| | | } |
| | | |
| | | const init = () => { |
| | |
| | | viewer.scene.postRender.addEventListener(determineScaling) |
| | | }) |
| | | |
| | | handlerInit() |
| | | } |
| | | |
| | | const handlerInit = () => { |
| | | if (handler) return |
| | | |
| | | handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas) |
| | | handler.setInputAction(singleMachineEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK) |
| | | mapHandlerInit() |
| | | } |
| | | |
| | | onBeforeUnmount(() => { }) |
| | |
| | | * @Author: shuishen 1109946754@qq.com |
| | | * @Date: 2025-04-15 22:41:40 |
| | | * @LastEditors: shuishen 1109946754@qq.com |
| | | * @LastEditTime: 2025-04-17 15:05:32 |
| | | * @LastEditTime: 2025-04-17 20:24:19 |
| | | * @FilePath: \command-center-dashboard\src\hooks\useSingleDroneMap\useSingleDroneMap.js |
| | | * @Description: |
| | | * |
| | | * Copyright (c) 2025 by shuishen, All Rights Reserved. |
| | | */ |
| | | import * as Cesium from 'cesium' |
| | | import { render } from 'vue' |
| | | import endingImg from '@/assets/images/aiNowFly/ending.png' |
| | | import endingHighImg from '@/assets/images/aiNowFly/ending-high.png' |
| | | import EventPopUpBox from '@/hooks/components/EventPopUpBox.vue' |
| | | |
| | | import eventSingle from '@/assets/images/home/useEventOperate/eventSingle.png' |
| | | import { getEventImage } from '@/utils/stateToImageMap/event' |
| | | import { getDroneFlagImage } from '@/utils/stateToImageMap/drone' |
| | | |
| | | import eventPending from '@/assets/images/home/useEventOperate/eventPending.png' // 待处理 0 |
| | | import eventWaitAudit from '@/assets/images/home/useEventOperate/eventWaitAudit.png' // 待审核 2 |
| | | import eventProcessing from '@/assets/images/home/useEventOperate/eventProcessing.png' // 处理中 3 |
| | | import eventCompleted from '@/assets/images/home/useEventOperate/eventCompleted.png' // 已完成 4 |
| | | import eventClosed from '@/assets/images/home/useEventOperate/eventClosed.png' // 已完结 5 |
| | | import { useMapHandlerClick } from '@/hooks/components/useMapHandlerClick' |
| | | |
| | | /** |
| | | * |
| | |
| | | eventApi: null, |
| | | eventApiParams: {} |
| | | }) { |
| | | const styleTransform = 'translate(-50%,-110%)' |
| | | |
| | | const { eventPositions, eventApi, eventApiParams } = options |
| | | const eventImage = { |
| | | 0: eventPending, |
| | | 2: eventWaitAudit, |
| | | 3: eventProcessing, |
| | | 4: eventCompleted, |
| | | 5: eventClosed |
| | | } |
| | | |
| | | let viewer = null |
| | | let handler = null |
| | | let currentEntity = null |
| | | |
| | | const { |
| | | handlerInit: mapHandlerInit, |
| | | removeAll: removeMapHandlerAll |
| | | } = useMapHandlerClick({ |
| | | getViewer () { |
| | | return viewer |
| | | } |
| | | }) |
| | | |
| | | // 初始化机场位置 |
| | | const initDroneEntity = (dronePosition) => { |
| | |
| | | |
| | | if (!lng || !lat) return |
| | | |
| | | const markerImg = status ? endingHighImg : endingImg |
| | | const markerImg = getDroneFlagImage(status) |
| | | const position = Cesium.Cartesian3.fromDegrees(+lng, +lat, 0) |
| | | |
| | | const droneEntity = viewer?.entities.add({ |
| | |
| | | eventData.length && eventData.forEach((item, index) => { |
| | | const { longitude, latitude, status, id } = item |
| | | |
| | | const curImg = eventImage[status] || eventSingle |
| | | const curImg = getEventImage(status) |
| | | |
| | | const position = Cesium.Cartesian3.fromDegrees(+longitude, +latitude, 0) |
| | | |
| | |
| | | customData: { |
| | | data: { |
| | | ...item, |
| | | eventId: item.id, |
| | | eventId: id, |
| | | type: 'single-drone-event' |
| | | } |
| | | } |
| | |
| | | }) |
| | | } |
| | | |
| | | // 查找特定类型的实体 |
| | | const findEntityByType = (entities, type) => { |
| | | return entities.find(entity => |
| | | entity?.properties?.customData?._value?.data?.type === type |
| | | ) |
| | | } |
| | | |
| | | // 左键单机事件 |
| | | const singleMachineEvent = async click => { |
| | | currentEntity = null |
| | | |
| | | let clickedEntities = viewer?.scene.drillPick(click.position).map(item => item.id) |
| | | |
| | | if (!clickedEntities?.length) return |
| | | |
| | | currentEntity = findEntityByType(clickedEntities, 'single-drone-event') |
| | | |
| | | removeLabel() |
| | | |
| | | if (currentEntity) { |
| | | viewer?.scene.postRender.addEventListener(labelBoxRender) |
| | | } |
| | | } |
| | | |
| | | // 事件初始化 |
| | | const handlerInit = () => { |
| | | if (handler) return |
| | | |
| | | handler = new Cesium.ScreenSpaceEventHandler(viewer?.scene.canvas) |
| | | handler.setInputAction(singleMachineEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK) |
| | | } |
| | | |
| | | // 事件移除 |
| | | const removeHandler = () => { |
| | | handler?.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK) |
| | | handler?.destroy() |
| | | handler = null |
| | | } |
| | | |
| | | // 获取弹框box |
| | | const getLabelDom = data => { |
| | | const vNode = h(EventPopUpBox, { data, removeLabel }) |
| | | const tooltipContainer = document.createElement('div') |
| | | tooltipContainer.id = 'mapPopUpBox' |
| | | tooltipContainer.style.position = 'absolute' |
| | | tooltipContainer.style.transform = styleTransform |
| | | tooltipContainer.style.pointerEvents = 'none' |
| | | document.querySelector('.page-index').append(tooltipContainer) |
| | | render(vNode, tooltipContainer) |
| | | return tooltipContainer |
| | | } |
| | | |
| | | // 弹框位置刷新 |
| | | const labelBoxRender = () => { |
| | | if (!currentEntity) return |
| | | let dom = document.querySelector('#mapPopUpBox') |
| | | |
| | | if (!dom) { |
| | | dom = getLabelDom(currentEntity.properties.customData._value.data) |
| | | } |
| | | const screenPosition = viewer?.scene.cartesianToCanvasCoordinates(currentEntity?.position?._value) |
| | | if (screenPosition) { |
| | | dom.style.left = `${screenPosition.x}px` |
| | | dom.style.top = `${screenPosition.y}px` |
| | | dom.style.display = 'block' |
| | | } |
| | | } |
| | | |
| | | const removeDom = () => { |
| | | const dom = document.querySelector('#mapPopUpBox') |
| | | if (dom && dom.parentNode) { |
| | | dom.parentNode.removeChild(dom) |
| | | } |
| | | } |
| | | |
| | | // 移除弹框标签 |
| | | const removeLabel = () => { |
| | | viewer?.scene.postRender.removeEventListener(labelBoxRender) |
| | | removeDom() |
| | | } |
| | | |
| | | |
| | | const init = () => { |
| | | viewer = window.$viewer |
| | | |
| | | handlerInit() |
| | | mapHandlerInit() |
| | | } |
| | | |
| | | const removeAll = () => { |
| | | removeLayer() |
| | | removeHandler() |
| | | removeLabel() |
| | | removeMapHandlerAll() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | nextTick(() => { |
| | | init() |
| | | }) |
| | | onMounted(async () => { |
| | | await nextTick() |
| | | init() |
| | | }) |
| | | |
| | | // 自动清理 |
| | |
| | | font-family: YouSheBiaoTiHei, YouSheBiaoTiHei; |
| | | font-weight: 400; |
| | | font-size: 45px; |
| | | color: #ffffff; |
| | | line-height: 53px; |
| | | letter-spacing: 11px; |
| | | text-shadow: 0px 4px 1px rgba(19, 80, 143, 0.66), 0px 0px 18px rgba(130, 165, 255, 0.4), |
| | | inset 0px 0px 2px rgba(255, 255, 255, 0.8); |
| | | text-shadow: 0px 4px 1px rgba(19,80,143,0.66), 0px 0px 18px rgba(130,165,255,0.4), inset 0px 0px 2px rgba(255,255,255,0.8); |
| | | text-align: center; |
| | | font-style: normal; |
| | | text-transform: none; |
| | | background: linear-gradient(180deg, #FFFFFF 23%, #E9F8FF 46%, #77BAFF 76%); |
| | | -webkit-background-clip: text; |
| | | color: transparent; |
| | | background-clip: text; |
| | | } |
| | | |
| | | .h-right { |
| | |
| | | <div class="login-header"> |
| | | <div class="title">中图智飞低空智能感知网平台</div> |
| | | </div> |
| | | <div class="login-left-title">中国图强 智领飞跃</div> |
| | | <!-- <div class="login-left-title">中国图强 智领飞跃</div> --> |
| | | <div class="login-left"></div> |
| | | <userLogin v-if="activeName === 'user'"></userLogin> |
| | | </div> |
| | |
| | | data() { |
| | | return { |
| | | login:{ |
| | | info: '中图智绘低空无人机监测网平台' |
| | | info: '中图智飞低空智能感知网平台' |
| | | }, |
| | | website: website, |
| | | time: '', |
| | |
| | | text-align: center; |
| | | font-style: normal; |
| | | text-transform: none; |
| | | color: #fff; |
| | | // background: linear-gradient(90deg, #FFFFFF 0%, #B2D5FF 100%); |
| | | background: linear-gradient(180deg, #FFFFFF 0%, #B2D5FF 100%); |
| | | -webkit-background-clip: text; |
| | | color: transparent; |
| | | background-clip: text; |
| | | } |
| | | } |
| | | .login-left-title-new { |
| | | .login-left-title { |
| | | position: absolute; |
| | | border: 1px solid greenyellow; |
| | | left: 147px; |
| | | top: 263px; |
| | | // border: 1px solid greenyellow; |
| | | left: 160px; |
| | | top: 240px; |
| | | width: 926px; |
| | | height: 42px; |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | |
| | | text-align: center; |
| | | font-style: normal; |
| | | text-transform: none; |
| | | // background: linear-gradient(90.00000000000004deg, #FFFFFF 0%, #E3FEFF 63%, #88BAFF 100%); |
| | | background: linear-gradient(90.00000000000004deg, #FFFFFF 0%, #E3FEFF 63%, #88BAFF 100%); |
| | | -webkit-background-clip: text; |
| | | color: transparent; |
| | | background-clip: text; |
| | | } |
| | | .login-left-new { |
| | | position: absolute; |
| New file |
| | |
| | | /* |
| | | * @Author: shuishen 1109946754@qq.com |
| | | * @Date: 2025-04-17 20:17:12 |
| | | * @LastEditors: shuishen 1109946754@qq.com |
| | | * @LastEditTime: 2025-04-17 20:28:08 |
| | | * @FilePath: \command-center-dashboard\src\utils\stateToImageMap\drone.js |
| | | * @Description: |
| | | * |
| | | * Copyright (c) 2025 by shuishen, All Rights Reserved. |
| | | */ |
| | | import endingImg from '@/assets/images/aiNowFly/ending.png' |
| | | import endingHighImg from '@/assets/images/aiNowFly/ending-high.png' |
| | | |
| | | const droneImage = { |
| | | 'OFFLINE': endingImg, |
| | | 'WORKING': endingHighImg, |
| | | 'LEISURE': endingHighImg |
| | | } |
| | | |
| | | /** |
| | | * 根据机巢状态获取图片 |
| | | * @param {string} status 状态 |
| | | * @returns |
| | | */ |
| | | export const getDroneStatusImage = (status) => droneImage[status] || endingHighImg |
| | | |
| | | /** |
| | | * 根据机巢状态获取图片 |
| | | * @param {boolean} isOnline 状态 |
| | | * @returns |
| | | */ |
| | | export const getDroneFlagImage = (isOnline) => isOnline ? endingHighImg : endingImg |
| New file |
| | |
| | | import eventSingle from '@/assets/images/home/useEventOperate/eventSingle.png' |
| | | |
| | | import eventPending from '@/assets/images/home/useEventOperate/eventPending.png' // 待处理 0 |
| | | import eventWaitAudit from '@/assets/images/home/useEventOperate/eventWaitAudit.png' // 待审核 2 |
| | | import eventProcessing from '@/assets/images/home/useEventOperate/eventProcessing.png' // 处理中 3 |
| | | import eventCompleted from '@/assets/images/home/useEventOperate/eventCompleted.png' // 已完成 4 |
| | | import eventClosed from '@/assets/images/home/useEventOperate/eventClosed.png' // 已完结 5 |
| | | |
| | | |
| | | const eventImage = { |
| | | 0: eventPending, |
| | | 2: eventWaitAudit, |
| | | 3: eventProcessing, |
| | | 4: eventCompleted, |
| | | 5: eventClosed |
| | | } |
| | | |
| | | /** |
| | | * 根据事件状态获取图片资源 |
| | | * @param {*} status 状态 |
| | | * @returns |
| | | */ |
| | | export const getEventImage = (status) => eventImage[status] || eventSingle |
| | |
| | | await ruleFormRef.value.validate() |
| | | if (!params.value.dock_sns.length) return ElMessage.warning('请选择将要飞行的机巢') |
| | | if (!params.value.longitude) return ElMessage.warning('请选择飞行的点') |
| | | // 先清除,再push |
| | | params.value.action_modes = [{action_actuator_func:'startRecord'}] |
| | | if (isPhoto.value) { |
| | | params.value.action_modes.push({action_actuator_func:'takePhoto'}) |
| | | } |
| | |
| | | ? 'finish ' |
| | | : scope.row.status === 5 |
| | | ? 'fail ' |
| | | : ' '"> |
| | | : ' ' |
| | | " |
| | | > |
| | | {{ |
| | | scope.row.status === 1 |
| | | ? '待执行' |
| | |
| | | <span>{{ scope.row.event_number ? scope.row.event_number : '/' }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="80"><div class="ztzf-view">查看</div></el-table-column> |
| | | <el-table-column label="操作" width="80"> |
| | | <template #default="scope"> |
| | | <div class="ztzf-view" @click="viewDetail(scope.row)">查看</div> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | <!-- 分页 --> |
| | |
| | | /> |
| | | </div> |
| | | </el-dialog> |
| | | <!-- 当前任务详情 --> |
| | | <CurrentTaskDetails |
| | | v-if="isShowCurrentTaskDetails" |
| | | v-model:show="isShowCurrentTaskDetails" |
| | | :id="currentTaskDetailsId" |
| | | /> |
| | | <!-- 历史任务详情 --> |
| | | <DeviceJobDetails |
| | | v-if="deviceJobDetailsShow" |
| | | v-model:show="deviceJobDetailsShow" |
| | | :wayLineJodInfoId="wayLineJodInfoId" |
| | | /> |
| | | </template> |
| | | <script setup> |
| | | import { pxToRem } from '@/utils/rem' |
| | |
| | | import { getDictionary } from '@/api/system/dict' |
| | | import { selectDevicePage } from '@/api/home/machineNest' |
| | | import { getMultipleDictionary } from '@/api/system/dictbiz' |
| | | import CurrentTaskDetails from '@/components/CurrentTaskDetails/CurrentTaskDetails.vue' |
| | | import DeviceJobDetails from '@/components/DeviceJobDetails/DeviceJobDetails.vue' |
| | | const isShowDetailsDialog = defineModel('show') |
| | | const dateRange = ref('') |
| | | const searchForm = reactive({ |
| | |
| | | const statusOptions = [ |
| | | { label: '待执行', value: 1 }, |
| | | { label: '执行中', value: 2 }, |
| | | { label: '已完成', value: 3 }, |
| | | { label: '已执行', value: 3 }, |
| | | |
| | | { label: '执行失败', value: 5 }, |
| | | ] |
| | |
| | | } |
| | | // 获取任务列表 |
| | | const getJobList = () => { |
| | | // 事件状态:0 =待处理,1=待分拨,2=待处理,3=处理中,4=已完成 5=已完结 |
| | | jobList(taskDetailParams).then(res => { |
| | | if (res.data.code !== 0) return |
| | | taskDetailData.value = res.data.data.records |
| | |
| | | return 'warning-row' |
| | | } else { |
| | | return 'success-row' |
| | | } |
| | | } |
| | | // 查看 |
| | | // 当前任务详情 1:待执行 2:执行中 |
| | | // 历史任务详情 3:已执行 5:执行失败 |
| | | const isShowCurrentTaskDetails = ref(false) |
| | | const currentTaskDetailsId = ref(null) |
| | | const deviceJobDetailsShow = ref(false) |
| | | const wayLineJodInfoId = ref(null) |
| | | const viewDetail = row => { |
| | | if (row.status === 1 || row.status === 2) { |
| | | currentTaskDetailsId.value = row.id |
| | | isShowCurrentTaskDetails.value = true |
| | | } |
| | | if (row.status === 3 || row.status === 5) { |
| | | wayLineJodInfoId.value = row.id |
| | | deviceJobDetailsShow.value = true |
| | | } |
| | | } |
| | | onMounted(() => { |
| | |
| | | } |
| | | // 执行中 |
| | | .distributed { |
| | | color: #FFA768; |
| | | color: #ffa768; |
| | | } |
| | | // 已执行 |
| | | .finish { |
| | |
| | | <template> |
| | | <div class="searchBox" ref="searchBoxRef"> |
| | | <div class="searchInput"> |
| | | <el-select v-model="optionsValue" @change="optionChange" placeholder="请选择查询"> |
| | | <el-option |
| | | v-for="item in options" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | <div class="searchBox" ref="searchBoxRef"> |
| | | <div class="searchInput"> |
| | | <el-select v-model="optionsValue" @change="optionChange" placeholder="请选择查询"> |
| | | <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" /> |
| | | </el-select> |
| | | |
| | | <el-input v-model="searchKey" @focus="handleFocus" placeholder="请输入搜索关键字"></el-input> |
| | | </div> |
| | | <div class="searchBtn" @click="searchClick"></div> |
| | | <div class="region"> |
| | | <el-tree-select |
| | | v-model="treeValue" |
| | | check-strictly |
| | | lazy |
| | | :load="load" |
| | | :props="props" |
| | | style="width: 240px" |
| | | @node-click="handleNodeClick" |
| | | /> |
| | | </div> |
| | | </div> |
| | | <div class="select-down-list" ref="selectDownRef" v-show="isSelectDown"> |
| | | <div class="item" v-for="item in downList" @click="selectedValue(item)">{{ item.nickname || item.name }}</div> |
| | | </div> |
| | | <el-input |
| | | v-model="searchKey" |
| | | @input="handlerInput" |
| | | @focus="handleFocus" |
| | | placeholder="请输入搜索关键字" |
| | | ></el-input> |
| | | </div> |
| | | <div class="searchBtn" @click="searchClick"></div> |
| | | <div class="region"> |
| | | <el-tree-select |
| | | v-model="treeValue" |
| | | check-strictly |
| | | lazy |
| | | :load="load" |
| | | :props="props" |
| | | style="width: 240px" |
| | | @node-click="handleNodeClick" |
| | | /> |
| | | </div> |
| | | </div> |
| | | <div class="select-down-list" ref="selectDownRef" v-show="isSelectDown"> |
| | | <div class="item" v-for="item in downList" @click="selectedValue(item)">{{ item.nickname || item.name }}</div> |
| | | </div> |
| | | </template> |
| | | <script setup> |
| | | import { getRegion } from '@/api/home'; |
| | | import { searchByKeyword, selectDeviceList } from '@/api/home/common'; |
| | | import { useStore } from 'vuex'; |
| | | import _ from 'lodash' |
| | | import { getRegion } from '@/api/home' |
| | | import { searchByKeyword, selectDeviceList } from '@/api/home/common' |
| | | import { useStore } from 'vuex' |
| | | import cesiumOperation from '@/utils/cesium-tsa' |
| | | |
| | | const { flyTo } = cesiumOperation() |
| | | |
| | | const store = useStore(); |
| | | const searchKey = ref(''); |
| | | const userAreaCode = computed(() => store.state.user.userInfo.detail.areaCode); |
| | | const selectedAreaCode = computed(() => store.state.user.selectedAreaCode); |
| | | const areaValue = ref('江西省'); |
| | | const treeValue = ref(userAreaCode.value); |
| | | let first = true; |
| | | const searchBoxRef = ref(null); |
| | | const selectDownRef = ref(null); |
| | | const store = useStore() |
| | | const searchKey = ref('') |
| | | const userAreaCode = computed(() => store.state.user.userInfo.detail.areaCode) |
| | | const selectedAreaCode = computed(() => store.state.user.selectedAreaCode) |
| | | const areaValue = ref('江西省') |
| | | const treeValue = ref(userAreaCode.value) |
| | | let first = true |
| | | const searchBoxRef = ref(null) |
| | | const selectDownRef = ref(null) |
| | | |
| | | function handleNodeClick(data) { |
| | | areaValue.value = data.name; |
| | | store.commit('setSelectedAreaCode', data.code); |
| | | areaValue.value = data.name |
| | | store.commit('setSelectedAreaCode', data.code) |
| | | } |
| | | |
| | | const props = { |
| | | label: 'name', |
| | | value: 'code', |
| | | children: 'children', |
| | | }; |
| | | label: 'name', |
| | | value: 'code', |
| | | children: 'children', |
| | | } |
| | | const load = async (node, resolve) => { |
| | | if (first) { |
| | | first = false; |
| | | const res = await getRegion(userAreaCode.value); |
| | | const { provinceCode, provinceName } = res?.data?.data?.[0] || {}; |
| | | resolve([{ code: provinceCode, name: provinceName }]); |
| | | return; |
| | | } |
| | | const code = node?.data?.code || userAreaCode.value; |
| | | if ((node?.data?.regionLevel || 0) > 2) return resolve([]); |
| | | getRegion(code).then(res => { |
| | | resolve(res?.data?.data || []); |
| | | }); |
| | | }; |
| | | if (first) { |
| | | first = false |
| | | const res = await getRegion(userAreaCode.value) |
| | | const { provinceCode, provinceName } = res?.data?.data?.[0] || {} |
| | | resolve([{ code: provinceCode, name: provinceName }]) |
| | | return |
| | | } |
| | | const code = node?.data?.code || userAreaCode.value |
| | | if ((node?.data?.regionLevel || 0) > 2) return resolve([]) |
| | | getRegion(code).then(res => { |
| | | resolve(res?.data?.data || []) |
| | | }) |
| | | } |
| | | |
| | | const optionsValue = ref('1'); |
| | | const optionsValue = ref('1') |
| | | const options = [ |
| | | { |
| | | value: '1', |
| | | label: '机巢', |
| | | }, |
| | | { |
| | | value: '2', |
| | | label: '地址', |
| | | }, |
| | | ]; |
| | | { |
| | | value: '1', |
| | | label: '机巢', |
| | | }, |
| | | { |
| | | value: '2', |
| | | label: '地址', |
| | | }, |
| | | ] |
| | | |
| | | const isSelectDown = ref(false); |
| | | // 下拉数据列表 |
| | | const machineNestList = ref([]); |
| | | const isSelectDown = ref(false) |
| | | |
| | | // 地址搜索结果 |
| | | const downList = ref([]); |
| | | const position = ref({}); |
| | | const downList = ref([]) |
| | | const position = ref({}) |
| | | |
| | | // 获取机巢搜索结果 |
| | | const getDeviceList = async () => { |
| | | const res = await selectDeviceList({nickname: searchKey.value}); |
| | | if (res.data.code !== 0) return; |
| | | machineNestList.value = res?.data?.data || []; |
| | | downList.value = res?.data?.data || []; |
| | | }; |
| | | const res = await selectDeviceList({ nickname: searchKey.value }) |
| | | if (res.data.code !== 0) return |
| | | downList.value = res?.data?.data || [] |
| | | } |
| | | // 获取地址搜索结果 |
| | | const getAddressList = async () => { |
| | | const res = await searchByKeyword(encodeURIComponent(`${areaValue.value}+${searchKey.value}`)); |
| | | if (res.data.code !== 0) return; |
| | | downList.value = res?.data?.data.tips || []; |
| | | if (downList.value.length > 0) { |
| | | isSelectDown.value = true; |
| | | } |
| | | }; |
| | | const res = await searchByKeyword(encodeURIComponent(`${areaValue.value}+${searchKey.value}`)) |
| | | if (res.data.code !== 0) return |
| | | downList.value = res?.data?.data.tips || [] |
| | | if (downList.value.length > 0) { |
| | | isSelectDown.value = true |
| | | } |
| | | } |
| | | // 搜索结果 |
| | | const searchClick = () => { |
| | | const longitude = Number(position.value.longitude) |
| | | const latitude = Number(position.value.latitude) |
| | | flyTo({ longitude, latitude }, 1, 1000) |
| | | }; |
| | | } |
| | | |
| | | // 地址和机巢的切换 |
| | | const optionChange = () => { |
| | | searchKey.value = ''; |
| | | downList.value = []; |
| | | }; |
| | | searchKey.value = '' |
| | | downList.value = [] |
| | | |
| | | inputSelect() |
| | | } |
| | | |
| | | // input对应下拉数据初始化 |
| | | const inputSelect = () => { |
| | | if (optionsValue.value === '2') { |
| | | getAddressList() |
| | | } else { |
| | | getDeviceList() |
| | | } |
| | | } |
| | | |
| | | inputSelect() |
| | | |
| | | // 输入框input事件 |
| | | const handlerInput = _.debounce(inputSelect, 1000) |
| | | |
| | | // 输入框获取焦点 |
| | | const handleFocus = () => { |
| | | if (optionsValue.value === '1') { |
| | | isSelectDown.value = true; |
| | | downList.value = machineNestList.value; |
| | | } |
| | | }; |
| | | isSelectDown.value = true |
| | | } |
| | | // 机巢下拉获取值 |
| | | const selectedValue = item => { |
| | | searchKey.value = item.nickname || item.name; |
| | | if (optionsValue.value === '1') { |
| | | position.value = item; |
| | | } else { |
| | | position.value = { longitude: item.location.split(',')[0], latitude: item.location.split(',')[1] }; |
| | | } |
| | | |
| | | isSelectDown.value = false; |
| | | }; |
| | | searchKey.value = item.nickname || item.name |
| | | if (optionsValue.value === '1') { |
| | | position.value = item |
| | | } else { |
| | | position.value = { longitude: item.location.split(',')[0], latitude: item.location.split(',')[1] } |
| | | } |
| | | |
| | | // 监听搜索关键字变化 |
| | | watch(searchKey, async (newVal) => { |
| | | if (optionsValue.value === '2') { |
| | | await getAddressList(); |
| | | } else { |
| | | await getDeviceList(); |
| | | } |
| | | }, { immediate: false }); |
| | | isSelectDown.value = false |
| | | } |
| | | |
| | | onMounted(() => { |
| | | document.addEventListener('click', handleClickOutside); |
| | | }); |
| | | document.addEventListener('click', handleClickOutside) |
| | | }) |
| | | |
| | | onUnmounted(() => { |
| | | document.removeEventListener('click', handleClickOutside); |
| | | }); |
| | | document.removeEventListener('click', handleClickOutside) |
| | | }) |
| | | |
| | | const handleClickOutside = (event) => { |
| | | if (!searchBoxRef.value?.contains(event.target) && !selectDownRef.value?.contains(event.target)) { |
| | | isSelectDown.value = false; |
| | | } |
| | | }; |
| | | const handleClickOutside = event => { |
| | | if (!searchBoxRef.value?.contains(event.target) && !selectDownRef.value?.contains(event.target)) { |
| | | isSelectDown.value = false |
| | | } |
| | | } |
| | | </script> |
| | | <style scoped lang="scss"> |
| | | .select-down-list { |
| | | position: absolute; |
| | | top: 188px; |
| | | left: 44%; |
| | | transform: translateX(-40%); |
| | | width: 220px; |
| | | height: 256px; |
| | | overflow-y: auto; |
| | | background: linear-gradient( 180deg, #0D3556 0%, #012350 100%); |
| | | border-radius: 0px 0px 8px 8px; |
| | | border: 1px solid; |
| | | border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1; |
| | | opacity: 0.8; |
| | | &::-webkit-scrollbar { |
| | | width: 0; |
| | | display: none; |
| | | } |
| | | -ms-overflow-style: none; /* IE and Edge */ |
| | | scrollbar-width: none; /* Firefox */ |
| | | .item { |
| | | color: #FFFFFF; |
| | | height: 32px; |
| | | line-height: 32px; |
| | | text-align: center; |
| | | cursor: pointer; |
| | | &:hover { |
| | | background: linear-gradient( 90deg, rgba(0,122,255,0) 0%, rgba(0,98,204,0.6) 50%, rgba(0,73,153,0) 100%); |
| | | border: 1px solid; |
| | | border-image: linear-gradient(90deg, rgba(0, 199, 190, 0), rgba(48, 176, 199, 1), rgba(0, 199, 190, 0)) 1 1; |
| | | } |
| | | } |
| | | position: absolute; |
| | | top: 188px; |
| | | left: 44%; |
| | | transform: translateX(-40%); |
| | | width: 220px; |
| | | height: 256px; |
| | | overflow-y: auto; |
| | | background: linear-gradient(180deg, #0d3556 0%, #012350 100%); |
| | | border-radius: 0px 0px 8px 8px; |
| | | border: 1px solid; |
| | | border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1; |
| | | opacity: 0.8; |
| | | &::-webkit-scrollbar { |
| | | width: 0; |
| | | display: none; |
| | | } |
| | | -ms-overflow-style: none; /* IE and Edge */ |
| | | scrollbar-width: none; /* Firefox */ |
| | | .item { |
| | | color: #ffffff; |
| | | height: 32px; |
| | | line-height: 32px; |
| | | text-align: center; |
| | | cursor: pointer; |
| | | &:hover { |
| | | background: linear-gradient(90deg, rgba(0, 122, 255, 0) 0%, rgba(0, 98, 204, 0.6) 50%, rgba(0, 73, 153, 0) 100%); |
| | | border: 1px solid; |
| | | border-image: linear-gradient(90deg, rgba(0, 199, 190, 0), rgba(48, 176, 199, 1), rgba(0, 199, 190, 0)) 1 1; |
| | | } |
| | | } |
| | | } |
| | | .searchBox { |
| | | width: 420px; |
| | | height: 43px; |
| | | position: absolute; |
| | | top: 145px; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | display: flex; |
| | | width: 420px; |
| | | height: 43px; |
| | | position: absolute; |
| | | top: 145px; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | display: flex; |
| | | |
| | | .el-select { |
| | | width: 130px; |
| | | height: 100%; |
| | | .el-select { |
| | | width: 130px; |
| | | height: 100%; |
| | | |
| | | :deep() { |
| | | .el-select__wrapper { |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | height: 100%; |
| | | padding-left: 20px; |
| | | } |
| | | :deep() { |
| | | .el-select__wrapper { |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | height: 100%; |
| | | padding-left: 20px; |
| | | } |
| | | |
| | | .el-select__selected-item { |
| | | font-family: Source Han Sans CN, Source Han Sans CN, serif; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #ffffff; |
| | | line-height: 18px; |
| | | } |
| | | } |
| | | } |
| | | .el-select__selected-item { |
| | | font-family: Source Han Sans CN, Source Han Sans CN, serif; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #ffffff; |
| | | line-height: 18px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .searchInput { |
| | | width: 243px; |
| | | height: 100%; |
| | | background: url('@/assets/images/home/searchBox/searchBg1.png') no-repeat center / 100% 100%; |
| | | display: flex; |
| | | .searchInput { |
| | | width: 243px; |
| | | height: 100%; |
| | | background: url('@/assets/images/home/searchBox/searchBg1.png') no-repeat center / 100% 100%; |
| | | display: flex; |
| | | |
| | | .el-input { |
| | | height: 100%; |
| | | .el-input { |
| | | height: 100%; |
| | | |
| | | :deep() { |
| | | .el-input__wrapper { |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | height: 100%; |
| | | } |
| | | :deep() { |
| | | .el-input__wrapper { |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | height: 100%; |
| | | } |
| | | |
| | | .el-input__inner { |
| | | font-family: Source Han Sans CN, Source Han Sans CN, serif; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #ffffff; |
| | | line-height: 18px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .el-input__inner { |
| | | font-family: Source Han Sans CN, Source Han Sans CN, serif; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #ffffff; |
| | | line-height: 18px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .searchBtn { |
| | | width: 67px; |
| | | height: 100%; |
| | | background: url('@/assets/images/home/searchBox/searchBg2.png') no-repeat center / 100% 100%; |
| | | cursor: pointer; |
| | | } |
| | | .searchBtn { |
| | | width: 67px; |
| | | height: 100%; |
| | | background: url('@/assets/images/home/searchBox/searchBg2.png') no-repeat center / 100% 100%; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .region { |
| | | width: 0; |
| | | flex-grow: 1; |
| | | background: url('@/assets/images/home/searchBox/searchBg3.png') no-repeat center / 100% 100%; |
| | | .region { |
| | | width: 0; |
| | | flex-grow: 1; |
| | | background: url('@/assets/images/home/searchBox/searchBg3.png') no-repeat center / 100% 100%; |
| | | |
| | | :deep() { |
| | | .el-select__wrapper { |
| | | width: 100px; |
| | | padding-left: 20px; |
| | | } |
| | | } |
| | | } |
| | | :deep() { |
| | | .el-select__wrapper { |
| | | width: 100px; |
| | | padding-left: 20px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <!-- 任务事件概况 --> |
| | | <template> |
| | | <common-title title="任务事件概况" :style="{ marginLeft: pxToRem(14) }"></common-title> |
| | | <div class="inspection-rask-details"> |
| | | <CommonDateTime :style="{ top: pxToRem(14),marginLeft: pxToRem(10) }" v-model="newTime" @change="getData"></CommonDateTime> |
| | | <div class="chart-container" ref="chartRef"></div> |
| | | <div class="pie-container" ref="pieRef"></div> |
| | | </div> |
| | | <common-title title="任务事件概况" :style="{ marginLeft: pxToRem(14) }"></common-title> |
| | | <div class="inspection-rask-details"> |
| | | <CommonDateTime |
| | | :style="{ top: pxToRem(14), marginLeft: pxToRem(10) }" |
| | | v-model="newTime" |
| | | @change="getData" |
| | | ></CommonDateTime> |
| | | <div class="chart-container" ref="chartRef"></div> |
| | | <div class="pie-container" ref="pieRef"></div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import * as echarts from 'echarts'; |
| | | import dayjs from 'dayjs'; |
| | | import { pxToRem } from '@/utils/rem'; |
| | | import CommonTitle from '@/components/CommonTitle.vue'; |
| | | import CommonDateTime from '@/components/CommonDateTime.vue'; |
| | | import * as echarts from 'echarts' |
| | | import dayjs from 'dayjs' |
| | | import { pxToRem } from '@/utils/rem' |
| | | import CommonTitle from '@/components/CommonTitle.vue' |
| | | import CommonDateTime from '@/components/CommonDateTime.vue' |
| | | import { jobNumBar, eventNumPie } from '@/api/home' |
| | | import useEchartsResize from '@/hooks/useEchartsResize' |
| | | import { useStore } from 'vuex' |
| | | |
| | | const store = useStore() |
| | | // 单个机巢信息 |
| | | const singleUavHome = computed(() => store.state.home.singleUavHome) |
| | | // 日期 |
| | | const currenDate = dayjs().format('YYYY-MM-DD'); |
| | | const newTime = ref([currenDate, currenDate]); |
| | | const currenDate = dayjs().format('YYYY-MM-DD') |
| | | const newTime = ref([currenDate, currenDate]) |
| | | // 统计图 |
| | | const chartRef = ref(null); |
| | | const chartRef = ref(null) |
| | | let { chart } = useEchartsResize(chartRef) |
| | | const pieRef = ref(null); |
| | | const pieRef = ref(null) |
| | | let { chart: pieChart } = useEchartsResize(pieRef) |
| | | |
| | | |
| | | const option = { |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | } |
| | | }, |
| | | grid: { |
| | | top: '15%', |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }, |
| | | legend: { |
| | | data: ['任务', '事件'], |
| | | textStyle: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | // data: ['1月', '2月', '3月', '4月', '5月', '6月'], |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | splitLine: { |
| | | lineStyle: { |
| | | color: 'rgba(255, 255, 255, 0.1)' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | series: [ |
| | | { |
| | | name: '任务', |
| | | type: 'bar', |
| | | barWidth: '20%', |
| | | // data: [10, 15, 20, 25, 30, 35], |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: '#1EE7E7' }, |
| | | { offset: 1, color: 'rgba(30, 231, 231, 0.1)' } |
| | | ]) |
| | | } |
| | | }, |
| | | { |
| | | name: '事件', |
| | | type: 'bar', |
| | | barWidth: '20%', |
| | | // data: [15, 20, 25, 30, 35, 40], |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: '#0070FF' }, |
| | | { offset: 1, color: 'rgba(0, 112, 255, 0.1)' } |
| | | ]) |
| | | } |
| | | } |
| | | ] |
| | | }; |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow', |
| | | }, |
| | | }, |
| | | grid: { |
| | | top: '15%', |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true, |
| | | }, |
| | | legend: { |
| | | data: ['任务', '事件'], |
| | | textStyle: { |
| | | color: '#fff', |
| | | }, |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | // data: ['1月', '2月', '3月', '4月', '5月', '6月'], |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#fff', |
| | | }, |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff', |
| | | }, |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#fff', |
| | | }, |
| | | }, |
| | | splitLine: { |
| | | lineStyle: { |
| | | color: 'rgba(255, 255, 255, 0.1)', |
| | | }, |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff', |
| | | }, |
| | | }, |
| | | series: [ |
| | | { |
| | | name: '任务', |
| | | type: 'bar', |
| | | barWidth: '20%', |
| | | // data: [10, 15, 20, 25, 30, 35], |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: '#1EE7E7' }, |
| | | { offset: 1, color: 'rgba(30, 231, 231, 0.1)' }, |
| | | ]), |
| | | }, |
| | | }, |
| | | { |
| | | name: '事件', |
| | | type: 'bar', |
| | | barWidth: '20%', |
| | | // data: [15, 20, 25, 30, 35, 40], |
| | | itemStyle: { |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: '#0070FF' }, |
| | | { offset: 1, color: 'rgba(0, 112, 255, 0.1)' }, |
| | | ]), |
| | | }, |
| | | }, |
| | | ], |
| | | } |
| | | |
| | | // chart.setOption(option); |
| | | |
| | | const pieOption = { |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{b}: {c} ({d}%)' |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | right: '5%', |
| | | top: 'middle', |
| | | textStyle: { |
| | | color: '#fff', |
| | | fontSize: 12 |
| | | }, |
| | | itemWidth: 10, |
| | | itemHeight: 10, |
| | | itemGap: 10, |
| | | formatter: function(name) { |
| | | let data = pieOption.series[0].data; |
| | | let total = 0; |
| | | let tarValue = 0; |
| | | for (let i = 0; i < data.length; i++) { |
| | | total += data[i].value; |
| | | if (data[i].name === name) { |
| | | tarValue = data[i].value; |
| | | } |
| | | } |
| | | let percentage = ((tarValue / total) * 100).toFixed(1); |
| | | return `${name} ${percentage}%`; |
| | | } |
| | | }, |
| | | series: [ |
| | | { |
| | | name: '任务状态', |
| | | type: 'pie', |
| | | radius: ['10%', '90%'], |
| | | center: ['35%', '50%'], // 将图表向左偏移 |
| | | roseType: 'radius', |
| | | itemStyle: { |
| | | borderRadius: 4 |
| | | }, |
| | | label: { |
| | | show: true, |
| | | position: 'inside', |
| | | formatter: '{c}%', |
| | | fontSize: 12, |
| | | color: '#fff' |
| | | }, |
| | | data: [ |
| | | { value: 0, name: '待审核', itemStyle: { color: '#1860EC' } }, |
| | | { value: 0, name: '待处理', itemStyle: { color: '#47D107' } }, |
| | | { value: 0, name: '已完成', itemStyle: { color: '#F29509' } }, |
| | | // { value: 0, name: '待分拨', itemStyle: { color: '#E9C81A' } }, |
| | | { value: 0, name: '处理中', itemStyle: { color: '#0FC1E8' } }, |
| | | { value: 0, name: '已完结', itemStyle: { color: '#FE577F' } } |
| | | ] |
| | | } |
| | | ] |
| | | }; |
| | | tooltip: { |
| | | trigger: 'item', |
| | | formatter: '{b}: {c} ({d}%)', |
| | | }, |
| | | legend: { |
| | | orient: 'vertical', |
| | | right: '5%', |
| | | top: 'middle', |
| | | textStyle: { |
| | | color: '#fff', |
| | | fontSize: 12, |
| | | }, |
| | | itemWidth: 10, |
| | | itemHeight: 10, |
| | | itemGap: 10, |
| | | formatter: function (name) { |
| | | let data = pieOption.series[0].data |
| | | let total = 0 |
| | | let tarValue = 0 |
| | | for (let i = 0; i < data.length; i++) { |
| | | total += data[i].value |
| | | if (data[i].name === name) { |
| | | tarValue = data[i].value |
| | | } |
| | | } |
| | | let percentage = ((tarValue / total) * 100).toFixed(1) |
| | | return `${name} ${percentage}%` |
| | | }, |
| | | }, |
| | | series: [ |
| | | { |
| | | name: '任务状态', |
| | | type: 'pie', |
| | | radius: ['10%', '90%'], |
| | | center: ['35%', '50%'], // 将图表向左偏移 |
| | | roseType: 'radius', |
| | | itemStyle: { |
| | | borderRadius: 4, |
| | | }, |
| | | label: { |
| | | show: true, |
| | | position: 'inside', |
| | | formatter: '{c}%', |
| | | fontSize: 12, |
| | | color: '#fff', |
| | | }, |
| | | data: [ |
| | | { value: 0, name: '待审核', itemStyle: { color: '#1860EC' } }, |
| | | { value: 0, name: '待处理', itemStyle: { color: '#47D107' } }, |
| | | { value: 0, name: '已完成', itemStyle: { color: '#F29509' } }, |
| | | // { value: 0, name: '待分拨', itemStyle: { color: '#E9C81A' } }, |
| | | { value: 0, name: '处理中', itemStyle: { color: '#0FC1E8' } }, |
| | | { value: 0, name: '已完结', itemStyle: { color: '#FE577F' } }, |
| | | ], |
| | | }, |
| | | ], |
| | | } |
| | | |
| | | // TODAY,CURRENT_WEEK,CURRENT_MONTH,CURRENT_YEAR |
| | | const getData = (value,date_enum) => { |
| | | const params = { |
| | | device_sn: '', |
| | | date_enum, |
| | | // start_date: newTime.value[0], |
| | | // end_date: newTime.value[1] |
| | | }; |
| | | // 获取柱状图 |
| | | jobNumBar(params).then(res => { |
| | | if (res.data.code !== 0) return; |
| | | option.xAxis.data = res.data?.data.map(item => item.name); |
| | | option.series[0].data = res.data?.data.map(item => item.data[0].value); |
| | | option.series[1].data = res.data?.data.map(item => item.data[1].value); |
| | | chart.value.setOption(option); |
| | | }) |
| | | // 获取饼状图 |
| | | eventNumPie(params).then(res => { |
| | | if (res.data.code!== 0) return; |
| | | // 更新饼图数据 |
| | | const updatedPieData = pieOption.series[0].data.map(item => { |
| | | const matchedData = res.data?.data.find(dataItem => dataItem.name === item.name); |
| | | return { |
| | | ...item, |
| | | value: matchedData ? matchedData.value : item.value |
| | | }; |
| | | }); |
| | | pieOption.series[0].data = updatedPieData; |
| | | pieChart.value.setOption(pieOption); |
| | | }) |
| | | }; |
| | | const getData = (value, date_enum) => { |
| | | const params = { |
| | | device_sn: singleUavHome.value.device_sn, |
| | | date_enum, |
| | | // start_date: newTime.value[0], |
| | | // end_date: newTime.value[1] |
| | | } |
| | | // 获取柱状图 |
| | | jobNumBar(params).then(res => { |
| | | if (res.data.code !== 0) return |
| | | option.xAxis.data = res.data?.data.map(item => item.name) |
| | | option.series[0].data = res.data?.data.map(item => item.data[0].value) |
| | | option.series[1].data = res.data?.data.map(item => item.data[1].value) |
| | | chart.value.setOption(option) |
| | | }) |
| | | // 获取饼状图 |
| | | eventNumPie(params).then(res => { |
| | | if (res.data.code !== 0) return |
| | | // 更新饼图数据 |
| | | const updatedPieData = pieOption.series[0].data.map(item => { |
| | | const matchedData = res.data?.data.find(dataItem => dataItem.name === item.name) |
| | | return { |
| | | ...item, |
| | | value: matchedData ? matchedData.value : item.value, |
| | | } |
| | | }) |
| | | pieOption.series[0].data = updatedPieData |
| | | pieChart.value.setOption(pieOption) |
| | | }) |
| | | } |
| | | onMounted(() => { |
| | | getData(newTime.value, 'TODAY'); |
| | | }); |
| | | getData(newTime.value, 'TODAY') |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .inspection-rask-details { |
| | | margin-left: 29px; |
| | | // padding: 16px 16px; |
| | | width: 390px; |
| | | height: 526px; |
| | | background: linear-gradient( |
| | | 270deg, |
| | | rgba(31, 62, 122, 0) 0%, |
| | | rgba(31, 62, 122, 0.35) 21%, |
| | | #1f3e7a 100% |
| | | ); |
| | | border-radius: 0px 0px 0px 0px; |
| | | opacity: 0.85; |
| | | .chart-container { |
| | | width: 100%; |
| | | height: 220px; |
| | | margin-top: 20px; |
| | | } |
| | | .pie-container { |
| | | width: 100%; |
| | | height: 212px; |
| | | margin-top: 10px; |
| | | } |
| | | margin-left: 29px; |
| | | // padding: 16px 16px; |
| | | width: 390px; |
| | | height: 526px; |
| | | background: linear-gradient(270deg, rgba(31, 62, 122, 0) 0%, rgba(31, 62, 122, 0.35) 21%, #1f3e7a 100%); |
| | | border-radius: 0px 0px 0px 0px; |
| | | opacity: 0.85; |
| | | .chart-container { |
| | | width: 100%; |
| | | height: 220px; |
| | | margin-top: 20px; |
| | | } |
| | | .pie-container { |
| | | width: 100%; |
| | | height: 212px; |
| | | margin-top: 10px; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <!-- |
| | | * @Author: shuishen 1109946754@qq.com |
| | | * @Date: 2025-04-15 17:19:35 |
| | | * @LastEditors: shuishen 1109946754@qq.com |
| | | * @LastEditTime: 2025-04-17 21:16:50 |
| | | * @FilePath: \command-center-dashboard\src\views\SignMachineNest\MachineRight\MachineMonitor.vue |
| | | * @Description: |
| | | * |
| | | * Copyright (c) 2025 by shuishen, All Rights Reserved. |
| | | --> |
| | | <!-- 机巢监控 --> |
| | | <template> |
| | | <CommonTitle title="直播监控" /> |
| | | <div :style="{ marginLeft: pxToRem(14) }"> |
| | | <div class="machine-monitor"> |
| | | <LiveVideo :videoUrl="airPortUrl" /> |
| | | </div> |
| | | </div> |
| | | <CommonTitle title="直播监控" /> |
| | | <div :style="{ marginLeft: pxToRem(14) }"> |
| | | <div class="machine-monitor"> |
| | | <LiveVideo :videoUrl="airPortUrl" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import CommonTitle from '@/components/CommonTitle.vue'; |
| | | import LiveVideo from '@/components/LiveVideo.vue'; |
| | | import { liveStart } from '@/api/home/machineNest'; |
| | | import CommonTitle from '@/components/CommonTitle.vue' |
| | | import LiveVideo from '@/components/LiveVideo.vue' |
| | | import { liveStart } from '@/api/home/machineNest' |
| | | // import { CURRENT_CONFIG as config } from '@/utils/http/config' |
| | | import { useStore } from 'vuex'; |
| | | import { useStore } from 'vuex' |
| | | |
| | | |
| | | const store = useStore(); |
| | | const store = useStore() |
| | | // 单个机巢信息 |
| | | const singleUavHome = computed(() => store.state.home.singleUavHome); |
| | | const singleUavHome = computed(() => store.state.home.singleUavHome) |
| | | // 直播地址 |
| | | let airPortUrl = ref(''); |
| | | let airPortUrl = ref('') |
| | | // 获取直播地址 |
| | | const getVideoUrl = (sn,quality) => { |
| | | liveStart(sn, quality).then(res => { |
| | | if (res.data.code !== 0) return; |
| | | airPortUrl.value = res.data.data.rtcs_url; |
| | | }); |
| | | }; |
| | | let isCurrentSn = ref(false); |
| | | let CurrentSn = ref(''); |
| | | const getVideoUrl = (sn, quality) => { |
| | | liveStart(sn, quality).then(res => { |
| | | if (res.data.code !== 0) return |
| | | airPortUrl.value = res.data.data.rtcs_url |
| | | }) |
| | | } |
| | | let isCurrentSn = ref(false) |
| | | let CurrentSn = ref('') |
| | | // 监听ws消息 |
| | | watch( |
| | | () => store.state.home.deviceState.deviceInfo, |
| | | (newValue) => { |
| | | CurrentSn.value = Object.keys(newValue)[0]; |
| | | const currentDevice = newValue[CurrentSn.value]; |
| | | if (currentDevice && currentDevice?.mode_code > 0) { |
| | | isCurrentSn.value = true; |
| | | } else if (currentDevice && currentDevice?.mode_code === 14) { |
| | | isCurrentSn.value = false; |
| | | } else { |
| | | isCurrentSn.value = false; |
| | | } |
| | | }, |
| | | { |
| | | immediate: true, |
| | | deep: true, |
| | | } |
| | | ); |
| | | () => store.state.home.deviceState.deviceInfo, |
| | | newValue => { |
| | | CurrentSn.value = Object.keys(newValue)[0] |
| | | const currentDevice = newValue[CurrentSn.value] |
| | | if (currentDevice && currentDevice?.mode_code > 0) { |
| | | isCurrentSn.value = true |
| | | } else if (currentDevice && currentDevice?.mode_code === 14) { |
| | | isCurrentSn.value = false |
| | | } else { |
| | | isCurrentSn.value = false |
| | | } |
| | | }, |
| | | { |
| | | immediate: true, |
| | | deep: true, |
| | | } |
| | | ) |
| | | // 监听 isCurrentSn |
| | | watch(isCurrentSn, (newVal) => { |
| | | if (newVal) { |
| | | getVideoUrl(CurrentSn.value, 2); |
| | | } else { |
| | | getVideoUrl(singleUavHome.value.device_sn,1); |
| | | } |
| | | }, { immediate: true,deep: true }); |
| | | watch( |
| | | isCurrentSn, |
| | | newVal => { |
| | | if (newVal) { |
| | | getVideoUrl(CurrentSn.value, 2) |
| | | } else { |
| | | getVideoUrl(singleUavHome.value.device_sn, 1) |
| | | } |
| | | }, |
| | | { immediate: true, deep: true } |
| | | ) |
| | | |
| | | onMounted(() => { |
| | | getVideoUrl(singleUavHome.value.device_sn,1); |
| | | }); |
| | | // getVideoUrl(singleUavHome.value.device_sn,1); |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .machine-monitor { |
| | | width: 390px; |
| | | height: 228px; |
| | | background: linear-gradient( |
| | | 270deg, |
| | | #1f3e7a 0%, |
| | | rgba(31, 62, 122, 0.35) 79%, |
| | | rgba(31, 62, 122, 0) 100% |
| | | ); |
| | | opacity: 0.85; |
| | | margin: 2px 0 13 0; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | padding: 12px 10px 0; |
| | | } |
| | | .machine-monitor { |
| | | width: 390px; |
| | | height: 228px; |
| | | background: linear-gradient(270deg, #1f3e7a 0%, rgba(31, 62, 122, 0.35) 79%, rgba(31, 62, 122, 0) 100%); |
| | | opacity: 0.85; |
| | | // margin: 2px 0 13 0; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | padding: 10px 10px; |
| | | } |
| | | </style> |
| | |
| | | let connectWs = ref(null) |
| | | // 单个机巢信息 |
| | | const singleUavHome = computed(() => store.state.home.singleUavHome) |
| | | const selectedAreaCode = computed(() => store.state.user.selectedAreaCode) |
| | | |
| | | let osdVisible = ref({}) |
| | | |