跳到主要内容

顶栏天气组件策略与实现说明

这篇文档记录顶栏天气组件的产品策略、实现思路和排障方式。

当前组件不是服务端天气组件,也不是显示服务器所在地天气。它在浏览器端运行,通过高德 JSAPI 尝试识别访问者所在城市,再查询该城市天气。实现代码集中在 src/components/NavbarWeather/index.js,高德加载与安全配置复用 src/utils/amap.js

目标策略

天气组件采用按需加载策略,不再在页面加载时自动请求天气 API:

  1. 页面加载后,仅检查 sessionStorage 缓存。若有有效缓存则直接展示天气,不做任何网络请求。
  2. 若无缓存,显示占位文字(weather?),不调用任何高德 API。
  3. 用户首次点击组件时,请求浏览器地理位置权限;授权成功后使用精确位置对应的区县或城市查询天气。
  4. 如果浏览器定位失败、用户拒绝授权或逆地理编码失败,再通过高德 IP 定位识别访问者城市并查询天气。
  5. 如果 IP 定位也失败,回退到默认城市成都。

如果点击时已有缓存数据,浏览器定位天气只展开详情,不再重复请求;IP 定位或默认城市天气会在下一次点击时补一次浏览器定位。

这样做的原因是:

  • 高德 API 配额有限,避免每次页面访问都消耗配额。
  • 缓存命中时直接展示,既能快速渲染又不消耗 API。
  • 天气请求只在用户主动点击后触发,避免页面加载即消耗 API。
  • 浏览器地理位置最接近用户真实位置,适合作为点击后的优先路径。
  • IP 定位不需要用户授权,适合作为浏览器定位失败后的兜底。
  • 默认城市能保证组件在公网、代理、海外网络、机房出口等场景下仍然可见。

默认城市配置为:

城市:成都市
adcode:510100

初始化流程

组件挂载时不再自动请求天气 API,仅执行缓存检查:

检查 sessionStorage 缓存 (key: navbar-weather-v3, TTL: 30 分钟)
-> 缓存有效:直接渲染天气按钮
-> 缓存无效或不存在:渲染占位按钮(显示 "weather?")

渲染占位按钮时,不加载高德 JSAPI,不发起任何网络请求。

组件依赖 useAmapConfig() 读取 customFields.amap。如果缺少 key,或者同时缺少 serviceHostsecurityJsCode,组件会保持隐藏状态。

首次点击流程

用户首次点击天气组件(无缓存数据时):

请求 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.CitySearchAMap.Weather,用于 IP 定位与天气查询。
  • 浏览器定位路径加载 AMap.CitySearchAMap.GeocoderAMap.Weather,用于坐标反查、兜底 IP 定位与天气查询。

高德鉴权配置来自 Docusaurus customFields.amap

customFields: {
amap: {
key,
serviceHost,
securityJsCode,
},
}

组件只有在存在 key,且存在 serviceHostsecurityJsCode 时才会尝试加载高德 JSAPI。

组件隐藏条件

当前组件仍然会在这些情况下隐藏:

  • 移动端 navbar item 渲染路径传入 mobile
  • 缺少高德 JSAPI key 或安全配置。
  • 高德 JSAPI 无法加载,且没有可用缓存。
  • 成都兜底天气也查询失败,且没有可用缓存。
  • CSS 媒体查询命中 max-width: 996px

除此之外,单纯 IP 定位失败不再导致组件隐藏。

隐私与交互原则

页面初次加载不发起任何天气 API 请求,也不请求用户位置权限。天气数据仅在用户主动点击组件后获取。

浏览器精确定位只在用户主动点击天气组件后才请求。若当前显示的是 IP 定位或默认城市天气,后续点击会补一次浏览器定位。

实现上不主动保存精确经纬度,也不把经纬度写入缓存。缓存里只保存查询天气所需的城市、adcode 和天气数据。

这保持了两个边界:

  • 页面初次加载零 API 消耗,零权限请求,不打扰用户。
  • 授权定位只用于本次天气城市更新,不把精确位置长期留在站点存储里。

排障检查清单

如果线上天气组件没有出现,按下面顺序排查:

  1. 确认当前视口宽度大于 996px
  2. 确认页面已经加载 main.*.js,并包含 custom-weather 相关代码。
  3. 点击天气组件后,确认 window._AMapSecurityConfig 存在。
  4. 点击天气组件后,确认 window.AMap 已加载。
  5. 检查高德 JSAPI、插件和天气请求是否返回 200。
  6. 检查 sessionStorage.navbar-weather-v3 是否存在有效数据。
  7. 如果 IP 定位返回空城市,确认组件是否回退到 source: default 的成都天气。
  8. 如果点击后没有切换城市,检查浏览器是否拒绝了 geolocation 权限,或是否仍命中 source: browser 的旧缓存。

本地验证可以使用生产构建:

npm run build
npm run serve -- --host 127.0.0.1 --port 3002

在桌面宽度下访问页面,授权浏览器地理位置后,点击天气组件应切换到对应位置的区县或城市天气;定位失败时应至少看到 IP 定位或成都天气。

相关实现文件

  • src/components/NavbarWeather/index.js
  • src/components/NavbarWeather/styles.module.css
  • src/utils/amap.js
  • docusaurus.config.js