微信小程序云开发

微信小程序云开发

一、云开发介绍

1.1 创建项目

  • 创建项目需要使用真实 AppID
  • 后端服务选择微信云开发

1.2 云开发项目目录介绍

  • cloudfunctions 文件夹:存放云函数
  • miniprogram 文件夹:与之前所学的项目类似

1.3 创建云开发环境

  • 点击云开发–设置

  • 第一次免费一个月的试用期限

  • 如果不是第一次,点击环境名称,创建环境,购买最便宜的一个月19.9元

  • 复制云环境id,修改app.js 中的 env

  • 修改 env 之后,重启微信开发者工具,会发现项目的环境已经更换为新创建的环境

二、基础概念

2.1 云数据库

JSON 数据库,每条记录都是一个 JSON 格式的对象,一个数据库可以有多个集合(相当于关系型数据中的表)

2.2 云存储

云端存储图片,返回存储路径

2.3 云函数

三、云数据库

3.1 集合的初始化

  • 手动创建集合,导入导出json数据

3.2 数据库查询操作

  • 根据id查询数据

    • home.js

      /**
         * 查询图书列表
         */
      bookList(){
          //1. 数据库初始化
          var db = wx.cloud.database();
          //doc("id"):找到id对应的这行数据
          //get():查询
          db.collection("book").doc("de90ae1f64be1725008fd66276e4f140")
              .get({
              //success:查询成功的回调函数   res:返回的结果
              success:res=>{
                  console.log("hello world");
                  console.log(res);
              }
          })
      },
      

    注意:

    • 直接查询手动插入的数据没有结果,需要修改集合的权限
  • 修改集合的权限

    • 云开发 ---- 存储 ---- 存储权限

    • 也可修改为所有用户可读可写

      • 选择自定义安全规则

      • {
        	"read": true,
        	"write": true
        }
        
  • 无条件查询所有图书

    bookList(){
        var db = wx.cloud.database();
        db.collection("book").get({
            success:res=>{
                console.log(res.data);
                this.setData({
                    bookList:res.data
                })
            }
        })
    },
    
  • 根据输入名称查询图书

    /**
       * 根据输入的图书名称查询
       */
    bookByName(){
        db.collection("book").where({
            name:this.data.inputName
        }).get({
            success:res=>{
                console.log(res.data);
                this.setData({
                    bookList:res.data
                })
            }
        })
    },
    
    

3.3 添加图书

/**
   * 添加图书
   */
addBook(){
    db.collection("book").add({
        data:{    //新增的数据
            name:"三国演义",
            publish:new Date()
        },
        success:res=>{
            console.log(res);
        }
    })
},

3.4 修改图书

/**
   * 修改图书
   */
updateBook(){
    db.collection("book").doc("8043219a64be1f5800002a350892f70f").update({
        data:{
            name:"ABC123",
            publish:"2023-07-24 15:02:03"
        },
        success:res=>{
            console.log(res);
        }
    })
},

四、存储

4.1 上传头像

/**
   * 上传图片
   */
uploadImage(){
    //选择图片或视频
    wx.chooseMedia({
        count:1,//上传图片或视频的个数,
        mediaType: ['image','video'],//文件类型
        sourceType: ['album', 'camera'],//来源
        camera: 'back',
        success:res=>{
            //获取选择图片的临时路径
            var tempFilePath = res.tempFiles[0].tempFilePath;
            //将临时文件路径上传到云存储中
            wx.cloud.uploadFile({
                //获取日期时间作为文件名称
                cloudPath: new Date().toLocaleString()+".png",//云存储中的路径
                filePath:tempFilePath,//临时路径
                success:res=>{
                    this.setData({
                        imgSrc:res.fileID
                    })
                }
            })
        }
    })

},

五、云函数

5.1 云函数介绍

一个云函数的写法与一个在本地定义的 JavaScript 方法无异,代码运行在云端 Node.js 中,云函数后端 SDK 搭配使用多种服务,比如使用云函数 SDK 中提供的数据库和存储 API 进行数据库和存储的操作

  • 传统的服务端开发: 客户端、浏览器、小程序----接口发请求---->后端【服务端】----->数据库

  • 小程序云开发:小程序--------->运行在云端 Node.js 的云函数------>云数据库

