vue3: tmap (腾讯地图)using 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 geovindu@163.com, 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>  

  

<!--
 *                   ___====-_  _-====___
 *             _--^^^#####//      \\#####^^^--_
 *          _-^##########// (    ) \\##########^-_
 *         -############//  |\^^/|  \\############-
 *       _/############//   (@::@)   \############\_
 *      /#############((     \\//     ))#############\
 *     -###############\\    (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 geovindu@163.com, 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>

  

/*
 * 
 *    ┏┓   ┏┓
 *  ┏┛┻━━━┛┻┓
 *  ┃       ┃
 *  ┃   ━   ┃
 *  ┃ >   < ┃
 *  ┃       ┃
 *  ┃... ⌒ ... ┃
 *  ┃       ┃
 *  ┗━┓   ┏━┛
 *      ┃   ┃ 
 *      ┃   ┃
 *      ┃   ┃
 *      ┃   ┃  神兽保佑
 *      ┃   ┃  代码无bug  
 *      ┃   ┃
 *      ┃   ┗━━━┓
 *      ┃       ┣┓
 *      ┃       ┏┛
 *      ┗┓┓┏━┳┓┏┛
 *        ┃┫┫ ┃┫┫
 *        ┗┻┛ ┗┻┛
 * 
 * @Author: geovindu
 * @Date: 2025-05-26 12:03:18
 * @LastEditors: geovindu
 * @LastEditTime: 2025-05-29 17:44:28
 * @FilePath: \vue\vuepdfpreview\src\router\index.ts
 * @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 geovindu@163.com, All Rights Reserved. 
 */


import { createRouter, createWebHistory} from 'vue-router';
import App from '../App.vue';
// 正确导入路由类型
import type { RouteRecordRaw } from 'vue-router';
import pdfview from '../viewer/pdfview.vue';
import pdfviewjs from '../viewer/pdfviewjs.vue';
import pdfviewprev from '../viewer/pdfprev.vue';
import tmap from '../viewer/tmap.vue';

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: App
  },
  {
    path: '/pdf',
    name: 'pdf',
    component: pdfview
  },
  {
    path: '/pdfjs',
    name: 'pdfjs',
    component: pdfviewjs
  }
  ,
  {
    path: '/pdfprev',
    name: 'pdfprev',
    component: pdfviewprev
  },
  {
    path: '/tmap',
    name: 'tmap',
    component: tmap
  }
  // 添加其他路由
];
 
const router = createRouter({
  history: createWebHistory(),
  routes
});
 
export default router;

  

<!--
 *                   江城子 . 程序员之歌
 * 
 *               十年生死两茫茫,写程序,到天亮。
 *                   千行代码,Bug何处藏。
 *               纵使上线又怎样,朝令改,夕断肠。
 * 
 *               领导每天新想法,天天改,日日忙。
 *                   相顾无言,惟有泪千行。
 *               每晚灯火阑珊处,夜难寐,加班狂。
 * 
 * 
 * @Author: geovindu
 * @Date: 2025-05-09 10:54:24
 * @LastEditors: geovindu
 * @LastEditTime: 2025-05-26 14:20:41
 * @FilePath: \vue\vuepdfpreview\src\App.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 geovindu@163.com, All Rights Reserved. 
 -->

