鸿蒙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()
    })
  })

}
相关推荐
高心星1 天前
鸿蒙5.0项目开发——鸿蒙天气项目的实现(介绍)
arkts·arkui·鸿蒙项目·harmonyos5.0·鸿蒙天气
鸿蒙布道师2 天前
鸿蒙NEXT开发动画案例5
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei
鸿蒙布道师5 天前
鸿蒙NEXT开发动画案例3
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei
鸿蒙布道师6 天前
鸿蒙NEXT开发动画案例2
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei
技术干货贩卖机9 天前
0基础 | STM32 | STM32F103C8T6开发板 | 项目开发
stm32·单片机·嵌入式硬件·源代码·项目开发·0基础
高心星19 天前
HarmonyOS 5.0应用开发——MVVM模式的应用
harmonyos·mvvm·鸿蒙5.0·备忘录应用
长沙火山20 天前
14.ArkUI Radio的介绍和使用
arkui
搞瓶可乐23 天前
鸿蒙ArkUI实战之组件;Text组件,Image组件,Button组件,Span组件和TextInput组件的使用场景及使用方法
华为·harmonyos·鸿蒙系统·arkui·组件化开发·基础组件使用
搞瓶可乐23 天前
鸿蒙ArkUI实战之TextArea组件、RichEditor组件、RichText组件、Search组件的使用
华为·harmonyos·arkui·搜索框·富文本组件·富文本输入框·鸿蒙原生api