5.2 创建并调用云函数

  • 右键 cloudfunctions 文件夹,并选择新建 Node.js 云函数
    • config.json:云函数的一些配置
    • index.js:函数核心内容
    • package.json:描述云函数的信息
  • 修改完 index.js 文件后,右键该文件选择云函数增量上传:更新文件
  • 编写查询所有的云函数

    //初始化数据库  const修饰的变量不能被修改
    const db = cloud.database();
    
    // 云函数入口函数
    exports.main = async (event, context) => {
      //查询book表所有数据并返回
      //这里不使用success接收,而是直接返回,供调用此函数的小程序接收
      return await db.collection("test").get()
    }
    
    • 修改完云函数一定记得更新文件
  • 小程序中调用

    /**
       * 调用云函数的查询
       */
    cloudFunction(){
        //云函数调用
        wx.cloud.callFunction({
            name:"getBook",//云函数的名称
            //云函数调用成功执行的方法
            complete:res=>{
                console.log(res);
            }
        })
    },
    
  • 给云函数传参

    • getBookById

      //初始化数据库
      const db = cloud.database();
      // 云函数入口函数
      exports.main = async (event, context) => {
        //根据id查询test
        //传递的参数都在event中
        return await db.collection("test").doc(event.id).get();
      }
      
    • home.js

      /**
         * 云函数传参
         */
      cloudFunctionArgs(){
          wx.cloud.callFunction({
              name:"getBookById",
              data:{
                  id:'14a8deea64bc98de00798182710429c8'
              },
              complete:res=>{
                  console.log(res);
              }
          })
      },
      
  • 调用云函数添加数据库

    • addBook

      const db = cloud.database()
      // 云函数入口函数
      exports.main = async (event, context) => {
        return await db.collection("test").add({
          data:{
            name:event.name,
            author:event.author,
            price:event.price,
            date:event.date
          }
        })
      }
      
    • home.js

      /**
         * 调用云函数添加数据库
         */
      addCloudFunction(){
          wx.cloud.callFunction({
              name:"addBook",
              data:{
                  name:"红楼梦",
                  author:['曹雪芹'],
                  price:3.8,
                  date:new Date()
              },
              complete:res=>{
                  console.log(res);
              }
          })
      },
      
      • 添加之后也会返回新增的id

Example:商城项目

环境配置

  • 在app.js修改云环境id
  • 修改表的数据权限【所有用户都可读写】

注册

  • 注册验证

    /**
         * 当点击注册时触发的函数
         * 1. 先校验输入的数据是否满足要求
         *      手机号满足,用户名、密码不能为空,密码和确认密码必须一致
         */
    submit(){
        //手机号:以1开头,第二位:3 4 5 6 7 8 9  第三位-第十一位:普通数字
        //正则表达式:通常被用来检索、替换那些符合某个模式(规则)的文本
        // ^:以什么开头 [3-9]:3到9中的任意一个   \d:等价于[0-9]
        // $:以什么结尾
        var reg = /^1[3-9]\d{9}$/;
        if(!reg.test(this.data.phone)){
            //给出提示
            wx.showToast({
                title: '手机号格式错误',
                icon:'error'
            })
        }else if(this.data.pass!=this.data.confirmPass){
            wx.showToast({
                title: '密码前后不一致',
                icon:'error'
            })
            //js:空字符串放在boolean中就是false
        }else if(this.data.username && this.data.pass){
            //1. 注册校验
            this.registCheck();
        }else{
            wx.showToast({
                title: '表单输入不正确',
                icon:'error'
            })
        }
    
    },
    
  • 注册插入

    /**
         * 注册校验的函数
         * 1. 先根据手机号查询数据是否存在
         * 2. 如果存在给出提示,如果不存在插入数据
         */
    registCheck(){
        wx.cloud.callFunction({
            name:"getUserByPhone",
            data:{
                phone:this.data.phone
            },
            complete:res=>{
                if(res.result.data.length!=0){
                    //给出手机号重复的提示
                    wx.showToast({
                        title: '手机号已存在',
                        icon:"error"
                    })
                }else{
                    //可以注册
                    wx.cloud.callFunction({
                        name:"regist",
                        data:{
                            phone:this.data.phone,
                            username:this.data.username,
                            pass:this.data.pass
                        },
                        complete:res=>{
                            if(res.result._id){
                                wx.showToast({
                                    title: '注册成功',
                                    icon:"success",
                                    duration:1500,//图标显示时长
                                })
                                setTimeout(()=>{
                                    wx.navigateBack()//返回上一页
                                },1500)
    
                            }
                        }
                    })
                }
            }
        })
    },
    

