课程地址: 黑马程序员HarmonyOS4+NEXT星河版入门到企业级实战教程,一套精通鸿蒙应用开发
(本篇笔记对应课程第 28 节)
P28《27.网络连接-Http请求数据》
案例:
这里不懂后端假设服务器的前端小伙伴就需要课程源码资料了,在课程说明里有获取课程资料的方法,我按照说明获取了如下链接:
黑马《HarmonyOS4.0开发应用从入门到实战》
在线学习:
https://www.bilibili.com/video/BV1Sa4y1Z7B1/
网盘链接:
https://pan.baidu.com/s/1EKJct0IoPRHQXboKrG4TCw?pwd=9988
提取码:9988
成功提取了课程相关资料~
新建一个页面路由;执行相关命令启动服务器:
ShopPage.ets 文件中的代码如下:
商店列表数组的数据类型是 ShopInfo 类型,由于这是与页面视图渲染相关的数据,该类型定义在 viewmodel 目录下的一个 ts 文件中:
页面渲染部分即build函数中的代码如下:
子组件 ShopItem.ets 中的代码如下:
其中,Text 的 ellipsisTextOverFlow 属性已不存在,查看文档,发现已替换为了以下属性:
新建 model 文件夹,其中的文件用于处理数据,在其中新建 ShopModel.ts 文件,在这个文件中获取商铺列表数据:
由于 request 方法返回的是一个 Promise ,通过这个请求获取到的 ShopInfo类型的数据是异步的,所以需要将 getShopList 方法的返回值写成 Promise 类型(Line 8),同时将方法中的http请求过程封装成返回一个Promise:
使用这个调用接口的方法:
下滑加载更多效果:添加 onReachEnd 函数,由于下滑触底时有回弹动画效果,会导致该触底函数被触发两次,因此增加一个 isLoading 变量来进行控制(实践中发现该问题好像不存在了):
此时再次观察上滑效果, “触底” 会触发 2 次,但加载数据只触发 1次,正是我们想要的效果。
增加翻页与查询效果:
此时发现问题,上滑查询下一页数据后,将之前的数据替换掉了,这是因为我们直接将新查询到的数据赋值给了 this.shops,应该改为将新查询到的数据追加到 this.shops 中;同时在查询成功后将 isLoading 的值改回false, 不然只会查询一次。
没有新的数据后,触底时仍然会请求数据,不合理。增加 isMore 变量控制,初始为true,没有更多数据后不再请求。
实践:
按照视频中的页面效果与代码,写好静态页面。过程中遇到如下报错:
猜测是图片问题,但具体什么问题,不知晓。只好问度娘咯。
找到解析同款报错的这篇文章:DevEco Studio 报错only contain [a-zA-z0-9_].
将图片名字改为 1_1 后报错消失。
鸿蒙中这个数组[ r ( ′ a p p . m e d i a . 1 1 ′ ) , r('app.media.1_1'), r(′app.media.11′),r(‘app.media.1_2’)]怎么定义类型?(不太懂,待解决)
暂时 ignore 了
// ShopPage.ets
import { Header } from '../components/CommonComponents'
import ShopInfo from '../viewmodel/ShopInfo'
import ShopItem from '../views/ShopItem'
@Entry
@Component
struct ShopPage {
@State shops: ShopInfo[] = []
aboutToAppear(){
// 加载商品数据
this.loadShopInfo()
}
build() {
Column({space:10}) {
Header({title:'商铺列表'})
List({space:10}){
ForEach(this.shops, shop=>{
ListItem(){
ShopItem({shop:shop})
}
})
}
.padding({left:14,right:14})
}
.width('100%')
.height('100%')
.backgroundColor('#efefef')
}
loadShopInfo(){
// TODO 加载数据
let shops = [
{
id:1,
name:'新白鹿餐厅(运河上街店)',
images:[
$r('app.media.1_1'),
$r('app.media.1_2')
],
area:'运河上街',
address:'台州路运河上街购物中心F5',
avgPrice:61,
comments:8045,
score:47,
openHours:'10:39-21:00'
},
{
id:2,
name:'新白鹿餐厅(运河上街店)',
images:[
$r('app.media.1_1'),
$r('app.media.1_2')
],
area:'运河上街',
address:'台州路运河上街购物中心F5',
avgPrice:61,
comments:8045,
score:47,
openHours:'10:39-21:00'
}
]
// 给图片加上服务器地址前缀
/*shops.forEach(s => {
s.images.forEach((src,i) => {
s.images[i] = 'http://localhost:3000' + src
})
})*/
this.shops = shops
}
}
// ShopItem.ets
import ShopInfo from '../viewmodel/ShopInfo'
@Component
export default struct ShopItem {
shop:ShopInfo
build() {
Column({space:5}){
Row(){
Text(this.shop.name)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.textOverflow({overflow: TextOverflow.Ellipsis})
}
.width('100%')
Row(){
Text(this.shop.address)
.fontColor('a3a3a3')
.textOverflow({overflow: TextOverflow.Ellipsis})
}
.width('100%')
Row(){
Text('★★★★★')
.fontColor(Color.Orange)
Text((this.shop.score/10).toString())
.fontColor(Color.Orange)
.margin({right:10})
Text(`${this.shop.comments}条`)
Blank()
Text(`¥${this.shop.avgPrice}/人`)
}
.width('100%')
Row({space:10}){
ForEach(this.shop.images,img =>{
Image(img)
.width('50%')
.height(100)
})
}
.width('100%')
}
.backgroundColor('#fff')
.borderRadius(10)
.padding(12)
}
}
// ShopModel.ts
import http from '@ohos.net.http'
import ShopInfo from '../viewmodel/ShopInfo'
class ShopModel{
baseURL:string = 'localhost:3000'
pageNo:number = 1
getShopList(): Promise<ShopInfo[]>{
return new Promise((resolve,reject) => {
// 1、创建http的请求对象
let httpRequest = http.createHttp()
// 2、发送请求
httpRequest.request(
`${this.baseURL}/shops?pageNo=${this.pageNo}&pageSize=3`,
{
method:http.RequestMethod.GET
}
)
.then(resp => {
if(resp.responseCode === 200){
// 查询成功
console.log('查询商铺成功!',resp.result)
resolve(JSON.parse(resp.result.toString()))
}else{
console.log('查询商铺信息失败!error:',JSON.stringify(resp))
reject('查询商铺失败')
}
})
.catch(error => {
console.log('查询商铺信息失败!error:',JSON.stringify(error))
})
})
}
}
const shopModel = new ShopModel
export default shopModel as ShopModel
// ShopInfo.ts
export default class ShopInfo {
id:number
name:string
// @ts-ignore
images:Resource[]
area:string
address:string
avgPrice:number
comments:number
score:number
openHours:string
}
将 下载到的课程资料中相应项目中的 shopServer 文件夹整体复制到自己的项目中:
在浏览器中输入接口访问地址,测试服务器:
将获取数据的代码修改为调用接口从服务器获取:
// ShopPage.ets
import { Header } from '../components/CommonComponents'
import ShopInfo from '../viewmodel/ShopInfo'
import ShopItem from '../views/ShopItem'
import ShopModel from '../model/ShopModel'
@Entry
@Component
struct ShopPage {
@State shops: ShopInfo[] = []
isLoading:boolean = false
isMore:boolean = true
aboutToAppear(){
// 加载商品数据
ShopModel.pageNo = 1
this.loadShopInfo()
}
build() {
Column({space:10}) {
Header({title:'商铺列表'})
List({space:10}){
ForEach(this.shops, shop=>{
ListItem(){
ShopItem({shop:shop})
}
})
}
.padding({left:14,right:14})
.layoutWeight(1)
.onReachEnd(()=>{
console.log('触底了!')
if(!this.isLoading && this.isMore){
this.isLoading = true
ShopModel.pageNo++
this.loadShopInfo()
}
})
}
.width('100%')
.height('100%')
.backgroundColor('#efefef')
}
loadShopInfo(){
// TODO 加载数据
/*let shops = [
{
id:1,
name:'新白鹿餐厅(运河上街店)',
images:[
$r('app.media.1_1'),
$r('app.media.1_2')
],
area:'运河上街',
address:'台州路运河上街购物中心F5',
avgPrice:61,
comments:8045,
score:47,
openHours:'10:39-21:00'
},
{
id:2,
name:'新白鹿餐厅(运河上街店)',
images:[
$r('app.media.1_1'),
$r('app.media.1_2')
],
area:'运河上街',
address:'台州路运河上街购物中心F5',
avgPrice:61,
comments:8045,
score:47,
openHours:'10:39-21:00'
}
]*/
// 加载数据
ShopModel.getShopList()
.then(shops => {
// 给图片加上服务器地址前缀
shops.forEach(s => {
s.images.forEach((src,i) => {
s.images[i] = 'http://localhost:3000' + src
})
})
this.shops = this.shops.concat(shops)
this.isLoading = false
if(!shops || shops.length === 0){
this.isMore = false
}
})
// 给图片加上服务器地址前缀
/*shops.forEach(s => {
s.images.forEach((src,i) => {
s.images[i] = 'http://localhost:3000' + src
})
})*/
// this.shops = shops
}
}