【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(下)

系列文章目录

【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(上)
【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(下)
【鸿蒙】HarmonyOS NEXT应用开发快速入门教程之布局篇(上)
【鸿蒙】HarmonyOS NEXT应用开发快速入门教程之布局篇(下)


系列文章目录2

HarmonyOS Next 系列之省市区弹窗选择器实现(一)
HarmonyOS Next 系列之验证码输入组件实现(二)
HarmonyOS Next 系列之底部标签栏TabBar实现(三)
HarmonyOS Next 系列之HTTP请求封装和Token持久化存储(四)
HarmonyOS Next 系列之从手机选择图片或拍照上传功能实现(五)
HarmonyOS Next 系列之可移动悬浮按钮实现(六)
HarmonyOS Next 系列之沉浸式状态实现的多种方式(七)
HarmonyOS Next系列之Echarts图表组件(折线图、柱状图、饼图等)实现(八)
HarmonyOS Next系列之地图组件(Map Kit)使用(九)
HarmonyOS Next系列之半圆环进度条实现(十)
HarmonyOS Next 系列之列表下拉刷新和触底加载更多数据实现(十一)
HarmonyOS Next系列之实现一个左右露出中间大两边小带缩放动画的轮播图(十二)
HarmonyOS Next系列之水波纹动画特效实现(十三)



前言

HarmonyOS NEXT(鸿蒙应用)开发快速入门教程ArkTS语法之装饰器篇(下),基于HarmonyOS NEXT Beta1版本(api 12)讲解。


一、装饰器语法

6.@Builder

自定义组件除了通过@Component申明方式构建,也可以通过函数方式构建。@Builder就是用来装饰函数定义一个轻量级组件(UI元素),因此@Builder装饰器也简称为自定义构建函数,返回一个组件或UI元素。类似vue的render函数,通过函数形式来构建UI元素。

语法:

(1)在组件内创建(按值传递):
@Component
struct Demo {
   @Builder 函数名(参数1:类型,参数2:类型,.....) {
    }
 }

调用:

this.函数名(参数1:类型,参数2:类型,.....)
(2)在组件外创建(按值传递)
@Builder function 函数名(参数1:类型,参数2:类型,.....) {
}

@Component
struct Demo {
 }

调用:

函数名(参数1:类型,参数2:类型,.....)
(3)对象字面量入参形式(引用传递)
class ClassName{
   a:xxxx
   b:xxxx
   c:xxxx
}

@Component
struct Demo {
   @Builder 函数名($$:ClassName) {
    }
 }
 

调用

函数名({
  a:value,
  b:value2,
  c:value3,
  ......
  .......
})

说明:
1、在组件内创建称为私有自定义构建函数,只有当前组件能使用,在组件外创建称为全局自定义构建函数,所有组件都能使用。
2、入参有2种写法,一种像上面示例传递多个形参方式,称为按值传递,另一种把多个参数写成对象字面量形式(只有一个入参)传入,这种叫按引用传递。按值传递只会在首次渲染时候有效,后续修改入参值将无法响应UI变化,而引用传递修改对象字面量属性值会响应UI变化,所以推荐入参全部写成对象字面量形式。

示例1:

按引用传递

class BuilderParams {
  image: Resource | string = ''
  label: string = ''
}

@Entry
@Component
struct Parent {
  @State image: Resource = $r('app.media.home')//图标
  @State label: string = '首页'; //标签

  //自定义构建函数
  @Builder
  childBuilder(params: BuilderParams) {
    Column({ space: 6 }) {
      Image(params.image).width(100)
      Text(params.label).fontSize(16)
    }
  }

  build() {
    Column({ space: 30 }) {
      this.childBuilder({ image: this.image, label: this.label })
      Button('改变图标和标签').onClick(() => {
        this.image = $r('app.media.mine')
        this.label = '我的'
      })
    }.width('100%')
  }
}

运行效果:

请添加图片描述

示例2:

示例1也可以改成 如下直接访问外部状态变量,效果一样

@Entry
@Component
struct Parent {
  @State image: Resource = $r('app.media.home')//图标
  @State label: string = '首页'; //标签

