当前位置: 首页 > article >正文

【鸿蒙】HarmonyOS NEXT星河入门到实战8-自定义组件-组件通信

目录

1、模块化语法

1.1 模块化基本认知

1.2 默认导出和导入

1.2.1 在ets下新建tools目录

1.2.2 在tools下新建moduls.ets文件

1.2.3 index.ets

1.3 按需导出和导入

1.4 全部导入

2、自定义组件 -基础

2.1 自定义组件 - 基本使用

 2.2 自定义组件 -通用样式

2.2.1 ets下新建components文件夹

2.2.2 components新建HelloCom.ets

2.2.3 Index.ets

2.3 自定义组件 -成员函数变量

3、@BuilderParam

3.1 @BuilderParam 传递UI

3.2  多个 @BuilderParam 参数

4、 状态管理

4.1 状态管理概述

4.2 @ State 自己的状态

4.3 @Prop -父子单向

4.4 掘金评论案例

4.4.1 头部组件

4.4.1.1 在ets/components下新建InfoTop.ets

4.4.1.2 Index.ets

4.4.2 评论案例 - List列表组件的使用

4.4.2.1 List列表基本使用

 4.4.2.2 中间评论列表

4.4.2.2.1 在components下新增InfoItem.ets

4.4.2.2.2 Index .ets

4.4.3 底部评价

4.4.3.1 IconFont的使用

4.4.3.1.1 ets\fonts下存放下载的文件

4.4.3.1.2 注册字体&使用字体

4.4.3.2  在components下新增InfoBottom.ets

4.4.3.3  Index .ets

4.4.4 列表渲染

4.4.4.1 准备数据

4.4.4.2 列表渲染完成

4.4.4.3 点赞

4.4.4.4 添加评论

4.4.4.5 评论排序

4.5 @Link双向同步 

 4.6 @Provide \@Consume后代组件

4.7 @ Observed &@ObjectLink嵌套对象数组属性变化


前言:自定义组件-组件通信(模块化语法)、自定义组件、传递UI、状态管理

1、模块化语法

1.1 模块化基本认知

1.2 默认导出和导入

1.2.1 在ets下新建tools目录

 

1.2.2 在tools下新建moduls.ets文件

interface  Person {
  name: string
  age: number
}

let num: number = 10
let person: Person ={
  name: '春天的菠菜',
  age: 18
}
// 默认导出(导出一个值)
export  default num

1.2.3 index.ets

导入进来  import 导入进来新取的名称  from 路径(相对路径)

import window from '@ohos.window';
// 相对路径
import myNum from '../tools/moduls'

@Extend(Text)
function textExtend(){
  .fontSize(20)
  .fontWeight(700)
  .backgroundColor(Color.Green)
  .padding(10)
  .margin(5)
}



@Entry
@Component
struct Index {
  @State message: string = '@春天的菠菜';

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }

  build() {
   Column(){
     Text('来自tools/modlus 模块的值:'+ myNum)
       .textExtend()
   }
   .width('100%').height(100)
  }
}

1.3 按需导出和导入

 tools下新建/module3.ets

// 按需导出
// 多个特性, 逐个 export 按需导出
// export let name1: string = '刘备'
// export let price: number = 9.98
// export let sayHi = () => {
//   console.log('打招呼')
// }

let name1: string = '刘备'
let name2: string = '张飞'
let name3: string = '关羽'

let price: number = 9.98
let price2: number = 10.1

let sayHi = () => {
  console.log('打招呼')
}
let run = () => {
  console.log('跑步')
}

// 一次性将多个特性, 进行导出
export {
  name1, name2, name3,
  price, price2,
  sayHi, run
}

import window from '@ohos.window';
// 相对路径
import myNum from '../tools/moduls'
import { name1, price, sayHi }from '../tools/module3'

sayHi()

@Extend(Text)
function textExtend(){
  .fontSize(20)
  .fontWeight(700)
  .backgroundColor(Color.Green)
  .padding(10)
  .margin(5)
}



@Entry
@Component
struct Index {
  @State message: string = '@春天的菠菜';

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }

  build() {
   Column(){
     Text('来自tools/modlus 模块的值:'+ myNum)
       .textExtend().margin(5)
     Text('来自tools/module3 模块的值:'+ name1 +'**' + price )
       .textExtend()
   }
   .width('100%').height(100)
  }
}

1.4 全部导入

2、自定义组件 -基础

2.1 自定义组件 - 基本使用

1、无@Entry 2、 struct 后面自定义名称

import window from '@ohos.window';


@Extend(Text)
function textExtend(){
  .fontSize(20)
  .fontWeight(700)
  .backgroundColor(Color.Green)
  .padding(10)
  .margin(5)
}



@Entry
@Component
struct Index {
  @State message: string = '@春天的菠菜';

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }

  build() {
    Column() {
      MyHeader()
      MyMain()
      MyFooter()
    }
  }
}


@Component
struct MyCom {
  @State count: number = 1
  build() {
    Row() {
      Text(this.count.toString())
        .fontColor(Color.White)
        .margin(10)
      Button('按钮')
        .onClick(() => {
          this.count++
        })
    }
  }
}

@Component
struct MyHeader {
  build() {
    Row() {
      Text('我是头部')
        .fontColor(Color.White)
    }
    .width('100%')
    .height(50)
    .backgroundColor(Color.Brown)
  }
}

@Component
struct MyMain {
  build() {
    Column() {
      // 将相同的业务逻辑, 封装成一个通用的组件
      MyCom()
      MyCom()
      MyCom()
    }
    .layoutWeight(1)
    .width('100%')
    .backgroundColor(Color.Gray)
  }
}

@Component
struct MyFooter {
  build() {
    Row() {
      Text('我是底部')
    }
    .width('100%')
    .height(50)
    .backgroundColor(Color.Green)
  }
}

 2.2 自定义组件 -通用样式

2.2.1 ets下新建components文件夹

2.2.2 components新建HelloCom.ets

@Preview
@Component
export struct HelloCom {
  build() {
    Row() {
      Text('自定义组件')
      Button('按钮')
    }
    .width(200)
    .height(50)
    .backgroundColor(Color.Orange)
  }
}

2.2.3 Index.ets

import window from '@ohos.window';
import { HelloCom } from '../components/HelloCom'

@Extend(Text)
function textExtend(){
  .fontSize(20)
  .fontWeight(700)
  .backgroundColor(Color.Green)
  .padding(10)
  .margin(5)
}



@Entry
@Component
struct Index {
  @State message: string = '@春天的菠菜';

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }

  build() {
    Column() {
      HelloCom()
        .width(250)
        .height(60)
        .backgroundColor(Color.Gray)
        .onClick(() => {
          AlertDialog.show({
            message: '测试点击'
          })
        })
    }
  }
}

2.3 自定义组件 -成员函数变量

带=符号的可以外部传参

import window from '@ohos.window';




