vue3: tmap (腾讯地图)using typescript

项目结构:

TypeScript 复制代码
<!--
 *                   ___====-_  _-====___
 *             _--^^^#####//      \\#####^^^--_
 *          _-^##########// (    ) \\##########^-_
 *         -############//  |\^^/|  \\############-
 *       _/############//   (@::@)   \############\_
 *      /#############((     \\//     ))#############\
 *     -###############\\    (oo)    //###############-
 *    -#################\\  / VV \  //#################-
 *   -###################\\/      \//###################-
 *  _#/|##########/\######(   /\   )######/\##########|\#_
 *  |/ |#/\#/\#/\/  \#/\##\  |  |  /##/\#/  \/\#/\#/\#| \|
 *  `  |/  V  V  `   V  \#\| |  | |/#/  V   '  V  V  \|  '
 *     `   `  `      `   / | |  | | \   '      '  '   '
 *                      (  | |  | |  )
 *                     __\ | |  | | /__
 *                    (vvv(VVV)(VVV)vvv)
 *
 *      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 *                神兽保佑            永无BUG
 *
 * @Author: geovindu
 * @Date: 2025-05-26 14:23:15
 * @LastEditors: geovindu
 * @LastEditTime: 2025-05-26 14:29:48
 * @FilePath: \vue\vuepdfpreview\src\viewer\tmap.vue
 * @Description: geovindu
 * @lib,packpage:
 *
 * @IDE: vscode
 * @jslib: node 20 vue.js 3.0
 * @OS: windows10
 * @database: mysql 8.0  sql server 2019 postgreSQL 16
 * Copyright (c) geovindu 2025 by [email protected], All Rights Reserved.
 -->
 
<template>
    <div id="app">
      <h1>深圳酒店地图(腾讯版)</h1>
      <div v-if="loading" class="loading-message">加载中...</div>
      <div v-else-if="error" class="error-message">
        加载失败: {{ error.message }}
      </div>
      <TencentMapMarker v-else :hotels="hotels" :ak="tencentMapAK" />
    </div>
  </template>
   
  <script lang="ts" setup>
  import { ref, onMounted } from 'vue';
  import TencentMapMarker from '../components/TencentMapMarker.vue';
   
  // 腾讯地图API密钥(需要替换为你自己的)
  const tencentMapAK = ref('your key');
  const hotels = ref<any[]>([]);
  const loading = ref(true);
  const error = ref<Error | null>(null);
   
  // 从JSON文件加载酒店数据
  const loadHotelData = async () => {
    try {
      const response = await fetch('hotels.json');
      if (!response.ok) {
        throw new Error(`HTTP错误! 状态码: ${response.status}`);
      }
      const data = await response.json();
      hotels.value = data;
      console.log('酒店数据加载成功:', data);
    } catch (err: any) {
      console.error('加载酒店数据失败:', err);
      error.value = err;
    } finally {
      loading.value = false;
    }
  };
   
  onMounted(() => {
    loadHotelData();
  });
  </script>
   
  <style>
  #app {
    font-family: Avenir, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
    margin-top: 60px;
    padding: 0 20px;
  }
   
  h1 {
    color: #333;
  }
   
  .loading-message, .error-message {
    padding: 20px;
    margin: 20px;
    background-color: #f5f5f5;
    border-radius: 8px;
  }
   
  .error-message {
    color: #f56c6c;
  }
  </style> 
TypeScript 复制代码
<!--
 *                   ___====-_  _-====___
 *             _--^^^#####//      \\#####^^^--_
 *          _-^##########// (    ) \\##########^-_
 *         -############//  |\^^/|  \\############-
 *       _/############//   (@::@)   \############\_
 *      /#############((     \\//     ))#############\
 *     -###############\\    (oo)    //###############-
 *    -#################\\  / VV \  //#################-
 *   -###################\\/      \//###################-
 *  _#/|##########/\######(   /\   )######/\##########|\#_
 *  |/ |#/\#/\#/\/  \#/\##\  |  |  /##/\#/  \/\#/\#/\#| \|
 *  `  |/  V  V  `   V  \#\| |  | |/#/  V   '  V  V  \|  '
 *     `   `  `      `   / | |  | | \   '      '  '   '
 *                      (  | |  | |  )
 *                     __\ | |  | | /__
 *                    (vvv(VVV)(VVV)vvv)
 *
 *      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 *                神兽保佑            永无BUG
 *
 * @Author: geovindu
 * @Date: 2025-05-26 14:23:15
 * @LastEditors: geovindu
 * @LastEditTime: 2025-05-29 17:38:46
 * @FilePath: \vue\vuepdfpreview\src\components\TencentMapMarker.vue
 * @Description: geovindu
 * @lib,packpage:
 *
 * @IDE: vscode
 * @jslib: node 20 vue.js 3.0
 * @OS: windows10
 * @database: mysql 8.0  sql server 2019 postgreSQL 16
 * Copyright (c) geovindu 2025 by [email protected], All Rights Reserved.
 -->
