hookehuyr

feat(map): 添加地图加载状态管理和错误处理功能

重构地图组件,增加加载状态管理和错误处理机制:
1. 添加加载中和错误状态UI展示
2. 实现带指数退避的重试机制
3. 优化错误信息提示
4. 清理未使用的组件导入
{
"globals": {
"EffectScope": true,
"ElMessage": true,
"computed": true,
"createApp": true,
"customRef": true,
......
......@@ -13,10 +13,6 @@ declare module '@vue/runtime-core' {
AudioBackground1: typeof import('./src/components/audioBackground1.vue')['default']
AudioList: typeof import('./src/components/audioList.vue')['default']
BottomNav: typeof import('./src/components/BottomNav.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElInput: typeof import('element-plus/es')['ElInput']
ElOption: typeof import('element-plus/es')['ElOption']
ElSelect: typeof import('element-plus/es')['ElSelect']
Floor: typeof import('./src/components/Floor/index.vue')['default']
InfoPopup: typeof import('./src/components/InfoPopup.vue')['default']
InfoPopupLite: typeof import('./src/components/InfoPopupLite.vue')['default']
......@@ -28,25 +24,9 @@ declare module '@vue/runtime-core' {
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SvgIcon: typeof import('./src/components/Floor/svgIcon.vue')['default']
VanBackTop: typeof import('vant/es')['BackTop']
VanButton: typeof import('vant/es')['Button']
VanCol: typeof import('vant/es')['Col']
VanConfigProvider: typeof import('vant/es')['ConfigProvider']
VanDialog: typeof import('vant/es')['Dialog']
VanField: typeof import('vant/es')['Field']
VanFloatingPanel: typeof import('vant/es')['FloatingPanel']
VanIcon: typeof import('vant/es')['Icon']
VanImage: typeof import('vant/es')['Image']
VanImagePreview: typeof import('vant/es')['ImagePreview']
VanOverlay: typeof import('vant/es')['Overlay']
VanPopup: typeof import('vant/es')['Popup']
VanRow: typeof import('vant/es')['Row']
VanSlider: typeof import('vant/es')['Slider']
VanSwipe: typeof import('vant/es')['Swipe']
VanSwipeItem: typeof import('vant/es')['SwipeItem']
VanTab: typeof import('vant/es')['Tab']
VanTabs: typeof import('vant/es')['Tabs']
VanToast: typeof import('vant/es')['Toast']
VRViewer: typeof import('./src/components/VRViewer/index.vue')['default']
}
}
......
......@@ -2,7 +2,6 @@
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const ElMessage: typeof import('element-plus/es')['ElMessage']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const customRef: typeof import('vue')['customRef']
......
<!--
* @Date: 2023-05-19 14:54:27
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-09-21 19:03:12
* @LastEditTime: 2025-09-25 21:54:49
* @FilePath: /map-demo/src/views/checkin/map.vue
* @Description: 公众地图主体页面
-->
<template>
<div ref="root" :style="{ height: mapHeight, position: 'relative', overflow: 'hidden' }">
<!-- 地图加载状态提示 -->
<div v-if="mapLoadingState.isLoading" class="map-loading-overlay">
<div class="loading-content">
<van-loading size="24px" color="#DD7850">地图加载中...</van-loading>
<p class="loading-text">正在初始化地图,请稍候</p>
</div>
</div>
<!-- 地图加载错误提示 -->
<div v-if="mapLoadingState.isError" class="map-error-overlay">
<div class="error-content">
<van-icon name="warning-o" size="48px" color="#ff4444" />
<p class="error-title">地图加载失败</p>
<p class="error-message">{{ mapLoadingState.errorMessage }}</p>
<van-button
type="primary"
color="#DD7850"
@click="retryLoadMap"
:loading="mapLoadingState.isLoading"
>
重新加载
</van-button>
</div>
</div>
<div id="container"></div>
<!-- 添加导航面板容器 -->
<div id="walking-panel" style="position: absolute; bottom: 1rem; left: 1rem; padding: 1rem;"></div>
......@@ -304,6 +329,14 @@ export default {
floatingPanelBorderRadius: '1.25rem'
},
showClose: false,
// 地图加载状态管理
mapLoadingState: {
isLoading: true,
isError: false,
errorMessage: '',
retryCount: 0,
maxRetries: 3
},
markerStyle2: { // 选中
//设置文本样式,Object 同 css 样式表
"padding": ".5rem .2rem .5rem .2rem",
......@@ -405,102 +438,44 @@ export default {
store.keepPages.push('CheckinMap');
}
const AMap = await AMapLoader.load({
key: '17b8fc386104b89db88b60b049a6dbce', // 控制台获取
version: '2.0', // 指定API版本
plugins: ['AMap.ElasticMarker','AMap.ImageLayer','AMap.ToolBar','AMap.IndoorMap','AMap.Walking','AMap.Geolocation'] // 必须加载步行导航插件
})
const code = this.$route.query.id;
const { data } = await mapAPI({ i: code });
this.navBarList = data.list; // 底部导航条
this.mapTiles = data.level; // 获取图层
this.navKey = data.list.length ? data.list[0]['id'] : 0; // 默认选中 第一个 id
this.navList = data.list.length ? data.list.filter(item => item.id === this.navKey)[0]['list'] : []; // 返回默认选中项的实体信息
this.data_center = data.map.center.map(item => Number(item)); // 地图中心点
this.data_zoom = data.map.zoom; // 地图默认缩放
this.data_rotation = data.map.rotation; // 地图旋转角度
this.data_zooms = data.map.zooms.map(item => Number(item)); // 地图默认缩放范围
this.data_paths = data.map.path ? data.map.path : {}; // 地图默认导航路径
this.data_logo = data.map.map_logo ? data.map.map_logo : ''; // 地图logo
this.point_range = data.map.map_range ? data.map.map_range : []; // 地图定位范围
if (data.map.map_layers) { // 地图默认图层
if (data.map.map_layers === 'satellite') { // 卫星和路网
this.data_layers = [new AMap.TileLayer.Satellite(), new AMap.TileLayer.RoadNet()]
} else { // 平面图
this.data_layers = [];
}
}
if (data.map.path) {
for (const key in data.map.path) {
const element = data.map.path[key];
this.data_path_list.push({
name: key,
path: element,
status: true
})
}
}
// 地图标题
document.title = data.map.map_title;
// 微信分享
const shareData = {
title: data.map.map_title, // 分享标题
desc: '别院地图', // 分享描述
link: location.origin + location.pathname + location.hash, // 分享链接,该链接域名或路径必须与当前页面对应的公众号 JS 安全域名一致
imgUrl: '', // 分享图标
success: function () {
// console.warn('设置成功');
}
// 开始加载地图
this.mapLoadingState.isLoading = true;
this.mapLoadingState.isError = false;
try {
await this.initializeMapWithRetry();
} catch (error) {
console.error('地图初始化失败:', error);
this.handleMapLoadError(error);
}
// 分享好友(微信好友或qq好友)
wx.updateAppMessageShareData(shareData);
// 分享到朋友圈或qq空间
wx.updateTimelineShareData(shareData);
// 分享到腾讯微博
wx.onMenuShareWeibo(shareData);
// 初始化地图
this.initMap();
// this.setMapBoundary();
// 使用之前获取当前地址,判断当前是否能够获取经纬度
// wx.getLocation({
// type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
// success: (res) => {
// var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
// var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
// var speed = res.speed; // 速度,以米/每秒计
// var accuracy = res.accuracy; // 位置精度
// this.current_lng = GPS.gcj_encrypt(latitude, longitude).lon;
// this.current_lat = GPS.gcj_encrypt(latitude, longitude).lat;
// },
// });
// 设置贴片地图
this.setTitleLayer();
// 地图标题
document.title = data.map.map_title;
//
// setTimeout(() => {
// this.info_height = (0.5 * window.innerHeight);
// // 浮动面板样式
// $('.van-floating-panel__content').css('borderRadius', '1.5rem');
// }, 2000);
// 初始化步行导航插件时指定面板容器
this.walking = new AMap.Walking({
map: this.map,
// panel: 'walking-panel', // 必须指定存在的DOM元素ID
hideMarkers: false, // 设置隐藏路径规划的起始点图标
isOutline: true, // 使用map属性时,绘制的规划线路是否显示描边
autoFitView: true, // 是否自动调整地图视野到显示的路线
});
},
// keep-alive 组件激活时调用
activated() {
// 组件被激活时,不需要重新初始化地图,保持之前的状态
console.log('地图组件已激活,保持之前状态');
// 组件被激活时,检查地图是否已经初始化
console.log('地图组件已激活');
// 确保当前页面在缓存列表中
const store = mainStore();
if (!store.keepPages.includes('CheckinMap')) {
store.keepPages.push('CheckinMap');
}
// 如果地图还未初始化或加载失败,重新尝试加载
if (!this.map || this.mapLoadingState.isError) {
console.log('地图未初始化或加载失败,重新加载');
this.mapLoadingState.isLoading = true;
this.mapLoadingState.isError = false;
this.initializeMapWithRetry().catch(error => {
console.error('重新激活时地图加载失败:', error);
this.handleMapLoadError(error);
});
} else {
// 地图已经初始化,重新设置地图大小以适应容器
this.$nextTick(() => {
this.map.getSize();
});
}
// 重置page-info组件状态,关闭浮动面板
this.info_height = 0;
this.itemInfo = {};
......@@ -511,12 +486,6 @@ export default {
// 还原标记点样式
this.resetMarkStyle();
});
// 如果地图已经初始化,重新设置地图大小以适应容器
if (this.map) {
this.$nextTick(() => {
this.map.getSize();
});
}
},
// keep-alive 组件停用时调用
deactivated() {
......@@ -539,6 +508,151 @@ export default {
methods: {
...mapActions(mainStore, ['changeAudio', 'changeAudioSrc', 'changeAudioStatus']),
/**
* 计算重试延迟时间(指数退避算法)
* @param {number} retryCount - 当前重试次数
* @returns {number} 延迟时间(毫秒)
*/
calculateRetryDelay(retryCount) {
// 基础延迟时间:1秒,每次重试翻倍,最大不超过10秒
const baseDelay = 1000;
const maxDelay = 10000;
const delay = Math.min(baseDelay * Math.pow(2, retryCount), maxDelay);
// 添加随机抖动,避免所有用户同时重试
const jitter = Math.random() * 0.3 * delay;
return delay + jitter;
},
/**
* 重试加载地图
*/
async retryLoadMap() {
if (this.mapLoadingState.retryCount >= this.mapLoadingState.maxRetries) {
this.mapLoadingState.errorMessage = '已达到最大重试次数,请稍后再试';
return;
}
this.mapLoadingState.isLoading = true;
this.mapLoadingState.isError = false;
this.mapLoadingState.retryCount++;
// 计算延迟时间
const delay = this.calculateRetryDelay(this.mapLoadingState.retryCount - 1);
try {
// 等待延迟时间
await new Promise(resolve => setTimeout(resolve, delay));
// 重新初始化地图
await this.initializeMapWithRetry();
} catch (error) {
console.error('重试加载地图失败:', error);
this.handleMapLoadError(error);
}
},
/**
* 处理地图加载错误
* @param {Error} error - 错误对象
*/
handleMapLoadError(error) {
this.mapLoadingState.isLoading = false;
this.mapLoadingState.isError = true;
// 根据错误类型设置不同的错误信息
if (error.message && error.message.includes('quota')) {
this.mapLoadingState.errorMessage = '地图服务繁忙,请稍后重试';
} else if (error.message && error.message.includes('network')) {
this.mapLoadingState.errorMessage = '网络连接异常,请检查网络后重试';
} else {
this.mapLoadingState.errorMessage = '地图加载失败,请重试';
}
},
/**
* 带重试机制的地图初始化
*/
async initializeMapWithRetry() {
try {
const AMap = await AMapLoader.load({
key: '17b8fc386104b89db88b60b049a6dbce',
version: '2.0',
plugins: ['AMap.ElasticMarker','AMap.ImageLayer','AMap.ToolBar','AMap.IndoorMap','AMap.Walking','AMap.Geolocation']
});
// 获取地图数据
const code = this.$route.query.id;
const { data } = await mapAPI({ i: code });
// 设置地图数据
this.navBarList = data.list;
this.mapTiles = data.level;
this.navKey = data.list.length ? data.list[0]['id'] : 0;
this.navList = data.list.length ? data.list.filter(item => item.id === this.navKey)[0]['list'] : [];
this.data_center = data.map.center.map(item => Number(item));
this.data_zoom = data.map.zoom;
this.data_rotation = data.map.rotation;
this.data_zooms = data.map.zooms.map(item => Number(item));
this.data_paths = data.map.path ? data.map.path : {};
this.data_logo = data.map.map_logo ? data.map.map_logo : '';
this.point_range = data.map.map_range ? data.map.map_range : [];
if (data.map.map_layers) {
if (data.map.map_layers === 'satellite') {
this.data_layers = [new AMap.TileLayer.Satellite(), new AMap.TileLayer.RoadNet()];
} else {
this.data_layers = [];
}
}
if (data.map.path) {
for (const key in data.map.path) {
const element = data.map.path[key];
this.data_path_list.push({
name: key,
path: element,
status: true
});
}
}
// 设置页面标题
document.title = data.map.map_title;
// 微信分享配置
const shareData = {
title: data.map.map_title,
desc: '别院地图',
link: location.origin + location.pathname + location.hash,
imgUrl: '',
success: function () {}
};
wx.updateAppMessageShareData(shareData);
wx.updateTimelineShareData(shareData);
wx.onMenuShareWeibo(shareData);
// 初始化地图
this.initMap();
this.setTitleLayer();
// 初始化步行导航
this.walking = new AMap.Walking({
map: this.map,
hideMarkers: false,
isOutline: true,
autoFitView: true,
});
// 地图加载成功
this.mapLoadingState.isLoading = false;
this.mapLoadingState.isError = false;
this.mapLoadingState.retryCount = 0;
} catch (error) {
console.error('地图初始化失败:', error);
throw error; // 重新抛出错误,让调用方处理
}
},
/**
* 创建标记点的HTML内容,包含图标和文字
* @param {Object} entityInfo - 实体信息
* @param {String} textDirection - 文字方向 ('vertical' 或 'horizontal')
......@@ -1510,4 +1624,68 @@ export default {
.van-floating-panel__header-bar {
background: none;
}
/* 地图加载状态样式 */
.map-loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.9);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.loading-content {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.loading-text {
margin-top: 1rem;
color: #666;
font-size: 0.9rem;
}
/* 地图加载错误状态样式 */
.map-error-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.95);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.error-content {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 2rem;
max-width: 300px;
}
.error-title {
margin: 1rem 0 0.5rem 0;
font-size: 1.1rem;
font-weight: 600;
color: #333;
}
.error-message {
margin: 0.5rem 0 1.5rem 0;
color: #666;
font-size: 0.9rem;
line-height: 1.4;
}
</style>
......