@Entry
@Component
struct Index {
  @State message: string = '@春天的菠菜';

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }

  build() {
    Column() {
      MyPanel({
        title: '我的订单',
        extra: '全部订单',
        getMore() {
          AlertDialog.show({
            message: '点击了全部订单'
          })
        }
      })
      MyPanel({
           title: '小米有品众筹',
        extra: '七款众筹中',
        getMore() {
          AlertDialog.show({
            message: '点击了众筹'
          })
        }
  }
      )

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

@Component
struct MyPanel {
  // 成员变量 -数据
  title: string = '默认的大标题'
  extra: string = '查看更多'

  // 成员变量 -函数 可以传入覆盖
  getMore = () =>{
    AlertDialog.show({
      message: '查看更多'
    })

  }

  // 成员函数  --  不可以外部传入覆盖
  sayHi (){
    AlertDialog.show({
      message: '打招呼 你好'
    })
}

  build() {
    Column(){
      Row(){
        Text(this.title)
          .fontSize(18)
        Text(this.extra)
          .fontSize(18)
          .onClick( () => {
            this.getMore()
          })

      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)

      Row(){
        Text('内容部分')
          .fontSize(18)
        Button('按钮')
          .onClick(() => {
            this.sayHi()
          })
      }
      .padding(20)

    }
    .width('100%').height(200)
    .margin({bottom: 20})
    .borderRadius(10)
    .padding(10)
    .backgroundColor(Color.White)
    
  }
}

3、@BuilderParam

3.1 @BuilderParam 传递UI

 

import window from '@ohos.window';



@Component
struct  SonCom {
  // 1、 定义构建函数
  @BuilderParam ContentBuilder: () => void = this.defaultBuilder
  @Builder
  defaultBuilder () {
    Text('默认的内容')
  }
  build() {
  //   2、 使用构建函数,构建结构
    Column(){
      this.ContentBuilder()
    }



  }
}

@Entry
@Component
struct Index {
  @State message: string = '@春天的菠菜';

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }

  build() {
    Column() {
      SonCom() {
        Button('传入的结构')
      }
    }


  }
}

 

练习

import window from '@ohos.window';



@Entry
@Component
struct Index {
  @State message: string = '@春天的菠菜';

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }

  build() {
    Column() {
      MyPanel({
        title: '我的订单',
        extra: '全部订单',
        getMore() {
          AlertDialog.show({
            message: '点击了全部订单'
          })
        }
      }) {
        Text('我是订单相关的文本')
      }
      MyPanel({
        title: '小米有品众筹',
        extra: '七款众筹中',
        getMore() {
          AlertDialog.show({
            message: '点击了众筹'
          })
        }
      }) {
        Button('我是小米众筹的按钮')
      }

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

@Component
struct MyPanel {
  // 成员变量 -数据
  title: string = '默认的大标题'
  extra: string = '查看更多'

  // 成员变量 -函数 可以传入覆盖
  getMore = () =>{
    AlertDialog.show({
      message: '查看更多'
    })

  }

  // 成员函数  --  不可以外部传入覆盖
  sayHi (){
    AlertDialog.show({
      message: '打招呼 你好'
    })
  }
  // 1、 定义构建函数
  @BuilderParam ContentBuilder: () => void = this.defaultBuilder
  @Builder
  defaultBuilder () {
    Text('默认的内容')
  }
  build() {
    Column(){
      Row(){
        Text(this.title)
          .fontSize(18)
        Text(this.extra)
          .fontSize(18)
          .onClick( () => {
            this.getMore()
          })

      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)

      Row(){
      //   这里结构不能写死,需要通过BUilderParam来进行构建
        this.ContentBuilder()
      }
      .padding(20)

    }
    .width('100%').height(200)
    .margin({bottom: 20})
    .borderRadius(10)
    .padding(10)
    .backgroundColor(Color.White)

  }
}

3.2  多个 @BuilderParam 参数

import window from '@ohos.window';

@Component
struct MyCard {
  @BuilderParam tBuilder: () => void = this.tDefaultBuilder
  @BuilderParam cBuilder: () => void = this.cDefaultBuilder
  @Builder tDefaultBuilder () {
    Text('我是默认的大标题')
  }
  @Builder cDefaultBuilder () {
    Text('我是默认的内容')
  }

  build() {
    // 卡片组件
    Column() {
      // 标题部分
      Row() {
        this.tBuilder()
      }
      .height(30)
      .width('100%')
      .border({ color: '#ccc', width: { bottom: 1 }})
      .padding({ left: 10 })
      // 内容部分
      Row() {
        this.cBuilder()
      }
      .width('100%')
      .padding(10)
    }
    .width('100%')
    .height(100)
    .backgroundColor(Color.White)
    .borderRadius(10)
    .justifyContent(FlexAlign.Start)
  }
}

@Entry
@Component
struct Index {
  @State message: string = '@春天的菠菜';

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }
  @Builder ftBuilder () {
    Text('我是传入的大标题结构')
  }
  @Builder fcBuilder () {
    Text('我是内容部分')
      .margin(5)
    Text('我是内容部分')
      .margin(5)
    Text('我是内容部分')
      .margin(5)
  }

  build() {
    Column({ space: 10 }) {
      MyCard()
      MyCard({
        tBuilder: this.ftBuilder,
        cBuilder: this.fcBuilder
      })
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#ccc')
  }
}

4、 状态管理

4.1 状态管理概述

4.2 @ State 自己的状态

import window from '@ohos.window';

interface Car {
  name: string
}
interface Person {
  name: string
  car: Car
}

const obj: Person = {
  name: 'zs',
  car: {
    name: '小黄车'
  }
}
console.log('查看第一层属性', Object.keys(obj))

@Entry
@Component
struct Index {

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }
  // 状态变量
  // 1. string number boolean 可以直接监视到变化
  @State message: string = 'hello world'
  // 2. 复杂类型 object class, 第一层随便改, 嵌套需要进行整个嵌套对象的替换
  @State person: Person = {
    name: 'jack',
    car: {
      name: '宝马车'
    }
  }
  build() {
    Column() {
      Text(this.message).fontSize(20)
      Button('改message').onClick(() => {
        this.message = '你好'
      })
      Text(JSON.stringify(this.person))
      Button('改person').onClick(() => {
        // this.person = {
        //   name: 'amy',
        //   car: {
        //     name: '保时捷'
        //   }
        // }

        // this.person.name = 'tony'

        // 如果不是对象的第一层属性, 修改时, 需要修改整个嵌套的对象
        // this.person.car.name = '小火车'

        // console.log('car name', this.person.car.name)
        this.person.car = {
          name: '老爷车'
        }
      })
    }
  }
}

 

4.3 @Prop -父子单向

import window from '@ohos.window';

@Component
struct SonCom {
  // 保证父组件的数据变化了, 能够往下响应式的更新
  @Prop sCar: string = ''
  changeCar = (newCar: string) => {}

  build() {
    Column() {
      Text(`子组件 ${this.sCar}`)
      Button('换车').onClick((event: ClickEvent) => {
        // 1. prop传值 → 单向传递
        // 子组件, 可以修改到 prop 传值, 但是修改的更新不会同步到父组件
        // 通常不太会直接修改 prop 传值, 父组件的状态一旦变化, 会自动向下同步
        // 修改就被覆盖了
        // this.sCar = '小黄车'

        // 2. 如果实在想更新, 希望保证父子同步 => 调用父组件传递过来的方法
        // 如果没有写箭头函数, 意味着, this 指向 调用者, 而此处执行环境 this → 子组件
        this.changeCar('蹦蹦车')
      })
    }
    .padding(20)
    .backgroundColor(Color.Orange)
  }
}


@Entry
@Component
struct Index {

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }
  @State fCar:string = '劳斯莱斯'
  build() {
    Column() {
      Text(`父组件 - ${this.fCar}`)
      Button('换车').onClick(() => {
        this.fCar = '三轮车'
      })

      SonCom({
        sCar: this.fCar,
        // 这里必须要用箭头函数, 否则会有 this 指向的问题
        // 使用箭头函数的好处, 可以使用外部环境的 this, 不受传递过去后的执行环境影响
        // 希望此处 this 指向 父组件
        changeCar: (newCar: string) => {
          this.fCar = newCar
        }
      })
    }
    .padding(50)
    .backgroundColor(Color.Pink)
  }
}

练习

import window from '@ohos.window';

@Component
struct NumberCount {
  @Prop num: number = 0
  SubFn = () => {}
  AddFn = () => {}
  build() {
    Row({space: 5}) {
      Button('-')
        .onClick( () => {
            this.SubFn()
        })
      Text(this.num.toString())
      Button('+')
        .onClick( () => {
            this.AddFn()
        })

    }
  }
}

@Entry
@Component
struct Index {

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }
  @State num1: number = 5
  @State num2: number = 3

  build() {
    Column({space: 5}){
      Row({space: 5}){
        Text('茄子')
        NumberCount({
          num: this.num1,
          AddFn: () => this.num1++,
          SubFn: () => this.num1--
        })

      }
      Row({space: 5}){
        Text('香蕉')
        NumberCount({
          num: this.num2,
          AddFn: () => this.num2++,
          SubFn: () => this.num2--
        })

      }

    }
    .padding(10)
  }
}

