鸿蒙5.0项目开发——鸿蒙天气项目的实现(主页1)

【高心星出品】

文章目录

这个页面是整个天气应用的核心,集成了天气查询、定位、搜索等主要功能,提供了完整的天气信息服务。

页面效果:

页面功能:

1. 天气信息展示

  • 当前天气信息:

  • 城市名称

  • 温度

  • 天气状况

  • 湿度

  • 风速

  • PM2.5

  • 空气质量

  • 未来天气预报:

  • 显示未来3天的天气

  • 包含星期、天气类型、温度范围

2. 定位功能

  • 自动定位:

  • 请求定位权限

  • 获取当前位置

  • 根据位置获取城市信息

  • 显示当前位置天气

3. 搜索功能

  • 城市搜索:

  • 搜索框输入城市名

  • 实时搜索天气信息

  • 保存搜索历史

4. 搜索历史管理

  • 历史记录功能:

  • 显示搜索历史列表

  • 支持删除单条记录

  • 支持清空所有历史

  • 点击历史记录可快速查看天气

5. 界面交互

  • 左侧抽屉菜单:

  • 显示搜索历史

  • 支持滑动关闭

  • 编辑模式切换

  • 删除历史记录

6. 加载状态

  • 加载提示:

  • 显示加载动画

  • 错误提示对话框

  • 操作结果提示

7. 数据管理

  • 数据持久化:

  • 保存搜索历史

  • 缓存天气数据

  • 管理城市信息

8. 错误处理

  • 异常处理:

  • 网络错误提示

  • 定位失败处理

  • 数据加载失败处理

页面执行流程:

1. 页面初始化阶段

  1. 页面加载时触发 aboutToAppear 生命周期

  2. 首先检查定位权限

    • 如果有权限,直接开启定位

    • 如果没有权限,向用户请求权限

    • 用户同意后开启定位,拒绝则使用默认城市(南京市)

typescript 复制代码
  /**
   * 组件即将出现时的生命周期函数
   * @description 
   * 1. 首先请求定位权限
   * 2. 获取当前位置信息
   */
  async aboutToAppear(): Promise<void> {
    // 请求定位权限
    await this.locationpermission()
    // 获取当前位置信息
    this.getlocation()

  }
typescript 复制代码
 /**
   * 请求定位权限
   * @description 
   * 1. 创建权限管理器实例
   * 2. 获取应用程序的accessTokenID
   * 3. 校验应用是否被授予定位权限
   * 4. 如果未授权,则请求用户授权
   * 5. 根据用户授权结果执行相应操作
   */
  async locationpermission() {
    // 创建权限管理器实例
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
    // 初始化权限状态为拒绝
    let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
    // 获取应用程序的accessTokenID
    let tokenId: number = 0;
    let bundleInfo: bundleManager.BundleInfo =
      await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
    let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
    tokenId = appInfo.accessTokenId;
    
    // 校验应用是否被授予定位权限
    grantStatus = await atManager.checkAccessToken(tokenId, 'ohos.permission.APPROXIMATELY_LOCATION');
    
    if (grantStatus == abilityAccessCtrl.GrantStatus.PERMISSION_DENIED) {
      // 如果未授权,请求用户授权
      atManager.requestPermissionsFromUser(this.context, ['ohos.permission.APPROXIMATELY_LOCATION']).then((data) => {
        let grantStatus: Array<number> = data.authResults;
        if (grantStatus[0] === 0) {
          // 用户同意授权,修改lcc值触发lc函数执行
          this.lcc = 2
        } else {
          // 用户拒绝授权,弹窗提示
          AlertDialog.show({
            message: '您已拒绝授权定位,无法体验当前位置天气的信息功能',
            title: '提示',
            confirm: {
              value: '确定', action: () => {
              }
            }
          })
        }
      }).catch((e: Error) => {
        // 授权过程发生错误
        console.error('gxxt 授权', e.message)
      })
    } else {
      // 已有权限,设置lcc=1
      this.lcc = 1
    }

  }

2. 定位获取阶段

  1. 检查系统定位是否开启

    • 如果已开启,直接获取位置

    • 如果未开启,请求用户开启定位

  2. 获取当前位置信息

  3. 根据位置信息获取城市名称

  4. 如果获取失败,使用默认城市

