Browse Source

远程计划任务

李富豪 1 năm trước cách đây
mục cha
commit
6a7e1011ec

+ 2 - 2
Web/env/.env.production

@@ -2,7 +2,7 @@
 VITE_ENV = 'production'
 
 # Api 地址
-VITE_API_URL = 'https://uas.ryuiso.com'
+VITE_API_URL = 'http://118.195.177.247:6789'
 
 # WebSocket 地址
-VITE_WEBSOCKET_URL = 'wss://uas.ryuiso.com:443/api/v1/ws'
+VITE_WEBSOCKET_URL = 'ws://118.195.177.247:6789/api/v1/ws'

+ 2 - 2
Web/env/.env.subsystem

@@ -2,7 +2,7 @@
 VITE_ENV = 'subsystem'
 
 # Api 地址
-VITE_API_URL = 'https://uas.ryuiso.com'
+VITE_API_URL = 'http://118.195.177.247:6789'
 
 # WebSocket 地址
-VITE_WEBSOCKET_URL = 'wss://uas.ryuiso.com:443/api/v1/ws'
+VITE_WEBSOCKET_URL = 'ws://118.195.177.247:6789/api/v1/ws'

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

@@ -6,7 +6,7 @@
           <a-range-picker style="width: 250px;" v-model:value="formModel.rangeDate" />
         </a-form-item>
         <a-form-item name="device_name">
-          <a-select style="width: 200px;" placeholder="请选择设备型号" v-model:value="formModel.device_name">
+          <a-select style="width: 200px;" placeholder="设备型号" v-model:value="formModel.device_name">
             <a-select-option v-for="item in state.deviceModelList" :value="item.value">
               {{ item.label }}
             </a-select-option>

+ 1 - 17
Web/src/components/devices/deviceList/components/Search.vue

@@ -32,24 +32,8 @@
     </a-col>
     <a-col>
       <a-form ref="formRef" layout="inline" :model="formModel" :colon="false">
-        <!-- <a-form-item name="status">
-          <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.device_type">
+          <a-select style="width: 200px;" placeholder="设备型号" v-model:value="formModel.device_type">
             <a-select-option v-for="item in state.deviceModelList" :value="item.value">
               {{ item.label }}
             </a-select-option>

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

@@ -6,7 +6,7 @@
           <a-range-picker style="width: 250px;" v-model:value="formModel.date" />
         </a-form-item>
         <a-form-item name="status">
-          <a-select style="width: 200px;" placeholder="请选择上传状态" v-model:value="formModel.status">
+          <a-select style="width: 200px;" placeholder="上传状态" v-model:value="formModel.status">
             <a-select-option :value="1">
               上传中
             </a-select-option>

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

@@ -24,7 +24,7 @@
           <a-range-picker style="width: 250px;" v-model:value="formModel.rangeDate" />
         </a-form-item>
         <a-form-item name="media_type">
-          <a-select style="width: 200px;" placeholder="请选择媒体类型" v-model:value="formModel.media_type">
+          <a-select style="width: 200px;" placeholder="媒体类型" v-model:value="formModel.media_type">
             <a-select-option :value="1">
               原图
             </a-select-option>

+ 14 - 12
Web/src/pages/page-web/projects/media/index/index.vue

@@ -28,8 +28,8 @@
           </div>
         </div>
         <div class="mediaList-table" v-if="state.mode === 'table'">
-          <a-table :scroll="{ x: '100%', y: 500 }"  rowKey="id" :loading="state.listLoading"
-            :columns="columns" :dataSource="state.list" :rowClassName="rowClassName" :pagination="paginationConfig"
+          <a-table :scroll="{ x: '100%', y: 500 }" rowKey="id" :loading="state.listLoading" :columns="columns"
+            :dataSource="state.list" :rowClassName="rowClassName" :pagination="paginationConfig"
             :rowSelection="{ selectedRowKeys: state.selectedRowKeys, onChange: onSelectChange }">
             <!-- 文件夹名称 -->
             <template #dir_name="{ record }">
@@ -44,12 +44,14 @@
             </template>
             <!-- 操作 -->
             <template #action="{ record }">
-              <a-tooltip title="压缩下载">
-                <DownloadOutlined style="color: #2d8cf0;" @click="onClickDownload(record)" />
-              </a-tooltip>
-              <a-tooltip title="删除">
-                <DeleteOutlined style="color: #2d8cf0;" @click="onClickDelete(record)" />
-              </a-tooltip>
+              <div class="flex-align-center flex-row" style="color: #2d8cf0">
+                <a-tooltip title="压缩下载">
+                  <DownloadOutlined style="margin-right: 10px;" @click="onClickDownload(record)" />
+                </a-tooltip>
+                <a-tooltip title="删除">
+                  <DeleteOutlined style="color: #2d8cf0;" @click="onClickDelete(record)" />
+                </a-tooltip>
+              </div>
             </template>
           </a-table>
         </div>
@@ -85,14 +87,14 @@
 
 <script lang="ts" setup>
 import { reactive, onMounted } from 'vue';
-import { MenuOutlined, AppstoreOutlined, DownloadOutlined, EnvironmentOutlined,DeleteOutlined } from '@ant-design/icons-vue';
+import { MenuOutlined, AppstoreOutlined, DownloadOutlined, EnvironmentOutlined, DeleteOutlined } from '@ant-design/icons-vue';
 import Search from './components/Search.vue';
 import fileSrc from '/@/assets/media/file.svg';
 import { apis } from '/@/api/custom';
 import router from '/@/router/index';
 import { downloadFile } from '/@/utils/common';
 import moment from 'moment';
-import { Modal,message } from 'ant-design-vue'
+import { Modal, message } from 'ant-design-vue'
 
 interface State {
   query: any,
@@ -314,8 +316,8 @@ const onClickDelete = async (record: any) => {
       }
     },
   })
-  
-  
+
+
 }
 </script>
 

+ 0 - 54
Web/src/pages/page-web/projects/task/taskList/components/CustomCell.vue

@@ -1,54 +0,0 @@
-<template>
-    <div class="customCell">
-        <template v-if="isEdit">
-            <div class="customCell-item">
-                <a-input v-model:value="record[fieldName]" />
-            </div>
-            <div class="customCell-item">
-                <a-input v-model:value="record.children[fieldName]" />
-            </div>
-        </template>
-        <template v-else>
-            <div class="customCell-item">
-                {{ record[fieldName] || '--' }}
-            </div>
-            <div class="customCell-item" v-if="record.children">
-                <div class="mt-5 ml0"
-                    style=" width:16px;height:16px;border-left:2px solid rgb(200,200,200);border-bottom:2px solid rgb(200,200,200);float:left;margin-right:5px;"
-                    v-if="showIcon">
-                </div>
-                <div>
-                    {{ record.children[fieldName] || '--' }}
-                </div>
-            </div>
-        </template>
-    </div>
-</template>
-
-<script lang="ts" setup>
-interface Props {
-    record: any,
-    fieldName: string,
-    showIcon?: boolean,
-    isEdit?: boolean,
-};
-
-const props = withDefaults(defineProps<Props>(), {
-    showIcon: false,
-    isEdit: false,
-});
-</script>
-
-<style lang="scss" scoped>
-.customCell {
-    display: flex;
-    flex-direction: column;
-    justify-content: center;
-
-    &-item {
-        height: 40px;
-        display: flex;
-        align-items: center;
-    }
-}
-</style>

+ 0 - 280
Web/src/pages/page-web/projects/task/taskList/components/FeedbackCreateModal.vue

