huweili-cesium 1.2.37 → 1.2.40

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.
@@ -1,1041 +1,191 @@
1
1
  /**
2
- * 防区设置页面 - 地图操作工具函数
3
- *
4
- * 提供防区设置页面所需的地图相关功能,包括:
5
- * 1. 指挥所半球区域的初始化和控制
6
- * 2. 多边形电子围栏的绘制和管理
7
- * 3. 围栏历史记录的管理
8
- *
9
- * @author
10
- * @version 1.0.0
2
+ * 电子围栏绘制完成后,按围栏在屏幕上的范围裁剪地图截图并输出 base64
11
3
  */
12
-
13
- // 导入依赖
14
4
  import * as Cesium from 'cesium'
15
- import { computed, ref, nextTick } from 'vue'
16
- import { ElMessage } from 'element-plus'
17
- import { basicConfig } from 'huweili-cesium/basis'
18
- import { drawFenceNew } from 'huweili-cesium/drawFenceNew'
19
- import { useMapStore } from 'huweili-cesium/stores/mapStore'
20
- import { BaseConfig } from '@/config/global'
21
- import {
22
- initCommandHemisphereZones, // 初始化指挥所半球区域
23
- updateRadiusCommandHemisphereZones, // 更新半球区域半径
24
- setVisibilityCommandHemisphereZones, // 设置半球区域可见性
25
- } from '@/utils/cesium/initCommandHemisphereZones.js'
26
- import { captureFenceScreenshot } from 'huweili-cesium/captureFenceScreenshot.js'
27
- import { listElectronicFence } from '@/api/fence'
28
-
29
- /**
30
- * UI 提示文字常量(使用 Unicode 转义避免 Windows 编辑器编码问题)
31
- */
32
- const MSG = {
33
- fileDesc: '\u9632\u533A\u8BBE\u7F6E\u9875 \u2014 \u5730\u56FE\u76F8\u5173\u80FD\u529B\uFF08\u6307\u6325\u6240\u534A\u7403\u3001\u591A\u8FB9\u5F62\u56F4\u680F\u7ED8\u5236\u7B49\uFF09',
34
- fenceHistoryList: '\u56F4\u680F\u5386\u53F2\u5217\u8868',
35
- zoneEnabled: '\u9632\u533A\u5F00\u5173',
36
- maxFenceCount: '\u6700\u5927\u56F4\u680F\u6570\u91CF',
37
- openFenceFirst: '\u8BF7\u5148\u5F00\u542F\u7535\u5B50\u56F4\u680F',
38
- fenceListFull: '\u56F4\u680F\u5386\u53F2\u5217\u8868\u5DF2\u6EE1\uFF0C\u65E0\u6CD5\u7ED8\u5236',
39
- mapNotReady: '\u5730\u56FE\u5C1A\u672A\u52A0\u8F7D\u5B8C\u6210',
40
- polygonDrawingInProgress: '\u6B63\u5728\u7ED8\u5236\u591A\u8FB9\u5F62\u56F4\u680F\uFF0C\u8BF7\u5148\u5B8C\u6210\u6216\u70B9\uFF0C\u53CC\u51FB\u6216\u53F3\u952E\u7ED3\u675F',
41
- circleDrawingInProgress: '\u6B63\u5728\u7ED8\u5236\u5706\u5F62\u56F4\u680F\uFF0C\u8BF7\u5148\u5B8C\u6210\u6216\u53D6\u6D88',
42
- polygonFencePrefix: '\u591A\u8FB9\u5F62\u56F4\u680F',
43
- circleFencePrefix: '\u5706\u5F62\u56F4\u680F',
44
- drawDone: '\u7ED8\u5236\u5B8C\u6210',
45
- polygonDrawingHint: '\u591A\u8FB9\u5F62\u56F4\u680F\u7ED8\u5236\u4E2D\uFF1A\u5DE6\u952E\u52A0\u70B9\uFF0C\u53CC\u51FB\u6216\u53F3\u952E\u7ED3\u675F',
46
- circleDrawingHint: '\u5706\u5F62\u56F4\u680F\u7ED8\u5236\u4E2D\uFF1A\u7B2C\u4E00\u6B21\u70B9\u51FB\u786E\u5B9A\u5706\u5FC3\uFF0C\u7B2C\u4E8C\u6B21\u70B9\u51FB\u786E\u5B9A\u534A\u5F84',
47
- polygonDrawCancelled: '\u591A\u8FB9\u5F62\u56F4\u680F\u7ED8\u5236\u5DF2\u53D6\u6D88',
48
- circleDrawCancelled: '\u5706\u5F62\u56F4\u680F\u7ED8\u5236\u5DF2\u53D6\u6D88',
49
- cannotStartPolygonDraw: '\u65E0\u6CD5\u5F00\u59CB\u591A\u8FB9\u5F62\u7ED8\u5236',
50
- cannotStartCircleDraw: '\u65E0\u6CD5\u5F00\u59CB\u5706\u5F62\u7ED8\u5236',
51
- userCancelled: '\u5DF2\u53D6\u6D88\u7ED8\u5236',
52
- fenceOutsideIdentifyZone: '\u7535\u5B50\u56F4\u680F\u5FC5\u987B\u5728\u8BC6\u522B\u533A\u4E4B\u5185\uFF0C\u8BF7\u91CD\u65B0\u7ED8\u5236',
53
- }
54
-
55
- /**
56
- * 防区设置页面地图实例ID
57
- * 优先从全局配置获取,若未配置则使用默认值
58
- */
59
- export const DEFENSE_ZONE_MAP_ID =
60
- window.MapInstanceIds?.DEFENSE_ZONE_SETTING || 'defense-zone-setting-map'
61
-
62
- /**
63
- * 最大围栏数量限制
64
- */
65
- const MAX_FENCE_COUNT = 6
5
+ import { useMapStore } from './stores/mapStore.js'
66
6
 
67
- /**
68
- * 获取地图状态管理实例
69
- */
70
- const mapStore = useMapStore()
71
-
72
- /**
73
- * 获取基础地图配置工具函数
74
- */
75
- const { setBeiJingTime, setCesiumCenter } = basicConfig()
76
-
77
- /**
78
- * 获取多边形围栏绘制工具函数
79
- */
80
- const { drawPolygonFenceNoLabel, destroyPolygonFenceNoLabel, destroyPolygonFence, showPolygonFence, drawCircleFence, destroyCircleFence, showCircleFence } = drawFenceNew()
81
-
82
- /**
83
- * 当前地图实例ID(运行时动态赋值)
84
- */
85
- let currentMapId = ''
86
-
87
- /**
88
- * 多边形绘制控制器(用于取消正在进行的绘制操作)
89
- * @type {{ cancel?: () => void } | null}
90
- */
91
- let polygonDrawController = null
92
-
93
- /**
94
- * 圆形绘制控制器(用于取消正在进行的绘制操作)
95
- * @type {{ cancel?: () => void } | null}
96
- */
97
- let circleDrawController = null
98
-
99
- /**
100
- * 是否已显示多边形绘制提示信息(防止重复提示)
101
- */
102
- let polygonDrawingTipShown = false
103
-
104
- /**
105
- * 是否已显示圆形绘制提示信息(防止重复提示)
106
- */
107
- let circleDrawingTipShown = false
108
-
109
- /**
110
- * 识别区信息(用于检测电子围栏是否在识别区内)
111
- */
112
- let identifyZoneInfo = {
113
- center: [0, 0], // [lng, lat]
114
- radius: 0 // 半径(米)
115
- }
116
-
117
- /**
118
- * 获取当前地图ID
119
- * @returns {string} 地图实例ID
120
- */
121
- const getMapId = () => currentMapId || DEFENSE_ZONE_MAP_ID
122
-
123
- /**
124
- * 更新识别区信息
125
- * @param {number} lng - 中心点经度
126
- * @param {number} lat - 中心点纬度
127
- * @param {number} radius - 半径(米)
128
- */
129
- const updateIdentifyZoneInfo = (lng, lat, radius) => {
130
- identifyZoneInfo.center = [lng, lat]
131
- identifyZoneInfo.radius = radius
132
- }
7
+ const scratchCartesian2 = new Cesium.Cartesian2()
133
8
 
