Merge branch 'refs/heads/feature/v2.0.0/后台管理数据中心'
# Conflicts:
# src/utils/util.js
# src/views/wel/components/calendarBox.vue
6 files modified
36 files added
| | |
| | | |
| | | # 服务地址 |
| | | VITE_APP_URL = https://wrj.shuixiongit.com/api |
| | | # VITE_APP_URL= http://192.168.1.7 |
| | | |
| | | #VITE_APP_URL= http://192.168.1.168 rjg |
| | | #VITE_APP_URL= http://192.168.1.33 |
| | | #新大屏地址 |
| | | VITE_APP_DASHBOARD_URL = 'https://wrj.shuixiongit.com/command-center-dashboard/' |
| | | |
| New file |
| | |
| | | import request from '@/axios'; |
| | | // 列表接口 |
| | | export const getaiImagesPageAPI = (data,params) => { |
| | | return request({ |
| | | url: `/blade-resource//attach/attachmentsPage`, |
| | | method: 'post', |
| | | data,params |
| | | }) |
| | | }; |
| | | // 详情接口 |
| | | export const getAttachInfoAPI = (id) => { |
| | | return request({ |
| | | url: `/blade-resource/attach/getAttachInfo`, |
| | | method: 'get', |
| | | params: { |
| | | id, |
| | | }, |
| | | }) |
| | | }; |
| | | //删除 |
| | | export const deleteFileMultipleApi = (ids) => { |
| | | return request({ |
| | | url: `/blade-resource/attach/remove`, |
| | | method: 'post', |
| | | params: { |
| | | ids, |
| | | }, |
| | | }) |
| | | } |
| | | // 下载 |
| | | export const downloadApi = (data) => { |
| | | return request({ |
| | | url: `/blade-resource/attach/download`, |
| | | method: 'post', |
| | | data |
| | | }) |
| | | } |
| | | // 地图 |
| | | export const getMapInfoAPI = (jobId) => { |
| | | return request({ |
| | | url: `/blade-resource/attach/getAttachInfoByJobId`, |
| | | method: 'get', |
| | | params: { |
| | | jobId, |
| | | }, |
| | | }) |
| | | }; |
| | | |
| | | //编辑文件名 |
| | | export const updataTitleApi = (data) => { |
| | | return request({ |
| | | url: `/blade-resource/attach/updateFileName`, |
| | | method: 'post', |
| | | params:data |
| | | }) |
| | | } |
| New file |
| | |
| | | /* |
| | | * @Author : yuan |
| | | * @Date : 2025-06-05 09:49:42 |
| | | * @LastEditors : yuan |
| | | * @LastEditTime : 2025-06-07 18:16:57 |
| | | * @FilePath : \src\api\panorama\index.js |
| | | * @Description : |
| | | * Copyright 2025 OBKoro1, All Rights Reserved. |
| | | * 2025-06-05 09:49:42 |
| | | */ |
| | | import request from '@/axios' |
| | | |
| | | export const getPanoramaList = (data) => { |
| | | return request({ |
| | | url: `/blade-resource/attach/mapAttachs`, |
| | | method: 'post', |
| | | data |
| | | }) |
| | | } |
| | | |
| | | export const getPanoramaDetails = (id) => { |
| | | return request({ |
| | | url: `/blade-resource/attach/detail?id=${id}`, |
| | | method: 'get' |
| | | }) |
| | | } |
| New file |
| | |
| | | <!-- |
| | | * @Author : yuan |
| | | * @Date : 2025-06-05 15:57:45 |
| | | * @LastEditors : yuan |
| | | * @LastEditTime : 2025-06-06 14:02:54 |
| | | * @FilePath : \src\components\PanoramaPopup\PanoramaPopup.vue |
| | | * @Description : |
| | | * Copyright 2025 OBKoro1, All Rights Reserved. |
| | | * 2025-06-05 15:57:45 |
| | | --> |
| | | <template> |
| | | <el-dialog |
| | | modal-class="showFullScreenDlg" |
| | | v-model="panoramaParamsShow" |
| | | :close-on-click-modal="false" |
| | | :destroy-on-close="true" |
| | | fullscreen |
| | | > |
| | | <iframe :src="iframeSrc" frameborder="0"></iframe> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { getPanoramaDetails } from '@/api/panorama/index' |
| | | import { getShowImg } from '@/utils/util' |
| | | |
| | | import EventBus from '@/utils/eventBus' |
| | | |
| | | const panoramaParamsShow = defineModel('panoramaParamsShow', { |
| | | default: false, |
| | | }) |
| | | const panoramaParamsUrl = defineModel('panoramaParamsUrl', { |
| | | default: '', |
| | | }) |
| | | const iframeSrc = computed(() => { |
| | | console.log('panoramaParamsShow',panoramaParamsShow.value ,panoramaParamsUrl.value); |
| | | |
| | | if (!panoramaParamsUrl.value) return '' |
| | | |
| | | return `https://wrj.shuixiongit.com/dronePanorama/html/simple-index.html?path=${getShowImg(panoramaParamsUrl.value)}` |
| | | }) |
| | | |
| | | const initPanorama = id => { |
| | | getPanoramaDetails(id).then(res => { |
| | | panoramaParamsUrl.value = res.data.data.link |
| | | panoramaParamsShow.value = true |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | EventBus.on('initPanorama', initPanorama) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | EventBus.off('initPanorama', initPanorama) |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped></style> |
| New file |
| | |
| | | <template> |
| | | <div class="mapPopUpBox"> |
| | | <div class="title"> |
| | | <span>{{ info.event_name }}</span> |
| | | <el-icon class="header-close" @click.stop="props.detailClick"><Warning /></el-icon> |
| | | <el-icon class="header-close" @click.stop="props.removeLabel"> |
| | | <Close /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="content"> |
| | | <div class="medium"> |
| | | <img |
| | | class="quanjing" |
| | | @click="clickpanorama(infoList)" |
| | | v-if="infoList?.resultType === 5" |
| | | :src="infoList?.link" |
| | | alt="" |
| | | /> |
| | | <el-image |
| | | v-else |
| | | class="eventImage" |
| | | :src="getSmallImg(infoList?.link)" |
| | | :preview-src-list="[getSmallImg(infoList?.link)]" |
| | | fit="cover" |
| | | preview-teleported |
| | | ></el-image> |
| | | </div> |
| | | |
| | | <div class="details"> |
| | | <div class="label">时间:</div> |
| | | <div class="value point"> |
| | | {{ |
| | | infoList?.create_time?.slice(5, 16).replace('-', '/') || |
| | | infoList?.createTime?.slice(5, 16).replace('-', '/') |
| | | }} |
| | | </div> |
| | | <div class="label">地点:</div> |
| | | <div class="value"> |
| | | {{ _.round(infoList?.metadata?.shootPosition?.lng, 3) }},{{ |
| | | _.round(infoList?.metadata?.shootPosition?.lat, 3) |
| | | }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <script setup> |
| | | import EventBus from '@/utils/eventBus'; |
| | | import { ElImage, ElIcon } from 'element-plus'; |
| | | import { Close, Warning } from '@element-plus/icons-vue'; |
| | | import _ from 'lodash'; |
| | | import { getShowImg, getSmallImg } from '@/utils/util'; |
| | | const props = defineProps(['data', 'removeLabel', 'detailClick']); |
| | | const loading = ref(true); |
| | | import PanoramaPopup from '@/components/PanoramaPopup/PanoramaPopup.vue'; //全景 |
| | | const emit = defineEmits(['update:panoramaParamsShow', 'update:panoramaParamsUrl']); |
| | | const info = ref({ |
| | | event_name: '', |
| | | status: 1, |
| | | url: '1', |
| | | longitude: '', |
| | | latitude: '', |
| | | create_time: '04/01 12:41', |
| | | }); |
| | | const infoList = props.data; |
| | | const clickpanorama = val => { |
| | | // 通过事件总线发送全景参数 |
| | | EventBus.emit('open-panorama', { |
| | | show: true, |
| | | url: val.link, |
| | | }); |
| | | // 保留原有事件触发,确保兼容性 |
| | | emit('update:panoramaParamsShow', true); |
| | | emit('update:panoramaParamsUrl', val.link); |
| | | }; |
| | | onMounted(async () => {}); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .mapPopUpBox { |
| | | width: 271px; |
| | | height: 153px; |
| | | // background: url('@/assets/images/dataCenter/datamap/popUpBox.png') no-repeat center / 100% 100%; |
| | | border-radius: 20px; |
| | | background-color: #fff; |
| | | |
| | | padding: 13px 13px 1px 13px; |
| | | pointer-events: all; |
| | | |
| | | .title { |
| | | font-family: YouSheBiaoTiHei, YouSheBiaoTiHei, serif; |
| | | font-weight: 400; |
| | | font-size: 18px; |
| | | line-height: 16px; |
| | | background: linear-gradient(180deg, #a8e5fb 0%, #e6f8ff 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | -moz-background-clip: text; |
| | | -moz-text-fill-color: transparent; |
| | | background-clip: text; |
| | | text-fill-color: transparent; |
| | | margin-bottom: 10px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: end; |
| | | |
| | | .header-close { |
| | | width: 20px; |
| | | height: 100%; |
| | | line-height: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | color: black; |
| | | pointer-events: all; |
| | | cursor: pointer; |
| | | } |
| | | } |
| | | |
| | | .content { |
| | | height: 102px; |
| | | width: 240px; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .medium { |
| | | height: 102px; |
| | | width: 120px; |
| | | margin-right: 10px; |
| | | |
| | | > img, |
| | | video, |
| | | div { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | } |
| | | |
| | | .details { |
| | | .label { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: black; |
| | | line-height: 16px; |
| | | margin-bottom: 2px; |
| | | } |
| | | |
| | | .value { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: black; |
| | | line-height: 16px; |
| | | margin-bottom: 2px; |
| | | } |
| | | |
| | | .point { |
| | | margin-bottom: 14px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | .avue--detail .el-form-item { |
| | | background-color: #fafafa; |
| | | } |
| | | |
| | | .showFullScreenDlg { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | |
| | | .el-dialog { |
| | | position: relative; |
| | | padding: 0; |
| | | overflow: hidden; |
| | | |
| | | .el-dialog__body { |
| | | width: 100%; |
| | | height: 100%; |
| | | |
| | | iframe { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | } |
| | | |
| | | .el-dialog__header { |
| | | height: 0; |
| | | overflow: hidden; |
| | | padding: 0; |
| | | |
| | | .el-dialog__headerbtn { |
| | | position: absolute; |
| | | left: 0; |
| | | top: 14px; |
| | | |
| | | .el-dialog__close { |
| | | color: #fff; |
| | | font-size: 35px; |
| | | z-index: 99; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | import mitt from 'mitt' |
| | | |
| | | const emitter = mitt() |
| | | |
| | | export default emitter |
| 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-offline.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 eventPending1 from '@/assets/images/home/useEventOperate/eventPending1.png' // 待处理 0 |
| | | import eventPending from '@/assets/images/home/useEventOperate/eventPending.png' // 待处理 0 |
| | | import eventWaitAudit1 from '@/assets/images/home/useEventOperate/eventWaitAudit1.png' // 待审核 2 |
| | | import eventWaitAudit from '@/assets/images/home/useEventOperate/eventWaitAudit.png' // 待审核 2 |
| | | import eventProcessing1 from '@/assets/images/home/useEventOperate/eventProcessing1.png' // 处理中 3 |
| | | import eventProcessing from '@/assets/images/home/useEventOperate/eventProcessing.png' // 处理中 3 |
| | | import eventCompleted1 from '@/assets/images/home/useEventOperate/eventCompleted1.png' // 已完成 4 |
| | | import eventCompleted from '@/assets/images/home/useEventOperate/eventCompleted.png' // 已完成 4 |
| | | import eventClosed1 from '@/assets/images/home/useEventOperate/eventClosed1.png' // 已完结 5 |
| | | import eventClosed from '@/assets/images/home/useEventOperate/eventClosed.png' // 已完结 5 |
| | | |
| | | |
| | | const eventImage = { |
| | | 0: eventPending, |
| | | 2: eventWaitAudit, |
| | | 3: eventProcessing, |
| | | 4: eventCompleted, |
| | | 5: eventClosed |
| | | } |
| | | const eventActiveImage = { |
| | | 0: eventPending1, |
| | | 2: eventWaitAudit1, |
| | | 3: eventProcessing1, |
| | | 4: eventCompleted1, |
| | | 5: eventClosed1 |
| | | } |
| | | |
| | | /** |
| | | * 根据事件状态获取图片资源 |
| | | * @param {*} status 状态 |
| | | * @returns |
| | | */ |
| | | export const getEventImage = (status) => eventImage[status] || eventCompleted |
| | | export const getEventActiveImage = (status) => eventActiveImage[status] || eventCompleted |
| | |
| | | * @returns {string} |
| | | */ |
| | | export const getSmallImg = url => { |
| | | if (!url) return '' |
| | | const lastDotIndex = url.lastIndexOf('.') |
| | | return `${url.substring(0, lastDotIndex)}_small${url.substring(lastDotIndex)}` |
| | | if (!url) return '' |
| | | const lastDotIndex = url.lastIndexOf('.') |
| | | return `${url.substring(0, lastDotIndex)}_small${url.substring(lastDotIndex)}` |
| | | } |
| | | /** |
| | | * 浏览器判断是否全屏 |
| | |
| | | // 设置默认范围 [上个月, 当前月] |
| | | return [new Date(lastMonthYear, lastMonth, 1), new Date(currentYear, currentMonth, 1)] |
| | | } |
| | | /** |
| | | * 获取中等缩略图路径 |
| | | * @param url |
| | | * @returns {string} |
| | | */ |
| | | export const getShowImg = url => { |
| | | if (!url) return '' |
| | | const lastDotIndex = url.lastIndexOf('.') |
| | | return `${url.substring(0, lastDotIndex)}_show${url.substring(lastDotIndex)}` |
| | | } |
| | | |
| | | // key驼峰转换为下划线 |
| | | export function camelToSnake (obj) { |
| New file |
| | |
| | | <template> |
| | | <el-dialog modal-class="mapDialog" v-model="isShow" width="80%"> |
| | | <div class="mapBox"> |
| | | <div v-if="isShow" id="dataCenterMap" class="ztzf-cesium"></div> |
| | | </div> |
| | | |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import EventBus from '@/utils/eventBus'; |
| | | import PanoramaPopup from '@/components/PanoramaPopup/PanoramaPopup.vue'; |
| | | import { getMapInfoAPI } from '@/api/dataCenter/dataCenter'; |
| | | import { useStore } from 'vuex'; |
| | | import { PublicCesium } from '@/utils/cesium/publicCesium'; |
| | | import { Cartesian3 } from 'cesium'; |
| | | import * as Cesium from 'cesium'; |
| | | import EventPopUpBox from '@/hooks/components/EventPopUpBox.vue'; |
| | | import { render, nextTick, watch, onMounted, onBeforeUnmount, shallowRef, ref, h } from 'vue'; |
| | | import panoramaPoint from '@/assets/images/panorama/panorama-point.png'; //全景图标 |
| | | import defaultIcon from '@/assets/images/dataCenter/datamap/eventCompleted.png'; //默认图标 |
| | | import activeIcon from '@/assets/images/dataCenter/datamap/activeevent.png'; // 激活图标 |
| | | import { getEventActiveImage, getEventImage } from '@/utils/stateToImageMap/event'; //点 |
| | | const emit = defineEmits(['lookDetail']); |
| | | const isShow = defineModel('show'); |
| | | const viewerRef = shallowRef(null); |
| | | let viewer = null; |
| | | const store = useStore(); |
| | | const currentAreaPosition = ref({ height: 1987280, latitude: 27.636112, longitude: 115.732975 }); |
| | | let handler = null; |
| | | const props = defineProps(['jobId', 'dotData']); |
| | | let currentClickEntity = null; |
| | | // 存储地图实体引用 |
| | | const dataPointEntities = ref([]); |
| | | const isMapInitialized = ref(false); //地图加载 |
| | | const dataPointList = ref([]); |
| | | const activeEntity = ref(null); // 当前激活的点 |
| | | // 获取弹框box |
| | | const detailId = ref(''); |
| | | const createLabelDom = data => { |
| | | detailId.value = data; |
| | | const vNode = h(EventPopUpBox, { data, removeLabel, detailClick }); |
| | | const tooltipContainer = document.createElement('div'); |
| | | tooltipContainer.id = 'mapPopUpBox'; |
| | | tooltipContainer.style.position = 'absolute'; |
| | | tooltipContainer.style.transform = 'translate(-50%,-125%)'; |
| | | tooltipContainer.style.pointerEvents = 'none'; |
| | | document.querySelector('#dataCenterMap')?.append(tooltipContainer); |
| | | render(vNode, tooltipContainer); |
| | | return tooltipContainer; |
| | | }; |
| | | // 弹框位置刷新 |
| | | const labelBoxUpdate = () => { |
| | | if (!currentClickEntity) return; |
| | | const mapPopUpBox = document.querySelector('#mapPopUpBox'); |
| | | let dom = mapPopUpBox |
| | | ? mapPopUpBox |
| | | : createLabelDom(currentClickEntity.properties?.customData._value.data || currentClickEntity); |
| | | const screenPosition = viewer?.scene.cartesianToCanvasCoordinates( |
| | | currentClickEntity?.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(labelBoxUpdate); |
| | | removeDom(); |
| | | }; |
| | | // 点击去到详情页面 |
| | | const detailClick = () => { |
| | | removeLabel(); |
| | | // 给父组件传值 |
| | | // emit('update:show', false); //关闭地图弹框 |
| | | emit('lookDetail', detailId.value); |
| | | }; |
| | | |
| | | // 恢复所有点的默认图标 |
| | | const restoreAllIcons = () => { |
| | | dataPointEntities.value.forEach(entity => { |
| | | if (entity.billboard) { |
| | | entity.billboard.image = |
| | | props.dotData.resultType === 2 ? getEventImage(entity.status) : defaultIcon; |
| | | } |
| | | }); |
| | | activeEntity.value = null; |
| | | }; |
| | | // 左键单机事件 |
| | | const singleMachineEvent = async click => { |
| | | let clickedEntities = click |
| | | ? viewer?.scene.drillPick(click.position).map(item => item.id) |
| | | : [currentClickEntity]; |
| | | if (!clickedEntities.length) { |
| | | // 点击空白处恢复所有图标并移除弹窗 |
| | | restoreAllIcons(); |
| | | removeLabel(); |
| | | return; |
| | | } |
| | | currentClickEntity = clickedEntities[0]; |
| | | // 恢复所有点的默认图标 |
| | | restoreAllIcons(); |
| | | if (currentClickEntity.billboard) { |
| | | currentClickEntity.billboard.image = |
| | | props.dotData.resultType === 2 ? getEventImage(currentClickEntity.status) : activeIcon; |
| | | currentClickEntity.billboard.scale = 1; // 可选缩放效果 |
| | | activeEntity.value = currentClickEntity; |
| | | } |
| | | |
| | | removeLabel(); |
| | | viewer.scene.postRender.addEventListener(labelBoxUpdate); |
| | | }; |
| | | |
| | | // 事件初始化 |
| | | const handlerInit = () => { |
| | | if (handler) return; |
| | | handler = new Cesium.ScreenSpaceEventHandler(viewer?.scene.canvas); |
| | | handler.setInputAction(singleMachineEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
| | | }; |
| | | |
| | | // 清除所有数据点实体 |
| | | const clearDataPoints = () => { |
| | | if (!viewer) return; |
| | | dataPointEntities.value.forEach(entity => { |
| | | viewer.entities.remove(entity); |
| | | }); |
| | | dataPointEntities.value = []; |
| | | activeEntity.value = null; |
| | | }; |
| | | |
| | | const removeHandler = () => { |
| | | handler?.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK); |
| | | handler?.destroy(); |
| | | handler = null; |
| | | }; |
| | | |
| | | const renderDataPoint = mapList => { |
| | | if (!viewer || !mapList?.length) return; |
| | | |
| | | // 清除旧实体 |
| | | clearDataPoints(); |
| | | |
| | | // 添加新实体 |
| | | mapList.forEach((item, index) => { |
| | | const entity = viewer?.entities.add({ |
| | | id: `dataCenter-point-${index}-${Date.now()}`, |
| | | position: Cesium.Cartesian3.fromDegrees( |
| | | Number(item.metadata.shootPosition.lng), |
| | | Number(item.metadata.shootPosition.lat) |
| | | ), |
| | | label: { |
| | | font: '12pt Source Han Sans CN', |
| | | fillColor: Cesium.Color.WHITE, |
| | | outlineColor: Cesium.Color.BLACK, |
| | | outlineWidth: 2, |
| | | style: Cesium.LabelStyle.FILL_AND_OUTLINE, |
| | | verticalOrigin: Cesium.VerticalOrigin.BOTTOM, |
| | | eyeOffset: new Cesium.Cartesian3(0, 0, -9), |
| | | pixelOffset: new Cesium.Cartesian2(0, 55), |
| | | }, |
| | | billboard: { |
| | | image: props.dotData.resultType === 2 ? getEventImage(item.status) : defaultIcon, // 初始为默认图标 |
| | | width: 40, |
| | | height: 40, |
| | | pixelOffset: new Cesium.Cartesian2(0, -15), |
| | | }, |
| | | properties: { |
| | | customData: { |
| | | data: item, |
| | | }, |
| | | }, |
| | | }); |
| | | // 点击定位点位触发 |
| | | if (props.dotData.id === item.id) { |
| | | currentClickEntity = entity; |
| | | activeEntity.value = entity; |
| | | singleMachineEvent(); |
| | | } |
| | | |
| | | dataPointEntities.value.push(entity); |
| | | }); |
| | | }; |
| | | const initMap = () => { |
| | | if (viewer || isMapInitialized.value) return; |
| | | |
| | | const container = document.getElementById('dataCenterMap'); |
| | | if (!container) { |
| | | console.error('地图容器未找到'); |
| | | return; |
| | | } |
| | | try { |
| | | const publicCesiumInstance = new PublicCesium({ |
| | | dom: 'dataCenterMap', |
| | | flatMode: false, |
| | | terrain: false, |
| | | mapFilter: true, |
| | | }); |
| | | viewer = publicCesiumInstance.getViewer(); |
| | | viewerRef.value = viewer; |
| | | |
| | | isViewerReady.value = true; |
| | | |
| | | // 初始化事件处理器 |
| | | handlerInit(); |
| | | isMapInitialized.value = true; |
| | | console.log('地图初始化完成'); |
| | | // 初始化后立即渲染已有数据 |
| | | if (dataPointList.value.length > 0) { |
| | | renderDataPoint(dataPointList.value); |
| | | } |
| | | } catch (error) { |
| | | console.error('地图初始化失败:', error); |
| | | } |
| | | }; |
| | | const flyToEntity = position => { |
| | | const longitude = Number(position.lng); |
| | | const latitude = Number(position.lat); |
| | | viewer?.camera.flyTo({ |
| | | destination: Cesium.Cartesian3.fromDegrees(longitude, latitude, 1000), |
| | | duration: 1, |
| | | orientation: { |
| | | heading: Cesium.Math.toRadians(0.0), |
| | | pitch: Cesium.Math.toRadians(-90.0), |
| | | roll: 0.0, |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | // 地图接口 |
| | | const loading = ref(false); |
| | | const getMapInfoAPIFun = async ids => { |
| | | try { |
| | | const res = await getMapInfoAPI(ids); |
| | | dataPointList.value = res.data.data || []; |
| | | console.log('dataPointList.value',dataPointList.value); |
| | | |
| | | // 确保地图已初始化后再渲染 |
| | | if (isMapInitialized.value && viewer) { |
| | | renderDataPoint(dataPointList.value); |
| | | } |
| | | } catch (error) { |
| | | console.error('获取地图数据失败:', error); |
| | | } |
| | | }; |
| | | const isViewerReady = ref(false); |
| | | |
| | | /** |
| | | * 初始化标注添加 |
| | | * @param data 数据 |
| | | */ |
| | | const initEntityOrPopup = data => { |
| | | //地图点在范围内 |
| | | watch( |
| | | () => isMapInitialized.value, |
| | | ready => { |
| | | if (ready) { |
| | | flyToEntity(data.metadata.shootPosition); |
| | | labelBoxUpdate(); |
| | | } |
| | | }, |
| | | { deep: true, immediate: true } // 初始化时立即执行 |
| | | ); |
| | | }; |
| | | watch( |
| | | () => props.jobId, |
| | | newVal => { |
| | | if (newVal) { |
| | | getMapInfoAPIFun(newVal); |
| | | } |
| | | }, |
| | | { immediate: true } |
| | | ); |
| | | |
| | | // 监听对话框状态 |
| | | watch(isShow, newVal => { |
| | | if (newVal) { |
| | | nextTick(() => { |
| | | initMap(); |
| | | }); |
| | | } else { |
| | | // 清理资源 |
| | | if (viewer) { |
| | | viewer.destroy(); |
| | | viewer = null; |
| | | } |
| | | isMapInitialized.value = false; |
| | | removeHandler(); |
| | | clearDataPoints(); |
| | | } |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | |
| | | }); |
| | | onBeforeUnmount(() => { |
| | | if (viewer) { |
| | | viewer.destroy(); |
| | | } |
| | | removeHandler(); |
| | | clearDataPoints(); |
| | | |
| | | }); |
| | | |
| | | // 暴露方法给父组件 |
| | | defineExpose({ |
| | | initEntityOrPopup, |
| | | }); |
| | | </script> |
| | | |
| | | <style> |
| | | .mapDialog .el-dialog__body { |
| | | height: 700px !important; |
| | | padding: 0 !important; |
| | | } |
| | | </style> |
| | | |
| | | <style scoped lang="scss"> |
| | | .mapBox { |
| | | z-index: 2; |
| | | height: 650px; |
| | | width: 98%; |
| | | position: absolute; |
| | | left: 13px; |
| | | top: 50px; |
| | | |
| | | #dataCenterMap { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <div class="search-box-test"> |
| | | <el-form :model="searchForm" inline> |
| | | <div class="search-first"> |
| | | <el-form-item label="行政区划:"> |
| | | <el-tree-select |
| | | popper-class="custom-tree-select" |
| | | v-model="searchForm.areaCode" |
| | | :data="deptTreeData" |
| | | :default-expanded-keys="[searchForm.areaCode]" |
| | | check-strictly |
| | | node-key="id" |
| | | :props="treeProps" |
| | | @node-click="handleNodeClick" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="所属机巢:"> |
| | | <el-select |
| | | :teleported="false" |
| | | v-model="searchForm.deviceSn" |
| | | placeholder="请选择" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in machineData" |
| | | :key="item.device_sn" |
| | | :label="item.nickname" |
| | | :value="item.device_sn" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="任务名称:"> |
| | | <el-input v-model="searchForm.jobName" placeholder="请输入" clearable /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-date-picker |
| | | popper-class="custom-date-picker" |
| | | v-model="dateRange" |
| | | type="daterange" |
| | | range-separator="至" |
| | | start-placeholder="开始日期" |
| | | end-placeholder="结束日期" |
| | | :value-format="timeFormat" |
| | | @change="handleDateChange" |
| | | :popper-options="{ |
| | | modifiers: [ |
| | | { |
| | | name: 'flip', |
| | | options: { |
| | | fallbackPlacements: ['bottom-start'], // 优先向下弹出 |
| | | allowedAutoPlacements: ['bottom-start'], // 禁止其他方向 |
| | | }, |
| | | }, |
| | | ], |
| | | }" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="文件格式:"> |
| | | <el-select |
| | | :teleported="false" |
| | | v-model="searchForm.resultType" |
| | | placeholder="请选择" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in fileFormatOption" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="文件类别:"> |
| | | <el-select |
| | | :teleported="false" |
| | | v-model="searchForm.photoType" |
| | | placeholder="请选择" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in CategoryOption" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </div> |
| | | <div class="search-first"> |
| | | <el-form-item label="文件名称:"> |
| | | <el-input v-model="searchForm.name" placeholder="请输入" clearable /> |
| | | </el-form-item> |
| | | <div class="search-btn"> |
| | | <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button> |
| | | <el-button icon="el-icon-refresh" @click="handleReset">清空</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="search-first"> |
| | | <div class="search-btn"> |
| | | <el-button type="primary" icon="el-icon-download" @click="allDownloadFun">全部下载</el-button> |
| | | <el-button type="success" plain icon="el-icon-download" @click="downloadFun">下载</el-button> |
| | | </div> |
| | | </div> |
| | | </el-form> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { pxToRem } from '@/utils/rem'; |
| | | import { ElMessage } from 'element-plus'; |
| | | import dayjs from 'dayjs'; |
| | | import { useStore } from 'vuex'; |
| | | import { getRegionTreeAll, getDeviceRegion, deptsByAreaCode } from '@/api/job/task'; |
| | | const store = useStore(); |
| | | const userAreaCode = computed(() => store.getters.userInfo.detail.areaCode); |
| | | const selectedAreaCode = computed(() => store.state.user.selectedAreaCode); |
| | | const emit = defineEmits(['search','downFun','allDownFun']); |
| | | const treeProps = { |
| | | label: 'name', |
| | | value: 'id', |
| | | children: 'childrens', |
| | | }; |
| | | const fileFormatOption = [ |
| | | { |
| | | value: '', |
| | | label: '全部', |
| | | }, |
| | | { |
| | | value: '0', |
| | | label: '照片', |
| | | }, |
| | | { |
| | | value: '1', |
| | | label: '视频', |
| | | }, |
| | | { |
| | | value: '2', |
| | | label: 'AI识别', |
| | | }, |
| | | { |
| | | value: '5', |
| | | label: '全景', |
| | | }, |
| | | { |
| | | value: '4', |
| | | label: '正射', |
| | | }, |
| | | ]; |
| | | const CategoryOption = [ |
| | | { |
| | | value: 'visible', |
| | | label: '可见光', |
| | | }, |
| | | { |
| | | value: 'ir', |
| | | label: '红外', |
| | | }, |
| | | ]; |
| | | const startTime = dayjs().subtract(6, 'day').startOf('day') |
| | | const endTime = dayjs().endOf('day') |
| | | const timeRange = [startTime.format('YYYY-MM-DD HH:mm:ss'), endTime.format('YYYY-MM-DD HH:mm:ss')] |
| | | const dateRange = ref(timeRange); |
| | | const timeFormat = 'YYYY-MM-DD HH:mm:ss'; |
| | | const searchForm = reactive({ |
| | | jobName: '', //任务名称 |
| | | name: '', //文件名称 |
| | | areaCode: userAreaCode.value, // 区域code |
| | | endTime: endTime.format(timeFormat), // 结束时间 |
| | | startTime: startTime.format(timeFormat), // 开始时间 |
| | | deviceSn: '', // 所属机巢 |
| | | resultType: '', //文件格式 |
| | | photoType: '', //文件类别 |
| | | }); |
| | | |
| | | // 部门 |
| | | let deptTreeData = ref([]); |
| | | // 机巢 |
| | | let machineData = ref([]); |
| | | let deptData = ref([]); |
| | | // 从单机巢任务传参值 |
| | | const signDevice_sn = ref(''); |
| | | // 日期限制 |
| | | const handleDateChange = val => { |
| | | if (val && val.length === 2) { |
| | | const start = dayjs(val[0]); |
| | | const end = dayjs(val[1]); |
| | | const diff = end.diff(start, 'day'); |
| | | |
| | | if (diff > 31) { |
| | | ElMessage.warning('日期范围不能超过31天'); |
| | | dateRange.value = []; |
| | | // 重置为默认时间范围 |
| | | setTimeout(() => { |
| | | dateRange.value = timeRange; |
| | | }, 0); |
| | | return; |
| | | } |
| | | |
| | | // 更新搜索表单中的时间 |
| | | searchForm.startTime = start.format(timeFormat); |
| | | searchForm.endTime = end.format(timeFormat); |
| | | } |
| | | handleSearch(); |
| | | }; |
| | | // 部门下得机巢 |
| | | const requestDockInfo = () => { |
| | | getRegionTreeAll({ parentCode: userAreaCode.value }).then(res => { |
| | | deptTreeData.value = res.data.data ? [res.data.data] : []; |
| | | |
| | | handleNodeClick({ id: userAreaCode.value }); |
| | | }); |
| | | }; |
| | | const handleNodeClick = async data => { |
| | | // 处理机巢数据 |
| | | searchForm.deviceSn = ''; |
| | | machineData.value = ''; |
| | | |
| | | const droneList = await getDeviceRegion({ areaCode: data.id }); |
| | | |
| | | machineData.value = droneList?.data?.data; |
| | | // 默认选中值 |
| | | if (signDevice_sn.value) { |
| | | searchForm.deviceSn = signDevice_sn.value; |
| | | } |
| | | // 所属部门重新请求值 |
| | | |
| | | deptData.value = []; |
| | | getDeptsByAreaCode(); |
| | | }; |
| | | // 所属部门信息 |
| | | const getDeptsByAreaCode = () => { |
| | | deptsByAreaCode(searchForm.areaCode).then(res => { |
| | | if (res.code !== 0) { |
| | | deptData.value = res.data.data; |
| | | } |
| | | }); |
| | | }; |
| | | // 搜索 |
| | | const handleSearch = () => { |
| | | if (!dateRange.value) { |
| | | dateRange.value = []; |
| | | } |
| | | let params = { |
| | | ...searchForm, |
| | | startTime: dateRange.value.length |
| | | ? dayjs(dateRange?.value[0]).startOf('day').format(timeFormat) |
| | | : null, |
| | | endTime: dateRange.value.length |
| | | ? dayjs(dateRange?.value[1]).endOf('day').format(timeFormat) |
| | | : null, |
| | | }; |
| | | console.log('searchForm', params); |
| | | // 调用父组件方法 |
| | | emit('search', params); |
| | | }; |
| | | // 清空 |
| | | const handleReset = () => { |
| | | dateRange.value = []; |
| | | Object.keys(searchForm).forEach(key => { |
| | | searchForm[key] = ''; |
| | | }); |
| | | handleNodeClick({ id: userAreaCode.value }); |
| | | handleSearch(); |
| | | }; |
| | | // 下载 |
| | | const downloadFun =()=>{ |
| | | emit('downFun'); |
| | | } |
| | | // 全部下载 |
| | | const allDownloadFun =()=>{ |
| | | emit('allDownFun'); |
| | | } |
| | | onMounted(() => { |
| | | requestDockInfo(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .search-box-test { |
| | | transition: all 0.3s; |
| | | |
| | | .search-first { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | align-items: center; |
| | | gap: 20px 7px; // 设置行间距和列间距 |
| | | margin-bottom: 10px; |
| | | justify-content: space-between; |
| | | |
| | | .search-btn { |
| | | margin-right: 32px; |
| | | } |
| | | .time-card { |
| | | text-align: center; |
| | | background: #ffffff; |
| | | border-radius: 4px 0px 0px 4px; |
| | | border: 1px solid #e5e5e5; |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #7c8091; |
| | | display: flex; |
| | | height: 32px; |
| | | |
| | | .card-item { |
| | | width: 60px; |
| | | height: 100%; |
| | | line-height: 32px; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .active { |
| | | background: #ffffff; |
| | | border-radius: 0px 0px 0px 0px; |
| | | border: 1px solid #1c5cff; |
| | | color: #1441ff; |
| | | } |
| | | } |
| | | } |
| | | |
| | | :deep(.el-form) { |
| | | :deep(.el-input__wrapper.is-disabled) { |
| | | box-shadow: 0 0 0 1px #026ad6; |
| | | } |
| | | |
| | | .el-form-item { |
| | | margin-bottom: 0; |
| | | width: 233px; |
| | | |
| | | .el-form-item__label { |
| | | color: #363636; |
| | | line-height: 32px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <div class="dataCenter-table"> |
| | | <searchData |
| | | @search="searchClick" |
| | | @downFun="downloadFile" |
| | | @allDownFun="aLLDownloadFile" |
| | | ></searchData> |
| | | <!-- 表格部分 --> |
| | | <div class="dataTable"> |
| | | <el-table |
| | | v-loading="loading" |
| | | element-loading-text="加载中" |
| | | stripe |
| | | :data="tableData" |
| | | class="custom-header" |
| | | @selection-change="handleSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="55" /> |
| | | <el-table-column label="序号" type="index" width="60"> |
| | | <template #default="{ $index }"> |
| | | {{ |
| | | ($index + 1 + (jobListParams.current - 1) * jobListParams.size) |
| | | .toString() |
| | | .padStart(2, '0') |
| | | }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="regionName" label="所属区域" /> |
| | | <el-table-column property="nestName" label="所属机巢" /> |
| | | <el-table-column property="jobName" label="任务名称" show-overflow-tooltip /> |
| | | <el-table-column prop="nickName" label="文件名称" show-overflow-tooltip /> |
| | | <el-table-column property="link" label="缩图" width="120"> |
| | | <template #default="scope"> |
| | | <img |
| | | class="quanjing" |
| | | @click="clickpanorama(scope.row)" |
| | | v-if="scope.row?.resultType === 5" |
| | | :src="scope.row?.link" |
| | | alt="" |
| | | /> |
| | | <img |
| | | v-else-if="scope.row?.resultType === 1" |
| | | :src="convertVideoUrlToThumbnail(scope.row?.link)" |
| | | alt="" |
| | | class="imageBox" |
| | | @click="enterFullScreen(scope.row)" |
| | | /> |
| | | <el-image |
| | | v-else |
| | | :src="scope.row?.smallUrl" |
| | | :preview-src-list="[scope.row?.showUrl]" |
| | | fit="cover" |
| | | preview-teleported |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="jobTime" label="任务时间" /> |
| | | <el-table-column property="photoType" label="文件类别"> |
| | | <template #default="scope"> |
| | | <span>{{ photoTypeMap[scope.row.photoType] }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column property="resultType" label="文件格式"> |
| | | <template #default="{ row }"> |
| | | <span>{{ resultTypeMap[row?.resultType] }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="150" align="center"> |
| | | <template #default="scope"> |
| | | <span class="look" @click="lookDetail(scope.row)">查看</span> |
| | | <span class="delete" @click="deleteDetail(scope.row)" v-if="scope.row.resultType !== 2" |
| | | >删除</span |
| | | > |
| | | <span |
| | | class="location" |
| | | @click="positionDetail(scope.row)" |
| | | v-if="scope.row.resultType !== 1" |
| | | >定位</span |
| | | > |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | <!-- 分页 --> |
| | | <div class="pagination"> |
| | | <el-pagination |
| | | v-model:current-page="jobListParams.current" |
| | | v-model:page-size="jobListParams.size" |
| | | :page-sizes="[10, 20, 30, 40]" |
| | | background |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :total="total" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handleCurrentChange" |
| | | /> |
| | | </div> |
| | | <!-- 查看弹框 --> |
| | | <el-dialog v-model="dialogVisible" width="60%" append-to-body> |
| | | <template #header="{ titleId, titleClass }"> |
| | | <div class="my-header"> |
| | | <h4 :id="titleId" :class="titleClass">{{ detailTitle}}</h4> |
| | | |
| | | </div> |
| | | </template> |
| | | <div class="detailContainer"> |
| | | <div class="leftImg"> |
| | | <img |
| | | v-if="dialogDetailList?.resultType === 1" |
| | | :src="convertVideoUrlToThumbnail(dialogDetailList?.link)" |
| | | alt="" |
| | | class="imageBox" |
| | | /> |
| | | <img v-else :src="getSmallImg(dialogDetailList?.link)" alt="" /> |
| | | </div> |
| | | <div class="rightDetail"> |
| | | <div class="title"> |
| | | <div class="inputEdit"> |
| | | 文件名称:<span class="fileTitle" v-if="!dialogDetailList?.checkedinput">{{ |
| | | dialogDetailList?.nickName |
| | | }}</span> |
| | | <el-input |
| | | v-else |
| | | v-model="dialogDetailList.nickName" |
| | | @keyup.enter="saveTitle()" |
| | | class="title-input" |
| | | clearable |
| | | /> |
| | | </div> |
| | | <div class="editname" > |
| | | <span v-if="!dialogDetailList?.checkedinput" @click="editTitle(dialogDetailList)" |
| | | ><el-icon><Edit /></el-icon |
| | | ></span> |
| | | <div v-else class="suffixBoxEdit"> |
| | | <div class="editText" @click="submitEditSuffix(dialogDetailList)">✔</div> |
| | | <div class="editText" @click="cancelEditSuffix(dialogDetailList)">✖</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div>任务名称:{{ dialogDetailList?.jobName }}</div> |
| | | <div>所属区域:{{ dialogDetailList?.regionName }}</div> |
| | | <div>拍摄机巢:{{ dialogDetailList?.nestName }}</div> |
| | | <div> |
| | | 照片位置:{{ _.round(dialogDetailList?.longitude, 3) }},{{ |
| | | _.round(dialogDetailList?.latitude, 3) |
| | | }} |
| | | </div> |
| | | <div>任务时间:{{ dialogDetailList?.jobTime }}</div> |
| | | <div>拍摄时间:{{ dialogDetailList?.createTime }}</div> |
| | | <div>文件类型:{{ photoTypeMap[dialogDetailList?.photoType] }}</div> |
| | | <div>文件格式:{{ resultTypeMap[dialogDetailList?.resultType] }}</div> |
| | | <div>照片文件大小:{{ dialogDetailList?.attachSize }}</div> |
| | | <div> <el-button |
| | | type="success" |
| | | plain |
| | | icon="el-icon-download" |
| | | @click="detailDownLoad(dialogDetailList)" |
| | | >下载</el-button |
| | | ></div> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | <!-- 全景预览 --> |
| | | <PanoramaPopup |
| | | v-model:panoramaParamsShow="panoramaParamsShow" |
| | | v-model:panoramaParamsUrl="panoramaParamsUrl" |
| | | ></PanoramaPopup> |
| | | <!-- 视频预览 --> |
| | | <el-dialog |
| | | :title="currentVideoTitle" |
| | | modal-class="videoDialog" |
| | | append-to-body |
| | | width="54%" |
| | | v-model="VideoShow" |
| | | :close-on-click-modal="false" |
| | | :destroy-on-close="true" |
| | | @close="currentVideoIndex = -1" |
| | | > |
| | | <div class="video-container"> |
| | | <video |
| | | style="width: 100%" |
| | | class="videoBox" |
| | | ref="videoRefs" |
| | | controls |
| | | autoplay |
| | | :src="currentVideoUrl" |
| | | ></video> |
| | | </div> |
| | | </el-dialog> |
| | | <!-- 地图弹框 --> |
| | | <dataCenterMap |
| | | ref="mapComponent" |
| | | v-model:show="dataCenterMapVisible" |
| | | :jobId="jobId" |
| | | |
| | | @lookDetail="lookDetail" |
| | | :dotData="mapList" |
| | | ></dataCenterMap> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import EventBus from '@/utils/eventBus'; |
| | | import dataCenterMap from '@/views/dataCenter/components/dataCenterMap.vue'; |
| | | import PanoramaPopup from '@/components/PanoramaPopup/PanoramaPopup.vue'; //全景 |
| | | import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'; |
| | | import searchData from '@/views/dataCenter/components/searchData.vue'; |
| | | import fy1 from '@/assets/images/dataCenter/1.jpeg'; |
| | | import _ from 'lodash'; |
| | | import { |
| | | getaiImagesPageAPI, |
| | | getAttachInfoAPI, |
| | | deleteFileMultipleApi, |
| | | downloadApi, |
| | | updataTitleApi, |
| | | } from '@/api/dataCenter/dataCenter'; |
| | | import { getShowImg, getSmallImg } from '@/utils/util'; |
| | | import { onMounted, watch } from 'vue'; |
| | | import dayjs from 'dayjs'; |
| | | // 视频一帧 |
| | | function convertVideoUrlToThumbnail(videoUrl) { |
| | | // 检查是否是有效的视频URL |
| | | if (!videoUrl || typeof videoUrl !== 'string') { |
| | | return videoUrl; |
| | | } |
| | | // 替换文件扩展名 |
| | | const thumbnailUrl = videoUrl.replace(/\.mp4$/, '_small.jpg'); |
| | | return thumbnailUrl; |
| | | } |
| | | const resultTypeMap = { |
| | | 0: '照片', |
| | | 1: '视频', |
| | | 2: 'AI识别', |
| | | 5: '全景', |
| | | 4: '正射', |
| | | }; |
| | | const photoTypeMap = { |
| | | visible: '可见光', |
| | | ir: '红外', |
| | | }; |
| | | const loading = ref(true); |
| | | const total = ref(0); |
| | | const startTime = dayjs().subtract(6, 'day').startOf('day'); |
| | | const endTime = dayjs().endOf('day'); |
| | | const timeRange = [startTime.format('YYYY-MM-DD HH:mm:ss'), endTime.format('YYYY-MM-DD HH:mm:ss')]; |
| | | // 全景预览 |
| | | const panoramaParamsShow = ref(false); |
| | | const panoramaParamsUrl = ref(null); |
| | | const clickpanorama = val => { |
| | | panoramaParamsShow.value = true; |
| | | panoramaParamsUrl.value = val.link; |
| | | }; |
| | | // 视频 |
| | | const currentVideoTitle = ref(''); |
| | | const VideoShow = ref(false); |
| | | const currentVideoUrl = ref(null); |
| | | const enterFullScreen = val => { |
| | | currentVideoTitle.value = val?.nickName; |
| | | currentVideoUrl.value = val?.link; |
| | | VideoShow.value = true; |
| | | }; |
| | | const jobListParams = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | orderByCreateTime: true, |
| | | searchParams: { startTime: timeRange[0], endTime: timeRange[1] }, |
| | | }); |
| | | const tableData = ref([]); |
| | | |
| | | // 获取列表数据 |
| | | const getaiImagesPage = () => { |
| | | const params = { |
| | | orderByCreateTime: jobListParams.orderByCreateTime, |
| | | ...jobListParams.searchParams, |
| | | }; |
| | | getaiImagesPageAPI(params, { current: jobListParams.current, size: jobListParams.size }).then( |
| | | res => { |
| | | loading.value = true; |
| | | total.value = res.data.data.total; |
| | | tableData.value = res.data.data.records.map(i => ({ |
| | | ...i, |
| | | checked: false, |
| | | url: i?.link, |
| | | smallUrl: getSmallImg(i?.link), |
| | | showUrl: getShowImg(i?.link), |
| | | file_name: i.name.split('/').pop(), |
| | | })); |
| | | // console.log('res', tableData.value); |
| | | loading.value = false; |
| | | } |
| | | ); |
| | | }; |
| | | // 查询 |
| | | const searchClick = params => { |
| | | jobListParams.current = 1; |
| | | jobListParams.size = 10; |
| | | jobListParams.searchParams = params; |
| | | getaiImagesPage(); |
| | | }; |
| | | const handleSizeChange = val => { |
| | | jobListParams.size = val; |
| | | getaiImagesPage(); |
| | | }; |
| | | const handleCurrentChange = val => { |
| | | jobListParams.current = val; |
| | | getaiImagesPage(); |
| | | }; |
| | | // 多选 |
| | | const selectedRows = ref([]); |
| | | const handleSelectionChange = val => { |
| | | // 更新选中状态 |
| | | tableData.value.forEach(item => { |
| | | item.checked = val.some(selected => selected.id === item.id); |
| | | }); |
| | | |
| | | selectedRows.value = val; |
| | | }; |
| | | // 删除 |
| | | const deleteDetail = val => { |
| | | ElMessageBox.confirm('您确定删除吗?', '提示', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning', |
| | | }) |
| | | .then(() => { |
| | | deleteFileMultipleApi(val.id) |
| | | .then(res => { |
| | | ElMessage.success('删除成功'); |
| | | getaiImagesPage(); |
| | | }) |
| | | .catch(error => { |
| | | ElMessage.error('删除失败'); |
| | | }); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | // url下载 |
| | | function aLinkDownload(url, name) { |
| | | const a = document.createElement('a'); |
| | | a.style.display = 'none'; |
| | | a.href = url; |
| | | a.download = name; |
| | | document.body.appendChild(a); |
| | | a.click(); |
| | | document.body.removeChild(a); |
| | | } |
| | | const fileDownload = () => { |
| | | const list = selectedRows.value.filter(i => i.checked); |
| | | if (!list?.length) return ElMessage.warning('请选择文件'); |
| | | if (list.length === 1) { |
| | | list.forEach((item, index) => { |
| | | setTimeout(() => { |
| | | aLinkDownload(item.url, item?.nickName); |
| | | }, index * 500); // 每个文件下载间隔50毫秒 |
| | | }); |
| | | } else { |
| | | // loading = ElLoading.service({ background: 'rgba(0, 0, 0, 0.5)', text: '打包中,请稍等...' }) |
| | | const fileIds = list.map(i =>i.id); |
| | | console.log('fileIds', fileIds, list); |
| | | let aaa = { |
| | | areaCode: '', |
| | | attachIds: fileIds, |
| | | dockSn: '', |
| | | endTime: '', |
| | | fileName: '', |
| | | fileType: '', |
| | | jobName: '', |
| | | resultType: '', |
| | | startTime: '', |
| | | wayLineJobIds: [], |
| | | }; |
| | | |
| | | downloadApi(aaa).then(res => { |
| | | // console.log('res.data.data', res.data.data); |
| | | aLinkDownload(res.data.data, `sjzx-file-pack-${dayjs().format('YYYYMMDDHHmmss')}.zip`); |
| | | // loading.close() |
| | | }); |
| | | } |
| | | }; |
| | | // 下载 |
| | | const downloadFile = () => { |
| | | fileDownload(); |
| | | }; |
| | | const detailDownLoad = val => { |
| | | aLinkDownload(val.link, val?.nickName); |
| | | }; |
| | | // 全部下载 |
| | | const aLLDownloadFile = () => { |
| | | |
| | | const params = { |
| | | ...jobListParams.searchParams, |
| | | }; |
| | | // console.log('params',params); |
| | | |
| | | downloadApi(params).then(res => { |
| | | // console.log('res.data.data', res.data.data); |
| | | aLinkDownload(res.data.data, `sjzx-file-pack-${dayjs().format('YYYYMMDDHHmmss')}.zip`); |
| | | |
| | | }); |
| | | |
| | | }; |
| | | // 查看弹框 |
| | | const dialogVisible = ref(false); |
| | | const dialogDetailList = ref(null); |
| | | const detailTitle = ref('') |
| | | const lookDetail = val => { |
| | | getAttachInfoAPI(val.id).then(res => { |
| | | detailTitle.value = res.data.data.nickName |
| | | dialogDetailList.value = res.data.data; |
| | | dialogDetailList.value = { ...res.data.data, checkedinput: false }; |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const fileNameedit = ref(''); |
| | | // 编辑文件名 |
| | | const editTitle = val => { |
| | | val.checkedinput = true; |
| | | fileNameedit.value = val?.nickName; |
| | | }; |
| | | // 取消 |
| | | const cancelEditSuffix = item => { |
| | | item.nickName = fileNameedit.value; |
| | | item.checkedinput = false; |
| | | }; |
| | | const submitEditSuffix = item => { |
| | | saveTitle(item); |
| | | }; |
| | | // 通用空值检查函数 |
| | | const validateNickname = (name, fieldName) => { |
| | | if (!name || name.trim() === '') { |
| | | ElMessage.warning(`${fieldName}不能为空`); |
| | | return false; |
| | | } |
| | | if (name.length > 50) { |
| | | ElMessage.warning(`${fieldName}不能超过50个字符`); |
| | | return false; |
| | | } |
| | | return true; |
| | | }; |
| | | // 保存文件名 |
| | | const saveTitle = item => { |
| | | const updateparams = { |
| | | id: Number(item.id), |
| | | nickName: item.nickName, |
| | | }; |
| | | // 验证并提示 |
| | | if (!validateNickname(updateparams.nickName, '名称')) return; |
| | | item.checkedinput = false; |
| | | detailTitle.value = item.nickName |
| | | updataTitleApi(updateparams) |
| | | .then(res => { |
| | | if (res.status === 200) { |
| | | ElMessage.success('修改成功'); |
| | | } else { |
| | | ElMessage.error(res.data.message || '修改失败'); |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | ElMessage.error('请求失败,请稍后重试'); |
| | | console.error('API error:', error); |
| | | }); |
| | | }; |
| | | |
| | | // 地图弹框 |
| | | const mapComponent = ref(null);// 创建子组件引用 |
| | | const mapList = ref(null); |
| | | const dataCenterMapVisible = ref(false); |
| | | const jobId = ref(''); |
| | | const statusType = ref(null) |
| | | const positionDetail = val => { |
| | | // console.log('地图',val); |
| | | |
| | | jobId.value = val.wayLineJobId; |
| | | // console.log('statusType.value',statusType.value); |
| | | mapList.value = val; |
| | | dataCenterMapVisible.value = true; |
| | | // 确保地图组件加载完成 |
| | | nextTick(() => { |
| | | if (mapComponent.value) { |
| | | // 调用子组件方法并传递数据 |
| | | mapComponent.value.initEntityOrPopup(val) |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getaiImagesPage(); |
| | | // 监听打开全景事件 |
| | | EventBus.on('open-panorama', params => { |
| | | // console.log('收到全景事件:', params); |
| | | panoramaParamsShow.value = params.show; |
| | | panoramaParamsUrl.value = params.url; |
| | | }); |
| | | }); |
| | | onBeforeUnmount(() => { |
| | | // 组件卸载时移除事件监听,防止内存泄漏 |
| | | EventBus.off('open-panorama'); |
| | | }); |
| | | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .dataCenter-table { |
| | | margin: 0 18px 16px 10px; |
| | | background-color: #ffffff; |
| | | padding: 14px 18px; |
| | | .dataTable { |
| | | height: 700px; |
| | | overflow: auto; |
| | | .look { |
| | | color: #1c5cff; |
| | | cursor: pointer; |
| | | margin-right: 10px; |
| | | } |
| | | .delete { |
| | | color: #ff241c; |
| | | margin-right: 10px; |
| | | cursor: pointer; |
| | | } |
| | | .location { |
| | | color: #19876d; |
| | | cursor: pointer; |
| | | } |
| | | } |
| | | .pagination { |
| | | display: flex; |
| | | justify-content: end; |
| | | margin-top: 20px; |
| | | } |
| | | .quanjing, |
| | | .el-image, |
| | | .imageBox { |
| | | cursor: pointer; |
| | | width: 76px; |
| | | height: 72px; |
| | | } |
| | | .videoDialog :deep(.el-dialog) { |
| | | height: 600px; |
| | | width: 54%; |
| | | } |
| | | .video-container { |
| | | width: 100%; |
| | | aspect-ratio: 16/9; /* 按视频比例设置(如16:9) */ |
| | | overflow: hidden; /* 隐藏溢出部分 */ |
| | | } |
| | | .videoBox { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: contain; /* 保持比例完整显示 */ |
| | | display: block; |
| | | } |
| | | } |
| | | :deep(.custom-header th.el-table__cell) { |
| | | color: rgba(0, 0, 0, 0.85); |
| | | } |
| | | // 弹框 |
| | | .detailContainer { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | .leftImg { |
| | | width: 70%; |
| | | height: 500px; |
| | | img { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | } |
| | | .rightDetail { |
| | | width: 30%; |
| | | padding-left: 40px; |
| | | .title { |
| | | display: flex; |
| | | margin: 0 !important; |
| | | .editname { |
| | | cursor: pointer; |
| | | } |
| | | .inputEdit { |
| | | display: flex; |
| | | align-items: center; |
| | | width: 100%; |
| | | .fileTitle{ |
| | | width: 70%; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | } |
| | | } |
| | | div { |
| | | margin-bottom: 20px; |
| | | } |
| | | } |
| | | } |
| | | .my-header :deep(.el-dialog__title) { |
| | | margin: 0 !important; |
| | | height: 19px; |
| | | |
| | | } |
| | | .my-header { |
| | | display: flex; |
| | | align-items: center; |
| | | .el-button { |
| | | margin-left: 20px; |
| | | } |
| | | } |
| | | .title-input { |
| | | margin-bottom: 0 !important; |
| | | width: 70%; |
| | | } |
| | | .editname { |
| | | margin-bottom: 0 !important; |
| | | } |
| | | .suffixBoxEdit { |
| | | display: flex; |
| | | // align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 0 !important; |
| | | font-size: 16px; |
| | | > .editText { |
| | | cursor: pointer; |
| | | margin-right: 6px; |
| | | &:hover { |
| | | transform: scale(1.2); |
| | | } |
| | | } |
| | | .editimg { |
| | | cursor: pointer; |
| | | width: 15px; |
| | | height: 15px; |
| | | &:hover { |
| | | transform: scale(1.2); |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | import eventPending1 from '@/assets/images/home/useEventOperate/eventPending1.png' // 待处理 0 |
| | | import eventPending from '@/assets/images/home/useEventOperate/eventPending.png' // 待处理 0 |
| | | import eventWaitAudit1 from '@/assets/images/home/useEventOperate/eventWaitAudit1.png' // 待审核 2 |
| | | import eventWaitAudit from '@/assets/images/home/useEventOperate/eventWaitAudit.png' // 待审核 2 |
| | | import eventProcessing1 from '@/assets/images/home/useEventOperate/eventProcessing1.png' // 处理中 3 |
| | | import eventProcessing from '@/assets/images/home/useEventOperate/eventProcessing.png' // 处理中 3 |
| | | import eventCompleted1 from '@/assets/images/home/useEventOperate/eventCompleted1.png' // 已完成 4 |
| | | import eventCompleted from '@/assets/images/home/useEventOperate/eventCompleted.png' // 已完成 4 |
| | | import eventClosed1 from '@/assets/images/home/useEventOperate/eventClosed1.png' // 已完结 5 |
| | | import eventClosed from '@/assets/images/home/useEventOperate/eventClosed.png' // 已完结 5 |
| | | |
| | | |
| | | const eventImage = { |
| | | 0: eventPending, |
| | | 2: eventWaitAudit, |
| | | 3: eventProcessing, |
| | | 4: eventCompleted, |
| | | 5: eventClosed |
| | | } |
| | | const eventActiveImage = { |
| | | 0: eventPending1, |
| | | 2: eventWaitAudit1, |
| | | 3: eventProcessing1, |
| | | 4: eventCompleted1, |
| | | 5: eventClosed1 |
| | | } |
| | | |
| | | /** |
| | | * 根据事件状态获取图片资源 |
| | | * @param {*} status 状态 |
| | | * @returns |
| | | */ |
| | | export const getEventImage = (status) => eventImage[status] || eventCompleted |
| | | export const getEventActiveImage = (status) => eventActiveImage[status] || eventCompleted |
| | |
| | | return date.getDate(); |
| | | }; |
| | | |
| | | |
| | | // 获取对应日期的事件 |
| | | const getEvents = dateString => { |
| | | return events.value[dateString] || []; |
| | | }; |
| | | const monthRange = getCurrentMonthRange(); |
| | | params.value = monthRange; |
| | | console.log('params.value', params.value); |
| | | |
| | | |
| | | const getJobEventBar = () => { |
| | | getCalen(params.value).then(res => { |