jihongshun
2025-07-08 e0683e37cdd7dc99adf2af6521962c3f29d0f9b7
模板 && 项目页面
已添加4个文件
已修改8个文件
977 ■■■■■ 文件已修改
src/api/system/template.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/components/cesium-map.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/components/init-map.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/device/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/model/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/project/components/addPorjectDialog.vue 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/project/components/chooseDeviceDialog.vue 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/project/components/chooseModelDialog.vue 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/project/index.vue 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/shootPoint/components/chooseModelDialog.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/shootPoint/components/shootPointDialog.vue 439 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/shootPoint/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/template.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
import request from '@/utils/request';
// æ–°å¢žæ‹æ‘„航线模版
export function addPoint(data) {
  return request({
    url: '/tower/point',
    method: 'post',
    data: data
  });
}
src/utils/components/cesium-map.vue
@@ -187,4 +187,10 @@
  border: none;
  overflow: hidden;
}
</style>
<style>
.cesium-viewer-bottom{
  display: none;
}
</style>
src/utils/components/init-map.vue
@@ -36,8 +36,8 @@
let  viewerM;
let  viewerC;
let  counter = 0;
 let globalon  = 125.1949
let globalat = 46.5143
 let globalon  = 0
let globalat = 0
let  pois = [globalon, globalat,0] 
// let  pois = [0, 0,0] 
src/views/system/device/index.vue
@@ -3,7 +3,7 @@
     <el-row :gutter="20">
       <el-col :span="showMap ? 12 : 24" :xs="24">
        <AppTable ref="AppTable" selection :showDeptSearch="false" :url="'tower/device/list'" :tableColumns="tableColumns"
          :tableFilter="tableFilter"  @clickRow="clickRow">
          :tableFilter="tableFilter">
          <template #operatorBox="{ row }">
            <el-button type="primary" plain icon="el-icon-plus" size="mini"
              @click="handleAdd">新增设备</el-button>
src/views/system/model/index.vue
@@ -4,7 +4,7 @@
     <el-row :gutter="20">
       <el-col :span="showMap ? 12 : 24" :xs="24">
        <AppTable ref="AppTable" selection :showDeptSearch="false" :url="'tower/model/list'" :tableColumns="tableColumns"
          :tableFilter="tableFilter"  @clickRow="clickRow">
          :tableFilter="tableFilter">
          <template #operatorBox="{ row }">
            <el-button type="primary" plain icon="el-icon-plus" size="mini"
              @click="handleAdd">新增模型</el-button>
src/views/system/project/components/addPorjectDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,188 @@
<template>
  <div>
    <el-dialog
      title="新建项目"
      :visible="dialogVisible"
      append-to-body
      fullscreen
      :before-close="handleClose">
       <el-row :gutter="6">
          <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'
                    style="width: 100%">
                    <el-table-column
                      prop="deviceName"
                      label="设备名称"
                      width="150">
                    </el-table-column>
                    <el-table-column
                      prop="muban"
                      label="模板"
                      width="150">
                    </el-table-column>
                    <el-table-column
                      fixed="right"
                      label="操作"
                      width="100">
                      <template slot-scope="scope">
                        <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>
                    </el-table-column>
                  </el-table>
                </div>
              </el-card>
              <el-card >
                <div class="chooseModelTree">
                  <!-- <el-button type="primary" @click='chooseModel'>添加设备</el-button> -->
                  <div class="fontJust">巡检点目录</div>
                  <el-tree
                    class="filter-tree"
                    :data="treeData"
                    :props="defaultProps"
                    draggable
                    default-expand-all
                    ref="tree">
                  </el-tree>
                </div>
              </el-card>
          </el-col>
          <el-col :span=18>
              <CesiumMap ></CesiumMap>
          </el-col>
      </el-row>
      <span slot="footer" class="dialog-footer">
        <el-button @click="handleClose">取 æ¶ˆ</el-button>
        <el-button type="primary" @click="submit()" >生成航线任务</el-button>
      </span>
    </el-dialog>
     <chooseDeviceDialog v-if="showModel" @cancel ='cancel' @dealChooseArr="dealChooseArr" ></chooseDeviceDialog>
  </div>
