@yangyongtao/gaea 1.1.10 → 1.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,289 @@
1
+ <template>
2
+ <Teleport to="body">
3
+ <div
4
+ v-if="dialogVisible"
5
+ class="common-dialog-overlay"
6
+ :class="{ 'overlay-enter': showAnimation }"
7
+ @click="handleOverlayClick"
8
+ >
9
+ <div
10
+ class="common-dialog-container"
11
+ :class="{ 'dialog-enter': showAnimation }"
12
+ :style="{ width: width + 'px', height: height + 'px' }"
13
+ @click.stop
14
+ >
15
+ <!-- 头部 -->
16
+ <div class="dialog-header" v-if="showHeader">
17
+ <span class="dialog-title">{{ title }}</span>
18
+ <span class="dialog-close" v-if="closable" @click="handleClose">✕</span>
19
+ </div>
20
+
21
+ <!-- 内容区 -->
22
+ <div class="dialog-body" :style="{ maxHeight: maxHeight + 'px' }">
23
+ <slot />
24
+ </div>
25
+
26
+ <!-- 底部 -->
27
+ <div class="dialog-footer" v-if="showFooter">
28
+ <slot name="footer">
29
+ <button class="dialog-btn btn-cancel" v-if="showCancelBtn" @click="handleCancel">
30
+ {{ cancelText }}
31
+ </button>
32
+ <button class="dialog-btn btn-confirm" :disabled="confirmLoading" @click="handleConfirm">
33
+ <span v-if="confirmLoading" class="loading-spinner"></span>
34
+ {{ confirmText }}
35
+ </button>
36
+ </slot>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </Teleport>
41
+ </template>
42
+
43
+ <script setup>
44
+ import { ref, watch, onBeforeUnmount } from 'vue';
45
+
46
+ const props = defineProps({
47
+ // v-model:visible
48
+ modelValue: { type: Boolean, default: false },
49
+ title: { type: String, default: '提示' },
50
+ width: { type: [String, Number], default: 1662 },
51
+ height: { type: [String, Number], default: 773 },
52
+ maxHeight: { type: [String, Number], default: 647 },
53
+ // 显示控制
54
+ showHeader: { type: Boolean, default: true },
55
+ showFooter: { type: Boolean, default: false },
56
+ showCancelBtn: { type: Boolean, default: true },
57
+ closable: { type: Boolean, default: true },
58
+ // 按钮文案
59
+ confirmText: { type: String, default: '确认' },
60
+ cancelText: { type: String, default: '取消' },
61
+ // 行为
62
+ closeOnOverlay: { type: Boolean, default: true },
63
+ confirmLoading: { type: Boolean, default: false },
64
+ // 确认后是否自动关闭
65
+ autoCloseOnConfirm: { type: Boolean, default: true },
66
+ });
67
+
68
+ const emit = defineEmits(['update:modelValue', 'confirm', 'cancel', 'close']);
69
+
70
+ const dialogVisible = ref(false);
71
+ const showAnimation = ref(false);
72
+ let timer = null;
73
+
74
+ watch(() => props.modelValue, (val) => {
75
+ if (val) {
76
+ dialogVisible.value = true;
77
+ // 下一帧触发动画
78
+ timer = setTimeout(() => { showAnimation.value = true; }, 16);
79
+ document.body.style.overflow = 'hidden';
80
+ } else {
81
+ showAnimation.value = false;
82
+ timer = setTimeout(() => {
83
+ dialogVisible.value = false;
84
+ }, 300);
85
+ document.body.style.overflow = '';
86
+ }
87
+ }, { immediate: true });
88
+
89
+ onBeforeUnmount(() => {
90
+ document.body.style.overflow = '';
91
+ clearTimeout(timer);
92
+ });
93
+
94
+ function close() {
95
+ emit('close');
96
+ emit('update:modelValue', false);
97
+ }
98
+
99
+ function handleOverlayClick() {
100
+ if (props.closeOnOverlay && props.closable) close();
101
+ }
102
+
103
+ function handleClose() {
104
+ if (props.closable) close();
105
+ }
106
+
107
+ function handleCancel() {
108
+ emit('cancel');
109
+ close();
110
+ }
111
+
112
+ function handleConfirm() {
113
+ if (props.confirmLoading) return;
114
+ emit('confirm');
115
+ if (props.autoCloseOnConfirm) close();
116
+ }
117
+ </script>
118
+
119
+ <style lang="less" scoped>
120
+ @font-face {
121
+ font-family: 'youshebiaotihei';
122
+ src: url('/font/youshebiaotihei.ttf') format('truetype');
123
+ font-weight: normal;
124
+ font-style: normal;
125
+ }
126
+
127
+ .common-dialog-overlay {
128
+ position: fixed;
129
+ inset: 0;
130
+ background: rgba(0, 0, 0, 0.5);
131
+ display: flex;
132
+ align-items: center;
133
+ justify-content: center;
134
+ z-index: 9999999;
135
+ opacity: 0;
136
+ transition: opacity 0.3s ease;
137
+
138
+ &.overlay-enter {
139
+ opacity: 1;
140
+ }
141
+ }
142
+
143
+ .common-dialog-container {
144
+ background: url('/image/CommonDialog/弹窗_bg.png') no-repeat center;
145
+ background-size: 100% 100%;
146
+ padding: 30px;
147
+ box-sizing: border-box;
148
+ display: flex;
149
+ flex-direction: column;
150
+
151
+ &.dialog-enter {
152
+ transform: translateY(0);
153
+ opacity: 1;
154
+ }
155
+ }
156
+
157
+ // 头部
158
+ .dialog-header {
159
+ display: flex;
160
+ align-items: center;
161
+ justify-content: space-between;
162
+ height: 44px;
163
+ padding: 0 16px;
164
+ border-radius: 8px 8px 0 0;
165
+ user-select: none;
166
+ background: url('/image/CommonDialog/弹窗标题背景.png') no-repeat center;
167
+ background-size: 100% 100%;
168
+
169
+ .dialog-title {
170
+ font-family: 'youshebiaotihei';
171
+ font-size: 24px;
172
+ font-weight: normal;
173
+ color: #fff;
174
+ letter-spacing: 1px;
175
+ -webkit-font-smoothing: antialiased;
176
+ -moz-osx-font-smoothing: grayscale;
177
+ display: flex;
178
+ align-items: center;
179
+ gap: 8px;
180
+
181
+ &::before {
182
+ content: '';
183
+ display: inline-block;
184
+ width: 40px;
185
+ height: 40px;
186
+ background: url('/image/CommonDialog/弹窗标题装饰@2x.png') no-repeat center;
187
+ background-size: contain;
188
+ }
189
+ }
190
+
191
+ .dialog-close {
192
+ width: 24px;
193
+ height: 24px;
194
+ display: flex;
195
+ align-items: center;
196
+ justify-content: center;
197
+ color: rgba(255, 255, 255, 0.7);
198
+ font-size: 16px;
199
+ cursor: pointer;
200
+ border-radius: 4px;
201
+ transition: all 0.2s;
202
+
203
+ &:hover {
204
+ background: rgba(255, 255, 255, 0.15);
205
+ color: #fff;
206
+ }
207
+ }
208
+ }
209
+
210
+ // 内容区
211
+ .dialog-body {
212
+ padding: 20px 24px;
213
+ color: rgba(255, 255, 255, 0.9);
214
+ font-size: 14px;
215
+ overflow-y: auto;
216
+ flex: 1;
217
+ display: flex;
218
+ flex-direction: column;
219
+
220
+ &::-webkit-scrollbar {
221
+ width: 4px;
222
+ }
223
+
224
+ &::-webkit-scrollbar-thumb {
225
+ background: #0088FF;
226
+ border-radius: 2px;
227
+ }
228
+ }
229
+
230
+ // 底部
231
+ .dialog-footer {
232
+ display: flex;
233
+ justify-content: flex-end;
234
+ gap: 10px;
235
+ padding: 12px 16px;
236
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
237
+
238
+ .dialog-btn {
239
+ padding: 7px 22px;
240
+ border-radius: 4px;
241
+ font-size: 13px;
242
+ cursor: pointer;
243
+ border: none;
244
+ transition: all 0.2s;
245
+ display: flex;
246
+ align-items: center;
247
+ gap: 6px;
248
+
249
+ &.btn-cancel {
250
+ background: rgba(255, 255, 255, 0.1);
251
+ color: rgba(255, 255, 255, 0.8);
252
+ border: 1px solid rgba(255, 255, 255, 0.15);
253
+
254
+ &:hover {
255
+ background: rgba(255, 255, 255, 0.2);
256
+ }
257
+ }
258
+
259
+ &.btn-confirm {
260
+ background: #3b82f6;
261
+ color: #fff;
262
+
263
+ &:hover {
264
+ background: #2563eb;
265
+ }
266
+
267
+ &:disabled {
268
+ opacity: 0.6;
269
+ cursor: not-allowed;
270
+ }
271
+ }
272
+
273
+ .loading-spinner {
274
+ width: 14px;
275
+ height: 14px;
276
+ border: 2px solid rgba(255, 255, 255, 0.3);
277
+ border-top-color: #fff;
278
+ border-radius: 50%;
279
+ animation: spin 0.6s linear infinite;
280
+ }
281
+ }
282
+ }
283
+
284
+ @keyframes spin {
285
+ to {
286
+ transform: rotate(360deg);
287
+ }
288
+ }
289
+ </style>
@@ -30,13 +30,12 @@
30
30
  </div>
