Bootstrap

鸿蒙项目《查询天气》

天气查询

一、项目目标

综合运用本学期所学内容及个人自学知识,使用HarmonyOS 4.0及以上版本开发一款具有实用性和创新
性的移动应用软件。

二、项目介绍

根据高德地图获取城市天气数据,将今日天气状况和最近几天的天气状况可视化,即数据的内容转化为图标的形式,展现出来。可以选择所在的一个城市也可以选择多个城市或者删除之前所选的城市。

三、代码结构解读

├──entry/src/main/ets // 代码区
│ ├──pages
│ │ ├──index.ets // 启动页
│ │ ├──AddCity.ets // 添加城市页
│ ├──cityView
│ │ ├──cityView.ets // 今日和最近天气
│ ├──viewmodel
│ │ ├──casts.ets // 天气状况类
│ │ ├──forecasts.ets // 城市类
│ │ ├──WeatherModel.ets // 城市编号类
│ ├──resources/base/media //图片夹
│ │ ├──cloud.png //多云图标
│ │ ├──icon.png //软件图标
│ │ ├──rain.png //下雨图标
│ │ ├──sun.png //太阳图标
└──entry/src/main/resources // 资源文件夹

四、关键技术

Tabs组件城市切换

import { cityView } from '../view/cityView'
//.....
  //tarBar 自定义函数
  @Builder tabBuild(index: number) {
    Circle({ width: 10, height: 10 })
        .fill(this.cityIndex === index ? Color.White : Color.Gray).opacity(0.4)
  }//城市切换
Tabs({ barPosition: BarPosition.Start, controller: this.tancontroller }) {
  ForEach(this.cityWeatherList, (cityWeatherList: WeatherModel) => {
    TabContent() {
      cityView({ casts: cityWeatherList.forecasts[0].casts })
    }.tabBar(this.tabBuild(this.cityWeatherList.findIndex(obj => obj === cityWeatherList)))
  })
}
.barHeight(20)
.barWidth(40)
.onChange((index: number) => {
  this.cityIndex = index
})

五、实验过程

1.通过获取的数据,进行类的构建

在module.json5文件内加入代码

"requestPermissions": [
  {"name": "ohos.permission.INTERNET"}
],

创建方法

通过异步获取,将数据以对象的形式返回

//获取数据
getWeather(cityCode:number){
  return new Promise<WeatherModel>((resolve,reject)=>{
    //创建
    let request = http.createHttp()
    let url= `https://restapi.amap.com/v3/weather/weatherInfo?key=<输入你申请的key值>&city=${cityCode}&extensions=all`
​
    let options = {
      method:http.RequestMethod.GET,
      header:{'Content-Type':'application/json'},
    } as http.HttpRequestOptions
    let result = request.request(url,options)
​
    result.then((res)=>{
      if (res.responseCode === 200) {
        console.log(res.result.toString())
        resolve( JSON.parse(res.result.toString()))
      }
    }).catch((err)=>{
      console.log(err)
    })
  })
}

使用Promise.all方法,同时返回多条数据
//直接获取所有数据
async  getWeathers (cityCodeList:Array<number>){
  let WeathersDate: Array<WeatherModel> = []
  let promises :Array<Promise<WeatherModel>> = []
  for (let i = 0; i < cityCodeList.length; i++) {
    promises.push(this.getWeather(cityCodeList[i]))
  }
  await Promise.all(promises).then(result =>{
    for (const element of result) {
      console.log(element.forecasts[0].city);
    }
    WeathersDate = result
  })
  return WeathersDate
}

根据返回的json数据,创建相应的对象

casts
export class casts{
  date:string
  dayweather:string
  nightweather:string
  daytemp:number
  nighttemp:number
  daywind:string
  daypower:string
  daytemp_float:number
  nighttemp_float:number
}

forecasts
import {casts} from "../viewmodel/casts"
​
export class forecasts{
  city:string
  adcode:number
  casts:Array<casts>
}

WeatherModel
import {forecasts} from "./forecasts"
​
export class WeatherModel{
  status:number
  count:number
  infocode:number
  forecasts:Array<forecasts> = []
}

2.主页面进行构建,将添加按钮,删除按钮,以及多个城市名称滑动时对应起来,也就是显示的天气状况和城市名称保持一致

页面显示框
  //当前tab组件索引
  @State cityIndex: number = 0