</template>
<script>
import CesiumMap from "../../../../utils/components/cesium-map.vue";
import chooseDeviceDialog from './chooseDeviceDialog.vue';
export  default{
  components: {
    CesiumMap,
    chooseDeviceDialog
  },
  data(){
    return{
       dialogVisible :true,
        tableData: [],
        treeData:[],
        defaultProps: {
          children: 'children',
          label: 'label'
        },
        showModel:false,
        multipleSelection: []
    }
  },
  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 heading = Cesium.Math.toRadians(row.face); // æœä¸œå—方向
      const pitch = 0;
      const roll = 0;
      const orientation = Cesium.Transforms.headingPitchRollQuaternion(
        position,
        new Cesium.HeadingPitchRoll(heading, pitch, roll)
      );
      // åŠ è½½ glTF æ¨¡åž‹
      const entity = viewer.entities.add({
        name: row.id,
        position: position,
        orientation: orientation,
        model: {
          uri: "http://192.168.1.5:9000/tower/2025/07/01/tower_20250701145739A004.glb", // æ›¿æ¢æˆä½ çš„æ¨¡åž‹è·¯å¾„
          scale: 1000,
        },
      });
      console.log(entity)
      // é£žè¡Œåˆ°æ¨¡åž‹ä½ç½®
      viewer.flyTo(entity)
    },
    addDevice(){
      this.showModel = true
    },
    cancel(){
      this.showModel = false
    },
    hasSameId(array1, array2) {
      const ids1 = new Set(array1.map(item => item.id));
      const ids2 = new Set(array2.map(item => item.id));
      return ids1.size !== [...ids1].filter(id => ids2.has(id)).length;
    },
    dealChooseArr(arr){
      if(this.tableData?.length == 0 ){
        this.tableData = arr
      }else {
        const hasSameId = arr.filter(obj1 =>
          this.tableData.some(obj2 => obj1.id === obj2.id)
        );
        if(hasSameId?.length != 0){
          return this.$message({
            message: '选择设备数据和已有数据重复',
            type: 'warning'
          })
        }
        this.tableData = this.tableData.concat(arr)
      }
      const dealTreeData =this.dealTee()
      this.treeData = dealTreeData
      console.log(this.treeData)
    },
    dealTee(){
     return  this.tableData.map(item => ({
        ...item,
        label: item.deviceName,
        children: []
      }));
    },
    deleteData(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
    }
  }
}
</script>
<style scoped>
.filter-tree{
  height: 365px;
  width: 100%;
  overflow: auto;
}
.fontJust{
  text-align: center;
}
.el-table {
  height: 180px;
}
</style>
src/views/system/project/components/chooseDeviceDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,94 @@
<template>
  <el-dialog
    title="模型列表"
    :visible.sync="dialogVisible"
    @close="cancel"
    width="50%">
    <el-row :gutter="20">
       <el-col :span="24" :xs="24">
        <AppTable ref="AppTable" selection  :showDeptSearch="false" :url="'tower/device/list'" :tableColumns="tableColumns"  @on-select-checkbox="handleSelectionChange"   :showSearchBtn="false">
        </AppTable>
      </el-col>
     </el-row>
    <span slot="footer" class="dialog-footer">
      <el-button @click="cancel">取 æ¶ˆ</el-button>
      <el-button type="primary" @click="submit">ç¡® å®š</el-button>
    </span>
  </el-dialog>
