HarmonyOS城市搜索与天气添加实战
·
✨ 一直在追求一针见血的发现问题,解决问题,提出问题 + 了解他崇拜他超过他
🚀 个人主页 :码到成功✓
🌱 代码仓库 :wwh
📌 专栏系列“虫子从来就没有被战胜过”
文章目录
HarmonyOS应用开发:城市搜索与天气添加功能实现(ArkTS)
在HarmonyOS应用开发中,城市搜索与天气数据关联是天气类App的核心功能模块之一。本文基于ArkTS语言,详细拆解城市搜索、历史记录存储、城市添加去重及UI组件封装的完整实现流程,提供可直接复用的代码方案与关键逻辑解析。
一、功能核心与技术栈
1. 核心功能
- 城市关键词搜索:调用天气API接口,实时返回匹配城市列表
- 搜索历史记录:输入框失焦时自动保存非空搜索内容
- 城市添加与去重:防止重复添加同一城市,并通过Toast提示结果
- 响应式UI:适配组件状态变化,展示城市名称、经纬度、国家等信息
2. 技术依赖
| 技术/框架 | 用途说明 |
|---|---|
| ArkTS (Stage模型) | 页面布局与组件逻辑实现 |
| Axios | 发起HTTP请求,获取城市搜索API数据 |
| HarmonyOS UI组件 | Column/Row/List/TextInput等布局组件 |
| @Builder装饰器 | 封装可复用的城市信息展示组件 |
二、数据模型定义
在实现功能前,需先定义核心数据模型,确保数据流转的一致性。以下是关键模型的简化定义(对应代码中model目录下的文件):
1. 城市数据模型(CityModel.ets)
存储已添加城市的核心标识信息
export interface CityModel {
locationId: number; // 城市唯一ID(用于去重)
locationName: string;// 城市名称
}
2. 搜索历史模型(SearchHistory.ets)
记录用户搜索过的城市名称
export interface SearchHistory {
locationName: string; // 搜索过的城市名称
}
3. 天气UI模型(WeatherUiModel.ets)
存储城市搜索结果与天气展示数据(核心为cityData数组)
// 城市搜索结果单项类型
export interface GeneratedTypeLiteralInterface_3 {
name: string; // 城市名
id: number; // 城市ID
lon: string; // 经度
lat: string; // 纬度
country: string; // 国家
rank: string; // 城市评分
}
// 天气UI总模型
export interface WeatherUiModel {
nowTemp: string;
nowWeatherText: string;
// ... 其他天气字段省略
cityData: GeneratedTypeLiteralInterface_3[]; // 城市搜索结果列表
}
三、核心页面实现(Addto.ets)
页面包含「顶部返回+搜索框」与「城市列表」两大模块,以下分模块解析关键逻辑。
1. 页面状态与生命周期
通过@Link接收父组件传递的状态(如已添加城市列表、侧边栏显示状态),aboutToAppear生命周期初始化搜索框默认值。
import { citySearch } from '../../network/api/WeatherApi';
import { WeatherUiModel, GeneratedTypeLiteralInterface_3 } from '../../model/WeatherUiModel';
import { caty } from '../../model/NowWeatherModel';
import { CityModel } from '../../model/CityModel';
import { promptAction } from '@kit.ArkUI';
import { SearchHistory } from '../../model/SearchHistory';
import { AxiosResponse } from '@ohos/axios';
@Component
export struct Addto {
// 父组件传递的已添加城市列表(双向绑定)
@Link cityArr: CityModel[];
// 搜索框输入内容
city: string = '';
// 搜索历史记录(双向绑定)
@Link searchHistory: SearchHistory[];
// 父组件传递的初始输入值(如从首页跳转带过来的城市名)
@Link initialInput: string;
// 天气UI数据模型(核心存储城市搜索结果)
@State weatherUiModel: WeatherUiModel = {
nowTemp: '',
nowWeatherText: '',
tempMax: '',
tempMin: '',
category: '',
precip: 0,
hourlyTemp: [],
iconDays: [],
cityData: [], // 城市搜索结果存储数组
date: '-月-日',
day: '',
dayArr: [],
hoursArr: [],
};
// 侧边栏显示状态(双向绑定)
@Link isShowSideBar1: boolean;
// 页面即将显示时,初始化搜索框内容
aboutToAppear(): void {
this.city = this.initialInput;
}
// 后续为build布局与业务方法
}
2. 页面布局(build方法)
采用Column嵌套结构,分为「顶部操作区」和「城市列表区」:
build() {
Column() {
// 1. 顶部操作区:返回图标 + 搜索框
Column() {
// 返回图标(控制侧边栏显示/隐藏)
Image($r('app.media.img_3'))
.width(40)
.height(40)
.onClick(() => {
this.isShowSideBar1 = !this.isShowSideBar1;
});
// 搜索框:实时触发搜索 + 失焦保存历史
TextInput({
placeholder: "请输入要查询的城市",
text: $$this.city // 双向绑定输入内容
})
.onChange(() => {
// 输入变化时,调用接口获取城市列表
this.initData(this.city);
})
.onBlur(() => {
// 输入框失焦时,保存非空内容到搜索历史
if (this.city !== '') {
this.searchHistory.push({ locationName: this.city });
}
});
}
.alignItems(HorizontalAlign.Start) // 左对齐
.padding(16); // 内边距
// 2. 城市列表区:展示搜索结果
List() {
ListItem() {
Column() {
// 循环渲染城市搜索结果
ForEach(
this.weatherUiModel.cityData,
(item: GeneratedTypeLiteralInterface_3) => {
// 调用自定义@Builder组件展示单个城市信息
wwh(
item.name,
item.id,
item.lon,
item.lat,
item.country,
item.rank,
this.cityArr
);
}
)
}
}
}
.padding(16);
}
.backgroundColor('#f5f5f5'); // 页面背景色
}
3. 城市搜索接口调用(initData方法)
通过Axios调用天气API的城市搜索接口,解析返回数据并更新到weatherUiModel.cityData:
/**
* 调用城市搜索API,获取匹配城市列表
* @param name 城市关键词
*/
async initData(name: string) {
try {
// 1. 调用API(citySearch为封装的Axios请求方法)
const response: AxiosResponse = await citySearch(name);
console.log('城市搜索API返回数据:', JSON.stringify(response));
// 2. 解析数据(caty为API返回数据的类型定义)
const resultData = response.data as caty;
// 清空原有结果,避免数据叠加
this.weatherUiModel.cityData = [];
// 3. 遍历数据,转换为UI所需格式并存储
for (let i = 0; i < resultData.location.length; i++) {
const location = resultData.location[i];
this.weatherUiModel.cityData.push({
name: location.name,
id: location.id,
lon: location.lon,
lat: location.lat,
country: location.country,
rank: location.rank,
});
}
} catch (error) {
console.error('城市搜索API调用失败:', error);
}
}
四、可复用UI组件封装(@Builder wwh)
通过@Builder装饰器封装单个城市的展示组件,包含城市信息展示与「添加」按钮,实现UI复用与逻辑内聚:
1. 组件实现
/**
* 城市信息展示组件
* @param name 城市名
* @param id 城市ID(用于去重)
* @param lon 经度
* @param lat 纬度
* @param country 国家
* @param rank 城市评分
* @param cityArr 已添加城市列表(用于判断是否重复)
*/
@Builder
function wwh(
name: string,
id: number,
lon: string,
lat: string,
country: string,
rank: string,
cityArr: CityModel[]
) {
Column({ space: 10 }) {
Row() {
// 左侧:城市信息展示区
Column() {
// 1. 城市名称 + 地标图标
Row() {
Image($r('app.media.img_8'))
.width(28)
.height(28)
.margin({ right: 8 });
Text(name)
.fontSize(20)
.fontWeight(FontWeight.Bold);
}
.margin({ bottom: 12 });
// 2. 国家信息 + 国旗图标
Row() {
Image($r('app.media.img_4'))
.width(20)
.height(20)
.borderRadius(4); // 圆角优化
Text(country)
.fontSize(14)
.margin({ left: 8 });
}
.padding(6)
.backgroundColor('#f5f5f5')
.borderRadius(8)
.margin({ bottom: 12 });
// 3. 城市评分 + 星标图标
Row() {
Image($r('app.media.img_6'))
.width(16)
.height(16)
.margin({ right: 4 });
Text("城市评分")
.fontSize(12);
Text(rank)
.fontColor('#ffd700') // 金色评分
.margin({ left: 4 });
}
.margin({ top: 12 });
// 4. 经纬度 + 定位图标
Row() {
Image($r('app.media.img_5'))
.width(16)
.height(16)
.margin({ right: 8 });
Text(`经度 ${lon}`)
.fontSize(12);
Text("•")
.margin({ left: 8, right: 8 });
Text(`纬度 ${lat}`)
.fontSize(12);
}
.padding(8)
.backgroundColor('#f8f9fa')
.borderRadius(6);
}
.layoutWeight(1); // 占满剩余宽度
// 右侧:添加按钮
Image($r('app.media.img_7'))
.onClick(() => {
// 调用judge方法判断是否已添加
if (judge(cityArr, id)) {
// 未添加则push到已添加列表
cityArr.push({
locationId: id,
locationName: name
});
} else {
// 已添加则提示
promptAction.showToast({
message: "已经添加过了",
duration: 2000 // 提示时长
});
}
})
.width(40)
.height(40)
.padding(8)
.backgroundColor('#e8f4ff')
.borderRadius(20); // 圆形按钮
}
.padding(16);
}
.backgroundColor('#ffffff')
.borderRadius(24)
.shadow(12) // 阴影效果,提升层次感
.margin({ bottom: 12 }); // 组件间距
}
2. 城市去重工具函数(judge)
判断当前城市是否已添加,避免重复存储:
/**
* 城市去重判断
* @param cityArr 已添加城市列表
* @param id 当前城市ID
* @returns true=未添加,false=已添加
*/
function judge(cityArr: CityModel[], id: number): boolean {
for (let i = 0; i < cityArr.length; i++) {
// 通过城市ID判断(ID唯一,比名称判断更准确)
if (cityArr[i].locationId === id) {
return false;
}
}
return true;
}
五、关键问题与优化建议
1. 常见问题解决
- 搜索接口频繁调用:当前
onChange实时触发接口,可添加「防抖」(如延迟300ms),减少无效请求。 - 搜索历史重复:当前
onBlur直接push,可在push前判断历史记录中是否已有该城市,避免重复。 - 列表性能优化:若搜索结果较多,可给
List添加itemSpace并使用「懒加载」,提升滚动流畅度。
2. 功能扩展建议
- 搜索历史管理:添加「清空历史」按钮,支持删除单条历史记录。
- 城市排序:已添加城市可按「添加时间」或「字母顺序」排序。
- 错误处理优化:API调用失败时,在页面显示「搜索失败,请重试」的友好提示,而非仅打印日志。
六、总结
本文基于ArkTS实现了HarmonyOS天气App的城市搜索与添加模块,核心亮点在于:
- 采用「状态驱动UI」思想,通过
@State/@Link实现组件间状态同步; - 封装
@Builder组件与工具函数,提升代码复用性与可维护性; - 完善的边界处理(如去重、错误捕获、用户提示),提升用户体验。
。
更多推荐

所有评论(0)