1. 自定义组件基础
相关资源:
- 📎day10 图片素材.zip
1. 自定义组件基础
概念:在ArkUI中由框架直接提供的称为系统组件 -> Column,Button等,由开发者定义的称为自定义组件
作用:自定义组件可以对 UI和业务逻辑进行封装,从而复用组件
自定义组件语法:
- struct:自定义组件基于struct实现,struct + 自定义组件名 + {...}的组合构成自定义组件,不能有继承关系。对于struct的实例化,可以省略new。
- @Component:@Component装饰器仅能装饰struct关键字声明的数据结构。
- build()函数:build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数。
- @Entry:@Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry装饰一个自定义组件。
- @Preview:如果想要单独预览组件,可以使用@Preview 进行装饰
注意:自定义组件必须导出后,才能在其他组件中导入使用
export struct SonCom {}
1.1. 成员函数/变量
自定义组件除了必须要实现build()函数外,还可以定义其他的成员函数,以及成员变量
注意:
- 成员函数、变量均为私有
- 可以在父组件调用子组件时向成员变量传递数据
// HelloComponent.ets
@Component
export struct HelloComponent {// 成员变量info: string = '感觉自己闷闷哒'// 成员变量也可以是函数sayHello = ()=>{}// 状态变量@State message: string = 'Hello, World!';// 成员函数sayHi() {console.log('你好呀')}build() {// HelloComponent自定义组件组合系统组件Row和TextColumn() {Text(this.message)Text(this.info)Button('修改数据').onClick(() => {this.info = '(*  ̄3)(ε ̄ *)'this.message = 'Hello,ArkTS'this.sayHi()this.sayHello()})}}
}
// 页面的.ets
import { HelloComponent } from './components/HelloComponent'@Entry
@Component
struct CustomComponentDemo {build() {Column() {// 使用组件内部定义的初始值HelloComponent()// 使用传入的值,覆盖子组件的默认值HelloComponent({ info: '你好', message: 'ArkTS' })// 函数也可以传入HelloComponent({ sayHello:()=>{ console.log('传入的逻辑') } })}}
}
@Component
struct MyCom {// 定义函数用来接收父传入的函数sayHell:(p:string)=>void = ()=>{}build() {Column() {Button('点我向父组件传值').onClick(()=>{// 调用父组件的函数this.sayHell('我是子组件传给父组件的数据')})}
}@Entry
@Component
struct Index {build() {Column() {// 函数也可以传入MyCom({ // 接收子组件传入的数据sayHell:(msg:string)=>{ console.log('接收子组件传入的数据:'+msg) }})}}
}
1.2. 通用样式事件
自定义组件可以通过点语法的形式设置通用样式,通用事件
子组件().width(100).height(100).backgroundColor(Color.Orange).onClick(() => {console.log('外部添加的点击事件')})
试一试:
- 添加自定义组件,随意设置内容
- 使用自定义组件,通过点语法设置通用样式
@Component
struct MyComponent2 {build() {Button(`Hello World`)}
}@Entry
@Component
struct MyComponent {build() {Row() {MyComponent2().width(200).height(300).backgroundColor(Color.Red).onClick(() => {console.log('外部添加的点击事件')})}}
}
说明
ArkUI给自定义组件设置样式时,相当于给MyComponent2套了一个不可见的容器组件,而这些样式是设置在容器组件上的,而非直接设置给MyComponent2的Button组件。通过渲染结果我们可以很清楚的看到,背景颜色红色并没有直接生效在Button上,而是生效在Button所处的开发者不可见的容器组件上。
1.3. 案例-卡片组件
日常开发中,经常会在圆角的容器中展示不同的内容,我们一般称之为卡片,
@Component
struct PanelComp {title: string = ''more: string = ''clickHandler: () => void = () => {console.log('默认的逻辑')}build() {Column() {Row() {Text(this.title).layoutWeight(1).fontWeight(600)Row() {Text(this.more).fontSize(14).fontColor('#666666').onClick(() => {this.clickHandler()})Image($r('app.media.ic_public_arrow_right')).width(16).fillColor('#666666')}}.padding(10)Row() {Text('默认内容')}.height(100)}.borderRadius(12).backgroundColor('#fff').width('100%')}
}@Entry
@Component
struct Index {build() {Column({ space: 15 }) {PanelComp({title: '评价(2000+)', more: '好评率98%', clickHandler() {console.log('传入的逻辑')}})Row({ space: 15 }) {PanelComp({ title: '推荐', more: '查看全部' }).layoutWeight(1)PanelComp({ title: '体验', more: '4 条测评' }).layoutWeight(1)}}.height('100%').padding(15).backgroundColor('#f5f5f5')}
}
2. 构建函数-@BuilderParam 传递 UI
@BuilderParam
该装饰器用于声明任意UI描述的一个元素,类似slot占位符。
链接
简而言之:就是自定义组件允许外部传递 UI
// SonCom 的实现略@Entry
@Component
struct Index {build() {Column({ space: 15 }) {SonCom() {// 直接传递进来(尾随闭包)Button('传入的结构').onClick(() => {AlertDialog.show({ message: '点了 Button' })})}}}
}
2.1. 单个@BuilderParam参数
首先来看看单个的情况
使用尾随闭包的方式传入:
- 组件内有且仅有一个使用 @BuilderParam 装饰的属性,即可使用尾随闭包
- 内容直接在 {} 传入即可
注意:
- 此场景下自定义组件不支持使用通用属性。
@Component
struct SonCom {// 1.设置默认 的 Builder,避免外部不传入@BuilderdefaultBuilder() {Text('默认的内容')}// 2.定义 BuilderParam 接受外部传入的 ui,并设置默认值@BuilderParam ContentBuilder: () => void = this.defaultBuilderbuild() {Column() {// 3. 使用 @BuilderParam 装饰的成员变量this.ContentBuilder()}.width(300).height(200).border({ width: .5 })}
}// 使用自定义组件时,就可以使用如下方式传递 UI
// 不传递时会使用默认值
SonCom(){// 传入的 UI
}
试一试:
- 添加自定义组件:
-
- 定义默认的 Builder
- 添加BuilderParam,添加类型,并设置默认的 Builder
- 组件内部使用 BuilderParam
- 外部使用自定义组件,分别测试传递,不传递 UI 的情况
/** @BuilderParam 作用:可以在子组件中提供一个变量(尾随闭包),以便接收父组件传入的【UI结构】* 基本使用:* 1. 定义尾随闭包* 2. 在组件中使用这个尾随闭包* 3. 在父组件中调用这个组件传入UI结构** 注意点:单个写法特点:如果一个组件中,只有一个尾随闭包,那么这个尾随闭包可以不写变量名,直接使用即可* */@Component
export struct MyBuilder {// 1. 定义一个变量,用来接收父组件传递过来的UI结构// 默认值@Builder defaultBuilder(){Text('默认的结构')}@BuilderParam defaultUI:() => void = this.defaultBuilderbuild() {Column(){// 2. 使用这个变量this.defaultUI()}}
}import { MyBuilder } from '../views/MyBuilder'@Entry
@Component
struct Index {build() {Column() {MyBuilder() {// 3. 调用子组件,并向子组件中传入自己定义的UI结构Button('按钮').backgroundColor(Color.Red).onClick(()=>{AlertDialog.show({message:'OK'})})}}.height('100%').width('100%').backgroundColor(Color.Pink)}
}
2.2. 多个@BuilderParam 参数
子组件有多个BuilderParam,必须通过参数的方式来传入
核心步骤:
- 自定义组件-定义:
-
- 添加多个 @BuilderParam ,并定义默认值
- 自定义组件-使用
-
- 通过参数的形式传入多个 Builder,比如
SonCom({ titleBuilder: this.fTitleBuilder, contentBuilder: this.fContentBuilder })
@Component
struct SonCom {// 由外部传入 UI@BuilderParam titleBuilder: () => void = this.titleDefaultBuilder@BuilderParam contentBuilder: () => void = this.contentDefaultBuilder// 设置默认 的 Builder,避免外部不传入@BuildertitleDefaultBuilder() {Text('默认标题')}@BuildercontentDefaultBuilder() {Text('默认内容')}build() {Column() {Row() {this.titleBuilder()}.layoutWeight(1)Divider()Row() {this.contentBuilder()}.layoutWeight(1)}.width(300).height(200).border({ width: .5 })}
}@Entry
@Component
struct Index {@BuilderfTitleBuilder() {Text('传入的标题').fontSize(20).fontWeight(600).fontColor(Color.White).backgroundColor(Color.Blue).padding(10)}@BuilderfContentBuilder() {Text('传入的标题').fontSize(20).fontWeight(600).fontColor(Color.White).backgroundColor(Color.Blue).padding(10)}build() {Column({ space: 15 }) {// 指定名字传入UI结构SonCom({ titleBuilder: this.fTitleBuilder, contentBuilder: this.fContentBuilder })}}
}
2.3. 总结
// 定义子组件
@Component
export struct MyBuilder{ @Builder defaultBuilder(){Text('标题默认的结构')}@BuilderParam defaultUI:() => void = this.defaultBuilderbuild() {Column(){this.defaultUI()}}
}// 父组件调用子组件MyBuilder(){Button('按钮')}
// 定义子组件
@Component
export struct MyBuilder{ @Builder defaultBuilder(){Text('标题默认的结构')}@Builder contentBuilder(){Text('内容默认的结构')}@BuilderParam defaultUI:() => void = this.defaultBuilder@BuilderParam contentUI:() => void = this.contentBuilderbuild() {Column(){this.defaultUI()this.contentUI()}}
}// 父组件调用子组件@Builder defaultBuilder(){Button('标题0')}@Builder contentBuilder(){Text('内容')}MyBuilder({defaultUI:this.defaultBuilder,contentUI:this.contentBuilder})
2.4. 案例-卡片组件优化
使用刚刚学习的知识,让外部可以传递Builder到卡片组件内部
需求:
- 调整 卡片自定义组件,支持传入 UI PanelComp(){ // 此处传入 }
思路:
- 直接大括号(尾随闭包)传入只需要设置一个BuilderParam即可:
参考代码:
import { PanelComp } from '../views/MyPanel'@Entry
@Component
struct Index {build() {Column() {PanelComp({ leftTitle:'评价(2000+)',rightTitle:'好评率98%' ,clickHandler:(id:number)=>{ AlertDialog.show({message:'组件1的回调内容'+id}) }}){Column(){Text('组件1的内容')Button('按钮')}}PanelComp({ leftTitle:'体验',rightTitle:'4条评测',clickHandler:(id:number)=>{ AlertDialog.show({message:'组件2的回调内容'+id}) }}){Row(){Text('组件1的内容')Button('按钮')}}}.height('100%').width('100%').backgroundColor(Color.Pink)}
}
/** 在组件中编写单个@BuilderParams写法步骤:* 1. 定义尾随闭包* 2. 准备一个默认的自定义构建函数* 3. 使用它* */@Preview
@Component
export struct PanelComp {leftTitle: string = '默认标题'rightTitle: string = '默认更多'clickHandler: (id:number) => void = () => {}@Builder defaultBuilder(){Text('默认的内容')}@BuilderParam defaultUI:() => void = this.defaultBuilderbuild() {Column() {Row() {Text(this.leftTitle).layoutWeight(1).fontWeight(600)Row() {Text(this.rightTitle).fontSize(14).fontColor('#666666')Image($r('app.media.ic_public_arrow_right')).width(16).fillColor('#666666')}.onClick(() => {// AlertDialog.show({ message: '子组件点击了' })this.clickHandler(100)})}.padding(10)Row() {// 接收父组件传递过来的UI结构// Text('默认内容')this.defaultUI()}.height(100)}.borderRadius(12).backgroundColor('#fff').width('100%')}
}
3. 页面路由
页面路由指的是在应用程序中实现不同页面之间的跳转,以及数据传递。
我们先明确自定义组件和页面的关系:
- 自定义组件:@Component 装饰的UI单元,
- 页面:即应用的UI页面。可以由一个或者多个自定义组件组成。
-
- @Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry
通过 Router 模块就可以实现这个功能. import { router } from '@kit.ArkUI'
步骤:
- 创建页面 -> 页面与组件不同的地方是有且只有一个入口组件( @Entry修饰),并且在
src/main/resources/base/profile/main_pages.json
有配置好了页面路径
-
- 口诀:一入口,一配置
- router控制页面跳转
- 带参数跳转并获取
3.1. 页面栈
页面栈是用来存储程序运行时页面的一种数据结构,遵循先进后出的原则
页面栈的最大容量为32个页面
3.1.1. pushUrl的情况
先来看看 pushUrl的情况
- 默认打开首页 → 首页入栈
- pushUrl 去详情页 → 详情页入栈
- back 返回上一页 → 详情页出栈
- 此时页面栈中应该只有一个页面
整一个过程中,都可以 router.getLength 进行查看
3.1.2. replaceUrl 的情况
再来看看replaceUrl的情况
- 默认打开首页 → 首页入栈
- replaceUrl 去详情页 → 详情页替换首页,首页销毁
- back 无法返回 → 没有上一页
跳转到登录页面时可以使用 replaceUrl,因为无需在页面栈中保存其他页面的页面栈信息了。
3.1.3. 页面栈相关 api
为了让咱们更好的获取页面栈的信息,router 模块也提供了对应的 api 以供使用
// 获取页面栈长度
router.getLength()// 获取页面状态
let page = router.getState();
console.log('current index = ' + page.index);
console.log('current name = ' + page.name);
console.log('current path = ' + page.path);// 清空页面栈
router.clear()
3.2. 路由模式
路由提供了两种不同的跳转模式:
- standard(标准实例模式)
- Single(单实例模式)
不同模式的决定了页面是否会创建多个实例:
- Standard:无论之前是否添加过,一直添加到页面栈【默认】
-
- 场景:适用于每次跳转都呈现全新内容或状态的场景,避免数据展示紊乱(数据变化)
- Single:如果之前加过页面,会使用之前添加的页面【需要添加参数手动修改】
-
- 场景:适用于那些需要保留页面状态或避免重复创建相同页面的场景(数据不变化)
路由模式语法:
3.3. 总结
路由跳转的方式有pushUrl和replaceUrl
pushUrl的跳转要考虑两种模式:标准模式,单例模式
- pushUrl({},模式为标准模式(默认的模式)) -> 页面栈中的表现形式
- pushUrl({},模式为单例模式) -> 页面栈中的表现形式
- replaceUrl() -> 跳转以后的前一个页面自动销毁了
4. 页面和自定义组件的生命周期
组件和页面在创建、显示、销毁的这一整个过程中,会自动执行 一系列的【生命周期钩子】,其实就是一系列的【函数】,让开发者有机会在特定的阶段运行自己的代码
页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:
- onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景,如果不能触发aboutToAppear函数的时候,网络请求数据代码写在onPageShow中
- onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
- onBackPress:当用户点击返回按钮时触发。
组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:
- aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行,通常在这里发送网络请求获取数据
- onDidBuild:组件build()函数执行完成之后回调该接口,开发者可以在这个阶段进行埋点数据上报等不影响实际UI的功能。不建议在onDidBuild函数中更改状态变量、这可能会导致不稳定的UI表现。
- aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量
注意:
- @Entry修饰的页面入口组件有:aboutToAppear、onDidBuild、aboutToDisappear、onPageShow、onPageHide、onBackPress
- @component修饰的组件有:aboutToAppear、onDidBuild、aboutToDisappear
// 只有被@Entry装饰的组件才可以调用页面的生命周期onPageShow() {console.info('onPageShow');}// 只有被@Entry装饰的组件才可以调用页面的生命周期onPageHide() {console.info('onPageHide');}// 只有被@Entry装饰的组件才可以调用页面的生命周期onBackPress() {console.info('onBackPress');return true; // 返回true表示页面自己处理返回逻辑,不进行页面路由;返回false表示使用默认的路由返回逻辑,不设置返回值按照false处理}// 组件生命周期aboutToAppear() {console.info('aboutToAppear');}// 组件生命周期onDidBuild() {console.info('onDidBuild');}// 组件生命周期aboutToDisappear() {console.info('aboutToDisappear');}
5. 状态管理补充
5.1. 装饰器总览
ArkUI提供了多种装饰器,通过使用这些装饰器,状态变量不仅可以观察在组件内的改变,还可以在不同组件层级间传递,比如父子组件、跨组件层级,也可以观察全局范围内的变化。
根据状态变量的影响范围,将所有的装饰器可以大致分为:
- 管理组件拥有状态的装饰器:组件级别的状态管理,可以观察组件内变化,和不同组件层级的变化,但需要唯一观察同一个组件树上,即同一个页面内。
- 管理应用拥有状态的装饰器:应用级别的状态管理,可以观察不同页面,甚至不同UIAbility的状态变化,是应用内全局的状态管理。
咱们来看一张完整的装饰器说明图,咱们后续的学习就围绕着这张图来展开
- 管理组件状态:小框中
- 管理应用状态:大框中
5.2. @State 组件内状态-补充
@State 装饰器是管理组件内部状态的
@State装饰的变量,或称为状态变量
但是,并不是状态变量的所有更改都会引起UI的刷新,只有可以被框架观察到的修改才会引起UI刷新。
观察变化注意点:
- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
- 当装饰的数据类型为Object或数组时
-
- 可以观察到自身的赋值的变化
- 可以观察到对象属性赋值的变化,即Object.keys(observedObject)返回的所有属性
-
-
- 注意:嵌套属性的赋值观察不到
-
-
- 可以观察到数组本身的赋值和添加、删除、更新数组项的变化
-
-
- 注意:数组项中属性的赋值观察不到
-
interface iPerson {name: stringdog: iDog
}interface iDog {name: string
}@Entry
@Component
struct Index {@State person: iPerson = {name: '张三',dog: {name: '旺财'}}build() {Column() {Text(JSON.stringify(this.person))Button('修改数据').onClick(() => {// ✔️1. 如果修改的是整个对象 -> 能观察到变化通知UI更新// this.person = {// name: '李四',// dog: {// name: '旺财1'// }// }// ✔️ 2. 如果修改的是对象的一层属性 -> 能观察到变化,通知UI更新// this.person.name = '李四'// this.person.dog = { name:'萨摩耶' }// ❌ 3. 如果修改的是对象的二层属性 -> 不能观察到变化,UI不会更新this.person.dog.name = '萨摩耶'})}.height('100%').width('100%')}
}
interface iPerson {name:string
}@Entry
@Component
struct Index {@State list:iPerson[] = [{name:'张三'}]build() {Column() {Text(JSON.stringify(this.list)).onClick(()=>{// 修改数组项this.list[0] = {name:'李四'}//✔️this.list[0].name = '李四'//❌})}.height('100%').width('100%').backgroundColor(Color.Pink)}
}
5.3. 状态共享@Prop -父子单向传递
@Prop 装饰的变量可以和父组件建立单向的同步关系
@Prop 装饰的变量是可变的,但是变化不会同步回其父组件
注意:
- 修改父组件数据,会同步更新子组件
- 修改子组件@Prop 修饰的数据,子组件 UI 更新,更新后的数据不会同步给父组件
- 通过回调函数的方式修改父组件的数据,然后触发@Prop数据的更新
随堂演示代码
/* @Prop作用:可以将父文件中的状态变量传递给子组件,让他们形成一个单向数据传递关系
单向 父组件--->子组件 子组件 ---❌--->父组件语法步骤:
1. 在子组件使用 @Prop定义一个变量
2. 在父组件中调用子组件,并传递参数(父组件中的状态变量)总结:
1. @Prop的应用场景:父组件要将一个变量传递给子组件,并且在父组件中改变这个变量的值,能让子组件也跟着改变
但是当子组件改了这个变量的值,父组件不会改变注意点:
1. 父组件传递给子组件的变量,只能是状态状态
2. 只有当父组件中的状态变量值发生改变,才能让子组件更新,但是如果子组件本身修改了这个变量的值也会刷新
3. 如果在子组件中改变的值要通知父组件更新,使用回调函数(父亲给我取的名字,和我自己改的名字同步了)** */
import { ChildCom } from './ChildCom'@Entry
@Component
struct Index {@State sonName:string = '张三'build() {Column({space:40}) {ChildCom({ name:this.sonName,updateName:(childName:string)=>{this.sonName = childName} })Divider().backgroundColor(Color.Red).height(2)Button('修改儿子的名字').onClick(()=>{this.sonName = '张四'})}.height('100%').width('100%')}
}
@Component
export struct ChildCom {// 定义了一个名字@Prop name: stringupdateName:(myname:string)=>void = ()=>{}build() {Row() {Text(this.name).fontSize(50)Button('改自己的名字').onClick(()=>{// this.name = '张思思'this.updateName('张思思')})}.height(50).width('100%')}
}
@Component
struct SonCom {@Prop info: stringchangeInfo = (newInfo: string) => {}build() {Button('info:' + this.info).onClick(() => {this.changeInfo('改啦')})}
}@Entry
@Component
struct FatherCom {@State info: string = '么么哒'build() {Column() {Text(this.info)SonCom({info: this.info,changeInfo: (newInfo: string) => {this.info = newInfo}})}.padding(20).backgroundColor(Color.Orange)}
}
interface User {name: stringage: number
}@Entry
@Component
struct Index {@StateuserInfo: User = {name: 'jack',age: 18}build() {Column({ space: 20 }) {Text('父组件').fontSize(30)Text('用户名:' + this.userInfo.name).white().onClick(() => {this.userInfo.name = 'rose'})Text('年龄:' + this.userInfo.age).white().onClick(() => {this.userInfo.age++})Child({user: this.userInfo,userChange: (newUser: User) => {this.userInfo.name = newUser.namethis.userInfo.age = newUser.age}})}.width('100%').height('100%').alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center).backgroundColor(Color.Pink)}
}@Component
struct Child {@Propuser: UseruserChange: (newUser: User) => void = (newUser: User) => {}build() {Text('子组件:' + JSON.stringify(this.user)).padding(10).backgroundColor('#0094ff').fontColor(Color.White).onClick(() => {this.userChange({name: '路飞',age: 26})})}
}@Extend(Text)
function white() {.fontSize(20).fontColor(Color.White)
}
6. 案例-知乎评论
6.1. 静态结构+数据准备
interface ReplyItem {id: numberavatar: ResourceStrauthor: stringcontent: stringtime: stringarea: stringlikeNum: numberlikeFlag: boolean
}@Entry
@Component
struct ZhiHu {@State commentList: ReplyItem[] = [{id: 1,avatar: 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63',author: '偏执狂-妄想家',content: '更何况还分到一个摩洛哥[惊喜]',time: '11-30',area: '海南',likeNum: 34,likeFlag: false},{id: 2,avatar: 'https://pic1.zhimg.com/v2-5a3f5190369ae59c12bee33abfe0c5cc_xl.jpg?source=32738c0c',author: 'William',content: '当年希腊可是把1:0发挥到极致了',time: '11-29',area: '北京',likeNum: 58,likeFlag: true},{id: 3,avatar: 'https://picx.zhimg.com/v2-e6f4605c16e4378572a96dad7eaaf2b0_l.jpg?source=06d4cd63',author: 'Andy Garcia',content: '欧洲杯其实16队球队打正赛已经差不多,24队打正赛意味着正赛阶段在小组赛一样有弱队。',time: '11-28',area: '上海',likeNum: 10,likeFlag: false},{id: 4,avatar: 'https://picx.zhimg.com/v2-53e7cf84228e26f419d924c2bf8d5d70_l.jpg?source=06d4cd63',author: '正宗好鱼头',content: '确实眼红啊,亚洲就没这种球队,让中国队刷',time: '11-27',area: '香港',likeNum: 139,likeFlag: true},{id: 5,avatar: 'https://pic1.zhimg.com/v2-eeddfaae049df2a407ff37540894c8ce_l.jpg?source=06d4cd63',author: '柱子哥',content: '我是支持扩大的,亚洲杯欧洲杯扩到32队,世界杯扩到64队才是好的,世界上有超过200支队伍,欧洲区55支队伍,亚洲区47支队伍,即使如此也就六成出现率',time: '11-27',area: '旧金山',likeNum: 29,likeFlag: false},{id: 6,avatar: 'https://picx.zhimg.com/v2-fab3da929232ae911e92bf8137d11f3a_l.jpg?source=06d4cd63',author: '飞轩逸',content: '禁止欧洲杯扩军之前,应该先禁止世界杯扩军,或者至少把亚洲名额一半给欧洲。',time: '11-26',area: '里约',likeNum: 100,likeFlag: false}]@State rootComment: ReplyItem = {id: 1,avatar: $r('app.media.avatar'),author: '周杰伦',content: '意大利拌面应该使用42号钢筋混凝土再加上量子力学缠绕最后通过不畏浮云遮望眼',time: '11-30',area: '海南',likeNum: 98,likeFlag: true}build() {Stack({ alignContent: Alignment.Bottom }) {Column() {Scroll() {Column() {// 顶部组件HmNavBar()// 顶部评论CommentItem()// 分割线Divider().strokeWidth(6).color("#f4f5f6")// 回复数ReplyCount()// 回复评论列表ForEach(Array.from({ length: 10 }), () => {CommentItem()})}.width('100%').backgroundColor(Color.White)}.padding({bottom: 60}).edgeEffect(EdgeEffect.Spring).scrollBar(BarState.Off)}.height('100%')ReplyInput()}.height('100%')}
}@Component
struct HmNavBar {build() {Row() {Row() {Image($r('app.media.ic_public_arrow_left')).width(20).height(20)}.borderRadius(20).backgroundColor('#f6f6f6').justifyContent(FlexAlign.Center).width(30).aspectRatio(1).margin({left: 15})Text("评论回复").layoutWeight(1).textAlign(TextAlign.Center).padding({right: 35})}.width('100%').height(50).border({width: {bottom: 1},color: '#f6f6f6',})}
}@Component
struct CommentItem {build() {Row() {Image($r('app.media.avatar')).width(32).height(32).borderRadius(16)Column({ space: 10 }) {Text('作者').fontWeight(600)Text('内容').lineHeight(20).fontSize(14).fontColor("#565656")Row() {Text(`11-30 . IP属地 北京`).fontColor("#c3c4c5").fontSize(12)Row() {Image($r('app.media.like')).width(14).aspectRatio(1).fillColor("#c3c4c5") // "#c3c4c5" 或 redText('10').fontSize(12).margin({left: 5})}}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.alignItems(HorizontalAlign.Start).layoutWeight(1).padding({left: 15,right: 5})}.justifyContent(FlexAlign.Start).alignItems(VerticalAlign.Top).width('100%').padding(15)}
}@Component
struct ReplyCount {build() {Text() {Span('回复')Span(`${27}`)}.padding(15).fontWeight(700).alignSelf(ItemAlign.Start)}
}@Component
struct ReplyInput {build() {Row() {TextInput({ placeholder: '回复' }).layoutWeight(1).backgroundColor("#f4f5f6").height(40)Text('发布').fontColor("#6ecff0").fontSize(14).margin({left: 10})}.padding(10).backgroundColor(Color.White).border({width: { top: 1 },color: "#f4f5f6"})}
}
6.2. 评论数据渲染
需求:
- 顶部评论组件 渲染评论数据
- 评论列表组件 渲染评论列表
核心步骤:
- 评论组件:
-
- 定义@Prop 接收评论数据
- 父组件:
-
- 评论数据传递给子组件
- 评论组件
-
- 评论组件,接收数据并使用
interface ReplyItem {id: numberavatar: ResourceStrauthor: stringcontent: stringtime: stringarea: stringlikeNum: numberlikeFlag: boolean
}class ReplyData {static getCommentList(): ReplyItem[] {return [{id: 1,avatar: 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63',author: '偏执狂-妄想家',content: '更何况还分到一个摩洛哥[惊喜]',time: '11-30',area: '海南',likeNum: 34,likeFlag: false},{id: 2,avatar: 'https://pic1.zhimg.com/v2-5a3f5190369ae59c12bee33abfe0c5cc_xl.jpg?source=32738c0c',author: 'William',content: '当年希腊可是把1:0发挥到极致了',time: '11-29',area: '北京',likeNum: 58,likeFlag: true},{id: 3,avatar: 'https://picx.zhimg.com/v2-e6f4605c16e4378572a96dad7eaaf2b0_l.jpg?source=06d4cd63',author: 'Andy Garcia',content: '欧洲杯其实16队球队打正赛已经差不多,24队打正赛意味着正赛阶段在小组赛一样有弱队。',time: '11-28',area: '上海',likeNum: 10,likeFlag: false},{id: 4,avatar: 'https://picx.zhimg.com/v2-53e7cf84228e26f419d924c2bf8d5d70_l.jpg?source=06d4cd63',author: '正宗好鱼头',content: '确实眼红啊,亚洲就没这种球队,让中国队刷',time: '11-27',area: '香港',likeNum: 139,likeFlag: true},{id: 5,avatar: 'https://pic1.zhimg.com/v2-eeddfaae049df2a407ff37540894c8ce_l.jpg?source=06d4cd63',author: '柱子哥',content: '我是支持扩大的,亚洲杯欧洲杯扩到32队,世界杯扩到64队才是好的,世界上有超过200支队伍,欧洲区55支队伍,亚洲区47支队伍,即使如此也就六成出现率',time: '11-27',area: '旧金山',likeNum: 29,likeFlag: false},{id: 6,avatar: 'https://picx.zhimg.com/v2-fab3da929232ae911e92bf8137d11f3a_l.jpg?source=06d4cd63',author: '飞轩逸',content: '禁止欧洲杯扩军之前,应该先禁止世界杯扩军,或者至少把亚洲名额一半给欧洲。',time: '11-26',area: '里约',likeNum: 100,likeFlag: false}]}static getRootComment(): ReplyItem {return {id: 1,avatar: $r('app.media.avatar'),author: '周杰伦',content: '意大利拌面应该使用42号钢筋混凝土再加上量子力学缠绕最后通过不畏浮云遮望眼',time: '11-30',area: '海南',likeNum: 98,likeFlag: true}}
}@Entry
@Component
struct ZhiHu {@State commentList: ReplyItem[] = ReplyData.getCommentList()@State rootComment: ReplyItem = ReplyData.getRootComment()build() {Stack({ alignContent: Alignment.Bottom }) {Column() {Scroll() {Column() {// 顶部组件HmNavBar()// 顶部评论CommentItem({item: this.rootComment})// 分割线Divider().strokeWidth(6).color("#f4f5f6")// 回复数ReplyCount()// 回复评论列表ForEach(this.commentList, (item: ReplyItem) => {CommentItem({ item: item })})}.width('100%').backgroundColor(Color.White)}.padding({bottom: 60}).edgeEffect(EdgeEffect.Spring).scrollBar(BarState.Off)}.height('100%')ReplyInput()}.height('100%')}
}@Component
struct HmNavBar {build() {Row() {Row() {Image($r('app.media.ic_public_arrow_left')).width(20).height(20)}.borderRadius(20).backgroundColor('#f6f6f6').justifyContent(FlexAlign.Center).width(30).aspectRatio(1).margin({left: 15})Text("评论回复").layoutWeight(1).textAlign(TextAlign.Center).padding({right: 35})}.width('100%').height(50).border({width: {bottom: 1},color: '#f6f6f6',})}
}@Component
struct CommentItem {@Prop item: ReplyItembuild() {Row() {Image(this.item.avatar).width(32).height(32).borderRadius(16)Column({ space: 10 }) {Text(this.item.author).fontWeight(600)Text(this.item.content).lineHeight(20).fontSize(14).fontColor("#565656")Row() {Text(`${this.item.time} . IP属地 ${this.item.area}`).fontColor("#c3c4c5").fontSize(12)Row() {Image($r('app.media.like')).width(14).aspectRatio(1).fillColor("#c3c4c5") // "#c3c4c5" 或 redText(this.item.likeNum.toString()).fontSize(12).margin({left: 5})}}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.alignItems(HorizontalAlign.Start).layoutWeight(1).padding({left: 15,right: 5})}.justifyContent(FlexAlign.Start).alignItems(VerticalAlign.Top).width('100%').padding(15)}
}@Component
struct ReplyCount {build() {Text() {Span('回复')Span(`${27}`)}.padding(15).fontWeight(700).alignSelf(ItemAlign.Start)}
}@Component
struct ReplyInput {build() {Row() {TextInput({ placeholder: '回复' }).layoutWeight(1).backgroundColor("#f4f5f6").height(40)Text('发布').fontColor("#6ecff0").fontSize(14).margin({left: 10})}.padding(10).backgroundColor(Color.White).border({width: { top: 1 },color: "#f4f5f6"})}
}
6.3. 评论点赞-顶部评论
完成顶部评论
需求:
- 点击顶部的❤,切换点赞状态
核心步骤:
- 子组件:
-
- 定义changeLike函数,在点赞时调用
- 数据使用@Prop修饰,父组件状态更新会触发子组件更新
- 父组件:
-
- 传递changeLike 给子组件,内部实现点赞顶部评论逻辑
interface ReplyItem {id: numberavatar: ResourceStrauthor: stringcontent: stringtime: stringarea: stringlikeNum: numberlikeFlag: boolean
}class ReplyData {static getCommentList(): ReplyItem[] {return [{id: 1,avatar: 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63',author: '偏执狂-妄想家',content: '更何况还分到一个摩洛哥[惊喜]',time: '11-30',area: '海南',likeNum: 34,likeFlag: false},{id: 2,avatar: 'https://pic1.zhimg.com/v2-5a3f5190369ae59c12bee33abfe0c5cc_xl.jpg?source=32738c0c',author: 'William',content: '当年希腊可是把1:0发挥到极致了',time: '11-29',area: '北京',likeNum: 58,likeFlag: true},{id: 3,avatar: 'https://picx.zhimg.com/v2-e6f4605c16e4378572a96dad7eaaf2b0_l.jpg?source=06d4cd63',author: 'Andy Garcia',content: '欧洲杯其实16队球队打正赛已经差不多,24队打正赛意味着正赛阶段在小组赛一样有弱队。',time: '11-28',area: '上海',likeNum: 10,likeFlag: false},{id: 4,avatar: 'https://picx.zhimg.com/v2-53e7cf84228e26f419d924c2bf8d5d70_l.jpg?source=06d4cd63',author: '正宗好鱼头',content: '确实眼红啊,亚洲就没这种球队,让中国队刷',time: '11-27',area: '香港',likeNum: 139,likeFlag: true},{id: 5,avatar: 'https://pic1.zhimg.com/v2-eeddfaae049df2a407ff37540894c8ce_l.jpg?source=06d4cd63',author: '柱子哥',content: '我是支持扩大的,亚洲杯欧洲杯扩到32队,世界杯扩到64队才是好的,世界上有超过200支队伍,欧洲区55支队伍,亚洲区47支队伍,即使如此也就六成出现率',time: '11-27',area: '旧金山',likeNum: 29,likeFlag: false},{id: 6,avatar: 'https://picx.zhimg.com/v2-fab3da929232ae911e92bf8137d11f3a_l.jpg?source=06d4cd63',author: '飞轩逸',content: '禁止欧洲杯扩军之前,应该先禁止世界杯扩军,或者至少把亚洲名额一半给欧洲。',time: '11-26',area: '里约',likeNum: 100,likeFlag: false}]}static getRootComment(): ReplyItem {return {id: 1,avatar: $r('app.media.avatar'),author: '周杰伦',content: '意大利拌面应该使用42号钢筋混凝土再加上量子力学缠绕最后通过不畏浮云遮望眼',time: '11-30',area: '海南',likeNum: 98,likeFlag: true}}
}@Entry
@Component
struct ZhiHu {@State commentList: ReplyItem[] = ReplyData.getCommentList()@State rootComment: ReplyItem = ReplyData.getRootComment()build() {Stack({ alignContent: Alignment.Bottom }) {Column() {Scroll() {Column() {// 顶部组件HmNavBar()// 顶部评论CommentItem({item: this.rootComment,changeLike: () => {this.rootComment.likeFlag = !this.rootComment.likeFlagif (this.rootComment.likeFlag == true) {// 累加this.rootComment.likeNum++} else {// 递减this.rootComment.likeNum--}}})// 分割线Divider().strokeWidth(6).color("#f4f5f6")// 回复数ReplyCount()// 回复评论列表ForEach(this.commentList, (item: ReplyItem, index: number) => {CommentItem({item: item,})})}.width('100%').backgroundColor(Color.White)}.padding({bottom: 60}).edgeEffect(EdgeEffect.Spring).scrollBar(BarState.Off)}.height('100%')ReplyInput()}.height('100%')}
}@Component
struct HmNavBar {build() {Row() {Row() {Image($r('app.media.ic_public_arrow_left')).width(20).height(20)}.borderRadius(20).backgroundColor('#f6f6f6').justifyContent(FlexAlign.Center).width(30).aspectRatio(1).margin({left: 15})Text("评论回复").layoutWeight(1).textAlign(TextAlign.Center).padding({right: 35})}.width('100%').height(50).border({width: {bottom: 1},color: '#f6f6f6',})}
}@Component
struct CommentItem {@Prop item: ReplyItemchangeLike = () => {}build() {Row() {Image(this.item.avatar).width(32).height(32).borderRadius(16)Column({ space: 10 }) {Text(this.item.author).fontWeight(600)Text(this.item.content).lineHeight(20).fontSize(14).fontColor("#565656")Row() {Text(`${this.item.time} . IP属地 ${this.item.area}`).fontColor("#c3c4c5").fontSize(12)Row() {Image($r('app.media.like')).width(14).aspectRatio(1).fillColor(this.item.likeFlag ? Color.Red : "#c3c4c5")// "#c3c4c5" 或 red.onClick(() => {this.changeLike()})Text(this.item.likeNum.toString()).fontSize(12).margin({left: 5})}}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.alignItems(HorizontalAlign.Start).layoutWeight(1).padding({left: 15,right: 5})}.justifyContent(FlexAlign.Start).alignItems(VerticalAlign.Top).width('100%').padding(15)}
}@Component
struct ReplyCount {build() {Text() {Span('回复')Span(`${27}`)}.padding(15).fontWeight(700).alignSelf(ItemAlign.Start)}
}@Component
struct ReplyInput {build() {Row() {TextInput({ placeholder: '回复' }).layoutWeight(1).backgroundColor("#f4f5f6").height(40)Text('发布').fontColor("#6ecff0").fontSize(14).margin({left: 10})}.padding(10).backgroundColor(Color.White).border({width: { top: 1 },color: "#f4f5f6"})}
}
6.4. 评论点赞-列表
完成 列表的点赞效果
核心步骤:
- 在父组件:
-
- 列表区域实现changeLike函数,实现点赞逻辑
- 页面的更新,通过数组的 splice 方法来实现
interface ReplyItem {id: numberavatar: ResourceStrauthor: stringcontent: stringtime: stringarea: stringlikeNum: numberlikeFlag: boolean
}class ReplyData {static getCommentList(): ReplyItem[] {return [{id: 1,avatar: 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63',author: '偏执狂-妄想家',content: '更何况还分到一个摩洛哥[惊喜]',time: '11-30',area: '海南',likeNum: 34,likeFlag: false},{id: 2,avatar: 'https://pic1.zhimg.com/v2-5a3f5190369ae59c12bee33abfe0c5cc_xl.jpg?source=32738c0c',author: 'William',content: '当年希腊可是把1:0发挥到极致了',time: '11-29',area: '北京',likeNum: 58,likeFlag: true},{id: 3,avatar: 'https://picx.zhimg.com/v2-e6f4605c16e4378572a96dad7eaaf2b0_l.jpg?source=06d4cd63',author: 'Andy Garcia',content: '欧洲杯其实16队球队打正赛已经差不多,24队打正赛意味着正赛阶段在小组赛一样有弱队。',time: '11-28',area: '上海',likeNum: 10,likeFlag: false},{id: 4,avatar: 'https://picx.zhimg.com/v2-53e7cf84228e26f419d924c2bf8d5d70_l.jpg?source=06d4cd63',author: '正宗好鱼头',content: '确实眼红啊,亚洲就没这种球队,让中国队刷',time: '11-27',area: '香港',likeNum: 139,likeFlag: true},{id: 5,avatar: 'https://pic1.zhimg.com/v2-eeddfaae049df2a407ff37540894c8ce_l.jpg?source=06d4cd63',author: '柱子哥',content: '我是支持扩大的,亚洲杯欧洲杯扩到32队,世界杯扩到64队才是好的,世界上有超过200支队伍,欧洲区55支队伍,亚洲区47支队伍,即使如此也就六成出现率',time: '11-27',area: '旧金山',likeNum: 29,likeFlag: false},{id: 6,avatar: 'https://picx.zhimg.com/v2-fab3da929232ae911e92bf8137d11f3a_l.jpg?source=06d4cd63',author: '飞轩逸',content: '禁止欧洲杯扩军之前,应该先禁止世界杯扩军,或者至少把亚洲名额一半给欧洲。',time: '11-26',area: '里约',likeNum: 100,likeFlag: false}]}static getRootComment(): ReplyItem {return {id: 1,avatar: $r('app.media.avatar'),author: '周杰伦',content: '意大利拌面应该使用42号钢筋混凝土再加上量子力学缠绕最后通过不畏浮云遮望眼',time: '11-30',area: '海南',likeNum: 98,likeFlag: true}}
}@Entry
@Component
struct ZhiHu {@State commentList: ReplyItem[] = ReplyData.getCommentList()@State rootComment: ReplyItem = ReplyData.getRootComment()build() {Stack({ alignContent: Alignment.Bottom }) {Column() {Scroll() {Column() {// 顶部组件HmNavBar()// 顶部评论CommentItem({item: this.rootComment,changeLike: () => {this.rootComment.likeFlag = !this.rootComment.likeFlagif (this.rootComment.likeFlag == true) {// 累加this.rootComment.likeNum++} else {// 递减this.rootComment.likeNum--}}})// 分割线Divider().strokeWidth(6).color("#f4f5f6")// 回复数ReplyCount()// 回复评论列表ForEach(this.commentList, (item: ReplyItem, index: number) => {CommentItem({item: item,changeLike: () => {item.likeFlag = !item.likeFlagif (item.likeFlag == true) {// 累加item.likeNum++} else {// 递减item.likeNum--}this.commentList.splice(index, 1, item)}})})}.width('100%').backgroundColor(Color.White)}.padding({bottom: 60}).edgeEffect(EdgeEffect.Spring).scrollBar(BarState.Off)}.height('100%')ReplyInput()}.height('100%')}
}@Component
struct HmNavBar {build() {Row() {Row() {Image($r('app.media.ic_public_arrow_left')).width(20).height(20)}.borderRadius(20).backgroundColor('#f6f6f6').justifyContent(FlexAlign.Center).width(30).aspectRatio(1).margin({left: 15})Text("评论回复").layoutWeight(1).textAlign(TextAlign.Center).padding({right: 35})}.width('100%').height(50).border({width: {bottom: 1},color: '#f6f6f6',})}
}@Component
struct CommentItem {@Prop item: ReplyItemchangeLike = () => {}build() {Row() {Image(this.item.avatar).width(32).height(32).borderRadius(16)Column({ space: 10 }) {Text(this.item.author).fontWeight(600)Text(this.item.content).lineHeight(20).fontSize(14).fontColor("#565656")Row() {Text(`${this.item.time} . IP属地 ${this.item.area}`).fontColor("#c3c4c5").fontSize(12)Row() {Image($r('app.media.like')).width(14).aspectRatio(1).fillColor(this.item.likeFlag ? Color.Red : "#c3c4c5")// "#c3c4c5" 或 red.onClick(() => {this.changeLike()})Text(this.item.likeNum.toString()).fontSize(12).margin({left: 5})}}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.alignItems(HorizontalAlign.Start).layoutWeight(1).padding({left: 15,right: 5})}.justifyContent(FlexAlign.Start).alignItems(VerticalAlign.Top).width('100%').padding(15)}
}@Component
struct ReplyCount {build() {Text() {Span('回复')Span(`${27}`)}.padding(15).fontWeight(700).alignSelf(ItemAlign.Start)}
}@Component
struct ReplyInput {build() {Row() {TextInput({ placeholder: '回复' }).layoutWeight(1).backgroundColor("#f4f5f6").height(40)Text('发布').fontColor("#6ecff0").fontSize(14).margin({left: 10})}.padding(10).backgroundColor(Color.White).border({width: { top: 1 },color: "#f4f5f6"})}
}
6.5. 发布评论
最后来完成发布评论
需求:
- 发布评论
- 评论个数同步更新
核心步骤:
- 发布评论
-
- 收集输入框内容
- 点击发布传递给父组件
- 父组件加入数据
- 评论个数同步更新
-
- 将 length 传入子组件
- 子组件内部通过 Prop 接收(响应更新)
interface ReplyItem {id: numberavatar: ResourceStrauthor: stringcontent: stringtime: stringarea: stringlikeNum: numberlikeFlag: boolean
}class ReplyData {static getCommentList(): ReplyItem[] {return [{id: 1,avatar: 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63',author: '偏执狂-妄想家',content: '更何况还分到一个摩洛哥[惊喜]',time: '11-30',area: '海南',likeNum: 34,likeFlag: false},{id: 2,avatar: 'https://pic1.zhimg.com/v2-5a3f5190369ae59c12bee33abfe0c5cc_xl.jpg?source=32738c0c',author: 'William',content: '当年希腊可是把1:0发挥到极致了',time: '11-29',area: '北京',likeNum: 58,likeFlag: true},{id: 3,avatar: 'https://picx.zhimg.com/v2-e6f4605c16e4378572a96dad7eaaf2b0_l.jpg?source=06d4cd63',author: 'Andy Garcia',content: '欧洲杯其实16队球队打正赛已经差不多,24队打正赛意味着正赛阶段在小组赛一样有弱队。',time: '11-28',area: '上海',likeNum: 10,likeFlag: false},{id: 4,avatar: 'https://picx.zhimg.com/v2-53e7cf84228e26f419d924c2bf8d5d70_l.jpg?source=06d4cd63',author: '正宗好鱼头',content: '确实眼红啊,亚洲就没这种球队,让中国队刷',time: '11-27',area: '香港',likeNum: 139,likeFlag: true},{id: 5,avatar: 'https://pic1.zhimg.com/v2-eeddfaae049df2a407ff37540894c8ce_l.jpg?source=06d4cd63',author: '柱子哥',content: '我是支持扩大的,亚洲杯欧洲杯扩到32队,世界杯扩到64队才是好的,世界上有超过200支队伍,欧洲区55支队伍,亚洲区47支队伍,即使如此也就六成出现率',time: '11-27',area: '旧金山',likeNum: 29,likeFlag: false},{id: 6,avatar: 'https://picx.zhimg.com/v2-fab3da929232ae911e92bf8137d11f3a_l.jpg?source=06d4cd63',author: '飞轩逸',content: '禁止欧洲杯扩军之前,应该先禁止世界杯扩军,或者至少把亚洲名额一半给欧洲。',time: '11-26',area: '里约',likeNum: 100,likeFlag: false}]}static getRootComment(): ReplyItem {return {id: 1,avatar: $r('app.media.avatar'),author: '周杰伦',content: '意大利拌面应该使用42号钢筋混凝土再加上量子力学缠绕最后通过不畏浮云遮望眼',time: '11-30',area: '海南',likeNum: 98,likeFlag: true}}
}@Entry
@Component
struct ZhiHu {@State commentList: ReplyItem[] = ReplyData.getCommentList()@State rootComment: ReplyItem = ReplyData.getRootComment()build() {Stack({ alignContent: Alignment.Bottom }) {Column() {Scroll() {Column() {// 顶部组件HmNavBar()// 顶部评论CommentItem({item: this.rootComment,changeLike: () => {this.rootComment.likeFlag = !this.rootComment.likeFlagif (this.rootComment.likeFlag == true) {// 累加this.rootComment.likeNum++} else {// 递减this.rootComment.likeNum--}}})// 分割线Divider().strokeWidth(6).color("#f4f5f6")// 回复数ReplyCount({ count: this.commentList.length })// 回复评论列表ForEach(this.commentList, (item: ReplyItem, index: number) => {CommentItem({item: item,changeLike: () => {item.likeFlag = !item.likeFlagif (item.likeFlag == true) {// 累加item.likeNum++} else {// 递减item.likeNum--}this.commentList.splice(index, 1, item)}})})}.width('100%').backgroundColor(Color.White)}.padding({bottom: 60}).edgeEffect(EdgeEffect.Spring).scrollBar(BarState.Off)}.height('100%')ReplyInput({addReply: (inputValue: string) => {this.commentList.unshift({id: Date.now(),avatar: $r('app.media.avatar'),author: '小狗钱钱',content: inputValue,time: `${new Date().getMonth() + 1}-${new Date().getDate()}`,area: '浙江',likeNum: 0,likeFlag: false})}})}.height('100%')}
}@Component
struct HmNavBar {build() {Row() {Row() {Image($r('app.media.ic_public_arrow_left')).width(20).height(20)}.borderRadius(20).backgroundColor('#f6f6f6').justifyContent(FlexAlign.Center).width(30).aspectRatio(1).margin({left: 15})Text("评论回复").layoutWeight(1).textAlign(TextAlign.Center).padding({right: 35})}.width('100%').height(50).border({width: {bottom: 1},color: '#f6f6f6',})}
}@Component
struct CommentItem {@Prop item: ReplyItemchangeLike = () => {}build() {Row() {Image(this.item.avatar).width(32).height(32).borderRadius(16)Column({ space: 10 }) {Text(this.item.author).fontWeight(600)Text(this.item.content).lineHeight(20).fontSize(14).fontColor("#565656")Row() {Text(`${this.item.time} . IP属地 ${this.item.area}`).fontColor("#c3c4c5").fontSize(12)Row() {Image($r('app.media.like')).width(14).aspectRatio(1).fillColor(this.item.likeFlag ? Color.Red : "#c3c4c5")// "#c3c4c5" 或 red.onClick(() => {this.changeLike()})Text(this.item.likeNum.toString()).fontSize(12).margin({left: 5})}}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.alignItems(HorizontalAlign.Start).layoutWeight(1).padding({left: 15,right: 5})}.justifyContent(FlexAlign.Start).alignItems(VerticalAlign.Top).width('100%').padding(15)}
}@Component
struct ReplyCount {@Propcount: numberbuild() {Text() {Span('回复')Span(`${this.count}`)}.padding(15).fontWeight(700).alignSelf(ItemAlign.Start)}
}@Component
struct ReplyInput {@State inputValue: string = ''addReply = (inputStr: string) => {}build() {Row() {TextInput({ placeholder: '回复', text: $$this.inputValue }).layoutWeight(1).backgroundColor("#f4f5f6").height(40)Text('发布').fontColor("#6ecff0").fontSize(14).margin({left: 10}).onClick(() => {this.addReply(this.inputValue)})}.padding(10).backgroundColor(Color.White).border({width: { top: 1 },color: "#f4f5f6"})}
}
相关文章:
1. 自定义组件基础
相关资源: 📎day10 图片素材.zip 1. 自定义组件基础 概念:在ArkUI中由框架直接提供的称为系统组件 -> Column,Button等,由开发者定义的称为自定义组件 作用:自定义组件可以对 UI和业务逻辑进行封装&…...
连接Sql Server时报错无法通过使用安全套接字层加密与 SQL Server 建立安全连接
文章目录 一. 前言二. 解决方案 方案1方案2 三. 总结 一. 前言 在《数据库原理》这门课的实验上,需要使用SQL Server,然后使用jdbc连接sql server突然报错为:SQLServerException: “Encrypt”属性设置为“true”且 “trustServerCertific…...
python使用httpx_sse调用sse流式接口对响应格式为application/json的错误信息的处理
目录 问题描述方案 问题描述 调用sse流式接口使用httpx_sse的方式 import httpxfrom httpx_sse import connect_sse# 省略无关代码try:with httpx.Client() as client:with connect_sse(client, "GET", url, paramsparam) as event_source:clear_textbox(response_t…...
R 语言科研绘图 --- 散点图-汇总
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...
【语音科学计算器】当前汇率
JSON_MARKER_HORN{“base”:“USD”,“rates”:{“EUR”:0.9758,“JPY”:157.68,“GBP”:0.8190,“CNY”:7.3327,“HKD”:7.7872,“AUD”:1.6260,“CAD”:1.4422,“CHF”:0.9157,“SGD”:1.3714,“KRW”:1473.05,“NZD”:1.7992,“THB”:34.54,“MYR”:4.4930,“PHP”:57.32,“…...
python 进程池的基本使用
Python 进程池:Pool任务调度实现 在现代计算机系统重,处理器核心数量的增加为并行计算提供了强大的硬件基础。Python的 multiprocessing 模块中的进程池(Pool)机制,为开发者提供了 一个高效且易用的并行处理框架。 通…...
初识XML
初识XML <?xml version"1.0" encoding"utf-8" ?> <!--根标签只能有一个--> <!--第一行永远都是 <?xml version"1.0" encoding"utf-8" ?> 前面不允许出现任何其他东西,空格换行等均不行 --> &…...
VisActor/VTable - 快速搭建表格
VTable源于VisActor体系,该体系是从字节跳动大量可视化场景沉淀而来,旨在提供面向叙事的智能可视化解决方案。VisActor包括渲染引擎、可视化语法、数据分析组件、图表组件、表格组件、GIS组件、图可视化组件、智能组件等多个模块,以及周边生态…...
网络安全营运周报
🍅 点击文末小卡片 ,免费获取网络安全全套资料,资料在手,涨薪更快 第三章网络安全基础 一、网络安全概述 1、网络安全现状及安全挑战 网络安全范畴极其广泛,可以说是涉及多方面。 因为计算机病毒层出不穷以及黑客的…...
网络运维学习笔记 017 HCIA-Datacom综合实验01
文章目录 综合实验1实验需求总部特性 分支8分支9 配置一、 基本配置(IP二层VLAN链路聚合)ACC_SWSW-S1SW-S2SW-Ser1SW-CoreSW8SW9DHCPISPGW 二、 单臂路由GW 三、 vlanifSW8SW9 四、 OSPFSW8SW9GW 五、 DHCPDHCPGW 六、 NAT缺省路由GW 七、 HTTPGW 综合实…...
【Viewer.js】vue3封装图片查看器
效果图 需求 点击图片放大可关闭放大的 图片 下载 cnpm in viewerjs状态管理方法 stores/imgSeeStore.js import { defineStore } from pinia export const imgSeeStore defineStore(imgSeeStore, {state: () > ({showImgSee: false,ImgUrl: ,}),getters: {},actions: {…...
hot100_108. 将有序数组转换为二叉搜索树
hot100_108. 将有序数组转换为二叉搜索树 思路 给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。 示例 1: 输入:nums [-10,-3,0,5,9] 输出:[0,-3,9,-10,null,5] 解释&#…...
给小米/红米手机root(工具基本为官方工具)——KernelSU篇
目录 前言准备工作下载刷机包xiaomirom下载刷机包【适用于MIUI和hyperOS】“hyper更新”微信小程序【只适用于hyperOS】 下载KernelSU刷机所需程序和驱动文件 开始刷机设置手机第一种刷机方式【KMI】推荐提取boot或init_boot分区 第二种刷机方式【GKI】不推荐 结语 前言 刷机需…...
(四)趣学设计模式 之 原型模式!
目录 一、 啥是原型模式?二、 为什么要用原型模式?三、 原型模式怎么实现?四、 原型模式的应用场景五、 原型模式的优点和缺点六、 总结 🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式…...
Linux 命令大全完整版(08)
3. 文档编辑命令 joe 功能说明:编辑文本文件。语 法:joe [-asis][-beep][-csmode][-dopadding][-exask][-force][-help][-keepup][-lightoff][-arking][-mid][-nobackups][-nonotice][-nosta][-noxon][-orphan][-backpath<目录>][-columns<…...
【DeepSeek-R1背后的技术】系列十一:RAG原理介绍和本地部署(DeepSeek+RAGFlow构建个人知识库)
【DeepSeek-R1背后的技术】系列博文: 第1篇:混合专家模型(MoE) 第2篇:大模型知识蒸馏(Knowledge Distillation) 第3篇:强化学习(Reinforcement Learning, RL)…...
[python脚本]论文1.(一)CPU/内存数据分析和分组
CPU 收集到的CPU数据,格式如下: 由于这里6个数据为一组来收集latency的数据以及各个分位值的数据,而本质上每一行都是一次完整的测试,因此这里将这个csv文件分为两个文件,第一个是和latency相关的,将6条数…...
git - 从一个远端git库只下载一个文件的方法
文章目录 git - 从一个远端git库只下载一个文件的方法概述笔记写一个bash脚本来自动下载get_github_raw_file_from_url.shreanme_file.shfind_key_value.sh执行命令 END git - 从一个远端git库只下载一个文件的方法 概述 github上有很多大佬上传了电子书库,如果只…...
【C++】list 链表的使用+模拟实现
目录 文章目录 前言 一、list的简介 二、list的使用方法 三、list的模拟实现 1.基本框架: 2.迭代器实现 3.常用接口实现 四、完整代码 总结 前言 本文主要介绍C【STL】容器中的 list,包括接口说明和模拟实现。其中讲解了迭代器功能上的分类&am…...
@Configuration与 @Component的差异
继承关系 Configuration确实可以视为Component的派生注解。从源码层面来看,Configuration本身通过元注解方式标记了Component,这意味着所有被Configuration注解的类本质上也会被Spring识别为组件(Component)。这种设计使得Config…...
c++第一课(基础c)
目录 1.开场白 2.char(字符) 3.字符数组 4.ASCII码 1.开场白 OK,咱们也是亿(不是作者故意的)天没见,话不多说,直接开始! 2.char(字符) 众所不周知&…...
element ui的time时间和table表格
<el-date-picker v-model"value1" align"right" type"date" placeholder"选择日期" value-format"yyyy-MM-dd" change"changeDate"></el-date-picker><el-date-picker v-model"datetime"…...
安装 tensorflow 遇到的问题
Q1: 没发现满足需求的版本 ERROR: Could not find a version that satisfies the requirement keras-nightly~2.5.0.dev (from tensorflow) (from versions: none) ERROR: No matching distribution found for keras-nightly~2.5.0.dev 按照官方文档Installation — TensorFl…...
音视频封装格式:多媒体世界的“容器”与“桥梁”
一、音视频封装格式的核心概念 音视频封装格式(容器)是一种将编码后的视频、音频、字幕等数据按规则整合的文件格式,其本质是多媒体数据容器,核心作用包含: 同步多轨道数据:通过时间戳(PTS/DTS)实现音画同步。组织数据流:统一管理视频流、音频流、字幕流等,并存储元…...
【学习资料】嵌入式人工智能Embedded AI
图片来源: Embedded Artificial Intelligence for Business Purposes | DAC.digital 随着AI在设备端的应用,我们看到越来越多的可穿戴设备出现以及自动驾驶汽车的发展,可以看到嵌入式人工智能是新的发展方向。我为大家介绍嵌入式人工智能的…...
Linux命令大全完整版
1. linux 系统管理命令 adduser 功能说明:新增用户帐号。语 法:adduser补充说明:在 Slackware 中,adduser 指令是个 script 程序,利用交谈的方式取得输入的用户帐号资料,然后再交由真正建立帐号的 use…...
红队内网攻防渗透:内网渗透之内网对抗:实战项目VPC2打靶父子域三层路由某绒免杀下载突破约束委派域控提权
红队内网攻防渗透 实战网络攻防靶场记录1.靶机配置信息讲解2.靶场渗透完整流程2.1 入口点:192.168.139.130(win2008 R2)2.1.1 tomcat后台war包获取权限2.1.2 tomcat使用后门上线CS平台2.1.3 信息收集获取数据库密码2.2 入口点横向:192.168.10.11 (win2012 SQL)2.2.1 SQLs…...
always和assign语法区别
always语句可以带时钟, 也可以不带时钟。 一,assign和always区别 assign 语句使用时不能带时钟。 assign a=1; assign b=2; 在always不带时钟时,逻辑功能和assign完全一致,都是只产生组合逻辑。比较简单的组合逻辑推荐使用assign语句,比较复杂的组合逻辑推荐使用 al…...
深入了解ThreadLocal底层原理-高并发架构
目录 什么是ThreadLocal应用场景需求实现 ThreadLocal核心源码解读Thread 、ThreadLocal、ThreadLocalMap 三者的关系 四大引用-强软弱虚类型ThreadLocal内存泄漏ThreadLocal为什么需要设计成弱引用?并且ThreadLocal用完需要remove呢?原因 什么是ThreadL…...
《AI与NLP:开启元宇宙社交互动新纪元》
在科技飞速发展的当下,元宇宙正从概念逐步走向现实,成为人们关注的焦点。而在元宇宙诸多令人瞩目的特性中,社交互动体验是其核心魅力之一。人工智能(AI)与自然语言处理(NLP)技术的迅猛发展&…...
基于vue和微信小程序的校园自助打印系统(springboot论文源码调试讲解)
第3章 系统设计 3.1系统功能结构设计 本系统的结构分为管理员和用户、店长。本系统的功能结构图如下图3.1所示: 图3.1系统功能结构图 3.2数据库设计 本系统为小程序类的预约平台,所以对信息的安全和稳定要求非常高。为了解决本问题,采用前端…...
电脑开机一段时间就断网,只有重启才能恢复网络(就算插网线都不行),本篇文章直接解决,不要再看别人的垃圾方法啦
下面的是我解决问题的心路历程,不想看的可以直接跳到解决方法上面! 内心思路: w11电脑更新过系统后,我的电脑是常年不关机的,但是一天突然断网,试了很多方法都连不上,重启电脑就会好࿰…...
go-zero学习笔记(五)
api自定义中间件 1. 修改.api文件 syntax"v1"type (GetInfoReq {IDs []string json:"IDs"}GetInfoData {ID string json:"ID"Name string json:"Name"MD5 string json:"md5"Size int64 json:"Size"Up…...
DeepSeek技术全景解析:架构创新与行业差异化竞争力
一、DeepSeek技术体系的核心突破 架构设计:效率与性能的双重革新 Multi-head Latent Attention (MLA):通过将注意力头维度与隐藏层解耦,实现显存占用降低30%的同时支持4096超长上下文窗口。深度优化的MoE架构:结合256个路由专家…...
函数中的形参和实参(吐槽)
def greet_user(user_name):print(f"Hello,{user_name.title()}!")greet_user("zhangsan") 在以上函数中,user_name是形参, 在greet_user("zhangsan")中,值“zhangsan”是实参。这本身没什么大问题。 但是这…...
使用 Promptic 进行对话管理需要具备python技术中的那些编程能力?
使用 Promptic 进行对话管理时,需要掌握一些基础的编程知识和技能,以下是详细说明: 1. Python 编程基础 Promptic 是一个基于 Python 的开发框架,因此需要具备一定的 Python 编程能力,包括: 函数定义与使用:了解如何定义函数、使用参数和返回值。类型注解:熟悉 Python…...
【模块】 ASFF 模块
ASFF (Adaptively Spatial Feature Fusion) 方法针对单次射击物体检测器的特征金字塔中存在的不同特征尺度之间的不一致性问题,提出了一种新颖的数据驱动策略进行金字塔特征融合。通过学习空间上筛选冲突信息的方法,减少了特征之间的不一致性,…...
第二十四周:OpenPose:使用部分亲和场的实时多人2D姿态估计
OpenPose 摘要Abstract文章信息引言方法同时进行检测和关联关键部位检测的置信图PAF使用PAF进行多人解析 关键代码实验结果创新与不足总结 摘要 本篇博客介绍了一种实时多人2D姿态估计框架——OpenPose,其核心思想是通过自底向上的全局关联策略,解决传统…...
ReACT agent和FC agent
rag系列文章目录 文章目录 rag系列文章目录前言一、简介二、示例说明三、对比总结 前言 大模型时代llm正在改变我们的生活,它可以帮助人类写作,写代码,翻译等等,但是llm的知识在训练时被冻结,无法直接使用api接入外部…...
大数据之常用Linux操作
一、 修改文件夹的所有者和所属组均为test用户 chown test:test /opt/文件夹名称二、使用rsync同步文件 rsync主要用于备份和镜像。具有速度快、避免复制相同内容和支持符号链接的优点。 rsync -av $pdir/$fname $user$host:$pdir/$fname三、配置环境变…...
计算机视觉行业洞察--影像行业系列第一期
计算机视觉行业产业链的上下游构成相对清晰,从基础技术研发到具体应用场景的多个环节相对成熟。 以下是我结合VisionChina经历和行业龙头企业对计算机视觉行业产业链上下游的拆解总结。 上下游总结 上游产业链分为软硬件两类,视觉的硬件主要指芯片、…...
自定义实现简版状态机
状态机(State Machine)是一种用于描述系统行为的数学模型,广泛应用于计算机科学、工程和自动化等领域。它通过定义系统的状态、事件和转移来模拟系统的动态行为。 基本概念 状态(State):系统在某一时刻的特…...
【Deepseek】Linux 本地部署 Deepseek
前言 本文介绍在 Linux 系统上部署 Deepseek AI。本文教程是面向所有想体验 AI 玩家的一个简易教程,因此即使是小白也可以轻松完成体验,话不多说立马着手去干。 [注]:笔者使用的系统为 Ubuntu 24.10 1. 关于 ollama Ollama 是一款开源应用…...
JavaScript系列(83)--正则表达式高级详解
JavaScript 正则表达式高级详解 🎯 正则表达式是处理文本的强大工具,掌握其高级特性可以让我们更高效地处理复杂的文本匹配和处理任务。让我们深入探讨JavaScript中正则表达式的高级应用。 正则表达式基础回顾 🌟 💡 小知识&…...
【行业解决方案篇九】【DeepSeek能源勘探:地震波数据智能解释】
第一章 先导课:给地球做CT的百年难题 各位老铁,今天咱们要聊的这个话题绝对硬核——给地球做CT还要用人工智能,这事儿到底有多刺激?想象一下你拿着医院CT报告单,但扫描对象换成深埋地下5000米的油气层,扫描仪换成总长300公里的地震波阵列,这操作难度直接飙升到地狱级。…...
密度提升30%!Intel 18A工艺正式开放代工
快科技2月23日消息,Intel官方网站悄然更新了对于18A(1.8nm级)工艺节点的描述,称已经做好了迎接客户项目的准备,将在今年上半年开始流片,有需求的客户可以随时联系。 Intel宣称,这是在北美地区率先量产的2nm以下工艺节…...
ESP32S3:参考官方提供的led_strip组件使用 SPI + DMA 方式驱动WS2812 RGB灯的实现思路 (实现各个平台移植使用该方式)
目录 引言使用SPI + DMA 方式实现思路分析1. 查看WS2812的datasheet手册2. 根据官方的led_strip组件的方式,自己手把手实现一遍3.完整的程序(实现霓虹灯效果)引言 参考官方提供的led_strip组件使用 SPI + DMA 方式驱动WS2812 RGB灯的实现思路,只有明白实现的思路,方能将其…...
java实现多图合成mp4和视频附件下载
java实现多图合成mp4和视频附件下载 在wutool中,封装了视频处理工具类,基于javacv和ffmpeg库,实现多图合成mp4、视频http附件下载等。 关于wutool wutool是一个java代码片段收集库,针对特定场景提供轻量解决方案,只…...
VulnOSv2 靶机渗透测试
春秋蝉鸣少年归~ arp发现靶机ip地址 发现开放80端口那先去访问一下 问题不大,没有什么有用的提示那就上dirb跑一下 这里给了一个版本号 通过searchsploit搜索了一下没有这个版本的poc/exp去网上搜搜看 这个也试了一下也利用不了回到页面上发现有个website可以点 然后…...
【STM32】内存管理
【STM32】内存管理 文章目录 【STM32】内存管理1、内存管理简介疑问:为啥不用标准的 C 库自带的内存管理算法?2、分块式内存管理(掌握)分配方向分配原理释放原理分块内存管理 管理内存情况 3、内存管理使用(掌握&#…...