【高心星出品】
文章目录
这个页面是整个天气应用的核心,集成了天气查询、定位、搜索等主要功能,提供了完整的天气信息服务。
页面效果:

页面功能:
1. 天气信息展示
-
当前天气信息:
-
城市名称
-
温度
-
天气状况
-
湿度
-
风速
-
PM2.5
-
空气质量
-
未来天气预报:
-
显示未来3天的天气
-
包含星期、天气类型、温度范围
2. 定位功能
-
自动定位:
-
请求定位权限
-
获取当前位置
-
根据位置获取城市信息
-
显示当前位置天气
3. 搜索功能
-
城市搜索:
-
搜索框输入城市名
-
实时搜索天气信息
-
保存搜索历史
4. 搜索历史管理
-
历史记录功能:
-
显示搜索历史列表
-
支持删除单条记录
-
支持清空所有历史
-
点击历史记录可快速查看天气
5. 界面交互
-
左侧抽屉菜单:
-
显示搜索历史
-
支持滑动关闭
-
编辑模式切换
-
删除历史记录
6. 加载状态
-
加载提示:
-
显示加载动画
-
错误提示对话框
-
操作结果提示
7. 数据管理
-
数据持久化:
-
保存搜索历史
-
缓存天气数据
-
管理城市信息
8. 错误处理
-
异常处理:
-
网络错误提示
-
定位失败处理
-
数据加载失败处理
页面执行流程:
1. 页面初始化阶段
-
页面加载时触发 aboutToAppear 生命周期
-
首先检查定位权限
-
如果有权限,直接开启定位
-
如果没有权限,向用户请求权限
-
用户同意后开启定位,拒绝则使用默认城市(南京市)
-
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. 定位获取阶段
-
检查系统定位是否开启
-
如果已开启,直接获取位置
-
如果未开启,请求用户开启定位
-
-
获取当前位置信息
-
根据位置信息获取城市名称
-
如果获取失败,使用默认城市
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. 天气数据加载阶段
-
根据城市名称查询城市编码
-
使用城市编码获取天气数据
-
更新界面显示:
-
当前天气信息(温度、天气状况等)
-
未来三天天气预报
-
空气质量等信息
-
-
如果是搜索操作,保存搜索历史
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()
})
})
}