@@ -1,280 +0,0 @@
-<template>
-    <a-modal :width="950" title="设备异常反馈" :maskClosable="false" :closable="false" okText="提交" v-model:visible="visible"
-        @ok="handleClickSubmit" @cancel="onClickClose">
-        <a-form ref="formRef" :model="formModel" :colon="false" :label-col="labelCol" :wrapper-col="wrapperCol">
-            <a-form-item label='异常描述' name="logs_info"
-                :rules="[{ required: true, message: '异常描述不能为空', whitespace: true }]">
-                <a-input style="width: 100%;" v-model:value="formModel.logs_info" placeholder="请输入异常描述" />
-            </a-form-item>
-            <a-form-item label='发生时间' name="happen_time" :rules="[{ required: true, message: '发生时间不能为空' }]">
-                <a-date-picker style="width: 100%;" showTime v-model:value="formModel.happen_time"
-                    placeholder="请选择发生时间" />
-            </a-form-item>
-            <a-form-item label='联系电话' name="contact_number">
-                <a-input style="width: 100%;" v-model:value="formModel.contact_number" placeholder="请输入联系电话" />
-            </a-form-item>
-            <a-form-item label='联系邮箱' name="contact_email">
-                <a-input style="width: 100%;" v-model:value="formModel.contact_email" placeholder="请输入联系邮箱" />
-            </a-form-item>
-            <a-form-item label='附件'>
-                <a-upload :action="getUploadPath()" method="POST" :headers="getHeaders()"
-                    :file-list="formModel.fileList" @change="handleChangeUpload">
-                    <a-button style="position: relative;">
-                        <div>
-                            上传附件
-                        </div>
-                        <div style="width: 400px;color:#8a8a8a;font-size: 12px;position: absolute;top: 6px;right: -410px;cursor:auto;"
-                            @click.stop>
-                            支持上传:图片、文档、视频、RAR 及 ZIP 等文件(至多上传 3 个文件)
-                        </div>
-                    </a-button>
-                </a-upload>
-            </a-form-item>
-        </a-form>
-        <div class="log-list">
-            <div class="log-list-item">
-                <a-table :columns="airportLogColumns" :scroll="{ x: '100%', y: 300 }"
-                    :data-source="state.airportLogList" :loading="state.listLoading" rowKey="boot_index"
-                    :row-selection="airportLogState.rowSelection" :pagination="false">
-                    <template #log="{ record }">
-                        <div style="font-size: 12px;">
-                            {{ getDateTime(record.start_time) }}
-                            <span style="margin: 0 5px;">
-                                -
-                            </span>
-                            {{ getDateTime(record.end_time) }}
-                        </div>
-                    </template>
-                    <template #size="{ record }">
-                        <div>
-                            {{ getLogSize(record.size) }} G
-                        </div>
-                    </template>
-                </a-table>
-            </div>
-            <div class="log-list-item">
-                <a-table :columns="droneLogColumns" :scroll="{ x: '100%', y: 300 }" :data-source="state.droneLogList"
-                    :loading="state.listLoading" rowKey="boot_index" :row-selection="droneLogState.rowSelection"
-                    :pagination="false">
-                    <template #log="{ record }">
-                        <div style="font-size: 12px;">
-                            {{ getDateTime(record.start_time) }}
-                            <span style="margin: 0 5px;">
-                                -
-                            </span>
-                            {{ getDateTime(record.end_time) }}
-                        </div>
-                    </template>
-                    <template #size="{ record }">
-                        <div>
-                            {{ getLogSize(record.size) }} G
-                        </div>
-                    </template>
-                </a-table>
-            </div>
-        </div>
-    </a-modal>
-</template>
-
-<script lang="ts" setup>
-import { ref, reactive, onMounted } from 'vue';
-import { message } from 'ant-design-vue';
-import { getUploadPath, apis, CreateDeviceFeedbackApiParams } from '/@/api/custom';
-import { getHeaders } from '/@/api/http/request';
-import moment from 'moment';
-
-interface Props {
-    sn: string,
-    visible: boolean,
-    onClickSubmit: (sn: string, data: CreateDeviceFeedbackApiParams) => Promise<any>,
-    onClickClose: () => void,
-};
-
-const props = withDefaults(defineProps<Props>(), {
-
-});
-
-const formRef = ref();
-
-const formModel = reactive({
-    logs_info: undefined,
-    happen_time: undefined,
-    contact_number: undefined,
-    contact_email: undefined,
-    fileList: [] as {
-        uid: string,
-        name: string,
-        url: string,
-    }[],
-})
-
-const labelCol = { span: 4 };
-
-const wrapperCol = { span: 18 };
-
-interface State {
-    listLoading: boolean,
-    airportLogList: {
-        boot_index: number,
-        start_time: number,
-        end_time: number,
-        size: number,
-    }[],
-    droneLogList: {
-        boot_index: number,
-        start_time: number,
-        end_time: number,
-        size: number,
-    }[],
-};
-
-const state: State = reactive({
-    listLoading: false,
-    airportLogList: [],
-    droneLogList: [],
-});
-
-const fetchList = async () => {
-    state.listLoading = true;
-    try {
-        const res = await apis.fetchDeviceLogList(props.sn, { domain_list: '0,3' });
-        const files = res.data.files;
-        const airportLog = files.find((item: any) => item.module === '3');
-        if (airportLog) {
-            state.airportLogList = airportLog.list;
-        }
-        const droneLog = files.find((item: any) => item.module === '0');
-        if (droneLog) {
-            state.droneLogList = droneLog.list;
-        }
-    } catch (error) {
-        console.error(error);
-    } finally {
-        state.listLoading = false;
-    }
-}
-
-const getDateTime = (timestamp: number) => {
-    const date = moment.unix(timestamp).format('YYYY-MM-DD HH:mm');
-    return date;
-}
-
-const getLogSize = (size: number) => {
-    // 将字节转换为GB
-    const gbSize = size / (1024 * 1024 * 1024);
-    // 保留一位小数
-    return parseFloat(gbSize.toFixed(1));
-};
-
-onMounted(async () => {
-    await fetchList();
-})
-
-const handleChangeUpload = (info: any) => {
-    const list = info.fileList.slice(-3);
-    const newFileList = list.map((file: any) => {
-        const data = file.response?.data;
-        return {
-            uid: data?.id || file.uid,
-            name: file.name,
-            url: data?.url || file.url,
-        }
-    });
-    formModel.fileList = newFileList;
-    if (info.file.status === 'done') {// 上传成功
-        const data = info.file.response;
-        if (data.code === 0) {
-            message.success('上传成功');
-        } else {
-            message.error(data.message);
-        }
-    }
-};
-
-const airportLogColumns = [
-    {
-        title: '机场日志',
-        dataIndex: 'log',
-        slots: { customRender: 'log' }
-    },
-    {
-        title: '文件大小',
-        dataIndex: 'size',
-        width: 100,
-        slots: { customRender: 'size' }
-    }
-]
-
-const droneLogColumns = [
-    {
-        title: '飞行器日志',
-        dataIndex: 'log',
-        slots: { customRender: 'log' }
-    },
-    {
-        title: '文件大小',
-        dataIndex: 'size',
-        width: 100,
-        slots: { customRender: 'size' }
-    }
-]
-
-const airportLogState = reactive({
-    selectedRows: [] as any[],
-    rowSelection: {
-        columnWidth: 40,
-        selectedRowKeys: [] as number[],
-        onChange: (selectedRowKeys: number[], selectedRows: any[]) => {
-            airportLogState.rowSelection.selectedRowKeys = selectedRowKeys;
-            airportLogState.selectedRows = selectedRows;
-        },
-    }
-})
-
-const droneLogState = reactive({
-    selectedRows: [] as any[],
-    rowSelection: {
-        columnWidth: 40,
-        selectedRowKeys: [] as number[],
-        onChange: (selectedRowKeys: number[], selectedRows: any[]) => {
-            droneLogState.rowSelection.selectedRowKeys = selectedRowKeys;
-            droneLogState.selectedRows = selectedRows;
-        },
-    }
-})
-
-// 点击提交
-const handleClickSubmit = () => {
-    formRef.value?.validateFields().then(async (values: any) => {
-        const list = [];
-        const airportLogSelectedList = airportLogState.selectedRows;
-        const droneLogSelectedList = droneLogState.selectedRows;
-        if (airportLogSelectedList.length) {
-            list.push({ list: airportLogSelectedList, module: '3' });
-        }
-        if (droneLogSelectedList.length) {
-            list.push({ list: droneLogSelectedList, module: '0' });
-        }
-        const data = {
-            ...values,
-            happen_time: moment(values.happenTime).valueOf(),
-            oss_ids: formModel.fileList.length ? formModel.fileList.map(item => item.uid).join(',') : undefined,
-            list: list.length ? list : undefined,
-        }
-        await props.onClickSubmit(props.sn, data);
-    }).catch((error: any) => {
-        console.error(error);
-    });
-}
-</script>
-
-<style lang="scss" scoped>
-.log-list {
-    display: flex;
-    justify-content: space-between;
-
-    &-item {
-        width: 48.5%;
-    }
-}
-</style>

