Browse Source

新建机场任务

李富豪 1 year ago
parent
commit
ed24d408e3

+ 9 - 0
Web/src/api/custom/index.ts

@@ -133,6 +133,7 @@ export type DeleteDirApiParams = {
 };
 
 // Api函数类型
+export type FetchBingCodeApi = () => Promise<any>;
 export type FetchJobListApi = (params: FetchJobListApiParams) => Promise<any>;
 export type DeleteJobApi = (params: { job_id: string }) => Promise<any>;
 export type FetchWaylineListApi = (params: FetchWaylineListApiParams) => Promise<any>;
@@ -167,6 +168,13 @@ export const getUploadPath = () => {
     return `api/media/api/v1/files/${getWorkspaceId()}/file/upload`;
 }
 
+// 获取设备绑定码
+const fetchBingCodeApi: FetchBingCodeApi = async () => {
+    const url = `/manage/api/v1/workspaces/${getWorkspaceId()}/bingCode`
+    const res = await request.get(url);
+    return res.data;
+};
+
 // 获取任务列表
 const fetchJobListApi: FetchJobListApi = async (params) => {
     const url = `/wayline/api/v1/workspaces/${getWorkspaceId()}/jobs`
@@ -358,6 +366,7 @@ const deleteDirApi: DeleteDirApi = async (params) => {
 };
 
 export const apis = {
+    fetchBingCode: fetchBingCodeApi,
     fetchJobList: fetchJobListApi,
     deleteJob: deleteJobApi,
     fetchWaylineList: fetchWaylineListApi,

+ 1 - 1
Web/src/components/devices/changeRecord/components/Search.vue

@@ -88,7 +88,7 @@ const handleClickSearch = async () => {
     ...values,
   };
   delete data.rangeDate;
-  if (values.rangeDate.length === 2) {
+  if (values.rangeDate?.length === 2) {
     data.begin_time = moment(values.rangeDate[0]).valueOf();
     data.end_time = moment(values.rangeDate[1]).valueOf();
   }

+ 1 - 1
Web/src/components/devices/deviceList/components/FeedbackDrawer.vue

@@ -222,7 +222,7 @@ const handleClickSearch = async () => {
     const values = formRef.value?.getFieldsValue();
     const data = { ...values };
     delete data.rangeDate;
-    if (values.rangeDate.length === 2) {
+    if (values.rangeDate?.length === 2) {
         data.begin_time = moment(values.rangeDate[0]).valueOf();
         data.end_time = moment(values.rangeDate[1]).valueOf();
     }

+ 15 - 4
Web/src/components/devices/deviceList/components/Search.vue

@@ -5,7 +5,7 @@
         机场设备绑定码
         <MenuOutlined />
       </a-button>
-      <a-popover trigger="hover">
+      <a-popover trigger="hover" v-if="false">
         <template #content>
           <div class="popover">
             <div>
@@ -81,7 +81,7 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive, onMounted } from 'vue';
+import { ref, reactive, onMounted, watch } from 'vue';
 import { ReadOutlined, MenuOutlined, SearchOutlined, ReloadOutlined } from '@ant-design/icons-vue';
 import { apis } from '/@/api/custom';
 
@@ -118,8 +118,8 @@ interface State {
 const state: State = reactive({
   visible: false,
   deviceInfo: {
-    name: '上海展域航空技术有限公司',
-    code: 'PB97VR',
+    name: '',
+    code: '',
   },
   deviceModelList: [],
 })
@@ -139,6 +139,17 @@ onMounted(async () => {
   }
 })
 
+// 设备绑定码弹出层
+watch(() => state.visible, async (value) => {
+  if (value) {
+    const res = await apis.fetchBingCode();
+    state.deviceInfo = {
+      name: res.data.workspace_name,
+      code: res.data.bind_code,
+    }
+  }
+})
+
 // 点击查询
 const handleClickSearch = async () => {
   const values = formRef.value?.getFieldsValue();

+ 0 - 1
Web/src/components/task/CreatePlan.vue

@@ -269,7 +269,6 @@ function validOverlapped(): Error | void {
 }
 
 function onSubmit() {
-  console.info(dock, '12131231')
   valueRef.value.validate().then(() => {
     disabled.value = true
     const createPlanBody = { ...planBody } as unknown as CreatePlan

+ 1 - 1
Web/src/pages/page-web/projects/media/detail/components/FileInfo.vue

@@ -183,7 +183,7 @@ const state = reactive({
     latitude: '',// 纬度
     coordinates: [],
   },
-  map: null, // 高德地图实例
+  map: null,// 高德地图实例
 })
 
 const contentRef = ref();

+ 1 - 1
Web/src/pages/page-web/projects/media/detail/components/Search.vue

@@ -125,7 +125,7 @@ const handleClickSearch = async () => {
     ...values,
   };
   delete data.rangeDate;
-  if (values.rangeDate.length === 2) {
+  if (values.rangeDate?.length === 2) {
     data.begin_time = moment(values.rangeDate[0]).valueOf();
     data.end_time = moment(values.rangeDate[1]).valueOf();
   }

+ 1 - 1
Web/src/pages/page-web/projects/media/index/components/Search.vue

@@ -113,7 +113,7 @@ const handleClickSearch = async () => {
     ...values,
   };
   delete data.rangeDate;
-  if (values.rangeDate.length === 2) {
+  if (values.rangeDate?.length === 2) {
     data.begin_time = moment(values.rangeDate[0]).valueOf();
     data.end_time = moment(values.rangeDate[1]).valueOf();
   }

+ 1 - 1
Web/src/pages/page-web/projects/mediaInfo/index.vue

@@ -138,7 +138,7 @@ const state = reactive({
     coordinates: [],
     thumbnail_url: '',
   },
-  map: null, // 高德地图实例
+  map: null,// 高德地图实例
 })
 
 // 高德地图Hook

+ 106 - 0
Web/src/pages/page-web/projects/task/taskList/components/CreateTaskModal.vue

@@ -0,0 +1,106 @@
+<template>
+    <a-modal width="100%" title="新建机场任务" :closable="false" :destroyOnClose="true" :maskClosable="false"
+        wrapClassName="createTask-modal" :visible="visible" :footer="null">
+        <div class="content">
+            <div class="content-panel">
+                <TaskPanel :onClickConfirm="onClickConfirm" :onClickCancel="onClickCancel" />
+            </div>
+            <div class="content-map">
+                <div id="taskMap" :style="{ width: '100%', height: '100%' }"></div>
+            </div>
+        </div>
+    </a-modal>
+</template>
+
+<script lang="ts" setup>
+import { reactive, onMounted } from 'vue';
+import TaskPanel from './TaskPanel.vue';
+import { useGMapManage } from '/@/hooks/use-g-map';
+
+interface Props {
+    jobId?: string,
+    visible: boolean,
+    onClickConfirm: (data: any) => Promise<any>,
+    onClickCancel: () => void,
+};
+
+const props = withDefaults(defineProps<Props>(), {
+
+});
+
+const state = reactive({
+    loading: false,
+    map: null,// 高德地图实例
+})
+
+// 高德地图Hook
+const AmapHook = useGMapManage();
+
+const init = async () => {
+    const AMap = await AmapHook.asyncInitMap();
+    const map = new AMap.Map('taskMap', {
+        center: [121.48, 31.22],
+        zoom: 12
+    })
+    state.map = map;
+}
+
+onMounted(() => {
+    init();
+})
+</script>
+
+<style lang="scss" scoped>
+.content {
+    width: 100%;
+    height: 100%;
+    padding: 20px;
+    display: flex;
+
+    &-panel {
+        width: 300px;
+        height: 100%;
+        background-color: #232323;
+    }
+
+    &-map {
+        flex: 1;
+        height: 100%;
+        margin-left: 20px;
+    }
+}
+</style>
+
+<style lang="scss">
+.createTask-modal {
+    .ant-modal-header {
+        height: 60px;
+    }
+
+    .ant-modal {
+        max-width: 100%;
+        top: 0;
+        padding-bottom: 0;
+        margin: 0;
+    }
+
+    .ant-modal-content {
+        display: flex;
+        flex-direction: column;
+        height: calc(100vh);
+    }
+
+    .ant-modal-body {
+        height: calc(100vh - 60px);
+        padding: 0;
+    }
+}
+
+.amap-logo {
+    display: none !important;
+}
+
+.amap-copyright {
+    display: none !important;
+}
+</style>

+ 1 - 1
Web/src/pages/page-web/projects/task/taskList/components/Search.vue

@@ -66,7 +66,7 @@ const handleClickSearch = async () => {
     ...values,
   };
   delete data.rangeDate;
-  if (values.rangeDate.length === 2) {
+  if (values.rangeDate?.length === 2) {
     data.begin_time = moment(values.rangeDate[0]).valueOf();
     data.end_time = moment(values.rangeDate[1]).valueOf();
   }

+ 258 - 0
Web/src/pages/page-web/projects/task/taskList/components/TaskPanel.vue

@@ -0,0 +1,258 @@
+<template>
+  <div class="taskPanel">
+    <a-spin :spinning="state.loading">
+      <a-form ref="formRef" layout="vertical" :hideRequiredMark="true" :model="state.formModel">
+        <!-- 任务名称 -->
+        <a-form-item label='任务名称' name="name" :rules="[{ required: true, message: '任务名称不能为空', whitespace: true }]">
+          <a-input placeholder="请输入任务名称" :maxlength="20" v-model:value="state.formModel.name" />
+        </a-form-item>
+        <!-- 执行航线 -->
+        <a-form-item label="执行航线" name="file_id" :rules="[{ required: true, message: '执行航线不能为空' }]">
+          <a-button type="primary">
+            选择航线
+          </a-button>
+          <div class="wayline-panel" style="padding-top: 5px;">
+            <div class="title">
+              <a-tooltip :title="wayline.name">
+                <div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">
+                  {{ wayline.name }}</div>
+              </a-tooltip>
+              <div class="ml10">
+                <UserOutlined />
+              </div>
+              <a-tooltip :title="wayline.user_name">
+                <div class="ml5 pr10"
+                  style="width: 80px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{
+                    wayline.user_name }}</div>
+              </a-tooltip>
+            </div>
+            <div class="ml10 mt5" style="color: hsla(0,0%,100%,0.65);">
+              <span>
+                <RocketOutlined />
+              </span>
+              <span class="ml5">{{ DEVICE_NAME[wayline.drone_model_key] }}</span>
+              <span class="ml10">
+                <CameraFilled style="border-top: 1px solid; padding-top: -3px;" />
+              </span>
+              <span class="ml5" v-for="payload in wayline.payload_model_keys" :key="payload.id">
+                {{ DEVICE_NAME[payload] }}
+              </span>
+            </div>
+            <div class="mt5 ml10" style="color: hsla(0,0%,100%,0.35);">
+              <span class="mr10">Update at {{ new Date(wayline.update_time).toLocaleString() }}</span>
+            </div>
+          </div>
+        </a-form-item>
+        <!-- 执行设备 -->
+        <a-form-item label="执行设备" name="dock_sn" :rules="[{ required: true, message: '执行设备不能为空' }]">
+          <a-button type="primary">
+            选择设备
+          </a-button>
+          <div class="panel" style="padding-top: 5px;">
+            <div class="title">
+              <a-tooltip :title="dock.nickname">
+                <div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">
+                  {{ dock.nickname }}</div>
+              </a-tooltip>
+            </div>
+            <div class="ml10 mt5" style="color: hsla(0,0%,100%,0.65);">
+              <span>
+                <RocketOutlined />
+              </span>
+              <!-- <span class="ml5">{{ dock.children?.nickname ?? 'No drone' }}</span> -->
+            </div>
+          </div>
+        </a-form-item>
+        <!-- 任务精度 -->
+        <a-form-item label="任务精度" name="wayline_precision_type">
+          <a-radio-group button-style="solid" v-model:value="state.formModel.wayline_precision_type">
+            <a-radio-button :value="1">
+              高精度RTK
+            </a-radio-button>
+            <a-radio-button :value="0">
+              GNSS
+            </a-radio-button>
+          </a-radio-group>
+        </a-form-item>
+        <!-- 任务策略 -->
+        <a-form-item label="任务策略" name="task_type">
+          <div style="white-space: nowrap;">
+            <a-radio-group button-style="solid" v-model:value="state.formModel.task_type">
+              <a-radio-button :value="0">
+                立即
+              </a-radio-button>
+              <a-radio-button :value="1">
+                定时
+              </a-radio-button>
+              <a-radio-button :value="2">
+                循环
+              </a-radio-button>
+            </a-radio-group>
+          </div>
+        </a-form-item>
+        <template v-if="state.formModel.task_type === 1">
+          <!-- 执行时间 -->
+          <a-form-item label="执行时间" name="select_execute_date_time" :rules="[{ required: true, message: '执行时间不能为空' }]">
+            <a-date-picker style="width: 100%;" show-time valueFormat="YYYY-MM-DD HH:mm:ss" placeholder="请选择执行时间"
+              v-model:value="state.formModel.select_execute_date_time" />
+          </a-form-item>
+        </template>
+        <template v-if="state.formModel.task_type === 2">
+          <!-- 执行日期 -->
+          <a-form-item label="执行日期" name="select_execute_range_date" :rules="[{ required: true, message: '执行日期不能为空' }]">
+            <a-range-picker style="width: 100%;" valueFormat="YYYY-MM-DD"
+              :disabledDate="(current: any) => current < moment().subtract(1, 'days')" :placeholder="['开始日期', '结束日期']"
+              v-model:value="state.formModel.select_execute_range_date" />
+          </a-form-item>
+          <!-- 执行时间 -->
+          <a-form-item label="执行时间" name="select_execute_range_time">
+            <a-range-picker style="width: 100%;" :show-time="{ format: 'HH:mm' }" format="HH:mm" valueFormat="HH:mm"
+              :placeholder="['开始时间', '结束时间']" v-model:value="state.formModel.select_execute_range_time" />
+          </a-form-item>
+          <!-- 任务开始执行的电量 -->
+          <a-form-item label="任务开始执行的电量" name="min_battery_capacity">
+            <a-input-number :min="50" :max="100" :formatter="(value: number) => `${value}%`"
+              :parser="(value: string) => value.replace('%', '')" v-model:value="state.formModel.min_battery_capacity">
+            </a-input-number>
+          </a-form-item>
+        </template>
+        <!-- 返航高度 -->
+        <a-form-item label="返航高度(相对机场返航高度)" name="rth_altitude">
+          <a-input-number style="width: 100%;" :min="20" :max="1500" v-model:value="state.formModel.rth_altitude" />
+        </a-form-item>
+        <!-- 自动断点续飞 -->
+        <a-form-item label="自动断点续飞" name="breakpoint_continuation">
+          <a-switch checked-children="开" un-checked-children="关"
+            v-model:checked="state.formModel.breakpoint_continuation" />
+        </a-form-item>
+        <!-- 自动断点续飞 -->
+        <a-form-item>
+          <div class="taskPanel-footer">
+            <a-button style="background: #3c3c3c;margin-right: 10px;" @click="onClickCancel">
+              取消
+            </a-button>
+            <a-button type="primary" @click="handleClickConfirm">
+              确定
+            </a-button>
+          </div>
+        </a-form-item>
+      </a-form>
+    </a-spin>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, computed, reactive } from 'vue'
+import { RocketOutlined, CameraFilled, UserOutlined } from '@ant-design/icons-vue'
+import { useMyStore } from '/@/store'
+import { WaylineFile } from '/@/types/wayline'
+import { Device, DEVICE_NAME } from '/@/types/device'
+import moment from 'moment'
+import { createPlan } from '/@/api/wayline'
+
+interface Props {
+  onClickConfirm: (data: any) => Promise<any>,
+  onClickCancel: () => void,
+};
+
+const props = withDefaults(defineProps<Props>(), {
+
+});
+
+const formRef = ref();
+
+const store = useMyStore()
+
+const dock = computed<Device>(() => {
+  return store.state.dockInfo
+})
+
+const wayline = computed<WaylineFile>(() => {
+  return store.state.waylineInfo
+})
+
+interface State {
+  loading: boolean,
+  formModel: {
+    name: string;
+    file_id: string;
+    dock_sn: string;
+    wayline_precision_type: number;
+    task_type: number;
+    select_execute_date_time?: string;
+    select_execute_range_date?: any[],
+    select_execute_range_time?: any[],
+    min_battery_capacity: number,
+    rth_altitude: number,
+    breakpoint_continuation: boolean;
+  }
+}
+
+const state: State = reactive({
+  loading: false,
+  formModel: {
+    name: '',// 任务名称
+    file_id: '',// 执行航线
+    dock_sn: '',// 执行设备
+    wayline_precision_type: 1,// 任务精度
+    task_type: 0,// 任务策略
+    select_execute_date_time: undefined,// 定时-执行时间
+    select_execute_range_date: undefined,// 循环-执行日期
+    select_execute_range_time: undefined,// 循环-执行时间
+    min_battery_capacity: 90,// 循环-任务开始执行的电量
+    rth_altitude: 20,// 返航高度
+    breakpoint_continuation: true,// 自动断点续飞
+  }
+})
+
+// 点击确定
+const handleClickConfirm = async () => {
+  formRef.value?.validateFields().then(async (values: any) => {
+    const data = { ...values };
+    // await createPlan()
+    console.log(data, 'data');
+  }).catch((error: any) => {
+    console.error(error);
+  });
+}
+</script>
+
+<style lang="scss">
+.taskPanel {
+  width: 100%;
+  height: 100%;
+  color: #FFFFFF;
+  overflow-y: auto;
+
+  &-footer {
+    display: flex;
+    padding: 10px 0;
+
+    button {
+      width: 45%;
+      color: #fff;
+      border: 0;
+    }
+  }
+
+  form {
+    margin: 10px;
+  }
+
+  form,
+  label,
+  input,
+  .ant-input,
+  .ant-calendar-range-picker-separator,
+  .ant-input:hover,
+  .ant-time-picker .anticon,
+  .ant-calendar-picker .anticon {
+    background-color: #232323;
+    color: #fff;
+  }
+}
+
+::-webkit-scrollbar {
+  display: none;
+}
+</style>

+ 32 - 16
Web/src/pages/page-web/projects/task/taskList/index.vue

@@ -23,8 +23,9 @@
       </div>
     </a-spin>
     <div :style="{ width: state.collapsed ? 'calc(100% - 250px)' : '100%' }">
-      <Search :onClickCollapsed="() => { state.collapsed = !state.collapsed }" :onClickCreateTask="() => { }"
-        :onClickSearch="onClickSearch" :onClickReset="onClickReset" />
+      <Search :onClickCollapsed="() => { state.collapsed = !state.collapsed }"
+        :onClickCreateTask="() => { state.visible = true }" :onClickSearch="onClickSearch"
+        :onClickReset="onClickReset" />
       <a-table :scroll="{ x: '100%', y: 500 }" rowKey="job_id" :loading="state.listLoading" :columns="columns"
         :dataSource="state.list" @change="refreshData" :rowClassName="rowClassName" :pagination="paginationConfig">
         <!-- 计划|实际时间 -->
@@ -95,6 +96,8 @@
         </template>
       </a-table>
     </div>
+    <CreateTaskModal :jobId="''" :visible="state.visible" :onClickConfirm="createTaskModalOnClickConfirm"
+      :onClickCancel="createTaskModalOnClickCancel" v-if="state.visible" />
   </div>
 </template>
 
@@ -103,16 +106,18 @@ import { reactive, onMounted, watch, computed } from 'vue';
 import { Modal, message } from 'ant-design-vue';
 import { CopyOutlined, GatewayOutlined, DeleteOutlined } from '@ant-design/icons-vue';
 import Search from './components/Search.vue';
-import Airport from '/@/components/airport/index.vue'
+import Airport from '/@/components/airport/index.vue';
+import CreateTaskModal from './components/CreateTaskModal.vue';
 import { useMyStore } from '/@/store';
-import { useFormatTask } from '/@/components/task/use-format-task'
+import { useFormatTask } from '/@/components/task/use-format-task';
 import { apis } from '/@/api/custom';
 import { getDeviceTopo, getUnreadDeviceHms } from '/@/api/manage';
 import { getWorkspaceId } from '/@/utils';
-import { OnlineDevice, EModeCode } from '/@/types/device'
-import { EDeviceTypeName } from '/@/types'
+import { OnlineDevice, EModeCode } from '/@/types/device';
+import { EDeviceTypeName } from '/@/types';
 
 interface State {
+  visible: boolean,
   collapsed: boolean,
   onlineDockListLoading: boolean,
   onlineDockList: OnlineDevice[],
@@ -122,6 +127,7 @@ interface State {
 };
 
 const state: State = reactive({
+  visible: false,
   collapsed: true,
   onlineDockListLoading: false,
   onlineDockList: [],
@@ -282,6 +288,16 @@ const onClickCheckItem = async (sn: string) => {
   await fetchList();
 }
 
+// 新建机场任务弹出层-点击确定
+const createTaskModalOnClickConfirm = async () => {
+  state.visible = false;
+}
+
+// 新建机场任务弹出层-点击取消
+const createTaskModalOnClickCancel = () => {
+  state.visible = false;
+}
+
 const paginationConfig = reactive({
   pageSizeOptions: ['20', '50', '100'],
   showQuickJumper: true,
@@ -305,20 +321,26 @@ const columns = [
     slots: { customRender: 'status' },
   },
   {
-    title: '类型',
+    title: '任务名称',
+    dataIndex: 'job_name',
+    width: 150,
+    ellipsis: true,
+  },
+  {
+    title: '任务策略',
     dataIndex: 'task_type',
     width: 120,
     customRender: ({ text }: any) => {
       let content = '';
       switch (text) {
         case 0:
-          content = '立即执行';
+          content = '立即';
           break;
         case 1:
-          content = '单次定时';
+          content = '定时';
           break;
         case 2:
-          content = '循环定时';
+          content = '循环';
           break;
         default:
           break;
@@ -326,12 +348,6 @@ const columns = [
       return content;
     }
   },
-  {
-    title: '计划名称',
-    dataIndex: 'job_name',
-    width: 150,
-    ellipsis: true,
-  },
   {
     title: '航线名称',
     dataIndex: 'file_name',

+ 1 - 1
Web/src/pages/page-web/projects/trajectory/components/Search.vue

@@ -105,7 +105,7 @@ const handleClickSearch = async () => {
     ...values,
   };
   delete data.rangeDate;
-  if (values.rangeDate.length === 2) {
+  if (values.rangeDate?.length === 2) {
     data.begin_time = moment(values.rangeDate[0]).valueOf();
     data.end_time = moment(values.rangeDate[1]).valueOf();
   }

+ 8 - 8
Web/src/types/task.ts

@@ -2,15 +2,15 @@ import { commonColor } from '/@/utils/color'
 
 // 任务类型
 export enum TaskType {
-  Immediate = 0, // 立即执行
-  Timed = 1, // 单次定时任务
-  Condition = 2,
+  Immediate = 0, // 立即任务
+  Timed = 1, // 定时任务
+  Condition = 2,// 循环任务
 }
 
 export const TaskTypeMap = {
-  [TaskType.Immediate]: 'Immediate',
-  [TaskType.Timed]: 'Timed',
-  [TaskType.Condition]: 'Continuous',
+  [TaskType.Immediate]: '立即',
+  [TaskType.Timed]: '定时',
+  [TaskType.Condition]: '循环',
 }
 
 export const TaskTypeOptions = [
@@ -82,12 +82,12 @@ export enum TaskProgressStatus {
 // 任务进度消息
 export interface TaskProgressInfo {
   bid: string,
-  output:{
+  output: {
     ext: {
       current_waypoint_index: number,
       media_count: number // 媒体文件
     },
-    progress:{
+    progress: {
       current_step: number,
       percent: number
     },