huweili-cesium 1.2.36 → 1.2.37
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.
- package/js/captureFenceScreenshot.js +989 -139
- package/package.json +1 -1
|
@@ -1,191 +1,1041 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 防区设置页面 - 地图操作工具函数
|
|
3
|
+
*
|
|
4
|
+
* 提供防区设置页面所需的地图相关功能,包括:
|
|
5
|
+
* 1. 指挥所半球区域的初始化和控制
|
|
6
|
+
* 2. 多边形电子围栏的绘制和管理
|
|
7
|
+
* 3. 围栏历史记录的管理
|
|
8
|
+
*
|
|
9
|
+
* @author
|
|
10
|
+
* @version 1.0.0
|
|
3
11
|
*/
|
|
4
|
-
import * as Cesium from 'cesium'
|
|
5
|
-
import { useMapStore } from './stores/mapStore.js'
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
// 导入依赖
|
|
14
|
+
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'
|
|
8
28
|
|
|
9
29
|
/**
|
|
10
|
-
*
|
|
11
|
-
* @param {Array<{ lng: number, lat: number, height?: number }>} positions
|
|
30
|
+
* UI 提示文字常量(使用 Unicode 转义避免 Windows 编辑器编码问题)
|
|
12
31
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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',
|
|
17
53
|
}
|
|
18
54
|
|
|
19
55
|
/**
|
|
20
|
-
*
|
|
56
|
+
* 防区设置页面地图实例ID
|
|
57
|
+
* 优先从全局配置获取,若未配置则使用默认值
|
|
21
58
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const cartesians = toCartesians(positions)
|
|
25
|
-
const sphere = Cesium.BoundingSphere.fromPoints(cartesians)
|
|
26
|
-
if (!sphere || !Number.isFinite(sphere.radius)) {
|
|
27
|
-
resolve()
|
|
28
|
-
return
|
|
29
|
-
}
|
|
59
|
+
export const DEFENSE_ZONE_MAP_ID =
|
|
60
|
+
window.MapInstanceIds?.DEFENSE_ZONE_SETTING || 'defense-zone-setting-map'
|
|
30
61
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
})
|
|
62
|
+
/**
|
|
63
|
+
* 最大围栏数量限制
|
|
64
|
+
*/
|
|
65
|
+
const MAX_FENCE_COUNT = 6
|
|
50
66
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
67
|
+
/**
|
|
68
|
+
* 获取地图状态管理实例
|
|
69
|
+
*/
|
|
70
|
+
const mapStore = useMapStore()
|
|
54
71
|
|
|
55
72
|
/**
|
|
56
|
-
*
|
|
73
|
+
* 获取基础地图配置工具函数
|
|
57
74
|
*/
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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 // 半径(米)
|
|
66
115
|
}
|
|
67
116
|
|
|
68
117
|
/**
|
|
69
|
-
*
|
|
118
|
+
* 获取当前地图ID
|
|
119
|
+
* @returns {string} 地图实例ID
|
|
70
120
|
*/
|
|
71
|
-
|
|
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
|
|
121
|
+
const getMapId = () => currentMapId || DEFENSE_ZONE_MAP_ID
|
|
78
122
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
)
|
|
90
|
-
if (!win || !Number.isFinite(win.x) || !Number.isFinite(win.y)) continue
|
|
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
|
+
}
|
|
91
133
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
134
|
+
/**
|
|
135
|
+
* 判断点是否在识别区内
|
|
136
|
+
* @param {number} lng - 点的经度
|
|
137
|
+
* @param {number} lat - 点的纬度
|
|
138
|
+
* @returns {boolean} - 是否在识别区内
|
|
139
|
+
*/
|
|
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
|
|
97
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
|
|
170
|
+
}
|
|
98
171
|
|
|
99
|
-
|
|
100
|
-
|
|
172
|
+
/**
|
|
173
|
+
* 将Cesium Cartesian3坐标转换为经纬度
|
|
174
|
+
* @param {Object} cartesian - Cesium Cartesian3对象
|
|
175
|
+
* @param {Object} map - Cesium地图实例
|
|
176
|
+
* @returns {Object} - 包含lng和lat的对象
|
|
177
|
+
*/
|
|
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
|
+
}
|
|
189
|
+
}
|
|
190
|
+
} catch (e) {
|
|
191
|
+
console.warn('Cartesian3转换经纬度失败:', e)
|
|
101
192
|
}
|
|
193
|
+
return null
|
|
194
|
+
}
|
|
102
195
|
|
|
103
|
-
|
|
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
|
+
}
|
|
281
|
+
|
|
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())}`
|
|
104
290
|
}
|
|
105
291
|
|
|
106
292
|
/**
|
|
107
|
-
*
|
|
293
|
+
* 压缩 base64 缩略图(缩小尺寸 + JPEG 低质量,用于入库)
|
|
294
|
+
* @param {string} dataUrl
|
|
295
|
+
* @param {{ maxWidth?: number, quality?: number }} [options]
|
|
296
|
+
*/
|
|
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}`
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
/** 是否围栏样式对象(含 opacity / outlineWidth) */
|
|
328
|
+
const isFenceStyleObject = (obj) =>
|
|
329
|
+
obj && typeof obj === 'object' && !Array.isArray(obj) && ('opacity' in obj || 'outlineWidth' in obj)
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* 将截图 base64 写入 dbData 内层样式对象(与 opacity、outlineWidth 同级),而非外层
|
|
108
333
|
*/
|
|
109
|
-
|
|
110
|
-
const
|
|
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)
|
|
334
|
+
const embedBase64InDbData = (dbData, thumbnail) => {
|
|
335
|
+
const base64 = thumbnail ?? ''
|
|
117
336
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
337
|
+
if (Array.isArray(dbData)) {
|
|
338
|
+
return dbData.map((item) =>
|
|
339
|
+
isFenceStyleObject(item) ? { ...item, base64 } : item
|
|
340
|
+
)
|
|
341
|
+
}
|
|
123
342
|
|
|
124
|
-
|
|
125
|
-
|
|
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
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return dbData
|
|
126
356
|
}
|
|
127
357
|
|
|
128
358
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* @param {
|
|
132
|
-
* @param {Array
|
|
133
|
-
* @param {
|
|
134
|
-
* @param {
|
|
135
|
-
*
|
|
136
|
-
* @
|
|
137
|
-
* @param {boolean} [options.printBase64=true] 是否在控制台打印 base64
|
|
138
|
-
* @returns {Promise<string|null>} PNG data URL(含 data:image/png;base64, 前缀)
|
|
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} 包含地图操作方法和计算属性的对象
|
|
139
367
|
*/
|
|
140
|
-
export
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
|
458
|
+
|
|
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
|
|
466
|
+
|
|
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
|
|
482
|
+
|
|
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
|
+
}
|
|
144
502
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* 设置识别区可见性
|
|
506
|
+
* @param {boolean} visible - 是否可见
|
|
507
|
+
*/
|
|
508
|
+
const setIdentifyZoneVisible = (visible) => {
|
|
509
|
+
setVisibilityCommandHemisphereZones({ mapId: getMapId(), visible })
|
|
148
510
|
}
|
|
149
511
|
|
|
150
|
-
|
|
151
|
-
|
|
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
|
+
}
|
|
152
528
|
|
|
153
|
-
|
|
154
|
-
|
|
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
|
+
}
|
|
155
541
|
}
|
|
156
542
|
|
|
157
|
-
|
|
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
|
|
158
565
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
582
|
+
|
|
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
|
+
}
|
|
163
609
|
}
|
|
164
610
|
|
|
165
|
-
|
|
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
|
+
}
|
|
166
638
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
639
|
+
// 生成唯一的围栏ID和名称
|
|
640
|
+
const polygonDrawId = `polygon_fence_${Date.now()}`
|
|
641
|
+
const name = `${MSG.polygonFencePrefix}${fenceHistoryList.value.length + 1}`
|
|
642
|
+
const fenceColor = BaseConfig.fenceColor || '#E81224'
|
|
643
|
+
|
|
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
|
+
}
|
|
171
728
|
}
|
|
172
729
|
|
|
173
|
-
|
|
174
|
-
|
|
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
|
+
}
|
|
175
846
|
}
|
|
176
847
|
|
|
177
|
-
|
|
178
|
-
|
|
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
|
+
}
|
|
179
868
|
|
|
180
|
-
/**
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
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
|
+
}
|
|
1013
|
+
|
|
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)
|
|
189
1040
|
}
|
|
190
|
-
return captureFenceScreenshot(map, positions, options)
|
|
191
1041
|
}
|