  //自定义构建函数
  @Builder
  childBuilder() {
    Column({ space: 6 }) {
      Image(this.image).width(100)
      Text(this.label).fontSize(16)
    }
  }

  build() {
    Column({ space: 30 }) {
      this.childBuilder()
      Button('改变图标和标签').onClick(() => {
        this.image = $r('app.media.mine')
        this.label = '我的'
      })
    }.width('100%')
  }
}

示例3:

示例1也可以改成全局构建自定义函数,效果一样

class BuilderParams {
  image: Resource | string = ''
  label: string = ''
}

//全局自定义构建函数
@Builder
function childBuilder(params: BuilderParams) {
  Column({ space: 6 }) {
    Image(params.image).width(100)
    Text(params.label).fontSize(16)
  }
}

@Entry
@Component
struct Parent {
  @State image: Resource = $r('app.media.home')//图标
  @State label: string = '首页'; //标签


  build() {
    Column({ space: 30 }) {
      childBuilder({ image: this.image, label: this.label })
      Button('改变图标和标签').onClick(() => {
        this.image = $r('app.media.mine')
        this.label = '我的'
      })
    }.width('100%')
  }
}

@Builder和@Component都可以自定义组件,我们要怎么选择呢?

@Builder主要封装轻量级UI元素,组件参数传递只靠入参或者直接访问外部状态变量,无生命周期函数、无法使用其他装饰器功能,有开发的局限性,而@Component适用于构建复杂逻辑的组件,有自己的生命周期可以使用各种类型装饰器,可实现更复杂的场景。

实际开发根据自己的需求场景去选择,如果2种方式都满足,选择@Builder方式性能会更好。

7.@BuilderParam

@BuilderParam相当于vue的插槽作用(slot),先在子组件调用@BuilderParam修饰的函数占位,具体渲染内容从父组件传入,通过上面介绍我们知道@Builder是用来自定义一个组件,父组件通过@Builder生成的组件当参数传入子组件,子组件拿到@Builder组件在占位地方填充渲染。所以可以简单理解和记忆@BuilderParam是子组件的入参(函数类型),入参类型是@Builder函数。

语法:

子组件:

 @Builder A() {};
 @BuilderParam B:类型=this.A
 build(){
    ...
    ...
    //插槽占位
    this.B()
 }

ps:@BuilderParam装饰的函数初始值必须设置且只能是@Builder装饰的函数

父组件:

//自定义插槽内容
@Builder C(){
}
build(){
    ...
    ...
   child({B:this.C})
 }

如果只有一个插槽,父组件可以省略自定义构建函数,直接在子组件内嵌套写布局,此方式叫尾随闭包初始化组件,相当于vue中的默认插槽场景

父组件

build(){
    ...
    ...
   child(){
    //自定义插槽内容
     ......
     ......
    }
 }

示例1:

只有一个@BuilderParam



@Entry
@Component
struct Parent {

  @Builder customBuild(){
    //插槽内容
    Button('插槽按钮')
  }

  build() {
    Column({ space: 30 }) {
      Child({customBuilderParam:this.customBuild})
    }.width('100%')
  }
}

@Component
struct  Child{
  @Builder customBuilder() {};
  @BuilderParam customBuilderParam:()=>void=this.customBuilder
  build() {
    Column(){
        Text('child组件')
         //插槽占位
        this.customBuilderParam()
    }
  }
}

示例2:

只有一个@BuilderParam可以写成尾随闭包方式:



@Entry
@Component
struct Parent {


  build() {
    Column({ space: 30 }) {
      Child(){
        //插槽内容
        Button('插槽按钮')
      }
    }.width('100%')
  }
}

@Component
struct  Child{
  @Builder customBuilder() {};
  @BuilderParam customBuilderParam:()=>void=this.customBuilder
  build() {
    Column(){
        Text('child组件')
         //插槽占位
        this.customBuilderParam()
    }
  }
}

运行效果:
在这里插入图片描述

8.@Styles

@Styles用于装饰函数,函数内封装公共通用属性或通用事件,提供给需要的组件复用。类似scss里面的@mixin的作用

语法:

和@Builder类似@Styles可以定义在组件内或全局,在全局定义时需在方法名前面添加function关键字,组件内定义时则不需要添加function关键字

(1)全局:

