优化SelectMap组件,增强TMap API加载和初始化逻辑,添加API可用性检查,改进标记样式创建和错误处理,提升用户体验和稳定性。

This commit is contained in:
超级老白兔
2025-11-22 14:16:40 +08:00
parent ab2ee050de
commit 43b60a0049

View File

@@ -70,7 +70,17 @@ const SelectMap: React.FC<SelectMapProps> = ({
useEffect(() => {
// 检查TMap是否已经加载
if (window.TMap) {
setTmapLoaded(true);
// 等待 API 完全初始化
const checkAPIReady = () => {
if (window.TMap && window.TMap.Map) {
console.log("腾讯地图SDK已加载API 可用");
setTmapLoaded(true);
} else {
// 如果 API 还未完全初始化,等待一段时间后重试
setTimeout(checkAPIReady, 100);
}
};
checkAPIReady();
return;
}
@@ -80,8 +90,19 @@ const SelectMap: React.FC<SelectMapProps> = ({
"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);
console.log("腾讯地图SDK脚本加载成功,等待 API 初始化...");
// 等待 API 完全初始化
const checkAPIReady = () => {
if (window.TMap && window.TMap.Map) {
console.log("腾讯地图SDK API 初始化完成");
setTmapLoaded(true);
} else {
// 如果 API 还未完全初始化,等待一段时间后重试(最多等待 5 秒)
setTimeout(checkAPIReady, 100);
}
};
// 延迟检查,给 API 一些初始化时间
setTimeout(checkAPIReady, 200);
};
script.onerror = () => {
console.error("腾讯地图SDK加载失败");
@@ -97,220 +118,359 @@ const SelectMap: React.FC<SelectMapProps> = ({
};
}, []);
// 检查 TMap API 是否可用(辅助函数)
const checkTMapAPI = () => {
if (!window.TMap) {
console.error("TMap 未加载");
return false;
}
// 检查 MultiMarker 是否可用
if (!window.TMap.MultiMarker) {
console.error("TMap.MultiMarker 不可用", {
TMap: window.TMap,
keys: Object.keys(window.TMap || {}),
});
return false;
}
// 检查 Style 是否存在(可能是构造函数、对象或命名空间)
// 注意Style 可能不是构造函数,而是配置对象或命名空间
const hasStyle =
window.TMap.MultiMarker.Style !== undefined ||
window.TMap.MarkerStyle !== undefined;
if (!hasStyle) {
console.warn("TMap Style API 不可用,将使用配置对象方式", {
MultiMarker: window.TMap.MultiMarker,
MultiMarkerKeys: Object.keys(window.TMap.MultiMarker || {}),
MarkerStyle: window.TMap.MarkerStyle,
});
// 不返回 false因为 MultiMarker 可能接受配置对象
}
return true;
};
// 创建标记样式(兼容不同的 API 版本)
const createMarkerStyle = (options: any) => {
// 检查 MultiMarker.Style 是否存在
if (window.TMap.MultiMarker?.Style) {
// 如果 Style 是函数(构造函数),使用 new
if (typeof window.TMap.MultiMarker.Style === "function") {
try {
return new window.TMap.MultiMarker.Style(options);
} catch (error) {
console.warn(
"使用 new MultiMarker.Style 失败,尝试直接返回配置对象:",
error,
);
// 如果构造函数调用失败,直接返回配置对象
return options;
}
} else {
// 如果 Style 不是函数,可能是对象或命名空间,直接返回配置对象
// MultiMarker 可能接受配置对象而不是 Style 实例
console.log("MultiMarker.Style 不是构造函数,直接使用配置对象");
return options;
}
}
// 尝试 MarkerStyle
if (window.TMap.MarkerStyle) {
if (typeof window.TMap.MarkerStyle === "function") {
try {
return new window.TMap.MarkerStyle(options);
} catch (error) {
console.warn(
"使用 new MarkerStyle 失败,尝试直接返回配置对象:",
error,
);
return options;
}
} else {
return options;
}
}
// 如果都不存在,直接返回配置对象(让 MultiMarker 自己处理)
console.warn("未找到 Style API直接使用配置对象");
return options;
};
// 初始化地图
useEffect(() => {
if (visible && mapContainerRef.current && tmapLoaded && window.TMap) {
console.log("开始初始化地图");
console.log("TMap API 检查:", {
TMap: !!window.TMap,
MultiMarker: !!window.TMap.MultiMarker,
MultiMarkerStyle: !!window.TMap.MultiMarker?.Style,
MarkerStyle: !!window.TMap.MarkerStyle,
});
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,
});
// 检查容器尺寸,确保容器有有效的宽高
const checkContainerSize = () => {
if (!mapContainerRef.current) return false;
const rect = mapContainerRef.current.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
};
setMap(mapInstance);
let mapInstance: any = null;
let handleMapClickFn: ((evt: any) => void) | null = null;
let delayTimer: NodeJS.Timeout | null = null;
let isMounted = true; // 标记弹窗是否仍然打开
// 创建地理编码服务(用于反向地理编码
geocoderRef.current = new window.TMap.service.Geocoder();
// 初始化地图函数(使用箭头函数避免函数声明位置问题
const initializeMap = () => {
if (!mapContainerRef.current) return;
// 创建IP定位服务
window.geolocationRef = new window.TMap.service.IPLocation();
// 创建搜索建议服务
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.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;
// 设置基本位置信息(防止白屏)
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;
try {
// 再次检查容器尺寸
const rect = mapContainerRef.current.getBoundingClientRect();
if (rect.width <= 0 || rect.height <= 0) {
console.error("地图容器尺寸无效:", rect);
message.error("地图容器尺寸无效,请刷新页面重试");
return;
}
const newMarker = new window.TMap.MultiMarker({
id: "marker-layer",
map: mapInstance,
geometries: [
{
id: "user-location",
styleId: "marker",
position: userLocation,
properties: {
title: isDefault ? "默认位置" : "当前位置",
},
},
],
// 创建地图实例
const center = new window.TMap.LatLng(39.908823, 116.39747); // 默认北京
mapInstance = new window.TMap.Map(mapContainerRef.current, {
center: center,
zoom: 13,
rotation: 0,
pitch: 0,
});
markerRef.current = newMarker;
setMap(mapInstance);
// 使用腾讯地图服务获取该位置的地址信息
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 || {};
// 创建地理编码服务(用于反向地理编码)
geocoderRef.current = new window.TMap.service.Geocoder();
const addressLabel =
formattedAddresses.recommend ||
formattedAddresses.rough ||
resultData.address ||
`${lat.toFixed(6)}, ${lng.toFixed(6)}`;
// 创建IP定位服务
window.geolocationRef = new window.TMap.service.IPLocation();
setSelectedLocation({
x: lng.toString(),
y: lat.toString(),
scale: "16",
label: addressLabel,
poiname:
addressComponent.street ||
(isDefault ? "默认位置" : "当前位置"),
maptype: "0",
poiid: resultData.poi_id || "",
});
// 创建搜索建议服务
suggestServiceRef.current = new window.TMap.service.Suggestion({
pageSize: 10,
autoExtend: true,
});
// 地图点击事件处理函数
handleMapClickFn = (evt: any) => {
try {
// 检查弹窗是否仍然打开以及必要的API是否可用
if (!isMounted || !mapInstance || !mapContainerRef.current) {
return;
}
})
.catch((error: any) => {
setIsReverseGeocoding(false);
console.error("获取地址信息失败:", error);
// 即使获取地址失败,也设置基本的位置信息
// 检查 TMap API 是否可用
if (!checkTMapAPI()) {
console.error("TMap API 不可用,无法创建标记点");
message.warning("地图标记功能不可用,请刷新页面重试");
return;
}
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 markerStyle = createMarkerStyle({
width: 25,
height: 35,
anchor: { x: 12, y: 35 },
src: "https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png",
});
// 创建新标记
const newMarker = new window.TMap.MultiMarker({
id: "marker-layer",
map: mapInstance,
styles: {
marker: markerStyle,
},
geometries: [
{
id: "selected-marker",
styleId: "marker",
position: new window.TMap.LatLng(lat, lng),
properties: {
title: "选中位置",
},
},
],
});
markerRef.current = newMarker;
// 设置基本位置信息(防止白屏)
setSelectedLocation({
x: lng.toString(),
y: lat.toString(),
scale: "16",
label: `${lat.toFixed(6)}, ${lng.toFixed(6)}`,
poiname: "选中位置",
maptype: "0",
poiid: "",
});
// 反向地理编码获取地址
if (!isMounted || !geocoderRef.current) {
return;
}
setIsReverseGeocoding(true);
geocoderRef.current
.getAddress({ location: new window.TMap.LatLng(lat, lng) })
.then((result: any) => {
// 检查弹窗是否仍然打开
if (!isMounted) {
return;
}
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) => {
// 检查弹窗是否仍然打开
if (!isMounted) {
return;
}
setIsReverseGeocoding(false);
console.error("反向地理编码错误:", error);
message.warning("获取详细地址信息失败,将使用坐标显示");
});
} catch (error) {
console.error("地图点击处理错误:", error);
message.error("处理地图点击时出错,请重试");
}
};
// 绑定地图点击事件
mapInstance.on("click", handleMapClickFn);
// 使用腾讯地图API初始化用户位置
const initializeUserLocation = (
lat: number,
lng: number,
isDefault: boolean = false,
) => {
// 检查弹窗是否仍然打开以及必要的API是否可用
if (!isMounted || !mapInstance || !mapContainerRef.current) {
console.log("弹窗已关闭或地图实例无效,跳过初始化位置");
return;
}
// 检查 TMap API 是否可用
if (!checkTMapAPI()) {
console.error("TMap API 不可用,无法创建标记点");
message.warning("地图标记功能不可用,请刷新页面重试");
return;
}
// 创建位置对象
let userLocation: any = null;
try {
console.log(isDefault ? "使用默认位置:" : "用户位置:", lat, lng);
// 移动地图中心到位置
userLocation = new window.TMap.LatLng(lat, lng);
mapInstance.setCenter(userLocation);
mapInstance.setZoom(16);
// 添加标记点
if (markerRef.current) {
markerRef.current.setMap(null);
markerRef.current = null;
}
// 创建标记样式
const markerStyle = createMarkerStyle({
width: 25,
height: 35,
anchor: { x: 12, y: 35 },
src: "https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png",
});
const newMarker = new window.TMap.MultiMarker({
id: "marker-layer",
map: mapInstance,
styles: {
marker: markerStyle,
},
geometries: [
{
id: "user-location",
styleId: "marker",
position: userLocation,
properties: {
title: isDefault ? "默认位置" : "当前位置",
},
},
],
});
markerRef.current = newMarker;
} catch (error) {
console.error("创建标记点失败:", error);
// 即使创建标记失败,也设置基本的位置信息
setSelectedLocation({
x: lng.toString(),
y: lat.toString(),
@@ -320,63 +480,186 @@ const SelectMap: React.FC<SelectMapProps> = ({
maptype: "0",
poiid: "",
});
});
};
return;
}
// 使用腾讯地图IP定位获取用户位置
setIsLocating(true);
try {
if (window.geolocationRef) {
window.geolocationRef
.locate()
// 使用腾讯地图服务获取该位置的地址信息
if (!isMounted || !geocoderRef.current || !userLocation) {
return;
}
setIsReverseGeocoding(true);
geocoderRef.current
.getAddress({ location: userLocation })
.then((result: any) => {
setIsLocating(false);
console.log("IP定位结果:", result);
if (result && result.result && result.result.location) {
const { lat, lng } = result.result.location;
message.info("已定位到您的大致位置");
initializeUserLocation(lat, lng, false);
} else {
// IP定位失败使用默认位置
message.info("无法获取您的位置,已定位到北京");
// 使用默认位置(北京市)
initializeUserLocation(39.908823, 116.39747, true);
// 检查弹窗是否仍然打开
if (!isMounted) {
return;
}
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) => {
setIsLocating(false);
console.error("IP定位失败:", error);
message.info("无法获取您的位置,已定位到北京");
// 使用默认位置(北京市)
initializeUserLocation(39.908823, 116.39747, true);
// 检查弹窗是否仍然打开
if (!isMounted) {
return;
}
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: "",
});
});
} else {
// IP定位服务未初始化使用默认位置
setIsLocating(false);
message.info("无法获取您的位置,已定位到北京");
// 使用默认位置(北京市)
initializeUserLocation(39.908823, 116.39747, true);
};
// 使用腾讯地图IP定位获取用户位置
setIsLocating(true);
try {
if (window.geolocationRef) {
window.geolocationRef
.locate()
.then((result: any) => {
// 检查弹窗是否仍然打开
if (!isMounted) {
return;
}
setIsLocating(false);
console.log("IP定位结果:", result);
if (result && result.result && result.result.location) {
const { lat, lng } = result.result.location;
// message.info("已定位到您的大致位置");
initializeUserLocation(lat, lng, false);
} else {
// IP定位失败使用默认位置
message.info("无法获取您的位置,已定位到北京");
// 使用默认位置(北京市)
initializeUserLocation(39.908823, 116.39747, true);
}
})
.catch((error: any) => {
// 检查弹窗是否仍然打开
if (!isMounted) {
return;
}
setIsLocating(false);
console.error("IP定位失败:", error);
message.info("无法获取您的位置,已定位到北京");
// 使用默认位置(北京市)
initializeUserLocation(39.908823, 116.39747, true);
});
} else {
// IP定位服务未初始化使用默认位置
setIsLocating(false);
message.info("无法获取您的位置,已定位到北京");
// 使用默认位置(北京市)
initializeUserLocation(39.908823, 116.39747, true);
}
} catch (error) {
// 捕获任何可能的错误,防止白屏
console.error("定位过程中发生错误:", error);
if (isMounted) {
setIsLocating(false);
message.error("定位服务出现异常,已定位到北京");
// 使用默认位置(北京市)
initializeUserLocation(39.908823, 116.39747, true);
}
}
} catch (error) {
// 捕获任何可能的错误,防止白屏
console.error("定位过程中发生错误:", error);
console.error("初始化地图时出错:", error);
message.error("地图加载失败,请刷新页面重试");
setIsLocating(false);
message.error("定位服务出现异常,已定位到北京");
// 使用默认位置(北京市)
initializeUserLocation(39.908823, 116.39747, true);
}
};
// 使用 requestAnimationFrame 确保容器尺寸正确后再初始化
const initTimer = requestAnimationFrame(() => {
// 再次检查容器尺寸
if (!checkContainerSize()) {
console.log("容器尺寸无效,延迟初始化地图");
delayTimer = setTimeout(() => {
if (checkContainerSize() && mapContainerRef.current) {
initializeMap();
} else {
console.error("地图容器尺寸仍然无效");
message.error("地图容器初始化失败,请刷新页面重试");
}
}, 100);
return;
}
return () => {
// 清理地图事件监听
if (mapInstance) {
mapInstance.off("click", handleMapClick);
// 容器尺寸有效,立即初始化
initializeMap();
});
// 清理函数
return () => {
// 标记弹窗已关闭
isMounted = false;
// 取消 requestAnimationFrame
cancelAnimationFrame(initTimer);
// 清理延迟定时器
if (delayTimer) {
clearTimeout(delayTimer);
}
// 清理地图事件监听
if (mapInstance && handleMapClickFn) {
try {
mapInstance.off("click", handleMapClickFn);
} catch (error) {
console.error("清理地图事件监听失败:", error);
}
};
} catch (error) {
console.error("初始化地图时出错:", error);
message.error("地图加载失败,请刷新页面重试");
setIsLocating(false);
}
}
// 清理地图实例
if (mapInstance) {
try {
mapInstance.destroy();
} catch (error) {
console.error("销毁地图实例失败:", error);
}
mapInstance = null;
}
// 清理标记点
if (markerRef.current) {
try {
markerRef.current.setMap(null);
} catch (error) {
console.error("清理标记点失败:", error);
}
markerRef.current = null;
}
// 重置地图状态
setMap(null);
};
}
}, [visible, tmapLoaded]);
@@ -445,6 +728,13 @@ const SelectMap: React.FC<SelectMapProps> = ({
return;
}
// 检查 TMap API 是否可用
if (!checkTMapAPI()) {
console.error("TMap API 不可用,无法创建标记点");
message.error("地图API不可用请刷新页面重试");
return;
}
const lat = result.location.lat;
const lng = result.location.lng;
@@ -460,9 +750,20 @@ const SelectMap: React.FC<SelectMapProps> = ({
markerRef.current = null;
}
// 创建标记样式
const markerStyle = createMarkerStyle({
width: 25,
height: 35,
anchor: { x: 12, y: 35 },
src: "https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png",
});
const newMarker = new window.TMap.MultiMarker({
id: "marker-layer",
map: map,
styles: {
marker: markerStyle,
},
geometries: [
{
id: "selected-poi",