|
|
|
@ -53,11 +53,16 @@ |
|
|
|
:value="item.id" |
|
|
|
/> |
|
|
|
</el-select> |
|
|
|
<el-input v-model="detailAddress" placeholder="输入地区" /> |
|
|
|
<el-input |
|
|
|
v-model="detailAddress" |
|
|
|
placeholder="输入详细地址" |
|
|
|
@keyup.enter="handleAddressSearch" |
|
|
|
@blur="handleAddressBlur" |
|
|
|
/> |
|
|
|
<el-button |
|
|
|
type="primary" |
|
|
|
@click="handleAddressSearch" |
|
|
|
:disabled="!province || !city || !district" |
|
|
|
:disabled="!province || !city" |
|
|
|
>{{ t('search') }}</el-button |
|
|
|
> |
|
|
|
</div> |
|
|
|
@ -127,12 +132,140 @@ const provinceList = ref<any[]>([]) |
|
|
|
const cityList = ref<any[]>([]) |
|
|
|
const districtList = ref<any[]>([]) |
|
|
|
|
|
|
|
// ====================== 地图相关方法 ====================== |
|
|
|
const cleanupMap = () => { |
|
|
|
if (mapScript) { |
|
|
|
try { |
|
|
|
document.body.removeChild(mapScript) |
|
|
|
} catch (e) { |
|
|
|
console.warn('移除地图脚本失败:', e) |
|
|
|
} |
|
|
|
mapScript = null |
|
|
|
} |
|
|
|
|
|
|
|
if (map) { |
|
|
|
try { |
|
|
|
map.destroy() |
|
|
|
} catch (e) { |
|
|
|
console.warn('销毁地图实例失败:', e) |
|
|
|
} |
|
|
|
map = null |
|
|
|
} |
|
|
|
|
|
|
|
marker = null |
|
|
|
|
|
|
|
if (resizeTimer) { |
|
|
|
clearTimeout(resizeTimer) |
|
|
|
resizeTimer = null |
|
|
|
} |
|
|
|
|
|
|
|
window.removeEventListener('resize', handleResize) |
|
|
|
} |
|
|
|
|
|
|
|
const loadMapSDK = (): Promise<void> => { |
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
// 检查是否已经加载 |
|
|
|
if ((window as any).TMap) { |
|
|
|
resolve() |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
mapKey.value = import.meta.env.VITE_MAP_KEY |
|
|
|
mapScript = document.createElement('script') |
|
|
|
mapScript.type = 'text/javascript' |
|
|
|
mapScript.src = `https://map.qq.com/api/gljs?libraries=tools,service&v=1.exp&key=${mapKey.value}` |
|
|
|
|
|
|
|
mapScript.onload = () => { |
|
|
|
if ((window as any).TMap) { |
|
|
|
resolve() |
|
|
|
} else { |
|
|
|
reject(new Error('TMap未定义')) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
mapScript.onerror = (error) => { |
|
|
|
reject(error) |
|
|
|
} |
|
|
|
|
|
|
|
document.body.appendChild(mapScript) |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
const initMap = () => { |
|
|
|
const container = document.getElementById('container') |
|
|
|
if (!(window as any).TMap || !container) { |
|
|
|
throw new Error('地图SDK未加载或容器不存在') |
|
|
|
} |
|
|
|
|
|
|
|
const TMap = (window as any).TMap |
|
|
|
let center |
|
|
|
|
|
|
|
// 优先使用传入的坐标 |
|
|
|
if (props.modelValue.lat && props.modelValue.lng) { |
|
|
|
center = new TMap.LatLng(props.modelValue.lat, props.modelValue.lng) |
|
|
|
} else { |
|
|
|
// 默认使用北京坐标 |
|
|
|
center = new TMap.LatLng(39.90403, 116.407526) |
|
|
|
} |
|
|
|
|
|
|
|
// 创建地图实例 |
|
|
|
map = new TMap.Map('container', { |
|
|
|
center, |
|
|
|
zoom: 12, |
|
|
|
}) |
|
|
|
|
|
|
|
// 创建标记 |
|
|
|
marker = createMarker(map) |
|
|
|
|
|
|
|
// 初始化时设置标记位置 |
|
|
|
if (props.modelValue.lat && props.modelValue.lng) { |
|
|
|
marker.updateGeometries({ |
|
|
|
id: 'center', |
|
|
|
position: new TMap.LatLng(props.modelValue.lat, props.modelValue.lng), |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
// 地图点击事件 |
|
|
|
map.on('click', (evt: any) => { |
|
|
|
map.setCenter(evt.latLng) |
|
|
|
marker.updateGeometries({ |
|
|
|
id: 'center', |
|
|
|
position: evt.latLng, |
|
|
|
}) |
|
|
|
emit('update:modelValue', { |
|
|
|
lat: evt.latLng.lat, |
|
|
|
lng: evt.latLng.lng, |
|
|
|
address: detailAddress.value, |
|
|
|
}) |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
const bindResizeListener = () => { |
|
|
|
window.addEventListener('resize', handleResize) |
|
|
|
} |
|
|
|
|
|
|
|
const handleResize = () => { |
|
|
|
if (resizeTimer) clearTimeout(resizeTimer) |
|
|
|
resizeTimer = setTimeout(() => { |
|
|
|
if (map && typeof map.setSize === 'function') { |
|
|
|
map.setSize() |
|
|
|
} else if (map && typeof map.getSize === 'function') { |
|
|
|
// 对于一些版本,可能需要使用不同的方法 |
|
|
|
map.invalidateSize && map.invalidateSize() |
|
|
|
} |
|
|
|
}, 300) |
|
|
|
} |
|
|
|
|
|
|
|
// ====================== 错误处理 ====================== |
|
|
|
const handleError = (error: any) => { |
|
|
|
console.error('地图组件错误:', error) |
|
|
|
ElMessage.error('地图加载失败,请重试') |
|
|
|
} |
|
|
|
|
|
|
|
// ====================== 生命周期 ====================== |
|
|
|
onMounted(() => { |
|
|
|
// 预加载地图SDK(可选优化) |
|
|
|
const preloadScript = document.createElement('script') |
|
|
|
preloadScript.src = `https://map.qq.com/api/gljs?key= ${mapKey.value}` |
|
|
|
document.head.appendChild(preloadScript) |
|
|
|
mapKey.value = import.meta.env.VITE_MAP_KEY || 'YOUR_API_KEY' |
|
|
|
}) |
|
|
|
|
|
|
|
// ====================== 监听器 ====================== |
|
|
|
@ -211,190 +344,137 @@ const handleConfirm = () => { |
|
|
|
dialogVisible.value = false |
|
|
|
} |
|
|
|
|
|
|
|
// 地址解析逻辑 |
|
|
|
// 地址解析逻辑(增强版) |
|
|
|
const parseAndSetAddress = async (address: string) => { |
|
|
|
if (!address) return |
|
|
|
|
|
|
|
try { |
|
|
|
// 解析地址 |
|
|
|
const { provinceName, cityName, districtName, detail } = parseAddress(address) |
|
|
|
detailAddress.value = detail |
|
|
|
|
|
|
|
// 1. 处理省份选择 |
|
|
|
// 1. 处理省级选择 |
|
|
|
if (provinceName) { |
|
|
|
const provinceItem = provinceList.value.find((p) => |
|
|
|
p.name.includes(provinceName) |
|
|
|
) |
|
|
|
const provinceItem = provinceList.value.find((p) => { |
|
|
|
return p.name.includes(provinceName.replace(/[省市区]/g, '')) || |
|
|
|
p.name === provinceName |
|
|
|
}) |
|
|
|
|
|
|
|
if (provinceItem) { |
|
|
|
province.value = provinceItem.id |
|
|
|
|
|
|
|
// 2. 处理城市选择 |
|
|
|
// 2. 处理地级市选择 |
|
|
|
const cityRes = await getAreaListByPid(province.value) |
|
|
|
cityList.value = cityRes.data |
|
|
|
|
|
|
|
if (cityName) { |
|
|
|
const cityItem = cityList.value.find((c) => c.name.includes(cityName)) |
|
|
|
const cityItem = cityList.value.find((c) => { |
|
|
|
return c.name.includes(cityName.replace(/[市盟州]/g, '')) || |
|
|
|
c.name === cityName |
|
|
|
}) |
|
|
|
|
|
|
|
if (cityItem) { |
|
|
|
city.value = cityItem.id |
|
|
|
|
|
|
|
// 3. 处理区县选择 |
|
|
|
// 3. 处理区县级选择 |
|
|
|
const districtRes = await getAreaListByPid(city.value) |
|
|
|
districtList.value = districtRes.data |
|
|
|
|
|
|
|
if (districtName) { |
|
|
|
const districtItem = districtList.value.find((d) => |
|
|
|
d.name.includes(districtName) |
|
|
|
) |
|
|
|
const districtItem = districtList.value.find((d) => { |
|
|
|
return d.name.includes(districtName.replace(/[区县旗市]/g, '')) || |
|
|
|
d.name === districtName |
|
|
|
}) |
|
|
|
|
|
|
|
if (districtItem) { |
|
|
|
district.value = districtItem.id |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} else if (provinceName.includes('市')) { |
|
|
|
// 如果省名包含"市",可能是直辖市,直接在市级查找 |
|
|
|
const directCity = cityList.value.find((c) => c.name === provinceName) |
|
|
|
if (directCity) { |
|
|
|
city.value = directCity.id |
|
|
|
|
|
|
|
// 触发地址搜索 |
|
|
|
handleAddressSearch() |
|
|
|
} |
|
|
|
const districtRes = await getAreaListByPid(city.value) |
|
|
|
districtList.value = districtRes.data |
|
|
|
|
|
|
|
// 地址拆分函数 |
|
|
|
const parseAddress = (address: string) => { |
|
|
|
let provinceName = '' |
|
|
|
let cityName = '' |
|
|
|
let districtName = '' |
|
|
|
let detail = address |
|
|
|
if (districtName) { |
|
|
|
const districtItem = districtList.value.find((d) => { |
|
|
|
return d.name.includes(districtName.replace(/[区县旗市]/g, '')) || |
|
|
|
d.name === districtName |
|
|
|
}) |
|
|
|
|
|
|
|
// 提取省/直辖市/自治区 |
|
|
|
const provinceMatch = address.match(/^([^省市自治区]+[省市区]|[^市]+市)/) |
|
|
|
if (provinceMatch) { |
|
|
|
provinceName = provinceMatch[0] |
|
|
|
detail = address.slice(provinceName.length) |
|
|
|
if (districtItem) { |
|
|
|
district.value = districtItem.id |
|
|
|
} |
|
|
|
|
|
|
|
// 提取市 |
|
|
|
const cityMatch = detail.match(/^([^市区]+市)/) |
|
|
|
if (cityMatch) { |
|
|
|
cityName = cityMatch[0] |
|
|
|
detail = detail.slice(cityName.length) |
|
|
|
} |
|
|
|
|
|
|
|
// 提取区/县 |
|
|
|
const districtMatch = detail.match(/^([^区县]+[区县])/) |
|
|
|
if (districtMatch) { |
|
|
|
districtName = districtMatch[0] |
|
|
|
detail = detail.slice(districtName.length) |
|
|
|
} |
|
|
|
|
|
|
|
return { |
|
|
|
provinceName, |
|
|
|
cityName, |
|
|
|
districtName, |
|
|
|
detail: detail.trim() || '', |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// ====================== 地图相关方法 ====================== |
|
|
|
const cleanupMap = () => { |
|
|
|
if (mapScript) { |
|
|
|
document.body.removeChild(mapScript) |
|
|
|
mapScript = null |
|
|
|
} |
|
|
|
|
|
|
|
if (map) { |
|
|
|
map.destroy() |
|
|
|
map = null |
|
|
|
} |
|
|
|
|
|
|
|
marker = null |
|
|
|
|
|
|
|
if (resizeTimer) { |
|
|
|
clearTimeout(resizeTimer) |
|
|
|
resizeTimer = null |
|
|
|
// 等待下一个事件循环,然后触发地址搜索 |
|
|
|
await nextTick() |
|
|
|
if (province.value && city.value) { |
|
|
|
handleAddressSearch() |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.warn('地址解析失败:', error) |
|
|
|
} |
|
|
|
|
|
|
|
window.removeEventListener('resize', handleResize) |
|
|
|
} |
|
|
|
|
|
|
|
const loadMapSDK = (): Promise<void> => { |
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
mapKey.value = import.meta.env.VITE_MAP_KEY |
|
|
|
mapScript = document.createElement('script') |
|
|
|
mapScript.type = 'text/javascript' |
|
|
|
mapScript.src = `https://map.qq.com/api/gljs?libraries=tools ,service&v=1.exp&key=${mapKey.value}` |
|
|
|
// 地址拆分函数(增强版) |
|
|
|
const parseAddress = (address: string) => { |
|
|
|
if (!address) return { provinceName: '', cityName: '', districtName: '', detail: '' } |
|
|
|
|
|
|
|
mapScript.onload = () => { |
|
|
|
if ((window as any).TMap) { |
|
|
|
resolve() |
|
|
|
} else { |
|
|
|
reject(new Error('TMap未定义')) |
|
|
|
let provinceName = '' |
|
|
|
let cityName = '' |
|
|
|
let districtName = '' |
|
|
|
let detail = address.trim() |
|
|
|
|
|
|
|
// 提取省级行政区(省/直辖市/自治区/特别行政区) |
|
|
|
const provincePatterns = [ |
|
|
|
/^([^省市自治区特别行政人民共和]+[省自治区])/, // XX省/XX自治区 |
|
|
|
/^(北京市|上海市|天津市|重庆市)/, // 直辖市 |
|
|
|
/^([^市]+特别行政区)/, // 特别行政区 |
|
|
|
] |
|
|
|
|
|
|
|
for (const pattern of provincePatterns) { |
|
|
|
const match = detail.match(pattern) |
|
|
|
if (match) { |
|
|
|
provinceName = match[1] |
|
|
|
detail = detail.slice(provinceName.length) |
|
|
|
break |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
mapScript.onerror = (error) => { |
|
|
|
reject(error) |
|
|
|
// 提取地级市(除非是直辖市) |
|
|
|
if (!provinceName.includes('市')) { |
|
|
|
const cityMatch = detail.match(/^([^市区县盟州]+[市盟州])/) |
|
|
|
if (cityMatch) { |
|
|
|
cityName = cityMatch[1] |
|
|
|
detail = detail.slice(cityName.length) |
|
|
|
} |
|
|
|
|
|
|
|
document.body.appendChild(mapScript) |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
const initMap = () => { |
|
|
|
const container = document.getElementById('container') |
|
|
|
if (!(window as any).TMap || !container) { |
|
|
|
throw new Error('地图SDK未加载或容器不存在') |
|
|
|
} |
|
|
|
|
|
|
|
const TMap = (window as any).TMap |
|
|
|
let center |
|
|
|
|
|
|
|
// 优先使用传入的坐标 |
|
|
|
if (props.modelValue.lat && props.modelValue.lng) { |
|
|
|
center = new TMap.LatLng(props.modelValue.lat, props.modelValue.lng) |
|
|
|
} else { |
|
|
|
// 默认使用北京坐标 |
|
|
|
center = new TMap.LatLng(39.90403, 116.407526) |
|
|
|
// 提取区县级 |
|
|
|
const districtMatch = detail.match(/^([^区县旗市]+[区县旗市])/) |
|
|
|
if (districtMatch) { |
|
|
|
districtName = districtMatch[1] |
|
|
|
detail = detail.slice(districtName.length) |
|
|
|
} |
|
|
|
|
|
|
|
// 创建地图实例 |
|
|
|
map = new TMap.Map('container', { |
|
|
|
center, |
|
|
|
zoom: 12, |
|
|
|
}) |
|
|
|
|
|
|
|
// 创建标记 |
|
|
|
marker = createMarker(map) |
|
|
|
|
|
|
|
// 初始化时设置标记位置 |
|
|
|
if (props.modelValue.lat && props.modelValue.lng) { |
|
|
|
marker.updateGeometries({ |
|
|
|
id: 'center', |
|
|
|
position: new TMap.LatLng(props.modelValue.lat, props.modelValue.lng), |
|
|
|
}) |
|
|
|
return { |
|
|
|
provinceName: provinceName.trim(), |
|
|
|
cityName: cityName.trim(), |
|
|
|
districtName: districtName.trim(), |
|
|
|
detail: detail.trim(), |
|
|
|
} |
|
|
|
|
|
|
|
// 地图点击事件 |
|
|
|
map.on('click', (evt: any) => { |
|
|
|
map.setCenter(evt.latLng) |
|
|
|
marker.updateGeometries({ |
|
|
|
id: 'center', |
|
|
|
position: evt.latLng, |
|
|
|
}) |
|
|
|
emit('update:modelValue', { |
|
|
|
lat: evt.latLng.lat, |
|
|
|
lng: evt.latLng.lng, |
|
|
|
address: detailAddress.value, |
|
|
|
}) |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
const bindResizeListener = () => { |
|
|
|
window.addEventListener('resize', handleResize) |
|
|
|
} |
|
|
|
|
|
|
|
const handleResize = () => { |
|
|
|
if (resizeTimer) clearTimeout(resizeTimer) |
|
|
|
resizeTimer = setTimeout(() => { |
|
|
|
map?.setSize() |
|
|
|
}, 300) |
|
|
|
} |
|
|
|
|
|
|
|
// ====================== 区域选择处理 ====================== |
|
|
|
const handleProvinceChange = async (val: string) => { |
|
|
|
@ -419,32 +499,54 @@ const handleCityChange = async (val: string) => { |
|
|
|
} |
|
|
|
|
|
|
|
const handleDistrictChange = (val: string) => { |
|
|
|
// TODO: 处理区县选择 |
|
|
|
// 区县选择完成,可以自动触发搜索 |
|
|
|
if (val && detailAddress.value.trim()) { |
|
|
|
handleAddressSearch() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 地址输入框失焦处理 |
|
|
|
const handleAddressBlur = () => { |
|
|
|
// 如果已选择省市且输入了详细地址,自动搜索 |
|
|
|
if (province.value && city.value && detailAddress.value.trim()) { |
|
|
|
handleAddressSearch() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 地址搜索 |
|
|
|
const handleAddressSearch = async () => { |
|
|
|
try { |
|
|
|
// 检查必要参数 |
|
|
|
if (!province.value || !city.value) { |
|
|
|
ElMessage.warning('请选择省市') |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
const address = [ |
|
|
|
province.value |
|
|
|
? provinceList.value.find((p) => p.id === province.value)?.name |
|
|
|
: '', |
|
|
|
city.value ? cityList.value.find((c) => c.id === city.value)?.name : '', |
|
|
|
provinceList.value.find((p) => p.id === province.value)?.name || '', |
|
|
|
cityList.value.find((c) => c.id === city.value)?.name || '', |
|
|
|
district.value |
|
|
|
? districtList.value.find((d) => d.id === district.value)?.name |
|
|
|
? districtList.value.find((d) => d.id === district.value)?.name || '' |
|
|
|
: '', |
|
|
|
detailAddress.value, |
|
|
|
].join('') |
|
|
|
detailAddress.value.trim(), |
|
|
|
].filter(Boolean).join('') |
|
|
|
|
|
|
|
if (!address.trim()) { |
|
|
|
ElMessage.warning('请输入地址') |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
const { message, result } = await addressToLatLng({ |
|
|
|
const result = await addressToLatLng({ |
|
|
|
mapKey: mapKey.value, |
|
|
|
address, |
|
|
|
}) |
|
|
|
|
|
|
|
if (message == 'Success' || message == 'query ok') { |
|
|
|
// 处理不同的响应状态 |
|
|
|
if (result.status === 0 && result.result) { |
|
|
|
// 成功获取到坐标 |
|
|
|
const latLng = new (window as any).TMap.LatLng( |
|
|
|
result.location.lat, |
|
|
|
result.location.lng |
|
|
|
result.result.location.lat, |
|
|
|
result.result.location.lng |
|
|
|
) |
|
|
|
map.setCenter(latLng) |
|
|
|
marker.updateGeometries({ |
|
|
|
@ -452,22 +554,47 @@ const handleAddressSearch = async () => { |
|
|
|
position: latLng, |
|
|
|
}) |
|
|
|
emit('update:modelValue', { |
|
|
|
lat: result.location.lat, |
|
|
|
lng: result.location.lng, |
|
|
|
address: detailAddress.value, |
|
|
|
lat: result.result.location.lat, |
|
|
|
lng: result.result.location.lng, |
|
|
|
address: address, // 使用完整地址 |
|
|
|
}) |
|
|
|
ElMessage.success('地址搜索成功') |
|
|
|
} else if (result.status === 112) { |
|
|
|
// IP授权问题的特殊处理 |
|
|
|
ElMessage({ |
|
|
|
type: 'warning', |
|
|
|
message: '地址搜索暂不可用(API授权限制),请直接在地图上点击选择位置', |
|
|
|
duration: 5000 |
|
|
|
}) |
|
|
|
console.warn('腾讯地图API IP授权限制:', result.message) |
|
|
|
|
|
|
|
// 仍然更新地址信息,但不移动地图位置 |
|
|
|
emit('update:modelValue', { |
|
|
|
lat: props.modelValue.lat || null, |
|
|
|
lng: props.modelValue.lng || null, |
|
|
|
address: address, // 保存用户输入的地址 |
|
|
|
}) |
|
|
|
} else { |
|
|
|
// 其他错误 |
|
|
|
ElMessage.warning(`搜索失败:${result.message || '未找到对应地址,请检查输入'}`) |
|
|
|
} |
|
|
|
} catch (error: any) { |
|
|
|
console.error('地址搜索异常:', error) |
|
|
|
|
|
|
|
// 检查是否是IP授权问题 |
|
|
|
if (error.status === 112 || (error.message && error.message.includes('IP未被授权'))) { |
|
|
|
ElMessage({ |
|
|
|
type: 'info', |
|
|
|
message: '地址搜索暂不可用,请直接在地图上点击选择位置', |
|
|
|
duration: 5000 |
|
|
|
}) |
|
|
|
} else { |
|
|
|
ElMessage.error('搜索失败,请重试') |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
handleError(error) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// ====================== 错误处理 ====================== |
|
|
|
const handleError = (error: any) => { |
|
|
|
console.error('地图组件错误:', error) |
|
|
|
ElMessage.error(t('mapLoadFailed')) |
|
|
|
dialogVisible.value = false |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 回显处理 |
|
|
|
watch( |
|
|
|
|