无人机管理后台前端(已迁走)
shuishen
2025-09-11 b5d3e5db9ef39cd0bdf6b49246be6818a58d731c
feat:空域录入基础配置相关处理
3 files modified
1 files added
770 ■■■■■ changed files
src/styles/element-ui.scss 27 ●●●●● patch | view | raw | blame | history
src/utils/cesium/publicCesium.js 17 ●●●● patch | view | raw | blame | history
src/views/airspace/airspaceEntering.vue 569 ●●●● patch | view | raw | blame | history
src/views/airspace/components/AirspaceMap.vue 157 ●●●●● patch | view | raw | blame | history
src/styles/element-ui.scss
@@ -138,4 +138,29 @@
      }
    }
  }
}
}
// ----------空域规划-----空域录入样式相关----------
.airspace-dialog {
  .el-dialog__header {
    span {
      padding-left: 16px !important;
      display: inline-block;
    }
    .el-dialog__title {
      font-family: 'Source Han Sans CN' !important;
      font-weight: bold !important;
      font-size: 16px !important;
      color: #363636 !important;
    }
  }
  .el-dialog__body {
    .el-textarea__inner {
      resize: none !important;
    }
  }
}
// ----------空域规划-----空域录入样式相关----------
src/utils/cesium/publicCesium.js
@@ -193,11 +193,12 @@
            gl = null
            this.viewer?.destroy() // 销毁Viewer实例
            this.viewer = null
            var cesiumContainer = document.getElementById(this.viewerDom)
            if (cesiumContainer && removeDom) {
                cesiumContainer.remove() // 移除与地图相关的DOM元素
                this.viewerDom = null
            }
            // var cesiumContainer = document.getElementById(this.viewerDom)
            // if (cesiumContainer && removeDom) {
            //     cesiumContainer.remove() // 移除与地图相关的DOM元素
            //     this.viewerDom = null
            // }
            this.viewerDom = null
        }
    }
@@ -209,7 +210,7 @@
    async switchContour (open) {
        if (open) {
            await this.boundary?.openContour()
        } else {
            this.boundary?.closeContour()
        }
@@ -752,7 +753,7 @@
            Cesium.Cartesian3.subtract(a, this.viewer?.camera.position, a),
            Cesium.Cartesian3.normalize(r, r),
            Cesium.Cartesian3.normalize(a, a)) : (r = c.direction,
            a = h.direction)
                a = h.direction)
        let d = Cesium.Cartesian3.dot(r, a)
            , p = 0
        return d < 1 && (p = Math.acos(d)),
@@ -781,7 +782,7 @@
                , r = Cesium.Math.acosClamped(-e)
            a > 0 && a > r && (a = r - Cesium.Math.EPSILON4),
                r = Cesium.Math.acosClamped(e),
            a < 0 && -a > r && (a = -r + Cesium.Math.EPSILON4)
                a < 0 && -a > r && (a = -r + Cesium.Math.EPSILON4)
        }
        return a
    }
src/views/airspace/airspaceEntering.vue
@@ -1,296 +1,319 @@
<!--
 * @Author       : yuan
 * @Date         : 2025-09-11 09:23:03
 * @LastEditors  : yuan
 * @LastEditTime : 2025-09-11 09:24:27
 * @FilePath     : \src\views\airspace\airspaceEntering.vue
 * @Description  :
 * Copyright 2025 OBKoro1, All Rights Reserved.
 * 2025-09-11 09:23:03