+ 0 - 210
Web/src/pages/page-web/projects/task/taskList/components/FeedbackDetailModal.vue

@@ -1,210 +0,0 @@
-<template>
-    <a-modal :width="800" title="设备异常反馈" :maskClosable="false" :closable="false" :okButtonProps="{ hidden: true }"
-        cancelText="关闭" v-model:visible="visible" @cancel="onClickClose">
-        <a-spin :spinning="state.loading">
-            <a-form :colon="false" :label-col="labelCol" :wrapper-col="wrapperCol">
-                <a-form-item label='异常描述'>
-                    {{ state.info.logs_info || '--' }}
-                </a-form-item>
-                <a-form-item label='发生时间'>
-                    {{ state.info.happen_time || '--' }}
-                </a-form-item>
-                <a-form-item label='联系电话'>
-                    {{ state.info.contact_number || '--' }}
-                </a-form-item>
-                <a-form-item label='联系邮箱'>
-                    {{ state.info.contact_email || '--' }}
-                </a-form-item>
-            </a-form>
-            <div class="log-list">
-                <div class="log-list-item borderRight">
-                    <div class="log-list-item-title">
-                        机场日志
-                    </div>
-                    <div class="log-list-item-content">
-                        <div class="log-list-item-content-cell" v-for="item in state.info.airportLogList"
-                            :key="item.boot_index">
-                            <div>
-                                <div>
-                                    {{ getDateTime(item.start_time) }}
-                                    <span style="margin: 0 5px;">
-                                        -
-                                    </span>
-                                    {{ getDateTime(item.end_time) }}
-                                </div>
-                            </div>
-                            <div>
-                                {{ getLogSize(item.size) }} G
-                            </div>
-                        </div>
-                    </div>
-                </div>
-                <div class="log-list-item borderLeft">
-                    <div class="log-list-item-title">
-                        飞行器日志
-                    </div>
-                    <div class="log-list-item-content">
-                        <div class="log-list-item-content-cell" v-for="item in state.info.droneLogList"
-                            :key="item.boot_index">
-                            <div>
-                                <div>
-                                    {{ getDateTime(item.start_time) }}
-                                    <span style="margin: 0 5px;">
-                                        -
-                                    </span>
-                                    {{ getDateTime(item.end_time) }}
-                                </div>
-                            </div>
-                            <div>
-                                {{ getLogSize(item.size) }} G
-                            </div>
-                        </div>
-                    </div>
-                </div>
-            </div>
-        </a-spin>
-    </a-modal>
-</template>
-
-<script lang="ts" setup>
-import { reactive, onMounted } from 'vue';
-import { apis } from '/@/api/custom';
-import moment from 'moment';
-
-interface Props {
-    currentId: string,
-    visible: boolean,
-    onClickClose: () => void,
-};
-
-const props = withDefaults(defineProps<Props>(), {
-
-});
-
-interface State {
-    loading: boolean,
-    info: {
-        logs_info: string,
-        happen_time: string,
-        contact_number: string,
-        contact_email: string,
-        airportLogList: {
-            boot_index: number,
-            end_time: number,
-            size: number,
-            start_time: number,
-        }[],
-        droneLogList: {
-            boot_index: number,
-            end_time: number,
-            size: number,
-            start_time: number,
-        }[],
-    }
-}
-
-const state: State = reactive({
-    loading: false,
-    info: {
-        logs_info: '',
-        happen_time: '',
-        contact_number: '',
-        contact_email: '',
-        airportLogList: [],
-        droneLogList: [],
-    }
-})
-
-const labelCol = { style: { width: '100px', marginRight: '30px' } };
-
-const wrapperCol = { span: 18 };
-
-const fetchDetail = async () => {
-    state.loading = true;
-    try {
-        const res = await apis.fetchDeviceLogDetail({ logsId: props.currentId });
-        if (res.code !== 0) {
-            return;
-        }
-        const info = res.data;
-        const list = info.list || [];
-        const data = {
-            logs_info: info.logs_info,
-            happen_time: moment(info.happen_time).format('YYYY-MM-DD HH:mm:ss'),
-            contact_number: info.contact_number,
-            contact_email: info.contact_email,
-            airportLogList: [],
-            droneLogList: [],
-        }
-        const airportLog = list.find((item: any) => item.module === '3');
-        if (airportLog) {
-            data.airportLogList = airportLog.list;
-        }
-        const droneLog = list.find((item: any) => item.module === '0');
-        if (droneLog) {
-            data.droneLogList = droneLog.list;
-        }
-        state.info = data;
-    } catch (error) {
-        console.error(error);
-    } finally {
-        state.loading = false;
-    }
-}
-
-onMounted(async () => {
-    await fetchDetail();
-})
-
-const getDateTime = (timestamp: number) => {
-    const date = moment.unix(timestamp).format('YYYY-MM-DD HH:mm');
-    return date;
-}
-
-const getLogSize = (size: number) => {
-    // 将字节转换为GB
-    const gbSize = size / (1024 * 1024 * 1024);
-    // 保留一位小数
-    return parseFloat(gbSize.toFixed(1));
-};
-</script>
-
-<style lang="scss" scoped>
-.log-list {
-    border-top: 1px solid #f0f0f0;
-    display: flex;
-    justify-content: space-between;
-
-    .borderRight {
-        border-right: 1px solid #f0f0f0;
-    }
-
-    .borderLeft {
-        border-left: 1px solid #f0f0f0;
-    }
-
-    &-item {
-        width: 48%;
-        padding: 10px;
-        margin-top: 20px;
-
-        &-title {
-            font-weight: bold;
-        }
-
-        &-content {
-            width: 100%;
-            max-height: 300px;
-            padding: 0 10px;
-            overflow-y: auto;
-
-            &-cell {
-                display: flex;
-                justify-content: space-between;
-                align-items: center;
-                margin-top: 10px;
-            }
-        }
-    }
-}
-</style>

+ 0 - 269
Web/src/pages/page-web/projects/task/taskList/components/FeedbackDrawer.vue