登录

登录成功之后将用户信息存到 storage

  • login.js

    /**
         * 登录验证
         */
    login(){
        wx.cloud.callFunction({
            name:"login",
            data:{
                phone:this.data.phone,
                pass:this.data.pass
            },
            complete:res=>{
                console.log(res);
                if(res.result.data.length==0){
                    wx.showToast({
                        title: '账号密码不正确',
                        icon: "error",
                    })
                }else{
                    //将登录的用户信息存到storage中
                    wx.setStorageSync('user', res.result.data[0])
                    //打印查询的对象
                    wx.showToast({
                        title: '登录成功',
                        icon:"success",
                        duration:1500,
                    })
                    setTimeout(()=>{
                        wx.navigateBack()
                    },1500)
                }
            }
        })
    },
    
  • mine.js

    onshow 中校验用户是否登录,因为 onLoad 只会初始化一次

    onShow() {
        //获取storage中的user
        var user = wx.getStorageSync('user');
        if(user){
            //登录成功
            this.setData({
                isLogin:true,
                user:user
            })
        }else{
            //没有登录
            this.setData({
                isLogin:false,
                user:{}
            })
        }
    },
    

    6.3 导入其他js库的函数

  • util.js
    const formatTime = date => {
      const year = date.getFullYear()
      const month = date.getMonth() + 1
      const day = date.getDate()
      const hour = date.getHours()
      const minute = date.getMinutes()
      const second = date.getSeconds()
    
      return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
    }
    //定义的函数如果想要被别人调用的话,必须要exports导出
    module.exports = {
      formatTime
    }
    
  • home.js
    var {formatTime} = require("../../utils/util.js")
    
    formatTime(new Date());
    

头像上传

  • mine.js

    /**
         * 上传头像
         */
    choosePhoto(){
        wx.chooseMedia({
            count:1,//上传个数
            mediaType: ['image','video'],
            sourceType: ['album', 'camera'],
            camera: 'back',
            success:(res)=>{
                wx.cloud.uploadFile({
                    cloudPath: Date.now()+'.png', // 上传至云端的路径
                    filePath: res.tempFiles[0].tempFilePath, // 小程序临时文件路径
                    success: res => {
                        // 返回文件 ID:图片存储的云路径
                        //将图片云路径赋值给user的头像
                        this.setData({
                            //修改user的头像路径:目的都是修改完立刻更新
                            "user.avatarurl":res.fileID
                        })
                        //修改storage中的头像:重新显示mine页面时图片路径改变
                        wx.setStorageSync('user', this.data.user);
                        //修改数据库的头像路径:退出登录之后再次登录时头像也会改变
                        wx.cloud.callFunction({
                            name:"updatePhoto",
                            data:{
                                id:this.data.user._id,
                                avatarurl:this.data.user.avatarurl
                            }
                        })
                    }
                })
            }
        })
    },
    