-->
<template>
  <div class="airspaceManage">
    <div class="search-box">
      <el-form :model="params" inline>
        <el-form-item label="类型名称:">
          <el-input v-model="params.algName" placeholder="请输入空域名称" clearable />
        </el-form-item>
        <el-form-item label="创建人:">
          <el-input v-model="params.algName" placeholder="请输入空域名称" clearable />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="getList">搜索</el-button>
          <el-button @click="cancelSearch">取消</el-button>
        </el-form-item>
      </el-form>
      <div>
        <el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增类型</el-button>
      </div>
    </div>
    <div class="mange-table">
      <el-table border :data="tableList" class="custom-header">
        <el-table-column label="序号" type="index" width="60"></el-table-column>
        <el-table-column prop="model_name" label="类型名称" align="center" show-overflow-tooltip></el-table-column>
        <el-table-column prop="alg_type" label="创建时间" align="center"></el-table-column>
        <el-table-column prop="alg_type" label="创建人" align="center"></el-table-column>
        <el-table-column label="操作" width="180" align="center">
          <template #default="scope">
            <el-button icon="el-icon-view" type="text" @click="handleDetail(scope.row)">查看</el-button>
            <el-button icon="el-icon-delete" type="text" @click="handleDelete">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <div class="pagination">
      <el-pagination class="ztzf-pagination" popper-class="custom-pagination-dropdown" background
        :page-sizes="[10, 20, 30, 40, 50, 100]" :size="size" v-model:current-page="params.current"
        v-model:page-size="params.size" layout="total, sizes, prev, pager, next, jumper" :total="total"
        @size-change="handleSizeChange" @current-change="handleCurrentChange" />
    </div>
  </div>
  <el-dialog class="ztzf-dialog" append-to-body v-model="isShowView" title="查看" :width="pxToRem(600)"
    :close-on-click-modal="false" :destroy-on-close="true">
    <div class="content">
      <table class="view-table" border>
        <tr>
          <td class="label">空域类型</td>
          <td class="value">{{ rowView.model_name }}</td>
          <td class="label">创建时间</td>
          <td class="value">{{ rowView.alg_type }}</td>
        </tr>
      </table>
    </div>
  </el-dialog>
  <el-dialog class="ztzf-dialog" append-to-body v-model="isShowEditView" title="新增" :width="pxToRem(800)"
    :close-on-click-modal="false" :destroy-on-close="true">
    <div class="content-edit">
      <el-form ref="ruleFormRef" :model="editParams" :rules="rules" inline>
        <el-form-item label="空域名称">
          <el-input v-model="editParams.model_name" />
        </el-form-item>
        <div class="btns">
          <el-button type="primary" @click="submit(ruleFormRef)">确认</el-button>
          <el-button @click="isShowEditView = false">取消</el-button>
        </div>
      </el-form>
    </div>
  </el-dialog>
  <basic-container>
    <avue-crud :option="option" :table-loading="loading" :data="data" v-model:page="page" :permission="permissionList"
      v-model="form" ref="crud" @row-update="rowUpdate" @row-save="rowSave" @row-del="rowDel" :before-open="beforeOpen"
      @search-change="searchChange" @search-reset="searchReset" @selection-change="selectionChange"
      @current-change="currentChange" @size-change="sizeChange" @refresh-change="refreshChange" @on-load="onLoad">
      <template #menu-left>
        <el-button type="primary" icon="el-icon-plus" @click="addAirspace">新增空域</el-button>
      </template>
      <template #status="{ row }">
        <el-tag>{{ row.statusName }}</el-tag>
      </template>
      <template #category="{ row }">
        <el-tag>{{ row.categoryName }}</el-tag>
      </template>
    </avue-crud>
  </basic-container>
  <AirspaceMap v-model:show="airspaceMapShow" />