@Styles function functionName() { ... }

调用:

组件(){

}.functionName()

(2)组件内:

// 在组件内
@Component
struct Demo{
  @Styles functionName() {
    .height(100)
  }
}

调用:

组件(){

}.functionName()

使用限制规则:
1、@Styles仅支持通用属性通用事件
2、@Styles修饰的方法不支持入参
3、只能在当前文件内使用,不支持export导出给其他文件使用
4、组件内的@Styles优先级高于全局@Style

示例1:

全局定义

//全局公共样式
@Styles
function globalStyle() {
  .width('100%')
  .height(100)
  .padding(10)
  .backgroundColor(Color.Green)
  .border({ width: 1, color: Color.Black, style: BorderStyle.Solid })
  .borderRadius(10)
}


@Entry
@Component
struct Parent {
  build() {
    Column({ space: 30 }) {
      Text('text1').fontColor(Color.White).globalStyle()
      Text('text2').fontColor(Color.White).globalStyle()
      Text('text3').fontColor(Color.White).globalStyle()
    }.width('100%')
    .padding(20)
  }
}


运行效果:
在这里插入图片描述

示例2:

组件内定义

@Entry
@Component
struct Demo {
  @State bgColor: ResourceColor = Color.Yellow //背景色
  @State iHeight: number = 100 //高度

  //组件内样式
  @Styles
  globalStyle() {
    .width('100%')
    .height(this.iHeight)
    .border({ width: 1, color: Color.Black, style: BorderStyle.Solid })
    .backgroundColor(this.bgColor)
    .borderRadius(10)
    .onClick(() => {
      //高度+10
      this.iHeight += 10
    })
  }

  //按钮样式
  @Styles
  custButtonStyle(){
    .width(200)
    .height(80)
    .onClick(() => {
      //切换背景色
      this.bgColor = this.bgColor === Color.Pink ? Color.Yellow : Color.Pink
    })
  }

  build() {
    Column({ space: 30 }) {
      Column() {
        Text('点击高度+10')
      }
      .justifyContent(FlexAlign.Center)
      .globalStyle()

      Button('改变颜色').custButtonStyle()
    }.width('100%')
    .padding(20)
  }
}


运行效果:

请添加图片描述

从上述示例看,尽管@Styles装饰的函数不能写入参数,但是我们可以把@Style定义在组件内,并通过this去访问组件内的状态变量,达到动态传参的效果。

9.@Extend

@Extend跟@Styles功能类似,都是用来抽离组件属性和事件,@Extend针对的是某个具体系统组件单独设置,单独使用,而@Styles所有组件通用。

语法:

@Extend(系统组件名) function functionName(入参可选) { ... }

使用规则:
1、和@Styles不同,@Extend仅支持在全局定义,不支持在组件内部定义。
2、和@Styles不同,@Extend支持封装指定组件的私有属性、私有事件和自身定义的全局方法。
3、和@Styles不同,@Extend装饰的方法支持参数,开发者可以在调用时传递参数,调用遵循TS方法传值调用。
4、只支持扩展原生组件,不支持自定义组件

示例:

/**
 * 自定义按钮样式
 * @param height:按钮高
 * @param width :按钮宽
 * @param backgourndColor :按钮背景色
 * @param type //按钮样式
 */
@Extend(Button)
function customButton(height: number, width: number, backgourndColor: ResourceColor, type: ButtonType) {
  .height(height)
  .width(width)
  .type(type)
  .backgroundColor(backgourndColor)
  .fontColor(Color.White)
  .fontSize(14)
}

@Entry
@Component
struct Demo {
  build() {
    Column({ space: 30 }) {
      Button('提交').customButton(50, 300, Color.Blue, ButtonType.Capsule)
      Button('删除').customButton(60, 60, Color.Red, ButtonType.Circle)
    }.width('100%')
    .padding(20)
  }
}


运行效果:

在这里插入图片描述


从以下开始的装饰器都是跟数据本地存储相关

10.@LocalStorageProp和@LocalStorageLink

@LocalStorageProp和@LocalStorageLink是页面级的UI状态存储,通过@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。