4.4 掘金评论案例

4.4.1 头部组件

4.4.1.1 在ets/components下新建InfoTop.ets
@Extend(Button)
function fancyButton (isOn: boolean) {
  .width(46)
  .height(32)
  .fontSize(12)
  .padding({ left: 5, right: 5 })
  .backgroundColor(isOn ? '#fff' : '#F7F8FA')
  .fontColor(isOn ? '#2f2e33' : '#8e9298')
  .border({ width: 1, color: isOn ? '#e4e5e6' : '#F7F8FA' })
}

@Component
struct InfoTop {
  @State isOn: boolean = true
  build() {
    Row(){
      Text('全部评论')
        .fontColor('#222')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      Row(){
        Button('最新',{ stateEffect: false})
          .fancyButton(this.isOn)
          .onClick( () => this.isOn = true)
        Button('最热',{ stateEffect: false})
          .fancyButton(!this.isOn)
          .onClick( () => this.isOn = false)
      }
      .backgroundColor('F7F8FA')
      .borderRadius(20)

    }
    .justifyContent(FlexAlign.SpaceBetween)
    .width('100%').height(60)
    .padding(16)


  }
}

export default InfoTop
4.4.1.2 Index.ets
import window from '@ohos.window';
import InfoTop from '../components/InfoTop'


@Entry
@Component
struct Index {

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }


  build() {
    Column(){
    //   头部
      InfoTop()


      //   中间
      Column(){

      }
      .width('100%')
      .backgroundColor(Color.Orange)
      .layoutWeight(1)
      //   底部
      Row(){

      }
      .width('100%').height(60)
      .backgroundColor(Color.Pink)


    }
    .width('100%').height('100%')
  }
}

4.4.2 评论案例 - List列表组件的使用

4.4.2.1 List列表基本使用

import window from '@ohos.window';
import InfoTop from '../components/InfoTop'


@Entry
@Component
struct Index {

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }


  build() {
    Column(){
    //   头部
      InfoTop()
      //   中间
      List(){
        ForEach(Array.from({ length: 20}), () => {
          ListItem(){
            Row().width('100%').height(100).backgroundColor(Color.Brown)
          }
          .padding(10)
        })
      

      }
      .width('100%')
      .backgroundColor(Color.Orange)
      .layoutWeight(1)
      .listDirection(Axis.Vertical) // 调整主轴方向 默认 Vertical  .listDirection(Axis.Horizontal)
      .lanes(2,5) //调整列和间距
      .alignListItem(ListItemAlign.Center) //控制列的对齐方式
      .scrollBar(BarState.Auto) //Auto 按需自动显示滚动条
      // .divider({
      //   strokeWidth: 3,  //线宽
      //   color:  Color.Blue, //颜色
      //   startMargin: 10,  //左边线距离边缘的间隙
      //   endMargin: 10   //右边线距离边缘的间隙
      // })

      //   底部
      Row(){

      }
      .width('100%').height(60)
      .backgroundColor(Color.Pink)


    }
    .width('100%').height('100%')
  }
}

 4.4.2.2 中间评论列表
4.4.2.2.1 在components下新增InfoItem.ets

@Component
struct InfoItem {

  build() {
    Column() {
      // 1. 头像, 昵称, 等级
      Row() {
        Image($r('app.media.startIcon'))
          .width(30).aspectRatio(1)
          .margin({ top: 10 })
          .borderRadius(15)
        Text('黑暗王国')
          .fontSize(13)
          .fontColor(Color.Gray)
          .margin({ top: 10, left: 8 })
        Image($r('app.media.level_6'))
          .width(20).aspectRatio(1)
          .margin({ top: 10, left: 8 })
          .borderRadius(15)
      }
      // 2. 评论内容
      Text('安抚巾阿范德萨见附件')
        .fontSize(13)
        .fontColor(Color.Black)
        .margin({ left: 40, top: 0, bottom: 8 })

      // 3. 评论日期 和 点赞互动
      Row() {
        Text('3天前')
          .fontSize(11)
          .fontColor(Color.Gray)
        Row() {
          Image($r('app.media.like_select'))
            .width(15).aspectRatio(1)
          Text('99')
            .fontSize(11)
            .fontColor(Color.Gray)
        }

      }
      .padding({ left: 40, top: 5 })
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
    }
    .padding({ left: 15, right: 15 })
    .alignItems(HorizontalAlign.Start)
  }
}

export default InfoItem
4.4.2.2.2 Index .ets
import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'


@Entry
@Component
struct Index {

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }


  build() {
    Column(){
    //   头部
      InfoTop()
      //   中间
    List(){

        ForEach(Array.from({ length: 20}), () => {
          ListItem(){
            // 列表项组件
            InfoItem()
          }
        })

    }
    .layoutWeight(1)
      .padding({bottom: 10})
      //   底部
      Row(){

      }
      .width('100%').height(60)
      .backgroundColor(Color.Pink)


    }
    .width('100%').height('100%')
  }
}

4.4.3 底部评价

4.4.3.1 IconFont的使用

4.4.3.1.1 ets\fonts下存放下载的文件

略 

 iconfont-阿里巴巴矢量图标库

4.4.3.1.2 注册字体&使用字体
import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'
import font from '@ohos.font';