</template>
<script setup>
import { getAlgorithmManageList, algorithmManageEdit } from '@/api/airspaceManage/airspaceManage'
import { getDictionaryByCode } from '@/api/system/dictbiz'
const total = ref(0)
const params = ref({
  current: 1,
  size: 10,
  algName: '',
  algTypeCode: '',
import {
  searchManagementApi,
  listOfSpotTypesApi,
  spotTypesCreateApi,
  editSpotTypeApi,
  deleteSpotTypeApi,
} from '@/api/patchManagement/index'
import { ref, computed, watch } from 'vue'
import { enable, disable } from '@/api/resource/oss'
import { useStore } from 'vuex'
import func from '@/utils/func'
import { useRouter } from 'vue-router'
import AirspaceMap from '@/views/airspace/components/AirspaceMap.vue'
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
const store = useStore()
const router = useRouter()
// ---------------- data ----------------
const form = ref({})
const query = ref({})
const creatorOption = ref([])
const loading = ref(true)
const page = ref({
  pageSize: 20,
  currentPage: 1,
  total: 0,
  lotValue: '',
  userName: '',
})
const selectionList = ref([])
let tableList = ref([])
let isShowView = ref(false)
let rowView = ref({})
let isShowEditView = ref(false)
let sfTypes = ref([])
const ruleFormRef = ref()
let editParams = ref({
  model_name: '',
  alg_type: '',
  qua_rate: '',
  pass_rate: '',
  event_type: '',
  remark: ''
})
const rules = reactive({
  qua_rate: [
    { required: true, message: '请输入最低准确率', trigger: 'blur' },
const option = ref({
  addBtn: false,
  tip: false,
  searchShow: true,
  searchMenuSpan: 12,
  searchMenuPosition: 'right',
  border: true,
  index: true,
  indexLabel: '序号',
  indexWidth: 60,
  selection: true,
  grid: false,
  menuWidth: 180,
  labelWidth: 100,
  dialogWidth: 880,
  dialogClickModal: false,
  height: 'auto',
  calcHeight: 20,
  refreshBtn: false,
  gridBtn: false,
  searchShowBtn: false,
  columnBtn: false,
  column: [
    {
      validator: (rule, value, callback) => {
        if (!/^\d+(\.\d+)?$/.test(value)) {
          callback(new Error('请输入有效的数字(整数或小数)'))
        } else if (parseFloat(value) < 0.4) {
          callback(new Error('最低准确率不能小于0.4'))
        } else if (parseFloat(value) >= parseFloat(editParams.value.pass_rate)) {
          callback(new Error('最低准确率必须小于最高准确率'))
        } else {
          callback()
        }
      },
      trigger: 'blur'
    }
  ],
  pass_rate: [
    { required: true, message: '请输入最低准确率', trigger: 'blur' },
      label: '名称',
      prop: 'patches_type',
      search: true,
      searchSpan: 4,
      searchLabelWidth: 54,
      rules: [{ required: true, message: '请输入名称', trigger: 'blur' }],
    },
    {
      validator: (rule, value, callback) => {
        if (!/^\d+(\.\d+)?$/.test(value)) {
          callback(new Error('请输入有效的数字(整数或小数)'))
        } else if (parseFloat(value) > 0.8) {
          callback(new Error('最高准确率不能大于0.8'))
        } else {
          callback()
        }
      },
      trigger: 'blur'
    }
  ],
  event_type: [
      label: '类型',
      prop: 'patches_type',
      search: true,
      searchSpan: 4,
      searchLabelWidth: 54,
      rules: [{ required: true, message: '请输入类型', trigger: 'blur' }],
    },
    {
      required: true,
      message: '请选择事件生成类型',
      trigger: 'change',
      label: '高度',
      prop: 'patches_type',
      rules: [{ required: true, message: '请输入高度', trigger: 'blur' }],
    },
    {
      label: '面积',
      prop: 'patches_type',
      rules: [{ required: true, message: '请输入面积', trigger: 'blur' }],
    },
    {
      label: '管控时段',
      prop: 'patches_type',
      search: true,
      searchSpan: 4,
      rules: [{ required: true, message: '请输入管控时间', trigger: 'blur' }],
    },
    {
      label: '创建人',
      prop: 'patches_type',
      rules: [{ required: true, message: '请输入创建人', trigger: 'blur' }],
    },
    {
      label: '创建时间',
      prop: 'patches_type',
      rules: [{ required: true, message: '请输入创建时间', trigger: 'blur' }],
    },
  ],
})
function cancelSearch () {
  params.value = {
    algName: '',
    algTypeCode: '',
const data = ref([])
// 获取搜索数据
const getsearchManagementApi = () => {
  searchManagementApi().then(res => {
    const creatorOptionuniqueMap = new Map()
    res.data.data.user_names.forEach(item => {
      const [key, value] = Object.entries(item)[0]
      if (!creatorOptionuniqueMap.has(key)) {
        creatorOptionuniqueMap.set(key, value)
      }
    })
    creatorOption.value = Array.from(creatorOptionuniqueMap).map(([key, value]) => ({
      label: value,
      value: value,
    }))
  })
}
// ---------------- watch ----------------
watch(
  () => form.value.category,
  () => {
    const category = func.toInt(form.value.category)
    option.value.column.filter(item => {
      if (item.prop === 'appId') {
        item.display = category === 4
      }
    })
  }
  getList()
)
// ---------------- computed ----------------
const permission = computed(() => store.getters.permission)
const permissionList = computed(() => ({
  addBtn: validData(permission.value.oss_add),
  viewBtn: validData(permission.value.oss_view),
  delBtn: validData(permission.value.oss_delete),
  editBtn: validData(permission.value.oss_edit),
}))
const ids = computed(() => {
  return selectionList.value.map(ele => ele.id).join(',')
})
const airspaceMapShow = ref(false)
// ---------------- methods ----------------
const addAirspace = () => {
  form.value = {}
  airspaceMapShow.value = true
}
function getList () {
  getAlgorithmManageList(params.value).then(res => {
    tableList.value = res.data.data.records || []
    total.value = res.data.data.total || 0
  })
}
function getSFType () {
  getDictionaryByCode('WORK_ORDER_TYPE').then(res => {
    sfTypes.value = res.data.data.WORK_ORDER_TYPE || []
  })
}
function handleDetail (row) {
  isShowView.value = true
  rowView.value = row
}
function handleDelete (row) {
  console.log(row)
}
function handleAdd (row) {
  isShowEditView.value = true
  // editParams.value = { ...row }
}
async function submit (formValidate) {
  if (!formValidate) return
  await formValidate.validate((valid, fields) => {
    if (valid) {
      algorithmManageEdit(editParams.value).then(res => {
        isShowEditView.value = false
        getList()
      })
const rowSave = (row, done, loadingFn) => {
  const createParams = {
    patches_type: row.patches_type,
  }
  spotTypesCreateApi(createParams).then(
    () => {
      onLoad(page.value)
      ElMessage.success('操作成功!')
      done()
    },
    error => {
      console.log(error)
      loadingFn()
    }
  )
}
const rowUpdate = (row, index, done, loadingFn) => {
  editSpotTypeApi(row).then(
    res => {
      onLoad(page.value)
      ElMessage.success('操作成功!')
      done()
    },
    error => {
      console.log(error)
      loadingFn()
    }
  )
}
const rowDel = row => {
  ElMessageBox.confirm('确定将选择数据删除?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(() => deleteSpotTypeApi([row.id]))
    .then(() => {
      onLoad(page.value)
      ElMessage.success('操作成功!')
    })
}
const searchReset = () => {
  page.value.userName = ''
  page.value.lotValue = ''
  page.value.currentPage = 1
  page.value.pageSize = 20
  onLoad(page.value)
}
const searchChange = (params, done) => {
  page.value.currentPage = 1
  page.value.lotValue = params.patches_type
  page.value.userName = params.user_name
  onLoad(page.value)
  done()
}
const selectionChange = list => {
  // selectionList.value = list
}
const selectionClear = () => {
  selectionList.value = []
  // crudRef.value.toggleSelection()
}
const handleEnable = row => {
  ElMessageBox.confirm('是否确定启用这条配置?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(() => enable(row.id))
    .then(() => {
      onLoad(page.value)
      ElMessage.success('操作成功!')
      // crudRef.value.toggleSelection()
    })
}
const handleDisable = row => {
  ElMessageBox.confirm('是否确定禁用这条配置?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(() => disable(row.id))
    .then(() => {
      onLoad(page.value)
      ElMessage.success('操作成功!')
      // crudRef.value.toggleSelection()
    })
}
const beforeOpen = (done, type) => {
  // if (['edit', 'view'].includes(type)) {
  //   // getDetail(form.value.id).then(res => {
  //   //   form.value = res.data.data
  //   // })
  // }
  done()
}
const currentChange = currentPage => {
  page.value.currentPage = currentPage
}
const sizeChange = pageSize => {
  page.value.pageSize = pageSize
}
const refreshChange = () => {
  onLoad(page.value, query.value)
}
const onLoad = (pageParam, params = {}) => {
  const searchparams = {
    current: pageParam.currentPage,
    size: pageParam.pageSize,
    lotValue: pageParam.lotValue,
    userName: pageParam.userName,
  }
  loading.value = true
  listOfSpotTypesApi(searchparams).then(res => {
    const resData = res.data.data
    page.value.total = resData.total
    data.value = resData.records
    loading.value = false
    selectionClear()
  })
}
// ---------------- utils ----------------
function validData (value) {
  return value !== undefined && value !== null && value !== false
}
onMounted(() => {
  getList()
  getSFType()
  getsearchManagementApi()
})
</script>
<style lang="scss" scoped>
.airspaceManage {
  height: 0;
  flex: 1;
  margin: 0 10px 10px 10px;
  background-color: #ffffff;
  padding: 10px 20px;
  border-radius: 5px;
  display: flex;
  flex-direction: column;
  .search-box {
    height: 80px;
  }
  :deep(.el-input) {
    .el-input__wrapper {
      width: 200px;
    }
  }
  // 表格
  .mange-table {
    height: 0;
    flex: 1;
    margin-top: 18px;
    overflow: auto;
  }
  :deep(.el-pagination) {
    display: flex;
    justify-content: right;
  }
  :deep(.el-pagination button) {
    background: center center no-repeat none !important;
    color: #8eb8ea !important;
  }
  :deep(.ztzf-select) {
    .el-select__selection {
      width: 200px;
    }
  }
}
.content {
  padding: 20px;
  .view-table {
    width: 100%;
    border-collapse: collapse;
    border: 1px solid #EBEEF5;
    tr {
      &:not(:last-child) {
        border-bottom: 1px solid #EBEEF5;
      }
      td {
        padding: 12px 10px;
        &.label {
          width: 140px;
          text-align: right;
          // color: #909399;
          // background-color: #F5F7FA;
          border-right: 1px solid #EBEEF5;
        }
        &.value {
          width: 180px;
          // color: #303133;
        }
      }
    }
  }
}
.content-edit {
  .el-form {
    .el-form-item {
      width: 300px;
      :deep(.el-form-item__label) {
        width: 120px;
      }
    }
    .btns {
      display: flex;
      justify-content: center
    }
  }
}
</style>
<style scoped lang="scss"></style>
src/views/airspace/components/AirspaceMap.vue
New file
@@ -0,0 +1,157 @@
<template>
    <el-dialog class="airspace-dialog" :title="props.title" v-model="dialogShow" width="60%" align-center
        @close="handleClose" :close-on-click-modal="false" :close-on-press-escape="false">
        <div class="custom-container">
            <div class="form-container">
                <avue-form ref="formEle" :option="option" v-model="form"></avue-form>
            </div>
            <div class="map-container">
                <div id="AirspaceMap" class="ztzf-cesium"></div>
            </div>
        </div>
    </el-dialog>
</template>
<script setup>
import * as Cesium from 'cesium'
import { PublicCesium } from '@/utils/cesium/publicCesium'
const props = defineProps({
    title: {
        type: String,
        default: '新增空域',
    },
})
const dialogShow = defineModel('show')
const formEle = ref(null)
const form = ref({})
const option = ref({
    submitBtn: false,
    emptyBtn: false,
    column: [
        {
            label: '名称',
            prop: 'input',
            type: 'input',
            rules: [
                {
                    required: true,
                    message: '请输入名称',
                    trigger: 'blur'
                }
            ]
        },
        {
            label: '高度',
            prop: 'input',
            type: 'input',
            rules: [
                {
                    required: true,
                    message: '请输入高度',
                    trigger: 'blur'
                }
            ]
        },
        {
            label: '类型',
            prop: 'select',
            type: 'select',
            dicData: [
                {
                    label: '字典1',
                    value: 0,
                },
                {
                    label: '字典2',
                    value: 1,
                },
            ],
            rules: [
                {
                    required: true,
                    message: '请选择类型',
                    trigger: 'blur'
                }
            ]
        },
        {
            label: '管控时段',
            prop: 'daterange',
            type: 'daterange',
            format: 'YYYY-MM-DD',
            valueFormat: 'YYYY-MM-DD',
            startPlaceholder: '开始日期',
            endPlaceholder: '结束日期',
            rules: [
                {
                    required: true,
                    message: '请选择管控时段',
                    trigger: 'blur'
                }
            ]
        },
        {
            label: '备注',
            prop: 'input',
            type: 'textarea',
            span: 24,
            rows: 1,
            minRows: 1,
            maxRows: 1,
            placeholder: '请输入备注',
        },
    ],
})
let publicCesiumInstance = null
let viewer = null
// 地图
const initMap = () => {
    if (!document.getElementById('AirspaceMap')) {
        return
    }
    publicCesiumInstance = new PublicCesium({
        dom: 'AirspaceMap',
        flatMode: false,
        terrain: true,
        layerMode: 4,
        contour: false,
    })
    viewer = publicCesiumInstance.getViewer()
}
watch(dialogShow, async (show) => {
    if (show) {
        await nextTick()
        initMap()
    }
})
const handleClose = () => {
    publicCesiumInstance?.viewerDestroy()
    publicCesiumInstance = null
    viewer = null
    form.value = {}
    formEle.value.resetForm()
    dialogShow.value = false
}
</script>
<style scoped lang="scss">
.custom-container {
    .map-container {
        height: 440px;
        #AirspaceMap {
            width: 100%;
            height: 100%;
        }
    }
}
</style>