Showing
8 changed files
with
415 additions
and
3 deletions
| ... | @@ -12,6 +12,10 @@ declare module '@vue/runtime-core' { | ... | @@ -12,6 +12,10 @@ declare module '@vue/runtime-core' { |
| 12 | AudioBackground: typeof import('./src/components/audioBackground.vue')['default'] | 12 | AudioBackground: typeof import('./src/components/audioBackground.vue')['default'] |
| 13 | AudioBackground1: typeof import('./src/components/audioBackground1.vue')['default'] | 13 | AudioBackground1: typeof import('./src/components/audioBackground1.vue')['default'] |
| 14 | AudioList: typeof import('./src/components/audioList.vue')['default'] | 14 | AudioList: typeof import('./src/components/audioList.vue')['default'] |
| 15 | + ElButton: typeof import('element-plus/es')['ElButton'] | ||
| 16 | + ElInput: typeof import('element-plus/es')['ElInput'] | ||
| 17 | + ElOption: typeof import('element-plus/es')['ElOption'] | ||
| 18 | + ElSelect: typeof import('element-plus/es')['ElSelect'] | ||
| 15 | Floor: typeof import('./src/components/Floor/index.vue')['default'] | 19 | Floor: typeof import('./src/components/Floor/index.vue')['default'] |
| 16 | InfoPopup: typeof import('./src/components/InfoPopup.vue')['default'] | 20 | InfoPopup: typeof import('./src/components/InfoPopup.vue')['default'] |
| 17 | InfoPopupLite: typeof import('./src/components/InfoPopupLite.vue')['default'] | 21 | InfoPopupLite: typeof import('./src/components/InfoPopupLite.vue')['default'] | ... | ... |
| ... | @@ -25,6 +25,8 @@ | ... | @@ -25,6 +25,8 @@ |
| 25 | "xys_upload": "npm run build_tar && npm run scp-xys && npm run dec-xys && npm run remove_tar" | 25 | "xys_upload": "npm run build_tar && npm run scp-xys && npm run dec-xys && npm run remove_tar" |
| 26 | }, | 26 | }, |
| 27 | "dependencies": { | 27 | "dependencies": { |
| 28 | + "@amap/amap-jsapi-loader": "^1.0.1", | ||
| 29 | + "@element-plus/icons-vue": "^2.3.1", | ||
| 28 | "@photo-sphere-viewer/core": "^5.7.3", | 30 | "@photo-sphere-viewer/core": "^5.7.3", |
| 29 | "@photo-sphere-viewer/gallery-plugin": "^5.7.3", | 31 | "@photo-sphere-viewer/gallery-plugin": "^5.7.3", |
| 30 | "@photo-sphere-viewer/gyroscope-plugin": "^5.7.3", | 32 | "@photo-sphere-viewer/gyroscope-plugin": "^5.7.3", |
| ... | @@ -39,6 +41,7 @@ | ... | @@ -39,6 +41,7 @@ |
| 39 | "animate.css": "^4.1.1", | 41 | "animate.css": "^4.1.1", |
| 40 | "dayjs": "^1.11.3", | 42 | "dayjs": "^1.11.3", |
| 41 | "default-passive-events": "^2.0.0", | 43 | "default-passive-events": "^2.0.0", |
| 44 | + "element-plus": "^2.9.3", | ||
| 42 | "font-awesome": "^4.7.0", | 45 | "font-awesome": "^4.7.0", |
| 43 | "global": "^4.4.0", | 46 | "global": "^4.4.0", |
| 44 | "html-to-json-parser": "^1.1.0", | 47 | "html-to-json-parser": "^1.1.0", | ... | ... |
| ... | @@ -2,7 +2,7 @@ | ... | @@ -2,7 +2,7 @@ |
| 2 | * @Author: hookehuyr hookehuyr@gmail.com | 2 | * @Author: hookehuyr hookehuyr@gmail.com |
| 3 | * @Date: 2022-05-31 12:06:19 | 3 | * @Date: 2022-05-31 12:06:19 |
| 4 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 4 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 5 | - * @LastEditTime: 2024-09-25 17:17:40 | 5 | + * @LastEditTime: 2025-01-23 16:10:45 |
| 6 | * @FilePath: /map-demo/src/main.js | 6 | * @FilePath: /map-demo/src/main.js |
| 7 | * @Description: | 7 | * @Description: |
| 8 | */ | 8 | */ |
| ... | @@ -57,6 +57,8 @@ import 'video.js/dist/video-js.css'; | ... | @@ -57,6 +57,8 @@ import 'video.js/dist/video-js.css'; |
| 57 | import 'viewerjs/dist/viewer.css'; | 57 | import 'viewerjs/dist/viewer.css'; |
| 58 | import VueViewer from 'v-viewer'; | 58 | import VueViewer from 'v-viewer'; |
| 59 | 59 | ||
| 60 | +import * as ElementPlusIconsVue from '@element-plus/icons-vue' | ||
| 61 | + | ||
| 60 | const pinia = createPinia(); | 62 | const pinia = createPinia(); |
| 61 | const app = createApp(App); | 63 | const app = createApp(App); |
| 62 | 64 | ||
| ... | @@ -102,4 +104,8 @@ app | ... | @@ -102,4 +104,8 @@ app |
| 102 | app.use(VueVideoPlayer) | 104 | app.use(VueVideoPlayer) |
| 103 | app.use(VueViewer); | 105 | app.use(VueViewer); |
| 104 | 106 | ||
| 107 | +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { | ||
| 108 | + app.component(key, component) | ||
| 109 | +} | ||
| 110 | + | ||
| 105 | app.mount('#app'); | 111 | app.mount('#app'); | ... | ... |
| 1 | /* | 1 | /* |
| 2 | * @Date: 2023-05-29 11:10:19 | 2 | * @Date: 2023-05-29 11:10:19 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2024-09-20 17:54:57 | 4 | + * @LastEditTime: 2025-01-23 16:22:10 |
| 5 | * @FilePath: /map-demo/src/route.js | 5 | * @FilePath: /map-demo/src/route.js |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | */ | 7 | */ |
| ... | @@ -69,4 +69,12 @@ export default [ | ... | @@ -69,4 +69,12 @@ export default [ |
| 69 | title: '详情页', | 69 | title: '详情页', |
| 70 | }, | 70 | }, |
| 71 | }, | 71 | }, |
| 72 | + { | ||
| 73 | + path: '/map_cutter', | ||
| 74 | + name: '瓦片切图工具', | ||
| 75 | + component: () => import('@/views/mapCutter.vue'), | ||
| 76 | + meta: { | ||
| 77 | + title: '瓦片切图工具', | ||
| 78 | + }, | ||
| 79 | + }, | ||
| 72 | ]; | 80 | ]; | ... | ... |
src/utils/TileCutter.js
0 → 100644
| 1 | +/* | ||
| 2 | + * @Date: 2025-01-22 11:45:30 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-01-23 16:38:00 | ||
| 5 | + * @FilePath: /map-demo/src/utils/TileCutter.js | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | + */ | ||
| 8 | +const tileSize = 512; | ||
| 9 | + | ||
| 10 | +export function TileCutter(imageURL, bounds, zoomLevel) { | ||
| 11 | + const img = new Image(); | ||
| 12 | + img.crossOrigin = "Anonymous"; // 避免跨域问题 | ||
| 13 | + img.src = imageURL; | ||
| 14 | + img.onload = () => { | ||
| 15 | + sliceImageToTiles(img, bounds, zoomLevel); | ||
| 16 | + }; | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | + | ||
| 20 | +function sliceImageToTiles(image, bounds, zoomLevel) { | ||
| 21 | + const canvas = document.createElement("canvas"); | ||
| 22 | + const ctx = canvas.getContext("2d"); | ||
| 23 | + | ||
| 24 | + const imgWidth = image.width; | ||
| 25 | + const imgHeight = image.height; | ||
| 26 | + | ||
| 27 | + const southWest = bounds.getSouthWest(); | ||
| 28 | + const northEast = bounds.getNorthEast(); | ||
| 29 | + | ||
| 30 | + const lonStart = southWest.lng; | ||
| 31 | + const latStart = southWest.lat; | ||
| 32 | + const lonEnd = northEast.lng; | ||
| 33 | + const latEnd = northEast.lat; | ||
| 34 | + | ||
| 35 | + let tileStartX = lonToTileX(lonStart, zoomLevel); | ||
| 36 | + let tileEndX = lonToTileX(lonEnd, zoomLevel); | ||
| 37 | + let tileStartY = latToTileY(latEnd, zoomLevel); // 取 latEnd 作为起点 | ||
| 38 | + let tileEndY = latToTileY(latStart, zoomLevel); // 取 latStart 作为终点 | ||
| 39 | + | ||
| 40 | + // 确保 tileStartX <= tileEndX,tileStartY <= tileEndY | ||
| 41 | + tileStartX = Math.min(tileStartX, tileEndX); | ||
| 42 | + tileEndX = Math.max(tileStartX, tileEndX); | ||
| 43 | + tileStartY = Math.min(tileStartY, tileEndY); | ||
| 44 | + tileEndY = Math.max(tileStartY, tileEndY); | ||
| 45 | + | ||
| 46 | + // console.warn(`瓦片编号: X(${tileStartX} -> ${tileEndX}), Y(${tileStartY} -> ${tileEndY})`); | ||
| 47 | + | ||
| 48 | + const cols = tileEndX - tileStartX + 1; | ||
| 49 | + const rows = tileEndY - tileStartY + 1; | ||
| 50 | + | ||
| 51 | + const tileWidth = imgWidth / cols; | ||
| 52 | + const tileHeight = imgHeight / rows; | ||
| 53 | + | ||
| 54 | + const scaleFactor = 2; // 调高分辨率倍率 | ||
| 55 | + | ||
| 56 | + canvas.width = tileSize * scaleFactor; | ||
| 57 | + canvas.height = tileSize * scaleFactor; | ||
| 58 | + | ||
| 59 | + ctx.scale(scaleFactor, scaleFactor); | ||
| 60 | + | ||
| 61 | + for (let x = 0; x < cols; x++) { | ||
| 62 | + for (let y = 0; y < rows; y++) { | ||
| 63 | + ctx.clearRect(0, 0, tileSize, tileSize); | ||
| 64 | + | ||
| 65 | + ctx.drawImage( | ||
| 66 | + image, | ||
| 67 | + x * tileWidth, y * tileHeight, tileWidth, tileHeight, // 源图像区域 | ||
| 68 | + 0, 0, tileSize, tileSize // 目标画布区域 | ||
| 69 | + ); | ||
| 70 | + | ||
| 71 | + canvas.toBlob((blob) => { | ||
| 72 | + if (!blob) { | ||
| 73 | + console.error("瓦片转换失败!"); | ||
| 74 | + return; | ||
| 75 | + } | ||
| 76 | + const tileX = tileStartX + x; | ||
| 77 | + const tileY = tileStartY + y; | ||
| 78 | + // console.warn(`保存瓦片: ${tileX}_${tileY}_${zoomLevel}.png`); | ||
| 79 | + saveTile(blob, `${tileX}_${tileY}_${zoomLevel}.png`); | ||
| 80 | + }, "image/png", 1.0); | ||
| 81 | + } | ||
| 82 | + } | ||
| 83 | +} | ||
| 84 | +// 经纬度转换为瓦片坐标 | ||
| 85 | +function lonToTileX(lon, zoom) { | ||
| 86 | + return Math.floor(((lon + 180) / 360) * Math.pow(2, zoom)); | ||
| 87 | +} | ||
| 88 | + | ||
| 89 | +function latToTileY(lat, zoom) { | ||
| 90 | + return Math.floor( | ||
| 91 | + ((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2) * Math.pow(2, zoom) | ||
| 92 | + ); | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +function saveTile(blob, filename) { | ||
| 96 | + const link = document.createElement("a"); | ||
| 97 | + link.href = URL.createObjectURL(blob); | ||
| 98 | + link.download = filename; | ||
| 99 | + document.body.appendChild(link); | ||
| 100 | + link.click(); | ||
| 101 | + document.body.removeChild(link); | ||
| 102 | +} |
src/views/mapCutter.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-01-22 11:40:12 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-01-23 16:32:53 | ||
| 5 | + * @FilePath: /map-demo/src/views/mapCutter.vue | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <div style="display: flex; padding: 1rem 0 0 1rem; gap: 1rem; align-items: center;"> | ||
| 10 | + <div>目标地图经纬度</div> | ||
| 11 | + <el-input | ||
| 12 | + v-model="map_center" | ||
| 13 | + style="width: 240px" | ||
| 14 | + placeholder="输入经纬度" | ||
| 15 | + @blur="onCenterBlur" | ||
| 16 | + /> | ||
| 17 | + </div> | ||
| 18 | + <div style="display: flex; padding: 1rem; gap: 1rem;"> | ||
| 19 | + <div style="display: flex; align-items: center;"> | ||
| 20 | + <div>地图层级: </div> | ||
| 21 | + <el-select v-model="map_zoom" placeholder="地图层级" style="width: 240px" @change="onZoomChange"> | ||
| 22 | + <el-option | ||
| 23 | + v-for="item in zoom_options" | ||
| 24 | + :key="item.value" | ||
| 25 | + :label="item.label" | ||
| 26 | + :value="item.value" | ||
| 27 | + /> | ||
| 28 | + </el-select> | ||
| 29 | + </div> | ||
| 30 | + | ||
| 31 | + <div style="display: flex; align-items: center;"> | ||
| 32 | + <div>上传图片范围: </div> | ||
| 33 | + <el-input | ||
| 34 | + v-model="map_left_bottom_range" | ||
| 35 | + style="width: 240px" | ||
| 36 | + placeholder="输入左下角经纬度" | ||
| 37 | + @blur="onLBRangeBlur" | ||
| 38 | + /> | ||
| 39 | + | ||
| 40 | + <el-input | ||
| 41 | + v-model="map_right_top_range" | ||
| 42 | + style="width: 240px" | ||
| 43 | + placeholder="输入右上角的经纬度" | ||
| 44 | + @blur="onRTRangeBlur" | ||
| 45 | + /> | ||
| 46 | + </div> | ||
| 47 | + <div v-if="showUpload"> | ||
| 48 | + <input type="file" @change="handleImageUpload" /> | ||
| 49 | + <el-button type="primary" @click="cutTiles">切割瓦片</el-button> | ||
| 50 | + </div> | ||
| 51 | + </div> | ||
| 52 | + <div id="map-container"></div> | ||
| 53 | + <div> | ||
| 54 | + | ||
| 55 | + </div> | ||
| 56 | + <div v-if="showUpload" class="controls"> | ||
| 57 | + <el-button type="primary" :icon="Top" @click="moveImage('up')">图片上移</el-button> | ||
| 58 | + <el-button type="primary" :icon="Bottom" @click="moveImage('down')">图片下移</el-button> | ||
| 59 | + <el-button type="primary" :icon="Back" @click="moveImage('left')">图片左移</el-button> | ||
| 60 | + <el-button type="primary" :icon="Right" @click="moveImage('right')">图片右移</el-button> | ||
| 61 | + <el-button type="primary" :icon="Plus" @click="scaleImage(1.01)">图片放大</el-button> | ||
| 62 | + <el-button type="primary" :icon="Minus" @click="scaleImage(0.99)">图片缩小</el-button> | ||
| 63 | + <el-button type="primary" @click="rotateMap(10)">地图顺时针旋转</el-button> | ||
| 64 | + <el-button type="primary" @click="rotateMap(-10)">地图逆时针旋转</el-button> | ||
| 65 | + </div> | ||
| 66 | +</template> | ||
| 67 | + | ||
| 68 | +<script setup> | ||
| 69 | +import { onMounted, ref, computed } from "vue"; | ||
| 70 | +// import AMapLoader from "@amap/amap-jsapi-loader"; | ||
| 71 | +import { TileCutter } from "@/utils/TileCutter"; | ||
| 72 | +import { Top, Bottom, Back, Right, Plus, Minus } from '@element-plus/icons-vue' | ||
| 73 | + | ||
| 74 | +const map = ref(null); | ||
| 75 | +const imageLayer = ref(null); | ||
| 76 | +const imageURL = ref(""); // 存储上传的图片 | ||
| 77 | +const bounds = ref(null); // 图片覆盖的边界 | ||
| 78 | +const mapRotation = ref(0); // 存储地图旋转角度 | ||
| 79 | +const zooms = ref([17, 18]); | ||
| 80 | + | ||
| 81 | +const map_zoom = ref(17) | ||
| 82 | + | ||
| 83 | +const zoom_options = [ | ||
| 84 | + { | ||
| 85 | + value: 17, | ||
| 86 | + label: 17, | ||
| 87 | + }, | ||
| 88 | + { | ||
| 89 | + value: 18, | ||
| 90 | + label: 18, | ||
| 91 | + }, | ||
| 92 | +] | ||
| 93 | + | ||
| 94 | +const map_left_bottom_range = ref(null); // 120.583625,31.311858 | ||
| 95 | +const map_right_top_range = ref(null); // 120.591047,31.318265 | ||
| 96 | + | ||
| 97 | +const map_center = ref(null); | ||
| 98 | + | ||
| 99 | +onMounted(async () => { | ||
| 100 | + loadMap(); | ||
| 101 | +}); | ||
| 102 | + | ||
| 103 | +function loadMap() { | ||
| 104 | + // 初始化地图 | ||
| 105 | + map.value = new AMap.Map('map-container', { | ||
| 106 | + zoom: 17, | ||
| 107 | + zooms: zooms.value, | ||
| 108 | + center: [120.587648, 31.314616], | ||
| 109 | + rotation: 0, // 初始地图角度 | ||
| 110 | + layers: [ | ||
| 111 | + new AMap.TileLayer.RoadNet(), | ||
| 112 | + new AMap.TileLayer.Satellite(), | ||
| 113 | + ], | ||
| 114 | + }); | ||
| 115 | + | ||
| 116 | + | ||
| 117 | + // Canvas作为切片 | ||
| 118 | + var layer1 = new AMap.TileLayer.Flexible({ | ||
| 119 | + tileSize: 256, | ||
| 120 | + // cacheSize: 300, | ||
| 121 | + zIndex: 200, | ||
| 122 | + createTile: function (x, y, z, success, fail) { | ||
| 123 | + var c = document.createElement('canvas'); | ||
| 124 | + c.width = c.height = 256; | ||
| 125 | + | ||
| 126 | + var cxt = c.getContext("2d"); | ||
| 127 | + cxt.font = "15px Verdana"; | ||
| 128 | + cxt.fillStyle = "#ff976a"; | ||
| 129 | + cxt.strokeStyle = "#ff976a"; | ||
| 130 | + cxt.strokeRect(0, 0, 256, 256); | ||
| 131 | + cxt.fillText('(' + [x, y, z].join(',') + ')', 10, 30); | ||
| 132 | + | ||
| 133 | + // 通知API切片创建完成 | ||
| 134 | + success(c); | ||
| 135 | + } | ||
| 136 | + }); | ||
| 137 | + | ||
| 138 | + layer1.setMap(map.value); | ||
| 139 | + | ||
| 140 | + // 监听 zoomchange 事件 | ||
| 141 | + map.value.on('zoomchange', () => { | ||
| 142 | + const currentZoom = map.value.getZoom(); | ||
| 143 | + if (currentZoom === 18) { | ||
| 144 | + map_zoom.value = 18; | ||
| 145 | + } else { | ||
| 146 | + map_zoom.value = 17; | ||
| 147 | + } | ||
| 148 | + }); | ||
| 149 | +} | ||
| 150 | + | ||
| 151 | +function handleImageUpload(event) { | ||
| 152 | + const file = event.target.files[0]; | ||
| 153 | + if (!file) return; | ||
| 154 | + | ||
| 155 | + const reader = new FileReader(); | ||
| 156 | + reader.onload = (e) => { | ||
| 157 | + imageURL.value = e.target.result; | ||
| 158 | + addImageToMap(imageURL.value); | ||
| 159 | + }; | ||
| 160 | + reader.readAsDataURL(file); | ||
| 161 | +} | ||
| 162 | + | ||
| 163 | +function addImageToMap(url) { | ||
| 164 | + if (imageLayer.value) { | ||
| 165 | + map.value.remove(imageLayer.value); | ||
| 166 | + } | ||
| 167 | + | ||
| 168 | + // TAG: 设置图片范围 | ||
| 169 | + bounds.value = new AMap.Bounds(map_left_bottom_range.value, map_right_top_range.value); // 设置图片范围 左下角 (西南) -> 右上角 (东北) | ||
| 170 | + imageLayer.value = new AMap.ImageLayer({ | ||
| 171 | + url: url, | ||
| 172 | + bounds: bounds.value, | ||
| 173 | + zooms: [17, 18], | ||
| 174 | + opacity: 0.6 // 透明度 (0 完全透明, 1 完全不透明) | ||
| 175 | + }); | ||
| 176 | + | ||
| 177 | + map.value.add(imageLayer.value); | ||
| 178 | +} | ||
| 179 | + | ||
| 180 | + | ||
| 181 | +function cutTiles() { | ||
| 182 | + if (!imageURL.value) { | ||
| 183 | + alert("请先上传图片"); | ||
| 184 | + return; | ||
| 185 | + } | ||
| 186 | + // TAG: 切割瓦片等级 | ||
| 187 | + TileCutter(imageURL.value, bounds.value, map_zoom.value); // 传入图片、地图范围、缩放级别 | ||
| 188 | +} | ||
| 189 | + | ||
| 190 | +// ✅ 1. 位置移动 | ||
| 191 | +const moveImage = (direction) => { | ||
| 192 | +const offset = 0.0001; // 移动步长(经纬度差值) | ||
| 193 | +const sw = bounds.value.getSouthWest(); | ||
| 194 | +const ne = bounds.value.getNorthEast(); | ||
| 195 | + | ||
| 196 | +let newBounds; | ||
| 197 | +switch (direction) { | ||
| 198 | + case "up": | ||
| 199 | + newBounds = new AMap.Bounds([sw.lng, sw.lat + offset], [ne.lng, ne.lat + offset]); | ||
| 200 | + break; | ||
| 201 | + case "down": | ||
| 202 | + newBounds = new AMap.Bounds([sw.lng, sw.lat - offset], [ne.lng, ne.lat - offset]); | ||
| 203 | + break; | ||
| 204 | + case "left": | ||
| 205 | + newBounds = new AMap.Bounds([sw.lng - offset, sw.lat], [ne.lng - offset, ne.lat]); | ||
| 206 | + break; | ||
| 207 | + case "right": | ||
| 208 | + newBounds = new AMap.Bounds([sw.lng + offset, sw.lat], [ne.lng + offset, ne.lat]); | ||
| 209 | + break; | ||
| 210 | +} | ||
| 211 | + | ||
| 212 | +bounds.value = newBounds; | ||
| 213 | +imageLayer.value.setBounds(bounds.value); | ||
| 214 | +}; | ||
| 215 | + | ||
| 216 | +// ✅ 2. 缩放图片 | ||
| 217 | +const scaleImage = (factor) => { | ||
| 218 | +const sw = bounds.value.getSouthWest(); | ||
| 219 | +const ne = bounds.value.getNorthEast(); | ||
| 220 | + | ||
| 221 | +const centerX = (sw.lng + ne.lng) / 2; | ||
| 222 | +const centerY = (sw.lat + ne.lat) / 2; | ||
| 223 | +const newWidth = (ne.lng - sw.lng) * factor; | ||
| 224 | +const newHeight = (ne.lat - sw.lat) * factor; | ||
| 225 | + | ||
| 226 | +bounds.value = new AMap.Bounds( | ||
| 227 | + [centerX - newWidth / 2, centerY - newHeight / 2], | ||
| 228 | + [centerX + newWidth / 2, centerY + newHeight / 2] | ||
| 229 | +); | ||
| 230 | + | ||
| 231 | +imageLayer.value.setBounds(bounds.value); | ||
| 232 | +}; | ||
| 233 | + | ||
| 234 | +// 旋转地图 | ||
| 235 | +const rotateMap = (deltaAngle) => { | ||
| 236 | + if (!map.value) return; | ||
| 237 | + | ||
| 238 | + mapRotation.value += deltaAngle; | ||
| 239 | + | ||
| 240 | + console.log(`地图旋转: ${mapRotation.value}°`); | ||
| 241 | + | ||
| 242 | + map.value.setRotation(mapRotation.value); | ||
| 243 | +}; | ||
| 244 | + | ||
| 245 | + | ||
| 246 | +const onZoomChange = (value) => { // 调整地图图层 | ||
| 247 | + map.value.setZoom(value); | ||
| 248 | +} | ||
| 249 | + | ||
| 250 | +const onLBRangeBlur = () => { | ||
| 251 | + const str = map_left_bottom_range.value; | ||
| 252 | + const formattedArray = str.split(',').map(Number); | ||
| 253 | + map_left_bottom_range.value = formattedArray; | ||
| 254 | +} | ||
| 255 | +const onRTRangeBlur = () => { | ||
| 256 | + const str = map_right_top_range.value; | ||
| 257 | + const formattedArray = str.split(',').map(Number); | ||
| 258 | + map_right_top_range.value = formattedArray; | ||
| 259 | +} | ||
| 260 | + | ||
| 261 | +const showUpload = computed(() => { | ||
| 262 | + return map_left_bottom_range.value && map_right_top_range.value ? true : false; | ||
| 263 | +}); | ||
| 264 | + | ||
| 265 | +const onCenterBlur = () => { | ||
| 266 | + const str = map_center.value; | ||
| 267 | + const formattedArray = str.split(',').map(Number); | ||
| 268 | + map.value.setCenter(formattedArray); | ||
| 269 | +} | ||
| 270 | +</script> | ||
| 271 | + | ||
| 272 | +<style> | ||
| 273 | +#map-container { | ||
| 274 | + width: 100%; | ||
| 275 | + height: 100vh; | ||
| 276 | +} | ||
| 277 | + | ||
| 278 | +.controls { | ||
| 279 | + position: absolute; | ||
| 280 | + top: 8rem; | ||
| 281 | + right: 10px; | ||
| 282 | + background: rgba(255, 255, 255, 0.8); | ||
| 283 | + padding: 10px; | ||
| 284 | + border-radius: 5px; | ||
| 285 | + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); | ||
| 286 | +} | ||
| 287 | +</style> |
| ... | @@ -9,6 +9,7 @@ import { createProxy } from './build/proxy' | ... | @@ -9,6 +9,7 @@ import { createProxy } from './build/proxy' |
| 9 | import DefineOptions from 'unplugin-vue-define-options/vite'; | 9 | import DefineOptions from 'unplugin-vue-define-options/vite'; |
| 10 | import AutoImport from 'unplugin-auto-import/vite'; | 10 | import AutoImport from 'unplugin-auto-import/vite'; |
| 11 | import postcsspxtoviewport from 'postcss-px-to-viewport' | 11 | import postcsspxtoviewport from 'postcss-px-to-viewport' |
| 12 | +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' | ||
| 12 | 13 | ||
| 13 | var path = require('path'); | 14 | var path = require('path'); |
| 14 | const fs = require('fs'); | 15 | const fs = require('fs'); |
| ... | @@ -30,7 +31,7 @@ export default ({ command, mode }) => { | ... | @@ -30,7 +31,7 @@ export default ({ command, mode }) => { |
| 30 | // 将要用到的插件数组。Falsy 虚值的插件将被忽略,插件数组将被扁平化(flatten)。查看 插件 API 获取 Vite 插件的更多细节。 | 31 | // 将要用到的插件数组。Falsy 虚值的插件将被忽略,插件数组将被扁平化(flatten)。查看 插件 API 获取 Vite 插件的更多细节。 |
| 31 | vue(), | 32 | vue(), |
| 32 | Components({ | 33 | Components({ |
| 33 | - resolvers: [VantResolver()], | 34 | + resolvers: [VantResolver(), ElementPlusResolver()], |
| 34 | }), | 35 | }), |
| 35 | // styleImport({ | 36 | // styleImport({ |
| 36 | // resolves: [VantResolve()], | 37 | // resolves: [VantResolve()], |
| ... | @@ -56,6 +57,7 @@ export default ({ command, mode }) => { | ... | @@ -56,6 +57,7 @@ export default ({ command, mode }) => { |
| 56 | eslintrc: { | 57 | eslintrc: { |
| 57 | enabled: true, | 58 | enabled: true, |
| 58 | }, | 59 | }, |
| 60 | + resolvers: [ElementPlusResolver()], | ||
| 59 | }), | 61 | }), |
| 60 | ], | 62 | ], |
| 61 | publicDir: 'public', // 作为静态资源服务的文件夹。这个目录中的文件会在开发中被服务于 /,在开发模式时,会被拷贝到 outDir 的根目录,并没有转换,永远只是复制到这里。该值可以是文件系统的绝对路径,也可以是相对于项目的根目录路径。 | 63 | publicDir: 'public', // 作为静态资源服务的文件夹。这个目录中的文件会在开发中被服务于 /,在开发模式时,会被拷贝到 outDir 的根目录,并没有转换,永远只是复制到这里。该值可以是文件系统的绝对路径,也可以是相对于项目的根目录路径。 | ... | ... |
This diff is collapsed. Click to expand it.
-
Please register or login to post a comment