@Entry
@Component
struct Index {
  // 一加载Index入口页面, 就进行注册
  // aboutToAppear → 会在组件一加载时, 自动调用执行(生命周期函数)
  aboutToAppear(): void {
    // 1. 注册字体
    font.registerFont({
      familyName: 'myfont',
      familySrc: '/fonts/iconfont.ttf'
    })


  }

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }


  build() {
    Column(){
      // 2. 使用字体测试
      Text('\ue600')
        .fontFamily('myfont')
        .fontSize(30)
        .fontColor(Color.Red)

      Text('\ue840')
        .fontFamily('myfont')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
        .fontColor(Color.Red)
    //   头部
      InfoTop()
      //   中间
    List(){

        ForEach(Array.from({ length: 20}), () => {
          ListItem(){
            // 列表项组件
            InfoItem()
          }
        })

    }
    .layoutWeight(1)
      .padding({bottom: 10})
      //   底部
      Row(){

      }
      .width('100%').height(60)
      .backgroundColor(Color.Pink)


    }
    .width('100%').height('100%')
  }
}

4.4.3.2  在components下新增InfoBottom.ets
@Component
struct InfoBottom {

  build() {
    Row() {
      Row() {
        Text('\ue840')
          .fontFamily('myfont')
          .fontSize(18)
          .margin({ left: 20 })
        TextInput({
          placeholder: '写评论...'
        })
          .backgroundColor(Color.Transparent)

      }
      .height(40)
      .backgroundColor('#f5f6f5')
      .borderRadius(20)
      .margin({ left: 15, right: 20, top: 10, bottom: 10 })
      .layoutWeight(1)

      Text('\ue600')
        .fontFamily('myfont')
        .fontSize(26)
        .margin({ left: 6, right: 6 })

      Text('\ue61d')
        .fontFamily('myfont')
        .fontSize(26)
        .margin({ left: 6, right: 6 })
    }
    .height(60)
    .width('100%')
  }
}

export default InfoBottom
4.4.3.3  Index .ets
import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'
import font from '@ohos.font';
import InfoBottom from '../components/InfoBottom';

@Entry
@Component
struct Index {
  // 一加载Index入口页面, 就进行注册
  // aboutToAppear → 会在组件一加载时, 自动调用执行(生命周期函数)
  aboutToAppear(): void {
    // 1. 注册字体
    font.registerFont({
      familyName: 'myfont',
      familySrc: '/fonts/iconfont.ttf'
    })


  }

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }


  build() {
    Column(){
      // 2. 使用字体测试
      // Text('\ue600')
      //   .fontFamily('myfont')
      //   .fontSize(30)
      //   .fontColor(Color.Red)
      //
      // Text('\ue840')
      //   .fontFamily('myfont')
      //   .fontSize(30)
      //   .fontWeight(FontWeight.Bold)
      //   .fontColor(Color.Red)
    //   头部
      InfoTop()
      //   中间
    List(){

        ForEach(Array.from({ length: 20}), () => {
          ListItem(){
            // 列表项组件
            InfoItem()
          }
        })

    }
    .layoutWeight(1)
      .padding({bottom: 10})
      //   底部
      InfoBottom()


    }
    .width('100%').height('100%')
  }
}

4.4.4 列表渲染

4.4.4.1 准备数据

ets\model\CommentData.ets

// 准备评论的数据类
export class CommentData {
  avatar: string; // 头像
  name: string; // 昵称
  level: number; //用户等级
  likeNum: number; //点赞数量
  commentTxt: string; //评论内容
  isLike: boolean; //是否喜欢
  levelIcon: Resource // level等级
  timeString: string // 发布时间-基于时间戳处理后,展示给用户看的属性(2天前)
  time: number // 时间戳-数字格式的日期

  constructor(avatar: string, name: string, time: number, level: number, lickNum: number, commentTxt: string, isLike: boolean, ) {
    this.avatar = avatar
    this.name = name
    this.timeString = this.convertTime(time) // 拿到的时间格式, 通常是时间戳的格式  1645820201123
    this.time = time
    this.level = level
    this.likeNum = lickNum
    this.commentTxt = commentTxt
    this.isLike = isLike
    this.levelIcon = this.convertLevel(this.level) // 基于等级数字, 转换成图片路径
  }

  convertTime(timestamp: number) {
    // 获取当前的时间戳
    const currentTimestamp = new Date().getTime();
    const timeDifference = (currentTimestamp - timestamp) / 1000; // 转换为秒

    if (timeDifference < 5 || timeDifference == 0) {
      return "刚刚";
    } else if (timeDifference < 60) {
      return `${Math.floor(timeDifference)}秒前`;
    } else if (timeDifference < 3600) {
      return `${Math.floor(timeDifference / 60)}分钟前`;
    } else if (timeDifference < 86400) {
      return `${Math.floor(timeDifference / 3600)}小时前`;
    } else if (timeDifference < 604800) {
      return `${Math.floor(timeDifference / 86400)}天前`;
    } else if (timeDifference < 2592000) {
      return `${Math.floor(timeDifference / 604800)}周前`;
    } else if (timeDifference < 31536000) {
      return `${Math.floor(timeDifference / 2592000)}个月前`;
    } else {
      return `${Math.floor(timeDifference / 31536000)}年前`;
    }
  }

  // 基于传入的level,转换成图片路径
  convertLevel(level: number) {
    const iconList = [
      $r('app.media.level_1'),
      $r('app.media.level_2'),
      $r('app.media.level_3'),
      $r('app.media.level_4'),
      $r('app.media.level_5'),
      $r('app.media.level_6'),
    ]
    return iconList[level]
  }
}

// 封装一个方法, 创建假数据
export const createListRange = (): CommentData[] => {
  let result: CommentData[] = new Array()
  result = [
    new CommentData(`https://fastly.picsum.photos/id/770/600/600.jpg?hmac=tuK9EHg1ifTU3xKAiZj2nHSdWy4mk7enhylgOc2BW7E`, "雪山飞狐", 1645820201123, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '23年一年干完的事😂😂😂真的非常仓促', false),
    new CommentData(`https://fastly.picsum.photos/id/225/600/600.jpg?hmac=v97zt_t4mxeyMttX_m09pxhCvftiTxFR1MMBZi5HQxs`, "千纸鹤", 1677356201123, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), 'Netty对象池源码分析来啦!juejin.cn欢迎点赞[奸笑]', false),
    new CommentData(`https://fastly.picsum.photos/id/122/600/600.jpg?hmac=1oA93YbjYVt96DcJcGQ5PLthzjUsdtrnBQaM0USBozI`, "烟雨江南", 1688772201123, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '有一个不听劝的Stable Diffusion出图的小伙伴,找我给她装填脑。 一个资深的IT工程师不能受这个委屈。', false),
    new CommentData(`https://fastly.picsum.photos/id/654/600/600.jpg?hmac=ewnK6Bx_MKQLJa9waZOV1xNO7--K5oSwCShtz1JDYw8`, "魔法小精灵", 1697484201123, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '有一点可以确信 前后端开发界限越来越模糊。后端可以不写 但是不能不会。', false),
    new CommentData(`https://fastly.picsum.photos/id/345/600/600.jpg?hmac=EQflzbIadAglm0RzotyKXM2itPfC49fR3QE7eW_UaPo`, "独行侠", 1704067200000, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '今天看到一个帖子,挺有意思的。', false),
    new CommentData(`https://fastly.picsum.photos/id/905/600/600.jpg?hmac=DvIKicBZ45DEZoZFwdZ62VbmaCwkK4Sv7rwYzUvwweU`, "枫叶飘零", 1706745600000, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '我想了搞钱的路子, 1:投资理财, 后来发下,不投资就是最好的理财, 2:买彩票,后来发现彩票都是人家预定好的,根本不是随机的,卒, 3:开店创业,隔行如隔山,开店失败,卒。', false),
    new CommentData(`https://fastly.picsum.photos/id/255/600/600.jpg?hmac=-lfdnAl71_eAIy1OPAupFFPh7EOJPmQRJFg-y7lRB3s`, "星空漫步", 1707523200000, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '优胜劣汰,自然选择吧,没什么好怪的。都是crud,招个大学生就能干了。', false),
    new CommentData(`https://fastly.picsum.photos/id/22/600/600.jpg?hmac=QEZq7KUHwBZCt3kGSEHMwJlZfnzCxCeBgHjYj7iQ-UY`, "剑指苍穹", 1708300800000, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '白嫖ChatGPT4的功能。然后,抱着试一试的态度把玩了一下。发现真的好用。', false),
    new CommentData(`https://fastly.picsum.photos/id/439/600/600.jpg?hmac=LC9k_bzrN0NhKRyV62fou3ix3cRFZKNfAyXgxGs6zh8`, "黑暗王国", 1708646400000, Math.floor(Math.random()*6) , Math.floor(Math.random()*100), '字数越少,事情越大。', false),
  ]
  return result
}


