jihongshun
2025-08-08 f79670d51e41f1e762762c73e423fb658556102b
src/views/system/project/components/addPorjectDialog.vue
@@ -10,12 +10,12 @@
          <el-col :span=6>
              <el-card class="noScroll">
                <div class="chooseModel">
                  <!-- <el-button type="primary" @click='chooseModel'>添加设备</el-button> -->
                  <div class="fontJust">设备列表</div>
                   <el-button @click="addDevice"> 新增</el-button>
                  <el-table
                    :data="tableData"
                    max-height = '180'
                    :row-key="getRowKeys"
                    style="width: 100%">
                    <el-table-column
                      prop="deviceName"
@@ -23,15 +23,16 @@
                      width="150">
                    </el-table-column>
                    <el-table-column
                      prop="muban"
                      prop="mode"
                      label="模板"
                      width="150">
                      width="120">
                    </el-table-column>
                    <el-table-column
                      fixed="right"
                      label="操作"
                      width="100">
                      width="130">
                      <template slot-scope="scope">
                        <el-button type="text" size="small" @click="chooseModel(scope.row)">模板</el-button>
                        <el-button type="text" size="small" @click="flyToLocal(scope.row)">定位</el-button>
                        <el-button type="text" size="small" @click="deleteData(scope.row)">移除</el-button>
                      </template>
@@ -45,9 +46,9 @@
                  <div class="fontJust">巡检点目录</div>
                  <el-tree
                    class="filter-tree"
                    node-key="id"
                    :data="treeData"
                    :props="defaultProps"
                    draggable
                    default-expand-all
                    ref="tree">
                  </el-tree>
@@ -63,16 +64,22 @@
        <el-button type="primary" @click="submit()" >生成航线任务</el-button>
      </span>
    </el-dialog>
     <chooseDeviceDialog v-if="showModel" @cancel ='cancel' @dealChooseArr="dealChooseArr" ></chooseDeviceDialog>
     <chooseDeviceDialog v-if="showDevice" @cancel ='cancel' @dealChooseArr="dealChooseArr" ></chooseDeviceDialog>
     <chooseModelDialog v-if="showModel" @cancelModel ='cancelModel' @receiveModel="receiveModel" :deviceId="deviceId"></chooseModelDialog>
  </div>
