af-mobile-client-vue3 1.1.3 → 1.1.5
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/package.json +1 -1
- package/src/assets/img/component/location.png +0 -0
- package/src/assets/img/component/mapLayers.png +0 -0
- package/src/assets/img/component/positioning.png +0 -0
- package/src/components/data/XOlMap/README.md +178 -169
- package/src/components/data/XOlMap/XLocationPicker/index.vue +191 -0
- package/src/components/data/XOlMap/index.vue +818 -433
- package/src/components/data/XOlMap/types.ts +138 -0
- package/src/router/routes.ts +7 -1
- package/src/services/api/common.ts +2 -2
- package/src/views/component/XOlMapView/XLocationPicker/index.vue +120 -0
- package/src/views/component/XOlMapView/index.vue +420 -0
- package/src/views/component/index.vue +5 -1
- package/src/components/data/XOlMap/demo.vue +0 -330
|
@@ -7,136 +7,50 @@
|
|
|
7
7
|
* - 天地图(矢量)
|
|
8
8
|
* - 天地图卫星图
|
|
9
9
|
*/
|
|
10
|
+
import type {
|
|
11
|
+
InitParams,
|
|
12
|
+
PhoneLocationStatus,
|
|
13
|
+
PointData,
|
|
14
|
+
PointLayerConfig,
|
|
15
|
+
WebGLPointOptions,
|
|
16
|
+
WMSLayerConfig,
|
|
17
|
+
WMSOptions,
|
|
18
|
+
} from './types'
|
|
19
|
+
import locationIcon from '@af-mobile-client-vue3/assets/img/component/positioning.png'
|
|
20
|
+
import { mobileUtil } from '@af-mobile-client-vue3/utils/mobileUtil'
|
|
10
21
|
import { Map, View } from 'ol'
|
|
11
22
|
import { defaults as defaultControls, ScaleLine } from 'ol/control'
|
|
12
23
|
import Feature from 'ol/Feature'
|
|
13
24
|
import Point from 'ol/geom/Point'
|
|
14
25
|
import { defaults as defaultInteractions } from 'ol/interaction'
|
|
15
26
|
import { Image as ImageLayer, Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'
|
|
16
|
-
import { fromLonLat } from 'ol/proj'
|
|
27
|
+
import { fromLonLat, toLonLat } from 'ol/proj'
|
|
17
28
|
import { ImageWMS, Vector as VectorSource, XYZ } from 'ol/source'
|
|
18
29
|
import { Fill, Icon, Stroke, Style, Text } from 'ol/style'
|
|
19
30
|
import { Button } from 'vant'
|
|
20
|
-
import { onUnmounted, ref } from 'vue'
|
|
31
|
+
import { getCurrentInstance, onUnmounted, ref } from 'vue'
|
|
21
32
|
import { wgs84ToGcj02Projection } from './utils/wgs84ToGcj02'
|
|
22
33
|
import 'vant/lib/index.css'
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
/** 地图缩放级别 */
|
|
29
|
-
zoom?: number
|
|
30
|
-
/** 最大缩放级别 */
|
|
31
|
-
maxZoom?: number
|
|
32
|
-
/** 最小缩放级别 */
|
|
33
|
-
minZoom?: number
|
|
34
|
-
/** 天地图密钥 */
|
|
35
|
-
tianDiTuKey?: string
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** 点位数据接口 */
|
|
39
|
-
interface PointData {
|
|
40
|
-
/** 经度 */
|
|
41
|
-
longitude: number
|
|
42
|
-
/** 纬度 */
|
|
43
|
-
latitude: number
|
|
44
|
-
/** 点标题 */
|
|
45
|
-
title?: string
|
|
46
|
-
/** 自定义数据 */
|
|
47
|
-
extData?: Record<string, any>
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** 点位图层配置接口 */
|
|
51
|
-
interface PointLayerConfig {
|
|
52
|
-
/** 图层ID */
|
|
53
|
-
id: number
|
|
54
|
-
/** 图层名称 */
|
|
55
|
-
value: string
|
|
56
|
-
/** 是否显示 */
|
|
57
|
-
show: boolean
|
|
58
|
-
/** 图标URL */
|
|
59
|
-
icon: string
|
|
60
|
-
/** 图标锚点 */
|
|
61
|
-
iconAnchor?: [number, number]
|
|
62
|
-
/** 点击回调函数 */
|
|
63
|
-
onClick?: (point: PointData, event: any) => void
|
|
64
|
-
/** 点位数据 */
|
|
65
|
-
data?: PointData[]
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** WebGL渲染配置接口 */
|
|
69
|
-
interface WebGLPointOptions extends PointLayerConfig {
|
|
70
|
-
/** 图标大小 */
|
|
71
|
-
iconSize?: [number, number]
|
|
72
|
-
/** 是否开启聚合 */
|
|
73
|
-
enableCluster?: boolean
|
|
74
|
-
/** 聚合距离,单位像素 */
|
|
75
|
-
clusterDistance?: number
|
|
76
|
-
/** 聚合样式配置 */
|
|
77
|
-
clusterStyle?: {
|
|
78
|
-
/** 填充颜色 */
|
|
79
|
-
fillColor?: string
|
|
80
|
-
/** 边框颜色 */
|
|
81
|
-
strokeColor?: string
|
|
82
|
-
/** 文字颜色 */
|
|
83
|
-
textColor?: string
|
|
84
|
-
}
|
|
85
|
-
/** 渲染性能配置 */
|
|
86
|
-
performance?: {
|
|
87
|
-
/** 是否开启分块加载 */
|
|
88
|
-
enableChunk?: boolean
|
|
89
|
-
/** 每块数据量 */
|
|
90
|
-
chunkSize?: number
|
|
91
|
-
/** 是否开启节流 */
|
|
92
|
-
enableThrottle?: boolean
|
|
93
|
-
/** 节流时间间隔(ms) */
|
|
94
|
-
throttleWait?: number
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** WMS 图层配置接口 */
|
|
99
|
-
interface WMSLayerConfig {
|
|
100
|
-
/** 图层名称 */
|
|
101
|
-
layerName: string
|
|
102
|
-
/** 图层显示名称 */
|
|
103
|
-
value: string
|
|
104
|
-
/** 是否显示 */
|
|
105
|
-
show: boolean
|
|
106
|
-
/** 手机端是否显示 */
|
|
107
|
-
phoneShow: boolean
|
|
108
|
-
/** 图层 ID */
|
|
109
|
-
id: number
|
|
110
|
-
}
|
|
35
|
+
// 在 script setup 中添加
|
|
36
|
+
const emit = defineEmits<{
|
|
37
|
+
(e: 'centerChange', center: [number, number]): void
|
|
38
|
+
}>()
|
|
111
39
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
/** 工作空间 */
|
|
115
|
-
workspace: string
|
|
116
|
-
/** 坐标系 */
|
|
117
|
-
srs: string
|
|
118
|
-
/** 图片格式 */
|
|
119
|
-
format: string
|
|
120
|
-
/** WMS 版本 */
|
|
121
|
-
version: string
|
|
122
|
-
/** WMS 服务地址 */
|
|
123
|
-
url: string
|
|
124
|
-
}
|
|
40
|
+
// 获取当前组件实例
|
|
41
|
+
const instance = getCurrentInstance()
|
|
125
42
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
/** WMS 图层列表 */
|
|
129
|
-
layers: WMSLayerConfig[]
|
|
130
|
-
/** WMS 服务配置 */
|
|
131
|
-
wms: WMSConfig
|
|
132
|
-
}
|
|
43
|
+
// 存储初始化参数
|
|
44
|
+
const mapParams = ref<InitParams>({})
|
|
133
45
|
|
|
134
46
|
/** 地图容器引用 */
|
|
135
47
|
const mapRef = ref<HTMLDivElement>()
|
|
136
48
|
/** 地图实例 */
|
|
137
49
|
let map: Map | null = null
|
|
138
50
|
/** 当前底图类型 */
|
|
139
|
-
const currentMapType = ref<string>('
|
|
51
|
+
const currentMapType = ref<string>('tianditu')
|
|
52
|
+
/** 控制图层面板显示状态 */
|
|
53
|
+
const showControls = ref<boolean>(false)
|
|
140
54
|
|
|
141
55
|
/** 图层选项配置 */
|
|
142
56
|
const layerOptions = [
|
|
@@ -159,6 +73,60 @@ const wmsLayerStatus = ref<WMSLayerConfig[]>([])
|
|
|
159
73
|
const vectorLayers: Record<number, VectorLayer<VectorSource>> = {}
|
|
160
74
|
const pointLayerStatus = ref<PointLayerConfig[]>([])
|
|
161
75
|
|
|
76
|
+
/** 导航模式 是否正在跟随定位 */
|
|
77
|
+
const isFollowingLocation = ref(false)
|
|
78
|
+
/** 定位定时器 */
|
|
79
|
+
let locationTimer: ReturnType<typeof setInterval> | null = null
|
|
80
|
+
/** 位置图标图层 */
|
|
81
|
+
let locationLayer: VectorLayer<VectorSource> | null = null
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 创建位置图标图层
|
|
85
|
+
*/
|
|
86
|
+
function createLocationLayer(): VectorLayer<VectorSource> {
|
|
87
|
+
const source = new VectorSource()
|
|
88
|
+
const layer = new VectorLayer({
|
|
89
|
+
source,
|
|
90
|
+
zIndex: 10, // 确保位置图标在最上层
|
|
91
|
+
})
|
|
92
|
+
return layer
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 更新位置图标
|
|
97
|
+
*/
|
|
98
|
+
function updateLocationMarker(center: [number, number]): void {
|
|
99
|
+
if (!map || !locationLayer)
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
const source = locationLayer.getSource()
|
|
103
|
+
if (!source)
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
// 清除现有图标
|
|
107
|
+
source.clear()
|
|
108
|
+
|
|
109
|
+
// 创建新的位置图标要素
|
|
110
|
+
const feature = new Feature({
|
|
111
|
+
geometry: new Point(fromLonLat(center)),
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// 设置图标样式
|
|
115
|
+
const style = new Style({
|
|
116
|
+
image: new Icon({
|
|
117
|
+
src: locationIcon,
|
|
118
|
+
scale: 0.2,
|
|
119
|
+
anchor: [0.5, 0.5],
|
|
120
|
+
anchorXUnits: 'fraction',
|
|
121
|
+
anchorYUnits: 'fraction',
|
|
122
|
+
crossOrigin: 'anonymous',
|
|
123
|
+
}),
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
feature.setStyle(style)
|
|
127
|
+
source.addFeature(feature)
|
|
128
|
+
}
|
|
129
|
+
|
|
162
130
|
/**
|
|
163
131
|
* 切换地图图层
|
|
164
132
|
* @param type - 图层类型
|
|
@@ -187,6 +155,11 @@ function handleMapChange(type: string): void {
|
|
|
187
155
|
baseMaps.gaode.setVisible(true)
|
|
188
156
|
}
|
|
189
157
|
currentMapType.value = type
|
|
158
|
+
|
|
159
|
+
// 强制更新地图视图,确保切换后的图层显示正确
|
|
160
|
+
if (map) {
|
|
161
|
+
map.updateSize()
|
|
162
|
+
}
|
|
190
163
|
}
|
|
191
164
|
|
|
192
165
|
/**
|
|
@@ -194,78 +167,139 @@ function handleMapChange(type: string): void {
|
|
|
194
167
|
* @param tianDiTuKey - 天地图密钥
|
|
195
168
|
*/
|
|
196
169
|
function initializeLayers(tianDiTuKey = ''): void {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
170
|
+
try {
|
|
171
|
+
// 高德地图
|
|
172
|
+
baseMaps.gaode = new TileLayer({
|
|
173
|
+
source: new XYZ({
|
|
174
|
+
url: 'https://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}',
|
|
175
|
+
crossOrigin: 'anonymous',
|
|
176
|
+
projection: 'EPSG:3857',
|
|
177
|
+
}),
|
|
178
|
+
})
|
|
205
179
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
180
|
+
// 高德卫星图
|
|
181
|
+
baseMaps.gaodeSatellite = new TileLayer({
|
|
182
|
+
source: new XYZ({
|
|
183
|
+
url: 'https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
|
|
184
|
+
crossOrigin: 'anonymous',
|
|
185
|
+
projection: 'EPSG:3857',
|
|
186
|
+
}),
|
|
187
|
+
visible: false,
|
|
188
|
+
})
|
|
215
189
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
190
|
+
// 高德标注图层
|
|
191
|
+
baseMaps.gaodelabelLayer = new TileLayer({
|
|
192
|
+
source: new XYZ({
|
|
193
|
+
url: 'https://webst02.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}',
|
|
194
|
+
crossOrigin: 'anonymous',
|
|
195
|
+
projection: 'EPSG:3857',
|
|
196
|
+
}),
|
|
197
|
+
visible: false,
|
|
198
|
+
})
|
|
225
199
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
200
|
+
// 天地图矢量图层
|
|
201
|
+
baseMaps.tianditu = new TileLayer({
|
|
202
|
+
source: new XYZ({
|
|
203
|
+
url: 'https://t0.tianditu.gov.cn/vec_w/wmts?'
|
|
204
|
+
+ 'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&'
|
|
205
|
+
+ `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tianDiTuKey}`,
|
|
206
|
+
projection: wgs84ToGcj02Projection,
|
|
207
|
+
}),
|
|
208
|
+
visible: false,
|
|
209
|
+
})
|
|
236
210
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
211
|
+
// 天地图标注图层
|
|
212
|
+
baseMaps.tianditulabel = new TileLayer({
|
|
213
|
+
source: new XYZ({
|
|
214
|
+
url: 'https://t0.tianditu.gov.cn/cva_w/wmts?'
|
|
215
|
+
+ 'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&'
|
|
216
|
+
+ `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tianDiTuKey}`,
|
|
217
|
+
projection: wgs84ToGcj02Projection,
|
|
218
|
+
}),
|
|
219
|
+
visible: false,
|
|
220
|
+
})
|
|
247
221
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
222
|
+
// 天地图卫星图层
|
|
223
|
+
baseMaps.tianditusatellite = new TileLayer({
|
|
224
|
+
source: new XYZ({
|
|
225
|
+
url: 'https://t0.tianditu.gov.cn/img_w/wmts?'
|
|
226
|
+
+ 'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&'
|
|
227
|
+
+ `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tianDiTuKey}`,
|
|
228
|
+
projection: wgs84ToGcj02Projection,
|
|
229
|
+
}),
|
|
230
|
+
visible: false,
|
|
231
|
+
})
|
|
258
232
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
233
|
+
// 天地图卫星标注
|
|
234
|
+
baseMaps.tianditusatlabel = new TileLayer({
|
|
235
|
+
source: new XYZ({
|
|
236
|
+
url: 'https://t0.tianditu.gov.cn/cia_w/wmts?'
|
|
237
|
+
+ 'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&'
|
|
238
|
+
+ `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tianDiTuKey}`,
|
|
239
|
+
projection: wgs84ToGcj02Projection,
|
|
240
|
+
}),
|
|
241
|
+
visible: false,
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
console.error('初始化地图图层失败:', error)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* 获取地址信息
|
|
251
|
+
* @param location - 经纬度坐标
|
|
252
|
+
*/
|
|
253
|
+
async function getAddressInfo(location: [number, number]): Promise<string> {
|
|
254
|
+
try {
|
|
255
|
+
const key = mapParams.value.amapKey
|
|
256
|
+
|
|
257
|
+
if (!key) {
|
|
258
|
+
return '获取地址失败: 未配置密钥'
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 高德逆地址编码请求说明 https://amap.apifox.cn/api-14551463
|
|
262
|
+
const response = await fetch(
|
|
263
|
+
`https://restapi.amap.com/v3/geocode/regeo?location=${location[0].toFixed(6)},${location[1].toFixed(6)}&key=${key}&output=JSON`,
|
|
264
|
+
)
|
|
265
|
+
const data = await response.json()
|
|
266
|
+
|
|
267
|
+
if (data.status === '1' && data.regeocode) {
|
|
268
|
+
return data.regeocode.formatted_address
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 处理错误情况
|
|
272
|
+
if (data.infocode === '10001') {
|
|
273
|
+
return '获取地址失败: key 无效'
|
|
274
|
+
}
|
|
275
|
+
if (data.infocode === '10002') {
|
|
276
|
+
return '获取地址失败: key 未配置平台'
|
|
277
|
+
}
|
|
278
|
+
return `获取地址失败: ${data.info || '未知错误'}`
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
return '获取地址失败: 网络错误'
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 处理地图移动结束事件
|
|
287
|
+
*/
|
|
288
|
+
async function handleMoveEnd() {
|
|
289
|
+
if (!map)
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
const view = map.getView()
|
|
293
|
+
const center = view.getCenter()
|
|
294
|
+
if (!center)
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
// 转换坐标为经纬度
|
|
298
|
+
const lonLat = toLonLat(center)
|
|
299
|
+
const formattedCenter: [number, number] = [Number(lonLat[0].toFixed(6)), Number(lonLat[1].toFixed(6))]
|
|
300
|
+
|
|
301
|
+
// 直接发送事件,让父组件决定是否处理
|
|
302
|
+
emit('centerChange', formattedCenter)
|
|
269
303
|
}
|
|
270
304
|
|
|
271
305
|
/**
|
|
@@ -273,70 +307,102 @@ function initializeLayers(tianDiTuKey = ''): void {
|
|
|
273
307
|
* @param params - 初始化参数
|
|
274
308
|
*/
|
|
275
309
|
function init(params: InitParams = {}): void {
|
|
276
|
-
if (!mapRef.value)
|
|
310
|
+
if (!mapRef.value) {
|
|
277
311
|
return
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 保存初始化参数
|
|
315
|
+
mapParams.value = params
|
|
278
316
|
|
|
279
317
|
// 设置默认参数
|
|
280
318
|
const {
|
|
281
|
-
center = [116.404, 39.915],
|
|
319
|
+
center = [116.404, 39.915],
|
|
282
320
|
zoom = 10,
|
|
283
321
|
maxZoom = 18,
|
|
284
322
|
minZoom = 4,
|
|
285
|
-
tianDiTuKey = '',
|
|
323
|
+
tianDiTuKey = '',
|
|
324
|
+
amapKey = '',
|
|
286
325
|
} = params
|
|
287
326
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
controls: defaultControls({
|
|
303
|
-
zoom: false,
|
|
304
|
-
rotate: false,
|
|
305
|
-
attribution: false,
|
|
306
|
-
}).extend([
|
|
307
|
-
new ScaleLine({
|
|
308
|
-
units: 'metric',
|
|
309
|
-
className: 'ol-scale-line',
|
|
327
|
+
try {
|
|
328
|
+
// 初始化所有底图图层
|
|
329
|
+
initializeLayers(tianDiTuKey)
|
|
330
|
+
|
|
331
|
+
// 创建地图实例 - 加载所有底图图层,但默认只显示高德地图
|
|
332
|
+
map = new Map({
|
|
333
|
+
target: mapRef.value,
|
|
334
|
+
layers: Object.values(baseMaps), // 加载所有底图图层
|
|
335
|
+
view: new View({
|
|
336
|
+
center: fromLonLat(center),
|
|
337
|
+
zoom,
|
|
338
|
+
projection: 'EPSG:3857',
|
|
339
|
+
maxZoom,
|
|
340
|
+
minZoom,
|
|
310
341
|
}),
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
342
|
+
controls: defaultControls({
|
|
343
|
+
zoom: false,
|
|
344
|
+
rotate: false,
|
|
345
|
+
attribution: false,
|
|
346
|
+
}).extend([
|
|
347
|
+
new ScaleLine({
|
|
348
|
+
units: 'metric',
|
|
349
|
+
className: 'ol-scale-line',
|
|
350
|
+
}),
|
|
351
|
+
]),
|
|
352
|
+
interactions: defaultInteractions({
|
|
353
|
+
altShiftDragRotate: false,
|
|
354
|
+
pinchRotate: false,
|
|
355
|
+
}),
|
|
356
|
+
})
|
|
322
357
|
|
|
323
|
-
//
|
|
324
|
-
|
|
358
|
+
// 更新地图大小,确保地图正确渲染
|
|
359
|
+
setTimeout(() => {
|
|
360
|
+
if (map) {
|
|
361
|
+
map.updateSize()
|
|
362
|
+
// 确保默认图层正确显示
|
|
363
|
+
handleMapChange('tianditu')
|
|
364
|
+
}
|
|
365
|
+
}, 200)
|
|
366
|
+
|
|
367
|
+
// 监听地图移动结束事件
|
|
368
|
+
map.on('moveend', handleMoveEnd)
|
|
369
|
+
|
|
370
|
+
// 设置鼠标样式
|
|
371
|
+
if (mapRef.value) {
|
|
372
|
+
mapRef.value.style.cursor = 'grab'
|
|
373
|
+
// 监听地图事件
|
|
374
|
+
const mapElement = mapRef.value
|
|
375
|
+
|
|
376
|
+
// 鼠标按下时
|
|
377
|
+
mapElement.addEventListener('mousedown', () => {
|
|
378
|
+
mapElement.style.cursor = 'grabbing'
|
|
379
|
+
// 用户开始拖动地图,取消跟随定位
|
|
380
|
+
if (locationTimer) {
|
|
381
|
+
isFollowingLocation.value = false
|
|
382
|
+
}
|
|
383
|
+
})
|
|
325
384
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
385
|
+
// 触摸开始时
|
|
386
|
+
mapElement.addEventListener('touchstart', () => {
|
|
387
|
+
// 用户开始拖动地图,取消跟随定位
|
|
388
|
+
if (locationTimer) {
|
|
389
|
+
isFollowingLocation.value = false
|
|
390
|
+
}
|
|
391
|
+
})
|
|
330
392
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
393
|
+
// 鼠标释放时
|
|
394
|
+
mapElement.addEventListener('mouseup', () => {
|
|
395
|
+
mapElement.style.cursor = 'grab'
|
|
396
|
+
})
|
|
335
397
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
398
|
+
// 鼠标离开地图时
|
|
399
|
+
mapElement.addEventListener('mouseleave', () => {
|
|
400
|
+
mapElement.style.cursor = 'grab'
|
|
401
|
+
})
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
console.error('地图初始化失败:', error)
|
|
340
406
|
}
|
|
341
407
|
}
|
|
342
408
|
|
|
@@ -467,10 +533,9 @@ function createPointFeature(point: PointData, icon: string, iconAnchor: [number,
|
|
|
467
533
|
/**
|
|
468
534
|
* 创建点位图层
|
|
469
535
|
* @param config - 图层配置
|
|
470
|
-
* @param points - 点位数据
|
|
471
536
|
* @returns 返回图层实例
|
|
472
537
|
*/
|
|
473
|
-
function createPointLayer(config: PointLayerConfig
|
|
538
|
+
function createPointLayer(config: PointLayerConfig): VectorLayer<VectorSource> {
|
|
474
539
|
const vectorSource = new VectorSource()
|
|
475
540
|
const vectorLayer = new VectorLayer({
|
|
476
541
|
source: vectorSource,
|
|
@@ -479,10 +544,15 @@ function createPointLayer(config: PointLayerConfig, points: PointData[]): Vector
|
|
|
479
544
|
})
|
|
480
545
|
|
|
481
546
|
// 添加点位要素
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
vectorSource.
|
|
485
|
-
|
|
547
|
+
const addFeatures = (data: PointData[]) => {
|
|
548
|
+
// 清除现有要素
|
|
549
|
+
vectorSource.clear()
|
|
550
|
+
// 添加新的点位要素
|
|
551
|
+
data.forEach((point) => {
|
|
552
|
+
const feature = createPointFeature(point, config.icon, config.iconAnchor)
|
|
553
|
+
vectorSource.addFeature(feature)
|
|
554
|
+
})
|
|
555
|
+
}
|
|
486
556
|
|
|
487
557
|
// 添加点击事件处理
|
|
488
558
|
if (config.onClick) {
|
|
@@ -498,23 +568,134 @@ function createPointLayer(config: PointLayerConfig, points: PointData[]): Vector
|
|
|
498
568
|
})
|
|
499
569
|
}
|
|
500
570
|
|
|
571
|
+
// 监听图层可见性变化
|
|
572
|
+
vectorLayer.on('change:visible', async () => {
|
|
573
|
+
if (vectorLayer.getVisible() && config.dataProvider) {
|
|
574
|
+
try {
|
|
575
|
+
const data = await config.dataProvider()
|
|
576
|
+
addFeatures(data)
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
console.error('获取点位数据失败:', error)
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
// 初始加载数据
|
|
585
|
+
if (config.show && config.dataProvider) {
|
|
586
|
+
const result = config.dataProvider()
|
|
587
|
+
if (result instanceof Promise) {
|
|
588
|
+
result.then((data) => {
|
|
589
|
+
addFeatures(data)
|
|
590
|
+
}).catch((error) => {
|
|
591
|
+
console.error('获取初始点位数据失败:', error)
|
|
592
|
+
})
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
addFeatures(result)
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
501
599
|
return vectorLayer
|
|
502
600
|
}
|
|
503
601
|
|
|
504
602
|
/**
|
|
505
|
-
*
|
|
506
|
-
* @param config -
|
|
603
|
+
* 添加点位图层
|
|
604
|
+
* @param config - 图层配置
|
|
507
605
|
* @returns 返回图层实例
|
|
508
606
|
*/
|
|
509
|
-
function
|
|
510
|
-
if (!map
|
|
607
|
+
function addPointLayer(config: PointLayerConfig): VectorLayer<VectorSource> | null {
|
|
608
|
+
if (!map)
|
|
511
609
|
return null
|
|
512
610
|
|
|
513
|
-
const vectorLayer = createPointLayer(config
|
|
611
|
+
const vectorLayer = createPointLayer(config)
|
|
514
612
|
map.addLayer(vectorLayer)
|
|
613
|
+
vectorLayers[config.id] = vectorLayer
|
|
614
|
+
|
|
615
|
+
// 更新图层状态
|
|
616
|
+
if (config.showInControl !== false) {
|
|
617
|
+
const existingIndex = pointLayerStatus.value.findIndex(layer => layer.id === config.id)
|
|
618
|
+
if (existingIndex === -1) {
|
|
619
|
+
pointLayerStatus.value.push(config)
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
pointLayerStatus.value[existingIndex] = config
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
515
626
|
return vectorLayer
|
|
516
627
|
}
|
|
517
628
|
|
|
629
|
+
/**
|
|
630
|
+
* 添加海量点图层
|
|
631
|
+
* @param config - 图层配置
|
|
632
|
+
*/
|
|
633
|
+
function addWebGLPoints(config: WebGLPointOptions): void {
|
|
634
|
+
if (!map)
|
|
635
|
+
return
|
|
636
|
+
|
|
637
|
+
const vectorSource = new VectorSource()
|
|
638
|
+
const vectorLayer = new VectorLayer({
|
|
639
|
+
source: vectorSource,
|
|
640
|
+
visible: config.show,
|
|
641
|
+
zIndex: config.id === undefined ? 1 : 3,
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
// 添加点位要素
|
|
645
|
+
const addFeatures = (data: PointData[]) => {
|
|
646
|
+
// 清除现有要素
|
|
647
|
+
vectorSource.clear()
|
|
648
|
+
// 添加新的点位要素
|
|
649
|
+
data.forEach((point) => {
|
|
650
|
+
const feature = createPointFeature(point, config.icon, config.iconAnchor)
|
|
651
|
+
vectorSource.addFeature(feature)
|
|
652
|
+
})
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// 添加点击事件处理
|
|
656
|
+
if (config.onClick) {
|
|
657
|
+
map.on('click', (event) => {
|
|
658
|
+
const feature = map.forEachFeatureAtPixel(event.pixel, feature => feature, {
|
|
659
|
+
layerFilter: layer => layer === vectorLayer,
|
|
660
|
+
})
|
|
661
|
+
if (feature) {
|
|
662
|
+
const properties = feature.getProperties()
|
|
663
|
+
const { geometry, ...pointData } = properties
|
|
664
|
+
config.onClick(pointData as PointData, event)
|
|
665
|
+
}
|
|
666
|
+
})
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// 初始加载数据
|
|
670
|
+
if (config.show && config.dataProvider) {
|
|
671
|
+
const result = config.dataProvider()
|
|
672
|
+
if (result instanceof Promise) {
|
|
673
|
+
result.then((data) => {
|
|
674
|
+
addFeatures(data)
|
|
675
|
+
}).catch((error) => {
|
|
676
|
+
console.error('获取初始点位数据失败:', error)
|
|
677
|
+
})
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
addFeatures(result)
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
map.addLayer(vectorLayer)
|
|
685
|
+
vectorLayers[config.id] = vectorLayer
|
|
686
|
+
|
|
687
|
+
// 更新图层状态
|
|
688
|
+
if (config.showInControl !== false) {
|
|
689
|
+
const existingIndex = pointLayerStatus.value.findIndex(layer => layer.id === config.id)
|
|
690
|
+
if (existingIndex === -1) {
|
|
691
|
+
pointLayerStatus.value.push(config)
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
pointLayerStatus.value[existingIndex] = config
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
518
699
|
/**
|
|
519
700
|
* 添加 WMS 图层
|
|
520
701
|
* @param options - WMS 配置
|
|
@@ -587,32 +768,6 @@ function handleToggleWMSLayer(layer: WMSLayerConfig): void {
|
|
|
587
768
|
setWMSLayerVisible(layer.layerName, layer.show)
|
|
588
769
|
}
|
|
589
770
|
|
|
590
|
-
/**
|
|
591
|
-
* 添加点位图层
|
|
592
|
-
* @param config - 图层配置
|
|
593
|
-
* @param points - 点位数据
|
|
594
|
-
* @returns 返回图层ID
|
|
595
|
-
*/
|
|
596
|
-
function addPointLayer(config: PointLayerConfig, points: PointData[]): number {
|
|
597
|
-
if (!map)
|
|
598
|
-
return -1
|
|
599
|
-
|
|
600
|
-
const vectorLayer = createPointLayer(config, points)
|
|
601
|
-
map.addLayer(vectorLayer)
|
|
602
|
-
vectorLayers[config.id] = vectorLayer
|
|
603
|
-
|
|
604
|
-
// 更新图层状态
|
|
605
|
-
const existingIndex = pointLayerStatus.value.findIndex(layer => layer.id === config.id)
|
|
606
|
-
if (existingIndex === -1) {
|
|
607
|
-
pointLayerStatus.value.push(config)
|
|
608
|
-
}
|
|
609
|
-
else {
|
|
610
|
-
pointLayerStatus.value[existingIndex] = config
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
return config.id
|
|
614
|
-
}
|
|
615
|
-
|
|
616
771
|
/**
|
|
617
772
|
* 控制点位图层显示/隐藏
|
|
618
773
|
* @param layerId - 图层ID
|
|
@@ -638,6 +793,87 @@ function handleTogglePointLayer(layer: PointLayerConfig): void {
|
|
|
638
793
|
setPointLayerVisible(layer.id, layer.show)
|
|
639
794
|
}
|
|
640
795
|
|
|
796
|
+
/** 处理定位请求 */
|
|
797
|
+
async function handleLocation() {
|
|
798
|
+
if (!map)
|
|
799
|
+
return
|
|
800
|
+
|
|
801
|
+
// 如果是导航模式,重新开启跟随定位
|
|
802
|
+
if (locationTimer) {
|
|
803
|
+
isFollowingLocation.value = true
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
try {
|
|
807
|
+
mobileUtil.execute({
|
|
808
|
+
param: undefined,
|
|
809
|
+
funcName: 'getPhoneStatus',
|
|
810
|
+
callbackFunc: (result) => {
|
|
811
|
+
const locationResult = result as PhoneLocationStatus
|
|
812
|
+
if (locationResult.f_latitude && locationResult.f_longitude) {
|
|
813
|
+
const center: [number, number] = [locationResult.f_longitude, locationResult.f_latitude]
|
|
814
|
+
setCenterAndZoom(center, getZoom())
|
|
815
|
+
// 更新位置图标
|
|
816
|
+
if (isFollowingLocation.value) {
|
|
817
|
+
updateLocationMarker(center)
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
},
|
|
821
|
+
})
|
|
822
|
+
}
|
|
823
|
+
catch (error) {
|
|
824
|
+
// 在 web 端测试时,使用模拟数据
|
|
825
|
+
console.log('获取实时位置失败,使用模拟数据')
|
|
826
|
+
// 生成一个随机偏移量
|
|
827
|
+
const offset = (Math.random() - 0.5) * 0.01
|
|
828
|
+
const center: [number, number] = [
|
|
829
|
+
108.948024 + offset, // 西安经度
|
|
830
|
+
34.263161 + offset, // 西安纬度
|
|
831
|
+
]
|
|
832
|
+
|
|
833
|
+
setCenterAndZoom(center, getZoom())
|
|
834
|
+
// 更新位置图标
|
|
835
|
+
if (isFollowingLocation.value) {
|
|
836
|
+
updateLocationMarker(center)
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// 开启导航
|
|
842
|
+
function startNavigation() {
|
|
843
|
+
isFollowingLocation.value = true
|
|
844
|
+
|
|
845
|
+
// 创建并添加位置图标图层
|
|
846
|
+
if (!locationLayer) {
|
|
847
|
+
locationLayer = createLocationLayer()
|
|
848
|
+
map?.addLayer(locationLayer)
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
locationTimer = setInterval(() => {
|
|
852
|
+
navigationHandleLocation()
|
|
853
|
+
}, 1000)
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function stopNavigation() {
|
|
857
|
+
isFollowingLocation.value = false
|
|
858
|
+
if (locationTimer) {
|
|
859
|
+
clearInterval(locationTimer)
|
|
860
|
+
locationTimer = null
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// 移除位置图标图层
|
|
864
|
+
if (locationLayer && map) {
|
|
865
|
+
map.removeLayer(locationLayer)
|
|
866
|
+
locationLayer = null
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function navigationHandleLocation() {
|
|
871
|
+
// 打开导航定时器, 并且跟随定位
|
|
872
|
+
if (isFollowingLocation.value) {
|
|
873
|
+
handleLocation()
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
641
877
|
// 暴露方法给父组件
|
|
642
878
|
defineExpose({
|
|
643
879
|
init,
|
|
@@ -646,12 +882,17 @@ defineExpose({
|
|
|
646
882
|
setZoom,
|
|
647
883
|
getZoom,
|
|
648
884
|
setCenterAndZoom,
|
|
649
|
-
|
|
885
|
+
addPointLayer,
|
|
886
|
+
addWebGLPoints,
|
|
650
887
|
addWMSLayers,
|
|
651
888
|
setWMSLayerVisible,
|
|
652
889
|
handleToggleWMSLayer,
|
|
653
|
-
addPointLayer,
|
|
654
890
|
setPointLayerVisible,
|
|
891
|
+
handleTogglePointLayer,
|
|
892
|
+
getAddressInfo,
|
|
893
|
+
handleLocation,
|
|
894
|
+
startNavigation,
|
|
895
|
+
stopNavigation,
|
|
655
896
|
})
|
|
656
897
|
|
|
657
898
|
// 组件卸载时清理地图实例
|
|
@@ -660,181 +901,306 @@ onUnmounted(() => {
|
|
|
660
901
|
map.setTarget(undefined)
|
|
661
902
|
map = null
|
|
662
903
|
}
|
|
904
|
+
if (locationTimer) {
|
|
905
|
+
clearInterval(locationTimer)
|
|
906
|
+
locationTimer = null
|
|
907
|
+
}
|
|
908
|
+
locationLayer = null
|
|
663
909
|
})
|
|
664
910
|
</script>
|
|
665
911
|
|
|
666
912
|
<template>
|
|
667
913
|
<div class="map-wrapper">
|
|
668
914
|
<div ref="mapRef" class="ol-map" />
|
|
669
|
-
|
|
670
|
-
|
|
915
|
+
<!-- 添加定位按钮 -->
|
|
916
|
+
<div class="location-button">
|
|
917
|
+
<Button size="small" square @click="handleLocation">
|
|
918
|
+
<img
|
|
919
|
+
src="@af-mobile-client-vue3/assets/img/component/location.png"
|
|
920
|
+
class="location-icon"
|
|
921
|
+
alt="定位"
|
|
922
|
+
>
|
|
923
|
+
</Button>
|
|
924
|
+
</div>
|
|
671
925
|
<div class="map-controls">
|
|
672
|
-
<!--
|
|
673
|
-
<div class="control-
|
|
674
|
-
<
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
926
|
+
<!-- 控制按钮 -->
|
|
927
|
+
<div class="control-toggle">
|
|
928
|
+
<Button
|
|
929
|
+
:type="showControls ? 'primary' : 'default'"
|
|
930
|
+
size="small"
|
|
931
|
+
square
|
|
932
|
+
@click="showControls = !showControls"
|
|
933
|
+
>
|
|
934
|
+
<img
|
|
935
|
+
src="@af-mobile-client-vue3/assets/img/component/mapLayers.png"
|
|
936
|
+
class="toggle-icon"
|
|
937
|
+
:class="[{ active: showControls }]"
|
|
938
|
+
alt="图层"
|
|
939
|
+
>
|
|
940
|
+
</Button>
|
|
682
941
|
</div>
|
|
683
942
|
|
|
684
|
-
<!--
|
|
685
|
-
<div v-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
:key="layer.
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
>
|
|
697
|
-
{{ layer.value }}
|
|
698
|
-
</Button>
|
|
943
|
+
<!-- 图层控制面板 -->
|
|
944
|
+
<div v-show="showControls" class="control-panels">
|
|
945
|
+
<!-- 底图切换 -->
|
|
946
|
+
<div class="control-panel base-layer-control">
|
|
947
|
+
<div class="control-title">
|
|
948
|
+
<i class="van-icon van-icon-map-marked" /> 底图切换
|
|
949
|
+
</div>
|
|
950
|
+
<select v-model="currentMapType" @change="handleMapChange(currentMapType)">
|
|
951
|
+
<option v-for="layer in layerOptions" :key="layer.value" :value="layer.value">
|
|
952
|
+
{{ layer.text }}
|
|
953
|
+
</option>
|
|
954
|
+
</select>
|
|
699
955
|
</div>
|
|
700
|
-
</div>
|
|
701
956
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
957
|
+
<!-- 图层控制 -->
|
|
958
|
+
<div v-if="wmsLayerStatus.length > 0" class="control-panel layer-control">
|
|
959
|
+
<div class="control-title">
|
|
960
|
+
<i class="van-icon van-icon-layers" /> 图层控制
|
|
961
|
+
</div>
|
|
962
|
+
<div class="layer-list">
|
|
963
|
+
<div
|
|
964
|
+
v-for="layer in wmsLayerStatus"
|
|
965
|
+
:key="layer.id"
|
|
966
|
+
class="layer-item"
|
|
967
|
+
:class="{ active: layer.show }"
|
|
968
|
+
@click="handleToggleWMSLayer(layer)"
|
|
969
|
+
>
|
|
970
|
+
<i class="van-icon" :class="layer.show ? 'van-icon-eye' : 'van-icon-closed-eye'" />
|
|
971
|
+
<span>{{ layer.value }}</span>
|
|
972
|
+
</div>
|
|
973
|
+
</div>
|
|
706
974
|
</div>
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
975
|
+
|
|
976
|
+
<!-- 点位图层 -->
|
|
977
|
+
<div v-if="pointLayerStatus.length > 0" class="control-panel layer-control">
|
|
978
|
+
<div class="control-title">
|
|
979
|
+
<i class="van-icon van-icon-location" /> 点位图层
|
|
980
|
+
</div>
|
|
981
|
+
<div class="layer-list">
|
|
982
|
+
<div
|
|
983
|
+
v-for="layer in pointLayerStatus"
|
|
984
|
+
:key="layer.id"
|
|
985
|
+
class="layer-item"
|
|
986
|
+
:class="{ active: layer.show }"
|
|
987
|
+
@click="handleTogglePointLayer(layer)"
|
|
988
|
+
>
|
|
989
|
+
<i class="van-icon" :class="layer.show ? 'van-icon-eye' : 'van-icon-closed-eye'" />
|
|
990
|
+
<span>{{ layer.value }}</span>
|
|
991
|
+
</div>
|
|
992
|
+
</div>
|
|
717
993
|
</div>
|
|
718
994
|
</div>
|
|
719
995
|
</div>
|
|
720
996
|
</div>
|
|
721
997
|
</template>
|
|
722
998
|
|
|
723
|
-
<style
|
|
999
|
+
<style lang="less" scoped>
|
|
724
1000
|
.map-wrapper {
|
|
725
1001
|
position: relative;
|
|
726
1002
|
width: 100%;
|
|
727
1003
|
height: 100%;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
.ol-map {
|
|
731
|
-
width: 100%;
|
|
732
|
-
height: 100%;
|
|
733
|
-
min-height: 400px;
|
|
734
|
-
touch-action: none;
|
|
735
1004
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
.map-controls {
|
|
742
|
-
position: absolute;
|
|
743
|
-
right: 10px;
|
|
744
|
-
top: 10px;
|
|
745
|
-
z-index: 1000;
|
|
746
|
-
display: flex;
|
|
747
|
-
flex-direction: column;
|
|
748
|
-
gap: 10px;
|
|
749
|
-
}
|
|
1005
|
+
.ol-map {
|
|
1006
|
+
width: 100%;
|
|
1007
|
+
height: 100%;
|
|
1008
|
+
min-height: 400px;
|
|
1009
|
+
touch-action: none;
|
|
750
1010
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
755
|
-
padding: 10px;
|
|
756
|
-
min-width: 140px;
|
|
757
|
-
|
|
758
|
-
.control-title {
|
|
759
|
-
font-size: 14px;
|
|
760
|
-
font-weight: 500;
|
|
761
|
-
color: #333;
|
|
762
|
-
margin-bottom: 8px;
|
|
763
|
-
padding-left: 4px;
|
|
764
|
-
border-left: 3px solid #1989fa;
|
|
1011
|
+
&:active {
|
|
1012
|
+
cursor: grabbing;
|
|
1013
|
+
}
|
|
765
1014
|
}
|
|
766
|
-
}
|
|
767
1015
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1016
|
+
// 定位按钮样式
|
|
1017
|
+
.location-button {
|
|
1018
|
+
position: absolute;
|
|
1019
|
+
left: 0.5em;
|
|
1020
|
+
bottom: calc(0.5em + 30px);
|
|
1021
|
+
z-index: 1000;
|
|
1022
|
+
|
|
1023
|
+
.van-button {
|
|
1024
|
+
width: 32px;
|
|
1025
|
+
height: 32px;
|
|
1026
|
+
padding: 4px;
|
|
1027
|
+
display: flex;
|
|
1028
|
+
align-items: center;
|
|
1029
|
+
justify-content: center;
|
|
1030
|
+
border: 1px solid #ebedf0;
|
|
1031
|
+
background: rgba(255, 255, 255, 0.9);
|
|
1032
|
+
backdrop-filter: blur(4px);
|
|
1033
|
+
box-shadow: none;
|
|
1034
|
+
|
|
1035
|
+
&:hover {
|
|
1036
|
+
border-color: #1989fa;
|
|
1037
|
+
.location-icon {
|
|
1038
|
+
opacity: 1;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
789
1041
|
|
|
790
|
-
|
|
791
|
-
|
|
1042
|
+
.location-icon {
|
|
1043
|
+
width: 20px;
|
|
1044
|
+
height: 20px;
|
|
1045
|
+
opacity: 0.7;
|
|
1046
|
+
transition: all 0.3s;
|
|
1047
|
+
}
|
|
792
1048
|
}
|
|
793
1049
|
}
|
|
794
|
-
}
|
|
795
1050
|
|
|
796
|
-
.
|
|
797
|
-
|
|
1051
|
+
.map-controls {
|
|
1052
|
+
position: absolute;
|
|
1053
|
+
right: 10px;
|
|
1054
|
+
top: 10px;
|
|
1055
|
+
z-index: 1000;
|
|
798
1056
|
display: flex;
|
|
799
1057
|
flex-direction: column;
|
|
800
1058
|
gap: 8px;
|
|
801
1059
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
1060
|
+
.control-toggle {
|
|
1061
|
+
align-self: flex-end;
|
|
1062
|
+
|
|
1063
|
+
.van-button {
|
|
1064
|
+
width: 32px;
|
|
1065
|
+
height: 32px;
|
|
1066
|
+
padding: 4px;
|
|
1067
|
+
display: flex;
|
|
1068
|
+
align-items: center;
|
|
1069
|
+
justify-content: center;
|
|
1070
|
+
border: 1px solid #ebedf0;
|
|
1071
|
+
background: rgba(255, 255, 255, 0.9);
|
|
1072
|
+
backdrop-filter: blur(4px);
|
|
1073
|
+
box-shadow: none;
|
|
1074
|
+
|
|
1075
|
+
&.van-button--primary {
|
|
1076
|
+
background: #1989fa;
|
|
1077
|
+
border-color: #1989fa;
|
|
1078
|
+
}
|
|
807
1079
|
|
|
808
|
-
|
|
809
|
-
|
|
1080
|
+
.toggle-icon {
|
|
1081
|
+
width: 20px;
|
|
1082
|
+
height: 20px;
|
|
1083
|
+
transition: all 0.3s;
|
|
1084
|
+
opacity: 0.7;
|
|
1085
|
+
|
|
1086
|
+
&.active {
|
|
1087
|
+
opacity: 1;
|
|
1088
|
+
filter: brightness(1) invert(1);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
810
1091
|
|
|
811
1092
|
&:hover {
|
|
812
1093
|
border-color: #1989fa;
|
|
813
|
-
|
|
1094
|
+
.toggle-icon {
|
|
1095
|
+
opacity: 1;
|
|
1096
|
+
}
|
|
814
1097
|
}
|
|
815
1098
|
}
|
|
816
1099
|
}
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
1100
|
|
|
820
|
-
.
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1101
|
+
.control-panels {
|
|
1102
|
+
background: white;
|
|
1103
|
+
border-radius: 8px;
|
|
1104
|
+
padding: 12px;
|
|
1105
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
1106
|
+
min-width: 160px;
|
|
1107
|
+
max-width: 180px;
|
|
1108
|
+
|
|
1109
|
+
.control-panel {
|
|
1110
|
+
& + .control-panel {
|
|
1111
|
+
margin-top: 12px;
|
|
1112
|
+
padding-top: 12px;
|
|
1113
|
+
border-top: 1px solid #f5f5f5;
|
|
1114
|
+
}
|
|
825
1115
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
1116
|
+
.control-title {
|
|
1117
|
+
font-size: 13px;
|
|
1118
|
+
font-weight: 500;
|
|
1119
|
+
color: #323233;
|
|
1120
|
+
margin-bottom: 8px;
|
|
1121
|
+
display: flex;
|
|
1122
|
+
align-items: center;
|
|
1123
|
+
gap: 4px;
|
|
1124
|
+
|
|
1125
|
+
.van-icon {
|
|
1126
|
+
font-size: 16px;
|
|
1127
|
+
color: #1989fa;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
831
1130
|
|
|
832
|
-
|
|
833
|
-
|
|
1131
|
+
select {
|
|
1132
|
+
width: 100%;
|
|
1133
|
+
padding: 6px 24px 6px 8px;
|
|
1134
|
+
font-size: 13px;
|
|
1135
|
+
border: 1px solid #dcdee0;
|
|
1136
|
+
border-radius: 4px;
|
|
1137
|
+
background-color: #f7f8fa;
|
|
1138
|
+
color: #323233;
|
|
1139
|
+
cursor: pointer;
|
|
1140
|
+
outline: none;
|
|
1141
|
+
appearance: none;
|
|
1142
|
+
-webkit-appearance: none;
|
|
1143
|
+
-moz-appearance: none;
|
|
1144
|
+
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
|
1145
|
+
background-repeat: no-repeat;
|
|
1146
|
+
background-position: right 6px center;
|
|
1147
|
+
background-size: 12px;
|
|
1148
|
+
|
|
1149
|
+
&:hover {
|
|
1150
|
+
border-color: #1989fa;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
option {
|
|
1154
|
+
padding: 6px;
|
|
1155
|
+
background: white;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
834
1158
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1159
|
+
.layer-list {
|
|
1160
|
+
display: flex;
|
|
1161
|
+
flex-direction: column;
|
|
1162
|
+
gap: 4px;
|
|
1163
|
+
|
|
1164
|
+
.layer-item {
|
|
1165
|
+
display: flex;
|
|
1166
|
+
align-items: center;
|
|
1167
|
+
gap: 6px;
|
|
1168
|
+
padding: 6px 8px;
|
|
1169
|
+
border-radius: 4px;
|
|
1170
|
+
cursor: pointer;
|
|
1171
|
+
transition: all 0.3s;
|
|
1172
|
+
background: #f7f8fa;
|
|
1173
|
+
border: 1px solid transparent;
|
|
1174
|
+
|
|
1175
|
+
.van-icon {
|
|
1176
|
+
font-size: 14px;
|
|
1177
|
+
color: #969799;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
span {
|
|
1181
|
+
font-size: 13px;
|
|
1182
|
+
color: #323233;
|
|
1183
|
+
flex: 1;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
&:hover {
|
|
1187
|
+
background: #f0f2f5;
|
|
1188
|
+
border-color: #dcdee0;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
&.active {
|
|
1192
|
+
background: #e6f3ff;
|
|
1193
|
+
border-color: #1989fa;
|
|
1194
|
+
|
|
1195
|
+
.van-icon {
|
|
1196
|
+
color: #1989fa;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
span {
|
|
1200
|
+
color: #1989fa;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
838
1204
|
}
|
|
839
1205
|
}
|
|
840
1206
|
}
|
|
@@ -842,7 +1208,7 @@ onUnmounted(() => {
|
|
|
842
1208
|
}
|
|
843
1209
|
|
|
844
1210
|
// 比例尺样式
|
|
845
|
-
|
|
1211
|
+
.ol-scale-line {
|
|
846
1212
|
position: absolute;
|
|
847
1213
|
left: 0.5em;
|
|
848
1214
|
bottom: 0.5em;
|
|
@@ -851,7 +1217,7 @@ onUnmounted(() => {
|
|
|
851
1217
|
border-radius: 4px;
|
|
852
1218
|
}
|
|
853
1219
|
|
|
854
|
-
|
|
1220
|
+
.ol-scale-line-inner {
|
|
855
1221
|
border: 2px solid #333;
|
|
856
1222
|
border-top: none;
|
|
857
1223
|
color: #333;
|
|
@@ -867,52 +1233,71 @@ onUnmounted(() => {
|
|
|
867
1233
|
.map-controls {
|
|
868
1234
|
right: 8px;
|
|
869
1235
|
top: 8px;
|
|
870
|
-
gap:
|
|
871
|
-
}
|
|
1236
|
+
gap: 6px;
|
|
872
1237
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1238
|
+
.control-panels {
|
|
1239
|
+
padding: 10px;
|
|
1240
|
+
min-width: 140px;
|
|
876
1241
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1242
|
+
.control-panel {
|
|
1243
|
+
& + .control-panel {
|
|
1244
|
+
margin-top: 10px;
|
|
1245
|
+
padding-top: 10px;
|
|
1246
|
+
}
|
|
882
1247
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
font-size: 12px;
|
|
887
|
-
background-size: 10px;
|
|
888
|
-
}
|
|
889
|
-
}
|
|
1248
|
+
.control-title {
|
|
1249
|
+
font-size: 12px;
|
|
1250
|
+
margin-bottom: 6px;
|
|
890
1251
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1252
|
+
.van-icon {
|
|
1253
|
+
font-size: 14px;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
894
1256
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}
|
|
1257
|
+
select {
|
|
1258
|
+
padding: 4px 20px 4px 6px;
|
|
1259
|
+
font-size: 12px;
|
|
1260
|
+
background-size: 10px;
|
|
1261
|
+
}
|
|
901
1262
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
gap: 6px;
|
|
1263
|
+
.layer-list {
|
|
1264
|
+
gap: 3px;
|
|
905
1265
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1266
|
+
.layer-item {
|
|
1267
|
+
padding: 4px 6px;
|
|
1268
|
+
|
|
1269
|
+
.van-icon {
|
|
1270
|
+
font-size: 12px;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
span {
|
|
1274
|
+
font-size: 12px;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
909
1278
|
}
|
|
910
1279
|
}
|
|
911
1280
|
}
|
|
1281
|
+
}
|
|
912
1282
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1283
|
+
// 比例尺样式
|
|
1284
|
+
:deep(.ol-scale-line) {
|
|
1285
|
+
position: absolute;
|
|
1286
|
+
left: 0.5em;
|
|
1287
|
+
bottom: 0.5em;
|
|
1288
|
+
background: rgba(255, 255, 255, 0.8);
|
|
1289
|
+
padding: 2px;
|
|
1290
|
+
border-radius: 4px;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
:deep(.ol-scale-line-inner) {
|
|
1294
|
+
border: 2px solid #333;
|
|
1295
|
+
border-top: none;
|
|
1296
|
+
color: #333;
|
|
1297
|
+
font-size: 12px;
|
|
1298
|
+
text-align: center;
|
|
1299
|
+
margin: 1px;
|
|
1300
|
+
will-change: contents, width;
|
|
1301
|
+
transition: width 0.25s;
|
|
917
1302
|
}
|
|
918
1303
|
</style>
|