HarmonyOS 鸿蒙开发 学习笔记(一)

1 篇文章 0 订阅
1 篇文章 0 订阅
  /*
  * arkTS 书写的文件 .ets 文件
  *
  * 装饰器
  * @Entry -- 独立的页面
  * @Component + struct Index--为自定义组件名字 { } -- 组件 可复用的UI单元
  * @State -- 标记为 响应式 变量
  *
  * build() { UI描述 其内部 以声明式的方式描述UI结构}
  * 内置组件 -- 1 属性方法  2 事件方法
  *
  * 尺寸单位
  * 100     数字   不写单位 默认 -- vp -- 虚拟像素 自适应
  * '100%'  字符串
  *
  * 配置权限
  * module.json5 文件中
  * requestPermissions:[{ },{ }]
  *
  * zh_CN目录 为 限定词目录 即 与系统匹配的限定目录,  base 为默认目录 必须要有
  * $r('app.string.XX') -- 读取 string 信息时  先根据 系统环境 读取 en_US 或者 zh_CN 等 语言中的string
  * 没有找到 才会 读取 base--element 中 的 string
  *
  * Image --
  * Image($r('app.media.xxx'))   读取 media    中文件 -- 无需后缀
  * Image($rawfile('xxx.png'))   读取 rawfile  中文件 -- 需要后缀
  * interpolation 图片插值 -- 放大后 相对清晰点
  *
  * Text --
  * Text("string")  Text($r('app.string.XX'))
  *
  * Button --
  * slider --
  * Blank() -- 占用剩余空间 没有UI显示
  * Stack -- 堆叠 后面 叠在 前面 依次入栈  配合 position 实现定位效果
  *
  * Column --
  * Row --
  * space -- 间距
  * 主轴 -- justifyContent -- FlexAlign                                   默认 start
  * 副轴 -- alignItems, Row -- VerticalAlign, Column -- HorizontalAlign   默认 center
  *
  * ForEach -- v-for
  * ForEach(
  *   [数组],
  *   (item,index?) => { 这里生成UI },
  *   (item: any, index?: number) => { string }   数据 变化是否重新渲染  默认是 item+index 的拼接
  * )
  *
  * if-else -- v-if
  * if( true ){ 这里生成UI }
  *   else{ 这里生成UI }
  *
  * List   List是容器  List内部跟着ListItem
  * 必须设置高度才可以滚动  一般占据剩余空间, layoutWeight(1)
  * 超出屏幕  会滚动 , 对比 -- 单纯的ForEach超出会隐藏
  * 可以纵向  横向 listDirection(Axis.Vertical)
  *
  * ListItem
  * ListItem不是容器,算是一个标记, 所以内部需要写 UI组件
  * 内部只能有一个根组件
  *
  * List(){ ForEach-->
  *   ListItem(){ UI }
  * }
  *
  * @Component--自定义组件 -- 通用组件的封装
  * 位置 -- components --Header.ets -- export struct Header{}
  * import {Header} from '../components/Header'
  *
  * @Component
  * struct Header {
  *   private title: String
  *   build(){ }
  * }
  *
  * 引用 -- Header( {title:"数据传递"} )
  *
  * @Builder--自定义构建函数  -- 当前页面自用组件的封装       作用相当于组件所以函数名大写
  * 1 全局  在 struct 之外写
  * @Builder function ItemCard(item:Item){ UI }
  * 使用 -- ItemCard(item)
  *
  * 2 局部 -- 组件内部 -- 没有 function
  * @Builder ItemCard(item:Item){ UI }
  * 使用 -- this.ItemCard(item)
  *
  * @Styles--自定义样式函数  --  小写开头  内部直接 .调用属性方法
  * 1 全局 在 struct 之外写
  * @Styles function fillScreen(){
  *   .width("100%")
  *   .padding(20)
  * }
  * 使用 -- Text().fillScreen()
  *
  * 2 局部 -- 组件内部 -- 没有 function
  * @Styles fillScreen(){
  *   .width("100%")
  *   .padding(20)
  * }
  * 使用 -- Text().fillScreen()
  *
  * @Extend(组件) -- 继承模式 必须写在全局
  * @Styles -- 全局样式 内 .css 必须是全局组件才有的样式 比如 宽 高 内外边距
  * 像字体大小 这样的 部分组件的独有的样式 要想定义在全局 使用 @Extend(Text)
  * @Extend(Text) 必须写在全局 内接非通用的组件 比如 Text
  * @Extend(Text) function TextPrice(){
  *   .fontSize(36)
  * }
  *
  * Text().TextPrice()
  *
  * 总结
  * 全局 必须添加 function
  * Styles 可以 用在 全局 或者 局部  必须是通用样式
  * Extend(XXX)   只能用在全局      支持所有的样式
  *
  * 状态管理 -- 装饰器
  * @State
  * 1 必须初始化
  * 2 Object Array string number boolean enum class  == 不可以的类型 -> any 联合类型 复杂的数据类型
  * 3 不支持 deep 的数据嵌套更改方式  比如 { { a:1 } ,b:2 } a改变无法触发视图更新 b可以
  *   此状态下 别的数据触发视图更新 也会带动a对应的视图更新  因为a已经变了 整个页面在更新
  *
  * 父子组件 之间的数据同步
  * @Prop  父传子  单向    父@State  子@Prop   父修改 子同步修改,子修改 父不变(父的数据拷贝到子,所以子不会修改父的原始数据)
  * @Link  父传子  双向    父@State  子@Link   父修改 子同步修改,子修改 父同步(父的数据引用给到子,二者操作的都是原始数据)
  *
  * 1 二者不能初始化 其值由父组件决定
  * 2 Link 传值时 传递的是 数据的引用 ({ data:$data }) -- $
  * 3 Prop 只能是简单数据类型 string number boolean enum
  *   Props 父是对象 , 子 是 对象的一个简单属性 可以 this.obj.name 传递的也是简单数据
  *   数组 对象 等 复杂类型都不可以传递
  * 4 Link 与 State 差不多基本都可以 但是不支持 deep
  *
  * @Provide
  * @Consume
  * 二者 相当于 父组件 与 后代组件的 双向数据传递
  * 父 与 后代 没有调用 关系  所以 是隐式传递  (provide  inject) 与儿子有调用关系 也不需要传递
  * 父 @Provide data = 1
  * 子 @Consume date :number
  * 使用的地方 直接  this.data  即可 没有传递 直接使用
  *
  * @Observed   --
  * @ObjectLink --
  * 对象嵌套对象,数组中为对象 类似支持数据 deep
  * 1 定义的数据 添加 @Observed 装饰器
  * 2 使用嵌套数据的组件 在 定义数据时 添加 @ObjectLink
  *   无法添加就将在组件抽取为独立的组件 组件中定义所需要的 数据,添加 @ObjectLink 修饰
  *
  * 总数据 @Observed 修饰, 使用的数据处 @ObjectLink 修饰
  *
  * 子组件要触发 父组件中的方法
  * 子组件中定义参数 为 taskHandle:()=>void
  * 父组件传递函数
  * taskChange(){
  *   this.data = data
  * }
  * Sun({ data,taskHandle:taskChange.bind(this) })
  * 因为函数中 有this 父组件中调用函数 this指向父组件
  * 该函数传递到子组件后被调用,this指向 子组件 数据就会找不到
  * 所以父组件在传递函数时需要绑定 this -- bind(this)
  *
  * 页面路由 -- router
  * 页面栈  先入后出 ,最大容量为 32 个 页面,使用 router.clean() 可以清空页面栈,释放内存
  * 1 router.pushUrl()      当前页入栈,新页面在栈顶  可以 router.back() 返回当前页,新页面出栈
  * 2 router.replaceUrl()   新页面替换当前页,当前页销毁释放资源,无法返回当前页
  *
  * Router页面有两种实例模式
  * 1 Standard 标准实例模式 默认模式 每一次都是重新创建 入栈
  * 2 Single   单利模式,如果栈中已经存在该页面,该页面就会被移到栈顶 重新加载显示
  *
  * 二者根据场景结合使用
  *
  * 新页面需要在 src/main/resources/base/profile/main_pages.json  注册
  * 创建 page 时会自动注册
  *
 /* import router from '@ohos.router'
  router.pushUrl({
  url: "", // 目标页路径
  params: { id: 1 } // 传递参数 可选
},
router.RouterMode.Single, // 页面模式
err => {
  */
  /*
     异常回调
     code
     100001 内部错误可能是渲染失败
     100002 路由地址错误
     100003 页面栈超过32个
     /*
  }
  )
  const params = router.getParams()
 
  // 返回前的弹窗提示信息
  router.showAlertBeforeBackPage({ message: "返回前的弹窗提示信息" })
  // 返回上一页
  router.back()
  // 返回指定页
  router.back({ url: "", params: {} })
  *
  *
  * 页面内动画
  * 属性动画 与 显式动画 基本实现的是一种过渡效果 渐变效果
  *
  * 属性动画 .animation( { duration:1000,curve:Curve.EaseInout } ) 参考文档
  * 该属性动画要放在最后面,其前面的属性发生变化才会触发动画 -- 属性值定义为 状态值
  * 支持 width height Opacity backgroundColor Scale rotate translate 实现渐变效果
  * 1 定义支持动画的 样式属性
  * 2 后面添加 animation属性
  * 3 触发之前定义的样式属性 --> 改变State
  *
  * 显式动画 animateTo()函数
  * animateTo(
  * { duration:1000,curve:Curve.EaseInout },参考文档
  * ()=>{ 直接修改 跟样式关联的 @State的值 改变谁 谁对应的样式做动画 -->this.x +=1 }
  * )
  *
  *
  * 组件内转场动画 基本实现的是组件 插入 , 移除 时的过渡动画
  * .transition({
  *  opacity:0  入场的起点和离场的重点 (根据if 逻辑判断是 入场还是离场)
  *  rotate:{angle:0}
  *  scale:{x:0,y:0}
  * })
  *
  * 搭配 if(this.isShow){
  *   Text().transition()
  * }
  * animateTo({},()=>this.isShow = true)
  * 必须使用 animateTo 改变 isShow 才会触发转场动画
  *
  * 页面间的动画
  * 共享元素转场动画
  * .sharedTransition(id: string, options?: sharedTransitionOptions)
  * SharedTransitionEffectType 设置 Exchange(默认) 还是 Static
  * Exchange类型的共享元素转场 -- 两个页面中,配置为相同id的组件,适用于两个页面间相同元素的衔接
  * Static类型的共享元素转场   -- 一个页面中有Static的共享元素,通常用于页面跳转时,标题逐渐出现或隐藏的场景
  *
  * PageA
  * Image($r('app.media.mountain')).width(50).height(50)
                                                                          *      .sharedTransition('sharedImage1', { duration: 1000, curve: Curve.Linear })
  * PageB
  * Image($r('app.media.mountain')).width(150).height(150)
  *      .sharedTransition('sharedImage1', { duration: 500, curve: Curve.Linear })
  *
  * Text("SharedTransition test page")
  *     .sharedTransition('text', { duration: 500, curve: Curve.Linear, type: SharedTransitionEffectType.Static })
  * 总结:Exchange 一个组件 存在于俩页面中,设置相同的 sharedTransition(id: string) 页面跳转时 该组件做动画
  *     Static   一个组件,只存在于一个页面中,当跳转到该页面或者离开时  该组件做动画 一般显示或隐藏
  *
  * 页面转场动画
  * 两个页面间发生跳转,一个页面消失,另一个页面出现,这时可以配置各自页面的页面转场参数实现自定义的页面转场效果
  *
  // page A
  pageTransition() {
  // 定义页面进入时的效果,从左侧滑入,时长为1200ms,无论页面栈发生push还是pop操作均可生效
  PageTransitionEnter({ type: RouteType.None, duration: 1200 })
    .slide(SlideEffect.Left)
  // 定义页面退出时的效果,向左侧滑出,时长为1000ms,无论页面栈发生push还是pop操作均可生效
  PageTransitionExit({ type: RouteType.None, duration: 1000 })
    .slide(SlideEffect.Left)
  }
  *
  * duration: 0 -- 禁用某页面的页面转场
  * type: RouteType.Push
  * type: RouteType.Pop
  * A B 页面
  * 新增 右侧滑入 左侧滑出
  * Push -- PageTransitionEnter(.slide(SlideEffect.Right)) -- PageTransitionExit(slide(SlideEffect.Left))
  * 返回 左侧滑入 右侧滑出
  * Pop -- PageTransitionEnter(.slide(SlideEffect.Left)) -- PageTransitionExit(slide(SlideEffect.Right))
  * 四个
  *
  *
  * Stage 模型  舞台模型
  * Ability Module (能力模块) -->打包--> HAP(Entry Feature 多个HAP)--Bundle--APP
  * library Module (共享模块) -->打包--> HSP
  *
  * 配置文件
  * 1 全局配置文件 AppScope--app.json5
  * bundleName -- 唯一标识 -- 包名
  * icon--label 对应的是设置里面--应用管理的 图标与描述
  *
  * 2 模块配置文件 entry--configuration--module.json5
  * requestPermissions -- 权限添加
  * type -- entry 入口类型, feature 功能类型, shared 共享类型
  * mainElement -- 该模块的入口文件
  * deviceTypes 每个模块对应的设备类型
  * deliveryWithInstall 该模块是否跟随APP 安装(entry 肯定安装 feature 不一定)
  * pages 该模块下所有页面的配置文件
  * abilities -- 创建的所有模块
  * abilities -- 内配置的 icon--label 对应的是桌面的 图标与描述
  * skills 该模块的 功能 根跳转有关系
  * 入口就是 entities -- entity.system.home
  *         actions -- action.system.home
  *
  * UIAbility 生命周期
  * onCreate --> onWindowStageCreate --> onForeground (visible , active) --> (inActive inVisible)
  * --> onBackground --> onWindowStageDestroy --> onDestroy
  *
  * onWindowStageCreate --> (visible , active, inActive inVisible)
  *
  * windowStage.loadContent('pages/Index'  默认加载的页面
  *
  * Ability 内 页面与组件 的 生命周期
  * 页面  只有带 @Entry 的组件才可以用
  * 展示页面    (onPageShow)
  * 返回       (onBackPress)
  * 隐藏页面    (onPageHide)
  *
  * 组件 -- 所有组件
  * 1 创建组件实例 --> aboutToAppear(数据初始化) -->
  * 2 执行 build 函数
  *
  * 销毁组件 (之前 aboutToDisappear)
  *
  * 页面销毁 其中所有组件必然销毁
  * 页面不销毁 其中 组件可以单独创建或者销毁(if)
  *
  * 这里页面也是组件 只不过是 入口组件
  * 页面
  * aboutToAppear --> onPageShow --> onPageHide
  * aboutToAppear --> onPageShow --> aboutToDisappear
  * 组件
  * aboutToAppear --> aboutToDisappear
  *
  *
  * 感觉类似 Android 的 Activity 嵌套 Fragment
  *
  *
  * UIAbility 启动模式  针对同一个 Ability 的实例
  * "launchType": "singleton",
  *
  * 1 Singleton 每次启动UIAbility 任务列表中只存在唯一实例 (默认)
  * home键 生命周期 不重复走 onCreate , onForeground -- onBackground 二者切换 ,任务栈只有一个实例
  *
  * 2 Standard  每次启动UIAbility 都会创建新实例,旧实例 也在
  *  home键 生命周期 重复走 onCreate -- onForeground -- onBackground ,任务栈有多个 实例共存
  *
  * 3 multition 每次启动UIAbility 都会创建新实例,旧实例 不在
  * home键 生命周期 重复走 onCreate -- onForeground -- onBackground ,任务栈只有一个实例
  *
  * 4 Specified 每个UIAbility 实例 设置一个 Key,启动 UIAbility 时 需要指定 key ,有 key 对应的实例直接使用,没有新创建
  *
  * 需要新的 ability 实现某个功能,比如文档编辑器
  * 点击 文档1 到 新界面 编辑1 ,点击 文档2 到新界面编辑 2,反复操作时
  * Singleton 不重走 onCreate  无法刷新数据
  * Standard 重复创建
  * multition 重复创建
  * Specified 已经存在 不创建 没有 才创建
  *
  * 一个 ability 调起 另一个 ability
  * 1
  * context = getContext(this) as common.UIAbilityContext
  *
  * let want = {
    deviceId: '', // deviceId为空表示本设备
    bundleName: 'com.example.myapplication', // 可以跨应用
    abilityName: 'FuncAbility', // 名字
    moduleName: 'module1', // 模块名 非必选
    parameters: { // 自定义信息 key
        instanceKey: getInstance(生成不同key),
    },
}
  *
  * // context为调用方UIAbility的AbilityContext
    this.context.startAbility(want)
    .then(() => {
    // ...
    }).catch((err) => {
    // ...
    })
  *
  * 2 目标ability
  * import AbilityStage from '@ohos.app.ability.AbilityStage';

   export default class MyAbilityStage extends AbilityStage {
    onAcceptWant(want): string {
        // 在被调用方的AbilityStage中,针对启动模式为specified的UIAbility返回一个UIAbility实例对应的一个Key值
        // 当前示例指的是module1 Module的FuncAbility
        if (want.abilityName === 'FuncAbility') {
            // 返回的字符串Key标识为自定义拼接的字符串内容
            return `ControlModule_EntryAbilityInstance_${want.parameters.instanceKey}`;
        }

        return '';
    }
}
  *
  * 3 配置 module.json5
  * name : "entry",
  * type : "entry",
  * srcEntry:"MyAbilityStage" // 所有的 ability 的舞台 根据 want.abilityName 做判断
  *
  * 直接跳转  省略 want 的 parameters  与 步骤 2,3 即可
  *
  *
  * 网络请求
  * Http
  * WebSocket
  * Socket
  *
  * 添加权限
  *
  * Http
  * 1 http 模块原生请求
  *
  * OpenHarmony 三方库中心仓
  *
  * 2 Axios
  * 1 ohpm 包安装管理工具
  * 1 下载 --> cmd -- ohpm/bin/ --> init.bat --> 配置环境变量
  *   --> 用户环境变量 新建 OHPM_HOME:ohpm/bin/ --> 在 PAth 中 新建 %OHPM_HOME%
  *   --> ohpm -v
  * 2 下载 axios
  * studio 中 执行
  * ohpm install @ohos/axios
  * 3 使用 axios
  * import axios from "@ohos/axios"
  * axios.get(
  *   "url",
  *    {
  *       params:{ "k":"v" } // get 传参
  *       data:{ "k":"v" }   // post 等 传参
  *    }.then()
  *     .catch()
  * )
  *
  * 后面 写一个封装好的 axios 来用
  *
  *
  * 数据持久化
  *
  * 用户首选项 -- Preference
  * 轻量级
  * key -- value (string number boolean 以及其数组)
  * 以 Promise 的方式 操作
  *
  * 封装一个工具类
  * 以 map 的形式存储 Preference
  * 与 sharePreference 的区别
  * put 之后 要刷到磁盘中
  * await pre.put('name',value)
  * await pre.flush()
  *
  *
  * 关系型数据库 -- SQLite
  * relationalStore
  *
  * 1 创建数据库
  * // rdb 配置
  * const config = {
  *   name:'MyApplication.db',
  *   securityLevel:relationalStore.SecurityLevel.S1
  * }
  *
  * // 初始化 SQL 语句
  * const sql = `CREATE TABLE IF NOT EXISTS TASK (
  *              ID INTEGER PRIMARY KEY AUTOINCREMENT,
  *              NAME TEXT NOT NULL,
  *              FINISHED bit
  *              )`
  *
  * // 获取 rdb
  * relationalStore.getRDBStore(context,config,(err,rdbStore) =>{
  *   if(err) return "失败"
  *  // 执行 SQL 语句
  * rdbStore.executeSql(sql)
  * this.rdbStore = rdbStore
  * })
  *
  *
  * 2 增删改 类似 Room操作
  * this.rdbStore.insert(tableName,{ id:1,name:'张三'} )
  *
  * // 条件谓词  不指定 就是全部
  * const predicates = new relationalStore.RdbPredicates(tableName)
  * predicates.equalTo("ID",id) // 删除指定 ID
  *
  * this.rdbStore.delete(predicates)
  *
  * const task = {name:'李四'}
  * this.rdbStore.update(task,predicates)
  *
  * 3 查
  * // 条件谓词  这里查询全部
  * const predicates = new relationalStore.RdbPredicates(tableName)
  * const result = await this.rdbStore.query(predicates,['id','name']) // 查询哪些字段
  *
  * 解析result
  * const tasks:any[] = []
  * // 遍历 result
  * while(!result.isAtLastRow){
  * // 指针移动到下一行
  * result.goToNextRow()
  * // 根据字段名 取出数据
  * const id = result.getLong(result.getColumnIndex('ID'))
  * const name = result.getString(result.getColumnIndex('NAME'))
  * tasks.push({id,name})
  * }
  *
  *
  *
  * 通知
  * 1 基础通知
  * notificationManager
  * // 通知请求
  * const request: notificationManager.NotificationRequest = {
  *   id: 10,
  *   content:{ // 通知内容
  *     contentType:
  *     1  普通文本型  只有一行  文本多了 隐藏
  *     2  长文本     只有一行  多了 可以通过折叠按钮打开显示       briefText:通知概要和总结 -->不起作用
  *     3  多行文本    多行     可以通过折叠按钮打开 多行并列显示   briefText:通知概要和总结 -->不起作用
  *     4  图片型      可以通过折叠按钮打开 显示图片               briefText:通知概要和总结 -->起作用
  *        picture:this.pixel
  *   }
  *   deliverTime: new Date().getTime()  // 显示当前时间
  *   showDeliverTime:true
  *   groupName:'test' // 分组
  *   slotType:  通知显示的类型  顶部状态栏是否有图标 是否有提示音  是否弹出横幅框提示
  *             1 社交类型  2 服务类型  3 内容类型 4 其他
  * }
  *
  * 获取 图片的 pixel
  * async aboutToAppear(){
  *   const rm = getContext(this).resourceManager
  *   const file = await rm.getMediaContent($r('app.media.XXX'))
  *   image.createImageSource(file.buffer).createPixelMap()
  *     .then(v=>this.pixel = v)
  *     .catch(e=>e)
  * }
  *
  * 具体看 文档
  *
  *
  * 2 进度条通知 -- 主要用于文件下载,长任务处理进度
  *
  *  1 判断当前系统是否支持进度条模版  可以放在初始化中
  *  this.isSupport = await notificationManager.isSupportTemplate('downloadTemplate')
  *  if(!this.isSupport) return
  *
  *  2 定义通知请求
  *  // 通知模版
  *  const template = {
  *    name:'downloadTemplate' ,// 固定写法
  *    data:{progressValue:this.progressValue,progressMaxValue:100}
  * }
  *
  *  // 通知请求
  *  const request:notificationManager.NotificationRequest = {
  *    id:999, // 一般是固定 id  进度条只有一个 重复覆盖
  *    template:template,
  *    content:{
  *      contentType:notify.ContentType>NOTIFICATION_CONTENT_BASIC_TEXT
  *      normal:{
  *        title:this,fileName + ':    ' + this.state,
  *        text:'' // 一般用不上
  *        additionalText:`${this.progressValue}`
  * }
  * }
  * }
  *
  *  3 发送通知 -- 请求需要循环发送 才会有进度
  *  notify.publish(request)
  *
  *
  * 3 通知意图 (行为意图) -- 即通过通知 跳转对应的页面
  *   也可以设置按钮 通过通知中的按钮 实现意图
  *
  *  1 意图行为信息
  *  const wantInfo = {
  *    wants:[
  *      {
  *        deviceId:'' // 不写就是本机
  *        bundleName:'包名'
  *        abilityName:'要启动的ability'
  *        action:''
  *        entities:[]
  * }
  * ]
  *  operationType:START_ABILITY  // 启动
  *  requestCode:0
  *  wantAgentFlags:[CONSTANT_FLAG] // 常量
  * }
  *
  * 2 创建 wantAgent 实例  初始化数据中创建
  *  this.wantAgentInstance = await wantAgent.getWantAgent(wantInfo)
  *
  * 3 通知请求
  *  const request:notificationManager.NotificationRequest = {
  *    id:999, // 一般是固定 id  进度条只有一个 重复覆盖
  *    template:template,
  *    wantAgent:this.wantAgentInstance // 设置通知意图
  *    content:{
  * */
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值