顶栏天气组件策略与实现说明
这篇文档记录顶栏天气组件的产品策略、实现思路和排障方式。
当前组件不是服务端天气组件,也不是显示服务器所在地天气。它在浏览器端运行,通过高德 JSAPI 尝试识别访问者所在城市,再查询该城市天气。实现代码集中在 src/components/NavbarWeather/index.js,高德加载与安全配置复用 src/utils/amap.js。
目标策略
天气组件采用按需加载策略,不再在页面加载时自动请求天气 API:
- 页面加载后,仅检查 sessionStorage 缓存。若有有效缓存则直接展示天气,不做任何网络请求。
- 若无缓存,显示占位文字(
weather?),不调用任何高德 API。 - 用户首次点击组件时,请求浏览器地理位置权限;授权成功后使用精确位置对应的区县或城市查询天气。
- 如果浏览器定位失败、用户拒绝授权或逆地理编码失败,再通过高德 IP 定位识别访问者城市并查询天气。
- 如果 IP 定位也失败,回退到默认城市成都。
如果点击时已有缓存数据,浏览器定位天气只展开详情,不再重复请求;IP 定位或默认城市天气会在下一次点击时补一次浏览器定位。
这样做的原因是:
- 高德 API 配额有限,避免每次页面访问都 消耗配额。
- 缓存命中时直接展示,既能快速渲染又不消耗 API。
- 天气请求只在用户主动点击后触发,避免页面加载即消耗 API。
- 浏览器地理位置最接近用户真实位置,适合作为点击后的优先路径。
- IP 定位不需要用户授权,适合作为浏览器定位失败后的兜底。
- 默认城市能保证组件在公网、代理、海外网络、机房出口等场景下仍然可见。
默认城市配置为:
城市:成都市
adcode:510100
初始化流程
组件挂载时不再自动请求天气 API,仅执行缓存检查:
检查 sessionStorage 缓存 (key: navbar-weather-v3, TTL: 30 分钟)
-> 缓存有效:直 接渲染天气按钮
-> 缓存无效或不存在:渲染占位按钮(显示 "weather?")
渲染占位按钮时,不加载高德 JSAPI,不发起任何网络请求。
组件依赖 useAmapConfig() 读取 customFields.amap。如果缺少 key,或者同时缺少 serviceHost 和 securityJsCode,组件会保持隐藏状态。
首次点击流程
用户首次点击天气组件(无缓存数据时):
请求 navigator.geolocation.getCurrentPosition()
-> 授权成功后加载高德 JSAPI 插件:CitySearch、Geocoder、Weather
-> 调用 AMap.convertFrom(..., 'gps') 转换为高德可用坐标
-> 调用 Geocoder.getAddress() 反查区县、城市和 adcode
-> 优先使用 district/adcode 查询天气并显示区县名
-> 如果浏览器定位、坐标转换、逆地理编码或天气查询失败,再调用 CitySearch.getLocalCity()
-> 如果 IP 定位失败,使用成都市 510100 查询天气
-> 成功后渲染顶栏按钮、展开详情面板,并写入 sessionStorage 缓存
浏览器定位成功时,天气数据会标记为:
source: browser
IP 定位成功时,天气数据会标记为:
source: ip
默认城市兜底成功时,天气数据会标记为:
source: default
这个 source 字段是后续点击策略的关键。组件通过它判断用户点击时是否需要请求浏览器地理位置。
后续点击流程(已有天气数据时)
用户在有天气数据时点击组件,组件首先切换详情面板的展开状态。
随后根据当前天气来源决定是否请求浏览器定位:
source: ip:说明已经通过 IP 识别到用户城市,点击时会补一次浏览器定位。source: browser:说明已经通过浏览器定位更新过,只展开详情,不重复请求权限。source: default:说明当前只是成都兜底天气,点击时请求浏览器定位。
浏览器定位流程:
navigator.geolocation.getCurrentPosition()
-> 获取 WGS84/GPS 经纬度
-> 调用 AMap.convertFrom(..., 'gps') 转换为高德可用坐标
-> 加载 AMap.Geocoder
-> 调用 geocoder.getAddress() 反查城市和 adcode
-> 使用 adcode 查询实时天气和天气预报
-> 更新组件并刷新 sessionStorage 缓存
如果用户拒绝权限、浏览器不支持定位、定位超时、坐标转换失败、逆地理编码失败或天气查询失败,组件不会报错给用户,也不会隐藏。它会继续展示 IP 定位或成都兜底天气。
为什么不能只依赖 IP 定位
IP 定位只能识别公网出口 IP,不等于用户真实 GPS 位置。
下面这些网络环境经常导致 IP 定位失败或不准:
- 校园网、公司网、酒店网络等统一出口
- 手机流量运营商 NAT 出口
- VPN、代理、加速器
- 海外网络或跨境出口
- 云服务器、机房、CDN 或远程浏览器环境
- 高德 IP 库暂未收录的地址段
线上排查时曾看到高德 IP 接口返回:
{
"status": "1",
"info": "OK",
"infocode": "10000",
"province": [],
"city": [],
"adcode": [],
"rectangle": []
}
这类响应代表请求本身成功,但没有可用城市数据。旧逻辑会因为拿不到城市而隐藏组件;新逻辑会回退到成都天气。
缓存策略
组件使用 sessionStorage 缓存天气数据:
key: navbar-weather-v3
ttl: 30 分钟
缓存目标是减少同一次浏览会话内重复请求高德接口。
缓存内容包括:
- 城市名
- adcode
- 省份
- 实时天气
- 温度
- 湿度
- 风向风力
- 预报列表
- 数据来源
source - 报告时间
缓存过期或解析失败时会被忽略,不影响组件重新请求。
高德 JSAPI 依赖
组件依赖的高德插件分两组按需加载:
- 无浏览器定位时加载
AMap.CitySearch和AMap.Weather,用于 IP 定位与天气查询。 - 浏览器定位路径加载
AMap.CitySearch、AMap.Geocoder和AMap.Weather,用于坐标反查、兜底 IP 定位与天气查询。
高德鉴权配置来自 Docusaurus customFields.amap:
customFields: {
amap: {
key,
serviceHost,
securityJsCode,
},
}
组件只有在存在 key,且存在 serviceHost 或 securityJsCode 时才会尝试加载高德 JSAPI。
组件隐藏条件
当前组件仍然会在这些情况下隐藏:
- 移动端 navbar item 渲染路径传入
mobile。 - 缺少高德 JSAPI key 或安全配置。
- 高德 JSAPI 无法加载,且没有可用缓存。
- 成都兜底天气也查询失败,且没有可用缓存。
- CSS 媒体查询命中
max-width: 996px。
除此之外,单纯 IP 定位失败不再导致组件 隐藏。
隐私与交互原则
页面初次加载不发起任何天气 API 请求,也不请求用户位置权限。天气数据仅在用户主动点击组件后获取。
浏览器精确定位只在用户主动点击天气组件后才请求。若当前显示的是 IP 定位或默认城市天气,后续点击会补一次浏览器定位。
实现上不主动保存精确经纬度,也不把经纬度写入缓存。缓存里只保存查询天气所需的城市、adcode 和天气数据。
这保持了两个边界:
- 页面初次加载零 API 消耗,零权限请求,不打扰用户。
- 授权定位只用于本次天气城市更新,不把精确位置长期留在站点存储里。
排障检查清单
如果线上天气组件没有出现,按下面顺序排查:
- 确认当前视口宽度大于
996px。 - 确认页面已经加载
main.*.js,并包含custom-weather相关代码。 - 点击天气组件后,确认
window._AMapSecurityConfig存在。 - 点击天气组件后,确认
window.AMap已加载。 - 检查高德 JSAPI、插件和天气请求是否返回 200。
- 检查
sessionStorage.navbar-weather-v3是否存在有效数据。 - 如果 IP 定位返回空城市,确认组件是否回退到
source: default的成都天气。 - 如果点击后没有切换城市,检查浏览器是否拒绝了 geolocation 权限,或是否仍命中
source: browser的旧缓存。
本地验证可以使用生产构建:
npm run build
npm run serve -- --host 127.0.0.1 --port 3002
在桌面宽度下访问页面,授权浏览器地理位置后,点击天气组件应切换到对应位置的区县或城市天气;定位失败时应至少看到 IP 定位或成都天气。
相关实现文件
src/components/NavbarWeather/index.jssrc/components/NavbarWeather/styles.module.csssrc/utils/amap.jsdocusaurus.config.js