一、和风天气api
实时天气功能:
请求URL #
开发版https://devapi.qweather.com/v7/weather/now?[请求参数]
请求参数 #
请求参数包括必选和可选参数,如不填写可选参数将使用其默认值,参数之间使用&进行分隔。
key
用户认证key,请参考如何获取你的KEY。支持数字签名方式进行认证。例如 key=123456789ABC
location
需要查询地区的LocationID或以英文逗号分隔的经度,纬度坐标(十进制),LocationID可通过城市搜索服务获取。例如 location=101010100 或 location=116.41,39.92
lang
多语言设置,默认中文,当数据不匹配你设置的语言时,将返回英文或其本地语言结果。
zh 中文, 默认
en 英语
fr 法语
es 西班牙语
ja 日语
ko 韩语
unit
度量衡单位参数选择,例如温度选摄氏度或华氏度、公里或英里。默认公制单位
m 公制单位,默认
i 英制单位
gzip
对API接口进行压缩,可以极大的减少API接口访问延迟,减少缓存空间,提高接口连接成功率。默认开启gzip
y 使用gzip方式压缩,默认
n 不使用压缩
返回数据:
参数 | 描述 |
---|---|
code | API状态码,具体含义请参考状态码 |
updateTime | 当前API的最近更新时间 |
fxLink | 当前数据的响应式页面,便于嵌入网站或应用 |
now.obsTime | 数据观测时间 |
now.temp | 温度,默认单位:摄氏度 |
now.feelsLike | 体感温度,默认单位:摄氏度 |
now.icon | 天气状况和图标的代码,图标可通过天气状况和图标下载 |
now.text | 天气状况的文字描述,包括阴晴雨雪等天气状态的描述 |
now.wind360 | 风向360角度 |
now.windDir | 风向 |
now.windScale | 风力等级 |
now.windSpeed | 风速,公里/小时 |
now.humidity | 相对湿度,百分比数值 |
now.precip | 当前小时累计降水量,默认单位:毫米 |
now.pressure | 大气压强,默认单位:百帕 |
now.vis | 能见度,默认单位:公里 |
now.cloud | 云量,百分比数值 |
now.dew | 露点温度 |
refer.sources | 原始数据来源,或数据源说明,可能为空 |
refer.license | 数据许可或版权声明,可能为空 |
然后注册账号,并在控制台——应用管理添加应用,获得key
将key和请求url导入到项目中:
引入到页面.js文件中
二、项目代码
index.wxml文件:
<navBar
title-text="天气功能"
back-icon="../../src/images/back@3x.png"
background="#f2f2f2"
bindback="back"/>
<view class="container {{isIPhoneX ? 'iphonex-padding' : ''}}" catchtap='menuHide'>
<heartbeat id='heartbeat' wx:if='{{showHeartbeat}}'></heartbeat>
<view class='bcg' wx:if='{{!bcgImg}}' style='background: {{bcgColor}}'></view>
<image class='bcg' wx:if='{{bcgImg}}' src='{{bcgImg}}' mode='aspectFill'></image>
<view class='search' wx:if='{{!setting.hiddenSearch && !bcgImgAreaShow}}' style='background:rgba(255, 255, 255, 0)'>
<view class='wrapper'>
<image src='/img/search.png'></image>
<input placeholder-class='placeholderClass' confirm-type='search' placeholder='请输入城市名,快速查询天气信息' maxlength='20' bindconfirm='commitSearch' value='{{searchText}}' disabled='{{!enableSearch}}'></input>
</view>
</view>
<view class='chooseBcg' wx:if='{{bcgImgAreaShow}}'>
<view class='top'>
<view class='title'>更换背景</view>
<view class='bcgs'>
<view class='border {{bcgImgIndex === index ? "active" : ""}}' wx:for='{{bcgImgList}}' wx:key='{{index}}'>
<image src='{{item.src}}' catchtap='chooseBcg' data-index='{{index}}' data-src='{{item.src}}'></image>
</view>
</view>
</view>
<view class='close' catchtap='hideBcgImgArea'>
<image src='/img/up-arrow.png'></image>
</view>
</view>
<view class='content' wx:if='{{!bcgImgAreaShow}}' style='margin-top: {{setting.hiddenSearch ? 20 : 60}}px'>
<view class='avatarInfo' catchtap='showBcgImgArea'>
<open-data class='avatar' type='userAvatarUrl'></open-data>
<open-data class='name' type='userNickName'></open-data>
<image class='downArrow' src='/img/down.png'></image>
</view>
<view class='info'>
<view class='city'>
<view class='name' bindtap='toCitychoose'>
<image wx:if='{{located}}' class='icon' src='/img/location_s_w.png'></image>
<view class='val'>{{cityDatas.basic.location || '定位中'}}</view>
<image class='down' src='/img/down.png'></image>
</view>
<text class='time' wx:if='{{cityDatas.updateTimeFormat}}'>{{cityDatas.updateTimeFormat}} 更新</text>
</view>
<view class='message'>{{message}}</view>
<view class='temp num' decode='true'>{{cityDatas.now.tmp || '-'}}<text style='font-size:50rpx;position:relative;top:-20px;'>℃</text></view>
<view class='weather'>{{cityDatas.now.cond_txt || '--'}}</view>
<view class='pm'>
<text>能见度 {{cityDatas.now.vis}}</text>
</view>
</view>
<view class='guide' wx:if='{{cityDatas.daily_forecast}}'>
<view class='title'>3 天预报</view>
<view class='guides'>
<view class='item' wx:for='{{cityDatas.daily_forecast}}' wx:key='{{index}}'>
<view class='date i'>{{item.date}}</view>
<view class='temperature i'>{{item.tmp_max}}~{{item.tmp_min}}℃</view>
<view class='weather i'>
<text>{{item.cond_txt_d}}</text>
<image mode='widthFix' src='{{weatherIconUrl}}{{item.cond_code_d}}.png'></image>
</view>
<view class='wind i'>{{item.wind_dir}}{{item.wind_sc}}级</view>
</view>
</view>
</view>
<view class='details'>
<view class='detail' wx:for='{{detailsDic.key}}' wx:key='{{index}}'>
<view>{{detailsDic.val[item]}}</view>
<view>{{cityDatas.now[item]}}</view>
</view>
</view>
<view class='hourly' wx:if='{{hourlyDatas.length}}'>
<view class='title'>24 小时逐 3 小时预报</view>
<view class='hours'>
<swiper style='height:360rpx;' indicator-dots="{{false}}" autoplay="{{false}}" circular="{{false}}" duration="300" next-margin="50rpx">
<block wx:for="{{hourlyDatas}}" wx:key="{{index}}">
<swiper-item>
<view class='hour'>
<view class='detail'>
<view>温度(℃)</view>
<view>{{item.tmp}}</view>
</view>
<view class='detail'>
<view>天气</view>
<view class='weather'>
<text>{{item.cond_txt}}</text>
<image mode='widthFix' src='{{weatherIconUrl}}{{item.cond_code}}.png'></image>
</view>
</view>
<view class='detail'>
<view>相对湿度(%)</view>
<view>{{item.hum}}</view>
</view>
<view class='detail'>
<view>露点温度(℃)</view>
<view>{{item.dew}}</view>
</view>
<view class='detail'>
<view>降水概率</view>
<view>{{item.pop}}</view>
</view>
<view class='detail'>
<view>风向</view>
<view>{{item.wind_dir}}</view>
</view>
<view class='detail'>
<view>风向角度(deg)</view>
<view>{{item.wind_deg}}</view>
</view>
<view class='detail'>
<view>风力(级)</view>
<view>{{item.wind_sc}}</view>
</view>
<view class='detail'>
<view>风速(mk/h)</view>
<view>{{item.wind_spd}}</view>
</view>
<view class='detail'>
<view>气压(mb)</view>
<view>{{item.pres}}</view>
</view>
<view class='detail'>
<view>云量</view>
<view>{{item.cloud}}</view>
</view>
</view>
<view class='time'>{{item.time}}</view>
</swiper-item>
</block>
</swiper>
</view>
</view>
<!-- <ad unit-id="adunit-785cc3f710cc3d4c"></ad> -->
<view class='livingIndex' wx:if='{{!setting.hiddenIndex}}'>
<view class='item' wx:for='{{cityDatas.lifestyle}}' wx:key='{{index}}'>
<image class='icon' src='/img/lifestyle_{{item.type}}.png'></image>
<view class='right'>
<view class='key'>{{lifestyles[item.type]}} {{item.brf}}</view>
<view class='value'>{{item.txt}}</view>
</view>
</view>
</view>
</view>
</view>
inde.wxss文件:
page {
-webkit-font-smoothing: antialiased;
font-family: "PingHei","Helvetica Neue","Helvetica","Arial","Verdana","sans-serif";
}
.num {
font-weight: 300;
}
.container {
display: flex;
flex-direction: column;
min-height: 100vh;
color: #fff;
font-size: 30rpx;
}
.bcg {
position: fixed;
z-index: 2;
height: 97%;
width: 100%;
top: 500;
right: 500;
bottom: 400;
left: 400;
/* background: #40a7e7;
background: linear-gradient(to bottom, #73C6F1, #50B5EC); */
}
.search {
position: fixed;
z-index: 4;
/* top: ; */
left: 0;
right: 0;
width: 100%;
height: 90rpx;
/* background: #40a7e7; */
}
.search .wrapper {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
font-size: 28rpx;
height: 60rpx;
margin: 15rpx 50rpx;
padding: 0 15rpx;
box-sizing: border-box;
/* border-radius: 10rpx; */
/* border: 1rpx solid #999; */
border: none;
border-bottom: 1rpx solid #fff;
}
.search image {
width: 28rpx;
height: 28rpx;
margin-right: 16rpx;
}
.search input {
flex: 1;
}
.placeholderClass {
color: #fff;
}
.chooseBcg {
display: flex;
flex-direction: column;
min-height: 100vh;
position: fixed;
z-index: 100;
top: 0;
left: 0;
right: 0;
width: 100%;
font-size: 24rpx;
color: #333;
}
.chooseBcg .top {
box-sizing: border-box;
padding: 20rpx 20rpx 50rpx;
background: rgba(255, 255, 255, 0);
color: #fff;
}
.chooseBcg .bcgs {
display: flex;
align-items: center;
margin-top: 20rpx;
overflow: scroll;
}
.chooseBcg .border {
padding: 6rpx;
margin-right: 14rpx;
border: 1rpx solid rgba(255, 255, 255, 0);
transition: .1s ease;
}
.chooseBcg .border:last-child {
margin-right: 0;
}
.chooseBcg .border.active {
border: 1rpx solid #40a7e7;
transition: .1s ease;
}
.chooseBcg .bcgs image {
width: 110rpx;
height: 190rpx;
}
.chooseBcg .close {
flex: 1;
background: rgba(0, 0, 0, 0);
padding: 20rpx 0;
text-align: center;
}
.chooseBcg .close image {
width: 64rpx;
height: 64rpx;
}
.avatarInfo {
display: flex;
align-items: center;
padding: 0 50rpx;
}
.avatarInfo .avatar {
display: block;
overflow: hidden;
width: 60rpx;
height: 60rpx;
border-radius: 50%;
}
.avatarInfo .name {
padding: 0 20rpx;
font-size: 30rpx;
}
.avatarInfo .downArrow {
width: 20rpx;
height: 20rpx;
}
.container .content {
flex: 2;
transition: .3s ease;
}
.content {
position: relative;
z-index: 2;
}
.info {
padding: 10rpx 0 50rpx;
/* border-bottom: 1rpx solid rgba(240, 240, 240, .4); */
}
.info .temp,
.info .pm,
.info .pm view,
.info .weather {
position: relative;
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.info .weather {
margin-bottom: 30rpx;
}
.info .pm text {
font-size: 24rpx;
height: 1em;
line-height: 1em;
padding: 10rpx 20rpx;
border-radius: 20rpx;
background: rgba(240, 240, 240, .2);
}
.city {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
padding: 0 50rpx;
font-size: 66rpx;
}
.city .name {
display: flex;
justify-content: center;
align-items: center;
}
.city .name .val {
max-width: 5em;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
}
.city .name .icon,
.city .name .down {
width: 30rpx;
height: 30rpx;
}
.city .name .icon {
margin-right: 20rpx;
}
.city .name .down {
margin-left: 20rpx;
}
.city .time {
display: block;
text-align: right;
font-size: 24rpx;
}
.message {
font-size: 22rpx;
color: #fff;
padding: 30rpx 50rpx 0;
box-sizing: border-box;
}
.temp {
height: 360rpx;
font-size: 200rpx;
}
.guide .guides {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
font-size: 24rpx;
padding-top: 20rpx;
/* border-bottom: 1rpx solid rgba(240, 240, 240, .4); */
background: rgba(0, 0, 0, .6);
margin-bottom: 20rpx;
overflow-x: scroll;
}
.guide .title,
.hourly .title {
border-bottom: 1rpx solid rgba(250, 250, 250, .2);
background: rgba(0, 0, 0, .6);
font-size: 24rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
}
.guide .item {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
width: 170rpx;
flex-shrink: 0;
}
.guide image {
width: 44rpx;
}
.guide .i {
padding-bottom: 20rpx;
}
.guide .i.weather,
.hour .weather {
display: flex;
justify-content: center;
align-items: center;
}
.guide .i image,
.hour .weather image {
width: 40rpx;
margin-left: 4rpx;
}
.hourly {
margin-bottom: 20rpx;
}
.hourly .hours {
padding: 20rpx 10rpx 0;
background: rgba(0, 0, 0, .6);
}
.hourly .hours .time {
font-size: 22rpx;
height: 60rpx;
line-height: 60rpx;
text-align: center;
}
.details {
display: flex;
align-items: center;
flex-wrap: wrap;
background:rgba(0, 0, 0, .6);
margin-bottom:20rpx;
font-size: 24rpx;
}
.hour {
display: flex;
align-items: center;
flex-wrap: wrap;
background: rgb(107, 102, 102);
font-size: 24rpx;
box-sizing: border-box;
border-radius: 8rpx;
}
.hourly swiper-item {
box-sizing: border-box;
padding: 0 10rpx;
background: rgba(0, 0, 0, 0);
}
.details .detail,
.hour .detail {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
padding: 10rpx 0;
height: 100rpx;
width: 25%;
border-right: 1rpx solid rgba(250, 250, 250, 0.2);
border-bottom: 1rpx solid rgba(250, 250, 250, 0.2);
box-sizing: border-box;
}
.details .detail:nth-child(4n),
.hour .detail:nth-child(4n) {
border-right: none;
}
.details .detail:nth-child(n+9),
.hour .detail:nth-child(n+9) {
border-bottom: none;
}
.livingIndex {
/* border-bottom: 1rpx solid rgba(240, 240, 240, .4); */
background: rgba(0, 0, 0, .6);
margin-bottom: 20rpx;
}
.livingIndex .item {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
border-bottom: 1rpx solid rgba(250, 250, 250, .2);
padding: 20rpx 0;
margin: 0 30rpx;
}
.livingIndex .item:last-child {
border-bottom: none;
}
.livingIndex .item .right {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
flex: 1;
}
.livingIndex .item .value {
margin-top: 10rpx;
font-size: 24rpx;
}
.livingIndex .icon {
width: 80rpx;
height: 80rpx;
padding: 0 24rpx;
}
index.js代码:
let utils = require('../../utils/utils')
let globalData = getApp().globalData
const key = globalData.key
let SYSTEMINFO = globalData.systeminfo
Page({
data: {
message: '',
cityDatas: {},
hourlyDatas: [],
weatherIconUrl: globalData.weatherIconUrl,
detailsDic: {
key: ['tmp', 'fl', 'hum', 'pcpn', 'wind_dir', 'wind_deg', 'wind_sc', 'wind_spd', 'vis', 'pres', 'cloud', ''],
val: {
tmp: '温度(℃)',
fl: '体感温度(℃)',
hum: '相对湿度(%)',
pcpn: '降水量(mm)',
wind_dir: '风向',
wind_deg: '风向角度(deg)',
wind_sc: '风力(级)',
wind_spd: '风速(mk/h)',
vis: '能见度(km)',
pres: '气压(mb)',
cloud: '云量',
},
},
lifestyles: {
'comf': '舒适度指数',
'cw': '洗车指数',
'drsg': '穿衣指数',
'flu': '感冒指数',
'sport': '运动指数',
'trav': '旅游指数',
'uv': '紫外线指数',
'air': '空气污染扩散条件指数',
'ac': '空调开启指数',
'ag': '过敏指数',
'gl': '太阳镜指数',
'mu': '化妆指数',
'airc': '晾晒指数',
'ptfc': '交通指数',
'fsh': '钓鱼指数',
'spi': '防晒指数',
},
// 用来清空 input
searchText: '',
// 是否已经弹出
hasPopped: false,
animationMain: {},
animationOne: {},
animationTwo: {},
animationThree: {},
// 是否切换了城市
located: true,
// 需要查询的城市
searchCity: '',
setting: {},
bcgImgIndex: 0,
bcgImg: '',
bcgImgAreaShow: false,
bcgColor: '#2d2225',
showHeartbeat: true,
enableSearch: true,
openSettingButtonShow: false,
shareInfo: {},
},
success(data, location) {
this.setData({
openSettingButtonShow: false,
searchCity: location,
})
wx.stopPullDownRefresh()
let now = new Date()
// 存下来源数据
data.updateTime = now.getTime()
data.updateTimeFormat = utils.formatDate(now, "MM-dd hh:mm")
wx.setStorage({
key: 'cityDatas',
data,
})
this.setData({
cityDatas: data,
})
},
fail(res) {
wx.stopPullDownRefresh()
let errMsg = res.errMsg || ''
// 拒绝授权地理位置权限
if (errMsg.indexOf('deny') !== -1 || errMsg.indexOf('denied') !== -1) {
wx.showToast({
title: '需要开启地理位置权限',
icon: 'none',
duration: 2500,
success: (res) => {
if (this.canUseOpenSettingApi()) {
let timer = setTimeout(() => {
clearTimeout(timer)
wx.openSetting({})
}, 2500)
} else {
this.setData({
openSettingButtonShow: true,
})
}
},
})
} else {
wx.showToast({
title: '网络不给力,请稍后再试',
icon: 'none',
})
}
},
commitSearch(res) {
let val = ((res.detail || {}).value || '').replace(/\s+/g, '')
this.search(val)
},
dance() {
this.setData({
enableSearch: false,
})
let heartbeat = this.selectComponent('#heartbeat')
heartbeat.dance(() => {
this.setData({
showHeartbeat: false,
enableSearch: true,
})
this.setData({
showHeartbeat: true,
})
})
},
clearInput() {
this.setData({
searchText: '',
})
},
search(val, callback) {
if (val === '520' || val === '521') {
this.clearInput()
this.dance()
return
}
wx.pageScrollTo({
scrollTop: 0,
duration: 300,
})
if (val) {
this.setData({
located: false,
})
this.getWeather(val)
this.getHourly(val)
}
callback && callback()
},
canUseOpenSettingApi() {
let systeminfo = getApp().globalData.systeminfo
let SDKVersion = systeminfo.SDKVersion
let version = utils.cmpVersion(SDKVersion, '2.0.7')
if (version < 0) {
return true
} else {
return false
}
},
init(params, callback) {
this.setData({
located: true,
})
wx.getLocation({
success: (res) => {
this.getWeather(`${res.latitude},${res.longitude}`)
this.getHourly(`${res.latitude},${res.longitude}`)
callback && callback()
},
fail: (res) => {
this.fail(res)
}
})
},
getWeather(location) {
wx.request({
url: `${globalData.requestUrl.weather}`,
data: {
location,
key,
},
success: (res) => {
if (res.statusCode === 200) {
let data = res.data.HeWeather6[0]
if (data.status === 'ok') {
this.clearInput()
this.success(data, location)
} else {
wx.showToast({
title: '查询失败',
icon: 'none',
})
}
}
},
fail: () => {
wx.showToast({
title: '查询失败',
icon: 'none',
})
},
})
},
getHourly(location) {
wx.request({
url: `${globalData.requestUrl.hourly}`,
data: {
location,
key,
},
success: (res) => {
if (res.statusCode === 200) {
let data = res.data.HeWeather6[0]
if (data.status === 'ok') {
this.setData({
hourlyDatas: data.hourly || []
})
}
}
},
fail: () => {
wx.showToast({
title: '查询失败',
icon: 'none',
})
},
})
},
onPullDownRefresh(res) {
this.reloadPage()
},
getCityDatas() {
let cityDatas = wx.getStorage({
key: 'cityDatas',
success: (res) => {
this.setData({
cityDatas: res.data,
})
},
})
},
setBcgImg(index) {
if (index !== undefined) {
this.setData({
bcgImgIndex: index,
bcgImg: this.data.bcgImgList[index].src,
bcgColor: this.data.bcgImgList[index].topColor,
})
this.setNavigationBarColor()
return
}
wx.getStorage({
key: 'bcgImgIndex',
success: (res) => {
let bcgImgIndex = res.data || 0
this.setData({
bcgImgIndex,
bcgImg: this.data.bcgImgList[bcgImgIndex].src,
bcgColor: this.data.bcgImgList[bcgImgIndex].topColor,
})
this.setNavigationBarColor()
},
fail: () => {
this.setData({
bcgImgIndex: 0,
bcgImg: this.data.bcgImgList[0].src,
bcgColor: this.data.bcgImgList[0].topColor,
})
this.setNavigationBarColor()
},
})
},
setNavigationBarColor(color) {
let bcgColor = color || this.data.bcgColor
wx.setNavigationBarColor({
frontColor: '#ffffff',
backgroundColor: this.data.bcgColor,
})
},
getBroadcast(callback) {
wx.cloud.callFunction({
name: 'getBroadcast',
data: {
hour: new Date().getHours(),
},
})
.then(res => {
let data = res.result.data
})
},
reloadWeather() {
if (this.data.located) {
this.init({})
} else {
this.search(this.data.searchCity)
this.setData({
searchCity: '',
})
}
},
onLoad() {
this.reloadPage()
},
reloadPage() {
this.setBcgImg()
this.getCityDatas()
this.reloadInitSetting()
this.reloadWeather()
// this.reloadGetBroadcast()
},
showBcgImgArea() {
this.setData({
bcgImgAreaShow: true,
})
},
hideBcgImgArea() {
this.setData({
bcgImgAreaShow: false,
})
},
toCitychoose() {
wx.navigateTo({
url: '/pages/citychoose/citychoose',
})
},
initSetting(successFunc) {
wx.getStorage({
key: 'setting',
success: (res) => {
let setting = res.data || {}
this.setData({
setting,
})
successFunc && successFunc(setting)
},
fail: () => {
this.setData({
setting: {},
})
},
})
},
})
三、效果展示
点击查看天气: