“面试通” HarmonyOS APP 试题搜索页面设计与搜索历史工具开发
本文介绍了“面试通”鸿蒙应用的搜索功能设计与实现,重点解析了搜索页面架构和搜索历史管理工具的开发。系统采用分层架构设计,通过用户界面层、业务逻辑层和数据层的协同工作实现搜索功能。搜索页面包含输入栏、历史记录和热搜推荐等组件,支持关键词联想和快捷搜索。核心的SearchHistoryManager工具利用鸿蒙Preferences实现本地持久化存储,提供历史记录的增删改查功能。整体设计注重用户体验与
·
本文以“面试通”鸿蒙应用项目为基础,结合华为官方开发指南,系统阐述试题搜索页面及其核心组件“搜索历史工具”的设计思路与开发实现。
一、 系统架构与数据流设计
一个高效的搜索功能,其背后是清晰的数据流转和处理逻辑。下图展示了“面试通”搜索模块从用户输入到结果展示的完整流程:
通过以上流程,我们将复杂的搜索交互分解为清晰的步骤,为后续的详细开发奠定基础。
二、 搜索页面(SearchPage)设计与实现
搜索首页是用户的第一触点,需要兼顾功能性和引导性。
1. 核心状态与页面结构
// SearchPage.ets
import { SearchHistoryManager, HistoryItem } from '../utils/SearchHistoryManager';
import router from '@ohos.router';
@Entry
@Component
struct SearchPage {
// 搜索框输入值
@State searchKeyword: string = '';
// 控制清空按钮显示
@State showClearIcon: boolean = false;
// 搜索历史列表
@State historyList: HistoryItem[] = [];
// 热搜推荐列表
@State hotSearchList: string[] = ['HarmonyOS', 'ArkTS', 'Ability', 'UI组件', '网络请求'];
// 引入历史管理工具
private historyManager: SearchHistoryManager = new SearchHistoryManager();
// 页面显示时加载历史记录
aboutToAppear(): void {
this.loadSearchHistory();
}
// 加载历史记录
private loadSearchHistory(): void {
this.historyList = this.historyManager.getHistoryList();
}
build() {
Column({ space: 0 }) {
// 顶部导航栏
this.buildTitleBar()
// 搜索输入区域
this.buildSearchInput()
// 内容区域:根据有无输入切换视图
if (this.searchKeyword.trim().length > 0) {
// 显示搜索联想建议(可根据需要实现)
this.buildSuggestions()
} else {
// 显示历史记录和热搜
Scroll() {
Column() {
this.buildSearchHistory()
this.buildHotSearch()
}
}
}
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.background'))
}
}
2. 搜索输入栏组件实现
搜索输入栏是核心交互组件,需提供良好的即时反馈。
// SearchPage.ets (续)
@Builder
buildSearchInput() {
Row({ space: 12 }) {
// 搜索图标
Image($r('app.media.ic_search'))
.width(24)
.height(24)
// 文本输入框
TextInput({
placeholder: '请输入试题关键词,如“生命周期”',
text: this.searchKeyword
})
.placeholderColor($r('app.color.placeholder'))
.placeholderFont({ size: 16 })
.height(40)
.flexGrow(1)
.fontSize(18)
.caretColor($r('app.color.primary'))
.onChange((value: string) => {
this.searchKeyword = value;
this.showClearIcon = value.length > 0;
// 此处可添加防抖函数,用于实时搜索建议
})
.onSubmit(() => {
// 回车键提交搜索
if (this.searchKeyword.trim()) {
this.doSearch();
}
})
// 动态清空按钮
if (this.showClearIcon) {
Image($r('app.media.ic_clear'))
.width(20)
.height(20)
.onClick(() => {
this.searchKeyword = '';
this.showClearIcon = false;
})
}
// 搜索按钮(移动端适配)
Button('搜索', { type: ButtonType.Normal })
.fontSize(16)
.height(40)
.padding({ left: 16, right: 16 })
.backgroundColor(this.searchKeyword.trim() ? $r('app.color.primary') : $r('app.color.button_disabled'))
.enabled(this.searchKeyword.trim().length > 0)
.onClick(() => {
if (this.searchKeyword.trim()) {
this.doSearch();
}
})
}
.width('100%')
.padding({ left: 24, right: 24, top: 16, bottom: 16 })
.backgroundColor(Color.White)
}
3. 搜索历史与热搜区域
// SearchPage.ets (续)
@Builder
buildSearchHistory() {
if (this.historyList.length > 0) {
Column({ space: 12 }) {
// 标题栏
Row({ space: 0 }) {
Text('搜索历史')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.flexGrow(1)
Image($r('app.media.ic_delete'))
.width(20)
.height(20)
.onClick(() => {
// 清空历史确认弹窗
this.showClearHistoryDialog();
})
}
.width('100%')
// 历史标签流式布局
Flow({ spacing: 12, direction: FlowDirection.LeftToRight }) {
ForEach(this.historyList, (item: HistoryItem) => {
// 历史记录标签组件
this.buildHistoryTag(item)
})
}
}
.width('100%')
.padding(24)
}
}
@Builder
buildHistoryTag(item: HistoryItem) {
Text(item.keyword)
.fontSize(14)
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor($r('app.color.tag_background'))
.fontColor($r('app.color.text_secondary'))
.borderRadius(20)
.onClick(() => {
// 点击历史记录,填充关键词并执行搜索
this.searchKeyword = item.keyword;
this.doSearch();
})
}
三、 搜索历史工具(SearchHistoryManager)核心开发
搜索历史工具是保障良好用户体验的关键,需要实现高效、持久化的本地存储。
1. 数据模型与存储方案设计
“面试通”采用鸿蒙首选项(Preferences)存储搜索历史,这是轻量键值存储的官方推荐方案。
// utils/SearchHistoryManager.ts
import dataPreferences from '@ohos.data.preferences';
import util from '@ohos.util';
// 历史记录项数据模型
export interface HistoryItem {
id: string; // 唯一ID(时间戳+随机数)
keyword: string; // 搜索关键词
timestamp: number; // 搜索时间戳
searchCount: number; // 搜索次数(用于智能排序)
}
// 搜索历史管理工具类
export class SearchHistoryManager {
private static readonly PREFERENCE_NAME: string = 'search_history_store';
private static readonly MAX_HISTORY_COUNT: number = 20; // 最大保存条数
private preferences: dataPreferences.Preferences | null = null;
// 初始化首选项数据库
async initialize(): Promise<void> {
try {
const context = getContext(this) as common.UIAbilityContext;
this.preferences = await dataPreferences.getPreferences(context, SearchHistoryManager.PREFERENCE_NAME);
} catch (error) {
console.error(`搜索历史首选项初始化失败: ${error.message}`);
}
}
// 添加搜索记录(核心方法)
async addSearchHistory(keyword: string): Promise<void> {
if (!keyword.trim() || !this.preferences) {
return;
}
const trimmedKeyword = keyword.trim();
let historyList = await this.getHistoryList();
// 1. 检查是否已存在相同关键词
const existingIndex = historyList.findIndex(item => item.keyword === trimmedKeyword);
const currentTime = new Date().getTime();
if (existingIndex >= 0) {
// 已存在:更新次数和时间,移至顶部
const existingItem = historyList[existingIndex];
existingItem.searchCount += 1;
existingItem.timestamp = currentTime;
// 从原位置移除
historyList.splice(existingIndex, 1);
} else {
// 新记录:创建新条目
const newItem: HistoryItem = {
id: this.generateId(trimmedKeyword, currentTime),
keyword: trimmedKeyword,
timestamp: currentTime,
searchCount: 1
};
// 如果达到上限,移除最旧的一条
if (historyList.length >= SearchHistoryManager.MAX_HISTORY_COUNT) {
historyList.pop(); // 按时间排序后,最后一条是最旧的
}
}
// 将更新的项目插入到数组开头
if (existingIndex >= 0) {
historyList.unshift(historyList.splice(existingIndex, 1)[0]);
} else {
historyList.unshift({
id: this.generateId(trimmedKeyword, currentTime),
keyword: trimmedKeyword,
timestamp: currentTime,
searchCount: 1
});
}
// 3. 保存更新后的列表
await this.saveHistoryList(historyList);
}
// 生成唯一ID
private generateId(keyword: string, timestamp: number): string {
const input = `${keyword}_${timestamp}_${Math.random()}`;
const md5 = util.hashString.hash(util.hashString.HashMd5, input);
return md5.substring(0, 12); // 取前12位作为ID
}
// 获取历史记录列表(按时间倒序)
async getHistoryList(): Promise<HistoryItem[]> {
if (!this.preferences) {
await this.initialize();
}
try {
const historyJson = await this.preferences.get('history_list', '[]');
const list = JSON.parse(historyJson) as HistoryItem[];
// 按时间戳降序排序(最新的在前)
return list.sort((a, b) => b.timestamp - a.timestamp);
} catch (error) {
console.error(`读取搜索历史失败: ${error.message}`);
return [];
}
}
// 保存历史记录列表到首选项
private async saveHistoryList(list: HistoryItem[]): Promise<void> {
if (!this.preferences) return;
try {
const jsonStr = JSON.stringify(list);
await this.preferences.put('history_list', jsonStr);
await this.preferences.flush(); // 提交更改
} catch (error) {
console.error(`保存搜索历史失败: ${error.message}`);
}
}
// 清空所有搜索历史
async clearAllHistory(): Promise<boolean> {
try {
await this.preferences.delete('history_list');
await this.preferences.flush();
return true;
} catch (error) {
console.error(`清空搜索历史失败: ${error.message}`);
return false;
}
}
// 删除单条历史记录
async deleteHistoryItem(id: string): Promise<boolean> {
const historyList = await this.getHistoryList();
const newList = historyList.filter(item => item.id !== id);
if (newList.length !== historyList.length) {
await this.saveHistoryList(newList);
return true;
}
return false;
}
}
2. 在页面中集成历史管理工具
// SearchPage.ets (续)
// 执行搜索并保存历史
private async doSearch(): Promise<void> {
const keyword = this.searchKeyword.trim();
if (!keyword) return;
// 1. 保存到搜索历史
await this.historyManager.addSearchHistory(keyword);
// 2. 刷新本地历史列表显示
this.loadSearchHistory();
// 3. 跳转到搜索结果页,传递搜索关键词
router.pushUrl({
url: 'pages/SearchResultPage',
params: { keyword: keyword }
}).catch(err => {
console.error(`跳转到搜索结果页失败: ${err.message}`);
});
}
// 显示清空历史确认对话框
private showClearHistoryDialog(): void {
AlertDialog.show({
title: '清空搜索历史',
message: '确定要清空所有搜索历史记录吗?此操作不可撤销。',
primaryButton: {
value: '取消',
action: () => {
// 取消操作
}
},
secondaryButton: {
value: '清空',
backgroundColor: $r('app.color.danger'),
fontColor: Color.White,
action: async () => {
const success = await this.historyManager.clearAllHistory();
if (success) {
this.historyList = [];
// 可在此处添加成功提示
}
}
}
});
}
四、 搜索结果页(SearchResultPage)实现
搜索结果页需要清晰地展示搜索条件和匹配的试题列表。
// SearchResultPage.ets
@Component
struct SearchResultPage {
// 通过路由参数接收的搜索关键词
private searchKeyword: string = router.getParams()?.['keyword'] || '';
// 试题列表
@State questionList: QuestionItem[] = [];
// 加载状态
@State isLoading: boolean = true;
// 是否无结果
@State noResults: boolean = false;
aboutToAppear(): void {
if (this.searchKeyword) {
this.performSearch();
}
}
// 执行搜索逻辑
private async performSearch(): Promise<void> {
this.isLoading = true;
// 模拟网络请求延迟
setTimeout(async () => {
try {
// 实际项目中,这里调用服务接口
const results = await this.mockSearchApi(this.searchKeyword);
this.questionList = results;
this.noResults = results.length === 0;
} catch (error) {
console.error(`搜索失败: ${error.message}`);
this.noResults = true;
} finally {
this.isLoading = false;
}
}, 300);
}
build() {
Column() {
// 固定顶部搜索栏
this.buildStickySearchBar()
// 内容区域
if (this.isLoading) {
this.buildLoadingView()
} else if (this.noResults) {
this.buildEmptyView()
} else {
this.buildResultList()
}
}
}
@Builder
buildStickySearchBar() {
Column() {
// 显示当前搜索关键词
Text(`"${this.searchKeyword}" 的搜索结果`)
.fontSize(18)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 8 })
// 结果显示统计
Text(`共找到 ${this.questionList.length} 道相关试题`)
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.border({ width: { bottom: 1 }, color: $r('app.color.border') })
}
}
五、 高级功能与优化建议
1. 搜索联想建议(Type-ahead Suggestions)
在用户输入时提供实时建议,可基于本地历史或调用服务端接口。
// 防抖函数优化搜索建议
private debounceTimer: number | undefined;
private onSearchInputChange(value: string): void {
this.searchKeyword = value;
this.showClearIcon = value.length > 0;
// 清除之前的定时器
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
// 设置新的防抖定时器(300毫秒后触发)
if (value.trim().length > 0) {
this.debounceTimer = setTimeout(() => {
this.fetchSuggestions(value);
}, 300);
}
}
private async fetchSuggestions(keyword: string): Promise<void> {
// 1. 首先从历史记录中匹配
const history = await this.historyManager.getHistoryList();
const historySuggestions = history
.filter(item => item.keyword.includes(keyword))
.slice(0, 5); // 最多显示5条
// 2. 如果有服务端接口,可以在此处调用
// const serverSuggestions = await searchApi.getSuggestions(keyword);
// 3. 更新UI显示建议
// this.suggestionList = [...historySuggestions, ...serverSuggestions];
}
2. 历史记录智能排序算法
结合搜索频率和时间因素进行智能排序。
// 在SearchHistoryManager中添加智能排序方法
getSmartHistoryList(): HistoryItem[] {
// 计算每个项目的权重分数
const scoredList = this.historyList.map(item => {
// 时间衰减因子(24小时衰减一半)
const timeFactor = Math.exp(-(Date.now() - item.timestamp) / (24 * 60 * 60 * 1000) * Math.LN2);
// 搜索频率因子
const frequencyFactor = Math.log(1 + item.searchCount);
// 综合权重
const score = timeFactor * frequencyFactor;
return { ...item, score };
});
// 按权重分数降序排列
return scoredList.sort((a, b) => b.score - a.score);
}
六、 效果对比与总结
| 特性 | 基础实现 | 优化实现 | 用户体验提升 |
|---|---|---|---|
| 历史存储 | 仅内存存储,应用关闭丢失 | 首选项持久化存储,长期保存 | 用户数据不丢失,体验连贯 |
| 历史排序 | 仅按时间倒序 | 智能排序(频率+时间衰减) | 高频搜索更易访问,更智能 |
| 输入交互 | 仅手动提交搜索 | 防抖联想建议 + 历史快捷填充 | 减少输入,搜索更便捷 |
| 性能优化 | 每次操作直接读写存储 | 批量操作 + 内存缓存 | 响应更快,更省电 |
| 数据安全 | 明文存储 | 关键词脱敏 + 数据加密(可扩展) | 用户隐私更安全 |
总结:
本文基于HarmonyOS ArkTS开发规范,完整实现了“面试通”应用的试题搜索功能。核心亮点在于:
- 架构清晰:通过分层设计,分离UI展示、业务逻辑与数据持久化。
- 体验流畅:结合搜索历史、智能联想、防抖优化,减少用户操作步骤。
- 存储可靠:使用鸿蒙首选项(Preferences)实现历史记录的可靠本地存储。
- 扩展性强:工具类设计便于复用,可轻松集成到应用其他模块。
此实现严格遵循了华为官方开发指南,充分利用了ArkUI声明式语法和状态管理,开发者可在此基础上,进一步集成网络搜索、试题高亮、搜索过滤等高级功能。
更多推荐




所有评论(0)