| src/api/dataCenter/dataCenter.js | ●●●●● patch | view | raw | blame | history | |
| src/assets/images/dataCenter/datamap/activeevent.png | patch | view | raw | blame | history | |
| src/assets/images/dataCenter/datamap/eventCompleted.png | patch | view | raw | blame | history | |
| src/hooks/components/EventPopUpBox.vue | ●●●●● patch | view | raw | blame | history | |
| src/views/dataCenter/components/dataCenterMap.vue | ●●●●● patch | view | raw | blame | history | |
| src/views/dataCenter/components/searchData.vue | ●●●●● patch | view | raw | blame | history | |
| src/views/dataCenter/dataCenter.vue | ●●●●● patch | view | raw | blame | history |
src/api/dataCenter/dataCenter.js
@@ -1,10 +1,10 @@ import request from '@/axios'; // 列表接口 export const getaiImagesPageAPI = (data) => { export const getaiImagesPageAPI = (data,params) => { return request({ url: `/blade-resource//attach/attachmentsPage`, method: 'post', data data,params }) }; // 详情接口 @@ -34,4 +34,23 @@ method: 'post', data }) } // 地图 export const getMapInfoAPI = (jobId) => { return request({ url: `/blade-resource/attach/getAttachInfoByJobId`, method: 'get', params: { jobId, }, }) }; //编辑文件名 export const updataTitleApi = (params) => { return request({ url: `/blade-resource/attach/updateFileName`, method: 'post', params }) } src/assets/images/dataCenter/datamap/activeevent.pngsrc/assets/images/dataCenter/datamap/eventCompleted.pngsrc/hooks/components/EventPopUpBox.vue
@@ -1,8 +1,8 @@ <template> <div class="mapPopUpBox"> <div class="title"> <span>222</span> <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> @@ -11,8 +11,8 @@ <div class="medium"> <el-image class="eventImage" :src="infoList?.smallUrl" :preview-src-list="[infoList?.smallUrl]" :src="getSmallImg(infoList?.link)" :preview-src-list="[getSmallImg(infoList?.link)]" fit="cover" preview-teleported ></el-image> @@ -21,11 +21,16 @@ <div class="details"> <div class="label">时间:</div> <div class="value point"> 11 {{ infoList?.create_time?.slice(5, 16).replace('-', '/') || infoList?.createTime?.slice(5, 16).replace('-', '/') }} </div> <div class="label">地点:</div> <div class="value"> 22 {{ _.round(infoList?.metadata?.shootPosition?.lng, 3) }},{{ _.round(infoList?.metadata?.shootPosition?.lat, 3) }} </div> </div> </div> @@ -33,12 +38,10 @@ </template> <script setup> import { ElImage, ElIcon } from 'element-plus' import { Close } from '@element-plus/icons-vue' // import { getEventDetails } from '@/api/home/aggregation' import { Close,Warning } from '@element-plus/icons-vue' import _ from 'lodash' import { getSmallImg } from '@/utils/util' const props = defineProps(['data', 'removeLabel']) import { getShowImg, getSmallImg } from '@/utils/util'; const props = defineProps(['data', 'removeLabel','detailClick']) const loading = ref(true) const info = ref({ @@ -50,12 +53,7 @@ create_time: '04/01 12:41', }) const infoList = props.data onMounted(async () => {}) const disposeUrl = ({ url }) => { return getSmallImg(url) } </script> <style scoped lang="scss"> @@ -81,7 +79,7 @@ margin-bottom: 10px; display: flex; align-items: center; justify-content: space-between; justify-content: end; .header-close { width: 20px; src/views/dataCenter/components/dataCenterMap.vue
@@ -1,42 +1,55 @@ <template> <el-dialog modal-class="mapDialog" v-model="isShow" width="80%"> <el-dialog modal-class="mapDialog" v-model="isShow" width="80%"> <div class="mapBox"> <div id="dataCenterMap" class="ztzf-cesium"></div> <div v-if="isShow" id="dataCenterMap" class="ztzf-cesium"></div> </div> </el-dialog> </template> <script setup> 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 } from 'vue'; import defaultIcon from '@/assets/images/dataCenter/datamap/eventCompleted.png'; import { render, nextTick, watch, onMounted, onBeforeUnmount, shallowRef, ref, h } from 'vue'; import defaultIcon from '@/assets/images/dataCenter/datamap/eventCompleted.png'; //默认图标 import activeIcon from '@/assets/images/dataCenter/datamap/activeevent.png'; // 激活图标 const emit = defineEmits(['lookDetail']); const isShow = defineModel('show'); const viewerRef = shallowRef(null); let viewer = null; const viewInstance = shallowRef(null); const store = useStore(); const currentAreaPosition = ref({ height: 1987280, latitude: 27.636112, longitude: 115.732975 }); let handler = null; const props = defineProps(['jobId','mapList']); // 存储地图实体引用 const dataPointEntities = ref([]); const isMapInitialized = ref(false); //地图加载 const dataPointList = ref([]); const activeEntity = ref(null); // 当前激活的点 // 获取弹框box const detailId = ref('') const createLabelDom = data => { console.log('data',data); const vNode = h(EventPopUpBox, { data, removeLabel }) const tooltipContainer = document.createElement('div') tooltipContainer.id = 'mapPopUpBox' tooltipContainer.style.position = 'absolute' tooltipContainer.style.transform = 'translate(-50%,-135%)' tooltipContainer.style.pointerEvents = 'none' document.querySelector('#dataCenterMap').append(tooltipContainer) render(vNode, tooltipContainer) return tooltipContainer 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; }; let currentClickEntity = null; // 弹框位置刷新 const labelBoxUpdate = () => { if (!currentClickEntity) return; @@ -53,6 +66,10 @@ dom.style.display = 'block'; } }; // 暴露方法给父组件 defineExpose({ labelBoxUpdate }); const removeDom = () => { const dom = document.querySelector('#mapPopUpBox'); if (dom && dom.parentNode) { @@ -64,13 +81,48 @@ 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 = defaultIcon; } }); activeEntity.value = null; }; // 左键单机事件 const singleMachineEvent = async click => { let clickedEntities = viewer?.scene.drillPick(click.position).map(item => item.id); if (!clickedEntities.length) return; if (!clickedEntities.length) { // 点击空白处恢复所有图标并移除弹窗 restoreAllIcons(); removeLabel(); return; } currentClickEntity = clickedEntities[0]; // 恢复所有点的默认图标 restoreAllIcons(); // 设置当前点击点为激活状态 if (currentClickEntity.billboard) { currentClickEntity.billboard.image = activeIcon; activeEntity.value = currentClickEntity; } removeLabel(); viewer.scene.postRender.addEventListener(labelBoxUpdate); }; @@ -82,38 +134,36 @@ 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 dataPointList = [ { longitude: '115.85708286111111', latitude: '28.62837602777778', }, { longitude: '115.8566981111111', latitude: '28.624613027777777', }, { longitude: '115.85474438888889', latitude: '28.624930444444445', }, { longitude: '115.85518375', latitude: '28.62479547222222', }, ]; function renderDataPoint() { dataPointList.forEach((item, index) => { const sizeObj = { width: 30, height: 30 }; viewer.entities.add({ id: `renderDataPoint-${index}`, position: Cesium.Cartesian3.fromDegrees(Number(item.longitude), Number(item.latitude)), 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, @@ -121,11 +171,14 @@ outlineWidth: 2, style: Cesium.LabelStyle.FILL_AND_OUTLINE, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, pixelOffset: new Cesium.Cartesian2(0, -9), eyeOffset: new Cesium.Cartesian3(0, 0, -9), pixelOffset: new Cesium.Cartesian2(0, 55), }, billboard: { image: defaultIcon, ...sizeObj, image: defaultIcon, // 初始为默认图标 width: 40, height: 40, pixelOffset: new Cesium.Cartesian2(0, -15), }, properties: { customData: { @@ -133,67 +186,116 @@ }, }, }); dataPointEntities.value.push(entity); }); } }; const initMap = () => { const publicCesiumInstance = new PublicCesium({ dom: 'dataCenterMap', flatMode: false, terrain: false, mapFilter: true, }); viewerRef.value = publicCesiumInstance.getViewer(); viewer = publicCesiumInstance.getViewer(); const { longitude, latitude, height } = currentAreaPosition.value; const position = Cartesian3.fromDegrees(longitude, latitude, height); viewerRef.value.camera.flyTo({ duration: 0, destination: position, orientation: { heading: Cesium.Math.toRadians(0.0), pitch: Cesium.Math.toRadians(-90.0), roll: 0.0, }, }); viewInstance.value = publicCesiumInstance; 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; // 相机飞行 const { longitude, latitude, height } = currentAreaPosition.value const position = Cartesian3.fromDegrees(longitude, latitude, height) viewerRef.value.camera.flyTo({ duration: 0, destination: position, orientation: { heading: Cesium.Math.toRadians(0.0), pitch: Cesium.Math.toRadians(-90.0), roll: 0.0, }, }) // 初始化事件处理器 handlerInit(); isMapInitialized.value = true; console.log('地图初始化完成'); // 初始化后立即渲染已有数据 if (dataPointList.value.length > 0) { renderDataPoint(dataPointList.value); } } catch (error) { console.error('地图初始化失败:', error); } }; // 地图接口 const loading = ref(false); const getMapInfoAPIFun = async ids => { try { const res = await getMapInfoAPI(ids); dataPointList.value = res.data.data || []; // 确保地图已初始化后再渲染 if (isMapInitialized.value && viewer) { renderDataPoint(dataPointList.value); } } catch (error) { console.error('获取地图数据失败:', error); } }; watch(() => props.jobId, (newVal) => { if (newVal) { getMapInfoAPIFun(newVal); } }, { immediate: true }); // 监听对话框状态 watch(isShow, (newVal) => { if (newVal) { nextTick(() => { initMap(); handlerInit(); renderDataPoint(); }); } else { // 对话框关闭时清理资源 viewInstance.value?.viewerDestroy(); // 清理资源 if (viewer) { viewer.destroy(); viewer = null; } isMapInitialized.value = false; removeHandler(); clearDataPoints(); } }); onMounted(() => { nextTick(() => { initMap(); handlerInit(); renderDataPoint(); }); }); onMounted(() => {}); onBeforeUnmount(() => { viewInstance.value?.viewerDestroy(); if (viewer) { viewer.destroy(); } removeHandler(); clearDataPoints(); }); </script> <style > <style> .mapDialog .el-dialog__body { height: 700px !important; padding: 0 !important; } </style> <style scoped lang="scss"> <style scoped lang="scss"> .mapBox { z-index: 2; height: 650px; @@ -207,4 +309,4 @@ height: 100%; } } </style> </style> src/views/dataCenter/components/searchData.vue
@@ -121,6 +121,10 @@ children: 'childrens', }; const fileFormatOption = [ { value: '', label: '全部', }, { value: '0', label: '照片', @@ -134,7 +138,7 @@ label: 'AI识别', }, { value: '3', value: '5', label: '全景', }, { src/views/dataCenter/dataCenter.vue
@@ -34,21 +34,21 @@ <img class="quanjing" @click="clickpanorama(scope.row)" v-if="scope.row?.resultType === 3" :src="scope.row.link" v-if="scope.row?.resultType === 5" :src="scope.row?.link" alt="" /> <img v-else-if="scope.row?.resultType === 1" :src="convertVideoUrlToThumbnail(scope.row.link)" :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]" :src="scope.row?.smallUrl" :preview-src-list="[scope.row?.showUrl]" fit="cover" preview-teleported /> @@ -68,13 +68,13 @@ <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.fileformat !== 'AI识别'" <span class="delete" @click="deleteDetail(scope.row)" v-if="scope.row.resultType !== 2" >删除</span > <span class="location" @click="positionDetail()" v-if="scope.row.fileformat !== '视频'" <span class="location" @click="positionDetail(scope.row)" v-if="scope.row.resultType !== 1" >定位</span > </template> @@ -98,29 +98,35 @@ <el-dialog v-model="dialogVisible" width="60%" append-to-body> <template #header="{ titleId, titleClass }"> <div class="my-header"> <h4 :id="titleId" :class="titleClass">{{ dialogDetailList?.filename }}</h4> <el-button type="success" plain icon="el-icon-download">下载</el-button> <h4 :id="titleId" :class="titleClass">{{ dialogDetailList?.nickName }}</h4> <el-button type="success" plain icon="el-icon-download" @click="detailDownLoad(dialogDetailList)" >下载</el-button > </div> </template> <div class="detailContainer"> <div class="leftImg"> <img v-if="dialogDetailList?.resultType === 1" :src="convertVideoUrlToThumbnail(dialogDetailList.link)" :src="convertVideoUrlToThumbnail(dialogDetailList?.link)" alt="" class="imageBox" /> <img v-else :src="dialogDetailList.link" alt="" /> <img v-else :src="getSmallImg(dialogDetailList?.link)" alt="" /> </div> <div class="rightDetail"> <div class="title"> <div class="inputEdit"> 文件名称:<span v-if="!dialogDetailList.checkedinput">{{ dialogDetailList?.filename dialogDetailList.nickName }}</span> <el-input v-else v-model="dialogDetailList.filename" v-model="dialogDetailList.nickName" @keyup.enter="saveTitle()" class="title-input" clearable @@ -131,15 +137,19 @@ ><el-icon><Edit /></el-icon ></span> <div v-else class="suffixBoxEdit"> <div class="editText" @click="submitEditSuffix(item, index)">✔</div> <div class="editText" @click="cancelEditSuffix(item, index)">✖</div> <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?.nickName }}</div> <div>照片位置:{{ dialogDetailList?.longitude }},{{ dialogDetailList?.latitude }}</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> @@ -176,7 +186,13 @@ </div> </el-dialog> <!-- 地图弹框 --> <dataCenterMap v-model:show="dataCenterMapVisible"></dataCenterMap> <dataCenterMap ref="mapComponent" v-model:show="dataCenterMapVisible" :jobId="jobId" @lookDetail="lookDetail" :mapList="mapList" ></dataCenterMap> </div> </template> @@ -186,11 +202,13 @@ 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 } from 'vue'; @@ -209,7 +227,7 @@ 0: '照片', 1: '视频', 2: 'AI识别', 3: '全景', 5: '全景', 4: '正射', }; const photoTypeMap = { @@ -232,25 +250,26 @@ // 获取列表数据 const getaiImagesPage = () => { getaiImagesPageAPI({ current: jobListParams.current, size: jobListParams.size, const params = { orderByCreateTime: jobListParams.orderByCreateTime, ...jobListParams.searchParams, }).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; }); }; 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 => { @@ -262,13 +281,10 @@ const handleSizeChange = val => { jobListParams.size = val; getaiImagesPage(); console.log('jobListParams.size', jobListParams.size); }; const handleCurrentChange = val => { jobListParams.current = val; getaiImagesPage(); console.log('jobListParams.current', jobListParams.current); }; // 多选 const selectedRows = ref([]); @@ -328,6 +344,9 @@ const downloadFile = () => { fileDownload(); }; const detailDownLoad = val => { aLinkDownload(val.link, val.nickName); }; // 全部下载 const aLLDownloadFile = () => { console.log('全部下载'); @@ -335,11 +354,12 @@ // 查看弹框 const dialogVisible = ref(false); const dialogDetailList = ref(null); const lookDetail = val => { console.log('val',val); const lookDetail = val => { getAttachInfoAPI(val.id).then(res => { dialogDetailList.value = res.data.data; dialogDetailList.value= {...res.data.data, checkedinput: false,} console.log('val111', dialogDetailList.value); }); dialogVisible.value = true; }; @@ -363,10 +383,60 @@ }) .catch(() => {}); }; const fileNameedit = ref('') // 编辑文件名 const editTitle = val => { // console.log('val', val); val.checkedinput = !val.checkedinput; const editTitle = (val) => { val.checkedinput = true fileNameedit.value = val.nickName } const cancelEditSuffix = (item) => { item.nickName = fileNameedit.value item.checkedinput = false console.log('item',item); } 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; updataTitleApi(updateparams) .then(res => { console.log('updateparams',updateparams); if (res.status === 200) { ElMessage.success('修改成功'); } else { ElMessage.error(res.data.message || '修改失败'); } }) .catch(error => { ElMessage.error('请求失败,请稍后重试'); console.error('API error:', error); }); }; // 全景预览 const panoramaParamsShow = ref(false); @@ -386,9 +456,18 @@ VideoShow.value = true; }; // 地图弹框 // 创建子组件引用 const mapComponent = ref(null); const mapList = ref(null); const dataCenterMapVisible = ref(false); const positionDetail = () => { const jobId = ref(''); const positionDetail = val => { jobId.value = val.wayLineJobId; mapList.value = val; dataCenterMapVisible.value = true; if(mapComponent.value){ mapComponent.value.labelBoxUpdate(); } }; onMounted(() => { getaiImagesPage();