forked from drone/command-center-dashboard

chenyao
2025-04-03 39747a03ff43680995107831f99f79287830a372
Merge branch 'master' of http://139.196.74.78:10010/r/drone/command-center-dashboard
7 files modified
3 files added
1 files deleted
7225 ■■■■ changed files
.env.development 17 ●●●● patch | view | raw | blame | history
package.json 1 ●●●● patch | view | raw | blame | history
pnpm-lock.yaml 6799 ●●●● patch | view | raw | blame | history
src/api/home/machineNest.js 20 ●●●●● patch | view | raw | blame | history
src/views/Home/useMapAggregation/useMapAggregation.js 84 ●●●● patch | view | raw | blame | history
src/views/SignMachineNest/SignMachineNest.vue 5 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/components/MachineRight/MachineStatus.vue 15 ●●●● patch | view | raw | blame | history
src/views/SignMachineNest/components/MachineRight/MachineTableDetails/DeviceEvent.vue 66 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/components/MachineRight/MachineTableDetails/DeviceJob.vue 58 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/components/MachineRight/MachineTableDetails/MachineTableDetails.vue 75 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/components/MachineRight/components/MachineTableDetails.vue 85 ●●●●● patch | view | raw | blame | history
.env.development
@@ -2,18 +2,7 @@
#开发环境配置
VITE_APP_ENV = 'development'
#接口地址
VITE_APP_API = /api
VITE_APP_API_URL = https://wrj.shuixiongit.com/api
#开发环境代理地址(推荐本地新建文件 .env.development.local 来进行覆盖)
# VITE_APP_API_URL = https://wrj.shuixiongit.com/api
# VITE_APP_API_URL = http://192.168.1.7
# 连接本地服务器
# VITE_APP_API_URL = http://192.168.1.204
#调试参数
VITE_APP_DEBUG_KEY = saber
VITE_APP_DEBUG_SWITCH = false
#页面基础路径
VITE_APP_BASE = /command-center-dashboard/
# ws地址
VITE_APP_WS_API_URL = wss://wrj.shuixiongit.com/drone-wss/api/v1/ws
package.json
@@ -16,6 +16,7 @@
    "@saber/nf-form-design-elp": "^1.4.1",
    "@saber/nf-form-elp": "^1.4.4",
    "@smallwei/avue": "^3.6.2",
    "@turf/turf": "^6.5.0",
    "animate.css": "^4.1.1",
    "avue-plugin-ueditor": "^1.0.4",
    "axios": "^1.8.3",
