| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416 |
- <template>
- <div class="map-page">
- <div ref="mapContainer" class="map-container"></div>
- </div>
- </template>
- <script setup>
- import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
- import { wgs84togcj02 } from 'coordtransform';
- const isMapReady = ref(false)
- const TMap = ref(null); // 存储腾讯地图SDK实例
- const mapContainer = ref(null); // 地图容器DOM
- const state = reactive({ excelData: [] }); // 存储处理后的数据
- const infoWindow = ref(null); // 信息窗口实例
- let map = null; // 地图实例
- let markersLayer = null; // 标记图层实例
- // 腾讯地图配置
- const tMapConfig = reactive({
- key: import.meta.env.VITE_TMAP_KEY, // 必须配置环境变量(腾讯地图开发者密钥)
- })
- // 加载腾讯地图SDK
- const loadSDK = () => {
- return new Promise((resolve, reject) => {
- if (window.TMap) {
- TMap.value = window.TMap
- return resolve(window.TMap)
- }
- const script = document.createElement('script')
- script.src = `https://map.qq.com/api/gljs?v=2.exp&libraries=basic&key=${tMapConfig.key}&callback=initTMap`
- window.initTMap = () => {
- TMap.value = window.TMap
- resolve(window.TMap)
- }
- script.onerror = (err) => {
- reject(`地图加载失败: ${err.message}`)
- document.head.removeChild(script)
- }
- document.head.appendChild(script)
- })
- }
- // 初始化断面数据(直接嵌入你的Excel数据)
- const initData = () => {
- const rawData = [
- { "断面编号": 0, "所属河流": "浈江", "断面位置": "小古录", "所属区县": "始兴县", "经度": 114.208543, "纬度": 25.059851, "Cd(ug/L)": 0.11 },
- { "断面编号": 1, "所属河流": "浈江", "断面位置": "长坝", "所属区县": "仁化县", "经度": 113.692874, "纬度": 24.874845, "Cd(ug/L)": 1.116 },
- { "断面编号": 2, "所属河流": "浈江", "断面位置": "东河桥", "所属区县": "浈江区", "经度": 113.601631, "纬度": 24.80784, "Cd(ug/L)": 3.46 },
- { "断面编号": 3, "所属河流": "武江", "断面位置": "坪石", "所属区县": "乐昌市", "经度": 113.066281, "纬度": 25.274421, "Cd(ug/L)": 0.98 },
- { "断面编号": 4, "所属河流": "武江", "断面位置": "乐昌", "所属区县": "乐昌市", "经度": 113.338782, "纬度": 25.129212, "Cd(ug/L)": 0.11 },
- { "断面编号": 5, "所属河流": "武江", "断面位置": "武江桥", "所属区县": "乐昌市", "经度": 113.349815, "纬度": 25.120278, "Cd(ug/L)": 0.15 },
- { "断面编号": 6, "所属河流": "北江", "断面位置": "九公里", "所属区县": "浈江区", "经度": 113.580758, "纬度": 24.761299, "Cd(ug/L)": 7.83 },
- { "断面编号": 7, "所属河流": "北江", "断面位置": "白土", "所属区县": "曲江区", "经度": 113.531284, "纬度": 24.679958, "Cd(ug/L)": 5.94 },
- { "断面编号": 8, "所属河流": "浈江", "断面位置": "昆仑水站", "所属区县": "南雄市", "经度": 114.3629285, "纬度": 25.10053746, "Cd(ug/L)": 0.517 },
- { "断面编号": 9, "所属河流": "北江", "断面位置": "白沙", "所属区县": "曲江", "经度": 113.5707136, "纬度": 24.58139261, "Cd(ug/L)": 1.54 },
- { "断面编号": 10, "所属河流": "浈江", "断面位置": "周田水站", "所属区县": "仁化县", "经度": 113.8293461, "纬度": 24.97851516, "Cd(ug/L)": 0.182 },
- { "断面编号": 11, "所属河流": "武江", "断面位置": "坪石水站", "所属区县": "乐昌市", "经度": 113.0467854, "纬度": 25.28883459, "Cd(ug/L)": 1.071 }
- ];
- // 处理坐标(WGS84转GCJ02,腾讯地图用GCJ02)
- state.excelData = rawData.map(item => {
- const lng = Number(item.经度);
- const lat = Number(item.纬度);
- if (isNaN(lat) || isNaN(lng)) {
- console.error('无效经纬度:', item);
- return null;
- }
- const [gcjLng, gcjLat] = wgs84togcj02(lng, lat); // 坐标转换
- return {
- id: item.断面编号,
- river: item.所属河流,
- location: item.断面位置,
- district: item.所属区县,
- cdValue: item["Cd(ug/L)"],
- latitude: gcjLat,
- longitude: gcjLng,
- };
- }).filter(item => item !== null);
- }
- // 初始化地图
- const initMap = async () => {
- try {
- await loadSDK()
- // 创建地图实例(中心设为数据区域:粤北)
- map = new TMap.value.Map(mapContainer.value, {
- center: new TMap.value.LatLng(25.2, 114), //前大往下,后大往左
- zoom: 9.8,
- minZoom: 9.8,
- maxZoom: 14,
- })
- // 创建标记图层
- markersLayer = new TMap.value.MultiMarker({
- map: map,
- styles: {
- default: new TMap.value.MarkerStyle({
- width: 30,
- height: 30,
- src: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0xMiAyTDIgMjJoMjBMMTIgMnoiIGZpbGw9IiMxRTg4RTUiLz48L3N2Zz4='
- })
- }
- });
- // 绑定标记点击事件
- markersLayer.on('click', handleMarkerClick);
- // 加载数据并渲染标记
- initData();
- updateMarkers();
- isMapReady.value = true;
- } catch (err) {
- console.error('地图初始化失败:', err);
- error.value = err.message;
- }
- }
- // 更新标记点
- const updateMarkers = () => {
- const geometries = state.excelData.map(item => ({
- id: String(item.id), // 统一转字符串,避免类型错误
- styleId: 'default',
- position: new TMap.value.LatLng(item.latitude, item.longitude),
- properties: {
- title: item.location, // 断面位置作为标题
- }
- }));
- markersLayer.setGeometries(geometries);
- }
- // 标记点击事件(直接用本地数据)
- const handleMarkerClick = (e) => {
- const marker = e.geometry;
- if (!marker) return;
- // 查找本地数据
- const markerId = String(marker.id);
- const matchedData = state.excelData.find(item => String(item.id) === markerId);
- if (!matchedData) {
- console.error('未找到数据:', markerId);
- return;
- }
- // 构建信息窗口内容
- const content = `
- <div class="water-info-window">
- <h3 class="info-title">断面编号:${matchedData.id}</h3>
- <div class="info-content">
- <div class="info-row">
- <span class="info-label">所属河流:</span>
- <span class="info-value">${matchedData.river}</span>
- </div>
- <div class="info-row">
- <span class="info-label">断面位置:</span>
- <span class="info-value">${matchedData.location}</span>
- </div>
- <div class="info-row">
- <span class="info-label">所属区县:</span>
- <span class="info-value">${matchedData.district}</span>
- </div>
-
- <div class="info-row">
- <span class="info-label">Cd含量:</span>
- <span class="info-value">${matchedData.cdValue} ug/L</span>
- </div>
-
- </div>
- </div>
- `;
- // 关闭之前的信息窗口
- if (infoWindow.value) {
- infoWindow.value.close();
- }
- // 打开新信息窗口
- infoWindow.value = new TMap.value.InfoWindow({
- map: map,
- position: marker.position,
- content,
- offset: { x: 0, y: -32 } // 向上偏移,避免遮挡标记
- });
- infoWindow.value.open();
- }
- // 生命周期
- onMounted(async () => {
- try {
- await loadSDK();
- await initMap();
- q //监听窗口大小变化,调整图表
- window.addEventListener('resize',()=>{
- if(chart){
- chart.resize();
- }
- })
- } catch (err) {
- error.value = err.message;
- }
- })
- onBeforeUnmount(() => {
- if (markersLayer) markersLayer.setMap(null); // 销毁标记图层
- if (infoWindow.value) infoWindow.value.close(); // 关闭信息窗口
- })
- </script>
- <style scoped>
- .map-page {
- width: 100%;
- margin: 0 auto 24px; /* 水平居中,底部间距24px */
- background-color: white;
- border-radius: 12px;
- padding: 20px;
- box-sizing: border-box;
- }
- /* 地图容器样式 */
- .map-container {
- width: 100%;
- height: 550px; /* 固定高度,确保地图显示完整 */
- margin: 1rem auto;
- border-radius: 12px;
- }
- /* 表格容器 */
- .table-page {
- width: 100%;
- background-color: rgba(255, 255, 255, 0.85);
- border-radius: 12px;
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
- overflow: hidden;
- transition: transform 0.3s ease;
- }
- .table-page:hover {
- transform: translateY(-3px);
- }
- .table-container {
- padding: 20px;
- flex: 1;
- overflow: auto;
- }
- .table-title {
- text-align: left;
- margin: 0;
- padding: 15px 15px 15px 24px;
- position: relative;
- font-size: 1.5rem;
- font-weight: 600;
- color: #1e88e5;
- line-height: 1.2;
- background-color: rgba(30, 136, 229, 0.08);
- border-bottom: 1px solid rgba(30, 136, 229, 0.15);
- }
- /* 蓝色小方块 */
- .table-title::before {
- content: "";
- position: absolute;
- left: 8px;
- top: 50%;
- transform: translateY(-50%);
- width: 8px;
- height: 8px;
- background-color: #1e88e5;
- border-radius: 50%;
- }
- .data-table {
- width: 100%;
- border-collapse: collapse;
- min-width: 800px;
- background-color: rgba(255, 255, 255, 0.7);
- }
- .data-table th,.data-table td {
- padding: 12px 15px;
- text-align: center;
- border: 1px solid rgba(229, 231, 235, 0.7);
- }
- .data-table th {
- background-color: rgba(243, 244, 246, 0.7);
- font-weight: bold;
- color: #1f2937;
- }
- .data-table tr:nth-child(even){
- background-color: rgba(249, 250, 251, 0.7);
- }
- .data-table tr:hover{
- background-color: rgba(243, 244, 246, 0.7);
- }
- /* 信息窗口核心调整 */
- :v-deep(.tmap-infowindow) {
- padding: 20px !important;
- min-width: 320px !important;
- font-size: 1.25rem !important;
- box-shadow: 0 4px 12px rgba(0,0,0,0.2) !important;
- border-radius: 8px !important;
- background: rgba(255, 255, 255, 0.95) !important;
- backdrop-filter: blur(4px);
- }
- .water-info-window {
- font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
- }
- .info-title {
- background: linear-gradient(135deg, rgba(30, 136, 229, 0.85), rgba(21, 101, 192, 0.85));
- color: white;
- font-size: 1.15rem;
- font-weight: 600;
- padding: 16px 20px;
- margin: 0;
- position: relative;
- letter-spacing: 0.5px;
- border-radius: 6px 6px 0 0;
- }
- .info-title:after {
- content: "";
- position: absolute;
- bottom: 0;
- left: 20px;
- right: 20px;
- height: 1px;
- background: linear-gradient(to right, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.35), rgba(255, 255, 255, 0.15));
- }
- .info-row {
- display: flex;
- margin-bottom: 15px;
- align-items: center;
- position: relative;
- padding-left: 20px;
- }
- .info-row:last-child {
- margin-bottom: 0;
- }
- .info-row:before {
- content: "";
- position: absolute;
- left: 0;
- top: 50%;
- transform: translateY(-50%);
- width: 14px;
- height: 14px;
- border-radius: 50%;
- background-color: #e3f2fd;
- border: 2px solid #90caf9;
- }
- .info-row:nth-child(4):before {
- background-color: #ffebee;
- border-color: #ffcdd2;
- }
- .info-label {
- flex: 0 0 100px;
- color: #546e7a;
- font-size: 0.95rem;
- font-weight: 500;
- text-align: right;
- padding-right: 15px;
- position: relative;
- }
- .info-label:after {
- content: ":";
- position: absolute;
- right: 5px;
- }
- .info-value {
- flex: 1;
- color: #263238;
- font-size: 1rem;
- background: rgba(248, 249, 250, 0.7);
- padding: 10px 15px;
- border-radius: 6px;
- border-left: 3px solid #64b5f6;
- font-weight: 500;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.03);
- transition: all 0.3s ease;
- }
- .info-row:nth-child(4) .info-value {
- color: #e53935;
- border-left-color: #ef9a9a;
- font-weight: 600;
- position: relative;
- }
- .info-row:nth-child(4) .info-value:after {
- content: "mg/L";
- position: absolute;
- right: 15px;
- font-size: 0.85rem;
- font-weight: normal;
- color: #78909c;
- }
- /* 响应式调整 */
- @media (max-width: 768px) {
- .map-page {
- width: 96%; /* 小屏幕稍窄,避免边缘拥挤 */
- }
- .map-container {
- height: 300px; /* 小屏幕缩短高度 */
- }
- }
- </style>
|