4.4.4.2 列表渲染完成

 InfoItem.etc

import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'
import font from '@ohos.font';
import InfoBottom from '../components/InfoBottom';
import { CommentData, createListRange } from '../model/CommentData'

import { CommentData } from '../model/CommentData'
@Component
struct InfoItem {
  @Prop itemObj: CommentData
  @Prop index: number
  onLikeClick = (index: number) => {}

  build() {
    Column() {
      // 1. 头像, 昵称, 等级
      Row() {
        Image(this.itemObj.avatar)
          .width(30).aspectRatio(1)
          .margin({ top: 10 })
          .borderRadius(15)
        Text(this.itemObj.name)
          .fontSize(13)
          .fontColor(Color.Gray)
          .margin({ top: 10, left: 8 })
        Image(this.itemObj.levelIcon)
          .width(20).aspectRatio(1)
          .margin({ top: 10, left: 8 })
          .borderRadius(15)
      }
      // 2. 评论内容
      Text(this.itemObj.commentTxt)
        .fontSize(13)
        .fontColor(Color.Black)
        .margin({ left: 40, top: 0, bottom: 8 })

      // 3. 评论日期 和 点赞互动
      Row() {
        Text(this.itemObj.timeString)
          .fontSize(11)
          .fontColor(Color.Gray)
        Row() {
          Image($r('app.media.like_unselect'))
            .width(15).aspectRatio(1)
          Text(this.itemObj.likeNum.toString())
            .fontSize(11)
            .fontColor(Color.Gray)
        }

      }
      .padding({ left: 40, top: 5 })
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
    }
    .padding({ left: 15, right: 15 })
    .alignItems(HorizontalAlign.Start)
  }
}

export default InfoItem
// 创建数据测试
// let listArr: CommentData[] = createListRange()
// console.log('数组列表', JSON.stringify(listArr))

// 从1970年01月01号 0:0:0
// 时间戳 → 特殊的日期格式(数字类型), 利于比较运算
// let nowTime: number = new Date().getTime() // 05-16
// let lastTime: number = new Date('2024-03-02').getTime()
// console.log('时间戳', nowTime, lastTime)
// console.log('时间戳', (nowTime - lastTime) / 1000 / 3600 / 24)
@Entry
@Component
struct Index {
  @State commentList: CommentData[] = createListRange()

  // 一加载Index入口页面, 就进行注册
  // aboutToAppear → 会在组件一加载时, 自动调用执行(生命周期函数)
  aboutToAppear(): void {
    // 1. 注册字体
    font.registerFont({
      familyName: 'myfont',
      familySrc: '/fonts/iconfont.ttf'
    })


  }

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }


  build() {
    Column(){
      // 2. 使用字体测试
      // Text('\ue600')
      //   .fontFamily('myfont')
      //   .fontSize(30)
      //   .fontColor(Color.Red)
      //
      // Text('\ue840')
      //   .fontFamily('myfont')
      //   .fontSize(30)
      //   .fontWeight(FontWeight.Bold)
      //   .fontColor(Color.Red)
    //   头部
      InfoTop()
      //   中间
    List(){

        ForEach(this.commentList, (item: CommentData, index: number) => {
          ListItem(){
            // 列表项组件 -- 将item对象, 通过prop传值, 传递下去
            InfoItem({
              itemObj: item
            })
          }
        })

    }
    .layoutWeight(1)
      .padding({bottom: 10})
      //   底部
      InfoBottom()


    }
    .width('100%').height('100%')
  }
}

Index.ets

import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'
import font from '@ohos.font';
import InfoBottom from '../components/InfoBottom';
import { CommentData, createListRange } from '../model/CommentData'


// 创建数据测试
// let listArr: CommentData[] = createListRange()
// console.log('数组列表', JSON.stringify(listArr))

// 从1970年01月01号 0:0:0
// 时间戳 → 特殊的日期格式(数字类型), 利于比较运算
// let nowTime: number = new Date().getTime() // 05-16
// let lastTime: number = new Date('2024-03-02').getTime()
// console.log('时间戳', nowTime, lastTime)
// console.log('时间戳', (nowTime - lastTime) / 1000 / 3600 / 24)
@Entry
@Component
struct Index {
  @State commentList: CommentData[] = createListRange()

  // 一加载Index入口页面, 就进行注册
  // aboutToAppear → 会在组件一加载时, 自动调用执行(生命周期函数)
  aboutToAppear(): void {
    // 1. 注册字体
    font.registerFont({
      familyName: 'myfont',
      familySrc: '/fonts/iconfont.ttf'
    })


  }

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }


  build() {
    Column(){
      // 2. 使用字体测试
      // Text('\ue600')
      //   .fontFamily('myfont')
      //   .fontSize(30)
      //   .fontColor(Color.Red)
      //
      // Text('\ue840')
      //   .fontFamily('myfont')
      //   .fontSize(30)
      //   .fontWeight(FontWeight.Bold)
      //   .fontColor(Color.Red)
    //   头部
      InfoTop()
      //   中间
    List(){

        ForEach(this.commentList, (item: CommentData, index: number) => {
          ListItem(){
            // 列表项组件 -- 将item对象, 通过prop传值, 传递下去
            InfoItem({
              itemObj: item
            })
          }
        })

    }
    .layoutWeight(1)
      .padding({bottom: 10})
      //   底部
      InfoBottom()


    }
    .width('100%').height('100%')
  }
}

4.4.4.3 点赞

infoItem.ets

import { CommentData } from '../model/CommentData'
@Component
struct InfoItem {
  @Prop itemObj: CommentData
  @Prop index: number
  onLikeClick = (index: number) => {}