31
31
 
32
32
  <div class="empty-tip" v-else>
33
- 暂无飞行点位,请通过地图点击或手动输入添加
33
+ 暂无飞行点位,请开启"地图拾取坐标"后点击地图添加
34
34
  </div>
35
35
 
36
- <!-- 新增点位 -->
36
+ <!-- 添加点位:统一使用地图拾取 -->
37
37
  <div class="form-section">
38
38
  <h4>添加点位</h4>
39
- <!-- 地图拾取模式开关 -->
40
39
  <div class="pick-row">
41
40
  <button class="btn" :class="isPicking ? 'btn-picking-active' : 'btn-picking'" @click="togglePickMode">
42
41
  <i class="fas" :class="isPicking ? 'fa-check-circle' : 'fa-map-marker-alt'"></i>
@@ -45,27 +44,14 @@
45
44
  </div>
46
45
  <div class="form-row">
47
46
  <div class="form-group">
48
- <label>名称</label>
49
- <input type="text" v-model="inputName" class="form-control" placeholder="点位名称" />
50
- </div>
51
- </div>
52
- <div class="form-row">
53
- <div class="form-group">
54
- <label>经度</label>
55
- <input type="number" v-model.number="inputLng" class="form-control" placeholder="点击地图或手动输入"
56
- step="0.000001" />
47
+ <label>飞行高度(m)</label>
48
+ <input type="number" v-model.number="flightHeight" class="form-control" min="0" step="10" placeholder="飞行高度" />
57
49
  </div>