</template>
<script>
import CesiumMap from "../../../../utils/components/cesium-map.vue";
import chooseDeviceDialog from './chooseDeviceDialog.vue';
import chooseModelDialog from './chooseModelDialog.vue';
import { obtainRealData ,buildKmz} from "@/api/system/template"
let rotationAngle = Cesium.Math.toRadians(0)
let lastConnectPolyline
export  default{
  components: {
    CesiumMap,
    chooseDeviceDialog
    chooseDeviceDialog,
    chooseModelDialog
  },
  data(){
    return{
@@ -83,21 +90,21 @@
          children: 'children',
          label: 'label'
        },
        showDevice:false,
        multipleSelection: [],
        showModel:false,
        multipleSelection: []
        deviceId:null
    }
  },
  methods:{
    handleClose(){
      console.log("close")
      this.dialogVisible = false
      this.$emit('close')
    },
    flyToLocal(row){
      console.log(row)
      console.log(viewer)
       const position = Cesium.Cartesian3.fromDegrees(row.longitude,row.latitude, row.altitude);
       const position = Cesium.Cartesian3.fromDegrees(row.longitude,row.latitude, row.deviceHeight);
      // rotationAngle  = Cesium.Math.toRadians(row.face)  ||  Cesium.Math.toRadians(0)
      rotationAngle  = Cesium.Math.toRadians(row.face)  ||  Cesium.Math.toRadians(0)
      // 设置模型方向(可选)
      const heading = Cesium.Math.toRadians(row.face); // 朝东南方向
      const pitch = 0;
@@ -113,19 +120,35 @@
        position: position,
        orientation: orientation,
        model: {
          uri: "http://192.168.1.5:9000/tower/2025/07/01/tower_20250701145739A004.glb", // 替换成你的模型路径
          scale: 1000,
          uri: row?.ardTowerModel.modelRoute, // 替换成你的模型路径
          scale: 1,
        },
        label: {
          show: true,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
          font: '28px Helvetica',
          outlineColor: Cesium.Color.BLUE,
          outlineWidth: 3,
          fillColor: Cesium.Color.fromCssColorString('#FFFFFF'), //44c3cc
          text: row.deviceName,
          style: Cesium.LabelStyle.FILL_AND_OUTLINE,
          pixelOffset: new Cesium.Cartesian2(0.0, -56.0),
          scaleByDistance: new Cesium.NearFarScalar(1000, 0.6, 10000, 0.4),
          pixelOffsetScaleByDistance: new Cesium.NearFarScalar(1000, 0.4, 10000, 0.4),
          disableDepthTestDistance: 100000000
        }
      });
      console.log(entity)
      // 飞行到模型位置
      console.log(this.treeData)
      viewer.flyTo(entity)
    },
    addDevice(){
      this.showModel = true
      this.showDevice = true
    },
    cancel(){
      this.showModel = false
      this.showDevice = false
    },
    hasSameId(array1, array2) {
      const ids1 = new Set(array1.map(item => item.id));
@@ -145,29 +168,427 @@
            type: 'warning'
          })
        }
        this.tableData = this.tableData.concat(arr)
        this.tableData = this.treeData.concat(arr)
      }
      const dealTreeData =this.dealTee()
      this.treeData = dealTreeData
      console.log(this.treeData)
    },
    dealTee(){
      console.log(this.tableData)
     return  this.tableData.map(item => ({
        ...item,
        label: item.deviceName,
        children: []
      }));
    },
    deleteData(row){
      console.log(row)
      // 找到 id 为 "1" 的元素索引
      const index = this.tableData.findIndex(item => item.id === row.id);
      // 如果找到了,删除该元素
      if (index !== -1) {
        this.tableData.splice(index, 1);
      }
      const dealTreeData = this.dealTee()
      this.treeData = dealTreeData
      const indexTree = this.treeData.findIndex(item => item.id === row.id);
      // 如果找到了,删除该元素
      if (index !== -1) {
        this.treeData.splice(indexTree, 1);
      }
      if(this.treeData?.length >=2 && this.treeData[0]?.children?.length > 0 && this.treeData[1]?.children?.length > 0 ) {
          this.dealAddHeight()
      } else if(this.treeData?.length ==1) {
        this.treeData.map((device,index) => {
          // 确保设备有children数组
          if(index == 0) {
            //第一个数据
            if (device.children && Array.isArray(device.children) && device.children.length > 0) {
                // 获取最后一个空中点
                const lastChild = device.children[device.children.length - 1];
                const firstChild = device.children[0];
                // 深拷贝最后一个元素(包括嵌套的children)
                const newChildJson = JSON.parse(JSON.stringify(lastChild));
                // 深拷贝最一一个元素(包括嵌套的children)
                const firstChildJson = JSON.parse(JSON.stringify(firstChild));
                if(newChildJson && newChildJson.label.includes('加高')){
                  device.children?.pop(newChild);
                }else if(firstChildJson && firstChildJson.label.includes('加高')) {
                    device.children?.shift(firstChildJson);
                }
                // 添加到children数组末尾
            }
          }
        })
      }
      const entitiesToRemove = [];
      viewer.entities.values.forEach(entity => {
        if (entity.id && entity.id.includes(row.id)) {
          entitiesToRemove.push(entity.id);
        }
      });
      entitiesToRemove.forEach(id => {
        viewer.entities.removeById(id);
      });
      this.drawConnectionsWithLabels(this.treeData)
      // console.log(this.tableData)
      console.log(this.treeData)
      // const dealTreeData = this.dealTee()
      // console.log(dealTreeData)
      // this.treeData = dealTreeData
      // this.drawConnectionsWithLabels(this.treeData)
    },
    chooseModel(row){
      this.deviceId = row.id
      this.showModel = true
    },
    cancelModel(){
      this.showModel = false
    },
    receiveModel(obj){
      //deal逻辑
      let flightTemplateId =obj.modelObj.id
      let deviceId = obj.deviceId
      this.dealTableTemplate(obj)
      // let
      obtainRealData(flightTemplateId,deviceId).then(res=>{
        console.log(res)
        let treeDealData = this.transformFlightData(res.data?.coordinateSystemVoS || [])
        console.log(treeDealData)
        this.dealTreeMerge(obj,treeDealData)
        this.rotateAllPoints(this.treeData || [],res.data.longitude,res.data.latitude,res.data.height)
        console.log(this.treeData)
        if(this.treeData?.length >=2 && this.treeData[0]?.children?.length > 0 && this.treeData[1]?.children?.length > 0 ) {
            this.dealAddHeight()
        }
        console.log(this.treeData)
        this.drawConnectionsWithLabels(this.treeData)
      })
      // this.dealTreeMerge(obj)
    },
    dealAddHeight(){
      let currentLength = this.treeData.filter(item=>item.children)?.length || 0
      console.log(this.treeData)
      console.log(currentLength)
      this.treeData.map((device,index) => {
          // 确保设备有children数组
          if(index == 0) {
            //第一个数据
            if (device.children && Array.isArray(device.children) && device.children.length > 0) {
                // 获取最后一个空中点
                const lastChild = device.children[device.children.length - 1];
                // 深拷贝最后一个元素(包括嵌套的children)
                const newChild = JSON.parse(JSON.stringify(lastChild));
                if(newChild.label.includes('加高')) {
                }else {
                  newChild.label = newChild.label + '-加高'
                  // 高度增加20
                  // newChild.height += 20;
                  //塔高 - 高度  + 往上飞多少米
                  newChild.height += (device.ardTowerModel?.modelHeight  - newChild.height +  20) ;
                  console.log('push11111111111111')
                  // 添加到children数组末尾
                  device.children.push(newChild);
                }
            }
          }
          else if(index == currentLength -1) {
            if (device.children && Array.isArray(device.children) && device.children.length > 0) {
                // 获取最后一个空中点
                const lastChild = device.children[0];
                // 深拷贝最一一个元素(包括嵌套的children)
                const newChild = JSON.parse(JSON.stringify(lastChild));
                if(newChild.label.includes('加高')) {
                }else {
                  newChild.label = newChild.label + '-加高第一个点'
                  newChild.height += (device.ardTowerModel?.modelHeight  - newChild.height +  20) ;
                  // 添加到children数组末尾
                  device.children.unshift(newChild);
                }
            }
          } else {
            if (device.children && Array.isArray(device.children) && device.children.length > 0) {
                // 获取最后一个空中点
                const firstChild = device.children[0];
                const lastChild = device.children[device.children.length - 1];
                // 深拷贝最一一个元素(包括嵌套的children)
                const newFirstChild = JSON.parse(JSON.stringify(firstChild));
                const newLastChildChild = JSON.parse(JSON.stringify(lastChild));
                //高度增加20
                if(newFirstChild.label.includes('加高')) {
                }else {
                    newFirstChild.label = newFirstChild.label + '-加高'
                    newFirstChild.height += (device.ardTowerModel?.modelHeight  - newFirstChild.height +  20) ;
                    device.children.unshift(firstChild);
                }
                if(newLastChildChild.label.includes('加高')) {
                }else {
                    newLastChildChild.label = newLastChildChild.label + '-加高'
                    newLastChildChild.height += (device.ardTowerModel?.modelHeight  - newLastChildChild.height +  20) ;
                    device.children.push(newLastChildChild);
                }
            }
          }
      })
    },
    //返回数据转换树结构
    transformFlightData(data) {
      console.log(data)
      const result = [];
      data.forEach(item => {
        // 找到第一个 groundPoint,作为代表
        const groundPoint = item.groundPointVo?.[0];
        // 生成 children 节点
        const children = groundPoint ? [{
          id: groundPoint.id,
          label: groundPoint.targetName,
          longitude: groundPoint.longitude,
          latitude: groundPoint.latitude,
          height: groundPoint.height
        }] : [];
        // 生成空中点
        result.push({
          id: item.id,
          label: item.targetName,
          longitude: item.longitude,
          latitude: item.latitude,
          height: item.altitude,
          children
        });
      });
      return result;
    },
    dealTableTemplate(obj){
      const targetTableData = this.tableData.find(item => item.id === obj.deviceId)
      const targetTreeData = this.treeData.find(item => item.id === obj.deviceId)
      if (targetTableData) {
        this.$set(targetTableData, 'mode', obj.modelObj.templateName) // ✅ Vue2 需要使用 $set 以确保响应式
      }
      if (targetTreeData) {
        this.$set(targetTreeData, 'mode', obj.modelObj.templateName) // ✅ Vue2 需要使用 $set 以确保响应式
      }
    },
    dealTreeMerge(obj,data){
      this.treeData.forEach(item => {
        if(item.id == obj.deviceId) {
          // 替换第一层每个节点的 children
          this.$set(item, 'children', JSON.parse(JSON.stringify(data)));
        }
      });
    },
    generateId() {
      return 'id_' + Math.random().toString(36).substring(2, 10);
    },
    drawConnectionsWithLabels(devices) {
      if(lastConnectPolyline) {
        viewer.entities.remove(lastConnectPolyline)
        lastConnectPolyline = null
      }
        // 直接操作EntityCollection(推荐)
      const entitiesToRemove = [];
      viewer.entities.values.forEach(entity => {
        if (entity.id && entity.id.includes(this.deviceId)) {
          entitiesToRemove.push(entity.id);
        }
      });
      entitiesToRemove.forEach(id => {
        viewer.entities.removeById(id);
      });
      const airPoints = [];
      console.log(devices)
      devices.forEach(device => {
        if (!Array.isArray(device.children)) return;
        device.children.forEach(airPoint => {
          // 空中点位置
          const airPos = Cesium.Cartesian3.fromDegrees(
            airPoint.longitude,
            airPoint.latitude,
            airPoint.height
          );
          // ✅ 添加空中点实体和 label
          viewer.entities.add({
            position: airPos,
            id:device.id +'airpoint' + this.generateId(),
            point: {
              pixelSize: 6,
              color: Cesium.Color.YELLOW
            },
            label: {
              text: airPoint.label || '',
              font: '14px sans-serif',
              fillColor: Cesium.Color.YELLOW,
              style: Cesium.LabelStyle.FILL_AND_OUTLINE,
              outlineColor: Cesium.Color.BLACK,
              outlineWidth: 2,
              verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
              pixelOffset: new Cesium.Cartesian2(0, -12)
            }
          });
          airPoints.push(airPos);
          // 遍历地面点(子项)
          if (Array.isArray(airPoint.children)) {
            airPoint.children.forEach(groundPoint => {
              const groundPos = Cesium.Cartesian3.fromDegrees(
                groundPoint.longitude,
                groundPoint.latitude,
                groundPoint.height
              );
              // ✅ 添加地面点实体和 label
              viewer.entities.add({
                id:device.id +'groundpoint' + this.generateId(),
                position: groundPos,
                point: {
                  pixelSize: 6,
                  color: Cesium.Color.BLUE
                },
                label: {
                  text: groundPoint.label || '',
                  font: '14px sans-serif',
                  fillColor: Cesium.Color.WHITE,
                  style: Cesium.LabelStyle.FILL_AND_OUTLINE,
                  outlineColor: Cesium.Color.BLACK,
                  outlineWidth: 2,
                  verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                  pixelOffset: new Cesium.Cartesian2(0, -12)
                }
              });
              // ✅ 蓝色虚线连接:空中点 ➜ 地面点
              viewer.entities.add({
                id:device.id +'polyline' + this.generateId(),
                polyline: {
                  positions: [airPos, groundPos],
                  width: 2,
                  material: new Cesium.PolylineDashMaterialProperty({
                    color: Cesium.Color.BLUE,
                    dashLength: 8
                  })
                }
              });
            });
          }
        });
      });
      console.log(airPoints)
      // ✅ 黄色实线连接所有空中点
      if (airPoints.length > 1) {
        lastConnectPolyline = viewer.entities.add({
          polyline: {
            positions: airPoints,
            width: 3,
            material: Cesium.Color.YELLOW
          }
        });
      }
    },
    rotateAllPoints(dataList,towerLongitude,towerLatitude,towerhHight) {
      return dataList.map(item => {
        if(!item.isDeal) {
          const towerPoint = Cesium.Cartesian3.fromDegrees(towerLongitude, towerLatitude, towerhHight);
          const newGround = {
            ...item,
          };
          if(item.id == this.deviceId ){
            item.isDeal = true
          }
          // 处理子空中点
          newGround.children = item.children?.map(child => {
            const airPoint = Cesium.Cartesian3.fromDegrees(child.longitude, child.latitude, child.height);
            const rotatedAirPoint = this.rotateAroundPoint( airPoint,towerPoint, rotationAngle);
            const rotatedAirCarto = Cesium.Cartographic.fromCartesian(rotatedAirPoint);
            child.longitude = Cesium.Math.toDegrees(rotatedAirCarto.longitude)
            child.latitude = Cesium.Math.toDegrees(rotatedAirCarto.latitude)
            child.height = rotatedAirCarto.height
            child.children = child.children?.map(sonChild => {
              const groundPoint = Cesium.Cartesian3.fromDegrees(sonChild.longitude, sonChild.latitude, sonChild.height);
              const newGroundPoint = this.rotateAroundPoint( groundPoint,towerPoint, rotationAngle);
              const newGroundCarto = Cesium.Cartographic.fromCartesian(newGroundPoint);
              return {
                ...sonChild,
                longitude: Cesium.Math.toDegrees(newGroundCarto.longitude),
                latitude: Cesium.Math.toDegrees(newGroundCarto.latitude),
                height: newGroundCarto.height
              };
            });
          });
          console.log(newGround.children)
          return newGround;
        }
      });
    },
    // 计算点A绕点B逆时针旋转指定角度后的新位置
    //  空中点或者地面点笛卡尔坐标 塔的笛卡尔坐标    塔的朝向值
    rotateAroundPoint(startPoint,pivotPoint,rotationAngle) {
      console.log(rotationAngle)
      // 创建一个从B点到本地坐标系的转换矩阵(东方向为X轴,北方向为Y轴,垂直方向为Z轴)
     const transformationMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(pivotPoint);
      // 获取世界坐标系到本地坐标系的转换矩阵
     const inverseTransformationMatrix = Cesium.Matrix4.inverse(transformationMatrix,new Cesium.Matrix4());
      // 将A点转换到局部坐标系中
     const localStartPoint = Cesium.Matrix4.multiplyByPoint(inverseTransformationMatrix, startPoint,new Cesium.Cartesian3());
      // 计算A点在局部坐标系中逆时针旋转指定角度后的新位置
     const rotatedX = localStartPoint.x * Math.cos(rotationAngle) + localStartPoint.y * Math.sin(rotationAngle);
     const rotatedY = localStartPoint.y * Math.cos(rotationAngle) - localStartPoint.x * Math.sin(rotationAngle);
     const rotatedZ = localStartPoint.z; // Z轴坐标保持不变
      // 将旋转后的局部坐标转换回世界坐标系
     return Cesium.Matrix4.multiplyByPoint(transformationMatrix, new Cesium.Cartesian3(rotatedX, rotatedY, rotatedZ), new Cesium.Cartesian3());
    },
    getRowKeys(row){
      console.log(row)
      return row.id +this.generateId()
    },
    submit(){
      console.log(this.treeData)
      const routePointList = [];
      this.treeData.forEach(device => {
        device.children.forEach((airPoint, index) => {
          routePointList.push({
            routePointIndex: routePointList.length,
            longitude: airPoint.longitude,
            latitude: airPoint.latitude,
            height: airPoint.height,
            isStartAndEndPoint: false,
            coordinatePointReq: {
                height: airPoint.children[0].height,
                latitude:airPoint.children[0].latitude,
                longitude: airPoint.children[0].longitude
              }
          });
        });
      });
      // 标记首尾为 true
      if (routePointList.length > 0) {
        routePointList[0].isStartAndEndPoint = true;
        routePointList[routePointList.length - 1].isStartAndEndPoint = true;
      }
      console.log(routePointList);
      let params  = {
        routePointList:routePointList
      }
      console.log('生成航线')
      buildKmz(params).then(res=>{
        if(res.code == 200) {
           this.$message({
              message: '生成航线成功',
              type: 'success'
            })
        }
      })
    }
  }
}