Browse Source

Merge remote-tracking branch 'origin/master'

S0025136190 1 year ago
parent
commit
4b0d7e5788

BIN
Web/src/assets/icons/jkec_logo.png


BIN
Web/src/assets/public/favicon.ico


+ 217 - 0
Web/src/components/devices/deviceList/components/FeedbackDrawer.vue

@@ -0,0 +1,217 @@
+<template>
+    <a-drawer width="70%" title="设备异常反馈记录" :visible="visible" @close="onClose">
+        <div class="top">
+            <a-button type="primary">
+                新建异常反馈
+            </a-button>
+            <div>
+                <a-form ref="formRef" layout="inline" :model="state.query" :colon="false">
+                    <a-form-item name="rangeDate">
+                        <a-range-picker style="width: 250px;" v-model:value="state.query.rangeDate" />
+                    </a-form-item>
+                    <a-form-item name="device_name">
+                        <a-select style="width: 200px;" placeholder="请选择反馈人" v-model:value="state.query.device_name">
+                            <a-select-option value="1">
+                                pilot
+                            </a-select-option>
+                        </a-select>
+                    </a-form-item>
+                    <a-form-item name="search_info">
+                        <a-input style="width: 200px;" placeholder="设备SN、设备异常描述"
+                            v-model:value="state.query.search_info" />
+                    </a-form-item>
+                    <a-form-item>
+                        <a-button style="margin-right: 10px;" @click="handleClicSekarch">
+                            <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 }" :childrenColumnName="null" rowKey="device_sn"
+                :loading="state.listLoading" :columns="columns" :dataSource="state.list" :pagination="paginationConfig">
+                <template #status="{ record }">
+                    上传状态
+                </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.id)" />
+                            </a-tooltip>
+                            <a-tooltip title="删除">
+                                <DeleteOutlined @click="onClickDelete(record.id)" />
+                            </a-tooltip>
+                        </div>
+                    </div>
+                </template>
+            </a-table>
+        </div>
+    </a-drawer>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, onMounted } from 'vue';
+import { SearchOutlined, ReloadOutlined } from '@ant-design/icons-vue';
+import { apis } from '/@/api/custom';
+import { Device } from '/@/types/device';
+
+interface Props {
+    visible: boolean,
+    device: Device,
+    onClose: () => void,
+};
+
+const props = withDefaults(defineProps<Props>(), {
+
+});
+
+const formRef = ref();
+
+interface State {
+    query: any,
+    listLoading: boolean,
+    list: any[],
+    currentId: string,
+    drawerVisible: boolean,
+};
+
+const state: State = reactive({
+    query: {
+        rangeDate: [],
+        device_name: undefined,
+        search_info: '',
+    },
+    listLoading: false,
+    list: [],
+    currentId: '',
+    drawerVisible: false,
+});
+
+const paginationConfig = reactive({
+    pageSizeOptions: ['20', '50', '100'],
+    showQuickJumper: true,
+    showSizeChanger: true,
+    pageSize: 20,
+    current: 1,
+    total: 0
+})
+
+const fetchList = async () => {
+    state.listLoading = true;
+    try {
+        const res = await apis.fetchChangeRecordList({
+            ...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 handleClicSekarch = 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();
+    }
+    await props.onClickSearch(data);
+}
+
+// 点击重置
+const handleClickReset = async () => {
+    formRef.value?.resetFields();
+    const values = formRef.value?.getFieldsValue();
+    const data = {
+        ...values,
+    };
+    delete data.rangeDate;
+    await props.onClickReset(data);
+}
+</script>
+
+<style lang="scss" scoped>
+.top {
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 20px;
+}
+</style>

+ 17 - 13
Web/src/components/devices/deviceList/index.vue

@@ -51,12 +51,15 @@
           </div>
           <!-- 非编辑态操作 -->
           <div v-else class="flex-align-center flex-row" style="color: #2d8cf0">
-            <a-tooltip title="设备日志">
-              <CloudServerOutlined style="margin-right: 10px;" @click="onClickDeviceLog(record)" />
+            <a-tooltip title="异常反馈" v-if="record.domain === 3">
+              <CloudServerOutlined style="margin-right: 10px;" @click="onClickFeedback(record)" />
             </a-tooltip>
-            <a-tooltip title="告警信息">
+            <a-tooltip title="告警信息" v-if="record.domain === 3">
               <FileSearchOutlined style="margin-right: 10px;" @click="onClickDeviceHms(record)" />
             </a-tooltip>
+            <a-tooltip title="设备运维" v-if="record.domain === 3">
+              <SettingOutlined style="margin-right: 10px;" />
+            </a-tooltip>
             <a-tooltip title="编辑">
               <EditOutlined style="margin-right: 10px;" @click="onClickEdit(record)" />
             </a-tooltip>
@@ -68,8 +71,9 @@
       </a-table>
     </div>
   </div>
-  <!-- 设备日志 -->
-  <DeviceLogUploadRecordDrawer v-model:visible="state.deviceLogDrawerVisible" :device="state.currentDevice" />
+  <!-- 异常反馈 -->
+  <FeedbackDrawer :visible="state.feedbackDrawerVisible" :device="state.currentDevice"
+    :onClose="() => state.feedbackDrawerVisible = false" />
   <!-- 告警信息 -->
   <DeviceHmsDrawer v-model:visible="state.deviceHmsDrawerVisible" :device="state.currentDevice" />
 </template>
@@ -77,11 +81,11 @@
 <script lang="ts" setup>
 import { reactive, onMounted, onUnmounted } from 'vue';
 import { Modal } from 'ant-design-vue';
-import { EditOutlined, CheckOutlined, CloseOutlined, DeleteOutlined, FileSearchOutlined, CloudServerOutlined } from '@ant-design/icons-vue';
+import { EditOutlined, CheckOutlined, CloseOutlined, SettingOutlined, DeleteOutlined, FileSearchOutlined, CloudServerOutlined } 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 DeviceLogUploadRecordDrawer from '/@/components/devices/device-log/DeviceLogUploadRecordDrawer.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';
@@ -98,7 +102,7 @@ interface State {
   editableData: {
     [key: string]: any,
   },
-  deviceLogDrawerVisible: boolean,
+  feedbackDrawerVisible: boolean,
   deviceHmsDrawerVisible: boolean,
 };
 
@@ -111,7 +115,7 @@ const state: State = reactive({
   selectedRowKeys: [],
   currentDevice: {},
   editableData: {},
-  deviceLogDrawerVisible: false,
+  feedbackDrawerVisible: false,
   deviceHmsDrawerVisible: false,
 })
 
@@ -257,7 +261,7 @@ const columns = [
     title: '操作',
     dataIndex: 'actions',
     fixed: 'right',
-    width: 120,
+    width: 140,
     slots: { customRender: 'action' },
   },
 ]
@@ -299,9 +303,9 @@ const onClickReset = async (query: any) => {
   await fetchList();
 }
 
-// 点击设备日志
-const onClickDeviceLog = (record: any) => {
-  state.deviceLogDrawerVisible = true;
+// 点击异常反馈
+const onClickFeedback = (record: any) => {
+  state.feedbackDrawerVisible = true;
   state.currentDevice = record;
 }
 

+ 3 - 3
Web/src/pages/page-pilot/pilot-index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="login flex-column flex-justify-center flex-align-center m0 b0">
-    <a-image style="width: 17vw; height: 10vw; margin-bottom: 50px" :src="djiLogo" />
-    <p class="logo fz35 pb50">上云无人机管理平台</p>
+    <a-image style="width: 17vw; height: 17vw; margin-bottom: 20px" :src="jkecLogo" />
+    <p class="logo fz35 pb50">无人机管理平台</p>
     <a-form layout="inline" :model="formState" class="flex-row flex-justify-center flex-align-center">
       <a-form-item>
         <a-input v-model:value="formState.username" placeholder="账号">
@@ -36,7 +36,7 @@ import apiPilot from '/@/api/pilot-bridge'
 import { getRoot } from '/@/root'
 import { EComponentName, ELocalStorageKey, ERouterName, EUserType } from '/@/types'
 import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
-import djiLogo from '/@/assets/icons/dji_logo.png'
+import jkecLogo from '/@/assets/icons/jkec_logo.png'
 
 const root = getRoot()
 

+ 3 - 3
Web/src/pages/page-web/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="login flex-column flex-justify-center flex-align-center m0 b0">
-    <a-image :preview="false" style="width: 17vw; height: 10vw; margin-bottom: 50px" :src="djiLogo" />
-    <p class="fz35 pb50" style="color: #2d8cf0">上云无人机管理平台</p>
+    <a-image :preview="false" style="width: 17vw; height: 17vw; margin-bottom: 50px" :src="jkecLogo" />
+    <p class="fz35 pb30" style="color: #2d8cf0">无人机管理平台</p>
     <a-form layout="inline" :model="formState" class="flex-row flex-justify-center flex-align-center">
       <a-form-item>
         <a-input v-model:value="formState.username" placeholder="账号">
@@ -27,7 +27,7 @@
 </template>
 
 <script lang="ts" setup>
-import djiLogo from '/@/assets/icons/dji_logo.png'
+import jkecLogo from '/@/assets/icons/jkec_logo.png'
 import { LockOutlined, UserOutlined } from '@ant-design/icons-vue'
 import { message } from 'ant-design-vue'
 import { reactive, computed, UnwrapRef } from 'vue'

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

@@ -2,118 +2,112 @@
   <div class="fileInfo">
     <a-spin :spinning="state.downloadLoading" tip="下载中...">
       <div class="fileInfo-detail">
-        <a-row>
-          <a-col flex="auto">
-            <div class="fileInfo-detail-left">
-              <div class="fileInfo-detail-left-background" v-if="state.imgLoading">
-                <a-spin tip="加载中..." />
-              </div>
-              <Panoramic :src="state.info.url" v-if="state.info.media_type === 3" />
-              <video style="width: 100%;height: 100%;" controls :src="state.info.url"
-                v-else-if="state.info.media_type === 4"></video>
-              <a-image width="100%" height="100%" :src="state.info.url" v-else />
+        <div class="fileInfo-detail-left">
+          <div class="fileInfo-detail-left-background" v-if="state.imgLoading">
+            <a-spin tip="加载中..." />
+          </div>
+          <Panoramic :src="state.info.url" v-if="state.info.media_type === 3" />
+          <video style="width: 100%;height: 100%;" controls :src="state.info.url"
+            v-else-if="state.info.media_type === 4"></video>
+          <a-image style="object-fit:cover;" width="100%" height="100%" :src="state.info.url" v-else />
+        </div>
+        <div class="fileInfo-detail-info">
+          <CloseOutlined class="fileInfo-detail-info-close" @click="onClose" />
+          <div class="fileInfo-detail-info-title">
+            详细信息
+          </div>
+          <div class="fileInfo-detail-info-item">
+            <div class="fileInfo-detail-info-item-title">
+              文件名称
             </div>
-          </a-col>
-          <a-col flex="420px">
-            <div class="fileInfo-detail-info">
-              <CloseOutlined class="fileInfo-detail-info-close" @click="onClose" />
-              <div class="fileInfo-detail-info-title">
-                详细信息
-              </div>
-              <div class="fileInfo-detail-info-item">
-                <div class="fileInfo-detail-info-item-title">
-                  文件名称
-                </div>
-                <div class="fileInfo-detail-info-item-content">
-                  {{ state.info.file_name || '--' }}
-                </div>
-              </div>
-              <div class="fileInfo-detail-info-item">
-                <div class="fileInfo-detail-info-item-title">
-                  文件类型
-                </div>
-                <div class="fileInfo-detail-info-item-content">
-                  {{ state.info.picture_type || '--' }}
-                </div>
-              </div>
-              <div class="fileInfo-detail-info-item">
-                <div class="fileInfo-detail-info-item-title">
-                  任务名称
-                </div>
-                <div class="fileInfo-detail-info-item-content">
-                  {{ state.info.task_name || '--' }}
-                </div>
-              </div>
-              <div class="fileInfo-detail-info-item">
-                <div class="fileInfo-detail-info-item-title">
-                  航线名称
-                </div>
-                <div class="fileInfo-detail-info-item-content">
-                  {{ state.info.wayline_name || '--' }}
-                </div>
-              </div>
-              <div class="fileInfo-detail-info-item">
-                <div class="fileInfo-detail-info-item-title">
-                  分辨率
-                </div>
-                <div class="fileInfo-detail-info-item-content">
-                  <div class="fileInfo-detail-info-item-content-line"
-                    v-if="state.info.image_width && state.info.image_height">
-                    {{ state.info.image_width }}*{{ state.info.image_height }}
-                  </div>
-                  <div v-else>
-                    --
-                  </div>
-                </div>
+            <div class="fileInfo-detail-info-item-content">
+              {{ state.info.file_name || '--' }}
+            </div>
+          </div>
+          <div class="fileInfo-detail-info-item">
+            <div class="fileInfo-detail-info-item-title">
+              文件类型
+            </div>
+            <div class="fileInfo-detail-info-item-content">
+              {{ state.info.picture_type || '--' }}
+            </div>
+          </div>
+          <div class="fileInfo-detail-info-item">
+            <div class="fileInfo-detail-info-item-title">
+              任务名称
+            </div>
+            <div class="fileInfo-detail-info-item-content">
+              {{ state.info.task_name || '--' }}
+            </div>
+          </div>
+          <div class="fileInfo-detail-info-item">
+            <div class="fileInfo-detail-info-item-title">
+              航线名称
+            </div>
+            <div class="fileInfo-detail-info-item-content">
+              {{ state.info.wayline_name || '--' }}
+            </div>
+          </div>
+          <div class="fileInfo-detail-info-item">
+            <div class="fileInfo-detail-info-item-title">
+              分辨率
+            </div>
+            <div class="fileInfo-detail-info-item-content">
+              <div class="fileInfo-detail-info-item-content-line"
+                v-if="state.info.image_width && state.info.image_height">
+                {{ state.info.image_width }}*{{ state.info.image_height }}
               </div>
-              <div class="fileInfo-detail-info-item">
-                <div class="fileInfo-detail-info-item-title">
-                  文件大小
-                </div>
-                <div class="fileInfo-detail-info-item-content">
-                  {{ state.info.size > 0 ? (state.info.size / 1024 / 1024).toFixed(1) + 'M' : '--' }}
-                </div>
+              <div v-else>
+                --
               </div>
-              <div class="fileInfo-detail-info-item">
-                <div class="fileInfo-detail-info-item-title">
-                  拍摄负载
-                </div>
-                <div class="fileInfo-detail-info-item-content">
-                  {{ state.info.payload || '--' }}
-                </div>
+            </div>
+          </div>
+          <div class="fileInfo-detail-info-item">
+            <div class="fileInfo-detail-info-item-title">
+              文件大小
+            </div>
+            <div class="fileInfo-detail-info-item-content">
+              {{ state.info.size > 0 ? (state.info.size / 1024 / 1024).toFixed(1) + 'M' : '--' }}
+            </div>
+          </div>
+          <div class="fileInfo-detail-info-item">
+            <div class="fileInfo-detail-info-item-title">
+              拍摄负载
+            </div>
+            <div class="fileInfo-detail-info-item-content">
+              {{ state.info.payload || '--' }}
+            </div>
+          </div>
+          <div class="fileInfo-detail-info-item">
+            <div class="fileInfo-detail-info-item-title">
+              拍摄时间
+            </div>
+            <div class="fileInfo-detail-info-item-content">
+              {{ state.info.picture_time || '--' }}
+            </div>
+          </div>
+          <div class="fileInfo-detail-info-map">
+            <div class="fileInfo-detail-info-map-title">
+              <div class="fileInfo-detail-info-map-title-text">
+                照片位置
               </div>
-              <div class="fileInfo-detail-info-item">
-                <div class="fileInfo-detail-info-item-title">
-                  拍摄时间
-                </div>
-                <div class="fileInfo-detail-info-item-content">
-                  {{ state.info.picture_time || '--' }}
-                </div>
+              <div class="fileInfo-detail-info-map-title-icon">
+                <EnvironmentOutlined @click="onClickMapLocationReset" />
               </div>
-              <div class="fileInfo-detail-info-map">
-                <div class="fileInfo-detail-info-map-title">
-                  <div class="fileInfo-detail-info-map-title-text">
-                    照片位置
-                  </div>
-                  <div class="fileInfo-detail-info-map-title-icon">
-                    <EnvironmentOutlined @click="onClickMapLocationReset" />
-                  </div>
-                </div>
-                <div class="fileInfo-detail-info-map-content">
-                  <div id="photoPositionMap" :style="{ width: '100%', height: '100%' }"></div>
-                  <div class="fileInfo-detail-info-map-content-title">
-                    <span>
-                      {{ state.info.latitude }}° N
-                    </span>
-                    <span>
-                      {{ state.info.longitude }}° E
-                    </span>
-                  </div>
-                </div>
+            </div>
+            <div class="fileInfo-detail-info-map-content">
+              <div id="photoPositionMap" :style="{ width: '100%', height: '100%' }"></div>
+              <div class="fileInfo-detail-info-map-content-title">
+                <span>
+                  {{ state.info.latitude }}° N
+                </span>
+                <span>
+                  {{ state.info.longitude }}° E
+                </span>
               </div>
             </div>
-          </a-col>
-        </a-row>
+          </div>
+        </div>
       </div>
       <div class="fileInfo-area">
         <a-tooltip placement="bottom" title="下载">
@@ -354,8 +348,10 @@ const onClickSelected = async (id: string) => {
   }
 
   &-detail {
+    display: flex;
+
     &-left {
-      width: calc(100vw - 420px);
+      flex: 1;
       height: calc(100vh - 130px);
       overflow: hidden;
 
@@ -370,7 +366,7 @@ const onClickSelected = async (id: string) => {
     }
 
     &-info {
-      width: 100%;
+      width: 420px;
       height: calc(100vh - 130px);
       padding: 24px;
       overflow: auto;
@@ -397,10 +393,11 @@ const onClickSelected = async (id: string) => {
         &-title {
           width: 85px;
           color: hsla(0, 0%, 100%, .45);
+          margin-right: 16px;
         }
 
         &-content {
-          margin-left: 16px;
+          flex: 1;
 
           &-line {
             display: flex;

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

@@ -2,9 +2,10 @@
   <a-row style="margin-bottom: 20px;" justify="space-between">
     <a-col>
       <div style="display: flex;">
-        <a-upload :action="uploadUrl" method="POST" :headers="getHeaders()" accept=".png,.jpg,.jpeg" :multiple="false"
-          :showUploadList="false" @change="handleChangeUploadFile">
-          <a-button style="margin-right: 10px;" type="primary" @click="onClickDownload">
+        <a-upload :action="uploadUrl" method="POST" :headers="getHeaders()"
+          accept=".png,.jpg,.jpeg,.mp4,.zip,.tif,.tiff,.las" :multiple="false" :showUploadList="false"
+          @change="handleChangeUploadFile">
+          <a-button style="margin-right: 10px;" type="primary">
             上传合成文件
           </a-button>
         </a-upload>

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

@@ -0,0 +1,331 @@
+<template>
+  <div class="mediaInfo">
+    <div class="mediaInfo-detail">
+      <div class="mediaInfo-detail-left">
+        <Panoramic :src="state.info.url" v-if="state.info.media_type === 3" />
+        <video style="width: 100%;height: 100%;" controls :src="state.info.url"
+          v-else-if="state.info.media_type === 4"></video>
+        <a-image style="object-fit: cover;" width="100%" height="100%" :src="state.info.url" v-else />
+      </div>
+      <div class="mediaInfo-detail-info">
+        <div class="mediaInfo-detail-info-title">
+          详细信息
+        </div>
+        <div class="mediaInfo-detail-info-item">
+          <div class="mediaInfo-detail-info-item-title">
+            文件名称
+          </div>
+          <div class="mediaInfo-detail-info-item-content">
+            {{ state.info.file_name || '--' }}
+          </div>
+        </div>
+        <div class="mediaInfo-detail-info-item">
+          <div class="mediaInfo-detail-info-item-title">
+            文件类型
+          </div>
+          <div class="mediaInfo-detail-info-item-content">
+            {{ state.info.picture_type || '--' }}
+          </div>
+        </div>
+        <div class="mediaInfo-detail-info-item">
+          <div class="mediaInfo-detail-info-item-title">
+            任务名称
+          </div>
+          <div class="mediaInfo-detail-info-item-content">
+            {{ state.info.task_name || '--' }}
+          </div>
+        </div>
+        <div class="mediaInfo-detail-info-item">
+          <div class="mediaInfo-detail-info-item-title">
+            航线名称
+          </div>
+          <div class="mediaInfo-detail-info-item-content">
+            {{ state.info.wayline_name || '--' }}
+          </div>
+        </div>
+        <div class="mediaInfo-detail-info-item">
+          <div class="mediaInfo-detail-info-item-title">
+            分辨率
+          </div>
+          <div class="mediaInfo-detail-info-item-content">
+            <div class="mediaInfo-detail-info-item-content-line"
+              v-if="state.info.image_width && state.info.image_height">
+              {{ state.info.image_width }}*{{ state.info.image_height }}
+            </div>
+            <div v-else>
+              --
+            </div>
+          </div>
+        </div>
+        <div class="mediaInfo-detail-info-item">
+          <div class="mediaInfo-detail-info-item-title">
+            文件大小
+          </div>
+          <div class="mediaInfo-detail-info-item-content">
+            {{ state.info.size > 0 ? (state.info.size / 1024 / 1024).toFixed(1) + 'M' : '--' }}
+          </div>
+        </div>
+        <div class="mediaInfo-detail-info-item">
+          <div class="mediaInfo-detail-info-item-title">
+            拍摄负载
+          </div>
+          <div class="mediaInfo-detail-info-item-content">
+            {{ state.info.payload || '--' }}
+          </div>
+        </div>
+        <div class="mediaInfo-detail-info-item">
+          <div class="mediaInfo-detail-info-item-title">
+            拍摄时间
+          </div>
+          <div class="mediaInfo-detail-info-item-content">
+            {{ state.info.picture_time || '--' }}
+          </div>
+        </div>
+        <div class="mediaInfo-detail-info-map">
+          <div class="mediaInfo-detail-info-map-title">
+            <div class="mediaInfo-detail-info-map-title-text">
+              照片位置
+            </div>
+            <div class="mediaInfo-detail-info-map-title-icon">
+              <EnvironmentOutlined @click="onClickMapLocationReset" />
+            </div>
+          </div>
+          <div class="mediaInfo-detail-info-map-content">
+            <div id="photoPositionMap" :style="{ width: '100%', height: '100%' }"></div>
+            <div class="mediaInfo-detail-info-map-content-title">
+              <span>
+                {{ state.info.latitude }}° N
+              </span>
+              <span>
+                {{ state.info.longitude }}° E
+              </span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { reactive, onMounted } from 'vue';
+import { EnvironmentOutlined } from '@ant-design/icons-vue';
+import Panoramic from '/@/components/panoramic/index.vue';
+import { useGMapManage } from '/@/hooks/use-g-map';
+import { apis } from '/@/api/custom';
+import { wgs84togcj02 } from '/@/vendors/coordtransform'
+import router from '/@/router';
+
+const state = reactive({
+  downloadLoading: false,
+  imgLoading: false,// 图片加载
+  info: {
+    url: '',
+    media_type: 0,
+    file_id: '',
+    element_id: '',
+    file_name: '',
+    picture_type: '',
+    task_name: '',
+    wayline_name: '',
+    image_width: '',// 照片分辨率-宽度
+    image_height: '',// 照片分辨率-高度
+    size: 0,
+    payload: '',
+    picture_time: '',
+    longitude: '',// 经度
+    latitude: '',// 纬度
+    coordinates: [],
+    thumbnail_url: '',
+  },
+  map: null, // 高德地图实例
+})
+
+// 高德地图Hook
+const AmapHook = useGMapManage();
+
+const init = async () => {
+  const { query } = router.currentRoute.value;
+  const fileId = query.fileId;
+  if (!fileId) {
+    return;
+  }
+  try {
+    const res = await apis.fetchFileDetail(fileId as string);
+    state.info = {
+      ...res.data,
+      longitude: res.data.longitude ? res.data.longitude.toFixed(6) : '',
+      latitude: res.data.latitude ? res.data.latitude.toFixed(6) : '',
+      coordinates: (res.data.longitude && res.data.latitude) ? wgs84togcj02(res.data.longitude, res.data.latitude) : [],
+    };
+    if (res.data.media_type !== 4) {
+      state.imgLoading = true;
+      const img = new Image();
+      img.src = state.info.url;
+      img.onload = () => {
+        state.imgLoading = false;
+      };
+    }
+    if (state.info.coordinates.length) {
+      const AMap = await AmapHook.asyncInitMap();
+      const map = new AMap.Map('photoPositionMap', {
+        layers: [new AMap.TileLayer.Satellite()],// 卫星图-图层
+        viewMode: '3D',// 3D地图
+        rotateEnable: true,// 开启地图旋转交互
+        pitchEnable: true,// 开启地图倾斜交互
+        zoom: 17, // 初始化地图层级
+        center: state.info.coordinates,// 中心点
+      })
+      state.map = map;
+      // 创建一个标记并将其添加到地图上
+      new AMap.Marker({
+        map: map,
+        cursor: 'pointer',
+        position: state.info.coordinates,
+      });
+    }
+  } catch (e) {
+    console.error(e);
+  }
+}
+
+onMounted(async () => {
+  await init()
+})
+
+// 点击地图位置重置
+const onClickMapLocationReset = () => {
+  const markerPosition = state.info.coordinates;
+  const map: any = state.map;
+  map.setCenter(markerPosition);
+  map.setZoom(17);
+}
+</script>
+
+<style lang="scss">
+.mediaInfo {
+  width: 100%;
+  background: #1C1C1C;
+  color: #FFFFFF;
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 9;
+  overflow: auto;
+
+  ::-webkit-scrollbar {
+    width: 8px;
+    height: 8px;
+    background: transparent;
+  }
+
+  ::-webkit-scrollbar-thumb {
+    border-radius: 4px;
+    border: none;
+    background: rgb(89, 89, 89);
+  }
+
+  &-detail {
+    height: 100vh;
+    display: flex;
+
+    &-left {
+      flex: 1;
+      height: 100%;
+      padding: 24px 0 24px 24px;
+      overflow: hidden;
+
+      &-background {
+        width: 100%;
+        height: 100%;
+        padding: 100px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+    }
+
+    &-info {
+      width: 420px;
+      height: 100%;
+      padding: 24px;
+      overflow: auto;
+
+      &-title {
+        font-size: 16px;
+        margin-bottom: 16px;
+      }
+
+      &-item {
+        width: 100%;
+        height: 38px;
+        display: flex;
+        align-items: center;
+
+        &-title {
+          width: 85px;
+          color: hsla(0, 0%, 100%, .45);
+          margin-right: 16px;
+        }
+
+        &-content {
+          flex: 1;
+
+          &-line {
+            display: flex;
+            align-items: center;
+          }
+        }
+      }
+
+      &-map {
+        &-title {
+          height: 38px;
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+
+          &-text {
+            color: hsla(0, 0%, 100%, .45);
+          }
+
+          &-icon {
+            color: $primary;
+          }
+        }
+
+        &-content {
+          width: 100%;
+          height: 250px;
+          background: #dddddd;
+          border-radius: 4px;
+          margin-top: 10px;
+          position: relative;
+          overflow: hidden;
+
+          &-title {
+            width: 100%;
+            height: 28px;
+            padding-left: 10px;
+            background: rgba(0, 0, 0, .5);
+            line-height: 28px;
+            font-size: 14px;
+            color: #fff;
+            position: absolute;
+            bottom: 0;
+            left: 0;
+          }
+        }
+      }
+    }
+  }
+}
+
+.amap-logo {
+  display: none !important;
+}
+
+.amap-copyright {
+  display: none !important;
+}
+</style>

+ 4 - 0
Web/src/router/index.ts

@@ -58,6 +58,10 @@ const routes: Array<RouteRecordRaw> = [
       },
     ]
   },
+  {
+    path: '/mediaInfo',
+    component: () => import('/@/pages/page-web/projects/mediaInfo/index.vue')
+  },
   {
     path: '/' + ERouterName.PILOT,
     name: ERouterName.PILOT,