@@ -80,11 +125,11 @@ const props = defineProps({
方位角
- {{ toFixed(item.angle, 1) }}°
+ {{ angleArr[index] }}°
相对距离
- {{ toFixed(item.distance, 2) }}米
+ {{ disArray[index] }}米
高度
@@ -92,7 +137,7 @@ const props = defineProps({
停留时长
- {{ item.stayTime }}秒
+ {{ item.duration }}秒
@@ -108,17 +153,20 @@ const props = defineProps({
left: 5px;
top: 5px;
background-color: rgba(0, 0, 0, 0.85);
+ backdrop-filter: blur(20px);
border-radius: 8px;
color: white;
font-size: 10px;
width: fit-content;
min-width: 17vw;
+ //max-height: 60vh;
z-index: 2;
- transition: transform 0.3s ease;
+ //overflow: auto;
+ //transition: transform 0.3s ease;
- &.hidden {
- transform: translateX(calc(-100% - 10px));
- }
+ //&.hidden {
+ // //transform: translateX(calc(-100% - 10px));
+ //}
:global {
.title {
@@ -154,6 +202,7 @@ const props = defineProps({
.content {
padding: 6px;
+ overflow: auto;
.route-point-info {
max-height: 60vh;
@@ -219,4 +268,4 @@ const props = defineProps({
}
}
}
-
\ No newline at end of file
+
diff --git a/src/config/errorMap.js b/src/config/errorMap.js
new file mode 100644
index 0000000..f0ac381
--- /dev/null
+++ b/src/config/errorMap.js
@@ -0,0 +1,97 @@
+/**
+ * 故障报警
+ * 参考1:http://vk-fly.com:10880/VKFLY_INDUSTRY/VK_Mavlink/src/main#22-%E7%B3%BB%E7%BB%9F%E7%8A%B6%E6%80%81-sys_status
+ * 参考2:http://vk-fly.com:10880/VKFLY_INDUSTRY/VK_Mavlink/src/main#16-%E9%94%99%E8%AF%AF%E7%8A%B6%E6%80%811-vkfly_sys_error1
+ */
+
+export const ERRORS_COUNT_1 = {
+ 1: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746604421642084386.mp3",
+ text: '地面站失联',
+ },
+ 2: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746604473069342913.mp3",
+ text: '电池电压低',
+ },
+ 4: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746604622658102213.mp3",
+ text: '电机平衡差',
+ },
+ 8: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746604672460876362.mp3",
+ text: '动力故障',
+ },
+ 16: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746604720009179855.mp3",
+ text: '飞控温度高',
+ },
+ 32: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746604764931252051.mp3",
+ text: '飞控无INS解算定位',
+ },
+ 64: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746604829931352407.mp3",
+ text: '超出电子围栏范围',
+ },
+ 128: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746604870398190001.mp3",
+ text: '备用链路失联'
+ },
+ 256: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746604912984080884.mp3",
+ text: '智能电池bms数据失联',
+ },
+ 512: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746604950827592367.mp3",
+ text: '发动机油量低'
+ },
+ 1024: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746604990069674822.mp3",
+ text: '发动机数据断开'
+ },
+ 2048: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746605027377313620.mp3",
+ text: '氢气压低'
+ },
+};
+
+export const ERRORS_COUNT_2 = {};
+
+export const ERRORS_COUNT_3 = {
+ 1: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746605075004362038.mp3",
+ text: 'mag1磁干扰'
+ },
+ 2: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746605119468335695.mp3",
+ text: 'mag2磁干扰'
+ },
+ 4: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746605149049385111.mp3",
+ text: 'imu1数据异常'
+ },
+ 8: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746605234388382865.mp3",
+ text: 'imu2数据异常'
+ },
+ 16: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746605260138418881.mp3",
+ text: '气压计数据异常'
+ },
+ 32: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746605293855295508.mp3",
+ text: '普通gps1数据异常'
+ },
+ 64: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746605323053728906.mp3",
+ text: '普通gps2数据异常'
+ },
+ 128: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746605358695187779.mp3",
+ text: 'RTK板卡数据异常'
+ },
+ 256: {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746605386457986166.mp3",
+ text: 'RTK和磁偏航角差异过大'
+ },
+};
diff --git a/src/config/gpsFixTypeMap.js b/src/config/gpsFixTypeMap.js
index 7d2df64..8b60141 100644
--- a/src/config/gpsFixTypeMap.js
+++ b/src/config/gpsFixTypeMap.js
@@ -27,3 +27,15 @@ export const GPS_FIX_TYPE2 = new Map([
]);
export const GPS_FIX_TYPE2_LABEL = [...GPS_FIX_TYPE2.values()];
+
+export const GPS_FIX_TYPE3 = {
+ 0: '无GPS',
+ 1: '无定位',
+ 2: '经纬定位',
+ 3: '经纬高定位',
+ 4: '差分GPS',
+ 5: 'RTK浮点解',
+ 6: 'RTK固定解',
+ 7: '静态固定定位',
+ 8: '精密单点定位',
+}
diff --git a/src/config/tipTextMap.js b/src/config/tipTextMap.js
index a35edc1..0be6a85 100644
--- a/src/config/tipTextMap.js
+++ b/src/config/tipTextMap.js
@@ -1,7 +1,8 @@
export const TIP_TEXT = {
"first_start_hints": {
- fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/04/audio_1745815890849334086.mp3",
- text: "考试开始,请操作无人机飞行至高度大于1.5米,小于5米",
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746782401303726331.mp3",
+ // fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/04/audio_1745815890849334086.mp3",
+ text: "考试开始",
},
"height_hints": {
fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/04/audio_1745815948315941248.mp3",
@@ -13,6 +14,8 @@ export const TIP_TEXT = {
},
"enter_center_time_out": {
icon: 'stopwatch',
+ key: 'outTime',
+ unit: 's',
fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/04/audio_1745816018284105950.mp3",
text: "操作失败,进入中心桶已超时"
},
@@ -34,11 +37,15 @@ export const TIP_TEXT = {
},
"fail_horizontal_large": {
icon: 'offset-h',
+ key: 'horizontal',
+ unit: 'm',
fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/04/audio_1745816356733350242.mp3",
text: "操作失败,水平偏差过大"
},
"fail_vertical_large": {
icon: 'offset-v',
+ key: 'vertical',
+ unit: 'm',
fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/04/audio_1745816464302514672.mp3",
text: "操作失败,垂直偏差过大",
},
@@ -49,6 +56,8 @@ export const TIP_TEXT = {
},
"fail_spin_time_out": {
icon: 'stopwatch',
+ key: 'outTime',
+ unit: 's',
fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/04/audio_1745816508442043575.mp3",
text: "操作失败,自旋已超时"
},
@@ -62,11 +71,15 @@ export const TIP_TEXT = {
},
"spin_fail_clock_time_out": {
icon: 'stopwatch',
+ key: 'outTime',
+ unit: 's',
fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/04/audio_1745816583793120597.mp3",
text: "操作失败,顺时针自旋已超时"
},
"spin_fail_rclock_time_out": {
icon: 'stopwatch',
+ key: 'outTime',
+ unit: 's',
fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/04/audio_1745816602031257891.mp3",
text: "操作失败,逆时针自旋已超时"
},
@@ -76,21 +89,29 @@ export const TIP_TEXT = {
},
"fail_8_time_out": {
icon: 'stopwatch',
+ key: 'outTime',
+ unit: 's',
fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/04/audio_1745816626528127642.mp3",
text: "操作失败,8字飞行已超时"
},
"fail_speed_large": {
icon: 'tangent-speed',
+ key: 'speed',
+ unit: 'm/s',
fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/04/audio_1745816721721001188.mp3",
text: "操作失败,切线速度过高"
},
"fail_speed_low": {
icon: 'tangent-speed',
+ key: 'speed',
+ unit: 'm/s',
fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/04/audio_1745816653069185482.mp3",
text: "操作失败,切线速度过低"
},
"fail_yaw_large": {
- icon: 'heading',
+ icon: 'tangent-angle',
+ key: 'angle',
+ unit: '°',
fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/04/audio_1745816746696830596.mp3",
text: "操作失败,航向偏差过大"
},
@@ -103,6 +124,8 @@ export const TIP_TEXT = {
text: "请降落到圈外"
},
"pasue_time_out": {
+ key: 'outTime',
+ unit: 's',
fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/04/audio_1745816842033628886.mp3",
text: "暂停已超时"
},
@@ -120,7 +143,45 @@ export const TIP_TEXT = {
},
'angle_speed_low': {
icon: 'angle-speed',
+ key: 'angleSpeed',
+ unit: '°/s',
fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/04/audio_1745829747486669352.mp3",
text: '操作失败,角速度过低'
+ },
+ 'fly_mode_error': {
+ icon: 'stability',
+ // key: 'angleSpeed',
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746690010348557569.mp3",
+ text: '操作失败,姿态模式错误',
+ },
+ 'return_start_hints': {
+ // icon: 'stability',
+ // key: 'angleSpeed',
+ // fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1746690010348557569.mp3",
+ text: '开始返航',
+ },
+ 'fail_return_time_out': {
+ key: 'outTime',
+ unit: 's',
+ text: '操作失败,返航超时',
+ },
+ 'fail_return_yaw_large': {
+ text: '操作失败,返航角偏差过大',
+ },
+ 'fail_return_vertical_large': {
+ text: '操作失败,垂直偏差过大',
+ },
+ 'pause_start': {
+ fileUrl: 'http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1748313316155342077.mp3',
+ text: '考试自动暂停中',
+ },
+ 'pause_end': {
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1748313445226119867.mp3",
+ text: '暂停已恢复',
+ },
+ 'fail_8_direction': {
+ icon: 'rotate360',
+ fileUrl: "http://gcs-edu.obs.cn-east-2.myhuaweicloud.com/audio/2025/05/audio_1748594886504325922.mp3",
+ text: '操作失败,飞行方向错误',
}
}
diff --git a/src/config/urls.js b/src/config/urls.js
index f30ad6e..9caef76 100644
--- a/src/config/urls.js
+++ b/src/config/urls.js
@@ -3,11 +3,14 @@
*/
import { buildURL } from '../utils/helpers';
const { TARO_APP_API: BASE_URL } = process.env; // 获取环境变量
-// const { TARO_APP_API: BASE_URL, TARO_APP_WS_API: WS_BASE_URL } = import.meta; // 获取环境变量
-// console.log(BASE_URL);
+// const { TARO_APP_API: BASE_URL } = import.meta; // 获取环境变量
+// console.log(import.env);
// 上传
export const UPLOAD = `${BASE_URL}/train/v1/files/upload`;
+// 刷新token
+export const REFRESH_TOKEN = `${BASE_URL}/user/tokens/refreshToken`;
+
// 用户登录
export const LOGIN_WITH_PASSWORD = `${BASE_URL}/user/user/token`;
export const STUDENT_LOGIN_WITH_PASSWORD = `${BASE_URL}/user/student/token`;
@@ -40,6 +43,7 @@ export const GET_ROUTE_PLAN_DETAIL = (id) => buildURL(`${BASE_URL}/train/v1/rout
// 应急返航管理
export const GET_RETURN_TRIP_LIST = `${BASE_URL}/train/v1/returnTrips`;
export const GET_RETURN_TRIP_DETAIL = (id) => buildURL(`${BASE_URL}/train/v1/returnTrips/{id}`, id);
+export const GET_RETURN_TRIP_TRACKS = (id) => buildURL(`${BASE_URL}/train/v1/returnTrips/{id}/tracks`, id);
// 环境参数管理
export const GET_ENV_LIST = `${BASE_URL}/train/v1/envs`;
@@ -49,7 +53,7 @@ export const GET_EXAM_LIST = `${BASE_URL}/train/v1/exams`;
// ws 飞机实时数据
// export const GET_DRONE_REAL_TIME_DATA = `${WS_BASE_URL}/data/ws`;
-export const GET_DRONE_REAL_TIME_DATA = `ws://uavedu.jiagutech.com/api/data/ws`;
+export const GET_DRONE_REAL_TIME_DATA = `wss://uavedu.jiagutech.com/api/data/ws`;
// 场地管理
export const GET_AIRFIELDS_LIST = `${BASE_URL}/train/v1/airfields`;
@@ -68,3 +72,14 @@ export const GET_CLASS_LIST = `${BASE_URL}/user/v1/classes`
// 执照等级
export const GET_LICENSE_GRADES_LIST = `${BASE_URL}/user/licenseGrades`;
+
+// 微信解绑 - 机构人员
+export const UNBIND_USER = `${BASE_URL}/user/v1/users/unbind`;
+
+// 微信解绑 - 学生
+export const UNBIND_STUDENT = `${BASE_URL}/user/v1/students/unbind`;
+
+// 同步优云飞行时长
+export const GET_STUDENT_U_FLIGHT_TIME = (id) => buildURL(`${BASE_URL}/user/v1/students/{id}/ucloud/flightTime`, id);
+// 获取学生详情
+export const GET_STUDENT_DETAIL = (id) => buildURL(`${BASE_URL}/user/v1/students/{id}`, id);
diff --git a/src/core/SimpleAudioManager.js b/src/core/SimpleAudioManager.js
new file mode 100644
index 0000000..010fe99
--- /dev/null
+++ b/src/core/SimpleAudioManager.js
@@ -0,0 +1,108 @@
+import Taro from "@tarojs/taro";
+
+class SimpleAudioManager {
+ // audio;
+ timer;
+
+ constructor() {
+ this.audio = Taro.createInnerAudioContext({ useWebAudioImplement: true });
+ // this.audio = Taro.getBackgroundAudioManager();
+ this.queue = []; // 待播放队列(仅存同优先级)
+ this.currentPriority = 0; // 当前播放优先级
+
+ // 监听播放结束
+ this.audio.onEnded(() => this.playNext());
+ this.audio.onError(() => this.playNext());
+ }
+
+ check() {
+ if (this.timer) clearTimeout(this.timer);
+ this.timer = setTimeout(this.playNext, 5000);
+ }
+
+ // 添加播放请求(核心逻辑)
+ play(url, priority = 0) {
+ console.log('play:url', url);
+ console.log('上一个优先级', this.currentPriority, '本次的优先级', priority);
+ // 情况1:更高优先级 → 立即打断并清空
+ if (priority > this.currentPriority) {
+ // this.audio.stop();
+ console.log('清空队列');
+ this.queue = [];
+ this.currentPriority = priority;
+ if (this.audio) {
+ this.audio.destroy();
+ this.audio = null;
+ this.audio = Taro.createInnerAudioContext({ useWebAudioImplement: true });
+ // 监听播放结束
+ this.audio.onEnded(() => this.playNext());
+ this.audio.onError(() => this.playNext());
+ //
+ this.audio.src = url;
+ this.audio.play();
+ this.check();
+ }
+ return;
+ }
+
+ // 情况2:同优先级 → 加入队列等待
+ if (priority === this.currentPriority) {
+ this.queue.push(url);
+ console.log('排队', [...(this.queue || [])]);
+ // return;
+ }
+
+ // if (!this.queue.length) {
+ // this.currentPriority = 0;
+ // }
+ // 情况3:低优先级 → 直接忽略
+ // 情况4:低优先级 → 但是queue 为空
+ // if (!this.queue.length) {
+ // // this.queue = [];
+ // this.currentPriority = priority;
+ // this.audio.src = url;
+ // this.audio.play();
+ // }
+ }
+
+ // 播放下一个(仅在同优先级队列有效)
+ playNext() {
+ if (this.queue?.length > 0) {
+ // 继续播放同优先级队列
+ // this.audio.src = this.queue.shift();
+ // this.audio.play();
+ if (this.audio) {
+ this.audio.destroy();
+ this.audio = null;
+ this.audio = Taro.createInnerAudioContext({ useWebAudioImplement: true });
+ // 监听播放结束
+ this.audio.onEnded(() => this.playNext());
+ this.audio.onError(() => this.playNext());
+ //
+ this.audio.src = this.queue?.shift();
+ this.audio.play();
+ this.check();
+ console.log('异步操作:排队播放中,剩余数量', this.queue?.length, [...(this.queue || [])]);
+ }
+ } else {
+ console.log('异步操作:排队播放完毕', [...(this.queue || [])]);
+ // this.queue = [];
+ // 队列清空,重置优先级
+ this.currentPriority = 0;
+ }
+ }
+
+ destroy() {
+ this.queue = [];
+ this.currentPriority = 0;
+ if (this.audio) {
+ this.audio.destroy();
+ this.audio = null
+ this.queue = []; // 待播放队列(仅存同优先级)
+ this.currentPriority = 0; // 当前播放优先级
+ console.log('audioManager.destroy');
+ }
+ }
+}
+
+export default SimpleAudioManager;
diff --git a/src/core/useDeviceCruise.js b/src/core/useDeviceCruise.js
index 466b205..76ce0e2 100755
--- a/src/core/useDeviceCruise.js
+++ b/src/core/useDeviceCruise.js
@@ -2,30 +2,15 @@
* 设备按轨迹巡航
*/
import {computed, reactive, ref} from 'vue';
-// import { interpolate } from 'popmotion';
-// import * as turf from '@turf/turf';
-// import deviceIcon from '../assets/deviceIcon.png';
-import deviceIcon from '../assets/droneImg.png';
-// import { GPS2GCJ } from '../utils/helpers';
-// import * as geo from "../utils/geo";
-
-const markerId = 1e6;
-const markerOption = {
- id: markerId,
- iconPath: deviceIcon,
- width: 18,
- height: 18,
- anchor: { x: 0.5, y: 0.5 },
-}
export const useDeviceCruise = () => {
- const markers = ref([]);
const points = ref([]);
const isPlaying = ref(false);
const isPaused = ref(true);
const rotate = ref(0);
const elapsedMs = ref(0);
const timelyData = ref({});
+ const extMarkerOption = ref({});
const datumTime = computed(() => {
const [{ timestamp: startTs } = {}] = points.value || [];
@@ -33,6 +18,17 @@ export const useDeviceCruise = () => {
});
const totalTime = computed(() => datumTime.value[datumTime.value.length - 1] || 0);
+ let initCallBack = () => {};
+ let moveCallBack = () => {};
+
+ function setInitCallBack(fn = () => {}) {
+ initCallBack = fn;
+ }
+
+ function setMoveCallBack(fn = () => {}) {
+ moveCallBack = fn;
+ }
+
function setCurrentTime(ms) {
if (ms < 0 || ms > totalTime.value) return;
elapsedMs.value = ms;
@@ -41,12 +37,7 @@ export const useDeviceCruise = () => {
const point = points.value[index];
const { lng, lat, yaw } = point;
timelyData.value = { ...point };
- markers.value = [{
- ...markerOption,
- latitude: lat,
- longitude: lng,
- rotate: yaw + rotate.value,
- }];
+ moveCallBack({ lng, lat, yaw });
}
function initRenderDevice() {
@@ -57,13 +48,7 @@ export const useDeviceCruise = () => {
const { lng, lat, yaw } = point;
timelyData.value = { ...point };
- // console.log(yaw, rotate.value, yaw + rotate.value);
- markers.value = [{
- ...markerOption,
- latitude: lat,
- longitude: lng,
- rotate: yaw + rotate.value,
- }];
+ initCallBack({ lng, lat, yaw });
}
let timer
@@ -84,13 +69,8 @@ export const useDeviceCruise = () => {
timelyData.value = { ...point };
const { lng, lat, yaw } = point;
- markers.value = [{
- ...markerOption,
- latitude: lat,
- longitude: lng,
- rotate: yaw + rotate.value,
- }];
- // console.log('yaw', yaw);
+ moveCallBack({ lng, lat, yaw });
+
elapsedMs.value += 100;
timer = setTimeout(autoRenderDevice, 100);
}
@@ -113,7 +93,6 @@ export const useDeviceCruise = () => {
}
return {
- markers,
points,
isPlaying,
isPaused,
@@ -127,6 +106,9 @@ export const useDeviceCruise = () => {
autoRenderDevice,
play,
pause,
+ extMarkerOption,
+ setInitCallBack,
+ setMoveCallBack
}
}
diff --git a/src/core/useDeviceCruise2.js b/src/core/useDeviceCruise2.js
new file mode 100755
index 0000000..7fdd1c1
--- /dev/null
+++ b/src/core/useDeviceCruise2.js
@@ -0,0 +1,466 @@
+/**
+ * 设备按轨迹巡航
+ */
+import {computed, reactive, ref} from 'vue';
+// import { interpolate } from 'popmotion';
+// import * as turf from '@turf/turf';
+// import deviceIcon from '../assets/deviceIcon.png';
+import deviceIcon from '../assets/droneImg.png';
+// import { GPS2GCJ } from '../utils/helpers';
+// import * as geo from "../utils/geo";
+
+const markerId = 1e8;
+const markerOption = {
+ id: markerId,
+ iconPath: deviceIcon,
+ width: 18,
+ height: 18,
+ anchor: { x: 0.5, y: 0.5 },
+}
+
+export const useDeviceCruise = () => {
+ const markers = ref([]);
+ const points = ref([]);
+ const isPlaying = ref(false);
+ const isPaused = ref(true);
+ const rotate = ref(0);
+ const elapsedMs = ref(0);
+ const timelyData = ref({});
+ const extMarkerOption = ref({});
+
+ const datumTime = computed(() => {
+ const [{ timestamp: startTs } = {}] = points.value || [];
+ return points.value.map(({ timestamp }) => timestamp - startTs);
+ });
+ const totalTime = computed(() => datumTime.value[datumTime.value.length - 1] || 0);
+
+ function setCurrentTime(ms) {
+ if (ms < 0 || ms > totalTime.value) return;
+ elapsedMs.value = ms;
+ const index = datumTime.value.findIndex(item => item >= ms);
+ // this._that.currentIndex = index;
+ const point = points.value[index];
+ const { lng, lat, yaw } = point;
+ timelyData.value = { ...point };
+ markers.value = [{
+ ...markerOption,
+ ...(extMarkerOption.value || {}),
+ latitude: lat,
+ longitude: lng,
+ rotate: yaw + rotate.value,
+ }];
+ }
+
+ function initRenderDevice() {
+ const [point] = points.value || [];
+ if (!point) {
+ return;
+ }
+
+ const { lng, lat, yaw } = point;
+ timelyData.value = { ...point };
+ // console.log(yaw, rotate.value, yaw + rotate.value);
+ markers.value = [{
+ ...markerOption,
+ ...(extMarkerOption.value || {}),
+ latitude: lat,
+ longitude: lng,
+ rotate: yaw + rotate.value,
+ }];
+ }
+
+ let timer
+ function autoRenderDevice() {
+ if (isPaused.value) return;
+ const index = datumTime.value.findIndex(item => item >= elapsedMs.value);
+ // this._that.currentIndex = index;
+ if (index === -1) {
+ isPlaying.value = false;
+ isPaused.value = true;
+ if (timer) {
+ clearTimeout(timer);
+ }
+ return;
+ }
+ const point = points.value[index];
+
+ timelyData.value = { ...point };
+ const { lng, lat, yaw } = point;
+
+ markers.value = [{
+ ...markerOption,
+ ...(extMarkerOption.value || {}),
+ latitude: lat,
+ longitude: lng,
+ rotate: yaw + rotate.value,
+ }];
+ // console.log('yaw', yaw);
+ elapsedMs.value += 100;
+ timer = setTimeout(autoRenderDevice, 100);
+ }
+
+ function play() {
+ isPlaying.value = true;
+ isPaused.value = false;
+ if (timer) {
+ clearTimeout(timer);
+ }
+ autoRenderDevice();
+ }
+
+ function pause() {
+ isPlaying.value = false;
+ isPaused.value = true;
+ if (timer) {
+ clearTimeout(timer);
+ }
+ }
+
+ return {
+ markers,
+ points,
+ isPlaying,
+ isPaused,
+ rotate,
+ elapsedMs,
+ timelyData,
+ datumTime,
+ totalTime,
+ setCurrentTime,
+ initRenderDevice,
+ autoRenderDevice,
+ play,
+ pause,
+ extMarkerOption,
+ }
+}
+
+// class DeviceCruise {
+//
+// _that = null;
+//
+// // 地图实例
+// _map = null;
+//
+// // 单条轨迹数据
+// _dataSource = {};
+//
+// // 巡航速率
+// speedRate = 1;
+//
+// // 动画计时器
+// _timer = null;
+//
+// // 上一帧时间点
+// _lastFrameAt = 0;
+//
+// // 累计播放时长(毫秒数)
+// elapsedMs = 0;
+//
+// // 每帧期望间隔(毫秒数,实际间隔取决于浏览器fps)
+// _fpsInterval = 1000 / 50;
+//
+// // 上一帧时间戳
+// _lastFrameTimestamp = 0;
+//
+// // 巡航到的时间点数据
+// timelyData = {};
+//
+// isPlaying = false;
+//
+// isPaused = false;
+//
+// isStopped = true;
+//
+// // currentIndex = 0;
+//
+// // 是否准备完毕
+// get ready() {
+// return Object.keys(this._that._dataSource).length > 0;
+// }
+//
+// get _points() {
+// const { points } = this._that._dataSource;
+// return points || [];
+// }
+//
+// // 基准时间点(从0开始的毫秒数)
+// get _datumTime() {
+// const [{ timestamp: startTs } = {}] = this._that._points || [];
+// return this._that._points.map(({ timestamp }) => timestamp - startTs);
+// }
+//
+// // 轨迹总时间(毫秒数)
+// get totalTime() {
+// // console.log('this._that._datumTime', this._that._datumTime);
+// return this._that._datumTime[this._that._datumTime.length - 1] || 0;
+// }
+//
+// // 是否已经开始播放了
+// get isStarted() {
+// return this._that.isPlaying || this._that.isPaused;
+// }
+//
+// constructor() {
+// this._that = reactive(this);
+// return this._that;
+// }
+//
+// // 设置地图实例
+// setMap(mapInstance) {
+// this._that._map = mapInstance;
+// }
+//
+// mapRotate = 0;
+// // 载入轨迹数据(point中必须包含lng, lat, timestamp, yaw)
+// loadTrack({ id, points, ...others }, rotate = 0) {
+// // if (!this._that._that._map) {
+// // throw new Error('请先设置地图实例');
+// // }
+// this._that.mapRotate = rotate;
+// this._that._dataSource = {
+// id,
+// points: (points || []).map(item => {
+// const [lng, lat] = GPS2GCJ([item.lng, item.lat]);
+// return {
+// ...item,
+// lng,
+// lat,
+// yaw: +geo.radToDeg(item.yaw || 0).toFixed(1),
+// };
+// }),
+// ...others,
+// };
+// this._that._reset();
+// this._that._initDevice();
+// }
+//
+// // 设置速率
+// setSpeedRate(val) {
+// this._that.speedRate = val;
+// }
+//
+// _setting = false;
+// // 设置当前巡航时间点
+// setCurrentTime(ms) {
+// if (ms < 0 || ms > this._that.totalTime) return;
+// // if (this._that.isPlaying) {
+// // this._that.isPlaying = false;
+// // this._that.isPaused = true;
+// // }
+//
+// // if (this._that._setting) return;
+// // this._that._setting = true;
+// this._that.elapsedMs = ms;
+// const index = this._that._datumTime.findIndex(item => item >= ms);
+// this._that.currentIndex = index;
+// const point = this._that._points[index];
+// const { lng, lat, yaw } = point;
+// // this._that._map.removeMarkers({
+// // markerIds: [markerId],
+// // success: () => {
+// // // this._that._renderDevice();
+// // this._that._map.addMarkers({
+// // markers: [{
+// // ...this._that._markerOptins,
+// // latitude: lat,
+// // longitude: lng,
+// // rotate: yaw + this._that.mapRotate,
+// // }],
+// // success: () => {
+// // this._that._setting = false;
+// // }
+// // });
+// // }
+// // });
+// this._that.markers.value = [{
+// ...this._that._markerOptins,
+// latitude: lat,
+// longitude: lng,
+// rotate: yaw + this._that.mapRotate,
+// }];
+// }
+//
+// // 获取指定毫秒处的数据值
+// _getTimelyData(ms = 0) {
+// // const genTimelyData = interpolate(this._that._datumTime, this._that._points);
+// const index = this._that._datumTime.findIndex(item => item >= ms);
+// // console.log('genTimelyData', genTimelyData(ms));
+// // if (index === -1) {
+// // return {};
+// // }
+//
+// // return genTimelyData(ms);
+// return this._that._points[index] || {};
+// }
+//
+// _reset() {
+// this._that.isPlaying = false;
+// this._that.isPaused = false;
+// this._that.isStopped = true;
+// this._that.speedRate = 1;
+// this._that._lastFrameAt = 0;
+// this._that.elapsedMs = 0;
+// this._that._lastFrameTimestamp = 0;
+// this._that.markers.value = [];
+// }
+//
+// markers = ref([]);
+// _initDevice() {
+// const [point] = this._that._points;
+// if (!point) {
+// return;
+// }
+//
+// const { lng, lat, yaw } = point;
+// this._that.markers.value = [{
+// ...this._that._markerOptins,
+// latitude: lat,
+// longitude: lng,
+// rotate: yaw + this._that.mapRotate,
+// }];
+// // this._that._map.addMarkers({
+// // markers: [{
+// // ...this._that._markerOptins,
+// // latitude: lat,
+// // longitude: lng,
+// // rotate: yaw + this._that.mapRotate,
+// // }]
+// // })
+// }
+//
+// _clearDevice() {
+// // 清除timelyData即可
+// this._that.timelyData = {};
+// // this._that._map.removeMarkers({
+// // markerIds: [markerId]
+// // });
+// this._that.markers.value = [];
+// }
+//
+// _renderDevice() {
+// if (!this._that._points.length) return;
+// // if (!this._that.isPlaying) return;
+//
+// // const lastData = this._that._getTimelyData(this._that.elapsedMs - 100 > 0 ? this._that.elapsedMs - 100 : 0);
+// const nextData = this._that._getTimelyData(this._that.elapsedMs);
+//
+// // const { deep, breadth, seeding, flow } = nextData;
+// this._that.timelyData = { ...nextData };
+// const { lng, lat, yaw } = nextData;
+//
+// // this._that._map.translateMarker({
+// // markerId,
+// // destination: {
+// // longitude: lng,
+// // latitude: lat,
+// // },
+// // autoRotate: false,
+// // duration: 1,
+// // rotate: yaw + this._that.mapRotate,
+// // moveWithRotate: true,
+// // animationEnd: () => {
+// // // this._that.timelyData = { ...nextData };
+// // // this._that.currentIndex += 1;
+// // // if (this._that.isPlaying) {
+// // // this._that.elapsedMs += (duration * this._that.speedRate);
+// // // if (this._that.elapsedMs >= this._that.totalTime) {
+// // // this._that.handleStop();
+// // // return;
+// // // }
+// // // this._that._renderDevice();
+// // // }
+// // }
+// // });
+//
+// // this._that._map.removeMarkers({
+// // markerIds: [markerId],
+// // success: () => {
+// // // this._that._renderDevice();
+// // this._that._map.addMarkers({
+// // markers: [{
+// // ...this._that._markerOptins,
+// // latitude: lat,
+// // longitude: lng,
+// // rotate: yaw + this._that.mapRotate,
+// // }],
+// // success: () => {
+// // // this._that._setting = false;
+// // }
+// // });
+// // }
+// // });
+// this._that.markers.value = [{
+// ...this._that._markerOptins,
+// latitude: lat,
+// longitude: lng,
+// rotate: yaw + this._that.mapRotate,
+// }];
+// }
+//
+// _ticker = timestamp => {
+// this._that.elapsedMs += Math.round((timestamp - this._that._lastFrameAt) * this._that.speedRate);
+// this._that._lastFrameAt = timestamp;
+// if (this._that.elapsedMs > this._that.totalTime) {
+// return;
+// }
+//
+// // 在期望的间隔内_renderDevice,而不是每个tick都_renderDevice(目的:降低render频率,提高显示性能)
+// const now = Date.now();
+// const timeDiff = now - this._that._lastFrameTimestamp;
+// if (timeDiff > this._that._fpsInterval) {
+// this._that._lastFrameTimestamp = now - (timeDiff % this._that._fpsInterval); // 矫正时间戳
+// this._that._renderDevice();
+// }
+//
+// this._that._timer = requestAnimationFrame(this._that._ticker);
+// };
+//
+// // 开始播放巡航动画、恢复播放巡航动画
+// handlePlay() {
+// requestAnimationFrame(ms => {
+// this._that._lastFrameAt = ms;
+// });
+// this._that.isPlaying = true;
+// this._that.isPaused = false;
+// this._that.isStopped = false;
+// requestAnimationFrame(this._that._ticker);
+// }
+//
+// // 暂停播放巡航动画
+// handlePause() {
+// this._that.isPlaying = false;
+// this._that.isPaused = true;
+// this._that.isStopped = false;
+// cancelAnimationFrame(this._that._timer);
+// }
+//
+// // 停止播放巡航动画
+// handleStop() {
+// this._that.isPlaying = false;
+// this._that.isPaused = false;
+// this._that.isStopped = true;
+// this._that._lastFrameAt = 0;
+// this._that.elapsedMs = 0;
+// cancelAnimationFrame(this._that._timer);
+// this._that._renderDevice();
+// this._that.timelyData = {};
+// }
+//
+// clear() {
+// if (!this._that._map) return;
+// this._that.handleStop();
+// this._that._clearDevice();
+// this._that._reset();
+// this._that._dataSource = {};
+// this._that.timelyData = {};
+// }
+//
+// destroy() {
+// this._that.clear();
+// this._that._timer = null;
+// this._that._map = null;
+// }
+// }
+
+// export default new DeviceCruise();
diff --git a/src/core/useDrone.js b/src/core/useDrone.js
new file mode 100644
index 0000000..6359dfa
--- /dev/null
+++ b/src/core/useDrone.js
@@ -0,0 +1,49 @@
+import { Image } from '@leafer-ui/miniapp';
+import deviceIcon from "../assets/droneImg.png";
+
+export const useDrone = () => {
+ let leafer;
+ let drone;
+
+ function setScene(Leafer) {
+ leafer = Leafer;
+ }
+
+ function createDrone({ x, y, rotation }) {
+ if (drone) {
+ drone.set({
+ x,
+ y,
+ rotation,
+ });
+ } else {
+ drone = new Image({
+ x,
+ y,
+ url: deviceIcon,
+ width: 200,
+ height: 200,
+ around: 'center', // 设置原点在中心
+ // draggable: true,
+ placeholderColor: 'rgba(120,120,120,0.2)', // 设置图片占位符的背景颜色 //
+ rotation,
+ zIndex: 3,
+ });
+ leafer.add(drone);
+ }
+ }
+
+ function moveDrone({ x, y, rotation }) {
+ drone.set({
+ x,
+ y,
+ rotation,
+ });
+ }
+
+ return {
+ createDrone,
+ setScene,
+ moveDrone
+ }
+}
diff --git a/src/core/useErrPoint.js b/src/core/useErrPoint.js
new file mode 100644
index 0000000..cc6b50c
--- /dev/null
+++ b/src/core/useErrPoint.js
@@ -0,0 +1,61 @@
+import { Path, Text } from '@leafer-ui/miniapp';
+
+export const useErrPoint = () => {
+ let leafer;
+ let bearing = 0;
+ let pathList = [];
+ let textList = [];
+
+ function remove() {
+ pathList.forEach(item => item.remove());
+ textList.forEach(item => item.remove());
+ }
+
+ function setScene(Leafer, Bearing) {
+ leafer = Leafer;
+ bearing = Bearing;
+ }
+
+ function createErrPoint(points = []) {
+ remove();
+ pathList = [];
+ textList = [];
+ points.forEach(((tmp, index) => {
+ const errPoint = new Path({
+ path: `P${tmp.x} ${tmp.y} 20`,
+ fill: "#ff0000",
+ strokeWidth: 2,
+ stroke: '#ffffff',
+ zIndex: 2,
+ })
+
+ const text = new Text({
+ x: tmp.x,
+ y: tmp.y,
+ fill: '#ff0000',
+ text: index + 1,
+ fontSize: 50,
+ around: 'center',
+ offsetX: -30,
+ offsetY: -60,
+ rotation: (bearing - 90),
+ stroke: {
+ type: 'solid',
+ color: '#ff0000'
+ },
+ zIndex: 2,
+ })
+
+ pathList.push(errPoint);
+ textList.push(text);
+ leafer.add(text);
+ leafer.add(errPoint);
+ }));
+ }
+
+ return {
+ createErrPoint,
+ setScene,
+ remove,
+ }
+}
diff --git a/src/core/useFence .js b/src/core/useFence .js
new file mode 100644
index 0000000..9f1c133
--- /dev/null
+++ b/src/core/useFence .js
@@ -0,0 +1,94 @@
+import {Path, Text} from '@leafer-ui/miniapp';
+
+export const useFence = () => {
+ let leafer;
+
+ function setScene(Leafer) {
+ leafer = Leafer;
+ }
+
+ function createFence({ leftTop, rightTop, leftBottom, rightBottom }) {
+ const fence = new Path({
+ path: `M${leftTop.x} ${leftTop.y} L${rightTop.x} ${rightTop.y} L${rightBottom.x} ${rightBottom.y} L${leftBottom.x} ${leftBottom.y}Z`,
+ // fill: 'rgba(0,255,234,0.05)',
+ zIndex: -1,
+ stroke: 'rgb(0,255,234)',
+ strokeWidth: 10,
+ dashPattern: [6, 6]
+ // zIndex: 2
+ // windingRule: 'evenodd',
+ })
+ // const text1 = new Text({
+ // x: leftTop.x,
+ // y: leftTop.y,
+ // fill: '#ffffff',
+ // text: '1',
+ // fontSize: 180,
+ // around: 'center',
+ // // offsetX: -30,
+ // // offsetY: -60,
+ // // rotation: (bearing - 90),
+ // stroke: {
+ // type: 'solid',
+ // color: '#000000'
+ // }
+ // })
+ // const text2 = new Text({
+ // x: rightTop.x,
+ // y: rightTop.y,
+ // fill: '#ffffff',
+ // text: '2',
+ // fontSize: 180,
+ // around: 'center',
+ // // offsetX: -30,
+ // // offsetY: -60,
+ // // rotation: (bearing - 90),
+ // stroke: {
+ // type: 'solid',
+ // color: '#000000'
+ // }
+ // })
+ // const text3 = new Text({
+ // x: rightBottom.x,
+ // y: rightBottom.y,
+ // fill: '#ffffff',
+ // text: '3',
+ // fontSize: 180,
+ // around: 'center',
+ // // offsetX: -30,
+ // // offsetY: -60,
+ // // rotation: (bearing - 90),
+ // stroke: {
+ // type: 'solid',
+ // color: '#000000'
+ // }
+ // })
+ // const text4 = new Text({
+ // x: leftBottom.x,
+ // y: leftBottom.y,
+ // fill: '#ffffff',
+ // text: '4',
+ // fontSize: 180,
+ // around: 'center',
+ // // offsetX: -30,
+ // // offsetY: -60,
+ // // rotation: (bearing - 90),
+ // stroke: {
+ // type: 'solid',
+ // color: '#000000'
+ // }
+ // })
+ // leafer.add(text1);
+ // leafer.add(text2);
+ // leafer.add(text3);
+ // leafer.add(text4);
+ // spin = fence;
+ leafer.add(fence)
+ }
+
+ return {
+ createFence,
+ setScene,
+ // remove,
+ }
+}
diff --git a/src/core/useField.js b/src/core/useField.js
new file mode 100644
index 0000000..ac7746f
--- /dev/null
+++ b/src/core/useField.js
@@ -0,0 +1,138 @@
+import { Path, Text } from '@leafer-ui/miniapp';
+import * as turf from "@turf/turf";
+import { getXYUnit, GPS2XY } from '../utils/helpers';
+
+export const useField = () => {
+ let leafer;
+
+ function setScene(Leafer) {
+ leafer = Leafer;
+ }
+
+ function createField(c1Config = {}, c2Config = {}, c3Config = {}) {
+ const bearing = turf.bearing(c1Config.LngLat, c2Config.LngLat); //
+ const origin = turf.midpoint(c1Config.LngLat, c2Config.LngLat);
+ const [originLng, originLat] = origin.geometry.coordinates;
+ const { xUnit, yUnit } = getXYUnit([originLng, originLat]);
+
+
+ const c1 = GPS2XY(c1Config.LngLat, [originLng, originLat], [xUnit, yUnit]);
+ const c1RMax = (c1Config.r + c1Config.rDiff) * 100;
+ const c1RMin = (c1Config.r - c1Config.rDiff) * 100;
+ const c1R = c1Config.r * 100;
+
+ const c2 = GPS2XY(c2Config.LngLat, [originLng, originLat], [xUnit, yUnit]);
+ const c2RMax = (c2Config.r + c2Config.rDiff) * 100;
+ const c2RMin = (c2Config.r - c2Config.rDiff) * 100;
+ const c2R = c2Config.r * 100;
+
+ const eightBg1 = new Path({
+ path: `P${c1.x} ${c1.y} ${c1RMin} M${c1.x + c1RMax} ${c1.y} P${c1.x} ${c1.y} ${c1RMax}`,
+ fill: '#ffffff5f',
+ windingRule: 'evenodd',
+ })
+ const eightBg1Center = new Path({
+ path: `P${c1.x} ${c1.y} 20`,
+ fill: '#FF0000'
+ })
+ const leftC = new Path({
+ path: `P${c1.x} ${c1.y} ${c1R}`,
+ stroke: '#FF0000',
+ strokeWidth: 4,
+ // windingRule: 'evenodd',
+ })
+
+ const eightBg2 = new Path({
+ path: `P${c2.x} ${c2.y} ${c2RMin} M${c2.x + c2RMax} ${c2.y} P${c2.x} ${c2.y} ${c2RMax}`,
+ fill: '#ffffff5f',
+ windingRule: 'evenodd',
+ })
+ const eightBg2Center = new Path({
+ path: `P${c2.x} ${c2.y} 20`,
+ fill: '#FF0000'
+ })
+ const rightC = new Path({
+ path: `P${c2.x} ${c2.y} ${c2R}`,
+ stroke: '#FF0000',
+ strokeWidth: 4,
+ // windingRule: 'evenodd',
+ })
+
+ leafer.add(eightBg1);
+ leafer.add(eightBg2);
+ leafer.add(eightBg1Center);
+ leafer.add(eightBg2Center);
+ leafer.add(leftC);
+ leafer.add(rightC);
+
+
+ const indexMap = [6, 0, 1, 3, 4, 5, 2];
+ const center1 = turf.point(c1Config.LngLat);
+ const center2 = turf.point(c2Config.LngLat);
+ const radius1 = c1Config.r;
+ const radius2 = c2Config.r;
+
+ // 生成基准交点
+ const basePoint1 = turf.destination(center1, radius1, bearing, { units: 'meters' }); // 圆1交点
+ const basePoint2 = turf.destination(center2, radius2, bearing + 180, { units: 'meters' }); // 圆2交点
+
+ // 生成旋转点集合
+ const generateRotatedPoints = (center, baseCoord) => {
+ const points = [];
+ for(let angle = 0; angle < 360; angle += 90) {
+ const rotatedCoord = turf.transformRotate(
+ turf.point(baseCoord),
+ angle,
+ {pivot: center}
+ ).geometry.coordinates;
+ points.push(rotatedCoord);
+ }
+ return points.slice(1); // 排除原始点
+ };
+
+ // 生成所有标记点
+ const positions = [
+ ...generateRotatedPoints(center1, basePoint1.geometry.coordinates),
+ ...generateRotatedPoints(center2, basePoint2.geometry.coordinates),
+ turf.midpoint(center1, center2).geometry.coordinates // 连线中点
+ ];
+
+ positions.forEach((coord, index) => {
+ // 为中点位置添加圆形背景
+ if (index === 6) {
+ const c3 = GPS2XY([coord[0], coord[1]], [originLng, originLat], [xUnit, yUnit]);
+ const c3R = c3Config.r * 100;
+ const midC = new Path({
+ path: `P${c3.x} ${c3.y} ${c3R}`,
+ stroke: '#FF0000',
+ strokeWidth: 4,
+ // windingRule: 'evenodd',
+ })
+ leafer.add(midC);
+ }
+
+ const tmp = GPS2XY([coord[0], coord[1]], [originLng, originLat], [xUnit, yUnit]);
+ const text = new Text({
+ x: tmp.x,
+ y: tmp.y,
+ fill: '#ffffff',
+ text: indexMap[index] + 1 + '',
+ fontSize: 80,
+ around: 'center',
+ // offsetX: -30,
+ // offsetY: -60,
+ rotation: (bearing - 90),
+ stroke: {
+ type: 'solid',
+ color: '#000000'
+ }
+ })
+ leafer.add(text);
+ });
+ }
+
+ return {
+ createField,
+ setScene,
+ }
+}
diff --git a/src/core/useLostPoint.js b/src/core/useLostPoint.js
new file mode 100644
index 0000000..7d72b21
--- /dev/null
+++ b/src/core/useLostPoint.js
@@ -0,0 +1,39 @@
+import { Path, Text } from '@leafer-ui/miniapp';
+
+export const useLostPoint = () => {
+ let leafer;
+
+ function setScene(Leafer) {
+ leafer = Leafer;
+ }
+
+ function createLostPoint({ x, y }) {
+ const lBg = new Path({
+ path: `P${x} ${y} 40`,
+ fill: '#a60000',
+ stroke: '#ffffff',
+ strokeWidth: 4,
+ })
+
+ const l = new Text({
+ x,
+ y,
+ fill: '#ffffff',
+ text: 'L',
+ fontSize: 80,
+ around: 'center',
+ // stroke: {
+ // type: 'solid',
+ // color: '#000000'
+ // }
+ })
+
+ leafer.add(lBg);
+ leafer.add(l);
+ }
+
+ return {
+ createLostPoint,
+ setScene,
+ }
+}
diff --git a/src/core/useScene.js b/src/core/useScene.js
new file mode 100644
index 0000000..94d0828
--- /dev/null
+++ b/src/core/useScene.js
@@ -0,0 +1,196 @@
+import Taro from '@tarojs/taro';
+import {Leafer, Path, Rect, useCanvas, Text, Group, Ellipse} from '@leafer-ui/miniapp';
+import '@leafer-in/view';
+import '@leafer-in/viewport';
+import { getXYUnit, GPS2XY } from "../utils/helpers";
+import * as turf from "@turf/turf";
+
+useCanvas('canvas', Taro) // 绑定平台全局变量
+
+export const useScene = () => {
+ let leafer = undefined;
+ let oLng = 0;
+ let oLat = 0;
+ let xUnit = 0;
+ let yUnit = 0;
+ let bearing = 0;
+
+ function receiveEvent(event) {
+ if (leafer) {
+ leafer.receiveEvent(event) // 需手动接收、传递画布交互事件给leafer
+ }
+ }
+
+ function init() {
+ leafer = new Leafer({ view: 'leafer', type: 'viewport' });
+ }
+
+ function createXYAxis({ c1LngLat, c2LngLat } = {}) {
+ const [originLng, originLat] = turf.midpoint(c1LngLat, c2LngLat).geometry.coordinates;
+ const { xUnit: x, yUnit: y } = getXYUnit([originLng, originLat]);
+ xUnit = x;
+ yUnit = y;
+ oLng = originLng;
+ oLat = originLat;
+ //
+ bearing = turf.bearing(c1LngLat, c2LngLat);
+ }
+
+ let c1Config = {};
+ let c2Config = {};
+ let c3Config = {};
+ function loadFieldData(c1, c2, c3) {
+ c1Config = { ...c1 };
+ c2Config = { ...c2 };
+ c3Config = { ...c3 };
+ }
+
+ function createField(field) {
+ field.setScene(leafer);
+ field.createField(c1Config, c2Config, c3Config);
+ }
+
+ function fitView() {
+ if (bearing) {
+ leafer.rotateOf('center', -(bearing - 90));
+ }
+ leafer.zoom('fit');
+ }
+
+ let trackList = [];
+ function loadTrackData(data = []) {
+ trackList = data.map(item => GPS2XY([item.lng, item.lat], [oLng, oLat], [xUnit, yUnit]));
+ }
+
+ function createTrack(track) {
+ track.setScene(leafer);
+ track.createTrack(trackList);
+ }
+
+ let errList = [];
+ function loadErrData(data = []) {
+ errList = data.map((item) => {
+ return GPS2XY([item?.lng, item?.lat], [oLng, oLat], [xUnit, yUnit]);
+ });
+ }
+
+ function createErrPoint(errPoint) {
+ errPoint.setScene(leafer, bearing);
+ errPoint.createErrPoint(errList);
+ }
+
+ let droneData = {};
+ function loadDroneData(data = {}) {
+ const { x, y } = GPS2XY([data?.lng, data?.lat], [oLng, oLat], [xUnit, yUnit]);
+ droneData = { x, y, rotation: data.yaw };
+ }
+
+ function createDrone(drone) {
+ drone.setScene(leafer);
+ drone.createDrone(droneData);
+ }
+
+ function moveDrone(drone) {
+ drone.moveDrone(droneData);
+ }
+
+ let spinData = {};
+ function loadSpinData(data = {}) {
+ const { x, y } = GPS2XY([data?.lng, data?.lat], [oLng, oLat], [xUnit, yUnit]);
+ spinData = { x, y, r: data.spinR * 100 }
+ }
+
+ function createSpin(spin) {
+ spin.setScene(leafer);
+ spin.createSpin(spinData);
+ }
+
+ function removeSpin(spin) {
+ spin.remove();
+ }
+
+ function createTripXYAxis([originLng, originLat] = []) {
+ const { xUnit: x, yUnit: y } = getXYUnit([originLng, originLat]);
+ xUnit = x;
+ yUnit = y;
+ oLng = originLng;
+ oLat = originLat;
+ }
+
+ let tripData = {};
+ function loadTripData(data = {}) {
+ const { x, y } = GPS2XY([data?.lng, data?.lat], [oLng, oLat], [xUnit, yUnit]);
+ tripData = { x, y, oR: data.oR, iR: data.iR, r: data.r };
+ }
+
+ function createTripField(tripField) {
+ tripField.setScene(leafer);
+ tripField.createTripField(tripData);
+ }
+
+ let lostPointData = {};
+ function loadLostPointData(data = {}) {
+ lostPointData = GPS2XY([data?.lng, data?.lat], [oLng, oLat], [xUnit, yUnit]);
+ }
+
+ function createLostPoint(lostPoint) {
+ lostPoint.setScene(leafer);
+ lostPoint.createLostPoint(lostPointData);
+ }
+
+ let fenceData = {};
+ function loadFenceData(data = {}) {
+ //
+ // const distance = 10;
+ // const destination2 = turf.destination(data.c1LngLat, data.topMargin, (bearing - 90), { units: "meters" }).geometry.coordinates;
+ // const leftT = turf.destination(destination2, data.leftMargin, -90 + (bearing - 90), { units: "meters" }).geometry.coordinates.map(item => item.toFixed(7) - 0);
+ //
+ // const destination1 = turf.destination(data.c1LngLat, data.bottomMargin, 180 + (bearing - 90), { units: "meters" }).geometry.coordinates;
+ // const leftB = turf.destination(destination1, data.leftMargin, -90 + (bearing - 90), { units: "meters" }).geometry.coordinates.map(item => item.toFixed(7) - 0);
+ //
+ // const destination3 = turf.destination(data.c2LngLat, data.bottomMargin, 180 + (bearing - 90), { units: "meters" }).geometry.coordinates;
+ // const rightB = turf.destination(destination3, data.rightMargin, 90 + (bearing - 90), { units: "meters" }).geometry.coordinates.map(item => item.toFixed(7) - 0);
+ //
+ // const destination4 = turf.destination(data.c2LngLat, data.topMargin, (bearing - 90), { units: "meters" }).geometry.coordinates;
+ // const rightT = turf.destination(destination4, data.rightMargin, 90 + (bearing - 90), { units: "meters" }).geometry.coordinates.map(item => item.toFixed(7) - 0);
+ //
+ //
+ const { leftT, rightT, leftB, rightB } = data || {};
+ //
+ const leftTop = GPS2XY(leftT, [oLng, oLat], [xUnit, yUnit]);
+ const rightTop = GPS2XY(rightT, [oLng, oLat], [xUnit, yUnit]);
+ const leftBottom = GPS2XY(leftB, [oLng, oLat], [xUnit, yUnit]);
+ const rightBottom = GPS2XY(rightB, [oLng, oLat], [xUnit, yUnit]);
+ fenceData = { leftTop, rightTop, leftBottom, rightBottom };
+ }
+ function createFence(fence) {
+ fence.setScene(leafer);
+ fence.createFence(fenceData);
+ }
+
+ return {
+ init,
+ createXYAxis,
+ loadFieldData,
+ createField,
+ receiveEvent,
+ fitView,
+ loadTrackData,
+ createTrack,
+ loadErrData,
+ createErrPoint,
+ loadDroneData,
+ createDrone,
+ moveDrone,
+ createSpin,
+ removeSpin,
+ loadSpinData,
+ createTripXYAxis,
+ loadTripData,
+ createTripField,
+ loadLostPointData,
+ createLostPoint,
+ loadFenceData,
+ createFence,
+ }
+}
diff --git a/src/core/useSpin.js b/src/core/useSpin.js
new file mode 100644
index 0000000..6b94fb7
--- /dev/null
+++ b/src/core/useSpin.js
@@ -0,0 +1,34 @@
+import { Path } from '@leafer-ui/miniapp';
+
+export const useSpin = () => {
+ let leafer;
+ let spin;
+
+ function setScene(Leafer) {
+ leafer = Leafer;
+ }
+
+ function remove() {
+ if (spin) {
+ spin.remove();
+ }
+ }
+
+ function createSpin({ x, y, r }) {
+ const spinC = new Path({
+ path: `P${x} ${y} ${r}`,
+ stroke: '#ffd500',
+ strokeWidth: 4,
+ zIndex: 2
+ // windingRule: 'evenodd',
+ })
+ spin = spinC;
+ leafer.add(spinC)
+ }
+
+ return {
+ createSpin,
+ setScene,
+ remove,
+ }
+}
diff --git a/src/core/useTrack.js b/src/core/useTrack.js
new file mode 100644
index 0000000..3610547
--- /dev/null
+++ b/src/core/useTrack.js
@@ -0,0 +1,37 @@
+import { Path } from '@leafer-ui/miniapp';
+
+export const useTrack = () => {
+ let leafer;
+ let line;
+
+ function remove() {
+ if (line) {
+ line.remove()
+ }
+ }
+
+ function setScene(Leafer) {
+ leafer = Leafer;
+ }
+
+ function createTrack(points = []) {
+ remove();
+ const pathData = points.map(((tmp, index) => {
+ if (index === 0) {
+ return [1, tmp.x, tmp.y];
+ }
+ return [2, tmp.x, tmp.y];
+ }));
+ line = new Path({
+ path: pathData.flat(),
+ stroke: '#25814f',
+ strokeWidth: 6,
+ zIndex: 1,
+ })
+ leafer.add(line);
+ }
+
+ return {
+ createTrack,
+ setScene,}
+}
diff --git a/src/core/useTripField.js b/src/core/useTripField.js
new file mode 100644
index 0000000..253ae2c
--- /dev/null
+++ b/src/core/useTripField.js
@@ -0,0 +1,58 @@
+import { Path, Text } from '@leafer-ui/miniapp';
+
+export const useTripField = () => {
+ let leafer;
+
+ function setScene(Leafer) {
+ leafer = Leafer;
+ }
+
+ function createTripField(c = {}) {
+ const cRMax = c.oR * 100;
+ const cRMin = c.iR * 100;
+ const cR2 = c.r * 100;
+
+ const field = new Path({
+ path: `P${c.x} ${c.y} ${cRMin} M${c.x + cRMax} ${c.y} P${c.x} ${c.y} ${cRMax}`,
+ fill: '#ffffff5f',
+ windingRule: 'evenodd',
+ });
+
+ const saveC = new Path({
+ path: `P${c.x} ${c.y} ${cR2}`,
+ stroke: '#00e1ff',
+ strokeWidth: 4,
+ // windingRule: 'evenodd',
+ });
+
+ const hBg = new Path({
+ path: `P${c.x} ${c.y} 40`,
+ fill: '#18a600',
+ stroke: '#ffffff',
+ strokeWidth: 4,
+ })
+
+ const h = new Text({
+ x: c.x,
+ y: c.y,
+ fill: '#ffffff',
+ text: 'H',
+ fontSize: 80,
+ around: 'center',
+ // stroke: {
+ // type: 'solid',
+ // color: '#000000'
+ // }
+ })
+
+ leafer.add(field);
+ leafer.add(saveC);
+ leafer.add(hBg);
+ leafer.add(h);
+ }
+
+ return {
+ createTripField,
+ setScene,
+ }
+}
diff --git a/src/pages/airfield/index.vue b/src/pages/airfield/index.vue
index 8806599..82d50b6 100644
--- a/src/pages/airfield/index.vue
+++ b/src/pages/airfield/index.vue
@@ -1,5 +1,5 @@
@@ -160,7 +188,7 @@ function onHandleEditor() {
-
![场地图片]()
+
当前班级:{{ item?.classDetailResp?.name || '-' }}
@@ -190,7 +218,7 @@ function onHandleEditor() {
@@ -211,7 +239,7 @@ function onHandleEditor() {
/>
-
+
@@ -223,6 +251,13 @@ function onHandleEditor() {
diff --git a/src/pages/flightMap2/LeftSide.vue b/src/pages/flightMap2/LeftSide.vue
new file mode 100644
index 0000000..beb12b6
--- /dev/null
+++ b/src/pages/flightMap2/LeftSide.vue
@@ -0,0 +1,205 @@
+
+
+
+
+
+
错误次数: {{ (info?.errInfo || []).length }}
+
+ {{ index + 1 }}.{{ item?.text }}
+
+
+
+
考试阶段
+
{{ info?.stageInfo?.text }}
+
+
+
+
+
+
+
+
diff --git a/src/pages/flightMap2/RightSide.vue b/src/pages/flightMap2/RightSide.vue
new file mode 100644
index 0000000..5f3bff1
--- /dev/null
+++ b/src/pages/flightMap2/RightSide.vue
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
航向角:
+
{{ (((info?.yaw ?? 0) + 360) % 360).toFixed(2) }}°
+
+
+
+
+
+
俯仰角:
+
{{ info?.pitch }}°
+
+
+
+
+
+
横滚角:
+
{{ info?.roll }}°
+
+
+
+
+
+
海拔:
+
{{ falsyTo(info?.alt) }} m
+
+
+
+
+
+
高度:
+
{{ falsyTo(info?.height) }} m
+
+
+
+
+
+
GPS:
+
{{ falsyTo(info?.satellite) }}
+
+
+
+
+
+
定位类型:
+
{{ info?.locType ? GPS_FIX_TYPE3[info?.locType] : '-' }}
+
+
+
+
+
+
模式:
+
{{ FLY_MODE[info?.flyMode] || '-' }}
+
+
+
+
+
+
+
diff --git a/src/pages/flightMap2/TopSide.vue b/src/pages/flightMap2/TopSide.vue
new file mode 100644
index 0000000..d946be3
--- /dev/null
+++ b/src/pages/flightMap2/TopSide.vue
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
{{ flightDetail?.studentName }}
+
姓名
+
+
+
+
{{ weightClasses[flightDetail?.weightClass] || '?' }}无人机
+
等级
+
+
+
{{ tmp[flightDetail?.licenseGrade] }}
+
考试标准
+
+
+
{{ flightDetail?.airfield?.name }}
+
场地
+
+
+
+
+
+
diff --git a/src/pages/flightMap2/index.config.js b/src/pages/flightMap2/index.config.js
new file mode 100644
index 0000000..cf36a4c
--- /dev/null
+++ b/src/pages/flightMap2/index.config.js
@@ -0,0 +1,5 @@
+export default {
+ navigationBarTitleText: '实践飞行回放',
+ navigationStyle: 'custom',
+ pageOrientation: 'landscape'
+};
diff --git a/src/pages/flightMap2/index.vue b/src/pages/flightMap2/index.vue
new file mode 100644
index 0000000..4cd5439
--- /dev/null
+++ b/src/pages/flightMap2/index.vue
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/home/index.config.js b/src/pages/home/index.config.js
index 2502353..3a59105 100644
--- a/src/pages/home/index.config.js
+++ b/src/pages/home/index.config.js
@@ -1,5 +1,5 @@
export default definePageConfig({
- navigationBarTitleText: '享飞低空培训',
+ navigationBarTitleText: '无人机能力评估系统',
enablePullDownRefresh: false,
disableScroll: false,
})
diff --git a/src/pages/home/index.vue b/src/pages/home/index.vue
index a41f0da..608cfa5 100644
--- a/src/pages/home/index.vue
+++ b/src/pages/home/index.vue
@@ -139,10 +139,6 @@
-
-
- 低空培训系统技术支持
-
diff --git a/src/pages/login/index.vue b/src/pages/login/index.vue
index 77e9074..92201ec 100644
--- a/src/pages/login/index.vue
+++ b/src/pages/login/index.vue
@@ -1,15 +1,31 @@
@@ -98,8 +160,12 @@
{{ userInfo?.name || '-' }}