tdtuMap.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. <template>
  2. <view class="map_container">
  3. <view id="MapContainer" style="width: 100%;height: 300px;position: relative;"></view>
  4. </view>
  5. </template>
  6. <script>
  7. export default {
  8. name: "TdMap",
  9. props: {
  10. allowSelection: {
  11. type: Boolean,
  12. default: false
  13. },
  14. showCoordinates: {
  15. type: Boolean,
  16. default: false
  17. },
  18. mapKey: {
  19. type: [String, Number],
  20. default: 0
  21. }
  22. },
  23. mounted() {
  24. console.log('【地图组件挂载】开始初始化');
  25. },
  26. beforeDestroy() {
  27. // 清理资源
  28. this.$emit('destroy-map');
  29. }
  30. };
  31. </script>
  32. <script module="randerJSMap" lang="renderjs">
  33. export default {
  34. data() {
  35. return {
  36. mapRef: null,
  37. markerRef: null,
  38. labelRef: null,
  39. mapLoaded: false,
  40. initPromise: null,
  41. lastDataHash: '',
  42. storageCheckTimer: null,
  43. lastStorageDataStr: ''
  44. };
  45. },
  46. methods: {
  47. /**
  48. * 获取地图容器
  49. */
  50. getMapContainer() {
  51. return new Promise((resolve) => {
  52. let container = document.getElementById('MapContainer');
  53. if (container) {
  54. console.log('【容器获取】成功', container);
  55. return resolve(container);
  56. }
  57. // APP端兜底
  58. uni.createSelectorQuery().in(this.$ownerInstance)
  59. .select('#MapContainer')
  60. .boundingClientRect((rect) => {
  61. if (rect) {
  62. container = document.querySelector('#MapContainer');
  63. resolve(container);
  64. } else {
  65. resolve(null);
  66. }
  67. }).exec();
  68. });
  69. },
  70. /**
  71. * 直接从storage获取地图数据
  72. */
  73. getMapDataFromStorage() {
  74. try {
  75. console.log('【RenderJS开始获取storage数据】');
  76. const storageData = uni.getStorageSync('address');
  77. console.log('【RenderJS从storage获取的原始数据】', storageData);
  78. console.log('【RenderJS数据类型】', typeof storageData);
  79. let parsedData = null;
  80. if (storageData) {
  81. // 如果是字符串,尝试解析为JSON
  82. if (typeof storageData === 'string') {
  83. try {
  84. parsedData = JSON.parse(storageData);
  85. console.log('【RenderJS解析后的数据】', parsedData);
  86. } catch (parseError) {
  87. console.error('【RenderJS JSON解析失败】', parseError);
  88. return this.getDefaultMapData();
  89. }
  90. } else {
  91. parsedData = storageData;
  92. }
  93. // 验证数据格式
  94. const isValidData = parsedData &&
  95. typeof parsedData === 'object' &&
  96. (typeof parsedData.lng !== 'undefined' || parsedData.longitude) &&
  97. (typeof parsedData.lat !== 'undefined' || parsedData.latitude);
  98. console.log('【RenderJS数据验证结果】', isValidData);
  99. if (isValidData) {
  100. // 支持多种字段名
  101. const lng = parsedData.lng || parsedData.longitude || 112.55;
  102. const lat = parsedData.lat || parsedData.latitude || 37.87;
  103. const address = parsedData.address || parsedData.name || parsedData.location || '未知位置';
  104. return {
  105. lng: Number(lng),
  106. lat: Number(lat),
  107. address: address,
  108. isValid: true
  109. };
  110. } else {
  111. console.warn('【RenderJS】storage数据格式无效,使用默认值');
  112. console.warn('【RenderJS无效数据详情】', parsedData);
  113. return this.getDefaultMapData();
  114. }
  115. } else {
  116. console.log('【RenderJS】storage中无address数据,使用默认值');
  117. return this.getDefaultMapData();
  118. }
  119. } catch (error) {
  120. console.error('【RenderJS】从storage获取数据失败', error);
  121. return this.getDefaultMapData();
  122. }
  123. },
  124. /**
  125. * 获取默认地图数据
  126. */
  127. getDefaultMapData() {
  128. return {
  129. lng: 112.55,
  130. lat: 37.87,
  131. address: '山西省太原市',
  132. isValid: false
  133. };
  134. },
  135. /**
  136. * 创建标记和标签
  137. */
  138. createMarkerWithLabel(lngLat, address) {
  139. try {
  140. const T = window.T;
  141. if (!this.mapLoaded || !lngLat || !this.mapRef) {
  142. console.error('【标点失败】前置条件不足');
  143. return;
  144. }
  145. // 1. 移除旧标记
  146. this.clearMarkers();
  147. // 2. 创建自定义图标(红色标记)
  148. const icon = new T.Icon({
  149. iconUrl: this.generateMarkerIcon('#ff0000'),
  150. iconSize: new T.Point(30, 30),
  151. iconAnchor: new T.Point(15, 30)
  152. });
  153. // 3. 创建标记(使用T.Marker类)
  154. this.markerRef = new T.Marker(lngLat, {
  155. icon: icon,
  156. title: address || '未知位置',
  157. draggable: false
  158. });
  159. // 4. 创建文本标签
  160. this.labelRef = new T.Label({
  161. text: address || '未知位置',
  162. position: lngLat,
  163. style: {
  164. color: '#333333',
  165. backgroundColor: 'rgba(255, 255, 255, 0.9)',
  166. borderColor: '#ff0000',
  167. borderWidth: 1,
  168. borderRadius: 4,
  169. fontSize: '14px',
  170. fontWeight: '500',
  171. padding: '6px 10px'
  172. },
  173. offset: new T.Point(0, -40) // 向上偏移
  174. });
  175. // 5. 添加到地图
  176. this.mapRef.addOverLay(this.markerRef);
  177. this.mapRef.addOverLay(this.labelRef);
  178. console.log('【创建标记】成功', {
  179. lng: lngLat.getLng(),
  180. lat: lngLat.getLat(),
  181. address
  182. });
  183. // 6. 调整地图视图
  184. this.mapRef.setViewport([lngLat]);
  185. // 7. 标记点击事件
  186. this.markerRef.addEventListener('click', (e) => {
  187. const coord = {
  188. lng: lngLat.getLng().toFixed(6),
  189. lat: lngLat.getLat().toFixed(6)
  190. };
  191. const message = `地址:${address}\n坐标:${coord.lng}, ${coord.lat}`;
  192. uni.showModal({
  193. title: '位置信息',
  194. content: message,
  195. showCancel: false,
  196. confirmText: '确定'
  197. });
  198. });
  199. // 8. 标签点击事件
  200. this.labelRef.addEventListener('click', (e) => {
  201. e.stopPropagation();
  202. uni.showToast({
  203. title: `地址:${address}`,
  204. icon: 'none',
  205. duration: 3000
  206. });
  207. });
  208. } catch (error) {
  209. console.error('【创建标记异常】', error);
  210. // 备用方案:使用默认标记
  211. this.createDefaultMarker(lngLat, address);
  212. }
  213. },
  214. /**
  215. * 生成标记图标
  216. */
  217. generateMarkerIcon(color) {
  218. const size = 30;
  219. const canvas = document.createElement('canvas');
  220. canvas.width = size;
  221. canvas.height = size;
  222. const ctx = canvas.getContext('2d');
  223. // 外圆(白色边框)
  224. ctx.beginPath();
  225. ctx.arc(size / 2, size / 2, size / 2 - 1, 0, Math.PI * 2);
  226. ctx.fillStyle = '#ffffff';
  227. ctx.fill();
  228. // 内圆(红色)
  229. ctx.beginPath();
  230. ctx.arc(size / 2, size / 2, size / 2 - 4, 0, Math.PI * 2);
  231. ctx.fillStyle = color;
  232. ctx.fill();
  233. return canvas.toDataURL();
  234. },
  235. /**
  236. * 创建默认标记(备用方案)
  237. */
  238. createDefaultMarker(lngLat, address) {
  239. try {
  240. const T = window.T;
  241. // 清除旧标记
  242. this.clearMarkers();
  243. // 创建默认标记
  244. this.markerRef = new T.Marker(lngLat, {
  245. title: address || '未知位置'
  246. });
  247. // 设置默认图标
  248. this.markerRef.setIcon(new T.Icon.Default());
  249. // 添加到地图
  250. this.mapRef.addOverLay(this.markerRef);
  251. // 标记点击事件
  252. this.markerRef.addEventListener('click', (e) => {
  253. const coord = {
  254. lng: lngLat.getLng().toFixed(6),
  255. lat: lngLat.getLat().toFixed(6)
  256. };
  257. const message = `地址:${address}\n坐标:${coord.lng}, ${coord.lat}`;
  258. uni.showModal({
  259. title: '位置信息',
  260. content: message,
  261. showCancel: false,
  262. confirmText: '确定'
  263. });
  264. });
  265. console.log('【创建默认标记】成功');
  266. } catch (error) {
  267. console.error('【创建默认标记失败】', error);
  268. }
  269. },
  270. /**
  271. * 清除所有标记
  272. */
  273. clearMarkers() {
  274. try {
  275. if (this.markerRef && this.mapRef) {
  276. this.mapRef.removeOverLay(this.markerRef);
  277. this.markerRef = null;
  278. }
  279. if (this.labelRef && this.mapRef) {
  280. this.mapRef.removeOverLay(this.labelRef);
  281. this.labelRef = null;
  282. }
  283. } catch (error) {
  284. console.warn('清除标记时出错', error);
  285. }
  286. },
  287. /**
  288. * 初始化地图
  289. */
  290. async initMap() {
  291. try {
  292. console.log('【初始化地图】开始');
  293. const T = window.T;
  294. if (!T || !T.Map) {
  295. throw new Error('天地图API未正确加载');
  296. }
  297. // 获取容器
  298. const container = await this.getMapContainer();
  299. if (!container) {
  300. throw new Error('地图容器获取失败');
  301. }
  302. // 直接从storage获取位置数据
  303. const mapData = this.getMapDataFromStorage();
  304. console.log('【初始化地图使用的数据】', mapData);
  305. const center = new T.LngLat(Number(mapData.lng), Number(mapData.lat));
  306. console.log('【地图中心点】', center);
  307. // 创建地图实例
  308. this.mapRef = new T.Map(container);
  309. this.mapRef.centerAndZoom(center, 16);
  310. this.mapRef.enableScrollWheelZoom();
  311. this.mapLoaded = true;
  312. console.log('【初始化地图】成功');
  313. // 创建标记和标签
  314. setTimeout(() => {
  315. this.createMarkerWithLabel(center, mapData.address);
  316. }, 500);
  317. } catch (error) {
  318. console.error('【初始化失败】', error);
  319. this.mapLoaded = false;
  320. uni.showToast({
  321. title: '地图初始化失败',
  322. icon: 'error',
  323. duration: 3000
  324. });
  325. }
  326. },
  327. /**
  328. * 确保API加载完成
  329. */
  330. async ensureAPILoaded() {
  331. if (window.T && window.T.Map && window.T.LngLat) {
  332. console.log('【API已加载】');
  333. return Promise.resolve();
  334. }
  335. return new Promise((resolve, reject) => {
  336. console.log('【开始加载API】');
  337. // 检查是否已经在加载中
  338. const existingScript = document.querySelector('script[src*="tianditu.gov.cn"]');
  339. if (existingScript) {
  340. console.log('【API已在加载中】');
  341. const checkInterval = setInterval(() => {
  342. if (window.T && window.T.Map && window.T.LngLat) {
  343. clearInterval(checkInterval);
  344. console.log('【API加载完成】');
  345. resolve();
  346. }
  347. }, 100);
  348. setTimeout(() => {
  349. clearInterval(checkInterval);
  350. reject(new Error('API加载超时'));
  351. }, 10000);
  352. return;
  353. }
  354. // 加载天地图4.0 API
  355. const script = document.createElement('script');
  356. script.src = 'https://api.tianditu.gov.cn/api?v=4.0&tk=9bcf63358817bfb878e8236de8bf1423';
  357. script.onload = () => {
  358. console.log('【API脚本加载完成】');
  359. const checkT = setInterval(() => {
  360. if (window.T && window.T.Map && window.T.LngLat) {
  361. clearInterval(checkT);
  362. console.log('【T对象初始化完成】');
  363. resolve();
  364. }
  365. }, 50);
  366. setTimeout(() => {
  367. clearInterval(checkT);
  368. reject(new Error('T对象初始化超时'));
  369. }, 5000);
  370. };
  371. script.onerror = (err) => {
  372. console.error('API加载失败', err);
  373. reject(new Error('API加载失败'));
  374. };
  375. document.head.appendChild(script);
  376. });
  377. },
  378. /**
  379. * 更新地图标记(从storage获取最新数据)
  380. */
  381. updateMapFromStorage() {
  382. if (!this.mapLoaded || !this.mapRef) {
  383. console.warn('【更新被跳过】地图未加载');
  384. return;
  385. }
  386. // 获取最新数据
  387. const mapData = this.getMapDataFromStorage();
  388. console.log('【更新地图使用的数据】', mapData);
  389. // 生成数据哈希,避免重复更新
  390. const dataHash = JSON.stringify(mapData);
  391. if (dataHash === this.lastDataHash) {
  392. console.log('【数据未变化,跳过更新】');
  393. return;
  394. }
  395. this.lastDataHash = dataHash;
  396. // 验证坐标有效性
  397. const isValidLng = typeof mapData.lng !== 'undefined' && !isNaN(Number(mapData.lng)) && mapData.lng !== null;
  398. const isValidLat = typeof mapData.lat !== 'undefined' && !isNaN(Number(mapData.lat)) && mapData.lat !== null;
  399. console.log('【坐标验证】', {
  400. isValidLng,
  401. isValidLat,
  402. lng: mapData.lng,
  403. lat: mapData.lat
  404. });
  405. if (!isValidLng || !isValidLat) {
  406. console.warn('无效坐标,使用默认值');
  407. const defaultLngLat = new window.T.LngLat(112.55, 37.87);
  408. this.createMarkerWithLabel(defaultLngLat, mapData.address || '默认位置');
  409. return;
  410. }
  411. // 创建新标记
  412. const targetLngLat = new window.T.LngLat(Number(mapData.lng), Number(mapData.lat));
  413. // 移动地图中心
  414. this.mapRef.panTo(targetLngLat);
  415. setTimeout(() => {
  416. this.createMarkerWithLabel(targetLngLat, mapData.address || '未知位置');
  417. }, 300);
  418. },
  419. /**
  420. * 设置storage监听
  421. */
  422. setupStorageListener() {
  423. // 监听uni-app自定义事件
  424. uni.$on('storageChange', (data) => {
  425. console.log('【RenderJS收到storage变化事件】', data);
  426. if (data && data.key === 'address') {
  427. this.updateMapFromStorage();
  428. }
  429. });
  430. // 定时检查(兼容性更好)
  431. this.storageCheckTimer = setInterval(() => {
  432. this.checkStorageUpdate();
  433. }, 2000);
  434. },
  435. /**
  436. * 检查storage是否更新
  437. */
  438. checkStorageUpdate() {
  439. try {
  440. const currentData = uni.getStorageSync('address');
  441. const currentStr = JSON.stringify(currentData);
  442. // 首次检查时初始化lastStorageData
  443. if (!this.lastStorageDataStr) {
  444. this.lastStorageDataStr = currentStr;
  445. return;
  446. }
  447. if (currentStr !== this.lastStorageDataStr) {
  448. console.log('【storage数据已更新】', currentData);
  449. this.lastStorageDataStr = currentStr;
  450. this.updateMapFromStorage();
  451. }
  452. } catch (error) {
  453. console.error('检查storage更新失败', error);
  454. }
  455. }
  456. },
  457. mounted() {
  458. console.log('【RenderJS】开始加载');
  459. // 延迟初始化
  460. setTimeout(async () => {
  461. try {
  462. await this.ensureAPILoaded();
  463. await this.initMap();
  464. // 初始化成功后设置监听
  465. this.setupStorageListener();
  466. // 保存初始数据用于比较
  467. try {
  468. this.lastStorageDataStr = JSON.stringify(uni.getStorageSync('address'));
  469. } catch (error) {
  470. this.lastStorageDataStr = '';
  471. }
  472. } catch (error) {
  473. console.error('【初始化失败】', error);
  474. uni.showToast({
  475. title: '地图加载失败',
  476. icon: 'error',
  477. duration: 3000
  478. });
  479. }
  480. }, 500);
  481. },
  482. beforeDestroy() {
  483. // 清理资源
  484. if (this.mapRef) {
  485. this.clearMarkers();
  486. this.mapRef = null;
  487. }
  488. // 清理定时器
  489. if (this.storageCheckTimer) {
  490. clearInterval(this.storageCheckTimer);
  491. this.storageCheckTimer = null;
  492. }
  493. // 移除事件监听
  494. uni.$off('storageChange');
  495. this.mapLoaded = false;
  496. console.log('【RenderJS】组件销毁');
  497. }
  498. };
  499. </script>
  500. <style scoped lang="scss">
  501. .map_container {
  502. position: relative;
  503. width: 100%;
  504. height: 300px;
  505. z-index: 1;
  506. overflow: hidden;
  507. background: #f0f0f0;
  508. border-radius: 8px;
  509. border: 1px solid #e0e0e0;
  510. }
  511. #MapContainer {
  512. position: relative;
  513. z-index: 9999;
  514. display: block;
  515. width: 100% !important;
  516. height: 100% !important;
  517. padding: 0 !important;
  518. margin: 0 !important;
  519. border: none !important;
  520. border-radius: 8px;
  521. }
  522. /* 自定义样式 */
  523. ::v-deep .tdt-label {
  524. cursor: pointer;
  525. transition: all 0.3s ease;
  526. &:hover {
  527. background-color: rgba(255, 255, 255, 1) !important;
  528. border-color: #ff4444 !important;
  529. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2) !important;
  530. }
  531. }
  532. ::v-deep .tdt-marker-icon {
  533. cursor: pointer;
  534. transition: transform 0.2s;
  535. &:hover {
  536. transform: scale(1.2);
  537. }
  538. }
  539. </style>