无人机管理后台前端(已迁走)
张含笑
2025-10-30 fd3d2b1b892539c96238387d560ca7fe04cb7e63
feat:围栏/禁飞区数据
5 files modified
597 ■■■■ changed files
src/api/layer/index.js 9 ●●●● patch | view | raw | blame | history
src/styles/element-ui.scss 273 ●●●●● patch | view | raw | blame | history
src/views/layerManagement/components/leftList.vue 26 ●●●● patch | view | raw | blame | history
src/views/layerManagement/components/rightEdit.vue 258 ●●●●● patch | view | raw | blame | history
src/views/layerManagement/index.vue 31 ●●●●● patch | view | raw | blame | history
src/api/layer/index.js
@@ -32,4 +32,11 @@
        method: 'post',
        data
    })
}
}
// 获取文件夹
export const dataFolderApi = (id) => {
    return request({
        url:`/drone-device-core/layer/api/v1/getFolderByCategoryId/${id}`,
        method: 'get',
    })
  };
src/styles/element-ui.scss
@@ -253,7 +253,280 @@
        border: 1px solid #fff !important;
    }
}
//时间日期选择器
.ztzf-layer-date-picker {
    box-shadow: none !important;
    background: transparent;
    border: 1px solid #fff;
    border-radius: 5px;
    .el-input__wrapper {
        box-shadow: none !important;
        background-color: transparent !important;
    width: 300px;
        &.is-focus {
            box-shadow: none !important;
        }
    }
    .el-range-input {
        color: #fff;
        &::placeholder {
            color: rgba(255, 255, 255, 0.37);
        }
    }
    .el-range-separator {
        color: #ffffff;
    }
}
// 日期下拉框
.ztzf-layer-date-picker-popper {
    background: rgba(0,0,0,0.8) !important;
    border: 1px solid !important;
    border-radius: 0px 0px 8px 8px;
    border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1 !important;
    .el-picker-panel {
        .el-picker-panel__body-wrapper {
            .el-picker-panel__body {
                background: rgba(0,0,0,0.8) !important;
                color: #fff !important;
                .el-date-picker__time-header {
                    border: none;
                    .el-date-picker__editor-wrap {
                        .el-time-panel {
                            background: rgba(0,0,0,0.8) !important;
                            border-radius: 0px 0px 8px 8px;
                            border: 1px solid !important;
                            border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1 !important;
                            // 将 time-picker-popper 的样式移到这里
                            .el-time-panel__header {
                background: rgba(0,0,0,0.8) !important;
                                color: #ffffff !important;
                                border-bottom: 1px solid rgba(255, 255, 255, 0.37) !important;
                            }
                            .el-time-panel__content {
                                .el-time-spinner__item {
                                    color: #e6e6e6 !important;
                                    &:hover {
                                        background: rgba(0,0,0,0.8) !important;
                                    }
                                    &.active {
                                        color: #479dff !important;
                                        font-weight: bold;
                                    }
                                }
                            }
                            .el-time-panel__footer {
                background: rgba(0,0,0,0.8) !important;
                                border-top: 1px solid rgba(71, 157, 255, 0.3) !important;
                                .el-button {
                                    color: #fff !important;
                                    &:hover {
                                        color: #479dff !important;
                                    }
                                }
                                .el-time-panel__btn.cancel {
                                    color: #fff !important;
                                    &:hover {
                                        color: #fff !important;
                                    }
                                }
                            }
                        }
                    }
                }
                .el-picker-panel__content {
                    .el-date-range-picker__header {
                        .el-picker-panel__icon-btn {
                            color: #fff !important;
                            &:hover {
                                color: var(--el-color-primary) !important;
                            }
                            .el-icon {
                                &::before {
                                    color: inherit !important;
                                }
                            }
                        }
                        & > div {
                            span {
                                color: #fff !important;
                            }
                        }
                    }
                    .el-date-table {
                        th {
                            color: #fff !important;
                        }
                        td.disabled div {
                            background-color: #00193b !important;
                            color: #505050 !important;
                            cursor: not-allowed;
                        }
                        // td:hover,
                        // td.current:not(.disabled) {
                        //     background: rgba(60, 121, 202) !important;
                        //     color: white !important;
                        // }
                        td.start-date span,
                        td.start-date span,
                        td.end-date span {
                            background-color: #012350 !important;
                        }
                        td.in-range div,
                        td.in-range div:hover,
                        &.is-week-mode .el-date-table__row.current div,
                        &.is-week-mode .el-date-table__row:hover div {
                            background-color: rgba(60, 121, 202) !important;
                        }
                    }
                }
            }
        }
    }
    .el-popper__arrow {
        &::before {
            background: #0d3556 !important;
            border: 1px solid #0d3556 !important;
            box-sizing: border-box;
        }
    }
    .el-picker-panel__sidebar {
        background: #012350;
        color: #fff;
        .el-picker-panel__shortcut {
            color: #ffffff;
        }
    }
    .el-picker-panel__body {
        .el-input.is-disabled .el-input__wrapper {
            background: #012350;
            box-shadow: 0 0 0 0.1rem#409eff;
            color: #fff;
            .el-input__inner {
                color: #fff !important;
            }
        }
        .el-button.is-disabled,
        .el-button.is-disabled:hover {
            background: #012350;
        }
        .el-input__wrapper {
            background: #012350;
            box-shadow: 0 0 0 0.1rem#409eff;
            color: #fff;
            .el-input__inner {
                color: #fff !important;
            }
        }
    }
    .el-picker-panel__footer {
        background: #012350;
        color: #fff;
        border-top: none;
        .el-button--small {
            box-shadow: 0 0 0 0.1rem#409eff;
            background-color: #012350;
            color: #fff;
            border: none;
            &:hover {
                background-color: #012350;
                color: #fff;
                border-color: #409eff;
            }
        }
    }
}
.ztzf-layer-time-picker-popper {
  background: rgba(0,0,0,0.8) !important;
    border-radius: 0px 0px 8px 8px;
    border: 1px solid !important;
    border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1 !important;
    .el-popper__arrow::before {
        background: rgba(0,0,0,0.8) !important;
        border: 1px solid #479dff !important;
    }
    .el-time-panel__header {
        background: rgba(0,0,0,0.8) !important;
        color: #ffffff !important;
        border-bottom: 1px solid rgba(71, 157, 255, 0.3) !important;
    }
    .el-time-panel__content {
        // background: #012350 !important;
        .el-time-spinner__item {
            color: #e6e6e6 !important;
            &:hover {
                background: rgba(71, 157, 255, 0.3) !important;
            }
            &.active {
                color: #479dff !important;
                font-weight: bold;
            }
        }
    }
    .el-time-panel__footer {
        background: rgba(0,0,0,0.8) !important;
        border-top: 1px solid rgba(71, 157, 255, 0.3) !important;
        .el-button {
            color: #fff !important;
            &:hover {
                color: #479dff !important;
            }
        }
        .el-time-panel__btn.cancel {
            color: #fff !important;
            &:hover {
                color: #fff !important;
            }
        }
    }
}
//======以下针对表格功能 增删查改样式和弹窗=========
.ztzf-dialog-mange {
  .el-dialog__body {
src/views/layerManagement/components/leftList.vue
@@ -12,7 +12,7 @@
      </div>
    </div>
    <div class="btnGroups">
      <el-button class="ztzf-button" @click="addFence">新增围栏</el-button>
      <el-button class="ztzf-button" @click="addFence">{{props.activeName === '电子围栏' ? '新增围栏' : '新增禁飞区'}}</el-button>
      <el-button class="ztzf-button" @click="addFolder">新增文件夹</el-button>
      <el-button class="ztzf-button" @click="selectAll">全选</el-button>
    </div>
@@ -38,7 +38,7 @@
            <span>{{ node.label }}</span>
            <!-- 操作按钮组 -->
            <div class="tree-node-actions">
              <el-button
              <el-button v-if="node.level !== 1"
                icon="el-icon-location"
                type="text"
                @click="handleLocation(node)"
@@ -57,6 +57,7 @@
import { treeDataApi ,deleteFenceApi} from '@/api/layer/index';
import { inject, onMounted } from 'vue';
const emit = defineEmits(['update:coverData', 'update:deitData','newFencesMethods']);
import EventBus from '@/utils/eventBus'
const coverData = ref();
const layerParams = inject('layerParams');
const checkedKeys = ref([]); // 存储勾选的节点key
@@ -80,6 +81,7 @@
  try {
    const res = await treeDataApi();
    treeAllData.value = res.data.data;
console.log('接口数据',treeAllData.value);
    setupWatch();
  } catch (error) {
@@ -147,6 +149,11 @@
  coverData.value = [];
    layerParams.value.editNest = true;
    layerParams.value.decideWhetherToAddOrEdit = 2
      if (props.activeName === '电子围栏') {
    layerParams.value.fenceType = 1; // 电子围栏类型标识
  } else if (props.activeName === '自定义禁飞区') {
    layerParams.value.fenceType = 2; // 自定义禁飞区类型标识
  }
  emit('update:deitData', node.data);
};
@@ -162,19 +169,26 @@
const addFence = () => {
  layerParams.value.addNest = true;
   layerParams.value.decideWhetherToAddOrEdit = 1
   emit('newFencesMethods');
  console.log('layerParams.value',layerParams.value);
   if (props.activeName === '电子围栏') {
    layerParams.value.fenceType = 1; // 电子围栏类型标识
  } else if (props.activeName === '自定义禁飞区') {
    layerParams.value.fenceType = 2; // 自定义禁飞区类型标识
  }
   emit('newFencesMethods');
};
// 新增文件夹
const addFolder = () => {
  layerParams.value.addFolder = true
  layerParams.value.addAnEditingFolder =1
};
onMounted(() => {
  gettreeDataApi();
  EventBus.on('gettreeDataApi', gettreeDataApi)
});
const changeClick = val => {};
onBeforeUnmount(()=>{
    EventBus.off('gettreeDataApi', gettreeDataApi)
})
</script>
<style scoped lang="scss">
src/views/layerManagement/components/rightEdit.vue
@@ -1,11 +1,17 @@
<template>
  <div class="rightContainer">
    <div class="contenttitle">围栏信息</div>
    <div class="contenttitle">{{ layerParams.fenceType === 1 ? '围栏' : '禁飞区' }}信息</div>
    <div class="centerBox">
      <div class="itemRow">
        <div class="label">文件夹名称</div>
        <div>
          <el-select class="ztzf-layer-search" popper-class="ztzf-select-popper" clearable v-model="formData.area" placeholder="请选择">
          <el-select
            class="ztzf-layer-search"
            popper-class="ztzf-select-popper"
            clearable
            v-model="formData.folder_id"
            placeholder="请选择"
          >
            <el-option
              v-for="item in options"
              :key="item.value"
@@ -16,37 +22,82 @@
        </div>
      </div>
      <div class="itemRow">
        <div class="label">围栏名称</div>
        <div class="label">{{ layerParams.fenceType === 1 ? '围栏' : '禁飞区' }}名称</div>
        <div>
          <el-input class="ztzf-input inputName" v-model="formData.name" placeholder="请输入" />
        </div>
      </div>
      <div class="itemRow" v-if="layerParams.fenceType === 2">
        <div class="label">禁飞区管控时间</div>
        <div>
          <el-date-picker
            popper-class="ztzf-layer-date-picker-popper"
            class="ztzf-layer-date-picker layertimer"
            v-model="timeValue"
             type="datetimerange"
              format="YYYY-MM-DD HH:mm"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
          />
        </div>
      </div>
      <div class="itemRow">
        <div class="label">围栏备注</div>
        <div class="label">{{ layerParams.fenceType === 1 ? '围栏' : '禁飞区' }}备注</div>
        <div>
          <el-input v-model="formData.description" :rows="5" type="textarea" placeholder="请输入" />
        </div>
      </div>
    </div>
    <div class="detailInfo">
      <div class="infpItem">
      <div class="infpItem" v-if="layerParams.fenceType === 1">
        <div class="itemData">
          <div class="title">围栏总数量</div>
          <div class="num"><span>{{totalCount}}</span>个</div>
          <div class="num">
            <span>{{ totalCount }}</span
            >个
          </div>
        </div>
        <div class="itemData">
          <div class="title">围栏总面积</div>
          <div class="num"><span>{{totalArea}}</span>m²</div>
          <div class="num">
            <span>{{ totalArea }}</span
            >m²
          </div>
        </div>
      </div>
      <div class="infpItem" v-if="layerParams.fenceType === 2">
        <div class="itemData">
          <div class="title">禁飞区面积</div>
          <div class="num">
            <span>1111</span>
          </div>
        </div>
        <div class="itemData">
          <div class="title">禁飞区高度</div>
          <div class="num noFly">
            <span
              ><el-input
                class="ztzf-input inputName"
                v-model="formData.altitude"
                placeholder="请输入" /></span
            >米
          </div>
        </div>
      </div>
      <div class="infpItem">
        <div class="itemData">
          <div class="title">创建时间</div>
          <div class="num"><span>{{formData?.create_time}}</span></div>
          <div class="num">
            <span>{{ formData?.create_time }}</span>
          </div>
        </div>
        <div class="itemData">
          <div class="title">创建人</div>
          <div class="num"><span>{{formData?.create_name}}</span></div>
          <div class="num">
            <span>{{ formData?.create_name }}</span>
          </div>
        </div>
      </div>
    </div>
@@ -58,31 +109,15 @@
</template>
<script setup>
import { deitFenceApi,addFenceApi } from '@/api/layer/index';
const emit = defineEmits([ 'callParentMethod']);
import { ElMessage } from 'element-plus';
import dayjs from 'dayjs'
import { deitFenceApi, addFenceApi } from '@/api/layer/index';
import EventBus from '@/utils/eventBus';
const emit = defineEmits(['callParentMethod']);
const layerParams = inject('layerParams');
const options = [
  {
    value: 'Option1',
    label: '111',
  },
  {
    value: 'Option2',
    label: 'Option2',
  },
  {
    value: 'Option3',
    label: 'Option3',
  },
  {
    value: 'Option4',
    label: 'Option4',
  },
  {
    value: 'Option5',
    label: 'Option5',
  },
]
const options = ref([]);
const timeValue = ref('')
options.value = layerParams.value.folderOption;
const formData = ref({
  folder_id: '',
  name: '',
@@ -93,17 +128,41 @@
  control_start_time: '',
  control_end_time: '',
  geo_data: '',
  noFlyZoneArea: '',
  altitude:''
});
const detailData = ref(null)
const totalArea = ref(null)
const totalCount = ref(null)
detailData.value = layerParams.value.editDetailData
 totalArea.value = layerParams.value.total_area
 totalCount.value = layerParams.value.total_count
const detailData = ref(null);
const totalArea = ref(null);
const totalCount = ref(null);
detailData.value = layerParams.value.editDetailData;
totalArea.value = layerParams.value.total_area;
totalCount.value = layerParams.value.total_count;
watch(
  () => timeValue.value,
  (newTimeArr) => {
    // timeValue 是数组:[开始时间, 结束时间](未选择时为 '' 或空数组)
    if (Array.isArray(newTimeArr) && newTimeArr.length === 2) {
      const startTime = newTimeArr[0]; // 开始时间(Date 对象或时间字符串)
      const endTime = newTimeArr[1];   // 结束时间(Date 对象或时间字符串)
      // 用 dayjs 格式化为 "YYYY-MM-DD HH:mm:ss" 格式
      formData.value.control_start_time = dayjs(startTime).format('YYYY-MM-DD HH:mm:ss');
      formData.value.control_end_time = dayjs(endTime).format('YYYY-MM-DD HH:mm:ss');
      console.log('格式化后开始时间:', formData.value.control_start_time);
      console.log('格式化后结束时间:', formData.value.control_end_time);
    } else {
      // 未选择时间时,清空两个字段
      formData.value.control_start_time = '';
      formData.value.control_end_time = '';
    }
  },
  { immediate: true } // 初始化时执行一次,处理默认值
);
const clearAllData = () => {
  detailData.value = {}
  detailData.value = {};
  // 清空回显相关数据
  detailData.value = {};
  totalArea.value = null;
@@ -117,63 +176,86 @@
  layerParams.value.polygonPosition = null;
};
const cancelHandel = () => {
   clearAllData()
   emit('callParentMethod');
  clearAllData();
  emit('callParentMethod');
  EventBus.emit('gettreeDataApi');
};
const submitHandle = () => {
    const params={
    folder_id:1,
    name:'测试',
    description:'333366663',
  const params = {
    folder_id: layerParams.value.fenceType === 1 ? 1 : 2,
    name: formData.value.name,
    description: formData.value.description,
    is_enabled: true,
    area:1200,
    altitude:120,
    area: formData.value.area,
    altitude: formData.value.altitude,
    geo_data: layerParams.value.polygonPosition,
    }
addFenceApi(params).then(res=>{
  console.log('保存',res);
})
  clearAllData()
   emit('callParentMethod');
     control_start_time:formData.value.control_start_time,
  control_end_time:formData.value.control_end_time,
  };
  // 如果是编辑模式,才添加 id
  if (layerParams.value.decideWhetherToAddOrEdit === 2) {
    params.id = detailData.value.id;
  }
  console.log('formData', params);
  if (layerParams.value.decideWhetherToAddOrEdit === 1) {
    //围栏新增
    addFenceApi(params).then(res => {
      if (res.status === 200) {
        ElMessage({
          message: '新增成功',
          type: 'success',
        });
      }
    });
  } else if (layerParams.value.decideWhetherToAddOrEdit === 2) {
    //围栏编辑
    deitFenceApi(params).then(res => {
      if (res.status === 200) {
        ElMessage({
          message: '编辑成功',
          type: 'success',
        });
      }
    });
  }
  clearAllData();
  emit('callParentMethod');
  EventBus.emit('gettreeDataApi')
};
onMounted(() => {
  // 先清空表单,避免残留旧数据
  formData.value = {
    folder_id: '',
    name: '',
    description: '',
    is_enabled: false,
    area: '',
    altitude: '',
    control_start_time: '',
    control_end_time: '',
    geo_data: '',
  };
  // 获取判断状态(默认设为 1,防止未定义时报错)
  // 获取判断状态(默认设为 1)
  const addOrEditStatus = layerParams.value.decideWhetherToAddOrEdit || 1;
  if (addOrEditStatus === 2) {
    // 编辑状态:回显数据(从 layerParams.editDetailData 读取)
    const editData = layerParams.value.editDetailData || {};
    // 表单数据回显(将编辑数据赋值给 formData,与表单字段一一对应)
    formData.value = {
      folder_id: editData.folder_id || '', // 文件夹ID(需与下拉框 options 的 value 匹配)
      name: editData.name || '', // 围栏名称
      description: editData.description || '', // 围栏备注
      is_enabled: editData.is_enabled || false, // 是否启用
      area: editData.area || '', // 面积
      altitude: editData.altitude || '', // 高度
      control_start_time: editData.control_start_time || '', // 控制开始时间
      control_end_time: editData.control_end_time || '', // 控制结束时间
      geo_data: editData.geo_data || '', // 地理数据(编辑时回显原始坐标)
      folder_id: editData.folder_id || '',
      name: editData.name || '',
      description: editData.description || '',
      is_enabled: editData.is_enabled || false,
      area: editData.area || '',
      altitude: editData.altitude || '',
      control_start_time: editData.control_start_time || '',
      control_end_time: editData.control_end_time || '',
      geo_data: editData.geo_data || '',
    };
    // 下拉框回显(若 editData 中有文件夹名称,需匹配 options 的 value)
    // 假设 editData.folder_name 是文件夹名称,需找到对应的 value 赋值给 formData.area
    const matchedFolder = options.find(item => item.label === (editData.folder_name || ''));
    if (matchedFolder) {
      formData.value.area = matchedFolder.value;
     if (editData.folder_id && options.value.length > 0) {
      const matchedFolder = options.value.find(
        item => item.value === String(editData.folder_id)
      );
      if (matchedFolder) {
        formData.value.folder_id = matchedFolder.value;
      }
    }
    // 回显总数和总面积(编辑时可能需要展示)
@@ -223,6 +305,10 @@
    .label {
      margin-bottom: 5px;
    }
   :deep(.el-date-editor.el-input__wrapper) {
  width: auto !important;
}
  }
  .detailInfo {
    .infpItem {
@@ -242,6 +328,10 @@
          color: #ffffff;
        }
      }
      .noFly {
        display: flex;
        align-items: center;
      }
      .itemData {
        width: 50%;
      }
src/views/layerManagement/index.vue
@@ -29,6 +29,7 @@
</template>
<script setup>
import { dataFolderApi } from '@/api/layer/index';
import folderFile from '@/views/layerManagement/components/folderFile.vue'
import { parseGeoDataToPositions } from '@/utils/geoParseUtil';
import { flyVisual } from '@/utils/cesium/mapUtil';
@@ -43,6 +44,8 @@
import { provide } from 'vue';
const activeName = ref('电子围栏');
const activeType = ref('0');
let tbJwdList = [];
const selectDataList = ref([]);
// 当前面位置信息
let curPolygonPosition = [];
const tabData = ref([
@@ -70,17 +73,37 @@
  addAnEditingFolder:null,//新增or编辑文件夹
  addFolder:false,
  editFolder:false,
  folderOption:[] ,//文件夹选项
  fenceType:1,
});
provide('layerParams', layerParams);
const handleClick = tab => {
  const clickedTab = tabData.value.find(item => item.type === tab.paneName);
  activeType.value = clickedTab.type;
  activeName.value = clickedTab.name;
    getdataFolderApi();
};
let tbJwdList = [];
const selectDataList = ref([]);
const getdataFolderApi=()=>{
 let id;
  if (activeName.value === '电子围栏') {
    id = 1;
  } else if (activeName.value === '自定义禁飞区') {
    id = 2;
  } else {
    return;
  }
  dataFolderApi(id).then(res=>{
 const originalFolderList = res.data.data || [];
  const formattedFolderOption = originalFolderList.map(folder => ({
        label: folder.name,
        value: folder.id.toString()
      }));
       layerParams.value.folderOption = formattedFolderOption;
  })
}
// 编辑围栏区域
const handleEdit = val => {
viewer.entities.removeAll();
@@ -281,9 +304,11 @@
    cesium.removeEventListener('contextmenu', preventDefault);
  }
};
provide('layerParams', layerParams);
onMounted(() => {
  initMap();
  cesiumContextMenu();
  getdataFolderApi()
});
onBeforeUnmount(() => {