<template>
  <div class="map-container" ref="mapContainer" style="height: 600px;"></div>
</template>
 
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, watch, defineComponent } from 'vue';
 
interface Hotel {
  name: string;
  content: string;
  center: string;
  type: number;
  icon: string;
}
 
const props = defineProps<{
  hotels: Hotel[];
  ak: string;
}>();
 
const mapContainer = ref<HTMLElement | null>(null);
let map: TMap.Map | null = null;
let markerLayer: TMap.MultiMarker | null = null;
let infoWindow: TMap.InfoWindow | null = null;
let currentHotel = ref<Hotel | null>(null);
 
// 加载腾讯地图API
const loadTMapScript = (ak: string) => {
  return new Promise<void>((resolve, reject) => {
    if (window.TMap) {
      resolve();
      return;
    }
 
    const script = document.createElement('script');
    script.src = `https://map.qq.com/api/gljs?v=1.0&key=${ak}`;
    script.async = true;
    script.onload = () => {
      if (window.TMap) {
        resolve();
      } else {
        reject(new Error('腾讯地图API加载失败,但脚本已加载'));
      }
    };
    script.onerror = (err) => reject(new Error('加载腾讯地图API失败,请检查网络连接或API密钥'));
    document.head.appendChild(script);
  });
};
 
// 初始化地图
const initMap = async () => {
  if (!mapContainer.value || !props.ak) {
    console.error('地图容器或API密钥未设置');
    return;
  }
 
  try {
    await loadTMapScript(props.ak);
     
    map = new TMap.Map(mapContainer.value, {
      center: new TMap.LatLng(22.543099, 114.057868),
      zoom: 12
    });
     
    // 创建信息窗口(不关联map,在显示时动态关联)
    infoWindow = new TMap.InfoWindow({
      enableCustom: true,
      map: map,
      position: new TMap.LatLng(39.984104, 116.307503),
      offset: { y: -70, x: -5 }
    });
     
    initMarkerLayer();
    console.log('腾讯地图初始化完成');
  } catch (error: any) {
    console.error('地图初始化失败:', error.message);
  }
};
 
// 初始化标记图层
const initMarkerLayer = () => {
  if (!map) return;
   
  markerLayer = new TMap.MultiMarker({
    id: 'hotel-markers',
    map: map,
    styles: {
      'hotel-marker': new TMap.MarkerStyle({
        width: 32,
        height: 32,
        anchor: { x: 16, y: 32 },
        src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png'
      })
    },
    geometries: []
  });
   
  // 标记点击事件
  markerLayer.on('click', (event) => {
    const geometry = event.geometry;
    if (!geometry || !geometry.position || !(geometry.position instanceof TMap.LatLng)) {
      console.error('无效的标记位置:', geometry);
      return;
    }
     
    if (infoWindow) {
      //console.log('信息窗口是否打开:', infoWindow.isOpen());
      //infoWindow.open();
      // 更新当前酒店数据
      currentHotel.value = {
        name: geometry.properties?.name || '酒店信息',
        content: geometry.properties?.content || '暂无描述',
        center: '',
        type: 0,
        icon: ''
      };
       
      // 创建唯一ID的DOM容器
      const containerId = 'info-window-content-' + Date.now();
      const container = document.createElement('div');
      container.id = containerId;
      document.body.appendChild(container);
       
      // 设置信息窗口内容
      infoWindow.setContent(`
        <div class="info_card">
          <div class="title">
            <span class="title_name">${currentHotel.value.name}</span>
            <button class="close_btn" id="close-btn-${containerId}">×</button>
          </div>
          <div class="content">${currentHotel.value.content}</div>
        </div>
      `);
       
      // 使用 setTimeout 确保DOM已渲染
      /* */
      setTimeout(() => {
        // 添加关闭按钮事件监听
        const closeBtn = document.getElementById(`close-btn-${containerId}`);
        if (closeBtn) {
          closeBtn.addEventListener('click', () => {
            if (infoWindow) infoWindow.close();
          });
        }
        
        // 关联地图并显示
        infoWindow.setMap(map);
        infoWindow.setPosition(geometry.position);
        infoWindow.open();
      }, 0);
    }
  });
   
  updateMarkers();
};
 