@@ -1,269 +0,0 @@
-<template>
-    <a-drawer width="70%" title="设备异常反馈记录" :visible="visible" @close="onClose">
-        <div class="top">
-            <a-button type="primary" @click="() => state.createModalVisible = true">
-                新建异常反馈
-            </a-button>
-            <div>
-                <a-form ref="formRef" layout="inline" :model="formModel" :colon="false">
-                    <a-form-item name="rangeDate">
-                        <a-range-picker style="width: 250px;" valueFormat="YYYY-MM-DD"
-                            v-model:value="formModel.rangeDate" />
-                    </a-form-item>
-                    <a-form-item name="username">
-                        <a-input style="width: 200px;" placeholder="反馈人" v-model:value="formModel.username" />
-                    </a-form-item>
-                    <a-form-item name="search_info">
-                        <a-input style="width: 200px;" placeholder="设备SN、设备异常描述"
-                            v-model:value="formModel.search_info" />
-                    </a-form-item>
-                    <a-form-item>
-                        <a-button style="margin-right: 10px;" @click="handleClickSearch">
-                            <template #icon>
-                                <SearchOutlined />
-                            </template>
-                        </a-button>
-                        <a-button @click="handleClickReset">
-                            <template #icon>
-                                <ReloadOutlined />
-                            </template>
-                        </a-button>
-                    </a-form-item>
-                </a-form>
-            </div>
-        </div>
-        <div>
-            <a-table :scroll="{ x: '100%', y: 500 }" rowKey="logs_id" :loading="state.listLoading" :columns="columns"
-                :dataSource="state.list" :pagination="paginationConfig">
-                <!-- 上传状态 -->
-                <template #status="{ record }">
-                    <div v-if="record.status === 1">
-                        上传中
-                    </div>
-                    <div v-else-if="record.status === 2">
-                        成功
-                    </div>
-                    <div v-else-if="record.status === 3">
-                        取消
-                    </div>
-                    <div v-else-if="record.status === 4">
-                        失败
-                    </div>
-                </template>
-                <!-- 操作 -->
-                <template #operation="{ record }">
-                    <a-tooltip title="详情">
-                        <FileSearchOutlined style="color: #2d8cf0" @click="handleClickDetail(record.logs_id)" />
-                    </a-tooltip>
-                </template>
-            </a-table>
-        </div>
-    </a-drawer>
-    <!-- 异常反馈-信息弹出层 -->
-    <FeedbackCreateModal :sn="device.device_sn" :visible="state.createModalVisible"
-        :onClickSubmit="createModalOnClickSubmit" :onClickClose="() => state.createModalVisible = false"
-        v-if="state.createModalVisible" />
-    <!-- 异常反馈-详情弹出层 -->
-    <FeedbackDetailModal :currentId="state.currentId" :visible="state.detailModalVisible"
-        :onClickClose="() => state.detailModalVisible = false" v-if="state.detailModalVisible" />
-</template>
-
-<script lang="ts" setup>
-import { ref, reactive, onMounted } from 'vue';
-import { message } from 'ant-design-vue';
-import { SearchOutlined, ReloadOutlined, FileSearchOutlined } from '@ant-design/icons-vue';
-import FeedbackCreateModal from '../components/FeedbackCreateModal.vue';
-import FeedbackDetailModal from '../components/FeedbackDetailModal.vue';
-import { apis, CreateDeviceFeedbackApiParams } from '/@/api/custom';
-import { Device } from '/@/types/device';
-import moment from 'moment';
-
-interface Props {
-    visible: boolean,
-    device: Device,
-    onClose: () => void,
-};
-
-const props = withDefaults(defineProps<Props>(), {
-
-});
-
-const formRef = ref();
-
-const formModel = reactive({
-    rangeDate: undefined,
-    username: undefined,
-    search_info: undefined,
-})
-
-type Query = Partial<{
-    begin_time: number,
-    end_time: number,
-    username: number,
-    search_info: string,
-}>;
-
-interface State {
-    query?: Query,
-    listLoading: boolean,
-    list: any[],
-    currentId: string,
-    createModalVisible: boolean,
-    detailModalVisible: boolean,
-};
-
-const state: State = reactive({
-    query: undefined,
-    listLoading: false,
-    list: [],
-    currentId: '',
-    createModalVisible: false,
-    detailModalVisible: false,
-});
-
-const paginationConfig = reactive({
-    pageSizeOptions: ['20', '50', '100'],
-    showQuickJumper: true,
-    showSizeChanger: true,
-    pageSize: 20,
-    current: 1,
-    total: 0,
-    onChange: async (current: number) => {
-        paginationConfig.current = current;
-        await fetchList();
-    },
-    onShowSizeChange: async (current: number, pageSize: number) => {
-        paginationConfig.pageSize = pageSize;
-        await fetchList();
-    }
-})
-
-const fetchList = async () => {
-    state.listLoading = true;
-    try {
-        const res = await apis.fetchDeviceFeedbackRecordList(
-            props.device.device_sn,
-            {
-                ...state.query,
-                page: paginationConfig.current,
-                page_size: paginationConfig.pageSize
-            }
-        );
-        if (res.code === 0) {
-            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 {
-        state.listLoading = false;
-    }
-}
-
-onMounted(async () => {
-    await fetchList();
-})
-
-const columns = [
-    {
-        title: '反馈时间',
-        dataIndex: 'create_time',
-        width: 200,
-        sorter: (a: any, b: any) => a.create_time.localeCompare(b.create_time),
-    },
-    {
-        title: '反馈人',
-        dataIndex: 'username',
-        width: 150,
-    },
-    {
-        title: '设备型号',
-        dataIndex: 'device_name',
-        width: 150,
-    },
-    {
-        title: '设备SN',
-        dataIndex: 'device_sn',
-        width: 250,
-        slots: { customRender: 'device_sn' }
-    },
-    {
-        title: '设备名称',
-        dataIndex: 'nick_name',
-        width: 150,
-        slots: { customRender: 'nick_name' },
-    },
-    {
-        title: '设备异常描述',
-        dataIndex: 'log_info',
-        width: 150,
-        ellipsis: true,
-    },
-    {
-        title: '上传状态',
-        dataIndex: 'status',
-        slots: { customRender: 'status' },
-        sorter: (a: any, b: any) => a.status.localeCompare(b.status),
-        width: 150,
-    },
-    {
-        title: '操作',
-        dataIndex: 'operation',
-        fixed: 'right',
-        width: 80,
-        slots: { customRender: 'operation' }
-    },
-]
-
-// 点击查询
-const handleClickSearch = async () => {
-    const values = formRef.value?.getFieldsValue();
-    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();
-    }
-    state.query = data;
-    await fetchList();
-}
-
-// 点击重置
-const handleClickReset = async () => {
-    formRef.value?.resetFields();
-    const values = formRef.value?.getFieldsValue();
-    const data = { ...values };
-    delete data.rangeDate;
-    state.query = data;
-    await fetchList();
-}
-
-const createModalOnClickSubmit = async (sn: string, data: CreateDeviceFeedbackApiParams) => {
-    state.createModalVisible = false;
-    try {
-        const res = await apis.createDeviceFeedback(sn, data);
-        if (res.code === 0) {
-            await fetchList();
-            message.success('提交成功');
-        }
-    } catch (error) {
-        console.error(error);
-    }
-}
-
-// 点击详情
-const handleClickDetail = (id: string) => {
-    state.currentId = id;
-    state.detailModalVisible = true;
-}
-</script>
-
-<style lang="scss" scoped>
-.top {
-    display: flex;
-    justify-content: space-between;
-    margin-bottom: 20px;
-}
-</style>

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

@@ -1,62 +1,50 @@
 <template>
   <a-row style="margin-bottom: 20px;" justify="space-between">
     <a-col>
-      <a-button style="margin-right: 10px;" @click="state.visible = true">
-        机场设备绑定码
-        <MenuOutlined />
-      </a-button>
-      <a-popover trigger="hover">
-        <template #content>
-          <div class="popover">
-            <div>
-              请使用遥控器连接飞行器后,在“Pilot2首页-云服务-三方云平台”绑定设备。
-            </div>
-            <div>
-              了解更多:
-              <span>
-                <a>
-                  <ReadOutlined />
-                  说明书
-                </a>
-              </span>
-            </div>
-          </div>
-        </template>
-        <a style="margin-right: 10px;">
-          如何绑定机场?
-        </a>
-      </a-popover>
-      <a-button type="primary" danger :disabled="!selectedRowKeys.length" @click="onClickDelete">
-        删除
+      <a-button type="primary">
+        新建机场任务
       </a-button>
     </a-col>
     <a-col>
       <a-form ref="formRef" layout="inline" :model="formModel" :colon="false">
-        <!-- <a-form-item name="status">
-          <a-select style="width: 200px;" placeholder="请选择状态" v-model:value="formModel.status">
-            <a-select-option value="1">
-              设备空闲中
+        <a-form-item name="rangeDate">
+          <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 :value="2">
+              成功
             </a-select-option>
-            <a-select-option value="3">
-              舱内关机
+            <a-select-option :value="3">
+              取消
             </a-select-option>
-            <a-select-option value="4">
-              离线
+            <a-select-option :value="4">
+              失败
             </a-select-option>
           </a-select>
-        </a-form-item> -->
+        </a-form-item>
         <a-form-item name="device_type">
-          <a-select style="width: 200px;" placeholder="请选择设备型号" v-model:value="formModel.device_type">
-            <a-select-option v-for="item in state.deviceModelList" :value="item.value">
-              {{ item.label }}
+          <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="设备SN、设备名称" v-model:value="formModel.search_info" />
+          <a-input style="width: 200px;" placeholder="任务名称、航线名称" v-model:value="formModel.search_info" />
         </a-form-item>
         <a-form-item>
           <a-button style="margin-right: 10px;" @click="handleClickSearch">
@@ -73,37 +61,13 @@
       </a-form>
     </a-col>
   </a-row>
-  <a-modal v-model:visible="state.visible" title="设备绑定码" :footer="null">
-    <div>
-      请在 Pilot2 机场部署流程-云服务配置中,以组织ID和设备绑定码来绑定机场设备。
-    </div>
-    <div class="modal-item">
-      <div class="modal-item-title">
-        项目名称
-      </div>
-      <div>
-        {{ state.deviceInfo.name }}
-      </div>
-    </div>
-    <div class="modal-item">
-      <div class="modal-item-title">
-        设备绑定码
-      </div>
-      <div>
-        {{ state.deviceInfo.code }}
-      </div>
-    </div>
-  </a-modal>
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive, onMounted } from 'vue';
-import { ReadOutlined, MenuOutlined, SearchOutlined, ReloadOutlined } from '@ant-design/icons-vue';
-import { apis } from '/@/api/custom';
+import { ref, reactive } from 'vue';
+import { SearchOutlined, ReloadOutlined } from '@ant-design/icons-vue';
 
 interface Props {
-  selectedRowKeys: string[],
-  onClickDelete: () => Promise<any>,
   onClickSearch: (query: any) => Promise<any>,
   onClickReset: (query: any) => Promise<any>,
 };