商城后台管理

  • 后台管理的账号:admin 密码:123

  • 商品表、分类表:分类和商品是一对多关系

  • 添加分类 (addCategory.js)

    /**
       * 添加分类
       */
    addCategory(){
        //1. 先验证分类是否存在
        wx.cloud.callFunction({
            name:"getCategoryByName",
            data:{
                categoryName:this.data.inputName
            },complete:res=>{
                if(res.result.data.length!=0){
                    wx.showToast({
                        title: '分类已存在',
                        icon:"error"
                    })
                }else{
                    //添加分类
                    wx.cloud.callFunction({
                        name:"addCategory",
                        data:{
                            categoryName:this.data.inputName
                        },complete:res=>{
                            if(res.result._id){
                                wx.showToast({
                                    title: '添加成功',
                                    icon:"success",
                                    duration:1500
                                })
                                setTimeout(()=>{
                                    wx.navigateBack()
                                },1500)
                            }
                        }
                    })
                }
            }
        })
    },
    
  • 添加商品页面 (add_product)

    /**
       * 生命周期函数--监听页面加载
       */
    onLoad(options) {
        //查询所有的分类并展示在页面上
        wx.cloud.callFunction({
            name:"categoryList",
            complete:res=>{
                this.setData({
                    categoryList:res.result.data
                })
            }
        })
    },
    

    使用 picker 组件实现滑动选择分类

    <!-- 滑动选择的组件 -->
    <picker range-key="name" model:value="{{index}}" range="{{categoryList}}">
        <view class="picker">
            {{categoryList[index].name|| '请选择'}}
        </view>
    </picker>
    

首页查询数据

  • 查询默认的 banner

    /**
       * 获取轮播图
       */
    getBanner(){
        wx.cloud.callFunction({
            name:"getBanner",
            complete:res=>{
                this.setData({
                    banner:res.result.data
                })
            }
        })
    },
    
  • 查询前9件商品

    /**
       * 查询默认商品
       */
    goodsList(){
        wx.cloud.callFunction({
            name:"getDefaultProduct",
            complete:res=>{
                this.setData({
                    goodsList:res.result.data
                })
            }
        })
    },
    

分类页面

  • 切换分类

    • 选择分类变色
    <!-- 小程序向函数中传参数,使用data-,例如data-id,data-name   -->
    <view  class="{{currentIndex==index?'current-category category-item':'category-item'}}"  bindtap="changeCategory" data-index="{{index}}"  wx:for="{{categoryList}}" >
        {{item.name}}
    </view>
    
    /**
       * 切换分类
       */
    changeCategory(e){
        //接收data-传递的参数
        var index = e.currentTarget.dataset.index;
        this.setData({
            currentIndex:index
        })
    },
    
  • 获取所有分类

    /**
       * 获取所有分类
       */
    getCategoryList(){
        wx.cloud.callFunction({
            name:"categoryList",
            complete:res=>{
                this.setData({
                    categoryList:res.result.data
                })
            }
        })
    },
    

