欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net


前言

在移动应用开发中,网络请求是非常常见的基础能力。很多页面看起来只是展示信息,但背后都需要从接口获取数据,例如天气信息、城市服务、商品列表、课程数据、校园通讯录等。

本篇文章以“校园通讯录”为场景,使用 OpenHarmony 三方库 @ohos/axios 实现一个成员信息列表页面。项目运行后会自动请求远程接口,获取成员数据,并将姓名、用户名、邮箱和城市信息渲染成卡片列表。

这篇文章的重点不是做复杂业务,而是先把三方库接入、网络请求、数据解析和页面渲染这一整套流程跑通。通过这个项目,可以比较清楚地理解 OpenHarmony 项目中三方库网络请求的基本开发方式。

相比直接使用系统网络 API,axios 的写法更接近前端常见开发方式,适合后续继续封装统一请求、错误处理、拦截器和业务接口模块。


一、项目效果

最终运行效果如下:

在这里插入图片描述

页面主要包含以下内容:

  • 顶部标题:校园通讯录;
  • 右上角刷新按钮;
  • 成员信息卡片列表;
  • 每个卡片展示成员编号、姓名、用户名、邮箱和城市;
  • 支持重新请求数据;
  • 页面支持上下滚动查看更多成员。

这个页面可以作为校园通讯录、社团成员列表、班级信息表等项目的基础版本。


二、项目目标

本次实践主要实现以下目标:

  • 在 OpenHarmony 项目中安装 @ohos/axios 三方库;
  • 配置网络访问权限;
  • 封装 axios 网络请求工具类;
  • 请求远程 JSON 数据;
  • 定义成员信息数据结构;
  • 实现加载中、加载失败、加载成功三种状态;
  • 使用 ArkUI 的 ListForEach 渲染成员卡片;
  • 在 OpenHarmony 模拟器中完成运行验证。

三、技术栈

类型 内容
实战方向 Flutter for OpenHarmony 三方库实战
实现平台 OpenHarmony
开发语言 ArkTS
三方库 @ohos/axios
UI 框架 ArkUI
数据格式 JSON
测试接口 JSONPlaceholder Users
开发工具 DevEco Studio
运行环境 OpenHarmony 模拟器

四、为什么选择 axios

在网络请求场景中,axios 是一个比较常见的请求库。它支持 Promise 风格的异步请求,代码结构比较清晰,适合封装统一的请求模块。

在 OpenHarmony 中,可以使用适配后的 @ohos/axios 三方库完成网络请求。相比每次都直接写系统 HTTP 请求,axios 更适合后续扩展:

  • 可以统一配置请求地址;
  • 可以统一处理请求错误;
  • 可以封装 GET、POST 等常用方法;
  • 后续可以添加请求拦截器;
  • 后续可以添加响应拦截器;
  • 适合做成项目级请求工具类。

本篇先完成最基础的 GET 请求和列表渲染。


五、安装 axios 三方库

在项目根目录打开终端,执行以下命令:

ohpm install @ohos/axios

安装完成后,可以在项目中看到相关依赖信息。

通常可以检查以下位置:

oh-package.json5
oh-package-lock.json5
oh_modules

如果 oh_modules 中出现 axios 相关内容,说明三方库已经安装成功。


六、配置网络权限

应用需要访问网络接口,因此必须配置网络权限。

打开文件:

entry/src/main/module.json5

module 节点下添加:

"requestPermissions": [
  {
    "name": "ohos.permission.INTERNET"
  }
]

完整示例:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "phone"
    ],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages",
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ],
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:icon",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:icon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "action.system.home"
            ]
          }
        ]
      }
    ]
  }
}

这里最关键的是:

"ohos.permission.INTERNET"

如果没有这个权限,网络请求可能无法正常执行。


七、项目结构

本次主要修改以下文件:

entry
 └── src
     └── main
         ├── ets
         │   ├── pages
         │   │   └── Index.ets
         │   └── utils
         │       └── AxiosUtils.ets
         └── module.json5

文件说明:

文件 作用
module.json5 配置网络权限
AxiosUtils.ets 封装 axios 网络请求
Index.ets 页面展示、状态管理和列表渲染

八、封装 axios 请求工具类

entry/src/main/ets/utils 目录下新建文件:

AxiosUtils.ets

完整代码如下:

import axios, { AxiosResponse } from '@ohos/axios';