</template>
<script>
  export default {
    name:'chooseModelDialog',
    components: {
    },
    data() {
      return {
        dialogVisible: true,
        showMap:false,
        tableColumns: [
            {
              label: '设备名称',
              prop: 'deviceName'
            },
             {
              label: '所属线路',
              prop: 'belongingRoute'
            },
        ],
        selectArr:[]
      };
    },
    methods: {
      cancel(){
        this.showMap = false
        this.$emit('cancel')
      },
      chooseRow(row){
        this.showMap = false
        this.$emit('cancel')
        this.$emit('getRowData',row)
      },
      submit(){
        // this.selectArr
        this.$emit('dealChooseArr',this.selectArr)
      },
      clickRow(row){
        console.log(row)
        this.showMap =true
        setTimeout(()=>{
          const position = Cesium.Cartesian3.fromDegrees(0, 0, 0);
          // è®¾ç½®æ¨¡åž‹æ–¹å‘(可选)
          // const heading = Cesium.Math.toRadians(135); // æœä¸œå—方向
          let model = viewer.entities.getById("modelList");
          const heading = Cesium.Math.toRadians(120); // æœä¸œå—方向
          const pitch = 0;
          const roll = 0;
          const orientation = Cesium.Transforms.headingPitchRollQuaternion(
            position,
            new Cesium.HeadingPitchRoll(heading, pitch, roll)
          );
          if(!model) {
            // åŠ è½½ glTF æ¨¡åž‹
            const entity = viewer.entities.add({
              id: "modelList",
              name: "modelList",
              position: position,
              orientation: orientation,
              model: {
                uri: row.modelRoute, // æ›¿æ¢æˆä½ çš„æ¨¡åž‹è·¯å¾„
                scale: 1000,
              },
            });
            viewer.flyTo(entity)
          }else {
            model.orientation = orientation
          }
        },1000)
      },
      handleSelectionChange(selection) {
        console.log(selection)
        this.selectArr = selection
      },
    }
  };
</script>
src/views/system/project/components/chooseModelDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,96 @@
<template>
  <el-dialog
    title="选择模板"
    :visible.sync="dialogVisible"
    @close="cancel"
    width="50%">
    <el-form ref="form" :model="form" label-width="80px">
      <el-form-item label="活动区域">
        <el-select v-model="form.region" placeholder="请选择活动区域">
          <el-option label="区域一" value="shanghai"></el-option>
          <el-option label="区域二" value="beijing"></el-option>
        </el-select>
      </el-form-item>
       <span slot="footer" class="dialog-footer">
        <el-button @click="cancel">取 æ¶ˆ</el-button>
        <el-button type="primary" @click="submit">ç¡® å®š</el-button>
      </span>
    </el-form>
  </el-dialog>
</template>
<script>
  export default {
    name:'chooseModelDialog',
    components: {
    },
    data() {
      return {
        dialogVisible: true,
        showMap:false,
        tableColumns: [
            {
              label: '设备名称',
              prop: 'deviceName'
            },
             {
              label: '所属线路',
              prop: 'belongingRoute'
            },
        ],
        selectArr:[]
      };
    },
    methods: {
      cancel(){
        this.showMap = false
        this.$emit('cancel')
      },
      chooseRow(row){
        this.showMap = false
        this.$emit('cancel')
        this.$emit('getRowData',row)
      },
      submit(){
        // this.selectArr
        this.$emit('dealChooseArr',this.selectArr)
      },
      clickRow(row){
        console.log(row)
        this.showMap =true
        setTimeout(()=>{
          const position = Cesium.Cartesian3.fromDegrees(0, 0, 0);
          // è®¾ç½®æ¨¡åž‹æ–¹å‘(可选)
          // const heading = Cesium.Math.toRadians(135); // æœä¸œå—方向
          let model = viewer.entities.getById("modelList");
          const heading = Cesium.Math.toRadians(120); // æœä¸œå—方向
          const pitch = 0;
          const roll = 0;
          const orientation = Cesium.Transforms.headingPitchRollQuaternion(
            position,
            new Cesium.HeadingPitchRoll(heading, pitch, roll)
          );
          if(!model) {
            // åŠ è½½ glTF æ¨¡åž‹
            const entity = viewer.entities.add({
              id: "modelList",
              name: "modelList",
              position: position,
              orientation: orientation,
              model: {
                uri: row.modelRoute, // æ›¿æ¢æˆä½ çš„æ¨¡åž‹è·¯å¾„
                scale: 1000,
              },
            });
            viewer.flyTo(entity)
          }else {
            model.orientation = orientation
          }
        },1000)
      },
      handleSelectionChange(selection) {
        console.log(selection)
        this.selectArr = selection
      },
    }
  };