商品添加到购物车

  • 首先对用户登录进行校验

    /**
       * 添加购物车
       * 1. 验证用户是否登录
       * 2. 先根据用户id和商品id查询购物车数据是否存在
       *    如果查得到,说明之前购买过,只更新数量
       *    如果查不到,没有购买过,直接插入
       * 购物车:id、productId(商品id【name、price】)、
       *        buyCount(购买数量)、userId(用户id)
       */
    addCart(e){
        var user = wx.getStorageSync('user')
        if(user){
            var userId = user._id;//用户id
            var productId = e.currentTarget.dataset.id;//商品id
            wx.cloud.callFunction({
                name:"getCartByUserAndProduct",
                data:{
                    userId:userId,
                    productId:productId
                },complete:res=>{
                    if(res.result.data.length!=0){
                        //之前购买过,只更新数量
                        this.updateCartNum(res.result.data[0])
                    }else{
                        //没购买过,插入数据
                        this.insertCart(userId,productId);
                    }
                }
            })
        }else{
            wx.showToast({
                title: '用户未登录',
                icon:"error",
                duration:1500
            })
        }
    },
    
  • 当前用户第一次购买此商品

    • 先插入购物车数据

      /**
         * 插入数据
         * _id[自增]、productId、userId、buyCount[默认为1]
         */
      insertCart(userId,productId){
          wx.cloud.callFunction({
              name:"addCart",
              data:{
                  userId:userId,
                  productId:productId
              },complete:res=>{
                  if(res.result._id){
                      wx.showToast({
                          title: '添加成功',
                          icon:'success'
                      })
                      //根据商品减库存
                      this.reduceProductCount(productId);
                  }
              }
          })
      },
      
    • 再减少此商品的库存

      /**
         * 对商品减库存
         */
      async reduceProductCount(productId){
          //根据商品id查询商品对象=====>获取商品的数量
          var oldProduct = await this.getProductById(productId);
          console.log(">>>",oldProduct);
          if(oldProduct.count>=1){
              wx.cloud.callFunction({
                  name:"reduceProductCount",
                  data:{
                      productId:productId,//商品id
                      currentCount:oldProduct.count//当前商品库存
                  },complete:res=>{
                      //重新查询当前分类的商品数据
                      this.getProductListByCategory();
                  }
              })
          }else{
              wx.showToast({
                  title: '库存不足',
              })
          }
      },
      
    • 根据 商品id 查询商品对象

      /**
         * 根据商品id查询商品对象
         */
      getProductById(productId){
          return new Promise((resolve,reject)=>{
              wx.cloud.callFunction({
                  name:"getProductById",
                  data:{
                      productId:productId
                  },complete:res=>{
                      console.log("根据商品id查询>>>>",res);
                      resolve(res.result.data);
                  }
              })
          })
      },
      
    • 将减库存这个函数由异步改为同步,并将 getProductById 函数修改为 Promise

  • 用户已经购买过此商品

    • 更新购物车数量

      /**
         * 更新数量
         * oldCart:之前购买过的这一条购物车数据对应的对象
         *  _id,productId,userId,buyCount
         */
      async updateCartNum(oldCart){
          //新数量
          var newNum = oldCart.buyCount+1;
          //1. 根据商品id查询商品的库存
          //根据商品id查询商品对象=====>查询商品的库存
          var oldProduct = await this.getProductById(oldCart.productId);
          //验证newNum<=此商品库存
          if(newNum<=oldProduct.count){
              //调用更新的函数
              wx.cloud.callFunction({
                  name:"updateCartNum",
                  data:{
                      cartId:oldCart._id,
                      buyCount:newNum
                  },complete:res=>{
                      console.log("修改数量>>>>>",res);
                      wx.showToast({
                          title: '添加成功',
                          icon:'success'
                      })
                      //根据商品减库存
                      this.reduceProductCount(oldCart.productId);
                  }
              })
          }else{
              wx.showToast({
                  title: '库存不足',
                  icon:"error"
              })
          }
      },
      
    • 减库存代码同上

分类查询补充

第一种方法:使用递归实现
  • 查询当前商品被当前用户购买的数量
/**
   * 根据分类查询对应的商品
   */
  getProductListByCategory(){
     	...
        complete:res=>{
          ...
          //对所有的商品进行遍历
          var productList = this.data.productList;
          //不要在for循环中写异步请求!!!!!
          //解决办法:1.改成promise语法(async await) 2. 递归
          this.getBuyCount(0,productList,productList.length);
        }
      })

  },
/**
   * 递归查询 每件商品在当前用户下被加入到购物车的数量
   * 参数:1. 下标   2. 商品列表   3. 商品列表的长度
   */
getBuyCount(i,productList,length){
    db.collection("cart").where({
        productId:productList[i]._id,
        userId:wx.getStorageSync('user')._id
    }).get({
        success:res=>{
            if(res.data.length!=0){
                //买过
                productList[i].buyCount = res.data[0].buyCount;
            }else{
                //没买过
                productList[i].buyCount = 0;
            }
            this.setData({
                productList:productList
            })
            if(++i<length){
                this.getBuyCount(i,productList,length);
            }
        }
    })
},
第二种方法:异步变同步
/**
   * 获取每个商品被当前用户购买的数量
   */
async getCountByProduct(productList){
    for(var i=0;i<productList.length;i++){
        var buyCount = await this.getBuyCountByProduct(productList[i]._id);
        productList[i]['buyCount']=buyCount;
    }
    this.setData({
        productList:productList
    })
},
/**
   * 根据商品id和用户id查询购物车的数量
   */
getBuyCountByProduct(productId){
    return new Promise((resolve,reject)=>{
        db.collection("cart").where({
            productId:productId,
            userId:wx.getStorageSync('user')._id
        }).get({
            success:res=>{
                var buyCount = 0;
                if(res.data.length!=0){
                    buyCount = res.data[0].buyCount;
                } 
                resolve(buyCount);
            }
        })
    })
},

购物车查询[cart.js]