export class AxiosUtils {
  static async get<T>(url: string): Promise<T> {
    try {
      const response: AxiosResponse = await axios.get(url);

      if (response.status === 200) {
        return response.data as T;
      } else {
        throw new Error(`请求失败,状态码:${response.status}`);
      }
    } catch (err) {
      console.error(`axios 请求错误:${JSON.stringify(err)}`);
      throw new Error('网络请求失败,请检查网络连接或接口地址');
    }
  }
}

这段代码主要完成以下功能:

  1. 导入 @ohos/axios
  2. 使用 axios.get() 发起 GET 请求;
  3. 判断响应状态码是否为 200;
  4. 请求成功后返回 response.data
  5. 请求失败时抛出统一错误信息;
  6. 将请求逻辑从页面中拆出来,方便后续复用。

这样页面中就不需要关心 axios 的具体请求过程,只需要调用:

AxiosUtils.get<UserInfo[]>(url)

即可获取数据。


九、定义成员数据结构

本次使用测试接口:

https://jsonplaceholder.typicode.com/users

接口返回的是一个用户数组,每个用户对象包含编号、姓名、用户名、邮箱、地址等字段。

Index.ets 中定义数据结构:

interface AddressInfo {
  city: string;
}

interface UserInfo {
  id: number;
  name: string;
  username: string;
  email: string;
  address: AddressInfo;
}

字段说明如下:

字段 含义
id 成员编号
name 成员姓名
username 用户名
email 邮箱
address.city 所在城市

定义接口类型后,页面中使用字段会更加清晰,也能减少后续维护时的混乱。


十、页面状态设计

一个完整的网络请求页面,不能只处理“请求成功”这一种情况。实际开发中至少需要考虑三种状态:

状态 页面表现
加载中 显示加载动画
加载失败 显示错误提示和重新加载按钮
加载成功 显示成员列表

因此在页面中定义三个状态变量:

@State users: UserInfo[] = [];
@State isLoading: boolean = false;
@State errorMessage: string = '';

含义如下:

  • users:保存接口返回的成员数据;
  • isLoading:控制加载动画显示;
  • errorMessage:保存错误提示信息。

这种写法可以避免页面请求失败时直接空白,提高交互体验。


十一、Index.ets 完整代码

打开文件:

entry/src/main/ets/pages/Index.ets

完整代码如下:

import { AxiosUtils } from '../utils/AxiosUtils';

interface AddressInfo {
  city: string;
}

interface UserInfo {
  id: number;
  name: string;
  username: string;
  email: string;
  address: AddressInfo;
}

@Entry
@Component
struct Index {
  @State users: UserInfo[] = [];
  @State isLoading: boolean = false;
  @State errorMessage: string = '';

  aboutToAppear(): void {
    this.loadUsers();
  }

  async loadUsers(): Promise<void> {
    this.isLoading = true;
    this.errorMessage = '';

    try {
      const data = await AxiosUtils.get<UserInfo[]>(
        'https://jsonplaceholder.typicode.com/users'
      );

      this.users = data;
    } catch (err) {
      this.errorMessage = '成员数据加载失败,请检查网络连接';
      console.error(`加载成员数据失败:${JSON.stringify(err)}`);
    } finally {
      this.isLoading = false;
    }
  }