</script>
src/views/system/project/index.vue
@@ -1,5 +1,126 @@
 <template>
    <div>
      é¡¹ç›®ç®¡ç†é¡µé¢
  <div class="app-container">
     <el-row :gutter="20">
       <el-col :span=24 :xs="24">
        <AppTable ref="AppTable" selection :showDeptSearch="false" :url="'tower/point/list'" :tableColumns="tableColumns"
          :tableFilter="tableFilter">
          <template #operatorBox="{ row }">
            <el-button type="primary" plain icon="el-icon-plus" size="mini"
              @click="handleAdd">新增项目</el-button>
          </template>
          <template #operator="{ row }">
            <el-button size="mini" type="text" icon="el-icon-edit"
              @click="handleUpdate(row)">修改</el-button>
            <el-button size="mini" type="text" icon="el-icon-delete"
              @click="handleDelete(row)">删除</el-button>
          </template>
        </AppTable>
      </el-col>
     </el-row>
    <addPorjectDialog  v-if="showDialog" @close="close"></addPorjectDialog>
    </div>
 </template>
<script>
import addPorjectDialog from './components/addPorjectDialog.vue';
import CesiumMap from "../../../utils/components/cesium-map.vue";
export default {
  name: "towers",
  dicts: ['model_typpe'],
  components: {
    addPorjectDialog,
    CesiumMap
  },
  data() {
    return {
      // é€‰ä¸­æ•°ç»„
      ids: [],
      // éžå•个禁用
      single: true,
      // éžå¤šä¸ªç¦ç”¨
      multiple: true,
      tableColumns: [
        {
          label: '模型名称',
          prop: 'name'
        },
        {
          label: '模型类型',
          prop: 'createBy'
        },
        {
          label: '模型路径',
          prop: 'createTime'
        },
        {
          label: '操作',
          type: 'slot',
          slotName: 'operator',
          width: '330px'
        },
      ],
      tableFilter: [
        {
          title: '项目名称',
          placeholder: '请输入项目名称',
          fieldName: 'name',
          clearable: true,
          type: 'search',
        }
      ],
      showMap:false,
      showDialog:false
    };
  },
  watch: {
  },
  created() {
  },
  mounted() {
  },
  methods: {
    // æ·»åŠ æ•°æ®
    handleAdd() {
      this.showMap  = false
      this.showDialog = true
      // this.$refs.shootPointDialog.show()
    },
    // æ›´æ–°æ•°æ®
    handleUpdate(row) {
      const ids = row.id || this.ids
      this.$refs.shootPointDialog.show(ids)
    },
    // åˆ é™¤æ•°æ®
    handleDelete(row) {
      const ids = row.id || this.ids
      const name = row.name || ''
      this.$api.deleteByName('/work/tower', ids,name).then(res => {
        this.$refs.AppTable.getData()
      })
    },
    // å¯¼å‡ºè¡¨æ ¼æ•°æ®
    handleExport() {
      this.$refs.AppTable.exportExcel()
    },
    // clickRow(row){
    //   console.log(row)
    //   this.showMap =true
    // },
    close(){
      this.showDialog =false
    }
  },
};
</script>
<style scoped>
.left-card {
  height: calc(100vh - 105px);
  overflow: auto;
}
.right-card {
  height: calc(100vh - 105px);
}
</style>
src/views/system/shootPoint/components/chooseModelDialog.vue
@@ -6,7 +6,7 @@
    width="70%">
    <el-row :gutter="20">
       <el-col :span="showMap ? 12 : 24" :xs="24">
        <AppTable ref="AppTable"  :showDeptSearch="false" :url="'tower/model/list'" :tableColumns="tableColumns"  :showSearchBtn="false">
        <AppTable ref="AppTable"  :showDeptSearch="false" :url="'tower/point/list'" :tableColumns="tableColumns"  :showSearchBtn="false">
          <template #operator="{ row }">
            <el-button size="mini" type="text"
              @click="clickRow(row)">预览</el-button>
src/views/system/shootPoint/components/shootPointDialog.vue
@@ -12,10 +12,10 @@
                <div class="chooseModel">
                  <el-button type="primary" @click='chooseModel'>选择模型</el-button>
                  <div class="modelType">
                    æ¨¡åž‹åç§°ï¼šæ¨¡åž‹å¡”1
                    æ¨¡åž‹åç§°ï¼š{{ templateName }}
                  </div>
                  <div>
                    æ¨¡åž‹ç±»åž‹ï¼šç”µå¡”
                    æ¨¡åž‹ç±»åž‹ï¼š{{ templateType }}
                  </div>
                </div>
                <div class="modelTree">
