Browse Source

任务列表

李富豪 1 year ago
parent
commit
d45eff2717

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

@@ -2,6 +2,14 @@ import request from '/@/api/http/request';
 import { getWorkspaceId } from '/@/utils/index';
 
 // Api参数类型
+export type FetchJobListApiParams = Partial<{
+    begin_time: number,
+    end_time: number,
+    search_info: string,
+    page: number,
+    page_size: number,
+}>;
+
 export type FetchWaylineListApiParams = Partial<{
     order_by: string,
     template_type: number,
@@ -124,6 +132,8 @@ export type DeleteDirApiParams = {
 };
 
 // Api函数类型
+export type FetchJobListApi = (params: FetchJobListApiParams) => Promise<any>;
+export type DeleteJobApi = (params: { job_id: string }) => Promise<any>;
 export type FetchWaylineListApi = (params: FetchWaylineListApiParams) => Promise<any>;
 export type FetchDeviceLogListApi = (sn: string, params: { domain_list: string }) => Promise<any>;
 export type FetchDeviceFeedbackRecordListApi = (sn: string, params: FetchDeviceFeedbackRecordListApiParams) => Promise<any>;
@@ -156,6 +166,20 @@ export const getUploadPath = () => {
     return `api/media/api/v1/files/${getWorkspaceId()}/file/upload`;
 }
 
+// 获取任务列表
+const fetchJobListApi: FetchJobListApi = async (params) => {
+    const url = `/wayline/api/v1/workspaces/${getWorkspaceId()}/jobs`
+    const res = await request.get(url, { params: params });
+    return res.data;
+};
+
+// 删除任务
+const deleteJobApi: DeleteJobApi = async (params) => {
+    const url = `/wayline/api/v1/workspaces/${getWorkspaceId()}/jobs`
+    const res = await request.delete(url, { params: params });
+    return res.data;
+};
+
 // 获取航线列表
 const fetchWaylineListApi: FetchWaylineListApi = async (params) => {
     const url = `/wayline/api/v1/workspaces/${getWorkspaceId()}/waylines`
@@ -333,6 +357,8 @@ const deleteDirApi: DeleteDirApi = async (params) => {
 };
 
 export const apis = {
+    fetchJobList: fetchJobListApi,
+    deleteJob: deleteJobApi,
     fetchWaylineList: fetchWaylineListApi,
     fetchDeviceLogList: fetchDeviceLogListApi,
     fetchDeviceFeedbackRecordList: fetchDeviceFeedbackRecordListApi,

+ 3 - 2
Web/src/components/airport/index.vue

@@ -69,7 +69,7 @@
           </div>
         </div>
       </div>
-      <div class="content-info-right">
+      <div class="content-info-right" v-if="lookInfo">
         <a-button style="padding: 0;" type="text"
           :disabled="!(dockInfo[dock.gateway.sn] && dockInfo[dock.gateway.sn].basic_osd?.mode_code !== EDockModeCode.Disconnected)"
           @click.stop="onClickLookInfo">
@@ -94,7 +94,8 @@ import { OnlineDevice, EModeCode, EDockModeCode } from '/@/types/device';
 
 interface Props {
   dock: OnlineDevice,
-  onClickLookInfo: () => void,
+  lookInfo: boolean,
+  onClickLookInfo?: () => void,
 };
 
 const props = withDefaults(defineProps<Props>(), {

+ 3 - 4
Web/src/components/devices/changeRecord/index.vue

@@ -2,9 +2,8 @@
   <div class="changeRecord">
     <Search :onClickSearch="onClickSearch" :onClickReset="onClickReset" />
     <div class="changeRecord-table">
-      <a-table :scroll="{ x: '100%', y: 500 }"  rowKey="device_sn"
-        :loading="state.listLoading" :columns="columns" @change="refreshData" :rowClassName="rowClassName"
-        :dataSource="state.list" :pagination="paginationConfig">
+      <a-table :scroll="{ x: '100%', y: 500 }" rowKey="device_sn" :loading="state.listLoading" :columns="columns"
+        @change="refreshData" :rowClassName="rowClassName" :dataSource="state.list" :pagination="paginationConfig">
         <!-- 设备型号 -->
         <template #device_name="{ record }">
           <CustomCell :record="record" fieldName="device_name" />
@@ -58,11 +57,11 @@ const fetchList = async () => {
       page_size: paginationConfig.pageSize
     });
     if (res.code === 0) {
+      state.list = res.data.list;
       paginationConfig.total = res.data.pagination.total
       paginationConfig.current = res.data.pagination.page
       paginationConfig.pageSize = res.data.pagination.page_size
     }
-    state.list = res.data.list;
   } catch (e) {
     console.error(e);
   } finally {

+ 1 - 1
Web/src/components/devices/device-hms/DeviceHmsDrawer.vue

@@ -94,11 +94,11 @@ const fetchList = async () => {
       page_size: paginationConfig.pageSize
     });
     if (res.code === 0) {
+      state.list = res.data.list;
       paginationConfig.total = res.data.pagination.total
       paginationConfig.current = res.data.pagination.page
       paginationConfig.pageSize = res.data.pagination.page_size
     }
-    state.list = res.data.list;
   } catch (e) {
     console.error(e);
   } finally {

+ 1 - 1
Web/src/components/devices/feedbackRecord/index.vue

@@ -78,11 +78,11 @@ const fetchList = async () => {
       page_size: paginationConfig.pageSize
     });
     if (res.code === 0) {
+      state.list = res.data.list;
       paginationConfig.total = res.data.pagination.total
       paginationConfig.current = res.data.pagination.page
       paginationConfig.pageSize = res.data.pagination.page_size
     }
-    state.list = res.data.list;
   } catch (e) {
     console.error(e);
   } finally {

+ 7 - 7
Web/src/components/task/use-format-task.ts

@@ -3,20 +3,20 @@ import { Task } from '/@/api/wayline'
 import { TaskStatusColor, TaskStatusMap, TaskTypeMap, OutOfControlActionMap, MediaStatusMap, MediaStatusColorMap, MediaStatus } from '/@/types/task'
 import { isNil } from 'lodash'
 
-export function useFormatTask () {
-  function formatTaskType (task: Task) {
+export function useFormatTask() {
+  function formatTaskType(task: Task) {
     return TaskTypeMap[task.task_type] || DEFAULT_PLACEHOLDER
   }
 
-  function formatTaskTime (time: string) {
+  function formatTaskTime(time: string) {
     return time || DEFAULT_PLACEHOLDER
   }
 
-  function formatLostAction (task: Task) {
+  function formatLostAction(task: Task) {
     return OutOfControlActionMap[task.out_of_control_action] || DEFAULT_PLACEHOLDER
   }
 
-  function formatTaskStatus (task: Task) {
+  function formatTaskStatus(task: Task) {
     const statusObj = {
       text: '',
       color: ''
@@ -27,7 +27,7 @@ export function useFormatTask () {
     return statusObj
   }
 
-  function formatMediaTaskStatus (task: Task) {
+  function formatMediaTaskStatus(task: Task) {
     const statusObj = {
       text: '',
       color: '',
@@ -70,4 +70,4 @@ export function useFormatTask () {
     formatTaskStatus,
     formatMediaTaskStatus,
   }
-}
+}

+ 6 - 6
Web/src/pages/page-web/projects/devices.vue

@@ -10,15 +10,15 @@
       设备变化记录
     </a-menu-item>
   </a-menu>
-  <div v-if="state.selectedKeys[0] === 1">
+  <template v-if="state.selectedKeys[0] === 1">
     <DeviceList />
-  </div>
-  <div v-else-if="state.selectedKeys[0] === 2">
+  </template>
+  <template v-else-if="state.selectedKeys[0] === 2">
     <FeedbackRecord />
-  </div>
-  <div v-else-if="state.selectedKeys[0] === 3">
+  </template>
+  <template v-else-if="state.selectedKeys[0] === 3">
     <ChangeRecord />
-  </div>
+  </template>
 </template>
 
 <script lang="ts" setup>

+ 4 - 4
Web/src/pages/page-web/projects/task/index.vue

@@ -7,12 +7,12 @@
       航线管理
     </a-menu-item>
   </a-menu>
-  <div v-if="state.selectedKeys[0] === 1">
+  <template v-if="state.selectedKeys[0] === 1">
     <TaskList />
-  </div>
-  <div v-else-if="state.selectedKeys[0] === 2">
+  </template>
+  <template v-else-if="state.selectedKeys[0] === 2">
     <WaylineList />
-  </div>
+  </template>
 </template>
 
 <script lang="ts" setup>

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

@@ -11,38 +11,6 @@
           <a-range-picker style="width: 250px;" valueFormat="YYYY-MM-DD" :placeholder="['开始时间', '结束时间']"
             v-model:value="formModel.rangeDate" />
         </a-form-item>
-        <a-form-item name="device_type">
-          <a-select style="width: 200px;" placeholder="类型" v-model:value="formModel.status">
-            <a-select-option :value="1">
-              上传中
-            </a-select-option>
-            <a-select-option :value="2">
-              成功
-            </a-select-option>
-            <a-select-option :value="3">
-              取消
-            </a-select-option>
-            <a-select-option :value="4">
-              失败
-            </a-select-option>
-          </a-select>
-        </a-form-item>
-        <a-form-item name="device_type">
-          <a-select style="width: 200px;" placeholder="执行状态" v-model:value="formModel.status">
-            <a-select-option :value="1">
-              上传中
-            </a-select-option>
-            <a-select-option :value="2">
-              成功
-            </a-select-option>
-            <a-select-option :value="3">
-              取消
-            </a-select-option>
-            <a-select-option :value="4">
-              失败
-            </a-select-option>
-          </a-select>
-        </a-form-item>
         <a-form-item name="search_info">
           <a-input style="width: 200px;" placeholder="任务名称、航线名称" v-model:value="formModel.search_info" />
         </a-form-item>
@@ -66,6 +34,7 @@
 <script lang="ts" setup>
 import { ref, reactive } from 'vue';
 import { SearchOutlined, ReloadOutlined } from '@ant-design/icons-vue';
+import moment from 'moment';
 
 interface Props {
   onClickSearch: (query: any) => Promise<any>,
@@ -79,21 +48,33 @@ const props = withDefaults(defineProps<Props>(), {
 const formRef = ref();
 
 const formModel = reactive({
-  device_type: undefined,
+  rangeDate: undefined,
   search_info: undefined,
 })
 
 // 点击查询
 const handleClickSearch = async () => {
   const values = formRef.value?.getFieldsValue();
-  await props.onClickSearch(values);
+  const data = {
+    ...values,
+  };
+  delete data.rangeDate;
+  if (values.rangeDate.length === 2) {
+    data.begin_time = moment(values.rangeDate[0]).valueOf();
+    data.end_time = moment(values.rangeDate[1]).valueOf();
+  }
+  await props.onClickSearch(data);
 }
 
 // 点击重置
 const handleClickReset = async () => {
   formRef.value?.resetFields();
   const values = formRef.value?.getFieldsValue();
-  await props.onClickReset(values);
+  const data = {
+    ...values,
+  };
+  delete data.rangeDate;
+  await props.onClickReset(data);
 }
 </script>
 

+ 141 - 80
Web/src/pages/page-web/projects/task/taskList/index.vue

@@ -1,21 +1,77 @@
 <template>
-  <div class="deviceList">
-    <Search :onClickSearch="onClickSearch" :onClickReset="onClickReset" />
-    <div class="deviceList-table">
-      <a-table :scroll="{ x: '100%', y: 500 }" childrenColumnName="null" rowKey="device_sn" :loading="state.listLoading"
-        :columns="columns" :dataSource="state.list" @change="refreshData" :rowClassName="rowClassName"
-        :pagination="paginationConfig" :rowSelection="rowSelection">
+  <div class="taskList">
+    <div class="taskList-left">
+      <div v-for="(dock, index) in state.onlineDockList" :key="dock.sn">
+        <Airport :dock="dock" :look-info="false" />
+      </div>
+    </div>
+    <div class="taskList-right">
+      <Search :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">
+        <!-- 计划|实际时间 -->
+        <template #duration="{ record }">
+          <div class="flex-row" style="white-space: pre-wrap">
+            <div>
+              <div>
+                {{ record.begin_time }}
+              </div>
+              <div>
+                {{ record.end_time }}
+              </div>
+            </div>
+            <div class="ml10">
+              <div>
+                {{ record.execute_time }}
+              </div>
+              <div>
+                {{ record.completed_time }}
+              </div>
+            </div>
+          </div>
+        </template>
+        <!-- 执行状态 -->
+        <template #status="{ record }">
+          <div style="color: #2B85E4;" v-if="record.status === 1">
+            待执行
+          </div>
+          <div style="color: #2B85E4;" v-else-if="record.status === 2">
+            执行中
+          </div>
+          <div style="color: #19BE6B;" v-else-if="record.status === 3">
+            完成
+          </div>
+          <div style="color: #E02020;" v-else-if="record.status === 4">
+            取消
+          </div>
+          <div style="color: #E02020;" v-else-if="record.status === 5">
+            失败
+          </div>
+          <div style="color: #2B85E4;" v-else-if="record.status === 6">
+            暂停
+          </div>
+        </template>
+        <!-- 媒体上传 -->
+        <template #media_upload="{ record }">
+          <div class="flex-display flex-align-center">
+            <span class="circle-icon" :style="{ backgroundColor: formatMediaTaskStatus(record).color }"></span>
+            {{ formatMediaTaskStatus(record).text }}
+          </div>
+          <div class="pl15">
+            {{ formatMediaTaskStatus(record).number }}
+          </div>
+        </template>
         <!-- 操作 -->
         <template #action="{ record }">
           <div class="flex-align-center flex-row" style="color: #2d8cf0">
             <a-tooltip title="复制任务">
               <CopyOutlined style="margin-right: 10px;" />
             </a-tooltip>
-            <a-tooltip title="查看轨迹">
+            <!-- <a-tooltip title="查看轨迹">
               <GatewayOutlined style="margin-right: 10px;" />
-            </a-tooltip>
+            </a-tooltip> -->
             <a-tooltip title="删除">
-              <DeleteOutlined />
+              <DeleteOutlined @click="onClickDelete(record.job_id, record.job_name)" />
             </a-tooltip>
           </div>
         </template>
@@ -26,40 +82,29 @@
 
 <script lang="ts" setup>
 import { reactive, onMounted } from 'vue';
-import { Modal } from 'ant-design-vue';
+import { Modal, message } from 'ant-design-vue';
 import { CopyOutlined, GatewayOutlined, DeleteOutlined } from '@ant-design/icons-vue';
 import Search from './components/Search.vue';
-import { getBindingDevices, updateDevice, unbindDevice } from '/@/api/manage';
+import Airport from '/@/components/airport/index.vue'
+import { useFormatTask } from '/@/components/task/use-format-task'
 import { apis } from '/@/api/custom';
-import { getWorkspaceId } from '/@/utils/index';
+import { OnlineDevice } from '/@/types/device'
 
 interface State {
-  workspaceId: string,
-  interval: number | null,
+  onlineDockList: OnlineDevice[],
   query: any,
   listLoading: boolean,
   list: any[],
-  selectedRowKeys: string[],
-  currentDevice: any,
-  editableData: {
-    [key: string]: any,
-  },
-  feedbackDrawerVisible: boolean,
-  deviceHmsDrawerVisible: boolean,
 };
 
 const state: State = reactive({
-  workspaceId: getWorkspaceId(),
-  interval: null,
+  onlineDockList: [],
   query: undefined,
   listLoading: false,
   list: [],
-  selectedRowKeys: [],
-  currentDevice: {},
-  editableData: {},
-  feedbackDrawerVisible: false,
-  deviceHmsDrawerVisible: false,
-})
+});
+
+const { formatMediaTaskStatus } = useFormatTask()
 
 const paginationConfig = reactive({
   pageSizeOptions: ['20', '50', '100'],
@@ -68,20 +113,22 @@ const paginationConfig = reactive({
   pageSize: 20,
   current: 1,
   total: 0
-})
+});
 
 const fetchList = async () => {
   state.listLoading = true;
   try {
-    const res = await getBindingDevices(state.workspaceId, {
+    const res = await apis.fetchJobList({
       ...state.query,
       page: paginationConfig.current,
-      page_size: paginationConfig.pageSize,
+      page_size: paginationConfig.pageSize
     });
-    state.list = res.data.list;
-    paginationConfig.total = res.data.pagination.total;
-    paginationConfig.current = res.data.pagination.page;
-    paginationConfig.pageSize = res.data.pagination.page_size;
+    if (res.code === 0) {
+      state.list = res.data.list;
+      paginationConfig.total = res.data.pagination.total
+      paginationConfig.current = res.data.pagination.page
+      paginationConfig.pageSize = res.data.pagination.page_size
+    }
   } catch (e) {
     console.error(e);
   } finally {
@@ -96,61 +143,66 @@ onMounted(async () => {
 const columns = [
   {
     title: '计划|实际时间',
-    dataIndex: 'device_name',
-    width: 150,
-    ellipsis: true,
-    sorter: (a: any, b: any) => a.device_name.localeCompare(b.device_name),
-    slots: { customRender: 'device_name' }
+    dataIndex: 'duration',
+    width: 200,
+    slots: { customRender: 'duration' },
   },
   {
     title: '执行状态',
-    dataIndex: 'device_sn',
-    width: 250,
-    ellipsis: true,
-    slots: { customRender: 'device_sn' }
+    dataIndex: 'status',
+    width: 100,
+    slots: { customRender: 'status' },
   },
   {
     title: '类型',
-    dataIndex: 'nickname',
-    width: 150,
-    ellipsis: true,
-    sorter: (a: any, b: any) => a.nickname.localeCompare(b.nickname),
-    slots: { customRender: 'nickname' }
+    dataIndex: 'task_type',
+    width: 120,
+    customRender: ({ text }: any) => {
+      let content = '';
+      switch (text) {
+        case 0:
+          content = '立即执行';
+          break;
+        case 1:
+          content = '单次定时';
+          break;
+        case 2:
+          content = '循环定时';
+          break;
+        default:
+          break;
+      }
+      return content;
+    }
   },
   {
     title: '计划名称',
-    dataIndex: 'firmware_version',
+    dataIndex: 'job_name',
     width: 150,
     ellipsis: true,
-    slots: { customRender: 'firmware_version' },
   },
   {
     title: '航线名称',
-    dataIndex: 'firmware_status',
+    dataIndex: 'file_name',
     width: 150,
     ellipsis: true,
-    slots: { customRender: 'firmware_status' },
   },
   {
     title: '设备名称',
-    dataIndex: 'status',
-    width: 100,
+    dataIndex: 'dock_name',
+    width: 200,
     ellipsis: true,
-    slots: { customRender: 'status' }
   },
   {
     title: '创建人',
-    dataIndex: 'bound_time',
-    width: 200,
-    sorter: (a: any, b: any) => a.bound_time.localeCompare(b.bound_time),
-    slots: { customRender: 'bound_time' },
+    dataIndex: 'username',
+    width: 150,
   },
   {
     title: '媒体上传',
-    dataIndex: 'login_time',
+    dataIndex: 'media_upload',
     width: 200,
-    sorter: (a: any, b: any) => a.login_time.localeCompare(b.login_time),
-    slots: { customRender: 'login_time' },
+    slots: { customRender: 'media_upload' },
   },
   {
     title: '操作',
@@ -159,7 +211,7 @@ const columns = [
     width: 100,
     slots: { customRender: 'action' },
   },
-]
+];
 
 const rowClassName = (record: any, index: number) => {
   const className = []
@@ -175,12 +227,6 @@ const refreshData = async (page: any) => {
   await fetchList();
 }
 
-const rowSelection = {
-  onChange: (selectedRowKeys: string[]) => {
-    state.selectedRowKeys = selectedRowKeys;
-  },
-}
-
 // 点击搜索
 const onClickSearch = async (query: any) => {
   state.query = query;
@@ -194,24 +240,39 @@ const onClickReset = async (query: any) => {
 }
 
 // 点击删除
-const onClickDelete = (record: any) => {
+const onClickDelete = (id: string, name: string) => {
   Modal.confirm({
-    title: '提示',
-    content: `确定删除${record.device_name}吗?`,
+    title: '删除任务',
+    content: `确定删除${name}吗?`,
+    okType: 'danger',
     onOk: async () => {
-      const res = await unbindDevice(record.device_sn);
-      if (res.code !== 0) {
-        return
+      try {
+        await apis.deleteJob({ job_id: id });
+        await fetchList();
+        message.success('删除成功');
+      } catch (error) {
+        message.error('删除失败: ' + error);
       }
-      await fetchList();
     },
-  });
+  })
 }
 </script>
 
 <style lang="scss">
-.deviceList {
+.taskList {
   padding: 20px;
+  display: flex;
+
+  &-left {
+    width: 240px;
+    height: calc(100vh - 146px);
+    background-color: #232323;
+    margin-right: 20px;
+  }
+
+  &-right {
+    width: calc(100% - 260px);
+  }
 }
 
 .ant-table {

+ 13 - 6
Web/src/pages/page-web/projects/task/waylineList/index.vue

@@ -68,10 +68,12 @@ const fetchList = async () => {
       page: paginationConfig.current,
       page_size: paginationConfig.pageSize,
     });
-    state.list = res.data.list;
-    paginationConfig.total = res.data.pagination.total;
-    paginationConfig.current = res.data.pagination.page;
-    paginationConfig.pageSize = res.data.pagination.page_size;
+    if (res.code === 0) {
+      state.list = res.data.list;
+      paginationConfig.total = res.data.pagination.total
+      paginationConfig.current = res.data.pagination.page
+      paginationConfig.pageSize = res.data.pagination.page_size
+    }
   } catch (e) {
     console.error(e);
   } finally {
@@ -219,8 +221,13 @@ const onClickDelete = (id: string, name: string) => {
     content: `确定删除${name}吗?`,
     okType: 'danger',
     onOk: async () => {
-      await deleteWaylineFile(getWorkspaceId(), id);
-      await fetchList();
+      try {
+        await deleteWaylineFile(getWorkspaceId(), id);
+        await fetchList();
+        message.success('删除成功');
+      } catch (error) {
+        message.error('删除失败: ' + error);
+      }
     },
   })
 }

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

@@ -58,11 +58,11 @@ const fetchList = async () => {
       page_size: paginationConfig.pageSize
     });
     if (res.code === 0) {
+      state.list = res.data.list;
       paginationConfig.total = res.data.pagination.total
       paginationConfig.current = res.data.pagination.page
       paginationConfig.pageSize = res.data.pagination.page_size
     }
-    state.list = res.data.list;
   } catch (e) {
     console.error(e);
   } finally {

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

@@ -13,7 +13,7 @@
               dock.sn === state.selectedSn ? 'item-border-selected' : ''
             ]" :style="{ 'margin-top': index === 0 ? '' : '10px' }" v-for="(dock, index) in onlineDocks.data"
               :key="dock.sn" @click="handleclickItem(dock)">
-              <Airport :dock="dock" :onClickLookInfo="() => switchVisible(dock, true)" />
+              <Airport :dock="dock" :lookInfo="true" :onClickLookInfo="() => switchVisible(dock, true)" />
             </div>
           </div>
         </a-collapse-panel>

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

@@ -117,10 +117,10 @@ export enum MediaStatus { // 媒体上传进度
 }
 
 export const MediaStatusMap = {
-  [MediaStatus.ToUpload]: 'Waiting to upload',
-  [MediaStatus.Uploading]: 'Uploading…',
-  [MediaStatus.Success]: 'Uploaded',
-  [MediaStatus.Empty]: 'No media files',
+  [MediaStatus.ToUpload]: '待上传',
+  [MediaStatus.Uploading]: '上传中',
+  [MediaStatus.Empty]: '无媒体文件',
+  [MediaStatus.Success]: '上传成功',
 }
 
 export const MediaStatusColorMap = {