pnpm-lock.yaml
Diff too large
src/api/home/machineNest.js
@@ -46,3 +46,23 @@
        params: {},
    })
}
// 设备-任务列表
export const getDeviceJobList = (data,params) => {
    return request({
        url: `/drone-device-core/wayline/waylineJobInfo/jobList`,
        method: 'post',
        data,
        params
    })
}
// 设备-事件列表
export const getDeviceEventList = (data,params) => {
    return request({
        url: `/drone-device-core/jobEvent/eventPage`,
        method: 'post',
        data,
        params
    })
}
src/views/Home/useMapAggregation/useMapAggregation.js
@@ -46,14 +46,11 @@
  const store = useStore();
  const userAreaCode = computed(() => store.state.user.userInfo.detail.areaCode);
  const selectedAreaCode = computed(() => store.state.user.selectedAreaCode);
  const listenerHeight = () => {
    determineScaling();
    viewer.camera.moveEnd.addEventListener(determineScaling);
  };
  // 确定缩放比例
  const determineScaling = index => {
    console.log('确定缩放比例');
  const determineScaling = () => {
    // console.log('确定缩放比例');
    if (!viewer) return;
    let height = viewer.camera.positionCartographic.height;
    // 根据高度展示对应的 gJson
@@ -64,13 +61,15 @@
        active = item.name;
        removeEntities();
        removeLabel();
        item.gJson ? aggregation(item) : splashed(item);
        renderOutline(item);
        if (!item.gJson && !item.splashedList?.length) return;
        item.gJson ? aggregation(item) : splashed(item);
        break;
      }
    }
  };
  // 转换为目录结构, 返回数组,
  function convertToHierarchy(code) {
    const codeStr = code.toString();
    const provinceCode = codeStr.slice(0, 2) + '0000';
@@ -83,18 +82,20 @@
    return [provinceCode, cityCode, code];
  }
  // 获取设备聚合
  function getDeviceCount(areaCode) {
    return getDeviceRegionCount({ areaCode }).then(res => {
      return (res?.data?.data || []).map(i=>({...i,type:'deviceAggregation'}));
      return (res?.data?.data || [])
    });
  }
  // 获取设备散点
  function getDeviceList(areaCode) {
    return getDeviceRegion({ areaCode }).then(res => {
      return (res?.data?.data || []).map(i =>({...i,type}));
    });
  }
  // 事件散点
  let eventList = []
  function processChildren(childrens) {
    return (childrens || []).map(item => {
@@ -106,7 +107,6 @@
        total_device_count: item.number,
        region_code: item.id,
        region_name: item.name,
        type:'eventAggregation'
      };
      if (arr.length !== 0) {
        returnObj.childrens = arr;
@@ -115,6 +115,7 @@
    });
  }
  // 获取事件聚合
  function getMapEventCount(areaCode) {
    return getMapEvents({ areaCode }).then(res => {
      const resData= res?.data?.data
@@ -145,7 +146,10 @@
  const injectData = (gJson, dataList) => {
    return {
      ...gJson,
      features: gJson.features.map(item => ({ ...item, data: dataList.find(item1 => findFun(item1, item)) })),
      features: gJson.features.map(item => {
        const findData = dataList.find(item1 => findFun(item1, item))
        return { ...item, data: {...findData,type: type + 'Aggregation'} }
      }),
    };
  };
@@ -165,23 +169,18 @@
    scalingJudgment[0].gJson = null;
    scalingJudgment[0].splashedList = splashedList;
    active = null;
    // 轮廓
    const outlineGJson = await getOutLine(jsonPathPre, hierarchy);
    scalingJudgment.forEach(item => item.show && (item.outline = outlineGJson));
    // 省
    if (hierarchy.length === 1) {
      const gJson1 = await getFiler(`${defaultDir}${jsonPath}/indexDistrict.json`);
      const gJson2 = await getFiler(`${defaultDir}${jsonPath}/index.json`);
      scalingJudgment[1].gJson = {
        ...gJson1,
        features: gJson1.features.map(item => ({
          ...item,
          data: list.flatMap(item => item.childrens || []).find(item1 => findFun(item1, item)),
        })),
        features: gJson1.features.map(item => {
          const findData = list.flatMap(item => item.childrens || []).find(item1 => findFun(item1, item))
          return { ...item, data: {...findData,type: type + 'Aggregation'} }
        }),
      };
      scalingJudgment[2].gJson = injectData(gJson2, list);
      const { lng, lat } = getCenterPoint(gJson2.features.map(item => item.properties.center));
      flyTo({ longitude: lng, latitude: lat }, 0, scalingJudgment[2].height);
    }
    // 市
    if (hierarchy.length === 2) {
@@ -189,8 +188,6 @@
      scalingJudgment[2].show = false;
      const gJson1 = await getFiler(`${defaultDir}${jsonPath}/index.json`);
      scalingJudgment[1].gJson = injectData(gJson1, list);
      const center = getCenterPoint(gJson1.features.map(item => item.properties.center));
      flyTo({ longitude: center.lng, latitude: center.lat }, 0, scalingJudgment[1].height);
    }
    // 区县
    if (hierarchy.length === 3) {
@@ -198,17 +195,22 @@
      scalingJudgment[1].show = false;
      scalingJudgment[2].gJson = null;
      scalingJudgment[2].show = false;
      const outlineGJson = await getOutLine(jsonPathPre, hierarchy);
      const center = outlineGJson.features[0].properties.center;
      flyTo({ longitude: center[0], latitude: center[1] }, 0, scalingJudgment[0].height);
    }
    determineScaling()
    // 轮廓
    const outlineGJson = await getOutLine(jsonPathPre, hierarchy);
    scalingJudgment.forEach(item => item.show && (item.outline = outlineGJson));
    const [longitude,latitude] = outlineGJson.features[0].properties.centroid
    flyTo({ longitude, latitude }, 0, scalingJudgment[(hierarchy.length-3) * (-1)].height);
  };
  watch(
    selectedAreaCode,
    async (newValue, oldValue) => {
      initMapData(newValue);
      if (!viewer) return
      viewer.scene.postRender.removeEventListener(determineScaling)
      initMapData(newValue).then(()=>{
        viewer.scene.postRender.addEventListener(determineScaling)
      })
    },
    { deep: true }
  );
@@ -273,12 +275,15 @@
  // 聚合机巢
  const aggregation = (item, flyTo) => {
    if (!item.gJson) return;
    const featuresList = item.gJson.features.map(item => ({
      name: item.properties.name,
      position: item.properties.center,
      data: item.data,
      id: item.region_code,
    }));
    const featuresList = item.gJson.features.map(item1 => {
      // const {lng,lat} = getCenterPoint(item1.geometry.coordinates[0][0])
      return {
        name: item1.properties.name,
        position: item1.properties.centroid,
        data: item1.data,
        id: item1.region_code,
      }
    });
    // 遍历特征并添加实体
    featuresList.forEach(feature => {
      if (!feature.position) return;
@@ -382,7 +387,7 @@
      if (!positionC3) return;
      removeLabel();
      const customData = entity.properties.customData._value.data;
      console.log('customData', customData);
      switch (customData.type) {
        case 'deviceAggregation':
          viewer.scene.postRender.addEventListener(labelBoxRender);
@@ -423,7 +428,8 @@
  const removeAll = () => {
    removeEntities();
    removeLabel();
    viewer.camera.moveEnd.removeEventListener(determineScaling);
    // viewer.camera.moveEnd.removeEventListener(determineScaling);
    viewer.scene.postRender.removeEventListener(determineScaling)
    handler?.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
    handler?.destroy();
    viewer = null;
@@ -433,11 +439,14 @@
    currentEntity = null;
  };
  const init = () => {
    initMapData(selectedAreaCode.value || userAreaCode.value);
    viewer = window.$viewer;
    viewer.scene.postRender.removeEventListener(determineScaling)
    initMapData(selectedAreaCode.value || userAreaCode.value).then(()=>{
      viewer.scene.postRender.addEventListener(determineScaling)
    })
    handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    handler.setInputAction(singleMachineEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    listenerHeight();
  };
  onBeforeUnmount(() => {});
  // onMounted(() => {
@@ -445,7 +454,6 @@
  //     viewer = window.$viewer;
  //     handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  //     handler.setInputAction(singleMachineEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  //     listenerHeight();
  //   });
  // });
src/views/SignMachineNest/SignMachineNest.vue
@@ -31,7 +31,6 @@
const messageHandler = (result) => {
  let payload = JSON.parse(result) // 为了兼容聊天消息
  // console.log('payload', payload)
  if (!payload) return
  switch (payload.biz_code) {
    case EBizCode.GatewayOsd: {
@@ -83,6 +82,8 @@
  getFlightStatistics(singleUavHome.value.device_sn).then(res => {
    if (res.data.code !== 0) return;
    const result = res.data.data;
    console.log(result);
    console.log(result);
    store.commit('setSingleTotal', result);
  })
};
@@ -95,4 +96,4 @@
onUnmounted(() => {
  connectWs?.value?.close();
});
</script>
</script>
src/views/SignMachineNest/components/MachineRight/MachineStatus.vue
@@ -66,7 +66,7 @@
      </div>
    </div>
  </div>
  <!-- <MachineTableDetails :dialogVisible="isShowDetails" /> -->
   <MachineTableDetails v-model:show="isShowDetails"/>
</template>
<script setup>
@@ -75,6 +75,7 @@
import { EModeCode, EDockModeText, EModeText } from '@/utils/staticData/device';
import { getLnglatAltitude } from '@/utils/cesium/mapUtil.js';
import { useStore } from 'vuex';
import MachineTableDetails from '@/views/SignMachineNest/components/MachineRight/MachineTableDetails/MachineTableDetails.vue';
const store = useStore();
// 获取机巢信息
@@ -269,7 +270,7 @@
    );
    opacity: 0.85;
    margin: 2px 0 13 0;
    padding: 8px 0px 20px 18px;
    .info {
      display: flex;
@@ -323,16 +324,16 @@
            color: #FFB26A;
            margin-right: 10px;
          }
          .wb {
          .wb {
            width: 60px;
            height: 20px;
            height: 20px;
            background: rgba(255,106,106,0.2);
            border-radius: 4px 4px 4px 4px;
            border: 1px solid #FF6A6A;
            color: #FF6A6A;
          }
        }
      }
    }
    .status {
@@ -341,7 +342,7 @@
      gap: 10px 20px;
      margin-top: 18px;
      .card {
        width: calc(33.33% - 14px);
        width: calc(33.33% - 14px);
      }
      img {
        width: 16px;
@@ -370,4 +371,4 @@
      }
    }
 }
</style>
</style>
src/views/SignMachineNest/components/MachineRight/MachineTableDetails/DeviceEvent.vue
New file
@@ -0,0 +1,66 @@
<template>
    <el-divider content-position="left">相关事件 {{total}}次</el-divider>
    <el-table :data="list" style="width: 100%">
        <el-table-column prop="event_num" label="编号" />
        <el-table-column prop="event_name" label="名称" />
        <el-table-column prop="media_type" label="类型" />
        <el-table-column prop="photo_url" label="图片url" />
        <el-table-column prop="video_url" label="视频url" />
        <el-table-column prop="remark" label="备注" />
        <el-table-column prop="status" label="状态">
            <template #default="scope">
                <el-tag v-if="scope.row.status === 0" type="info">待处理</el-tag>
                <el-tag v-if="scope.row.status === 1" type="success">待分拨</el-tag>
                <el-tag v-if="scope.row.status === 2" type="warning">待处理</el-tag>
                <el-tag v-if="scope.row.status === 3" type="success">处理中</el-tag>
                <el-tag v-if="scope.row.status === 4" type="success">已完成</el-tag>
                <el-tag v-if="scope.row.status === 5" type="success">已完结</el-tag>
            </template>
        </el-table-column>
        <el-table-column prop="wayline_job_id" label="    任务id" />
    </el-table>
    <el-pagination
        background
        :page-sizes="[10, 20, 30, 50]"
        v-model:current-page="sizeParams.current"
        v-model:page-size="sizeParams.size"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
        @change="pageChange"
    />
</template>
<script setup>
import { useStore } from 'vuex'
import { getDeviceEventList } from '@/api/home/machineNest'
const list = ref([])
const params = ref({
    device_sn: null,
})
const sizeParams = ref({
    current: 1,
    size: 1,
})
const store = useStore()
const child_sn = computed(() => store.state.home.singleUavHome.child_sn)
const total = ref(0)
const getList = () => {
    params.value.device_sn = child_sn.value
    getDeviceEventList(params.value, sizeParams.value).then(res => {
        const resData = res?.data?.data || {}
        list.value = resData.records
        total.value = resData.total
    })
}
const pageChange = val => {
    sizeParams.value.current = val
    getList()
}
onMounted(() => {
    getList()
})
</script>
<style scoped lang="scss"></style>
src/views/SignMachineNest/components/MachineRight/MachineTableDetails/DeviceJob.vue
New file
@@ -0,0 +1,58 @@
<template>
    <el-divider content-position="left">相关事件 {{ total }}次</el-divider>
    <el-table :data="list" style="width: 100%">
        <el-table-column prop="id" label="id" />
        <el-table-column prop="name" label="名称" />
        <el-table-column prop="industry_type_str" label="任务类型" />
        <el-table-column prop="ai_type_str" label="关联算法" />
        <el-table-column prop="event_number" label="事件工单" />
        <el-table-column prop="remark" label="备注" />
        <el-table-column prop="area_code" label="地区代码" />
        <el-table-column prop="begin_time" label="开始时间" />
        <el-table-column prop="end_time" label="结束时间" />
        <el-table-column prop="device_names" label="设备名称" />
        <el-table-column prop="creator_name" label="创建者姓名" />
    </el-table>
    <el-pagination
        background
        :page-sizes="[10, 20, 30, 50]"
        v-model:current-page="sizeParams.current"
        v-model:page-size="sizeParams.size"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
        @change="pageChange"
    />
</template>
<script setup>
import { useStore } from 'vuex'
import { getDeviceJobList } from '@/api/home/machineNest'
const list = ref([])
const params = ref({
    device_sn: null,
})
const sizeParams = ref({
    current: 1,
    size: 1,
})
const store = useStore()
const child_sn = computed(() => store.state.home.singleUavHome.child_sn)
const total = ref(0)
const getList = () => {
    params.value.device_sn = child_sn.value
    getDeviceJobList(params.value, sizeParams.value).then(res => {
        const resData = res?.data?.data || {}
        list.value = resData.records
        total.value = resData.total
    })
}
const pageChange = val => {
    sizeParams.value.current = val
    getList()
}
onMounted(() => {
    getList()
})
</script>
<style scoped lang="scss"></style>
src/views/SignMachineNest/components/MachineRight/MachineTableDetails/MachineTableDetails.vue
New file
@@ -0,0 +1,75 @@
<!-- 机巢列表详情 -->
<template>
    <el-dialog
        modal-class="machineTableDetails"
        v-model="isShowDetails"
        title="机巢详情"
        :width="pxToRem(1500)"
        :close-on-click-modal="false"
        :destroy-on-close="true"
    >
        <el-divider content-position="left">详情</el-divider>
        <div class="infoBox">
            <div class="itemBox" v-for="item in infoList">
                <div class="itemTitle">{{ item.name }}:</div>
                <div class="itemValue">{{ item.value }}</div>
            </div>
        </div>
        <DeviceJob v-if="isShowDetails" />
        <DeviceEvent v-if="isShowDetails" />
    </el-dialog>
</template>
<script setup>
import DeviceEvent from '@/views/SignMachineNest/components/MachineRight/MachineTableDetails/DeviceEvent.vue'
import DeviceJob from '@/views/SignMachineNest/components/MachineRight/MachineTableDetails/DeviceJob.vue'
import { pxToRem } from '@/utils/rem';
const isShowDetails = defineModel('show')
const infoList = ref([
    { name: '机巢名称', value: 'xxx' },
    { name: '机巢所属地区', value: 'xxx' },
    { name: '机巢状态', value: 'xxx' },
    { name: '任务成功', value: 'xxx' },
    { name: '机巢位置', value: 'xxx' },
])
onMounted(() => {})
</script>
<style lang="scss">
.machineTableDetails {
    .el-pagination {
        text-align: left;
        padding: 20px;
    }
}
</style>
<style lang="scss" scoped>
.infoBox {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px; // 列之间的间距
    padding: 10px;
    .itemBox {
        background-color: #fff;
        border: 1px solid #ddd;
        border-radius: 4px;
        padding: 15px;
        box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
    }
    .itemTitle {
        font-weight: bold;
        color: #333;
    }
    .itemValue {
        margin-top: 5px;
        color: #666;
    }
}
</style>
src/views/SignMachineNest/components/MachineRight/components/MachineTableDetails.vue
File was deleted