tdtuMap.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. <template>
  2. <view class="container">
  3. <!-- 地图容器 - 必须设置明确高度 -->
  4. <view id="mapContainer" class="map-container"></view>
  5. <!-- 坐标显示区域 -->
  6. <view class="coordinates" v-if="showCoordinates">
  7. <view class="coordinate-item">
  8. <text class="coordinate-label">经度:</text>
  9. <text class="coordinate-value">{{ longitude }}</text>
  10. </view>
  11. <view class="coordinate-item">
  12. <text class="coordinate-label">纬度:</text>
  13. <text class="coordinate-value">{{ latitude }}</text>
  14. </view>
  15. <view class="coordinate-item">
  16. <text class="coordinate-label">地址:</text>
  17. <text class="coordinate-value">{{ address }}</text>
  18. </view>
  19. </view>
  20. </view>
  21. </template>
  22. <script>
  23. export default {
  24. props: {
  25. initialPosition: {
  26. type: Object,
  27. default: () => ({
  28. lnglat: null,
  29. address: ''
  30. })
  31. },
  32. allowSelection: {
  33. type: Boolean,
  34. default: true
  35. },
  36. showCoordinates: {
  37. type: Boolean,
  38. default: true
  39. }
  40. },
  41. data() {
  42. return {
  43. map: null,
  44. geocoder: null,
  45. tk: '92c4ddd919f4e41a30e765680e3a29b7', // 替换为实际密钥
  46. longitude: '未选择',
  47. latitude: '未选择',
  48. address: '未获取',
  49. markers: [],
  50. labels: [], // 存储文本标签
  51. mapInitialized: false
  52. };
  53. },
  54. watch: {
  55. initialPosition: {
  56. immediate: true,
  57. handler(newVal) {
  58. if (this.mapInitialized && newVal && this.isValidPosition(newVal)) {
  59. this.processPosition(newVal);
  60. }
  61. }
  62. },
  63. allowSelection(newVal) {
  64. if (this.map) {
  65. if (newVal) {
  66. this.enableMapSelection();
  67. } else {
  68. this.disableMapSelection();
  69. }
  70. }
  71. }
  72. },
  73. mounted() {
  74. // 确保在H5环境下运行
  75. // #ifdef H5
  76. this.$nextTick(() => {
  77. setTimeout(() => {
  78. this.initMap();
  79. }, 500);
  80. });
  81. // #endif
  82. // 非H5环境提示
  83. // #ifndef H5
  84. console.warn("天地图组件仅支持H5环境");
  85. uni.showToast({
  86. title: '地图功能仅在H5环境可用',
  87. icon: 'none'
  88. });
  89. // #endif
  90. },
  91. methods: {
  92. // 验证坐标是否有效
  93. isValidPosition(position) {
  94. const lng = position.lng ||
  95. (position.lnglat && (position.lnglat.lng || position.lnglat.longitude));
  96. const lat = position.lat ||
  97. (position.lnglat && (position.lnglat.lat || position.lnglat.latitude));
  98. return lng && lat && !isNaN(parseFloat(lng)) && !isNaN(parseFloat(lat));
  99. },
  100. // 处理坐标数据
  101. processPosition(position) {
  102. const lng = position.lng ||
  103. (position.lnglat && (position.lnglat.lng || position.lnglat.longitude));
  104. const lat = position.lat ||
  105. (position.lnglat && (position.lnglat.lat || position.lnglat.latitude));
  106. if (lng && lat) {
  107. this.setMapCenter(parseFloat(lng), parseFloat(lat));
  108. this.reverseGeocode(parseFloat(lng), parseFloat(lat));
  109. }
  110. },
  111. initMap() {
  112. if (typeof T === 'undefined') {
  113. this.loadTiandituAPI().then(() => {
  114. this.createMap();
  115. }).catch(err => {
  116. console.error("天地图加载失败:", err);
  117. uni.showToast({
  118. title: '地图加载失败',
  119. icon: 'none'
  120. });
  121. });
  122. } else {
  123. this.createMap();
  124. }
  125. },
  126. loadTiandituAPI() {
  127. return new Promise((resolve, reject) => {
  128. if (window.T) return resolve();
  129. const script = document.createElement('script');
  130. script.src = `https://api.tianditu.gov.cn/api?v=4.0&tk=${this.tk}`;
  131. script.onload = resolve;
  132. script.onerror = () => reject(new Error('天地图脚本加载失败'));
  133. document.head.appendChild(script);
  134. });
  135. },
  136. createMap() {
  137. try {
  138. // 创建地图实例
  139. this.map = new T.Map('mapContainer');
  140. // 设置默认中心点(太原)
  141. let centerLng = 112.55;
  142. let centerLat = 37.87;
  143. let name = ''
  144. // 使用初始坐标(如果存在)
  145. if (this.initialPosition && this.isValidPosition(this.initialPosition)) {
  146. const lng = this.initialPosition.lng ||
  147. (this.initialPosition.lnglat && (this.initialPosition.lnglat.lng || this.initialPosition.lnglat
  148. .longitude));
  149. const lat = this.initialPosition.lat ||
  150. (this.initialPosition.lnglat && (this.initialPosition.lnglat.lat || this.initialPosition.lnglat.latitude));
  151. centerLng = parseFloat(lng);
  152. centerLat = parseFloat(lat);
  153. name = this.initialPosition.address
  154. // 更新坐标显示
  155. this.longitude = centerLng.toFixed(6);
  156. this.latitude = centerLat.toFixed(6);
  157. this.address = name;
  158. }
  159. // 设置地图中心和缩放级别
  160. this.map.centerAndZoom(new T.LngLat(centerLng, centerLat), 12);
  161. // 创建逆地理编码对象
  162. this.geocoder = new T.Geocoder();
  163. // 添加正确的地图图层(使用经纬度投影)
  164. const vecLayer = new T.TileLayer(
  165. `https://t{s}.tianditu.gov.cn/vec_c/wmts?tk=${this.tk}`, {
  166. subdomains: ['0', '1', '2', '3', '4', '5', '6', '7']
  167. }
  168. );
  169. const cvaLayer = new T.TileLayer(
  170. `https://t{s}.tianditu.gov.cn/cva_c/wmts?tk=${this.tk}`, {
  171. subdomains: ['0', '1', '2', '3', '4', '5', '6', '7']
  172. }
  173. );
  174. this.map.addLayer(vecLayer);
  175. this.map.addLayer(cvaLayer);
  176. // 添加缩放控件
  177. this.map.addControl(new T.Control.Zoom());
  178. // 启用选点功能
  179. if (this.allowSelection) this.enableMapSelection();
  180. // 添加初始标记
  181. if (this.initialPosition && this.isValidPosition(this.initialPosition)) {
  182. const lng = this.initialPosition.lng ||
  183. (this.initialPosition.lnglat && (this.initialPosition.lnglat.lng || this.initialPosition.lnglat
  184. .longitude));
  185. const lat = this.initialPosition.lat ||
  186. (this.initialPosition.lnglat && (this.initialPosition.lnglat.lat || this.initialPosition.lnglat.latitude));
  187. this.addMarker(parseFloat(lng), parseFloat(lat));
  188. this.reverseGeocode(parseFloat(lng), parseFloat(lat));
  189. }
  190. this.mapInitialized = true;
  191. } catch (e) {
  192. console.error('地图初始化错误:', e);
  193. }
  194. },
  195. // 启用地图选点
  196. enableMapSelection() {
  197. this.map.on('click', this.handleMapClick);
  198. },
  199. // 禁用地图选点
  200. disableMapSelection() {
  201. this.map.off('click', this.handleMapClick);
  202. },
  203. // 设置地图中心点并更新坐标显示
  204. setMapCenter(lng, lat) {
  205. this.longitude = parseFloat(lng).toFixed(6);
  206. this.latitude = parseFloat(lat).toFixed(6);
  207. // 清除现有标记
  208. this.clearMarkers();
  209. // 添加新标记
  210. this.addMarker(lng, lat);
  211. // 移动地图视图
  212. this.map.panTo(new T.LngLat(lng, lat));
  213. // 通知父组件
  214. this.notifyParent();
  215. },
  216. // 逆地理编码
  217. reverseGeocode(lng, lat) {
  218. if (!this.geocoder) return;
  219. this.address = '正在查询...';
  220. const point = new T.LngLat(lng, lat);
  221. this.geocoder.getLocation(point, (result) => {
  222. if (result.getStatus() === 0) {
  223. this.address = result.getAddress();
  224. } else {
  225. this.address = '查询失败';
  226. }
  227. // 更新标签内容
  228. this.updateLabelContent();
  229. this.notifyParent();
  230. });
  231. },
  232. // 地图点击事件
  233. handleMapClick(e) {
  234. if (!this.allowSelection) return;
  235. this.setMapCenter(e.lnglat.lng, e.lnglat.lat);
  236. this.reverseGeocode(e.lnglat.lng, e.lnglat.lat);
  237. },
  238. // 通知父组件位置信息
  239. notifyParent() {
  240. this.$emit('update-location', {
  241. longitude: this.longitude,
  242. latitude: this.latitude,
  243. address: this.address
  244. });
  245. },
  246. // 添加标记
  247. addMarker(lng, lat) {
  248. try {
  249. const point = new T.LngLat(lng, lat);
  250. // 添加标记
  251. const marker = new T.Marker(point);
  252. this.map.addOverLay(marker);
  253. this.markers.push(marker);
  254. // 添加文本标签
  255. this.addLabel(lng, lat, this.address);
  256. console.log('标记添加成功:', lng, lat);
  257. } catch (error) {
  258. console.error('添加标记失败:', error);
  259. }
  260. },
  261. // 添加文本标签
  262. addLabel(lng, lat, content) {
  263. try {
  264. const point = new T.LngLat(lng, lat);
  265. // 创建文本标签
  266. const label = new T.Label({
  267. text: content || '未知地址',
  268. position: point,
  269. offset: new T.Point(-50, -40) // 调整标签位置使其在标记点上方
  270. });
  271. // 设置标签样式
  272. // label.setStyle({
  273. // color: '#333',
  274. // backgroundColor: 'rgba(255, 255, 255, 0.9)',
  275. // border: '1px solid #ddd',
  276. // borderRadius: '4px',
  277. // padding: '5px 10px',
  278. // fontSize: '12px',
  279. // fontWeight: 'bold',
  280. // boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
  281. // maxWidth: '150px',
  282. // textAlign: 'center'
  283. // });
  284. this.map.addOverLay(label);
  285. // this.labels.push(label);
  286. } catch (error) {
  287. console.error('添加标签失败:', error);
  288. }
  289. },
  290. // 更新标签内容
  291. updateLabelContent() {
  292. if (this.labels.length > 0) {
  293. const lastLabel = this.labels[this.labels.length - 1];
  294. lastLabel.setText(this.address);
  295. }
  296. },
  297. // 清除标记和标签
  298. clearMarkers() {
  299. // 清除标记
  300. this.markers.forEach(marker => {
  301. try {
  302. this.map.removeOverLay(marker);
  303. } catch (error) {
  304. console.error('移除标记失败:', error);
  305. }
  306. });
  307. this.markers = [];
  308. // 清除标签
  309. this.labels.forEach(label => {
  310. try {
  311. this.map.removeOverLay(label);
  312. } catch (error) {
  313. console.error('移除标签失败:', error);
  314. }
  315. });
  316. this.labels = [];
  317. }
  318. }
  319. };
  320. </script>
  321. <style scoped>
  322. /* 容器样式 */
  323. .container {
  324. width: 100%;
  325. display: flex;
  326. flex-direction: column;
  327. position: relative; /* 为绝对定位的信息窗口提供参考 */
  328. z-index: 0;
  329. }
  330. /* 地图容器 - 关键:必须设置高度 */
  331. .map-container {
  332. width: 100%;
  333. height: 300rpx;
  334. /* 使用rpx单位适应不同屏幕 */
  335. border: 1px solid #eee;
  336. background-color: #f8f8f8;
  337. }
  338. /* 坐标显示区域 */
  339. .coordinates {
  340. margin-top: 20rpx;
  341. padding: 20rpx;
  342. background-color: #f8f9fa;
  343. border-radius: 8rpx;
  344. }
  345. .coordinate-item {
  346. display: flex;
  347. margin: 15rpx 0;
  348. padding: 15rpx 0;
  349. border-bottom: 1rpx dashed #dee2e6;
  350. }
  351. .coordinate-item:last-child {
  352. border-bottom: none;
  353. }
  354. .coordinate-label {
  355. font-weight: bold;
  356. color: #495057;
  357. min-width: 120rpx;
  358. }
  359. .coordinate-value {
  360. color: #333333;
  361. font-weight: 500;
  362. flex: 1;
  363. }
  364. /* 天地图控件层级调整 */
  365. ::v-deep .tdt-bottom {
  366. z-index: 0;
  367. }
  368. ::v-deep .tdt-top {
  369. z-index: 0;
  370. }
  371. ::v-deep .tdt-bottom, .tdt-control {
  372. z-index: 0;
  373. }
  374. </style>