@@ -34,7 +34,7 @@
              </el-card>
          </el-col>
          <el-col :span="20">
              <InitMap v-if="showMap" @mergePoint="mergePoint" @renderData="renderData" :towerUrl="towerUrl"></InitMap>
              <InitMap v-if="showMap" @mergePoint="mergePoint" :towerUrl="towerUrl"></InitMap>
          </el-col>
      </el-row>
      <span slot="footer" class="dialog-footer">
@@ -49,12 +49,11 @@
import CesiumMap from "../../../../utils/components/cesium-map.vue";
import InitMap from "../../../../utils/components/init-map.vue";
import ChooseModelDialog from './chooseModelDialog.vue';
// const centerCartesian = Cesium.Cartesian3.fromDegrees(0,0, 236)
// const centerCartesian = Cesium.Cartesian3.fromDegrees(125.1949, 46.5143, 0)
 let globalon  = 125.1949
let globalat = 46.5143
const centerCartesian = Cesium.Cartesian3.fromDegrees(globalon,globalat , 236)
import { addPoint} from "@/api/system/template"
 let globalon  = 0
let globalat =0
const centerCartesian = Cesium.Cartesian3.fromDegrees(globalon,globalat , 0)
let rotationAngle = Cesium.Math.toRadians(60)
let relativeData 
export default{
  name:'shootPointDialog',
@@ -78,45 +77,14 @@
        animationFrameId: null,
        showMap:false,
        towerUrl:null,
        chooseModelId:null
        chooseModelId:null,
        templateName:null,
        templateType:null
    }
  },
  mounted(){
  },  
  methods:{
    renderRelativePoints(viewer, data) {
      const airPoints = [];
      data.forEach(item => {
        const groundRel = item.relativePosition;
        const groundPos = Cesium.Cartesian3.add(
          centerCartesian,
          new Cesium.Cartesian3(groundRel.x, groundRel.y, groundRel.z),
          new Cesium.Cartesian3()
        );
        this.addPoint(viewer, groundPos, Cesium.Color.BLUE, item.label);
        item.children?.forEach(child => {
          const airRel = child.relativePosition;
          const airPos = Cesium.Cartesian3.add(
            centerCartesian,
            new Cesium.Cartesian3(airRel.x, airRel.y, airRel.z),
            new Cesium.Cartesian3()
          );
          this.addPoint(viewer, airPos, Cesium.Color.YELLOW, child.label);
          // åœ°é¢ç‚¹åˆ°ç©ºä¸­ç‚¹ç”¨è™šçº¿
          this.drawLine(viewer, [groundPos, airPos], Cesium.Color.GRAY, true);
          airPoints.push(airPos);
        });
      });
      // ç©ºä¸­ç‚¹ä¹‹é—´è¿žé»„线
      if (airPoints.length > 1) {
        this.drawLine(viewer, airPoints, Cesium.Color.YELLOW);
      }
    },
    addPoint(viewer, position, color, label) {
      viewer.entities.add({
        name: label,
@@ -150,220 +118,6 @@
        }
      });
    },
    //空间坐标反渲染
    renderData(viewerM){
      let data = [
    {
        "id": "197ced8ed37d58af",
        "label": "地面点0",
        "longitude": 125.15396706149502,
        "latitude": 46.5542193007498,
        "height": 110.34582699579633,
        "children": [
            {
                "id": "197ced8ed37d12bf",
                "label": "空中点0",
                "longitude": 125.15269101779384,
                "latitude": 46.554404555297005,
                "height": 110.34669117272557,
                "relativePosition": {
                    "x": 147.6013223491609,
                    "y": -21.93333655036986,
                    "z": -75.59047747310251
                }
            }
        ],
        "relativePosition": {
            "x": 58.98418062273413,
            "y": -66.05175063293427,
            "z": -89.7526711942628
        }
    },
    {
        "id": "197ced8ed372ae58",
        "label": "地面点1",
        "longitude": 125.15396775899208,
        "latitude": 46.55421205599431,
        "height": 129.0598176992881,
        "children": [
            {
                "id": "197ced8ed3796b97",
                "label": "空中点1",
                "longitude": 125.15267500755259,
                "latitude": 46.55432990371885,
                "height": 129.06068032390132,
                "relativePosition": {
                    "x": 137.72684245044366,
                    "y": -5.778328162617981,
                    "z": -67.71026689186692
                }
            }
        ],
        "relativePosition": {
            "x": 51.19412293517962,
            "y": -55.08268230734393,
            "z": -76.7196572907269
        }
    },
    {
        "id": "197ced8ed373aacd",
        "label": "地面点2",
        "longitude": 125.15398622045456,
        "latitude": 46.55420774953827,
        "height": 147.7039314240401,
        "children": [
            {
                "id": "197ced8ed3744bed",
                "label": "空中点2",
                "longitude": 125.15268854335208,
                "latitude": 46.5542961263164,
                "height": 147.70478259476405,
                "relativePosition": {
                    "x": 127.92680113529786,
                    "y": 6.335606334730983,
                    "z": -56.756238551810384
                }
            }
        ],
        "relativePosition": {
            "x": 42.45445924857631,
            "y": -45.13116115285084,
            "z": -63.51276256516576
        }
    },
    {
        "id": "id_v5g7eyc8",
        "label": "地面点2-加高",
        "longitude": 125.15398622045456,
        "latitude": 46.55420774953827,
        "height": 147.7039314240401,
        "children": [
            {
                "id": "id_6w8bt6y5",
                "label": "空中点2-加高",
                "longitude": 125.15268854335208,
                "latitude": 46.5542961263164,
                "height": 197.70478259476405,
                "relativePosition": {
                    "x": 108.13033775659278,
                    "y": 34.448136328253895,
                    "z": -20.454920462332666
                }
            }
        ],
        "relativePosition": {
            "x": 42.45445924857631,
            "y": -45.13116115285084,
            "z": -63.51276256516576
        }
    },
    {
        "id": "id_wztyoy7w",
        "label": "地面点3",
        "longitude": 125.15421307297069,
        "latitude": 46.55421685854876,
        "height": 148.48833812454288,
        "children": [
            {
                "id": "id_cu78b9f1",
                "label": "空中点3-加高",
                "longitude": 125.15548763490776,
                "latitude": 46.55440688016316,
                "height": 198.48919020619783,
                "relativePosition": {
                    "x": -62.53922047605738,
                    "y": -96.01450884295627,
                    "z": -11.418861056677997
                }
            }
        ],
        "relativePosition": {
            "x": 28.343377863056958,
            "y": -55.307947248686105,
            "z": -62.246930407360196
        }
    },
    {
        "id": "197ced8ed37d0c3c",
        "label": "地面点3",
        "longitude": 125.15421307297069,
        "latitude": 46.55421685854876,
        "height": 148.48833812454288,
        "children": [
            {
                "id": "197ced8ed376225d",
                "label": "空中点3",
                "longitude": 125.15548763490776,
                "latitude": 46.55440688016316,
                "height": 148.48919020619783,
                "relativePosition": {
                    "x": -42.741424133535475,
                    "y": -124.12601430807263,
                    "z": -47.72024560905993
                }
            }
        ],
        "relativePosition": {
            "x": 28.343377863056958,
            "y": -55.307947248686105,
            "z": -62.246930407360196
        }
    },
    {
        "id": "197ced8ed378f22b",
        "label": "地面点4",
        "longitude": 125.15423241198647,
        "latitude": 46.55421195661101,
        "height": 129.07723014528972,
        "children": [
            {
                "id": "197ced8ed37bbaff",
                "label": "空中点4",
                "longitude": 125.1555253737729,
                "latitude": 46.55432870085922,
                "height": 129.07809286219634,
                "relativePosition": {
                    "x": -41.05480555212125,
                    "y": -131.5471526850015,
                    "z": -67.78957635723054
                }
            }
        ],
        "relativePosition": {
            "x": 34.58874865854159,
            "y": -66.75211867084727,
            "z": -76.71461268886924
        }
    },
    {
        "id": "197ced8ed372f8ad",
        "label": "地面点5",
        "longitude": 125.15423571155253,
        "latitude": 46.55422768619577,
        "height": 111.12748104722147,
        "children": [
            {
                "id": "197ced8ed373e993",
                "label": "空中点5",
                "longitude": 125.15551594222936,
                "latitude": 46.554398406019274,
                "height": 112.12216624922397,
                "relativePosition": {
                    "x": -30.5103326481767,
                    "y": -145.26342066843063,
                    "z": -74.77150713000447
                }
            }
        ],
        "relativePosition": {
            "x": 42.219932773150504,
            "y": -78.02784454869106,
            "z": -88.54415378533304
        }
    }
]
      this.renderRelativePoints(viewerM,data)
    },
    handleClose(){
      this.dialogVisible = false
    },
