HarmonyOS6 - 调用第三方接口实现新闻APP

调用第三方接口

鸿蒙ArkTS-发请求给第三方接口显示实时新闻列表页面

开发环境为:

开发工具:DevEco Studio 6.0.1 Release

API版本是:API21

本文所有代码都已使用模拟器测试成功!

1. 准备工作

1. 申请key

我们本案例中调用的第三方接口来自于聚合数据,官网地址:https://dashboard.juhe.cn

我们页面的所有数据都会调用聚合提供的接口,来获取数据,所以我们需要先到官网上进行账号注册

然后进行实名认证,之后我们需要申请具体接口的key(获取不同数据的接口需要不同的key,这个key类似于授权码,没有这个key,就无法调用接口)

下面是申请key的步骤:

我这里以新闻为例,比如我想调用接口获取实时新闻数据,那我就这样选:

这样我们就得到了调用这个获取新闻信息的key了

但是需要注意的是:免费额度是每天只能调用50次,如果需要更多次数的调用,需要开会员额外收费

另外,每个账号只能申请3个免费接口,超出3个的话,需要额外收费了

2. 开发思路

准备工作做好之后,接下来我们开始按照上面的开发思路,分为如下四步实现我们的需求

  1. 发送HTTP请求给聚合网
  2. 查看聚合网给我们返回的响应数据
  3. 处理响应数据
  4. 响应数据显示到页面上

2. 实战编码

1. 发送HTTP请求给聚合网

代码如下:

js 复制代码
import { http } from '@kit.NetworkKit';

@Entry
@Component
struct NewsTest1 {
  //调用聚合网API接口的key(换成自己申请的key即可)
  @State keyString: string = 'xxxxxxxxxxxxxxxxxx';
  //新闻类型
  @State newsType: string = 'top';

  /**
   * aboutToAppear:页面加载时就会执行,在build之前执行
   */
  aboutToAppear(): void {
    //定义变量url,保存访问第三方接口的地址
    let url: string = 'http://v.juhe.cn/toutiao/index?key=' + this.keyString + '&type=' + this.newsType + '&page=1&page_size=20&is_filter=1';
    //向第三方接口发请求
    MyTools.getHTTPData(url);
  }

  build() {
    Column() {
      Text('Hello World')
    }
    .height('100%')
    .width('100%')
  }
}

/**
 * 工具类
 */
class MyTools {

  /**
   * 发请求给第三方接口获取数据
   */
  static getHTTPData(url: string): Promise<object> {
    return new Promise((resolve: Function, reject: Function) => {
      let httpRequest = http.createHttp();
      httpRequest.request(url, { method: http.RequestMethod.GET }
      ).then((resp: http.HttpResponse) => {
        //HTTP响应状态码200表示请求成功
        if (resp.responseCode === 200) {
          console.log('第三方接口返回数据:', resp.result)
          //将从第三方接口获取的数据返回给调用方
          resolve(JSON.parse(resp.result.toString()));//JSON.parse:作用是将字符串转成对象
        } else {
          console.log('HTTP请求获取数据失败,error:', JSON.stringify(resp))
          reject('HTTP请求获取数据失败!')
        }
      })
    })
  }
}

2. 查看相应数据

控制台可以看到打印日志:

有数据返回,说明我们调用第三方接口成功了,接下来就是需要将这些数据显示到页面上就可以了

3. 处理响应数据

通过官网API接口说明文档,我们可以得知,响应数据的格式如下:

每个字段的含义官网也有详细解释,如下:

此时我们需要定义一些数据结构,来接收官网给我们返回的数据,最终代码如下:

