# Weather **Repository Path**: pengyoucongcode/Weather ## Basic Information - **Project Name**: Weather - **Description**: 使用 HarmonyOS ArkUI 开发天气查询 App - **Primary Language**: TypeScript - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2024-04-13 - **Last Updated**: 2024-09-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Weather ## 一、项目介绍 本项目属于 `HarmonyOS App` 项目,以 `ArKTS` 编程语言为开发语言,同时具有前端和后端, 实现天气查询功能。 实现前端的 UI 开发,采用 `MVVM(Model-View-ViewModel)` 模型,遵循 `Component 化`原则, 进行视图页面的设计和实现,并通过数据绑定实现页面的动态刷新和用户操作的回传。前端部分的代码,是 本项目中占比最大的代码,也是最值得学习的部分。 本项目的后台比较简单,一个实现从指定天气查询接口请求数据的脚本,几个实现将天气查询接口返回的数据装载到 App 中的工具类。 本项目的后台,相当于天气查询服务的客户端,而服务端由发布在阿里云市场的第三方提供。 使用本项目必须有以下几个前提必须具备: - 懂得如何在阿里云市场获取合适的数据服务接口; - 具有数据接口测试能力,能够拼接请求参数和 header 属性调通链路的能力; - 具备将将数据接口返回的 JSON 对象转换成与项目编程语言所适配的数据类的能力; - 了解页面布局和通用视图组件的相关使用知识; - 具备部署和运行 HarmonyOS 应用的终端设备,主要是手机,同时必须有一个华为开发者账户以便获取真机调试用的证书; - 而最为重要的是具备了 HarmonyOS 应用开发的基础知识,例如了解和会使用 HarmonyOS 的常用 API; ## 二、目录说明 ### 1、整体说明 完整的 HarmonyOS APP 项目的工程目录内容如下所示: ![atl 工程目录](./doc/img.png) 虽然文件和目录比较多,但开发者所需要注意的并不多,也就一个 `AppScope` 目录和 `entry/src/main` 目录,而其他文件或目录由 DevEco Studio 自身自动维护即可,开发者无需太多关注和留意,除非具有特定的编译需求或项目配置需求,才需要对这些与项目编译和配置相关的目录和文件进行修改。 下面就需要留意的 AppScope 目录和 entry/src/main 目录进行展开说明。 ### 2、AppScope 目录 ![alt AppScope目录](./doc/AppScope.png) AppScope 目录下的具体情况如上所示,由一个 app.json5 文件和一个存放图片和文本值的资源目录 resources 构成. app.json5 负责有关应用全局范围的设置数据的存放,例如应用在手机桌面上的图标样式和显示的应用名;设置的时候采用引用值填写, 即 "$media:weather" 的形式,而具体的图标文件放置在 resources/base/media 目录下、文本值则存放在 resources\base\element\string.json 中。 ### 3、entry/src/main 目录说明 ![alt entry/src/main目录](./doc/entry.png) 该目录下的基本构成如上所示,由存放着项目源代码脚本文件的 ets 目录、存放着应用模块中使用到的图片、数值和文本资源文件的 resources 目录和一个对当前应用模块进行设置的 modules.json5 文件组成。 modules.json5 中的情况请阅读具体的文件,这里不展开,这里只针对目录进行说明。 ##### 3.1、main/resources 目录 ![alt main/resources目录](./doc/main_resources.png) 该目录下的情况与 AppScope 目录下的相似,但是多了很多内容。 - base 目录:是当前 resources 目录的第一个子目录,里面的 element 目录保存了应用的颜色值、数字值和文本值等值数据文件, 而其中的 media 目录则是存放图像文件,剩下的 profile 目录则是存放一个记录应用 page 路径的 json 文件。 - en_US 目录:该子目录下只有一个存放英语文本的 string.json 文件 - zh_CN 目录:存放中文文本值的地方 - rawfile 目录:也是一个存放图像文件的目录 #### 3.2、main/ets 目录 ![alt main/ets 目录](./doc/ets.png) 该目录下都是子目录,而无直接可见的文件。ets 目录下的子目录命名,充分体现了本项目所使用的 UI 开发模型, 即 MVVM(Model-View-ViewModel)。下面就各个子目录的作用进行说明: - constants 目录,一个存放了数据请求接口 URL、身份验证码等固定不变的数据的目录; - entryability,即存放了应用入口类 EntryAbility 的目录,在 HarmonyOS 中,应用被视为实现了某种能力的东西,因而都称之为 Ability; 应用功能比较单一的时候,一个 Ability 类就足够了,而当应用功能比较丰富时就应该根据功能划分成不同的 Ability,而每个 Ability 类都单独一个目录进行存放。 - model 目录,存放了实现 Model 层的代码,具体而言就是一些根据源数据进行字段定义的数据类,相当于 Java 中 DataBean。 - pages 目录和 view 目录,共同实现 View 层,不同的是 view 目录里面存放的是 Component,而 pages 目录下存放的是完整的 page 页面; 而在使用方面,pages 目录下的各个 page 通过 router 进行跳转,而 view 目录下的 Component 则是直接作为 page 根容器(一级布局)的子容器(非一级布局)直接嵌入视图中。 - view_model 目录,存放实现 ViewModel 层的代码,ViewModel 类也是一种数据类,它的字段定义根据的不是源数据,而是 view 层的 page 或 Component 中需要动态刷新的内容, 因此当应用页面内容简单时,有几个 page 就有几个 ViewModel 类,而当页面复杂被拆分成 Component 时, 有几个 Component 就有几个 ViewModel。本项目的 ViewModel 就是根据 Component 进行定义的。 - util 目录,存放工具类的地方,例如将 Model 转换成 ViewModel 的方法,以及网络请求方法。 ## 三、编码指导 ![alt 天气数据接口](./doc/weather.png) 本项目所使用的天气数据接口,是上面这个发布在[阿里云市场](https://market.aliyun.com/products/57096001/cmapi00035384.html?spm=5176.2020520132.101.3.43477218rKkKh6#sku=yuncode2938400001) 的数据接口,如果没有自己的天气数据接口、又不想因为更换数据接口而重写 Model 乃至整个项目的,还请前往阿里云市场购买服务以获得自己的 APPCODE, 而不是直接使用本项目中的 APPCODE,作为开发者,对于开源的项目要有起码的尊重。 本项目具体所使用的天气数据为实时天气,如果你也选择用这个接口的数据去使用本项目,那么可以按照如下的代码编写顺序进行开发: ### 1、编写 Model 类 首先,就是根据天气数据接口返回的json 编写相应的 Model 类,而这个步骤可以借助具有 json 转 Typescript 类的功能的 IDE 插件去生成, 而可用的此类插件我已经一起提供了,即 doc 目录下`JsonToTypeScriptClassPlugin-1.0.0.jar`,所以,将本项目打包下载到你的电脑时, 就可以使用 DevEco Studio 的从磁盘安装插件的功能进行插件安装: ![alt 安装插件](./doc/install.png) 而该插件的使用也比较简单,就是在 model 目录处单击鼠标右键,选择新建-JSON TO TS Class 选项: ![alt json to class](./doc/json_to_class.png) 就能打开一个粘贴 json 数据的对话框: ![alt 粘贴json 数据生成 class 的窗口](./doc/json_edit.png) 目前,该插件只支持生成 TS 文件,所以生成之后为了保持文件后缀的一致性,可以将这些非 Ability 类的文件改名为 ets 文件,即将文件后缀改成 `.ets`。 而获取数据接口返回的json,既可以用阿里云市场平台提供的相应工具去获取,也可以在本地电脑中用 Apifox 等接口测试工具去获取。而我更建议你采用第二种方式, 因为第一种方式时阿里云平台接口测试页面是帮你自动填写了参数的和拼接了 header 的,不如用本地的测试工具自行拼参和填写 header 来得好, 因为用本地测试工具调通接口链路的过程,可以让自己在编写 http 请求类的时候,知道该怎么拼接参数和 APPCODE。 ### 2、实现 View 层 在 Model 类编写好之后,先不着急去编写 ViewModel 层,而应该先进行 View 层的实现,也就是设计视图页面, 因为只有将视图页面定下来之后,你才知道需要用上那些天气信息,而我也建议你不要直接抄我的页面布局,而是根据自己对天气查询页面的理解进行自定义设计, 因为也没必要像我一样将所有天气信息都放在一个页面进行展示,甚至,你可以因为页面展示的需要,将返回七天天气数据、24 小时天气数据的接口一并用上,当然了, 这样你就需要编写更多的 Model 类。 编写 View 层的时候,为了方便布置页面元素,可以先将天气信息以硬编码的形式写死在 page 中, 而这些天气信息可以通过上面的 Apifox 工具向指定数据接口获取。 ### 3、实现 ViewModel 层 当 View 层实现之后,也就确定了需要使用到的天气信息,那么,就可以回过头来编写 ViewModel 类了, 而之后,就是在 util 目录下编写一个用于完成 Model 到 ViewModel 转换的工具类,例如本项目中的 WeatherDataHelper.ets 和 WeatherDefault.ets, 前一个工具类用于生成携带真实数据的 ViewModel,而第二个则是一些提供天气信息默认值的 ViewModel 生成类。 ### 4、编写 http 请求类 上面的准备阶段完成之后,为了让 App 脱胎换骨进化成真正能够查询天气的应用,就需要编写一个 http 请求类,去向指定的数据接口发起请求并获得返回数据, 例如本项目中的 HttpGet.ets 类,而该类中最关键的代码如下: ```extendtypescript export class HttpGet { async doGetWithProvCityArea( prov:string, city:string, area:string, callback:(result)=>void ) { let httpRequest = http.createHttp() let res = httpRequest.request( /* * https://iweather.market.alicloudapi.com/address? * needday=1&prov=%E5%B9%BF%E4%B8%9C&city=%E6%B1%95%E5%B0%BE&%20area=%E9%99%86%E4%B8%B0 * */ CommonConstants.WEATHER_URL + "?needday=1&prov="+encodeURI(prov) +"&city="+encodeURI(city)+"&area="+encodeURI(area), //"https://iweather.market.alicloudapi.com/address?" + //"needday=1&prov=%E5%B9%BF%E4%B8%9C&city=%E6%B1%95%E5%B0%BE&area=%E9%99%86%E4%B8%B0", { method: http.RequestMethod.GET, header: { "Content-Type": "application/json; charset=UTF-8", //请求头中携带app_code "Authorization": CommonConstants.APP_CODE }, connectTimeout: 5000, // 连接超时时间 readTimeout: 5000, // 读取数据超时时间 } ) //hilog.info(0x0000, 'WeatherTAG', 'data1:' + JSON.stringify(res)); // 异步处理结果 res.then((data) => { //hilog.info(0x0000, 'WeatherTAG', 'data1:' + JSON.stringify(data)); callback(data.result) // 返回数据后回调 httpRequest.destroy(); }).catch((err) => { httpRequest.destroy(); // 销毁 }); } } ``` 如果你需要调用多个接口,那么就多写几个 doGet 方法即可,而最后接口返回的数据转换为 Model 时,则可以用类似如下的代码: ```extendtypescript @Component export struct MainComponent { @Provide nowViewModel: NowViewModel = WeatherDefault.getDefaultNowViewModel() @Provide alarmViewModelList: Array = WeatherDefault.getDefaultAlarmViewModelList() @Provide aqiViewModel: AqiViewModel = WeatherDefault.getDefaultAqiViewModel() @Provide detailViewModel: DetailViewModel = WeatherDefault.getDefaultDetailViewModel() private httpGet: HttpGet = new HttpGet() async aboutToAppear() { this.httpGet.doGetWithProvCityArea(this.prov, this.city, this.area, (data)=>{ hilog.info(0x0000, 'WeatherTAG', 'data0:' + JSON.stringify(data)); if (data.result == "") { Prompt.showToast({ message: '获取天气信息失败,可能是请求次数用尽', duration: 3000, bottom: 200 }); return; } hilog.info(0x0000, 'WeatherTAG', 'data1:' + JSON.stringify(data)); let weatherModel: WeatherModel = JSON.parse(data); hilog.info(0x0000, 'WeatherTAG', 'model:' + JSON.stringify( weatherModel )); if (weatherModel.ret != 200 || weatherModel.data == null) { Prompt.showToast({ message: '查询失败,请确认输入信息是否有误', duration: 3000, bottom: 200 }); return; } let weatherData: DataModel = WeatherDataHelper.getWeatherData(weatherModel); let now: NowModel = WeatherDataHelper.getNow(weatherData); this.nowViewModel = WeatherDataHelper.getNowViewModel(now); this.alarmViewModelList = WeatherDataHelper.getAlarmViewModelList(now); this.aqiViewModel = WeatherDataHelper.getAqiViewModel(now); this.detailViewModel = WeatherDataHelper.getDetailViewModel(now); }) } build() { } } ``` 在 Component 生命周期的第一步去请求天气接口,从而实现数据的装载。