134
9
  /**
135
- * 判断点是否在识别区内
136
- * @param {number} lng - 点的经度
137
- * @param {number} lat - 点的纬度
138
- * @returns {boolean} - 是否在识别区内
10
+ * 将围栏顶点经纬度转为 Cartesian3
11
+ * @param {Array<{ lng: number, lat: number, height?: number }>} positions
139
12
  */
140
- const isPointInIdentifyZone = (lng, lat) => {
141
- const { center, radius } = identifyZoneInfo
142
-
143
- // 如果识别区未正确初始化,允许绘制(避免因配置问题导致无法绘制)
144
- if (!center || center.length < 2 || typeof center[0] !== 'number' || typeof center[1] !== 'number') {
145
- console.warn('识别区中心点未正确初始化,允许绘制')
146
- return true
147
- }
148
-
149
- // 如果识别区半径未启用或无效,允许绘制
150
- if (!radius || radius <= 0 || typeof radius !== 'number') {
151
- console.warn('识别区半径未正确初始化,允许绘制')
152
- return true
153
- }
154
-
155
- // 使用Haversine公式计算两点之间的距离(米)
156
- const R = 6371000 // 地球半径(米)
157
- const dLat = (lat - center[1]) * Math.PI / 180
158
- const dLng = (lng - center[0]) * Math.PI / 180
159
- const a =
160
- Math.sin(dLat/2) * Math.sin(dLat/2) +
161
- Math.cos(center[1] * Math.PI / 180) * Math.cos(lat * Math.PI / 180) *
162
- Math.sin(dLng/2) * Math.sin(dLng/2)
163
- const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
164
- const distance = R * c
165
-
166
- const isInside = distance <= radius
167
- console.log(`点(${lng}, ${lat}) 到中心点(${center[0]}, ${center[1]}) 的距离: ${distance.toFixed(2)}m, 半径: ${radius}m, 是否在圈内: ${isInside}`)
168
-
169
- return isInside
13
+ function toCartesians(positions) {
14
+ return positions.map((p) =>
15
+ Cesium.Cartesian3.fromDegrees(Number(p.lng), Number(p.lat), Number(p.height) || 0)
16
+ )
170
17
  }
171
18
 
172
19
  /**
173
- * 将Cesium Cartesian3坐标转换为经纬度
174
- * @param {Object} cartesian - Cesium Cartesian3对象
175
- * @param {Object} map - Cesium地图实例
176
- * @returns {Object} - 包含lng和lat的对象
20
+ * 相机飞到刚好能看清围栏
177
21
  */
178
- const cartesianToLngLat = (cartesian, map) => {
179
- if (!cartesian || !map) return null
180
-
181
- try {
182
- const ellipsoid = map.scene?.globe?.ellipsoid || Cesium.Ellipsoid.WGS84
183
- const cartographic = ellipsoid.cartesianToCartographic(cartesian)
184
- if (cartographic) {
185
- return {
186
- lng: Cesium.Math.toDegrees(cartographic.longitude),
187
- lat: Cesium.Math.toDegrees(cartographic.latitude)
188
- }
22
+ function flyCameraToFence(map, positions, options = {}) {
23
+ return new Promise((resolve) => {
24
+ const cartesians = toCartesians(positions)
25
+ const sphere = Cesium.BoundingSphere.fromPoints(cartesians)
26
+ if (!sphere || !Number.isFinite(sphere.radius)) {
27
+ resolve()
28
+ return
189
29
  }
190
- } catch (e) {
191
- console.warn('Cartesian3转换经纬度失败:', e)
192
- }
193
- return null
194
- }
195
30
 
196
- /**
197
- * 判断多边形是否完全在识别区内
198
- * @param {Array} positions - 多边形顶点数组,格式为 [[lng, lat, height], ...] 或 Cartesian3数组
199
- * @returns {boolean} - 是否完全在识别区内
200
- */
201
- const isPolygonInIdentifyZone = (positions) => {
202
- // 如果没有positions数据,返回false(需要至少3个点才能构成多边形)
203
- if (!positions || !Array.isArray(positions) || positions.length < 3) {
204
- console.warn('positions数据无效:', positions)
205
- return true // 数据无效时允许绘制
206
- }
207
-
208
- console.log('检测多边形顶点数:', positions.length)
209
-
210
- // 获取地图实例(用于坐标转换)
211
- const map = mapStore.getMap(getMapId())
212
-
213
- // 检查每个顶点是否都在识别区内
214
- for (let i = 0; i < positions.length; i++) {
215
- const pos = positions[i]
216
- let lng, lat
217
-
218
- // 处理不同的数据格式
219
- if (Array.isArray(pos)) {
220
- // 格式1: [lng, lat, height] 数组
221
- lng = pos[0]
222
- lat = pos[1]
223
- console.log(`第${i}个顶点(数组格式): (${lng}, ${lat})`)
224
- } else if (pos && typeof pos === 'object') {
225
- // 格式2: 对象格式
226
- if (pos.longitude !== undefined || pos.latitude !== undefined) {
227
- // GeoJSON格式
228
- lng = pos.longitude
229
- lat = pos.latitude
230
- } else if (pos.lng !== undefined || pos.lat !== undefined) {
231
- // 简化格式
232
- lng = pos.lng
233
- lat = pos.lat
234
- } else if (pos.x !== undefined && pos.y !== undefined && pos.z !== undefined) {
235
- // 可能是Cartesian3格式,尝试转换
236
- const latLng = cartesianToLngLat(pos, map)
237
- if (latLng) {
238
- lng = latLng.lng
239
- lat = latLng.lat
240
- console.log(`第${i}个顶点(Cartesian3转换): (${lng}, ${lat})`)
241
- } else {
242
- console.warn(`第${i}个顶点Cartesian3转换失败:`, pos)
243
- continue
244
- }
245
- } else {
246
- console.warn(`第${i}个顶点格式未知:`, pos)
247
- continue
248
- }
249
- } else {
250
- console.warn(`第${i}个顶点格式未知:`, pos)
251
- continue
252
- }
253
-
254
- // 检查坐标是否有效
255
- if (typeof lng !== 'number' || typeof lat !== 'number' || isNaN(lng) || isNaN(lat)) {
256
- console.warn(`第${i}个顶点坐标无效:`, lng, lat)
257
- continue
258
- }
259
-
260
- // 检查坐标范围是否合理(经纬度范围检查)
261
- if (lng < -180 || lng > 180 || lat < -90 || lat > 90) {
262
- console.warn(`第${i}个顶点坐标超出范围: (${lng}, ${lat}),可能是未转换的Cartesian3坐标`)
263
- // 尝试当作Cartesian3转换
264
- const latLng = cartesianToLngLat({ x: lng, y: lat, z: pos?.z || 0 }, map)
265
- if (latLng) {
266
- lng = latLng.lng
267
- lat = latLng.lat
268
- console.log(`转换后坐标: (${lng}, ${lat})`)
269
- } else {
270
- continue
271
- }
272
- }
273
-
274
- if (!isPointInIdentifyZone(lng, lat)) {
275
- console.warn(`第${i}个顶点(${lng}, ${lat})不在识别区内`)
276
- return false
277
- }
278
- }
279
- return true
280
- }
31
+ const range = Math.max(sphere.radius * 2.8, 80)
32
+ const duration = options.flyDuration ?? 0.6
33
+ let settled = false
34
+ const done = () => {
35
+ if (settled) return
36
+ settled = true
37
+ resolve()
38
+ }
39
+
40
+ map.camera.flyToBoundingSphere(sphere, {
41
+ duration,
42
+ offset: new Cesium.HeadingPitchRange(
43
+ 0,
44
+ Cesium.Math.toRadians(options.pitch ?? -55),
45
+ range
46
+ ),
47
+ complete: done,
48
+ cancel: done,
49
+ })
281
50
 
282
- /**
283
- * 格式化围栏时间戳(用于历史记录显示)
284
- * @returns {string} 格式化的时间字符串,格式:YYYY-MM-DD HH:MM:SS
285
- */
286
- const formatFenceTime = () => {
287
- const pad = (n) => String(n).padStart(2, '0')
288
- const d = new Date()
289
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
51
+ setTimeout(done, duration * 1000 + 400)
52
+ })
290
53
  }
291
54
 
292
55
  /**
293
- * 压缩 base64 缩略图(缩小尺寸 + JPEG 低质量,用于入库)
294
- * @param {string} dataUrl
295
- * @param {{ maxWidth?: number, quality?: number }} [options]
56
+ * 等待地图完成一次真实绘制(postRender 后再截图,避免画布为空)
296
57
  */
297
- const compressBase64Image = (dataUrl, { maxWidth = 480, quality = 1 } = {}) =>
298
- new Promise((resolve) => {
299
- if (!dataUrl) {
300
- resolve('')
301
- return
302
- }
303
- const img = new Image()
304
- img.onload = () => {
305
- const scale = img.width > maxWidth ? maxWidth / img.width : 1
306
- const w = Math.max(1, Math.round(img.width * scale))
307
- const h = Math.max(1, Math.round(img.height * scale))
308
- const canvas = document.createElement('canvas')
309
- canvas.width = w
310
- canvas.height = h
311
- const ctx = canvas.getContext('2d')
312
- if (!ctx) {
313
- resolve(dataUrl)
314
- return
315
- }
316
- ctx.drawImage(img, 0, 0, w, h)
317
- try {
318
- resolve(canvas.toDataURL('image/jpeg', quality))
319
- } catch {
320
- resolve(dataUrl)
321
- }
322
- }
323
- img.onerror = () => resolve(dataUrl)
324
- img.src = dataUrl.startsWith('data:') ? dataUrl : `data:image/png;base64,${dataUrl}`
58
+ function waitScenePostRender(map) {
59
+ return new Promise((resolve) => {
60
+ const remover = map.scene.postRender.addEventListener(() => {
61
+ remover()
62
+ resolve()
63
+ })
64
+ map.scene.requestRender()
325
65
  })
326
-
327
- /** 是否围栏样式对象(含 opacity / outlineWidth) */
328
- const isFenceStyleObject = (obj) =>
329
- obj && typeof obj === 'object' && !Array.isArray(obj) && ('opacity' in obj || 'outlineWidth' in obj)
66
+ }
330
67
 
331
68
  /**
332
- * 将截图 base64 写入 dbData 内层样式对象(与 opacity、outlineWidth 同级),而非外层
69
+ * 计算围栏顶点在 canvas 绘图缓冲中的包围盒(像素)
333
70
  */
334
- const embedBase64InDbData = (dbData, thumbnail) => {
335
- const base64 = thumbnail ?? ''
71
+ function computeFenceScreenBBox(map, positions) {
72
+ const scene = map.scene
73
+ let minX = Infinity
74
+ let minY = Infinity
75
+ let maxX = -Infinity
76
+ let maxY = -Infinity
77
+ let valid = 0
336
78
 
337
- if (Array.isArray(dbData)) {
338
- return dbData.map((item) =>
339
- isFenceStyleObject(item) ? { ...item, base64 } : item
79
+ for (const p of positions) {
80
+ const cartesian = Cesium.Cartesian3.fromDegrees(
81
+ Number(p.lng),
82
+ Number(p.lat),
83
+ Number(p.height) || 0
84
+ )
85
+ const win = Cesium.SceneTransforms.worldToDrawingBufferCoordinates(
86
+ scene,
87
+ cartesian,
88
+ scratchCartesian2
340
89
  )
90
+ if (!win || !Number.isFinite(win.x) || !Number.isFinite(win.y)) continue
91
+
92
+ minX = Math.min(minX, win.x)
93
+ minY = Math.min(minY, win.y)
94
+ maxX = Math.max(maxX, win.x)
95
+ maxY = Math.max(maxY, win.y)
96
+ valid++
341
97
  }
342
98
 
343
- if (typeof dbData === 'object' && dbData !== null) {
344
- if (isFenceStyleObject(dbData)) {
345
- return { ...dbData, base64 }
346
- }
347
- for (const key of Object.keys(dbData)) {
348
- const val = dbData[key]
349
- if (isFenceStyleObject(val)) {
350
- return { ...dbData, [key]: { ...val, base64 } }
351
- }
352
- }
99
+ if (valid < 2 || !Number.isFinite(minX)) {
100
+ return null
353
101
  }
354
102
 
355
- return dbData
103
+ return { minX, minY, maxX, maxY }
356
104
  }
357
105
 
358
106
  /**
359
- * 创建防区设置页面的地图操作Hook
360
- *
361
- * @param {Object} options - 配置选项
362
- * @param {import('vue').Ref<Array>} options.fenceHistoryList - 围栏历史列表(响应式)
363
- * @param {import('vue').Reactive<{ fence: boolean, identify: boolean }>} options.zoneEnabled - 防区开关状态(响应式)
364
- * @param {number} [options.maxFenceCount=6] - 最大围栏数量限制
365
- *
366
- * @returns {Object} 包含地图操作方法和计算属性的对象
107
+ * 按包围盒裁剪 Cesium 画布并转为 PNG base64
367
108
  */
368
- export function useProtectSetMap({ fenceHistoryList, zoneEnabled, maxFenceCount = MAX_FENCE_COUNT }) {
369
- /** 最近一次绘制完成、待点击保存的多边形围栏数据 */
370
- const pendingPolygonFence = ref(null)
371
- /** 最近一次绘制完成、待点击保存的圆形围栏数据 */
372
- const pendingCircleFence = ref(null)
373
- /**
374
- * 是否可以绘制围栏(围栏数量未达到上限)
375
- * @type {import('vue').ComputedRef<boolean>}
376
- */
377
- const canDraw = computed(() => fenceHistoryList.value.length < maxFenceCount)
378
-
379
- /**
380
- * 是否可以绘制多边形围栏(电子围栏已开启且数量未达上限)
381
- * @type {import('vue').ComputedRef<boolean>}
382
- */
383
- const canDrawPolygon = computed(
384
- () => zoneEnabled.fence && fenceHistoryList.value.length < maxFenceCount
385
- )
386
-
387
- /**
388
- * 是否可以绘制圆形围栏(电子围栏已开启且数量未达上限)
389
- * @type {import('vue').ComputedRef<boolean>}
390
- */
391
- const canDrawCircle = computed(
392
- () => zoneEnabled.fence && fenceHistoryList.value.length < maxFenceCount
393
- )
394
-
395
- /**
396
- * 多边形绘制按钮的提示标题(根据状态显示不同提示)
397
- * @type {import('vue').ComputedRef<string>}
398
- */
399
- const polygonDrawTitle = computed(() => {
400
- if (!zoneEnabled.fence) return MSG.openFenceFirst // 电子围栏未开启
401
- if (fenceHistoryList.value.length >= maxFenceCount) return MSG.fenceListFull // 围栏数量已满
402
- return '' // 可以绘制,无提示
403
- })
404
-
405
- /**
406
- * 圆形绘制按钮的提示标题(根据状态显示不同提示)
407
- * @type {import('vue').ComputedRef<string>}
408
- */
409
- const circleDrawTitle = computed(() => {
410
- if (!zoneEnabled.fence) return MSG.openFenceFirst // 电子围栏未开启
411
- if (fenceHistoryList.value.length >= maxFenceCount) return MSG.fenceListFull // 围栏数量已满
412
- return '' // 可以绘制,无提示
413
- })
414
-
415
- /**
416
- * 地图加载完成回调函数
417
- * 初始化地图时间、指挥所半球区域和地图中心点
418
- *
419
- * @param {Object} options - 地图加载完成参数
420
- * @param {Object} options.map - Cesium地图实例
421
- * @param {Object} options.center - 地图中心点信息(包含lng, lat)
422
- */
423
- const mapOnLoad = (options) => {
424
- const { map, center } = options
425
- currentMapId = mapStore.getCurrentMapId() || DEFENSE_ZONE_MAP_ID
426
- setBeiJingTime(currentMapId) // 设置地图时间为北京时间
427
-
428
- // 调试信息:查看center对象的实际属性
429
- console.log('mapOnLoad center:', center)
430
- console.log('center属性名:', Object.keys(center))
431
-
432
- // 处理center可能的不同属性名
433
- const centerLng = center.lng || center.longitude || center.x || 0
434
- const centerLat = center.lat || center.latitude || center.y || 0
435
-
436
- // 获取识别区半径配置
437
- const commandIdentificationRadius = window.BaseConfig?.commandHemisphereZone?.identificationRadius || 3000
438
-
439
- // 更新识别区信息(用于后续电子围栏检测)
440
- updateIdentifyZoneInfo(centerLng, centerLat, commandIdentificationRadius)
441
-
442
- console.log('识别区信息已初始化:', identifyZoneInfo)
443
-
444
- initCommandHemisphereZones({ mapId: currentMapId, lng: centerLng, lat: centerLat }) // 初始化指挥所半球
445
- setCesiumCenter({ lng: centerLng, lat: centerLat, map }) // 设置地图中心点
446
-
447
- loadFences()
448
- }
449
-
450
- const loadFences = async () => {
451
- try {
452
- const response = await listElectronicFence()
453
- const data = response?.rows || response || []
454
-
455
- data.forEach((item) => {
456
- if (item.isEnable !== 1) return
457
- if (!item.pointParam) return
109
+ function cropCanvasToBase64(map, bbox, padding = 48) {
110
+ const source = map.scene.canvas
111
+ const x = Math.max(0, Math.floor(bbox.minX - padding))
112
+ const y = Math.max(0, Math.floor(bbox.minY - padding))
113
+ const right = Math.min(source.width, Math.ceil(bbox.maxX + padding))
114
+ const bottom = Math.min(source.height, Math.ceil(bbox.maxY + padding))
115
+ const width = Math.max(1, right - x)
116
+ const height = Math.max(1, bottom - y)
458
117
 
459
- try {
460
- const fenceData = JSON.parse(item.pointParam)
461
- const fenceId = fenceData.id || `fence_${item.id}`
462
- const color = fenceData.style?.color || '#E81224'
463
- const opacity = fenceData.style?.opacity || 0.35
464
- const outlineWidth = fenceData.style?.outlineWidth || 2
465
- const height = fenceData.height || 2
118
+ const target = document.createElement('canvas')
119
+ target.width = width
120
+ target.height = height
121
+ const ctx = target.getContext('2d')
122
+ if (!ctx) return null
466
123
 
467
- if (fenceData.type === 'polygon') {
468
- if (!fenceData.positions || fenceData.positions.length < 3) return
469
-
470
- showPolygonFence({
471
- id: fenceId,
472
- mapId: currentMapId,
473
- positions: fenceData.positions,
474
- name: item.fenceName,
475
- color,
476
- opacity,
477
- outlineWidth,
478
- height,
479
- })
480
- } else if (fenceData.type === 'circle') {
481
- if (!fenceData.center || typeof fenceData.center.lng !== 'number') return
124
+ ctx.drawImage(source, x, y, width, height, 0, 0, width, height)
125
+ return target.toDataURL('image/png')
126
+ }
482
127
 
483
- showCircleFence({
484
- id: fenceId,
485
- mapId: currentMapId,
486
- center: fenceData.center,
487
- radius: fenceData.radius,
488
- name: item.fenceName,
489
- color,
490
- opacity,
491
- outlineWidth,
492
- height,
493
- })
494
- }
495
- } catch {
496
- console.warn('解析电子围栏数据失败:', item.fenceName)
497
- }
498
- })
499
- } catch (error) {
500
- console.error('加载电子围栏列表失败:', error)
501
- }
128
+ /**
129
+ * 围栏绘制完成后截图:先定位围栏,再裁剪仅包含围栏的区域
130
+ *
131
+ * @param {import('cesium').Viewer} map Cesium Viewer
132
+ * @param {Array<{ lng: number, lat: number, height?: number }>} positions 围栏顶点(drawFence onFinish 的 result.positions)
133
+ * @param {Object} [options]
134
+ * @param {boolean} [options.flyTo=false] 截图前是否飞行定位到围栏
135
+ * @param {number} [options.padding=48] 裁剪边距(像素)
136
+ * @param {number} [options.flyDuration=0.6] 飞行动画时长(秒)
137
+ * @param {boolean} [options.printBase64=true] 是否在控制台打印 base64
138
+ * @returns {Promise<string|null>} PNG data URL(含 data:image/png;base64, 前缀)
139
+ */
140
+ export async function captureFenceScreenshot(map, positions, options = {}) {
141
+ if (!map?.scene?.canvas) {
142
+ console.warn('[captureFenceScreenshot] 地图实例无效')
143
+ return null
502
144
  }
503
-
504
- /**
505
- * 设置识别区可见性
506
- * @param {boolean} visible - 是否可见
507
- */
508
- const setIdentifyZoneVisible = (visible) => {
509
- setVisibilityCommandHemisphereZones({ mapId: getMapId(), visible })
145
+ if (!Array.isArray(positions) || positions.length < 3) {
146
+ console.warn('[captureFenceScreenshot] 围栏顶点不足,无法截图')
147
+ return null
510
148
  }
511
149
 
512
- /**
513
- * 更新识别区半径
514
- * 将公里值转换为米后调用更新方法
515
- *
516
- * @param {number|string} kmValue - 半径值(单位:公里)
517
- */
518
- const updateIdentifyZoneRadius = (kmValue) => {
519
- const radius = parseFloat(kmValue)
520
- if (!Number.isFinite(radius)) return // 无效值则不更新
521
- const radiusInMeters = radius * 1000
522
- updateRadiusCommandHemisphereZones({ mapId: getMapId(), radius: radiusInMeters })
523
-
524
- // 同步更新识别区信息(用于电子围栏检测)
525
- const { center } = identifyZoneInfo
526
- updateIdentifyZoneInfo(center[0], center[1], radiusInMeters)
527
- }
150
+ const padding = options.padding ?? 48
151
+ const printBase64 = options.printBase64 !== false
528
152
 
529
- /**
530
- * 从地图上移除指定围栏
531
- * @param {Object} item - 围栏历史记录项
532
- */
533
- const removeFenceFromMap = (item) => {
534
- if (!item?.fenceId) return
535
-
536
- if (item.type === 'polygon') {
537
- destroyPolygonFenceNoLabel(item.fenceId, getMapId())
538
- } else if (item.type === 'circle') {
539
- destroyCircleFence(item.fenceId, getMapId())
540
- }
153
+ if (options.flyTo === true) {
154
+ await flyCameraToFence(map, positions, options)
541
155
  }
542
156
 
543
- /**
544
- * 切换围栏的显隐状态
545
- * @param {Object} item - 围栏历史记录项
546
- * @param {boolean} visible - 是否可见
547
- */
548
- const toggleFenceVisibility = (item, visible) => {
549
- const mapId = getMapId()
550
-
551
- if (!item?.fenceId || !item.pointParam) return
552
-
553
- try {
554
- const fenceData = JSON.parse(item.pointParam)
555
-
556
- if (fenceData.type === 'polygon') {
557
- if (!fenceData.positions || fenceData.positions.length < 3) return
558
-
559
- if (visible) {
560
- const fenceId = fenceData.id || `fence_${item.id}`
561
- const color = fenceData.style?.color || '#E81224'
562
- const opacity = fenceData.style?.opacity || 0.35
563
- const outlineWidth = fenceData.style?.outlineWidth || 2
564
- const height = fenceData.height || 2
565
-
566
- showPolygonFence({
567
- id: fenceId,
568
- mapId: mapId,
569
- positions: fenceData.positions,
570
- name: item.name,
571
- color,
572
- opacity,
573
- outlineWidth,
574
- height,
575
- })
576
- } else {
577
- const fenceId = fenceData.id || `fence_${item.id}`
578
- destroyPolygonFence(fenceId, mapId)
579
- }
580
- } else if (fenceData.type === 'circle') {
581
- if (!fenceData.center || typeof fenceData.center.lng !== 'number') return
157
+ await waitScenePostRender(map)
582
158
 
583
- if (visible) {
584
- const fenceId = fenceData.id || `fence_${item.id}`
585
- const color = fenceData.style?.color || '#E81224'
586
- const opacity = fenceData.style?.opacity || 0.35
587
- const outlineWidth = fenceData.style?.outlineWidth || 2
588
- const height = fenceData.height || 2
589
-
590
- showCircleFence({
591
- id: fenceId,
592
- mapId: mapId,
593
- center: fenceData.center,
594
- radius: fenceData.radius,
595
- name: item.name,
596
- color,
597
- opacity,
598
- outlineWidth,
599
- height,
600
- })
601
- } else {
602
- const fenceId = fenceData.id || `fence_${item.id}`
603
- destroyCircleFence(fenceId, mapId)
604
- }
605
- }
606
- } catch (error) {
607
- console.error('切换围栏显隐失败:', error)
608
- }
159
+ const bbox = computeFenceScreenBBox(map, positions)
160
+ if (!bbox) {
161
+ console.warn('[captureFenceScreenshot] 围栏不在可视区域,截图失败')
162
+ return null
609
163
  }
610
164
 
611
- /**
612
- * 开始绘制多边形电子围栏
613
- * 处理绘制状态检查、创建绘制控制器、绑定回调函数
614
- */
615
- const drawPolygonWall = () => {
616
- // 检查是否可以绘制
617
- if (!canDrawPolygon.value) return
618
-
619
- const mapId = getMapId()
620
-
621
- // 检查地图是否已加载
622
- if (!mapStore.getMap(mapId)) {
623
- ElMessage.warning(MSG.mapNotReady)
624
- return
625
- }
626
-
627
- // 检查是否正在绘制中
628
- if (polygonDrawController) {
629
- ElMessage.info(MSG.polygonDrawingInProgress)
630
- return
631
- }
632
-
633
- // 如果正在绘制圆形,先取消
634
- if (circleDrawController) {
635
- circleDrawController.cancel()
636
- circleDrawController = null
637
- }
638
-
639
- // 生成唯一的围栏ID和名称
640
- const polygonDrawId = `polygon_fence_${Date.now()}`
641
- const name = `${MSG.polygonFencePrefix}${fenceHistoryList.value.length + 1}`
642
- const fenceColor = BaseConfig.fenceColor || '#E81224'
165
+ await waitScenePostRender(map)
643
166
 
644
- // 重置绘制状态
645
- polygonDrawingTipShown = false
646
-
647
- // 创建绘制控制器
648
- polygonDrawController = drawPolygonFenceNoLabel({
649
- mapId,
650
- id: polygonDrawId,
651
- name,
652
- color: fenceColor,
653
-
654
- /** 绘制完成回调 */
655
- onFinish: async (result) => {
656
- polygonDrawController = null
657
- polygonDrawingTipShown = false
658
-
659
- // 调试信息:查看识别区信息和positions格式
660
- console.log('识别区信息:', identifyZoneInfo)
661
- console.log('positions:', result?.positions)
662
- console.log('positions类型:', Array.isArray(result?.positions))
663
-
664
- // 如果识别区未启用或未正确初始化,直接跳过检测
665
- if (!identifyZoneInfo.center ||
666
- identifyZoneInfo.center.length < 2 ||
667
- identifyZoneInfo.center[0] === 0 ||
668
- identifyZoneInfo.center[1] === 0 ||
669
- !identifyZoneInfo.radius ||
670
- identifyZoneInfo.radius <= 0) {
671
- console.log('识别区未启用或未正确初始化,跳过检测')
672
- } else {
673
- // 检测电子围栏是否在识别区内
674
- if (!isPolygonInIdentifyZone(result?.positions)) {
675
- // 销毁已绘制的围栏
676
- destroyPolygonFenceNoLabel(polygonDrawId, mapId)
677
- ElMessage.warning(MSG.fenceOutsideIdentifyZone)
678
- return
679
- }
680
- }
681
-
682
- const map = mapStore.getMap(mapId)
683
- let thumbnail = null
684
- if (map && result?.positions?.length >= 3) {
685
- await nextTick()
686
- await new Promise((resolve) => requestAnimationFrame(() => resolve()))
687
- await new Promise((resolve) => requestAnimationFrame(() => resolve()))
688
-
689
- const rawThumbnail = await captureFenceScreenshot(map, result.positions, {
690
- printBase64: true,
691
- flyTo: false,
692
- })
693
- if (rawThumbnail) {
694
- thumbnail = await compressBase64Image(rawThumbnail)
695
- }
696
- }
697
-
698
- pendingPolygonFence.value = {
699
- polygonDrawId,
700
- name,
701
- thumbnail,
702
- dbData: result?.dbData,
703
- }
704
-
705
- ElMessage.success(`\u300C${name}\u300D${MSG.drawDone}`)
706
- },
707
-
708
- /** 绘制过程中回调(显示提示) */
709
- onChange: () => {
710
- if (!polygonDrawingTipShown) {
711
- polygonDrawingTipShown = true
712
- ElMessage.info(MSG.polygonDrawingHint)
713
- }
714
- },
715
-
716
- /** 取消绘制回调 */
717
- onCancel: () => {
718
- polygonDrawController = null
719
- polygonDrawingTipShown = false
720
- ElMessage.info(MSG.polygonDrawCancelled)
721
- },
722
- })
723
-
724
- // 检查绘制控制器是否创建成功
725
- if (!polygonDrawController) {
726
- ElMessage.error(MSG.cannotStartPolygonDraw)
727
- }
167
+ const base64 = cropCanvasToBase64(map, bbox, padding)
168
+ if (!base64) {
169
+ console.warn('[captureFenceScreenshot] 裁剪画布失败')
170
+ return null
728
171
  }
729
172
 
730
- /**
731
- * 开始绘制圆形电子围栏
732
- * 处理绘制状态检查、创建绘制控制器、绑定回调函数
733
- */
734
- const drawCircleWall = () => {
735
- // 检查是否可以绘制
736
- if (!canDrawCircle.value) return
737
-
738
- const mapId = getMapId()
739
-
740
- // 检查地图是否已加载
741
- if (!mapStore.getMap(mapId)) {
742
- ElMessage.warning(MSG.mapNotReady)
743
- return
744
- }
745
-
746
- // 检查是否正在绘制中
747
- if (circleDrawController) {
748
- ElMessage.info(MSG.circleDrawingInProgress)
749
- return
750
- }
751
-
752
- // 如果正在绘制多边形,先取消
753
- if (polygonDrawController) {
754
- polygonDrawController.cancel()
755
- polygonDrawController = null
756
- }
757
-
758
- // 生成唯一的围栏ID和名称
759
- const circleDrawId = `circle_fence_${Date.now()}`
760
- const name = `${MSG.circleFencePrefix}${fenceHistoryList.value.length + 1}`
761
- const fenceColor = BaseConfig.fenceColor || '#E81224'
762
-
763
- // 重置绘制状态
764
- circleDrawingTipShown = false
765
-
766
- // 创建绘制控制器
767
- circleDrawController = drawCircleFence({
768
- mapId,
769
- id: circleDrawId,
770
- name,
771
- color: fenceColor,
772
-
773
- /** 绘制完成回调 */
774
- onFinish: async (result) => {
775
- circleDrawController = null
776
- circleDrawingTipShown = false
777
-
778
- // 调试信息:查看识别区信息
779
- console.log('识别区信息:', identifyZoneInfo)
780
- console.log('circle center:', result?.center)
781
- console.log('circle radius:', result?.radius)
782
-
783
- // 如果识别区未启用或未正确初始化,直接跳过检测
784
- if (!identifyZoneInfo.center ||
785
- identifyZoneInfo.center.length < 2 ||
786
- identifyZoneInfo.center[0] === 0 ||
787
- identifyZoneInfo.center[1] === 0 ||
788
- !identifyZoneInfo.radius ||
789
- identifyZoneInfo.radius <= 0) {
790
- console.log('识别区未启用或未正确初始化,跳过检测')
791
- } else {
792
- // 检测电子围栏圆心是否在识别区内
793
- // 圆形围栏的圆心必须在识别区内,且半径不能超过到识别区边界的距离
794
- const center = result?.center
795
- if (center) {
796
- const isCenterInside = isPointInIdentifyZone(center.lng, center.lat)
797
- if (!isCenterInside) {
798
- // 销毁已绘制的围栏
799
- destroyCircleFence(circleDrawId, mapId)
800
- ElMessage.warning(MSG.fenceOutsideIdentifyZone)
801
- return
802
- }
803
-
804
- // 检查圆上最远点是否在识别区内(圆心到识别区边界的距离 >= 圆半径)
805
- const centerDistance = getDistanceToIdentifyZoneCenter(center.lng, center.lat)
806
- const maxAllowedRadius = identifyZoneInfo.radius - centerDistance
807
- if (result.radius > maxAllowedRadius) {
808
- // 销毁已绘制的围栏
809
- destroyCircleFence(circleDrawId, mapId)
810
- ElMessage.warning(MSG.fenceOutsideIdentifyZone)
811
- return
812
- }
813
- }
814
- }
815
-
816
- pendingCircleFence.value = {
817
- circleDrawId,
818
- name,
819
- thumbnail: null,
820
- dbData: result?.dbData,
821
- }
822
-
823
- ElMessage.success(`\u300C${name}\u300D${MSG.drawDone}`)
824
- },
825
-
826
- /** 绘制过程中回调(显示提示) */
827
- onChange: () => {
828
- if (!circleDrawingTipShown) {
829
- circleDrawingTipShown = true
830
- ElMessage.info(MSG.circleDrawingHint)
831
- }
832
- },
833
-
834
- /** 取消绘制回调 */
835
- onCancel: () => {
836
- circleDrawController = null
837
- circleDrawingTipShown = false
838
- ElMessage.info(MSG.circleDrawCancelled)
839
- },
840
- })
841
-
842
- // 检查绘制控制器是否创建成功
843
- if (!circleDrawController) {
844
- ElMessage.error(MSG.cannotStartCircleDraw)
845
- }
846
- }
847
-
848
- /**
849
- * 计算点到识别区中心点的距离(米)
850
- * @param {number} lng - 点的经度
851
- * @param {number} lat - 点的纬度
852
- * @returns {number} - 距离(米)
853
- */
854
- const getDistanceToIdentifyZoneCenter = (lng, lat) => {
855
- const { center, radius } = identifyZoneInfo
856
- if (!center || center.length < 2) return 0
857
-
858
- const R = 6371000
859
- const dLat = (lat - center[1]) * Math.PI / 180
860
- const dLng = (lng - center[0]) * Math.PI / 180
861
- const a =
862
- Math.sin(dLat/2) * Math.sin(dLat/2) +
863
- Math.cos(center[1] * Math.PI / 180) * Math.cos(lat * Math.PI / 180) *
864
- Math.sin(dLng/2) * Math.sin(dLng/2)
865
- const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
866
- return R * c
867
- }
868
-
869
- /**
870
- * 取消正在进行的绘制(多边形或圆形)
871
- */
872
- const cancelDraw = () => {
873
- if (polygonDrawController?.cancel) {
874
- polygonDrawController.cancel()
875
- polygonDrawController = null
876
- polygonDrawingTipShown = false
877
- ElMessage.info(MSG.userCancelled)
878
- } else if (circleDrawController?.cancel) {
879
- circleDrawController.cancel()
880
- circleDrawController = null
881
- circleDrawingTipShown = false
882
- ElMessage.info(MSG.userCancelled)
883
- } else if (pendingPolygonFence.value) {
884
- const mapId = getMapId()
885
- const polygonDrawId = pendingPolygonFence.value.polygonDrawId
886
- if (polygonDrawId) {
887
- destroyPolygonFenceNoLabel(polygonDrawId, mapId)
888
- }
889
- pendingPolygonFence.value = null
890
- polygonDrawingTipShown = false
891
- ElMessage.info(MSG.userCancelled)
892
- } else if (pendingCircleFence.value) {
893
- const mapId = getMapId()
894
- const circleDrawId = pendingCircleFence.value.circleDrawId
895
- if (circleDrawId) {
896
- destroyCircleFence(circleDrawId, mapId)
897
- }
898
- pendingCircleFence.value = null
899
- circleDrawingTipShown = false
900
- ElMessage.info(MSG.userCancelled)
901
- }
902
- }
903
-
904
- /**
905
- * 保存待提交的多边形围栏:写入历史列表并调用后端接口(由页面「保存」按钮触发)
906
- * @param {Function} saveToServer - (payload) => Promise,由页面注入 addElectronicFence 等
907
- */
908
- const savePolygonFence = async (saveToServer) => {
909
- const pending = pendingPolygonFence.value
910
- if (!pending) {
911
- ElMessage.warning('\u8BF7\u5148\u5B8C\u6210\u591A\u8FB9\u5F62\u56F4\u680F\u7ED8\u5236')
912
- return
913
- }
914
- if (!pending.dbData) {
915
- ElMessage.error('\u56F4\u680F\u6570\u636E\u65E0\u6548\uFF0C\u65E0\u6CD5\u4FDD\u5B58')
916
- return
917
- }
918
-
919
- fenceHistoryList.value.push({
920
- id: Date.now(),
921
- time: formatFenceTime(),
922
- fenceId: pending.polygonDrawId,
923
- name: pending.name,
924
- type: 'polygon',
925
- thumbnail: pending.thumbnail,
926
- })
927
-
928
- const pointParamData = embedBase64InDbData(pending.dbData, pending.thumbnail)
929
-
930
- console.log(pointParamData)
931
-
932
- try {
933
- const response = await saveToServer({
934
- fenceName: pending.name,
935
- pointParam: JSON.stringify(pointParamData),
936
- isEnable: 1,
937
- })
938
- if (response?.code === 200) {
939
- ElMessage.success('\u7535\u5B50\u56F4\u680F\u4FDD\u5B58\u6210\u529F')
940
- pendingPolygonFence.value = null
941
- } else {
942
- ElMessage.error(response?.msg || '\u7535\u5B50\u56F4\u680F\u4FDD\u5B58\u5931\u8D25')
943
- }
944
- } catch {
945
- ElMessage.error('\u7535\u5B50\u56F4\u680F\u4FDD\u5B58\u5931\u8D25')
946
- }
173
+ if (printBase64) {
174
+ console.log('[围栏截图 base64]', base64)
947
175
  }
948
176
 
949
- /**
950
- * 保存待提交的圆形围栏:写入历史列表并调用后端接口(由页面「保存」按钮触发)
951
- * @param {Function} saveToServer - (payload) => Promise,由页面注入 addElectronicFence 等
952
- */
953
- const saveCircleFence = async (saveToServer) => {
954
- const pending = pendingCircleFence.value
955
- if (!pending) {
956
- ElMessage.warning('\u8BF7\u5148\u5B8C\u6210\u5706\u5F62\u56F4\u680F\u7ED8\u5236')
957
- return
958
- }
959
- if (!pending.dbData) {
960
- ElMessage.error('\u56F4\u680F\u6570\u636E\u65E0\u6548\uFF0C\u65E0\u6CD5\u4FDD\u5B58')
961
- return
962
- }
963
-
964
- fenceHistoryList.value.push({
965
- id: Date.now(),
966
- time: formatFenceTime(),
967
- fenceId: pending.circleDrawId,
968
- name: pending.name,
969
- type: 'circle',
970
- thumbnail: pending.thumbnail,
971
- })
972
-
973
- const pointParamData = embedBase64InDbData(pending.dbData, pending.thumbnail)
974
-
975
- console.log(pointParamData)
976
-
977
- try {
978
- const response = await saveToServer({
979
- fenceName: pending.name,
980
- pointParam: JSON.stringify(pointParamData),
981
- isEnable: 1,
982
- })
983
- if (response?.code === 200) {
984
- ElMessage.success('\u7535\u5B50\u56F4\u680F\u4FDD\u5B58\u6210\u529F')
985
- pendingCircleFence.value = null
986
- } else {
987
- ElMessage.error(response?.msg || '\u7535\u5B50\u56F4\u680F\u4FDD\u5B58\u5931\u8D25')
988
- }
989
- } catch {
990
- ElMessage.error('\u7535\u5B50\u56F4\u680F\u4FDD\u5B58\u5931\u8D25')
991
- }
992
- }
993
-
994
- /**
995
- * 保存围栏(根据类型自动选择保存方法)
996
- */
997
- const saveFence = async (saveToServer) => {
998
- if (pendingPolygonFence.value) {
999
- await savePolygonFence(saveToServer)
1000
- } else if (pendingCircleFence.value) {
1001
- await saveCircleFence(saveToServer)
1002
- } else {
1003
- ElMessage.warning('\u8BF7\u5148\u5B8C\u6210\u56F4\u680F\u7ED8\u5236')
1004
- }
1005
- }
1006
-
1007
- /**
1008
- * 取消正在进行的多边形绘制(保留旧方法名以保持兼容性)
1009
- */
1010
- const cancelPolygonDraw = () => {
1011
- cancelDraw()
1012
- }
177
+ return base64
178
+ }
1013
179
 
1014
- /**
1015
- * 返回暴露给外部使用的方法和属性
1016
- */
1017
- return {
1018
- DEFENSE_ZONE_MAP_ID, // 地图实例ID常量
1019
- maxFenceCount, // 最大围栏数量
1020
- mapOnLoad, // 地图加载回调
1021
- canDraw, // 是否可绘制(计算属性)
1022
- canDrawPolygon, // 是否可绘制多边形(计算属性)
1023
- canDrawCircle, // 是否可绘制圆形(计算属性)
1024
- polygonDrawTitle, // 多边形绘制按钮提示(计算属性)
1025
- circleDrawTitle, // 圆形绘制按钮提示(计算属性)
1026
- drawPolygonWall, // 绘制多边形围栏
1027
- drawCircleWall, // 绘制圆形围栏
1028
- cancelDraw, // 取消绘制(通用)
1029
- cancelPolygonDraw, // 取消绘制(兼容旧接口)
1030
- pendingPolygonFence, // 待保存的多边形围栏绘制结果
1031
- pendingCircleFence, // 待保存的圆形围栏绘制结果
1032
- savePolygonFence, // 保存多边形围栏(历史 + 后端)
1033
- saveCircleFence, // 保存圆形围栏(历史 + 后端)
1034
- saveFence, // 保存围栏(自动检测类型)
1035
- removeFenceFromMap, // 从地图移除围栏
1036
- toggleFenceVisibility, // 切换围栏显隐
1037
- setIdentifyZoneVisible, // 设置识别区可见性
1038
- updateIdentifyZoneRadius, // 更新识别区半径
1039
- captureFenceScreenshot, // 围栏区域截图(base64)
180
+ /**
181
+ * 根据 mapId 截图(便于在 onFinish 回调里直接调用)
182
+ */
183
+ export async function captureFenceScreenshotByMapId(mapId, positions, options = {}) {
184
+ const mapStore = useMapStore()
185
+ const map = mapStore.getMap(mapId)
186
+ if (!map) {
187
+ console.warn('[captureFenceScreenshot] 地图未加载, mapId:', mapId)
188
+ return null
1040
189
  }
1041
- }
190
+ return captureFenceScreenshot(map, positions, options)
191
+ }
@@ -618,24 +618,28 @@ export function drawFenceNew() {
618
618
  }
619
619
  })
620
620
 
621
- const labelEntity = map.entities.add({
622
- id: `${options.id}_fence_name_label`,
623
- position: centerCartesian,
624
- label: {
625
- text: fenceName,
626
- font: BaseConfig.fenceLabelFont,
627
- fillColor: Cesium.Color.WHITE,
628
- style: Cesium.LabelStyle.FILL,
629
- showBackground: true,
630
- backgroundColor: Cesium.Color.fromCssColorString(BaseConfig.fenceLabelBgColor),
631
- backgroundPadding: new Cesium.Cartesian2(5, 3),
632
- verticalOrigin: Cesium.VerticalOrigin.CENTER,
633
- horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
634
- pixelOffset: new Cesium.Cartesian2(0, 0),
635
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
636
- distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, Number.POSITIVE_INFINITY)
637
- }
638
- })
621
+ const withLabel = options.withLabel !== false
622
+ let labelEntity = null
623
+ if (withLabel) {
624
+ labelEntity = map.entities.add({
625
+ id: `${options.id}_fence_name_label`,
626
+ position: centerCartesian,
627
+ label: {
628
+ text: fenceName,
629
+ font: BaseConfig.fenceLabelFont,
630
+ fillColor: Cesium.Color.WHITE,
631
+ style: Cesium.LabelStyle.FILL,
632
+ showBackground: true,
633
+ backgroundColor: Cesium.Color.fromCssColorString(BaseConfig.fenceLabelBgColor),
634
+ backgroundPadding: new Cesium.Cartesian2(5, 3),
635
+ verticalOrigin: Cesium.VerticalOrigin.CENTER,
636
+ horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
637
+ pixelOffset: new Cesium.Cartesian2(0, 0),
638
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
639
+ distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, Number.POSITIVE_INFINITY)
640
+ }
641
+ })
642
+ }
639
643
 
640
644
  fenceEntity._labelEntity = labelEntity
641
645
  fenceEntity._originalOptions = {
@@ -651,7 +655,9 @@ export function drawFenceNew() {
651
655
 
652
656
  if (options.show === false) {
653
657
  fenceEntity.show = false
654
- labelEntity.show = false
658
+ if (labelEntity) {
659
+ labelEntity.show = false
660
+ }
655
661
  }
656
662
 
657
663
  if (options.zoomTo) {
@@ -671,6 +677,15 @@ export function drawFenceNew() {
671
677
  }
672
678
  }
673
679
 
680
+ /**
681
+ * 显示多边形电子围栏(无名称标签版本)
682
+ * @param {Object} options 围栏选项
683
+ * @returns {Object}
684
+ */
685
+ const showPolygonFenceNoLabel = (options = {}) => {
686
+ return showPolygonFence({ ...options, withLabel: false })
687
+ }
688
+
674
689
  /**
675
690
  * 删除多边形电子围栏
676
691
  * @param {string} id 围栏ID
@@ -824,24 +839,28 @@ export function drawFenceNew() {
824
839
  }
825
840
  })
826
841
 
827
- const labelEntity = map.entities.add({
828
- id: `${options.id}_fence_name_label`,
829
- position: centerCartesian,
830
- label: {
831
- text: fenceName,
832
- font: BaseConfig.fenceLabelFont,
833
- fillColor: Cesium.Color.WHITE,
834
- style: Cesium.LabelStyle.FILL,
835
- showBackground: true,
836
- backgroundColor: Cesium.Color.fromCssColorString(BaseConfig.fenceLabelBgColor),
837
- backgroundPadding: new Cesium.Cartesian2(5, 3),
838
- verticalOrigin: Cesium.VerticalOrigin.CENTER,
839
- horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
840
- pixelOffset: new Cesium.Cartesian2(0, 0),
841
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
842
- distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, Number.POSITIVE_INFINITY)
843
- }
844
- })
842
+ const withLabel = options.withLabel !== false
843
+ let labelEntity = null
844
+ if (withLabel) {
845
+ labelEntity = map.entities.add({
846
+ id: `${options.id}_fence_name_label`,
847
+ position: centerCartesian,
848
+ label: {
849
+ text: fenceName,
850
+ font: BaseConfig.fenceLabelFont,
851
+ fillColor: Cesium.Color.WHITE,
852
+ style: Cesium.LabelStyle.FILL,
853
+ showBackground: true,
854
+ backgroundColor: Cesium.Color.fromCssColorString(BaseConfig.fenceLabelBgColor),
855
+ backgroundPadding: new Cesium.Cartesian2(5, 3),
856
+ verticalOrigin: Cesium.VerticalOrigin.CENTER,
857
+ horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
858
+ pixelOffset: new Cesium.Cartesian2(0, 0),
859
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
860
+ distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, Number.POSITIVE_INFINITY)
861
+ }
862
+ })
863
+ }
845
864
 
846
865
  fenceEntity._labelEntity = labelEntity
847
866
  fenceEntity._originalOptions = {
@@ -858,6 +877,9 @@ export function drawFenceNew() {
858
877
 
859
878
  if (options.show === false) {
860
879
  fenceEntity.show = false
880
+ if (labelEntity) {
881
+ labelEntity.show = false
882
+ }
861
883
  }
862
884
 
863
885
  if (options.zoomTo) {
@@ -876,6 +898,10 @@ export function drawFenceNew() {
876
898
  }
877
899
  }
878
900
 
901
+ const showCircleFenceNoLabel = (options = {}) => {
902
+ return showCircleFence({ ...options, withLabel: false })
903
+ }
904
+
879
905
  /**
880
906
  * 交互式创建圆形电子围栏(带高度)
881
907
  *
@@ -1508,10 +1534,12 @@ export function drawFenceNew() {
1508
1534
  drawPolygonFence,
1509
1535
  drawPolygonFenceNoLabel,
1510
1536
  showPolygonFence,
1537
+ showPolygonFenceNoLabel,
1511
1538
  updatePolygonFenceName,
1512
1539
  destroyPolygonFence,
1513
1540
  destroyPolygonFenceNoLabel,
1514
1541
  showCircleFence,
1542
+ showCircleFenceNoLabel,
1515
1543
  drawCircleFence,
1516
1544
  updateCircleFenceName,
1517
1545
  destroyCircleFence,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huweili-cesium",
3
- "version": "1.2.37",
3
+ "version": "1.2.40",
4
4
  "description": "基于 Cesium 的地图工具库(无人机态势、轨迹、围栏、工具栏等)",
5
5
  "type": "module",
6
6
  "main": "./index.js",