refactor(custom): 地图服务商更换
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-06-16 17:56:39 +08:00
parent 41ee5a03dd
commit aa8e3100a2
2 changed files with 537 additions and 7 deletions

View File

@@ -0,0 +1,527 @@
<template>
<div class="tianditu-picker">
<!-- 顶部搜索框 -->
<div class="search-header">
<n-input-group>
<n-auto-complete
v-model:value="keyword"
placeholder="搜索地点"
clearable
@select="selectFromSearch"
@input:value="handleInput"
/>
<n-button type="primary" @click="search">搜索位置</n-button>
</n-input-group>
</div>
<!-- 地图区域 -->
<div id="mapDiv" class="map-container"></div>
<!-- 底部位置信息和搜索结果 -->
<div class="location-footer">
<!-- 统一的结果列表 -->
<div class="results-section">
<div class="results-header">位置选择</div>
<div class="results-list">
<!-- 我的位置项 -->
<div
class="result-item current-location-item"
:class="{ selected: !showSearchResults }"
@click="relocateToCurrentPosition"
>
<div class="location-icon">📍</div>
<div class="result-content">
<div class="result-name">我的位置</div>
<div class="result-address">
{{ currentLocation?.address || '请点击获取当前位置' }}
</div>
</div>
</div>
<!-- 搜索结果项 -->
<div
v-for="(result, index) in searchResults"
:key="`search-${index}`"
class="result-item search-result-item"
@click="selectSearchResult(result)"
>
<div class="location-icon">📍</div>
<div class="result-content">
<div class="result-name">{{ result.name }}</div>
<div class="result-address">{{ result.address }}</div>
</div>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="action-buttons">
<n-space justify="space-between">
<n-button type="tertiary" @click="getCurrentLocation">定位当前位置</n-button>
<n-space>
<n-button @click="$emit('cancel')">取消</n-button>
<n-button type="primary" @click="confirmLocation">确认位置</n-button>
</n-space>
</n-space>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import axios from 'axios'
const emit = defineEmits(['confirm', 'cancel'])
const keyword = ref('')
const selectedLocation = ref(null)
const currentLocation = ref(null)
const searchResults = ref([])
const showSearchResults = ref(false)
let map = null
let marker = null
const TDT_KEY = '42db4f3dfd1a18d31e73ee90aa2ce054'
// 初始化地图
const initMap = () => {
if (typeof T === 'undefined') {
console.error('天地图API未加载完成')
return
}
map = new window.T.Map('mapDiv')
map.centerAndZoom(new window.T.LngLat(116.40969, 39.89945), 12)
// 地图点击事件
map.addEventListener('click', function (e) {
const lnglat = e.lnglat
addMarker(lnglat)
getAddress(lnglat)
// 点击地图时隐藏搜索结果
showSearchResults.value = false
})
// 获取当前位置
getCurrentLocation()
}
// 添加标记
const addMarker = (lnglat) => {
if (marker) {
map.removeOverLay(marker)
}
// 创建自定义图标
const icon = new window.T.Icon({
iconUrl:
'data:image/svg+xml;base64,' +
btoa(`
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="40" viewBox="0 0 32 40">
<path fill="#ff4757" d="M16 0C7.2 0 0 7.2 0 16c0 8.8 16 24 16 24s16-15.2 16-24C32 7.2 24.8 0 16 0zm0 22c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6z"/>
<circle fill="white" cx="16" cy="16" r="4"/>
</svg>
`),
iconSize: new window.T.Point(32, 40),
iconAnchor: new window.T.Point(16, 40),
})
marker = new window.T.Marker(lnglat, { icon })
map.addOverLay(marker)
map.panTo(lnglat)
selectedLocation.value = {
lat: lnglat.lat,
lng: lnglat.lng,
address: '正在获取地址...',
}
}
// 地理编码
const getAddress = (lnglat) => {
const geocoder = new window.T.Geocoder()
geocoder.getLocation(lnglat, function (result) {
const address = result.getAddress()
if (selectedLocation.value) {
selectedLocation.value.address = address
}
})
}
// 获取当前位置
const getCurrentLocation = () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const lat = position.coords.latitude
const lng = position.coords.longitude
const lnglat = new window.T.LngLat(lng, lat)
map.centerAndZoom(lnglat, 15)
addMarker(lnglat)
// 获取地址信息,专门为当前位置设置
const geocoder = new window.T.Geocoder()
geocoder.getLocation(lnglat, function (result) {
const address = result.getAddress()
// 同时更新当前位置和选中位置
currentLocation.value = {
lat: lat,
lng: lng,
address: address,
}
if (selectedLocation.value) {
selectedLocation.value.address = address
}
})
},
(error) => {
console.error('获取位置失败:', error)
$message.warning('无法获取当前位置,请手动选择')
}
)
} else {
$message.warning('浏览器不支持定位功能')
}
}
// 使用axios调用天地图HTTP API搜索
const searchWithAPI = async (query) => {
try {
console.log('搜索关键词:', query)
// 构建搜索参数
const postStr = {
keyWord: query,
level: 12,
mapBound: '116.02524,39.83833,116.65592,39.99185',
queryType: 1,
start: 0,
count: 10,
}
const response = await axios.get('https://api.tianditu.gov.cn/v2/search', {
params: {
postStr: JSON.stringify(postStr),
type: 'query',
tk: TDT_KEY,
},
timeout: 10000, // 10秒超时
})
console.log('搜索响应:', response.data)
if (
response.data.status.infocode === 1000 &&
response.data.pois &&
response.data.pois.length > 0
) {
return response.data.pois.map((poi) => ({
name: poi.name,
address: poi.address,
lonlat: poi.lonlat,
adminName: poi.adminName,
}))
}
return []
} catch (error) {
console.error('搜索失败:', error)
// 更详细的错误处理
if (error.code === 'ECONNABORTED') {
$message.error('请求超时,请重试')
} else if (error.response) {
$message.error(`搜索失败: ${error.response.status}`)
} else if (error.request) {
$message.error('网络请求失败,请检查网络连接')
} else {
$message.error('搜索出错,请重试')
}
return []
}
}
// 搜索功能
const search = async () => {
if (!keyword.value || !map) return
try {
const results = await searchWithAPI(keyword.value)
if (results.length > 0) {
searchResults.value = results
showSearchResults.value = true
console.log('搜索结果:', results)
} else {
$message.warning('未找到相关位置')
searchResults.value = []
showSearchResults.value = false
}
} catch (error) {
console.error('搜索出错:', error)
$message.error('搜索失败,请重试')
}
}
// 选择搜索结果
const selectSearchResult = (result) => {
if (!result.lonlat) return
const [lng, lat] = result.lonlat.split(',').map(Number)
const lnglat = new window.T.LngLat(lng, lat)
addMarker(lnglat)
// 只更新选中位置,不影响当前位置
if (selectedLocation.value) {
selectedLocation.value.address = `${result.name} - ${result.address}`
}
showSearchResults.value = false
}
// 处理搜索输入
const handleInput = (value) => {
if (value && value.length > 1) {
// 延迟搜索,避免频繁请求
clearTimeout(handleInput.timer)
handleInput.timer = setTimeout(() => {
search()
}, 500)
} else {
searchResults.value = []
showSearchResults.value = false
}
}
// 从搜索建议中选择
const selectFromSearch = (value, option) => {
if (option && option.data) {
selectSearchResult(option.data)
}
}
// 确认选择的位置
const confirmLocation = () => {
if (selectedLocation.value) {
emit('confirm', {
latlng: {
lat: selectedLocation.value.lat,
lng: selectedLocation.value.lng,
},
address: selectedLocation.value.address || '',
})
} else {
$message.warning('请先选择位置')
}
}
// 重新定位到当前位置
const relocateToCurrentPosition = () => {
if (currentLocation.value) {
// 如果已有当前位置信息,直接使用
const lnglat = new window.T.LngLat(currentLocation.value.lng, currentLocation.value.lat)
addMarker(lnglat)
if (selectedLocation.value) {
selectedLocation.value.address = currentLocation.value.address
}
} else {
// 重新获取当前位置
getCurrentLocation()
}
showSearchResults.value = false
}
// 加载天地图API
const loadTiandituScript = () => {
return new Promise((resolve, reject) => {
if (typeof T !== 'undefined') {
resolve()
return
}
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = `https://api.tianditu.gov.cn/api?v=4.0&tk=${TDT_KEY}`
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
}
onMounted(async () => {
try {
await loadTiandituScript()
await nextTick()
initMap()
} catch (error) {
console.error('加载天地图API失败:', error)
$message.error('地图加载失败,请检查网络连接')
}
})
onUnmounted(() => {
if (map) {
map = null
marker = null
}
if (handleInput.timer) {
clearTimeout(handleInput.timer)
}
})
</script>
<style scoped lang="scss">
.tianditu-picker {
display: flex;
flex-direction: column;
height: 100%;
background-color: #f5f5f5;
.search-header {
padding: 12px 0;
background-color: #fff;
border-bottom: 1px solid #eee;
z-index: 1000;
}
.map-container {
flex: 1;
width: 100%;
min-height: 400px;
}
.location-footer {
background-color: #fff;
border-top: 1px solid #eee;
max-height: 300px;
overflow-y: auto;
.results-section {
.results-header {
padding: 12px 16px;
font-size: 14px;
font-weight: 600;
color: #333;
background-color: #f5f5f5;
border-bottom: 1px solid #eee;
}
.results-list {
max-height: 180px;
overflow-y: auto;
.result-item {
display: flex;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: background-color 0.2s;
&:hover {
background-color: #f5f5f5;
}
&:last-child {
border-bottom: none;
}
.location-icon {
font-size: 18px;
margin-right: 12px;
flex-shrink: 0;
}
.result-content {
flex: 1;
.result-name {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 4px;
}
.result-address {
font-size: 12px;
color: #666;
line-height: 1.4;
}
}
}
.current-location-item {
background-color: #f8f9fa;
}
.search-result-item {
background-color: #fff;
}
}
}
.action-buttons {
padding: 12px 16px;
border-top: 1px solid #f0f0f0;
background-color: #fff;
}
}
}
// 深色模式适配
.dark {
.tianditu-picker {
background-color: #1f1f1f;
.search-header {
background-color: #2a2a2a;
border-bottom-color: #3a3a3a;
}
.location-footer {
background-color: #2a2a2a;
border-top-color: #3a3a3a;
.results-section {
.results-header {
background-color: #333;
color: #fff;
border-bottom-color: #3a3a3a;
}
.results-list {
.result-item {
border-bottom-color: #3a3a3a;
&:hover {
background-color: #333;
}
.result-name {
color: #fff;
}
.result-address {
color: #ccc;
}
}
.current-location-item {
background-color: #333;
}
.search-result-item {
background-color: #333;
}
}
}
.action-buttons {
background-color: #2a2a2a;
border-top-color: #3a3a3a;
}
}
}
}
</style>

View File

@@ -1,6 +1,7 @@
<script setup>
import api from '../api'
import Upload from '@/components/Upload.vue'
import TiandituPicker from '@/components/TiandituPicker.vue'
onMounted(() => {
getInfo()
@@ -114,6 +115,13 @@ window.addEventListener('message', (res) => {
showModal.value = false
})
const confirm = (data) => {
model.value.lt = `${data.latlng.lat},${data.latlng.lng}`
model.value.lat = data.latlng.lat
model.value.lon = data.latlng.lng
showModal.value = false
}
const submit = () => {
formRef.value?.validate(async (errors) => {
if (!errors) {
@@ -214,19 +222,14 @@ const submit = () => {
<!-- h5地图 -->
<n-modal v-if="showModal" v-model:show="showModal">
<n-card
style="width: 600px; height: 600px"
style="width: 600px; height: auto"
title="查找地图位置"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<iframe
src="https://apis.map.qq.com/tools/locpicker?type=1&key=4EJBZ-TZXCV-IHUPX-UMI2L-MK3N3-37FSQ&referer=myapp"
width="100%"
height="100%"
frameborder="0"
></iframe>
<TiandituPicker @confirm="confirm" @cancel="showModal = false" />
</n-card>
</n-modal>
</CommonPage>