58
50
  <div class="form-group">
59
- <label>纬度</label>
60
- <input type="number" v-model.number="inputLat" class="form-control" placeholder="点击地图或手动输入"
61
- step="0.000001" />
62
- </div>
63
- <div class="form-group">
64
- <label>高度(m)</label>
65
- <input type="number" v-model.number="inputAlt" class="form-control" placeholder="高度" />
51
+ <label>飞行时长(秒)</label>
52
+ <input type="number" v-model.number="flightDuration" class="form-control" min="1" step="1" placeholder="飞行时长" />
66
53
  </div>
67
54
  </div>
68
- <button class="btn btn-add" @click="addManualWaypoint">添加点位</button>
69
55
  </div>
70
56
 
71
57
  <!-- 飞行控制 -->
@@ -105,11 +91,10 @@ const currentIndex = ref(-1);
105
91
  const isFlying = ref(false);
106
92
  const isFlyOver = ref(false);
107
93
 
108
- // 手动输入
109
- const inputName = ref('');
110
- const inputLng = ref(null);
111
- const inputLat = ref(null);
112
- const inputAlt = ref(500);
94
+ // 固定飞行高度(避免拾取时高度随地形累加),可在面板中调整
95
+ const flightHeight = ref(150);
96
+ // 飞行时长(秒),可在面板中调整
97
+ const flightDuration = ref(20);
113
98
 