typescript 复制代码
/**
   * 获取位置信息
   * @description 根据定位权限状态决定是否开启定位
   * - 如果lcc=1表示有定位权限,则开启定位
   * - 否则直接加载默认城市天气
   */
  getlocation() {
    if (this.lcc == 1) {
      // 开启定位
      this.openlocation()
    } else {
      this.loadweather(this.cityName, false)
    }
  }

  /**
   * 开启定位功能
   * @description 检查并请求开启系统定位功能
   * 1. 首先检查定位功能是否已开启
   * 2. 如果未开启,则请求用户开启
   * 3. 开启成功后调用locationreq()获取位置
   * 4. 开启失败则提示用户
   * @throws 可能抛出定位相关错误
   */
  openlocation() {
    try {
      // 检查定位功能是否开启
      let enable = geoLocationManager.isLocationEnabled()
      if (!enable) {
        // 创建权限管理器
        let atmanager = abilityAccessCtrl.createAtManager()
        // 请求开启定位功能
        atmanager.requestGlobalSwitch(this.context, abilityAccessCtrl.SwitchType.LOCATION).then((value) => {
          if (value) { // 开启定位成功
            this.locationreq()
          } else { // 开启定位失败
            AlertDialog.show({
              message: '定位开启失败', 
              title: '提示', 
              confirm: {
                value: '确定',
                action: () => {}
              }
            })
          }
        })
      } else {
        // 定位已开启,直接获取位置
        this.locationreq()
      }
    } catch (e) {
      console.error('gxxt location ', JSON.stringify(e))
    }
  }

  /**
   * 获取定位城市信息
   * @description 通过系统定位服务获取当前位置并解析城市名称
   * 1. 首先显示加载动画弹窗
   * 2. 配置定位请求参数,优先考虑定位速度,超时时间10秒
   * 3. 调用getCurrentLocation获取经纬度坐标
   * 4. 根据经纬度反向解析获取城市名称
   * 5. 成功获取城市名称后加载该城市天气
   * 6. 失败则加载默认城市天气
   */
  locationreq() {
    // 显示加载动画弹窗
    this.opendialog()
    // 配置定位请求参数
    let request: geoLocationManager.SingleLocationRequest = {
      'locatingPriority': geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,
      'locatingTimeoutMs': 10000
    }
    // 获取当前位置
    geoLocationManager.getCurrentLocation(request).then((result) => {
      // 配置反向地理编码请求参数
      let reverseGeocodeRequest: geoLocationManager.ReverseGeoCodeRequest =
        { "latitude": result.latitude, "longitude": result.longitude, "maxItems": 1 };
      // 根据经纬度获取地址信息
      geoLocationManager.getAddressesFromLocation(reverseGeocodeRequest, (err, data) => {
        if (err) {
          // 获取城市名称失败
          console.log('gxxt getAddressesFromLocation err: ' + JSON.stringify(err));
          promptAction.showToast({ message: '获取城市名称失败,请连接网络!' })
          // 加载默认城市天气
          this.loadweather(this.cityName, false)
        } else {
          // 获取城市名称成功,加载该城市天气
          this.loadweather(data[0].locality as string, false)
        }
      });
    })
      .catch((error: BusinessError) => {
        // 定位失败处理
        console.error('gxxt , getCurrentLocation: error=' + JSON.stringify(error));
        promptAction.showToast({ message: '无法获取地理位置' })
        // 加载默认城市天气
        this.loadweather(this.cityName, false)
      });
  }

3. 天气数据加载阶段

  1. 根据城市名称查询城市编码

  2. 使用城市编码获取天气数据

  3. 更新界面显示:

    • 当前天气信息(温度、天气状况等)

    • 未来三天天气预报

    • 空气质量等信息

  4. 如果是搜索操作,保存搜索历史

typescript 复制代码
/**
   * 加载天气信息
   * @param cityname 城市名称
   * @param issearch 是否是搜索请求
   * @description 根据城市名称加载天气信息
   * 1. 首先根据城市名称查询城市编码
   * 2. 使用城市编码获取天气数据
   * 3. 如果不是搜索请求则关闭加载弹窗
   * 4. 如果是搜索请求则保存搜索历史
   * 5. 更新页面上的天气信息显示
   * 6. 包括:城市名、温度、天气描述、湿度、风速、PM2.5、空气质量
   * 7. 更新未来3天的天气预报信息
   */
  private async loadweather(cityname: string, issearch: boolean) {
    let ad = new adcodedao(this.context)
    try {
      // 查询城市编码
      let code = await ad.querybyname(cityname)
      // 获取天气数据
      let weather = await getweather(code)
      if (!issearch) {
        // 非搜索请求,关闭加载弹窗
        this.closedialog()
      } else {
        // 搜索请求,保存搜索历史
        let sd = new searchdao(this.context)
        sd.insert({ keyword: cityname })
      }
      // 更新城市基本信息
      this.cityName = weather.cityInfo.city
      this.temperature = weather.data.wendu
      this.weatherDesc = weather.data.forecast[0].type
      this.humidity = weather.data.shidu
      this.windSpeed = weather.data.forecast[0].fl
      this.pm25 = weather.data.pm25 + ''
      this.quality = weather.data.quality

      // 更新未来3天天气预报
      this.day1week = weather.data.forecast[1].week
      this.day1type = weather.data.forecast[1].type
      this.day1wendu = getwendu(weather.data.forecast[1].low, weather.data.forecast[1].high)
      this.day2week = weather.data.forecast[2].week
      this.day2type = weather.data.forecast[2].type
      this.day2wendu = getwendu(weather.data.forecast[2].low, weather.data.forecast[2].high)
      this.day3week = weather.data.forecast[3].week
      this.day3type = weather.data.forecast[3].type
      this.day3wendu = getwendu(weather.data.forecast[3].low, weather.data.forecast[3].high)
    } catch (e) {
      // 显示错误提示对话框
      AlertDialog.show({
        message: '该地区天气信息暂无法获取!', 
        title: '提示', 
        confirm: {
          value: '确定', 
          action: () => {
          }
        }
      })
    }
  }