<!--
 *                                |~~~~~~~|
 *                                |       |
 *                                |       |
 *                                |       |
 *                                |       |
 *                                |       |
 *     |~.\\\_\~~~~~~~~~~~~~~xx~~~         ~~~~~~~~~~~~~~~~~~~~~/_//;~|
 *     |  \  o \_         ,XXXXX),                         _..-~ o /  |
 *     |    ~~\  ~-.     XXXXX`)))),                 _.--~~   .-~~~   |
 *      ~~~~~~~`\   ~\~~~XXX' _/ ';))     |~~~~~~..-~     _.-~ ~~~~~~~
 *               `\   ~~--`_\~\, ;;;\)__.---.~~~      _.-~
 *                 ~-.       `:;;/;; \          _..-~~
 *                    ~-._      `''        /-~-~
 *                        `\              /  /
 *                          |         ,   | |
 *                           |  '        /  |
 *                            \/;          |
 *                             ;;          |
 *                             `;   .       |
 *                             |~~~-----.....|
 *                            | \             \
 *                           | /\~~--...__    |
 *                           (|  `\       __-\|
 *                           ||    \_   /~    |
 *                           |)     \~-'      |
 *                            |      | \      '
 *                            |      |  \    :
 *                             \     |  |    |
 *                              |    )  (    )
 *                               \  /;  /\  |
 *                               |    |/   |
 *                               |    |   |
 *                                \  .'  ||
 *                                |  |  | |
 *                                (  | |  |
 *                                |   \ \ |
 *                                || o `.)|
 *                                |`\\) |
 *                                |       |
 *                                |       |
 * 
 * @Author: geovindu
 * @Date: 2025-05-09 10:54:24
 * @LastEditors: geovindu
 * @LastEditTime: 2025-05-09 14:55:13
 * @FilePath: \vue\vuepdfpreview\src\App.vue
 * @Description: geovindu
 * @lib,packpage: 
 * npm install vue-pdf-embed
 * npm install vue3-pdfjs
 *  npm install pdfjs-dist@2.16.105 
 * @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 geovindu@163.com, All Rights Reserved. 
 -->


<template>
   <header>
    <div class="wrapper">
<div class="pdf-container">
<nav>
  <RouterLink to="/">home</RouterLink> 
  <RouterLink to="/pdf">pdf</RouterLink> 
  <RouterLink to="/pdfjs">pdfjs</RouterLink>  
  <RouterLink to="/pdfprev">pdfprev</RouterLink>  
  <RouterLink to="/tmap">tmap</RouterLink>    
</nav>

<RouterView />
 </div>
</div>
</header>
 </template>
 <script setup lang="ts"> 

 import { RouterLink, RouterView } from 'vue-router'
 
 //import HelloWorld from './components/HelloWorld.vue'

 //import PDFView from './components/vuepdfjs.vue' // 可以
 //import PDFView from './components/pdfPreview.vue' // 可以
 //import PDFView from "./components/pdfjs.vue"   //可以
 //import pdfUrl from "./pdfs/01.pdf"
 //const pdfUrl = "./pdfs/09.pdf"
 </script>
 
<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

  

/*
 *  ┌───┐   ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┐
 *  │Esc│   │ F1│ F2│ F3│ F4│ │ F5│ F6│ F7│ F8│ │ F9│F10│F11│F12│ │P/S│S L│P/B│  ┌┐    ┌┐    ┌┐
 *  └───┘   └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┘  └┘    └┘    └┘
 *  ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐ ┌───┬───┬───┐ ┌───┬───┬───┬───┐
 *  │~ `│! 1│@ 2│# 3│$ 4│% 5│^ 6│& 7│* 8│( 9│) 0│_ -│+ =│ BacSp │ │Ins│Hom│PUp│ │N L│ / │ * │ - │
 *  ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ ├───┼───┼───┤ ├───┼───┼───┼───┤
 *  │ Tab │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │{ [│} ]│ | \ │ │Del│End│PDn│ │ 7 │ 8 │ 9 │   │
 *  ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ └───┴───┴───┘ ├───┼───┼───┤ + │
 *  │ Caps │ A │ S │ D │ F │ G │ H │ J │ K │ L │: ;│" '│ Enter  │               │ 4 │ 5 │ 6 │   │
 *  ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤     ┌───┐     ├───┼───┼───┼───┤
 *  │ Shift  │ Z │ X │ C │ V │ B │ N │ M │< ,│> .│? /│  Shift   │     │ ↑ │     │ 1 │ 2 │ 3 │   │
 *  ├─────┬──┴─┬─┴──┬┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ ┌───┼───┼───┐ ├───┴───┼───┤ E││
 *  │ Ctrl│    │Alt │         Space         │ Alt│    │    │Ctrl│ │ ← │ ↓ │ → │ │   0   │ . │←─┘│
 *  └─────┴────┴────┴───────────────────────┴────┴────┴────┴────┘ └───┴───┴───┘ └───────┴───┴───┘
 * 
 * @Author: geovindu
 * @Date: 2025-05-12 09:52:10
 * @LastEditors: geovindu
 * @LastEditTime: 2025-05-12 17:12:18
 * @FilePath: \vue\vuepdfpreview\src\main.ts
 * @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 geovindu@163.com, All Rights Reserved. 
*/

import { createApp } from 'vue'
import router from './router'; // 导入路由配置
import VuePdfEmbed from "vue-pdf-embed"
import { createLoadingTask } from "vue3-pdfjs"
import './style.css'
import App from './App.vue'

createApp(App)
.use(router) // 使用路由
.mount('#app')

  

 

 

https://lbs.qq.com/webDemoCenter/glAPI/glMarker/markerAdd
https://lbs.qq.com/webDemoCenter/glAPI/glServiceLib/ipLocation

地图api地址:
v1:
https://wemap.qq.com/Vis/JavascriptAPI/APIDoc/map
v2:
https://lbs.qq.com/webApi/javascriptV2/jsGuide/jsOverview
同样是v2的:
https://lbs.qq.com/javascript_v2/doc/map.html

posted @ 2025-05-29 18:05  ®Geovin Du Dream Park™  阅读(78)  评论(0)    收藏  举报