js 复制代码
import { http } from '@kit.NetworkKit';
import { promptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

interface NewsResult {
  reason: string
  result: ResultJson
  error_code: number
}

interface ResultJson {
  stat: string
  data: Array<ResultData>
  page: string
  pageSize: string
}

interface ResultData {
  uniquekey: string
  title: string
  date: string
  category: string
  author_name: string
  url: string
  thumbnail_pic_s: string
  is_content: string
}

@Entry
@Component
struct NewsTest1 {
  //调用聚合网API接口的key(换成自己申请的key即可)
  @State keyString: string = 'xxxxxxxx';
  //新闻类型
  @State newsType: string = 'top';
  //定义响应数据存放的变量
  @State responseResult: NewsResult | null = null;
  //定义新闻数据存放的变量
  @State dataList: Array<ResultData> = [];

  /**
   * aboutToAppear:页面加载时就会执行,在build之前执行
   */
  aboutToAppear(): void {
    //定义变量url,保存访问第三方接口的地址
    let url: string = 'http://v.juhe.cn/toutiao/index?key=' + this.keyString + '&type=' + this.newsType +
      '&page=1&page_size=20&is_filter=1';
    //向第三方接口发请求
    MyTools.getHTTPData(url).then((res: NewsResult) => {
      //打印请求结果
      console.log('接口响应数据=', JSON.stringify(res)); //JSON.stringify:作用是将res对象转成JSON字符串
      //将新闻结果数据赋值给全局变量:newsResult
      this.responseResult = res;
      if (this.responseResult?.error_code == 0) {
        this.dataList = this.responseResult.result.data;
        console.log('新闻数据=', JSON.stringify(this.dataList));
      } else {
        //弹框提示
        promptAction.showToast({ message: this.responseResult?.reason })
      }
    }).catch((error: BusinessError) => {
      //发生异常时,弹框提示错误信息
      promptAction.showToast({ message: '发生异常:' + JSON.stringify(error) })
    })
  }

  build() {
    Column() {
      Text('Hello World')
    }
    .height('100%')
    .width('100%')
  }
}

/**
 * 工具类
 */
class MyTools {
  /**
   * 发请求给第三方接口获取数据
   */
  static getHTTPData(url: string): Promise<NewsResult> {
    return new Promise((resolve: Function, reject: Function) => {
      let httpRequest = http.createHttp();
      httpRequest.request(url, { method: http.RequestMethod.GET }
      ).then((resp: http.HttpResponse) => {
        //HTTP响应状态码200表示请求成功
        if (resp.responseCode === 200) {
          console.log('第三方接口返回数据:', resp.result)
          //将从第三方接口获取的数据返回给调用方
          resolve(JSON.parse(resp.result.toString())); //JSON.parse:作用是将字符串转成对象
        } else {
          console.log('HTTP请求获取数据失败,error:', JSON.stringify(resp))
          reject('HTTP请求获取数据失败!')
        }
      })
    })
  }
}

观察控制台,发现数据新闻数据已经获取到了,并且存入全局变量dataList中了,如下图所示:

4. 响应数据显示到页面上

全局变量dataList中有数据了,那现在就是将变量中的值循环展示到页面中就可以了

代码如下:

js 复制代码
import { http } from '@kit.NetworkKit';
import { promptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

interface NewsResult {
  reason: string
  result: ResultJson
  error_code: number
}

interface ResultJson {
  stat: string
  data: Array<ResultData>
  page: string
  pageSize: string
}

interface ResultData {
  uniquekey: string
  title: string
  date: string
  category: string
  author_name: string
  url: string
  thumbnail_pic_s: string
  is_content: string
}

@Entry
@Component
struct NewsTest1 {
  //调用聚合网API接口的key(换成自己申请的key即可)
  @State keyString: string = 'xxxxxxxxxxxxxxxxxxxx';
  //新闻类型
  @State newsType: string = 'top';
  //定义响应数据存放的变量
  @State responseResult: NewsResult | null = null;
  //定义新闻数据存放的变量
  @State dataList: Array<ResultData> = [];

  /**
   * aboutToAppear:页面加载时就会执行,在build之前执行
   */
  aboutToAppear(): void {
    //定义变量url,保存访问第三方接口的地址
    let url: string = 'http://v.juhe.cn/toutiao/index?key=' + this.keyString + '&type=' + this.newsType +
      '&page=1&page_size=20&is_filter=1';
    //向第三方接口发请求
    MyTools.getHTTPData(url).then((res: NewsResult) => {
      //打印请求结果
      console.log('接口响应数据=', JSON.stringify(res)); //JSON.stringify:作用是将res对象转成JSON字符串
      //将新闻结果数据赋值给全局变量:newsResult
      this.responseResult = res;
      if (this.responseResult?.error_code == 0) {
        this.dataList = this.responseResult.result.data;
        console.log('新闻数据=', JSON.stringify(this.dataList));
      } else {
        //弹框提示
        promptAction.showToast({ message: this.responseResult?.reason })
      }
    }).catch((error: BusinessError) => {
      //发生异常时,弹框提示错误信息
      promptAction.showToast({ message: '发生异常:' + JSON.stringify(error) })
    })
  }

  build() {
    Column() {
      List() {
        ForEach(this.dataList, (item: ResultData) => {
          ListItem() {
            Column() {
              Row() {
                //标题
                Text(item.title)
                  .fontSize(17)//设置文字大小
                  .lineHeight(26)//设置行高度
                  .fontWeight(300)//设置文字加粗效果
                  .maxLines(2)//最多显示n行
                  .textOverflow({ overflow: TextOverflow.Ellipsis }) //超过n行就显示省略号
              }
              .padding({ top: 2, bottom: 2 }) //设置内边距
              .width('100%') //设置宽度

              //配图
              if (item.thumbnail_pic_s) {
                Image(item.thumbnail_pic_s)
                  .width('100%')
                  .height(140)
              }

              Row({ space: 12 }) {
                //作者
                Text(item.author_name)
                  .fontSize(13)
                  .fontWeight(500)
                  .fontColor('#cccccc')
                //时间
                Text(item.date)
                  .fontSize(13)
                  .fontWeight(500)
                  .fontColor('#cccccc')
              }
              .padding({ top: 5, bottom: 5 }) //设置内边距
              .width('100%')

              //分割线
              Divider()
                .strokeWidth(1)
                .color('#fff1f1f1')
                .opacity(1)//设置透明度
                .width('100%')

            }
            .padding(12) //设置内边距
            .width('100%')
          }
        })
      }
      .alignListItem(ListItemAlign.Center) //设置List每个子组件都居中显示
      .height('98%')
    }
    .height('100%')
    .width('100%')
  }
}

/**
 * 工具类
 */
class MyTools {
  /**
   * 发请求给第三方接口获取数据
   */
  static getHTTPData(url: string): Promise<NewsResult> {
    return new Promise((resolve: Function, reject: Function) => {
      let httpRequest = http.createHttp();
      httpRequest.request(url, { method: http.RequestMethod.GET }
      ).then((resp: http.HttpResponse) => {
        //HTTP响应状态码200表示请求成功
        if (resp.responseCode === 200) {
          console.log('第三方接口返回数据:', resp.result)
          //将从第三方接口获取的数据返回给调用方
          resolve(JSON.parse(resp.result.toString())); //JSON.parse:作用是将字符串转成对象
        } else {
          console.log('HTTP请求获取数据失败,error:', JSON.stringify(resp))
          reject('HTTP请求获取数据失败!')
        }
      })
    })
  }
}

5. 成果图

6. 代码优化(新增Tabs)

效果图如下:

全量代码如下:

js 复制代码
import { http } from '@kit.NetworkKit';
import { promptAction } from '@kit.ArkUI';

class Header {
  public contentType: string;

  constructor(contentType: string) {
    this.contentType = contentType;
  }
}

interface R {
  reason?: string
  result?: ResultModel
  error_code?: number
}

interface ResultModel {
  stat: string
  data: Array<NewsModel>
  page: string
  pageSize: string
}

interface NewsModel {
  uniquekey: string
  title: string
  date: string
  category: string
  author_name: string
  url: string
  thumbnail_pic_s: string
  thumbnail_pic_s02: string
  thumbnail_pic_s03: string
  is_content: string
}

/**
 * Desc: 新闻页面
 * Author: 波波老师(weixin: javabobo0513)
 */
@Entry
@Component
struct NewsPage {
  @State fontColor: ResourceStr = '#ff6d6d6e'
  @State selectedFontColor: ResourceStr = '#111111'
  @State currentIndex: number = 0
  private controller: TabsController = new TabsController()
  @State r: R = {} //保存第三方接口返回的所有数据
  @State newsList: Array<NewsModel> = [] //只保存新闻数据
  @State newsType: string = 'top' //新闻类型

  @Builder
  TabBuilder(index: number, name: string) {
    Column({ space: 5 }) {
      Text(name)
        .fontColor(this.currentIndex === index ? this.selectedFontColor : this.fontColor)
        .fontSize(this.currentIndex === index ? 18 : 16)
        .fontWeight(this.currentIndex === index ? 900 : 400)
        .lineHeight(22)
      Divider()
        .width(23)
        .strokeWidth(4)
        .color('#111111')
        .opacity(this.currentIndex === index ? 1 : 0)
    }
    .width(50)
  }

  //发请求获取新闻数据-函数
  getNewsData(): void {
    console.log('开始执行函数了.....')
    let httpRequest = http.createHttp();
    let promise =
      httpRequest.request("http://v.juhe.cn/toutiao/index?key=xxxxxxxxxxxxxxxxx&type=" + this.newsType +
        "&page=20&page_size=30&is_filter=1",
        {
          method: http.RequestMethod.GET,
          connectTimeout: 60000,
          readTimeout: 60000,
          header: new Header('application/json')
        });
    promise.then((data: http.HttpResponse) => {
      console.info('Result:' + data.result);
      if (data.responseCode == 200) {
        /**
         * JSON.stringify:将对象转成JSON格式的字符串
         * JSON.parse:将JSON格式字符串转成对象
         */
        this.r = JSON.parse(data.result.toString())
        if (this.r.error_code == 0) {
          //接口OK,处理数据
          this.newsList = this.r.result?.data as Array<NewsModel>
          console.info('新闻数据=' + JSON.stringify(this.r.result?.data));
        } else {
          //错误弹框
          promptAction.showToast({
            message: this.r.reason,
            duration: 5000 //单位:毫秒
          })
        }
      } else {
        //错误弹框
        promptAction.showToast({
          message: '网络故障...',
          duration: 5000 //单位:毫秒
        })
      }

    }).catch((err: Error) => {
      console.error('error:' + JSON.stringify(err));
    });
  }

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

  //展示新闻数据的函数
  @Builder
  NewsView() {
    Scroll() {
      Column({ space: 13 }) {

        ForEach(this.newsList, (item: NewsModel) => {
          //新闻小卡片
          Column({ space: 13 }) {
            //新闻标题
            Text(item.title)
            //新闻封面照
            Image(item.thumbnail_pic_s ? item.thumbnail_pic_s :
              (item.thumbnail_pic_s02 ? item.thumbnail_pic_s02 : item.thumbnail_pic_s03))
              .width('100%')
              .height(180)
              .borderRadius(5)
            //作者+时间
            Row({ space: 10 }) {
              Text(item.author_name)
                .fontColor('#ccc')
              Text(item.date)
                .fontColor('#ccc')
            }
            .width('100%')
          }
          .backgroundColor('#ffffff')
          .borderRadius(8)
          .padding(5)
          .alignItems(HorizontalAlign.Start)
        })


      }
      .padding({ left: 10, right: 10 })
      .width('100%')

    }
    .height('100%')
    .width('100%')
    .backgroundColor('#ccc')
  }

  build() {
    Column() {
      Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.controller }) {
        TabContent() {
          this.NewsView();
        }.tabBar(this.TabBuilder(0, '推荐'))

        TabContent() {
          this.NewsView();
        }.tabBar(this.TabBuilder(1, '国内'))

        TabContent() {
          this.NewsView();
        }.tabBar(this.TabBuilder(2, '国际'))

        TabContent() {
          this.NewsView();
        }.tabBar(this.TabBuilder(3, '娱乐'))

        TabContent() {
          this.NewsView();
        }.tabBar(this.TabBuilder(4, '体育'))

        TabContent() {
          this.NewsView();
        }.tabBar(this.TabBuilder(5, '军事'))

        TabContent() {
          this.NewsView();
        }.tabBar(this.TabBuilder(6, '科技'))

        TabContent() {
          this.NewsView();
        }.tabBar(this.TabBuilder(7, '财经'))

        TabContent() {
          this.NewsView();
        }.tabBar(this.TabBuilder(8, '游戏'))

      }
      .vertical(false) //设置为false是为横向Tabs,设置为true时为纵向Tabs
      .scrollable(true) //设置为true时可以通过滑动页面进行页面切换,为false时不可滑动切换页面
      .barMode(BarMode.Scrollable) //TabBar布局模式
      .barWidth('100%') //TabBar的宽度值
      .animationDuration(100) //TabContent滑动动画时长。不设置时,点击切换页签无动画,滑动切换有动画;设置时,点击切换和滑动切换都有动画
      .width('100%')
      .height('100%')
      .backgroundColor('#ffffff')
      .onChange((index: number) => {
        console.log('index=' + index)
        this.currentIndex = index;
        if (index == 0) {
          this.newsType = 'top'
        }
        else if (index == 1) {
          this.newsType = 'guonei'
        }
        else if (index == 2) {
          this.newsType = 'guoji'
        }
        else if (index == 3) {
          this.newsType = 'yule'
        }
        else if (index == 4) {
          this.newsType = 'tiyu'
        }
        else if (index == 5) {
          this.newsType = 'junshi'
        }
        else if (index == 6) {
          this.newsType = 'keji'
        }
        else if (index == 7) {
          this.newsType = 'caijing'
        }
        else if (index == 8) {
          this.newsType = 'youxi'
        }
        this.getNewsData();//重新发请求获取最新的新闻数据
      })
    }
  }
}