​
​
//城市选择+城市标题
Row() {
  Button("添加")
    .fontSize(25)
    .fontColor(Color.Gray)
    .opacity(0.7)
    .backgroundColor("#87CEEB")
    .margin({ bottom: 15 })
  Text(this.cityNameList[this.cityIndex])
      .fontSize(40).fontColor(Color.Orange)
  Button("删除")
    .fontSize(25)
    .fontColor(Color.Gray)
    .opacity(0.7)
    .backgroundColor("#87CEEB")
    .margin({ bottom: 15 })
}.height("10%")
.width("100%")
.justifyContent(FlexAlign.SpaceBetween)

Tabs组件城市切换

import { cityView } from '../view/cityView'
//.....
  //tarBar 自定义函数
  @Builder tabBuild(index: number) {
    Circle({ width: 10, height: 10 })
        .fill(this.cityIndex === index ? Color.White : Color.Gray).opacity(0.4)
  }//城市切换
Tabs({ barPosition: BarPosition.Start, controller: this.tancontroller }) {
  ForEach(this.cityWeatherList, (cityWeatherList: WeatherModel) => {
    TabContent() {
      cityView({ casts: cityWeatherList.forecasts[0].casts })
    }.tabBar(this.tabBuild(this.cityWeatherList.findIndex(obj => obj === cityWeatherList)))
  })
}
.barHeight(20)
.barWidth(40)
.onChange((index: number) => {
  this.cityIndex = index
})

3.将主页面的今日天气和近期天气状况显示出来,将数据转化为图标

今日天气显示
遍历列表,索引为0的对象为今日天气数据
//当前城市天气

  casts: casts[] = []ForEach(this.casts, (cast: casts) => {
  if (cast === this.casts[0]) {
        
  }
})


设置天气图片
//图片
Row() {
  if (cast.dayweather === "晴") {
    Image($r('app.media.sun')).width(260)
  }
  if (cast.dayweather === "多云") {
    Image($r("app.media.cloud")).width(260)
  }
  if (cast.dayweather === "阴") {
    Image($r("app.media.cloud")).width(260)
  }
  if (cast.dayweather.includes("雨")) {
    Image($r("app.media.rain")).width(260)
  }
}.height("30%")


设置天气数据
Column() {
  //天气 温度
  Row() {
    Text(cast.dayweather).fontSize(30)
        .fontColor(Color.White).fontWeight(FontWeight.Bold)
    Text("  " + cast.daytemp + "°~" + cast.nighttemp + "°")
      .fontSize(30)
      .fontColor(Color.White)
      .fontWeight(FontWeight.Bold)
  }
  //风力 等级
  Row() {
    Text(cast.daywind + "风  ").fontSize(30)
        .fontColor(Color.White).fontWeight(FontWeight.Bold)
    Text(cast.daypower + "级").fontSize(30)
        .fontColor(Color.White).fontWeight(FontWeight.Bold)
  }}.margin({ top: 10 })

casts集合内所有天气数据

天气图标自定义函数

@Builder weatherImage(dayweather: string) {
  if (dayweather === "晴") {
    Image($r('app.media.sun')).width(30)
  }
  if (dayweather === "多云") {
    Image($r("app.media.cloud")).width(30)
  }
  if (dayweather === "阴") {
    Image($r("app.media.cloud")).width(30)
  }
  if (dayweather.includes("雨")) {
    Image($r("app.media.rain")).width(30)
  }
}

近期天气列表
//近期天气列表
Column() {
  Text("查看近期天气").fontSize(26).margin({ top: 30 })
  //天气列表
  Row() {
    ForEach(this.casts, (cast: casts) => {
      Column() {
        Text(cast.date.substring(5))
        this.weatherImage(cast.dayweather)
        Blank()
        Text(cast.daytemp.toString())
        Line()
        .width(20).height(80).startPoint([10, 0])
        .endPoint([10, 70]).stroke(Color.Black)
        .strokeWidth(3).strokeDashArray([10, 3])
          
        Text(cast.nighttemp.toString())
        Blank()
        this.weatherImage(cast.dayweather)
      }.height("90%").width("20%")
    })
  }
  .width("80%")
  .height("60%")
  .backgroundColor("#ffbab8b8")
  .opacity(0.5)
  .justifyContent(FlexAlign.SpaceAround)}.height("45%").width("100%")

四、添加城市的页面,当点击添加时,将网络获取的数据城市,可以添加到主页面,将已经添加的和未添加的做标识,实现城市的添加

添加城市页面

添加城市跳转
//主页面按钮跳转
Button("添加")
.onClick(() => {
  router.pushUrl({
    url: "pages/AddCity",
    params: {
      Codes: this.cityCodeList,
      Names: this.cityNameList
    }
  })
})//.....
//添加城市页面接收数据
  onPageShow(){
    let params = router.getParams()
    this.cityCodeList = params["Codes"]
    this.cityNameList = params["Names"]
  }