简单说每个注册的路由页面(@Entry装饰的组件)单独享有和管理一个LocalStorage实例,包括该页面引入的子孙组件共用同一个LocalStorage来存取数据。每个页面单独使用互不影响,默认不能相互访问,不能覆盖存储,关闭页面LocalStorage实例被系统回收数据被清除,类似web sessionStorage。

@LocalStorageProp(key)是和LocalStorage实例中key对应的属性建立单向数据同步。LocalStorage实例中key数据改变,@LocalStorageProp(key)装饰的变量也将跟着改变并能响应UI变化。
@LocalStorageLink(key)是和LocalStorage实例中key对应的属性建立双向数据同步,两者区别跟@Prop和@Link一样,一个数据单向流动一个双向绑定,其他作用一致。

语法:

创建LocalStorage实例:

let storage: LocalStorage = new LocalStorage();
storage.setOrCreate(key,value)//创建属性
storage.setOrCreate(key2,value2)//创建属性
storage.setOrCreate(key3,value3)//创建属性
...
...
@Entry(storage)//页面绑定LocalStorage
@Component
struct Parent {

}

组件调用:

@Entry(storage)
@Component
struct Parent {
    @LocalStorageProp(key) 变量:类型=默认值 
    @LocalStorageLink(key) 变量:类型=默认值 
}

修改值:
分两种方式,如果是@LocalStorageLink(key)双向绑定只要修改@LocalStorageLink绑定变量即可。另一种通过调用LocalStorage实例的方法修改

storage.set(key,value)

也可以通过storage获取值:

storage.get(key)

判断key是否存在

storage.has(key)

删除LocalStorage中所有的属性

storage.clear()

更多的api可以查看LocalStorage9+文档

示例


//城市地区对象
class AddressProp{
  city:string
  area:string

  constructor(city:string,area:string) {
    this.city=city
    this.area=area
  }
}

let storage: LocalStorage = new LocalStorage();
storage.setOrCreate('name','李磊')
storage.setOrCreate('age',0)
storage.setOrCreate('address',new AddressProp('厦门市','思明区'))


@Entry(storage)
@Component
struct Parent {
   @LocalStorageProp('name') name:string='' //姓名
   @LocalStorageLink('age') age:number=0 //年龄
   @LocalStorageLink('address') address:AddressProp=new AddressProp('','') //地址

  build() {
    Column({ space: 20 }) {
      Text(`姓名:${this.name}`)
      Text(`年龄:${this.age}`)
      //子组件
      Child()
      Button('修改姓名').onClick(()=>{
         storage.set('name','小明')
      })
      Button('修改年龄').onClick(()=>{
        this.age=20
      })
      Button('修改城市').onClick(()=>{
         this.address.city='福州市'
      })
      Button('修改地区').onClick(()=>{
        this.address.area='鼓楼区'
      })
      Button('修改城市+地区').onClick(()=>{
        storage.set('address',new AddressProp('泉州市','丰泽区'))
      })
    }.padding(20).alignItems(HorizontalAlign.Start)
  }
}

//子组件
@Component
struct Child {
  @LocalStorageLink('address') address:AddressProp=new AddressProp('','')
  build() {
    Column({space:20}){
      Text(`地址:${this.address.city}${this.address.area}`)
    }

  }
}


运行效果:
请添加图片描述

11.@StorageProp和@StorageLink

@StorageProp和@StorageLink是应用全局的UI状态存储,与之对应的是全局静态类AppStorage。和进程绑定,同一进程下的所有页面共用,生命周期和LocalStorage类似,当关闭应用将被回收,如果想要数据持久化本地保存需要结合PersistentStorage使用。

语法:

@Entry
@Component
struct Parent {
    @StorageLink(key) 变量:类型=默认值 
    @StorageProp(key) 变量:类型=默认值 
}

无需创建实例,直接使用

AppStorage和LocalStorage实例内置方法一样

AppStorage.setOrCreate(key,value) //创建属性
AppStorage.set(key,value) //设置值
AppStorage.get(key) //获取值
AppStorage.has(key)//判断是否存在属性
AppStorage.clear()//清除所有属性

示例:

Index.ets (第一个页面)

import { router } from '@kit.ArkUI'

//商品对象类型
export class GoodsProp{
  name:string
  price:number
  image:Resource|string