7. 开通网络权限(模拟器)

注意:我们使用预览器时,不需要开通网络权限就可以正常发请求,但是如果使用模拟器的话,就不能了,必须要先开通网络权限后,APP才能发请求出去,如何开通网络权限呢?看下面介绍即可,比较简单

在module.json5文件后面加入下面代码即可开通网络权限

json 复制代码
//权限放行
"requestPermissions": [
    {
        "name": "ohos.permission.INTERNET", //网络权限
    },
]

3. 优化

现在可以展示新闻列表了,但是只有标题和配图,没有新闻详情,所以我们需要加一个新闻详情页面,点击某一个新闻之后,跳转到新闻详情页面

通过官网API接口字段解释,我们得知,返回数据字段中有一个url字段,这个字段就是新闻详情的访问地址了,我们只需要在新的页面中访问这个url地址就可以了

1. 新闻详情页面

首先我们新建一个页面,叫做:NewsDetails.ets,代码如下:

js 复制代码
import { router } from '@kit.ArkUI';
import { webview } from '@kit.ArkWeb';

/**
 * 新闻详情页面
 */
@Entry
@Component
struct NewsDetails {
  //web控制器
  controllerWeb: webview.WebviewController = new webview.WebviewController();
  //接收上一个页面传来的参数 url 的值(网址)
  @State url: string = (router.getParams() as Record<string, string>)['url'];

