鸿蒙eTS版《坚果天气》

2022-12-05 15:07:27 浏览数 (1)

作者:坚果 公众号:"大前端之旅" 华为云享专家,InfoQ签约作者,OpenHarmony布道师

先来看一下效果

image-20220720175843998

本项目界面搭建基于ArkUI中TS扩展的声明式开发范式,

数据接口是?和风(天气预报),

使用ArkUI自带的网络请求调用接口。

我想要实现的一个功能是,查询当前城市的实时天气,

目前已实现的功能有:

  • 默认查询北京的天气预报
  • 查看当前的天气
  • 查看未来七天的天气

通过本项目,你能学到的知识有:

  • 网络请求
  • 条件渲染
  • 状态管理

先来看一下

目录结构

代码语言:javascript复制
├── config.json
├── ets
│   └── MainAbility
│       ├── app.ets
│       ├── common
│       │   └── RealtimeWeather.ets
│       ├── data
│       │   ├── get_test.ets
│       │   └── get_week_test.ets
│       ├── model
│       │   ├── daily.ets
│       │   ├── now.ets
│       │   └── weatherModel.ets
│       └── pages
│           └── Main.ets
└── resources
    ├── base
    │   ├── element
    │   │   ├── color.json
    │   │   └── string.json
    │   └── media
    │       └── icon.png
    └── rawfile


接下来开始正文,

我们先分析一下结构:

image-20220720212654686

我们可以分为三块

第一部分为实时天气信息栏

image-20220720213659159

代码如下

代码语言:javascript复制
// @ts-nocheck

/**
 * 该组件为实时天气预报组件
 *
 * powered by 坚果
 * 2022/7/20
 */

@Entry
@Component
 export struct RealtimeWeather{
  @State temp: string = "9"
  @State text: string = "坚果"
  @State isRequestSucceed: boolean = true

 build(){


    Column() {
      Text($r("app.string.city"))
        .fontSize(30)

      Row() {

        Text(this.temp)
          .fontSize(100)

        Text('℃')
          .fontSize(30)
          .margin({ top: 10 })
      }
      .alignItems(VerticalAlign.Top)
      .margin({ top: 5 })

      Text(this.text)
        .fontSize(36)
      .margin({ top: 5 })
    }.margin({ top: 50 })
  }



 }

第二部分为

代码语言:javascript复制
 this.WeatherText("日期")
          this.WeatherText("天气")
          this.WeatherText("日出")
          this.WeatherText("日落")

第三部分为:

代码语言:javascript复制
Scroll(){
 Column(){
   ForEach(this.future, (item: WeatherWeekData) => {

     Row() {
       this.WeatherText(item.fxDate)
       this.WeatherText(item.textDay)
       this.WeatherText(item.sunrise)
       this.WeatherText(item.sunset)


     }.margin({left:10})


   }, item => item.fxDate)
 }
}

最后用Column包裹

完整的代码如下:

Main.ets

代码语言:javascript复制
// @ts-nocheck

