更新SelectMap组件以使用新版腾讯地图API,优化地图加载和用户位置获取逻辑,增强错误处理和用户体验。

This commit is contained in:
超级老白兔
2025-11-22 11:45:53 +08:00
parent 782031cbc6
commit 21de87ca32
3 changed files with 534 additions and 283 deletions

View File

@@ -13,7 +13,7 @@
<script type="text/javascript" src="/websdk.js"></script>
<script
charset="utf-8"
src="https://map.qq.com/api/js?v=2.exp&key=5ZSBZ-23ICU-XDKVU-4ZQ7Z-O35AJ-XUF6S"
src="https://map.qq.com/api/gljs?v=1.exp&libraries=service&key=7DZBZ-ZSRK3-QJN3W-O5VTV-4E2P6-7GFYX"
></script>
</head>
<body>

View File

@@ -4,10 +4,11 @@ import { SearchOutlined, EnvironmentOutlined } from "@ant-design/icons";
import { useWebSocketStore } from "@/store/module/websocket/websocket";
import styles from "./selectMap.module.scss";
// 声明腾讯地图类型
// 声明腾讯地图类型新版TMap API
declare global {
interface Window {
qq: any;
TMap: any;
geolocationRef: any; // 全局IP定位服务引用
}
}
@@ -58,281 +59,454 @@ const SelectMap: React.FC<SelectMapProps> = ({
const [map, setMap] = useState<any>(null);
const [isReverseGeocoding, setIsReverseGeocoding] = useState(false);
const [isLocating, setIsLocating] = useState(false);
const [tmapLoaded, setTmapLoaded] = useState(false);
const mapContainerRef = useRef<HTMLDivElement>(null);
const geocoderRef = useRef<any>(null);
const searchServiceRef = useRef<any>(null);
const suggestServiceRef = useRef<any>(null);
const markerRef = useRef<any>(null);
const { sendCommand } = useWebSocketStore.getState();
// 加载腾讯地图SDK
useEffect(() => {
// 检查TMap是否已经加载
if (window.TMap) {
setTmapLoaded(true);
return;
}
// 动态加载腾讯地图SDK使用与index.html相同的密钥
const script = document.createElement("script");
script.src =
"https://map.qq.com/api/gljs?v=1.exp&libraries=service&key=7DZBZ-ZSRK3-QJN3W-O5VTV-4E2P6-7GFYX";
script.async = true;
script.onload = () => {
console.log("腾讯地图SDK加载成功");
setTmapLoaded(true);
};
script.onerror = () => {
console.error("腾讯地图SDK加载失败");
message.error("地图加载失败,请刷新页面重试");
};
document.head.appendChild(script);
return () => {
// 清理script标签
if (document.head.contains(script)) {
document.head.removeChild(script);
}
};
}, []);
// 初始化地图
useEffect(() => {
if (visible && mapContainerRef.current && window.qq && window.qq.maps) {
// 创建地图实例
const center = new window.qq.maps.LatLng(39.908823, 116.39747); // 默认北京
const mapInstance = new window.qq.maps.Map(mapContainerRef.current, {
center: center,
zoom: 13,
});
if (visible && mapContainerRef.current && tmapLoaded && window.TMap) {
console.log("开始初始化地图");
setMap(mapInstance);
// 创建地理编码服务
geocoderRef.current = new window.qq.maps.Geocoder({
complete: (result: any) => {
setIsReverseGeocoding(false);
try {
if (result && result.detail) {
const detail = result.detail;
const location = detail.location || detail.latLng;
if (location) {
const lat =
location.lat || (location.getLat ? location.getLat() : null);
const lng =
location.lng || (location.getLng ? location.getLng() : null);
if (lat && lng) {
// 构建地址标签
let addressLabel = "";
if (detail.formatted_addresses) {
addressLabel =
detail.formatted_addresses.recommend ||
detail.formatted_addresses.rough ||
"";
}
if (!addressLabel && detail.address) {
addressLabel = detail.address;
}
if (!addressLabel && detail.addressComponents) {
const addr = detail.addressComponents;
const parts = [];
if (addr.province) parts.push(addr.province);
if (addr.city) parts.push(addr.city);
if (addr.district) parts.push(addr.district);
if (addr.street) parts.push(addr.street);
if (addr.street_number) parts.push(addr.street_number);
addressLabel = parts.join("");
}
if (!addressLabel) {
addressLabel = `${lat.toFixed(6)}, ${lng.toFixed(6)}`;
}
setSelectedLocation({
x: lng.toString(),
y: lat.toString(),
scale: "16",
label: addressLabel,
poiname:
detail.addressComponents?.street || detail.poiid || "",
maptype: "0",
poiid: detail.poiid || "",
});
} else {
message.warning("无法解析位置信息");
}
} else {
message.warning("未找到位置信息");
}
} else {
message.warning("获取地址信息失败:返回数据为空");
}
} catch (error) {
console.error("解析地址信息错误:", error);
message.error("解析地址信息失败");
}
},
error: (error: any) => {
setIsReverseGeocoding(false);
console.error("反向地理编码错误:", error);
message.error("获取地址信息失败,请稍后重试");
},
});
// 创建搜索服务
searchServiceRef.current = new window.qq.maps.SearchService({
complete: (result: any) => {
setIsSearching(false);
if (result && result.detail) {
const pois = result.detail.pois || [];
if (pois.length > 0) {
const searchResults = pois.map((poi: any) => {
const location = poi.location || poi.latLng;
return {
id:
poi.id ||
`${location.lat || location.getLat()},${
location.lng || location.getLng()
}`,
title: poi.title || poi.name || "",
address: poi.address || poi.ad_info?.adcode || "",
location: {
lat: location.lat || location.getLat(),
lng: location.lng || location.getLng(),
},
adcode: poi.adcode || poi.ad_info?.adcode || "",
city: poi.city || poi.ad_info?.city || "",
district: poi.district || poi.ad_info?.district || "",
};
});
setSearchResults(searchResults);
} else {
setSearchResults([]);
message.info("未找到相关地址");
}
} else {
setSearchResults([]);
message.info("未找到相关地址");
}
},
error: () => {
setIsSearching(false);
message.error("搜索失败,请重试");
},
});
// 地图点击事件
window.qq.maps.event.addListener(mapInstance, "click", (event: any) => {
const lat = event.latLng.lat;
const lng = event.latLng.lng;
// 更新标记点
if (markerRef.current) {
markerRef.current.setMap(null);
}
const newMarker = new window.qq.maps.Marker({
position: new window.qq.maps.LatLng(lat, lng),
map: mapInstance,
try {
// 创建地图实例
const center = new window.TMap.LatLng(39.908823, 116.39747); // 默认北京
const mapInstance = new window.TMap.Map(mapContainerRef.current, {
center: center,
zoom: 13,
rotation: 0,
pitch: 0,
});
markerRef.current = newMarker;
setMap(mapInstance);
// 反向地理编码
if (geocoderRef.current) {
setIsReverseGeocoding(true);
geocoderRef.current.getAddress(new window.qq.maps.LatLng(lat, lng));
}
});
// 创建地理编码服务(用于反向地理编码
geocoderRef.current = new window.TMap.service.Geocoder();
// 获取用户当前位置
if (navigator.geolocation) {
setIsLocating(true);
navigator.geolocation.getCurrentPosition(
position => {
setIsLocating(false);
const userLat = position.coords.latitude;
const userLng = position.coords.longitude;
// 使用腾讯地图内置的定位服务
window.geolocationRef = window.TMap.service.Geolocation;
// 移动地图中心到用户位置
const userLocation = new window.qq.maps.LatLng(userLat, userLng);
mapInstance.setCenter(userLocation);
mapInstance.setZoom(16);
// 创建搜索建议服务
suggestServiceRef.current = new window.TMap.service.Suggestion({
pageSize: 10,
autoExtend: true,
});
// 添加标记点
// 地图点击事件处理函数
const handleMapClick = (evt: any) => {
try {
const lat = evt.latLng.getLat();
const lng = evt.latLng.getLng();
console.log("地图点击:", lat, lng);
// 更新标记点
if (markerRef.current) {
markerRef.current.setMap(null);
markerRef.current = null;
}
const newMarker = new window.qq.maps.Marker({
position: userLocation,
// 创建新标记
const newMarker = new window.TMap.MultiMarker({
id: "marker-layer",
map: mapInstance,
geometries: [
{
id: "selected-marker",
styleId: "marker",
position: new window.TMap.LatLng(lat, lng),
properties: {
title: "选中位置",
},
},
],
});
markerRef.current = newMarker;
// 获取用户位置的地址信息
if (geocoderRef.current) {
setIsReverseGeocoding(true);
geocoderRef.current.getAddress(userLocation);
}
},
error => {
// 设置基本位置信息(防止白屏)
setSelectedLocation({
x: lng.toString(),
y: lat.toString(),
scale: "16",
label: `${lat.toFixed(6)}, ${lng.toFixed(6)}`,
poiname: "选中位置",
maptype: "0",
poiid: "",
});
// 反向地理编码获取地址
setIsReverseGeocoding(true);
geocoderRef.current
.getAddress({ location: new window.TMap.LatLng(lat, lng) })
.then((result: any) => {
setIsReverseGeocoding(false);
console.log("反向地理编码结果:", result);
try {
if (result && result.result) {
const resultData = result.result;
const address = resultData.address || "";
const addressComponent = resultData.address_component || {};
const formattedAddresses =
resultData.formatted_addresses || {};
// 构建地址标签
let addressLabel =
formattedAddresses.recommend ||
formattedAddresses.rough ||
address;
if (!addressLabel) {
const parts = [];
if (addressComponent.province)
parts.push(addressComponent.province);
if (addressComponent.city)
parts.push(addressComponent.city);
if (addressComponent.district)
parts.push(addressComponent.district);
if (addressComponent.street)
parts.push(addressComponent.street);
if (addressComponent.street_number)
parts.push(addressComponent.street_number);
addressLabel = parts.join("");
}
if (!addressLabel) {
addressLabel = `${lat.toFixed(6)}, ${lng.toFixed(6)}`;
}
setSelectedLocation({
x: lng.toString(),
y: lat.toString(),
scale: "16",
label: addressLabel,
poiname: addressComponent.street || "未知位置",
maptype: "0",
poiid: resultData.poi_id || "",
});
} else {
message.warning("获取详细地址信息失败,将使用坐标显示");
}
} catch (error) {
console.error("解析地址信息错误:", error);
message.warning("解析地址信息失败,将使用坐标显示");
}
})
.catch((error: any) => {
setIsReverseGeocoding(false);
console.error("反向地理编码错误:", error);
message.warning("获取详细地址信息失败,将使用坐标显示");
});
} catch (error) {
console.error("地图点击处理错误:", error);
message.error("处理地图点击时出错,请重试");
}
};
// 绑定地图点击事件
mapInstance.on("click", handleMapClick);
// 使用腾讯地图API初始化用户位置
const initializeUserLocation = (
lat: number,
lng: number,
isDefault: boolean = false,
) => {
console.log(isDefault ? "使用默认位置:" : "用户位置:", lat, lng);
// 移动地图中心到位置
const userLocation = new window.TMap.LatLng(lat, lng);
mapInstance.setCenter(userLocation);
mapInstance.setZoom(16);
// 添加标记点
if (markerRef.current) {
markerRef.current.setMap(null);
markerRef.current = null;
}
const newMarker = new window.TMap.MultiMarker({
id: "marker-layer",
map: mapInstance,
geometries: [
{
id: "user-location",
styleId: "marker",
position: userLocation,
properties: {
title: isDefault ? "默认位置" : "当前位置",
},
},
],
});
markerRef.current = newMarker;
// 使用腾讯地图服务获取该位置的地址信息
setIsReverseGeocoding(true);
geocoderRef.current
.getAddress({ location: userLocation })
.then((result: any) => {
setIsReverseGeocoding(false);
if (result && result.result) {
const resultData = result.result;
const formattedAddresses = resultData.formatted_addresses || {};
const addressComponent = resultData.address_component || {};
const addressLabel =
formattedAddresses.recommend ||
formattedAddresses.rough ||
resultData.address ||
`${lat.toFixed(6)}, ${lng.toFixed(6)}`;
setSelectedLocation({
x: lng.toString(),
y: lat.toString(),
scale: "16",
label: addressLabel,
poiname:
addressComponent.street ||
(isDefault ? "默认位置" : "当前位置"),
maptype: "0",
poiid: resultData.poi_id || "",
});
}
})
.catch((error: any) => {
setIsReverseGeocoding(false);
console.error("获取地址信息失败:", error);
// 即使获取地址失败,也设置基本的位置信息
setSelectedLocation({
x: lng.toString(),
y: lat.toString(),
scale: "16",
label: `${lat.toFixed(6)}, ${lng.toFixed(6)}`,
poiname: isDefault ? "默认位置" : "当前位置",
maptype: "0",
poiid: "",
});
});
};
// 使用腾讯地图IP定位获取用户位置
setIsLocating(true);
try {
if (window.geolocationRef) {
window.geolocationRef.getLocation({
timeout: 10000,
convert: true,
success: function (result: any) {
setIsLocating(false);
if (result && result.location) {
const { lat, lng } = result.location;
message.info("已定位到您的大致位置");
initializeUserLocation(lat, lng, false);
} else {
// IP定位失败使用默认位置
message.info("无法获取您的位置,已定位到北京");
// 使用默认位置(北京市)
initializeUserLocation(39.908823, 116.39747, true);
}
},
error: function () {
setIsLocating(false);
message.info("无法获取您的位置,已定位到北京");
// 使用默认位置(北京市)
initializeUserLocation(39.908823, 116.39747, true);
},
});
} else {
// 地理编码服务未初始化:使用默认位置
setIsLocating(false);
console.error("获取位置失败:", error);
// 如果获取位置失败,使用默认位置(北京)
message.info("无法获取您的位置,已定位到默认位置");
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0,
},
);
} else {
message.info("您的浏览器不支持地理定位功能");
message.info("无法获取您的位置,已定位到北京");
// 使用默认位置(北京
initializeUserLocation(39.908823, 116.39747, true);
}
} catch (error) {
// 捕获任何可能的错误,防止白屏
console.error("定位过程中发生错误:", error);
setIsLocating(false);
message.error("定位服务出现异常,已定位到北京");
// 使用默认位置(北京市)
initializeUserLocation(39.908823, 116.39747, true);
}
return () => {
// 清理地图事件监听
if (mapInstance) {
mapInstance.off("click", handleMapClick);
}
};
} catch (error) {
console.error("初始化地图时出错:", error);
message.error("地图加载失败,请刷新页面重试");
setIsLocating(false);
}
}
}, [visible, tmapLoaded]);
// 搜索地址(获取搜索建议)
const handleSearch = () => {
try {
if (!searchValue.trim()) {
message.warning("请输入搜索关键词");
return;
}
return () => {
if (mapInstance) {
window.qq.maps.event.clearListeners(mapInstance, "click");
}
};
}
}, [visible]);
if (!suggestServiceRef.current) {
message.error("搜索服务未初始化,请刷新页面重试");
return;
}
// 搜索地址
const handleSearch = () => {
if (!searchValue.trim()) {
message.warning("请输入搜索关键词");
return;
}
setIsSearching(true);
suggestServiceRef.current
.getSuggestions({
keyword: searchValue,
location: map ? map.getCenter() : undefined,
})
.then((result: any) => {
setIsSearching(false);
console.log("搜索建议结果:", result);
if (!searchServiceRef.current) {
message.error("搜索服务未初始化");
return;
if (result && result.data && result.data.length > 0) {
const searchResults = result.data.map((item: any) => ({
id: item.id,
title: item.title || item.name || "",
address: item.address || "",
location: {
lat: item.location.lat,
lng: item.location.lng,
},
adcode: item.adcode || "",
city: item.city || "",
district: item.district || "",
}));
setSearchResults(searchResults);
} else {
setSearchResults([]);
message.info("未找到相关地址");
}
})
.catch((error: any) => {
setIsSearching(false);
console.error("搜索失败:", error);
message.error("搜索失败,请重试");
// 确保搜索状态被重置
setSearchResults([]);
});
} catch (error) {
setIsSearching(false);
console.error("搜索处理错误:", error);
message.error("搜索过程中出错,请重试");
setSearchResults([]);
}
setIsSearching(true);
searchServiceRef.current.search(searchValue);
};
// 选择搜索结果
const handleSelectResult = (result: SearchResult) => {
if (!map) return;
try {
if (!map) {
message.error("地图未初始化,请刷新页面重试");
return;
}
const lat = result.location.lat;
const lng = result.location.lng;
const lat = result.location.lat;
const lng = result.location.lng;
// 移动地图中心
map.setCenter(new window.qq.maps.LatLng(lat, lng));
map.setZoom(16);
console.log("选择搜索结果:", result);
// 更新标记点
if (markerRef.current) {
markerRef.current.setMap(null);
// 移动地图中心
map.setCenter(new window.TMap.LatLng(lat, lng));
map.setZoom(16);
// 更新标记点
if (markerRef.current) {
markerRef.current.setMap(null);
markerRef.current = null;
}
const newMarker = new window.TMap.MultiMarker({
id: "marker-layer",
map: map,
geometries: [
{
id: "selected-poi",
styleId: "marker",
position: new window.TMap.LatLng(lat, lng),
properties: {
title: result.title,
},
},
],
});
markerRef.current = newMarker;
// 设置选中的位置信息
setSelectedLocation({
x: lng.toString(),
y: lat.toString(),
scale: "16",
label: result.address || result.title,
poiname: result.title || "",
maptype: "0",
poiid: result.id || "",
});
// 清空搜索结果
setSearchResults([]);
setSearchValue("");
} catch (error) {
console.error("选择搜索结果错误:", error);
message.error("选择位置时出错,请重试");
}
const newMarker = new window.qq.maps.Marker({
position: new window.qq.maps.LatLng(lat, lng),
map: map,
});
markerRef.current = newMarker;
// 设置选中的位置信息
setSelectedLocation({
x: lng.toString(),
y: lat.toString(),
scale: "16",
label: result.address || result.title,
poiname: result.title || "",
maptype: "0",
poiid: result.id || "",
});
// 清空搜索结果
setSearchResults([]);
setSearchValue("");
};
// 确认选择
const handleConfirm = () => {
if (!selectedLocation) {
message.warning("请先选择位置");
return;
}
try {
if (!selectedLocation) {
message.warning("请先选择位置");
return;
}
// 生成XML格式的位置信息
const locationXml = `<msg><location
// 生成XML格式的位置信息
const locationXml = `<msg><location
x="${selectedLocation.x}"
y="${selectedLocation.y}"
scale="${selectedLocation.scale}"
@@ -342,54 +516,58 @@ const SelectMap: React.FC<SelectMapProps> = ({
maptype="${selectedLocation.maptype}"
poiid="${selectedLocation.poiid}" /></msg>`;
// 如果有onConfirm回调调用它
if (onConfirm) {
onConfirm(locationXml);
// 如果有onConfirm回调调用它
if (onConfirm) {
onConfirm(locationXml);
}
// 如果有addMessage和contract发送位置消息
if (addMessage && contract) {
const messageId = +Date.now();
const localMessage = {
id: messageId,
wechatAccountId: contract.wechatAccountId,
wechatFriendId: contract?.chatroomId ? 0 : contract.id,
wechatChatroomId: contract?.chatroomId ? contract.id : 0,
tenantId: 0,
accountId: 0,
synergyAccountId: 0,
content: locationXml,
msgType: 48, // 位置消息类型
msgSubType: 0,
msgSvrId: "",
isSend: true,
createTime: new Date().toISOString(),
isDeleted: false,
deleteTime: "",
sendStatus: 1,
wechatTime: Date.now(),
origin: 0,
msgId: 0,
recalled: false,
seq: messageId,
};
addMessage(localMessage);
// 发送消息到服务器
sendCommand("CmdSendMessage", {
wechatAccountId: contract.wechatAccountId,
wechatChatroomId: contract?.chatroomId ? contract.id : 0,
wechatFriendId: contract?.chatroomId ? 0 : contract.id,
msgSubType: 0,
msgType: 48,
content: locationXml,
seq: messageId,
});
}
// 关闭弹窗并重置状态
handleClose();
} catch (error) {
console.error("确认位置时出错:", error);
message.error("发送位置信息时出错,请重试");
}
// 如果有addMessage和contract发送位置消息
if (addMessage && contract) {
const messageId = +Date.now();
const localMessage = {
id: messageId,
wechatAccountId: contract.wechatAccountId,
wechatFriendId: contract?.chatroomId ? 0 : contract.id,
wechatChatroomId: contract?.chatroomId ? contract.id : 0,
tenantId: 0,
accountId: 0,
synergyAccountId: 0,
content: locationXml,
msgType: 48, // 位置消息类型
msgSubType: 0,
msgSvrId: "",
isSend: true,
createTime: new Date().toISOString(),
isDeleted: false,
deleteTime: "",
sendStatus: 1,
wechatTime: Date.now(),
origin: 0,
msgId: 0,
recalled: false,
seq: messageId,
};
addMessage(localMessage);
// 发送消息到服务器
sendCommand("CmdSendMessage", {
wechatAccountId: contract.wechatAccountId,
wechatChatroomId: contract?.chatroomId ? contract.id : 0,
wechatFriendId: contract?.chatroomId ? 0 : contract.id,
msgSubType: 0,
msgType: 48,
content: locationXml,
seq: messageId,
});
}
// 关闭弹窗并重置状态
handleClose();
};
// 关闭弹窗

View File

@@ -0,0 +1,73 @@
# 腾讯地图定位服务修复说明
## 问题描述
`selectMap.tsx` 文件中使用腾讯地图定位服务时出现以下错误:
```
TypeError: window.TMap.service.Location is not a constructor
at selectMap.tsx:121:33
```
## 原因分析
错误原因是尝试将 `TMap.service.Location` 作为构造函数使用,但在腾讯地图 GL API 中,定位服务不是通过构造函数方式创建的。
## 修复方法
### 1. 修改定位服务的初始化方式
将原来的代码:
```typescript
// 创建IP定位服务
window.geolocationRef = new window.TMap.service.Location({
timeout: 10000,
convert: true,
});
```
修改为:
```typescript
// 使用腾讯地图内置的定位服务
window.geolocationRef = window.TMap.service.Geolocation;
```
### 2. 修改定位服务的调用方式
在调用定位服务时,将配置参数直接传入 `getLocation` 方法:
```typescript
window.geolocationRef.getLocation({
timeout: 10000,
convert: true,
success: function (result: any) {
// 处理成功回调
},
error: function () {
// 处理错误回调
},
});
```
## 技术说明
1. **腾讯地图 GL API 中的定位服务**
- 正确的服务名称是 `TMap.service.Geolocation`,而非 `TMap.service.Location`
- 它是一个对象,不需要使用 `new` 关键字实例化
- 配置参数应该直接传递给 `getLocation` 方法
2. **定位服务参数**
- `timeout`:定位超时时间,单位毫秒
- `convert`:是否将坐标转换为腾讯地图坐标系
3. **回调处理**
- `success`:定位成功回调函数,返回位置信息
- `error`:定位失败回调函数
## 注意事项
1. 确保腾讯地图 SDK 已正确加载
2. 确保 API 密钥有定位服务的权限
3. 定位精度可能受网络环境影响