// 更新标记
const updateMarkers = () => {
  if (!markerLayer || !props.hotels || props.hotels.length === 0) return;
   
  const geometries = props.hotels.map((hotel, index) => {
    try {
      const [lng, lat] = hotel.center.split(',').map(Number);
      return {
        id: `hotel-${index}`,
        styleId: 'hotel-marker',
        position: new TMap.LatLng(lat, lng),
        properties: {
          name: hotel.name,
          content: hotel.content,
          type: hotel.type
        }
      };
    } catch (e) {
      console.error(`酒店数据解析错误 (${hotel.name}):`, e);
      return null;
    }
  }).filter(Boolean) as TMap.MultiMarkerGeometry[];
   
  markerLayer.setGeometries(geometries);
};
 
onMounted(() => {
  initMap();
});
 
watch(() => props.hotels, () => {
  if (markerLayer) updateMarkers();
});
 
onUnmounted(() => {
  if (infoWindow) {
    infoWindow.close();
    infoWindow.setMap(null);
  }
  if (markerLayer) markerLayer.setMap(null);
  if (map) {
    map.destroy();
    map = null;
  }
  markerLayer = null;
  infoWindow = null;
});
</script>
 
<style scoped>
.map-container {
  width: 100%;
  height: 100%;
}
 
/* 信息窗口样式 */
.info_card {
  width: 240px;
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
  overflow: hidden;
  font-family: Arial, sans-serif;
}
 
.title {
  height: 36px;
  line-height: 36px;
  background-color: #f5f5f5;
  padding: 0 12px;
  position: relative;
}
 
.title_name {
  font-weight: bold;
  color: #333;
  font-size: 15px;
}
 
.close_btn {
  position: absolute;
  right: 10px;
  top: 8px;
  width: 20px;
  height: 20px;
  background: none;
  border: none;
  font-size: 18px;
  color: #999;
  cursor: pointer;
  padding: 0;
}
 
.close_btn:hover {
  color: #333;
}
 
.content {
  padding: 12px;
  font-size: 14px;
  color: #666;
  line-height: 1.5;
}
</style>

输出:

相关推荐
划水小将军10 分钟前
睡眠分期 html
前端·javascript·html
工业聚19 分钟前
AI 时代的前端成长之路(2025版)
前端·人工智能
m0_6948455728 分钟前
服务器如何配置防火墙管理端口访问?
linux·运维·服务器·前端
EndingCoder36 分钟前
React从基础入门到高级实战:React 高级主题 - 性能优化:深入探索与实践指南
前端·javascript·react.js·性能优化·前端框架
Sunny_lxm41 分钟前
在 Vue 2中使用 dhtmlxGantt 7.1.13组件,并解决使用时遇到的问题汇总.“dhtmlx-gantt“: “^7.1.13“,
前端·vue.js·甘特图·dhtmlxgantt
江城开朗的豌豆1 小时前
JavaScript篇:前端兼容性历险记:那些年我们踩过的浏览器坑
前端·javascript·面试
江城开朗的豌豆1 小时前
JavaScript篇:JS事件冒泡:别让点击事件‘传染’!
前端·javascript·面试
极客密码1 小时前
Cursor,2天,一个小程序!
前端·ai编程·cursor
不知几秋2 小时前
pikachu通关教程-CSRF XSS
前端·xss
lyz2468592 小时前
动态报表筛选多项时的优化处理
javascript·uni-app·view design