@@ -119,42 +83,6 @@ const formModel = reactive({
   search_info: undefined,
 })
 
-interface State {
-  visible: boolean,
-  deviceInfo: {
-    name: string,
-    code: string,
-  },
-  deviceModelList: {
-    value: string,
-    label: string,
-  }[],
-};
-
-const state: State = reactive({
-  visible: false,
-  deviceInfo: {
-    name: '上海展域航空技术有限公司',
-    code: 'PB97VR',
-  },
-  deviceModelList: [],
-})
-
-onMounted(async () => {
-  try {
-    const res = await apis.fetchDeviceModel();
-    const list = res.data.map((item: any) => {
-      return {
-        value: item.device_value,
-        label: item.device_name,
-      }
-    })
-    state.deviceModelList = list;
-  } catch (e) {
-    console.error(e);
-  }
-})
-
 // 点击查询
 const handleClickSearch = async () => {
   const values = formRef.value?.getFieldsValue();
@@ -169,18 +97,4 @@ const handleClickReset = async () => {
 }
 </script>
 
-<style lang="scss" scoped>
-.popover {
-  width: 200px;
-}
-
-.modal-item {
-  display: flex;
-  margin-top: 20px;
-
-  &-title {
-    width: 100px;
-    font-weight: bold;
-  }
-}
-</style>
+<style lang="scss" scoped></style>

+ 20 - 163
Web/src/pages/page-web/projects/task/taskList/index.vue

@@ -1,99 +1,34 @@
 <template>
   <div class="deviceList">
-    <Search :selectedRowKeys="state.selectedRowKeys" :onClickDelete="onClickBatchDelete" :onClickSearch="onClickSearch"
-      :onClickReset="onClickReset" />
+    <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">
-        <!-- 设备型号 -->
-        <template #device_name="{ record }">
-          <CustomCell :record="record" fieldName="device_name" :showIcon="true" />
-        </template>
-        <!-- 设备SN -->
-        <template #device_sn="{ record }">
-          <CustomCell :record="record" fieldName="device_sn" />
-        </template>
-        <!-- 设备名称 -->
-        <template #nickname="{ record }">
-          <CustomCell :record="record" fieldName="nickname" :isEdit="!!state.editableData[record.device_sn]" />
-        </template>
-        <!-- 固件版本 -->
-        <template #firmware_version="{ record }">
-          <CustomCell :record="record" fieldName="firmware_version" />
-        </template>
-        <!-- 固件升级 -->
-        <template #firmware_status="{ record }">
-          <DeviceFirmwareUpgrade :device="record" />
-        </template>
-        <!-- 当前状态 -->
-        <template #status="{ record }">
-          <CustomCell :record="record" fieldName="status_text" />
-        </template>
-        <!-- 加入项目时间 -->
-        <template #bound_time="{ record }">
-          <CustomCell :record="record" fieldName="bound_time" />
-        </template>
-        <!-- 最后在线时间 -->
-        <template #login_time="{ record }">
-          <CustomCell :record="record" fieldName="login_time" />
-        </template>
+      <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">
         <!-- 操作 -->
         <template #action="{ record }">
-          <!-- 编辑态操作 -->
-          <div v-if="state.editableData[record.device_sn]">
-            <a-tooltip title="确定">
-              <CheckOutlined style="color: #28d445;margin-right: 10px;" @click="onClickSave(record)" />
-            </a-tooltip>
-            <a-tooltip title="取消">
-              <CloseOutlined style="color: #e70102;" @click="() => delete state.editableData[record.device_sn]" />
+          <div class="flex-align-center flex-row" style="color: #2d8cf0">
+            <a-tooltip title="复制任务">
+              <CopyOutlined style="margin-right: 10px;" />
             </a-tooltip>
-          </div>
-          <!-- 非编辑态操作 -->
-          <div v-else class="flex-align-center flex-row" style="color: #2d8cf0">
-            <a-tooltip title="编辑">
-              <EditOutlined style="margin-right: 10px;" @click="onClickEdit(record)" />
+            <a-tooltip title="查看轨迹">
+              <GatewayOutlined style="margin-right: 10px;" />
             </a-tooltip>
             <a-tooltip title="删除">
-              <DeleteOutlined @click="onClickDelete(record)" />
+              <DeleteOutlined />
             </a-tooltip>
-            <a-dropdown v-if="record.domain === 3">
-              <EllipsisOutlined style="font-size: 20px;margin-left: 10px;" />
-              <template #overlay>
-                <a-menu>
-                  <a-menu-item @click="onClickFeedback(record)">
-                    异常反馈
-                  </a-menu-item>
-                  <a-menu-item @click="onClickDeviceHms(record)">
-                    告警信息
-                  </a-menu-item>
-                  <!-- <a-menu-item>
-                    设备运维
-                  </a-menu-item> -->
-                </a-menu>
-              </template>
-            </a-dropdown>
           </div>
         </template>
       </a-table>
     </div>
   </div>
-  <!-- 异常反馈 -->
-  <FeedbackDrawer :visible="state.feedbackDrawerVisible" :device="state.currentDevice"
-    :onClose="() => state.feedbackDrawerVisible = false" v-if="state.feedbackDrawerVisible" />
-  <!-- 告警信息 -->
-  <DeviceHmsDrawer v-model:visible="state.deviceHmsDrawerVisible" :device="state.currentDevice" />
 </template>
 
 <script lang="ts" setup>
-import { reactive, onMounted, onUnmounted } from 'vue';
+import { reactive, onMounted } from 'vue';
 import { Modal } from 'ant-design-vue';
-import { EditOutlined, CheckOutlined, CloseOutlined, DeleteOutlined, EllipsisOutlined } from '@ant-design/icons-vue';
+import { CopyOutlined, GatewayOutlined, DeleteOutlined } from '@ant-design/icons-vue';
 import Search from './components/Search.vue';
-import CustomCell from './components/CustomCell.vue';
-import DeviceFirmwareUpgrade from '/@/components/devices/device-upgrade/DeviceFirmwareUpgrade.vue';
-import FeedbackDrawer from './components/FeedbackDrawer.vue';
-import DeviceHmsDrawer from '/@/components/devices/device-hms/DeviceHmsDrawer.vue';
 import { getBindingDevices, updateDevice, unbindDevice } from '/@/api/manage';
 import { apis } from '/@/api/custom';
 import { getWorkspaceId } from '/@/utils/index';
@@ -147,7 +82,6 @@ const fetchList = async () => {
     paginationConfig.total = res.data.pagination.total;
     paginationConfig.current = res.data.pagination.page;
     paginationConfig.pageSize = res.data.pagination.page_size;
-    await autoUpdateDeviceStatus()
   } catch (e) {
     console.error(e);
   } finally {
@@ -155,59 +89,13 @@ const fetchList = async () => {
   }
 }
 
-// 自动更新设备状态
-const autoUpdateDeviceStatus = async () => {
-  if (state.list.length === 0) {
-    return;
-  }
-  const snList: string[] = [];
-  state.list.forEach(item => {
-    if (item.children) {
-      snList.push(item.children.device_sn);
-    }
-    snList.push(item.device_sn);
-  })
-  const data = {
-    snList: snList.join(','),
-  }
-  const res = await apis.fetchDeviceStatus(data);
-  const deviceStatusMap = new Map();
-  res.data.forEach((item: any) => {
-    deviceStatusMap.set(item.device_sn, item.status_text);
-  });
-  state.list.forEach(item => {
-    if (item.children) {
-      item.children.status_text = deviceStatusMap.get(item.children.device_sn);
-    }
-    item.status_text = deviceStatusMap.get(item.device_sn);
-  })
-}
-
-// 开始定时器
-const startAutoUpdate = () => {
-  state.interval = window.setInterval(autoUpdateDeviceStatus, 10000);
-};
-
-// 清除定时器
-const clearAutoUpdate = () => {
-  if (state.interval !== null) {
-    clearInterval(state.interval);
-    state.interval = null;
-  }
-};
-
 onMounted(async () => {
   await fetchList();
-  startAutoUpdate(); // 页面加载完成后启动定时器
-});
-
-onUnmounted(() => {
-  clearAutoUpdate(); // 页面卸载前清除定时器
 });
 
 const columns = [
   {
-    title: '设备型号',
+    title: '计划|实际时间',
     dataIndex: 'device_name',
     width: 150,
     ellipsis: true,
@@ -215,14 +103,14 @@ const columns = [
     slots: { customRender: 'device_name' }
   },
   {
-    title: '设备SN',
+    title: '执行状态',
     dataIndex: 'device_sn',
     width: 250,
     ellipsis: true,
     slots: { customRender: 'device_sn' }
   },
   {
-    title: '设备名称',
+    title: '类型',
     dataIndex: 'nickname',
     width: 150,
     ellipsis: true,
@@ -230,35 +118,35 @@ const columns = [
     slots: { customRender: 'nickname' }
   },
   {
-    title: '固件版本',
+    title: '计划名称',
     dataIndex: 'firmware_version',
     width: 150,
     ellipsis: true,
     slots: { customRender: 'firmware_version' },
   },
   {
-    title: '固件升级',
+    title: '航线名称',
     dataIndex: 'firmware_status',
     width: 150,
     ellipsis: true,
     slots: { customRender: 'firmware_status' },
   },
   {
-    title: '当前状态',
+    title: '设备名称',
     dataIndex: 'status',
     width: 100,
     ellipsis: true,
     slots: { customRender: 'status' }
   },
   {
-    title: '加入项目时间',
+    title: '创建人',
     dataIndex: 'bound_time',
     width: 200,
     sorter: (a: any, b: any) => a.bound_time.localeCompare(b.bound_time),
     slots: { customRender: 'bound_time' },
   },
   {
-    title: '最后在线时间',
+    title: '媒体上传',
     dataIndex: 'login_time',
     width: 200,
     sorter: (a: any, b: any) => a.login_time.localeCompare(b.login_time),
@@ -293,11 +181,6 @@ const rowSelection = {
   },
 }
 
-// 点击批量删除
-const onClickBatchDelete = async () => {
-  console.log(state.selectedRowKeys, '点击批量删除');
-}
-
 // 点击搜索
 const onClickSearch = async (query: any) => {
   state.query = query;
@@ -310,32 +193,6 @@ const onClickReset = async (query: any) => {
   await fetchList();
 }
 
-// 点击异常反馈
-const onClickFeedback = (record: any) => {
-  state.feedbackDrawerVisible = true;
-  state.currentDevice = record;
-}
-
-// 点击告警信息
-const onClickDeviceHms = (record: any) => {
-  state.deviceHmsDrawerVisible = true;
-  state.currentDevice = record;
-}
-
-// 点击编辑
-const onClickEdit = (record: any) => {
-  state.editableData[record.device_sn] = record;
-}
-
-// 点击保存
-const onClickSave = async (record: any) => {
-  delete state.editableData[record.device_sn];
-  await updateDevice({ nickname: record.nickname }, state.workspaceId, record.device_sn)
-  if (record.children) {
-    await updateDevice({ nickname: record.children.nickname }, state.workspaceId, record.children.device_sn)
-  }
-}
-
 // 点击删除
 const onClickDelete = (record: any) => {
   Modal.confirm({

+ 42 - 22
Web/src/pages/page-web/projects/task/waylineList/components/Search.vue

@@ -1,12 +1,30 @@
 <template>
-  <a-row style="margin-bottom: 20px;" justify="end">
+  <a-row style="margin-bottom: 20px;" justify="space-between">
+    <a-col>
+      <a-button type="primary">
+        上传航线
+      </a-button>
+    </a-col>
     <a-col>
       <a-form ref="formRef" layout="inline" :model="formModel" :colon="false">
-        <a-form-item name="date">
-          <a-range-picker style="width: 250px;" v-model:value="formModel.date" />
+        <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="status">
-          <a-select style="width: 200px;" placeholder="请选择上传状态" v-model:value="formModel.status">
+        <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>
@@ -21,11 +39,24 @@
             </a-select-option>
           </a-select>
         </a-form-item>
-        <a-form-item name="username">
-          <a-input style="width: 200px;" placeholder="反馈人" v-model:value="formModel.username" />
+        <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="设备SN、设备异常描述" v-model:value="formModel.search_info" />
+          <a-input style="width: 200px;" placeholder="航线名称" v-model:value="formModel.search_info" />
         </a-form-item>
         <a-form-item>
           <a-button style="margin-right: 10px;" @click="handleClickSearch">
@@ -47,7 +78,6 @@
 <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>,
@@ -61,31 +91,21 @@ const props = withDefaults(defineProps<Props>(), {
 const formRef = ref();
 
 const formModel = reactive({
-  date: [],
-  status: undefined,
-  username: undefined,
+  device_type: undefined,
   search_info: undefined,
 })
 
 // 点击查询
 const handleClickSearch = async () => {
   const values = formRef.value?.getFieldsValue();
-  const data = { ...values };
-  delete data.date;
-  if (values.date.length === 2) {
-    data.begin_time = moment(values.date[0]).valueOf();
-    data.end_time = moment(values.date[1]).valueOf();
-  }
-  await props.onClickSearch(data);
+  await props.onClickSearch(values);
 }
 
 // 点击重置
 const handleClickReset = async () => {
   formRef.value?.resetFields();
   const values = formRef.value?.getFieldsValue();
-  const data = { ...values };
-  delete data.date;
-  await props.onClickReset(data);
+  await props.onClickReset(values);
 }
 </script>
 

+ 107 - 87
Web/src/pages/page-web/projects/task/waylineList/index.vue

@@ -1,64 +1,58 @@
 <template>
-  <div class="feedbackRecord">
+  <div class="deviceList">
     <Search :onClickSearch="onClickSearch" :onClickReset="onClickReset" />
-    <div class="feedbackRecord-table">
-      <a-table :scroll="{ x: '100%', y: 500 }" rowKey="logs_id" :loading="state.listLoading" :columns="columns"
-        @change="refreshData" :rowClassName="rowClassName" :dataSource="state.list" :pagination="paginationConfig">
-        <!-- 上传状态 -->
-        <template #status="{ record }">
-          <div v-if="record.status === 1">
-            上传中
-          </div>
-          <div v-else-if="record.status === 2">
-            成功
-          </div>
-          <div v-else-if="record.status === 3">
-            取消
-          </div>
-          <div v-else-if="record.status === 4">
-            失败
-          </div>
+    <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">
+        <!-- 航线名称 -->
+        <template #name="{ record }">
+          <a-tooltip :title="record.name">
+            {{ record.name }}
+          </a-tooltip>
         </template>
         <!-- 操作 -->
-        <template #operation="{ record }">
-          <div class="editable-row-operations">
-            <div class="flex-align-center flex-row" style="color: #2d8cf0">
-              <a-tooltip title="详情">
-                <FileSearchOutlined style="margin-right: 10px;" @click="onClickDetail(record.logs_id)" />
-              </a-tooltip>
-            </div>
+        <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="查看轨迹">
+              <GatewayOutlined style="margin-right: 10px;" />
+            </a-tooltip>
+            <a-tooltip title="删除">
+              <DeleteOutlined />
+            </a-tooltip>
           </div>
         </template>
       </a-table>
     </div>
-    <!-- 异常反馈-详情弹出层 -->
-    <FeedbackDetailModal :currentId="state.currentId" :visible="state.drawerVisible"
-      :onClickClose="() => state.drawerVisible = false" v-if="state.drawerVisible" />
   </div>
 </template>
 
 <script lang="ts" setup>
 import { reactive, onMounted } from 'vue';
-import { FileSearchOutlined } from '@ant-design/icons-vue';
+import { Modal } from 'ant-design-vue';
+import { CopyOutlined, GatewayOutlined, DeleteOutlined } from '@ant-design/icons-vue';
 import Search from './components/Search.vue';
-import FeedbackDetailModal from '../deviceList/components/FeedbackDetailModal.vue';
-import { apis } from '/@/api/custom/index';
+import { getWaylineFiles, importKmzFile, deleteWaylineFile, downloadWaylineFile } from '/@/api/wayline'
+import { getWorkspaceId } from '/@/utils/index';
+import moment from 'moment';
+import { DEVICE_NAME } from '/@/types/device'
 
 interface State {
+  workspaceId: string,
   query: any,
   listLoading: boolean,
   list: any[],
-  currentId: string,
-  drawerVisible: boolean,
 };
 
 const state: State = reactive({
+  workspaceId: getWorkspaceId(),
   query: undefined,
   listLoading: false,
   list: [],
-  currentId: '',
-  drawerVisible: false,
-});
+})
 
 const paginationConfig = reactive({
   pageSizeOptions: ['20', '50', '100'],
@@ -72,17 +66,16 @@ const paginationConfig = reactive({
 const fetchList = async () => {
   state.listLoading = true;
   try {
-    const res = await apis.fetchFeedbackRecordList({
+    const res = await getWaylineFiles(state.workspaceId, {
       ...state.query,
+      order_by: 'update_time desc',
       page: paginationConfig.current,
-      page_size: paginationConfig.pageSize
+      page_size: paginationConfig.pageSize,
     });
-    if (res.code === 0) {
-      paginationConfig.total = res.data.pagination.total
-      paginationConfig.current = res.data.pagination.page
-      paginationConfig.pageSize = res.data.pagination.page_size
-    }
     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 {
@@ -92,69 +85,77 @@ const fetchList = async () => {
 
 onMounted(async () => {
   await fetchList();
-})
+});
 
 const columns = [
   {
-    title: '反馈时间',
-    dataIndex: 'create_time',
-    width: 200,
-    sorter: (a: any, b: any) => a.create_time.localeCompare(b.create_time),
-  },
-  {
-    title: '反馈人',
-    dataIndex: 'user_name',
-    width: 150,
-  },
-  {
-    title: '设备型号',
-    dataIndex: 'device_name',
+    title: '航线名称',
+    dataIndex: 'name',
     width: 150,
-    sorter: (a: any, b: any) => a.device_name.localeCompare(b.device_name),
-    slots: { customRender: 'device_name' }
+    ellipsis: true,
+    slots: { customRender: 'name' }
   },
   {
-    title: '设备SN',
-    dataIndex: 'device_sn',
-    width: 250,
-    slots: { customRender: 'device_sn' }
+    title: '上传时间',
+    dataIndex: 'create_time',
+    width: 200,
+    sorter: (a: any, b: any) => a.create_time.localeCompare(b.create_time),
+    customRender: ({ text }: any) => {
+      return moment(text).format('YYYY-MM-DD HH:mm:ss')
+    }
   },
   {
-    title: '设备名称',
-    dataIndex: 'nick_name',
+    title: '航线类型',
+    dataIndex: 'template_types',
     width: 150,
-    slots: { customRender: 'nick_name' },
-    sorter: (a: any, b: any) => a.nick_name.localeCompare(b.nick_name),
+    customRender: ({ text }: any) => {
+      let content = ''
+      switch (text[0]) {
+        case 0:
+          content = '航点航线';
+          break;
+        case 1:
+          content = '二维正射';
+          break;
+        case 2:
+          content = '倾斜摄影';
+          break;
+        case 3:
+          content = '带状航线';
+          break;
+        default:
+          break;
+      }
+      return content;
+    }
   },
   {
-    title: '固件版本',
-    dataIndex: 'firmware_version',
+    title: '飞行器型号',
+    dataIndex: 'drone_model_key',
     width: 150,
-    ellipsis: true,
-    slots: { customRender: 'firmware_version' }
+    customRender: ({ text }: any) => {
+      return DEVICE_NAME[text]
+    }
   },
   {
-    title: '设备异常描述',
-    dataIndex: 'logs_info',
+    title: '负载云台',
+    dataIndex: 'payload_model_keys',
     width: 150,
-    ellipsis: true,
     customRender: ({ text }: any) => {
-      return text || '--';
+      return DEVICE_NAME[text[0]]
     }
   },
   {
-    title: '上传状态',
-    dataIndex: 'status',
-    slots: { customRender: 'status' },
-    sorter: (a: any, b: any) => a.status.localeCompare(b.status),
+    title: '上传操作人',
+    dataIndex: 'user_name',
     width: 150,
   },
   {
     title: '操作',
-    dataIndex: 'operation',
+    dataIndex: 'actions',
     fixed: 'right',
-    width: 80,
-    slots: { customRender: 'operation' }
+    width: 100,
+    slots: { customRender: 'action' },
   },
 ]
 
@@ -172,6 +173,12 @@ const refreshData = async (page: any) => {
   await fetchList();
 }
 
+const rowSelection = {
+  onChange: (selectedRowKeys: string[]) => {
+    state.selectedRowKeys = selectedRowKeys;
+  },
+}
+
 // 点击搜索
 const onClickSearch = async (query: any) => {
   state.query = query;
@@ -184,15 +191,24 @@ const onClickReset = async (query: any) => {
   await fetchList();
 }
 
-// 点击详情
-const onClickDetail = (id: string) => {
-  state.currentId = id;
-  state.drawerVisible = true;
+// 点击删除
+const onClickDelete = (record: any) => {
+  Modal.confirm({
+    title: '提示',
+    content: `确定删除${record.device_name}吗?`,
+    onOk: async () => {
+      const res = await unbindDevice(record.device_sn);
+      if (res.code !== 0) {
+        return
+      }
+      await fetchList();
+    },
+  });
 }
 </script>
 
 <style lang="scss">
-.feedbackRecord {
+.deviceList {
   padding: 20px;
 }
 
@@ -204,4 +220,8 @@ const onClickDetail = (id: string) => {
 .ant-table-tbody tr td {
   border: 0;
 }
+
+.table-striped {
+  background-color: #f7f9fa;
+}
 </style>

+ 272 - 0
Web/src/pages/page-web/projects/task/waylineList/index22.vue

@@ -0,0 +1,272 @@
+<template>
+  <div class="project-wayline-wrapper height-100">
+    <a-spin :spinning="loading" :delay="300" tip="downloading" size="large">
+      <div style="height: 50px; line-height: 50px; border-bottom: 1px solid #4f4f4f; font-weight: 450;">
+        <a-row>
+          <a-col :span="1"></a-col>
+          <a-col :span="14">Flight Route Library</a-col>
+          <a-col :span="8" v-if="importVisible" class="flex-row flex-justify-end flex-align-center">
+            <a-upload name="file" :multiple="false" :before-upload="beforeUpload" :show-upload-list="false"
+              :customRequest="uploadFile">
+              <a-button type="primary">
+                上传
+              </a-button>
+            </a-upload>
+          </a-col>
+          <a-col :span="1"></a-col>
+        </a-row>
+      </div>
+      <div :style="{ height: height + 'px' }" class="scrollbar">
+        <div id="data" class="height-100 uranus-scrollbar" v-if="waylinesData.data.length !== 0" @scroll="onScroll">
+          <div v-for="wayline in waylinesData.data" :key="wayline.id">
+            <div class="wayline-panel" style="padding-top: 5px;" @click="selectRoute(wayline)">
+              <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 class="fz20">
+                  <a-dropdown>
+                    <a style="color: white;">
+                      <EllipsisOutlined />
+                    </a>
+                    <template #overlay>
+                      <a-menu theme="dark" class="more" style="background: #3c3c3c;">
+                        <a-menu-item @click="downloadWayline(wayline.id, wayline.name)">
+                          <span>Download</span>
+                        </a-menu-item>
+                        <a-menu-item @click="showWaylineTip(wayline.id)">
+                          <span>Delete</span>
+                        </a-menu-item>
+                      </a-menu>
+                    </template>
+                  </a-dropdown>
+                </div>
+              </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>
+          </div>
+        </div>
+        <div v-else>
+          <a-empty :image-style="{ height: '60px', marginTop: '60px' }" />
+        </div>
+        <a-modal v-model:visible="deleteTip" width="450px" :closable="false" :maskClosable="false" centered
+          :okButtonProps="{ danger: true }" @ok="deleteWayline">
+          <p class="pt10 pl20" style="height: 50px;">Wayline file is unrecoverable once deleted. Continue?</p>
+          <template #title>
+            <div class="flex-row flex-justify-center">
+              <span>Delete</span>
+            </div>
+          </template>
+        </a-modal>
+      </div>
+    </a-spin>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { message } from 'ant-design-vue'
+import { onMounted, reactive, ref } from 'vue'
+import { deleteWaylineFile, downloadWaylineFile, getWaylineFiles, importKmzFile } from '/@/api/wayline'
+import { ERouterName } from '/@/types'
+import { EllipsisOutlined, RocketOutlined, CameraFilled, UserOutlined, SelectOutlined } from '@ant-design/icons-vue'
+import { DEVICE_NAME } from '/@/types/device'
+import { useMyStore } from '/@/store'
+import { WaylineFile } from '/@/types/wayline'
+import { downloadFile } from '/@/utils/common'
+import { IPage } from '/@/api/http/type'
+import { getRoot } from '/@/root'
+import { getWorkspaceId } from '/@/utils/index'
+
+const loading = ref(false)
+const store = useMyStore()
+const pagination: IPage = {
+  page: 1,
+  total: -1,
+  page_size: 10
+}
+
+const waylinesData = reactive({
+  data: [] as WaylineFile[]
+})
+
+const root = getRoot()
+const deleteTip = ref(false)
+const deleteWaylineId = ref<string>('')
+const canRefresh = ref(true)
+const importVisible = ref<boolean>(true)
+const height = ref()
+
+onMounted(() => {
+  const parent = document.getElementsByClassName('scrollbar').item(0)?.parentNode as HTMLDivElement
+  height.value = document.body.clientHeight - parent.firstElementChild!.clientHeight
+  getWaylines()
+
+  const key = setInterval(() => {
+    const data = document.getElementById('data')?.lastElementChild as HTMLDivElement
+    if (pagination.total === 0 || Math.ceil(pagination.total / pagination.page_size) <= pagination.page || height.value <= data?.clientHeight + data?.offsetTop) {
+      clearInterval(key)
+      return
+    }
+    pagination.page++
+    getWaylines()
+  }, 1000)
+})
+
+function getWaylines() {
+  if (!canRefresh.value) {
+    return
+  }
+  canRefresh.value = false
+  getWaylineFiles(getWorkspaceId(), {
+    page: pagination.page,
+    page_size: pagination.page_size,
+    order_by: 'update_time desc'
+  }).then(res => {
+    if (res.code !== 0) {
+      return
+    }
+    waylinesData.data = [...waylinesData.data, ...res.data.list]
+    pagination.total = res.data.pagination.total
+    pagination.page = res.data.pagination.page
+  }).finally(() => {
+    canRefresh.value = true
+  })
+}
+
+function showWaylineTip(waylineId: string) {
+  deleteWaylineId.value = waylineId
+  deleteTip.value = true
+}
+
+function deleteWayline() {
+  deleteWaylineFile(getWorkspaceId(), deleteWaylineId.value).then(res => {
+    if (res.code === 0) {
+      message.success('Wayline file deleted')
+    }
+    deleteWaylineId.value = ''
+    deleteTip.value = false
+    pagination.total = 0
+    pagination.page = 1
+    waylinesData.data = []
+    getWaylines()
+  })
+}
+
+function downloadWayline(waylineId: string, fileName: string) {
+  loading.value = true
+  downloadWaylineFile(getWorkspaceId(), waylineId).then(res => {
+    if (!res) {
+      return
+    }
+    const data = new Blob([res], { type: 'application/zip' })
+    downloadFile(data, fileName + '.kmz')
+  }).finally(() => {
+    loading.value = false
+  })
+}
+
+function selectRoute(wayline: WaylineFile) {
+  store.commit('SET_SELECT_WAYLINE_INFO', wayline)
+}
+
+function onScroll(e: any) {
+  const element = e.srcElement
+  if (element.scrollTop + element.clientHeight >= element.scrollHeight - 5 && Math.ceil(pagination.total / pagination.page_size) > pagination.page && canRefresh.value) {
+    pagination.page++
+    getWaylines()
+  }
+}
+
+interface FileItem {
+  uid: string;
+  name?: string;
+  status?: string;
+  response?: string;
+  url?: string;
+}
+
+interface FileInfo {
+  file: FileItem;
+  fileList: FileItem[];
+}
+const fileList = ref<FileItem[]>([])
+
+function beforeUpload(file: FileItem) {
+  fileList.value = [file]
+  loading.value = true
+  return true
+}
+const uploadFile = async () => {
+  fileList.value.forEach(async (file: FileItem) => {
+    const fileData = new FormData()
+    fileData.append('file', file, file.name)
+    await importKmzFile(getWorkspaceId(), fileData).then((res) => {
+      if (res.code === 0) {
+        message.success(`${file.name} file uploaded successfully`)
+        canRefresh.value = true
+        pagination.total = 0
+        pagination.page = 1
+        waylinesData.data = []
+        getWaylines()
+      }
+    }).finally(() => {
+      loading.value = false
+      fileList.value = []
+    })
+  })
+}
+
+</script>
+
+<style lang="scss" scoped>
+.wayline-panel {
+  background: #3c3c3c;
+  margin-left: auto;
+  margin-right: auto;
+  margin-top: 10px;
+  height: 90px;
+  width: 95%;
+  font-size: 13px;
+  border-radius: 2px;
+  cursor: pointer;
+
+  .title {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    height: 30px;
+    font-weight: bold;
+    margin: 0px 10px 0 10px;
+  }
+}
+
+.uranus-scrollbar {
+  overflow: auto;
+  scrollbar-width: thin;
+  scrollbar-color: #c5c8cc transparent;
+}
+</style>

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

@@ -2,14 +2,15 @@
   <div class="trajectory">
     <Search :onClickSearch="onClickSearch" :onClickReset="onClickReset" />
     <div class="trajectory-table">
-      <a-table :scroll="{ x: '100%', y: 500 }"  rowKey="id" :loading="state.listLoading"
-        :columns="columns" @change="refreshData" :rowClassName="rowClassName" :dataSource="state.list"
-        :pagination="paginationConfig">
+      <a-table :scroll="{ x: '100%', y: 500 }" rowKey="id" :loading="state.listLoading" :columns="columns"
+        @change="refreshData" :rowClassName="rowClassName" :dataSource="state.list" :pagination="paginationConfig">
         <!-- 操作 -->
         <template #action="{ record }">
-          <a-tooltip title="查看轨迹">
-            <EnvironmentOutlined style="color: #2d8cf0;" @click="onClickLookTrajectory(record.id)" />
-          </a-tooltip>
+          <div class="flex-align-center flex-row" style="color: #2d8cf0">
+            <a-tooltip title="查看轨迹">
+              <GatewayOutlined @click="onClickLookTrajectory(record.id)" />
+            </a-tooltip>
+          </div>
         </template>
       </a-table>
     </div>
@@ -18,7 +19,7 @@
 
 <script lang="ts" setup>
 import { reactive, onMounted } from 'vue';
-import { EnvironmentOutlined } from '@ant-design/icons-vue';
+import { GatewayOutlined } from '@ant-design/icons-vue';
 import Search from './components/Search.vue';
 import { apis } from '/@/api/custom/index';
 import router from '/@/router';

+ 1 - 1
Web/src/router/index.ts

@@ -34,7 +34,7 @@ const routes: Array<RouteRecordRaw> = [
       {
         path: '/' + ERouterName.TASK,
         name: ERouterName.TASK,
-        component: () => import('/@/pages/page-web/projects/wayline.vue')
+        component: () => import('/@/pages/page-web/projects/task/index.vue')
       },
       {
         path: '/' + ERouterName.MEDIA,