Browse Source

Merge remote-tracking branch 'origin/master'

S0025136190 1 year ago
parent
commit
78824ec07d

+ 2 - 2
Web/env/.env.test

@@ -2,7 +2,7 @@
 VITE_ENV = 'test'
 
 # Api 地址
-VITE_API_URL = 'http://192.168.3.42:6789'
+VITE_API_URL = 'http://192.168.3.89:6789'
 
 # WebSocket 地址
-VITE_WEBSOCKET_URL = 'ws://192.168.3.42:6789/api/v1/ws'
+VITE_WEBSOCKET_URL = 'ws://192.168.3.89:6789/api/v1/ws'

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

@@ -4,6 +4,15 @@ import { ELocalStorageKey } from "/@/types";
 const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '';
 
 // Api参数类型
+export type SignLoginApiParams = {
+    username: string,
+    client_id: string,
+    timestamp: number,
+    workspace_id: string,
+    workspace_name: string,
+    signature: string,
+};
+
 export type FetchFeedbackRecordListApiParams = {
     page: number,
     page_size: number,
@@ -60,6 +69,7 @@ export type FetchTrajectoryListApiParams = Partial<{
 }>;
 
 // Api函数类型
+export type SignLoginApi = (data: SignLoginApiParams) => Promise<any>;
 export type FetchPayloadListApi = () => Promise<any>;
 export type FetchFeedbackRecordListApi = (data: FetchFeedbackRecordListApiParams) => Promise<any>;
 export type FetchChangeRecordListApi = (params: FetchFeedbackRecordListApiParams) => Promise<any>;
@@ -76,6 +86,12 @@ export type BatchDeletePictureApi = (params: BatchDeletePictureApiParams) => Pro
 export type FetchTrajectoryListApi = (params: FetchTrajectoryListApiParams) => Promise<any>;
 export type FetchTrajectoryMapApi = (taskId: string) => Promise<any>;
 
+// 密钥登录
+const signLoginApi: SignLoginApi = async (data) => {
+    const res = await request.post('/manage/api/v1/signLogin', data);
+    return res.data;
+}
+
 // 获取负载列表
 const fetchPayloadListApi: FetchPayloadListApi = async () => {
     const res = await request.get(`/manage/api/v1/devices/${workspaceId}/payloads`);
@@ -178,6 +194,7 @@ const fetchTrajectoryMapApi: FetchTrajectoryMapApi = async (taskId) => {
 };
 
 export const apis = {
+    signLogin: signLoginApi,
     fetchPayloadList: fetchPayloadListApi,
     fetchFeedbackRecordList: fetchFeedbackRecordListApi,
     fetchChangeRecordList: fetchChangeRecordListApi,

+ 1 - 14
Web/src/api/http/request.ts

@@ -10,24 +10,11 @@ const REQUEST_ID = 'X-Request-Id'
 export const getHeaders = () => {
   const headers: {
     'x-auth-token'?: string,
-    'sign': string,
-    'key': string,
-    'userCode': string,
-  } = {
-    'sign': '',
-    'key': '',
-    'userCode': '',
-  };
+  } = {};
   const token = localStorage.getItem(ELocalStorageKey.Token)
   if (token) {
     headers['x-auth-token'] = token;
   }
-  const sign = localStorage.getItem('sign') || '';
-  const key = localStorage.getItem('key') || '';
-  const userCode = localStorage.getItem('userCode') || '';
-  headers['sign'] = sign;
-  headers['key'] = key;
-  headers['userCode'] = userCode;
   return headers;
 };
 

File diff suppressed because it is too large
+ 0 - 0
Web/src/assets/media/panorama.svg


+ 0 - 1
Web/src/assets/media/picture.svg

@@ -1 +0,0 @@
-<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1719905467401" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10546" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M930.936471 0C992.978824 0 1024 31.021176 1024 93.063529v837.872942c0 62.042353-31.021176 93.063529-93.063529 93.063529H93.063529C31.021176 1024 0 992.978824 0 930.936471V93.063529C0 31.021176 31.021176 0 93.063529 0h837.872942zM601.630118 384.722824a15.811765 15.811765 0 0 0-13.70353 7.318588l-147.57647 231.303529-67.282824-65.867294a15.811765 15.811765 0 0 0-24.18447 2.379294l-101.797648 149.624471a15.811765 15.811765 0 0 0 13.101177 24.69647h524.468706a15.811765 15.811765 0 0 0 13.733647-23.732706l-183.416471-317.801411a15.811765 15.811765 0 0 0-13.342117-7.920941z m-244.254118-93.816471a79.811765 79.811765 0 1 0 0 159.593412 79.811765 79.811765 0 0 0 0-159.593412z" p-id="10547" fill="#c0c6d1"></path></svg>

+ 1 - 1
Web/src/assets/media/video.svg

@@ -1 +1 @@
-<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1719905321017" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5358" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M450.048 213.504L305.152 42.496H140.8l144.896 170.496h164.352z m426.496 0L731.648 42.496h-164.352l144.896 170.496h164.352z m-212.992 0L518.656 42.496H354.304l144.896 170.496h164.352z m274.944-171.008h-157.696l144.896 170.496h76.8V106.496c0-36.352-27.648-64-64-64z m-846.848 0h-6.144c-36.352 0-64 27.648-64 64v106.496h215.552L91.648 42.496zM21.504 917.504c0 36.352 27.648 64 64 64h853.504c36.352 0 64-27.648 64-64V256H21.504v661.504z m340.992-469.504c0-36.352 29.696-53.248 64-53.248 10.752 0 23.552 2.048 34.304 8.704l243.2 140.8a60.416 60.416 0 0 1 0 106.496L460.8 791.552c-10.752 6.656-21.504 8.704-34.304 8.704-34.304 0-64-16.896-64-53.248V448z" fill="#c0c6d1" p-id="5359"></path></svg>
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1722855934710" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13498" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M512.1 63.9c-247.4 0-447.9 200.5-447.9 447.9 0 247.4 200.5 447.9 447.9 447.9S960 759.2 960 511.8c0.1-247.4-200.5-447.9-447.9-447.9z m210.5 480.2L413.7 722.4c-24.9 14.4-56-3.6-56-32.3V333.5c0-28.7 31.1-46.7 56-32.3l308.8 178.3c24.9 14.3 24.9 50.3 0.1 64.6z" p-id="13499" fill="#e6e6e6"></path></svg>

+ 93 - 20
Web/src/components/GMap.vue

@@ -62,12 +62,11 @@
               </a-tooltip>
             </a-col>
             <a-col span="20">
-              <span
-                :style="deviceInfo.device.mode_code === EModeCode.Disconnected ? 'color: red; font-weight: 700;' : 'color: rgb(25,190,107)'">
-                {{ EModeCode[deviceInfo.device.mode_code] }}
+              <span>
+                飞行状态:
               </span>
               <span>
-                飞行状态
+                {{ getTextByModeCode(deviceInfo.device.mode_code) }}
               </span>
               <div class="openLiveButton" @click="state.deviceLiveStatus = true" v-if="!state.deviceLiveStatus">
                 开启直播
@@ -564,6 +563,7 @@ import FlightAreaActionIcon from './flight-area/FlightAreaActionIcon.vue'
 import { EFlightAreaType } from '../types/flight-area'
 import { useFlightArea } from './flight-area/use-flight-area'
 import { useFlightAreaDroneLocationEvent } from './flight-area/use-flight-area-drone-location-event'
+import { getTextByModeCode } from '/@/utils/index'
 
 export default defineComponent({
   components: {
@@ -855,25 +855,29 @@ export default defineComponent({
       setLayers(req)
       const coordinates = req.resource.content.geometry.coordinates
       updateCoordinates('gcj02-wgs84', req);
-      (req.resource.content.geometry.coordinates as GeojsonCoordinate).push((coordinates as GeojsonCoordinate)[2])
       await postElementsReq(shareId.value, req)
       obj.setExtData({ id: req.id, name: req.name })
+      const title = coordinates.map((item: any, index: number) => {
+        if (index < 2) {
+          return item.toFixed(4)
+        }
+      });
+      obj.setTitle(title)
       store.state.coverMap[req.id] = [obj]
       const map = root.$map
       const AMap = root.$aMap
       const text = new AMap.Text({
         position: new AMap.LngLat(coordinates[0], coordinates[1]),
-        offset: new AMap.Pixel(30, 0),
+        offset: new AMap.Pixel(20, -2),
         text: req.name,
         style: {
-          fontSize: 12,
-          padding: 0,
           backgroundColor: 'transparent',
           borderColor: 'transparent',
         }
       })
-      map.add(text);
-      store.state.coverMap[req.id + '_text'] = [text]
+      const other = [text]
+      map.add(other);
+      store.state.coverMap[req.id + '_other'] = other
       obj.on('click', function () {
         store.commit('SET_MAP_CLICK_ELEMENT', {
           id: req.id,
@@ -887,30 +891,63 @@ export default defineComponent({
       req.element_from = 1
       req.resource.user_name = userName
       setLayers(req)
-      const coordinatesList = req.resource.content.geometry.coordinates
       updateCoordinates('gcj02-wgs84', req)
       await postElementsReq(shareId.value, req)
       obj.setExtData({ id: req.id, name: req.name })
       store.state.coverMap[req.id] = [obj]
       const map = root.$map
       const AMap = root.$aMap
+      const coordinatesList = req.resource.content.geometry.coordinates.map((item: any) => wgs84togcj02(item[0], item[1]))
       if (coordinatesList.length < 2) {
         return
       }
+      const color = req.resource.content.properties.color
+      const circles = coordinatesList.map((item: any) => {
+        return new AMap.Circle({
+          center: new AMap.LngLat(item[0], item[1]),
+          radius: 8, // 半径
+          strokeColor: color,
+          fillColor: color,
+          fillOpacity: 1,
+          strokeWeight: 8,
+        });
+      })
       const coordinates = coordinatesList[0];
       const text = new AMap.Text({
         position: new AMap.LngLat(coordinates[0], coordinates[1]),
         offset: new AMap.Pixel(-30, -30),
         text: req.name,
         style: {
-          fontSize: 12,
-          padding: 0,
           backgroundColor: 'transparent',
           borderColor: 'transparent',
         }
       })
-      map.add(text);
-      store.state.coverMap[req.id + '_text'] = [text]
+      const distances = [];
+      const paths = [...coordinatesList]
+      // 计算并显示每段线的距离
+      for (let i = 0; i < paths.length - 1; i++) {
+        const distance = AMap.GeometryUtil.distance(new AMap.LngLat(paths[i][0], paths[i][1]), new AMap.LngLat(paths[i + 1][0], paths[i + 1][1]));
+        // 计算两个点之间的中点坐标
+        const midLng = (paths[i][0] + paths[i + 1][0]) / 2;
+        const midLat = (paths[i][1] + paths[i + 1][1]) / 2;
+        const midPoint = new AMap.LngLat(midLng, midLat);
+        // 在中点位置放置文本以显示距离
+        const distanceText = new AMap.Text({
+          position: midPoint,
+          offset: new AMap.Pixel(-16, 10),
+          text: `${distance.toFixed(1)} m`,// 距离
+          style: {
+            fontSize: '10px',
+            color: '#FFFFFF',
+            backgroundColor: 'rgba(0, 0, 0, 0.75)',
+            borderColor: 'transparent',
+          },
+        });
+        distances.push(distanceText);
+      }
+      const other = [...circles, text, ...distances]
+      map.add(other);
+      store.state.coverMap[req.id + '_other'] = other;
       obj.on('click', function () {
         store.commit('SET_MAP_CLICK_ELEMENT', {
           id: req.id,
@@ -924,30 +961,64 @@ export default defineComponent({
       req.element_from = 1
       req.resource.user_name = userName
       setLayers(req)
-      const coordinatesList = req.resource.content.geometry.coordinates[0]
       updateCoordinates('gcj02-wgs84', req)
       await postElementsReq(shareId.value, req)
       obj.setExtData({ id: req.id, name: req.name })
       store.state.coverMap[req.id] = [obj]
       const map = root.$map
       const AMap = root.$aMap
+      const coordinatesList = req.resource.content.geometry.coordinates[0].map((item: any) => wgs84togcj02(item[0], item[1]))
       if (coordinatesList.length < 3) {
         return
       }
+      const color = req.resource.content.properties.color
+      const circles = coordinatesList.map((item: any) => {
+        return new AMap.Circle({
+          center: new AMap.LngLat(item[0], item[1]),
+          radius: 8, // 半径
+          strokeColor: color,
+          fillColor: color,
+          fillOpacity: 1,
+          strokeWeight: 8,
+        });
+      })
       const coordinates = coordinatesList[0];
       const text = new AMap.Text({
         position: new AMap.LngLat(coordinates[0], coordinates[1]),
         offset: new AMap.Pixel(0, -30),
         text: req.name,
         style: {
-          fontSize: 12,
-          padding: 0,
           backgroundColor: 'transparent',
           borderColor: 'transparent',
         }
       })
-      map.add(text);
-      store.state.coverMap[req.id + '_text'] = [text]
+      const distances = [];
+      // 确保首尾相连
+      const paths = [...coordinatesList, coordinatesList[0]]
+      // 计算并显示每段线的距离
+      for (let i = 0; i < paths.length - 1; i++) {
+        const distance = AMap.GeometryUtil.distance(new AMap.LngLat(paths[i][0], paths[i][1]), new AMap.LngLat(paths[i + 1][0], paths[i + 1][1]));
+        // 计算两个点之间的中点坐标
+        const midLng = (paths[i][0] + paths[i + 1][0]) / 2;
+        const midLat = (paths[i][1] + paths[i + 1][1]) / 2;
+        const midPoint = new AMap.LngLat(midLng, midLat);
+        // 在中点位置放置文本以显示距离
+        const distanceText = new AMap.Text({
+          position: midPoint,
+          offset: new AMap.Pixel(-16, 10),
+          text: `${distance.toFixed(1)} m`,// 距离
+          style: {
+            fontSize: '10px',
+            color: '#FFFFFF',
+            backgroundColor: 'rgba(0, 0, 0, 0.75)',
+            borderColor: 'transparent',
+          },
+        });
+        distances.push(distanceText);
+      }
+      const other = [...circles, text, ...distances]
+      map.add(other);
+      store.state.coverMap[req.id + '_other'] = other;
       obj.on('click', function () {
         store.commit('SET_MAP_CLICK_ELEMENT', {
           id: req.id,
@@ -1066,6 +1137,7 @@ export default defineComponent({
         }
       }
     }
+
     return {
       draw,
       mouseMode,
@@ -1094,6 +1166,7 @@ export default defineComponent({
       closeLivestreamAgora,
       qualityStyle,
       selectFlightAreaAction,
+      getTextByModeCode,
     }
   }
 })

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

@@ -17,6 +17,6 @@ export const AMapConfig = {
     'AMap.RangingTool',
     'AMap.Weather',
     'AMap.MouseTool',
-    'AMap.MoveAnimation'
+    'AMap.MoveAnimation',
   ]
 }

+ 96 - 13
Web/src/hooks/use-g-map-cover.ts

@@ -45,25 +45,28 @@ export function useGMapCover() {
   }
 
   function init2DPin(name: string, coordinates: GeojsonCoordinate, color?: string, data?: {}) {
+    const title = coordinates.map((item: any, index) => {
+      if (index < 2) {
+        return item.toFixed(4)
+      }
+    });
     const marker = new AMap.Marker({
       position: new AMap.LngLat(coordinates[0], coordinates[1]),
-      title: name,
+      title: title,
       icon: getPinIcon(color),
       extData: data
     })
     const text = new AMap.Text({
       position: new AMap.LngLat(coordinates[0], coordinates[1]),
-      offset: new AMap.Pixel(-30, -30),
+      offset: new AMap.Pixel(20, -2),
       text: name,
       style: {
-        fontSize: 12,
-        padding: 0,
         backgroundColor: 'transparent',
         borderColor: 'transparent',
       },
       extData: {
         ...data,
-        id: data.id + '_text',
+        id: data.id + '_other',
       },
     })
     AddCoverToMap(text)
@@ -91,23 +94,57 @@ export function useGMapCover() {
       strokeStyle: 'solid',
       extData: data
     })
+    const circles = path.map((item, index) => {
+      return new AMap.Circle({
+        center: item,
+        radius: 8, // 半径
+        strokeColor: color,
+        fillColor: color,
+        fillOpacity: 1,
+        strokeWeight: 8,
+      });
+    })
     const coordinatesList = coordinates[0];
     const text = new AMap.Text({
       position: new AMap.LngLat(coordinatesList[0], coordinatesList[1]),
       offset: new AMap.Pixel(-30, -30),
       text: name,
       style: {
-        fontSize: 12,
-        padding: 0,
         backgroundColor: 'transparent',
         borderColor: 'transparent',
       },
       extData: {
         ...data,
-        id: data.id + '_text',
+        id: data.id + '_other',
       },
     })
-    AddCoverToMap(text)
+    const distances = [];
+    // 确保首尾相连
+    const paths = [...coordinates, coordinates[0]]
+    // 计算并显示每段线的距离
+    for (let i = 0; i < paths.length - 1; i++) {
+      const distance = AMap.GeometryUtil.distance(new AMap.LngLat(paths[i][0], paths[i][1]), new AMap.LngLat(paths[i + 1][0], paths[i + 1][1]));
+      // 计算两个点之间的中点坐标
+      const midLng = (paths[i][0] + paths[i + 1][0]) / 2;
+      const midLat = (paths[i][1] + paths[i + 1][1]) / 2;
+      const midPoint = new AMap.LngLat(midLng, midLat);
+      // 在中点位置放置文本以显示距离
+      const distanceText = new AMap.Text({
+        position: midPoint,
+        offset: new AMap.Pixel(-16, 10),
+        text: `${distance.toFixed(1)} m`,// 距离
+        style: {
+          fontSize: '10px',
+          color: '#FFFFFF',
+          backgroundColor: 'rgba(0, 0, 0, 0.75)',
+          borderColor: 'transparent',
+        },
+      });
+      distances.push(distanceText);
+    }
+    const other = [...circles, text, ...distances]
+    root.$map.add(other)
+    coverMap[data.id + '_other'] = [other]
     polyline.on('click', function () {
       const options = polyline.getOptions()
       const id = options.extData.id;
@@ -133,23 +170,57 @@ export function useGMapCover() {
       strokeColor: color || normalColor,
       extData: data
     })
+    const circles = path.map((item, index) => {
+      return new AMap.Circle({
+        center: item,
+        radius: 8, // 半径
+        strokeColor: color,
+        fillColor: color,
+        fillOpacity: 1,
+        strokeWeight: 8,
+      });
+    })
     const coordinatesList = coordinates[0][0];
     const text = new AMap.Text({
       position: new AMap.LngLat(coordinatesList[0], coordinatesList[1]),
       offset: new AMap.Pixel(-30, -30),
       text: name,
       style: {
-        fontSize: 12,
-        padding: 0,
         backgroundColor: 'transparent',
         borderColor: 'transparent',
       },
       extData: {
         ...data,
-        id: data.id + '_text',
+        id: data.id + '_other',
       },
     })
-    AddCoverToMap(text)
+    const distances = [];
+    // 确保首尾相连
+    const paths = [...coordinates[0], coordinates[0][0]]
+    // 计算并显示每段线的距离
+    for (let i = 0; i < paths.length - 1; i++) {
+      const distance = AMap.GeometryUtil.distance(new AMap.LngLat(paths[i][0], paths[i][1]), new AMap.LngLat(paths[i + 1][0], paths[i + 1][1]));
+      // 计算两个点之间的中点坐标
+      const midLng = (paths[i][0] + paths[i + 1][0]) / 2;
+      const midLat = (paths[i][1] + paths[i + 1][1]) / 2;
+      const midPoint = new AMap.LngLat(midLng, midLat);
+      // 在中点位置放置文本以显示距离
+      const distanceText = new AMap.Text({
+        position: midPoint,
+        offset: new AMap.Pixel(-16, 10),
+        text: `${distance.toFixed(1)} m`,// 距离
+        style: {
+          fontSize: '10px',
+          color: '#FFFFFF',
+          backgroundColor: 'rgba(0, 0, 0, 0.75)',
+          borderColor: 'transparent',
+        },
+      });
+      distances.push(distanceText);
+    }
+    const other = [...circles, text, ...distances]
+    root.$map.add(other)
+    coverMap[data.id + '_other'] = [other]
     polygon.on('click', function () {
       const options = polygon.getOptions()
       const id = options.extData.id;
@@ -161,6 +232,17 @@ export function useGMapCover() {
     AddOverlayGroup(polygon)
   }
 
+  function getPolygonArea(coordinates: GeojsonCoordinate[][]) {
+    const path = [] as GeojsonCoordinate[]
+    coordinates[0].forEach(coordinate => {
+      path.push(new AMap.LngLat(coordinate[0], coordinate[1]))
+    })
+    const polygon = new AMap.Polygon({
+      path: path,
+    })
+    return polygon.getArea();
+  }
+
   function AddOverlayGroup(overlayGroup: any) {
     root.$map.add(overlayGroup)
     const id = overlayGroup.getExtData().id
@@ -336,6 +418,7 @@ export function useGMapCover() {
     init2DPin,
     initPolyline,
     initPolygon,
+    getPolygonArea,
     removeCoverFromMap,
     getElementFromMap,
     updatePhotoElement,

+ 38 - 6
Web/src/hooks/use-g-map-trajectory.ts

@@ -29,22 +29,54 @@ export function useGMapTrajectory() {
       strokeWeight: 6,//线宽
       strokeStyle: 'solid',
     })
-    // 渲染序号
-    const serialList = list.filter(item => [2, 3].includes(item.type));
+    const serialList = list.filter((item) => [2, 3].includes(item.type));
+    // 序号
     const text = serialList.map((item, index) => {
       return new AMap.Text({
         position: new AMap.LngLat(item.paths[0], item.paths[1]),
-        // offset: new AMap.Pixel(-30, -30),
+        offset: new AMap.Pixel(-8, -30),
         text: index + 1,
         style: {
-          fontSize: 12,
-          padding: 0,
           backgroundColor: 'transparent',
           borderColor: 'transparent',
         }
       })
     })
-    root.$map.add([startMarker, polyline, ...text]);
+    // 序号下方圆形标记
+    const circles = serialList.map((item, index) => {
+      return new AMap.Circle({
+        center: new AMap.LngLat(item.paths[0], item.paths[1]),
+        radius: 0.5, // 半径
+        // strokeColor: 'white',
+        fillColor: 'white',
+        fillOpacity: 1,
+        strokeWeight: 2,
+      });
+    })
+    // 计算并显示每段线的距离
+    const distances = [];
+    const serialPaths = serialList.map(item => item.paths)
+    for (let i = 0; i < serialPaths.length - 1; i++) {
+      const distance = AMap.GeometryUtil.distance(new AMap.LngLat(serialPaths[i][0], serialPaths[i][1]), new AMap.LngLat(serialPaths[i + 1][0], serialPaths[i + 1][1]));
+      // 计算两个点之间的中点坐标
+      const midLng = (serialPaths[i][0] + serialPaths[i + 1][0]) / 2;
+      const midLat = (serialPaths[i][1] + serialPaths[i + 1][1]) / 2;
+      const midPoint = new AMap.LngLat(midLng, midLat);
+      // 在中点位置放置文本以显示距离
+      const distanceText = new AMap.Text({
+        position: midPoint,
+        offset: new AMap.Pixel(-16, 10),
+        text: `${distance.toFixed(1)} m`,// 距离
+        style: {
+          fontSize: '10px',
+          color: '#FFFFFF',
+          backgroundColor: 'rgba(0, 0, 0, 0.75)',
+          borderColor: 'transparent',
+        },
+      });
+      distances.push(distanceText);
+    }
+    root.$map.add([startMarker, polyline, ...text, ...circles, ...distances]);
     // 自动缩放地图到合适的视野级别
     root.$map.setFitView([startMarker, polyline]);
   }

+ 25 - 12
Web/src/pages/page-web/projects/layer/index.vue

@@ -8,23 +8,27 @@
       getContainer="#g-container" :wrap-style="{ position: 'absolute' }" wrapClassName="drawer-element-wrapper"
       @close="closeDrawer" width="300">
       <div class="drawer-element-content">
-        <div class="name element-item">
+        <div class="element-item">
           <span class="mr30">名称:</span>
           <a-input v-model:value="layerState.layerName" style="width:110px" placeholder="element name"
             @change="changeLayer" />
         </div>
-        <div class="longitude element-item" v-if="layerState.currentType === geoType.Point">
+        <div class="element-item" v-if="layerState.currentType === geoType.Point">
           <span class="mr30">经度:</span>
           {{ layerState.longitude || '--' }}
         </div>
-        <div class="latitude element-item" v-if="layerState.currentType === geoType.Point">
+        <div class="element-item" v-if="layerState.currentType === geoType.Point">
           <span class="mr30">纬度:</span>
           {{ layerState.latitude || '--' }}
         </div>
-        <div class="latitude element-item" v-if="layerState.element_from === 2">
+        <div class="element-item" v-if="layerState.element_from === 2">
           <span class="mr30">高度:</span>
           {{ layerState.height || '--' }}
         </div>
+        <div class="element-item" v-if="layerState.currentType === geoType.Polygon">
+          <span class="mr30">面积:</span>
+          {{ layerState.area || '--' }}
+        </div>
         <div class="color-content">
           <span class="mr30">颜色: </span>
           <div v-for="item in colors" :key="item.id" class="color-item" :style="'background:' + item.color"
@@ -153,6 +157,7 @@ const layerState = reactive({
   longitude: 0,
   latitude: 0,
   height: 0,
+  area: '',// 面积
   currentType: '',// “LineString”,"Polygon","Point"
   color: '#212121',
   user_name: '',
@@ -265,17 +270,26 @@ function getCurrentLayer(id: string) {
   findCan(Layers)
   return layer
 }
+
 function setBaseInfo() {
   const layer = selectedLayer.value
   if (layer) {
-    const geoType = layer.resource?.content.geometry.type;
-    layerState.currentType = geoType;
-    layerState.layerName = layer.name;
+    const content = layer.resource?.content;
+    const coordinates = content.geometry.coordinates;
+    const type = content.geometry.type;
     layerState.layerId = layer.id;
-    layerState.color = layer.resource?.content.properties.color;
+    layerState.layerName = layer.name;
+    layerState.longitude = coordinates[0];
+    layerState.latitude = coordinates[1];
+    layerState.height = layer.height;
+    layerState.currentType = type;
+    layerState.color = content.properties.color;
     layerState.user_name = layer.resource?.user_name;
     layerState.element_from = layer.element_from;
-    layerState.height = layer.height
+    if (type === 'Polygon') {
+      const area = useGMapCoverHook.getPolygonArea(coordinates);
+      layerState.area = area + 'm²';
+    }
   }
 }
 
@@ -316,7 +330,7 @@ async function deleteElement() {
     // 移除标点
     useGMapCoverHook.removeCoverFromMap(elementid)
     // 移除文本覆盖物
-    useGMapCoverHook.removeCoverFromMap(elementid + '_text')
+    useGMapCoverHook.removeCoverFromMap(elementid + '_other')
     getElementGroups()
   })
 }
@@ -373,7 +387,7 @@ async function updateElements() {
   // 移除标点
   useGMapCoverHook.removeCoverFromMap(currentLayer.id)
   // 移除文本覆盖物
-  useGMapCoverHook.removeCoverFromMap(currentLayer.id + '_text')
+  useGMapCoverHook.removeCoverFromMap(currentLayer.id + '_other')
   updateMapElement(selectedLayer.value, layerState.layerName, layerState.color)
   const result = await updateElementsReq(layerState.layerId, {
     name: layerState.layerName,
@@ -435,7 +449,6 @@ function updateCoordinates(transformType: string, element: LayerResource) {
     } else if (MapElementEnum.POLY === type) {
       const coordinates = element.resource?.content.geometry
         .coordinates[0] as GeojsonCoordinate[]
-
       if (transformType === 'wgs84-gcj02') {
         coordinates.forEach((coordinate, i, arr) => {
           arr[i] = wgs84togcj02(

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

@@ -129,8 +129,16 @@
         </div>
       </div>
       <div class="fileInfo-previewList">
-        <div class="fileInfo-previewList-item">
-          <img :src="state.info.thumbnail_url" />
+        <div class="fileInfo-previewList-button" @click="onClickScroll('LEFT')" @mousedown.prevent>
+          <VerticalRightOutlined />
+        </div>
+        <div ref="contentRef" class="fileInfo-previewList-content">
+          <img
+            :class="['fileInfo-previewList-content-item', state.currentId === item.file_id ? 'fileInfo-previewList-content-item-selected' : '']"
+            v-for="item in fileList" :src="item.thumbnail_url" @click="onClickSelected(item.file_id)" />
+        </div>
+        <div class="fileInfo-previewList-button" @click="onClickScroll('RIGHT')" @mousedown.prevent>
+          <VerticalLeftOutlined />
         </div>
       </div>
     </a-spin>
@@ -138,9 +146,9 @@
 </template>
 
 <script lang="ts" setup>
-import { reactive, onMounted } from 'vue';
+import { ref, reactive, onMounted } from 'vue';
 import { message } from 'ant-design-vue';
-import { CloseOutlined, EnvironmentOutlined, DownloadOutlined, AimOutlined } from '@ant-design/icons-vue';
+import { CloseOutlined, EnvironmentOutlined, DownloadOutlined, AimOutlined, VerticalRightOutlined, VerticalLeftOutlined } from '@ant-design/icons-vue';
 import Panoramic from '/@/components/panoramic/index.vue';
 import { useGMapManage } from '/@/hooks/use-g-map';
 import { apis } from '/@/api/custom';
@@ -150,6 +158,7 @@ import { downloadMediaFile } from '/@/api/media';
 
 interface Props {
   fileId: string,
+  fileList: any[],
   onClose: () => Promise<any>,
 };
 
@@ -158,11 +167,11 @@ const props = withDefaults(defineProps<Props>(), {
 });
 
 const state = reactive({
+  currentId: props.fileId,
   downloadLoading: false,
   imgLoading: false,// 图片加载
   info: {
     url: '',
-    thumbnail_url: '',// 缩略图
     media_type: 0,
     file_id: '',
     element_id: '',
@@ -181,12 +190,14 @@ const state = reactive({
   map: null, // 高德地图实例
 })
 
+const contentRef = ref();
+
 // 高德地图Hook
 const AmapHook = useGMapManage();
 
-onMounted(async () => {
+const init = async () => {
   try {
-    const res = await apis.fetchFileDetail(props.fileId);
+    const res = await apis.fetchFileDetail(state.currentId);
     state.info = res.data;
     if (res.data.media_type !== 4) {
       state.imgLoading = true;
@@ -217,6 +228,10 @@ onMounted(async () => {
   } catch (e) {
     console.error(e);
   }
+}
+
+onMounted(async () => {
+  await init()
 })
 
 // 点击下载
@@ -268,6 +283,42 @@ const onClickMapLocationReset = () => {
   map.setCenter(markerPosition);
   map.setZoom(17);
 }
+
+// 点击滚动
+const onClickScroll = (type: 'LEFT' | 'RIGHT') => {
+  const contentElement = contentRef.value;
+  const scrollAmount = 80; // 滚动的像素数等于图片宽度
+  if (type === 'LEFT') {
+    contentElement.scrollLeft -= scrollAmount; // 向左滚动
+  } else if (type === 'RIGHT') {
+    contentElement.scrollLeft += scrollAmount; // 向右滚动
+  }
+};
+
+// 点击选中
+const onClickSelected = async (id: string) => {
+  state.currentId = id;
+  state.downloadLoading = false;
+  state.imgLoading = false;
+  state.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: '',// 纬度
+  }
+  await init()
+}
 </script>
 
 <style lang="scss">
@@ -297,7 +348,7 @@ const onClickMapLocationReset = () => {
   &-detail {
     &-left {
       width: calc(100vw - 420px);
-      height: calc(100vh - 120px);
+      height: calc(100vh - 130px);
       overflow: hidden;
 
       &-background {
@@ -312,7 +363,7 @@ const onClickMapLocationReset = () => {
 
     &-info {
       width: 100%;
-      height: calc(100vh - 120px);
+      height: calc(100vh - 130px);
       padding: 24px;
       overflow: auto;
 
@@ -402,23 +453,45 @@ const onClickMapLocationReset = () => {
 
   &-previewList {
     width: 100%;
-    height: 70px;
+    height: 60px;
     display: flex;
-    justify-content: center;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
 
-    &-item {
-      width: 80px;
-      height: 60px;
-      border: 2px solid $primary;
-      border-radius: 4px;
-      margin-bottom: 10px;
-      cursor: pointer;
+    &-content {
+      width: calc(100vw - 80px);
+      display: flex;
+      overflow: auto;
+      scrollbar-width: none;
 
-      img {
-        width: 100%;
-        height: 100%;
+      &-item {
+        width: 80px;
+        height: 60px;
         object-fit: cover;
+        border: 2px solid transparent;
+        border-radius: 4px;
+        margin-right: 10px;
+        cursor: pointer;
+
+        &-selected {
+          border-color: $primary;
+        }
       }
+
+      &-item:last-child {
+        margin: 0;
+      }
+    }
+
+    &-button {
+      width: 30px;
+      height: 60px;
+      border: 1px solid #FFFFFF;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      cursor: pointer;
     }
   }
 }

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

@@ -9,11 +9,10 @@
           </a-button>
         </a-upload>
         <a-button style="margin-right: 10px;" type="primary" :disabled="!selectedRowKeys.length"
-          @click="onClickDownload" v-if="mode === 'table'">
+          @click="onClickDownload">
           压缩下载
         </a-button>
-        <a-button type="primary" danger :disabled="!selectedRowKeys.length" @click="onClickDelete"
-          v-if="mode === 'table'">
+        <a-button type="primary" danger :disabled="!selectedRowKeys.length" @click="onClickDelete">
           删除
         </a-button>
       </div>
@@ -71,7 +70,6 @@ import { ELocalStorageKey } from '/@/types'
 import moment from 'moment';
 
 interface Props {
-  mode: 'table' | 'list',
   fetchList: () => Promise<any>,
   selectedRowKeys: string[],
   onClickDownload: () => Promise<any>,

+ 71 - 102
Web/src/pages/page-web/projects/media/detail/index.vue

@@ -1,9 +1,8 @@
 <template>
   <a-spin :spinning="state.downloadLoading" tip="下载中...">
     <div class="mediaDetail">
-      <Search :mode="state.mode" :fetchList="fetchList" :selectedRowKeys="state.selectedRowKeys"
-        :onClickDownload="onClickBatchDownload" :onClickDelete="onClickBatchDelete" :onClickSearch="onClickSearch"
-        :onClickReset="onClickReset" />
+      <Search :fetchList="fetchList" :selectedRowKeys="state.selectedRowKeys" :onClickDownload="onClickBatchDownload"
+        :onClickDelete="onClickBatchDelete" :onClickSearch="onClickSearch" :onClickReset="onClickReset" />
       <div style="background: #FFFFFF;padding: 20px;">
         <div class="mediaDetail-info">
           <div class="mediaDetail-info-left">
@@ -26,110 +25,77 @@
                 {{ state.selectedRowKeys.length }}/{{ paginationConfig.total }}
               </div>
             </div>
-            <a-button style="padding:0 5px;" :type="state.mode === 'table' ? 'primary' : 'default'"
-              :ghost="state.mode === 'table'" size="small" @click="state.mode = 'table'">
-              <MenuOutlined />
-            </a-button>
-            <a-button style="padding:0 5px;" :type="state.mode === 'list' ? 'primary' : 'default'"
-              :ghost="state.mode === 'list'" size="small" @click="state.mode = 'list'">
-              <AppstoreOutlined />
-            </a-button>
           </div>
         </div>
-        <div class="mediaDetail-table" v-if="state.mode === 'table'">
-          <a-table :scroll="{ x: '100%', y: 500 }" :childrenColumnName="null" rowKey="file_id"
-            :loading="state.listLoading" :columns="columns" :dataSource="state.list" :rowClassName="rowClassName"
-            :pagination="paginationConfig"
-            :rowSelection="{ selectedRowKeys: state.selectedRowKeys, onChange: onSelectChange }">
-            <!-- 文件夹名称 -->
-            <template #file_name="{ record }">
-              <a-tooltip :title="record.file_name">
-                <div class="fileName">
-                  <div @click="onClickLookFile(record)">
-                    <img :src="panoramaSrc" v-if="record.media_type === 3" />
-                    <img :src="videoSrc" v-else-if="record.media_type === 4" />
-                    <img :src="pictureSrc" v-else />
-                  </div>
-                  <div>
-                    <a-input v-model:value="record.file_name" v-if="state.editableData[record.file_id]" />
-                    <div @click="onClickLookFile(record)" v-else>
-                      {{ record.file_name }}
-                    </div>
+        <a-table :scroll="{ x: '100%', y: 500 }" :childrenColumnName="null" rowKey="file_id"
+          :loading="state.listLoading" :columns="columns" :dataSource="state.list" :rowClassName="rowClassName"
+          :pagination="paginationConfig"
+          :rowSelection="{ selectedRowKeys: state.selectedRowKeys, onChange: onSelectChange }">
+          <!-- 文件夹名称 -->
+          <template #file_name="{ record }">
+            <a-tooltip :title="record.file_name">
+              <div class="fileName">
+                <div class="fileName-cover" @click="onClickLookFile(record)">
+                  <img class="fileName-cover-img" :src="record.thumbnail_url" />
+                  <img class="fileName-cover-icon" :src="panoramaSrc" v-if="record.media_type === 3" />
+                  <img class="fileName-cover-icon" :src="videoSrc" v-else-if="record.media_type === 4" />
+                </div>
+                <div style="margin-left: 5px;">
+                  <a-input v-model:value="record.file_name" v-if="state.editableData[record.file_id]" />
+                  <div @click="onClickLookFile(record)" v-else>
+                    {{ record.file_name }}
                   </div>
                 </div>
-              </a-tooltip>
-            </template>
-            <!-- 操作 -->
-            <template #action="{ record }">
-              <!-- 编辑态操作 -->
-              <div v-if="state.editableData[record.file_id]">
-                <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.file_id]" />
-                </a-tooltip>
               </div>
-              <!-- 非编辑态操作 -->
-              <div class="flex-align-center flex-row" style="color: #2d8cf0" v-else>
-                <div v-if="[1, 3].includes(record.media_type)">
-                  <a-tooltip title="在地图上加载" v-if="!record.element_id">
-                    <AimOutlined style="margin-right: 10px;" @click="onClickCreateMapElement(record.file_id)" />
-                  </a-tooltip>
-                  <a-tooltip title="在地图上取消加载" v-else>
-                    <AimOutlined style="color: #e70102;margin-right: 10px;"
-                      @click="onClickDeleteMapElement(record.file_id)" />
-                  </a-tooltip>
-                </div>
-                <a-tooltip title="重命名" v-else-if="record.media_type === 4">
-                  <EditOutlined style="margin-right: 10px;" @click="onClickRechristen(record)" />
-                </a-tooltip>
-                <a-tooltip title="删除" v-else-if="record.media_type === 2">
-                  <DeleteOutlined style="margin-right: 10px;" @click="onClickDelete(record)" />
+            </a-tooltip>
+          </template>
+          <!-- 操作 -->
+          <template #action="{ record }">
+            <!-- 编辑态操作 -->
+            <div v-if="state.editableData[record.file_id]">
+              <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.file_id]" />
+              </a-tooltip>
+            </div>
+            <!-- 非编辑态操作 -->
+            <div class="flex-align-center flex-row" style="color: #2d8cf0" v-else>
+              <div v-if="[1, 3].includes(record.media_type)">
+                <a-tooltip title="在地图上加载" v-if="!record.element_id">
+                  <AimOutlined style="margin-right: 10px;" @click="onClickCreateMapElement(record.file_id)" />
                 </a-tooltip>
-                <a-tooltip title="压缩下载">
-                  <DownloadOutlined @click="onClickDownload(record)" />
+                <a-tooltip title="在地图上取消加载" v-else>
+                  <AimOutlined style="color: #e70102;margin-right: 10px;"
+                    @click="onClickDeleteMapElement(record.file_id)" />
                 </a-tooltip>
               </div>
-            </template>
-          </a-table>
-        </div>
-        <div class="mediaDetail-list" v-else>
-          <a-list :grid="{ gutter: 16, xs: 1, sm: 2, md: 3, lg: 4, xl: 8, xxl: 12 }" :loading="state.listLoading"
-            :dataSource="state.list" :pagination="paginationConfig">
-            <template #renderItem="{ item }">
-              <a-list-item>
-                <a-card hoverable @click="onClickLookFile(item)">
-                  <template #cover>
-                    <div style="display: flex;justify-content:center;align-items: center;">
-                      <img style="width: 70%;margin: 10px;" :src="pictureSrc" />
-                    </div>
-                  </template>
-                  <a-card-meta>
-                    <template #description>
-                      <div class="mediaDetail-list-name">
-                        {{ item.file_name }}
-                      </div>
-                    </template>
-                  </a-card-meta>
-                </a-card>
-              </a-list-item>
-            </template>
-          </a-list>
-        </div>
+              <a-tooltip title="重命名" v-else-if="record.media_type === 4">
+                <EditOutlined style="margin-right: 10px;" @click="onClickRechristen(record)" />
+              </a-tooltip>
+              <a-tooltip title="删除" v-else-if="record.media_type === 2">
+                <DeleteOutlined style="margin-right: 10px;" @click="onClickDelete(record)" />
+              </a-tooltip>
+              <a-tooltip title="压缩下载">
+                <DownloadOutlined @click="onClickDownload(record)" />
+              </a-tooltip>
+            </div>
+          </template>
+        </a-table>
       </div>
     </div>
   </a-spin>
-  <FileInfo :fileId="state.fileId" :onClose="fileInfoOnClickClose" v-if="state.fileInfoVisible" />
+  <FileInfo :fileId="state.fileId" :fileList="state.list" :onClose="fileInfoOnClickClose"
+    v-if="state.fileInfoVisible" />
 </template>
 
 <script lang="ts" setup>
 import { reactive, onMounted } from 'vue';
 import { message } from 'ant-design-vue';
-import { MenuOutlined, AppstoreOutlined, EditOutlined, DeleteOutlined, DownloadOutlined, AimOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons-vue';
+import { EditOutlined, DeleteOutlined, DownloadOutlined, AimOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons-vue';
 import Search from './components/Search.vue';
 import FileInfo from './components/FileInfo.vue';
-import pictureSrc from '/@/assets/media/picture.svg';
 import panoramaSrc from '/@/assets/media/panorama.svg';
 import videoSrc from '/@/assets/media/video.svg';
 import { apis } from '/@/api/custom';
@@ -148,7 +114,6 @@ interface State {
     [key: string]: string,
   },
   downloadLoading: boolean,
-  mode: 'table' | 'list',
   fileId: string,
   fileInfoVisible: boolean,
 };
@@ -161,7 +126,6 @@ const state: State = reactive({
   selectedRowKeys: [],
   editableData: {},
   downloadLoading: false,
-  mode: 'table',
   fileId: '',
   fileInfoVisible: false,
 })
@@ -427,18 +391,23 @@ const onClickDownload = async (record: any) => {
     align-items: center;
     cursor: pointer;
 
-    img {
-      width: 36px;
-      height: 36px;
-      margin-right: 5px;
-    }
-  }
+    &-cover {
+      position: relative;
 
-  &-list {
-    &-name {
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
+      &-img {
+        width: 36px;
+        height: 36px;
+      }
+
+      &-icon {
+        width: 20px;
+        height: 20px;
+        position: absolute;
+        z-index: 2;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+      }
     }
   }
 }

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

@@ -1,5 +1,10 @@
 <template>
-  <a-row style="margin-bottom: 20px;" justify="end">
+  <a-row style="margin-bottom: 20px;" justify="space-between">
+    <a-col>
+      <a-button type="primary" @click="onClickAddUser">
+        添加飞行员
+      </a-button>
+    </a-col>
     <a-col>
       <a-form ref="formRef" layout="inline" :model="formModel" :colon="false">
         <a-form-item name="device_name">
@@ -34,6 +39,7 @@ import { ref, reactive } from 'vue';
 import { SearchOutlined, ReloadOutlined } from '@ant-design/icons-vue';
 
 interface Props {
+  onClickAddUser: () => void,
   onClickSearch: (query: any) => Promise<any>,
   onClickReset: (query: any) => Promise<any>,
 };

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

@@ -1,6 +1,6 @@
 <template>
   <div class="mediaList">
-    <Search :onClickSearch="onClickSearch" :onClickReset="onClickReset" />
+    <Search :onClickAddUser="onClickAddUser" :onClickSearch="onClickSearch" :onClickReset="onClickReset" />
     <div class="mediaList-table">
       <a-table :scroll="{ x: '100%', y: 500 }" rowKey="user_id" :loading="state.listLoading" :columns="columns"
         @change="refreshData" :rowClassName="rowClassName" :dataSource="state.list" :pagination="paginationConfig" />
@@ -106,6 +106,11 @@ const refreshData = async (page: any) => {
   await fetchList();
 }
 
+// 点击添加用户
+const onClickAddUser = () => {
+
+}
+
 // 点击搜索
 const onClickSearch = async (query: any) => {
   await fetchList(query);

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

@@ -185,10 +185,11 @@
                     <span class="ml5 mr5">
                       <RocketOutlined />
                     </span>
-                    <span class="font-bold"
-                      :style="deviceInfo[device.sn] && deviceInfo[device.sn].mode_code !== EModeCode.Disconnected ? 'color: #00ee8b' : 'color: red;'">
-                      {{ deviceInfo[device.sn] ? EModeCode[deviceInfo[device.sn].mode_code] :
-                        EModeCode[EModeCode.Disconnected] }}
+                    <span class="font-bold">
+                      {{
+                        getTextByModeCode(deviceInfo[device.sn] ? deviceInfo[device.sn].mode_code :
+                          EModeCode.Disconnected)
+                      }}
                     </span>
                   </div>
                 </div>
@@ -238,17 +239,18 @@
 
 <script lang="ts" setup>
 import { computed, onMounted, reactive, ref, watch } from 'vue'
+import { RocketOutlined, EyeInvisibleOutlined, EyeOutlined, RobotOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'
 import Layer from './layer/index.vue'
-import { EDeviceTypeName, ELocalStorageKey } from '/@/types'
 import noData from '/@/assets/icons/no-data.png'
 import rc from '/@/assets/icons/rc.png'
-import { OnlineDevice, EModeCode, EDockModeCode } from '/@/types/device'
 import { useMyStore } from '/@/store'
 import { getDeviceTopo, getUnreadDeviceHms, updateDeviceHms } from '/@/api/manage'
-import { RocketOutlined, EyeInvisibleOutlined, EyeOutlined, RobotOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'
 import { EHmsLevel, ERouterName } from '/@/types/enums'
+import { OnlineDevice, EModeCode, EDockModeCode } from '/@/types/device'
+import { EDeviceTypeName, ELocalStorageKey } from '/@/types'
 import { getRoot } from '/@/root'
 import { wgs84togcj02 } from '/@/vendors/coordtransform'
+import { getTextByModeCode } from '/@/utils/index'
 
 const store = useMyStore()
 const workspaceId = ref(localStorage.getItem(ELocalStorageKey.WorkspaceId)!)

+ 24 - 7
Web/src/router/index.ts

@@ -1,8 +1,10 @@
+import { message } from 'ant-design-vue'
 import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
-import { ERouterName } from '/@/types/index'
+import { ELocalStorageKey, ERouterName, EUserType } from '/@/types'
 import CreatePlan from '/@/components/task/CreatePlan.vue'
 import WaylinePanel from '/@/pages/page-web/projects/wayline.vue'
 import DockPanel from '/@/pages/page-web/projects/dock.vue'
+import { apis, SignLoginApiParams } from '/@/api/custom/index'
 
 const routes: Array<RouteRecordRaw> = [
   {
@@ -101,12 +103,27 @@ const router = createRouter({
   routes
 })
 
-router.beforeEach((to, from, next) => {
-  const { sign, key, userCode } = from.query as any
-  if (sign && key && userCode) {
-    localStorage.setItem('sign', sign);
-    localStorage.setItem('key', key);
-    localStorage.setItem('userCode', userCode);
+const updateToken = async (data: SignLoginApiParams) => {
+  try {
+    const result = await apis.signLogin(data);
+    if (result.code === 0) {
+      localStorage.setItem(ELocalStorageKey.Token, result.data.access_token)
+      localStorage.setItem(ELocalStorageKey.WorkspaceId, result.data.workspace_id)
+      localStorage.setItem(ELocalStorageKey.Username, result.data.username)
+      localStorage.setItem(ELocalStorageKey.UserId, result.data.user_id)
+      localStorage.setItem(ELocalStorageKey.Flag, EUserType.Web.toString())
+    } else {
+      message.error(result.message)
+    }
+  } catch (e) {
+    console.error(e);
+  }
+}
+
+router.beforeEach(async (to, from, next) => {
+  const { username, client_id, timestamp, workspace_id, workspace_name, signature } = to.query as any;
+  if (username && client_id && timestamp && workspace_id && workspace_name && signature) {
+    await updateToken({ username, client_id, timestamp, workspace_id, workspace_name, signature })
     next();
   } else {
     next();

+ 73 - 68
Web/src/types/map.d.ts

@@ -1,100 +1,105 @@
-
 export interface MapGeographicPosition {
- longitude: number;
- latitude: number;
- height?: number;
+  longitude: number;
+  latitude: number;
+  height?: number;
 }
+
 export enum LayerType {
- Normal,
- Default,
- Share
+  Normal,
+  Default,
+  Share
 }
+
 export interface pinAMapPosition {
- KL: number
- className: string
- kT: number
- lng: number
- lat: number
+  KL: number
+  className: string
+  kT: number
+  lng: number
+  lat: number
 }
+
 export enum ResourceStatus {
- NotShow,
- Show
+  NotShow,
+  Show
 }
+
 export type GeojsonCoordinate = [number, number, number?]
 
 export interface GeojsonLine {
- type: 'Feature'
- properties: {
-   color: string
-   directConnected?: boolean
- }
- geometry: {
-   type: 'LineString'
-   coordinates: GeojsonCoordinate[]
- }
+  type: 'Feature'
+  properties: {
+    color: string
+    directConnected?: boolean
+  }
+  geometry: {
+    type: 'LineString'
+    coordinates: GeojsonCoordinate[]
+  }
 }
 
 export interface GeojsonPolygon {
- type: 'Feature'
- properties: {
-   color: string
- }
- geometry: {
-   type: 'Polygon'
-   coordinates: GeojsonCoordinate[][]
- }
+  type: 'Feature'
+  properties: {
+    color: string
+  }
+  geometry: {
+    type: 'Polygon'
+    coordinates: GeojsonCoordinate[][]
+  }
 }
 
 export interface GeojsonPoint {
- type: 'Feature'
- properties: {
-   color: string
-   clampToGround?: boolean
- }
- geometry: {
-   type: 'Point'
-   coordinates: GeojsonCoordinate
- }
+  type: 'Feature'
+  properties: {
+    color: string
+    clampToGround?: boolean
+  }
+  geometry: {
+    type: 'Point'
+    coordinates: GeojsonCoordinate
+  }
 }
+
 export type GeojsonFeature = GeojsonLine | GeojsonPolygon | GeojsonPoint
 
 interface ResourceObjectBasic {
- user_name: string
- user_id?: string
- type:0| 1 | 2
- content: unknown
+  user_name: string
+  user_id?: string
+  type: 0 | 1 | 2
+  content: unknown
 }
+
 export interface PinResource extends ResourceObjectBasic {
- type: 0
- content: GeojsonFeature
+  type: 0
+  content: GeojsonFeature
 }
 
 export type ResourceObject = PinResource
 export enum LayerElevationLoadStatus {
- Unload,
- Load
+  Unload,
+  Load
 }
 
 export interface LayerResource {
- id: string
- name: string
- order: number
- status: ResourceStatus
- resource: ResourceObject | null
- display: number
- create_time: number
- elevation_load_status?: LayerElevationLoadStatus //
+  id: string
+  name: string
+  order: number
+  status: ResourceStatus
+  resource: ResourceObject | null
+  display: number
+  create_time: number
+  elevation_load_status?: LayerElevationLoadStatus //
 }
-export interface Layer {
- id: string
- name: string
- order: number
- create_time: number
- type: LayerType
- is_distributed: boolean
- is_lock: boolean
- elements: null | LayerResource[],
- is_check?: boolean
- is_select?: boolean
 
-}
+export interface Layer {
+  id: string
+  name: string
+  order: number
+  create_time: number
+  type: LayerType
+  is_distributed: boolean
+  is_lock: boolean
+  elements: null | LayerResource[],
+  is_check?: boolean
+  is_select?: boolean
+}

+ 1 - 0
Web/src/types/mapLayer.ts

@@ -10,6 +10,7 @@ export interface mapLayerChildren {
   title: string
   obj: any
 }
+
 export interface mapLayerChildrenObj {
   className: string
   key: string

+ 65 - 0
Web/src/utils/index.ts

@@ -0,0 +1,65 @@
+export const getTextByModeCode = (code: number) => {
+    let text = '';
+    switch (code) {
+        case 0:
+            text = '待机';
+            break;
+        case 1:
+            text = '起飞准备';
+            break;
+        case 2:
+            text = '起飞准备完毕';
+            break;
+        case 3:
+            text = '手动飞行';
+            break;
+        case 4:
+            text = '自动起飞';
+            break;
+        case 5:
+            text = '航线飞行';
+            break;
+        case 6:
+            text = '全景拍照';
+            break;
+        case 7:
+            text = '智能跟随';
+            break;
+        case 8:
+            text = 'ADS-B 躲避';
+            break;
+        case 9:
+            text = '自动返航';
+            break;
+        case 10:
+            text = '自动降落';
+            break;
+        case 11:
+            text = '强制降落';
+            break;
+        case 12:
+            text = '三桨叶降落';
+            break;
+        case 13:
+            text = '升级中';
+            break;
+        case 14:
+            text = '未连接';
+            break;
+        case 15:
+            text = 'APAS';
+            break;
+        case 16:
+            text = '虚拟摇杆状态';
+            break;
+        case 17:
+            text = '指令飞行';
+            break;
+        case 18:
+            text = '空中 RTK 收敛模式';
+            break;
+        default:
+            break;
+    }
+    return text;
+}

Some files were not shown because too many files changed in this diff