  constructor(name:string,price:number,image:Resource|string) {
    this.name=name
    this.price=price
    this.image=image
  }
}
//创建属性并设置初始值
AppStorage.setOrCreate('category','水果')
AppStorage.setOrCreate('place','海南')
AppStorage.setOrCreate('goods',new GoodsProp('榴莲',200,'https://img0.baidu.com/it/u=211945568,1824960805&fm=253&fmt=auto&app=120&f=JPEG?w=399&h=399'))


@Entry
@Component
struct Demo {
  @StorageProp('category') category:string=''
  @StorageLink('place') place:string=''
  @StorageLink('goods') goods:GoodsProp=new GoodsProp('',0,'')
  build() {
    Column({ space: 20 }) {
      Text(`类别:${this.category}`)
      Text(`产地:${this.place}`)
      Text(`名称:${this.goods.name}`)
      Text(`价格:¥${this.goods.price}`)
      Image(this.goods.image).width(100)
      Button('修改商品信息').onClick(()=>{
        //修改商品信息
        AppStorage.set('category','饮料')
        this.place='福建'
        this.goods=new GoodsProp('雪津啤酒',10,'https://img1.baidu.com/it/u=3892212987,1352825001&fm=253&fmt=auto&app=138&f=JPEG?w=283&h=800')

      })
      Button('跳转下一页').onClick(()=>{
        router.pushUrl({url:'pages/Second'})
      })

    }.padding(20).alignItems(HorizontalAlign.Start)
  }
}

Second.ets(第二个页面)

import {GoodsProp} from './Index'
import { router } from '@kit.ArkUI'

@Entry
@Component
struct  Second{
  @StorageLink('category') category:string=''
  @StorageProp('place') place:string=''
  @StorageProp('goods') goods:GoodsProp=new GoodsProp('',0,'')
  build() {
    Column({ space: 20 }) {
      Text(`类别:${this.category}`)
      Text(`产地:${this.place}`)
      Text(`名称:${this.goods.name}`)
      Text(`价格:¥${this.goods.price}`)
      Image(this.goods.image).width(100)
      Button('修改商品信息').onClick(()=>{
        //修改商品信息
        this.category="食物"
        AppStorage.set('place','北京')
        AppStorage.set('goods',new GoodsProp('北京烤鸭',50,'https://img2.baidu.com/it/u=543904622,2448295859&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=667'))


      })
      Button('返回上一页').onClick(()=>{
        router.back()
      })

    }.padding(20).alignItems(HorizontalAlign.Start)
  }
}

运行效果:

请添加图片描述

想要持久化数据只需页面开头用PersistentStorage.persistProp(key,value)初始化属性即可

例如上个例子改为:
Index.ets:

import { router } from '@kit.ArkUI'



//商品对象类型
export class GoodsProp{
  name:string
  price:number
  image:Resource|string

  constructor(name:string,price:number,image:Resource|string) {
    this.name=name
    this.price=price
    this.image=image
  }
}

PersistentStorage.persistProp('category','水果')
PersistentStorage.persistProp('place','海南')
PersistentStorage.persistProp('goods',new GoodsProp('榴莲',200,'https://img0.baidu.com/it/u=211945568,1824960805&fm=253&fmt=auto&app=120&f=JPEG?w=399&h=399'))


@Entry
@Component
struct Demo {
  @StorageProp('category') category:string=''
  @StorageLink('place') place:string=''
  @StorageLink('goods') goods:GoodsProp=new GoodsProp('',0,'')
  build() {
    Column({ space: 20 }) {
      Text(`类别:${this.category}`)
      Text(`产地:${this.place}`)
      Text(`名称:${this.goods.name}`)
      Text(`价格:¥${this.goods.price}`)
      Image(this.goods.image).width(100)
      Button('修改商品信息').onClick(()=>{
        //修改商品信息
        AppStorage.set('category','饮料')
        this.place='福建'
        this.goods=new GoodsProp('雪津啤酒',10,'https://img1.baidu.com/it/u=3892212987,1352825001&fm=253&fmt=auto&app=138&f=JPEG?w=283&h=800')

      })
      Button('跳转下一页').onClick(()=>{
        router.pushUrl({url:'pages/Second'})
      })

    }.padding(20).alignItems(HorizontalAlign.Start)
  }
}