  build() {
    Column() {
      // 1. 头像, 昵称, 等级
      Row() {
        Image(this.itemObj.avatar)
          .width(30).aspectRatio(1)
          .margin({ top: 10 })
          .borderRadius(15)
        Text(this.itemObj.name)
          .fontSize(13)
          .fontColor(Color.Gray)
          .margin({ top: 10, left: 8 })
        Image(this.itemObj.levelIcon)
          .width(20).aspectRatio(1)
          .margin({ top: 10, left: 8 })
          .borderRadius(15)
      }
      // 2. 评论内容
      Text(this.itemObj.commentTxt)
        .fontSize(13)
        .fontColor(Color.Black)
        .margin({ left: 40, top: 0, bottom: 8 })

      // 3. 评论日期 和 点赞互动
      Row() {
        Text(this.itemObj.timeString)
          .fontSize(11)
          .fontColor(Color.Gray)
        Row() {
          Image(this.itemObj.isLike ? $r('app.media.like_select') : $r('app.media.like_unselect'))
            .width(15).aspectRatio(1)
          Text(this.itemObj.likeNum.toString())
            .fontSize(11)
            .fontColor(this.itemObj.isLike ? Color.Blue : Color.Gray)
        }
        .onClick(() => {
          // 此处的this是子组件
          // 让当前项的 likeNum 变化 (老爹的数据, 需要调用老爹的方法才能改)
          this.onLikeClick(this.index)
        })

      }
      .padding({ left: 40, top: 5 })
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
    }
    .padding({ left: 15, right: 15 })
    .alignItems(HorizontalAlign.Start)
  }
}

export default InfoItem

Index.ets

import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'
import font from '@ohos.font';
import InfoBottom from '../components/InfoBottom';
import { CommentData, createListRange } from '../model/CommentData'


// 创建数据测试
// let listArr: CommentData[] = createListRange()
// console.log('数组列表', JSON.stringify(listArr))

// 从1970年01月01号 0:0:0
// 时间戳 → 特殊的日期格式(数字类型), 利于比较运算
// let nowTime: number = new Date().getTime() // 05-16
// let lastTime: number = new Date('2024-03-02').getTime()
// console.log('时间戳', nowTime, lastTime)
// console.log('时间戳', (nowTime - lastTime) / 1000 / 3600 / 24)
@Entry
@Component
struct Index {
  @State commentList: CommentData[] = createListRange()

  // 一加载Index入口页面, 就进行注册
  // aboutToAppear → 会在组件一加载时, 自动调用执行(生命周期函数)
  aboutToAppear(): void {
    // 1. 注册字体
    font.registerFont({
      familyName: 'myfont',
      familySrc: '/fonts/iconfont.ttf'
    })

  }

  // 处理喜欢
  handleLike (index: number) {
    // 1. 获取下标 或 id 加以区分
    // 子调用父的方法时, 每个子都能调用父的方法, 需要加以区分
    // 此时由于是数组操作, 可以传递下标, [快速定位到操作的是哪项]
    // 将来发请求 → id 作为区分值, 也可以
    // AlertDialog.show({
    //   message: index.toString()
    // })

    // 2. 父组件的方法, 如果抽取出来, 如果直接传递给子组件
    //    会有 this 指向问题, this 通常直接指向调用者
    //    需要, 用箭头函数函数包一层, 保证 this 还是指向父组件
    //    console.log('父组件的数据', this.commentList)

    // 3. 根据 index, 根据 数组数据, 进行判断 +1 还是 -1
    let itemData = this.commentList[index]
    if (itemData.isLike) {
      itemData.likeNum -= 1
    }
    else {
      itemData.likeNum += 1
    }
    itemData.isLike = !itemData.isLike

    // 4. 对于复杂类型: 状态对象, 状态数组, 只会对第一层数据, 进行监视变化
    // 数组.splice(从哪开始删除, 删除几个, 替换的项1, 替换的项2, ...)
    this.commentList.splice(index, 1, itemData)
  }

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }


  build() {
    Column(){
      // 2. 使用字体测试
      // Text('\ue600')
      //   .fontFamily('myfont')
      //   .fontSize(30)
      //   .fontColor(Color.Red)
      //
      // Text('\ue840')
      //   .fontFamily('myfont')
      //   .fontSize(30)
      //   .fontWeight(FontWeight.Bold)
      //   .fontColor(Color.Red)
    //   头部
      InfoTop()
      // 中间
      List() {
        ForEach(this.commentList, (item: CommentData, index: number) => {
          ListItem() {
            // 列表项组件 - 将item对象, 通过prop传值, 传递下去
            InfoItem({
              index: index,
              itemObj: item,
              onLikeClick: (index: number) => {
                // 此处的this → 访问的是外部环境的 this → 就是父组件
                this.handleLike(index)
              }
            })
          }
        })
      }
      .layoutWeight(1)
      .padding({ bottom: 10 })

      // 底部
      InfoBottom()
    }
    .width('100%')
    .height('100%')
  }
}

4.4.4.4 添加评论

infoBottom.ets

@Component
struct InfoBottom {
  @State txt: string = ''
  onSubmitComment = (content: string) => {}
  build() {
    Row() {
      Row() {
        Text('\ue840')
          .fontFamily('myfont')
          .fontSize(18)
          .margin({ left: 20 })
        TextInput({
          placeholder: '写评论...',
          text: $$this.txt
        })
          .backgroundColor(Color.Transparent)
          .fontSize(18)
          .onSubmit(() => {
            // 这里不能直接添加, 需要调用父组件传递过来的方法
            this.onSubmitComment(this.txt)
          })
      }
      .height(40)
      .backgroundColor('#f5f6f5')
      .borderRadius(20)
      .margin({ left: 15, right: 20, top: 10, bottom: 10 })
      .layoutWeight(1)

      Text('\ue600')
        .fontFamily('myfont')
        .fontSize(26)
        .margin({ left: 6, right: 6 })

      Text('\ue61d')
        .fontFamily('myfont')
        .fontSize(26)
        .margin({ left: 6, right: 6 })
    }
    .height(60)
    .width('100%')
  }
}

export default InfoBottom

Index.ets

import window from '@ohos.window';
import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'
import font from '@ohos.font';
import InfoBottom from '../components/InfoBottom';
import { CommentData, createListRange } from '../model/CommentData'


// 创建数据测试
// let listArr: CommentData[] = createListRange()
// console.log('数组列表', JSON.stringify(listArr))

// 从1970年01月01号 0:0:0
// 时间戳 → 特殊的日期格式(数字类型), 利于比较运算
// let nowTime: number = new Date().getTime() // 05-16
// let lastTime: number = new Date('2024-03-02').getTime()
// console.log('时间戳', nowTime, lastTime)
// console.log('时间戳', (nowTime - lastTime) / 1000 / 3600 / 24)
@Entry
@Component
struct Index {
  @State commentList: CommentData[] = createListRange()

  // 一加载Index入口页面, 就进行注册
  // aboutToAppear → 会在组件一加载时, 自动调用执行(生命周期函数)
  aboutToAppear(): void {
    // 1. 注册字体
    font.registerFont({
      familyName: 'myfont',
      familySrc: '/fonts/iconfont.ttf'
    })

  }

