tdtuMap.vue 21 KB

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