  build() {
    Column() {
      Row() {
        Text('校园通讯录')
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .fontColor('#182431')

        Blank()

        Button('刷新')
          .fontSize(14)
          .height(36)
          .onClick(() => {
            this.loadUsers();
          })
      }
      .width('100%')
      .padding({
        left: 16,
        right: 16,
        top: 24,
        bottom: 12
      })

      if (this.isLoading) {
        Column() {
          LoadingProgress()
            .width(50)
            .height(50)

          Text('正在加载成员数据...')
            .fontSize(16)
            .fontColor('#666666')
            .margin({ top: 12 })
        }
        .width('100%')
        .layoutWeight(1)
        .justifyContent(FlexAlign.Center)
      } else if (this.errorMessage !== '') {
        Column() {
          Text(this.errorMessage)
            .fontSize(18)
            .fontColor('#E84026')
            .margin({ bottom: 18 })

          Button('重新加载')
            .fontSize(16)
            .onClick(() => {
              this.loadUsers();
            })
        }
        .width('100%')
        .layoutWeight(1)
        .justifyContent(FlexAlign.Center)
      } else {
        List({ space: 12 }) {
          ForEach(this.users, (item: UserInfo) => {
            ListItem() {
              Row() {
                Text(item.id.toString())
                  .fontSize(18)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#FFFFFF')
                  .textAlign(TextAlign.Center)
                  .width(46)
                  .height(46)
                  .backgroundColor('#0A59F7')
                  .borderRadius(23)

                Column() {
                  Text(item.name)
                    .fontSize(18)
                    .fontWeight(FontWeight.Bold)
                    .fontColor('#182431')
                    .maxLines(1)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })

                  Text(`用户名:${item.username}`)
                    .fontSize(14)
                    .fontColor('#666666')
                    .margin({ top: 6 })

                  Text(`邮箱:${item.email}`)
                    .fontSize(14)
                    .fontColor('#666666')
                    .margin({ top: 4 })

                  Text(`城市:${item.address.city}`)
                    .fontSize(14)
                    .fontColor('#666666')
                    .margin({ top: 4 })
                }
                .layoutWeight(1)
                .margin({ left: 14 })
                .alignItems(HorizontalAlign.Start)
              }
              .width('100%')
              .padding(16)
              .backgroundColor('#F7F8FA')
              .borderRadius(16)
            }
          }, (item: UserInfo) => item.id.toString())
        }
        .width('100%')
        .layoutWeight(1)
        .padding({
          left: 16,
          right: 16,
          bottom: 16
        })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
}

十二、页面实现说明

页面整体使用 Column 作为根布局,顶部使用 Row 实现标题和刷新按钮。

Row() {
  Text('校园通讯录')
  Blank()
  Button('刷新')
}

点击刷新按钮后,会重新调用:

this.loadUsers();

列表区域使用 ListForEach 渲染成员数据:

List({ space: 12 }) {
  ForEach(this.users, (item: UserInfo) => {
    ListItem() {
      // 成员卡片内容
    }
  }, (item: UserInfo) => item.id.toString())
}

每个成员卡片分为两部分:

  • 左侧蓝色圆形编号;
  • 右侧成员姓名、用户名、邮箱、城市。

这样设计后,页面结构比较清楚,也更适合截图展示。


十三、运行效果

完成代码后,点击 DevEco Studio 中的运行按钮,将应用运行到 OpenHarmony 模拟器中。

运行成功后,页面顶部显示:

校园通讯录

下方展示成员卡片,例如:

1
Leanne Graham
用户名:Bret
邮箱:Sincere@april.biz
城市:Gwenborough

右上角的“刷新”按钮可以重新请求数据。


十四、开发中遇到的问题

1. ohpm install 安装失败

如果执行:

ohpm install @ohos/axios

失败,可以先检查终端是否在项目根目录下,或者检查 DevEco Studio 的 ohpm 环境是否正常。

也可以查看项目中是否已经生成:

oh_modules
oh-package-lock.json5

2. 找不到 @ohos/axios

如果代码中出现类似找不到模块的问题,可以检查:

oh-package.json5

中是否已经出现 axios 依赖。

还可以重新执行:

ohpm install @ohos/axios

3. 网络请求失败

如果页面显示“成员数据加载失败”,需要检查以下内容:

  • 模拟器是否能正常联网;
  • 接口地址是否写错;
  • module.json5 是否配置 ohos.permission.INTERNET
  • axios 是否安装成功;
  • 控制台是否有具体错误信息。

4. 页面卡片显示拥挤

如果列表内容太贴边,可以通过 paddingmarginborderRadius 调整样式。

例如本项目中使用:

.padding(16)
.backgroundColor('#F7F8FA')
.borderRadius(16)

让每个成员卡片更像真实应用中的信息块。


十五、总结

本篇完成了一个基于 @ohos/axios 的校园通讯录成员列表页面。项目通过 axios 请求远程成员数据,并使用 ArkUI 将数据渲染成卡片式列表。

通过本次实践,我主要完成了以下内容:

  • 安装并使用 @ohos/axios 三方库;
  • 配置 OpenHarmony 网络权限;
  • 封装 axios 请求工具类;
  • 请求远程 JSON 数据;
  • 定义成员信息数据结构;
  • 实现加载中、加载失败和加载成功三种状态;
  • 使用 ListForEach 渲染成员卡片;
  • 在模拟器中完成运行验证。

虽然这个项目只是校园通讯录的基础版本,但它已经具备真实应用开发中常见的数据请求和列表展示能力。

后续可以继续扩展为一个更完整的校园通讯录应用,例如:

  • 成员搜索与筛选;
  • 成员详情页跳转;
  • 收藏常用联系人;
  • 本地缓存通讯录数据;
  • 下拉刷新与分页加载;
  • 接入真实校园接口。

整体来看,axios 三方库让网络请求代码更加简洁,也方便后续进行统一封装。通过这个项目,可以更清楚地理解 OpenHarmony 中三方库接入、网络请求、状态管理和列表渲染之间的关系。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