  // 处理喜欢
  handleLike (index: number) {
    // 1. 获取下标 或 id 加以区分
    // 子调用父的方法时, 每个子都能调用父的方法, 需要加以区分
    // 此时由于是数组操作, 可以传递下标, [快速定位到操作的是哪项]
    // 将来发请求 → id 作为区分值, 也可以
    // AlertDialog.show({
    //   message: index.toString()
    // })

    // 2. 父组件的方法, 如果抽取出来, 如果直接传递给子组件
    //    会有 this 指向问题, this 通常直接指向调用者
    //    需要, 用箭头函数函数包一层, 保证 this 还是指向父组件
    //    console.log('父组件的数据', this.commentList)

    // 3. 根据 index, 根据 数组数据, 进行判断 +1 还是 -1
    let itemData = this.commentList[index]
    if (itemData.isLike) {
      itemData.likeNum -= 1
    }
    else {
      itemData.likeNum += 1
    }
    itemData.isLike = !itemData.isLike

    // 4. 对于复杂类型: 状态对象, 状态数组, 只会对第一层数据, 进行监视变化
    // 数组.splice(从哪开始删除, 删除几个, 替换的项1, 替换的项2, ...)
    this.commentList.splice(index, 1, itemData)
  }


  // 处理提交
  handleSubmit (content: string) {
    // 往数组的最前面, 新增一项
    const newItem: CommentData = new CommentData(
      "https://img0.baidu.com/it/u=3316636492,2799302396&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1708707600&t=fc2a4907d0fae5c7b2d2d5f1511c24b3",
      "我", new Date().getTime(), 5, 0, content, false)
    this.commentList = [newItem, ...this.commentList]
  }

  onPageShow(): void {
    window.getLastWindow(AppStorage.get("context"), (err, data) => {
      if (err.code) {
        console.error('Failed to get last window. Cause:' + JSON.stringify(err));
        return;
      }
      data.setFullScreen(true)
    });
  }


  build() {
    Column(){
      // 2. 使用字体测试
      // Text('\ue600')
      //   .fontFamily('myfont')
      //   .fontSize(30)
      //   .fontColor(Color.Red)
      //
      // Text('\ue840')
      //   .fontFamily('myfont')
      //   .fontSize(30)
      //   .fontWeight(FontWeight.Bold)
      //   .fontColor(Color.Red)
    //   头部
      InfoTop()
      // 中间
      List() {
        ForEach(this.commentList, (item: CommentData, index: number) => {
          ListItem() {
            // 列表项组件 - 将item对象, 通过prop传值, 传递下去
            InfoItem({
              index: index,
              itemObj: item,
              onLikeClick: (index: number) => {
                // 此处的this → 访问的是外部环境的 this → 就是父组件
                this.handleLike(index)
              }
            })
          }
        })
      }
      .layoutWeight(1)
      .padding({ bottom: 10 })


      // 底部
      InfoBottom({
        onSubmitComment: (content: string) => {
          this.handleSubmit(content)
        }
      })
    }
    .width('100%')
    .height('100%')
  }
}

 

4.4.4.5 评论排序

 infoTop.ets

@Extend(Button)
function fancyButton (isOn: boolean) {
  .width(46)
  .height(32)
  .fontSize(12)
  .padding({ left: 5, right: 5 })
  .backgroundColor(isOn ? '#fff' : '#F7F8FA')
  .fontColor(isOn ? '#2f2e33' : '#8e9298')
  .border({ width: 1, color: isOn ? '#e4e5e6' : '#F7F8FA' })
}

@Component
struct InfoTop {
  @State isOn: boolean = true
  onSort = (type: number) => {}
  build() {
    Row() {
      Text('全部评论')
        .fontColor('#222')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      Row() {
        Button('最新', { stateEffect: false })
          .fancyButton(this.isOn)
          .onClick(() => {
            this.isOn = true
            this.onSort(0)
          })
        Button('最热', { stateEffect: false })
          .fancyButton(!this.isOn)
          .onClick(() => {
            this.isOn = false
            this.onSort(1)
          })
      }
      .backgroundColor('#F7F8FA')
      .borderRadius(20)
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .padding(16)
  }
}

export default InfoTop

Index.ets

import InfoTop from '../components/InfoTop'
import InfoItem from '../components/InfoItem'
import InfoBottom from '../components/InfoBottom'
import font from '@ohos.font'
import { CommentData, createListRange } from '../model/CommentData'

// 创建数据测试
// let listArr: CommentData[] = createListRange()
// console.log('数组列表', JSON.stringify(listArr))

// 从1970年01月01号 0:0:0
// 时间戳 → 特殊的日期格式(数字类型), 利于比较运算
// let nowTime: number = new Date().getTime() // 05-16
// let lastTime: number = new Date('2024-03-02').getTime()
// console.log('时间戳', nowTime, lastTime)
// console.log('时间戳', (nowTime - lastTime) / 1000 / 3600 / 24)

@Entry
@Component
struct Index {
  @State commentList: CommentData[] = createListRange()

  // 一加载Index入口页面, 就进行注册
  // aboutToAppear → 会在组件一加载时, 自动调用执行(生命周期函数)
  aboutToAppear(): void {
    // 1. 注册字体
    font.registerFont({
      familyName: 'myfont',
      familySrc: '/fonts/iconfont.ttf'
    })

    this.handleSort(0)
  }

  // 处理喜欢
  handleLike (index: number) {
    // 1. 获取下标 或 id 加以区分
    // 子调用父的方法时, 每个子都能调用父的方法, 需要加以区分
    // 此时由于是数组操作, 可以传递下标, [快速定位到操作的是哪项]
    // 将来发请求 → id 作为区分值, 也可以
    // AlertDialog.show({
    //   message: index.toString()
    // })

    // 2. 父组件的方法, 如果抽取出来, 如果直接传递给子组件
    //    会有 this 指向问题, this 通常直接指向调用者
    //    需要, 用箭头函数函数包一层, 保证 this 还是指向父组件
    //    console.log('父组件的数据', this.commentList)

    // 3. 根据 index, 根据 数组数据, 进行判断 +1 还是 -1
    let itemData = this.commentList[index]
    if (itemData.isLike) {
      itemData.likeNum -= 1
    }
    else {
      itemData.likeNum += 1
    }
    itemData.isLike = !itemData.isLike

    // 4. 对于复杂类型: 状态对象, 状态数组, 只会对第一层数据, 进行监视变化
    // 数组.splice(从哪开始删除, 删除几个, 替换的项1, 替换的项2, ...)
    this.commentList.splice(index, 1, itemData)
  }

  // 处理提交
  handleSubmit (content: string) {
    // 往数组的最前面, 新增一项
    const newItem: CommentData = new CommentData(
      "https://img0.baidu.com/it/u=3316636492,2799302396&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1708707600&t=fc2a4907d0fae5c7b2d2d5f1511c24b3",
      "我", new Date().getTime(), 5, 0, content, false)
    this.commentList = [newItem, ...this.commentList]
  }

  // 处理排序 0 最新 time时间戳,  1 最热 likeCount 点赞数
  handleSort (type: number) {
    if (type === 0) {
      // 时间戳, 从大到小排序
      this.commentList.sort((a, b) => {
        // a 前一项, b 后一项
        return b.time - a.time // 返回值如果 > 0, 交换位置
      })
    }
    else {
      // 点赞数, 从大到小排序
      this.commentList.sort((a, b) => {
        return b.likeNum - a.likeNum
      })
    }
  }