​


页面数据
@State AllCityCodeList :Array<number> =
[110000,120000,130000,140000,210000,220000,310000]
@State AllCityNameList :Array<string> = 
["北京市","天津市","河北省","山西省","辽宁省","吉林省","上海市"]//当前城市代码列表 接收传入数据的载体
@State cityCodeList :Array<number> = []
@State cityNameList :Array<string> = []


页面标头
Row(){
  Text("添加城市列表").fontSize(35).fontColor(Color.White)
  Blank()
  Button("完成").backgroundColor("").fontSize(26)
}.height("10%").width("95%")

城市列表
List遍历
//城市列表
Column() {
  List() {
    ForEach(this.AllCityNameList, (name: string) => {
      ListItem() {
        //写入单个城市名字
      }
    })
  }
}.width("100%").height("90%")

判断当前遍历的AllCityNameList元素是否包含在cityNameList集合中,并作出对应样式 ,方法调整
if (this.cityNameList.includes(name)) {
  Column() {
    Row() {     Text(name).fontSize(35).fontColor(Color.White).width("60%")
        .margin({ top: 20, left: 30 })
      Blank()
      Text("已添加").backgroundColor("").fontSize(18)
        .margin({ right: 5 })
        .opacity(0.8)
    }.width("100%")Blank()
    Divider().strokeWidth(5)
  }.height(90).width("100%").margin({ top: 20 })
  .backgroundColor("#4682B4")
} else {
  Column() {
    Row() {
      Text(name).fontSize(35).fontColor(Color.White).width("60%")
        .margin({ top: 20, left: 30 })
      Blank()
      Button("添加").backgroundColor("").fontSize(18)
        .margin({ right: 5 })
        .onClick(() => {
          //根据name 获取所在索引
          let index = this.AllCityNameList.findIndex(obj => obj === name)
          console.log("index:"+index)
          //根据索引获得 城市对应的编码
          let cityCode: number = this.AllCityCodeList[index]
          console.log("cityCode= "+cityCode)
          //将编码加入列表
          this.cityCodeList.push(cityCode)
          this.cityNameList.push(name)
          console.log(this.cityCodeList.toString())
        })
    }.width("100%")Blank()
    Divider().strokeWidth(5)
  }.height(90).width("100%").margin({ top: 20 })
  .backgroundColor("#87CEEB")
}


页面返回
Button("完成")
.onClick(()=>{
  router.back({
    url:"pages/Index",
    params:{
      Codes:this.cityCodeList
    }
  })
})//主界面接收数据
  onPageShow() {
    let params = router.getParams()
    if (params) {
      this.cityCodeList = params["Codes"]
      this.cityWeatherList = []
      this.cityNameList = []
      this.initData()
    }
  }

五、城市的删除,当点击删除按钮时,将弹出对话框,确认删除后,即可将当前的城市

城市删除方法
Button("删除")
.onClick(() => {
  AlertDialog.show({ title: "删除",
    message: `你确定要删除${this.cityNameList[this.cityIndex]}消息吗?`,
    confirm: {
      value: "确定",
      action: () => {
        this.cityNameList = this.cityNameList.filter(item => item !== this.cityNameList[this.cityIndex])
        this.cityCodeList = this.cityCodeList.filter(item => item !== this.cityCodeList[this.cityIndex])
        this.cityWeatherList = this.cityWeatherList.filter(item => item !== this.cityWeatherList[this.cityIndex])
        
      }
    }
  })
})

六、添加风力的卡片

Text("风向风力").fontSize(26).margin({ top: 30 })
      Row() {
        ForEach(this.casts, (cast: casts, index: number) => {

          // 风向风力内容
          Column() {
            Row() {
              Image($r("app.media.wind")) // 假设 'app.media.wind' 是风力的图片资源
                .width(120) // 设置图片宽度
                .height(100) // 设置图片高度

                .margin({ right: 35 }) // 设置图片与右边距
              Image($r("app.media.w2")) // 假设 'app.media.wind' 是风力的图片资源
                .width(120) // 设置图片宽度
                .height(100) // 设置图片高度
                .margin({ right: 35 })

            }
            .width("100%")
              Text("风向:" + cast.daywind)
                .fontSize(24)
                .fontColor(Color.White)
                .fontWeight(FontWeight.Bold)
                .margin({ right: 20 }) // 右边距,根据需要调整


            Text("风力:" + cast.daypower)
              .fontSize(24)
              .fontColor(Color.White)
              .fontWeight(FontWeight.Bold)
              .margin({ right: 20 })
          }.margin({ top: 10 })
          // 根据需要添加分隔线或间距
          if (index !== this.casts.length - 1) {
            Line().width("100%").height(1).stroke(Color.Black).strokeWidth(1)
          }
        })
      }
      .width("100%")
      .height("45%")
      .backgroundColor("#ffbab8b8")
      .opacity(0.5)
      .justifyContent(FlexAlign.SpaceAround)
    }
  }
})