/**
   * 查询当前用户的购物车
   */
getCartListByUser(){
    db.collection("cart").where({
        userId:wx.getStorageSync('user')._id
    }).get({
        success:res=>{
            this.getProductByCart(res.data);
        }
    })
},
/**
   * 通过购物车获取对应的商品
   */
async getProductByCart(cartList){
    for(var i=0;i<cartList.length;i++){ // 0 1 2 3 4
        var product = await this.getProductById(cartList[i].productId);
        //每一个购物车对象都包含一个商品属性
        cartList[i]['product'] = product;
    }
    this.setData({
        cartList:cartList
    })
},
/**
   * 根据商品id查询商品对象
   */
getProductById(productId){
    return new Promise((resolve,reject)=>{
        wx.cloud.callFunction({
            name:"getProductById",
            data:{
                productId:productId
            },complete:res=>{
                resolve(res.result.data);
            }
        })
    })
},

购物车全选

/**
   * 点击全选按钮
   * 勾选、取消勾选
   */
selectAll(){
    // 获取当前全选按钮的状态
    var selectAllStatus = this.data.selectAllStatus;
    //获取点击后的全选按钮状态
    selectAllStatus = !selectAllStatus;
    //获取当前用户的购物车列表
    var cartList = this.data.cartList;
    if(selectAllStatus){
        //全选
        //给每个购物车都添加check属性设置为true
        for(var i=0;i<cartList.length;i++){
            cartList[i].check = true;
        }
    }else{
        //取消全选
        //给每个购物车都添加check属性设置为false
        for(var i=0;i<cartList.length;i++){
            cartList[i].check = false;
        }
    }
    this.setData({
        selectAllStatus:selectAllStatus,
        cartList:cartList
    })
    //调用计算总金额的方法
    this.calcTotalPrice();
},

购物车反选

/**
   * 单选按钮
   */
selectOne(e){
    //接收点击的下标
    var index = e.currentTarget.dataset.index;
    var cartList = this.data.cartList;
    cartList[index].check = !cartList[index].check
    //默认定义全选按钮的状态为true
    var selectAllStatus = true;
    for(var i=0;i<cartList.length;i++){
        //只要购物车对象有一个check是false,selectAllStatus变为false
        if(!cartList[i].check){
            //至少一个单选按钮不是选中状态
            selectAllStatus = false;
            break;
        }
    }
    this.setData({
        cartList:cartList,
        selectAllStatus:selectAllStatus
    })
    //调用计算总金额的方法
    this.calcTotalPrice();
},

结算金额

/**
   * 总金额计算
   * 只计算选中的金额: check==true
   */
calcTotalPrice(){
    var cartList = this.data.cartList;
    //定义总金额
    var totalPrice = 0;
    for(var i=0;i<cartList.length;i++){
        if(cartList[i].check){
            totalPrice+=cartList[i].buyCount*cartList[i].product.price;
        }
    }
    this.setData({
        //toFixed(1):保留1位小数
        totalPrice:parseFloat(totalPrice.toFixed(1))
    })
},

减少购物车数量

/**
   * 减少当前购物车的数量
   */
reduceCount(e){
    //接收点击的下标
    var index = e.currentTarget.dataset.index;
    var cartList = this.data.cartList;
    //判断当前购物车的数量大于等于1
    if(cartList[index].buyCount<1){
        return;//1. 返回值   2. 方法结束
    }
    var newCount = cartList[index].buyCount-1;
    //修改当前点击的数量
    cartList[index].buyCount = newCount;
    this.calcTotalPrice();
    if(newCount==0){
		//重点:把购物车中数量为0的那个购物车的id拿到,以便后面删除该购物车
		var removeId = cartList[index]._id;
		//数量为0,就删掉界面上列表那一个购物车
		cartList.splice(index,1);
		//删除购物数为0的购物车
		db.collection("cart").doc(removeId).remove()
    }else{
		//更新数据库中的数量
		db.collection("cart").doc(cartList[index]._id).update({
		    data:{
		        buyCount:newCount
		    },success:res=>{
		        this.setData({
		            cartList:cartList
		        })
		    }
		})
    }
},
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值