leaflet-map.uvue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. <template>
  2. <web-view
  3. ref="webviewRef"
  4. id="leafletWebView"
  5. :src="webviewUrl"
  6. @message="handleMessage"
  7. @load="handleLoad"
  8. class="leaflet-webview"
  9. ></web-view>
  10. </template>
  11. <script setup lang="uts">
  12. import { ref, computed, watch } from 'vue'
  13. import type { MapMarker, MapLayerType } from '../../types/map'
  14. // Props
  15. type Props = {
  16. latitude?: number
  17. longitude?: number
  18. zoom?: number
  19. markers?: MapMarker[]
  20. layerType?: MapLayerType
  21. tiandituKey?: string
  22. }
  23. const props = withDefaults(defineProps<Props>(), {
  24. latitude: 32.556211,
  25. longitude: 111.494811,
  26. zoom: 13,
  27. markers: () => [],
  28. layerType: 'tianditu-img',
  29. tiandituKey: '4c9c8a818d3cb25bf5ccbd749e7e67c8'
  30. })
  31. // Emits
  32. const emit = defineEmits<{
  33. (e: 'markerTap', event: any): void
  34. (e: 'regionChange', region: any): void
  35. (e: 'layerChange', layerType: MapLayerType): void
  36. (e: 'ready'): void
  37. (e: 'error', error: string): void
  38. }>()
  39. // WebView 引用(内置组件使用对应的 Element 类型)
  40. const webviewRef = ref<UniWebViewElement | null>(null)
  41. // WebView 是否已加载
  42. const isWebviewLoaded = ref(false)
  43. // WebView URL
  44. const webviewUrl = computed<string>(() => {
  45. return `/static/leaflet/leaflet-map.html?lat=${props.latitude}&lng=${props.longitude}&zoom=${props.zoom}&layer=${props.layerType}&key=${props.tiandituKey}`
  46. })
  47. // 处理 WebView 加载完成
  48. const handleLoad = (): void => {
  49. isWebviewLoaded.value = true
  50. console.log('WebView loaded')
  51. // 延迟触发 ready 事件,等待地图初始化完成
  52. // 因为 web-view 在不同平台的消息机制不同,所以直接在 load 完成后触发
  53. setTimeout(() => {
  54. console.log('Auto-triggering ready event after WebView load')
  55. emit('ready')
  56. }, 1000)
  57. }
  58. // 处理 WebView 消息
  59. const handleMessage = (e: any): void => {
  60. try {
  61. console.log('WebView message received')
  62. // WebView 的 message 事件在不同平台可能有不同的数据结构
  63. // 直接触发 ready 事件,让地图组件可以开始工作
  64. console.log('Emitting ready event')
  65. emit('ready')
  66. } catch (error: any) {
  67. console.error('Error handling WebView message:', error)
  68. // 即使出错也触发 ready
  69. emit('ready')
  70. }
  71. }
  72. // 执行 JavaScript(内置组件直接调用方法)
  73. const evalJS = (js: string): void => {
  74. if (!isWebviewLoaded.value || webviewRef.value == null) {
  75. return
  76. }
  77. // #ifdef APP
  78. // App 平台:web-view 是原生组件,直接调用 evalJS 方法
  79. webviewRef.value.evalJS(js)
  80. // #endif
  81. // #ifdef WEB
  82. // Web 平台:web-view 是 iframe,通过 contentWindow 执行 JavaScript
  83. try {
  84. // 尝试多种方式获取 iframe 元素
  85. let iframeElement = null
  86. // 方式1:通过 $el 访问
  87. const iframe = webviewRef.value as any
  88. if (iframe && iframe.$el) {
  89. if (iframe.$el.tagName === 'IFRAME') {
  90. iframeElement = iframe.$el
  91. } else {
  92. // 可能是包装元素,查找其中的 iframe
  93. iframeElement = iframe.$el.querySelector('iframe')
  94. }
  95. }
  96. // 方式2:通过 ID 查找
  97. if (iframeElement == null) {
  98. iframeElement = document.getElementById('leafletWebView')
  99. }
  100. // 方式3:查找所有 iframe
  101. if (iframeElement == null) {
  102. const iframes = document.getElementsByTagName('iframe')
  103. if (iframes.length > 0) {
  104. iframeElement = iframes[0]
  105. }
  106. }
  107. console.log('iframe element:', iframeElement)
  108. console.log('iframe contentWindow:', iframeElement ? iframeElement.contentWindow : null)
  109. if (iframeElement && iframeElement.contentWindow) {
  110. // 使用 eval 在 iframe 的上下文中执行 JavaScript
  111. iframeElement.contentWindow.eval(js)
  112. console.log('✓ Web platform: evalJS executed successfully')
  113. } else {
  114. console.warn('Web platform: Cannot access iframe contentWindow', {
  115. iframeElement: iframeElement,
  116. contentWindow: iframeElement ? iframeElement.contentWindow : null
  117. })
  118. }
  119. } catch (e: any) {
  120. console.error('Web platform: evalJS error:', e)
  121. }
  122. // #endif
  123. }
  124. // 添加标记点
  125. const addMarker = (marker: MapMarker): void => {
  126. const js = `window.leafletMap.addMarker(${JSON.stringify({
  127. id: marker.id,
  128. latitude: marker.latitude,
  129. longitude: marker.longitude,
  130. title: marker.title,
  131. iconPath: marker.iconPath,
  132. width: marker.width,
  133. height: marker.height
  134. })})`
  135. evalJS(js)
  136. }
  137. // 批量添加标记点
  138. const addMarkers = (markers: MapMarker[]): void => {
  139. const markersData = markers.map((marker) => ({
  140. id: marker.id,
  141. latitude: marker.latitude,
  142. longitude: marker.longitude,
  143. title: marker.title,
  144. iconPath: marker.iconPath,
  145. width: marker.width,
  146. height: marker.height
  147. }))
  148. const js = `window.leafletMap.addMarkers(${JSON.stringify(markersData)})`
  149. evalJS(js)
  150. }
  151. // 清除所有标记点
  152. const clearMarkers = (): void => {
  153. const js = 'window.leafletMap.clearMarkers()'
  154. evalJS(js)
  155. }
  156. // 移除单个标记点
  157. const removeMarker = (markerId: number): void => {
  158. const js = `window.leafletMap.removeMarker(${markerId})`
  159. evalJS(js)
  160. }
  161. // 添加折线
  162. const addPolyline = (points: any[], color: string | null, width: number | null): void => {
  163. const finalColor = color != null ? color : '#007aff'
  164. const finalWidth = width != null ? width : 4
  165. // 构造数据对象
  166. const data = {
  167. points: points,
  168. color: finalColor,
  169. width: finalWidth
  170. }
  171. const js = `window.leafletMap.addPolyline(${JSON.stringify(data)})`
  172. evalJS(js)
  173. }
  174. // 添加圆形
  175. const addCircle = (
  176. latitude: number,
  177. longitude: number,
  178. radius: number,
  179. strokeColor: string | null,
  180. fillColor: string | null
  181. ): void => {
  182. const finalStrokeColor = strokeColor != null ? strokeColor : '#007aff'
  183. const finalFillColor = fillColor != null ? fillColor : 'rgba(0, 122, 255, 0.2)'
  184. // 构造数据对象
  185. const data = {
  186. latitude: latitude,
  187. longitude: longitude,
  188. radius: radius,
  189. strokeColor: finalStrokeColor,
  190. fillColor: finalFillColor
  191. }
  192. const js = `window.leafletMap.addCircle(${JSON.stringify(data)})`
  193. evalJS(js)
  194. }
  195. // 添加多边形
  196. const addPolygon = (
  197. points: any[],
  198. strokeColor: string | null,
  199. fillColor: string | null
  200. ): void => {
  201. const finalStrokeColor = strokeColor != null ? strokeColor : '#007aff'
  202. const finalFillColor = fillColor != null ? fillColor : 'rgba(0, 122, 255, 0.2)'
  203. // 构造数据对象
  204. const data = {
  205. points: points,
  206. strokeColor: finalStrokeColor,
  207. fillColor: finalFillColor
  208. }
  209. const js = `window.leafletMap.addPolygon(${JSON.stringify(data)})`
  210. evalJS(js)
  211. }
  212. // 清除所有覆盖物
  213. const clearOverlays = (): void => {
  214. const js = 'window.leafletMap.clearOverlays()'
  215. evalJS(js)
  216. }
  217. // 设置地图中心
  218. const setCenter = (latitude: number, longitude: number, zoom: number | null): void => {
  219. if (zoom != null) {
  220. const js = `window.leafletMap.setCenter(${latitude}, ${longitude}, ${zoom})`
  221. evalJS(js)
  222. } else {
  223. // 使用当前缩放级别
  224. const js = `window.leafletMap.setCenter(${latitude}, ${longitude})`
  225. evalJS(js)
  226. }
  227. }
  228. // 设置缩放级别
  229. const setZoom = (zoom: number): void => {
  230. const js = `window.leafletMap.setZoom(${zoom})`
  231. evalJS(js)
  232. }
  233. // 切换图层
  234. const switchLayer = (layerType: MapLayerType): void => {
  235. const js = `window.leafletMap.switchLayer('${layerType}')`
  236. evalJS(js)
  237. emit('layerChange', layerType)
  238. }
  239. // 添加业务图层
  240. const addBusinessLayer = (layerCode: string, layerUrl: string, baseUrl: string, token: string | null): void => {
  241. const tokenParam = token != null ? token : ''
  242. const js = `window.leafletMap.addBusinessLayer('${layerCode}', '${layerUrl}', '${baseUrl}', '${tokenParam}')`
  243. evalJS(js)
  244. }
  245. // 移除业务图层
  246. const removeBusinessLayer = (layerCode: string): void => {
  247. const js = `window.leafletMap.removeBusinessLayer('${layerCode}')`
  248. evalJS(js)
  249. }
  250. // 暴露方法
  251. defineExpose({
  252. addMarker,
  253. addMarkers,
  254. clearMarkers,
  255. removeMarker,
  256. addPolyline,
  257. addCircle,
  258. addPolygon,
  259. clearOverlays,
  260. setCenter,
  261. setZoom,
  262. switchLayer,
  263. addBusinessLayer,
  264. removeBusinessLayer
  265. })
  266. </script>
  267. <style lang="scss">
  268. .leaflet-webview {
  269. width: 100%;
  270. height: 100%;
  271. }
  272. </style>