根据城市名称查询城市编码:

typescript 复制代码
  /**
   * 根据城市名称查询城市编码
   * @param name 城市名称
   * @returns 城市编码
   * @description 
   * 1. 根据城市名称模糊查询数据库
   * 2. 只查询code字段
   * 3. 如果查询到结果则返回城市编码
   * 4. 如果查询失败则返回空字符串
   */
  async querybyname(name: string) {
    let code: string = ''
    try {
      let rdb = await getrdb(this.context)
      let predicates = new relationalStore.RdbPredicates(TABLENAME)
      predicates.like('name', '%' + filter(name) + '%')
      let rs = await rdb.query(predicates, ['code'])
      if (rs.goToNextRow()) {
        code = rs.getString(rs.getColumnIndex('code'))
      }
    } catch (e) {
      console.error('gxxt adcodedao 查询失败: ', JSON.stringify(e))
    }
    return code
  }

使用城市编码获取天气数据

typescript 复制代码
/**
 * 天气API的基础URL
 */
export const BASEURL = 'http://t.weather.itboy.net/api/weather/city/'

/**
 * 获取城市天气信息
 * @param citycode 城市编码
 * @returns Promise<weathermodel> 返回天气数据模型
 * @description 
 * 1. 根据城市编码拼接完整的API URL
 * 2. 创建HTTP请求实例
 * 3. 配置请求头为JSON格式
 * 4. 发送GET请求获取天气数据
 * 5. 解析响应数据为weathermodel类型
 * 6. 如果状态码为200则返回数据
 * 7. 否则抛出错误
 * 8. 最后销毁HTTP请求实例
 */
export function getweather(citycode: string) {
  let url = BASEURL + citycode
  let req = http.createHttp()
  let opt: http.HttpRequestOptions = {
    header: {
      'Content-Type': 'application/json;charset=UTF-8'
    }
  }
  return new Promise<weathermodel>((resove, reject) => {
    req.request(url, opt).then((res) => {
      let data =JSON.parse( res.result as string) as weathermodel
      if(data.status==200) {
        resove(data)
      }else{
        reject('不在范围之内')
      }
    }).catch((e: Error) => {
      reject(e)
    }).finally(() => {
      req.destroy()
    })
  })

}
相关推荐
SuperHeroWu73 天前
【 HarmonyOS 5 入门系列 】鸿蒙HarmonyOS示例项目讲解
华为·harmonyos·arkts·讲解·arkui·空ability示例项目
simple丶3 天前
【HarmonyOS】项目工程化工具 持续更迭...
harmonyos·arkts·arkui
simple丶5 天前
HarmonyOS 鸿蒙应用 - NEWS项目详细介绍
harmonyos·arkts·arkui
simple丶6 天前
【HarmonyOS 】工具函数封装 持续更迭...
harmonyos·arkts·arkui
九九花开9 天前
HarmonyOS NEXT从图库选择资源上传到服务器或者把网络资源下载到图库
移动应用开发·harmonynext·鸿蒙5.0
马剑威(威哥爱编程)13 天前
鸿蒙 HarmonyOS NEXT 系统 Preference 首选项使用全解析
华为·harmonyos·arkts·arkui
马剑威(威哥爱编程)13 天前
HarmonyOS NEXT 使用 relationalStore 实现数据库操作
数据库·华为·harmonyos·arkts·arkui
特立独行的猫a13 天前
HarmonyOS 鸿蒙应用开发基础:父组件和子组件的通信方法总结
华为·harmonyos·arkts·arkui
鸿蒙布道师16 天前
鸿蒙NEXT开发动画案例9
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei