Initial commit

This commit is contained in:
zyp
2026-05-22 16:13:20 +08:00
commit b3089a254e
1444 changed files with 372077 additions and 0 deletions

235
src/api/3ddaping/3dtask.ts Normal file
View File

@@ -0,0 +1,235 @@
/*
* @Date: 2025-02-22 13:34:10
* @LastEditors: jiangchao
* @LastEditTime: 2025-03-11 17:20:07
* @Description: 文件注释
* @Author: jiangchao
* @FilePath: \mars3dMap\src\api\3ddaping\3dtask.ts
*/
import request from '/@/api/http/requestsaas'
import req from '/@/api/http/request'
import moment from 'moment'
let workspace_id = localStorage.getItem('workspace_id')
// 获取所有行政区
export const getSpClue = async () => {
let spClue = await request.get('/admin-api/system/ad/list')
return spClue.data
}
// 获取单个行政区
export const getSpClueById = async (ad_code: string = 'list') => {
let spClue = await req.get(`/api/v1/ad/${workspace_id}/${ad_code}`)
// let spClue = await .get(`/api/v1/ad/${workspace_id}/610802`)
return spClue.data
}
// 获取多个行政区
export const getSpClueByAll = async (ad_code: string) => {
let codes = ad_code.split(',')
let spClue = await req.get(`/api/v1/ad/${workspace_id}/getAdAreas`,{
params:{
ad_codes:ad_code
}
})
return spClue.data
}
// 获取网格数据
// export const getGridData = async () => {
// let spClue = await req.get(`/api/v1/web/tool/calculate_square_grid?ad_code=320104`)
// return spClue.data
// }
// 获取网格数据
export const getSendVr = async (code:number=320104) => {
let spClue = await req.get(`/api/v1/ad/${workspace_id}/grid/${code}`)
return spClue.data
}
// 获取多个网格数据
export const getSendVrAll = async (code:string="320104") => {
let codes = code.split(',')
console.log("codes",codes)
let spClue = await req.get(`/api/v1/ad/${workspace_id}/gridsByAdCodes`,{params:{
ad_codes:code
}})
return spClue.data
}
/**
* 获取网格对应图例
* @param code
* @returns
*/
export const getSendVrByClue = async (code:string="320104",start_time:number=1740422224847,end_time:number=1740722224847) => {
let spClue = await req.get(`/api/v1/task/${workspace_id}/clue/statistics/grid`,{params:{
// ad_codes:code
start_time: start_time,
end_time
}})
return spClue.data
}
/**
*
* @param id
* @returns
*/
export const getWangtgeInfo = async (id:number,start_time:number=0,end_time:number = Date.now()) => {
let spClue = await req.get(`/api/v1/task/${workspace_id}/clue/statistics/list_by_grid_id`,{params:{
grid_id:id,
start_time,
end_time
}})
return spClue.data
}
export const getPanoramicDataById = async (id:number) => {
let spClue = await req.get(`/task/api/v1/task/${workspace_id}/${id}/get`)
return spClue.data
}
// 查询全景数据
export const getPanoramicData = async () => {
let spClue = await req.get(`/media/api/v1/files/${workspace_id}/files/getFilePage`,{params:{
page: 1,
page_size: 50,
searchAll: 1,
file_type: "jpeg",
isSendVr: 1
}})
return spClue.data
}
// 查询图例数据
export const getLegendData = async (start_time:number=1740422224847,end_time:number=1740722224847) => {
let legendData = await req.get(`/api/v1/task/${workspace_id}/clue/statistics/grid_legend`,{params:{
start_time: start_time,
end_time
}})
return legendData.data
}
// 根据网格id显示线索
export const getClueDataByGridId = async (grid_id:number,start_time:number,end_time:number) => {
let clueData = await req.get(`/api/v1/task/${workspace_id}/clue/page_by_grid_id`,{params:{
grid_id:grid_id,
start_time,
end_time,
page:1,
page_size:10000
}})
return clueData.data
}
// 查询热力点数据
export const getHotMapPoint = async (start_time:number,end_time:number) => {
let clueData = await req.get(`/api/v1/task/${workspace_id}/clue/statistics/hotmap`,{params:{
start_time,
end_time,
}})
return clueData.data
}
// 根据网格查询媒体文件
export const getMediaFileByGridId = async (grid_id:number,start_time:number,end_time:number) => {
let mediaFile = await req.get(`/media/api/v1/files/${workspace_id}/files/pageByGrid`,{params:{
grid_id:grid_id,
page:1,
start_time,
end_time,
page_size:100
}})
return mediaFile.data
}
// 根据网格查询媒体文件
export const getHandlerStats = async (start_time:number,end_time:number) => {
let mediaFile = await req.get(`/api/v1/task/${workspace_id}/clue/handler/stats`,{params:{
start_time,
end_time,
}})
return mediaFile.data
}
//获取无人机列表getDeviceList
export const getDeviceList = async (domain) => {
let res = await req.get(`/manage/api/v1/devices/${workspace_id}/devices/bound`,{params:{
page:1,
page_size:10,
domain,
}})
return res.data
}
// 指哪飞哪 - 一键起飞
export const feijizhidianfeixing = async (target_latitude:number,target_longitude:number, height :number) => {
let mediaFile = await req.post(`/control/api/v1/devices/jobs/takeoff-to-point/custom-task`,{
task_name: `指哪飞哪_${moment().format('YYYY-MM-DD HH:mm:ss')}`,
target_latitude,
target_longitude,
target_height:height
})
return mediaFile.data
}
// 指哪飞哪 - 一键起飞
export const addjingqing = async (task_name:string,target_latitude:number,target_longitude:number) => {
let mediaFile = await req.post(`/control/api/v1/devices/jobs/takeoff-to-point/custom-task`,{
task_name: task_name,
target_latitude,
task_detail: `${task_name}_${moment().format('YYYY-MM-DD HH:mm:ss')}`,
target_longitude
})
return mediaFile.data
}
// 指哪飞哪 - 一键起飞
export const yijianqifei = async (sn:string,target_latitude:number,target_longitude:number, height :number,target_height:number) => {
let mediaFile = await req.post(`/control/api/v1/devices/${sn}/jobs/takeoff-to-point`,{
target_latitude,
target_longitude,
target_height:target_height,
exit_wayline_when_rc_lost: 1,
"security_takeoff_height": height,
"rth_altitude": height,
commander_flight_mode: 1,
"max_speed": 14,
"rc_lost_action": 2,
"rth_mode": 1,
"commander_mode_lost_action":0,
// "commander_flight_mode": 1,
"commander_flight_height": height
})
return mediaFile.data
}
// 指哪飞哪 - 指点飞行
export const zhidianfeixingbysn = async (sn:string,target_latitude:number,target_longitude:number, height :number) => {
let mediaFile = await req.post(`/control/api/v1/devices/${sn}/jobs/fly-to-point`,{
max_speed: 10,
points: [{
latitude: target_latitude,
longitude: target_longitude,
height: height
}],
})
return mediaFile.data
}

View File

@@ -0,0 +1,381 @@
import request,{ IPage, IWorkspaceResponse } from "../api/http/request"
const workspace_id = localStorage.getItem("workspace_id")!
const jsonByUrl = function () {
return function (obj: any, isFirst: boolean = true) {
let url = '';
let indexData = 0;
for (let [key, value] of Object.entries(obj)) {
url += `${isFirst && indexData == 0 ? '?' : '&'}${key}=${value}`;
indexData++;
}
return url;
};
};
export const getAdEquipment = async (pagination: IPage, keyword: string = '') => {
try {
const response = await request.get(`/api/v1/ad/${workspace_id}/pageByKeyword?`,{ params: { page:pagination.page,page_size:pagination.page_size,keyword }})
return response.data
} catch (error) {
throw error
}
}
// 按区域统计线索等级
export const getClueLevelStatsPage = async (pagination: IPage, start_time: number = 1710747379775, end_time: number = 1741508070659) => {
try {
const response = await request.get(`/api/v1/ad/${workspace_id}/clue/level_stats_page`,{ params: {
page:pagination.page,
page_size:pagination.page_size,
start_time,
end_time
}})
return response.data
} catch (error) {
throw error
}
}
// 查询任务
export const getTicketStatsPage = async (pagination: IPage, start_time: number = 1710747379775, end_time: number = 1741508070659) => {
try {
const response = await request.get(`/api/v1/task/${workspace_id}/clue/level_stats_by_ticket`,{ params: {
start_time,
end_time
}})
return response.data
} catch (error) {
throw error
}
}
export const getAiTypeStatsByTicket = async (
ticket_id:number,
start_time: number = 1710747379775,
end_time: number = 1741508070659) => {
try {
const response = await request.get(`/api/v1/task/${workspace_id}/clue/ai_type_stats_by_ticket`,{ params: {
start_time,
ticket_id,
end_time
}})
return response.data
} catch (error) {
throw error
}
}
// 根据区域id获取类型数据
export const getStatsBytype = async (ad_code: number, start_time: number = 1710747379775, end_time: number = 1741508070659) => {
try {
const response = await request.get(`/api/v1/ad/${workspace_id}/clue/type_stats_list`,{ params: {
ad_code,
start_time,
end_time
}})
return response.data
} catch (error) {
throw error
}
}
// 飞行统计
// 巡逻次数
export const getPatrolCount = async () => {
const apiUrl = `/dashboard/api/v1/dashboard/${workspace_id}/getPatrolCount`;
let responseData = await request(apiUrl);
return responseData.data as any;
};
//巡检数据统计
export const getCountAvgAndMaxInspections = async (params) => {
let paramsurl = jsonByUrl()(params);
const apiUrl = `/dashboard/api/v1/dashboard/${workspace_id}/countAvgAndMaxInspections${paramsurl}`;
let responseData = await request(apiUrl);
return responseData.data as any;
};
//基站top5
export const getCountDeviceTask = async (params) => {
let paramsurl = jsonByUrl()(params);
const apiUrl = `/dashboard/api/v1/dashboard/${workspace_id}/countDeviceTask${paramsurl}`;
let responseData = await request(apiUrl);
return responseData.data as any;
};
//媒体数据采集以及top5
export const getDeviceMedia = async (params) => {
let paramsurl = jsonByUrl()(params);
const apiUrl = `/dashboard/api/v1/dashboard/${workspace_id}/getDeviceMedia${paramsurl}`;
let responseData = await request(apiUrl);
return responseData.data as any;
};
export interface AllTaskParams {
state?: string;
status?: string;
current: string;
size: string;
startTime?: string;
endTime?: string;
}
//获取资源数据视频、图片
export const getTaskListMonthDay = async function (params: AllTaskParams): Promise<IWorkspaceResponse<any>> {
let paramsurl = jsonByUrl()(params);
const url = `/task/api/v1/task/${workspace_id}/getTaskListMonthDay${paramsurl}`;
const result = await request.get(url);
return result.data;
};
//获取巡检次数
export const getTaskCountByDateRange = async function (params: AllTaskParams): Promise<IWorkspaceResponse<any>> {
let paramsurl = jsonByUrl()(params);
const url = `/dashboard/api/v1/dashboard/${workspace_id}/getTaskCountByDateRange${paramsurl}`;
const result = await request.get(url);
return result.data;
};
// Get Wayline Jobs
export const getWaylineJobs = async function (workspaceId: string, page: IPage): Promise<IWorkspaceResponse<any>> {
const url = `/wayline/api/v1/workspaces/${workspaceId}/jobs?page=${page.page}&page_size=${page.page_size}&device_sn=`;
const result = await request.get(url);
return result.data;
};
export const getAiLiveData = async() => {
let url = `/aiIdentify/checkServices`;
const result = await request.get(url);
return result.data;
};
// Get Device Topo
export const getDeviceTopo = async function (): Promise<IWorkspaceResponse<any>> {
const url = `/manage/api/v1/devices/${workspace_id}/devices`;
const result = await request.get(url);
return result.data;
};
//get allClue
export interface IClueRequest {
/**
* ai类型
*/
aiType?: number;
/**
* 当前页数
*/
current: number;
/**
* 查询结束时间(毫秒)
*/
endTime: number;
/**
* 默认1
*/
flag: number;
/**
* 分页数
*/
size: number;
/**
* 查询开始时间(毫秒)
*/
startTime: number;
[property: string]: any;
}
export const getAllClue = async function (params:IClueRequest): Promise<IWorkspaceResponse<any>> {
const url = `/task/api/v1/task/${workspace_id}/getAllClue`;
const result = await request.get(url,{params});
return result.data;
};
export const exportAllClue = async function (params:IClueRequest): Promise<any> {
const url = `/task/api/v1/task/${workspace_id}/exportAllClue`;
const result = await request.get(url,{params});
return result;
};
export const getTicketAllClue = async function (params:IClueRequest): Promise<IWorkspaceResponse<any>> {
const url = `/task/api/v1/task/${workspace_id}/ticket/getAllClue`;
const result = await request.get(url,{params});
return result.data;
};
// 网格线索确认状态
export interface IConfirmAnalysis {
/**
* 线索分析状态(1.待分析待核实 2.已分析待核实 3已核实)
*/
analysisStatus: string;
/**
* 线索ID
*/
clueId: string;
[property: string]: any;
}
//通过审核线索
export const confirmAnalysis = async function (body:IConfirmAnalysis): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/task/${workspace_id}/clue/analysis/status`;
const result = await request.put(url,body);
return result.data;
};
//根据gridId获取网格人员
export const getUserListByGridId = async function (grid_id:string): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/grid/${workspace_id}/userListByGridId?grid_id=${grid_id}`;
const result = await request.get(url);
return result.data;
};
//获取网格线索下拉
export const getClueRegion = async function (): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/task/${workspace_id}/clue/region`;
const result = await request.get(url);
return result.data;
};
export interface IAnalyseStatistics {
/**
* ai类型 如16、25
*/
aiType?: string;
/**
* 天数用于控制折线图如果不传值默认30天
*/
days?: string;
/**
* 机场设备SN 如: 4TADL21001002H
*/
deviceSn?: string;
endTime?: number;
startTime?: number;
[property: string]: any;
}
export const getAnalysisStatistics = async function (params:IAnalyseStatistics): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/task/${workspace_id}/clue/analyse/statistics`;
const result = await request.get(url, {params});
return result.data;
};
export const getTicketAnalysisStatistics = async function (params:IAnalyseStatistics): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/task/${workspace_id}/clue/ticket/analyse/statistics`;
const result = await request.get(url, {params});
return result.data;
};
// /api/v1/ad/{workspace_id}/ad/users_by_point
interface UsersByPoint {
lng:number
lat:number
}
export const usersByPoint = async function (params:UsersByPoint): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/ad/${workspace_id}/ad/users_by_point`;
const result = await request.get(url, {params});
return result.data;
};
export const getClueCountByGroup = async (params:IAnalyseStatistics) => {
const url = `/task/api/v1/task/${workspace_id}/getClueCountByMenu`;
const result = await request.get(url, {params});
return result.data;
}
export interface TaskWorkParams {
task_id:string
}
export const getWorkTaskByTaskId = async (params:TaskWorkParams) => {
const url = `/task/api/v1/task/${workspace_id}/get_work_task_by_task_id`;
const result = await request.get(url, {params});
return result.data;
}
//北斗设备视频-同北斗设备接口
export const BDLivePage = async () => {
let url = `/api/v1/ue/pageList`;
const result = await request.get(url);
return result.data;
}
//关闭所有视频流
export const stopAllStream = async () => {
let url = `/api/v1/ue/stopAllStream`;
const result = await request.get(url);
return result.data;
};
//开启视频流
export const getBdstreaming = async (param: any) => {
let url = `/api/v1/ue/startStream`;
const result = await request.put(url, param);
return result.data;
};
//关闭视频流
export const getBdstopStream = async (param: any) => {
let url = `/api/v1/ue/stopStream`;
const result = await request.put(url, param);
return result.data;
};
//保存返回标注
export const saveMarker = async(param:any) => {
let url = `/map/api/v1/workspaces/${workspace_id}/saveOrUpdate`;
const result = await request.post(url,param);
return result.data;
};
//下发线索到各个平台
export const sendClueToSys = async(param:any) => {
let url = `/api/v1/nationalScience-events/${workspace_id}/pushMqttApi`;
const result = await request.post(url,param);
return result.data;
};
//删除返回标注
export const deleteMarker = async(id:string) => {
let url = `/map/api/v1/workspaces/${workspace_id}/delete/${id}`;
const result = await request.delete(url);
return result.data;
};
//获取标注点
export const getMarkerDraws = async() => {
let url = `/map/api/v1/workspaces/${workspace_id}/pageList?page_num=1&page_size=10000`;
const result = await request.get(url);
return result.data;
};
//军标库上传图片
export const upImgBase64 = async(param: any) => {
// let url = `/map/api/v1/icon/update`;
let url = `/map/api/v1/icon/save`;
const result = await request.post(url,param);
return result.data;
};
//获取军标库图片
export const getJunBiao = async() => {
let url = `/map/api/v1/icon/pageList?page_num=1&page_size=10000`;
const result = await request.get(url);
return result.data;
};
// 属性编辑面板:创建标注(带图标)
export const createMarker = async(param: any) => {
let url = `/map/api/v1/icon/createMarker`;
const result = await request.post(url, param);
return result.data;
};
// 按 original_id 批量/逻辑删除标注
export const deleteMarkers = async (body: { original_id: string }) => {
const url = `/map/api/v1/icon/deleteMarkers`;
const result = await request.delete(url, { data: body });
return result.data;
};
// 根据标记类型获取标记列表
export const getMarkersByType = async(markerType: number = 1) => {
let url = `/map/api/v1/workspaces/${workspace_id}/listByMarkerType?markerType=${markerType}`;
const result = await request.get(url);
return result.data;
};
// 根据ID获取标记详情
export const getMarkerById = async(markerId: string) => {
let url = `/map/api/v1/workspaces/${workspace_id}/detail/${markerId}`;
const result = await request.get(url);
return result.data;
};

317
src/api/ai.ts Normal file
View File

@@ -0,0 +1,317 @@
/*
* @Date: 2023-09-25 10:35:10
* @LastEditors: jiangchao
* @LastEditTime: 2024-11-16 14:51:31
* @Description: 文件注释
* @Author: jiangchao
* @FilePath: \redfly-web\src\api\ai.ts
*/
import moment from 'moment';
import request from '/@/api/http/request';
import { ELocalStorageKey } from '../types';
const jsonByUrl = function () {
return function (obj: any, isFirst: boolean = true) {
let url = '';
for (let [key, value] of Object.entries(obj)) {
url += `${isFirst ? '?' : '&'}${key}=${value}`;
}
return url;
};
};
export const aiTypeByWare = async (workspace_id:string,ai_type:string,description:string) => {
const url = `/description/${workspace_id}/save`
let body = {
workspace_id:workspace_id,
description:description,
ai_type: ai_type
};
return (await request.post(url,body)).data
}
export const startGBAi = async function (workspace_id: string, data: any): Promise<any> {
let url = `/task/api/v1/task/${workspace_id}/startGBAi`;
const result = await request.post(url, data);
return result.data;
};
export const stopGBAi = async function (workspace_id: string, data: any): Promise<any> {
let url = `/task/api/v1/task/${workspace_id}/stopGBAi`;
const result = await request.post(url, data);
return result.data;
};
export const getGBAi = async function (workspace_id: string, data: any): Promise<any> {
let url = `/task/api/v1/task/${workspace_id}/GBAi`;
const result = await request.post(url, data);
return result.data;
};
export const getAiTypeByWare = async (ai_type?:string) => {
let workspace_id = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
const url = `/description/${workspace_id}/list`
let body:{
ai_type?:string
} = {
};
if(ai_type){
body.ai_type = ai_type;
}
return (await request.post(url,body)).data
}
export const getAiLiveData = (params = {}) => {
let url = `/aiIdentify/checkServices`;
return requestGet(url,{params});
};
export interface pageParams {
page: number;
pageSize: number;
id?: string;
}
//查询车牌
export const getPlateNumber = async (params: pageParams) => {
let url = `/gateway/ai/plate-number/page`;
let res = await request.get(url, { params });
return res.data;
};
//新增车牌号
export const addPlateNumber = async (data: { plateNumber: string; aiTypeId: string }) => {
let url = `/gateway/ai/plate-number/insert`;
let res = await request.post(url, data);
return res.data;
};
//编辑车牌号
export const editPlateNumber = async (data: { id: string; plateNumber: string; aiTypeId: string }) => {
let url = `/gateway/ai/plate-number/update`;
let res = await request.put(url, data);
return res.data;
};
//删除车牌号
export const deletePlateNumber = async (params: { ids: string[] }) => {
let res = await request.delete(`/gateway/ai/plate-number/delete?ids=` + params.ids.join('&ids'));
return res.data;
};
export const getTool = (typeId: any) => {
let url = `/aiIdentify/train/getAiMarkTools?typeId=${typeId}`;
return requestGet(url, {
responseType: 'blob',
});
};
export const startTraining = (query: { typeId: number; fileId: string }) => {
let url = `/aiIdentify/train/startTrain`;
let firstIndex = true;
for (let [queryKey, queryValue] of Object.entries(query)) {
url += `${firstIndex ? '?' : '&'}${queryKey}=${queryValue}`;
firstIndex = false;
}
return requestGet(url);
};
export const getSchedule = (typeId: any, rindexByid: string | number) => {
let url = `/aiIdentify/train/getTrainProgress?typeId=${typeId}&index=${rindexByid}`;
return requestGet(url);
};
export const getAllData = () => {
let url = `/aiIdentify/train/getTrain`;
return requestGet(url);
};
export const removeData = (typeId: any) => {
let url = `/aiIdentify/train/deleteTrain?typeId=${typeId}`;
return requestGet(url);
};
export const changeData = (typeId: any) => {
let url = `/aiIdentify/train/replaceTrain?typeId=${typeId}`;
return requestGet(url);
};
export const getStorageConfig = () => {
let url = `/system/base/getStorageConfig`;
return requestGet(url);
};
interface UploadFileByOssObject {
ossObject: string;
}
export const uploadFileByOssObject = (body: UploadFileByOssObject) => {
let url = `/system/base/uploadFileByOssObject?ossObject=${body.ossObject}`;
return requestPost(url);
};
// 设置短信通知
export const setSmsNotify = (body: { id: string; isNotify: string }) => {
let url = `/admin-api/system/user/setNotify`;
return requestPost(url, body);
};
export interface OpenAiStartLives {
workSpaceId: string;
sn: string;
flag: number;
serviceName?: number;
cameraMode: 'ir' | 'normal' | 'zoom';
sharpness: number;
}
export const openAiStartLive = (data: OpenAiStartLives) => {
let url = `/aiIdentify/getCapacityThenStopLiveThenStartLive`;
let isFirst = true;
for (let [key, value] of Object.entries(data)) {
url += `${isFirst ? '?' : '&'}${key}=${value}`;
isFirst = false;
}
return requestGet(url);
};
export interface StopAiLives {
// serviceName:string
// rtmpUrl:string
// userName:string
sn: string;
}
export const stopAiLive = (data: StopAiLives) => {
let url = `/aiIdentify/capacityThenStopLiveThenStopLive`;
let isFirst = true;
for (let [key, value] of Object.entries(data)) {
url += `${isFirst ? '?' : '&'}${key}=${value}`;
isFirst = false;
}
return requestGet(url);
};
const requestGet = (url: string, config = {}) => {
return request.get(url, config);
};
const requestPost = (url: string, data: any = {}, config: any = {}) => {
return request.post(url, data, config);
};
const requestPut = (url: string, data: any = {}, config: any = {}) => {
return request.put(url, data, config);
};
/**
* 根据Ai类型获取线索图片
* @param { }
* @returns
*/
const workSpaceId = localStorage.getItem('workspace_id')!;
export const getAllClueByAiType = async (data: {}) => {
try {
let res = await request.get(`/task/api/v1/task/${workSpaceId}/getAllClueByAiType`, { params: data });
return res.data;
} catch (error) {
throw error;
}
};
/**
* 获取多个Ai类型配置
* @param { }
* @returns
*/
export const getAiConfigByaiTypes = async (data: {}) => {
try {
let res = await request.get(`/api/v1/ai/config/lists`, { params: data });
return res.data;
} catch (error) {
throw error;
}
};
/**
* 更新Ai类型配置
* @param { }
* @returns
*/
export const updateByAiType = async (AiType: any, data: {}) => {
try {
let res = await request.put(`/api/v1/ai/config/updateByAiType/${AiType}`, data);
return res.data;
} catch (error) {
throw error;
}
};
/**
* 分页查询AI航线列表
* @param { }
* @returns
*/
export const getWaylinePage = async (data: {}) => {
try {
let res = await request.get(`/api/v1/ai/config/wayline/getWaylinePage`, { params: data });
return res.data;
} catch (error) {
throw error;
}
};
/**
* 查询AI航线航点
* @param { }
* @returns
*/
export const getWaylinePoint = async (data: {}) => {
try {
let res = await request.get(`/api/v1/ai/config/wayline/getWaylinePoint`, { params: data });
return res.data;
} catch (error) {
throw error;
}
};
/**
* ai配置获取关联航线媒体文件
* @param { }
* @returns
*/
export const getMediaFilesByWayline = async (data: {}) => {
try {
let res = await request.get(`/api/v1/ai/config/wayline/getMediaFilesByWayline`, { params: data });
return res.data;
} catch (error) {
throw error;
}
};
/**
* 新增AI航线列表
* @param { }
* @returns
*/
export const saveConfigWayline = async (data: {}) => {
try {
let res = await request.post(`/api/v1/ai/config/wayline/saveConfigWayline`, data);
return res.data;
} catch (error) {
throw error;
}
};
/**
* 新增AI航点
* @param { }
* @returns
*/
export const saveWaylinePoint = async (data: {}) => {
try {
let res = await request.post(`/api/v1/ai/config/wayline/saveWaylinePoint`, data);
return res.data;
} catch (error) {
throw error;
}
};
/**
* ai配置航线点区域保存
* @param { }
* @returns
*/
export const saveWaylinePointArea = async (data: {}) => {
try {
let res = await request.post(`/api/v1/ai/config/wayline/saveWaylinePointArea`, data);
return res.data;
} catch (error) {
throw error;
}
};

130
src/api/chatAiRequest.ts Normal file
View File

@@ -0,0 +1,130 @@
/*
* @Date: 2024-12-21 17:41:07
* @LastEditors: jiangchao
* @LastEditTime: 2025-03-05 21:06:17
* @Description: 文件注释
* @Author: jiangchao
* @FilePath: \mars3dMap\src\api\chatAiRequest.ts
*/
import { ELocalStorageKey } from '@mars/types';
const controller = new AbortController();
function getAuthToken() {
return localStorage.getItem(ELocalStorageKey.Token);
}
function getAuthTokenNew() {
return localStorage.getItem(ELocalStorageKey.TokenNew);
}
function getTenantId() {
return localStorage.getItem('tenantId');
}
export class EventEmitter {
private events: Record<string, Function[]> = {};
on(event: string, callback: Function) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
once(event: string, callback: Function) {
const onceCallback = (...args: any[]) => {
callback(...args);
this.off(event, onceCallback); // 移除事件监听
};
this.on(event, onceCallback);
}
off(event: string, callback: Function) {
const callbacks = this.events[event];
if (callbacks) {
const index = callbacks.indexOf(callback);
if (index !== -1) {
callbacks.splice(index, 1);
}
}
}
emit(event: string, ...args: any[]) {
const callbacks = this.events[event];
if (callbacks) {
for (const callback of callbacks) {
callback(...args);
}
}
}
}
export class SSE extends EventEmitter{
data: {
value: any[];
};
dataState:number;
constructor() {
super();
this.data = {
value: []
};
}
connect(url: string,body:any) {
this.data.value = [];
this.dataState = 0;
this.connectSSE(url,body);
}
async connectSSE (url:string,body:any) {
try {
const response = await fetch(url,{
// signal: controller.signal,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-token':getAuthToken(),
'tenant-id':getTenantId(),
'Authorization':'Bearer ' + getAuthTokenNew()
},
body: JSON.stringify(body),
});
if (!response.body) {
throw new Error('ReadableStream not supported in this environment.');
}
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) {
console.info("done")
this.dataState = 1;
break;
}
buffer += decoder.decode(value, { stream: true });
let boundary = buffer.indexOf('\n');
console.info("boundary",boundary)
while (boundary !== -1) {
const line = buffer.slice(0, boundary).trim();
buffer = buffer.slice(boundary + 1);
if (line.startsWith('data:')) {
const jsonData = line.replace('data:', '');
try {
this.emit('message', jsonData);
this.data.value.push(jsonData);
} catch (e) {
}
}
boundary = buffer.indexOf('\n');
}
}
} catch (error) {
console.error('Error connecting to SSE:', error);
}
};
quxiao(){
controller.abort();
}
}

View File

@@ -0,0 +1,45 @@
import request from "../api/http/request"
const workspace_id = localStorage.getItem("workspace_id")
export const getClusterDevices = async () => {
try {
const response = await request.get(`/manage/api/v1/devices/${workspace_id}/devices`)
return response.data
} catch (error) {
console.error("获取集群设备失败:", error)
throw error
}
}
export const getClusterDevicesBySn = async (sn:string) => {
try {
const response = await request.get(`/manage/api/v1/devices/${workspace_id}/devices/${sn}`)
return response.data
} catch (error) {
console.error("获取集群设备失败:", error)
throw error
}
}
export const getDeviceCapacity = async (sn: string) => {
try {
const response = await request.get(
`/manage/api/v1/live/capacityBySn?deviceSn=${sn}`
)
return response.data
} catch (error) {
console.error("获取设备能力失败:", error)
throw error
}
}
export const startLiveStream = async (data: any) => {
try {
const response = await request.post(
`/manage/api/v1/live/streams/start`,
data
)
return response.data
} catch (error) {
console.error("启动直播流失败:", error)
throw error
}
}

18
src/api/d.ts/media.ts Normal file
View File

@@ -0,0 +1,18 @@
export interface IfileParams {
name?: string;
isStarred?: string;
isSendVr?: string;
file_name?: string;
remark?: string;
oldfolderId?: string;
folder?: string;
folderId?: string;
file_type?: string;
parentId?: string;
searchAll?: string;
keyWords?: string;
}
export interface IfolderTree {
id: string;
}

31
src/api/d.ts/task.ts Normal file
View File

@@ -0,0 +1,31 @@
/*
* @Author: gejia 905034981@qq.com
* @Date: 2023-11-17 16:32:08
* @LastEditors: gejia 905034981@qq.com
* @LastEditTime: 2023-11-21 09:59:21
* @FilePath: \redfly-web\src\api\d.ts\task.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { Dayjs } from 'dayjs';
export interface ItaskForm {
taskMode: string;
taskStartPoint: string;
taskStopPoint: string;
taskStartTime: Dayjs | number;
taskEndTime: Dayjs | number;
taskRiverbank: number;
taskName: string;
recognitionType: string;
[key: string]: any;
}
export interface IClueForm {
taskRiverbank: number;
clueType: number;
dangerLevel: number;
taskMode: string;
clueLocation: string;
deviceSn: string;
imageFiles: string;
videoFiles: string;
[key: string]: any;
}

266
src/api/daping.ts Normal file
View File

@@ -0,0 +1,266 @@
import moment from 'moment';
import request from '../api/http/request'
import requestModel from '../api/http/requestModel'
import axios from "axios"
const jsonByUrl = function(){
return function(obj:any ,isFirst:boolean = true){
let url = ""
for(let [key,value] of Object.entries(obj)){
url += `${(isFirst?"?":"&")}${key}=${value}`
}
return url
}
}
// Object.prototype.jsonByUrl = function(isFirst:boolean = true){
// let url = ""
// for(let [key,value] of Object.entries(this)){
// url += `${(isFirst?"?":"&")}${key}=${value}`
// }
// return url
// }
const getWorkspaceId = () =>{
return localStorage.getItem('workspace_id')
}
export const getAiLiveData = () => {
let url = `/aiIdentify/checkServices`;
return requestGet(url);
};
//图标列表分页
export const getJBIconList = () => {
let url = `/map/api/v1/icon/pageList?current_page=1&page_size=10000`;
return requestGet(url);
};
//修改AI类型图标
export const putToChangeAiIcon = async (param: any) => {
let url = `/map/api/v1/icon/update/aiIcon`;
const result = await request.put(url, param);
return result.data;
};
// 城市天气
export const getTianqiRequest = async (city: string = '江苏南京天气') => {
const apiUrl =
`/dji/weathernew/pc?query=${city}&srcid=4982`;
let responseData = await axios.get(apiUrl)
return responseData.data as any
}
// 飞行统计
export const getFlightData = async () => {
const apiUrl =
`/dashboard/api/v1/dashboard/${getWorkspaceId()}/getFightCount`;
let responseData = await requestGet(apiUrl)
return responseData.data as any
}
// 疑似次数
export const getClueData = async () => {
const query = {
'startTime': moment().subtract(100, 'days').valueOf(),
'endTime': moment().valueOf()
}
let apiUrl =
`/task/api/v1/task/${getWorkspaceId()}/getClueCount?1=1`;
if (query && query.startTime && query.endTime) {
for (let [queryKey, queryValue] of Object.entries(query)) {
apiUrl += `&${queryKey}=${queryValue}`
}
}
let responseData = await requestGet(apiUrl)
return responseData.data as any
}
// 具体线索
export const getSpClue = async () => {
const query = {
'startTime': moment().subtract(7, 'days').valueOf(),
'endTime': moment().valueOf()
}
let apiUrl =
`/dashboard/api/v1/dashboard/${getWorkspaceId()}/getTaskClueList?1=1`
if (query && query.startTime && query.endTime) {
for (let [queryKey, queryValue] of Object.entries(query)) {
apiUrl += `&${queryKey}=${queryValue}`
}
}
let responseData = await requestGet(apiUrl)
return responseData.data as any
}
// 任务完成数
export const getTaskCount = async () => {
const apiUrl =
`/dashboard/api/v1/dashboard/${getWorkspaceId()}/getTaskCount`;
let responseData = await requestGet(apiUrl)
return responseData.data as any
}
// 今日巡检人员
export const getPatrol = async () => {
const apiUrl =
`/dashboard/api/v1/dashboard/${getWorkspaceId()}/getStaffOnline`;
let responseData = await requestGet(apiUrl)
return responseData.data as any
}
// 巡逻次数
export const getPatrolCount = async () => {
const apiUrl =
`/dashboard/api/v1/dashboard/${getWorkspaceId()}/getPatrolCount`;
let responseData = await requestGet(apiUrl)
return responseData.data as any
}
// 获取线索详情
export const getClueDetails = async (clueId: string) => {
const apiUrl =
`/task/api/v1/task/${getWorkspaceId()}/${clueId}/get`
let responseData = await requestGet(apiUrl)
return responseData.data as any
}
// 获取人员情况
export const getStaffInfo = async (clueId: string) => {
const apiUrl =
`/dashboard/api/v1/dashboard/${getWorkspaceId()}/${clueId}/getStaffByClue`
let responseData = await requestGet(apiUrl)
return responseData.data as any
}
export const getAddress = (location:string) => {
let url = `/task/api/v1/task/getAddress`
return request.get(url,{
params:{
location
}
})
}
// 获取全部线索列表
export const getAllClue = async (pageOptions: { size: number | string; current: number | string }) => {
const query = {
'startTime': moment().subtract(30, 'days').valueOf(),
'endTime': moment().valueOf(),
'flag': 1,
...pageOptions
}
let apiUrl =
`/task/api/v1/task/${getWorkspaceId()}/getAllClue`;
if (query && query.startTime && query.endTime) {
let isFirst = true
for (let [queryKey, queryValue] of Object.entries(query)) {
apiUrl += `${isFirst?'?':'&'}${queryKey}=${queryValue}`
isFirst = false
}
}
let responseData = await requestGet(apiUrl)
return responseData.data as any
}
// 获取二维码
export const QRcode = async (clueId: string) => {
const apiUrl = `/vx/task/1/${clueId}`
let responseData = await requestPost(apiUrl)
return responseData.data as any
}
// 执行情况
export const getCommentList = async (clueId: string) => {
const apiUrl = `/vx/${clueId}/getCommentList`
let responseData = await requestGet(apiUrl)
return responseData.data as any
}
// 获取设备总数
export const getDevicesCount = async () => {
const apiUrl =
`/dashboard/api/v1/dashboard/${getWorkspaceId()}/getDevicesCount`;
let responseData = await requestGet(apiUrl)
return responseData.data as any
}
// 获取在线设备数
export const getOnlineCount = async () => {
const apiUrl =
`/dashboard/api/v1/dashboard/${getWorkspaceId()}/getDevicesOnlineCount`;
let responseData = await requestGet(apiUrl)
return responseData.data as any
}
// 获取在线飞手
export const getFlightUserCount = async () => {
const apiUrl =
`/dashboard/api/v1/dashboard/${getWorkspaceId()}/getFlightUserCount`;
let responseData = await requestGet(apiUrl)
return responseData.data as any
}
// 获取ai直播流
export const getAiLive = async (data:any) => {
let apiUrl =
`/aiIdentify/AIRtmp`
// apiUrl += data.jsonByUrl(true)
let dataJson = jsonByUrl()
let datastring = dataJson(data)
apiUrl += datastring
let responseData = await requestGet(apiUrl)
return responseData.data as any
}
// 获取线索关联人员
export const getClueStaff = async (clueId: string) => {
const apiUrl =
`/task/api/v1/task/${getWorkspaceId()}/${clueId}/users`;
let responseData = await requestGet(apiUrl)
return responseData.data as any
}
export const getConfigData = (id:string,tenId:string,w:string) => {
const apiUrl = `/dji/model/${w}/file/model-config`
return requestModel.get(apiUrl,{
params:{
id
},
headers:{
"non-auth": "11111",
"tenant-id":tenId,
}
})
}
export const saveClueData = async (data) => {
const url = `/task/api/v1/task/${getWorkspaceId()}/drawing`
return request.post(url,data)
}
export const getConfigInfo = (url) => {
const apiUrl = url
return requestModel.get(apiUrl)
}
const requestGet = (url: string) => {
return request.get(url)
}
const requestPost = (url: string) => {
return request.post(url)
}

View File

@@ -0,0 +1,23 @@
import request, { IWorkspaceResponse } from '/@/api/http/request'
import { DeviceCmd, DeviceCmdItemAction } from '/@/types/device-cmd'
const CMD_API_PREFIX = '/control/api/v1'
export interface SendCmdParams {
dock_sn: string, // 机场cn
device_cmd: DeviceCmd // 指令
}
export interface PostSendCmdBody {
action: DeviceCmdItemAction
}
/**
* 发送机场控制指令
* @param params
* @returns
*/
// /control/api/v1/devices/{dock_sn}/jobs/{service_identifier}
export async function postSendCmd (params: SendCmdParams, body?: PostSendCmdBody): Promise<IWorkspaceResponse<{}>> {
const resp = await request.post(`${CMD_API_PREFIX}/devices/${params.dock_sn}/jobs/${params.device_cmd}`, body)
return resp.data
}

View File

@@ -0,0 +1,62 @@
import { type } from 'os'
import request, { IWorkspaceResponse } from '/@/api/http/request'
import { ELocalStorageKey } from '/@/types'
import { NightLightsStateEnum, DistanceLimitStatus, ObstacleAvoidance } from '/@/types/device-setting'
const MNG_API_PREFIX = '/manage/api/v1'
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
export interface PutDevicePropsBody {
night_lights_state?: NightLightsStateEnum;// 夜航灯开关
height_limit?: number;// 限高设置
distance_limit_status?: DistanceLimitStatus;// 限远开关
obstacle_avoidance?: ObstacleAvoidance;// 飞行器避障开关设置
thermal_current_palette_style?:thermalCurrentPaletteStyle
}
type ThermalCurrentPaletteStyleD = thermalCurrentPaletteStyleEnum
export enum thermalCurrentPaletteStyleEnum {
= 0,
,
,
,
"彩虹 1" = 5,
=6,
=8,
=11,
,
"彩虹 2",
}
export type Shermal = keyof typeof thermalCurrentPaletteStyleEnum;
type ShermalList = typeof thermalCurrentPaletteStyleEnum;
type thermalCurrentPaletteStyle = {
payload_index:string
thermal_current_palette_style:ThermalCurrentPaletteStyleD
}
// let a:thermalCurrentPaletteStyle
// a.thermal_current_palette_style = thermalCurrentPaletteStyleEnum['白热']
// {"0":"白热","1":"黑热","2":"描红","3":"医疗","5":"彩虹 1","6":"铁红","8":"北极","11":"熔岩","12":"热铁","13":"彩虹 2"}
// {
//     "thermal_current_palette_style": {
//         "payload_index":"53-0-0",
//         "thermal_current_palette_style": 6
//     }
// }
/**
* 设置设备属性
* @param params
* @returns
*/
// /manage/api/v1/devices/{{workspace_id}}/devices/{{device_sn}}/property
export async function putDeviceProps (deviceSn: string, body: PutDevicePropsBody): Promise<IWorkspaceResponse<{}>> {
const resp = await request.put(`${MNG_API_PREFIX}/devices/${workspaceId}/devices/${deviceSn}/property`, body)
return resp.data
}

89
src/api/drc.ts Normal file
View File

@@ -0,0 +1,89 @@
/*
* @Author: gejia 905034981@qq.com
* @Date: 2024-08-12 10:34:21
* @LastEditors: gejia 905034981@qq.com
* @LastEditTime: 2025-01-17 11:05:43
* @FilePath: \redfly-web\src\api\drc.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import request, { IWorkspaceResponse } from '/@/api/http/request';
import { ELocalStorageKey } from '/@/types';
import { DroneControlProtocol } from '/@/types/drc';
// DRC 链路
const DRC_API_PREFIX = '/control/api/v1';
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '';
export interface PostDrcBody {
client_id?: string; // token过期时用于续期则必填
expire_sec?: number; // 过期时间单位秒默认3600
}
export interface DrcParams {
address: string;
username: string;
password: string;
client_id: string;
expire_time: number; // 过期时间
enable_tls: boolean; // 是否开启tls
}
// 获取 mqtt 连接认证
export async function postDrc(body: PostDrcBody): Promise<IWorkspaceResponse<DrcParams>> {
const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/connect`, body);
return resp.data;
}
export interface DrcEnterBody {
client_id?: string;
clientId?: string;
dock_sn: string;
expire_sec?: number; // 过期时间单位秒默认3600
device_info?: {
osd_frequency?: number;
hsi_frequency?: number;
};
}
export interface DrcEnterResp {
sub: string[]; // 需要订阅接收的topic
pub: string[]; // 推送的topic地址
}
// 进入飞行控制 建立drc连接&获取云控控制权)
export async function postDrcEnter(body: DrcEnterBody): Promise<IWorkspaceResponse<DrcEnterResp>> {
const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/enter`, body);
return resp.data;
}
export interface DrcExitBody {
client_id: string;
dock_sn: string;
}
// 退出飞行控制 退出drc连接&退出云控控制权)
export async function postDrcExit(body: DrcExitBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/exit`, body);
return resp.data;
}
export interface ControlParams extends DroneControlProtocol {
dock_sn?: string | number;
}
//飞行控制
export async function postDroneControlDown(body: ControlParams): Promise<IWorkspaceResponse<DrcEnterResp>> {
const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/drone-control/down`, body);
return resp.data;
}
//drc紧急降落
export async function postEmergencyLandingDown(body: { gateway_sn: string }): Promise<IWorkspaceResponse<DrcEnterResp>> {
const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/emergency_landing/down`, body);
return resp.data;
}
//drc强制降落
export async function postForceLandingDown(body: { gateway_sn: string }): Promise<IWorkspaceResponse<DrcEnterResp>> {
const resp = await request.post(`${DRC_API_PREFIX}/workspaces/${workspaceId}/drc/force_landing/down`, body);
return resp.data;
}

View File

@@ -0,0 +1,108 @@
/*
* @Author: gejia 905034981@qq.com
* @Date: 2024-01-09 11:15:56
* @LastEditors: gejia 905034981@qq.com
* @LastEditTime: 2024-03-06 11:48:32
* @FilePath: \redfly-web\src\api\drone-control\drone.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import request, { IWorkspaceResponse } from '/@/api/http/request';
// import { ELocalStorageKey } from '/@/types'
const API_PREFIX = '/control/api/v1';
// const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '
// 获取飞行控制权
export async function postFlightAuth(sn: string): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/authority/flight`);
return resp.data;
}
export enum WaylineLostControlActionInCommandFlight {
CONTINUE = 0,
EXEC_LOST_ACTION = 1,
}
export enum LostControlActionInCommandFLight {
HOVER = 0, // 悬停
Land = 1, // 着陆
RETURN_HOME = 2, // 返航
}
export enum ERthMode {
SMART = 0,
SETTING = 1,
}
export enum ECommanderModeLostAction {
CONTINUE = 0,
EXEC_LOST_ACTION = 1,
}
export enum ECommanderFlightMode {
SMART = 0,
SETTING = 1,
}
export interface PointBody {
latitude: number;
longitude: number;
height: number;
}
export interface PostFlyToPointBody {
max_speed: number;
points: PointBody[];
}
// 飞向目标点
export async function postFlyToPoint(sn: string, body: PostFlyToPointBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/jobs/fly-to-point`, body);
return resp.data;
}
// 停止飞向目标点
export async function deleteFlyToPoint(sn: string): Promise<IWorkspaceResponse<null>> {
const resp = await request.delete(`${API_PREFIX}/devices/${sn}/jobs/fly-to-point`);
return resp.data;
}
const workSpaceId = localStorage.getItem('workspace_id')!;
// 急停
export async function emergencyStop(sn: string): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/workspaces/${workSpaceId}/drc/drone-emergency-stop/down`, { dock_sn: sn });
return resp.data;
}
/////////////////////////////////////环绕飞行
//Poi Mode Enter
export async function postPoiModeEnter(sn: string, body: PointBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/poi/poi-mode-enter`, body);
return resp.data;
}
//Poi Mode Exit
export async function postPoiModeExit(sn: string): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/poi/poi-mode-exit`);
return resp.data;
}
//Poi Circle Speed Set
export async function postPoiCircleSpeedSet(sn: string, body: { circle_speed: string }): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/poi/poi-circle-speed-set`, body);
return resp.data;
}
export interface PostTakeoffToPointBody {
target_height: number;
target_latitude: number;
target_longitude: number;
security_takeoff_height: number; // 安全起飞高
max_speed: number; // flyto过程中能达到的最大速度, 单位m/s 跟飞机档位有关
rc_lost_action: LostControlActionInCommandFLight; // 失控行为
rth_altitude: number; // 返航高度
exit_wayline_when_rc_lost: WaylineLostControlActionInCommandFlight;
rth_mode: ERthMode;
commander_mode_lost_action: ECommanderModeLostAction;
commander_flight_mode: ECommanderFlightMode;
commander_flight_height: number;
flight_id?: string;
}
// 一键起飞
export async function postTakeoffToPoint(sn: string, body: PostTakeoffToPointBody): Promise<IWorkspaceResponse<null>> {
// body.flight_id = "656f5d27-d804-4e9e-8c5e-09ee920bb52e"
const resp = await request.post(`${API_PREFIX}/devices/${sn}/jobs/takeoff-to-point`, body);
return resp.data;
}

View File

@@ -0,0 +1,177 @@
import request, { IWorkspaceResponse } from '/@/api/http/request';
import { CameraType, CameraMode } from '/@/types/live-stream';
import { GimbalResetMode } from '/@/types/drone-control';
import { ELocalStorageKey } from '/@/types';
const API_PREFIX = '/control/api/v1';
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || '';
export interface PostPayloadAuthBody {
payload_index: string;
}
// 获取负载控制权
export async function postPayloadAuth(sn: string, body: PostPayloadAuthBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/authority/payload`, body);
return resp.data;
}
export interface saveStorageSetBody {
cmd: string;
data: {
payload_index: string;
photo_storage_settings?: string[];
video_storage_settings?: string[];
};
}
//拍照存储设置
export async function saveStorageSet(sn: string, body: saveStorageSetBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/payload/commands`, body);
return resp.data;
}
//查看图像存储
export async function getPhotoStorage(dock_sn: string): Promise<IWorkspaceResponse<any>> {
const resp = await request.get(`/device-set/storage/photo?dock_sn=` + dock_sn);
return resp.data;
}
//查看视频存储
export async function getVideoStorage(dock_sn: string): Promise<IWorkspaceResponse<any>> {
const resp = await request.get(`/device-set/storage/video?dock_sn=` + dock_sn);
return resp.data;
}
//查看机场的返回设置
export async function getBackByDeviceSn(dock_sn: string): Promise<IWorkspaceResponse<any>> {
const resp = await request.get(`/device-config/${workspaceId}/by-deviceSn?device_sn=` + dock_sn);
return resp.data;
}
//查看
export async function saveBackByDeviceSn(body: any): Promise<IWorkspaceResponse<any>> {
const resp = await request.post(`/device-config/${workspaceId}/edit`, body);
return resp.data;
}
//根据飞机SN查询飞机最后位置
export async function getFlightAircraft(drone_sn: string, tenant_id: string): Promise<IWorkspaceResponse<any>> {
const resp = await request.get(`/flight-aircraft/query?drone_sn=${drone_sn}&tenant_id=${tenant_id}`);
return resp.data;
}
export interface ISaveResourceStorage {
dock_sn: string;
storage_settings_enum: string[];
type: number;
}
//图像、视频设置
export async function setResourceStorage(body: ISaveResourceStorage): Promise<IWorkspaceResponse<any>> {
const resp = await request.post(`/device-set/storage/video/set`, body);
return resp.data;
}
//视频存储设置
// TODO: 画面拖动控制
export enum PayloadCommandsEnum {
CameraModeSwitch = 'camera_mode_switch',
CameraPhotoTake = 'camera_photo_take',
CameraRecordingStart = 'camera_recording_start',
CameraRecordingStop = 'camera_recording_stop',
CameraFocalLengthSet = 'camera_focal_length_set',
CameraScreenSplit = 'camera_screen_split',
GimbalReset = 'gimbal_reset',
CameraAim = 'camera_aim',
PoiModeEnter = 'poi_mode_enter',
PoiModeExit = 'poi_mode_exit',
PoiCircleSpeedSet = 'poi_circle_speed_set',
CameraFrameZoom = 'camera_frame_zoom',
}
export interface PostCameraModeBody {
payload_index: string;
camera_mode: CameraMode;
}
export interface PostCameraPhotoBody {
payload_index: string;
}
export interface PostCameraRecordingBody {
payload_index: string;
}
export interface DeleteCameraRecordingParams {
payload_index: string;
}
export interface PostCameraFocalLengthBody {
payload_index: string;
camera_type: CameraType;
zoom_factor: number;
}
export interface PostGimbalResetBody {
payload_index: string;
reset_mode: GimbalResetMode;
}
export interface PostCameraScreenSplitBody {
payload_index: string;
enable: boolean;
}
export interface PostCameraAimBody {
payload_index: string;
camera_type: CameraType;
locked: boolean;
x: number;
y: number;
}
export interface PostCameraFrameZoomBody {
payload_index: string;
camera_type: 'ir' | 'wide' | 'zoom';
locked: boolean;
x: number;
y: number;
width: number;
height: number;
}
export type PostPayloadCommandsBody =
| {
cmd: PayloadCommandsEnum.CameraModeSwitch;
data: PostCameraModeBody;
}
| {
cmd: PayloadCommandsEnum.CameraPhotoTake;
data: PostCameraPhotoBody;
}
| {
cmd: PayloadCommandsEnum.CameraRecordingStart;
data: PostCameraRecordingBody;
}
| {
cmd: PayloadCommandsEnum.CameraRecordingStop;
data: DeleteCameraRecordingParams;
}
| {
cmd: PayloadCommandsEnum.CameraFocalLengthSet;
data: PostCameraFocalLengthBody;
}
| {
cmd: PayloadCommandsEnum.GimbalReset;
data: PostGimbalResetBody;
}
| {
cmd: PayloadCommandsEnum.CameraScreenSplit;
data: PostCameraScreenSplitBody;
}
| {
cmd: PayloadCommandsEnum.CameraAim;
data: PostCameraAimBody;
}
| {
cmd: PayloadCommandsEnum.CameraFrameZoom;
data: PostCameraFrameZoomBody;
};
// 发送负载名称
export async function postPayloadCommands(sn: string, body: PostPayloadCommandsBody): Promise<IWorkspaceResponse<null>> {
const resp = await request.post(`${API_PREFIX}/devices/${sn}/payload/commands`, body);
return resp.data;
}

174
src/api/homeShow/index.js Normal file
View File

@@ -0,0 +1,174 @@
import req from '/@/api/http/request'
let workspace_id = localStorage.getItem('workspace_id')
//获取无人机列表getDeviceList
export const getDeviceList = async (domain) => {
let res = await req.get(`/manage/api/v1/devices/${workspace_id}/devices/bound`,{params:{
page:1,
page_size:1000,
domain,
}})
return res.data
}
//查询所有对应装备视频数据
export const getDeviceSnTypeList = async (type) => {
let res = await req.get(`/api/v1/missilepatrol-events/deviceSn/type/list`,{params:{
ue_device_type:type
}})
return res.data
}
// 新增获取无人装备列表MySQL存储
export const getUeDeviceList = async (domain) => {
let res = await req.get(`/manage/api/v1/devices/${workspace_id}/ue-devices`, {
params: {
domain,
page: 1,
page_size: 1000
}
});
return res.data;
};
// 新增:创建无人装备
export const createUeDevice = async (deviceData) => {
let res = await req.post(`/manage/api/v1/devices/${workspace_id}/ue-device/create`, deviceData);
return res.data;
};
// GK数据查询接口
export const getGKTaskList = async (params) => {
let res = await req.post(`/api/v1/national-science/tasks/query`, params);
return res.data;
};
// ==================== 建模管理相关接口 ====================
// 上传模型文件
export const uploadModelFile = async (file, workspaceId, description) => {
const formData = new FormData();
formData.append('file', file);
formData.append('workspaceId', workspaceId);
if (description) {
formData.append('description', description);
}
let res = await req.post('/dji/api/modeling/models/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
return res.data;
};
// 获取模型文件列表
export const getModelList = async (params = {}) => {
const {
page = 1,
pageSize = 10,
fileType = 'all',
workspaceId = workspace_id
} = params;
let res = await req.get('/dji/api/modeling/models', {
params: {
page,
pageSize,
fileType,
workspaceId
}
});
return res.data;
};
// 删除模型文件
export const deleteModelFile = async (id, workspaceId = workspace_id) => {
let res = await req.delete(`/dji/api/modeling/models/${id}`, {
params: {
workspaceId
}
});
return res.data;
};
// 获取模型下载URL
export const getModelDownloadUrl = async (id, workspaceId = workspace_id) => {
let res = await req.get(`/dji/api/modeling/models/${id}/download`, {
params: {
workspaceId
}
});
return res.data;
};
// 直接下载模型文件(二进制,带鉴权)
export const downloadModelFile = async (id, workspaceId = workspace_id) => {
return await req.get(`/dji/api/modeling/models/${id}/download`, {
params: { workspaceId },
responseType: 'blob'
});
};
// 保存模型调整参数
export const saveModelAdjustments = async (modelId, adjustments, workspaceId = workspace_id) => {
let res = await req.post(`/dji/api/modeling/models/${modelId}/adjustments`, {
adjustments,
workspaceId
});
return res.data;
};
// 获取模型调整参数
export const getModelAdjustments = async (modelId, workspaceId = workspace_id) => {
let res = await req.get(`/dji/api/modeling/models/${modelId}/adjustments`, {
params: {
workspaceId
}
});
return res.data;
};
// 批量保存模型调整参数
export const batchSaveModelAdjustments = async (adjustmentsData, workspaceId = workspace_id) => {
let res = await req.post('/dji/api/modeling/models/batch-adjustments', {
adjustments: adjustmentsData,
workspaceId
});
return res.data;
};
// ==================== 态势数据相关接口 ====================
// 获取态势列表(我方态势/我方装备等)
export const getSituationList = async (payload = {}) => {
// 需要 header: x-auth-token、Authorization、tenant-id
// 这些已由全局请求拦截器在 headers 中统一注入
const defaultWorkspaceId = localStorage.getItem('workspace_id') || workspace_id;
const defaultTenantId = Number(localStorage.getItem('tenantId')) || undefined;
const body = {
target_type: 'DEVICE',
workspace_id: defaultWorkspaceId,
tenant_id: defaultTenantId,
...payload,
};
const res = await req.post(`/api/v1/situation/list`, body);
return res.data;
};
// ==================== 地图标绘相关接口 ====================
// 按标绘类型获取标绘列表markerType=1
export const getMarkerListByType = async (markerType = 0) => {
const defaultWorkspaceId = localStorage.getItem('workspace_id') || workspace_id;
const res = await req.get(`/map/api/v1/workspaces/${defaultWorkspaceId}/listByMarkerType`, {
params: { markerType }
});
return res.data;
};

51
src/api/http/config.ts Normal file
View File

@@ -0,0 +1,51 @@
export const CURRENT_CONFIG = {
// license
appId: '126079', // You need to go to the development website to apply.
appKey: '8d44f9bb14cacb64b445c8a9dd3f643', // You need to go to the development website to apply.
appLicense: 'vX017uRJnpAiICbZKI0etZCsgjx4Rd6zFsR2Iaui9qWws/6v7IDRNyWjk5gTQwh/Reo4E1A4cJdSKaeqqsUa+wGGcsuXgTne/3MTT5y3mCLdfvy9SqdOot2CSZyH/i4xpXkbb/8Dar+IYfYAEWP0kNjL9KjSolrZjH2tWJnLDLs=', // You need to go to the development website to apply.
// // http
// baseURL: 'https://jw.redfly.tech/vxapi/',
// websocketURL: 'wss://js.redfly.tech/vxapi/api/v1/ws',
// baseURL: 'http://192.168.3.228:6791', // This url must end with "/". Example: 'http://192.168.1.1:6789/'
// baseURL: 'http://192.168.8.104:6791/dji',
// baseURL: 'http://192.168.3.98:6791/dji',
baseURL: 'https://uav3.redfly.tech/dji',
// baseURL: 'http://192.168.3.4:6791',
// websocketURL: 'ws://121.43.121.94:6791/api/v1/ws',
// websocketURL: 'ws://127.0.0.1:8080/dji/api/v1/ws',
websocketURL: 'wss://uav3.redfly.tech/dji/api/v1/ws',
// baseURL: 'http://192.168.3.145:6789/', // This url must end with "/". Example: 'http://192.168.1.1:6789/'
// websocketURL: 'ws://192.168.3.20:6791/api/v1/ws', // Example: 'ws://192.168.1.1:6789/api/v1/ws'
// livestreaming
// RTMP Note: This IP is the address of the streaming server. If you want to see livestream on web page, you need to convert the RTMP stream to WebRTC stream.
rtmpURL: 'rtmp://127.0.0.1/live/', // Example: 'rtmp://192.168.1.1/live/'
// GB28181 Note:If you don't know what these parameters mean, you can go to Pilot2 and select the GB28181 page in the cloud platform. Where the parameters same as these parameters.
gbServerIp: 'Please enter the server ip.', // SIP服务器地址
gbServerPort: 'Please enter the server port.', // SIP服务器端口
gbServerId: 'Please enter the server id.', // 服务器编号
gbAgentId: 'Please enter the agent id', // 设备编号视频终端的设备国际编号20位数字
gbPassword: 'Please enter the agent password', // 注册密码
gbAgentPort: 'Please enter the local port.', // SIP本地端口
gbAgentChannel: 'Please enter the channel.', // 本地通道视频通道编号20位数字与设备编号保持一致
// RTSP
rtspUserName: 'Please enter the username.',
rtspPassword: 'Please enter the password.',
rtspPort: '8554',
// Agora 0c0e0966bb694f808284606e9fdb8f48
// 0c0e0966bb694f808284606e9fdb8f48
agoraAPPID: '0c0e0966bb694f808284606e9fdb8f48',
agoraToken: '007eJxTYPjD6Jt2MoR50nKpo7bXJnl0XVdyv7qlWMqakb/HmONQvJYCg0GyQaqBpZlZUpKZpUmahYGFkYWJmYFZqmVaSpJFmolFy879yQ2BjAypb8qZGRkgEMRnZUjOyS9NYWAAAK5jHbk=',
agoraChannel: 'cloud',
// map
// You can apply on the AMap website.
amapKey: 'd1ea9f84e9c19583e36dc4dbd135d264',
}

207
src/api/http/request.ts Normal file
View File

@@ -0,0 +1,207 @@
import axios from 'axios';
import { uuidv4 } from '/@/utils/uuid';
import { CURRENT_CONFIG } from './config';
import { message } from 'ant-design-vue';
// import router from '/@/router';
import { ELocalStorageKey, ERouterName, EUserType } from '/@/types/enums';
export * from './type'
const REQUEST_ID = 'X-Request-Id';
function getAuthToken() {
return localStorage.getItem(ELocalStorageKey.Token);
}
function getAuthTokenNew() {
return localStorage.getItem(ELocalStorageKey.TokenNew);
}
function getTenantId() {
return localStorage.getItem('tenantId');
}
const instance = axios.create({
// withCredentials: true,
// headers: {
// 'Content-Type': 'application/json',
// },
// timeout: 12000,
});
const loginData: any = {
'user/setNotify': {
value: 'saas',
isShowToken: true,
},
'get-id-by-name': {
value: 'saas',
isShowToken: false,
},
'tenant/get?': {
value: 'saas',
isShowToken: true,
},
'mqtt/login': {
value: 'saas',
isShowToken: false,
},
'auth/login': {
value: 'saas',
isShowtenantId: true,
isShowToken: false,
},
'role/page': {
value: 'saas',
isShowToken: true,
},
'dept/list': {
value: 'saas',
isShowToken: true,
},
};
instance.interceptors.request.use(
(config) => {
config.maxContentLength = 1024 * 1024 * 1024 * 1024;
if (!config.headers['Content-Type']) {
config.headers['Content-Type'] = 'application/json';
// config.headers["Access-Control-Allow-Origin"]= "*"
}
config.headers['Authorization'] = 'Bearer ' + getAuthTokenNew();
let baseUrl = '/dji';
let isShowSass = false;
for (let l in loginData) {
let ele = loginData[l];
if (config.url && config.url.includes(`/${l}`)) {
isShowSass = true;
if (ele.isShowToken) {
config.headers[ELocalStorageKey.Token] = getAuthToken();
config.headers['tenant-id'] = getTenantId();
config.headers['Authorization'] = 'Bearer ' + getAuthTokenNew();
} else {
if (!ele.isShowtenantId) {
delete config.headers['tenant-id'];
}
delete config.headers[ELocalStorageKey.Token];
delete config.headers['Authorization'];
}
// baseUrl = `${CURRENT_CONFIG.baseURL}${ele.value}`
if (!CURRENT_CONFIG.baseURL.includes('dji')) {
baseUrl = CURRENT_CONFIG.baseURL + '/dji';
}
baseUrl = baseUrl.replace('dji', `${ele.value}`);
if (baseUrl.includes('192.168')) {
baseUrl = `https://lssl.web.redfly.tech/saas`;
}
}
}
if (!isShowSass) {
if (baseUrl == CURRENT_CONFIG.baseURL) {
config.headers['tenant-id'] = getTenantId();
config.headers['Authorization'] = 'Bearer ' + getAuthTokenNew();
config.headers[ELocalStorageKey.Token] = getAuthToken();
baseUrl = CURRENT_CONFIG.baseURL;
} else {
config.headers['tenant-id'] = getTenantId();
config.headers['Authorization'] = 'Bearer ' + getAuthTokenNew();
config.headers[ELocalStorageKey.Token] = getAuthToken();
}
}
// /weathernew
if (config.url.includes('/system/tenant/get-id-by-name')) {
delete config.headers['tenant-id'];
}
// if (config.url.includes('/weathernew')) {
// baseUrl = 'http://weathernew.pae.baidu.com';
// }
config.baseURL = baseUrl;
return config;
},
(error) => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
(response: AxiosResponse) => {
if (response.data.code == 401 || response.data.code == 501) {
const flag: number = Number(localStorage.getItem(ELocalStorageKey.Flag));
console.log('requestflag', flag);
switch (flag) {
// case EUserType.Web:
// router.push(ERouterName.PROJECT);
// break;
// case EUserType.Pilot:
// router.push(ERouterName.PILOT);
// break;
// default:
// router.push(ERouterName.PROJECT);
// break;
}
}
if (response.data.code && response.data.code !== 0) {
if (response.data && response.data.message) {
}
}
return response;
},
(err) => {
const requestId = err?.config?.headers && err?.config?.headers[REQUEST_ID];
if (requestId) {
console.info(REQUEST_ID, '', requestId);
}
console.info('url: ', err?.config?.url, `${err?.config?.method}\n>>>> err: `, err);
let description = '-';
if (err.response?.data && err.response.data.message) {
description = err.response.data.message;
}
if (err.response?.data && err.response.data.result) {
description = err.response.data.result.message;
}
// @See: https://github.com/axios/axios/issues/383
if (!err.response || !err.response.status) {
// message.error('网络异常,请检查后端服务,然后重试')
return;
}
if (err.response?.status !== 200) {
// message.error(`ERROR_CODE: ${err.response?.status}`)
}
// if (err.response?.status === 403) {
// // window.location.href = '/'
// }
if (err.response?.status === 401) {
console.info('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>', err);
// console.error(err.response?.data.message)
if (err.config.url.includes('/token/refresh')) {
// router.push(ERouterName.PILOT);
} else {
const flag: number = Number(localStorage.getItem(ELocalStorageKey.Flag));
console.info('flag', flag);
switch (flag) {
// case EUserType.Web:
// router.push(ERouterName.PROJECT);
// break;
// case EUserType.Pilot:
// router.push(ERouterName.PILOT);
// break;
// default:
// router.push(ERouterName.PROJECT);
// break;
}
}
}
return Promise.reject(err);
}
);
export default instance;

View File

@@ -0,0 +1,92 @@
/*
* @Date: 2024-08-07 14:36:15
* @LastEditors: jiangchao
* @LastEditTime: 2024-08-07 14:40:13
* @Description: 文件注释
* @Author: jiangchao
* @FilePath: \mars3d-vue-project\src\api\http\requestModel.ts
*/
import axios from 'axios';
import { uuidv4 } from '/@/utils/uuid';
import { CURRENT_CONFIG } from './config';
import { message } from 'ant-design-vue';
// import router from '/@/router';
import { ELocalStorageKey, ERouterName, EUserType } from '/@/types/enums';
const instance = axios.create({
// withCredentials: true,
// headers: {
// 'Content-Type': 'application/json',
// },
// timeout: 12000,
});
instance.interceptors.request.use(
(config) => {
return config;
},
(error) => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
(response) => {
if (response.data.code == 401 || response.data.code == 501) {
const flag: number = Number(localStorage.getItem(ELocalStorageKey.Flag));
console.log('requestflag', flag);
switch (flag) {
}
}
if (response.data.code && response.data.code !== 0) {
if (response.data && response.data.message) {
}
}
return response;
},
(err) => {
// const requestId = err?.config?.headers && err?.config?.headers[REQUEST_ID];
// if (requestId) {
// console.info(REQUEST_ID, '', requestId);
// }
console.info('url: ', err?.config?.url, `${err?.config?.method}\n>>>> err: `, err);
let description = '-';
if (err.response?.data && err.response.data.message) {
description = err.response.data.message;
}
if (err.response?.data && err.response.data.result) {
description = err.response.data.result.message;
}
// @See: https://github.com/axios/axios/issues/383
if (!err.response || !err.response.status) {
// message.error('网络异常,请检查后端服务,然后重试')
return;
}
if (err.response?.status !== 200) {
// message.error(`ERROR_CODE: ${err.response?.status}`)
}
// if (err.response?.status === 403) {
// // window.location.href = '/'
// }
if (err.response?.status === 401) {
// console.error(err.response?.data.message)
if (err.config.url.includes('/token/refresh')) {
// router.push(ERouterName.PILOT);
} else {
const flag: number = Number(localStorage.getItem(ELocalStorageKey.Flag));
switch (flag) {
}
}
}
return Promise.reject(err);
}
);
export default instance;

110
src/api/http/requestsaas.ts Normal file
View File

@@ -0,0 +1,110 @@
import axios from 'axios';
// import router from '/@/router';
import { ELocalStorageKey, ERouterName, EUserType } from '/@/types/enums';
export * from './type'
const REQUEST_ID = 'X-Request-Id';
function getAuthToken() {
return localStorage.getItem(ELocalStorageKey.Token);
}
function getAuthTokenNew() {
return localStorage.getItem(ELocalStorageKey.TokenNew);
}
function getTenantId() {
return localStorage.getItem('tenantId');
}
const instance = axios.create({
});
instance.interceptors.request.use(
(config) => {
config.maxContentLength = 1024 * 1024 * 1024 * 1024;
if (!config.headers['Content-Type']) {
config.headers['Content-Type'] = 'application/json';
// config.headers["Access-Control-Allow-Origin"]= "*"
}
config.headers['tenant-id'] = getTenantId();
config.headers['Authorization'] = 'Bearer ' + getAuthTokenNew();
config.headers[ELocalStorageKey.Token] = getAuthToken();
config.baseURL = "/saas";
return config;
},
(error) => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
(response) => {
if (response.data.code == 401 || response.data.code == 501) {
const flag: number = Number(localStorage.getItem(ELocalStorageKey.Flag));
console.log('requestflag', flag);
switch (flag) {
}
}
if (response.data.code && response.data.code !== 0) {
if (response.data && response.data.message) {
}
}
return response;
},
(err) => {
const requestId = err?.config?.headers && err?.config?.headers[REQUEST_ID];
if (requestId) {
console.info(REQUEST_ID, '', requestId);
}
console.info('url: ', err?.config?.url, `${err?.config?.method}\n>>>> err: `, err);
let description = '-';
if (err.response?.data && err.response.data.message) {
description = err.response.data.message;
}
if (err.response?.data && err.response.data.result) {
description = err.response.data.result.message;
}
// @See: https://github.com/axios/axios/issues/383
if (!err.response || !err.response.status) {
// message.error('网络异常,请检查后端服务,然后重试')
return;
}
if (err.response?.status !== 200) {
// message.error(`ERROR_CODE: ${err.response?.status}`)
}
// if (err.response?.status === 403) {
// // window.location.href = '/'
// }
if (err.response?.status === 401) {
console.info('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>', err);
// console.error(err.response?.data.message)
if (err.config.url.includes('/token/refresh')) {
// router.push(ERouterName.PILOT);
} else {
const flag: number = Number(localStorage.getItem(ELocalStorageKey.Flag));
console.info('flag', flag);
switch (flag) {
// case EUserType.Web:
// router.push(ERouterName.PROJECT);
// break;
// case EUserType.Pilot:
// router.push(ERouterName.PILOT);
// break;
// default:
// router.push(ERouterName.PROJECT);
// break;
}
}
}
return Promise.reject(err);
}
);
export default instance;

89
src/api/http/type.ts Normal file
View File

@@ -0,0 +1,89 @@
/*
* @Date: 2024-01-20 18:36:24
* @LastEditors: jiangchao
* @LastEditTime: 2025-03-12 09:51:45
* @Description: 文件注释
* @Author: jiangchao
* @FilePath: \mars3dMap\src\api\http\type.ts
*/
// 固件升级状态
export enum DeviceFirmwareStatusEnum {
None = 1, // 无需升级
ToUpgraded = 2, // 待升级
ConsistencyUpgrade = 3, // 一致性升级
DuringUpgrade = 4, // 升级中
}
export interface Device {
key: string;
device_name: string;
device_sn: string;
nickname: string;
firmware_version: string;
firmware_status: DeviceFirmwareStatusEnum;
status: string;
workspace_name: string;
bound_time: string;
login_time: string;
children?: Device[];
domain: number;
type: number;
firmware_progress?: string; // 升级进度
}
export interface IPage {
page: number;
total?: number;
page_size: number;
}
export interface IListWorkspaceResponse<T> {
// value: import("d:/flyRed/redfly-web/src/types/device").Device[];
value:Device;
code: number;
message: string;
data: {
list: T[];
pagination: IPage;
};
}
// Workspace
export interface IWorkspaceResponse<T> {
code: number;
data: T;
message: string;
}
export interface IResult {
code: number;
message: string;
}
export interface IDeviceResponse {
total: number
size: number
current: number,
pages: number
}
export interface IRecordsResponse<T> extends IResult {
data: IDeviceResponse & {
records: T[];
};
}
export type IStatus = 'WAITING' | 'DOING' | 'SUCCESS' | 'FAILED';
export interface CommonListResponse<T> extends IResult {
data: {
list: T[];
pagination: IPage;
};
}
export interface CommonResponse<T> extends IResult {
data: T
}

118
src/api/im/ImUtil.js Normal file
View File

@@ -0,0 +1,118 @@
//请求工具
const targetUrl = process.env.BASE_TARGET_URL.replaceAll("/", "");
//获取聊天服务的地址
export function getImIframeUrl(token){
let SERVER_IP = targetUrl.replaceAll("\"", "");
let IFRAME_URL = `http://${SERVER_IP}:18666/client/login?token=${token}&tenantId=1&timestap=${new Date().getTime()}`
return IFRAME_URL;
}
//用户上线方法
export function userOnline() {
let SERVER_IP = targetUrl.replaceAll("\"", "");
// 封装【上线】逻辑
const performUserOnline = (user) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', `http://${SERVER_IP}:18666/app-api/im/open/user-online`, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('tenant-id', '1');
xhr.onload = function() {
if (xhr.status == 200) {
const response = JSON.parse(xhr.responseText);
if (response.code == 0) {
localStorage.setItem("im-token", JSON.stringify(response.data));
resolve(response.data);
} else {
reject(new Error("上线失败"));
}
} else {
reject(new Error(xhr.statusText));
}
};
xhr.onerror = function() {
reject(new Error('上线网络错误'));
};
xhr.send(JSON.stringify(user));
});
};
// 封装【刷新Token】逻辑
const performRefreshToken = (oldToken) => {
return new Promise((resolveRefresh, rejectRefresh) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', `http://${SERVER_IP}:18666/app-api/im/v1/client/refresh-token`, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.setRequestHeader('tenant-id', '1');
xhr.onload = function() {
if (xhr.status == 200) {
const response = JSON.parse(xhr.responseText);
if (response.code === 0) {
localStorage.setItem("im-token", JSON.stringify(response.data));
resolveRefresh(response.data);
} else {
rejectRefresh(new Error("刷新Token错误"));
}
} else {
rejectRefresh(new Error(xhr.statusText));
}
};
xhr.onerror = function() {
rejectRefresh(new Error('刷新Token网络错误'));
};
const requestBody = `refreshToken=${encodeURIComponent(oldToken.refreshToken)}`;
xhr.send(requestBody);
});
};
// --- 主逻辑开始 ---
return new Promise((resolve, reject) => {
let localUser = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : null;
if (localUser) {
let data = {
name: localUser.username,
mobile: String(localUser.user_id).padStart(11, '0'),
gender: 1,
avatar: ""
}
let imToken = localStorage.getItem("im-token") ? JSON.parse(localStorage.getItem("im-token")) :
null;
if (imToken) {
const {
expiresTime
} = imToken;
if (Date.now() <= expiresTime) {
resolve(imToken);
} else {
performRefreshToken(imToken)
.then(newImToken => {
resolve(newImToken);
})
.catch(error => {
localStorage.removeItem("im-token");
performUserOnline(data).then(res => {
resolve(res);
}).catch(error => {
reject("上线失败")
});
});
}
} else {
performUserOnline(data).then(res => {
resolve(res);
}).catch(error => {
reject("上线失败")
})
}
}else{
reject("本地用户信息不存在!")
}
}).catch(err => {
return Promise.reject(err)
});
}

165
src/api/im/Socket.js Normal file
View File

@@ -0,0 +1,165 @@
//Socket连接
const targetUrl = process.env.BASE_TARGET_URL.replace("/", "");
import EventBus from './eventBus.js'
let ws = null
let heartTimer = null
let timer = null
let lockReconnect = false
let token = null
const reconnectCountMax = 200
let reconnectCount = 0
let isConnect = false
let SERVER_IP = targetUrl.replaceAll("\"", "");
//服务器推送消息
function response(event) {
if (event.type !== 'message') {
onCloseHandler()
return
}
let wsContent
try {
wsContent = JSON.parse(event.data)
} catch {
onCloseHandler()
return
}
if (wsContent.type) {
if (wsContent.data && wsContent.data.code === -1) {
onCloseHandler()
} else {
switch (wsContent.type) {
//心跳回复
case 'Heartbeat-Reply': {
EventBus.emit('Heartbeat-Reply', wsContent.content)
break
}
//在线用户推送
case 'Online-UserIds': {
EventBus.emit('Online-UserIds', wsContent.content)
break
}
//接收消息
case 'Message-Receive': {
EventBus.emit('Message-Receive', wsContent.content)
break
}
}
}
} else {
onCloseHandler()
}
}
//连接Socket
function connect(tokenStr) {
if (isConnect || ws) return
isConnect = true
token = tokenStr
try {
const wsIp = import.meta.env.VITE_SOCKET_URL
ws = new WebSocket(`ws://${SERVER_IP}:18666/infra/ws?token=${token}`)
ws.onopen = () => {
console.log('Socket连接成功!')
clearTimer()
sendHeartPack()
}
ws.onmessage = response
ws.onclose = onCloseHandler
ws.onerror = onCloseHandler
} catch {
onCloseHandler()
}
}
//发送消息
function send(msg) {
if (ws && ws.readyState === WebSocket.OPEN) ws.send(msg)
}
//开启心跳,上报在线状态
const sendHeartPack = () => {
heartTimer = setInterval(() => {
let data = {
msgType:"Heartbeat",
msgContent:"我在线,时间:"+ new Date().getTime()
}
let wsdata = {
type: "app-message-send",
content: JSON.stringify(data)
}
send(JSON.stringify(wsdata));
}, 10000)
}
const onCloseHandler = () => {
clearHeartPackTimer()
if (ws) {
ws.close()
ws = null
}
isConnect = false
if (lockReconnect) return
lockReconnect = true
if (timer) {
clearTimeout(timer)
timer = null
}
if (reconnectCount >= reconnectCountMax) {
reconnectCount = 0
return
}
if (token) {
timer = setTimeout(() => {
connect(token)
reconnectCount++
lockReconnect = false
}, 5000)
}
}
//清除心跳定时器
const clearHeartPackTimer = () => {
console.log('Socket连接关闭中')
if (heartTimer) {
clearInterval(heartTimer)
heartTimer = null
}
}
//清除Timer定时器
const clearTimer = () => {
if (timer) {
clearInterval(timer)
timer = null
}
}
//断开Socket连接
const disConnect = () => {
clearHeartPackTimer()
token = null
if (ws) {
ws.close()
ws = null
}
isConnect = false
}
export default {
connect,
disConnect
}

4
src/api/im/eventBus.js Normal file
View File

@@ -0,0 +1,4 @@
//事件总线
import mitt from 'mitt'
const eventBus = mitt()
export default eventBus

121
src/api/layers.ts Normal file
View File

@@ -0,0 +1,121 @@
import { ELocalStorageKey } from '../types/enums';
import request, { IWorkspaceResponse } from '/@/api/http/request';
import { elementGroupsReq, PostElementsBody, PutElementsBody } from '/@/types/mapLayer';
const PREFIX = '/map/api/v1';
const workspace_id = localStorage.getItem(ELocalStorageKey.WorkspaceId);
type UnknownResponse = Promise<IWorkspaceResponse<unknown>>;
// get elements group
// export const getLayers = async (reqParams: elementGroupsReq): UnknownResponse => {
// const url = `${PREFIX}/workspaces/${workspace_id}/element_groups`
// const result = await request.get(url, {
// params: {
// group_id: reqParams.groupId,
// is_distributed: reqParams.isDistributed
// },
// })
// return result.data
// }
// Get elements groups request
export const getElementGroupsReq = async (body: elementGroupsReq): Promise<IWorkspaceResponse<any>> => {
const url = `${PREFIX}/workspaces/` + workspace_id + '/element-groups';
const result = await request.get(url, body);
return result.data;
};
// add element
export const postElementsReq = async (pid: string, body: PostElementsBody): Promise<IWorkspaceResponse<{ id: string }>> => {
const url = `${PREFIX}/workspaces/` + workspace_id + `/element-groups/${pid}/elements`;
const result = await request.post(url, body);
return result.data;
};
// Update map element request
export const updateElementsReq = async (id: string, body: PutElementsBody): Promise<IWorkspaceResponse<{ id: string }>> => {
const url = `${PREFIX}/workspaces/` + workspace_id + `/elements/${id}`;
const result = await request.put(url, body);
return result.data;
};
// Delete map element
export const deleteElementReq = async (id: string, body: {}): Promise<any> => {
const url = `${PREFIX}/workspaces/` + workspace_id + `/elements/${id}`;
const result = await request.delete(url, body);
return result.data;
};
// Delete layer elements
export const deleteLayerEleReq = async (id: string, body: {}): Promise<any> => {
const url = `${PREFIX}/workspaces/` + workspace_id + `/element-groups/${id}/elements`;
const result = await request.delete(url, body);
return result.data;
};
// export layer kml file
export const exportKml = async (body: any): Promise<any> => {
const url = `${PREFIX}/workspaces/` + workspace_id + `/export`;
const result = await request.post(url, body, { responseType: 'blob' });
if (result.data.type === 'application/vnd.google-earth.kmz+xml') {
const href = URL.createObjectURL(result.data);
const box = document.createElement('a');
box.download = 'Layer导出.kml';
box.href = href;
box.click();
const reader = new FileReader();
/* reader.onload = function (e) {
const href = URL.createObjectURL(res.data)
const box = document.createElement('a')
box.download = 'Layer导出.xml'
box.href = href
box.click()
// message.error(result.message)
}; */
// reader.readAsText(result.data, 'utf-8');
} else {
return result.data;
}
};
// import layer kml file
export const importKml = async (body: {}): Promise<any> => {
const url = `${PREFIX}/workspaces/` + workspace_id + `/import`;
const result = await request.post(url, body);
return result.data;
};
// add layer group
export const addElementGroupsReq = async (group_name: string, group_id?: string, is_distributed = false): Promise<IWorkspaceResponse<any>> => {
const url =
`${PREFIX}/workspaces/` +
workspace_id +
`/element-groups?is_distributed=${is_distributed}&group_name=${group_name}${group_id ? '&group_id=' + group_id : ''}`;
const result = await request.put(url);
return result.data;
};
// move layer element
// source_group_id 源组id
// target_group_id 目标组id
export const moveElementReq = async (element_id: string, source_group_id: string, target_group_id: string): Promise<IWorkspaceResponse<any>> => {
const url =
`${PREFIX}/workspaces/` + workspace_id + `/elements/${element_id}/move?source_group_id=${source_group_id}&target_group_id=${target_group_id}`;
const result = await request.put(url);
return result.data;
};
// del layer group
export const delElementGroupsReq = async (id: string): Promise<IWorkspaceResponse<any>> => {
const url = `${PREFIX}/workspaces/` + workspace_id + `/element-groups?group_id=${id}`;
const result = await request.delete(url);
return result.data;
};
export const delTaskByName = async (caozuo: string,taskId:string): Promise<IWorkspaceResponse<any>> => {
const url = `/vx/task/${caozuo}/${taskId}`;
const tenantId = localStorage.getItem(ELocalStorageKey.TenantId);
const result = await request.post(url,{
"tenant-id": tenantId,
});
return result.data;
};
export const getMapConfig = async function (): Promise<IWorkspaceResponse<any>> {
let workSpaceId = localStorage.getItem('workspace_id');
const url = `/model/${workSpaceId}/file/config`;
const result = await request.get(url);
return result.data;
};

808
src/api/manage.ts Normal file
View File

@@ -0,0 +1,808 @@
import { Firmware, FirmwareQueryParam, FirmwareUploadParam } from '/@/types/device-firmware'
import request, { CommonListResponse, IListWorkspaceResponse, IPage, IRecordsResponse, IWorkspaceResponse } from '/@/api/http/request'
import { Device } from '/@/types/device'
import { ELocalStorageKey } from '@mars/types'
const HTTP_PREFIX = '/manage/api/v1'
const RUOYI_PREFIX = '/OnWCtDVKE4H6'
let workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId);
// login
export interface LoginBody {
username: string,
password: string,
flag?: number,
tenantId?: string
}
export interface BindBody {
device_sn: string,
user_id: string,
workspace_id: string,
domain?: string
}
export interface HmsQueryBody {
sns: string[],
children_sn: string,
device_sn: string,
language: string,
level: number | string,
begin_time: number,
end_time: number,
message: string,
domain: number,
}
export interface AddPostDataBody{
roleName:string
roleKey:string
roleSort:number
status:string
menuCheckStrictly:boolean
deptCheckStrictly:boolean
remark?:string
menuIds:number[]
deptIds:number[]
createBy:string
}
export interface AddJobDataBody{
parentId:number
deptName:string
leader:string
status:string
info:string
createBy:string
}
export interface userModal {
username:string
workspaceId?:string
password:string
respassword?:string
deptId:number|string
roleIds:any[]
userType:string
phonenumber:string
email:string
status:number
delFlag:string
}
/**
*
* @param fid
* @param wid
* @returns
*
* {
"file_name": "公司楼22024-08-09-33",
"file_path": "/3d-model/203/3d/20240809125000",
"file_type": "3dtiles",
"model_type": 3,
"suffixes": "b3dm",
"url": "/3d-model/203/3d/20240809125000/tileset.json"
}
*/
export interface RedModel {
file_name: string;
file_path:string;
file_type:string;
model_type:string;
suffixes:string;
url:string
}
export const getModelData = async (fid:string, wid:string=workspaceId ):Promise<IWorkspaceResponse<RedModel>> => {
let res = await request.get(`/model/${wid}/file/${fid}`)
return res.data
}
interface reqAiModelClue{
page:number
page_size:number
analyze_id:number | string
}
export interface resAiModelClue {
"img_url": string
"ai_detect_name": string,
"analyze_result": string,
"ai_model_analyze_id": number,
"latitude": number,
"longitude": number
}
export const getModelAiClue = async (params:reqAiModelClue, wid:string=workspaceId ):Promise<IRecordsResponse<resAiModelClue>> => {
let res = await request.get(`/ai_model/${wid}/analyze/image/getPageList`,{params})
return res.data
}
export interface ResAIChat {
"role": string,
"type": string,
"message": string,
"create_time": number
}
export interface reqModelBody {
elements:elementsBody[]
id: string
is_lock: boolean
name: string
task_id: number
type: number
}
interface elementsBody {
"create_time": number
"id": string
"name": string
"update_time": number
"resource": Resource
}
interface Resource {
"content": ResourceContent,
"nickname": string,
"type": number,
"user_name": string
}
interface ResourceContent {
type:string
properties:{
clampToGround:boolean
color:string
}
geometry:{
type:string
coordinates:number[]
}
}
export const getElement = async (wid:string=workspaceId):Promise<IWorkspaceResponse<reqModelBody[]>> => {
let res = await request.get(`/map/api/v1/workspaces/${wid}/element-groups`)
return res.data
}
interface reqAiModelClue{
page:number
page_size:number
analyze_id:number | string
}
export interface resAiModelClue {
"img_url": string
"ai_detect_name": string,
"analyze_result": string,
"ai_model_analyze_id": number,
"latitude": number,
"longitude": number
}
export interface ResAiChatById {
"chat_id": string,
}
export const getAiChatById = async (wid:string=workspaceId):Promise<IWorkspaceResponse<ResAiChatById>> => {
let res = await request.get(`/model/${wid}/chat`)
return res.data
}
const getWorkspaceId = () =>{
return localStorage.getItem('workspace_id')
}
//删除-按照线索id删除
export const deleteClue = async (wid,clue_id) => {
let res = await request.delete(`/task/api/v1/task/${wid}/remove/${clue_id}`)
return res.data
}
// 复检功能-指派最近的无人机进行复检
export const againCheckClue = async (wid,clue_id) => {
let res = await request.get(`/task/api/v1/task/${wid}/retest/${clue_id}`)
return res.data
}
// 获取全部线索列表
export const getAllClue = async (pageOptions: { startTime: number | string; endTime: number | string }) => {
const query = {
'flag': 1,
size:100000,
current:1,
...pageOptions
}
// let apiUrl =
// `/task/api/v1/task/${getWorkspaceId()}/getAllClue`;
let apiUrl =
`/task/api/v1/task/${getWorkspaceId()}/allClue
`;
let responseData = await request.get(apiUrl,{params:query})
return responseData.data as any
}
export interface ResAIChat {
"role": string,
"type": string,
"message": string,
"create_time": number
}
export const getAiChat = async (fid:string, wid:string=workspaceId ):Promise<IRecordsResponse<ResAIChat>> => {
let res = await request.get(`/model/${wid}/chat/${fid}/page?page=1&page_size=200`)
return res.data
}
interface reqPostAiBody {
"role": string
"type": string
"message": string
}
export const addAiChat = async (chat_id:string,body:reqPostAiBody,wid:string=workspaceId) => {
let res = await request.post(`/model/${wid}/chat/${chat_id}`,body)
return res.data
}
export interface RedModelList {
"ai_type": string
"wayline_id": string
"model_id": string
"model_pic_url": string
"shot_time": number
"create_time": number
"update_time": number
}
export interface ModelListParams {
page:number
page_size:number
ai_type:number
wayline_id:string
}
export const getModelList = async function (wid: string = workspaceId, body: ModelListParams): Promise<IRecordsResponse<RedModelList>> {
const url = `/model/${wid}/wayline/getPageList`;
const result = await request.get(url,{params:body});
return result.data;
};
export async function getTanantById(tenant_id:string){
let tenantid
if(!tenant_id){
tenantid = localStorage.getItem("tenantId")
}else{
tenantid = tenant_id
}
if(tenantid){
let tan = await request.get(`/admin-api/system/tenant/get?id=${tenantid}`)
return tan
}else{
return {code:-1}
}
}
export async function getRolePage(pageNo:number,pageSize:number,current:number){
let data = await request.get(`/admin-api/system/role/page?pageNo=${pageNo}&pageSize=${pageSize}&current=${current}`)
return data.data
}
export async function getdeptData(){
let data = await request.get(`/admin-api/system/dept/list`)
return data.data
}
interface MqttLoginBody {
clientId:string
password:string
username:string
}
export const mqttLogin = async(body:MqttLoginBody)=>{
let url = `/admin-api/system/auth/mqtt/login`
const result = await request.post(url,body)
return result.data
}
export const getIdByName = async (name='水利管理平台')=>{
let url = `/admin-api/system/tenant/get-id-by-name?name=${name}`
const result = await request.get(url)
return result.data
}
export const deleteJobData = async (id:string|number) => {
let url = '/system/dept/'
url += `${id}`
const result = await request.delete(url)
return result.data
}
export const deleteUserData = async (id:string|number, currentId:string|number|null) => {
let url = '/manage/api/v1/users/remove?'
url += `userIds=${id}&currentId=${currentId}`
const result = await request.delete(url)
return result.data
}
interface WaylineBody {
name:string
drone:string
payload:string
template:string
username:any
workSpaceId:any
}
// 获取当前租户下所有用户
export const getTenantUserList = async (workspace_id: string) => {
const url = `/manage/api/v1/users/${workspace_id}/allUsers`;
const result = await request.get(url)
return result.data
}
export const getWaylineById = async (workSpaceId:string,waypointId:string)=>{
let url = `/wayline/api/v1/workspaces/${workSpaceId}/waylines/get/${waypointId}`
const result = await request.get(url)
return result.data
}
export const saveWaylineDetail = async (lineId:string, workSpaceId:any,body:any,tenantId:any)=>{
let url = `/wayline/api/v1/workspaces/${workSpaceId}/waylines/${lineId}/saveWaylineDetail/${tenantId}`
const result = await request.post(url,body)
return result.data
}
export const saveWayline = async (body:WaylineBody) => {
let url = `/wayline/api/v1/workspaces/${body.workSpaceId}/waylines/saveWayline`
const result = await request.post(url,body)
return result.data
}
export const deletePostData = async (id:string|number) => {
let url = '/system/role/'
url += `${id}`
const result = await request.delete(url)
return result.data
}
export const addJob = async (body:AddJobDataBody) => {
const url = '/system/dept/'
const result = await request.post(url, body)
return result.data
}
export const updateJob = async (id:string, body:any) => {
const url = '/system/dept'
const result = await request.put(url, body)
return result.data
}
export const addPost = async (body:AddPostDataBody) => {
const url = '/system/role'
const result = await request.post(url, body)
return result.data
}
export const addUsers = async (body:userModal) => {
const url = '/manage/api/v1/users'
const result = await request.post(url, body)
return result.data
}
export const login2 = async function (body: LoginBody,header:any): Promise<IWorkspaceResponse<any>> {
const url = `/admin-api/system/auth/login`
const result = await request.post(url, body,{headers:header})
return result.data
}
export const getUserInfoData = async function (userId:string): Promise<any> {
const url= `${RUOYI_PREFIX}/admin-api/system/user/get?id=${userId}`
const result = await request.get(url)
return result.data
}
export const login = async function (body: LoginBody):Promise<IWorkspaceResponse<any>>{
const url = `${HTTP_PREFIX}/login`
const result = await request.post(url, body)
return result.data
}
// Refresh Token
export const refreshToken = async function (body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/token/refresh`
const result = await request.post(url, body)
return result.data
}
// Get Platform Info
export const getPlatformInfo = async function (): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/current`
const result = await request.get(url)
return result.data
}
// Get User Info
export const getUserInfo = async function (): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/users/current`
const result = await request.get(url)
return result.data
}
// Get Device Topo
export const getDeviceTopo = async function (workspace_id: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices`
const result = await request.get(url)
return result.data
}
// Get Livestream Capacity
export const getLiveCapacity = async function (body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/live/capacity`
const result = await request.get(url, body)
return result.data
}
// 通过sn获取设备信息
export const getLiveCapacityBySn = async function (deviceSn:string, body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/live/capacityBySn?deviceSn=${deviceSn}`
const result = await request.get(url, body)
return result.data
}
// Start Livestream
export const startLivestream = async function (body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/live/streams/start`
const result = await request.post(url, body)
return result.data
}
//军工无人机视频流地址
export const liveStartUrl = async (sn: string): Promise<IWorkspaceResponse<ConfigData>> => {
let url = `/api/v1/live-events/start?device_sn=${sn}`;
const result = await request.get(url);
return result.data;
};
export const getShengToken = async (agoraAPPID:string, agoraChannel:string) => {
const url = `/system/base/getAgoraToken?agoraAPPID=${agoraAPPID}&agoraChannel=${agoraChannel}`
const result = await request.get(url)
return result.data
}
// Stop Livestream
export const stopLivestream = async function (body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/live/streams/stop`
const result = await request.post(url, body)
return result.data
}
// Update Quality
export const setLivestreamQuality = async function (body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/live/streams/update`
const result = await request.post(url, body)
return result.data
}
export const getAllJobInfo = async (deptName?:string) => {
let url = '/system/dept/list?'
if (deptName) {
url += `&deptName=${deptName}`
}
const result = await request.get(url)
return result.data
}
export const getUserInfoById = async (id:string) => {
const url = `/manage/api/v1/users/${id}`
const result = await request.get(url)
return result.data
}
// 获取部门树结构
export const getAllJobTree = async () => {
const url = '/system/dept/deptTree'
const result = await request.get(url)
return result.data
}
// 获取职务数据
export const getAllPostInfo = async function (body?: IPage, roleName?: string, sortType?:string): Promise<CommonListResponse<any>> {
let url = '/system/role/list?'
if (body && body.page && body.page_size) {
url += `&page=${body.page}&page_size=${body.page_size}`
}
if (roleName) {
url += `&roleName=${roleName}`
}
if (sortType) {
url += `&sortType=${sortType}`
}
const result = await request.get(url)
return result.data
}
export const updateUserInfoData = async (workspace_id:string, user_id:string, body:any) => {
const url = `/manage/api/v1/users/${workspace_id}/users/${user_id}`
const result = await request.put(url, body)
return result.data
}
export const getAllUsersInfo = async function (wid: string, body: IPage): Promise<CommonListResponse<any>> {
const url = `${HTTP_PREFIX}/users/${wid}/users?&page=${body.page}&page_size=${body.page_size}`
const result = await request.get(url)
return result.data
}
export const getAllPostInfoData = async function (id:string) {
const url = `/system/role/${id}`
const result = await request.get(url)
return result.data
}
export const updatePost = async function (body:any) {
const url = '/system/role/'
const result = await request.put(url, body)
return result.data
}
export const getAllJobInfoData = async (id:string) => {
const url = `/system/dept/${id}`
const result = await request.get(url)
return result.data
}
export const updateUserInfo = async function (wid: string, user_id: string, body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/users/${wid}/users/${user_id}`
const result = await request.put(url, body)
return result.data
}
export const bindDevice = async function (body: BindBody): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/devices/${body.device_sn}/binding`
const result = await request.post(url, body)
return result.data
}
export const unbindDevice = async function (device_sn: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/devices/${device_sn}/unbinding`
const result = await request.delete(url)
return result.data
}
export const getDeviceBySn = async function (workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/${device_sn}`
const result = await request.get(url)
return result.data
}
export enum Domain {
"sub-device" = "SUB_DEVICE",
"gateway"="GATEWAY",
"payload"="PAYLOAD",
'dock'='DOCK'
}
export const getDevicesByDomain = async (domain:Domain,workspace_id:string,childSn?:string) => {
let url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/getDevicesByDomain?domain=${domain}`
if(childSn){
url += `&childSn=${childSn}`
}
const result = await request.get(url)
return result.data
}
/**
* 获取绑定设备信息
* @param workspace_id
* @param body
* @param domain
* @returns
*/
export const getBindingDevices = async function (workspace_id: string, body: IPage, domain: number): Promise<IListWorkspaceResponse<Device>> {
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/bound?&page=${body.page}&page_size=${body.page_size}&domain=${domain}`
const result = await request.get(url)
return result.data
}
export const updateDevice = async function (body: {}, workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/${device_sn}`
const result = await request.put(url, body)
return result.data
}
export const getUnreadDeviceHms = async function (workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms/${device_sn}`
const result = await request.get(url)
return result.data
}
export const updateDeviceHms = async function (workspace_id: string, device_sn: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms/${device_sn}`
const result = await request.put(url)
return result.data
}
export const getDeviceHms = async function (body: HmsQueryBody, workspace_id: string, pagination: IPage): Promise<IListWorkspaceResponse<any>> {
let url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/hms?page=${pagination.page}&page_size=${pagination.page_size}` +
`&level=${body.level ?? ''}&begin_time=${body.begin_time ?? ''}&end_time=${body.end_time ?? ''}&message=${body.message ?? ''}&language=${body.language}`
body.sns.forEach((sn: string) => {
if (sn !== '') {
url = url.concat(`&device_sn=${sn}`)
}
})
const result = await request.get(url)
return result.data
}
export const changeLivestreamLens = async function (body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/live/streams/switch`
const result = await request.post(url, body)
return result.data
}
export const getFirmwares = async function (workspace_id: string, page: IPage, body: FirmwareQueryParam): Promise<IListWorkspaceResponse<Firmware>> {
const url = `${HTTP_PREFIX}/workspaces/${workspace_id}/firmwares?page=${page.page}&page_size=${page.page_size}` +
`&device_name=${body.device_name}&product_version=${body.product_version}&status=${body.firmware_status ?? ''}`
const result = await request.get(url)
return result.data
}
export const importFirmareFile = async function (workspaceId: string, param: FormData): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/firmwares/file/upload`
const result = await request.post(url, param)
return result.data
}
export const changeFirmareStatus = async function (workspaceId: string, firmwareId: string, param: {status: boolean}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/firmwares/${firmwareId}`
const result = await request.put(url, param)
return result.data
}
interface ConfigData {
device_sn?: string; // 设备sn
lost_action: number; // 失联动作
flight_height: number; // 飞行作业高
target_height: number; // 目标点高度
back_height: number; // 返航高
security_height: number; // 安全起飞高度
fly_speed: number; // 飞行速度
}
export const updateConfigData = async (sn: string, body: ConfigData): Promise<IWorkspaceResponse<ConfigData>> => {
let url = `${HTTP_PREFIX}/devices/flight/setting/${sn}`;
const result = await request.put(url, body);
return result.data;
};
export const getConfigData1 = (id:string,tenId:string,w:string) => {
const apiUrl = `/dji/model/${w}/file/model-config`
return request.get(apiUrl,{
params:{
id
},
headers:{
"non-auth": "11111",
"tenant-id":tenId,
}
})
}
//获取psdk资源
export const getPsdkRsource = async function (workspace_id: string, sn: string, psdk_index: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/psdk/${workspace_id}/psdk/ui-resource?device_sn=${sn}&psdk_index=${psdk_index}`;
const result = await request.get(url);
return result.data;
};
export interface IpsdkWidgetValueSet {
gateway_sn?: string;
index?: number;
psdk_index?: number;
value?: string;
[property: string]: any;
}
//psdk-设置控件值
export const psdkWidgetValueSet = async function (workspace_id: string, body: IpsdkWidgetValueSet): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/psdk/${workspace_id}/psdk/psdkWidgetValueSet`;
const result = await request.post(url, body);
return result.data;
};
//psdk-发送文本框内容
export const psdkInputBoxTextSet = async function (workspace_id: string, body: IpsdkWidgetValueSet): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/psdk/${workspace_id}/psdk/psdkInputBoxTextSet`;
const result = await request.post(url, body);
return result.data;
};
let userWorkSpaceId = localStorage.getItem('workspace_id');
interface SpeakerFileUrlData {
url: string;
id: string;
name: string;
type: 1 | 2;
tts_text: string;
object_key: string;
format: string;
md5: string;
}
export const getQuerySpeakerFileUrl = async (play_file_md5: string): Promise<IWorkspaceResponse<SpeakerFileUrlData>> => {
let url = `${HTTP_PREFIX}/psdk/${userWorkSpaceId}/querySpeakerFile`;
const result = await request.get(url, { params: { play_file_md5 } });
return result.data;
};
import axios from 'axios';
const cancelToken = axios.CancelToken;
let source: any = null;
export const singleSoldierDevice = async (workspace_id: string, data: {}) => {
/* if(source){
source()
} */
try {
let res = await request.get(`/singleSoldierDevice/${workspace_id}/queryByPage`, {
params: data,
cancelToken: new axios.CancelToken(function executor(c) {
source = c;
}),
});
return res.data;
} catch (error) {
throw error;
}
};
/**
* 播放单兵视频
* @param { }
* @returns
*/
export const singleSoldierDeviceStartStream = async (workspace_id: string, data: {}) => {
try {
let res = await request.post(`/singleSoldierDevice/${workspace_id}/startStream`, data);
return res.data;
} catch (error) {
throw error;
}
};
export const getConfigData = async (sn: string): Promise<IWorkspaceResponse<ConfigData>> => {
let url = `${HTTP_PREFIX}/devices/flight/setting/${sn}`;
const result = await request.get(url);
return result.data;
};
//云端控制授权接口
export const getCloudControl = async (sn: string) => {
let url = `/control/api/v1/devices/${sn}/authority/cloud-control`;
const result = await request.post(url, { sn });
return result.data;
};
//释放云端控制
export const deleteCloudControl = async (sn: string) => {
let url = `/control/api/v1/devices/${sn}/authority/cloud-control`;
const result = await request.delete(url, { params: sn });
return result.data;
};

84
src/api/media.ts Normal file
View File

@@ -0,0 +1,84 @@
import req from './http/request'
// 上传图片到 MinIO
export const uploadImageToMinio = async (
file: File,
businessType: string,
fileName: string,
originalId?: string
) => {
const form = new FormData()
form.append('file', file)
form.append('businessType', businessType)
form.append('fileName', fileName)
if (originalId) form.append('originalId', String(originalId))
return await req.post('/api/v1/file/upload/image', form, {
headers: { 'Content-Type': 'multipart/form-data' }
})
}
// 删除图片(按文件名)
export const deleteImageFromMinio = async (
fileName: string,
originalId?: string
) => {
return await req.delete('/api/v1/file/image', {
data: { fileName, originalId }
})
}
// 覆盖更新图片内容
export const updateImageInMinio = async (
fileName: string,
businessType: string,
file: File,
originalId?: string
) => {
const form = new FormData()
form.append('fileName', fileName)
form.append('businessType', businessType)
form.append('file', file)
if (originalId) form.append('originalId', String(originalId))
return await req.put('/api/v1/file/image', form, {
headers: { 'Content-Type': 'multipart/form-data' }
})
}
// 获取图片可访问URL
export const getImageUrlFromMinio = async (
fileName: string,
businessType: string
) => {
return await req.get('/api/v1/file/image/url', {
params: {
fileName,
businessType
}
})
}
/*
* @Author: gejia 905034981@qq.com
* @Date: 2024-03-30 14:06:44
* @LastEditors: gejia 905034981@qq.com
* @LastEditTime: 2024-11-07 13:36:40
* @FilePath: \redfly-web\src\api\media.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import request, { IPage, IWorkspaceResponse } from './http/request';
import type { IfileParams } from './d.ts/media'
const HTTP_PREFIX = '/media/api/v1';
//获取文件列表
export const getFileList = async function (wid: string, pagination: IPage, params: IfileParams): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/files/${wid}/files/getFilePage?page=${pagination.page}&page_size=${pagination.page_size}`;
// const result = await request.get(url)
return request.get(url, { params });
};
//修改文件相应的参数
export const updateFile = async function (wid: string, id: string, params: IfileParams): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/files/${wid}/files/updateFile/${id}`;
return request.put(url, params);
};

226
src/api/psdk.ts Normal file
View File

@@ -0,0 +1,226 @@
import request, { CommonListResponse, IListWorkspaceResponse, IPage, IWorkspaceResponse } from '/@/api/http/request';
const HTTP_PREFIX = '/manage/api/v1';
export interface psdkInsertBody {
name: string;
tts_text?: string;
type: number;
id?: string;
object_key?: string;
}
export interface pageParams {
page: number;
pageSize: number;
}
/**
* 新增喊话文件
* */
export const insert = async (data: psdkInsertBody) => {
try {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
let res = await request.post(`${HTTP_PREFIX}/psdk/${workspaceId}/insert`, data);
return res.data;
} catch (error) {
throw error;
}
};
/**
* 编辑喊话文件
* */
export const update = async (data: psdkInsertBody) => {
try {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
let res = await request.put(`${HTTP_PREFIX}/psdk/${workspaceId}/update`, data);
return res.data;
} catch (error) {
throw error;
}
};
export const speakerLoopPlayList = async (params: { gateway_sn: string }) => {
try {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
let res = await request.get(`${HTTP_PREFIX}/psdk/${workspaceId}/speaker/speakerLoopPlayList`, { params });
return res.data;
} catch (error) {
throw error;
}
};
/**
* 分页查询文件列表
* */
export const queryByPage = async (params: pageParams) => {
try {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
let res = await request.get(`${HTTP_PREFIX}/psdk/${workspaceId}/queryByPage`, { params });
return res.data;
} catch (error) {
throw error;
}
};
/**
* 负载列表数据
* */
export const speakerListBySn = async (params: { device_sn: string }) => {
try {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
let res = await request.get(`${HTTP_PREFIX}/psdk/${workspaceId}/speakerListBySn`, { params });
return res.data;
} catch (error) {
throw error;
}
};
/**
* 删除语音文件
* */
export const deleteById = async (params: { id: string }) => {
try {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
let res = await request.delete(`${HTTP_PREFIX}/psdk/${workspaceId}/deleteById`, { params });
return res.data;
} catch (error) {
throw error;
}
};
/**
* 播放tts语音
* */
export interface SpeakerTtsPlayData {
gateway_sn: string;
psdk_index: number;
name: string;
text?: string;
tts_text?: string;
object_key?: string;
playVolume?: number;
md5: string;
id?: string;
type?: number;
format?: string;
}
export const speakerTtsPlayStart = async (data: SpeakerTtsPlayData) => {
try {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
let res = await request.post(`${HTTP_PREFIX}/psdk/${workspaceId}/speaker/speakerTtsPlayStart`, data);
return res.data;
} catch (error) {
throw error;
}
};
/**
* 批量播放tts语音
* */
export interface SpeakerTtsPlayDatas {
gateway_sn: string;
speaker_loop_play_list: SpeakerTtsPlayData[];
}
export const speakerLoopPlayStart = async (data: SpeakerTtsPlayDatas) => {
try {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
let res = await request.post(`${HTTP_PREFIX}/psdk/${workspaceId}/speaker/speakerLoopPlayStart`, data);
return res.data;
} catch (error) {
throw error;
}
};
/**
* 播放语音
* */
interface SpeakerAudioPlayData {
gateway_sn: string;
psdk_index: number;
name: string;
object_key: string;
md5: string;
format: string;
}
export const speakerAudioPlayStart = async (data: SpeakerAudioPlayData) => {
try {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
let res = await request.post(`${HTTP_PREFIX}/psdk/${workspaceId}/speaker/speakerAudioPlayStart`, data);
return res.data;
} catch (error) {
throw error;
}
};
/**
* 设置喊话器音量
* */
interface speakerPlayVolumeData {
gateway_sn: string;
psdk_index: number;
play_volume: number;
}
export const speakerPlayVolumeSet = async (data: speakerPlayVolumeData) => {
try {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
let res = await request.post(`${HTTP_PREFIX}/psdk/${workspaceId}/speaker/speakerPlayVolumeSet`, data);
return res.data;
} catch (error) {
throw error;
}
};
/**
* 设置喊话器模式
* */
interface speakerPlayModeData {
gateway_sn: string;
psdk_index: number;
play_mode: string;
}
export const speakerPlayModeSet = async (data: speakerPlayModeData) => {
try {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
let res = await request.post(`${HTTP_PREFIX}/psdk/${workspaceId}/speaker/speakerPlayModeSet`, data);
return res.data;
} catch (error) {
throw error;
}
};
/**
* 停止播放
* */
interface speakerStopModeData {
gateway_sn: string;
psdk_index?: number;
speaker_tts_play_list?: [];
}
export const speakerPlayStop = async (data: speakerStopModeData) => {
try {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
let res = await request.post(`${HTTP_PREFIX}/psdk/${workspaceId}/speaker/speakerPlayStop`, data);
return res.data;
} catch (error) {
throw error;
}
};
/**
* 重新播放
* */
interface speakerReplayData {
gateway_sn: string;
psdk_index: number;
}
export const speakerReplay = async (data: speakerReplayData) => {
try {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
let res = await request.post(`${HTTP_PREFIX}/psdk/${workspaceId}/speaker/speakerReplay`, data);
return res.data;
} catch (error) {
throw error;
}
};
/**
* 上传文件
*/
export const uploadAudio = async function (file: {}): Promise<IWorkspaceResponse<any>> {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
const url = `${HTTP_PREFIX}/psdk/${workspaceId}/speaker/uploadAudio`;
const result = await request.post(url, file, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
return result.data;
};

View File

@@ -0,0 +1,149 @@
import request, { IRecordsResponse, CommonResponse } from '/@/api/http/request';
// 区域侦查任务类型声明
export interface Point {
latitude: number;
longitude: number;
altitude: number;
}
export interface RegionZone {
pointList: Point[];
}
export interface RegionReconnaissanceTask {
taskId: string;
taskName: string;
taskType: number; // 1-区域侦查, 2-航线侦查
status: number; // 0-待执行,1-执行中,2-已完成,3-已取消,4-执行失败
regionZone: RegionZone;
description?: string;
executeTime?: string; // ISO 8601 UTC
createTime: string;
updateTime: string;
createUserId: string;
mqttSendStatus: number;
}
export interface CreateTaskParams {
taskName: string;
taskType: 1 | 2;
regionZone: RegionZone;
description?: string;
executeTime?: string; // ISO 8601 UTC
}
// 原始后端规范pointList 顶层)
export interface CreateTaskRawParams {
taskName: string;
taskType: 1 | 2;
pointList: Array<{ longitude: number; latitude: number; altitude: number }>;
description?: string;
}
export interface ListParams {
page?: number;
size?: number;
[key: string]: any;
}
// 创建任务
export async function createRegionReconnaissanceTask(
body: CreateTaskParams
): Promise<CommonResponse<{ taskId: string }>> {
try {
const res = await request.post('/api/v1/region-reconnaissance/tasks', body);
return (res && res.data) || (res as any);
} catch (e) {
throw e;
}
}
// 直接按原始规范提交,支持指定绝对基地址(如 http://27.11.11.30/dji
export async function createRegionReconnaissanceTaskRaw(
body: CreateTaskRawParams,
absoluteBaseUrl?: string
): Promise<CommonResponse<{ taskId: string }>> {
const url = absoluteBaseUrl
? `${absoluteBaseUrl}/api/v1/region-reconnaissance/tasks`
: '/api/v1/region-reconnaissance/tasks';
try {
const res = await request.post(url, body);
// 某些网关可能返回空响应体(如 204此时直接回传原响应
return (res && res.data) || (res as any);
} catch (e) {
// 透传错误,由调用方展示
throw e;
}
}
// 发送 MQTTPOST /api/v1/region-reconnaissance/tasks/{taskId}/send-mqtt
export async function sendRegionReconnaissanceTaskMqtt(
taskId: string,
absoluteBaseUrl?: string,
payload?: any
): Promise<CommonResponse<any>> {
const url = absoluteBaseUrl
? `${absoluteBaseUrl}/api/v1/region-reconnaissance/tasks/${taskId}/send-mqtt`
: `/api/v1/region-reconnaissance/tasks/${taskId}/send-mqtt`;
try {
const res = await request.post(url, payload || {});
return (res && (res as any).data) || (res as any);
} catch (e) {
throw e;
}
}
// 获取任务列表(分页)
export async function getRegionReconnaissanceTasks(
params: ListParams = {}
): Promise<IRecordsResponse<RegionReconnaissanceTask>> {
const searchParams = new URLSearchParams(
Object.fromEntries(
Object.entries({ page: 1, size: 10, ...params }).filter(([, v]) => v !== undefined && v !== null && v !== '')
) as Record<string, string>
).toString();
const res = await request.get(`/api/v1/region-reconnaissance/tasks?${searchParams}`);
return res.data;
}
// 获取任务详情
export async function getRegionReconnaissanceTaskDetail(
taskId: string
): Promise<CommonResponse<RegionReconnaissanceTask>> {
const res = await request.get(`/api/v1/region-reconnaissance/tasks/${taskId}`);
return res.data;
}
// 更新任务状态
export async function updateRegionTaskStatus(
taskId: string,
status: number
): Promise<CommonResponse<any>> {
const res = await request.put(`/api/v1/region-reconnaissance/tasks/${taskId}/status`, { status });
return res.data;
}
// 删除任务
export async function deleteRegionReconnaissanceTask(taskId: string): Promise<CommonResponse<any>> {
const res = await request.delete(`/api/v1/region-reconnaissance/tasks/${taskId}`);
// 某些实现可能返回 204无 data。axios 会给出空字符串,这里交由调用方判断。
return res.data ?? { code: 200, message: 'OK', data: null } as any;
}
// 校验任务数据
export function validateTaskData(task: CreateTaskParams) {
const errors: string[] = [];
if (!task.taskName || task.taskName.trim() === '') {
errors.push('任务名称不能为空');
}
if (![1, 2].includes(task.taskType)) {
errors.push('任务类型必须是1(区域侦查)或2(航线侦查)');
}
if (!task.regionZone || !Array.isArray(task.regionZone.pointList) || task.regionZone.pointList.length < 3) {
errors.push('区域至少需要3个点');
}
return { isValid: errors.length === 0, errors };
}

32
src/api/share-live.ts Normal file
View File

@@ -0,0 +1,32 @@
import request, { CommonListResponse, IListWorkspaceResponse, IPage, IWorkspaceResponse } from '/@/api/http/request';
const HTTP_PREFIX = '';
/**
* 负载列表数据
* */
/* export const speakerListBySn = async (params: { device_sn: string }) => {
try {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
let res = await request.get(`${HTTP_PREFIX}/psdk/${workspaceId}/speakerListBySn`, { params });
return res.data;
} catch (error) {
throw error;
}
}; */
/**
* 保存直播数据
* */
interface ShareLiveData {
gateway_sn: string;
expire_time: number;
device_info: string;
}
export const ShareLiveAdd = async (data: ShareLiveData) => {
try {
const workspaceId: string = localStorage.getItem('workspace_id') || '';
let res = await request.post(`${HTTP_PREFIX}/live/${workspaceId}/share/add`, data);
return res.data;
} catch (error) {
throw error;
}
};

290
src/api/task.ts Normal file
View File

@@ -0,0 +1,290 @@
import { ItaskForm, IClueForm } from '/@/api/d.ts/task';
import request, { CommonListResponse, IListWorkspaceResponse, IPage, IWorkspaceResponse } from '/@/api/http/request';
const jsonByUrl = function () {
return function (obj: any, isFirst: boolean = true) {
let url = '';
let indexData = 0;
for (let [key, value] of Object.entries(obj)) {
url += `${isFirst && indexData == 0 ? '?' : '&'}${key}=${value}`;
indexData++;
}
return url;
};
};
interface AllTaskParams {
state?: string;
status?: string;
current: string;
size: string;
startTime?: string;
endTime?: string;
}
export const getAllClue = async function (workspace_id: string, params: AllTaskParams): Promise<IWorkspaceResponse<any>> {
let paramsurl = jsonByUrl()(params);
const url = `/task/api/v1/task/${workspace_id}/getTaskList${paramsurl}`;
const result = await request.get(url);
return result.data;
};
interface StartTaskParams {
taskId: string;
sn?: string;
}
interface StartAiTaskParams {
taskId: string;
sn?: string;
rtmpPullUrl?: string;
serviceName?: string;
flag?: string;
recognitionType?: any;
}
export const startTask = async function (workspace_id: string, params: StartTaskParams): Promise<IWorkspaceResponse<any>> {
let url = `/task/api/v1/task/${workspace_id}/${params.taskId}/${params.sn}/start`;
const result = await request.get(url);
return result.data;
};
export const startAiTask = async function (workspace_id: string, params: StartAiTaskParams): Promise<IWorkspaceResponse<any>> {
let url = `/task/api/v1/task/${workspace_id}/${params.taskId}/${params.sn}/start`;
const result = await request.get(url, {
params: params
});
return result.data;
};
export const getClueById = async function (workspace_id: string, params: StartTaskParams): Promise<IWorkspaceResponse<any>> {
let url = `/task/api/v1/task/${workspace_id}/${params.taskId}/getClue`;
const result = await request.get(url);
return result.data;
};
// 获取任务列表
// export const getTaskAllList = async () => {
// let url = `/task/api/v1/task/${workspace_id}/getTaskList`
// }
/**
* 暂时不用 替换新的接口getTaskListMonthDay
* @param workspace_id
* @param params
* @returns
* @deprecated
*/
export const getAllCluePage = async function (workspace_id: string, params: AllTaskParams): Promise<IWorkspaceResponse<any>> {
let paramsurl = jsonByUrl()(params);
const url = `/task/api/v1/task/${workspace_id}/getTaskListPage${paramsurl}`;
const result = await request.get(url);
return result.data;
};
export const getTaskListMonthDay = async function (workspace_id: string, params: AllTaskParams): Promise<IWorkspaceResponse<any>> {
let paramsurl = jsonByUrl()(params);
const url = `/task/api/v1/task/${workspace_id}/getTaskListMonthDay${paramsurl}`;
const result = await request.get(url);
return result.data;
};
export const updateTaskData = async function (workspace_id: string, body: any): Promise<IWorkspaceResponse<any>> {
const url = `/task/api/v1/task/${workspace_id}/updateTask`;
const result = await request.post(url, body);
return result.data;
};
// 获取队长
export const getTaskRelation = async (workspace_id: string, taskId: string) => {
const url = `/task/api/v1/task/${workspace_id}/${taskId}/getTaskRelation`;
const result = await request.get(url);
return result.data;
};
// 查询所有的飞手
export const getAllFlightUser = async (workspace_id: string, task_id: string) => {
const url = `task/api/v1/task/${workspace_id}/${task_id}/getAllFlightUser`;
const result = await request.get(url);
return result.data;
};
export const getAllUserList = async (workspace_id: string, taskId: string) => {
const url = `/task/api/v1/task/${workspace_id}/${taskId}/getAllUser`;
const result = await request.get(url);
return result.data;
};
export const getAllClueData = async (workspace_id: string, taskId: string) => {
const url = `/task/api/v1/task/${workspace_id}/${taskId}/getClue`;
const result = await request.get(url);
return result.data;
};
export const getAllClueByTaskId = async (workspace_id: string, taskId: string) => {
const url = `/task/api/v1/task/${taskId}/getAllClueById`;
const result = await request.get(url);
return result.data;
};
export const getTaskClue = async (workspace_id: string, taskId: string) => {
const url = `/task/api/v1/task/${workspace_id}/${taskId}/getTaskClue`;
const result = await request.get(url);
return result.data;
};
// 分页获取用户信息
export const setTaskRelation = async (workspace_id: string, taskId: string, body: any) => {
const url = `/task/api/v1/task/${workspace_id}/${taskId}/setTaskRelation`;
const result = await request.post(url, body);
return result.data;
};
// 获取设备信息
export const getDeviceSelect = async (workspace_id: string) => {
const url = `/task/api/v1/task/${workspace_id}/getDeviceSelect/1`;
const result = await request.get(url);
return result.data;
};
// 获取设备信息类型
export const getDeviceSelectType = async (workspace_id: string) => {
const url = `/task/api/v1/task/${workspace_id}/getDeviceTypeSelect`;
const result = await request.get(url);
return result.data;
};
// 获取设备信息类型
export const saveTaskUser = async (workspace_id: string, body: any) => {
const url = `/task/api/v1/task/${workspace_id}/saveTaskUser`;
const result = await request.post(url, body);
return result.data;
};
export interface JobData {
month: string;
deviceType: string;
taskName: string;
deviceSn: string;
}
export interface ResultJobData {
ai_recognition: boolean;
create_time: number;
create_user_id: string;
id: string;
repeat_mode: string;
sharpness: number;
task_end_time: number;
task_id: string;
task_mode: string;
task_name: string;
task_riverbank: number;
task_start_time: number;
task_state: number;
task_type: string;
update_time: number;
wayline_job_id: string;
workspace_id: string;
past: boolean;
remark?: string;
name: string;
}
export interface ResultUnJobData {
name: string;
create_user_id: string;
remark: string;
task_name: string;
task_start_time: number;
task_end_time: number;
past?: boolean;
}
interface resAllData {
code: number;
data: {
[key: string]: ResultJobData[];
};
message: string;
}
// 按照月份查询计划库任务
export const getJobDaraByMonth = async (workspace_id: string, jobData: JobData): Promise<resAllData> => {
const url = `/task/api/v1/task/${workspace_id}/getTaskListMonth`;
const paramsUrl = jsonByUrl()(jobData);
const result = await request.get(url + paramsUrl);
return result.data;
};
// 按照月份查询计划库任务
export const getJobDaraByMonthNew = async (workspace_id: string, jobData: JobData): Promise<resAllData> => {
const url = `/task/api/v1/task/${workspace_id}/getTaskListMonthNew`;
const paramsUrl = jsonByUrl()(jobData);
const result = await request.get(url + paramsUrl);
return result.data;
};
//计划库新建巡堤探险任务
export const saveTask = async (workspace_id: string, params: ItaskForm): Promise<IWorkspaceResponse<any>> => {
const url = `/task/api/v1/task/${workspace_id}/saveTask`;
return request.post(url, {
...params,
});
};
//计划库新建巡堤探险任务
export const saveTaskAndRelation = async (workspace_id: string, params: ItaskForm): Promise<IWorkspaceResponse<any>> => {
const url = `/task/api/v1/task/${workspace_id}/saveTaskAndRelation`;
return request.post(url, {
...params,
});
};
//计划库新建巡堤探险任务
export const saveClue = async (workspace_id: string, params: IClueForm): Promise<IWorkspaceResponse<any>> => {
const url = `/task/api/v1/task/${workspace_id}/clue/saveClue`;
return request.post(url, {
...params,
});
};
//根据经纬度搜位置
export const getAddress = async (location: string): Promise<IWorkspaceResponse<any>> => {
const url = `/task/api/v1/task/getAddress`;
return request.get(url + '?location=' + location);
};
// 开启指令飞行任务Aii
export interface aiTaskParams {
gateway_sn: string;
recognition_type: string;
camera_mode?: string;
}
export const startCmdFlightAi = async (workspace_id: string, params: aiTaskParams): Promise<resAllData> => {
const url = `/task/api/v1/task/${workspace_id}/startCmdFlightAi`;
return request.post(url, {
...params,
});
};
// 查询当前飞机Ai状态
export const queryAiStatusBySn = async (data:{serviceName: string,uavSn:string}) => {
try {
let res = await request.get(`/aiIdentify/serviceStatus`, { params: data });
return res.data;
} catch (error) {
throw error;
}
};
// 查询当前飞机Ai状态
export const stopAiBySn = async (data:{serviceName: string,uavSn:string}) => {
try {
let res = await request.get(`/aiIdentify/serviceStop`, { params: {...data,rtmpUrl:"rtmp://192.168.3.66:1935/live/ai",userName:"test"} });
return res.data;
} catch (error) {
throw error;
}
};

270
src/api/taskPlanning.ts Normal file
View File

@@ -0,0 +1,270 @@
/**
* 任务规划相关API接口
*/
import request from './http/request';
// 任务规划请求接口
export interface TaskPlanningRequest {
target: {
name: string;
longitude: number;
latitude: number;
altitude?: number;
priority: number;
description?: string;
};
missionType: 'reconnaissance' | 'strike';
completionTime: string;
maxDevices: number;
deviceIds: string[];
}
// 任务规划响应接口
export interface TaskPlanningResponse {
taskId: string;
target: {
id: string;
name: string;
longitude: number;
latitude: number;
altitude?: number;
priority: number;
description?: string;
};
missionType: 'reconnaissance' | 'strike';
devices: Array<{
id: string;
name: string;
type: number;
longitude: number;
latitude: number;
strikeRange: number;
maxSpeed: number;
endurance: number;
battery?: number;
onlineStatus: boolean;
}>;
planningResult: {
feasibility: 'high' | 'medium' | 'low';
estimatedTime: number;
totalDistance: number;
recommendations: string[];
};
status: 'pending' | 'approved' | 'rejected' | 'executing' | 'completed' | 'failed';
createdAt: string;
updatedAt: string;
}
// 设备选择请求接口
export interface DeviceSelectionRequest {
targetLongitude: number;
targetLatitude: number;
missionType: 'reconnaissance' | 'strike';
maxDevices: number;
excludeDeviceIds?: string[];
}
// 设备选择响应接口
export interface DeviceSelectionResponse {
devices: Array<{
device: {
id: string;
name: string;
type: number;
longitude: number;
latitude: number;
strikeRange: number;
maxSpeed: number;
endurance: number;
battery?: number;
onlineStatus: boolean;
};
distance: number;
flightTime: number;
canReach: boolean;
inRange: boolean;
batterySufficient: boolean;
score: number;
}>;
totalCount: number;
availableCount: number;
}
// 任务列表查询请求接口
export interface TaskListRequest {
page: number;
pageSize: number;
status?: string;
missionType?: string;
startTime?: string;
endTime?: string;
keyword?: string;
}
// 任务列表响应接口
export interface TaskListResponse {
tasks: TaskPlanningResponse[];
total: number;
page: number;
pageSize: number;
}
// 任务详情响应接口
export interface TaskDetailResponse extends TaskPlanningResponse {
executionLog: Array<{
timestamp: string;
level: 'info' | 'warning' | 'error';
message: string;
deviceId?: string;
}>;
progress: {
currentStep: string;
percentage: number;
estimatedRemainingTime: number;
};
}
/**
* 创建任务规划
* @param data 任务规划请求数据
* @returns 任务规划响应
*/
export const createTaskPlanning = async (data: TaskPlanningRequest): Promise<TaskPlanningResponse> => {
const response = await request.post('/api/v1/task-planning', data);
return response.data;
};
/**
* 获取设备选择建议
* @param data 设备选择请求数据
* @returns 设备选择响应
*/
export const getDeviceSelection = async (data: DeviceSelectionRequest): Promise<DeviceSelectionResponse> => {
const response = await request.post('/api/v1/task-planning/device-selection', data);
return response.data;
};
/**
* 获取任务列表
* @param params 查询参数
* @returns 任务列表响应
*/
export const getTaskList = async (params: TaskListRequest): Promise<TaskListResponse> => {
const response = await request.get('/api/v1/task-planning/tasks', { params });
return response.data;
};
/**
* 获取任务详情
* @param taskId 任务ID
* @returns 任务详情响应
*/
export const getTaskDetail = async (taskId: string): Promise<TaskDetailResponse> => {
const response = await request.get(`/api/v1/task-planning/tasks/${taskId}`);
return response.data;
};
/**
* 更新任务状态
* @param taskId 任务ID
* @param status 新状态
* @returns 更新结果
*/
export const updateTaskStatus = async (taskId: string, status: string): Promise<void> => {
await request.put(`/api/v1/task-planning/tasks/${taskId}/status`, { status });
};
/**
* 删除任务
* @param taskId 任务ID
* @returns 删除结果
*/
export const deleteTask = async (taskId: string): Promise<void> => {
await request.delete(`/api/v1/task-planning/tasks/${taskId}`);
};
/**
* 执行任务
* @param taskId 任务ID
* @returns 执行结果
*/
export const executeTask = async (taskId: string): Promise<void> => {
await request.post(`/api/v1/task-planning/tasks/${taskId}/execute`);
};
/**
* 暂停任务
* @param taskId 任务ID
* @returns 暂停结果
*/
export const pauseTask = async (taskId: string): Promise<void> => {
await request.post(`/api/v1/task-planning/tasks/${taskId}/pause`);
};
/**
* 取消任务
* @param taskId 任务ID
* @returns 取消结果
*/
export const cancelTask = async (taskId: string): Promise<void> => {
await request.post(`/api/v1/task-planning/tasks/${taskId}/cancel`);
};
/**
* 获取任务执行日志
* @param taskId 任务ID
* @param page 页码
* @param pageSize 每页大小
* @returns 执行日志
*/
export const getTaskExecutionLog = async (
taskId: string,
page: number = 1,
pageSize: number = 50
): Promise<{
logs: Array<{
timestamp: string;
level: 'info' | 'warning' | 'error';
message: string;
deviceId?: string;
}>;
total: number;
page: number;
pageSize: number;
}> => {
const response = await request.get(`/api/v1/task-planning/tasks/${taskId}/logs`, {
params: { page, pageSize }
});
return response.data;
};
/**
* 获取任务统计信息
* @param startTime 开始时间
* @param endTime 结束时间
* @returns 统计信息
*/
export const getTaskStatistics = async (
startTime?: string,
endTime?: string
): Promise<{
totalTasks: number;
completedTasks: number;
failedTasks: number;
pendingTasks: number;
executingTasks: number;
averageExecutionTime: number;
successRate: number;
deviceUtilization: Array<{
deviceId: string;
deviceName: string;
utilizationRate: number;
taskCount: number;
}>;
}> => {
const response = await request.get('/api/v1/task-planning/statistics', {
params: { startTime, endTime }
});
return response.data;
};

563
src/api/taskWork.ts Normal file
View File

@@ -0,0 +1,563 @@
import request,{ CommonListResponse, IPage, IWorkspaceResponse } from "../api/http/request"
const workspace_id = localStorage.getItem("workspace_id")!
const jsonByUrl = function () {
return function (obj: any, isFirst: boolean = true) {
let url = '';
let indexData = 0;
for (let [key, value] of Object.entries(obj)) {
url += `${isFirst && indexData == 0 ? '?' : '&'}${key}=${value}`;
indexData++;
}
return url;
};
};
export const getAdEquipment = async (pagination: IPage, keyword: string = '') => {
try {
const response = await request.get(`/api/v1/ad/${workspace_id}/pageByKeyword?`,{ params: { page:pagination.page,page_size:pagination.page_size,keyword }})
return response.data
} catch (error) {
throw error
}
}
// 飞行统计
// 巡逻次数
export const getPatrolCount = async () => {
const apiUrl = `/dashboard/api/v1/dashboard/${workspace_id}/getPatrolCount`;
let responseData = await request(apiUrl);
return responseData.data as any;
};
//巡检数据统计
export const getCountAvgAndMaxInspections = async (params) => {
let paramsurl = jsonByUrl()(params);
const apiUrl = `/dashboard/api/v1/dashboard/${workspace_id}/countAvgAndMaxInspections${paramsurl}`;
let responseData = await request(apiUrl);
return responseData.data as any;
};
//基站top5
export const getCountDeviceTask = async (params) => {
let paramsurl = jsonByUrl()(params);
const apiUrl = `/dashboard/api/v1/dashboard/${workspace_id}/countDeviceTask${paramsurl}`;
let responseData = await request(apiUrl);
return responseData.data as any;
};
//媒体数据采集以及top5
export const getDeviceMedia = async (params) => {
let paramsurl = jsonByUrl()(params);
const apiUrl = `/dashboard/api/v1/dashboard/${workspace_id}/getDeviceMedia${paramsurl}`;
let responseData = await request(apiUrl);
return responseData.data as any;
};
export interface AllTaskParams {
state?: string;
status?: string;
current: string;
size: string;
startTime?: string;
endTime?: string;
}
//获取资源数据视频、图片
export const getTaskListMonthDay = async function (params: AllTaskParams): Promise<IWorkspaceResponse<any>> {
let paramsurl = jsonByUrl()(params);
const url = `/task/api/v1/task/${workspace_id}/getTaskListMonthDay${paramsurl}`;
const result = await request.get(url);
return result.data;
};
//获取巡检次数
export const getTaskCountByDateRange = async function (params: AllTaskParams): Promise<IWorkspaceResponse<any>> {
let paramsurl = jsonByUrl()(params);
const url = `/dashboard/api/v1/dashboard/${workspace_id}/getTaskCountByDateRange${paramsurl}`;
const result = await request.get(url);
return result.data;
};
// Get Wayline Jobs
export const getWaylineJobs = async function (workspaceId: string, page: IPage): Promise<IWorkspaceResponse<any>> {
const url = `/wayline/api/v1/workspaces/${workspaceId}/jobs?page=${page.page}&page_size=${page.page_size}&device_sn=`;
const result = await request.get(url);
return result.data;
};
export const getAiLiveData = async() => {
let url = `/aiIdentify/checkServices`;
const result = await request.get(url);
return result.data;
};
// Get Device Topo
export const getDeviceTopo = async function (): Promise<IWorkspaceResponse<any>> {
const url = `/manage/api/v1/devices/${workspace_id}/devices`;
const result = await request.get(url);
return result.data;
};
//get allClue
export interface IClueRequest {
/**
* ai类型
*/
aiType?: number;
/**
* 当前页数
*/
current: number;
/**
* 查询结束时间(毫秒)
*/
endTime: number;
/**
* 默认1
*/
flag: number;
/**
* 分页数
*/
size: number;
/**
* 查询开始时间(毫秒)
*/
startTime: number;
[property: string]: any;
}
export const getAllClue = async function (params:IClueRequest): Promise<IWorkspaceResponse<any>> {
const url = `/task/api/v1/task/${workspace_id}/getAllClue`;
const result = await request.get(url,{params});
return result.data;
};
// 网格线索确认状态
export interface IConfirmAnalysis {
/**
* 线索分析状态(1.待分析待核实 2.已分析待核实 3已核实)
*/
analysisStatus: string;
/**
* 线索ID
*/
clueId: string;
[property: string]: any;
}
//通过审核线索
export const confirmAnalysis = async function (body:IConfirmAnalysis): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/task/${workspace_id}/clue/analysis/status`;
const result = await request.put(url,body);
return result.data;
};
//根据gridId获取网格人员
export const getUserListByGridId = async function (grid_id:string): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/grid/${workspace_id}/userListByGridId?grid_id=${grid_id}`;
const result = await request.get(url);
return result.data;
};
//获取网格线索下拉
export const getClueRegion = async function (): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/task/${workspace_id}/clue/region`;
const result = await request.get(url);
return result.data;
};
export interface IAnalyseStatistics {
/**
* ai类型 如16、25
*/
aiType?: string;
/**
* 天数用于控制折线图如果不传值默认30天
*/
days?: string;
/**
* 机场设备SN 如: 4TADL21001002H
*/
deviceSn?: string;
endTime?: number;
startTime?: number;
[property: string]: any;
}
export const getAnalysisStatistics = async function (params:IAnalyseStatistics): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/task/${workspace_id}/clue/analyse/statistics`;
const result = await request.get(url, {params});
return result.data;
};
//获取工单等级
export const getPriorityLevel = async function (): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/task/${workspace_id}/ticket/priority_level`;
const result = await request.get(url, );
return result.data;
};
//获取工单类型
export const getPriorityType = async function (): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/task/${workspace_id}/ticket/type`;
const result = await request.get(url, );
return result.data;
};
//获取工单列表
export interface ITicketParams {
/**
* 巡查周期结束
*/
end_time?: number;
/**
* 组织id
*/
group_id?: string;
/**
* 工单名
*/
name?: string;
page: number;
page_size: number;
/**
* 优先等级
*/
priority_level?: number;
/**
* 巡查周期开始
*/
start_time?: number;
/**
* 工单类型
*/
ticket_type?: number;
[property: string]: any;
}
//获取工单列表
export const getTicketList = async function (params:ITicketParams): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/task/${workspace_id}/ticket/page`;
const result = await request.get(url, {params});
return result.data;
};
//删除工单
//删除
export const deleteTicketById = async function (id: string): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/task/${workspace_id}/ticket/${id}`;
return request.delete(url);
};
//获取工单统计
export const getTicketStatistics = async function (): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/task/${workspace_id}/ticket/stats`;
const result = await request.get(url);
return result.data;
};
//提交工单
export interface IAddTicket {
/**
* 组织id
*/
group_id: number;
/**
* 巡查结束时间
*/
inspection_end_time: number;
/**
* 识别模式单选0 图片 1 实时
*/
inspection_mode: number;
/**
* 巡查开始时间
*/
inspection_start_time: number;
/**
* 图片纬度
*/
lat: number;
/**
* 图片经度
*/
lng: number;
/**
* 工单名称
*/
name: string;
/**
* 优先等级单选0 一般 1 中等 2 极高 3 重要
*/
priority_level: number;
/**
* 工单描述
*/
remark: string;
/**
* 工单类型(单选),城管、国土、消防、交通、水务、环保、应急、公安
*/
ticket_type: number;
[property: string]: any;
}
export const addTicket = async (body: IAddTicket): Promise<IWorkspaceResponse<{ id: string }>> => {
const url = `/api/v1/task/${workspace_id}/ticket`;
const result = await request.post(url, body);
return result.data;
};
export interface IJobsTicket {
/**
* 机场sn执行机场sn
*/
dock_sn: string;
/**
* 航线文件航线列表接口中航线idid
*/
file_id: string;
/**
* 任务周期结束,时间戳
*/
inspection_end_time: number;
/**
* 任务周期开始,时间戳
*/
inspection_start_time: number;
/**
* 最小存储电量,条件飞行有效
*/
min_battery_capacity?: number;
/**
* 最小存储容量,条件飞行有效,在条件任务中才可以使用
*/
min_storage_capacity?: number;
/**
* 航线名称,航线名不可重复
*/
name: string;
/**
* 失联策略丢失信号后无人机动作1:返航 2:继续执行)
*/
out_of_control_action: number;
/**
* 返航高度返航高度建议100米以上
*/
rth_altitude: number;
/**
* 任务时间
*/
task_days: number[];
/**
* 任务周期
*/
task_periods: Array<number[]>;
/**
* 任务类型 0:立即飞行 1:单次定时
*/
task_type: number;
/**
* 工单id
*/
ticket_id: number;
/**
* 航线类型, 0:waypoint
*/
wayline_type: number;
[property: string]: any;
}
export const addJobsTicket = async (body: IJobsTicket): Promise<IWorkspaceResponse<{ id: string }>> => {
const url = `/wayline/api/v1/workspaces/${workspace_id}/jobs/ticket`;
const result = await request.post(url, body);
return result.data;
};
export interface ITicketJobsParams {
/**
* 设备的SN如 4TADL21001002H,7CTDLCE00A215X可传递一个多个逗号拼接
*/
device_sn?: string;
/**
* 计划名称
*/
job_name?: string;
page: number;
pageSize: number;
/**
* 工单id
*/
ticket_id?: number;
[property: string]: any;
}
export interface IJobList {
job_id: string;
job_name: string;
file_id: string;
file_name: string;
dock_sn: string;
dock_name: string;
workspace_id: string;
wayline_type: number;
task_type: number;
execute_time: string;
begin_time: string;
end_time: string;
completed_time: string;
status: number;
username: string;
code: number;
rth_altitude: number;
out_of_control_action: number;
media_count: number;
uploaded_count: number;
}
export const getTicketJobs = async (params: ITicketJobsParams): Promise<CommonListResponse<IJobList>> => {
const url = `/wayline/api/v1/workspaces/${workspace_id}/jobs`;
const result = await request.get(url, {params});
return result.data;
};
export const getTicketJobsData = async (params: ITicketJobsParams): Promise<CommonListResponse<IJobList>> => {
const url = `/task/api/v1/task/${workspace_id}/getTaskDetail`;
const result = await request.get(url, {params});
return result.data;
};
export const getTicketCluePage = async (params: ITicketJobsParams): Promise<IWorkspaceResponse<{ id: string }>> => {
const url = `/task/api/v1/task/${workspace_id}/ticket_clue_page`;
const result = await request.get(url, {params});
return result.data;
};
export const getWorkTaskByJobId = async (job_id:string): Promise<IWorkspaceResponse<{ id: string }>> => {
const url = `/task/api/v1/task/${workspace_id}/get_work_task_by_job_id`;
const result = await request.get(url, {params:{job_id}});
return result.data;
};
//获取申请设备列表
export const getDeclareDevices = async (page: number, page_size: number,device_sn?:string,start_time?: number,end_time?:number): Promise<IWorkspaceResponse<{ id: string }>> => {
const url = `/api/v1/device/${workspace_id}/declare/page`;
const result = await request.get(url, {params:{page,page_size,device_sn,start_time,end_time}});
return result.data;
};
export const getOneDeclareDevice = async function (id: string): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/device/${workspace_id}/declare/${id}`;
const result = await request.get(url, );
return result.data;
};
//新增申请设备
export interface IDeclareDevice {
/**
* 身份证号
*/
cardId: string;
/**
* 证件编号
*/
certificate_number: string;
/**
* 申请名称,用户或组织名称
*/
declare_name: string;
/**
* 设备sn
*/
device_sn: string;
/**
* 设备类型M3T,M3E
*/
device_type: string;
/**
* 手机号
*/
mobile: string;
[property: string]: any;
}
export const addDeclareDevice = async (body:IDeclareDevice): Promise<IWorkspaceResponse<{ id: string }>> => {
const url = `/api/v1/device/${workspace_id}/declare`;
const result = await request.post(url, body);
return result.data;
};
export const editDeclareDevice = async (id: string, body:IDeclareDevice): Promise<IWorkspaceResponse<{ id: string }>> => {
const url = `/api/v1/device/${workspace_id}/declare/${id}`;
const result = await request.put(url, body);
return result.data;
};
export const deleteDeclareDevice = async function (id: string): Promise<IWorkspaceResponse<any>> {
const url = `/api/v1/device/${workspace_id}/declare/${id}`;
return request.delete(url);
};
//分页获取飞行任务线索分析
export interface ITaskAnalyzeClue {
/**
* ai类型 没有传 -1
*/
aiType?: number;
/**
* 线索分析状态(1.待分析待核实 2.已分析待核实 3已核实)
*/
analysisStatus?: string;
/**
* 当前页数
*/
current: number;
/**
* 0正常危险程度递增 现在默认是3
*/
dangerLevel?: string;
/**
* 设备sn
*/
deviceSn?: string;
/**
* 查询结束时间(毫秒)
*/
endTime: number;
/**
* 默认1
*/
flag: number;
/**
* 分页数
*/
size: number;
/**
* 查询开始时间(毫秒)
*/
startTime: number;
/**
* 工单类型
*/
ticketType?: number;
[property: string]: any;
}
export const getTaskAnalyzeClue = async (params): Promise<IWorkspaceResponse<{ id: string }>> => {
const url = `/task/api/v1/task/${workspace_id}/get_page_task_analyze_clue`;
const result = await request.get(url, {params});
return result.data;
};
export const getTaskClueByParams = async (taskId: string,params:any) => {
const url = `/task/api/v1/task/${workspace_id}/${taskId}/getTaskClue`;
const result = await request.get(url,{params});
return result.data;
};
export const getClueByTaskId = async (taskId: string) => {
const url = `/task/api/v1/task/${workspace_id}/${taskId}/getClue`;
const result = await request.get(url);
return result.data;
};
export const getMediaListAll = async (workspaceId: string,taskId: string) => {
const url = `/task/api/v1/task/${workspaceId}/${taskId}/getMediaListAll`;
const result = await request.get(url);
return result.data;
};
export const getTaskFileData = async (params:any) => {
const url = `/task/api/v1/task/${workspace_id}/getTaskFileData`;
const result = await request.get(url,{params});
return result.data;
}
export interface IFilePageParams {
page: number;
page_size: number;
searchAll:string
file_type:string
}
export const getFilePage = async (params:IFilePageParams) => {
const url = `/media/api/v1/files/${workspace_id}/files/getFilePage`;
const result = await request.get(url,{params});
return result.data;
}
export const updateFileMapState = async (fileId:string,params:any) => {
const url = `/media/api/v1/files/${workspace_id}/files/updateFile/${fileId}`;
const result = await request.put(url,params);
return result.data;
}

352
src/api/wayline.ts Normal file
View File

@@ -0,0 +1,352 @@
import { message } from 'ant-design-vue';
import request, { IPage, IWorkspaceResponse, IListWorkspaceResponse } from '/@/api/http/request';
import { TaskType, TaskStatus, OutOfControlAction } from '/@/types/task';
import { WaylineType } from '/@/types/wayline';
import { ComputedRef } from 'vue';
import { Moment } from 'moment';
const HTTP_PREFIX = '/wayline/api/v1';
// 获取单条航线信息
export const getRouteInfo = async function (workspaceId: string, waylineId: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/waylines/${waylineId}/getOne`;
const result = await request.get(url);
return result.data;
};
// 获取单条航线信息
export const getMapConfig = async function (): Promise<IWorkspaceResponse<any>> {
let workSpaceId= localStorage.getItem('workspace_id');
const url = `/model/${workSpaceId}/file/config`;
const result = await request.get(url);
return result.data;
};
// Get Wayline Files
export const getWaylineFiles = async function (wid: string, body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${wid}/waylines/pc?order_by=${body.order_by}&page=${body.page}&page_size=${body.page_size}&payloadModelKeys=${body.payloadModelKeys}&droneModelKey=${body.droneModelKey}`;
const result = await request.get(url);
return result.data;
};
export const getDeviceBound = async function (wid: string, body: {}): Promise<IWorkspaceResponse<any>> {
const url = `/manage/api/v1/devices/${wid}/devices/bound`;
const result = await request.get(url,{
params:body
});
return result.data;
};
export const getTaskDataAll = async function (wid: string, body: {}): Promise<IWorkspaceResponse<any>> {
const url = `/task/api/v1/task/${wid}/getTaskListMonthDay`;
const result = await request.get(url,{
params:body
});
return result.data;
};
export const getTaskDataClue = async function (wid: string,taskId:string="ea605789-6759-4189-bbe3-7d26d37bb728"): Promise<IWorkspaceResponse<any>> {
const url = `/task/api/v1/task/${wid}/${taskId}/getTaskClue`;
const result = await request.get(url);
return result.data;
};
// Get Wayline Files
export const getWaylineFilesMenu = async function (wid: string, body: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${wid}/waylines/pc/menu`;
const result = await request.get(url,{
params:body
});
return result.data;
};
// Download Wayline File
export const downloadWaylineFile = async function (workspaceId: string, waylineId: string): Promise<any> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/waylines/${waylineId}/url`;
const result = await request.get(url, { responseType: 'blob' });
if (result.data.type === 'application/json') {
const reader = new FileReader();
reader.onload = function (e) {
const text = reader.result as string;
const result = JSON.parse(text);
// message.error(result.message)
};
reader.readAsText(result.data, 'utf-8');
} else {
return result.data;
}
};
// Delete Wayline File
export const deleteWaylineFile = async function (workspaceId: string, waylineId: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/waylines/${waylineId}`;
const result = await request.delete(url);
return result.data;
};
export interface AiVideo {
aiRecognition: boolean;
recognitionType: string;
video_quality: string;
execute_time?: number, // 执行时间(毫秒)
}
export interface CreatePlan extends AiVideo {
name: string,
file_id: string,
dock_sn: string,
task_type: TaskType, // 任务类型
wayline_type: WaylineType, // 航线类型
task_days?: number[] // 执行任务的日期(秒)
task_periods?: number[][], // 执行任务的时间点(秒)
rth_altitude: number, // 相对机场返航高度 20 - 500
out_of_control_action: OutOfControlAction // 失控动作
min_battery_capacity?: number, // The minimum battery capacity of aircraft.
min_storage_capacity?: number, // The minimum storage capacity of dock and aircraft.
}
export interface RepeatTimed {
repeatMode: number;
cron: string;
}
interface PlanBody {
name: string;
file_id: string | ComputedRef<string>;
dock_sn: string | ComputedRef<string>;
task_type: number | string;
select_execute_time?: Moment | undefined;
out_of_control_action: string | number;
}
interface CreatePlanBodyData {
aiRecognition: boolean;
recognitionType: number;
cameraMode: 'ir' | 'normal';
sharpness: string;
}
interface CreatePlanInfo {
task_days: string;
task_periods: string;
rth_altitude?: number;
wayline_type: string | any;
}
export type CreatePlanBody = PlanBody & CreatePlanBodyData & CreatePlanInfo & RepeatTimed;
// Create Wayline Job
export const createPlan = async function (workspaceId: string, plan: CreatePlanBody): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/flight-tasks`;
const result = await request.post(url, plan);
return result.data;
};
export interface Task {
job_id: string;
job_name: string;
task_type: TaskType; // 任务类型
file_id: string; // 航线文件id
file_name: string; // 航线名称
wayline_type: WaylineType; // 航线类型
dock_sn: string;
dock_name: string;
workspace_id: string;
username: string;
begin_time: string;
end_time: string;
execute_time: string;
completed_time: string;
status: TaskStatus; // 任务状态
progress: number; // 执行进度
code: number; // 错误码
rth_altitude: number; // 相对机场返航高度 20 - 500
out_of_control_action: OutOfControlAction; // 失控动作
media_count: number; // 媒体数量
uploading: boolean; // 是否正在上传媒体
uploaded_count: number; // 已上传媒体数量
}
// Get Wayline Jobs
export const getWaylineJobs = async function (workspaceId: string, page: IPage): Promise<IListWorkspaceResponse<Task>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs?page=${page.page}&page_size=${page.page_size}`;
const result = await request.get(url);
return result.data;
};
export interface DeleteTaskParams {
job_id: string;
}
// 删除机场任务
export async function deleteTask(workspaceId: string, params: DeleteTaskParams): Promise<IWorkspaceResponse<{}>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs`;
const result = await request.delete(url, {
params: params,
});
return result.data;
}
export enum UpdateTaskStatus {
Suspend = 0, // 暂停
Resume = 1, // 恢复
}
export interface UpdateTaskStatusBody {
sn: string;
status: UpdateTaskStatus | number;
}
export async function getJobIdBySn(workspaceId: string, dock_sn: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/find/job/${dock_sn}`
const result = await request.get(url)
return result.data
}
// 恢复航线
export async function RestoreTaskStatus(workspaceId: string, body: UpdateTaskStatusBody): Promise<IWorkspaceResponse<{}>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${body.sn}`
const result = await request.put(url, {
status: body.status
})
return result.data
}
// 更新机场任务状态
export async function updateTaskStatus(workspaceId: string, body: UpdateTaskStatusBody): Promise<IWorkspaceResponse<{}>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${body.job_id}`
const result = await request.put(url, {
status: body.status
})
return result.data
}
// 启用定时任务任务状态
export async function startRepeatJob(workspaceId: string, job_id: string): Promise<IWorkspaceResponse<{}>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/startRepeatJob/${job_id}`
const result = await request.put(url, {
})
return result.data
}
// 停用定时任务任务状态
export async function stopRepeatJob(workspaceId: string, job_id: string): Promise<IWorkspaceResponse<{}>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/stopRepeatJob/${job_id}`
const result = await request.put(url, {
})
return result.data
}
//是否开启了AI
export async function getJobByJobId(workspaceId: string, job_id: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/getJobByJobId/${job_id}`
const result = await request.get(url, {
})
return result.data
}
// Upload Wayline file
export const importKmzFile = async function (workspaceId: string, file: {}): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/waylines/file/upload`;
const result = await request.post(url, file, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
return result.data;
};
// 媒体立即上传
export const uploadMediaFileNow = async function (workspaceId: string, jobId: string): Promise<IWorkspaceResponse<{}>> {
const url = `${HTTP_PREFIX}/workspaces/${workspaceId}/jobs/${jobId}/media-highest`
const result = await request.post(url)
return result.data
}
// 获取航线
export const getTrack = async function (sn: string, taskId: string,startTime:string,endTime:string): Promise<IWorkspaceResponse<{}>> {
const url = `/aiIdentify/getOSDBySN?sn=${sn}&taskId=${taskId}&startTime=${startTime}&endTime=${endTime}`
const result = await request.get(url)
return result.data
}
// 获取航线
export const getWaylineByJobId = async function (job_id: string): Promise<IWorkspaceResponse<{ lon: string; lat: string }[]>> {
const workspace_id = localStorage.getItem('workspace_id');
const url = `/wayline/api/v1/workspaces/${workspace_id}/waylines/get/jobId/${job_id}`;
const result = await request.get(url);
return result.data;
};
//获取航线视频
export const getMediaByData = async function (taskId: string): Promise<IWorkspaceResponse<{}>> {
const workspace_id = localStorage.getItem('workspace_id');
const url = `/wayline/api/v1/workspaces/${workspace_id}/waylines/get/Media/${taskId}`;
const result = await request.get(url);
if(
result.data.code == 0 &&
result.data.data &&
result.data.data.filePath
){
return result.data;
}else{
let getMediaAiData = await getMediaAi(taskId)
console.info("getMediaAiData",getMediaAiData)
return getMediaAiData
}
};
/**
* @param taskId 任务id
* @returns
*/
export const getMediaAi = async function (taskId: string): Promise<IWorkspaceResponse<{}>> {
const workspace_id = localStorage.getItem('workspace_id');
const url = `/wayline/api/v1/workspaces/${workspace_id}/waylines/get/mediaAi/${taskId}`;
const result = await request.get(url);
if(
result.data.code == 0 &&
result.data.data &&
result.data.data.filePath
){
return result.data;
}
return {
code:0,
message:"",
data:{
filePath:"https://shanghai-jsredfly-test.oss-cn-shanghai.aliyuncs.com/test/202311220001.mp4"
}
}
};
/**
* @param taskId 任务id
* @returns
*/
export const getMedia = async function (taskId: string): Promise<IWorkspaceResponse<{}>> {
const workspace_id = localStorage.getItem('workspace_id');
const url = `/wayline/api/v1/workspaces/${workspace_id}/waylines/get/mediaVideo/${taskId}`;
const result = await request.get(url);
if(
result.data.code == 0 &&
result.data.data &&
result.data.data.filePath
){
return result.data;
}else{
let getMediaAiData = await getMediaAi(taskId)
console.info("getMediaAiData",getMediaAiData)
return getMediaAiData
}
};
// 获取航线
export const getWaylineByMenu = async function (): Promise<IWorkspaceResponse<{ lon: string; lat: string }[]>> {
const workspace_id = localStorage.getItem('workspace_id');
const url = `/wayline/api/v1/wayline/menu/${workspace_id}/list`;
const result = await request.get(url);
return result.data;
};

View File

@@ -0,0 +1,410 @@
# GK数据后端API集成指南
## 🎯 后端接口分析
您提供的后端接口:
```
http://27.11.11.30:6791/api/v1/nationalScience-events/e3dea0f5-37f2-4d79-ae58-490af3228069/ue-device/tasks
```
### 📋 接口结构分析
```
协议://域名:端口/API版本/业务模块/事件ID/设备类型/资源
http://27.11.11.30:6791/api/v1/nationalScience-events/e3dea0f5-37f2-4d79-ae58-490af3228069/ue-device/tasks
```
**接口组成部分:**
- **基础URL**: `http://27.11.11.30:6791/api/v1`
- **业务模块**: `nationalScience-events` (国科事件)
- **事件ID**: `e3dea0f5-37f2-4d79-ae58-490af3228069`
- **设备类型**: `ue-device` (无人装备)
- **资源**: `tasks` (任务集合)
## 🔧 已实现的功能
### 1. **GKDataBackendService** - 后端存储服务
专门适配您的后端接口提供完整的CRUD操作
```typescript
// 创建服务实例
const backendService = new GKDataBackendService({
baseUrl: 'http://27.11.11.30:6791/api/v1',
eventId: 'e3dea0f5-37f2-4d79-ae58-490af3228069',
deviceType: 'ue-device',
timeout: 15000
})
// 保存任务
await backendService.saveTask(taskData)
// 获取任务列表
await backendService.getTasks({ page: 1, pageSize: 20 })
// 更新任务
await backendService.updateTask(taskId, updates)
// 删除任务
await backendService.deleteTask(taskId)
```
### 2. **GKDataApiTester** - API测试工具
用于测试后端接口的可用性和数据格式:
```typescript
// 创建测试器
const tester = new GKDataApiTester({
baseUrl: 'http://27.11.11.30:6791/api/v1',
eventId: 'e3dea0f5-37f2-4d79-ae58-490af3228069',
deviceType: 'ue-device'
})
// 运行完整测试
const results = await tester.runFullTest(sampleTask)
```
### 3. **GKApiTestPanel** - 可视化测试面板
提供界面化的API测试工具可以直接在浏览器中测试
- 🔗 **连接测试**: 验证API可访问性
- 📋 **获取任务**: 测试GET请求
- 💾 **保存任务**: 测试POST请求
- 📦 **批量保存**: 测试批量操作
- 🧪 **快速测试**: 运行完整测试套件
## 📊 数据格式转换
### GK数据 → 后端API格式
```typescript
// GK原始数据
interface ParsedTaskInfo {
taskId: string
taskName: string
taskType: string
priority: number
battleUnitCode: string
sn: string
routePoints: RoutePoint[]
positionPoints: PositionPoint[]
// ...
}
// 转换为后端格式
interface BackendTaskData {
taskId: string
taskName: string
taskType: number // 转换为数字
priority: number
battleUnitCode: string
deviceSn: string // 字段名调整
startTime: string // ISO格式
endTime: string
routePoints: Array<{
longitude: number
latitude: number
altitude: number
index?: number // 添加序号
speed?: number
action?: string
}>
positionPoints: Array<{
longitude: number
latitude: number
altitude: number
targetType?: string // 扩展字段
description?: string
}>
source: 'gk_panel' // 数据来源标识
metadata: { // 元数据
clientVersion: string
userAgent: string
timestamp: string
}
createdAt: string
updatedAt: string
}
```
## 🚀 使用步骤
### 1. **打开API测试面板**
在GK数据面板中点击 `🔌` 按钮打开API测试面板。
### 2. **配置API参数**
```
基础URL: http://27.11.11.30:6791/api/v1
事件ID: e3dea0f5-37f2-4d79-ae58-490af3228069
设备类型: ue-device
超时时间: 15000
```
### 3. **运行测试**
#### **连接测试**
```bash
GET http://27.11.11.30:6791/api/v1/nationalScience-events/e3dea0f5-37f2-4d79-ae58-490af3228069/ue-device/tasks
```
验证接口是否可访问。
#### **保存任务测试**
```bash
POST http://27.11.11.30:6791/api/v1/nationalScience-events/e3dea0f5-37f2-4d79-ae58-490af3228069/ue-device/tasks
Content-Type: application/json
Authorization: Bearer <token>
{
"taskId": "API_TEST_1734567890",
"taskName": "API测试任务",
"taskType": 1,
"priority": 3,
"battleUnitCode": "API_TEST_UNIT",
"deviceSn": "TEST_DEVICE_001",
"routePoints": [...],
"positionPoints": [...],
"source": "gk_panel"
}
```
#### **获取任务列表测试**
```bash
GET http://27.11.11.30:6791/api/v1/nationalScience-events/e3dea0f5-37f2-4d79-ae58-490af3228069/ue-device/tasks?page=1&pageSize=10
```
### 4. **查看测试结果**
测试面板会显示:
- ✅ 成功的操作及响应时间
- ❌ 失败的操作及错误信息
- 📊 测试统计信息
- 📝 详细的响应数据
## 🔄 集成到GK数据面板
### 自动存储到后端
```typescript
// 在GKDataPanel中集成后端存储
import { getGKBackendService } from '../../../api/websocket/gkDataBackendService'
const backendService = getGKBackendService({
baseUrl: 'http://27.11.11.30:6791/api/v1',
eventId: 'e3dea0f5-37f2-4d79-ae58-490af3228069',
deviceType: 'ue-device'
})
// 接收到新任务时自动保存
EventBus.on('GKData-ParsedTask', async (taskInfo) => {
// 保存到本地存储
gkDataStorage.saveTask(taskInfo)
// 同时保存到后端数据库
try {
const result = await backendService.saveTask(taskInfo)
if (result.success) {
console.log('任务已保存到后端:', result.taskId)
}
} catch (error) {
console.error('保存到后端失败:', error)
}
})
```
### 从后端加载历史数据
```typescript
// 加载历史任务
const loadHistoryFromBackend = async () => {
try {
const result = await backendService.getTasks({
page: 1,
pageSize: 100,
startDate: '2024-01-01',
endDate: new Date().toISOString()
})
if (result.success) {
// 将后端数据同步到本地存储
for (const task of result.tasks) {
gkDataStorage.saveTask(convertBackendToLocal(task))
}
console.log(`从后端加载了 ${result.tasks.length} 个历史任务`)
}
} catch (error) {
console.error('加载后端数据失败:', error)
}
}
```
## 📋 API接口规范
### 请求格式
**Headers:**
```
Content-Type: application/json
Authorization: Bearer <your-token>
```
**POST /tasks** - 创建任务
```json
{
"taskId": "string",
"taskName": "string",
"taskType": number,
"priority": number,
"battleUnitCode": "string",
"deviceSn": "string",
"startTime": "ISO8601",
"endTime": "ISO8601",
"routePoints": [
{
"longitude": number,
"latitude": number,
"altitude": number,
"index": number
}
],
"positionPoints": [
{
"longitude": number,
"latitude": number,
"altitude": number
}
],
"source": "gk_panel"
}
```
**GET /tasks** - 获取任务列表
```
?page=1&pageSize=20&startDate=2024-01-01&endDate=2024-12-31
```
**PUT /tasks/{taskId}** - 更新任务
```json
{
"taskName": "string",
"priority": number,
"userNotes": "string",
"isImportant": boolean
}
```
**DELETE /tasks/{taskId}** - 删除任务
### 响应格式
**成功响应:**
```json
{
"code": 200,
"message": "操作成功",
"data": { ... },
"success": true,
"timestamp": 1734567890
}
```
**错误响应:**
```json
{
"code": 400,
"message": "请求参数错误",
"success": false,
"timestamp": 1734567890
}
```
## 🛠️ 故障排除
### 1. 连接问题
- 检查网络连通性
- 验证URL地址是否正确
- 确认端口6791是否开放
### 2. 认证问题
- 检查token是否有效
- 确认token格式是否正确
- 验证用户权限
### 3. 数据格式问题
- 确认请求体格式符合后端要求
- 检查必填字段是否完整
- 验证数据类型是否正确
### 4. 超时问题
- 调整timeout配置
- 检查网络延迟
- 确认服务器响应时间
## 📈 性能优化
### 1. 批量操作
- 使用批量保存减少请求次数
- 实现请求队列管理
- 添加请求重试机制
### 2. 缓存策略
- 本地存储作为缓存
- 离线模式支持
- 增量同步机制
### 3. 错误处理
- 自动重试失败的请求
- 离线队列机制
- 冲突解决策略
## 🎉 完整示例
```typescript
// 完整的后端集成示例
import { GKDataBackendService } from '@/api/websocket/gkDataBackendService'
import { gkDataStorage } from '@/api/websocket/gkDataStorage'
// 初始化后端服务
const backendService = new GKDataBackendService({
baseUrl: 'http://27.11.11.30:6791/api/v1',
eventId: 'e3dea0f5-37f2-4d79-ae58-490af3228069',
deviceType: 'ue-device',
autoSync: true,
enableRetry: true
})
// 双重存储:本地 + 后端
const saveTaskBoth = async (taskData) => {
// 保存到本地存储(立即可用)
gkDataStorage.saveTask(taskData)
// 保存到后端数据库(持久化)
try {
const result = await backendService.saveTask(taskData)
if (result.success) {
console.log('✅ 任务已保存到后端和本地')
return true
}
} catch (error) {
console.error('❌ 后端保存失败,已保存到本地:', error)
return false
}
}
// 数据同步
const syncData = async () => {
try {
// 获取本地数据
const localTasks = gkDataStorage.getAllTasks()
// 批量同步到后端
const result = await backendService.saveTasks(localTasks)
console.log(`同步完成: 成功 ${result.success}, 失败 ${result.failed}`)
} catch (error) {
console.error('数据同步失败:', error)
}
}
```
现在您可以使用这个完整的后端集成方案实现GK数据的永久存储🚁✨

View File

@@ -0,0 +1,282 @@
# GK数据WebSocket监听器 - 配置完成
## 🎉 配置完成总结
已成功为你的无人机指挥屏项目配置了专门的GK数据WebSocket监听器用于接收和处理来自GK_SERVER的数据推送业务代码`gk_data`)。
## 📁 创建的文件列表
### 核心文件
1. **`src/api/websocket/gkDataListener.ts`** - GK数据监听器核心类
2. **`src/api/websocket/gkDataManager.ts`** - GK数据管理器提供高级功能
3. **`src/api/websocket/gkDataConfig.ts`** - WebSocket配置管理器
### 示例和演示
4. **`src/api/websocket/gkDataExample.ts`** - 详细使用示例
5. **`src/api/websocket/gkDataDemo.vue`** - 可视化演示组件
6. **`src/api/websocket/gkDataUsageExample.vue`** - 实际使用示例
### 文档
7. **`src/api/websocket/README_GK.md`** - 完整使用文档
8. **`src/api/websocket/gkDataIntegration.md`** - 项目集成指南
9. **`src/api/websocket/GK_DATA_SETUP.md`** - 本配置总结文档
## 🌐 WebSocket配置
### 主服务器(当前使用)
```
URL: ws://27.11.11.30/dji/api/v1/ws
```
### 备用服务器
```
URL: ws://221.226.33.58:18800/dji/api/v1/ws
```
### 连接特性
- ✅ 自动重连机制
- ✅ 主备服务器切换
- ✅ 连接状态监控
- ✅ 错误处理和日志记录
## 🚀 快速开始
### 1. 基础使用
```typescript
import { GKDataListener } from '@/api/websocket/gkDataListener'
const listener = new GKDataListener('ws://27.11.11.30/dji/api/v1/ws')
listener.registerDataHandler((gkData) => {
console.log('接收到GK数据:', gkData)
// 处理任务信息
if (gkData.data.task_info) {
console.log('任务:', gkData.data.task_info.task_name)
}
// 处理位置数据
if (gkData.data.position_data) {
console.log('位置:', gkData.data.position_data.latitude, gkData.data.position_data.longitude)
}
})
listener.initSocket()
```
### 2. 使用配置管理器
```typescript
import { GKDataManager } from '@/api/websocket/gkDataManager'
import { getGKWebSocketUrl } from '@/api/websocket/gkDataConfig'
const manager = new GKDataManager({
url: getGKWebSocketUrl(), // 自动使用配置的服务器地址
autoConnect: true,
enableLogging: true,
dataBufferSize: 100
})
manager.init()
// 获取最新数据
const latestTaskInfo = manager.getLatestTaskInfo()
const latestPositionData = manager.getLatestPositionData()
```
### 3. 在Vue组件中使用
```vue
<script setup>
import { GKDataManager } from '@/api/websocket/gkDataManager'
import { getGKWebSocketUrl } from '@/api/websocket/gkDataConfig'
const manager = new GKDataManager({
url: getGKWebSocketUrl()
})
onMounted(() => {
manager.init()
})
onUnmounted(() => {
manager.close()
})
</script>
```
## 📊 数据格式
GK_SERVER推送的数据格式如下
```json
{
"biz_code": "gk_data",
"timestamp": "2024-12-19T10:30:00Z",
"data": {
"task_info": {
"task_id": "task_001",
"task_name": "巡航任务",
"status": "执行中",
"create_time": "2024-12-19T10:00:00Z"
},
"route_data": {
"route_id": "route_001",
"route_name": "巡逻路线A",
"waypoints": [
{
"index": 1,
"latitude": 39.9042,
"longitude": 116.4074,
"altitude": 100
}
],
"total_distance": 1500,
"estimated_time": 900
},
"position_data": {
"device_id": "drone_001",
"latitude": 39.9042,
"longitude": 116.4074,
"altitude": 100,
"heading": 45,
"speed": 15,
"timestamp": "2024-12-19T10:30:00Z",
"battery_level": 85,
"signal_strength": 4
}
}
}
```
## 🎯 事件监听
系统提供丰富的事件监听机制:
```typescript
import EventBus from '@/api/im/eventBus.js'
// 连接状态事件
EventBus.on('GKDataManager-Connected', (data) => {
console.log('GK数据服务器连接成功')
})
EventBus.on('GKDataManager-Disconnected', (data) => {
console.log('GK数据服务器连接断开')
})
// 数据事件
EventBus.on('GKDataManager-TaskInfoUpdated', (taskInfo) => {
console.log('任务信息更新:', taskInfo)
})
EventBus.on('GKDataManager-PositionDataUpdated', (positionData) => {
console.log('位置数据更新:', positionData)
})
EventBus.on('GKDataManager-RouteDataUpdated', (routeData) => {
console.log('路线数据更新:', routeData)
})
```
## 🧪 测试和演示
### 使用演示组件
```vue
<template>
<div>
<!-- 基础演示组件 -->
<GKDataDemo />
<!-- 实际使用示例 -->
<GKDataUsageExample />
</div>
</template>
<script>
import GKDataDemo from '@/api/websocket/gkDataDemo.vue'
import GKDataUsageExample from '@/api/websocket/gkDataUsageExample.vue'
export default {
components: {
GKDataDemo,
GKDataUsageExample
}
}
</script>
```
### 手动测试
1. 打开浏览器开发者工具
2. 导入演示组件到你的页面
3. 查看控制台日志确认连接状态
4. 检查网络面板确认WebSocket连接
## 📈 项目集成
### 在无人机指挥屏中集成
1. **地图集成** - 将位置数据实时显示在mars3d地图上
2. **任务管理** - 集成到现有的任务管理系统
3. **状态监控** - 添加到系统状态面板
4. **数据存储** - 与Vuex/Pinia状态管理集成
详细集成方法请参考:`src/api/websocket/gkDataIntegration.md`
## 🔧 配置管理
### 主备服务器切换
```typescript
import { gkWebSocketConfigManager } from '@/api/websocket/gkDataConfig'
// 获取当前服务器URL
const currentUrl = gkWebSocketConfigManager.getCurrentUrl()
// 切换到备用服务器
gkWebSocketConfigManager.switchToFallback()
// 重置到主服务器
gkWebSocketConfigManager.resetToPrimary()
```
### 重连配置
系统默认配置:
- 最大重连延迟20秒
- 最小重连延迟5秒
- 最大重试次数10次
- 延迟增长因子1.3
## 🚨 注意事项
1. **业务代码过滤**:只处理 `biz_code: 'gk_data'` 的消息
2. **内存管理**:数据缓冲区有大小限制,避免内存溢出
3. **事件清理**:在组件卸载时记得清理事件监听器
4. **错误处理**:包含完整的错误处理和日志记录
5. **网络兼容**:支持主备服务器自动切换
## 📞 技术支持
如果在使用过程中遇到问题:
1. 检查WebSocket服务器地址是否正确
2. 确认网络连接是否正常
3. 查看浏览器控制台错误信息
4. 参考文档和示例代码
5. 查看 `README_GK.md` 中的故障排除部分
## 🎊 恭喜!
GK数据WebSocket监听器已经成功配置完成现在你可以
- ✅ 实时接收GK_SERVER推送的数据
- ✅ 处理任务信息、路线数据、位置数据
- ✅ 与现有系统无缝集成
- ✅ 监控连接状态和数据流
- ✅ 使用主备服务器保障稳定性
开始享受实时的GK数据监听功能吧🚁✨

View File

@@ -0,0 +1,252 @@
# GK数据存储功能使用指南
## 🎯 功能概述
GK数据面板现在支持完整的Local Storage存储功能可以保存任务数据、用户设置和历史记录让您的工作更加高效和便捷。
## 📦 核心功能
### 1. **自动任务存储** 💾
- ✅ 接收到新任务时自动保存到本地存储
- ✅ 支持任务数据的完整保存(路径点、位置点、任务详情等)
- ✅ 智能去重,避免重复保存相同任务
- ✅ 可配置是否自动保存新任务
### 2. **历史任务管理** 📋
- ✅ 查看所有保存的历史任务
- ✅ 搜索和筛选历史任务
- ✅ 加载历史任务到当前显示
- ✅ 删除不需要的历史任务
- ✅ 标记重要任务
### 3. **用户设置持久化** ⚙️
- ✅ 面板位置自动保存
- ✅ 连接配置持久化
- ✅ 显示偏好设置保存
- ✅ 主题和界面设置
### 4. **数据导入导出** 📤📥
- ✅ 导出所有存储数据为JSON文件
- ✅ 导入之前导出的数据
- ✅ 支持数据合并和覆盖选项
## 🎛️ 界面操作指南
### **打开存储管理面板**
1. 在GK数据面板标题栏找到 **💾** 图标按钮
2. 点击即可展开/收起存储管理面板
### **存储管理面板功能**
```
💾 存储管理
┌─────────────────────────────────────────────┐
│ [💾 保存当前任务] [📋 查看历史 (5)] │
├─────────────────────────────────────────────┤
│ 历史任务: 5 重要任务: 2 存储大小: 15KB │
├─────────────────────────────────────────────┤
│ [📤 导出存储数据] [📥 导入数据] [🗑️ 清空存储] │
└─────────────────────────────────────────────┘
```
#### **保存当前任务**
- 手动将当前显示的任务保存到本地存储
- 会保存完整的任务信息,包括用户添加的备注
#### **查看历史任务**
- 显示所有保存的历史任务列表
- 支持任务搜索和筛选功能
### **历史任务面板功能**
```
📋 历史任务 (5) [搜索框] [✕]
┌─────────────────────────────────────────────┐
│ ⭐ 位置点侦查任务 2024/12/19 14:30 │
│ 侦查任务 | 高优先级 | 1581F5FHC248A00DXGLU │
│ 📝 重要侦查点,需要仔细观察 │
├─────────────────────────────────────────────┤
│ ☆ 巡逻任务A 2024/12/19 13:15 │
│ 巡逻任务 | 普通优先级 | 1581F5FHC248A00DXGL │
└─────────────────────────────────────────────┘
```
#### **搜索历史任务**
- 在搜索框中输入关键词任务名称、ID、设备SN、备注等
- 实时筛选显示匹配的任务
#### **任务操作**
- **⭐/☆**: 点击切换任务重要标记
- **🗑️**: 删除历史任务
- **点击任务**: 加载任务到当前显示
### **任务备注功能**
在任务详情下方有备注输入框:
```
📝 任务备注
┌─────────────────────────────────────────────┐
│ 添加任务备注... │
│ │
└─────────────────────────────────────────────┘
```
- 输入备注后失去焦点时自动保存
- 备注会显示在历史任务列表中
### **任务重要标记**
在任务操作按钮区域:
```
[📍 在地图显示] [📤 导出数据] [⭐ 已标记] [🗑️ 清除地图]
```
- **☆ 标记重要**: 将当前任务标记为重要
- **⭐ 已标记**: 取消重要标记
- 重要任务在历史列表中有特殊显示
## 💾 存储数据结构
### **任务存储格式**
```json
{
"taskId": "TASK_001",
"taskName": "位置点侦查",
"taskType": "1",
"taskTypeName": "侦查任务",
"priority": 3,
"priorityName": "高优先级",
"sn": "1581F5FHC248A00DXGLU",
"routePoints": [...],
"positionPoints": [...],
"storedAt": "2024-12-19T14:30:00.000Z",
"isImportant": true,
"userNotes": "重要侦查点,需要仔细观察",
"tags": ["侦查", "重要"]
}
```
### **用户设置格式**
```json
{
"panelPosition": { "x": 100, "y": 50 },
"autoConnect": true,
"autoSaveNewTasks": true,
"displaySettings": {
"showTaskList": true,
"showRouteDetails": true,
"showPositionDetails": true,
"maxVisibleTasks": 10
},
"connectionConfig": {
"url": "ws://27.11.11.30/dji/api/v1/ws",
"useDirectConnect": false,
"autoReconnect": true
},
"theme": {
"darkMode": false,
"panelOpacity": 0.95,
"fontSize": "medium"
}
}
```
## 📊 存储统计信息
在存储管理面板中显示:
- **历史任务**: 保存的任务总数
- **重要任务**: 标记为重要的任务数量
- **存储大小**: 占用的存储空间大小
## 🔄 数据导入导出
### **导出数据**
1. 点击 **📤 导出存储数据** 按钮
2. 自动下载包含所有数据的JSON文件
3. 文件名格式: `GK_Storage_Export_时间戳.json`
### **导入数据**
1. 点击 **📥 导入数据** 按钮
2. 选择之前导出的JSON文件
3. 数据会自动合并到现有存储中
### **导出数据包含**
- 所有保存的任务数据
- 用户设置配置
- 历史操作记录(可选)
- 统计信息和导出时间
## 🛡️ 数据安全与管理
### **自动清理机制**
- 默认保留30天内的数据
- 自动清理过期的历史记录
- 限制最大存储任务数量默认100个
### **存储空间管理**
- 监控存储空间使用情况
- 超出配额时自动清理最旧数据
- 支持手动清空所有存储数据
### **数据验证**
- 导入时验证数据格式
- 防止无效数据损坏存储
- 错误处理和恢复机制
## 🔧 高级配置
### **存储配置选项**
```typescript
const storageConfig = {
maxTasks: 100, // 最大任务数量
maxHistory: 500, // 最大历史记录数
expireDays: 30, // 数据过期天数
enableCompression: false // 是否启用压缩
}
```
### **自定义存储行为**
- 可配置自动保存新任务
- 可调整数据清理策略
- 可设置存储空间限制
## 📝 使用最佳实践
### **1. 任务管理**
- 及时为重要任务添加备注
- 定期标记重要任务便于查找
- 清理不需要的历史任务
### **2. 数据备份**
- 定期导出重要数据
- 保存导出文件作为备份
- 在更换设备前导出数据
### **3. 存储优化**
- 避免保存过多不重要的任务
- 定期清理过期数据
- 监控存储空间使用情况
## 🚨 注意事项
1. **存储限制**: 浏览器Local Storage有大小限制通常5-10MB
2. **数据安全**: 数据仅保存在本地浏览器,清除浏览器数据会丢失
3. **版本兼容**: 导入导出数据时注意版本兼容性
4. **备份重要**: 重要数据请及时导出备份
## 🎉 使用场景示例
### **日常工作流**
1. **接收任务** → 自动保存到存储
2. **添加备注** → 记录重要信息
3. **标记重要** → 便于后续查找
4. **历史查看** → 回顾之前的任务
5. **数据导出** → 定期备份数据
### **数据分析**
1. **搜索历史** → 查找特定时期的任务
2. **筛选类型** → 分析不同类型任务的分布
3. **导出数据** → 进行深度数据分析
现在您可以充分利用GK数据面板的存储功能让任务管理更加高效🚁✨

227
src/api/websocket/README.md Normal file
View File

@@ -0,0 +1,227 @@
# 双WebSocket服务
这个模块提供了一个双WebSocket服务可以同时连接两个不同的WebSocket服务器包括你的外部服务 `ws://27.11.11.177:3000`
## 功能特性
- ✅ 同时连接两个WebSocket服务
- ✅ 自动重连机制
- ✅ 消息处理器和事件分发
- ✅ 连接状态监控
- ✅ 与现有WebSocket系统集成
- ✅ 无需token认证的外部WebSocket连接
## 文件结构
```
src/api/websocket/
├── dualWebSocket.ts # 双WebSocket服务核心类
├── dualWebSocketManager.ts # 双WebSocket管理器
├── util/
│ ├── config.ts # 原有WebSocket配置
│ └── externalConfig.ts # 外部WebSocket配置
├── example.ts # 使用示例
└── README.md # 说明文档
src/components/websocket/
└── DualWebSocketDemo.vue # 演示组件
```
## 快速开始
### 1. 基本使用
```typescript
import DualWebSocketManager from '@/api/websocket/dualWebSocketManager'
// 在Vue组件中使用
export default {
data() {
return {
dualWebSocketManager: null
}
},
mounted() {
// 初始化双WebSocket管理器
this.dualWebSocketManager = new DualWebSocketManager(this.$store)
// 启动服务
this.dualWebSocketManager.init()
},
beforeUnmount() {
// 关闭连接
this.dualWebSocketManager?.close()
}
}
```
### 2. 使用示例类
```typescript
import { useDualWebSocketInVueComponent } from '@/api/websocket/example'
export default {
setup() {
const { example, startService, stopService } = useDualWebSocketInVueComponent(this.$store)
onMounted(() => {
startService()
})
onUnmounted(() => {
stopService()
})
return {
example
}
}
}
```
### 3. 发送消息
```typescript
// 发送消息到主WebSocket
this.dualWebSocketManager.sendPrimaryMessage({
type: 'command',
data: { action: 'start' }
})
// 发送消息到外部WebSocket (ws://27.11.11.177:3000)
this.dualWebSocketManager.sendExternalMessage({
type: 'request',
data: { query: 'status' }
})
```
### 4. 监听消息
```typescript
import EventBus from '@/event-bus'
// 监听主WebSocket消息
EventBus.on('PrimaryWebSocket-Message', (data) => {
console.log('主WebSocket消息:', data)
})
// 监听外部WebSocket消息
EventBus.on('ExternalWebSocket-Message', (data) => {
console.log('外部WebSocket消息:', data)
})
// 监听外部数据更新
EventBus.on('ExternalDataUpdate', (data) => {
console.log('外部数据更新:', data)
})
```
## 配置说明
### 外部WebSocket配置
默认配置连接到 `ws://27.11.11.177:3000`无需token认证
```typescript
// 默认配置
{
url: 'ws://27.11.11.177:3000',
name: 'ExternalService',
autoReconnect: true,
maxReconnectionDelay: 20000,
minReconnectionDelay: 5000,
maxRetries: 5
}
```
### 自定义配置
```typescript
import { createExternalWebSocketConfig } from '@/api/websocket/util/externalConfig'
const customConfig = createExternalWebSocketConfig(
'ws://your-server.com:8080',
'CustomService',
{
maxReconnectionDelay: 30000,
minReconnectionDelay: 10000,
maxRetries: 3
}
)
```
## 事件说明
### 连接事件
- `PrimaryWebSocket-Open`: 主WebSocket连接成功
- `PrimaryWebSocket-Close`: 主WebSocket连接断开
- `PrimaryWebSocket-Error`: 主WebSocket连接错误
- `ExternalWebSocket-Open`: 外部WebSocket连接成功
- `ExternalWebSocket-Close`: 外部WebSocket连接断开
- `ExternalWebSocket-Error`: 外部WebSocket连接错误
### 消息事件
- `PrimaryWebSocket-Message`: 主WebSocket消息
- `ExternalWebSocket-Message`: 外部WebSocket消息
- `ExternalDataUpdate`: 外部数据更新
- `ExternalStatusChange`: 外部状态变化
- `ExternalWebSocket-Data`: 通用外部数据
## 演示组件
使用 `DualWebSocketDemo.vue` 组件来测试和演示双WebSocket功能
```vue
<template>
<DualWebSocketDemo />
</template>
<script>
import DualWebSocketDemo from '@/components/websocket/DualWebSocketDemo.vue'
export default {
components: {
DualWebSocketDemo
}
}
</script>
```
## 注意事项
1. **无需token认证**: 外部WebSocket连接 `ws://27.11.11.177:3000` 不需要token认证
2. **自动重连**: 两个WebSocket连接都支持自动重连机制
3. **事件总线**: 使用EventBus进行事件分发确保消息能够正确传递
4. **内存管理**: 消息列表会自动限制数量,避免内存溢出
5. **错误处理**: 包含完整的错误处理和日志记录
## 故障排除
### 连接失败
1. 检查网络连接
2. 确认WebSocket服务器地址正确
3. 查看浏览器控制台错误信息
### 消息丢失
1. 检查事件监听器是否正确注册
2. 确认消息格式是否正确
3. 查看EventBus事件是否正常触发
### 重连问题
1. 检查重连配置参数
2. 确认服务器是否支持重连
3. 查看网络稳定性
## 扩展功能
如果需要添加更多功能,可以:
1.`DualWebSocketService` 中添加新的方法
2.`DualWebSocketManager` 中添加新的消息处理逻辑
3.`externalConfig.ts` 中添加新的配置选项
4. 在演示组件中添加新的UI功能

View File

@@ -0,0 +1,335 @@
# GK数据WebSocket监听器
专门用于接收和处理来自GK_SERVER的WebSocket数据推送业务代码为 `gk_data`
## 功能特性
- ✅ 专门监听 `gk_data` 业务代码的WebSocket消息
- ✅ 自动重连机制,确保连接稳定
- ✅ 完整的TypeScript类型定义
- ✅ 事件总线分发,支持多组件监听
- ✅ 数据缓存和管理功能
- ✅ 支持任务信息、路线数据、位置数据的分类处理
- ✅ 详细的使用示例和文档
## 文件结构
```
src/api/websocket/
├── gkDataListener.ts # GK数据监听器核心类
├── gkDataManager.ts # GK数据管理器提供高级功能
├── gkDataExample.ts # 使用示例和集成指南
└── README_GK.md # 使用说明文档
```
## 快速开始
### 1. 基础使用(监听器)
```typescript
import { GKDataListener } from '@/api/websocket/gkDataListener'
// 创建监听器实例使用实际的GK_SERVER地址
const listener = new GKDataListener('ws://27.11.11.30/dji/api/v1/ws')
// 注册数据处理器
listener.registerDataHandler((gkData) => {
console.log('接收到GK数据:', gkData)
// 处理任务信息
if (gkData.data.task_info) {
console.log('任务信息:', gkData.data.task_info)
}
// 处理路线数据
if (gkData.data.route_data) {
console.log('路线数据:', gkData.data.route_data)
}
// 处理位置数据
if (gkData.data.position_data) {
console.log('位置数据:', gkData.data.position_data)
}
})
// 初始化连接
listener.initSocket()
```
### 2. 高级使用(管理器)
```typescript
import { GKDataManager } from '@/api/websocket/gkDataManager'
// 创建管理器配置使用实际的GK_SERVER地址
const config = {
url: 'ws://27.11.11.30/dji/api/v1/ws',
autoConnect: true,
enableLogging: true,
dataBufferSize: 200
}
// 创建管理器实例
const manager = new GKDataManager(config)
// 初始化
manager.init()
// 获取最新数据
const latestData = manager.getLatestData()
const latestTaskInfo = manager.getLatestTaskInfo()
const latestRouteData = manager.getLatestRouteData()
const latestPositionData = manager.getLatestPositionData()
```
### 3. 在Vue组件中使用
```vue
<template>
<div>
<div>连接状态: {{ isConnected ? '已连接' : '未连接' }}</div>
<div v-if="latestTaskInfo">
当前任务: {{ latestTaskInfo.task_name }} - {{ latestTaskInfo.status }}
</div>
<div v-if="latestPositionData">
位置: {{ latestPositionData.latitude }}, {{ latestPositionData.longitude }}
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { useGKDataInVueComponent } from '@/api/websocket/gkDataExample'
import EventBus from '@/api/im/eventBus.js'
const isConnected = ref(false)
const latestTaskInfo = ref(null)
const latestPositionData = ref(null)
const {
initGKDataListener,
cleanup
} = useGKDataInVueComponent()
onMounted(() => {
// 初始化GK数据监听器使用实际的GK_SERVER地址
initGKDataListener('ws://27.11.11.30/dji/api/v1/ws')
// 监听连接状态
EventBus.on('GKDataManager-Connected', () => {
isConnected.value = true
})
EventBus.on('GKDataManager-Disconnected', () => {
isConnected.value = false
})
// 监听数据更新
EventBus.on('GKDataManager-TaskInfoUpdated', (taskInfo) => {
latestTaskInfo.value = taskInfo
})
EventBus.on('GKDataManager-PositionDataUpdated', (positionData) => {
latestPositionData.value = positionData
})
})
onUnmounted(() => {
cleanup()
})
</script>
```
## 数据格式说明
### GK数据结构
```typescript
interface GKData {
biz_code: 'gk_data' // 业务代码,固定为 'gk_data'
timestamp: string // 时间戳
data: {
task_info?: GKTaskInfo // 任务信息(可选)
route_data?: GKRouteData // 路线数据(可选)
position_data?: GKPositionData // 位置数据(可选)
raw_data?: any // 原始数据(可选)
}
}
```
### 任务信息格式
```typescript
interface GKTaskInfo {
task_id: string // 任务ID
task_name: string // 任务名称
task_type: string // 任务类型
status: string // 任务状态
create_time: string // 创建时间
start_time?: string // 开始时间
end_time?: string // 结束时间
description?: string // 任务描述
}
```
### 路线数据格式
```typescript
interface GKRouteData {
route_id: string // 路线ID
route_name: string // 路线名称
waypoints: GKWaypoint[] // 航点列表
route_type: string // 路线类型
total_distance: number // 总距离
estimated_time: number // 预计时间
}
interface GKWaypoint {
index: number // 航点序号
latitude: number // 纬度
longitude: number // 经度
altitude: number // 高度
speed?: number // 飞行速度
action?: string // 动作类型
action_params?: any // 动作参数
}
```
### 位置数据格式
```typescript
interface GKPositionData {
device_id: string // 设备ID
latitude: number // 纬度
longitude: number // 经度
altitude: number // 高度
heading: number // 航向角
speed: number // 速度
timestamp: string // 时间戳
battery_level?: number // 电池电量
signal_strength?: number // 信号强度
}
```
## 事件说明
### 连接事件
- `GKDataListener-Connected`: GK数据监听器连接成功
- `GKDataListener-Disconnected`: GK数据监听器连接断开
- `GKDataListener-Error`: GK数据监听器连接错误
### 数据事件
- `GKData-Received`: 接收到GK数据原始
- `GKData-TaskInfo`: 接收到任务信息
- `GKData-RouteData`: 接收到路线数据
- `GKData-PositionData`: 接收到位置数据
- `GKData-RawData`: 接收到原始数据
### 管理器事件
- `GKDataManager-Connected`: 管理器连接成功
- `GKDataManager-Disconnected`: 管理器连接断开
- `GKDataManager-Error`: 管理器连接错误
- `GKDataManager-DataReceived`: 管理器接收到数据
- `GKDataManager-TaskInfoUpdated`: 任务信息更新
- `GKDataManager-RouteDataUpdated`: 路线数据更新
- `GKDataManager-PositionDataUpdated`: 位置数据更新
## API参考
### GKDataListener
#### 构造函数
```typescript
constructor(url: string)
```
#### 主要方法
- `initSocket()`: 初始化WebSocket连接
- `registerDataHandler(handler)`: 注册数据处理器
- `unregisterDataHandler(handler)`: 移除数据处理器
- `sendMessage(message)`: 发送消息到GK_SERVER
- `requestGKData(dataType)`: 请求特定类型的数据
- `isConnected()`: 获取连接状态
- `reconnect()`: 手动重连
- `close()`: 关闭连接
### GKDataManager
#### 构造函数
```typescript
constructor(config: GKDataManagerConfig)
```
#### 主要方法
- `init()`: 初始化管理器
- `getLatestData()`: 获取最新数据
- `getLatestTaskInfo()`: 获取最新任务信息
- `getLatestRouteData()`: 获取最新路线数据
- `getLatestPositionData()`: 获取最新位置数据
- `getDataByTimeRange(start, end)`: 获取时间范围内的数据
- `getStatistics()`: 获取统计信息
- `sendMessage(message)`: 发送消息
- `requestData(dataType)`: 请求数据
- `clearBuffer()`: 清空缓冲区
- `close()`: 关闭管理器
## 配置选项
### GKDataManagerConfig
```typescript
interface GKDataManagerConfig {
url: string // WebSocket服务器地址
autoConnect?: boolean // 自动连接(默认: true
enableLogging?: boolean // 启用日志(默认: true
dataBufferSize?: number // 数据缓冲区大小(默认: 100
}
```
## 使用示例
完整的使用示例请参考 `gkDataExample.ts` 文件,包含:
1. 基础使用示例
2. 自定义数据处理器
3. 在Vue组件中的集成
4. 数据查询和管理
5. 消息发送示例
## 注意事项
1. **业务代码过滤**: 监听器只处理 `biz_code``'gk_data'` 的消息
2. **自动重连**: 内置自动重连机制,无需手动处理连接断开
3. **内存管理**: 数据缓冲区有大小限制,避免内存溢出
4. **事件清理**: 在组件卸载时记得清理事件监听器
5. **错误处理**: 包含完整的错误处理和日志记录
## 故障排除
### 连接问题
1. 检查GK_SERVER的WebSocket地址是否正确
2. 确认网络连接是否正常
3. 查看浏览器控制台是否有错误信息
### 数据接收问题
1. 确认GK_SERVER确实在推送 `gk_data` 业务代码的数据
2. 检查数据格式是否符合预期
3. 查看事件监听器是否正确注册
### 性能问题
1. 适当调整数据缓冲区大小
2. 避免在数据处理器中执行耗时操作
3. 定期清理不需要的事件监听器
## 扩展功能
如需添加更多功能,可以:
1. 扩展 `GKData` 接口,添加新的数据类型
2. 在数据处理器中添加新的业务逻辑
3. 创建自定义的数据过滤和验证规则
4. 添加数据持久化功能
5. 集成更多的事件通知机制

View File

@@ -0,0 +1,234 @@
# GK数据监听器 - 数据格式更新说明
## 🔄 数据格式更新
根据你提供的实际数据格式我已经更新了GK数据监听器来支持 `national_science_data` 格式的数据。
## 📊 实际数据格式
你的GK数据格式如下
```json
{
"version": "1.0",
"timestamp": 1757229578577,
"data": {
"host": {
"task_name": "位置点侦查",
"auto_level": 2,
"end_time": 1757036431559,
"receive_time": 1757229578577,
"task_id": "\u0001",
"target_id": "",
"force_num": 0,
"priority": 3,
"sid": "5",
"start_time": 1757036234000,
"route": [
{
"longitude": 118.10012079,
"latitude": 32.508601899999995,
"altitude": 115.69229944870787
},
// ... 更多航点
],
"battle_unit_code": "1581F5FHC248A00DXGLU",
"task_type": 1,
"position_list": [
{
"longitude": 118.10029444232556,
"latitude": 32.51149610752683,
"altitude": 0
}
],
"timestamp": 1757036234000
},
"sn": "1581F5FHC248A00DXGLU"
},
"biz_code": "gk_data" // 或 "national_science_data"
}
```
## 🔧 更新内容
### 1. 数据类型定义更新
更新了 `src/api/websocket/gkDataListener.ts` 中的类型定义:
```typescript
// 新的数据结构
export interface HostData {
task_name: string
auto_level: number
end_time: number
receive_time: number
task_id: string
target_id: string
force_num: number
priority: number
sid: string
start_time: number
route: RoutePoint[]
battle_unit_code: string
task_type: number
position_list: PositionPoint[]
timestamp: number
}
export interface GKData {
version: string
timestamp: number
data: {
host: HostData
sn: string
}
biz_code: 'gk_data' | 'national_science_data'
}
```
### 2. 数据处理逻辑更新
- 支持解析 `host` 数据结构
- 自动提取路线航点 (`route`)
- 自动提取位置点 (`position_list`)
- 解析任务类型和优先级
- 计算路线距离
### 3. 新增文件
1. **`gkDataMapDisplay.vue`** - 地图显示组件
- 显示任务路线和位置点
- 集成mars3d地图
- 实时数据更新
2. **`gkDataSimulator.ts`** - 数据模拟器
- 生成模拟数据进行测试
- 支持你的实际数据格式
## 🚀 使用方法
### 1. 基础监听
```typescript
import { GKDataListener } from '@/api/websocket/gkDataListener'
import { getGKWebSocketUrl } from '@/api/websocket/gkDataConfig'
const listener = new GKDataListener(getGKWebSocketUrl())
listener.registerDataHandler((gkData) => {
console.log('收到GK数据:', gkData)
// 访问任务信息
const taskInfo = gkData.data.host
console.log('任务名称:', taskInfo.task_name)
console.log('路线航点:', taskInfo.route)
console.log('位置点:', taskInfo.position_list)
})
listener.initSocket()
```
### 2. 地图显示组件
```vue
<template>
<div>
<GKDataMapDisplay />
</div>
</template>
<script>
import GKDataMapDisplay from '@/api/websocket/gkDataMapDisplay.vue'
export default {
components: {
GKDataMapDisplay
}
}
</script>
```
### 3. 监听解析后的任务数据
```typescript
import EventBus from '@/api/im/eventBus.js'
// 监听解析后的任务信息
EventBus.on('GKData-ParsedTask', (taskInfo) => {
console.log('任务ID:', taskInfo.taskId)
console.log('任务名称:', taskInfo.taskName)
console.log('任务类型:', taskInfo.taskTypeName)
console.log('优先级:', taskInfo.priorityName)
console.log('路线距离:', taskInfo.routeDistance)
console.log('航点数量:', taskInfo.routePoints.length)
})
// 监听路线数据
EventBus.on('GKData-RouteData', (data) => {
console.log('路线数据:', data.route_data.route_points)
})
// 监听位置数据
EventBus.on('GKData-PositionData', (data) => {
console.log('位置数据:', data.position_data.positions)
})
```
## 🎯 数据解析
监听器会自动解析你的数据并提供以下信息:
### 任务信息解析
- **任务类型名称**: `1: '侦查任务', 2: '巡逻任务'`
- **优先级名称**: `1: '低优先级', 3: '高优先级'`
- **路线距离**: 自动计算航点间距离
- **预计时长**: 基于开始和结束时间
### 地图显示
- **路线显示**: 红色线条连接所有航点
- **航点标记**: 显示航点序号和详细信息
- **位置点标记**: 显示位置点和设备信息
- **自动适应**: 地图自动缩放到数据范围
## 🧪 测试数据
使用数据模拟器进行测试:
```typescript
import { createGKDataSimulator } from '@/api/websocket/gkDataSimulator'
const simulator = createGKDataSimulator()
// 发送你提供的实际数据格式
simulator.sendUserProvidedData()
// 或启动持续模拟
simulator.start((data) => {
console.log('模拟数据:', data)
}, 5000) // 每5秒推送一次
```
## 📍 坐标信息
基于你提供的数据,坐标范围在:
- **经度**: 118.100° 附近
- **纬度**: 32.508° 附近
- **高度**: 0-151米
这个区域应该在江苏南京附近。地图会自动定位到这个区域并显示你的任务数据。
## 🔄 兼容性
更新后的监听器同时支持:
-`biz_code: "gk_data"`
-`biz_code: "national_science_data"`
无论你的服务器发送哪种业务代码,监听器都能正确处理。
现在你可以:
1. 实时接收你的GK数据格式
2. 在地图上查看任务路线和位置点
3. 获取解析后的任务详细信息
4. 使用模拟器进行测试
开始享受实时的GK数据可视化功能吧🚁✨

View File

@@ -0,0 +1,292 @@
// /*
// * @Date: 2024-12-19
// * @Description: 双WebSocket服务类支持同时连接两个不同的WebSocket服务
// * @Author: Assistant
// * @FilePath: \mars3d-vue-project\src\api\websocket\dualWebSocket.ts
// */
// import { message } from 'ant-design-vue'
// import ReconnectingWebSocket from 'reconnecting-websocket'
// import EventBus from '../im/eventBus.js'
// interface WebSocketOptions {
// data: any
// cache?: boolean | string
// destroyCache?: string
// }
// export interface MessageHandler {
// (data: {[key: string]: any}): void
// }
// export interface WebSocketConfig {
// url: string
// name: string
// autoReconnect?: boolean
// maxReconnectionDelay?: number
// minReconnectionDelay?: number
// maxRetries?: number
// }
// /**
// * 双WebSocket服务类
// * 支持同时连接两个不同的WebSocket服务
// */
// export class DualWebSocketService {
// private _primarySocket: ReconnectingWebSocket | null = null
// private _externalSocket: ReconnectingWebSocket | null = null
// private _primaryConfig: WebSocketConfig
// private _externalConfig: WebSocketConfig
// private _primaryMessageHandler: MessageHandler | null = null
// private _externalMessageHandler: MessageHandler | null = null
// private _isInitialized: boolean = false
// constructor(primaryConfig: WebSocketConfig, externalConfig: WebSocketConfig) {
// this._primaryConfig = primaryConfig
// this._externalConfig = externalConfig
// }
// /**
// * 初始化双WebSocket连接
// */
// initDualSockets(): void {
// if (this._isInitialized) {
// console.warn('DualWebSocketService already initialized')
// return
// }
// this._initPrimarySocket()
// this._initExternalSocket()
// this._isInitialized = true
// }
// /**
// * 初始化主WebSocket连接
// */
// private _initPrimarySocket(): void {
// if (!this._primaryConfig.url) {
// console.warn('Primary WebSocket URL not provided')
// return
// }
// this._primarySocket = new ReconnectingWebSocket(this._primaryConfig.url, [], {
// maxReconnectionDelay: this._primaryConfig.maxReconnectionDelay || 20000,
// minReconnectionDelay: this._primaryConfig.minReconnectionDelay || 5000,
// maxRetries: this._primaryConfig.maxRetries || 5
// })
// this._primarySocket.addEventListener('open', () => this._onPrimaryOpen())
// this._primarySocket.addEventListener('close', () => this._onPrimaryClose())
// this._primarySocket.addEventListener('error', (err) => this._onPrimaryError(err))
// this._primarySocket.addEventListener('message', (msg) => this._onPrimaryMessage(msg))
// }
// /**
// * 初始化外部WebSocket连接
// */
// private _initExternalSocket(): void {
// if (!this._externalConfig.url) {
// console.warn('External WebSocket URL not provided')
// return
// }
// this._externalSocket = new ReconnectingWebSocket(this._externalConfig.url, [], {
// maxReconnectionDelay: this._externalConfig.maxReconnectionDelay || 20000,
// minReconnectionDelay: this._externalConfig.minReconnectionDelay || 5000,
// maxRetries: this._externalConfig.maxRetries || 5
// })
// this._externalSocket.addEventListener('open', () => this._onExternalOpen())
// this._externalSocket.addEventListener('close', () => this._onExternalClose())
// this._externalSocket.addEventListener('error', (err) => this._onExternalError(err))
// this._externalSocket.addEventListener('message', (msg) => this._onExternalMessage(msg))
// }
// /**
// * 主WebSocket连接成功
// */
// private _onPrimaryOpen(): void {
// console.log(`[${this._primaryConfig.name}] WebSocket连接成功`)
// EventBus.emit('PrimaryWebSocket-Open', { name: this._primaryConfig.name })
// }
// /**
// * 主WebSocket连接关闭
// */
// private _onPrimaryClose(): void {
// console.log(`[${this._primaryConfig.name}] WebSocket连接已断开`)
// EventBus.emit('PrimaryWebSocket-Close', { name: this._primaryConfig.name })
// }
// /**
// * 主WebSocket连接错误
// */
// private _onPrimaryError(err: any): void {
// console.error(`[${this._primaryConfig.name}] WebSocket连接错误:`, err)
// EventBus.emit('PrimaryWebSocket-Error', { name: this._primaryConfig.name, error: err })
// }
// /**
// * 主WebSocket消息处理
// */
// private _onPrimaryMessage(msg: MessageEvent): void {
// try {
// const data = JSON.parse(msg.data)
// console.log(`[${this._primaryConfig.name}] 接收消息:`, data)
// if (this._primaryMessageHandler) {
// this._primaryMessageHandler(data)
// }
// EventBus.emit('PrimaryWebSocket-Message', { name: this._primaryConfig.name, data })
// } catch (error) {
// console.error(`[${this._primaryConfig.name}] 消息解析错误:`, error)
// }
// }
// /**
// * 外部WebSocket连接成功
// */
// private _onExternalOpen(): void {
// console.log(`[${this._externalConfig.name}] WebSocket连接成功`)
// EventBus.emit('ExternalWebSocket-Open', { name: this._externalConfig.name })
// }
// /**
// * 外部WebSocket连接关闭
// */
// private _onExternalClose(): void {
// console.log(`[${this._externalConfig.name}] WebSocket连接已断开`)
// EventBus.emit('ExternalWebSocket-Close', { name: this._externalConfig.name })
// }
// /**
// * 外部WebSocket连接错误
// */
// private _onExternalError(err: any): void {
// console.error(`[${this._externalConfig.name}] WebSocket连接错误:`, err)
// EventBus.emit('ExternalWebSocket-Error', { name: this._externalConfig.name, error: err })
// }
// /**
// * 外部WebSocket消息处理
// */
// private _onExternalMessage(msg: MessageEvent): void {
// try {
// const data = JSON.parse(msg.data)
// console.log(`[${this._externalConfig.name}] 接收消息:`, data)
// if (this._externalMessageHandler) {
// this._externalMessageHandler(data)
// }
// EventBus.emit('ExternalWebSocket-Message', { name: this._externalConfig.name, data })
// } catch (error) {
// console.error(`[${this._externalConfig.name}] 消息解析错误:`, error)
// }
// }
// /**
// * 注册主WebSocket消息处理器
// */
// registerPrimaryMessageHandler(handler: MessageHandler): void {
// this._primaryMessageHandler = handler
// }
// /**
// * 注册外部WebSocket消息处理器
// */
// registerExternalMessageHandler(handler: MessageHandler): void {
// this._externalMessageHandler = handler
// }
// /**
// * 发送消息到主WebSocket
// */
// sendPrimaryMessage(message: WebSocketOptions): void {
// if (this._primarySocket && this._primarySocket.readyState === WebSocket.OPEN) {
// this._primarySocket.send(JSON.stringify(message.data))
// } else {
// console.warn(`[${this._primaryConfig.name}] WebSocket未连接无法发送消息`)
// }
// }
// /**
// * 发送消息到外部WebSocket
// */
// sendExternalMessage(message: WebSocketOptions): void {
// if (this._externalSocket && this._externalSocket.readyState === WebSocket.OPEN) {
// this._externalSocket.send(JSON.stringify(message.data))
// } else {
// console.warn(`[${this._externalConfig.name}] WebSocket未连接无法发送消息`)
// }
// }
// /**
// * 获取主WebSocket连接状态
// */
// getPrimaryConnectionState(): number {
// return this._primarySocket?.readyState || WebSocket.CLOSED
// }
// /**
// * 获取外部WebSocket连接状态
// */
// getExternalConnectionState(): number {
// return this._externalSocket?.readyState || WebSocket.CLOSED
// }
// /**
// * 关闭主WebSocket连接
// */
// closePrimarySocket(): void {
// if (this._primarySocket) {
// this._primarySocket.close()
// this._primarySocket = null
// }
// }
// /**
// * 关闭外部WebSocket连接
// */
// closeExternalSocket(): void {
// if (this._externalSocket) {
// this._externalSocket.close()
// this._externalSocket = null
// }
// }
// /**
// * 关闭所有WebSocket连接
// */
// closeAllSockets(): void {
// this.closePrimarySocket()
// this.closeExternalSocket()
// this._isInitialized = false
// }
// /**
// * 重新连接主WebSocket
// */
// reconnectPrimarySocket(): void {
// this.closePrimarySocket()
// this._initPrimarySocket()
// }
// /**
// * 重新连接外部WebSocket
// */
// reconnectExternalSocket(): void {
// this.closeExternalSocket()
// this._initExternalSocket()
// }
// /**
// * 重新连接所有WebSocket
// */
// reconnectAllSockets(): void {
// this.reconnectPrimarySocket()
// this.reconnectExternalSocket()
// }
// }
// export default DualWebSocketService

View File

@@ -0,0 +1,319 @@
// /*
// * @Date: 2024-12-19
// * @Description: 双WebSocket管理器集成现有的WebSocket功能
// * @Author: Assistant
// * @FilePath: \mars3d-vue-project\src\api\websocket\dualWebSocketManager.ts
// */
// import { EBizCode } from "@mars/types/enums"
// import DualWebSocketService, { MessageHandler } from './dualWebSocket'
// import { getPrimaryWebSocketConfig, getExternalWebSocketConfig } from './util/externalConfig'
// import { storeKey, useStore } from "vuex"
// import EventBus from '/@/event-bus'
// export class DualWebSocketManager {
// private store: any
// private dualWebSocketService: DualWebSocketService
// private isInitialized: boolean = false
// constructor(store: any) {
// this.store = store
// this.dualWebSocketService = new DualWebSocketService(
// getPrimaryWebSocketConfig(),
// getExternalWebSocketConfig()
// )
// }
// /**
// * 初始化双WebSocket服务
// */
// init(): void {
// if (this.isInitialized) {
// console.warn('DualWebSocketManager already initialized')
// return
// }
// // 注册主WebSocket消息处理器使用现有的消息处理逻辑
// this.dualWebSocketService.registerPrimaryMessageHandler(this.primaryMessageHandler)
// // 注册外部WebSocket消息处理器
// this.dualWebSocketService.registerExternalMessageHandler(this.externalMessageHandler)
// // 初始化双WebSocket连接
// this.dualWebSocketService.initDualSockets()
// this.isInitialized = true
// console.log('DualWebSocketManager initialized successfully')
// }
// /**
// * 主WebSocket消息处理器复用现有的业务逻辑
// */
// private primaryMessageHandler: MessageHandler = (payload: any) => {
// if (!payload) {
// return
// }
// switch (payload.biz_code) {
// case EBizCode.GatewayOsd: {
// this.store.commit('SET_GATEWAY_INFO', payload.data)
// break
// }
// case EBizCode.DeviceOsd: {
// this.store.commit('SET_DEVICE_INFO', {
// ...payload.data,
// timestamp: payload.timestamp
// })
// break
// }
// case EBizCode.RadioRobotDog: {
// this.store.commit('SET_ROBOTDOG_INFO', payload.data)
// break
// }
// case EBizCode.DeviceAir: {
// this.store.commit('SET_DEVICEAIR_INFO', payload.data)
// break
// }
// case EBizCode.CruiseMissile: {
// this.store.commit('SET_MISSILE_INFO', payload.data)
// break
// }
// case EBizCode.WrcPtzt: {
// this.store.commit('SET_WRC_INFO', payload.data)
// break
// }
// case EBizCode.OtherEquipment: {
// this.store.commit('SET_OTHER_EQUIPMENT_INFO', payload.data)
// EventBus.emit('OtherEquipment-Update', payload.data)
// break
// }
// case EBizCode.Qb303DeviceStatus: {
// this.store.commit('SET_QB303_DEVICE_INFO', payload.data)
// EventBus.emit('QB303Device-Update', payload.data)
// break
// }
// case EBizCode.GkData: {
// this.store.commit('SET_GK_DATA', payload.data)
// break
// }
// case EBizCode.RemoteControl: {
// this.store.commit('SET_RC_ONLINE_INFO', payload.data)
// break
// }
// case EBizCode.HitDroneY: {
// this.store.commit('SET_HIT_DRONE_INFO', payload.data)
// break
// }
// case EBizCode.DockOsd: {
// this.store.commit('SET_DOCK_INFO', payload.data)
// break
// }
// case EBizCode.DeviceOnline: {
// this.store.commit('SET_DEVICE_ONLINE', payload.data)
// break
// }
// case EBizCode.DeviceOffline: {
// this.store.commit('SET_DEVICE_OFFLINE', payload.data)
// break
// }
// case EBizCode.AiServiceNotify: {
// console.info("Primary WebSocket payload", payload.data)
// this.store.commit('SET_AI_SERVICE_NOTIFY', payload.data)
// break
// }
// case EBizCode.FlightTaskProgress:
// case EBizCode.FlightTaskMediaProgress:
// case EBizCode.FlightTaskMediaHighestPriority: {
// let payloads = {
// ...payload,
// ...payload.data,
// }
// EventBus.emit('flightTaskWs', payloads)
// break
// }
// case EBizCode.DeviceHms: {
// this.store.commit('SET_DEVICE_HMS_INFO', payload.data)
// break
// }
// case EBizCode.DeviceReboot:
// case EBizCode.DroneOpen:
// case EBizCode.DroneClose:
// case EBizCode.CoverOpen:
// case EBizCode.CoverClose:
// case EBizCode.PutterOpen:
// case EBizCode.PutterClose:
// case EBizCode.ChargeOpen:
// case EBizCode.ChargeClose:
// case EBizCode.DeviceFormat:
// case EBizCode.DroneFormat: {
// this.store.commit('SET_DEVICES_CMD_EXECUTE_INFO', {
// biz_code: payload.biz_code,
// timestamp: payload.timestamp,
// ...payload.data,
// })
// break
// }
// case EBizCode.CoverForceClose: {
// this.store.commit('SET_COVER_FORCE_CLOSE', payload.data)
// break
// }
// case EBizCode.ControlSourceChange:
// case EBizCode.FlyToPointProgress:
// case EBizCode.TakeoffToPointProgress:
// case EBizCode.JoystickInvalidNotify:
// case EBizCode.DrcStatusNotify: {
// EventBus.emit('droneControlWs', payload)
// break
// }
// case EBizCode.PsdkWidgetValues: {
// this.store.commit('SET_SN_PSDK_WIDGET', payload.data)
// break
// }
// case EBizCode.PsdkFloatingWindowText: {
// this.store.commit('SET_SN_PSDK_Floating_WINDOWS', payload.data)
// break
// }
// default:
// break
// }
// }
// /**
// * 外部WebSocket消息处理器
// * 处理来自 ws://27.11.11.177:3000 的消息
// */
// private externalMessageHandler: MessageHandler = (payload: any) => {
// if (!payload) {
// return
// }
// console.log('External WebSocket message received:', payload)
// // 根据外部服务的消息格式进行处理
// // 这里可以根据你的外部服务消息格式进行自定义处理
// try {
// // 示例:如果外部服务发送的是标准格式的消息
// if (payload.type) {
// this.handleExternalMessageByType(payload)
// } else if (payload.event) {
// this.handleExternalMessageByEvent(payload)
// } else {
// // 通用处理
// this.handleGenericExternalMessage(payload)
// }
// } catch (error) {
// console.error('Error processing external WebSocket message:', error)
// }
// }
// /**
// * 根据消息类型处理外部消息
// */
// private handleExternalMessageByType(payload: any): void {
// switch (payload.type) {
// case 'data':
// EventBus.emit('ExternalData-Message', payload)
// break
// case 'status':
// EventBus.emit('ExternalStatus-Message', payload)
// break
// case 'notification':
// EventBus.emit('ExternalNotification-Message', payload)
// break
// default:
// EventBus.emit('ExternalGeneric-Message', payload)
// break
// }
// }
// /**
// * 根据事件类型处理外部消息
// */
// private handleExternalMessageByEvent(payload: any): void {
// switch (payload.event) {
// case 'connect':
// console.log('External service connected')
// break
// case 'disconnect':
// console.log('External service disconnected')
// break
// case 'data_update':
// EventBus.emit('ExternalDataUpdate', payload.data)
// break
// case 'status_change':
// EventBus.emit('ExternalStatusChange', payload.data)
// break
// default:
// EventBus.emit('ExternalEvent', payload)
// break
// }
// }
// /**
// * 通用外部消息处理
// */
// private handleGenericExternalMessage(payload: any): void {
// // 将外部消息存储到store中如果需要
// this.store.commit('SET_EXTERNAL_WS_DATA', payload)
// // 发送通用事件
// EventBus.emit('ExternalWebSocket-Data', payload)
// }
// /**
// * 发送消息到主WebSocket
// */
// sendPrimaryMessage(data: any): void {
// this.dualWebSocketService.sendPrimaryMessage({ data })
// }
// /**
// * 发送消息到外部WebSocket
// */
// sendExternalMessage(data: any): void {
// this.dualWebSocketService.sendExternalMessage({ data })
// }
// /**
// * 获取连接状态
// */
// getConnectionStatus(): {
// primary: number
// external: number
// } {
// return {
// primary: this.dualWebSocketService.getPrimaryConnectionState(),
// external: this.dualWebSocketService.getExternalConnectionState()
// }
// }
// /**
// * 重新连接主WebSocket
// */
// reconnectPrimary(): void {
// this.dualWebSocketService.reconnectPrimarySocket()
// }
// /**
// * 重新连接外部WebSocket
// */
// reconnectExternal(): void {
// this.dualWebSocketService.reconnectExternalSocket()
// }
// /**
// * 重新连接所有WebSocket
// */
// reconnectAll(): void {
// this.dualWebSocketService.reconnectAllSockets()
// }
// /**
// * 关闭所有连接
// */
// close(): void {
// this.dualWebSocketService.closeAllSockets()
// this.isInitialized = false
// }
// }
// export default DualWebSocketManager

View File

@@ -0,0 +1,222 @@
/*
* @Date: 2024-12-19
* @Description: 双WebSocket服务使用示例
* @Author: Assistant
* @FilePath: \mars3d-vue-project\src\api\websocket\example.ts
*/
import DualWebSocketManager from './dualWebSocketManager'
import { createExternalWebSocketConfig } from './util/externalConfig'
import EventBus from '@/event-bus'
/**
* 双WebSocket服务使用示例
*/
export class DualWebSocketExample {
private dualWebSocketManager: DualWebSocketManager | null = null
constructor(store: any) {
this.dualWebSocketManager = new DualWebSocketManager(store)
}
/**
* 初始化并启动双WebSocket服务
*/
async startDualWebSocketService(): Promise<void> {
if (!this.dualWebSocketManager) {
throw new Error('DualWebSocketManager not initialized')
}
try {
// 初始化双WebSocket连接
this.dualWebSocketManager.init()
// 设置事件监听器
this.setupEventListeners()
console.log('双WebSocket服务启动成功')
} catch (error) {
console.error('启动双WebSocket服务失败:', error)
throw error
}
}
/**
* 设置事件监听器
*/
private setupEventListeners(): void {
// 监听主WebSocket事件
EventBus.on('PrimaryWebSocket-Open', (data) => {
console.log('主WebSocket连接成功:', data)
})
EventBus.on('PrimaryWebSocket-Close', (data) => {
console.log('主WebSocket连接断开:', data)
})
EventBus.on('PrimaryWebSocket-Error', (data) => {
console.error('主WebSocket连接错误:', data)
})
// 监听外部WebSocket事件
EventBus.on('ExternalWebSocket-Open', (data) => {
console.log('外部WebSocket连接成功:', data)
})
EventBus.on('ExternalWebSocket-Close', (data) => {
console.log('外部WebSocket连接断开:', data)
})
EventBus.on('ExternalWebSocket-Error', (data) => {
console.error('外部WebSocket连接错误:', data)
})
// 监听外部WebSocket数据事件
EventBus.on('ExternalWebSocket-Data', (data) => {
console.log('收到外部WebSocket数据:', data)
this.handleExternalData(data)
})
// 监听外部数据更新事件
EventBus.on('ExternalDataUpdate', (data) => {
console.log('外部数据更新:', data)
this.handleExternalDataUpdate(data)
})
// 监听外部状态变化事件
EventBus.on('ExternalStatusChange', (data) => {
console.log('外部状态变化:', data)
this.handleExternalStatusChange(data)
})
}
/**
* 处理外部WebSocket数据
*/
private handleExternalData(data: any): void {
// 根据你的业务需求处理外部数据
// 例如更新UI、存储到store、触发其他业务逻辑等
// 示例:如果数据包含位置信息
if (data.latitude && data.longitude) {
console.log('收到位置数据:', {
lat: data.latitude,
lng: data.longitude,
timestamp: data.timestamp || Date.now()
})
}
// 示例:如果数据包含设备状态
if (data.deviceStatus) {
console.log('设备状态更新:', data.deviceStatus)
}
}
/**
* 处理外部数据更新
*/
private handleExternalDataUpdate(data: any): void {
// 处理数据更新逻辑
console.log('处理外部数据更新:', data)
}
/**
* 处理外部状态变化
*/
private handleExternalStatusChange(data: any): void {
// 处理状态变化逻辑
console.log('处理外部状态变化:', data)
}
/**
* 发送消息到主WebSocket
*/
sendToPrimaryWebSocket(message: any): void {
if (this.dualWebSocketManager) {
this.dualWebSocketManager.sendPrimaryMessage(message)
}
}
/**
* 发送消息到外部WebSocket
*/
sendToExternalWebSocket(message: any): void {
if (this.dualWebSocketManager) {
this.dualWebSocketManager.sendExternalMessage(message)
}
}
/**
* 获取连接状态
*/
getConnectionStatus(): { primary: number, external: number } | null {
if (this.dualWebSocketManager) {
return this.dualWebSocketManager.getConnectionStatus()
}
return null
}
/**
* 重新连接所有WebSocket
*/
reconnectAll(): void {
if (this.dualWebSocketManager) {
this.dualWebSocketManager.reconnectAll()
}
}
/**
* 关闭所有连接
*/
closeAll(): void {
if (this.dualWebSocketManager) {
this.dualWebSocketManager.close()
}
}
}
/**
* 创建自定义外部WebSocket配置的示例
*/
export function createCustomExternalWebSocketExample(): void {
// 示例创建自定义的外部WebSocket配置
const customConfig = createExternalWebSocketConfig(
'ws://your-custom-server.com:8080',
'CustomExternalService',
{
maxReconnectionDelay: 30000,
minReconnectionDelay: 10000,
maxRetries: 3
}
)
console.log('自定义外部WebSocket配置:', customConfig)
}
/**
* 在Vue组件中使用的示例
*/
export function useDualWebSocketInVueComponent(store: any) {
const example = new DualWebSocketExample(store)
// 在组件挂载时启动服务
const startService = async () => {
try {
await example.startDualWebSocketService()
} catch (error) {
console.error('启动双WebSocket服务失败:', error)
}
}
// 在组件卸载时关闭服务
const stopService = () => {
example.closeAll()
}
return {
example,
startService,
stopService
}
}
export default DualWebSocketExample

View File

@@ -0,0 +1,481 @@
/*
* @Date: 2024-12-19
* @Description: GK数据后端API测试工具
* @Author: Assistant
* @FilePath: \drone-v2-command-screen\src\api\websocket\gkDataApiTester.ts
*/
import request from '/@/api/http/request'
import { ParsedTaskInfo } from './gkDataListener'
import { StoredTaskData } from './gkDataStorage'
/**
* 后端API配置
*/
export interface BackendApiConfig {
baseUrl: string
eventId: string
deviceType: string
timeout: number
}
/**
* API响应格式
*/
export interface ApiResponse<T = any> {
code?: number
message?: string
data?: T
success?: boolean
[key: string]: any
}
/**
* 航线数据格式根据GK数据结构
*/
export interface RouteTaskData {
taskId: string
taskName: string
taskType: number
priority: number
battleUnitCode: string
deviceSn: string
startTime: string
endTime: string
receiveTime: string
routePoints: Array<{
longitude: number
latitude: number
altitude: number
index?: number
speed?: number
action?: string
}>
positionPoints: Array<{
longitude: number
latitude: number
altitude: number
}>
routeDistance?: number
estimatedDuration?: number
userNotes?: string
isImportant?: boolean
tags?: string[]
source: 'gk_panel' | 'manual' | 'import'
createdAt: string
updatedAt: string
}
/**
* GK数据后端API测试类
*/
export class GKDataApiTester {
private config: BackendApiConfig
constructor(config?: Partial<BackendApiConfig>) {
this.config = {
baseUrl: 'http://27.11.11.30:6791/api/v1',
eventId: 'e3dea0f5-37f2-4d79-ae58-490af3228069',
deviceType: 'ue-device',
timeout: 10000,
...config
}
}
/**
* 获取完整的API URL
*/
private getApiUrl(): string {
return `${this.config.baseUrl}/nationalScience-events/${this.config.eventId}/${this.config.deviceType}/tasks`
}
/**
* 测试API连接性
*/
async testConnection(): Promise<{
success: boolean
status: number
message: string
responseTime: number
}> {
const startTime = Date.now()
try {
console.log('测试API连接:', this.getApiUrl())
// 先尝试GET请求查看接口是否可访问
const response = await request({
url: this.getApiUrl(),
method: 'GET',
timeout: this.config.timeout,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('x-auth-token') || ''}`
}
})
const responseTime = Date.now() - startTime
console.log('API连接测试成功:', response)
return {
success: true,
status: 200,
message: '接口连接成功',
responseTime
}
} catch (error: any) {
const responseTime = Date.now() - startTime
console.error('API连接测试失败:', error)
return {
success: false,
status: error.status || 0,
message: error.message || '连接失败',
responseTime
}
}
}
/**
* 测试保存单个任务
*/
async testSaveTask(taskData: ParsedTaskInfo | StoredTaskData): Promise<{
success: boolean
response?: any
error?: string
}> {
try {
console.log('测试保存任务:', taskData.taskId)
// 将GK数据转换为后端期望的格式
const routeTaskData: RouteTaskData = this.convertToRouteTaskData(taskData)
const response = await request({
url: this.getApiUrl(),
method: 'POST',
data: routeTaskData,
timeout: this.config.timeout,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('x-auth-token') || ''}`
}
})
console.log('保存任务成功:', response)
return {
success: true,
response
}
} catch (error: any) {
console.error('保存任务失败:', error)
return {
success: false,
error: error.message || '保存失败'
}
}
}
/**
* 测试批量保存任务
*/
async testBatchSaveTasks(tasks: (ParsedTaskInfo | StoredTaskData)[]): Promise<{
success: number
failed: number
errors: string[]
}> {
const results = {
success: 0,
failed: 0,
errors: [] as string[]
}
console.log(`开始批量保存任务,数量: ${tasks.length}`)
for (const task of tasks) {
try {
const result = await this.testSaveTask(task)
if (result.success) {
results.success++
} else {
results.failed++
results.errors.push(`任务 ${task.taskId}: ${result.error}`)
}
} catch (error: any) {
results.failed++
results.errors.push(`任务 ${task.taskId}: ${error.message}`)
}
// 避免请求过于频繁,添加小延迟
await new Promise(resolve => setTimeout(resolve, 100))
}
console.log('批量保存完成:', results)
return results
}
/**
* 测试获取任务列表
*/
async testGetTasks(params?: {
page?: number
pageSize?: number
startDate?: string
endDate?: string
}): Promise<{
success: boolean
tasks?: RouteTaskData[]
total?: number
error?: string
}> {
try {
console.log('测试获取任务列表')
const response = await request({
url: this.getApiUrl(),
method: 'GET',
params,
timeout: this.config.timeout,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('x-auth-token') || ''}`
}
})
console.log('获取任务列表成功:', response)
// 检查响应格式
let tasks: RouteTaskData[] = []
let total = 0
if (Array.isArray(response)) {
tasks = response
total = response.length
} else if (response.data && Array.isArray(response.data)) {
tasks = response.data
total = response.total || response.data.length
} else if (response.tasks && Array.isArray(response.tasks)) {
tasks = response.tasks
total = response.total || response.tasks.length
}
return {
success: true,
tasks,
total
}
} catch (error: any) {
console.error('获取任务列表失败:', error)
return {
success: false,
error: error.message || '获取失败'
}
}
}
/**
* 测试删除任务
*/
async testDeleteTask(taskId: string): Promise<{
success: boolean
response?: any
error?: string
}> {
try {
console.log('测试删除任务:', taskId)
const response = await request({
url: `${this.getApiUrl()}/${taskId}`,
method: 'DELETE',
timeout: this.config.timeout,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('x-auth-token') || ''}`
}
})
console.log('删除任务成功:', response)
return {
success: true,
response
}
} catch (error: any) {
console.error('删除任务失败:', error)
return {
success: false,
error: error.message || '删除失败'
}
}
}
/**
* 运行完整的API测试套件
*/
async runFullTest(sampleTask?: ParsedTaskInfo): Promise<{
connectionTest: any
saveTest?: any
getTest?: any
deleteTest?: any
summary: {
totalTests: number
passedTests: number
failedTests: number
}
}> {
const results = {
connectionTest: {},
summary: {
totalTests: 0,
passedTests: 0,
failedTests: 0
}
} as any
console.log('开始运行GK数据API完整测试套件')
// 1. 连接测试
console.log('1. 测试API连接...')
results.connectionTest = await this.testConnection()
results.summary.totalTests++
if (results.connectionTest.success) {
results.summary.passedTests++
} else {
results.summary.failedTests++
}
// 如果连接失败,不继续后续测试
if (!results.connectionTest.success) {
console.log('连接测试失败,跳过后续测试')
return results
}
// 2. 获取任务列表测试
console.log('2. 测试获取任务列表...')
results.getTest = await this.testGetTasks()
results.summary.totalTests++
if (results.getTest.success) {
results.summary.passedTests++
} else {
results.summary.failedTests++
}
// 3. 保存任务测试(如果提供了示例任务)
if (sampleTask) {
console.log('3. 测试保存任务...')
results.saveTest = await this.testSaveTask(sampleTask)
results.summary.totalTests++
if (results.saveTest.success) {
results.summary.passedTests++
// 4. 删除测试任务
console.log('4. 测试删除任务...')
results.deleteTest = await this.testDeleteTask(sampleTask.taskId)
results.summary.totalTests++
if (results.deleteTest.success) {
results.summary.passedTests++
} else {
results.summary.failedTests++
}
} else {
results.summary.failedTests++
}
}
console.log('API测试套件完成:', results.summary)
return results
}
/**
* 将GK数据转换为后端API期望的格式
*/
private convertToRouteTaskData(taskData: ParsedTaskInfo | StoredTaskData): RouteTaskData {
const now = new Date().toISOString()
return {
taskId: taskData.taskId,
taskName: taskData.taskName,
taskType: parseInt(taskData.taskType),
priority: taskData.priority,
battleUnitCode: taskData.battleUnitCode,
deviceSn: taskData.sn,
startTime: taskData.startTime.toISOString(),
endTime: taskData.endTime.toISOString(),
receiveTime: taskData.receiveTime?.toISOString() || now,
routePoints: taskData.routePoints.map((point, index) => ({
...point,
index: index + 1
})),
positionPoints: taskData.positionPoints || [],
routeDistance: taskData.routeDistance || 0,
estimatedDuration: taskData.estimatedDuration || 0,
userNotes: (taskData as StoredTaskData).userNotes,
isImportant: (taskData as StoredTaskData).isImportant || false,
tags: (taskData as StoredTaskData).tags || [],
source: 'gk_panel',
createdAt: (taskData as StoredTaskData).storedAt || now,
updatedAt: now
}
}
/**
* 创建测试用的示例任务数据
*/
static createSampleTask(): ParsedTaskInfo {
return {
taskId: `TEST_TASK_${Date.now()}`,
taskName: 'API测试任务',
taskType: '1',
taskTypeName: '侦查任务',
priority: 3,
priorityName: '高优先级',
battleUnitCode: 'TEST_UNIT',
sn: 'TEST_DEVICE_001',
startTime: new Date(),
endTime: new Date(Date.now() + 3600000), // 1小时后
receiveTime: new Date(),
routePoints: [
{ longitude: 118.100121, latitude: 32.508602, altitude: 115.69 },
{ longitude: 118.100094, latitude: 32.510282, altitude: 151.90 },
{ longitude: 118.100279, latitude: 32.510483, altitude: 144.76 }
],
positionPoints: [
{ longitude: 118.100294, latitude: 32.511496, altitude: 0.0 }
],
routeDistance: 500,
estimatedDuration: 1800 // 30分钟
}
}
}
/**
* 快速测试函数
*/
export async function quickApiTest(): Promise<void> {
const tester = new GKDataApiTester()
const sampleTask = GKDataApiTester.createSampleTask()
console.log('开始快速API测试...')
const results = await tester.runFullTest(sampleTask)
console.log('=== API测试结果总结 ===')
console.log(`总测试数: ${results.summary.totalTests}`)
console.log(`通过测试: ${results.summary.passedTests}`)
console.log(`失败测试: ${results.summary.failedTests}`)
console.log(`成功率: ${((results.summary.passedTests / results.summary.totalTests) * 100).toFixed(1)}%`)
if (results.connectionTest.success) {
console.log('✅ API连接正常')
} else {
console.log('❌ API连接失败:', results.connectionTest.message)
}
return results
}
export default GKDataApiTester

View File

@@ -0,0 +1,727 @@
/*
* @Date: 2024-12-19
* @Description: GK数据后端存储服务适配现有的nationalScience-events接口
* @Author: Assistant
* @FilePath: \drone-v2-command-screen\src\api\websocket\gkDataBackendService.ts
*/
import { message } from 'ant-design-vue'
import request from '/@/api/http/request'
import { ParsedTaskInfo } from './gkDataListener'
import { StoredTaskData } from './gkDataStorage'
import EventBus from '/@/api/im/eventBus.js'
/**
* 后端服务配置
*/
export interface BackendServiceConfig {
/** 基础URL */
baseUrl: string
/** 事件ID */
eventId: string
/** 设备类型 */
deviceType: string
/** 请求超时时间 */
timeout: number
/** 是否启用自动重试 */
enableRetry: boolean
/** 重试次数 */
maxRetries: number
/** 重试间隔 */
retryInterval: number
}
/**
* 后端任务数据格式
*/
export interface BackendTaskData {
taskId: string
taskName: string
taskType: number
priority: number
battleUnitCode: string
deviceSn: string
startTime: string
endTime: string
receiveTime: string
routePoints: Array<{
longitude: number
latitude: number
altitude: number
index?: number
speed?: number
action?: string
actionParams?: any
}>
positionPoints: Array<{
longitude: number
latitude: number
altitude: number
targetType?: string
description?: string
}>
routeDistance?: number
estimatedDuration?: number
userNotes?: string
isImportant?: boolean
tags?: string[]
source: 'gk_panel' | 'manual' | 'import' | 'api'
metadata?: {
clientVersion?: string
userAgent?: string
timestamp?: string
[key: string]: any
}
createdAt: string
updatedAt: string
}
/**
* API响应格式
*/
export interface BackendResponse<T = any> {
code?: number
message?: string
data?: T
success?: boolean
timestamp?: number
[key: string]: any
}
/**
* 查询参数
*/
export interface QueryParams {
page?: number
pageSize?: number
startDate?: string
endDate?: string
taskType?: number
priority?: number
isImportant?: boolean
keyword?: string
tags?: string[]
deviceSn?: string
battleUnitCode?: string
tenantId?: string
}
/**
* GK数据后端存储服务类
*/
export class GKDataBackendService {
private config: BackendServiceConfig
private retryQueue: Array<() => Promise<any>> = []
private isProcessingRetry: boolean = false
constructor(config?: Partial<BackendServiceConfig>) {
this.config = {
baseUrl: 'http://27.11.11.30:6791/api/v1',
eventId: 'e3dea0f5-37f2-4d79-ae58-490af3228069',
deviceType: 'ue-device',
timeout: 15000,
enableRetry: true,
maxRetries: 3,
retryInterval: 2000,
...config
}
console.log('GK数据后端存储服务初始化:', this.config)
}
/**
* 获取API完整URL
*/
private getApiUrl(path: string = ''): string {
// 使用正确的API路径
const baseUrl = `${this.config.baseUrl}/national-science/tasks/query`
return path ? `${baseUrl}${path}` : baseUrl
}
/**
* 保存任务到后端
*/
async saveTask(taskData: ParsedTaskInfo | StoredTaskData): Promise<{
success: boolean
taskId?: string
message?: string
error?: string
}> {
try {
console.log('开始保存任务到后端:', taskData.taskId)
const backendData = this.convertToBackendFormat(taskData)
const response = await this.makeRequest<BackendResponse<{ taskId: string }>>({
url: this.getApiUrl(),
method: 'POST',
data: backendData
})
if (this.isSuccessResponse(response)) {
console.log('任务保存成功:', response)
// 发送成功事件
EventBus.emit('GKBackend-TaskSaved', {
originalTaskId: taskData.taskId,
backendTaskId: response.data?.taskId || taskData.taskId,
timestamp: new Date().toISOString()
})
return {
success: true,
taskId: response.data?.taskId || taskData.taskId,
message: response.message || '保存成功'
}
} else {
throw new Error(response.message || '保存失败')
}
} catch (error: any) {
console.error('保存任务失败:', error)
// 发送失败事件
EventBus.emit('GKBackend-TaskSaveFailed', {
taskId: taskData.taskId,
error: error.message,
timestamp: new Date().toISOString()
})
// 添加到重试队列
if (this.config.enableRetry) {
this.addToRetryQueue(() => this.saveTask(taskData))
}
return {
success: false,
error: error.message || '保存失败'
}
}
}
/**
* 批量保存任务
*/
async saveTasks(tasks: (ParsedTaskInfo | StoredTaskData)[]): Promise<{
success: number
failed: number
errors: Array<{ taskId: string; error: string }>
details: Array<{ taskId: string; backendTaskId?: string; success: boolean }>
}> {
const results = {
success: 0,
failed: 0,
errors: [] as Array<{ taskId: string; error: string }>,
details: [] as Array<{ taskId: string; backendTaskId?: string; success: boolean }>
}
console.log(`开始批量保存任务,数量: ${tasks.length}`)
// 分批处理,避免服务器压力过大
const batchSize = 5
for (let i = 0; i < tasks.length; i += batchSize) {
const batch = tasks.slice(i, i + batchSize)
// 并发处理当前批次
const batchPromises = batch.map(async (task) => {
const result = await this.saveTask(task)
results.details.push({
taskId: task.taskId,
backendTaskId: result.taskId,
success: result.success
})
if (result.success) {
results.success++
} else {
results.failed++
results.errors.push({
taskId: task.taskId,
error: result.error || '未知错误'
})
}
})
await Promise.all(batchPromises)
// 批次间添加小延迟
if (i + batchSize < tasks.length) {
await new Promise(resolve => setTimeout(resolve, 500))
}
}
console.log('批量保存完成:', results)
// 发送批量保存完成事件
EventBus.emit('GKBackend-BatchSaveCompleted', {
total: tasks.length,
success: results.success,
failed: results.failed,
timestamp: new Date().toISOString()
})
return results
}
/**
* 获取任务列表
*/
async getTasks(params?: QueryParams): Promise<{
success: boolean
tasks: BackendTaskData[]
total: number
page: number
pageSize: number
error?: string
}> {
try {
console.log('获取任务列表:', params)
const response = await this.makeRequest<BackendResponse<{
tasks?: BackendTaskData[]
list?: BackendTaskData[]
data?: BackendTaskData[]
total?: number
count?: number
page?: number
pageSize?: number
}> | BackendTaskData[]>({
url: this.getApiUrl(),
method: 'POST',
data: params // 使用data而不是params因为POST请求的参数应该在请求体中
})
// 处理不同的响应格式
let tasks: BackendTaskData[] = []
let total = 0
let page = params?.page || 1
let pageSize = params?.pageSize || 50
if (Array.isArray(response)) {
// 直接返回数组
tasks = response
total = response.length
} else if (this.isSuccessResponse(response)) {
// 包装在response中
const data = response.data
if (data) {
tasks = data.tasks || data.list || data.data || []
total = data.total || data.count || tasks.length
page = data.page || page
pageSize = data.pageSize || pageSize
}
} else {
throw new Error(response.message || '获取任务列表失败')
}
console.log(`获取任务列表成功: ${tasks.length}/${total}`)
return {
success: true,
tasks,
total,
page,
pageSize
}
} catch (error: any) {
console.error('获取任务列表失败:', error)
// 提供更详细的错误信息
let errorMessage = '获取失败'
if (error.message) {
errorMessage = error.message
} else if (error.code === 'ERR_NETWORK') {
errorMessage = '网络连接失败,请检查服务器是否可访问'
} else if (error.code === 'ERR_FAILED') {
errorMessage = '请求失败,服务器可能不可用'
} else if (error.status) {
errorMessage = `HTTP ${error.status}: ${error.statusText || '请求失败'}`
}
return {
success: false,
tasks: [],
total: 0,
page: params?.page || 1,
pageSize: params?.pageSize || 50,
error: errorMessage
}
}
}
/**
* 根据ID获取任务详情
*/
async getTaskById(taskId: string): Promise<{
success: boolean
task?: BackendTaskData
error?: string
}> {
try {
console.log('获取任务详情:', taskId)
const response = await this.makeRequest<BackendResponse<BackendTaskData>>({
url: this.getApiUrl(`/${taskId}`),
method: 'GET'
})
if (this.isSuccessResponse(response)) {
return {
success: true,
task: response.data
}
} else {
throw new Error(response.message || '获取任务详情失败')
}
} catch (error: any) {
console.error('获取任务详情失败:', error)
return {
success: false,
error: error.message || '获取失败'
}
}
}
/**
* 更新任务
*/
async updateTask(taskId: string, updates: Partial<BackendTaskData>): Promise<{
success: boolean
message?: string
error?: string
}> {
try {
console.log('更新任务:', taskId, updates)
const response = await this.makeRequest<BackendResponse>({
url: this.getApiUrl(`/${taskId}`),
method: 'PUT',
data: {
...updates,
updatedAt: new Date().toISOString()
}
})
if (this.isSuccessResponse(response)) {
console.log('任务更新成功:', response)
// 发送更新成功事件
EventBus.emit('GKBackend-TaskUpdated', {
taskId,
updates,
timestamp: new Date().toISOString()
})
return {
success: true,
message: response.message || '更新成功'
}
} else {
throw new Error(response.message || '更新失败')
}
} catch (error: any) {
console.error('更新任务失败:', error)
// 添加到重试队列
if (this.config.enableRetry) {
this.addToRetryQueue(() => this.updateTask(taskId, updates))
}
return {
success: false,
error: error.message || '更新失败'
}
}
}
/**
* 删除任务
*/
async deleteTask(taskId: string): Promise<{
success: boolean
message?: string
error?: string
}> {
try {
console.log('删除任务:', taskId)
const response = await this.makeRequest<BackendResponse>({
url: this.getApiUrl(`/${taskId}`),
method: 'DELETE'
})
if (this.isSuccessResponse(response)) {
console.log('任务删除成功:', response)
// 发送删除成功事件
EventBus.emit('GKBackend-TaskDeleted', {
taskId,
timestamp: new Date().toISOString()
})
return {
success: true,
message: response.message || '删除成功'
}
} else {
throw new Error(response.message || '删除失败')
}
} catch (error: any) {
console.error('删除任务失败:', error)
return {
success: false,
error: error.message || '删除失败'
}
}
}
/**
* 测试API连接
*/
async testConnection(): Promise<{
success: boolean
status: number
message: string
responseTime: number
}> {
const startTime = Date.now()
try {
console.log('测试API连接:', this.getApiUrl())
const response = await this.makeRequest({
url: this.getApiUrl(),
method: 'GET',
params: { page: 1, pageSize: 1 } // 只获取一条记录测试连接
})
const responseTime = Date.now() - startTime
console.log('API连接测试成功, 响应时间:', responseTime + 'ms')
return {
success: true,
status: 200,
message: '连接成功',
responseTime
}
} catch (error: any) {
const responseTime = Date.now() - startTime
console.error('API连接测试失败:', error)
return {
success: false,
status: error.status || 0,
message: error.message || '连接失败',
responseTime
}
}
}
/**
* 获取统计信息
*/
async getStatistics(): Promise<{
success: boolean
stats?: {
totalTasks: number
todayTasks: number
importantTasks: number
tasksByType: Record<number, number>
tasksByPriority: Record<number, number>
}
error?: string
}> {
try {
// 获取所有任务来统计(实际项目中后端应该提供统计接口)
const result = await this.getTasks({ pageSize: 1000 })
if (!result.success) {
throw new Error(result.error || '获取统计数据失败')
}
const tasks = result.tasks
const today = new Date().toDateString()
const stats = {
totalTasks: tasks.length,
todayTasks: tasks.filter(task =>
new Date(task.createdAt).toDateString() === today
).length,
importantTasks: tasks.filter(task => task.isImportant).length,
tasksByType: {} as Record<number, number>,
tasksByPriority: {} as Record<number, number>
}
// 统计任务类型分布
tasks.forEach(task => {
stats.tasksByType[task.taskType] = (stats.tasksByType[task.taskType] || 0) + 1
stats.tasksByPriority[task.priority] = (stats.tasksByPriority[task.priority] || 0) + 1
})
return {
success: true,
stats
}
} catch (error: any) {
console.error('获取统计信息失败:', error)
return {
success: false,
error: error.message || '获取统计失败'
}
}
}
/**
* 处理重试队列
*/
async processRetryQueue(): Promise<void> {
if (this.isProcessingRetry || this.retryQueue.length === 0) {
return
}
this.isProcessingRetry = true
const operations = [...this.retryQueue]
this.retryQueue = []
console.log(`开始处理重试队列,操作数量: ${operations.length}`)
for (const operation of operations) {
try {
await operation()
console.log('重试操作成功')
} catch (error) {
console.error('重试操作仍然失败:', error)
}
// 重试间隔
await new Promise(resolve => setTimeout(resolve, this.config.retryInterval))
}
this.isProcessingRetry = false
console.log('重试队列处理完成')
}
/**
* 将GK数据转换为后端格式
*/
private convertToBackendFormat(taskData: ParsedTaskInfo | StoredTaskData): BackendTaskData {
const now = new Date().toISOString()
return {
taskId: taskData.taskId,
taskName: taskData.taskName,
taskType: parseInt(taskData.taskType),
priority: taskData.priority,
battleUnitCode: taskData.battleUnitCode,
deviceSn: taskData.sn,
startTime: taskData.startTime.toISOString(),
endTime: taskData.endTime.toISOString(),
receiveTime: taskData.receiveTime?.toISOString() || now,
routePoints: taskData.routePoints.map((point, index) => ({
...point,
index: index + 1
})),
positionPoints: taskData.positionPoints || [],
routeDistance: taskData.routeDistance || 0,
estimatedDuration: taskData.estimatedDuration || 0,
userNotes: (taskData as StoredTaskData).userNotes,
isImportant: (taskData as StoredTaskData).isImportant || false,
tags: (taskData as StoredTaskData).tags || [],
source: 'gk_panel',
metadata: {
clientVersion: '1.0.0',
userAgent: navigator.userAgent,
timestamp: now
},
createdAt: (taskData as StoredTaskData).storedAt || now,
updatedAt: now
}
}
/**
* 发起API请求
*/
private async makeRequest<T>(options: {
url: string
method: 'GET' | 'POST' | 'PUT' | 'DELETE'
data?: any
params?: any
}): Promise<T> {
const { url, method, data, params } = options
return await request({
url,
method,
data,
params,
timeout: this.config.timeout,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('x-auth-token') || ''}`
}
})
}
/**
* 判断响应是否成功
*/
private isSuccessResponse(response: any): boolean {
// 如果响应为空或undefined返回false
if (!response) return false
// 检查多种成功状态的可能性
if (response.success === true) return true
if (response.code === 200 || response.code === 0) return true
if (!response.code && !response.success && response.data !== undefined) return true
return false
}
/**
* 添加到重试队列
*/
private addToRetryQueue(operation: () => Promise<any>): void {
if (this.retryQueue.length < this.config.maxRetries * 10) { // 限制队列大小
this.retryQueue.push(operation)
console.log(`操作已加入重试队列,当前队列长度: ${this.retryQueue.length}`)
// 延迟处理重试队列
setTimeout(() => this.processRetryQueue(), this.config.retryInterval)
}
}
/**
* 销毁服务
*/
destroy(): void {
this.retryQueue = []
this.isProcessingRetry = false
console.log('GK数据后端服务已销毁')
}
}
/**
* 创建后端服务实例
*/
export function createGKBackendService(config?: Partial<BackendServiceConfig>): GKDataBackendService {
return new GKDataBackendService(config)
}
/**
* 全局后端服务实例(单例)
*/
let gkBackendServiceInstance: GKDataBackendService | null = null
export function getGKBackendService(config?: Partial<BackendServiceConfig>): GKDataBackendService {
if (!gkBackendServiceInstance) {
gkBackendServiceInstance = createGKBackendService(config)
}
return gkBackendServiceInstance
}
export default GKDataBackendService

View File

@@ -0,0 +1,201 @@
/*
* @Date: 2024-12-19
* @Description: GK数据WebSocket配置文件
* @Author: Assistant
* @FilePath: \drone-v2-command-screen\src\api\websocket\gkDataConfig.ts
*/
import { ELocalStorageKey } from '/@/types/enums'
/**
* GK数据WebSocket配置接口
*/
export interface GKWebSocketConfig {
/** 主服务器地址 */
primary: string
/** 备用服务器地址 */
fallback: string
/** WebSocket连接路径 */
path: string
/** 自动重连配置 */
reconnect: {
maxReconnectionDelay: number
minReconnectionDelay: number
maxRetries: number
reconnectionDelayGrowFactor: number
}
}
/**
* 默认GK数据WebSocket配置
* 基于项目中的主服务器配置
*/
export const DEFAULT_GK_WEBSOCKET_CONFIG: GKWebSocketConfig = {
// 主服务器地址(当前使用)
primary: 'ws://27.11.11.30',
// 备用服务器地址
fallback: 'ws://221.226.33.58:18800',
// WebSocket连接路径与项目中的WebSocket路径保持一致
path: '/dji/api/v1/ws',
// 自动重连配置
reconnect: {
maxReconnectionDelay: 20000, // 最大重连延迟20秒
minReconnectionDelay: 5000, // 最小重连延迟5秒
maxRetries: 10, // 最大重试次数
reconnectionDelayGrowFactor: 1.3
}
}
/**
* 获取完整的GK数据WebSocket URL带token认证
* @param useFallback 是否使用备用地址
* @returns 完整的WebSocket URL
*/
export function getGKWebSocketUrl(useFallback: boolean = false): string {
// 获取token与项目现有方式保持一致
const token: string = localStorage.getItem(ELocalStorageKey.Token) || localStorage.getItem(ELocalStorageKey.WsToken) || ''
// 在开发环境下使用代理路径,生产环境使用直接连接
if (import.meta.env.DEV) {
// 开发环境使用vite代理
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const host = window.location.host
const baseUrl = `${protocol}//${host}/dji/api/v1/ws`
// 添加token认证参数
if (token) {
return `${baseUrl}?x-auth-token=${encodeURIComponent(token)}`
}
return baseUrl
} else {
// 生产环境:直接连接
const config = DEFAULT_GK_WEBSOCKET_CONFIG
const baseUrl = useFallback ? config.fallback : config.primary
const fullUrl = `${baseUrl}${config.path}`
// 添加token认证参数
if (token) {
return `${fullUrl}?x-auth-token=${encodeURIComponent(token)}`
}
return fullUrl
}
}
/**
* 获取所有可用的GK数据WebSocket URL
* @returns 主地址和备用地址数组
*/
export function getAllGKWebSocketUrls(): string[] {
return [
getGKWebSocketUrl(false), // 主地址
getGKWebSocketUrl(true) // 备用地址
]
}
/**
* 从环境变量或进程中获取基础URL
* 与vite.config.ts中的配置保持一致
*/
export function getBaseTargetUrl(): string {
// 尝试从环境变量中获取与vite.config.ts保持一致
if (typeof process !== 'undefined' && process.env?.BASE_TARGET_URL) {
return process.env.BASE_TARGET_URL.replace(/"/g, '') // 移除JSON.stringify添加的引号
}
// 默认返回主服务器地址
return '27.11.11.30/'
}
/**
* 动态构建GK数据WebSocket URL
* 基于项目运行时的配置
*/
export function buildGKWebSocketUrl(): string {
const baseUrl = getBaseTargetUrl()
// 确保URL格式正确
let wsUrl = baseUrl.startsWith('ws://') ? baseUrl : `ws://${baseUrl}`
// 移除末尾的斜杠(如果存在)
if (wsUrl.endsWith('/')) {
wsUrl = wsUrl.slice(0, -1)
}
return `${wsUrl}${DEFAULT_GK_WEBSOCKET_CONFIG.path}`
}
/**
* GK数据WebSocket配置管理器
*/
export class GKWebSocketConfigManager {
private currentUrl: string
private fallbackUrl: string
private retryCount: number = 0
constructor() {
this.currentUrl = getGKWebSocketUrl(false)
this.fallbackUrl = getGKWebSocketUrl(true)
}
/**
* 获取当前使用的WebSocket URL
*/
getCurrentUrl(): string {
return this.currentUrl
}
/**
* 切换到备用地址
*/
switchToFallback(): void {
console.log(`GK WebSocket 切换到备用地址: ${this.fallbackUrl}`)
this.currentUrl = this.fallbackUrl
this.retryCount++
}
/**
* 重置到主地址
*/
resetToPrimary(): void {
console.log(`GK WebSocket 重置到主地址: ${getGKWebSocketUrl(false)}`)
this.currentUrl = getGKWebSocketUrl(false)
this.retryCount = 0
}
/**
* 获取重试次数
*/
getRetryCount(): number {
return this.retryCount
}
/**
* 是否应该切换到备用地址
*/
shouldSwitchToFallback(): boolean {
return this.retryCount >= 3 && this.currentUrl !== this.fallbackUrl
}
/**
* 获取重连配置
*/
getReconnectConfig() {
return DEFAULT_GK_WEBSOCKET_CONFIG.reconnect
}
}
/**
* 全局GK数据WebSocket配置管理器实例
*/
export const gkWebSocketConfigManager = new GKWebSocketConfigManager()
export default {
DEFAULT_GK_WEBSOCKET_CONFIG,
getGKWebSocketUrl,
getAllGKWebSocketUrls,
buildGKWebSocketUrl,
GKWebSocketConfigManager,
gkWebSocketConfigManager
}

View File

@@ -0,0 +1,471 @@
<!--
@Date: 2024-12-19
@Description: GK数据监听器演示组件
@Author: Assistant
@FilePath: \drone-v2-command-screen\src\api\websocket\gkDataDemo.vue
-->
<template>
<div class="gk-data-demo">
<h2>GK数据监听器演示</h2>
<!-- 连接控制 -->
<div class="connection-section">
<h3>连接控制</h3>
<div class="controls">
<input
v-model="serverUrl"
placeholder="输入GK_SERVER WebSocket地址"
class="url-input"
/>
<button @click="connect" :disabled="isConnected" class="btn btn-primary">
连接
</button>
<button @click="disconnect" :disabled="!isConnected" class="btn btn-secondary">
断开
</button>
<span :class="['status', isConnected ? 'connected' : 'disconnected']">
{{ isConnected ? '已连接' : '未连接' }}
</span>
</div>
</div>
<!-- 数据发送 -->
<div class="send-section">
<h3>发送消息到GK_SERVER</h3>
<div class="controls">
<button @click="requestAllData" :disabled="!isConnected" class="btn btn-info">
请求所有数据
</button>
<button @click="requestTaskInfo" :disabled="!isConnected" class="btn btn-info">
请求任务信息
</button>
<button @click="requestRouteData" :disabled="!isConnected" class="btn btn-info">
请求路线数据
</button>
<button @click="requestPositionData" :disabled="!isConnected" class="btn btn-info">
请求位置数据
</button>
</div>
</div>
<!-- 数据显示 -->
<div class="data-section">
<h3>接收到的GK数据</h3>
<!-- 统计信息 -->
<div class="stats" v-if="statistics">
<div class="stat-item">
<label>总接收数量:</label>
<span>{{ statistics.totalReceived }}</span>
</div>
<div class="stat-item">
<label>最后接收时间:</label>
<span>{{ statistics.lastReceiveTime || '无' }}</span>
</div>
<div class="stat-item">
<label>有任务信息:</label>
<span>{{ statistics.hasTaskInfo ? '是' : '否' }}</span>
</div>
<div class="stat-item">
<label>有路线数据:</label>
<span>{{ statistics.hasRouteData ? '是' : '否' }}</span>
</div>
<div class="stat-item">
<label>有位置数据:</label>
<span>{{ statistics.hasPositionData ? '是' : '否' }}</span>
</div>
</div>
<!-- 最新任务信息 -->
<div class="data-card" v-if="latestTaskInfo">
<h4>最新任务信息</h4>
<pre>{{ JSON.stringify(latestTaskInfo, null, 2) }}</pre>
</div>
<!-- 最新路线数据 -->
<div class="data-card" v-if="latestRouteData">
<h4>最新路线数据</h4>
<pre>{{ JSON.stringify(latestRouteData, null, 2) }}</pre>
</div>
<!-- 最新位置数据 -->
<div class="data-card" v-if="latestPositionData">
<h4>最新位置数据</h4>
<pre>{{ JSON.stringify(latestPositionData, null, 2) }}</pre>
</div>
<!-- 原始数据日志 -->
<div class="log-section">
<h4>
数据接收日志
<button @click="clearLogs" class="btn btn-small">清空日志</button>
</h4>
<div class="log-container">
<div
v-for="(log, index) in logs"
:key="index"
class="log-item"
>
<span class="log-time">{{ log.time }}</span>
<span class="log-type">{{ log.type }}</span>
<span class="log-content">{{ log.content }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'
import { GKDataManager, GKDataManagerConfig } from './gkDataManager'
import { GKData, ParsedTaskInfo } from './gkDataListener'
import { getGKWebSocketUrl } from './gkDataConfig'
import { createGKDataSimulator } from './gkDataSimulator'
import EventBus from '../im/eventBus.js'
// 响应式数据
const serverUrl = ref(getGKWebSocketUrl()) // 默认使用GK_SERVER地址: ws://27.11.11.30/dji/api/v1/ws
const isConnected = ref(false)
const latestTaskInfo = ref(null)
const latestRouteData = ref(null)
const latestPositionData = ref(null)
const logs = ref<Array<{time: string, type: string, content: string}>>([])
// GK数据管理器实例
let gkDataManager: GKDataManager | null = null
// 统计信息
const statistics = computed(() => {
return gkDataManager?.getStatistics() || null
})
// 添加日志
const addLog = (type: string, content: string) => {
logs.value.unshift({
time: new Date().toLocaleTimeString(),
type,
content
})
// 限制日志数量
if (logs.value.length > 100) {
logs.value = logs.value.slice(0, 100)
}
}
// 连接到GK_SERVER
const connect = () => {
if (!serverUrl.value.trim()) {
alert('请输入WebSocket服务器地址')
return
}
try {
const config: GKDataManagerConfig = {
url: serverUrl.value.trim(),
autoConnect: true,
enableLogging: true,
dataBufferSize: 50
}
gkDataManager = new GKDataManager(config)
gkDataManager.init()
addLog('INFO', `开始连接到 ${serverUrl.value}`)
} catch (error) {
addLog('ERROR', `连接失败: ${error}`)
}
}
// 断开连接
const disconnect = () => {
if (gkDataManager) {
gkDataManager.close()
gkDataManager = null
}
isConnected.value = false
addLog('INFO', '连接已断开')
}
// 请求所有数据
const requestAllData = () => {
gkDataManager?.requestData('all')
addLog('REQUEST', '请求所有GK数据')
}
// 请求任务信息
const requestTaskInfo = () => {
gkDataManager?.requestData('task_info')
addLog('REQUEST', '请求任务信息')
}
// 请求路线数据
const requestRouteData = () => {
gkDataManager?.requestData('route_data')
addLog('REQUEST', '请求路线数据')
}
// 请求位置数据
const requestPositionData = () => {
gkDataManager?.requestData('position_data')
addLog('REQUEST', '请求位置数据')
}
// 清空日志
const clearLogs = () => {
logs.value = []
}
// 设置事件监听器
const setupEventListeners = () => {
// 连接状态事件
EventBus.on('GKDataManager-Connected', (data: any) => {
isConnected.value = true
addLog('CONNECTED', `连接成功: ${JSON.stringify(data)}`)
})
EventBus.on('GKDataManager-Disconnected', (data: any) => {
isConnected.value = false
addLog('DISCONNECTED', `连接断开: ${JSON.stringify(data)}`)
})
EventBus.on('GKDataManager-Error', (error: any) => {
addLog('ERROR', `连接错误: ${JSON.stringify(error)}`)
})
// 数据接收事件
EventBus.on('GKDataManager-DataReceived', (gkData: GKData) => {
addLog('DATA', `接收到GK数据: ${gkData.timestamp}`)
})
// 特定数据更新事件
EventBus.on('GKDataManager-TaskInfoUpdated', (taskInfo: any) => {
latestTaskInfo.value = taskInfo
addLog('TASK', `任务信息更新: ${taskInfo.task_name}`)
})
EventBus.on('GKDataManager-RouteDataUpdated', (routeData: any) => {
latestRouteData.value = routeData
addLog('ROUTE', `路线数据更新: ${routeData.route_name}`)
})
EventBus.on('GKDataManager-PositionDataUpdated', (positionData: any) => {
latestPositionData.value = positionData
addLog('POSITION', `位置数据更新: 设备${positionData.device_id}`)
})
}
// 清理事件监听器
const cleanupEventListeners = () => {
EventBus.off('GKDataManager-Connected')
EventBus.off('GKDataManager-Disconnected')
EventBus.off('GKDataManager-Error')
EventBus.off('GKDataManager-DataReceived')
EventBus.off('GKDataManager-TaskInfoUpdated')
EventBus.off('GKDataManager-RouteDataUpdated')
EventBus.off('GKDataManager-PositionDataUpdated')
}
// 组件生命周期
onMounted(() => {
setupEventListeners()
addLog('INFO', 'GK数据监听器演示组件已加载')
})
onUnmounted(() => {
disconnect()
cleanupEventListeners()
})
</script>
<style scoped>
.gk-data-demo {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.connection-section,
.send-section,
.data-section {
margin-bottom: 30px;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 20px;
}
h2, h3, h4 {
color: #333;
margin-bottom: 15px;
}
.controls {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.url-input {
flex: 1;
min-width: 300px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover:not(:disabled) {
background-color: #0056b3;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover:not(:disabled) {
background-color: #545b62;
}
.btn-info {
background-color: #17a2b8;
color: white;
}
.btn-info:hover:not(:disabled) {
background-color: #117a8b;
}
.btn-small {
padding: 4px 8px;
font-size: 12px;
background-color: #f8f9fa;
color: #333;
}
.status {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.status.connected {
background-color: #d4edda;
color: #155724;
}
.status.disconnected {
background-color: #f8d7da;
color: #721c24;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
margin-bottom: 20px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 4px;
}
.stat-item {
display: flex;
justify-content: space-between;
}
.stat-item label {
font-weight: bold;
color: #666;
}
.data-card {
margin-bottom: 20px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 4px;
border-left: 4px solid #007bff;
}
.data-card h4 {
margin-top: 0;
margin-bottom: 10px;
color: #007bff;
}
.data-card pre {
background-color: #fff;
padding: 10px;
border-radius: 4px;
border: 1px solid #e0e0e0;
font-size: 12px;
overflow-x: auto;
margin: 0;
}
.log-section h4 {
display: flex;
justify-content: space-between;
align-items: center;
}
.log-container {
max-height: 300px;
overflow-y: auto;
border: 1px solid #e0e0e0;
border-radius: 4px;
background-color: #f8f9fa;
}
.log-item {
display: flex;
padding: 8px 12px;
border-bottom: 1px solid #e0e0e0;
font-family: monospace;
font-size: 12px;
}
.log-item:last-child {
border-bottom: none;
}
.log-time {
color: #666;
margin-right: 10px;
min-width: 80px;
}
.log-type {
color: #007bff;
margin-right: 10px;
min-width: 80px;
font-weight: bold;
}
.log-content {
flex: 1;
color: #333;
}
</style>

View File

@@ -0,0 +1,317 @@
/*
* @Date: 2024-12-19
* @Description: GK数据监听器使用示例和集成指南
* @Author: Assistant
* @FilePath: \drone-v2-command-screen\src\api\websocket\gkDataExample.ts
*/
import { GKDataManager, GKDataManagerConfig } from './gkDataManager'
import { GKDataListener, GKData, GKDataHandler } from './gkDataListener'
import { getGKWebSocketUrl } from './gkDataConfig'
import EventBus from '../im/eventBus.js'
/**
* GK数据监听使用示例类
*/
export class GKDataExample {
private gkDataManager: GKDataManager | null = null
/**
* 基础使用示例
*/
async basicUsageExample(): Promise<void> {
console.log('=== GK数据监听基础使用示例 ===')
// 1. 创建GK数据管理器配置
const config: GKDataManagerConfig = {
url: getGKWebSocketUrl(), // 使用实际的GK_SERVER地址ws://27.11.11.30/dji/api/v1/ws
autoConnect: true,
enableLogging: true,
dataBufferSize: 200
}
// 2. 创建GK数据管理器实例
this.gkDataManager = new GKDataManager(config)
// 3. 设置事件监听器
this.setupEventListeners()
// 4. 初始化管理器
this.gkDataManager.init()
console.log('GK数据监听器已启动等待接收gk_data业务代码的数据...')
}
/**
* 设置事件监听器示例
*/
private setupEventListeners(): void {
// 监听连接状态变化
EventBus.on('GKDataManager-Connected', (data) => {
console.log('✅ GK数据服务器连接成功:', data)
})
EventBus.on('GKDataManager-Disconnected', (data) => {
console.log('❌ GK数据服务器连接断开:', data)
})
EventBus.on('GKDataManager-Error', (error) => {
console.error('🚨 GK数据服务器连接错误:', error)
})
// 监听数据接收
EventBus.on('GKDataManager-DataReceived', (gkData: GKData) => {
console.log('📦 接收到GK数据:', gkData)
this.handleReceivedData(gkData)
})
// 监听特定类型的数据更新
EventBus.on('GKDataManager-TaskInfoUpdated', (taskInfo) => {
console.log('📋 任务信息更新:', taskInfo)
this.handleTaskInfoUpdate(taskInfo)
})
EventBus.on('GKDataManager-RouteDataUpdated', (routeData) => {
console.log('🗺️ 路线数据更新:', routeData)
this.handleRouteDataUpdate(routeData)
})
EventBus.on('GKDataManager-PositionDataUpdated', (positionData) => {
console.log('📍 位置数据更新:', positionData)
this.handlePositionDataUpdate(positionData)
})
}
/**
* 处理接收到的数据
*/
private handleReceivedData(gkData: GKData): void {
const { data } = gkData
// 根据数据类型进行不同处理
if (data.task_info) {
console.log('处理任务信息:', data.task_info)
}
if (data.route_data) {
console.log('处理路线数据:', data.route_data)
// 可以在这里更新地图上的路线显示
}
if (data.position_data) {
console.log('处理位置数据:', data.position_data)
// 可以在这里更新设备在地图上的位置
}
if (data.raw_data) {
console.log('处理原始数据:', data.raw_data)
}
}
/**
* 处理任务信息更新
*/
private handleTaskInfoUpdate(taskInfo: any): void {
// 更新任务状态显示
console.log(`任务 ${taskInfo.task_name} 状态: ${taskInfo.status}`)
// 可以在这里触发UI更新
// 例如:更新任务列表、任务进度条等
}
/**
* 处理路线数据更新
*/
private handleRouteDataUpdate(routeData: any): void {
// 更新地图上的路线显示
console.log(`路线 ${routeData.route_name} 包含 ${routeData.waypoints.length} 个航点`)
// 可以在这里调用地图API更新路线
// 例如mars3d地图添加路线、航点标记等
}
/**
* 处理位置数据更新
*/
private handlePositionDataUpdate(positionData: any): void {
// 更新设备位置显示
console.log(`设备 ${positionData.device_id} 位置: ${positionData.latitude}, ${positionData.longitude}`)
// 可以在这里更新设备在地图上的实时位置
// 例如:移动设备图标、更新轨迹等
}
/**
* 自定义数据处理器示例
*/
createCustomDataHandler(): GKDataHandler {
return (gkData: GKData) => {
// 自定义数据处理逻辑
console.log('自定义处理器处理GK数据:', gkData)
// 例如:保存到本地存储
localStorage.setItem(`gk_data_${gkData.timestamp}`, JSON.stringify(gkData))
// 例如:发送到其他服务
// this.sendToOtherService(gkData)
}
}
/**
* 发送消息到GK_SERVER示例
*/
sendMessageExample(): void {
if (!this.gkDataManager) {
console.error('GK数据管理器未初始化')
return
}
// 发送订阅消息
this.gkDataManager.sendMessage({
action: 'subscribe',
biz_code: 'gk_data',
data_types: ['task_info', 'route_data', 'position_data'],
timestamp: new Date().toISOString()
})
// 请求特定数据
this.gkDataManager.requestData('all')
}
/**
* 数据查询示例
*/
dataQueryExample(): void {
if (!this.gkDataManager) {
console.error('GK数据管理器未初始化')
return
}
// 获取最新数据
const latestData = this.gkDataManager.getLatestData()
console.log('最新数据:', latestData)
// 获取最新任务信息
const latestTaskInfo = this.gkDataManager.getLatestTaskInfo()
console.log('最新任务信息:', latestTaskInfo)
// 获取最新路线数据
const latestRouteData = this.gkDataManager.getLatestRouteData()
console.log('最新路线数据:', latestRouteData)
// 获取最新位置数据
const latestPositionData = this.gkDataManager.getLatestPositionData()
console.log('最新位置数据:', latestPositionData)
// 获取时间范围内的数据
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString()
const now = new Date().toISOString()
const recentData = this.gkDataManager.getDataByTimeRange(oneHourAgo, now)
console.log('最近一小时的数据:', recentData)
// 获取统计信息
const stats = this.gkDataManager.getStatistics()
console.log('统计信息:', stats)
}
/**
* 关闭示例
*/
closeExample(): void {
if (this.gkDataManager) {
this.gkDataManager.close()
this.gkDataManager = null
console.log('GK数据监听器已关闭')
}
}
}
/**
* 在Vue组件中使用GK数据监听器的示例
*/
export function useGKDataInVueComponent() {
let gkDataManager: GKDataManager | null = null
// 初始化函数
const initGKDataListener = (serverUrl: string) => {
const config: GKDataManagerConfig = {
url: serverUrl,
autoConnect: true,
enableLogging: true,
dataBufferSize: 100
}
gkDataManager = new GKDataManager(config)
// 注册自定义数据处理器
gkDataManager.registerDataHandler((gkData: GKData) => {
// 在Vue组件中处理数据
console.log('Vue组件接收到GK数据:', gkData)
})
gkDataManager.init()
}
// 获取最新数据
const getLatestData = () => {
return gkDataManager?.getLatestData() || null
}
// 发送消息
const sendMessage = (message: any) => {
gkDataManager?.sendMessage(message)
}
// 获取连接状态
const isConnected = () => {
return gkDataManager?.isConnected() || false
}
// 清理函数
const cleanup = () => {
gkDataManager?.close()
gkDataManager = null
}
return {
initGKDataListener,
getLatestData,
sendMessage,
isConnected,
cleanup
}
}
/**
* 简单的GK数据监听器使用示例不使用管理器
*/
export function simpleGKDataListenerExample(serverUrl: string): void {
console.log('=== 简单GK数据监听器使用示例 ===')
// 创建监听器实例
const listener = new GKDataListener(serverUrl)
// 注册数据处理器
listener.registerDataHandler((gkData: GKData) => {
console.log('接收到GK数据:', gkData)
// 简单的数据处理
if (gkData.data.position_data) {
console.log('位置更新:', {
device: gkData.data.position_data.device_id,
lat: gkData.data.position_data.latitude,
lng: gkData.data.position_data.longitude
})
}
})
// 初始化连接
listener.initSocket()
// 设置定时器5分钟后关闭连接
setTimeout(() => {
listener.close()
console.log('简单监听器示例结束')
}, 5 * 60 * 1000)
}
export default GKDataExample

View File

@@ -0,0 +1,575 @@
# GK数据监听器集成指南
## 概述
这是一个完整的集成指南说明如何在你的无人机指挥屏项目中集成GK数据监听器接收和处理来自GK_SERVER的WebSocket数据推送业务代码`gk_data`)。
## 集成步骤
### 1. 基础集成
在你的Vue组件中集成GK数据监听
```vue
<!-- src/pages/yourPage/index.vue -->
<!-- 在你的无人机指挥屏页面中集成GK数据监听器 -->
<template>
<div class="your-page">
<!-- GK数据状态显示 -->
<div class="gk-status-panel">
<div class="status-item">
<span>GK连接状态:</span>
<span :class="gkConnected ? 'connected' : 'disconnected'">
{{ gkConnected ? '已连接' : '未连接' }}
</span>
</div>
<div class="status-item" v-if="currentTask">
<span>当前任务:</span>
<span>{{ currentTask.task_name }} ({{ currentTask.status }})</span>
</div>
</div>
<!-- 你的其他组件 -->
<!-- ... -->
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { GKDataManager, GKDataManagerConfig } from '@/api/websocket/gkDataManager'
import { GKData } from '@/api/websocket/gkDataListener'
import { getGKWebSocketUrl } from '@/api/websocket/gkDataConfig'
import EventBus from '@/api/im/eventBus.js'
// 响应式数据
const gkConnected = ref(false)
const currentTask = ref(null)
const currentPosition = ref(null)
const currentRoute = ref(null)
// GK数据管理器
let gkDataManager: GKDataManager | null = null
// 初始化GK数据监听
const initGKDataListener = () => {
const config: GKDataManagerConfig = {
url: getGKWebSocketUrl(), // 使用实际的GK_SERVER地址ws://27.11.11.30/dji/api/v1/ws
autoConnect: true,
enableLogging: true,
dataBufferSize: 100
}
gkDataManager = new GKDataManager(config)
// 注册自定义数据处理器
gkDataManager.registerDataHandler(handleGKData)
// 初始化连接
gkDataManager.init()
}
// 自定义GK数据处理
const handleGKData = (gkData: GKData) => {
console.log('处理GK数据:', gkData)
// 根据数据类型进行不同处理
if (gkData.data.task_info) {
handleTaskInfoUpdate(gkData.data.task_info)
}
if (gkData.data.route_data) {
handleRouteDataUpdate(gkData.data.route_data)
}
if (gkData.data.position_data) {
handlePositionDataUpdate(gkData.data.position_data)
}
}
// 处理任务信息更新
const handleTaskInfoUpdate = (taskInfo: any) => {
currentTask.value = taskInfo
// 可以在这里触发其他业务逻辑
// 例如:更新任务列表、发送通知等
}
// 处理路线数据更新
const handleRouteDataUpdate = (routeData: any) => {
currentRoute.value = routeData
// 可以在这里更新地图显示
// 例如更新mars3d地图上的路线
updateMapRoute(routeData)
}
// 处理位置数据更新
const handlePositionDataUpdate = (positionData: any) => {
currentPosition.value = positionData
// 可以在这里更新设备位置显示
// 例如移动mars3d地图上的设备图标
updateDevicePosition(positionData)
}
// 更新地图路线(示例)
const updateMapRoute = (routeData: any) => {
// 这里集成你的mars3d地图操作
console.log('更新地图路线:', routeData)
// 示例代码需要根据你的mars3d集成调整
// if (window.map && routeData.waypoints) {
// const positions = routeData.waypoints.map(wp => [wp.longitude, wp.latitude, wp.altitude])
// window.map.addPolyline(positions)
// }
}
// 更新设备位置(示例)
const updateDevicePosition = (positionData: any) => {
// 这里集成你的mars3d地图操作
console.log('更新设备位置:', positionData)
// 示例代码需要根据你的mars3d集成调整
// if (window.map && positionData) {
// window.map.updateDevicePosition(
// positionData.device_id,
// [positionData.longitude, positionData.latitude, positionData.altitude]
// )
// }
}
// 设置事件监听
const setupEventListeners = () => {
EventBus.on('GKDataManager-Connected', () => {
gkConnected.value = true
})
EventBus.on('GKDataManager-Disconnected', () => {
gkConnected.value = false
})
EventBus.on('GKDataManager-Error', (error: any) => {
console.error('GK数据连接错误:', error)
})
}
// 清理事件监听
const cleanupEventListeners = () => {
EventBus.off('GKDataManager-Connected')
EventBus.off('GKDataManager-Disconnected')
EventBus.off('GKDataManager-Error')
}
// 组件生命周期
onMounted(() => {
setupEventListeners()
initGKDataListener()
})
onUnmounted(() => {
if (gkDataManager) {
gkDataManager.close()
}
cleanupEventListeners()
})
</script>
<style scoped>
.gk-status-panel {
background: #f5f5f5;
padding: 10px;
border-radius: 4px;
margin-bottom: 20px;
}
.status-item {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
}
.connected {
color: #52c41a;
font-weight: bold;
}
.disconnected {
color: #ff4d4f;
font-weight: bold;
}
</style>
```
### 2. 在Vuex/Store中集成
如果你想在全局状态管理中使用GK数据
```typescript
// src/store/modules/gkData.ts
import { Module } from 'vuex'
import { GKDataManager } from '@/api/websocket/gkDataManager'
interface GKDataState {
isConnected: boolean
latestTaskInfo: any
latestRouteData: any
latestPositionData: any
dataHistory: any[]
}
const gkDataModule: Module<GKDataState, any> = {
namespaced: true,
state: {
isConnected: false,
latestTaskInfo: null,
latestRouteData: null,
latestPositionData: null,
dataHistory: []
},
mutations: {
SET_CONNECTION_STATUS(state, status: boolean) {
state.isConnected = status
},
SET_TASK_INFO(state, taskInfo: any) {
state.latestTaskInfo = taskInfo
},
SET_ROUTE_DATA(state, routeData: any) {
state.latestRouteData = routeData
},
SET_POSITION_DATA(state, positionData: any) {
state.latestPositionData = positionData
},
ADD_DATA_HISTORY(state, data: any) {
state.dataHistory.unshift(data)
// 限制历史数据数量
if (state.dataHistory.length > 100) {
state.dataHistory = state.dataHistory.slice(0, 100)
}
}
},
actions: {
initGKDataListener({ commit, dispatch }) {
const gkDataManager = new GKDataManager({
url: getGKWebSocketUrl(), // ws://27.11.11.30/dji/api/v1/ws
autoConnect: true,
enableLogging: true
})
// 注册数据处理器
gkDataManager.registerDataHandler((gkData) => {
commit('ADD_DATA_HISTORY', gkData)
if (gkData.data.task_info) {
commit('SET_TASK_INFO', gkData.data.task_info)
}
if (gkData.data.route_data) {
commit('SET_ROUTE_DATA', gkData.data.route_data)
}
if (gkData.data.position_data) {
commit('SET_POSITION_DATA', gkData.data.position_data)
}
})
gkDataManager.init()
// 存储管理器实例到全局(可选)
window.gkDataManager = gkDataManager
}
}
}
export default gkDataModule
```
### 3. 与现有WebSocket系统集成
如果你想将GK数据监听器与现有的双WebSocket系统集成
```typescript
// src/api/websocket/integratedWebSocketManager.ts
import DualWebSocketManager from './dualWebSocketManager'
import { GKDataManager } from './gkDataManager'
import { GKData } from './gkDataListener'
export class IntegratedWebSocketManager {
private dualWebSocketManager: DualWebSocketManager
private gkDataManager: GKDataManager
private store: any
constructor(store: any, gkServerUrl: string) {
this.store = store
// 初始化双WebSocket管理器
this.dualWebSocketManager = new DualWebSocketManager(store)
// 初始化GK数据管理器
this.gkDataManager = new GKDataManager({
url: gkServerUrl,
autoConnect: true,
enableLogging: true
})
}
init() {
// 初始化双WebSocket系统
this.dualWebSocketManager.init()
// 初始化GK数据监听器
this.gkDataManager.init()
// 设置GK数据处理器
this.gkDataManager.registerDataHandler(this.handleGKData.bind(this))
}
private handleGKData(gkData: GKData) {
// 将GK数据也存储到主store中
this.store.commit('SET_GK_DATA', gkData)
// 可以在这里进行数据融合处理
// 例如将GK位置数据与设备OSD数据进行对比
}
close() {
this.dualWebSocketManager.close()
this.gkDataManager.close()
}
}
```
### 4. 在mars3d地图中使用
如果你的项目使用mars3d地图可以这样集成
```typescript
// src/widgets/basic/gk-data-layer/index.ts
import { GKDataManager } from '@/api/websocket/gkDataManager'
import { GKData } from '@/api/websocket/gkDataListener'
export class GKDataMapLayer {
private map: any
private gkDataManager: GKDataManager
private deviceLayers: Map<string, any> = new Map()
private routeLayers: Map<string, any> = new Map()
constructor(map: any, gkServerUrl: string) {
this.map = map
this.gkDataManager = new GKDataManager({
url: gkServerUrl,
autoConnect: true,
enableLogging: true
})
this.setupGKDataHandler()
this.gkDataManager.init()
}
private setupGKDataHandler() {
this.gkDataManager.registerDataHandler((gkData: GKData) => {
if (gkData.data.route_data) {
this.updateRouteOnMap(gkData.data.route_data)
}
if (gkData.data.position_data) {
this.updateDevicePositionOnMap(gkData.data.position_data)
}
})
}
private updateRouteOnMap(routeData: any) {
const routeId = routeData.route_id
// 移除旧路线
if (this.routeLayers.has(routeId)) {
this.map.removeLayer(this.routeLayers.get(routeId))
}
// 添加新路线
const positions = routeData.waypoints.map((wp: any) => [
wp.longitude, wp.latitude, wp.altitude
])
const routeLayer = new mars3d.layer.GraphicLayer()
const polyline = new mars3d.graphic.PolylineEntity({
positions: positions,
style: {
color: '#ff0000',
width: 3,
clampToGround: true
}
})
routeLayer.addGraphic(polyline)
this.map.addLayer(routeLayer)
this.routeLayers.set(routeId, routeLayer)
}
private updateDevicePositionOnMap(positionData: any) {
const deviceId = positionData.device_id
const position = [positionData.longitude, positionData.latitude, positionData.altitude]
if (this.deviceLayers.has(deviceId)) {
// 更新现有设备位置
const deviceLayer = this.deviceLayers.get(deviceId)
const graphic = deviceLayer.graphics[0]
graphic.position = position
} else {
// 创建新设备标记
const deviceLayer = new mars3d.layer.GraphicLayer()
const marker = new mars3d.graphic.BillboardEntity({
position: position,
style: {
image: '/img/icon/device.png',
scale: 1,
horizontalOrigin: mars3d.HorizontalOrigin.CENTER,
verticalOrigin: mars3d.VerticalOrigin.BOTTOM
},
popup: `设备ID: ${deviceId}<br/>位置: ${position.join(', ')}`
})
deviceLayer.addGraphic(marker)
this.map.addLayer(deviceLayer)
this.deviceLayers.set(deviceId, deviceLayer)
}
}
destroy() {
// 清理地图图层
this.routeLayers.forEach(layer => this.map.removeLayer(layer))
this.deviceLayers.forEach(layer => this.map.removeLayer(layer))
// 关闭GK数据管理器
this.gkDataManager.close()
}
}
```
## 配置说明
### 环境配置
在你的环境配置文件中添加GK_SERVER配置
```typescript
// src/config/index.ts
export const config = {
// 其他配置...
gkServer: {
websocketUrl: process.env.VUE_APP_GK_WEBSOCKET_URL || 'ws://localhost:8080/websocket',
reconnectInterval: 5000,
maxReconnectAttempts: 10
}
}
```
### .env文件配置
```bash
# .env.development
VUE_APP_GK_WEBSOCKET_URL=ws://dev-gk-server:8080/websocket
# .env.production
VUE_APP_GK_WEBSOCKET_URL=ws://prod-gk-server:8080/websocket
```
## 最佳实践
### 1. 错误处理
```typescript
const handleGKDataError = (error: any) => {
console.error('GK数据处理错误:', error)
// 发送错误报告
// reportError('GK_DATA_ERROR', error)
// 显示用户友好的错误信息
// message.error('GK数据连接异常请检查网络')
}
```
### 2. 数据验证
```typescript
const validateGKData = (gkData: GKData): boolean => {
// 验证数据完整性
if (!gkData.biz_code || gkData.biz_code !== 'gk_data') {
return false
}
if (!gkData.timestamp || !gkData.data) {
return false
}
return true
}
```
### 3. 性能优化
```typescript
// 使用防抖处理高频位置数据更新
import { debounce } from 'lodash-es'
const updateDevicePosition = debounce((positionData: any) => {
// 更新设备位置
}, 100) // 100ms防抖
```
## 调试和监控
### 1. 开发环境调试
在开发环境中启用详细日志:
```typescript
const gkDataManager = new GKDataManager({
url: config.gkServer.websocketUrl,
enableLogging: process.env.NODE_ENV === 'development'
})
```
### 2. 生产环境监控
在生产环境中添加监控:
```typescript
// 监控连接状态
setInterval(() => {
if (gkDataManager && !gkDataManager.isConnected()) {
console.warn('GK数据连接已断开尝试重连...')
gkDataManager.reconnect()
}
}, 30000) // 每30秒检查一次
```
## 故障排除
### 常见问题及解决方案
1. **连接失败**
- 检查GK_SERVER地址是否正确
- 确认网络连接是否正常
- 检查防火墙设置
2. **数据接收异常**
- 确认GK_SERVER正在推送 `gk_data` 业务代码
- 检查数据格式是否符合预期
- 查看浏览器控制台错误信息
3. **性能问题**
- 调整数据缓冲区大小
- 使用防抖处理高频数据
- 定期清理不需要的数据
这样你就可以在你的无人机指挥屏项目中完整地集成GK数据监听功能了。

View File

@@ -0,0 +1,578 @@
/*
* @Date: 2024-12-19
* @Description: GK数据WebSocket监听器专门用于接收和处理gk_data业务代码的数据
* @Author: Assistant
* @FilePath: \drone-v2-command-screen\src\api\websocket\gkDataListener.ts
*/
import { message } from 'ant-design-vue'
import ReconnectingWebSocket from 'reconnecting-websocket'
import EventBus from '../im/eventBus.js'
// 路线航点定义
export interface RoutePoint {
longitude: number
latitude: number
altitude: number
}
// 位置点定义
export interface PositionPoint {
longitude: number
latitude: number
altitude: number
}
// Host数据结构任务详情
export interface HostData {
/** 任务名称 */
task_name: string
/** 自动等级 */
auto_level: number
/** 结束时间 */
end_time: number
/** 接收时间 */
receive_time: number
/** 任务ID */
task_id: string
/** 目标ID */
target_id: string
/** 强制数量 */
force_num: number
/** 优先级 */
priority: number
/** 会话ID */
sid: string
/** 开始时间 */
start_time: number
/** 路线航点列表 */
route: RoutePoint[]
/** 作战单位代码 */
battle_unit_code: string
/** 任务类型 */
task_type: number
/** 位置点列表 */
position_list: PositionPoint[]
/** 时间戳 */
timestamp: number
}
// GK数据主体结构支持national_science_data格式
export interface GKData {
/** 版本号 */
version: string
/** 时间戳 */
timestamp: number
/** 数据内容 */
data: {
/** 主机数据 */
host: HostData
/** 序列号 */
sn: string
}
/** 业务代码 */
biz_code: 'gk_data' | 'national_science_data'
}
// KMZ文件数据接口
export interface KMZFileData {
/** 版本号 */
version: string
/** 时间戳 */
timestamp: number
/** 数据内容 */
data: {
/** 主机数据 */
host: {
message_type: string
task_id: string
task_name: string
battle_unit_code: string
data_type: string
kmz_file: {
file_name: string
file_size: number
file_path: string
download_url: string
file_type: string
}
waypoints: any[]
task_config: any
create_time: string
update_time: string
file_data: string // Base64编码的文件内容
file_size: number
}
/** 序列号 */
sn: string
}
/** 业务代码 */
biz_code: 'kmz_file_data'
}
// 解析后的任务信息用于UI显示
export interface ParsedTaskInfo {
taskId: string
taskName: string
taskType: string
taskTypeName: string
priority: number
priorityName: string
battleUnitCode: string
sn: string
startTime: Date
endTime: Date
receiveTime: Date
routePoints: RoutePoint[]
positionPoints: PositionPoint[]
routeDistance?: number
estimatedDuration?: number
}
export interface GKDataHandler {
(data: GKData): void
}
/**
* GK数据WebSocket监听器类
* 专门处理来自GK_SERVER的数据推送业务代码为 gk_data
*/
export class GKDataListener {
private _url: string
private _socket: ReconnectingWebSocket | null
private _hasInit: boolean
private _dataHandlers: Set<GKDataHandler>
private _isConnected: boolean
constructor(url: string) {
this._url = url
this._socket = null
this._hasInit = false
this._dataHandlers = new Set()
this._isConnected = false
}
/**
* 初始化WebSocket连接
*/
initSocket(): void {
if (this._hasInit) {
console.warn('GKDataListener already initialized')
return
}
if (!this._url) {
console.error('GKDataListener: WebSocket URL is required')
return
}
console.log(`GKDataListener: Connecting to ${this._url}`)
// 创建自动重连的WebSocket连接
this._socket = new ReconnectingWebSocket(this._url, [], {
maxReconnectionDelay: 20000, // 最大重连延迟20秒
minReconnectionDelay: 5000, // 最小重连延迟5秒
maxRetries: 10, // 最大重试次数
reconnectionDelayGrowFactor: 1.3
})
this._hasInit = true
// 绑定事件处理器
this._socket.addEventListener('open', this._onOpen.bind(this))
this._socket.addEventListener('close', this._onClose.bind(this))
this._socket.addEventListener('error', this._onError.bind(this))
this._socket.addEventListener('message', this._onMessage.bind(this))
}
/**
* 连接成功处理
*/
private _onOpen(): void {
this._isConnected = true
console.log('GKDataListener: WebSocket连接成功')
// 发送事件通知
EventBus.emit('GKDataListener-Connected', {
url: this._url,
timestamp: new Date().toISOString()
})
// 可选:发送初始化消息或请求数据
this._sendInitialRequest()
}
/**
* 连接关闭处理
*/
private _onClose(): void {
this._isConnected = false
console.log('GKDataListener: WebSocket连接已断开')
// 发送事件通知
EventBus.emit('GKDataListener-Disconnected', {
url: this._url,
timestamp: new Date().toISOString()
})
}
/**
* 连接错误处理
*/
private _onError(err: any): void {
console.error('GKDataListener: WebSocket连接错误', err)
// 发送事件通知
EventBus.emit('GKDataListener-Error', {
error: err,
url: this._url,
timestamp: new Date().toISOString()
})
}
/**
* 消息接收处理
*/
private _onMessage(msg: MessageEvent): void {
try {
const data = JSON.parse(msg.data)
console.log('GKDataListener: 接收到消息', data)
// 检查是否是gk_data或national_science_data业务代码的消息
if (data.biz_code === 'gk_data' || data.biz_code === 'national_science_data') {
this._handleGKData(data as GKData)
} else if (data.biz_code === 'kmz_file_data') {
this._handleKMZFileData(data)
} else {
console.log('GKDataListener: 非支持的业务代码消息,忽略处理', data)
}
} catch (error) {
console.error('GKDataListener: 解析消息失败', error, msg.data)
}
}
/**
* 处理GK数据
*/
private _handleGKData(gkData: GKData): void {
console.log('GKDataListener: 处理GK数据', gkData)
// 数据验证
if (!this._validateGKData(gkData)) {
console.warn('GKDataListener: GK数据格式无效', gkData)
return
}
// 解析和处理不同类型的数据
this._processGKData(gkData)
// 通知所有注册的处理器
this._dataHandlers.forEach(handler => {
try {
handler(gkData)
} catch (error) {
console.error('GKDataListener: 数据处理器执行错误', error)
}
})
// 发送到事件总线
EventBus.emit('GKData-Received', gkData)
}
/**
* 验证GK数据格式
*/
private _validateGKData(data: any): data is GKData {
return (
data &&
(data.biz_code === 'gk_data' || data.biz_code === 'national_science_data') &&
data.version &&
data.timestamp &&
data.data &&
data.data.host &&
data.data.sn &&
typeof data.data === 'object'
)
}
/**
* 处理和分类GK数据
*/
private _processGKData(gkData: GKData): void {
const { data } = gkData
// 解析任务信息
const parsedTaskInfo = this._parseTaskInfo(gkData)
// 处理任务信息
if (data.host) {
console.log('GKDataListener: 收到任务信息', data.host)
EventBus.emit('GKData-TaskInfo', {
...gkData,
task_info: parsedTaskInfo
})
}
// 处理路线数据
if (data.host.route && data.host.route.length > 0) {
console.log('GKDataListener: 收到路线数据', data.host.route)
EventBus.emit('GKData-RouteData', {
...gkData,
route_data: {
route_points: data.host.route,
route_distance: this._calculateRouteDistance(data.host.route),
task_name: data.host.task_name
}
})
}
// 处理位置数据
if (data.host.position_list && data.host.position_list.length > 0) {
console.log('GKDataListener: 收到位置数据', data.host.position_list)
EventBus.emit('GKData-PositionData', {
...gkData,
position_data: {
positions: data.host.position_list,
device_id: data.sn,
task_name: data.host.task_name
}
})
}
// 发送解析后的任务信息
EventBus.emit('GKData-ParsedTask', parsedTaskInfo)
}
/**
* 处理KMZ文件数据
*/
private _handleKMZFileData(kmzData: KMZFileData): void {
console.log('GKDataListener: 处理KMZ文件数据', kmzData)
// 数据验证
if (!this._validateKMZFileData(kmzData)) {
console.warn('GKDataListener: KMZ文件数据格式无效', kmzData)
return
}
// 发送到事件总线
EventBus.emit('GKData-KMZFileReceived', kmzData)
console.log('GKDataListener: KMZ文件数据处理完成', {
fileName: kmzData.data.host.kmz_file.file_name,
fileSize: kmzData.data.host.kmz_file.file_size,
taskName: kmzData.data.host.task_name
})
}
/**
* 验证KMZ文件数据格式
*/
private _validateKMZFileData(data: any): data is KMZFileData {
return (
data &&
data.biz_code === 'kmz_file_data' &&
data.version &&
data.timestamp &&
data.data &&
data.data.host &&
data.data.host.kmz_file &&
data.data.host.file_data &&
data.data.sn &&
typeof data.data === 'object'
)
}
/**
* 解析任务信息
*/
private _parseTaskInfo(gkData: GKData): ParsedTaskInfo {
const host = gkData.data.host
return {
taskId: host.task_id,
taskName: host.task_name,
taskType: host.task_type.toString(),
taskTypeName: this._getTaskTypeName(host.task_type),
priority: host.priority,
priorityName: this._getPriorityName(host.priority),
battleUnitCode: host.battle_unit_code,
sn: gkData.data.sn,
startTime: new Date(host.start_time),
endTime: new Date(host.end_time),
receiveTime: new Date(host.receive_time),
routePoints: host.route || [],
positionPoints: host.position_list || [],
routeDistance: this._calculateRouteDistance(host.route || []),
estimatedDuration: host.end_time - host.start_time
}
}
/**
* 获取任务类型名称
*/
private _getTaskTypeName(taskType: number): string {
const typeMap: Record<number, string> = {
1: '侦查任务',
2: '巡逻任务',
3: '搜索任务',
4: '救援任务',
5: '运输任务'
}
return typeMap[taskType] || `未知任务类型(${taskType})`
}
/**
* 获取优先级名称
*/
private _getPriorityName(priority: number): string {
const priorityMap: Record<number, string> = {
1: '低优先级',
2: '普通优先级',
3: '高优先级',
4: '紧急',
5: '危急'
}
return priorityMap[priority] || `未知优先级(${priority})`
}
/**
* 计算路线距离(简单的直线距离计算)
*/
private _calculateRouteDistance(route: RoutePoint[]): number {
if (route.length < 2) return 0
let totalDistance = 0
for (let i = 1; i < route.length; i++) {
const prev = route[i - 1]
const curr = route[i]
// 使用简单的欧几里得距离计算(实际项目中可使用更精确的地理距离计算)
const dx = curr.longitude - prev.longitude
const dy = curr.latitude - prev.latitude
const dz = (curr.altitude - prev.altitude) / 1000 // 转换为公里
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz) * 111320 // 粗略转换为米
totalDistance += distance
}
return Math.round(totalDistance)
}
/**
* 发送初始化请求(可选)
*/
private _sendInitialRequest(): void {
// 可以向GK_SERVER发送初始化请求请求当前状态或数据
const initMessage = {
action: 'subscribe',
biz_code: 'gk_data',
timestamp: new Date().toISOString()
}
this.sendMessage(initMessage)
}
/**
* 注册GK数据处理器
*/
registerDataHandler(handler: GKDataHandler): void {
this._dataHandlers.add(handler)
}
/**
* 移除GK数据处理器
*/
unregisterDataHandler(handler: GKDataHandler): void {
this._dataHandlers.delete(handler)
}
/**
* 发送消息到GK_SERVER
*/
sendMessage(message: any): void {
if (!this._isConnected || !this._socket) {
console.warn('GKDataListener: WebSocket未连接无法发送消息')
return
}
try {
const messageStr = JSON.stringify(message)
this._socket.send(messageStr)
console.log('GKDataListener: 发送消息', message)
} catch (error) {
console.error('GKDataListener: 发送消息失败', error)
}
}
/**
* 请求特定类型的GK数据
*/
requestGKData(dataType: 'task_info' | 'route_data' | 'position_data' | 'all'): void {
const requestMessage = {
action: 'request_data',
biz_code: 'gk_data',
data_type: dataType,
timestamp: new Date().toISOString()
}
this.sendMessage(requestMessage)
}
/**
* 获取连接状态
*/
isConnected(): boolean {
return this._isConnected
}
/**
* 获取WebSocket就绪状态
*/
getReadyState(): number {
return this._socket?.readyState ?? WebSocket.CLOSED
}
/**
* 手动重连
*/
reconnect(): void {
if (this._socket) {
this._socket.reconnect()
}
}
/**
* 关闭连接
*/
close(): void {
if (this._socket) {
this._socket.close()
this._socket = null
}
this._hasInit = false
this._isConnected = false
this._dataHandlers.clear()
console.log('GKDataListener: 连接已关闭')
}
}
/**
* 创建GK数据监听器实例的工厂函数
*/
export function createGKDataListener(url: string): GKDataListener {
return new GKDataListener(url)
}
/**
* 默认的GK数据处理器示例
*/
export const defaultGKDataHandler: GKDataHandler = (gkData: GKData) => {
console.log('Default GK Data Handler:', gkData)
// 这里可以添加默认的数据处理逻辑
// 比如存储到本地、更新UI等
}
export default GKDataListener

View File

@@ -0,0 +1,316 @@
/*
* @Date: 2024-12-19
* @Description: GK数据管理器提供便捷的GK数据监听和管理功能
* @Author: Assistant
* @FilePath: \drone-v2-command-screen\src\api\websocket\gkDataManager.ts
*/
import { GKDataListener, GKData, GKDataHandler } from './gkDataListener'
import EventBus from '../im/eventBus.js'
/**
* GK数据管理器配置
*/
export interface GKDataManagerConfig {
url: string
autoConnect?: boolean
enableLogging?: boolean
dataBufferSize?: number
}
/**
* GK数据管理器
* 提供更高级的GK数据管理功能包括数据缓存、状态管理等
*/
export class GKDataManager {
private listener: GKDataListener
private config: GKDataManagerConfig
private dataBuffer: GKData[] = []
private isInitialized: boolean = false
private lastTaskInfo: any = null
private lastRouteData: any = null
private lastPositionData: any = null
constructor(config: GKDataManagerConfig) {
this.config = {
autoConnect: true,
enableLogging: true,
dataBufferSize: 100,
...config
}
this.listener = new GKDataListener(this.config.url)
this.setupEventHandlers()
}
/**
* 初始化GK数据管理器
*/
init(): void {
if (this.isInitialized) {
console.warn('GKDataManager already initialized')
return
}
// 注册数据处理器
this.listener.registerDataHandler(this.handleGKData.bind(this))
// 初始化WebSocket连接
this.listener.initSocket()
this.isInitialized = true
if (this.config.enableLogging) {
console.log('GKDataManager initialized successfully')
}
}
/**
* 设置事件处理器
*/
private setupEventHandlers(): void {
// 监听连接状态变化
EventBus.on('GKDataListener-Connected', (data) => {
if (this.config.enableLogging) {
console.log('GKDataManager: 连接成功', data)
}
EventBus.emit('GKDataManager-Connected', data)
})
EventBus.on('GKDataListener-Disconnected', (data) => {
if (this.config.enableLogging) {
console.log('GKDataManager: 连接断开', data)
}
EventBus.emit('GKDataManager-Disconnected', data)
})
EventBus.on('GKDataListener-Error', (data) => {
console.error('GKDataManager: 连接错误', data)
EventBus.emit('GKDataManager-Error', data)
})
// 监听特定类型的GK数据
EventBus.on('GKData-TaskInfo', (data: any) => {
this.lastTaskInfo = data.task_info
EventBus.emit('GKDataManager-TaskInfoUpdated', data.task_info)
})
EventBus.on('GKData-RouteData', (data: any) => {
this.lastRouteData = data.route_data
EventBus.emit('GKDataManager-RouteDataUpdated', data.route_data)
})
EventBus.on('GKData-PositionData', (data: any) => {
this.lastPositionData = data.position_data
EventBus.emit('GKDataManager-PositionDataUpdated', data.position_data)
})
}
/**
* 处理GK数据
*/
private handleGKData(gkData: GKData): void {
// 将数据添加到缓冲区
this.addToBuffer(gkData)
if (this.config.enableLogging) {
console.log('GKDataManager: 处理GK数据', gkData)
}
// 发送管理器级别的事件
EventBus.emit('GKDataManager-DataReceived', gkData)
}
/**
* 将数据添加到缓冲区
*/
private addToBuffer(gkData: GKData): void {
this.dataBuffer.push(gkData)
// 限制缓冲区大小
if (this.dataBuffer.length > (this.config.dataBufferSize || 100)) {
this.dataBuffer.shift() // 移除最旧的数据
}
}
/**
* 获取缓冲区中的所有数据
*/
getBufferedData(): GKData[] {
return [...this.dataBuffer]
}
/**
* 获取最新的数据
*/
getLatestData(): GKData | null {
return this.dataBuffer.length > 0 ? this.dataBuffer[this.dataBuffer.length - 1] : null
}
/**
* 获取最新的任务信息
*/
getLatestTaskInfo(): any {
return this.lastTaskInfo
}
/**
* 获取最新的路线数据
*/
getLatestRouteData(): any {
return this.lastRouteData
}
/**
* 获取最新的位置数据
*/
getLatestPositionData(): any {
return this.lastPositionData
}
/**
* 根据时间范围获取数据
*/
getDataByTimeRange(startTime: string, endTime: string): GKData[] {
const start = new Date(startTime).getTime()
const end = new Date(endTime).getTime()
return this.dataBuffer.filter(data => {
const dataTime = new Date(data.timestamp).getTime()
return dataTime >= start && dataTime <= end
})
}
/**
* 获取特定类型的最新数据
*/
getLatestDataByType(type: 'task_info' | 'route_data' | 'position_data'): GKData | null {
for (let i = this.dataBuffer.length - 1; i >= 0; i--) {
const data = this.dataBuffer[i]
if (data.data[type]) {
return data
}
}
return null
}
/**
* 清空缓冲区
*/
clearBuffer(): void {
this.dataBuffer = []
if (this.config.enableLogging) {
console.log('GKDataManager: 缓冲区已清空')
}
}
/**
* 注册自定义数据处理器
*/
registerDataHandler(handler: GKDataHandler): void {
this.listener.registerDataHandler(handler)
}
/**
* 移除自定义数据处理器
*/
unregisterDataHandler(handler: GKDataHandler): void {
this.listener.unregisterDataHandler(handler)
}
/**
* 发送消息到GK_SERVER
*/
sendMessage(message: any): void {
this.listener.sendMessage(message)
}
/**
* 请求特定类型的GK数据
*/
requestData(dataType: 'task_info' | 'route_data' | 'position_data' | 'all'): void {
this.listener.requestGKData(dataType)
}
/**
* 获取连接状态
*/
isConnected(): boolean {
return this.listener.isConnected()
}
/**
* 获取WebSocket就绪状态
*/
getConnectionState(): number {
return this.listener.getReadyState()
}
/**
* 手动重连
*/
reconnect(): void {
this.listener.reconnect()
}
/**
* 获取统计信息
*/
getStatistics(): {
totalReceived: number
bufferSize: number
lastReceiveTime: string | null
isConnected: boolean
hasTaskInfo: boolean
hasRouteData: boolean
hasPositionData: boolean
} {
const latestData = this.getLatestData()
return {
totalReceived: this.dataBuffer.length,
bufferSize: this.config.dataBufferSize || 100,
lastReceiveTime: latestData?.timestamp || null,
isConnected: this.isConnected(),
hasTaskInfo: !!this.lastTaskInfo,
hasRouteData: !!this.lastRouteData,
hasPositionData: !!this.lastPositionData
}
}
/**
* 关闭管理器
*/
close(): void {
// 移除事件监听器
EventBus.off('GKDataListener-Connected')
EventBus.off('GKDataListener-Disconnected')
EventBus.off('GKDataListener-Error')
EventBus.off('GKData-TaskInfo')
EventBus.off('GKData-RouteData')
EventBus.off('GKData-PositionData')
// 关闭监听器
this.listener.close()
// 清理数据
this.clearBuffer()
this.lastTaskInfo = null
this.lastRouteData = null
this.lastPositionData = null
this.isInitialized = false
if (this.config.enableLogging) {
console.log('GKDataManager: 已关闭')
}
}
}
/**
* 创建GK数据管理器的工厂函数
*/
export function createGKDataManager(config: GKDataManagerConfig): GKDataManager {
return new GKDataManager(config)
}
export default GKDataManager

View File

@@ -0,0 +1,649 @@
<!--
@Date: 2024-12-19
@Description: GK数据地图显示组件展示任务路线和位置点
@Author: Assistant
@FilePath: \drone-v2-command-screen\src\api\websocket\gkDataMapDisplay.vue
-->
<template>
<div class="gk-data-map-display">
<div class="map-header">
<h2>GK数据地图显示</h2>
<div class="map-controls">
<button @click="connectToGKServer" :disabled="isConnected" class="btn btn-primary">
{{ isConnected ? '已连接' : '连接GK服务器' }}
</button>
<button @click="clearMap" class="btn btn-secondary">
清空地图
</button>
<button @click="fitToData" :disabled="!hasData" class="btn btn-info">
适应数据范围
</button>
</div>
</div>
<!-- 任务信息面板 -->
<div class="task-info-panel" v-if="currentTask">
<div class="task-header">
<h3>{{ currentTask.taskName }}</h3>
<span class="task-type">{{ currentTask.taskTypeName }}</span>
<span class="task-priority" :class="getPriorityClass(currentTask.priority)">
{{ currentTask.priorityName }}
</span>
</div>
<div class="task-details">
<div class="detail-item">
<span class="label">任务ID:</span>
<span class="value">{{ currentTask.taskId }}</span>
</div>
<div class="detail-item">
<span class="label">设备序列号:</span>
<span class="value">{{ currentTask.sn }}</span>
</div>
<div class="detail-item">
<span class="label">作战单位:</span>
<span class="value">{{ currentTask.battleUnitCode }}</span>
</div>
<div class="detail-item">
<span class="label">路线距离:</span>
<span class="value">{{ currentTask.routeDistance }}m</span>
</div>
<div class="detail-item">
<span class="label">航点数量:</span>
<span class="value">{{ currentTask.routePoints.length }}</span>
</div>
<div class="detail-item">
<span class="label">位置点数量:</span>
<span class="value">{{ currentTask.positionPoints.length }}</span>
</div>
<div class="detail-item">
<span class="label">开始时间:</span>
<span class="value">{{ formatTime(currentTask.startTime) }}</span>
</div>
<div class="detail-item">
<span class="label">结束时间:</span>
<span class="value">{{ formatTime(currentTask.endTime) }}</span>
</div>
</div>
</div>
<!-- 地图容器 -->
<div class="map-container">
<div ref="mapContainer" class="mars3d-map"></div>
</div>
<!-- 数据统计面板 -->
<div class="data-stats" v-if="dataStats">
<h3>数据统计</h3>
<div class="stats-grid">
<div class="stat-item">
<span class="stat-label">接收数据总数:</span>
<span class="stat-value">{{ dataStats.totalReceived }}</span>
</div>
<div class="stat-item">
<span class="stat-label">最后更新时间:</span>
<span class="stat-value">{{ dataStats.lastUpdateTime || '无' }}</span>
</div>
<div class="stat-item">
<span class="stat-label">连接状态:</span>
<span class="stat-value" :class="isConnected ? 'connected' : 'disconnected'">
{{ isConnected ? '已连接' : '未连接' }}
</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'
import { GKDataListener, GKData, ParsedTaskInfo, RoutePoint, PositionPoint } from './gkDataListener'
import { getGKWebSocketUrl } from './gkDataConfig'
import EventBus from '../im/eventBus.js'
// 响应式数据
const isConnected = ref(false)
const currentTask = ref<ParsedTaskInfo | null>(null)
const mapContainer = ref<HTMLElement>()
const dataStats = ref({
totalReceived: 0,
lastUpdateTime: ''
})
// GK数据监听器和地图实例
let gkDataListener: GKDataListener | null = null
let map: any = null
let routeLayer: any = null
let positionLayer: any = null
// 计算属性
const hasData = computed(() => {
return currentTask.value && (
currentTask.value.routePoints.length > 0 ||
currentTask.value.positionPoints.length > 0
)
})
// 初始化地图
const initMap = () => {
if (!mapContainer.value) return
try {
// 初始化mars3d地图
map = new mars3d.Map(mapContainer.value, {
scene: {
center: { lat: 32.51, lng: 118.10, alt: 1000, heading: 0, pitch: -45 },
fxaa: true
},
control: {
baseLayerPicker: true,
sceneModePicker: true
},
terrain: {
url: "//data.mars3d.cn/terrain"
}
})
// 创建路线图层
routeLayer = new mars3d.layer.GraphicLayer({
name: "GK任务路线",
pid: 99
})
map.addLayer(routeLayer)
// 创建位置点图层
positionLayer = new mars3d.layer.GraphicLayer({
name: "GK位置点",
pid: 98
})
map.addLayer(positionLayer)
console.log('地图初始化成功')
} catch (error) {
console.error('地图初始化失败:', error)
}
}
// 连接到GK服务器
const connectToGKServer = () => {
if (gkDataListener) {
gkDataListener.close()
}
gkDataListener = new GKDataListener(getGKWebSocketUrl())
// 注册数据处理器
gkDataListener.registerDataHandler(handleGKData)
// 初始化连接
gkDataListener.initSocket()
console.log('正在连接到GK服务器:', getGKWebSocketUrl())
}
// 处理GK数据
const handleGKData = (gkData: GKData) => {
console.log('收到GK数据:', gkData)
dataStats.value.totalReceived++
dataStats.value.lastUpdateTime = new Date().toLocaleString()
}
// 设置事件监听器
const setupEventListeners = () => {
// 连接状态事件
EventBus.on('GKDataListener-Connected', () => {
isConnected.value = true
console.log('GK数据服务器连接成功')
})
EventBus.on('GKDataListener-Disconnected', () => {
isConnected.value = false
console.log('GK数据服务器连接断开')
})
EventBus.on('GKDataListener-Error', (error: any) => {
console.error('GK数据服务器连接错误:', error)
})
// 解析后的任务数据
EventBus.on('GKData-ParsedTask', (taskInfo: ParsedTaskInfo) => {
currentTask.value = taskInfo
console.log('收到解析后的任务信息:', taskInfo)
// 更新地图显示
updateMapDisplay(taskInfo)
})
// 路线数据事件
EventBus.on('GKData-RouteData', (data: any) => {
console.log('收到路线数据:', data)
if (data.route_data && data.route_data.route_points) {
displayRoute(data.route_data.route_points, data.route_data.task_name)
}
})
// 位置数据事件
EventBus.on('GKData-PositionData', (data: any) => {
console.log('收到位置数据:', data)
if (data.position_data && data.position_data.positions) {
displayPositions(data.position_data.positions, data.position_data.device_id)
}
})
}
// 更新地图显示
const updateMapDisplay = (taskInfo: ParsedTaskInfo) => {
if (!map) return
// 清空现有图层
clearMap()
// 显示路线
if (taskInfo.routePoints.length > 0) {
displayRoute(taskInfo.routePoints, taskInfo.taskName)
}
// 显示位置点
if (taskInfo.positionPoints.length > 0) {
displayPositions(taskInfo.positionPoints, taskInfo.sn)
}
// 适应数据范围
setTimeout(() => {
fitToData()
}, 500)
}
// 显示路线
const displayRoute = (routePoints: RoutePoint[], taskName: string) => {
if (!map || !routeLayer || routePoints.length < 2) return
try {
// 转换坐标格式
const positions = routePoints.map(point => [
point.longitude,
point.latitude,
point.altitude
])
// 创建路线
const polyline = new mars3d.graphic.PolylineEntity({
positions: positions,
style: {
color: '#ff4d4f',
width: 3,
clampToGround: false,
outline: true,
outlineColor: '#ffffff',
outlineWidth: 1
},
popup: `
<div>
<h4>${taskName}</h4>
<p>路线航点数: ${routePoints.length}</p>
<p>总距离: ${currentTask.value?.routeDistance || 0}m</p>
</div>
`
})
routeLayer.addGraphic(polyline)
// 添加航点标记
routePoints.forEach((point, index) => {
const marker = new mars3d.graphic.BillboardEntity({
position: [point.longitude, point.latitude, point.altitude],
style: {
image: '/img/icon/route-point.png',
scale: 0.5,
horizontalOrigin: mars3d.HorizontalOrigin.CENTER,
verticalOrigin: mars3d.VerticalOrigin.BOTTOM,
label: {
text: (index + 1).toString(),
font: '14px Arial',
color: '#ffffff',
outlineColor: '#000000',
outlineWidth: 2,
horizontalOrigin: mars3d.HorizontalOrigin.CENTER,
verticalOrigin: mars3d.VerticalOrigin.MIDDLE,
pixelOffset: [0, -20]
}
},
popup: `
<div>
<h4>航点 ${index + 1}</h4>
<p>经度: ${point.longitude.toFixed(6)}</p>
<p>纬度: ${point.latitude.toFixed(6)}</p>
<p>高度: ${point.altitude.toFixed(2)}m</p>
</div>
`
})
routeLayer.addGraphic(marker)
})
console.log(`已显示路线,包含 ${routePoints.length} 个航点`)
} catch (error) {
console.error('显示路线失败:', error)
}
}
// 显示位置点
const displayPositions = (positionPoints: PositionPoint[], deviceId: string) => {
if (!map || !positionLayer || positionPoints.length === 0) return
try {
positionPoints.forEach((point, index) => {
const marker = new mars3d.graphic.BillboardEntity({
position: [point.longitude, point.latitude, point.altitude],
style: {
image: '/img/icon/position-marker.png',
scale: 0.8,
horizontalOrigin: mars3d.HorizontalOrigin.CENTER,
verticalOrigin: mars3d.VerticalOrigin.BOTTOM
},
popup: `
<div>
<h4>位置点 ${index + 1}</h4>
<p>设备ID: ${deviceId}</p>
<p>经度: ${point.longitude.toFixed(6)}</p>
<p>纬度: ${point.latitude.toFixed(6)}</p>
<p>高度: ${point.altitude.toFixed(2)}m</p>
</div>
`
})
positionLayer.addGraphic(marker)
})
console.log(`已显示 ${positionPoints.length} 个位置点`)
} catch (error) {
console.error('显示位置点失败:', error)
}
}
// 清空地图
const clearMap = () => {
if (routeLayer) {
routeLayer.clear()
}
if (positionLayer) {
positionLayer.clear()
}
}
// 适应数据范围
const fitToData = () => {
if (!map || !hasData.value) return
try {
const allPoints: any[] = []
// 添加路线点
if (currentTask.value?.routePoints) {
currentTask.value.routePoints.forEach(point => {
allPoints.push([point.longitude, point.latitude, point.altitude])
})
}
// 添加位置点
if (currentTask.value?.positionPoints) {
currentTask.value.positionPoints.forEach(point => {
allPoints.push([point.longitude, point.latitude, point.altitude])
})
}
if (allPoints.length > 0) {
map.flyToPositions(allPoints, {
radius: 1000, // 缓冲距离
duration: 2 // 飞行时间
})
}
} catch (error) {
console.error('适应数据范围失败:', error)
}
}
// 辅助函数
const formatTime = (date: Date) => {
return date.toLocaleString()
}
const getPriorityClass = (priority: number) => {
const classMap: Record<number, string> = {
1: 'priority-low',
2: 'priority-normal',
3: 'priority-high',
4: 'priority-urgent',
5: 'priority-critical'
}
return classMap[priority] || 'priority-normal'
}
// 清理事件监听器
const cleanupEventListeners = () => {
EventBus.off('GKDataListener-Connected')
EventBus.off('GKDataListener-Disconnected')
EventBus.off('GKDataListener-Error')
EventBus.off('GKData-ParsedTask')
EventBus.off('GKData-RouteData')
EventBus.off('GKData-PositionData')
}
// 组件生命周期
onMounted(() => {
setupEventListeners()
initMap()
})
onUnmounted(() => {
if (gkDataListener) {
gkDataListener.close()
}
cleanupEventListeners()
if (map) {
map.destroy()
}
})
</script>
<style scoped>
.gk-data-map-display {
height: 100vh;
display: flex;
flex-direction: column;
}
.map-header {
padding: 15px 20px;
background: #f8f9fa;
border-bottom: 1px solid #e9ecef;
display: flex;
justify-content: space-between;
align-items: center;
}
.map-header h2 {
margin: 0;
color: #333;
}
.map-controls {
display: flex;
gap: 10px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background: #1890ff;
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #40a9ff;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover:not(:disabled) {
background: #5a6268;
}
.btn-info {
background: #17a2b8;
color: white;
}
.btn-info:hover:not(:disabled) {
background: #138496;
}
.task-info-panel {
padding: 15px 20px;
background: #fff;
border-bottom: 1px solid #e9ecef;
}
.task-header {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 15px;
}
.task-header h3 {
margin: 0;
color: #1890ff;
}
.task-type {
background: #e6f7ff;
color: #1890ff;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
.task-priority {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.priority-low {
background: #f6ffed;
color: #52c41a;
}
.priority-normal {
background: #e6f7ff;
color: #1890ff;
}
.priority-high {
background: #fff2e8;
color: #fa8c16;
}
.priority-urgent {
background: #fff1f0;
color: #ff4d4f;
}
.priority-critical {
background: #ff4d4f;
color: white;
}
.task-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
}
.detail-item {
display: flex;
justify-content: space-between;
}
.label {
color: #666;
font-size: 14px;
}
.value {
color: #333;
font-weight: 500;
font-size: 14px;
}
.map-container {
flex: 1;
position: relative;
}
.mars3d-map {
width: 100%;
height: 100%;
}
.data-stats {
padding: 15px 20px;
background: #f8f9fa;
border-top: 1px solid #e9ecef;
}
.data-stats h3 {
margin: 0 0 10px 0;
color: #333;
font-size: 16px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.stat-item {
display: flex;
justify-content: space-between;
align-items: center;
}
.stat-label {
color: #666;
font-size: 14px;
}
.stat-value {
color: #333;
font-weight: 500;
font-size: 14px;
}
.stat-value.connected {
color: #52c41a;
}
.stat-value.disconnected {
color: #ff4d4f;
}
</style>

View File

@@ -0,0 +1,223 @@
/*
* @Date: 2024-12-19
* @Description: GK数据模拟器用于测试和演示
* @Author: Assistant
* @FilePath: \drone-v2-command-screen\src\api\websocket\gkDataSimulator.ts
*/
import { GKData, RoutePoint, PositionPoint } from './gkDataListener'
/**
* GK数据模拟器类
* 用于生成模拟的GK数据进行测试
*/
export class GKDataSimulator {
private isRunning: boolean = false
private intervalId: number | null = null
private messageCallback: ((data: GKData) => void) | null = null
/**
* 开始模拟数据推送
* @param callback 数据回调函数
* @param interval 推送间隔(毫秒)
*/
start(callback: (data: GKData) => void, interval: number = 5000): void {
if (this.isRunning) {
console.warn('GK数据模拟器已在运行中')
return
}
this.messageCallback = callback
this.isRunning = true
// 立即发送一次数据
this.sendMockData()
// 设置定时推送
this.intervalId = window.setInterval(() => {
this.sendMockData()
}, interval)
console.log(`GK数据模拟器已启动推送间隔: ${interval}ms`)
}
/**
* 停止模拟数据推送
*/
stop(): void {
if (!this.isRunning) {
return
}
if (this.intervalId) {
clearInterval(this.intervalId)
this.intervalId = null
}
this.isRunning = false
this.messageCallback = null
console.log('GK数据模拟器已停止')
}
/**
* 发送模拟数据
*/
private sendMockData(): void {
if (!this.messageCallback) return
const mockData = this.generateMockGKData()
this.messageCallback(mockData)
}
/**
* 生成模拟的GK数据
*/
private generateMockGKData(): GKData {
const now = Date.now()
const taskId = Math.floor(Math.random() * 1000).toString()
// 生成随机路线(基于你提供的实际坐标附近)
const baseCoords = {
longitude: 118.10012079,
latitude: 32.508601899999995,
altitude: 115
}
const routePoints: RoutePoint[] = []
for (let i = 0; i < 5; i++) {
routePoints.push({
longitude: baseCoords.longitude + (Math.random() - 0.5) * 0.002,
latitude: baseCoords.latitude + (Math.random() - 0.5) * 0.002,
altitude: baseCoords.altitude + Math.random() * 50
})
}
// 生成随机位置点
const positionPoints: PositionPoint[] = [{
longitude: baseCoords.longitude + (Math.random() - 0.5) * 0.001,
latitude: baseCoords.latitude + (Math.random() - 0.5) * 0.001,
altitude: Math.random() * 10
}]
const mockData: GKData = {
version: "1.0",
timestamp: now,
data: {
host: {
task_name: `模拟任务_${taskId}`,
auto_level: Math.floor(Math.random() * 3) + 1,
end_time: now + 3600000, // 1小时后结束
receive_time: now,
task_id: taskId,
target_id: "",
force_num: 0,
priority: Math.floor(Math.random() * 5) + 1,
sid: Math.floor(Math.random() * 100).toString(),
start_time: now - 300000, // 5分钟前开始
route: routePoints,
battle_unit_code: "MOCK" + Math.random().toString(36).substr(2, 16).toUpperCase(),
task_type: Math.floor(Math.random() * 5) + 1,
position_list: positionPoints,
timestamp: now - 300000
},
sn: "MOCK" + Math.random().toString(36).substr(2, 16).toUpperCase()
},
biz_code: "gk_data"
}
return mockData
}
/**
* 生成基于用户提供数据的模拟数据
*/
generateUserProvidedMockData(): GKData {
return {
"version": "1.0",
"timestamp": Date.now(),
"data": {
"host": {
"task_name": "位置点侦查",
"auto_level": 2,
"end_time": Date.now() + 3600000,
"receive_time": Date.now(),
"task_id": "\u0001",
"target_id": "",
"force_num": 0,
"priority": 3,
"sid": "5",
"start_time": Date.now() - 300000,
"route": [
{
"longitude": 118.10012079,
"latitude": 32.508601899999995,
"altitude": 115.69229944870787
},
{
"longitude": 118.10012079,
"latitude": 32.508601899999995,
"altitude": 115.69229944888502
},
{
"longitude": 118.10009403585885,
"latitude": 32.51028167710136,
"altitude": 151.89730232022703
},
{
"longitude": 118.10027905266666,
"latitude": 32.510482726486885,
"altitude": 144.75666878931224
},
{
"longitude": 118.10029126536723,
"latitude": 32.511286912471355,
"altitude": 71.94120359607041
}
],
"battle_unit_code": "1581F5FHC248A00DXGLU",
"task_type": 1,
"position_list": [
{
"longitude": 118.10029444232556,
"latitude": 32.51149610752683,
"altitude": 0
}
],
"timestamp": Date.now() - 300000
},
"sn": "1581F5FHC248A00DXGLU"
},
"biz_code": "gk_data"
}
}
/**
* 手动发送用户提供的数据
*/
sendUserProvidedData(): void {
if (!this.messageCallback) {
console.warn('模拟器未启动,无法发送数据')
return
}
const userData = this.generateUserProvidedMockData()
this.messageCallback(userData)
console.log('已发送用户提供的模拟数据')
}
/**
* 获取运行状态
*/
isSimulatorRunning(): boolean {
return this.isRunning
}
}
/**
* 创建GK数据模拟器实例
*/
export function createGKDataSimulator(): GKDataSimulator {
return new GKDataSimulator()
}
export default GKDataSimulator

View File

@@ -0,0 +1,628 @@
/*
* @Date: 2024-12-19
* @Description: GK数据Local Storage存储管理器
* @Author: Assistant
* @FilePath: \drone-v2-command-screen\src\api\websocket\gkDataStorage.ts
*/
import { ParsedTaskInfo } from './gkDataListener'
import { ELocalStorageKey } from '@mars/types/enums'
/**
* GK数据存储配置
*/
export interface GKStorageConfig {
/** 最大存储的任务数量 */
maxTasks: number
/** 最大存储的历史记录数量 */
maxHistory: number
/** 自动清理过期数据的天数 */
expireDays: number
/** 是否启用数据压缩 */
enableCompression: boolean
}
/**
* 用户设置数据结构
*/
export interface GKUserSettings {
/** 面板位置 */
panelPosition: { x: number; y: number }
/** 自动连接设置 */
autoConnect: boolean
/** 自动保存新任务 */
autoSaveNewTasks?: boolean
/** 显示设置 */
displaySettings: {
showTaskList: boolean
showRouteDetails: boolean
showPositionDetails: boolean
maxVisibleTasks: number
}
/** 连接配置 */
connectionConfig: {
url: string
useDirectConnect: boolean
autoReconnect: boolean
}
/** 主题设置 */
theme: {
darkMode: boolean
panelOpacity: number
fontSize: 'small' | 'medium' | 'large'
}
}
/**
* 历史记录数据结构
*/
export interface GKHistoryRecord {
id: string
timestamp: string
action: 'task_received' | 'task_completed' | 'connection_change' | 'user_action'
data: any
description: string
}
/**
* 存储的任务数据结构
*/
export interface StoredTaskData extends ParsedTaskInfo {
/** 存储时间 */
storedAt: string
/** 是否标记为重要 */
isImportant: boolean
/** 用户备注 */
userNotes?: string
/** 标签 */
tags?: string[]
}
/**
* GK数据Local Storage管理器
*/
export class GKDataStorage {
private config: GKStorageConfig
private readonly STORAGE_KEYS = {
TASKS: 'gk_tasks_data',
SETTINGS: 'gk_user_settings',
HISTORY: 'gk_history_records',
STATISTICS: 'gk_statistics',
CACHE: 'gk_data_cache'
}
constructor(config?: Partial<GKStorageConfig>) {
this.config = {
maxTasks: 100,
maxHistory: 500,
expireDays: 30,
enableCompression: false,
...config
}
// 初始化时清理过期数据
this.cleanExpiredData()
}
/**
* 保存任务数据
*/
saveTask(task: ParsedTaskInfo, options?: { isImportant?: boolean; userNotes?: string; tags?: string[] }): void {
try {
const storedTask: StoredTaskData = {
...task,
storedAt: new Date().toISOString(),
isImportant: options?.isImportant || false,
userNotes: options?.userNotes,
tags: options?.tags || []
}
const existingTasks = this.getAllTasks()
// 检查是否已存在相同任务ID
const existingIndex = existingTasks.findIndex(t => t.taskId === task.taskId)
if (existingIndex !== -1) {
// 更新现有任务
existingTasks[existingIndex] = { ...existingTasks[existingIndex], ...storedTask }
this.addHistoryRecord('task_received', storedTask, `更新任务: ${task.taskName}`)
} else {
// 添加新任务
existingTasks.unshift(storedTask)
this.addHistoryRecord('task_received', storedTask, `新任务: ${task.taskName}`)
}
// 限制存储数量
if (existingTasks.length > this.config.maxTasks) {
const removedTasks = existingTasks.splice(this.config.maxTasks)
console.log(`GKDataStorage: 移除了 ${removedTasks.length} 个旧任务`)
}
this.setItem(this.STORAGE_KEYS.TASKS, existingTasks)
console.log('GKDataStorage: 任务保存成功', task.taskId)
} catch (error) {
console.error('GKDataStorage: 保存任务失败', error)
}
}
/**
* 获取所有任务
*/
getAllTasks(): StoredTaskData[] {
try {
return this.getItem(this.STORAGE_KEYS.TASKS, [])
} catch (error) {
console.error('GKDataStorage: 获取任务列表失败', error)
return []
}
}
/**
* 根据ID获取任务
*/
getTaskById(taskId: string): StoredTaskData | null {
try {
const tasks = this.getAllTasks()
return tasks.find(task => task.taskId === taskId) || null
} catch (error) {
console.error('GKDataStorage: 获取任务失败', error)
return null
}
}
/**
* 删除任务
*/
deleteTask(taskId: string): boolean {
try {
const tasks = this.getAllTasks()
const taskIndex = tasks.findIndex(task => task.taskId === taskId)
if (taskIndex !== -1) {
const deletedTask = tasks.splice(taskIndex, 1)[0]
this.setItem(this.STORAGE_KEYS.TASKS, tasks)
this.addHistoryRecord('user_action', { taskId }, `删除任务: ${deletedTask.taskName}`)
return true
}
return false
} catch (error) {
console.error('GKDataStorage: 删除任务失败', error)
return false
}
}
/**
* 标记任务为重要
*/
markTaskAsImportant(taskId: string, isImportant: boolean): boolean {
try {
const tasks = this.getAllTasks()
const task = tasks.find(t => t.taskId === taskId)
if (task) {
task.isImportant = isImportant
this.setItem(this.STORAGE_KEYS.TASKS, tasks)
this.addHistoryRecord('user_action', { taskId, isImportant },
`${isImportant ? '标记' : '取消标记'}重要任务: ${task.taskName}`)
return true
}
return false
} catch (error) {
console.error('GKDataStorage: 标记任务失败', error)
return false
}
}
/**
* 添加任务备注
*/
addTaskNote(taskId: string, note: string): boolean {
try {
const tasks = this.getAllTasks()
const task = tasks.find(t => t.taskId === taskId)
if (task) {
task.userNotes = note
this.setItem(this.STORAGE_KEYS.TASKS, tasks)
this.addHistoryRecord('user_action', { taskId, note }, `添加备注: ${task.taskName}`)
return true
}
return false
} catch (error) {
console.error('GKDataStorage: 添加备注失败', error)
return false
}
}
/**
* 保存用户设置
*/
saveUserSettings(settings: Partial<GKUserSettings>): void {
try {
const currentSettings = this.getUserSettings()
const updatedSettings = { ...currentSettings, ...settings }
this.setItem(this.STORAGE_KEYS.SETTINGS, updatedSettings)
this.addHistoryRecord('user_action', settings, '更新用户设置')
console.log('GKDataStorage: 用户设置保存成功')
} catch (error) {
console.error('GKDataStorage: 保存用户设置失败', error)
}
}
/**
* 获取用户设置
*/
getUserSettings(): GKUserSettings {
try {
const defaultSettings: GKUserSettings = {
panelPosition: { x: 0, y: 0 },
autoConnect: true,
autoSaveNewTasks: true,
displaySettings: {
showTaskList: true,
showRouteDetails: true,
showPositionDetails: true,
maxVisibleTasks: 10
},
connectionConfig: {
url: 'ws://27.11.11.30/dji/api/v1/ws',
useDirectConnect: false,
autoReconnect: true
},
theme: {
darkMode: false,
panelOpacity: 0.95,
fontSize: 'medium'
}
}
const savedSettings = this.getItem(this.STORAGE_KEYS.SETTINGS, {})
return { ...defaultSettings, ...savedSettings }
} catch (error) {
console.error('GKDataStorage: 获取用户设置失败', error)
return this.getUserSettings() // 返回默认设置
}
}
/**
* 添加历史记录
*/
addHistoryRecord(action: GKHistoryRecord['action'], data: any, description: string): void {
try {
const history = this.getHistoryRecords()
const record: GKHistoryRecord = {
id: `history_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
timestamp: new Date().toISOString(),
action,
data,
description
}
history.unshift(record)
// 限制历史记录数量
if (history.length > this.config.maxHistory) {
history.splice(this.config.maxHistory)
}
this.setItem(this.STORAGE_KEYS.HISTORY, history)
} catch (error) {
console.error('GKDataStorage: 添加历史记录失败', error)
}
}
/**
* 获取历史记录
*/
getHistoryRecords(limit?: number): GKHistoryRecord[] {
try {
const history = this.getItem(this.STORAGE_KEYS.HISTORY, [])
return limit ? history.slice(0, limit) : history
} catch (error) {
console.error('GKDataStorage: 获取历史记录失败', error)
return []
}
}
/**
* 获取统计信息
*/
getStatistics(): {
totalTasks: number
importantTasks: number
todayTasks: number
totalHistoryRecords: number
storageUsage: string
} {
try {
const tasks = this.getAllTasks()
const history = this.getHistoryRecords()
const today = new Date().toDateString()
const todayTasks = tasks.filter(task =>
new Date(task.storedAt).toDateString() === today
).length
const importantTasks = tasks.filter(task => task.isImportant).length
// 计算存储使用量
let totalSize = 0
Object.values(this.STORAGE_KEYS).forEach(key => {
const value = localStorage.getItem(key)
if (value) {
totalSize += value.length
}
})
const storageUsage = `${(totalSize / 1024).toFixed(2)} KB`
return {
totalTasks: tasks.length,
importantTasks,
todayTasks,
totalHistoryRecords: history.length,
storageUsage
}
} catch (error) {
console.error('GKDataStorage: 获取统计信息失败', error)
return {
totalTasks: 0,
importantTasks: 0,
todayTasks: 0,
totalHistoryRecords: 0,
storageUsage: '0 KB'
}
}
}
/**
* 搜索任务
*/
searchTasks(query: string, filters?: {
isImportant?: boolean
taskType?: string
dateRange?: { start: string; end: string }
tags?: string[]
}): StoredTaskData[] {
try {
let tasks = this.getAllTasks()
// 文本搜索
if (query.trim()) {
const searchTerm = query.toLowerCase()
tasks = tasks.filter(task =>
task.taskName.toLowerCase().includes(searchTerm) ||
task.taskId.toLowerCase().includes(searchTerm) ||
task.sn.toLowerCase().includes(searchTerm) ||
task.battleUnitCode.toLowerCase().includes(searchTerm) ||
(task.userNotes && task.userNotes.toLowerCase().includes(searchTerm))
)
}
// 应用过滤器
if (filters) {
if (filters.isImportant !== undefined) {
tasks = tasks.filter(task => task.isImportant === filters.isImportant)
}
if (filters.taskType) {
tasks = tasks.filter(task => task.taskType === filters.taskType)
}
if (filters.dateRange) {
const start = new Date(filters.dateRange.start).getTime()
const end = new Date(filters.dateRange.end).getTime()
tasks = tasks.filter(task => {
const taskTime = new Date(task.storedAt).getTime()
return taskTime >= start && taskTime <= end
})
}
if (filters.tags && filters.tags.length > 0) {
tasks = tasks.filter(task =>
task.tags && task.tags.some(tag => filters.tags!.includes(tag))
)
}
}
return tasks
} catch (error) {
console.error('GKDataStorage: 搜索任务失败', error)
return []
}
}
/**
* 导出数据
*/
exportData(includeHistory: boolean = false): {
tasks: StoredTaskData[]
settings: GKUserSettings
history?: GKHistoryRecord[]
statistics: any
exportTime: string
} {
try {
const exportData = {
tasks: this.getAllTasks(),
settings: this.getUserSettings(),
statistics: this.getStatistics(),
exportTime: new Date().toISOString()
}
if (includeHistory) {
exportData.history = this.getHistoryRecords()
}
this.addHistoryRecord('user_action', { includeHistory }, '导出数据')
return exportData
} catch (error) {
console.error('GKDataStorage: 导出数据失败', error)
throw error
}
}
/**
* 导入数据
*/
importData(data: any, options?: { overwrite?: boolean; mergeSettings?: boolean }): boolean {
try {
const { overwrite = false, mergeSettings = true } = options || {}
// 导入任务数据
if (data.tasks && Array.isArray(data.tasks)) {
if (overwrite) {
this.setItem(this.STORAGE_KEYS.TASKS, data.tasks)
} else {
const existingTasks = this.getAllTasks()
const mergedTasks = [...data.tasks, ...existingTasks]
// 去重
const uniqueTasks = mergedTasks.filter((task, index, self) =>
index === self.findIndex(t => t.taskId === task.taskId)
)
this.setItem(this.STORAGE_KEYS.TASKS, uniqueTasks.slice(0, this.config.maxTasks))
}
}
// 导入设置
if (data.settings) {
if (mergeSettings) {
const currentSettings = this.getUserSettings()
const mergedSettings = { ...currentSettings, ...data.settings }
this.setItem(this.STORAGE_KEYS.SETTINGS, mergedSettings)
} else {
this.setItem(this.STORAGE_KEYS.SETTINGS, data.settings)
}
}
// 导入历史记录
if (data.history && Array.isArray(data.history)) {
if (overwrite) {
this.setItem(this.STORAGE_KEYS.HISTORY, data.history)
} else {
const existingHistory = this.getHistoryRecords()
const mergedHistory = [...data.history, ...existingHistory]
this.setItem(this.STORAGE_KEYS.HISTORY, mergedHistory.slice(0, this.config.maxHistory))
}
}
this.addHistoryRecord('user_action', { dataSize: JSON.stringify(data).length }, '导入数据')
return true
} catch (error) {
console.error('GKDataStorage: 导入数据失败', error)
return false
}
}
/**
* 清理过期数据
*/
cleanExpiredData(): void {
try {
const expireTime = Date.now() - (this.config.expireDays * 24 * 60 * 60 * 1000)
// 清理过期任务
const tasks = this.getAllTasks()
const validTasks = tasks.filter(task =>
new Date(task.storedAt).getTime() > expireTime
)
if (validTasks.length !== tasks.length) {
this.setItem(this.STORAGE_KEYS.TASKS, validTasks)
console.log(`GKDataStorage: 清理了 ${tasks.length - validTasks.length} 个过期任务`)
}
// 清理过期历史记录
const history = this.getHistoryRecords()
const validHistory = history.filter(record =>
new Date(record.timestamp).getTime() > expireTime
)
if (validHistory.length !== history.length) {
this.setItem(this.STORAGE_KEYS.HISTORY, validHistory)
console.log(`GKDataStorage: 清理了 ${history.length - validHistory.length} 条过期历史记录`)
}
} catch (error) {
console.error('GKDataStorage: 清理过期数据失败', error)
}
}
/**
* 清空所有数据
*/
clearAllData(): void {
try {
Object.values(this.STORAGE_KEYS).forEach(key => {
localStorage.removeItem(key)
})
console.log('GKDataStorage: 所有数据已清空')
} catch (error) {
console.error('GKDataStorage: 清空数据失败', error)
}
}
/**
* 获取存储项
*/
private getItem<T>(key: string, defaultValue: T): T {
try {
const item = localStorage.getItem(key)
if (item === null) {
return defaultValue
}
const parsed = JSON.parse(item)
return parsed !== null ? parsed : defaultValue
} catch (error) {
console.error(`GKDataStorage: 获取存储项失败 ${key}`, error)
return defaultValue
}
}
/**
* 设置存储项
*/
private setItem(key: string, value: any): void {
try {
const serialized = JSON.stringify(value)
localStorage.setItem(key, serialized)
} catch (error) {
console.error(`GKDataStorage: 设置存储项失败 ${key}`, error)
// 如果存储空间不足,尝试清理一些数据
if (error.name === 'QuotaExceededError') {
console.warn('GKDataStorage: 存储空间不足,尝试清理数据')
this.cleanExpiredData()
try {
localStorage.setItem(key, JSON.stringify(value))
} catch (retryError) {
console.error('GKDataStorage: 重试后仍然失败', retryError)
}
}
}
}
}
/**
* 全局GK数据存储管理器实例
*/
export const gkDataStorage = new GKDataStorage()
/**
* 创建GK数据存储管理器的工厂函数
*/
export function createGKDataStorage(config?: Partial<GKStorageConfig>): GKDataStorage {
return new GKDataStorage(config)
}
export default GKDataStorage

View File

@@ -0,0 +1,652 @@
<!--
@Date: 2024-12-19
@Description: GK数据监听器实际使用示例
@Author: Assistant
@FilePath: \drone-v2-command-screen\src\api\websocket\gkDataUsageExample.vue
-->
<template>
<div class="gk-usage-example">
<div class="page-header">
<h1>GK数据监听器 - 实际使用示例</h1>
<p>连接到真实的GK_SERVER: {{ currentServerUrl }}</p>
</div>
<!-- 连接状态面板 -->
<div class="status-panel">
<div class="status-grid">
<div class="status-card">
<div class="status-icon" :class="connectionStatus">
{{ isConnected ? '🟢' : '🔴' }}
</div>
<div class="status-info">
<h3>连接状态</h3>
<p>{{ isConnected ? '已连接' : '未连接' }}</p>
<small>{{ lastConnectTime || '从未连接' }}</small>
</div>
</div>
<div class="status-card" v-if="statistics">
<div class="status-icon">📊</div>
<div class="status-info">
<h3>数据统计</h3>
<p>已接收: {{ statistics.totalReceived }}</p>
<small>缓冲区: {{ dataBuffer.length }}/{{ statistics.bufferSize }}</small>
</div>
</div>
<div class="status-card">
<div class="status-icon">🚁</div>
<div class="status-info">
<h3>当前任务</h3>
<p>{{ currentTask?.task_name || '无活动任务' }}</p>
<small>{{ currentTask?.status || '' }}</small>
</div>
</div>
<div class="status-card">
<div class="status-icon">📍</div>
<div class="status-info">
<h3>设备位置</h3>
<p v-if="currentPosition">
{{ currentPosition.latitude?.toFixed(6) }}, {{ currentPosition.longitude?.toFixed(6) }}
</p>
<p v-else>无位置数据</p>
<small v-if="currentPosition">高度: {{ currentPosition.altitude }}m</small>
</div>
</div>
</div>
</div>
<!-- 控制操作面板 -->
<div class="control-panel">
<h2>控制操作</h2>
<div class="control-buttons">
<button
@click="reconnect"
:disabled="isConnected"
class="btn btn-primary"
>
重新连接
</button>
<button
@click="switchToBackup"
:disabled="!canSwitchToBackup"
class="btn btn-warning"
>
切换备用服务器
</button>
<button
@click="requestAllData"
:disabled="!isConnected"
class="btn btn-info"
>
请求全部数据
</button>
<button
@click="clearData"
class="btn btn-secondary"
>
清空数据
</button>
</div>
</div>
<!-- 数据展示区域 -->
<div class="data-display">
<div class="data-grid">
<!-- 最新任务信息 -->
<div class="data-card" v-if="currentTask">
<h3>📋 任务信息</h3>
<div class="data-content">
<div class="data-row">
<span class="label">任务ID:</span>
<span class="value">{{ currentTask.task_id }}</span>
</div>
<div class="data-row">
<span class="label">任务名称:</span>
<span class="value">{{ currentTask.task_name }}</span>
</div>
<div class="data-row">
<span class="label">状态:</span>
<span class="value" :class="getTaskStatusClass(currentTask.status)">
{{ currentTask.status }}
</span>
</div>
<div class="data-row">
<span class="label">创建时间:</span>
<span class="value">{{ formatTime(currentTask.create_time) }}</span>
</div>
</div>
</div>
<!-- 路线信息 -->
<div class="data-card" v-if="currentRoute">
<h3>🗺 路线信息</h3>
<div class="data-content">
<div class="data-row">
<span class="label">路线ID:</span>
<span class="value">{{ currentRoute.route_id }}</span>
</div>
<div class="data-row">
<span class="label">路线名称:</span>
<span class="value">{{ currentRoute.route_name }}</span>
</div>
<div class="data-row">
<span class="label">航点数量:</span>
<span class="value">{{ currentRoute.waypoints?.length || 0 }}</span>
</div>
<div class="data-row">
<span class="label">总距离:</span>
<span class="value">{{ currentRoute.total_distance }}m</span>
</div>
</div>
</div>
<!-- 位置信息 -->
<div class="data-card" v-if="currentPosition">
<h3>📍 位置信息</h3>
<div class="data-content">
<div class="data-row">
<span class="label">设备ID:</span>
<span class="value">{{ currentPosition.device_id }}</span>
</div>
<div class="data-row">
<span class="label">经纬度:</span>
<span class="value">
{{ currentPosition.latitude?.toFixed(6) }}, {{ currentPosition.longitude?.toFixed(6) }}
</span>
</div>
<div class="data-row">
<span class="label">高度:</span>
<span class="value">{{ currentPosition.altitude }}m</span>
</div>
<div class="data-row">
<span class="label">速度:</span>
<span class="value">{{ currentPosition.speed }}m/s</span>
</div>
<div class="data-row">
<span class="label">航向:</span>
<span class="value">{{ currentPosition.heading }}°</span>
</div>
</div>
</div>
</div>
</div>
<!-- 实时数据流 -->
<div class="live-data">
<h2>实时数据流</h2>
<div class="live-data-container">
<div
v-for="(data, index) in recentDataStream"
:key="index"
class="live-data-item"
:class="getDataTypeClass(data)"
>
<div class="data-time">{{ formatTime(data.timestamp) }}</div>
<div class="data-type">{{ getDataTypeLabel(data) }}</div>
<div class="data-summary">{{ getDataSummary(data) }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'
import { GKDataManager, GKDataManagerConfig } from './gkDataManager'
import { GKData } from './gkDataListener'
import { getGKWebSocketUrl, getAllGKWebSocketUrls, gkWebSocketConfigManager } from './gkDataConfig'
import EventBus from '../im/eventBus.js'
// 响应式数据
const isConnected = ref(false)
const lastConnectTime = ref('')
const currentTask = ref(null)
const currentRoute = ref(null)
const currentPosition = ref(null)
const dataBuffer = ref<GKData[]>([])
const recentDataStream = ref<GKData[]>([])
// GK数据管理器
let gkDataManager: GKDataManager | null = null
// 计算属性
const currentServerUrl = computed(() => gkWebSocketConfigManager.getCurrentUrl())
const connectionStatus = computed(() => isConnected.value ? 'connected' : 'disconnected')
const canSwitchToBackup = computed(() => !isConnected.value || gkWebSocketConfigManager.getRetryCount() > 3)
const statistics = computed(() => {
return gkDataManager?.getStatistics() || null
})
// 初始化GK数据监听器
const initGKDataListener = () => {
const config: GKDataManagerConfig = {
url: getGKWebSocketUrl(), // ws://27.11.11.30/dji/api/v1/ws
autoConnect: true,
enableLogging: true,
dataBufferSize: 50
}
gkDataManager = new GKDataManager(config)
// 注册数据处理器
gkDataManager.registerDataHandler(handleGKData)
// 初始化连接
gkDataManager.init()
// console.log('GK数据监听器已初始化连接到:', config.url)
}
// 处理GK数据
const handleGKData = (gkData: GKData) => {
// 更新数据缓冲区
dataBuffer.value = gkDataManager?.getBufferedData() || []
// 更新实时数据流最新10条
recentDataStream.value.unshift(gkData)
if (recentDataStream.value.length > 10) {
recentDataStream.value = recentDataStream.value.slice(0, 10)
}
// console.log('接收到GK数据:', gkData)
}
// 设置事件监听器
const setupEventListeners = () => {
EventBus.on('GKDataManager-Connected', (data: any) => {
isConnected.value = true
lastConnectTime.value = new Date().toLocaleString()
// console.log('GK数据服务器连接成功:', data)
})
EventBus.on('GKDataManager-Disconnected', (data: any) => {
isConnected.value = false
// console.log('GK数据服务器连接断开:', data)
})
EventBus.on('GKDataManager-Error', (error: any) => {
console.error('GK数据服务器连接错误:', error)
})
EventBus.on('GKDataManager-TaskInfoUpdated', (taskInfo: any) => {
currentTask.value = taskInfo
})
EventBus.on('GKDataManager-RouteDataUpdated', (routeData: any) => {
currentRoute.value = routeData
})
EventBus.on('GKDataManager-PositionDataUpdated', (positionData: any) => {
currentPosition.value = positionData
})
}
// 清理事件监听器
const cleanupEventListeners = () => {
EventBus.off('GKDataManager-Connected')
EventBus.off('GKDataManager-Disconnected')
EventBus.off('GKDataManager-Error')
EventBus.off('GKDataManager-TaskInfoUpdated')
EventBus.off('GKDataManager-RouteDataUpdated')
EventBus.off('GKDataManager-PositionDataUpdated')
}
// 控制操作
const reconnect = () => {
gkDataManager?.reconnect()
}
const switchToBackup = () => {
gkWebSocketConfigManager.switchToFallback()
if (gkDataManager) {
gkDataManager.close()
initGKDataListener()
}
}
const requestAllData = () => {
gkDataManager?.requestData('all')
}
const clearData = () => {
gkDataManager?.clearBuffer()
dataBuffer.value = []
recentDataStream.value = []
currentTask.value = null
currentRoute.value = null
currentPosition.value = null
}
// 辅助函数
const formatTime = (timestamp: string) => {
return new Date(timestamp).toLocaleString()
}
const getTaskStatusClass = (status: string) => {
const statusMap: Record<string, string> = {
'执行中': 'status-running',
'已完成': 'status-completed',
'已暂停': 'status-paused',
'已取消': 'status-cancelled'
}
return statusMap[status] || 'status-default'
}
const getDataTypeClass = (data: GKData) => {
if (data.data.task_info) return 'data-task'
if (data.data.route_data) return 'data-route'
if (data.data.position_data) return 'data-position'
return 'data-other'
}
const getDataTypeLabel = (data: GKData) => {
if (data.data.task_info) return '任务'
if (data.data.route_data) return '路线'
if (data.data.position_data) return '位置'
return '其他'
}
const getDataSummary = (data: GKData) => {
if (data.data.task_info) {
return `${data.data.task_info.task_name} - ${data.data.task_info.status}`
}
if (data.data.route_data) {
return `${data.data.route_data.route_name} (${data.data.route_data.waypoints?.length || 0}个航点)`
}
if (data.data.position_data) {
return `设备${data.data.position_data.device_id} - ${data.data.position_data.latitude?.toFixed(4)}, ${data.data.position_data.longitude?.toFixed(4)}`
}
return '数据更新'
}
// 组件生命周期
onMounted(() => {
setupEventListeners()
initGKDataListener()
})
onUnmounted(() => {
if (gkDataManager) {
gkDataManager.close()
}
cleanupEventListeners()
})
</script>
<style scoped>
.gk-usage-example {
padding: 20px;
max-width: 1400px;
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.page-header {
text-align: center;
margin-bottom: 30px;
}
.page-header h1 {
color: #1890ff;
margin-bottom: 10px;
}
.page-header p {
color: #666;
font-size: 14px;
}
.status-panel {
margin-bottom: 30px;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
color: white;
}
.status-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.status-card {
display: flex;
align-items: center;
padding: 15px;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
backdrop-filter: blur(10px);
}
.status-icon {
font-size: 24px;
margin-right: 15px;
}
.status-info h3 {
margin: 0 0 5px 0;
font-size: 16px;
}
.status-info p {
margin: 0 0 5px 0;
font-size: 18px;
font-weight: bold;
}
.status-info small {
opacity: 0.8;
font-size: 12px;
}
.control-panel {
margin-bottom: 30px;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
}
.control-panel h2 {
margin-bottom: 15px;
color: #333;
}
.control-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background: #1890ff;
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #40a9ff;
}
.btn-warning {
background: #fa8c16;
color: white;
}
.btn-warning:hover:not(:disabled) {
background: #ffa940;
}
.btn-info {
background: #13c2c2;
color: white;
}
.btn-info:hover:not(:disabled) {
background: #36cfc9;
}
.btn-secondary {
background: #d9d9d9;
color: #333;
}
.btn-secondary:hover:not(:disabled) {
background: #f0f0f0;
}
.data-display {
margin-bottom: 30px;
}
.data-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
}
.data-card {
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border-left: 4px solid #1890ff;
}
.data-card h3 {
margin: 0 0 15px 0;
color: #1890ff;
font-size: 16px;
}
.data-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.data-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
}
.data-row:last-child {
border-bottom: none;
}
.label {
color: #666;
font-size: 14px;
min-width: 80px;
}
.value {
color: #333;
font-weight: 500;
text-align: right;
}
.status-running {
color: #52c41a;
}
.status-completed {
color: #1890ff;
}
.status-paused {
color: #fa8c16;
}
.status-cancelled {
color: #ff4d4f;
}
.live-data {
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
}
.live-data h2 {
margin-bottom: 15px;
color: #333;
}
.live-data-container {
max-height: 400px;
overflow-y: auto;
}
.live-data-item {
display: flex;
align-items: center;
padding: 10px 15px;
margin-bottom: 8px;
background: white;
border-radius: 6px;
border-left: 4px solid #d9d9d9;
transition: all 0.3s;
}
.live-data-item:hover {
transform: translateX(5px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.data-task {
border-left-color: #52c41a;
}
.data-route {
border-left-color: #1890ff;
}
.data-position {
border-left-color: #fa8c16;
}
.data-other {
border-left-color: #d9d9d9;
}
.data-time {
min-width: 120px;
font-size: 12px;
color: #666;
font-family: monospace;
}
.data-type {
min-width: 60px;
font-size: 12px;
font-weight: bold;
color: #1890ff;
}
.data-summary {
flex: 1;
font-size: 14px;
color: #333;
}
</style>

102
src/api/websocket/index.ts Normal file
View File

@@ -0,0 +1,102 @@
/*
* @Date: 2024-04-15 17:48:09
* @LastEditors: jiangchao
* @LastEditTime: 2024-04-15 17:48:13
* @Description: 文件注释
* @Author: jiangchao
* @FilePath: \mars3d-vue-project\src\api\websocket\index.ts
*/
import { message } from 'ant-design-vue'
import ReconnectingWebSocket from 'reconnecting-websocket'
import EventBus from '../im/eventBus.js'
interface WebSocketOptions {
data: any
cache?: boolean | string
destroyCache?: string
}
export interface MessageHandler {
(data : {[key: string]: any}): void
}
/**
* ConnectWebSocket 类
* TODO: 优化messageHandler: EventEmitter。暂时传入回调函数
*/
class ConnectWebSocket {
_url: string
_socket: ReconnectingWebSocket | null
_hasInit: boolean
_messageHandler:
| null
constructor (url: string) {
this._url = url
this._socket = null
this._hasInit = false
this._messageHandler = null
}
initSocket () {
if (this._hasInit) {
return
}
if (!this._url) {
return
}
// 会自动重连,无需处理重连逻辑
this._socket = new ReconnectingWebSocket(this._url, [], {
maxReconnectionDelay: 20000, // 断开后最大的重连时间: 20s每多一次重连会增加 1.3 倍5 * 1.3 * 1.3 * 1.3...
minReconnectionDelay: 5000, // 断开后最短的重连时间: 5s
maxRetries: 5
})
this._hasInit = true
this._socket.addEventListener('open', this._onOpen.bind(this))
this._socket.addEventListener('close', this._onClose.bind(this))
this._socket.addEventListener('error', this._onError.bind(this))
this._socket.addEventListener('message', this._onMessage.bind(this))
}
_onOpen () {
console.log('连接成功')
// message.success("ws链接成功")
}
_onClose () {
// message.error("连接已断开")
localStorage.removeItem('index-otherEquipmentList')
console.log('连接已断开')
}
_onError (err:any) {
console.info("err",err)
// message.error("ws连接错误请检查网络是否通畅")
}
registerMessageHandler (messageHandler: MessageHandler) {
this._messageHandler = messageHandler
}
_onMessage (msg: MessageEvent) {
const data = JSON.parse(msg.data)
EventBus.emit('WebScoket-Message',data)
this._messageHandler && this._messageHandler(data)
}
sendMessage = (message: WebSocketOptions): void => {
this._socket?.send(JSON.stringify(message.data))
}
close () {
this._socket?.close()
}
}
export default ConnectWebSocket

View File

@@ -0,0 +1,566 @@
import { EBizCode } from "@mars/types/enums";
import ConnectWebSocket from ".";
import { getWebsocketUrl } from "./util/config";
import { storeKey, useStore } from "vuex";
import EventBus from '/@/event-bus';
import {getUVDeviceList} from "@mars/pages/dataGovernanceDashboard/views/homeShow/components/uv/UVDevice.js";
//无人设备列表
var socketUVDeviceList = null;
//大疆无人机标记获取数据库字段方法
const findUVDeviceByCode = (code,type) => {
if(!socketUVDeviceList){
return null;
}
return socketUVDeviceList.find(item => item.device.device_code === code && item.device.device_type === type) || null;
}
export class EventEmitter {
private events: Record<string, Function[]> = {};
on(event: string, callback: Function) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
once(event: string, callback: Function) {
const onceCallback = (...args: any[]) => {
callback(...args);
this.off(event, onceCallback); // 移除事件监听
};
this.on(event, onceCallback);
}
off(event: string, callback: Function) {
const callbacks = this.events[event];
if (callbacks) {
const index = callbacks.indexOf(callback);
if (index !== -1) {
callbacks.splice(index, 1);
}
}
}
emit(event: string, ...args: any[]) {
const callbacks = this.events[event];
if (callbacks) {
for (const callback of callbacks) {
callback(...args);
}
}
}
}
/**
* {
"version": "1.0",
"timestamp": 1743139908347,
"data": {
"uavSn": "4TADL210010018",
"event": "detect_end_event",
"data": {}
},
"biz_code": "ai_service_notify"
}
*/
/**
* {
* "version":"1.0","timestamp":1743139908347,"data":{"uavSn":"4TADL210010018","event":"detect_end_event","data":{}},"biz_code":"ai_service_notify"}
*/
export class LoadWebSocket extends EventEmitter {
store
webSocket
constructor(store){
super()
// this.store
this.store = store
// this.init()
}
init(){
let webSocket = new ConnectWebSocket(getWebsocketUrl())
this.webSocket = webSocket
this.webSocket?.registerMessageHandler(this.messageHandler)
this.webSocket?.initSocket()
}
messageHandler = (payload: any) => {
if (!payload) {
return;
}
//无人设备列表无数据时加载一次数据
if(!socketUVDeviceList){
getUVDeviceList().then((res)=>{
socketUVDeviceList = res;
})
}
//匹配无人设备列表,加载数据库的新名称
if(socketUVDeviceList){
// 设备名称映射配置
const deviceNameMappingConfig = [
{
bizCode: "device_osd",//大疆无人机的
getDeviceSn: (payload) => payload.data.sn,
deviceType: 0,
setDeviceName: (payload, deviceName) => {
if (payload.data?.host?.device) {
payload.data.host.device.nickname = deviceName;
}
}
},
{
bizCode: "drone_data", // 296设备支持无人机和无人车
getDeviceSn: (payload) => {
const deviceId = payload.data?.host?.deviceId;
return deviceId ? String(deviceId) : null; // 转换为字符串与UVDevice.vue保持一致
},
deviceType: (payload) => {
// 根据设备类型描述动态判断设备类型
const description = payload.data?.host?.deviceType?.description;
if (description === "无人机") {
return 0; // 无人机
} else if (description === "无人车") {
return 1; // 无人车
}
return 0; // 默认无人机
},
setDeviceName: (payload, device_name) => {
if (payload.data?.host) {
payload.data.host.device_name = device_name;
payload.data.host.nickname = device_name;
// 兼容性也设置到device对象中
if (!payload.data.host.device) payload.data.host.device = {};
payload.data.host.device.nickname = device_name;
}
}
},
{
bizCode: "qb303_device_status", // QB303(国科)设备
getDeviceSn: (payload) => payload.data?.host?.battle_unit_code,
deviceType: 0,
setDeviceName: (payload, device_name) => {
// 直接在host中添加设备名称字段这样会被store保存
if (payload.data?.host) {
payload.data.host.device_name = device_name;
payload.data.host.nickname = device_name;
// 兼容性也设置到device对象中
if (!payload.data.host.device) payload.data.host.device = {};
payload.data.host.device.nickname = device_name;
}
}
},
{
bizCode: "wrc_ptzt", // 无人车设备
getDeviceSn: (payload) => {
// 根据UVDevice.vue中的逻辑提取设备标识
const car = payload.data?.host?.XXXX?.at?.(0);
return car?.WRZB?.ZBID;
},
deviceType: 1, // 无人车类型
setDeviceName: (payload, device_name) => {
// 设置设备名称到多个位置,确保被正确保存和显示
if (payload.data?.host) {
payload.data.host.device_name = device_name;
payload.data.host.nickname = device_name;
// 设置到车辆数据中
const car = payload.data.host.XXXX?.at?.(0);
if (car) {
car.device_name = device_name;
car.nickname = device_name;
}
// 兼容性也设置到device对象中
if (!payload.data.host.device) payload.data.host.device = {};
payload.data.host.device.nickname = device_name;
}
}
},
{
bizCode: "hava_osd_data", // 哈瓦无人机设备
getDeviceSn: (payload) => {
// 根据UVDevice.vue中的逻辑提取设备标识
return payload.data?.host?.sn;
},
deviceType: 0, // 无人机类型
setDeviceName: (payload, device_name) => {
// 设置设备名称到多个位置,确保被正确保存和显示
if (payload.data?.host) {
payload.data.host.device_name = device_name;
payload.data.host.nickname = device_name;
// 兼容性也设置到device对象中
if (!payload.data.host.device) payload.data.host.device = {};
payload.data.host.device.nickname = device_name;
}
}
},
{
bizCode: "missile_patrol_message", // 巡飞弹
getDeviceSn: (payload) => {
// UVDevice.vue 中按 host.name 查找findUVDeviceByCode(name, 0)
return payload.data?.host?.name;
},
deviceType: 0, // 默认按无人机类型匹配名称
setDeviceName: (payload, device_name) => {
if (payload.data?.host) {
payload.data.host.device_name = device_name;
payload.data.host.nickname = device_name;
if (!payload.data.host.device) payload.data.host.device = {};
payload.data.host.device.nickname = device_name;
}
}
},
{
bizCode: "DEVICE_DATA_UPDATE", // 电台设备
getDeviceSn: (payload) => {
// 电台设备数据是数组格式,需要特殊处理
const dtArr = payload.data?.data;
if (Array.isArray(dtArr) && dtArr.length > 0) {
// 返回第一个设备的sn用于查找设备名称
return dtArr[0]?.sn;
}
return null;
},
deviceType: 2, // 电台类型
setDeviceName: (payload, device_name) => {
// 电台设备是数组格式,需要为每个设备设置名称
const dtArr = payload.data?.data;
if (Array.isArray(dtArr)) {
dtArr.forEach(item => {
if (item) {
item.device_name = device_name;
item.nickname = device_name;
}
});
}
},
// 特殊处理:电台设备需要为数组中的每个设备查找名称
isArrayData: true
},
{
bizCode: "hit_drone", // Y16击毁无人机设备
getDeviceSn: (payload) => {
// 根据UVDevice.vue中的逻辑提取设备标识
return payload.data?.host?.name;
},
deviceType: 0, // 无人机类型
setDeviceName: (payload, device_name) => {
// 设置设备名称到多个位置,确保被正确保存和显示
if (payload.data?.host) {
payload.data.host.device_name = device_name;
payload.data.host.nickname = device_name;
// 兼容性也设置到device对象中
if (!payload.data.host.device) payload.data.host.device = {};
payload.data.host.device.nickname = device_name;
}
}
},
{
bizCode: "ue_drone_friends", // 蜂刺无人机设备
getDeviceSn: (payload) => {
// 根据UVDevice.vue中的逻辑提取设备标识
return payload.data?.sn;
},
deviceType: 0, // 无人机类型
setDeviceName: (payload, device_name) => {
// 设置设备名称到多个位置,确保被正确保存和显示
if (payload.data?.host) {
payload.data.host.device_name = device_name;
payload.data.host.nickname = device_name;
// 设置到友方数据中
if (payload.data.host.friends_data?.friend_info) {
payload.data.host.friends_data.friend_info.device_name = device_name;
payload.data.host.friends_data.friend_info.nickname = device_name;
}
// 兼容性也设置到device对象中
if (!payload.data.host.device) payload.data.host.device = {};
payload.data.host.device.nickname = device_name;
}
// 也在根级别设置设备名称
if (payload.data) {
payload.data.device_name = device_name;
payload.data.nickname = device_name;
}
}
}
// 后续可以继续添加更多配置...
];
// 循环处理所有配置的设备名称映射
for (const config of deviceNameMappingConfig) {
if (payload.biz_code === config.bizCode) {
// console.log(`🎯 匹配到biz_code: ${config.bizCode}`);
const deviceSn = config.getDeviceSn(payload);
// console.log(`📱 提取到的deviceSn: ${deviceSn}`);
if (deviceSn) {
// console.log(`📋 设备列表长度: ${socketUVDeviceList?.length || 0}`);
// 查看设备列表中的前几个设备信息
if (socketUVDeviceList && socketUVDeviceList.length > 0) {
// console.log('📋 设备列表中的前3个设备:');
socketUVDeviceList.slice(0, 3).forEach((item, index) => {
// console.log(` ${index}: code="${item.device?.device_code}", type=${item.device?.device_type}, name="${item.device?.device_name}"`);
});
}
// 支持动态设备类型
const deviceType = typeof config.deviceType === 'function' ? config.deviceType(payload) : config.deviceType;
const deviceTypeText = deviceType === 0 ? '无人机' : deviceType === 1 ? '无人车' : '电台';
// console.log(`🔧 设备类型: ${deviceType} (${deviceTypeText})`);
// 特殊处理电台设备的数组格式
if (config.isArrayData && payload.biz_code === "DEVICE_DATA_UPDATE") {
// console.log(`🔄 处理电台设备数组数据`);
const dtArr = payload.data?.data;
if (Array.isArray(dtArr)) {
dtArr.forEach((item, index) => {
const itemSn = item?.sn;
if (itemSn) {
const findUVdevice = findUVDeviceByCode(itemSn, deviceType);
// console.log(`🔍 电台设备[${index}] sn=${itemSn} 查找结果:`, findUVdevice?.device?.device_name || 'null');
if (findUVdevice?.device?.device_name) {
item.device_name = findUVdevice.device.device_name;
item.nickname = findUVdevice.device.device_name;
// console.log(`✅ 电台设备[${index}] 设置名称: ${findUVdevice.device.device_name}`);
}
}
});
}
} else {
// 常规处理逻辑
const findUVdevice = findUVDeviceByCode(deviceSn, deviceType);
// console.log(`🔍 查找结果:`, findUVdevice);
if (findUVdevice?.device?.device_name) {
// console.log(`✅ 设置设备名称: ${findUVdevice.device.device_name}`);
// console.log(`📤 设置前的payload.data.host:`, JSON.stringify(payload.data.host, null, 2));
config.setDeviceName(payload, findUVdevice.device.device_name);
// console.log(`📥 设置后的payload.data.host:`, JSON.stringify(payload.data.host, null, 2));
} else {
// console.log(`❌ 未找到匹配的设备或设备名称为空`);
// 如果没找到精确匹配,尝试查找包含该标识的设备
if (socketUVDeviceList) {
const partialMatches = socketUVDeviceList.filter(item =>
item.device?.device_code?.toString().includes(deviceSn.toString()) ||
deviceSn.toString().includes(item.device?.device_code?.toString())
);
if (partialMatches.length > 0) {
// console.log('🔍 找到部分匹配的设备:', partialMatches.map(item => ({
// code: item.device?.device_code,
// type: item.device?.device_type,
// name: item.device?.device_name
// })));
}
// 查看同类型的所有设备
const deviceType = typeof config.deviceType === 'function' ? config.deviceType(payload) : config.deviceType;
const typeMatches = socketUVDeviceList.filter(item =>
item.device?.device_type === deviceType
);
// console.log(`🔍 所有device_type=${deviceType}的设备:`, typeMatches.map(item => ({
// code: item.device?.device_code,
// name: item.device?.device_name
// })));
}
}
}
} else {
// console.log(`❌ deviceSn为空无法查找`);
// console.log('原始payload数据:', JSON.stringify(payload, null, 2));
}
break; // 找到匹配的配置后退出循环
}
}
}
switch (payload.biz_code) {
case EBizCode.GatewayOsd: {
this.store.commit('SET_GATEWAY_INFO', payload.data);
break;
}
case EBizCode.DeviceOsd: {
this.store.commit('SET_DEVICE_INFO', {
...payload.data,
timestamp: payload.timestamp
});
break;
}
case EBizCode.RadioRobotDog: {
this.store.commit('SET_ROBOTDOG_INFO',payload.data)
break;
}
case EBizCode.DeviceAir: {
this.store.commit('SET_DEVICEAIR_INFO',payload.data)
break;
}
//巡飞弹
case EBizCode.CruiseMissile: {
this.store.commit('SET_MISSILE_INFO',payload.data)
break;
}
//无人车
case EBizCode.WrcPtzt: {
this.store.commit('SET_WRC_INFO',payload.data)
break;
}
//电台
case EBizCode.deviceDataUpdate: {
this.store.commit('SET_DEVICE_DATA_UPDATE_INFO',payload.data)
break;
}
//哈瓦无人机
case EBizCode.havaOsdData: {
this.store.commit('SET_HAVA_OSD_DATA_INFO',payload.data)
break;
}
//296设备
case EBizCode.droneData: {
if(payload.data.host.deviceType?.description === "无人机"){
this.store.commit('SET_DRONE_DATA_INFO',payload.data)
} else if(payload.data.host.deviceType?.description === "无人车"){
this.store.commit('SET_DRONE_WRC_DATA_INFO',payload.data)
}
break;
}
//国科QB303
case EBizCode.qb303DeviceData: {
this.store.commit('SET_QB303_DEVICE_DATA_INFO',payload)
break;
}
//国科QB303视频流
case EBizCode.qb303DeviceDataVideo: {
this.store.commit('SET_QB303_DEVICE_DATA_INFO',payload)
break;
}
//蜂刺无人机
case EBizCode.uefcDeviceData: {
this.store.commit('SET_UEFC_DEVICE_DATA_INFO',payload.data)
break;
}
//高科数据
case EBizCode.GkData: {
this.store.commit('SET_GK_DATA',payload.data)
break;
}
case EBizCode.RemoteControl: {
this.store.commit('SET_RC_ONLINE_INFO',payload.data)
break;
}
//Y16
case EBizCode.HitDroneY: {
this.store.commit('SET_HIT_DRONE_INFO',payload.data)
break;
}
case EBizCode.DockOsd: {
this.store.commit('SET_DOCK_INFO', payload.data);
break;
}
case EBizCode.DeviceOnline: {
this.store.commit('SET_DEVICE_ONLINE', payload.data);
break;
}
// case EBizCode.AutelOnline: {
// this.store.commit('SET_ONLINE_INFO', payload.data);
// break;
// }
case EBizCode.DeviceOffline: {
this.store.commit('SET_DEVICE_OFFLINE', payload.data);
break;
}
case EBizCode.AiServiceNotify: {
console.info("payload",payload.data)
// this.store.commit('SET_DEVICE_OFFLINE', payload.data);
this.store.commit('SET_AI_SERVICE_NOTIFY', payload.data);
break;
}
case EBizCode.FlightTaskProgress:
case EBizCode.FlightTaskMediaProgress:
case EBizCode.FlightTaskMediaHighestPriority: {
let payloads = {
...payload,
...payload.data,
};
this.emit('flightTaskWs', payloads);
break;
}
case EBizCode.DeviceHms: {
this.store.commit('SET_DEVICE_HMS_INFO', payload.data);
break;
}
case EBizCode.DeviceReboot:
case EBizCode.DroneOpen:
case EBizCode.DroneClose:
case EBizCode.CoverOpen:
case EBizCode.CoverClose:
case EBizCode.PutterOpen:
case EBizCode.PutterClose:
case EBizCode.ChargeOpen:
case EBizCode.ChargeClose:
case EBizCode.DeviceFormat:
case EBizCode.DroneFormat: {
this.store.commit('SET_DEVICES_CMD_EXECUTE_INFO', {
biz_code: payload.biz_code,
timestamp: payload.timestamp,
...payload.data,
});
break;
}
case EBizCode.CoverForceClose: {
this.store.commit('SET_COVER_FORCE_CLOSE', payload.data);
break;
}
case EBizCode.ControlSourceChange:
case EBizCode.FlyToPointProgress:
case EBizCode.TakeoffToPointProgress:
case EBizCode.JoystickInvalidNotify:
case EBizCode.DrcStatusNotify: {
this.emit('droneControlWs', payload);
break;
}
// case EBizCode.CameraPhotoTakeProgress: {
// console.log('SET_PHOTO_TAKE_PROGRESS************', payload.data);
// this.store.commit('SET_PHOTO_TAKE_PROGRESS', payload.data);
// break;
// }
case EBizCode.PsdkWidgetValues: {
this.store.commit('SET_SN_PSDK_WIDGET', payload.data);
break;
}
case EBizCode.PsdkFloatingWindowText: {
this.store.commit('SET_SN_PSDK_Floating_WINDOWS', payload.data);
break;
}
// //飞机mode_code_reason
// case EBizCode.ModeCodeReason: {
// this.store.commit('SET_DEVICE_MODE_CODE_REASON', payload.data);
// break;
// }
default:
break;
}
};
close(){
this.webSocket.close()
}
}

View File

@@ -0,0 +1,21 @@
/*
* @Date: 2023-12-26 21:00:26
* @LastEditors: jiangchao
* @LastEditTime: 2024-04-15 17:49:03
* @Description: 文件注释
* @Author: jiangchao
* @FilePath: \mars3d-vue-project\src\api\websocket\util\config.ts
*/
import { ELocalStorageKey } from '/@/types/enums'
export function getWebsocketUrl () {
const token: string = localStorage.getItem(ELocalStorageKey.Token) || '' as string
let protocol = ''
if(location.protocol == 'http:'){
protocol = 'ws'
}else{
protocol = 'wss'
}
const url = protocol+'://'+ location.host + '/dji/api/v1/ws?x-auth-token=' + encodeURI(token)
return url
}

View File

@@ -0,0 +1,84 @@
/*
* @Date: 2024-12-19
* @Description: 外部WebSocket配置文件
* @Author: Assistant
* @FilePath: \mars3d-vue-project\src\api\websocket\util\externalConfig.ts
*/
export interface ExternalWebSocketConfig {
url: string
name: string
autoReconnect?: boolean
maxReconnectionDelay?: number
minReconnectionDelay?: number
maxRetries?: number
}
/**
* 获取外部WebSocket配置
* 默认连接到 ws://27.11.11.177:3000
*/
export function getExternalWebSocketConfig(): ExternalWebSocketConfig {
return {
url: 'ws://27.11.11.177:3000',
name: 'ExternalService',
autoReconnect: true,
maxReconnectionDelay: 20000,
minReconnectionDelay: 5000,
maxRetries: 5
}
}
/**
* 获取主WebSocket配置基于现有配置
*/
export function getPrimaryWebSocketConfig(): ExternalWebSocketConfig {
// 按优先级获取tokenx-auth-token > ws-auth-token > Authorization
const token: string = localStorage.getItem('x-auth-token') ||
localStorage.getItem('ws-auth-token') ||
localStorage.getItem('Authorization') || ''
let protocol = ''
if (location.protocol === 'http:') {
protocol = 'ws'
} else {
protocol = 'wss'
}
// 如果没有token记录警告但仍然尝试连接
if (!token) {
console.warn('WebSocket连接警告: 未找到有效的认证token可能导致403错误')
} else {
console.log(`WebSocket使用token认证: ${token.substring(0, 10)}...`)
}
const url = protocol + '://' + location.host + '/dji/api/v1/ws?x-auth-token=' + encodeURIComponent(token)
return {
url,
name: 'PrimaryService',
autoReconnect: true,
maxReconnectionDelay: 20000,
minReconnectionDelay: 5000,
maxRetries: 5
}
}
/**
* 创建自定义外部WebSocket配置
*/
export function createExternalWebSocketConfig(
url: string,
name: string = 'CustomExternalService',
options: Partial<ExternalWebSocketConfig> = {}
): ExternalWebSocketConfig {
return {
url,
name,
autoReconnect: true,
maxReconnectionDelay: 20000,
minReconnectionDelay: 5000,
maxRetries: 5,
...options
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1014 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,10 @@
<svg width="24" height="1064" viewBox="0 0 24 1064" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.35032 1055.97L1.24632 1028.85V1001.86V898.427L1.35032 898.327L22.9607 877.439V191.939L1.14258 170.85V65.2033L1.35032 65.1027L1.14258 40.6723V5.02148L2.18153 6.52786V41.6765V65.7054V170.549L23.9996 191.638V877.84L23.8956 877.941L2.28528 898.829V1001.46V1028.79V1053.46L1.35032 1055.97Z" fill="#01AAF2"/>
<path d="M5.61035 199.271L13.6105 205.898V865.187L5.61035 872.92V199.271Z" fill="black" stroke="#01AAF2"/>
<path d="M7.37598 156.188L11.9474 160.105V174.365L7.37598 170.449V156.188Z" fill="#00FFDC"/>
<path d="M0 1064L1.24707 1029.86V1001.96V899.632H3.32498V1001.16V1029.66V1045.42V1064L0 1064Z" fill="#01AAF2"/>
<path d="M2.07791 167.134H0V64.8014L0.311738 64.5003L0 38.1614V0L2.07791 0.5V38.316V65.6049V167.134Z" fill="#01AAF2"/>
<path d="M11.9474 895.314L7.37598 899.231V913.491L11.9474 909.575V895.314Z" fill="#00FFDC"/>
<path d="M16.8516 1063.5L20.6839 1054.5H22.6L18.7677 1063.5H16.8516ZM1 1063.5L4.83218 1054.5H18.7677L14.9355 1063.5H1Z" fill="black" stroke="#01AAF2"/>
<path d="M21.0484 10L17.1452 1H19.0967L23 10H21.0484ZM4.90315 10L1 1H15.1936L19.0967 10H4.90315Z" fill="black" stroke="#01AAF2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,10 @@
<svg width="24" height="1064" viewBox="0 0 24 1064" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.6497 1055.97L22.7537 1028.85V1001.86V898.427L22.6497 898.327L1.03899 877.439V191.939L22.8574 170.85V65.2033L22.6497 65.1027L22.8574 40.6723V5.02148L21.8184 6.52786V41.6765V65.7054V170.549L1.64509e-05 191.638V877.84L0.104015 877.941L21.7147 898.829V1001.46V1028.79V1053.46L22.6497 1055.97Z" fill="#01AAF2"/>
<path d="M18.3896 199.271L10.3895 205.898V865.187L18.3896 872.92V199.271Z" fill="black" stroke="#01AAF2"/>
<path d="M16.624 156.188L12.0526 160.105V174.365L16.624 170.449V156.188Z" fill="#00FFDC"/>
<path d="M24 1064L22.7529 1029.86V1001.96V899.632H20.675V1001.16V1029.66V1045.42V1064L24 1064Z" fill="#01AAF2"/>
<path d="M21.9221 167.134H24V64.8014L23.6883 64.5003L24 38.1614V0L21.9221 0.5V38.316V65.6049V167.134Z" fill="#01AAF2"/>
<path d="M12.0526 895.314L16.624 899.231V913.491L12.0526 909.575V895.314Z" fill="#00FFDC"/>
<path d="M7.14814 1063.5L3.31579 1054.5H1.39966L5.23201 1063.5H7.14814ZM23 1063.5L19.1678 1054.5H5.23201L9.06426 1063.5H23Z" fill="black" stroke="#01AAF2"/>
<path d="M2.95126 10.0833L6.85458 0.916504H4.90297L0.999654 10.0833H2.95126ZM19.0968 10.0833L23 0.916504H8.80619L4.90297 10.0833H19.0968Z" fill="black" stroke="#01AAF2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

@@ -0,0 +1,284 @@
<svg width="368" height="161" viewBox="0 0 368 161" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="&#229;&#183;&#165;&#229;&#141;&#149;&#230;&#128;&#187;&#230;&#149;&#176;&#233;&#135;&#143;">
<g id="Group 427318988">
<g id="Mask group">
<path id="Rectangle 34624167" opacity="0.5" d="M368 13H16C11.5817 13 8 16.5817 8 21V30L9.68426 29.0435C10.8886 28.3596 12.2499 28 13.6349 28H184.967H250.567C252.459 28 254.325 27.5524 256.011 26.6937L276.413 16.3063C278.099 15.4476 279.965 15 281.857 15H301.478H368V13Z" fill="url(#paint0_linear_18851_1407)"/>
<mask id="mask0_18851_1407" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="8" y="13" width="360" height="17">
<path id="Rectangle 34624168" opacity="0.5" d="M368 13H16C11.5817 13 8 16.5817 8 21V30L9.91711 28.9632C11.086 28.331 12.3939 28 13.7228 28H188H254.333C255.738 28 257.119 27.6299 258.336 26.9269L276.192 16.6096C278.017 15.5552 280.088 15 282.196 15H301.478H368V13Z" fill="url(#paint1_linear_18851_1407)"/>
</mask>
<g mask="url(#mask0_18851_1407)">
<g id="Frame 427319359">
<path id="Line 15" opacity="0.1" d="M23.6953 12.498L-23.3047 62.498" stroke="#4798F7"/>
<path id="Line 16" opacity="0.1" d="M28.6953 12.498L-18.3047 62.498" stroke="#4798F7"/>
<path id="Line 17" opacity="0.1" d="M33.6953 12.498L-13.3047 62.498" stroke="#4798F7"/>
<path id="Line 18" opacity="0.1" d="M38.6953 12.498L-8.30469 62.498" stroke="#4798F7"/>
<path id="Line 19" opacity="0.1" d="M43.6953 12.498L-3.30469 62.498" stroke="#4798F7"/>
<path id="Line 20" opacity="0.1" d="M48.6953 12.498L1.69531 62.498" stroke="#4798F7"/>
<path id="Line 21" opacity="0.1" d="M53.6953 12.498L6.69531 62.498" stroke="#4798F7"/>
<path id="Line 22" opacity="0.1" d="M58.6953 12.498L11.6953 62.498" stroke="#4798F7"/>
<path id="Line 23" opacity="0.1" d="M63.6953 12.498L16.6953 62.498" stroke="#4798F7"/>
<path id="Line 24" opacity="0.1" d="M68.6953 12.498L21.6953 62.498" stroke="#4798F7"/>
<path id="Line 25" opacity="0.1" d="M73.6953 12.498L26.6953 62.498" stroke="#4798F7"/>
<path id="Line 26" opacity="0.1" d="M78.6953 12.498L31.6953 62.498" stroke="#4798F7"/>
<path id="Line 27" opacity="0.1" d="M83.6953 12.498L36.6953 62.498" stroke="#4798F7"/>
<path id="Line 28" opacity="0.1" d="M88.6953 12.498L41.6953 62.498" stroke="#4798F7"/>
<path id="Line 29" opacity="0.1" d="M93.6953 12.498L46.6953 62.498" stroke="#4798F7"/>
<path id="Line 30" opacity="0.1" d="M98.6953 12.498L51.6953 62.498" stroke="#4798F7"/>
<path id="Line 31" opacity="0.1" d="M103.695 12.498L56.6953 62.498" stroke="#4798F7"/>
<path id="Line 32" opacity="0.1" d="M108.695 12.498L61.6953 62.498" stroke="#4798F7"/>
<path id="Line 33" opacity="0.1" d="M113.695 12.498L66.6953 62.498" stroke="#4798F7"/>
<path id="Line 34" opacity="0.1" d="M118.695 12.498L71.6953 62.498" stroke="#4798F7"/>
<path id="Line 35" opacity="0.1" d="M123.695 12.498L76.6953 62.498" stroke="#4798F7"/>
<path id="Line 36" opacity="0.1" d="M128.695 12.498L81.6953 62.498" stroke="#4798F7"/>
<path id="Line 37" opacity="0.1" d="M133.695 12.498L86.6953 62.498" stroke="#4798F7"/>
<path id="Line 38" opacity="0.1" d="M138.695 12.498L91.6953 62.498" stroke="#4798F7"/>
<path id="Line 39" opacity="0.1" d="M143.695 12.498L96.6953 62.498" stroke="#4798F7"/>
<path id="Line 40" opacity="0.1" d="M148.695 12.498L101.695 62.498" stroke="#4798F7"/>
<path id="Line 41" opacity="0.1" d="M153.695 12.498L106.695 62.498" stroke="#4798F7"/>
<path id="Line 42" opacity="0.1" d="M158.695 12.498L111.695 62.498" stroke="#4798F7"/>
<path id="Line 43" opacity="0.1" d="M163.695 12.498L116.695 62.498" stroke="#4798F7"/>
<path id="Line 44" opacity="0.1" d="M168.695 12.498L121.695 62.498" stroke="#4798F7"/>
<path id="Line 45" opacity="0.1" d="M173.695 12.498L126.695 62.498" stroke="#4798F7"/>
<path id="Line 46" opacity="0.1" d="M178.695 12.498L131.695 62.498" stroke="#4798F7"/>
<path id="Line 47" opacity="0.1" d="M183.695 12.498L136.695 62.498" stroke="#4798F7"/>
<path id="Line 48" opacity="0.1" d="M188.695 12.498L141.695 62.498" stroke="#4798F7"/>
<path id="Line 49" opacity="0.1" d="M193.695 12.498L146.695 62.498" stroke="#4798F7"/>
<path id="Line 50" opacity="0.1" d="M198.695 12.498L151.695 62.498" stroke="#4798F7"/>
<path id="Line 51" opacity="0.1" d="M203.695 12.498L156.695 62.498" stroke="#4798F7"/>
<path id="Line 52" opacity="0.1" d="M208.695 12.498L161.695 62.498" stroke="#4798F7"/>
<path id="Line 53" opacity="0.1" d="M213.695 12.498L166.695 62.498" stroke="#4798F7"/>
<path id="Line 54" opacity="0.1" d="M218.695 12.498L171.695 62.498" stroke="#4798F7"/>
<path id="Line 55" opacity="0.1" d="M223.695 12.498L176.695 62.498" stroke="#4798F7"/>
<path id="Line 56" opacity="0.1" d="M228.695 12.498L181.695 62.498" stroke="#4798F7"/>
<path id="Line 57" opacity="0.1" d="M233.695 12.498L186.695 62.498" stroke="#4798F7"/>
<path id="Line 58" opacity="0.1" d="M238.695 12.498L191.695 62.498" stroke="#4798F7"/>
<path id="Line 59" opacity="0.1" d="M243.695 12.498L196.695 62.498" stroke="#4798F7"/>
<path id="Line 60" opacity="0.1" d="M248.695 12.498L201.695 62.498" stroke="#4798F7"/>
<path id="Line 61" opacity="0.1" d="M253.695 12.498L206.695 62.498" stroke="#4798F7"/>
<path id="Line 62" opacity="0.1" d="M258.695 12.498L211.695 62.498" stroke="#4798F7"/>
<path id="Line 63" opacity="0.1" d="M263.695 12.498L216.695 62.498" stroke="#4798F7"/>
<path id="Line 64" opacity="0.1" d="M268.695 12.498L221.695 62.498" stroke="#4798F7"/>
<path id="Line 65" opacity="0.1" d="M273.695 12.498L226.695 62.498" stroke="#4798F7"/>
<path id="Line 66" opacity="0.1" d="M278.695 12.498L231.695 62.498" stroke="#4798F7"/>
<path id="Line 67" opacity="0.1" d="M283.695 12.498L236.695 62.498" stroke="#4798F7"/>
<path id="Line 68" opacity="0.1" d="M288.695 12.498L241.695 62.498" stroke="#4798F7"/>
<path id="Line 69" opacity="0.1" d="M293.695 12.498L246.695 62.498" stroke="#4798F7"/>
<path id="Line 70" opacity="0.1" d="M298.695 12.498L251.695 62.498" stroke="#4798F7"/>
<path id="Line 71" opacity="0.1" d="M303.695 12.498L256.695 62.498" stroke="#4798F7"/>
<path id="Line 72" opacity="0.1" d="M308.695 12.498L261.695 62.498" stroke="#4798F7"/>
<path id="Line 73" opacity="0.1" d="M313.695 12.498L266.695 62.498" stroke="#4798F7"/>
<path id="Line 74" opacity="0.1" d="M318.695 12.498L271.695 62.498" stroke="#4798F7"/>
<path id="Line 75" opacity="0.1" d="M323.695 12.498L276.695 62.498" stroke="#4798F7"/>
<path id="Line 76" opacity="0.1" d="M328.695 12.498L281.695 62.498" stroke="#4798F7"/>
<path id="Line 77" opacity="0.1" d="M333.695 12.498L286.695 62.498" stroke="#4798F7"/>
<path id="Line 78" opacity="0.1" d="M338.695 12.498L291.695 62.498" stroke="#4798F7"/>
<path id="Line 79" opacity="0.1" d="M343.695 12.498L296.695 62.498" stroke="#4798F7"/>
<path id="Line 80" opacity="0.1" d="M348.695 12.498L301.695 62.498" stroke="#4798F7"/>
<path id="Line 81" opacity="0.1" d="M353.695 12.498L306.695 62.498" stroke="#4798F7"/>
<path id="Line 82" opacity="0.1" d="M358.695 12.498L311.695 62.498" stroke="#4798F7"/>
<path id="Line 83" opacity="0.1" d="M363.695 12.498L316.695 62.498" stroke="#4798F7"/>
<path id="Line 84" opacity="0.1" d="M368.695 12.498L321.695 62.498" stroke="#4798F7"/>
<path id="Line 85" opacity="0.1" d="M373.695 12.498L326.695 62.498" stroke="#4798F7"/>
<path id="Line 86" opacity="0.1" d="M378.695 12.498L331.695 62.498" stroke="#4798F7"/>
<path id="Line 87" opacity="0.1" d="M383.695 12.498L336.695 62.498" stroke="#4798F7"/>
<path id="Line 88" opacity="0.1" d="M388.695 12.498L341.695 62.498" stroke="#4798F7"/>
<path id="Line 89" opacity="0.1" d="M393.695 12.498L346.695 62.498" stroke="#4798F7"/>
<path id="Line 90" opacity="0.1" d="M398.695 12.498L351.695 62.498" stroke="#4798F7"/>
<path id="Line 91" opacity="0.1" d="M403.695 12.498L356.695 62.498" stroke="#4798F7"/>
<path id="Line 92" opacity="0.1" d="M408.695 12.498L361.695 62.498" stroke="#4798F7"/>
<path id="Line 93" opacity="0.1" d="M413.695 12.498L366.695 62.498" stroke="#4798F7"/>
</g>
</g>
</g>
<g id="Rectangle 34624172" filter="url(#filter0_d_18851_1407)">
<path d="M8 21V35.5682L8.23007 35.2155C9.70689 32.951 12.2275 31.5856 14.931 31.5856H47.48V13H16C11.5817 13 8 16.5817 8 21Z" fill="url(#paint2_linear_18851_1407)" shape-rendering="crispEdges"/>
<path d="M14.931 31.0856C12.4436 31.0856 10.102 32.1734 8.5 34.0273V21C8.5 16.8579 11.8579 13.5 16 13.5H46.98V31.0856H14.931Z" stroke="url(#paint3_linear_18851_1407)" stroke-opacity="0.5" shape-rendering="crispEdges"/>
</g>
<rect id="Rectangle 34624175" x="312.435" y="14" width="7.82609" height="2" fill="#3DFFFF"/>
</g>
<path id="Rectangle 34624176" d="M8.75 43.1111C8.75 36.8979 13.7868 31.8611 20 31.8611H251.441C253.054 31.8611 254.653 31.5551 256.151 30.9592L277.316 22.5458C278.638 22.02 280.048 21.75 281.472 21.75H356C362.213 21.75 367.25 26.7868 367.25 33V149C367.25 155.213 362.213 160.25 356 160.25H20C13.7868 160.25 8.75 155.213 8.75 149V109.059C8.75 108.616 8.98378 108.207 9.36475 107.982L10.528 107.296C11.3661 106.801 11.8804 105.901 11.8804 104.927V101.111V91.1944V87.3782C11.8804 86.405 11.3661 85.5043 10.528 85.0098L9.36475 84.3234C8.98378 84.0986 8.75 83.6892 8.75 83.2468V50.9444V43.1111Z" fill="url(#paint4_linear_18851_1407)" stroke="url(#paint5_linear_18851_1407)" stroke-width="1.5"/>
<g id="Rectangle 34624177">
<path d="M12.8541 54.8304C12.8724 49.5967 17.1203 45.3636 22.354 45.3636H251.496C252.904 45.3636 254.302 45.1256 255.631 44.6597L277.058 37.1476C278.28 36.719 279.567 36.5 280.863 36.5H353.968C359.226 36.5 363.485 40.7721 363.467 46.0308L363.143 146.031C363.126 151.265 358.878 155.5 353.643 155.5H22.035C16.7754 155.5 12.5167 151.226 12.5351 145.967L12.8541 54.8304Z" stroke="url(#paint6_linear_18851_1407)"/>
<path d="M12.8541 54.8304C12.8724 49.5967 17.1203 45.3636 22.354 45.3636H251.496C252.904 45.3636 254.302 45.1256 255.631 44.6597L277.058 37.1476C278.28 36.719 279.567 36.5 280.863 36.5H353.968C359.226 36.5 363.485 40.7721 363.467 46.0308L363.143 146.031C363.126 151.265 358.878 155.5 353.643 155.5H22.035C16.7754 155.5 12.5167 151.226 12.5351 145.967L12.8541 54.8304Z" stroke="url(#paint7_linear_18851_1407)"/>
</g>
<path id="Subtract" fill-rule="evenodd" clip-rule="evenodd" d="M286.893 29.6203H350.703C350.982 28.1282 352.273 27 353.824 27C355.578 27 357 28.4438 357 30.2249C357 32.006 355.578 33.4499 353.824 33.4499C352.273 33.4499 350.982 32.3217 350.703 30.8296H286.893C284.865 30.8296 282.897 31.5305 281.313 32.8172L268.218 43.6093C267.958 43.8237 267.572 43.7822 267.364 43.5174C267.162 43.2608 267.202 42.8895 267.454 42.6817L280.569 31.8728C282.364 30.4147 284.594 29.6203 286.893 29.6203ZM353.824 31.8374C354.701 31.8374 355.412 31.1155 355.412 30.2249C355.412 29.3344 354.701 28.6125 353.824 28.6125C352.947 28.6125 352.236 29.3344 352.236 30.2249C352.236 31.1155 352.947 31.8374 353.824 31.8374Z" fill="url(#paint8_linear_18851_1407)"/>
<g id="Group 427318987">
<g id="Ellipse 2026" filter="url(#filter1_d_18851_1407)">
<ellipse cx="8.37071" cy="92.6643" rx="0.370705" ry="0.370371" fill="#3DFFFF"/>
</g>
<ellipse id="Ellipse 2027" cx="8.37071" cy="94.8909" rx="0.370705" ry="0.370371" fill="#3DFFFF"/>
<ellipse id="Ellipse 2028" cx="8.37071" cy="97.1087" rx="0.370705" ry="0.370371" fill="#3DFFFF"/>
<ellipse id="Ellipse 2029" cx="8.37071" cy="99.3342" rx="0.370705" ry="0.370371" fill="#3DFFFF"/>
<path id="Vector 118" d="M8.36914 92.6667V87.4815L9.48126 86" stroke="url(#paint9_linear_18851_1407)"/>
<path id="Vector 119" d="M8.36914 99.3333V104.519L9.48126 106" stroke="url(#paint10_linear_18851_1407)"/>
</g>
<g id="Group 427318985">
<g id="Rectangle 34624158" filter="url(#filter2_f_18851_1407)">
<path d="M124 12.666L24 12.666L24 25.9993L124 25.9993L124 12.666Z" fill="url(#paint11_linear_18851_1407)"/>
</g>
<g id="Rectangle 34624159" filter="url(#filter3_f_18851_1407)">
<path d="M124 14.3335L24 14.3335L24 19.3335L124 19.3335L124 14.3335Z" fill="url(#paint12_linear_18851_1407)"/>
</g>
<g id="Rectangle 34624160" filter="url(#filter4_f_18851_1407)">
<path d="M124 16.0005L24 16.0005L24 18.5005L124 18.5005L124 16.0005Z" fill="url(#paint13_linear_18851_1407)"/>
</g>
<g id="Rectangle 34624161" filter="url(#filter5_f_18851_1407)">
<path d="M124 16.0005L24 16.0005L24 16.8338L124 16.8338L124 16.0005Z" fill="url(#paint14_linear_18851_1407)"/>
</g>
<path id="Rectangle 34624162" d="M124 16.0005L24 16.0005L24 16.8338L124 16.8338L124 16.0005Z" fill="url(#paint15_linear_18851_1407)"/>
</g>
<g id="Group 427318992">
<g id="Group 427318991">
<g id="Rectangle 6531" filter="url(#filter6_d_18851_1407)">
<path d="M29 19H24.3042C23.9595 19 23.639 19.1776 23.4562 19.47L20 25H24.6958C25.0405 25 25.361 24.8224 25.5438 24.53L29 19Z" fill="url(#paint16_linear_18851_1407)"/>
</g>
<g id="Rectangle 6532" filter="url(#filter7_d_18851_1407)">
<path d="M38 19H33.3042C32.9595 19 32.639 19.1776 32.4562 19.47L29 25H33.6958C34.0405 25 34.361 24.8224 34.5438 24.53L38 19Z" fill="url(#paint17_linear_18851_1407)"/>
</g>
</g>
</g>
</g>
<defs>
<filter id="filter0_d_18851_1407" x="0" y="9" width="55.4805" height="38.5684" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_18851_1407"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_18851_1407" result="shape"/>
</filter>
<filter id="filter1_d_18851_1407" x="7" y="91.2939" width="2.74121" height="2.74072" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.239216 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_18851_1407"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_18851_1407" result="shape"/>
</filter>
<filter id="filter2_f_18851_1407" x="12" y="0.666016" width="124" height="37.3335" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="6" result="effect1_foregroundBlur_18851_1407"/>
</filter>
<filter id="filter3_f_18851_1407" x="20" y="10.3335" width="108" height="13" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2" result="effect1_foregroundBlur_18851_1407"/>
</filter>
<filter id="filter4_f_18851_1407" x="22" y="14.0005" width="104" height="6.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1" result="effect1_foregroundBlur_18851_1407"/>
</filter>
<filter id="filter5_f_18851_1407" x="22" y="14.0005" width="104" height="4.8335" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1" result="effect1_foregroundBlur_18851_1407"/>
</filter>
<filter id="filter6_d_18851_1407" x="13" y="12" width="23" height="20" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="3.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.278431 0 0 0 0 0.596078 0 0 0 0 0.968627 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_18851_1407"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_18851_1407" result="shape"/>
</filter>
<filter id="filter7_d_18851_1407" x="22" y="12" width="23" height="20" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="3.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.533333 0 0 0 0 0.870588 0 0 0 0 0.870588 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_18851_1407"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_18851_1407" result="shape"/>
</filter>
<linearGradient id="paint0_linear_18851_1407" x1="8" y1="24.5" x2="368" y2="24.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#0053AD"/>
<stop offset="0.717391" stop-color="#023164"/>
<stop offset="1" stop-color="#023164" stop-opacity="0.5"/>
</linearGradient>
<linearGradient id="paint1_linear_18851_1407" x1="8" y1="24.5" x2="368" y2="24.5" gradientUnits="userSpaceOnUse">
<stop offset="0.73913" stop-color="#023164"/>
<stop offset="1" stop-color="#023164" stop-opacity="0.5"/>
</linearGradient>
<linearGradient id="paint2_linear_18851_1407" x1="8" y1="28.2668" x2="47.48" y2="28.2668" gradientUnits="userSpaceOnUse">
<stop stop-color="#0051AA" stop-opacity="0.6"/>
<stop offset="1" stop-color="#023164" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint3_linear_18851_1407" x1="10.3376" y1="44.5292" x2="52.0872" y2="30.4644" gradientUnits="userSpaceOnUse">
<stop stop-color="#3DFFFF" stop-opacity="0"/>
<stop offset="0.12113" stop-color="#43BDFA" stop-opacity="0.7"/>
<stop offset="0.201379" stop-color="#91DEFF"/>
<stop offset="0.279659" stop-color="#4798F7" stop-opacity="0.616509"/>
<stop offset="0.835558" stop-color="#4798F7" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint4_linear_18851_1407" x1="8" y1="71.9443" x2="368" y2="71.9443" gradientUnits="userSpaceOnUse">
<stop stop-color="#003977" stop-opacity="0.9"/>
<stop offset="1" stop-color="#023164" stop-opacity="0.6"/>
</linearGradient>
<linearGradient id="paint5_linear_18851_1407" x1="8" y1="65.7222" x2="368" y2="65.7222" gradientUnits="userSpaceOnUse">
<stop stop-color="#4798F7" stop-opacity="0.6"/>
<stop offset="1" stop-color="#4798F7" stop-opacity="0.2"/>
</linearGradient>
<linearGradient id="paint6_linear_18851_1407" x1="360.888" y1="154.977" x2="359.663" y2="152.492" gradientUnits="userSpaceOnUse">
<stop stop-color="#3DFFFF"/>
<stop offset="1" stop-color="#3DFFFF" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint7_linear_18851_1407" x1="14.3337" y1="155.148" x2="15.9317" y2="151.805" gradientUnits="userSpaceOnUse">
<stop stop-color="#3DFFFF"/>
<stop offset="1" stop-color="#3DFFFF" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint8_linear_18851_1407" x1="275.701" y1="44.144" x2="363.046" y2="44.144" gradientUnits="userSpaceOnUse">
<stop stop-color="#3DFFFF"/>
<stop offset="1" stop-color="#4798F7"/>
</linearGradient>
<linearGradient id="paint9_linear_18851_1407" x1="8.9252" y1="86" x2="8.9252" y2="92.6667" gradientUnits="userSpaceOnUse">
<stop stop-color="#2B6BB4"/>
<stop offset="1" stop-color="#3FFFFF"/>
</linearGradient>
<linearGradient id="paint10_linear_18851_1407" x1="8.9252" y1="106" x2="8.9252" y2="99.3333" gradientUnits="userSpaceOnUse">
<stop stop-color="#2B6BB4"/>
<stop offset="1" stop-color="#3FFFFF"/>
</linearGradient>
<linearGradient id="paint11_linear_18851_1407" x1="124" y1="19.3329" x2="24" y2="19.3329" gradientUnits="userSpaceOnUse">
<stop stop-color="#4798F7" stop-opacity="0"/>
<stop offset="0.5" stop-color="#4798F7" stop-opacity="0.6"/>
<stop offset="1" stop-color="#4798F7" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint12_linear_18851_1407" x1="124" y1="16.0007" x2="24" y2="16.0007" gradientUnits="userSpaceOnUse">
<stop stop-color="#4798F7" stop-opacity="0"/>
<stop offset="0.5" stop-color="#4798F7" stop-opacity="0.7"/>
<stop offset="1" stop-color="#4798F7" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint13_linear_18851_1407" x1="124" y1="16.8341" x2="24" y2="16.8341" gradientUnits="userSpaceOnUse">
<stop stop-color="#4798F7" stop-opacity="0"/>
<stop offset="0.5" stop-color="#4798F7" stop-opacity="0.8"/>
<stop offset="1" stop-color="#4798F7" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint14_linear_18851_1407" x1="124" y1="16.2783" x2="24" y2="16.2784" gradientUnits="userSpaceOnUse">
<stop stop-color="#3DFFFF" stop-opacity="0"/>
<stop offset="0.5" stop-color="#3DFFFF"/>
<stop offset="1" stop-color="#3DFFFF" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint15_linear_18851_1407" x1="124" y1="16.2783" x2="24" y2="16.2784" gradientUnits="userSpaceOnUse">
<stop stop-color="#3DFFFF" stop-opacity="0"/>
<stop offset="0.5" stop-color="#C8FFFF" stop-opacity="0.8"/>
<stop offset="1" stop-color="#3DFFFF" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint16_linear_18851_1407" x1="29" y1="19" x2="20.375" y2="25" gradientUnits="userSpaceOnUse">
<stop stop-color="#9DCAFF"/>
<stop offset="1" stop-color="#4798F7"/>
</linearGradient>
<linearGradient id="paint17_linear_18851_1407" x1="38" y1="19" x2="29.375" y2="25" gradientUnits="userSpaceOnUse">
<stop stop-color="#A1FFF9"/>
<stop offset="1" stop-color="#00D8DB"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View File

@@ -0,0 +1,297 @@
<svg width="493" height="163" viewBox="0 0 493 163" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="&#229;&#183;&#165;&#229;&#141;&#149;&#231;&#177;&#187;&#229;&#158;&#139;&#231;&#187;&#159;&#232;&#174;&#161;">
<g id="Group 427318988">
<g id="Mask group">
<path id="Rectangle 34624167" opacity="0.5" d="M493 12.3394H13C8.58172 12.3394 5 15.9211 5 20.3394V29.5681L8.27539 28.1775C9.26427 27.7576 10.3276 27.5412 11.4019 27.5412H244.889H335.509C336.967 27.5412 338.414 27.2752 339.778 26.7562L370.264 15.1513C371.627 14.6323 373.074 14.3663 374.533 14.3663H402.826H493V12.3394Z" fill="url(#paint0_linear_18856_1033)"/>
<mask id="mask0_18856_1033" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="5" y="12" width="488" height="18">
<path id="Rectangle 34624168" opacity="0.5" d="M493 12.3394H13C8.58172 12.3394 5 15.9211 5 20.3394V29.5681L8.57025 28.1245C9.52311 27.7393 10.5413 27.5412 11.5691 27.5412H249H340.172C341.263 27.5412 342.343 27.318 343.344 26.8853L370.048 15.3501C371.551 14.7011 373.17 14.3663 374.807 14.3663H402.826H493V12.3394Z" fill="url(#paint1_linear_18856_1033)"/>
</mask>
<g mask="url(#mask0_18856_1033)">
<g id="Frame 427319359">
<path id="Line 15" opacity="0.1" d="M9.57031 11.8306L-37.4297 61.8306" stroke="#4798F7"/>
<path id="Line 16" opacity="0.1" d="M14.5703 11.8306L-32.4297 61.8306" stroke="#4798F7"/>
<path id="Line 17" opacity="0.1" d="M19.5703 11.8306L-27.4297 61.8306" stroke="#4798F7"/>
<path id="Line 18" opacity="0.1" d="M24.5703 11.8306L-22.4297 61.8306" stroke="#4798F7"/>
<path id="Line 19" opacity="0.1" d="M29.5703 11.8306L-17.4297 61.8306" stroke="#4798F7"/>
<path id="Line 20" opacity="0.1" d="M34.5703 11.8306L-12.4297 61.8306" stroke="#4798F7"/>
<path id="Line 21" opacity="0.1" d="M39.5703 11.8306L-7.42969 61.8306" stroke="#4798F7"/>
<path id="Line 22" opacity="0.1" d="M44.5703 11.8306L-2.42969 61.8306" stroke="#4798F7"/>
<path id="Line 23" opacity="0.1" d="M49.5703 11.8306L2.57031 61.8306" stroke="#4798F7"/>
<path id="Line 24" opacity="0.1" d="M54.5703 11.8306L7.57031 61.8306" stroke="#4798F7"/>
<path id="Line 25" opacity="0.1" d="M59.5703 11.8306L12.5703 61.8306" stroke="#4798F7"/>
<path id="Line 26" opacity="0.1" d="M64.5703 11.8306L17.5703 61.8306" stroke="#4798F7"/>
<path id="Line 27" opacity="0.1" d="M69.5703 11.8306L22.5703 61.8306" stroke="#4798F7"/>
<path id="Line 28" opacity="0.1" d="M74.5703 11.8306L27.5703 61.8306" stroke="#4798F7"/>
<path id="Line 29" opacity="0.1" d="M79.5703 11.8306L32.5703 61.8306" stroke="#4798F7"/>
<path id="Line 30" opacity="0.1" d="M84.5703 11.8306L37.5703 61.8306" stroke="#4798F7"/>
<path id="Line 31" opacity="0.1" d="M89.5703 11.8306L42.5703 61.8306" stroke="#4798F7"/>
<path id="Line 32" opacity="0.1" d="M94.5703 11.8306L47.5703 61.8306" stroke="#4798F7"/>
<path id="Line 33" opacity="0.1" d="M99.5703 11.8306L52.5703 61.8306" stroke="#4798F7"/>
<path id="Line 34" opacity="0.1" d="M104.57 11.8306L57.5703 61.8306" stroke="#4798F7"/>
<path id="Line 35" opacity="0.1" d="M109.57 11.8306L62.5703 61.8306" stroke="#4798F7"/>
<path id="Line 36" opacity="0.1" d="M114.57 11.8306L67.5703 61.8306" stroke="#4798F7"/>
<path id="Line 37" opacity="0.1" d="M119.57 11.8306L72.5703 61.8306" stroke="#4798F7"/>
<path id="Line 38" opacity="0.1" d="M124.57 11.8306L77.5703 61.8306" stroke="#4798F7"/>
<path id="Line 39" opacity="0.1" d="M129.57 11.8306L82.5703 61.8306" stroke="#4798F7"/>
<path id="Line 40" opacity="0.1" d="M134.57 11.8306L87.5703 61.8306" stroke="#4798F7"/>
<path id="Line 41" opacity="0.1" d="M139.57 11.8306L92.5703 61.8306" stroke="#4798F7"/>
<path id="Line 42" opacity="0.1" d="M144.57 11.8306L97.5703 61.8306" stroke="#4798F7"/>
<path id="Line 43" opacity="0.1" d="M149.57 11.8306L102.57 61.8306" stroke="#4798F7"/>
<path id="Line 44" opacity="0.1" d="M154.57 11.8306L107.57 61.8306" stroke="#4798F7"/>
<path id="Line 45" opacity="0.1" d="M159.57 11.8306L112.57 61.8306" stroke="#4798F7"/>
<path id="Line 46" opacity="0.1" d="M164.57 11.8306L117.57 61.8306" stroke="#4798F7"/>
<path id="Line 47" opacity="0.1" d="M169.57 11.8306L122.57 61.8306" stroke="#4798F7"/>
<path id="Line 48" opacity="0.1" d="M174.57 11.8306L127.57 61.8306" stroke="#4798F7"/>
<path id="Line 49" opacity="0.1" d="M179.57 11.8306L132.57 61.8306" stroke="#4798F7"/>
<path id="Line 50" opacity="0.1" d="M184.57 11.8306L137.57 61.8306" stroke="#4798F7"/>
<path id="Line 51" opacity="0.1" d="M189.57 11.8306L142.57 61.8306" stroke="#4798F7"/>
<path id="Line 52" opacity="0.1" d="M194.57 11.8306L147.57 61.8306" stroke="#4798F7"/>
<path id="Line 53" opacity="0.1" d="M199.57 11.8306L152.57 61.8306" stroke="#4798F7"/>
<path id="Line 54" opacity="0.1" d="M204.57 11.8306L157.57 61.8306" stroke="#4798F7"/>
<path id="Line 55" opacity="0.1" d="M209.57 11.8306L162.57 61.8306" stroke="#4798F7"/>
<path id="Line 56" opacity="0.1" d="M214.57 11.8306L167.57 61.8306" stroke="#4798F7"/>
<path id="Line 57" opacity="0.1" d="M219.57 11.8306L172.57 61.8306" stroke="#4798F7"/>
<path id="Line 58" opacity="0.1" d="M224.57 11.8306L177.57 61.8306" stroke="#4798F7"/>
<path id="Line 59" opacity="0.1" d="M229.57 11.8306L182.57 61.8306" stroke="#4798F7"/>
<path id="Line 60" opacity="0.1" d="M234.57 11.8306L187.57 61.8306" stroke="#4798F7"/>
<path id="Line 61" opacity="0.1" d="M239.57 11.8306L192.57 61.8306" stroke="#4798F7"/>
<path id="Line 62" opacity="0.1" d="M244.57 11.8306L197.57 61.8306" stroke="#4798F7"/>
<path id="Line 63" opacity="0.1" d="M249.57 11.8306L202.57 61.8306" stroke="#4798F7"/>
<path id="Line 64" opacity="0.1" d="M254.57 11.8306L207.57 61.8306" stroke="#4798F7"/>
<path id="Line 65" opacity="0.1" d="M259.57 11.8306L212.57 61.8306" stroke="#4798F7"/>
<path id="Line 66" opacity="0.1" d="M264.57 11.8306L217.57 61.8306" stroke="#4798F7"/>
<path id="Line 67" opacity="0.1" d="M269.57 11.8306L222.57 61.8306" stroke="#4798F7"/>
<path id="Line 68" opacity="0.1" d="M274.57 11.8306L227.57 61.8306" stroke="#4798F7"/>
<path id="Line 69" opacity="0.1" d="M279.57 11.8306L232.57 61.8306" stroke="#4798F7"/>
<path id="Line 70" opacity="0.1" d="M284.57 11.8306L237.57 61.8306" stroke="#4798F7"/>
<path id="Line 71" opacity="0.1" d="M289.57 11.8306L242.57 61.8306" stroke="#4798F7"/>
<path id="Line 72" opacity="0.1" d="M294.57 11.8306L247.57 61.8306" stroke="#4798F7"/>
<path id="Line 73" opacity="0.1" d="M299.57 11.8306L252.57 61.8306" stroke="#4798F7"/>
<path id="Line 74" opacity="0.1" d="M304.57 11.8306L257.57 61.8306" stroke="#4798F7"/>
<path id="Line 75" opacity="0.1" d="M309.57 11.8306L262.57 61.8306" stroke="#4798F7"/>
<path id="Line 76" opacity="0.1" d="M314.57 11.8306L267.57 61.8306" stroke="#4798F7"/>
<path id="Line 77" opacity="0.1" d="M319.57 11.8306L272.57 61.8306" stroke="#4798F7"/>
<path id="Line 78" opacity="0.1" d="M324.57 11.8306L277.57 61.8306" stroke="#4798F7"/>
<path id="Line 79" opacity="0.1" d="M329.57 11.8306L282.57 61.8306" stroke="#4798F7"/>
<path id="Line 80" opacity="0.1" d="M334.57 11.8306L287.57 61.8306" stroke="#4798F7"/>
<path id="Line 81" opacity="0.1" d="M339.57 11.8306L292.57 61.8306" stroke="#4798F7"/>
<path id="Line 82" opacity="0.1" d="M344.57 11.8306L297.57 61.8306" stroke="#4798F7"/>
<path id="Line 83" opacity="0.1" d="M349.57 11.8306L302.57 61.8306" stroke="#4798F7"/>
<path id="Line 84" opacity="0.1" d="M354.57 11.8306L307.57 61.8306" stroke="#4798F7"/>
<path id="Line 85" opacity="0.1" d="M359.57 11.8306L312.57 61.8306" stroke="#4798F7"/>
<path id="Line 86" opacity="0.1" d="M364.57 11.8306L317.57 61.8306" stroke="#4798F7"/>
<path id="Line 87" opacity="0.1" d="M369.57 11.8306L322.57 61.8306" stroke="#4798F7"/>
<path id="Line 88" opacity="0.1" d="M374.57 11.8306L327.57 61.8306" stroke="#4798F7"/>
<path id="Line 89" opacity="0.1" d="M379.57 11.8306L332.57 61.8306" stroke="#4798F7"/>
<path id="Line 90" opacity="0.1" d="M384.57 11.8306L337.57 61.8306" stroke="#4798F7"/>
<path id="Line 91" opacity="0.1" d="M389.57 11.8306L342.57 61.8306" stroke="#4798F7"/>
<path id="Line 92" opacity="0.1" d="M394.57 11.8306L347.57 61.8306" stroke="#4798F7"/>
<path id="Line 93" opacity="0.1" d="M399.57 11.8306L352.57 61.8306" stroke="#4798F7"/>
<path id="Line 94" opacity="0.1" d="M404.57 11.8306L357.57 61.8306" stroke="#4798F7"/>
<path id="Line 95" opacity="0.1" d="M409.57 11.8306L362.57 61.8306" stroke="#4798F7"/>
<path id="Line 96" opacity="0.1" d="M414.57 11.8306L367.57 61.8306" stroke="#4798F7"/>
<path id="Line 97" opacity="0.1" d="M419.57 11.8306L372.57 61.8306" stroke="#4798F7"/>
<path id="Line 98" opacity="0.1" d="M424.57 11.8306L377.57 61.8306" stroke="#4798F7"/>
<path id="Line 99" opacity="0.1" d="M429.57 11.8306L382.57 61.8306" stroke="#4798F7"/>
<path id="Line 100" opacity="0.1" d="M434.57 11.8306L387.57 61.8306" stroke="#4798F7"/>
<path id="Line 101" opacity="0.1" d="M439.57 11.8306L392.57 61.8306" stroke="#4798F7"/>
<path id="Line 102" opacity="0.1" d="M444.57 11.8306L397.57 61.8306" stroke="#4798F7"/>
<path id="Line 103" opacity="0.1" d="M449.57 11.8306L402.57 61.8306" stroke="#4798F7"/>
<path id="Line 104" opacity="0.1" d="M454.57 11.8306L407.57 61.8306" stroke="#4798F7"/>
<path id="Line 105" opacity="0.1" d="M459.57 11.8306L412.57 61.8306" stroke="#4798F7"/>
<path id="Line 106" opacity="0.1" d="M464.57 11.8306L417.57 61.8306" stroke="#4798F7"/>
</g>
</g>
</g>
<g id="Rectangle 34624172" filter="url(#filter0_d_18856_1033)">
<path d="M8.47363 21.3057V35.2114L8.62745 34.9755C10.1043 32.7111 12.6249 31.3457 15.3283 31.3457H46.7946V13.3057H16.4736C12.0554 13.3057 8.47363 16.8874 8.47363 21.3057Z" fill="url(#paint2_linear_18856_1033)" shape-rendering="crispEdges"/>
<path d="M15.3283 30.8457C12.8802 30.8457 10.5732 31.8995 8.97363 33.7003V21.3057C8.97363 17.1635 12.3315 13.8057 16.4736 13.8057H46.2946V30.8457H15.3283Z" stroke="url(#paint3_linear_18856_1033)" stroke-opacity="0.5" shape-rendering="crispEdges"/>
</g>
<rect id="Rectangle 34624175" x="417.685" y="13.3521" width="10.6087" height="2.02691" fill="#3DFFFF"/>
</g>
<path id="Rectangle 34624176" d="M5.75 42.694C5.75 36.4808 10.7868 31.444 17 31.444H336.368C337.597 31.444 338.821 31.266 340 30.9156L371.132 21.663C372.172 21.3538 373.252 21.1968 374.337 21.1968H481C487.213 21.1968 492.25 26.2336 492.25 32.4468V150.331C492.25 156.544 487.213 161.581 481 161.581H17C10.7868 161.581 5.75 156.544 5.75 150.331V109.837C5.75 109.342 6.04236 108.893 6.49546 108.693L8.35347 107.873C9.35028 107.434 9.99348 106.447 9.99348 105.357V101.636V91.5859V87.8644C9.99348 86.7749 9.35028 85.7882 8.35347 85.3484L6.49546 84.5287C6.04236 84.3288 5.75 83.8803 5.75 83.3851V50.7942V42.694Z" fill="url(#paint4_linear_18856_1033)" stroke="url(#paint5_linear_18856_1033)" stroke-width="1.5"/>
<g id="Rectangle 34624177">
<path d="M11.3586 54.5864C11.3831 49.3571 15.6291 45.1309 20.8585 45.1309H336.25C337.32 45.1309 338.385 44.9936 339.419 44.7224L370.698 36.5237C371.65 36.2742 372.63 36.1479 373.614 36.1479H477.491C482.753 36.1479 487.013 40.4264 486.991 45.6891L486.55 147.304C486.527 152.535 482.281 156.763 477.05 156.763H20.4248C15.1608 156.763 10.9003 152.482 10.925 147.218L11.3586 54.5864Z" stroke="url(#paint6_linear_18856_1033)"/>
<path d="M11.3586 54.5864C11.3831 49.3571 15.6291 45.1309 20.8585 45.1309H336.25C337.32 45.1309 338.385 44.9936 339.419 44.7224L370.698 36.5237C371.65 36.2742 372.63 36.1479 373.614 36.1479H477.491C482.753 36.1479 487.013 40.4264 486.991 45.6891L486.55 147.304C486.527 152.535 482.281 156.763 477.05 156.763H20.4248C15.1608 156.763 10.9003 152.482 10.925 147.218L11.3586 54.5864Z" stroke="url(#paint7_linear_18856_1033)"/>
</g>
<path id="Subtract" fill-rule="evenodd" clip-rule="evenodd" d="M387.609 28.2556H462.285C462.611 26.5041 464.122 25.1797 465.937 25.1797C467.989 25.1797 469.654 26.8746 469.654 28.9654C469.654 31.0563 467.989 32.7512 465.937 32.7512C464.122 32.7512 462.611 31.4268 462.285 29.6753H387.609C385.235 29.6753 382.932 30.4981 381.078 32.0085L365.756 44.6757C365.45 44.9281 364.997 44.8794 364.753 44.5679C364.516 44.2673 364.563 43.8332 364.858 43.5896L380.207 30.8999C382.308 29.1882 384.918 28.2556 387.609 28.2556ZM465.937 30.8583C466.963 30.8583 467.795 30.0109 467.795 28.9654C467.795 27.92 466.963 27.0726 465.937 27.0726C464.91 27.0726 464.078 27.92 464.078 28.9654C464.078 30.0109 464.91 30.8583 465.937 30.8583Z" fill="url(#paint8_linear_18856_1033)"/>
<g id="Group 427318987">
<g id="Ellipse 2026" filter="url(#filter1_d_18856_1033)">
<ellipse cx="5.53423" cy="92.6995" rx="0.417045" ry="0.431408" fill="#3DFFFF"/>
</g>
<ellipse id="Ellipse 2027" cx="5.53423" cy="95.3626" rx="0.417045" ry="0.431408" fill="#3DFFFF"/>
<ellipse id="Ellipse 2028" cx="5.53423" cy="98.0169" rx="0.417045" ry="0.431408" fill="#3DFFFF"/>
<ellipse id="Ellipse 2029" cx="5.53423" cy="100.684" rx="0.417045" ry="0.431408" fill="#3DFFFF"/>
<path id="Vector 118" d="M5.61426 92.7141V86.5061L6.89038 84.7324" stroke="url(#paint9_linear_18856_1033)"/>
<path id="Vector 119" d="M5.61523 100.689V106.897L6.89135 108.671" stroke="url(#paint10_linear_18856_1033)"/>
</g>
<g id="Group 427318985">
<g id="Rectangle 34624158" filter="url(#filter2_f_18856_1033)">
<path d="M153.065 12.0029L35.8563 12.0029L35.8563 27.6308L153.065 27.6308L153.065 12.0029Z" fill="url(#paint11_linear_18856_1033)"/>
</g>
<g id="Rectangle 34624159" filter="url(#filter3_f_18856_1033)">
<path d="M153.065 13.96L35.8563 13.96L35.8563 19.8204L153.065 19.8204L153.065 13.96Z" fill="url(#paint12_linear_18856_1033)"/>
</g>
<g id="Rectangle 34624160" filter="url(#filter4_f_18856_1033)">
<path d="M153.065 15.9121L35.8563 15.9121L35.8563 18.8423L153.065 18.8423L153.065 15.9121Z" fill="url(#paint13_linear_18856_1033)"/>
</g>
<g id="Rectangle 34624161" filter="url(#filter5_f_18856_1033)">
<path d="M153.065 15.9121L35.8563 15.9121L35.8563 16.8889L153.065 16.8888L153.065 15.9121Z" fill="url(#paint14_linear_18856_1033)"/>
</g>
<path id="Rectangle 34624162" d="M153.065 15.9121L35.8563 15.9121L35.8563 16.8889L153.065 16.8888L153.065 15.9121Z" fill="url(#paint15_linear_18856_1033)"/>
</g>
<g id="Group 427318992">
<g id="Group 427318991">
<g id="Rectangle 6531" filter="url(#filter6_d_18856_1033)">
<path d="M33.4492 17.9438H27.8488C27.5041 17.9438 27.1837 18.1214 27.0009 18.4137L22.8986 24.9752H28.499C28.8437 24.9752 29.1641 24.7976 29.3469 24.5053L33.4492 17.9438Z" fill="url(#paint16_linear_18856_1033)"/>
</g>
<g id="Rectangle 6532" filter="url(#filter7_d_18856_1033)">
<path d="M43.9932 17.9438H38.3928C38.048 17.9438 37.7276 18.1214 37.5448 18.4137L33.4425 24.9752H39.0429C39.3876 24.9752 39.7081 24.7976 39.8908 24.5053L43.9932 17.9438Z" fill="url(#paint17_linear_18856_1033)"/>
</g>
</g>
</g>
</g>
<defs>
<filter id="filter0_d_18856_1033" x="0.473633" y="9.30566" width="54.3213" height="37.9058" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_18856_1033"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_18856_1033" result="shape"/>
</filter>
<filter id="filter1_d_18856_1033" x="4.11719" y="91.2681" width="2.83398" height="2.86279" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.239216 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_18856_1033"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_18856_1033" result="shape"/>
</filter>
<filter id="filter2_f_18856_1033" x="23.8564" y="0.00292969" width="141.209" height="39.6279" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="6" result="effect1_foregroundBlur_18856_1033"/>
</filter>
<filter id="filter3_f_18856_1033" x="31.8564" y="9.95996" width="125.209" height="13.8604" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2" result="effect1_foregroundBlur_18856_1033"/>
</filter>
<filter id="filter4_f_18856_1033" x="33.8564" y="13.9121" width="121.209" height="6.93018" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1" result="effect1_foregroundBlur_18856_1033"/>
</filter>
<filter id="filter5_f_18856_1033" x="33.8564" y="13.9121" width="121.209" height="4.97656" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1" result="effect1_foregroundBlur_18856_1033"/>
</filter>
<filter id="filter6_d_18856_1033" x="15.8984" y="10.9438" width="24.5508" height="21.0312" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="3.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.278431 0 0 0 0 0.596078 0 0 0 0 0.968627 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_18856_1033"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_18856_1033" result="shape"/>
</filter>
<filter id="filter7_d_18856_1033" x="26.4424" y="10.9438" width="24.5508" height="21.0312" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="3.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.533333 0 0 0 0 0.870588 0 0 0 0 0.870588 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_18856_1033"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_18856_1033" result="shape"/>
</filter>
<linearGradient id="paint0_linear_18856_1033" x1="5" y1="23.9941" x2="493" y2="23.9941" gradientUnits="userSpaceOnUse">
<stop stop-color="#0053AD"/>
<stop offset="0.717391" stop-color="#023164"/>
<stop offset="1" stop-color="#023164" stop-opacity="0.5"/>
</linearGradient>
<linearGradient id="paint1_linear_18856_1033" x1="5" y1="23.9941" x2="493" y2="23.9941" gradientUnits="userSpaceOnUse">
<stop offset="0.73913" stop-color="#023164"/>
<stop offset="1" stop-color="#023164" stop-opacity="0.5"/>
</linearGradient>
<linearGradient id="paint2_linear_18856_1033" x1="8.47363" y1="28.1242" x2="46.7946" y2="28.1242" gradientUnits="userSpaceOnUse">
<stop stop-color="#0051AA" stop-opacity="0.6"/>
<stop offset="1" stop-color="#023164" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint3_linear_18856_1033" x1="10.7426" y1="43.9092" x2="51.2665" y2="30.2573" gradientUnits="userSpaceOnUse">
<stop stop-color="#3DFFFF" stop-opacity="0"/>
<stop offset="0.12113" stop-color="#43BDFA" stop-opacity="0.7"/>
<stop offset="0.201379" stop-color="#91DEFF"/>
<stop offset="0.279659" stop-color="#4798F7" stop-opacity="0.616509"/>
<stop offset="0.835558" stop-color="#4798F7" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint4_linear_18856_1033" x1="5" y1="72.0767" x2="493" y2="72.0767" gradientUnits="userSpaceOnUse">
<stop stop-color="#003977" stop-opacity="0.9"/>
<stop offset="1" stop-color="#023164" stop-opacity="0.6"/>
</linearGradient>
<linearGradient id="paint5_linear_18856_1033" x1="5" y1="65.7708" x2="493" y2="65.7709" gradientUnits="userSpaceOnUse">
<stop stop-color="#4798F7" stop-opacity="0.6"/>
<stop offset="1" stop-color="#4798F7" stop-opacity="0.2"/>
</linearGradient>
<linearGradient id="paint6_linear_18856_1033" x1="483.316" y1="156.226" x2="482.3" y2="153.47" gradientUnits="userSpaceOnUse">
<stop stop-color="#3DFFFF"/>
<stop offset="1" stop-color="#3DFFFF" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint7_linear_18856_1033" x1="13.5414" y1="156.399" x2="14.8604" y2="152.709" gradientUnits="userSpaceOnUse">
<stop stop-color="#3DFFFF"/>
<stop offset="1" stop-color="#3DFFFF" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint8_linear_18856_1033" x1="374.511" y1="45.3051" x2="476.729" y2="45.3051" gradientUnits="userSpaceOnUse">
<stop stop-color="#3DFFFF"/>
<stop offset="1" stop-color="#4798F7"/>
</linearGradient>
<linearGradient id="paint9_linear_18856_1033" x1="6.25232" y1="84.7324" x2="6.25232" y2="92.7141" gradientUnits="userSpaceOnUse">
<stop stop-color="#2B6BB4"/>
<stop offset="1" stop-color="#3FFFFF"/>
</linearGradient>
<linearGradient id="paint10_linear_18856_1033" x1="6.25329" y1="108.671" x2="6.25329" y2="100.689" gradientUnits="userSpaceOnUse">
<stop stop-color="#2B6BB4"/>
<stop offset="1" stop-color="#3FFFFF"/>
</linearGradient>
<linearGradient id="paint11_linear_18856_1033" x1="153.065" y1="19.8171" x2="35.8563" y2="19.8171" gradientUnits="userSpaceOnUse">
<stop stop-color="#4798F7" stop-opacity="0"/>
<stop offset="0.5" stop-color="#4798F7" stop-opacity="0.6"/>
<stop offset="1" stop-color="#4798F7" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint12_linear_18856_1033" x1="153.065" y1="15.914" x2="35.8563" y2="15.9141" gradientUnits="userSpaceOnUse">
<stop stop-color="#4798F7" stop-opacity="0"/>
<stop offset="0.5" stop-color="#4798F7" stop-opacity="0.7"/>
<stop offset="1" stop-color="#4798F7" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint13_linear_18856_1033" x1="153.065" y1="16.8891" x2="35.8563" y2="16.8892" gradientUnits="userSpaceOnUse">
<stop stop-color="#4798F7" stop-opacity="0"/>
<stop offset="0.5" stop-color="#4798F7" stop-opacity="0.8"/>
<stop offset="1" stop-color="#4798F7" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint14_linear_18856_1033" x1="153.065" y1="16.2378" x2="35.8563" y2="16.2378" gradientUnits="userSpaceOnUse">
<stop stop-color="#3DFFFF" stop-opacity="0"/>
<stop offset="0.5" stop-color="#3DFFFF"/>
<stop offset="1" stop-color="#3DFFFF" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint15_linear_18856_1033" x1="153.065" y1="16.2378" x2="35.8563" y2="16.2378" gradientUnits="userSpaceOnUse">
<stop stop-color="#3DFFFF" stop-opacity="0"/>
<stop offset="0.5" stop-color="#C8FFFF" stop-opacity="0.8"/>
<stop offset="1" stop-color="#3DFFFF" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint16_linear_18856_1033" x1="33.4492" y1="17.9438" x2="23.3405" y2="24.9785" gradientUnits="userSpaceOnUse">
<stop stop-color="#9DCAFF"/>
<stop offset="1" stop-color="#4798F7"/>
</linearGradient>
<linearGradient id="paint17_linear_18856_1033" x1="43.9932" y1="17.9438" x2="33.8844" y2="24.9785" gradientUnits="userSpaceOnUse">
<stop stop-color="#A1FFF9"/>
<stop offset="1" stop-color="#00D8DB"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +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="1677138232065" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4115" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 93c-187.775 0-340 152.225-340 340s340 497.248 340 497.248S852 620.785 852 433 699.775 93 512 93z m123.721 618.329C590.033 765.615 543.873 813.306 512 844.86c-31.873-31.554-78.033-79.245-123.721-133.531-46.777-55.579-83.799-106.948-110.03-152.656-36.885-64.259-44.62-103.8-44.62-125.673a278.371 278.371 0 1 1 556.741 0c0 21.858-7.745 61.414-44.62 125.673-26.233 45.719-63.252 97.077-110.029 152.656z" p-id="4116" fill="#1296db"></path><path d="M512 270.655a147.22 147.22 0 1 0 147.22 147.22A147.217 147.217 0 0 0 512 270.655z m0 234.44a87.22 87.22 0 1 1 87.22-87.22 87.323 87.323 0 0 1-87.22 87.22z" p-id="4117" fill="#1296db"></path></svg>

After

Width:  |  Height:  |  Size: 977 B

BIN
src/assets/icons/banner.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" width="72" height="72"><defs><linearGradient x1="1" y1=".5" x2="-.095" y2=".5" id="b"><stop offset="0%" stop-color="#0AEE8B" stop-opacity="0"/><stop offset="100%" stop-color="#0AEE8B"/></linearGradient><linearGradient x1="1" y1=".5" x2=".015" y2=".5" id="c"><stop offset="0%" stop-color="#0AEE8B" stop-opacity="0"/><stop offset="100%" stop-color="#0FA"/></linearGradient><clipPath id="a"><rect width="72" height="72" rx="0"/></clipPath></defs><g clip-path="url(#a)"><g fill-rule="evenodd"><path d="m56.112 33 14-8v22l-14-8v-6z" fill="url(#b)" transform="matrix(-1.00012 0 0 1.00012 82.12 -.003)"/><path d="M55.112 32.42v7.16l16 9.143V23.277l-15.496 8.855-.504.288zm1 6.58v-6l13-7.429 1-.571v22l-1-.571-13-7.429z" fill="url(#c)" transform="matrix(-1.00012 0 0 1.00012 82.12 -.003)"/></g><path d="M1.963 35.5H1v1h.963v-1zm2.889 0H2.926v1h1.926v-1zm2.889 0H5.815v1H7.74v-1zm2.889 0H8.704v1h1.926v-1zm2.889 0h-1.926v1h1.926v-1zm2.888 0h-1.926v1h1.926v-1zm2.89 0H17.37v1h1.926v-1zm2.888 0H20.26v1h1.926v-1zm2.89 0h-1.927v1h1.926v-1zm1.925 0h-.963v1H27v-1z" fill-rule="evenodd" fill="#0AEE8B"/><path d="M30.055 30h9.001a2 2 0 0 1 2 2v8.001a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2v-1l-4.001 2v-10l4 2v-1a2 2 0 0 1 2-2.001z" fill-rule="evenodd" fill="#F7F9FA"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 61.2 (89653) - https://sketch.com -->
<title>ic/panel/checkbox_active</title>
<desc>Created with Sketch.</desc>
<g id="ic/panel/checkbox_active" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group-3" fill="#FFFFFF">
<polygon id="Shape-Copy" points="11.2103387 4.23529412 6.47425817 8.97137463 4.50089127 6.99800776 2.82352941 8.65563591 4.79689632 10.6290028 6.37558979 12.2076964 6.47425817 12.286631 12.8877005 5.8929223"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 731 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,9 @@
<svg width="275" height="91" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><filter x="-27.7%" y="-22.2%" width="155.5%" height="144.4%" filterUnits="objectBoundingBox" id="a"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" in="shadowBlurOuter1"/></filter><filter x="-27.7%" y="-22.2%" width="155.5%" height="144.4%" filterUnits="objectBoundingBox" id="c"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" in="shadowBlurOuter1"/></filter><filter x="-27.7%" y="-22.2%" width="155.5%" height="144.4%" filterUnits="objectBoundingBox" id="e"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" in="shadowBlurOuter1"/></filter><filter x="-25%" y="-25%" width="150%" height="150%" filterUnits="objectBoundingBox" id="g"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.6 0" in="shadowBlurOuter1"/></filter><path d="M10.817 27.042c5.974 0 10.817-4.843 10.817-10.817 0-2.282-.707-4.399-1.913-6.144C18.419 8.2 15.45 4.84 10.817 0 6.175 4.843 3.203 8.208 1.903 10.096A10.767 10.767 0 0 0 0 16.226c0 5.973 4.843 10.816 10.817 10.816z" id="b"/><path d="M10.817 27.042c5.974 0 10.817-4.843 10.817-10.817 0-2.282-.707-4.399-1.913-6.144C18.419 8.2 15.45 4.84 10.817 0 6.175 4.843 3.203 8.208 1.903 10.096A10.767 10.767 0 0 0 0 16.226c0 5.973 4.843 10.816 10.817 10.816z" id="d"/><path d="M10.817 27.042c5.974 0 10.817-4.843 10.817-10.817 0-2.282-.707-4.399-1.913-6.144C18.419 8.2 15.45 4.84 10.817 0 6.175 4.843 3.203 8.208 1.903 10.096A10.767 10.767 0 0 0 0 16.226c0 5.973 4.843 10.816 10.817 10.816z" id="f"/><path d="M12 24c6.627 0 12-5.373 12-12S18.627 0 12 0 0 5.373 0 12s5.373 12 12 12z" id="h"/></defs><g fill="none" fill-rule="evenodd"><path d="m19.558 15.393-.9 45.202 141.5-18.076L254 61" stroke="#BEBEBE" stroke-width="3" stroke-linecap="square" stroke-dasharray="3,6"/><path d="M19.108 10.685c-3.12 20.167-3.969 34.417-2.548 42.75.608 3.562 1.602 6.972 2.548 10.258C35.728 121.403 138.27 35 188 35c16 0 36.744 8.007 62.232 24.021" stroke="#0AEE8B" stroke-width="4" stroke-linecap="square"/><g transform="rotate(90 111.415 159.585)"><use fill="#000" filter="url(#a)" xlink:href="#b"/><use fill="#FFF" xlink:href="#b"/></g><path d="m254.255 58.189 12.554-18.831a1 1 0 0 0-.832-1.555h-25.108a1 1 0 0 0-.833 1.555l12.554 18.83a1 1 0 0 0 1.665 0z" fill="#5CADFF"/><text font-family="PingFangSC-Semibold, PingFang SC" font-size="16" font-weight="500" fill="#FFF" transform="translate(239 36)">
<tspan x="9.915" y="15.915">4</tspan>
</text><g transform="rotate(90 71.915 103.085)"><use fill="#000" filter="url(#c)" xlink:href="#d"/><use fill="#FFF" xlink:href="#d"/></g><path d="m158.255 41.189 12.554-18.831a1 1 0 0 0-.832-1.555h-25.108a1 1 0 0 0-.833 1.555l12.554 18.83a1 1 0 0 0 1.665 0z" fill="#5CADFF"/><text font-family="PingFangSC-Semibold, PingFang SC" font-size="16" font-weight="500" fill="#FFF" transform="translate(143 19)">
<tspan x="9.915" y="15.915">3</tspan>
</text><g><g transform="rotate(90 -8.085 40.085)"><use fill="#000" filter="url(#e)" xlink:href="#f"/><use fill="#FFF" xlink:href="#f"/></g><path d="m15.255 58.189 12.554-18.831a1 1 0 0 0-.832-1.555H1.869a1 1 0 0 0-.833 1.555l12.554 18.83a1 1 0 0 0 1.665 0z" fill="#5CADFF"/><text font-family="PingFangSC-Semibold, PingFang SC" font-size="16" font-weight="500" fill="#FFF" transform="translate(0 36)">
<tspan x="9.915" y="15.915">2</tspan>
</text></g><g><path d="M3.715 0h32v32h-32z"/><g transform="translate(7.715 4)"><use fill="#000" filter="url(#g)" xlink:href="#h"/><path stroke="#FFF" stroke-width="3" d="M12 1.5c2.9 0 5.524 1.175 7.425 3.075 1.9 1.9 3.075 4.526 3.075 7.425 0 2.9-1.175 5.524-3.075 7.425A10.467 10.467 0 0 1 12 22.5c-2.9 0-5.524-1.175-7.425-3.075A10.467 10.467 0 0 1 1.5 12c0-2.9 1.175-5.524 3.075-7.425C6.475 2.675 9.101 1.5 12 1.5z" stroke-linejoin="square" fill="#1FA3F6"/></g><text font-family="Helvetica" font-size="16" fill="#FFF" transform="translate(7.715 4)">
<tspan x="6.664" y="17">S</tspan>
</text></g><g><path d="M83.826 72.676 66.648 89.129l-.091-9.476-6.517-6.88 23.786-.097z" fill="#FC302D"/><path fill="#B50000" d="m83.826 72.676-17.23 9.868-4.4-1.13z"/><path fill="#9A0000" d="m83.879 72.647-19.296 4.889-2.39 3.872z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -0,0 +1,9 @@
<svg width="275" height="91" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><filter x="-27.7%" y="-22.2%" width="155.5%" height="144.4%" filterUnits="objectBoundingBox" id="a"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" in="shadowBlurOuter1"/></filter><filter x="-27.7%" y="-22.2%" width="155.5%" height="144.4%" filterUnits="objectBoundingBox" id="c"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" in="shadowBlurOuter1"/></filter><filter x="-27.7%" y="-22.2%" width="155.5%" height="144.4%" filterUnits="objectBoundingBox" id="e"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" in="shadowBlurOuter1"/></filter><filter x="-25%" y="-25%" width="150%" height="150%" filterUnits="objectBoundingBox" id="g"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.6 0" in="shadowBlurOuter1"/></filter><path d="M10.817 27.042c5.974 0 10.817-4.843 10.817-10.817 0-2.282-.707-4.399-1.913-6.144C18.419 8.2 15.45 4.84 10.817 0 6.175 4.843 3.203 8.208 1.903 10.096A10.767 10.767 0 0 0 0 16.226c0 5.973 4.843 10.816 10.817 10.816z" id="b"/><path d="M10.817 27.042c5.974 0 10.817-4.843 10.817-10.817 0-2.282-.707-4.399-1.913-6.144C18.419 8.2 15.45 4.84 10.817 0 6.175 4.843 3.203 8.208 1.903 10.096A10.767 10.767 0 0 0 0 16.226c0 5.973 4.843 10.816 10.817 10.816z" id="d"/><path d="M10.817 27.042c5.974 0 10.817-4.843 10.817-10.817 0-2.282-.707-4.399-1.913-6.144C18.419 8.2 15.45 4.84 10.817 0 6.175 4.843 3.203 8.208 1.903 10.096A10.767 10.767 0 0 0 0 16.226c0 5.973 4.843 10.816 10.817 10.816z" id="f"/><path d="M12 24c6.627 0 12-5.373 12-12S18.627 0 12 0 0 5.373 0 12s5.373 12 12 12z" id="h"/></defs><g fill="none" fill-rule="evenodd"><path d="m19.558 15.393-.9 45.202 141.5-18.076L254 61" stroke="#BEBEBE" stroke-width="3" stroke-linecap="square" stroke-dasharray="3,6"/><path d="M19.108 10.685c-3.12 20.167-3.969 34.417-2.548 42.75.608 3.562 1.602 6.972 2.548 10.258C35.728 121.403 138.27 35 188 35c16 0 36.744 8.007 62.232 24.021" stroke="#0AEE8B" stroke-width="4" stroke-linecap="square"/><g transform="rotate(90 111.415 159.585)"><use fill="#000" filter="url(#a)" xlink:href="#b"/><use fill="#FFF" xlink:href="#b"/></g><path d="m254.255 58.189 12.554-18.831a1 1 0 0 0-.832-1.555h-25.108a1 1 0 0 0-.833 1.555l12.554 18.83a1 1 0 0 0 1.665 0z" fill="#5CADFF"/><text font-family="PingFangSC-Semibold, PingFang SC" font-size="16" font-weight="500" fill="#FFF" transform="translate(239 36)">
<tspan x="9.915" y="15.915">4</tspan>
</text><g transform="rotate(90 71.915 103.085)"><use fill="#000" filter="url(#c)" xlink:href="#d"/><use fill="#FFF" xlink:href="#d"/></g><path d="m158.255 41.189 12.554-18.831a1 1 0 0 0-.832-1.555h-25.108a1 1 0 0 0-.833 1.555l12.554 18.83a1 1 0 0 0 1.665 0z" fill="#5CADFF"/><text font-family="PingFangSC-Semibold, PingFang SC" font-size="16" font-weight="500" fill="#FFF" transform="translate(143 19)">
<tspan x="9.915" y="15.915">3</tspan>
</text><g><g transform="rotate(90 -8.085 40.085)"><use fill="#000" filter="url(#e)" xlink:href="#f"/><use fill="#FFF" xlink:href="#f"/></g><path d="m15.255 58.189 12.554-18.831a1 1 0 0 0-.832-1.555H1.869a1 1 0 0 0-.833 1.555l12.554 18.83a1 1 0 0 0 1.665 0z" fill="#5CADFF"/><text font-family="PingFangSC-Semibold, PingFang SC" font-size="16" font-weight="500" fill="#FFF" transform="translate(0 36)">
<tspan x="9.915" y="15.915">2</tspan>
</text></g><g><path d="M3.715 0h32v32h-32z"/><g transform="translate(7.715 4)"><use fill="#000" filter="url(#g)" xlink:href="#h"/><path stroke="#FFF" stroke-width="3" d="M12 1.5c2.9 0 5.524 1.175 7.425 3.075 1.9 1.9 3.075 4.526 3.075 7.425 0 2.9-1.175 5.524-3.075 7.425A10.467 10.467 0 0 1 12 22.5c-2.9 0-5.524-1.175-7.425-3.075A10.467 10.467 0 0 1 1.5 12c0-2.9 1.175-5.524 3.075-7.425C6.475 2.675 9.101 1.5 12 1.5z" stroke-linejoin="square" fill="#1FA3F6"/></g><text font-family="Helvetica" font-size="16" fill="#FFF" transform="translate(7.715 4)">
<tspan x="6.664" y="17">S</tspan>
</text></g><g><path d="M83.826 72.676 66.648 89.129l-.091-9.476-6.517-6.88 23.786-.097z" fill="#FC302D"/><path fill="#B50000" d="m83.826 72.676-17.23 9.868-4.4-1.13z"/><path fill="#9A0000" d="m83.879 72.647-19.296 4.889-2.39 3.872z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -0,0 +1,9 @@
<svg width="275" height="91" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><filter x="-27.7%" y="-22.2%" width="155.5%" height="144.4%" filterUnits="objectBoundingBox" id="a"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" in="shadowBlurOuter1"/></filter><filter x="-27.7%" y="-22.2%" width="155.5%" height="144.4%" filterUnits="objectBoundingBox" id="c"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" in="shadowBlurOuter1"/></filter><filter x="-27.7%" y="-22.2%" width="155.5%" height="144.4%" filterUnits="objectBoundingBox" id="e"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" in="shadowBlurOuter1"/></filter><filter x="-25%" y="-25%" width="150%" height="150%" filterUnits="objectBoundingBox" id="g"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.6 0" in="shadowBlurOuter1"/></filter><path d="M10.817 27.042c5.974 0 10.817-4.843 10.817-10.817 0-2.282-.707-4.399-1.913-6.144C18.419 8.2 15.45 4.84 10.817 0 6.175 4.843 3.203 8.208 1.903 10.096A10.767 10.767 0 0 0 0 16.226c0 5.973 4.843 10.816 10.817 10.816z" id="b"/><path d="M10.817 27.042c5.974 0 10.817-4.843 10.817-10.817 0-2.282-.707-4.399-1.913-6.144C18.419 8.2 15.45 4.84 10.817 0 6.175 4.843 3.203 8.208 1.903 10.096A10.767 10.767 0 0 0 0 16.226c0 5.973 4.843 10.816 10.817 10.816z" id="d"/><path d="M10.817 27.042c5.974 0 10.817-4.843 10.817-10.817 0-2.282-.707-4.399-1.913-6.144C18.419 8.2 15.45 4.84 10.817 0 6.175 4.843 3.203 8.208 1.903 10.096A10.767 10.767 0 0 0 0 16.226c0 5.973 4.843 10.816 10.817 10.816z" id="f"/><path d="M12 24c6.627 0 12-5.373 12-12S18.627 0 12 0 0 5.373 0 12s5.373 12 12 12z" id="h"/></defs><g fill="none" fill-rule="evenodd"><path d="m19.558 15.393-.9 45.202 141.5-18.076L254 61" stroke="#BEBEBE" stroke-width="3" stroke-linecap="square" stroke-dasharray="3,6"/><path d="M19.108 10.685c-3.12 20.167-3.969 34.417-2.548 42.75.608 3.562 1.602 6.972 2.548 10.258C35.728 121.403 138.714 32.946 188 35c16 .667 36.744 8.674 62.232 24.021" stroke="#0AEE8B" stroke-width="4" stroke-linecap="square"/><g transform="rotate(90 111.415 159.585)"><use fill="#000" filter="url(#a)" xlink:href="#b"/><use fill="#FFF" xlink:href="#b"/></g><path d="m254.255 58.189 12.554-18.831a1 1 0 0 0-.832-1.555h-25.108a1 1 0 0 0-.833 1.555l12.554 18.83a1 1 0 0 0 1.665 0z" fill="#5CADFF"/><text font-family="PingFangSC-Semibold, PingFang SC" font-size="16" font-weight="500" fill="#FFF" transform="translate(239 36)">
<tspan x="9.915" y="15.915">4</tspan>
</text><g transform="rotate(90 71.915 103.085)"><use fill="#000" filter="url(#c)" xlink:href="#d"/><use fill="#FFF" xlink:href="#d"/></g><path d="m158.255 41.189 12.554-18.831a1 1 0 0 0-.832-1.555h-25.108a1 1 0 0 0-.833 1.555l12.554 18.83a1 1 0 0 0 1.665 0z" fill="#5CADFF"/><text font-family="PingFangSC-Semibold, PingFang SC" font-size="16" font-weight="500" fill="#FFF" transform="translate(143 19)">
<tspan x="9.915" y="15.915">3</tspan>
</text><g><g transform="rotate(90 -8.085 40.085)"><use fill="#000" filter="url(#e)" xlink:href="#f"/><use fill="#FFF" xlink:href="#f"/></g><path d="m15.255 58.189 12.554-18.831a1 1 0 0 0-.832-1.555H1.869a1 1 0 0 0-.833 1.555l12.554 18.83a1 1 0 0 0 1.665 0z" fill="#5CADFF"/><text font-family="PingFangSC-Semibold, PingFang SC" font-size="16" font-weight="500" fill="#FFF" transform="translate(0 36)">
<tspan x="9.915" y="15.915">2</tspan>
</text></g><g><path d="M3.715 0h32v32h-32z"/><g transform="translate(7.715 4)"><use fill="#000" filter="url(#g)" xlink:href="#h"/><path stroke="#FFF" stroke-width="3" d="M12 1.5c2.9 0 5.524 1.175 7.425 3.075 1.9 1.9 3.075 4.526 3.075 7.425 0 2.9-1.175 5.524-3.075 7.425A10.467 10.467 0 0 1 12 22.5c-2.9 0-5.524-1.175-7.425-3.075A10.467 10.467 0 0 1 1.5 12c0-2.9 1.175-5.524 3.075-7.425C6.475 2.675 9.101 1.5 12 1.5z" stroke-linejoin="square" fill="#1FA3F6"/></g><text font-family="Helvetica" font-size="16" fill="#FFF" transform="translate(7.715 4)">
<tspan x="6.664" y="17">S</tspan>
</text></g><g><path d="M83.826 72.676 66.648 89.129l-.091-9.476-6.517-6.88 23.786-.097z" fill="#FC302D"/><path fill="#B50000" d="m83.826 72.676-17.23 9.868-4.4-1.13z"/><path fill="#9A0000" d="m83.879 72.647-19.296 4.889-2.39 3.872z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

9
src/assets/icons/ddt.svg Normal file
View File

@@ -0,0 +1,9 @@
<svg width="275" height="91" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><filter x="-27.7%" y="-22.2%" width="155.5%" height="144.4%" filterUnits="objectBoundingBox" id="a"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" in="shadowBlurOuter1"/></filter><filter x="-27.7%" y="-22.2%" width="155.5%" height="144.4%" filterUnits="objectBoundingBox" id="c"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" in="shadowBlurOuter1"/></filter><filter x="-27.7%" y="-22.2%" width="155.5%" height="144.4%" filterUnits="objectBoundingBox" id="e"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" in="shadowBlurOuter1"/></filter><filter x="-25%" y="-25%" width="150%" height="150%" filterUnits="objectBoundingBox" id="g"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.6 0" in="shadowBlurOuter1"/></filter><path d="M10.817 27.042c5.974 0 10.817-4.843 10.817-10.817 0-2.282-.707-4.399-1.913-6.144C18.419 8.2 15.45 4.84 10.817 0 6.175 4.843 3.203 8.208 1.903 10.096A10.767 10.767 0 0 0 0 16.226c0 5.973 4.843 10.816 10.817 10.816z" id="b"/><path d="M10.817 27.042c5.974 0 10.817-4.843 10.817-10.817 0-2.282-.707-4.399-1.913-6.144C18.419 8.2 15.45 4.84 10.817 0 6.175 4.843 3.203 8.208 1.903 10.096A10.767 10.767 0 0 0 0 16.226c0 5.973 4.843 10.816 10.817 10.816z" id="d"/><path d="M10.817 27.042c5.974 0 10.817-4.843 10.817-10.817 0-2.282-.707-4.399-1.913-6.144C18.419 8.2 15.45 4.84 10.817 0 6.175 4.843 3.203 8.208 1.903 10.096A10.767 10.767 0 0 0 0 16.226c0 5.973 4.843 10.816 10.817 10.816z" id="f"/><path d="M12 24c6.627 0 12-5.373 12-12S18.627 0 12 0 0 5.373 0 12s5.373 12 12 12z" id="h"/></defs><g fill="none" fill-rule="evenodd"><path d="m19.558 15.393-.9 45.202 141.5-18.076L254 61" stroke="#BEBEBE" stroke-width="3" stroke-linecap="square" stroke-dasharray="3,6"/><path d="M19.108 10.685c-3.12 20.167-3.969 34.417-2.548 42.75.608 3.562 1.602 6.972 2.548 10.258C35.728 121.403 138.714 32.946 188 35c16 .667 36.744 8.674 62.232 24.021" stroke="#0AEE8B" stroke-width="4" stroke-linecap="square"/><g transform="rotate(90 111.415 159.585)"><use fill="#000" filter="url(#a)" xlink:href="#b"/><use fill="#FFF" xlink:href="#b"/></g><path d="m254.255 58.189 12.554-18.831a1 1 0 0 0-.832-1.555h-25.108a1 1 0 0 0-.833 1.555l12.554 18.83a1 1 0 0 0 1.665 0z" fill="#5CADFF"/><text font-family="PingFangSC-Semibold, PingFang SC" font-size="16" font-weight="500" fill="#FFF" transform="translate(239 36)">
<tspan x="9.915" y="15.915">4</tspan>
</text><g transform="rotate(90 71.915 103.085)"><use fill="#000" filter="url(#c)" xlink:href="#d"/><use fill="#FFF" xlink:href="#d"/></g><path d="m158.255 41.189 12.554-18.831a1 1 0 0 0-.832-1.555h-25.108a1 1 0 0 0-.833 1.555l12.554 18.83a1 1 0 0 0 1.665 0z" fill="#5CADFF"/><text font-family="PingFangSC-Semibold, PingFang SC" font-size="16" font-weight="500" fill="#FFF" transform="translate(143 19)">
<tspan x="9.915" y="15.915">3</tspan>
</text><g><g transform="rotate(90 -8.085 40.085)"><use fill="#000" filter="url(#e)" xlink:href="#f"/><use fill="#FFF" xlink:href="#f"/></g><path d="m15.255 58.189 12.554-18.831a1 1 0 0 0-.832-1.555H1.869a1 1 0 0 0-.833 1.555l12.554 18.83a1 1 0 0 0 1.665 0z" fill="#5CADFF"/><text font-family="PingFangSC-Semibold, PingFang SC" font-size="16" font-weight="500" fill="#FFF" transform="translate(0 36)">
<tspan x="9.915" y="15.915">2</tspan>
</text></g><g><path d="M3.715 0h32v32h-32z"/><g transform="translate(7.715 4)"><use fill="#000" filter="url(#g)" xlink:href="#h"/><path stroke="#FFF" stroke-width="3" d="M12 1.5c2.9 0 5.524 1.175 7.425 3.075 1.9 1.9 3.075 4.526 3.075 7.425 0 2.9-1.175 5.524-3.075 7.425A10.467 10.467 0 0 1 12 22.5c-2.9 0-5.524-1.175-7.425-3.075A10.467 10.467 0 0 1 1.5 12c0-2.9 1.175-5.524 3.075-7.425C6.475 2.675 9.101 1.5 12 1.5z" stroke-linejoin="square" fill="#1FA3F6"/></g><text font-family="Helvetica" font-size="16" fill="#FFF" transform="translate(7.715 4)">
<tspan x="6.664" y="17">S</tspan>
</text></g><g><path d="M83.826 72.676 66.648 89.129l-.091-9.476-6.517-6.88 23.786-.097z" fill="#FC302D"/><path fill="#B50000" d="m83.826 72.676-17.23 9.868-4.4-1.13z"/><path fill="#9A0000" d="m83.879 72.647-19.296 4.889-2.39 3.872z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

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