二、 总结

至此11个常用的装饰器已经讲完了,完全掌握你将能从容应对大部分的需求场景开发。v2版本官方也在加速推进开发中,我们完全能期待下一个版本正式发布,因为v2版本又会新增多个实用装饰器,改进v1版本不足将带来更加便捷的开发方式。例如有类似vue中的compute计算属性装饰器、类似@click装饰事件的装饰器,以及对对象深度监听支持等。

<think>嗯,用户想了解如何使用鸿蒙NextArkTS进行开发,需要教程或示例代码。首先,我得先确认用户的需求。鸿蒙Next应该是指HarmonyOS NEXT,而ArkTS鸿蒙主推的开发语言,基于TypeScript。用户可能已经有一定的开发经验,但需要入门指南和示例。 根据提供的引用,特别是引用[2]和[3],里面提到了快速入门教程和核心知识点,比如ArkTS语法装饰器、布局篇等。我应该整理这些信息,给出一个结构化的学习路径。可能需要分步骤讲解,从环境搭建到基础语法,再到UI组件和布局,最后是实战示例。 用户可能需要具体的代码示例,比如如何创建一个简单的页面,使用ArkTS装饰器,比如@Entry和@Component。引用[5]提到了子组件和孙组件的导入,这可能和组件化开发有关,可以结合这点给出组件通信的示例。 另外,引用[4]提到for-of循环,这在ArkTS中应该同样适用,可以用来遍历数据生成列表。可能需要展示如何在UI中使用循环结构。 还要注意引用[1]中的三个关键步骤:基础UI设计、代码开发和应用上线。这可以作为回答的框架,分步骤介绍。同时,引用[3]中的内容可能涉及更高级的主题,如Stage模型、多端部署,但用户当前可能需要基础教程,所以先聚焦在入门部分。 需要确保回答符合系统级指令,使用正确的中文和LaTeX格式。不过用户的问题主要是开发教程,可能涉及数学公式较少,但如果有性能分析或算法部分,可能需要用LaTeX,但当前可能不需要。 最后,生成相关问题时要围绕鸿蒙NextArkTS开发,比如性能优化、组件通信、布局技巧等,参考已有的引用内容来提出相关问题。</think>### 鸿蒙NextArkTS开发入门指南 以下是基于HarmonyOS NEXTArkTS开发核心步骤与示例代码,按照官方推荐路径整理: #### 一、环境搭建与基础语法 1. **开发工具**:安装DevEco Studio 4.0及以上版本,支持ArkTS语言 2. **ArkTS基础语法**: ```typescript // 声明式UI组件 @Component struct HelloWorld { @State message: string = 'Hello HarmonyOS' build() { Row() { Text(this.message) .fontSize(20) .onClick(() => { this.message = 'Clicked!' }) } } } ``` 此代码展示了状态管理装饰器`@State`和组件点击事件绑定[^3] #### 二、核心UI开发 1. **布局实现**(引用[2]布局篇教程): ```typescript @Entry @Component struct Index { build() { Column() { // 纵向布局 Text('Header').fontSize(24) List() { // 列表组件 ForEach([1,2,3], (item: number) => { ListItem() { Text(`Item ${item}`) } }) } .layoutWeight(1) // 权重布局 } .height('100%') } } ``` 使用`ForEach`实现动态列表渲染(参考引用[4]的遍历语法) #### 三、组件通信实践 父子组件传参示例(参考引用[5]的组件结构): ```typescript // 父组件 @Component struct Parent { @State data: string = 'From Parent' build() { Column() { Child({ receiveData: this.data }) } } } // 子组件 @Component struct Child { @Prop receiveData: string build() { Text(this.receiveData) } } ``` 通过`@Prop`装饰器实现单向数据流 #### 四、进阶开发技巧 1. **Stage模型应用**(引用[3]核心知识): ```typescript // 生命周期管理 import Ability from '@ohos.app.ability.UIAbility' export default class EntryAbility extends UIAbility { onCreate(want, launchParam) { // 应用初始化 } onWindowStageCreate(windowStage) { // 窗口创建 windowStage.loadContent("pages/Index") } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pixle0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值