「Mac畅玩AIGC与多模态41」开发篇36 - 用 ArkTS 构建聚合搜索前端页面

一、概述

本篇基于上一节 Python 实现的双通道搜索服务(聚合 SearxNG + 本地知识库),构建一个完整的 HarmonyOS ArkTS 前端页面。用户可在输入框中输入关键词,实时查询本地服务 http://localhost:5001/search?q=...,返回结果自动渲染在页面中,支持中文和英文关键词的综合查询体验。

二、目标说明

  • 使用 ArkTS 开发搜索界面组件
  • 输入关键词并发起 HTTP 请求
  • 支持异步加载并实时显示搜索结果
  • 解析标准结构体 { results: [{ title, content, url }] } 并渲染结果列表

三、目录结构

复制代码
SearchApp/
├── src/main/ets/
│   ├── pages/
│   │   └── Index.ets                # 主页面:包含输入框、按钮和结果列表
│   ├── components/
│   │   └── SearchDisplay.ets        # 每条搜索结果的显示组件
│   ├── services/
│   │   └── SearchService.ets        # 封装 HTTP 调用和 JSON 解析
│   ├── utils/
│   │   └── HttpClient.ets           # 通用的 httpRequestText 实现
│   ├── types/
│   │   └── SearchTypes.ets          # SearchResponse 和 SearchResultList 类型定义
│   └── configs/
│       └── Config.ets               # 搜索 API URL 配置项

四、关键代码实现

1. 配置文件(Config.ets)

ts 复制代码
export const config = {
  searchApiUrl: "http://192.168.1.103:5001/search"
}

2. 类型定义(SearchTypes.ets)

ts 复制代码
// src/main/ets/types/SearchTypes.ets

/**
 * 单条搜索结果
 */
export interface SearchResponse {
  title: string
  content: string
  url: string
}

/**
 * 后端返回的结果结构
 */
export interface SearchResultList {
  results: SearchResponse[]
}

3. 搜索服务(SearchService.ets)

ts 复制代码
// src/main/ets/services/SearchService.ets

import { httpRequestText } from '../utils/HttpClient'
import { http } from '@kit.NetworkKit'
import { SearchResponse, SearchResultList } from '../types/SearchTypes'

export class SearchService {
  private apiUrl: string

  constructor(apiUrl: string) {
    this.apiUrl = apiUrl
  }

  public async search(
    query: string,
    onItem?: (item: SearchResponse) => void
  ): Promise<SearchResponse[]> {
    const url: string = `${this.apiUrl}?q=${encodeURIComponent(query)}`
    let buffer: string = ''

    try {
      await httpRequestText(
        url,
        http.RequestMethod.GET,
        '',
        60000,
        (chunk: string) => {
          buffer += chunk
        }
      )
    } catch {
      console.error('SearchService 网络请求失败')
      throw new Error('Search 请求失败')
    }

    let list: SearchResponse[]
    try {
      // 使用已定义的接口类型,而非内联对象字面量
      const parsed = JSON.parse(buffer) as SearchResultList
      list = parsed.results
    } catch {
      console.error('SearchService JSON 解析失败,buffer=', buffer)
      throw new Error('返回数据格式错误')
    }

    list.forEach((item: SearchResponse) => {
      if (onItem) {
        onItem(item)
      }
    })
    return list
  }
}

4. 单条结果展示组件(SearchDisplay.ets)

ts 复制代码
// src/main/ets/components/SearchDisplay.ets

import { SearchResponse } from '../types/SearchTypes';

@Component
export struct SearchDisplay {
  private item!: SearchResponse;

  build() {
    Column() {
      Text(this.item.title)
        .fontSize(18)
        .fontWeight('bold')
        .margin({ bottom: 4 });

      Text(this.item.content)
        .fontSize(14)
        .margin({ bottom: 4 });

      Text(this.item.url)
        .fontSize(12)
        .fontColor('#888');    // 使用 fontColor 设置文本颜色
    }
    .padding(10)
    .backgroundColor('#F5F5F5')
    .margin({ bottom: 10 });
  }
}

5. 主页面实现(Index.ets)

ts 复制代码
// src/main/ets/pages/Index.ets

import { SearchDisplay } from '../components/SearchDisplay'
import { config } from '../configs/Config'
import { SearchService } from '../services/SearchService'
import { SearchResponse } from '../types/SearchTypes'

@Entry
@Component
export struct Index {
  @State query: string = 'HarmonyOS'
  @State results: SearchResponse[] = []

  public async fetchData(): Promise<void> {
    this.results = []
    try {
      const res: SearchResponse[] = await new SearchService(config.searchApiUrl)
        .search(this.query)
      this.results = res
    } catch {
      console.error('搜索失败')
    }
  }

  public build(): void {
    Column() {
      TextInput({ text: this.query, placeholder: '输入搜索关键词,如 ChatGPT' })
        .onChange((value: string): void => {
          this.query = value
        })
        .width('match_parent')
        .height(50)
        .width(300)
        .margin({ bottom: 12 })

      Button('搜索')
        .width('match_parent')
        .height(50)
        .onClick((): void => {
          this.fetchData()
        })
        .margin({ bottom: 20 })

      ForEach(this.results, (item: SearchResponse): void => {
        SearchDisplay({ item })
      })
    }
    .padding({ left: 20, right: 20, top: 20 })
  }
}

五、运行效果示例

如图所示,输入 "HarmonyOS" 或 "ChatGPT" 后,前端立即展示聚合搜索结果:

六、总结

本篇在第40篇的 Python 搜索服务基础上,构建了 HarmonyOS 的前端页面。整个流程完整覆盖:

  • 构建 TextInput、按钮、结果展示组件
  • 使用 HttpClient.ets 封装请求
  • 实现 JSON 结构的严格类型解析与响应式渲染

通过该示例,开发人员可以快速将本地服务能力集成进 HarmonyOS App,用于搭建多模态查询工具、Dify Agent 插件原型或独立智能体前端。下一步可考虑引入分页、加载动画或语音输入等多模态交互能力。

相关推荐
想用offer打牌6 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
未来侦察班8 小时前
一晃13年过去了,苹果的Airdrop依然很坚挺。
macos·ios·苹果vision pro
在校大学生0078 小时前
AI教我赚100万用1年的时间–4(水文)
aigc
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端