1. 项目概述:uniapp在App端实现天气详情展示
最近在做一个uniapp项目时,遇到了一个看似简单但实际坑点不少的需求:在App端展示天气详情。这个功能看似基础,但涉及到跨平台兼容性、数据获取、UI适配等多个技术点。作为一个踩过不少坑的老手,我想分享一下在uniapp中实现这个功能的完整思路和实战经验。
uniapp作为一款跨平台开发框架,理论上可以一套代码运行在多个平台。但在实际开发中,App端和小程序端、H5端在API支持、性能表现上都有差异。特别是天气功能这种需要网络请求和复杂UI展示的场景,更需要特别注意平台差异带来的问题。
2. 天气数据获取方案选择与实现
2.1 主流天气API对比
要实现天气展示,首先需要获取天气数据。目前市面上提供天气数据的API不少,各有特点:
| API提供商 | 免费额度 | 数据精度 | 响应速度 | 特色功能 |
|---|---|---|---|---|
| 和风天气 | 1000次/天 | 高 | 快 | 空气质量、生活指数 |
| 心知天气 | 500次/天 | 中 | 一般 | 多语言支持 |
| OpenWeather | 1000次/天 | 高 | 较慢 | 全球覆盖 |
| 高德地图 | 不限次 | 高 | 快 | 结合地理位置 |
经过对比,我最终选择了和风天气API,主要考虑以下几点:
- 免费额度足够个人和小型项目使用
- 数据精度高,更新及时
- 提供了丰富的附加数据(如空气质量、生活建议等)
2.2 在uniapp中封装天气请求
在uniapp中请求天气API,需要注意跨平台兼容性问题。以下是我的封装方案:
javascript复制// utils/weather.js
const WEATHER_KEY = '你的API密钥'; // 实际项目中应从配置读取
export const getWeatherByLocation = async (location) => {
try {
// 区分平台处理URL
let baseUrl;
if (process.env.UNI_PLATFORM === 'h5') {
baseUrl = '/api/weather'; // H5端走代理
} else {
baseUrl = 'https://free-api.heweather.net/s6/weather'; // App端直接请求
}
const response = await uni.request({
url: baseUrl,
method: 'GET',
data: {
location,
key: WEATHER_KEY
}
});
if (response[1].statusCode === 200) {
return response[1].data.HeWeather6[0];
}
throw new Error('天气数据获取失败');
} catch (error) {
console.error('获取天气数据出错:', error);
throw error;
}
};
重要提示:实际项目中不要在前端直接写API密钥,应该通过后端转发请求,这里仅为演示简化处理。
2.3 处理定位权限问题
在App端获取天气通常需要用户当前位置,这就涉及到定位权限问题。uniapp提供了统一的API,但不同平台表现有差异:
javascript复制async function getLocation() {
try {
// 检查权限状态
const status = await uni.getSetting({
type: 'userLocation'
});
if (status.authSetting['scope.userLocation'] === false) {
// 已拒绝过权限,需要引导用户手动开启
await uni.showModal({
title: '提示',
content: '需要位置权限才能获取当地天气',
confirmText: '去设置',
success(res) {
if (res.confirm) {
uni.openSetting();
}
}
});
return null;
}
// 获取当前位置
const location = await uni.getLocation({
type: 'wgs84',
altitude: true
});
return {
longitude: location.longitude,
latitude: location.latitude
};
} catch (error) {
console.error('获取位置失败:', error);
throw error;
}
}
在Android和iOS上,定位权限的处理方式有所不同:
- iOS需要先在manifest.json中配置权限描述
- Android需要在打包时勾选相应权限
- 真机调试时,iOS模拟器无法获取真实位置,需要特别注意
3. 天气详情页面设计与实现
3.1 UI布局方案选择
天气详情页通常包含以下元素:
- 当前温度
- 天气状况图标
- 最高/最低温度
- 空气质量指数
- 未来几天预报
- 生活建议(穿衣、紫外线等)
在uniapp中实现这样的复杂布局,有几种方案可选:
- 纯view布局:使用flex布局实现,兼容性好但代码量大
- uView UI框架:使用现成组件,开发快但定制性差
- 自定义组件:封装可复用的天气组件,长期项目推荐
我选择了第三种方案,结合vue3的composition API,创建了可复用的天气组件。
3.2 核心组件实现
以下是温度显示组件的实现示例:
vue复制<template>
<view class="temperature-container">
<text class="current-temp">{{ currentTemp }}°</text>
<view class="temp-range">
<text class="max-temp">↑{{ maxTemp }}°</text>
<text class="min-temp">↓{{ minTemp }}°</text>
</view>
<text class="weather-desc">{{ weatherDesc }}</text>
</view>
</template>
<script setup>
const props = defineProps({
currentTemp: {
type: Number,
required: true
},
maxTemp: Number,
minTemp: Number,
weatherDesc: String
});
</script>
<style scoped>
.temperature-container {
display: flex;
flex-direction: column;
align-items: center;
margin: 20rpx 0;
}
.current-temp {
font-size: 72rpx;
font-weight: bold;
}
.temp-range {
display: flex;
margin: 10rpx 0;
}
.max-temp {
color: #ff4757;
margin-right: 20rpx;
}
.min-temp {
color: #1e90ff;
}
</style>
3.3 天气图标处理方案
天气图标处理有几个常见方案:
- 使用字体图标:如阿里图标库,体积小但颜色单一
- SVG图标:矢量清晰但需要额外处理
- PNG雪碧图:兼容性好但适配不同分辨率麻烦
我最终选择了SVG方案,配合uniapp的image组件实现:
vue复制<template>
<image
:src="getWeatherIcon(weatherCode)"
mode="aspectFit"
class="weather-icon"
/>
</template>
<script setup>
const getWeatherIcon = (code) => {
// 根据天气代码返回对应SVG路径
const iconMap = {
'100': '/static/weather/sunny.svg',
'101': '/static/weather/cloudy.svg',
// 其他天气代码映射...
};
return iconMap[code] || '/static/weather/unknown.svg';
};
</script>
实际项目中,可以将SVG转换为base64直接嵌入,减少HTTP请求。对于复杂动画效果,可以考虑使用lottie或svga库。
4. 性能优化与异常处理
4.1 数据缓存策略
天气数据不需要实时更新,合理的缓存策略可以显著提升用户体验并减少API调用:
javascript复制// 封装带缓存的天气获取方法
const getWeatherWithCache = async (location) => {
const cacheKey = `weather_${location}`;
try {
// 尝试从缓存获取
const cache = uni.getStorageSync(cacheKey);
if (cache && Date.now() - cache.timestamp < 30 * 60 * 1000) {
return cache.data; // 30分钟内使用缓存
}
// 缓存无效,请求新数据
const freshData = await getWeatherByLocation(location);
uni.setStorageSync(cacheKey, {
data: freshData,
timestamp: Date.now()
});
return freshData;
} catch (error) {
// 即使出错也尝试返回缓存
const cache = uni.getStorageSync(cacheKey);
if (cache) {
return cache.data;
}
throw error;
}
};
4.2 网络异常处理
移动端网络环境复杂,必须做好异常处理:
javascript复制async function loadWeather() {
try {
this.loading = true;
const location = await getLocation();
if (!location) return;
const weather = await getWeatherWithCache(
`${location.latitude},${location.longitude}`
);
this.weatherData = processWeatherData(weather);
} catch (error) {
console.error('加载天气失败:', error);
uni.showToast({
title: '加载天气失败',
icon: 'none'
});
// 可以在这里添加重试逻辑
if (this.retryCount < 3) {
setTimeout(() => {
this.retryCount++;
this.loadWeather();
}, 2000);
}
} finally {
this.loading = false;
}
}
4.3 平台特定优化
不同平台需要不同的优化策略:
iOS平台:
- 注意WKWebView的缓存策略
- 关注滚动性能,减少复杂CSS效果
- 使用
-webkit-overflow-scrolling: touch提升滚动体验
Android平台:
- 注意内存使用,大图需要优化
- WebView版本差异大,做好兼容测试
- 关注后台网络请求可能被系统限制
5. 扩展功能与进阶技巧
5.1 天气动画效果实现
提升用户体验的一个好方法是添加天气相关的动画效果。在uniapp中可以通过几种方式实现:
- CSS动画:适用于简单动画,如雨滴下落
css复制.raindrop {
position: absolute;
background: rgba(174, 194, 224, 0.5);
border-radius: 50%;
animation: fall linear infinite;
}
@keyframes fall {
to {
transform: translateY(100vh);
}
}
- Canvas绘制:适用于复杂天气效果
vue复制<template>
<canvas
canvas-id="weatherCanvas"
class="weather-canvas"
@touchstart="handleTouch"
></canvas>
</template>
<script>
export default {
mounted() {
this.initCanvas();
},
methods: {
initCanvas() {
this.ctx = uni.createCanvasContext('weatherCanvas', this);
this.drawWeatherEffect();
},
drawWeatherEffect() {
// 根据天气类型绘制不同效果
if (this.weatherType === 'rain') {
this.drawRain();
} else if (this.weatherType === 'snow') {
this.drawSnow();
}
// ...其他天气类型
}
}
}
</script>
- Lottie动画:适用于设计师制作的复杂动画
javascript复制// 需要引入lottie-uniapp插件
import lottie from '@/components/lottie/lottie.vue';
export default {
components: { lottie },
data() {
return {
animationData: require('@/static/animations/sunny.json')
};
}
};
5.2 主题切换与夜间模式
天气应用通常需要根据昼夜变化调整主题:
vue复制<template>
<view :class="['weather-container', isDaytime ? 'day-theme' : 'night-theme']">
<!-- 内容 -->
</view>
</template>
<script>
export default {
computed: {
isDaytime() {
const hours = new Date().getHours();
return hours > 6 && hours < 18;
}
}
};
</script>
<style>
.weather-container.day-theme {
background: linear-gradient(to bottom, #87CEEB, #E0F7FA);
color: #333;
}
.weather-container.night-theme {
background: linear-gradient(to bottom, #0F2027, #203A43);
color: #FFF;
}
</style>
5.3 天气预警功能实现
对于极端天气情况,应该添加预警提示:
javascript复制function checkWeatherWarning(weatherData) {
const warnings = [];
if (weatherData.air?.aqi > 150) {
warnings.push({
level: 'warning',
text: '空气质量较差',
icon: 'warning'
});
}
if (weatherData.daily_forecast?.[0]?.tmp_max > 35) {
warnings.push({
level: 'danger',
text: '高温预警',
icon: 'temperature-high'
});
}
return warnings;
}
在UI上突出显示这些预警信息:
vue复制<template>
<view v-if="warnings.length" class="warning-container">
<view
v-for="(warning, index) in warnings"
:key="index"
:class="['warning-item', warning.level]"
>
<uni-icons :type="warning.icon" size="18" />
<text>{{ warning.text }}</text>
</view>
</view>
</template>
<style>
.warning-container {
margin: 20rpx;
border-radius: 8rpx;
overflow: hidden;
}
.warning-item {
padding: 15rpx 20rpx;
display: flex;
align-items: center;
}
.warning-item.warning {
background-color: #FFF3CD;
color: #856404;
}
.warning-item.danger {
background-color: #F8D7DA;
color: #721C24;
}
</style>
6. 测试与发布注意事项
6.1 多平台测试要点
在发布前,需要在不同平台和设备上进行充分测试:
iOS测试重点:
- 定位权限弹窗是否正常显示
- 后台刷新天气数据是否正常
- 应用切换到后台后重新打开时的状态恢复
- 不同iOS版本的表现差异
Android测试重点:
- 不同厂商手机的权限管理差异
- 低端设备上的性能表现
- 各种屏幕尺寸的适配情况
- 后台服务保活能力
通用测试项:
- 无网络情况下的降级处理
- 网络从无到有的自动恢复
- 数据加载中的状态显示
- 错误边界情况处理
6.2 发布前的优化建议
-
图片资源优化:
- 使用TinyPNG等工具压缩图片
- 考虑使用WebP格式减小体积
- 实现懒加载非关键图片
-
代码优化:
- 使用uniapp的条件编译处理平台差异
javascript复制// #ifdef APP-PLUS // App端特有代码 // #endif- 移除未使用的组件和代码
- 启用生产环境构建的代码压缩
-
性能监测:
- 添加性能统计代码
javascript复制// 在关键生命周期添加性能标记 export default { onLoad() { this.loadStart = Date.now(); }, onReady() { const loadTime = Date.now() - this.loadStart; uni.reportAnalytics('page_load_time', { time: loadTime, page: 'weather' }); } }
6.3 实际开发中的经验教训
在开发过程中,我积累了一些宝贵的经验:
-
定位精度问题:
- 在iOS上,首次定位可能不够精确,需要设置
enableHighAccuracy: true - Android上可能需要先检查GPS是否开启
- 城市级别天气可以接受几百米误差,不需要过于精确
- 在iOS上,首次定位可能不够精确,需要设置
-
天气数据更新频率:
- 免费API通常有1-3小时的更新间隔
- 不要频繁请求,可能触发限流
- 客户端应该显示数据更新时间,避免用户困惑
-
UI性能优化:
- 避免在滚动容器中使用复杂CSS效果
- 天气动画在后台标签页应该暂停
- 使用
transform代替top/left做动画性能更好
-
跨平台差异:
- iOS的WebView对CSS支持更严格
- Android的WebView版本差异大
- 真机调试必不可少,模拟器不能完全代表真实环境
这个uniapp天气详情功能的实现过程让我深刻体会到,即使是看似简单的功能,在跨平台开发中也会遇到各种预料之外的挑战。关键在于充分了解各平台特性,做好异常处理,并在性能和用户体验之间找到平衡点。
