af-mobile-client-vue3 1.1.3 → 1.1.4

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.
@@ -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
- interface InitParams {
26
- /** 地图中心点坐标 [经度, 纬度] */
27
- center?: [number, number]
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
- /** WMS 服务配置接口 */
113
- interface WMSConfig {
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
- /** WMS 完整配置接口 */
127
- interface WMSOptions {
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>('gaode')
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
- baseMaps.gaode = new TileLayer({
199
- source: new XYZ({
200
- url: 'http://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}',
201
- crossOrigin: 'anonymous',
202
- projection: 'EPSG:3857',
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
- baseMaps.gaodeSatellite = new TileLayer({
208
- source: new XYZ({
209
- url: 'https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
210
- crossOrigin: 'anonymous',
211
- projection: 'EPSG:3857',
212
- }),
213
- visible: false,
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
- baseMaps.gaodelabelLayer = new TileLayer({
218
- source: new XYZ({
219
- url: 'http://webst02.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}',
220
- crossOrigin: 'anonymous',
221
- projection: 'EPSG:3857',
222
- }),
223
- visible: false,
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
- baseMaps.tianditu = new TileLayer({
228
- source: new XYZ({
229
- url: 'http://t0.tianditu.gov.cn/vec_w/wmts?'
230
- + 'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&'
231
- + `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tianDiTuKey}`,
232
- projection: wgs84ToGcj02Projection,
233
- }),
234
- visible: false,
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
- baseMaps.tianditulabel = new TileLayer({
239
- source: new XYZ({
240
- url: 'http://t0.tianditu.gov.cn/cva_w/wmts?'
241
- + 'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&'
242
- + `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tianDiTuKey}`,
243
- projection: wgs84ToGcj02Projection,
244
- }),
245
- visible: false,
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
- baseMaps.tianditusatellite = new TileLayer({
250
- source: new XYZ({
251
- url: 'http://t0.tianditu.gov.cn/img_w/wmts?'
252
- + 'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&'
253
- + `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tianDiTuKey}`,
254
- projection: wgs84ToGcj02Projection,
255
- }),
256
- visible: false,
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
- baseMaps.tianditusatlabel = new TileLayer({
261
- source: new XYZ({
262
- url: 'http://t0.tianditu.gov.cn/cia_w/wmts?'
263
- + 'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&'
264
- + `FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tianDiTuKey}`,
265
- projection: wgs84ToGcj02Projection,
266
- }),
267
- visible: false,
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
- initializeLayers(tianDiTuKey)
290
-
291
- // 创建地图实例
292
- map = new Map({
293
- target: mapRef.value,
294
- layers: Object.values(baseMaps),
295
- view: new View({
296
- center: fromLonLat(center),
297
- zoom,
298
- projection: 'EPSG:3857',
299
- maxZoom,
300
- minZoom,
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
- interactions: defaultInteractions({
314
- altShiftDragRotate: false, // 禁用 Alt+Shift+拖动 旋转
315
- pinchRotate: false, // 禁用双指旋转
316
- }),
317
- })
318
-
319
- // 设置鼠标样式
320
- if (mapRef.value) {
321
- mapRef.value.style.cursor = 'grab'
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
- const mapElement = mapRef.value
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
- mapElement.addEventListener('mousedown', () => {
328
- mapElement.style.cursor = 'grabbing'
329
- })
385
+ // 触摸开始时
386
+ mapElement.addEventListener('touchstart', () => {
387
+ // 用户开始拖动地图,取消跟随定位
388
+ if (locationTimer) {
389
+ isFollowingLocation.value = false
390
+ }
391
+ })
330
392
 
331
- // 鼠标释放时
332
- mapElement.addEventListener('mouseup', () => {
333
- mapElement.style.cursor = 'grab'
334
- })
393
+ // 鼠标释放时
394
+ mapElement.addEventListener('mouseup', () => {
395
+ mapElement.style.cursor = 'grab'
396
+ })
335
397
 
336
- // 鼠标离开地图时
337
- mapElement.addEventListener('mouseleave', () => {
338
- mapElement.style.cursor = 'grab'
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, points: PointData[]): VectorLayer<VectorSource> {
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
- points.forEach((point) => {
483
- const feature = createPointFeature(point, config.icon, config.iconAnchor)
484
- vectorSource.addFeature(feature)
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 addVectorPoints(config: PointLayerConfig): VectorLayer<VectorSource> | null {
510
- if (!map || !config.data)
607
+ function addPointLayer(config: PointLayerConfig): VectorLayer<VectorSource> | null {
608
+ if (!map)
511
609
  return null
512
610
 
513
- const vectorLayer = createPointLayer(config, config.data)
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
- addVectorPoints,
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-panel base-layer-control">
674
- <div class="control-title">
675
- 底图切换
676
- </div>
677
- <select v-model="currentMapType" @change="handleMapChange(currentMapType)">
678
- <option v-for="layer in layerOptions" :key="layer.value" :value="layer.value">
679
- {{ layer.text }}
680
- </option>
681
- </select>
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
- <!-- WMS 图层控制 -->
685
- <div v-if="wmsLayerStatus.length > 0" class="control-panel wms-layer-control">
686
- <div class="control-title">
687
- 图层控制
688
- </div>
689
- <div class="control-buttons">
690
- <Button
691
- v-for="layer in wmsLayerStatus"
692
- :key="layer.id"
693
- :type="layer.show ? 'primary' : 'default'"
694
- size="small"
695
- @click="handleToggleWMSLayer(layer)"
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
- <div v-if="pointLayerStatus.length > 0" class="control-panel point-layer-control">
704
- <div class="control-title">
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
- <div class="control-buttons">
708
- <Button
709
- v-for="layer in pointLayerStatus"
710
- :key="layer.id"
711
- :type="layer.show ? 'primary' : 'default'"
712
- size="small"
713
- @click="handleTogglePointLayer(layer)"
714
- >
715
- {{ layer.value }}
716
- </Button>
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 scoped lang="less">
999
+ <style lang="scss" 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
- &:active {
737
- cursor: grabbing;
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
- .control-panel {
752
- background: white;
753
- border-radius: 4px;
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
- .base-layer-control {
769
- select {
770
- width: 100%;
771
- padding: 6px 24px 6px 12px;
772
- font-size: 14px;
773
- border: 1px solid #dcdee0;
774
- border-radius: 4px;
775
- background-color: white;
776
- cursor: pointer;
777
- outline: none;
778
- appearance: none;
779
- -webkit-appearance: none;
780
- -moz-appearance: none;
781
- 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");
782
- background-repeat: no-repeat;
783
- background-position: right 6px center;
784
- background-size: 12px;
785
-
786
- &:hover {
787
- border-color: #1989fa;
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
- option {
791
- padding: 8px;
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
- .wms-layer-control {
797
- .control-buttons {
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
- :deep(.van-button) {
803
- width: 100%;
804
- justify-content: flex-start;
805
- padding-left: 12px;
806
- border-radius: 4px;
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
- &--default {
809
- border: 1px solid #dcdee0;
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
- color: #1989fa;
1094
+ .toggle-icon {
1095
+ opacity: 1;
1096
+ }
814
1097
  }
815
1098
  }
816
1099
  }
817
- }
818
- }
819
1100
 
820
- .point-layer-control {
821
- .control-buttons {
822
- display: flex;
823
- flex-direction: column;
824
- gap: 8px;
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
- :deep(.van-button) {
827
- width: 100%;
828
- justify-content: flex-start;
829
- padding-left: 12px;
830
- border-radius: 4px;
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
- &--default {
833
- border: 1px solid #dcdee0;
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
- &:hover {
836
- border-color: #1989fa;
837
- color: #1989fa;
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
- :deep(.ol-scale-line) {
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
- :deep(.ol-scale-line-inner) {
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: 8px;
871
- }
1236
+ gap: 6px;
872
1237
 
873
- .control-panel {
874
- padding: 8px;
875
- min-width: 120px;
1238
+ .control-panels {
1239
+ padding: 10px;
1240
+ min-width: 140px;
876
1241
 
877
- .control-title {
878
- font-size: 12px;
879
- margin-bottom: 6px;
880
- }
881
- }
1242
+ .control-panel {
1243
+ & + .control-panel {
1244
+ margin-top: 10px;
1245
+ padding-top: 10px;
1246
+ }
882
1247
 
883
- .base-layer-control {
884
- select {
885
- padding: 4px 20px 4px 8px;
886
- font-size: 12px;
887
- background-size: 10px;
888
- }
889
- }
1248
+ .control-title {
1249
+ font-size: 12px;
1250
+ margin-bottom: 6px;
890
1251
 
891
- .wms-layer-control {
892
- .control-buttons {
893
- gap: 6px;
1252
+ .van-icon {
1253
+ font-size: 14px;
1254
+ }
1255
+ }
894
1256
 
895
- :deep(.van-button) {
896
- font-size: 12px;
897
- padding: 0 8px;
898
- }
899
- }
900
- }
1257
+ select {
1258
+ padding: 4px 20px 4px 6px;
1259
+ font-size: 12px;
1260
+ background-size: 10px;
1261
+ }
901
1262
 
902
- .point-layer-control {
903
- .control-buttons {
904
- gap: 6px;
1263
+ .layer-list {
1264
+ gap: 3px;
905
1265
 
906
- :deep(.van-button) {
907
- font-size: 12px;
908
- padding: 0 8px;
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
- :deep(.ol-scale-line) {
914
- transform: scale(0.8);
915
- transform-origin: left bottom;
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>