七、将两个卡片实现滑动

 build() {
    //单个tabcontent 展示的所有内容
    Column() {
      ForEach(this.casts, (cast: casts) => {
        if (cast === this.casts[0]) {
          //上半部分 图片+当天天气
          //图片
          Row() {
            if (cast.dayweather === "晴") {
              Image($r('app.media.sun')).width(260)
            }
            if (cast.dayweather === "多云") {
              Image($r("app.media.cloud")).width(260)
            }
            if (cast.dayweather === "阴") {
              Image($r("app.media.cloud")).width(260)
            }
            if (cast.dayweather.includes("雨")) {
              Image($r("app.media.rain")).width(260)
            }
            if (cast.dayweather.includes("沙")) {
              Image($r("app.media.sand")).width(260)
            }
          }.height("30%")

          Column() {
            //天气 温度
            Row() {
              Text(cast.dayweather).fontSize(30).fontColor(Color.White).fontWeight(FontWeight.Bold)
              Text("  " + cast.daytemp + "°~" + cast.nighttemp + "°")
                .fontSize(30)
                .fontColor(Color.White)
                .fontWeight(FontWeight.Bold)
            }
            //风力 等级
            Row() {
              Text(cast.daywind + "风  ").fontSize(30).fontColor(Color.White).fontWeight(FontWeight.Bold)
              Text(cast.daypower + "级").fontSize(30).fontColor(Color.White).fontWeight(FontWeight.Bold)
            }

          }.margin({ top: 10 })
        }
      })
      List() {
        ForEach(this.casts, (cast: casts, index: number) => {
          ListItem() {
            //近期天气列表
            Column() {
              Text("查看近期天气").fontSize(26).margin({ top: 30 })
              //天气列表
              Row() {
                ForEach(this.casts, (cast: casts) => {
                  Column() {
                    Text(cast.date.substring(5))
                    this.weatherImage(cast.dayweather)
                    Blank()
                    Text(cast.daytemp.toString() + "°")
                    Line()
                      .width(20)
                      .height(80)
                      .startPoint([10, 0])
                      .endPoint([10, 70])
                      .stroke(Color.Black)
                      .strokeWidth(3)
                      .strokeDashArray([10, 3])
                    Text(cast.nighttemp.toString() + "°")
                    Blank()
                    this.weatherImage(cast.dayweather)
                  }.height("90%").width("20%")
                })
              }
              .width("100%")
              .height("40%")
              .backgroundColor("#ffbab8b8")
              .opacity(0.5)
              .justifyContent(FlexAlign.SpaceAround)
.padding({left:15,right:15})
.margin({bottom:35})
              Text("风向风力").fontSize(26).margin({ top: 30 })
              Row() {
                ForEach(this.casts, (cast: casts, index: number) => {

                  // 风向风力内容
                  Column() {
                    Row() {
                      Image($r("app.media.wind")) // 假设 'app.media.wind' 是风力的图片资源
                        .width(120) // 设置图片宽度
                        .height(100) // 设置图片高度

                        .margin({ right: 35 }) // 设置图片与右边距
                      Image($r("app.media.w2")) // 假设 'app.media.wind' 是风力的图片资源
                        .width(120) // 设置图片宽度
                        .height(100) // 设置图片高度
                        .margin({ right: 35 })

                    }
                    .width("100%")
                      Text("风向:" + cast.daywind)
                        .fontSize(24)
                        .fontColor(Color.White)
                        .fontWeight(FontWeight.Bold)
                        .margin({ right: 20 }) // 右边距,根据需要调整


                    Text("风力:" + cast.daypower)
                      .fontSize(24)
                      .fontColor(Color.White)
                      .fontWeight(FontWeight.Bold)
                      .margin({ right: 20 })
                  }.margin({ top: 10 })
                  // 根据需要添加分隔线或间距
                  if (index !== this.casts.length - 1) {
                    Line().width("100%").height(1).stroke(Color.Black).strokeWidth(1)
                  }
                })
              }
              .width("100%")
              .height("45%")
              .backgroundColor("#ffbab8b8")
              .opacity(0.5)
              .justifyContent(FlexAlign.SpaceAround)
            }
          }
        })


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


;