@@ -375,6 +129,39 @@
    },
    cancel(){
      this.showModel = false
    },
    rotateAllPoints(dataList) {
      return dataList.map(item => {
        console.log(item)
        const groundPoint = Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude, item.height);
        const towerPoint = Cesium.Cartesian3.fromDegrees(0, 0, 0);
        // æ—‹è½¬åœ°é¢ç‚¹æœ¬èº«ï¼ˆä¼šå˜ï¼Œä½†ç»•自身旋转不会移动)
        const newGroundPoint = this.rotateAroundPoint( groundPoint,towerPoint, rotationAngle);
        const newGroundCarto = Cesium.Cartographic.fromCartesian(newGroundPoint);
        const newGround = {
          ...item,
          longitude: Cesium.Math.toDegrees(newGroundCarto.longitude),
          latitude: Cesium.Math.toDegrees(newGroundCarto.latitude),
          height: newGroundCarto.height
        };
        console.log(newGround)
        // å¤„理子空中点
        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);
          return {
            ...child,
            longitude: Cesium.Math.toDegrees(rotatedAirCarto.longitude),
            latitude: Cesium.Math.toDegrees(rotatedAirCarto.latitude),
            height: rotatedAirCarto.height
          };
        });
        return newGround;
      });
    },
    mergePoint(arr,mergeNumber,viewer){
      this.treeData = arr
@@ -415,8 +202,6 @@
        })
      }
      console.log(this.treeData)
      // this.conversionPosition()
      this.dealData()
    },
    insertRaisedPoints(data, centerLon, centerLat, centerHeight, radius = 130) {
      const center = Cesium.Cartesian3.fromDegrees(centerLon, centerLat, centerHeight);
@@ -564,6 +349,8 @@
    },
    getRowData(row){
      console.log(row)
      this.templateName = row.modelName
      this.templateType = row.modelType
      this.chooseModelId = row.id
      this.showMap =false
      this.$nextTick(()=>{
@@ -603,88 +390,64 @@
    handleDrop (draggingNode, dropNode, dropType, ev) {
      console.log('拖拽完成', { draggingNode, dropNode, dropType })
    },
    toRelativePosition(lon, lat, height, centerCartesian) {
      const worldPos = Cesium.Cartesian3.fromDegrees(lon, lat, height);
      const relative = new Cesium.Cartesian3();
      Cesium.Cartesian3.subtract(worldPos, centerCartesian, relative);
      return relative;
    },
     wgs84ToCartesian(longitude, latitude, height) {
      return Cesium.Cartesian3.fromDegrees(longitude, latitude, height);
    },
    getOffsetFromTower(lon, lat, height, tower) {
      const point = this.wgs84ToCartesian(lon, lat, height);
      const towerPoint = this.wgs84ToCartesian(tower.longitude, tower.latitude, tower.height);
      const offset = Cesium.Cartesian3.subtract(point, towerPoint, new Cesium.Cartesian3());
      return {
        x: offset.x,
        y: offset.y,
        z: offset.z
      };
    },
    dealData(){
      const rawData = this.treeData
      let tower  ={
        longitude:globalon,
        latitude:globalat,
        height:0
      }
      relativeData= rawData.forEach(item => {
        item.offset = this.getOffsetFromTower(item.longitude, item.latitude, item.height, tower);
        item.children?.forEach(child => {
          child.offset = this.getOffsetFromTower(child.longitude, child.latitude, child.height, tower);
        });
      })
      console.log(relativeData)
    },
    // conversionPosition(){
    //   const rawData = this.treeData
    //   // const centerCartesian = Cesium.Cartesian3.fromDegrees(0,0, 236);
    //   const centerCartesian = Cesium.Cartesian3.fromDegrees(globalon, globalat, 236);
    //   relativeData= rawData.map(ground => {
    //     const relativeGround = this.toRelativePosition(
    //       ground.longitude,
    //       ground.latitude,
    //       ground.height,
    //       centerCartesian
    //     );
    //     const children = ground.children?.map(child => {
    //       const relativeChild = this.toRelativePosition(
    //         child.longitude,
    //         child.latitude,
    //         child.height,
    //         centerCartesian
    //       );
    //       return {
    //         ...child,
    //         relativePosition: {
    //           x: relativeChild.x,
    //           y: relativeChild.y,
    //           z: relativeChild.z,
    //         }
    //       };
    //     });
    //     return {
    //       ...ground,
    //       relativePosition: {
    //         x: relativeGround.x,
    //         y: relativeGround.y,
    //         z: relativeGround.z,
    //       },
    //       children
    //     };
    //   });
    //   console.log(relativeData)
    // },
    submit(){
      // this.dialogVisible = false
      // const dealArr = this.submitDealData()
      const transformed = this.treeData.map((groundPoint, index) => {
      const pointNumber = index + 1;
      const airPoints = groundPoint.children.map(child => ({
        altitude: child.height,
        flightTemplateId: "",
        latitude: child.latitude,
        longitude: child.longitude,
        pointNumber: 1, // å¯æ ¹æ®éœ€è¦æ”¹ä¸º child ç¼–号
        targetName:child.label
      }));
      return {
        ardAirPointLs: airPoints,
        ardGroundPoint: {
          flightTemplateId: "",
          height: groundPoint.height,
          latitude: groundPoint.latitude,
          longitude: groundPoint.longitude,
          pointNumber: 1,
          targetName: groundPoint.label
        },
        pointNumber: pointNumber
      };
    });
    let parmas = {
      modelId:this.chooseModelId,
      ard3DCoordinateSystemLs:transformed
    }
    addPoint(parmas).then(res=>{
      console.log(res)
      if(res.code == 200) {
        this.$message({
          message: '新增模板成功',
          type: 'success'
        })
        this.$emit('on-submit')
      this.$emit('close')
      console.log(this.chooseModelId)
      console.log(relativeData)
      }
    })
    console.log(parmas)
    },
    // 计算点A绕点B逆时针旋转指定角度后的新位置
    //  ç©ºä¸­ç‚¹æˆ–者地面点笛卡尔坐标 å¡”的笛卡尔坐标    å¡”的朝向值
    rotateAroundPoint(startPoint,pivotPoint,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());
    }
  }
}
src/views/system/shootPoint/index.vue
@@ -3,14 +3,12 @@
     <el-row :gutter="20">
       <el-col :span="showMap ? 12 : 24" :xs="24">
        <AppTable ref="AppTable" selection :showDeptSearch="false" :url="'tower/point/list'" :tableColumns="tableColumns"
          :tableFilter="tableFilter"  @clickRow="clickRow">
          :tableFilter="tableFilter">
          <template #operatorBox="{ row }">
            <el-button type="primary" plain icon="el-icon-plus" size="mini"
              @click="handleAdd">新增模型</el-button>
              @click="handleAdd">新增模板</el-button>
          </template>
          <template #operator="{ row }">
            <el-button size="mini" type="text" icon="el-icon-edit"
              @click="handleUpdate(row)">修改</el-button>
            <el-button size="mini" type="text" icon="el-icon-delete"
              @click="handleDelete(row)">删除</el-button>
          </template>
@@ -21,7 +19,7 @@
       </el-col>
     </el-row>
    
    <shootPointDialog  v-if="showDialog" @close="close"></shootPointDialog>
    <shootPointDialog  @on-submit="$refs.AppTable.getData()"  v-if="showDialog" @close="close"></shootPointDialog>
  </div>
</template>
@@ -112,6 +110,7 @@
    // },
    close(){
      this.showDialog =false
      this
    }
  },
};