  build() {
    Column() {
      Row({ space: 3 }) {
        Text('返回')
          .onClick(() => {
            router.back();//返回上一页
          })
      }
      .padding(10)
      .width('100%')

      /**
       * Web组件,就是用来显示一个网址的
       */
      Web({ controller: this.controllerWeb, src: this.url })
        .id(String(new Date().getTime()))
        .domStorageAccess(true)
    }
  }
}

2. 新闻列表页面优化

新闻列表页面,我们需要给每一个新闻加上一个点击事件,这样用户点击某一个新闻就可以额跳转到上面写的新闻详情页面了

改动如下:

添加代码如下:

js 复制代码
.onClick(() => {
    //跳转到新闻详情页面
    router.pushUrl({
        url: 'pages/NewsDetails',
        params: {
            url: item.url,
        },
    }, router.RouterMode.Single)
})

注意:router需要引入依赖包:import { router } from '@kit.ArkUI';

3. 测试

现在我们随便点击某一个新闻,就可以跳转到新闻相亲页面了,如下图所示:(注意:此时需要用模拟器来访问,因为使用到了web组件,预览器是没有效果的

4. 小作业

以上代码只能查看某一页的数据,不能查看第二页或者后面其他页的数据

请自由编写代码,实现以下功能:可以下一页,或者上一页(其实很简单,加油💪,你可以的...)

相关推荐
奋斗的小青年!!4 小时前
Flutter跨平台开发适配OpenHarmony:文件系统操作深度实践
flutter·harmonyos·鸿蒙
奋斗的小青年!!5 小时前
Flutter跨平台开发OpenHarmony应用:个人中心实现
开发语言·前端·flutter·harmonyos·鸿蒙
小学生波波6 小时前
HarmonyOS6 - 运动健身三环展示图页面案例
arkts·鸿蒙·鸿蒙开发·harmonyos6
奋斗的小青年!!8 小时前
OpenHarmony Flutter 穿梭框组件深度实践与优化
flutter·harmonyos·鸿蒙
奋斗的小青年!!11 小时前
Flutter开发OpenHarmony打卡进度环组件:实现与跨平台兼容性实践
flutter·harmonyos·鸿蒙
奋斗的小青年!!12 小时前
Flutter跨平台开发鸿蒙应用:表情选择器组件的深度实践
flutter·harmonyos·鸿蒙
世人万千丶12 小时前
鸿蒙跨端框架Flutter学习day 1、变量与基本类型-智能家居监控模型
学习·flutter·ui·智能家居·harmonyos·鸿蒙·鸿蒙系统
世人万千丶12 小时前
鸿蒙跨端框架Flutter学习day 1、变量与基本类型-咖啡店点餐逻辑
学习·flutter·ui·交互·鸿蒙·鸿蒙系统
小雨下雨的雨13 小时前
鸿蒙 PC 应用开发:初始化工程与原生程序构建
华为·交互·harmonyos·鸿蒙系统