/*
 * Copyright (c) 2021 JianGuo Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { WeatherModel, WeatherData, WeatherWeekData, } from '../model/weatherModel';

import { RealtimeWeather } from '../common/RealtimeWeather'
import { getWeekTest } from '../data/get_week_test'
import { getTest } from '../data/get_test'

import prompt from '@system.prompt';
import http from '@ohos.net.http';


@Entry
@Component
struct Main {
  aboutToAppear() {

    this.getRequest()
    this.getWeekRequest()
  }

  @State realtime: WeatherData = getTest()
  @State future: Array<WeatherWeekData> = getWeekTest()
  @State isRequestSucceed: boolean = true

  @Builder WeatherText(text: string) {
    Text(text)
      .fontSize(14)
      .layoutWeight(1)
      .textAlign(TextAlign.Center)
      .margin({ top: 10, bottom: 10 })
  }

  build() {


    Column() {
      if (this.isRequestSucceed) {
        // 当前天气
        RealtimeWeather({ temp: this.realtime.temp, text: this.realtime.text })

        Row() {
          this.WeatherText("日期")
          this.WeatherText("天气")
          this.WeatherText("日出")
          this.WeatherText("日落")


        }.margin({top:20})


       Scroll(){
        Column(){
          ForEach(this.future, (item: WeatherWeekData) => {

            Row() {
              this.WeatherText(item.fxDate)
              this.WeatherText(item.textDay)
              this.WeatherText(item.sunrise)
              this.WeatherText(item.sunset)


            }.margin({left:10})


          }, item => item.fxDate)
        }
       }

        Text("数据来自和风天气")
          .fontSize(14)

          .margin({ bottom: 30 })


      }
    }.width("100%").height("100%")
  }


  // 请求方式:GET 获取一周天气预报

  getWeekRequest() {
    // 每一个httpRequest对应一个http请求任务,不可复用
    let httpRequest = http.createHttp()
    let url = 'https://devapi.qweather.com/v7/weather/7d?location=101010100&key=48fbadf80bbc43ce853ab9a92408373e'
    httpRequest.request(url, (err, data) => {
      if (!err) {
        if (data.responseCode == 200) {
          console.info('=====data.result====='   data.result)

          // 解析数据
          var weatherModel: WeatherModel = JSON.parse(data.result.toString())
          // 判断接口返回码,0成功
          if (weatherModel.code == 200) {
            // 设置数据

            this.future = weatherModel.daily
            this.isRequestSucceed = true;
            ForEach(weatherModel.daily, (item: WeatherWeekData) => {
              console.log(console.info('=====data.result item.fxDate====='   item.fxDate))

            }, item => item.date)

            console.info('=====data.result==='   weatherModel.daily)

          } else {
            // 接口异常,弹出提示
            prompt.showToast({ message: "数据请求失败" })
          }

        } else {
          // 请求失败,弹出提示
          prompt.showToast({ message: '网络异常' })
        }
      } else {
        // 请求失败,弹出提示
        prompt.showToast({ message: err.message })
      }
    })
  }


  // 请求方式:GET
  getRequest() {
    // 每一个httpRequest对应一个http请求任务,不可复用
    let httpRequest = http.createHttp()
    let url = 'https://devapi.qweather.com/v7/weather/now?location=101010100&key=48fbadf80bbc43ce853ab9a92408373e'
    httpRequest.request(url, (err, data) => {
      if (!err) {
        if (data.responseCode == 200) {
          console.info('=====data.result====='   data.result)
          // 解析数据
          //this.content= data.result;
          // 解析数据
          var weatherModel: WeatherModel = JSON.parse(data.result.toString())
          // 判断接口返回码,0成功
          if (weatherModel.code == 200) {
            // 设置数据

            this.realtime = weatherModel.now


            this.isRequestSucceed = true;

            console.info('=====data.result===this.content=='   weatherModel.now)

          } else {
            // 接口异常,弹出提示
            prompt.showToast({ message: "数据请求失败" })
          }

        } else {
          // 请求失败,弹出提示
          prompt.showToast({ message: '网络异常' })
        }
      } else {
        // 请求失败,弹出提示
        prompt.showToast({ message: err.message })
      }
    })
  }
}

里面用到了网络请求

网络请求的步骤

1、声明网络请求权限

entry下的config.jsonmodule字段下配置权限

代码语言:javascript复制
"reqPermissions": [
   {
      "name": "ohos.permission.INTERNET"
   }
]

2、支持http明文请求

默认支持https,如果要支持http,在entry下的config.jsondeviceConfig字段下配置

代码语言:javascript复制
"default": {
  "network": {
    "cleartextTraffic": true
  }
}

3、创建HttpRequest

代码语言:javascript复制
// 导入模块
import http from '@ohos.net.http';
// 创建HttpRequest对象
let httpRequest = http.createHttp();

4、发起请求

GET请求(默认为GET请求

代码语言:javascript复制
  // 请求方式:GET
  getRequest() {
    // 每一个httpRequest对应一个http请求任务,不可复用
    let httpRequest = http.createHttp()
    let url = 'https://devapi.qweather.com/v7/weather/now?location=101010100&key=48fbadf80bbc43ce853ab9a92408373e'
    httpRequest.request(url, (err, data) => {
      if (!err) {
        if (data.responseCode == 200) {
          console.info('=====data.result====='   data.result)
          // 解析数据
          //this.content= data.result;
          // 解析数据
          var weatherModel: WeatherModel = JSON.parse(data.result.toString())
          // 判断接口返回码,0成功
          if (weatherModel.code == 200) {
            // 设置数据

            this.realtime = weatherModel.now


            this.isRequestSucceed = true;

            console.info('=====data.result===this.content=='   weatherModel.now)

          } else {
            // 接口异常,弹出提示
            prompt.showToast({ message: "数据请求失败" })
          }

        } else {
          // 请求失败,弹出提示
          prompt.showToast({ message: '网络异常' })
        }
      } else {
        // 请求失败,弹出提示
        prompt.showToast({ message: err.message })
      }
    })}

5、解析数据(简单示例)

1.网络请求到的json字符串

代码语言:javascript复制
export function getTest() {
  return [
    {
      "obsTime": "2022-07-20T09:24 08:00",
      "temp": "28",
      "feelsLike": "29",
      "icon": "101",
      "text": "多云",
      "wind360": "225",
      "windDir": "西南风",
      "windScale": "3",
      "windSpeed": "17",
      "humidity": "71",
      "precip": "0.0",
      "pressure": "1000",
      "vis": "8",
      "cloud": "91",
      "dew": "21"
    },
  ]
}

2.创建相应的对象

代码语言:javascript复制

export class WeatherWeekData {
  fxDate: string //
  sunrise: string //
  sunset: string //
  moonrise: string //
  moonset: string //
  moonPhase: string //
  moonPhaseIcon: string //
  tempMax: string //
  tempMin: string //
  iconDay: string //
  textDay: string
  textNight: string //
  wind360Day: string //
  windDirDay: string //
  windScaleDay: string //
  windSpeedDay: string //
  wind360Night: string //
  windDirNight: string //
  dew: string //


  windScaleNight: string // ,
  windSpeedNight: string //
  humidity: string //
  precip: string //
  pressure: string //
  vis: string //


  cloud: string //
  uvIndex: string //


}

实况天气

目前支持全国4000 个市县区和海外15万个城市实时天气数据,包括实时温度、体感温度、风力风向、相对湿度、大气压强、降水量、能见度、露点温度、云量等数据。

请求URL

代码语言:javascript复制
// 北京实况天气 

 https://devapi.qweather.com/v7/weather/now?location=101010100&key=你的KEY

请求参数

请求参数包括必选和可选参数,如不填写可选参数将使用其默认值,参数之间使用&进行分隔。

key

用户认证key,请参考?如何获取你的KEY。例如 key=123456789ABC

location

需要查询地区的LocationID或以英文逗号分隔的经度,纬度坐标十进制,最多支持小数点后两位),LocationID可通过?城市搜索服务获取。例如 location=101010100location=116.41,39.92

返回数据格式

代码语言:javascript复制
// 北京实况天气 

//  https://devapi.qweather.com/v7/weather/now?location=101010100&key=你的KEY

{
  "code": "200",
  "updateTime": "2020-06-30T22:00 08:00",
  "fxLink": "http://hfx.link/2ax1",
  "now": {
    "obsTime": "2020-06-30T21:40 08:00",
    "temp": "24",
    "feelsLike": "26",
    "icon": "101",
    "text": "多云",
    "wind360": "123",
    "windDir": "东南风",
    "windScale": "1",
    "windSpeed": "3",
    "humidity": "72",
    "precip": "0.0",
    "pressure": "1003",
    "vis": "16",
    "cloud": "10",
    "dew": "21"
  },
  "refer": {
    "sources": [
      "QWeather",
      "NMC",
      "ECMWF"
    ],
    "license": [
      "commercial license"
    ]
  }
}
代码语言:javascript复制

  // 请求方式:GET
  getRequest() {
    // 每一个httpRequest对应一个http请求任务,不可复用
    let httpRequest = http.createHttp()
    let url = 'https://devapi.qweather.com/v7/weather/now?location=101010100&key=48fbadf80bbc43ce853ab9a92408373e'
    httpRequest.request(url, (err, data) => {
      if (!err) {
        if (data.responseCode == 200) {
          console.info('=====data.result====='   data.result)
          // 解析数据
          //this.content= data.result;
          // 解析数据
          var weatherModel: WeatherModel = JSON.parse(data.result.toString())
          // 判断接口返回码,0成功
          if (weatherModel.code == 200) {
            // 设置数据

            this.realtime = weatherModel.now


            this.isRequestSucceed = true;

            console.info('=====data.result===this.content=='   weatherModel.now)

          } else {
            // 接口异常,弹出提示
            prompt.showToast({ message: "数据请求失败" })
          }

        } else {
          // 请求失败,弹出提示
          prompt.showToast({ message: '网络异常' })
        }
      } else {
        // 请求失败,弹出提示
        prompt.showToast({ message: err.message })
      }
    })
  }

七天天气预报

接口

代码语言:javascript复制
// 北京7天预报 

//  https://devapi.qweather.com/v7/weather/7d?location=101010100&key=你的KEY

返回数据

代码语言:javascript复制
// 北京3天预报 
// 商业版 https://api.qweather.com/v7/weather/3d?location=101010100&key=你的KEY
// 开发版 https://devapi.qweather.com/v7/weather/3d?location=101010100&key=你的KEY

{
  "code": "200",
  "updateTime": "2021-11-15T16:35 08:00",
  "fxLink": "http://hfx.link/2ax1",
  "daily": [
    {
      "fxDate": "2021-11-15",
      "sunrise": "06:58",
      "sunset": "16:59",
      "moonrise": "15:16",
      "moonset": "03:40",
      "moonPhase": "盈凸月",
      "moonPhaseIcon": "803",
      "tempMax": "12",
      "tempMin": "-1",
      "iconDay": "101",
      "textDay": "多云",
      "iconNight": "150",
      "textNight": "晴",
      "wind360Day": "45",
      "windDirDay": "东北风",
      "windScaleDay": "1-2",
      "windSpeedDay": "3",
      "wind360Night": "0",
      "windDirNight": "北风",
      "windScaleNight": "1-2",
      "windSpeedNight": "3",
      "humidity": "65",
      "precip": "0.0",
      "pressure": "1020",
      "vis": "25",
      "cloud": "4",
      "uvIndex": "3"
    },
    {
      "fxDate": "2021-11-16",
      "sunrise": "07:00",
      "sunset": "16:58",
      "moonrise": "15:38",
      "moonset": "04:40",
      "moonPhase": "盈凸月",
      "moonPhaseIcon": "803",
      "tempMax": "13",
      "tempMin": "0",
      "iconDay": "100",
      "textDay": "晴",
      "iconNight": "101",
      "textNight": "多云",
      "wind360Day": "225",
      "windDirDay": "西南风",
      "windScaleDay": "1-2",
      "windSpeedDay": "3",
      "wind360Night": "225",
      "windDirNight": "西南风",
      "windScaleNight": "1-2",
      "windSpeedNight": "3",
      "humidity": "74",
      "precip": "0.0",
      "pressure": "1016",
      "vis": "25",
      "cloud": "1",
      "uvIndex": "3"
    },
    {
      "fxDate": "2021-11-17",
      "sunrise": "07:01",
      "sunset": "16:57",
      "moonrise": "16:01",
      "moonset": "05:41",
      "moonPhase": "盈凸月",
      "moonPhaseIcon": "803",
      "tempMax": "13",
      "tempMin": "0",
      "iconDay": "100",
      "textDay": "晴",
      "iconNight": "150",
      "textNight": "晴",
      "wind360Day": "225",
      "windDirDay": "西南风",
      "windScaleDay": "1-2",
      "windSpeedDay": "3",
      "wind360Night": "225",
      "windDirNight": "西南风",
      "windScaleNight": "1-2",
      "windSpeedNight": "3",
      "humidity": "56",
      "precip": "0.0",
      "pressure": "1009",
      "vis": "25",
      "cloud": "0",
      "uvIndex": "3"
    }
  ],
  "refer": {
    "sources": [
      "QWeather",
      "NMC",
      "ECMWF"
    ],
    "license": [
      "commercial license"
    ]
  }
}

代码

代码语言:javascript复制
  // 请求方式:GET 获取一周天气预报

  getWeekRequest() {
    // 每一个httpRequest对应一个http请求任务,不可复用
    let httpRequest = http.createHttp()
    let url = 'https://devapi.qweather.com/v7/weather/7d?location=101010100&key=48fbadf80bbc43ce853ab9a92408373e'
    httpRequest.request(url, (err, data) => {
      if (!err) {
        if (data.responseCode == 200) {
          console.info('=====data.result====='   data.result)

          // 解析数据
          var weatherModel: WeatherModel = JSON.parse(data.result.toString())
          // 判断接口返回码,0成功
          if (weatherModel.code == 200) {
            // 设置数据

            this.future = weatherModel.daily
            this.isRequestSucceed = true;
            ForEach(weatherModel.daily, (item: WeatherWeekData) => {
              console.log(console.info('=====data.result item.fxDate====='   item.fxDate))

            }, item => item.date)

            console.info('=====data.result==='   weatherModel.daily)

          } else {
            // 接口异常,弹出提示
            prompt.showToast({ message: "数据请求失败" })
          }

        } else {
          // 请求失败,弹出提示
          prompt.showToast({ message: '网络异常' })
        }
      } else {
        // 请求失败,弹出提示
        prompt.showToast({ message: err.message })
      }
    })
  }

城市搜索

调用接口(Get方式)

请求URL

代码语言:javascript复制
# 搜索关键字beij 
// https://geoapi.qweather.com/v2/city/lookup?location=beij&key=你的KEY
location

需要查询地区的名称,支持文字、以英文逗号分隔的经度,纬度坐标(十进制,最多支持小数点后两位)、LocationID或Adcode(仅限中国城市)。例如 location=北京 或 location=116.41,39.92

模糊搜索,当location传递的为文字时,支持模糊搜索,即用户可以只输入城市名称一部分进行搜索,最少一个汉字或2个字符,结果将按照相关性和Rank值进行排列,便于开发或用户进行选择他们需要查看哪个城市的天气。例如location=bei,将返回与bei相关性最强的若干结果,包括黎巴嫩的贝鲁特和中国的北京市

重名,当location传递的为文字时,可能会出现重名的城市,例如陕西省西安市、吉林省辽源市下辖的西安区和黑龙江省牡丹江市下辖的西安区,此时会根据Rank值排序返回所有结果。在这种情况下,可以通过adm参数的方式进一步确定需要查询的城市或地区,例如location=西安&adm=黑龙江

名词解释

Rank值

Rank值是表明一个城市或地区排名的数字,基于多种因素综合计算而来,例如:人口、面积、GDP、搜索热度等。取值范围为1-10,在定位搜索服务中,返回的结果除了关键字的相关性以外,也会参考该城市的Rank值。数值越大代表该城市或地区的人口越多、面积更大或更加热门。例如陕西省西安市的Rank值就要比黑龙江省牡丹江市西安区更高,当使用“西安”作为关键字定位的时候,西安市的排名要高于西安区。

LocationID

LocationID或locid,是城市、地区或POI点的ID,一般由数字或字母 数字组成,是一个地点的唯一标识。LocationID可以通过定位搜索服务获取,中国地区、热门海外城市、一些POI点的LocationID还可以通过?城市列表下载。

参考文档

城市信息查询

和风(实时天气预报)

逐天天气预报

https://dev.qweather.com/docs/api/weather/weather-daily-forecast/

0 人点赞