  build() {
    Column() {
      // 2. 使用字体测试
      // Text('\ue600')
      //   .fontFamily('myfont')
      //   .fontSize(30)
      //   .fontColor(Color.Red)

      // 头部标题组件
      InfoTop({
        onSort: (type: number) => {
          this.handleSort(type)
        }
      })

      // 中间
      List() {
        ForEach(this.commentList, (item: CommentData, index: number) => {
          ListItem() {
            // 列表项组件 - 将item对象, 通过prop传值, 传递下去
            InfoItem({
              index: index,
              itemObj: item,
              onLikeClick: (index: number) => {
                // 此处的this → 访问的是外部环境的 this → 就是父组件
                this.handleLike(index)
              }
            })
          }
        })
      }
      .layoutWeight(1)
      .padding({ bottom: 10 })

      // 底部
      InfoBottom({
        onSubmitComment: (content: string) => {
          this.handleSubmit(content)
        }
      })
    }
    .width('100%')
    .height('100%')
  }
}

4.5 @Link双向同步 

interface Person {
  name: string
  age: number
}


@Entry
@Component
  // 父组件
struct KnowledgePage {
  @State count: number = 0
  @State person: Person = {
    name: 'zs',
    age: 18
  }

  build() {
    Column() {
      Text('父组件')
        .fontSize(30)
      Text(this.count.toString())
      Text(JSON.stringify(this.person))
      Button('修改数据')
        .onClick(() => {
          this.count++
        })
      SonComponent({
        count: this.count,
        person: this.person
      })
    }
    .padding(10)
    .height('100%')
    .backgroundColor('#eee')
    .width('100%')
    .alignItems(HorizontalAlign.Center)
    .padding({ top: 100 })
  }
}


@Component
  // 子组件
struct SonComponent {
  @Link count:number
  @Link person: Person
  // 编写 UI
  build() {
    Column({ space: 20 }) {
      Text('我是子组件')
        .fontSize(20)

      Text(this.count.toString())
      Text(JSON.stringify(this.person))
      Column() {
        Button('修改count')
          .onClick(() => {
            // this.count++
            this.person.age++
          })

      }

    }
    .backgroundColor('#a6c398')
    .alignItems(HorizontalAlign.Center)
    .width('80%')
    .margin({ top: 100 })
    .padding(10)
    .borderRadius(10)

  }
}

 4.6 @Provide \@Consume后代组件

interface Car {
  name: string
  brand: string
}

@Entry
@Component
  // 顶级组件
struct RootComponent {
  @Provide themeColor: string = 'yellow'
  @Provide car: Car = {
    name: '小黄',
    brand: '美团'
  }
  build() {
    Column() {
      Text('顶级组件')
        .fontSize(30)
        .fontWeight(900)
      Text(this.themeColor)
      Text(JSON.stringify(this.car))

      // 二级组件
      ParentComponent()
      ParentComponent()
    }
    .padding(10)
    .height('100%')
    .backgroundColor('#ccc')
    .width('100%')
    .alignItems(HorizontalAlign.Center)
    .padding({ top: 100 })
  }
}


@Component
  // 二级组件
struct ParentComponent {
  @Consume themeColor: string
  // 编写 UI
  build() {
    Column({ space: 20 }) {
      Text('我是二级组件')
        .fontSize(22)
        .fontWeight(900)
      Text(this.themeColor)

      // 内层子组件
      SonComponent()
    }
    .backgroundColor('#a6c398')
    .alignItems(HorizontalAlign.Center)
    .width('90%')
    .margin({ top: 50 })
    .padding(10)
    .borderRadius(10)

  }
}

@Component
  // 内层组件
struct SonComponent {
  @Consume themeColor: string
  @Consume car: Car
  // 编写 UI
  build() {
    Column({ space: 20 }) {
      Text('我是内层组件' + this.themeColor)
        .fontSize(20)
        .fontWeight(900)
        .onClick(() => {
          // this.themeColor = 'orange'
          this.car.name = '小绿'
        })
      Text(JSON.stringify(this.car))
    }
    .backgroundColor('#bf94e4')
    .alignItems(HorizontalAlign.Center)
    .width('90%')
    .margin({ top: 50 })
    .padding(10)
    .borderRadius(10)

  }
}

4.7 @ Observed &@ObjectLink嵌套对象数组属性变化

interface IPerson {
  id: number
  name: string
  age: number
}

@Observed
class Person {
  id: number
  name: string
  age: number

  constructor(obj: IPerson) {
    this.id = obj.id
    this.name = obj.name
    this.age = obj.age
  }
}


@Entry
@Component
struct ObservedAndLink {
  @State personList: Person[] = [
    new Person({
      id: 1,
      name: '张三',
      age: 18
    }),
    new Person({
      id: 2,
      name: '李四',
      age: 19
    }),
    new Person({
      id: 3,
      name: '王五',
      age: 20
    })
  ]

  build() {
    Column({ space: 20 }) {
      Text('父组件')
        .fontSize(30)
      List({ space: 10 }) {
        ForEach(this.personList, (item: Person, index: number) => {
          ItemCom({
            info: item,
            addAge: () => {
              // 修改嵌套的数据 => 普通的情况, 监视不到更新
              item.age++ // 如果能监视到
              AlertDialog.show({
                message: JSON.stringify(this.personList)
              })
              // this.personList.splice(index, 1, item) // 无需手动替换更新
            }
          })
        })
      }

    }
    .backgroundColor('#cbe69b')
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

@Component
struct ItemCom {
  @ObjectLink info: Person
  addAge = () => {

  }

  build() {
    ListItem() {
      Row({ space: 10 }) {
        Text('姓名:' + this.info.name)
        Text('年龄:' + this.info.age)
        Blank()
        Button('修改数据')
          .onClick(() => {
            // this.addAge()
            this.info.age++
          })
      }
      .backgroundColor(Color.Pink)
      .padding(10)
      .width('100%')
    }
  }
}

 


http://www.kler.cn/news/310011.html

相关文章:

  • 江科大笔记—STM32课程简介
  • 基于SpringBoot+Vue+MySQL的家乡特色推荐系统
  • 英飞凌—TC377芯片详解(1)
  • npm包管理工具
  • UGit:腾讯自研的Git客户端新宠
  • Spring如何处理线程并发问题?
  • 传输层协议 —— UDP协议
  • LXDE lxpanel桌面环境中打开一个终端窗口 lxterminal
  • PHP 中传值与传引用的区别
  • 线程有哪几种状态?
  • web杂项
  • 腾讯云技术深度探索:构建高效云原生微服务架构
  • Java Stream API | Java Stream API 中 `filter()`的使用
  • TCP/IP Socket用于测试免费使用的服务器端
  • window下idea中scala的配置
  • web基础—dvwa靶场(五)File Upload
  • HarmonyOS开发之自定义构建函数
  • gi清除无用缓存
  • 【Elasticsearch系列】Elasticsearch中的分页
  • Python用TOPSIS熵权法重构粮食系统及期刊指标权重多属性决策MCDM研究|附数据代码
  • 利用物化视图刷新同步表记录(2-rowid)
  • 828华为云征文|Flexus云服务器X实例部署宝塔运维面板
  • 大数据处理技术:分布式文件系统HDFS
  • 基础漏洞——SQL注入原理和利用
  • 职业院校数据科学与大数据技术专业人工智能实训室建设方案
  • 【Linux篇】TCP/IP协议(笔记)
  • 深入探索Go语言中的函数:匿名函数、指针参数与函数返回
  • vue3中如何拿到vue2中的this
  • 嵌入式epoll面试题面试题及参考答案
  • 金蝶SHR,在列表对某个金额字段汇总展示的需求