114
99
  // 地图拾取模式
115
100
  const isPicking = ref(false);
@@ -149,7 +134,7 @@ const togglePickMode = () => {
149
134
  name: `点位 ${waypoints.value.length + 1}`,
150
135
  lat: +pos.lat.toFixed(6),
151
136
  lng: +pos.lng.toFixed(6),
152
- alt: +pos.alt.toFixed(1) || 500,
137
+ alt: Number(flightHeight.value) || 150,
153
138
  });
154
139
  }
155
140
  };
@@ -171,22 +156,6 @@ const removeCanvasListener = () => {
171
156
  }
172
157
  };
173
158
 
174
- // 手动添加点位
175
- const addManualWaypoint = () => {
176
- if (inputLng.value == null || inputLat.value == null) return;
177
- const name = inputName.value || `点位 ${waypoints.value.length + 1}`;
178
- waypoints.value.push({
179
- name,
180
- lat: inputLat.value,
181
- lng: inputLng.value,
182
- alt: inputAlt.value || 500,
183
- });
184
- inputName.value = '';
185
- inputLng.value = null;
186
- inputLat.value = null;
187
- inputAlt.value = 500;
188
- };
189
-
190
159
  // 删除点位
191
160
  const removeWaypoint = (index) => {
192
161
  waypoints.value.splice(index, 1);
@@ -217,11 +186,11 @@ const startFlight = async () => {
217
186
  const lineData = waypoints.value.map(wp => ({
218
187
  x: wp.lat,
219
188
  y: wp.lng,
220
- z: 150
189
+ z: Number(flightHeight.value) || 150
221
190
  }));
222
191
 
223
192
  await APP.flyAlongPath(lineData, {
224
- duration: 20.0,
193
+ duration: Number(flightDuration.value) > 0 ? Number(flightDuration.value) : 20,
225
194
  xrotation: 30,
226
195
  lockEye: false
227
196
  });
@@ -278,7 +247,7 @@ onBeforeUnmount(() => {
278
247
  position: absolute;
279
248
  top: 100px;
280
249
  left: 600px;
281
- z-index: 2000;
250
+ z-index: 10000;
282
251
  width: 380px;
283
252
  background: rgba(15, 23, 42, 0.95);
284
253
  border-radius: 12px;
@@ -369,6 +338,27 @@ onBeforeUnmount(() => {
369
338
  .waypoint-list {
370
339
  max-height: 200px;
371
340
  overflow-y: auto;
341
+ padding-right: 4px;
342
+ scrollbar-width: thin;
343
+ scrollbar-color: rgba(59, 130, 246, 0.6) transparent;
344
+ }
345
+
346
+ .waypoint-list::-webkit-scrollbar {
347
+ width: 6px;
348
+ }
349
+
350
+ .waypoint-list::-webkit-scrollbar-track {
351
+ background: transparent;
352
+ border-radius: 3px;
353
+ }
354
+
355
+ .waypoint-list::-webkit-scrollbar-thumb {
356
+ background: linear-gradient(180deg, rgba(59, 130, 246, 0.7), rgba(6, 182, 212, 0.7));
357
+ border-radius: 3px;
358
+ }
359
+
360
+ .waypoint-list::-webkit-scrollbar-thumb:hover {
361
+ background: linear-gradient(180deg, rgba(59, 130, 246, 1), rgba(6, 182, 212, 1));
372
362
  }
373
363
 
374
364
  .waypoint-item {