学习 SpiderMonkey

60 篇文章 0 订阅

规则:

js逻辑代码中使用的是jsval类型
计算结果和传递的行参都是RootedValue类型  RootedObject JSString Number或其他类型的值都保存在RootedValue类型的变量中。

json是RootedObject类型
数组也是RootedObject类型 用 JS_IsArrayObject 判断是否为数组
数组长度通过JS_GetArrayLength获取
元素通过JS_GetElement获取

js to c++
value.isNumber(),JS::ToNumber()
isObject(),JS_ValueToObject()
v.isString();      JSString *tmp =JS::ToString(cx, v);      JSStringWrapper str(tmp);      *ret = str.get();
读取json类型值:

bool jsval_to_vector2(JSContext *cx, JS::HandleValue vp/*传入js类型的值, 该值可能是一个js对象或者是一个js变量*/, cocos2d::Vec2* ret/*传出*/)
{
    //创建js对象
    JS::RootedObject tmp(cx);
    //创建js变量
    JS::RootedValue jsx(cx);
    JS::RootedValue jsy(cx);
    double x, y;
    bool ok = vp.isObject() &&//vp是否可转为对象
    JS_ValueToObject(cx, vp, &tmp) &&//vp转为对象
    JS_GetProperty(cx, tmp, "x", &jsx) &&//获取json属性 中的js变量值
    JS_GetProperty(cx, tmp, "y", &jsy) &&
    JS::ToNumber(cx, jsx, &x) &&//js变量转C++浮点
    JS::ToNumber(cx, jsy, &y);
    
    JSB_PRECONDITION3(ok, cx, false, "Error processing arguments");
    
    ret->x = (float)x;//赋值
    ret->y = (float)y;
    return true;
}
读取js数组:
bool jsval_to_std_vector_int( JSContext *cx, JS::HandleValue vp, std::vector<int>* ret)
{
    //创建js 对象
    JS::RootedObject jsobj(cx);
    //如果能转为对象 则转为对象   计算结果得到的都是RootedValue,  RootedObject, JSString, Number 或其他类型的值都保存在RootedValue中
    bool ok = vp.isObject() && JS_ValueToObject( cx, vp, &jsobj );
    JSB_PRECONDITION3( ok, cx, false, "Error converting value to object");
    JSB_PRECONDITION3( jsobj && JS_IsArrayObject( cx, jsobj),  cx, false, "Object must be an array");
    
    uint32_t len = 0;
    //获取对象的数组长度
    JS_GetArrayLength(cx, jsobj, &len);
    
    for (uint32_t i=0; i < len; i++)
    {
        //创建js类型变量
        JS::RootedValue value(cx);
        if (JS_GetElement(cx, jsobj, i, &value))
        {
            if (value.isNumber())
            {
                double number = 0.0;
                //转类型
                ok = JS::ToNumber(cx, value, &number);
                if (ok)
                {
                    ret->push_back(static_cast<int>(number));
                }
            }
            else
            {
                JS_ReportError(cx, "not supported type in array");
                return false;
            }
        }
    }
    
    return true;
}



c++ to js
如果是 obj   JS_NewObject     JS_DefineProperty    OBJECT_TO_JSVAL
如果是Array   JS_NewArrayObject     JS_SetElement    OBJECT_TO_JSVAL
int32_to_jsval
std_string_to_jsval
创建json类型值:

jsval vector2_to_jsval(JSContext *cx, const cocos2d::Vec2& v)
{
    //创建一个js对象
    JS::RootedObject proto(cx);
    JS::RootedObject parent(cx);
    JS::RootedObject tmp(cx, JS_NewObject(cx, NULL, proto, parent));
    if (!tmp) return JSVAL_NULL;
    //定义对象的属性x和y
    bool ok = JS_DefineProperty(cx, tmp, "x", v.x, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
    JS_DefineProperty(cx, tmp, "y", v.y, JSPROP_ENUMERATE | JSPROP_PERMANENT);
    if (ok) {
        //返回一个jsval值
        return OBJECT_TO_JSVAL(tmp);
    }
    return JSVAL_NULL;
}

创建一个js数组

jsval std_vector_int_to_jsval( JSContext *cx, const std::vector<int>& v)
{
    JS::RootedObject jsretArr(cx, JS_NewArrayObject(cx, 0));
    
    int i = 0;
    for (const int obj : v)
    {
        JS::RootedValue arrElement(cx);
        arrElement = int32_to_jsval(cx, obj);
        
        if (!JS_SetElement(cx, jsretArr, i, arrElement)) {
            break;
        }
        ++i;
    }
    return OBJECT_TO_JSVAL(jsretArr);
}


全局函数   c++调js   js调c++
//
//  jsb_kenko_auto.h
//  Fishing2d36JSB
//
//  Created by TinyUlt on 11/16/15.
//
//

#ifndef jsb_jsb_kenko_auto_h
#define jsb_jsb_kenko_auto_h

#include "cocos2d.h"
#include "jsapi.h"
#include "jsfriendapi.h"

std::string os_info();
bool jsb_os_info(JSContext *cx, uint32_t argc, JS::Value *vp);
bool jsb_callback(JSContext *cx, uint32_t argc, JS::Value *vp);
void register_jsb_kenko_all(JSContext* cx, JS::HandleObject obj);

#endif
//
//  jsb_kenko_auto.cpp
//  Fishing2d36JSB
//
//  Created by TinyUlt on 11/16/15.
//
//

#include "jsb_kenko_auto.h"
#include "cocos2d_specifics.hpp"

std::string os_info() {
    CCLOG("it's c++ os_info here");
    return "os_info";
}


bool jsb_os_info(JSContext *cx, uint32_t argc, JS::Value *vp) {
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);

    if (argc == 0) {
        std::string ret = os_info();
        jsval jsret = JSVAL_NULL;
        jsret = std_string_to_jsval(cx, ret);
        args.rval().set(jsret);
        return true;
    }
    
    JS_ReportError(cx, "js_FishAlg_FishAlg_getOutRotation3D : wrong number of arguments: %d, was expecting %d", argc, 0);
    return false;
}
bool jsb_callback(JSContext *cx, uint32_t argc, JS::Value *vp) {
    CCLOG("it's c++ testCallback here");
    JSContext* jc = ScriptingCore::getInstance()->getGlobalContext();
    // 注释部分适合有对象化的调用
    // 参考:http://www.tairan.com/archives/4902
    //jsval v[2];
    //v[0] = int32_to_jsval(jc, 32);
    //v[1] = int32_to_jsval(jc, 12);
    
    // 通过 ScriptingCore 封装好的方法实现回调,可以帮助我们节省很多细节上的研究
    //js_proxy_t * p = jsb_get_native_proxy();
    //return ScriptingCore::getInstance()->executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), "cpp_callback", 2, v);        //2是参数个数,v是参数列表
    
    //找到一个更适合全局函数的方法
    jsval ret;
    return ScriptingCore::getInstance()->evalString("cpp_callback(2,3)", &ret);
}
void register_jsb_kenko_all(JSContext *cx, JS::HandleObject obj) {
    JS_DefineFunction(cx, obj, "os_Info", jsb_os_info, 0, 0);  //生成名为osInfo的js全局函数
    JS_DefineFunction(cx, obj, "test_cpp_callback", jsb_callback, 0, 0);
//     JS_DefineFunction(cx, tmpObj, "getInstance", js_PlistParser_getInstance, 0, JSPROP_READONLY | JSPROP_PERMANENT);
}
function cpp_callback(a, b) {
    cc.log("cpp return two integer: " + a + " " + b);
}
cc.log("js get from c++: " + os_Info());
        test_cpp_callback();


c++类中的回调函数
//
//  Network.h
//  cocos2d_libs
//
//  Created by TinyUlt on 11/16/15.
//
//

#ifndef __cocos2d_libs__Network__
#define __cocos2d_libs__Network__

#include <stdio.h>
class Network{
public:
    Network();
    ~Network();
    bool OnMessage();
};
#endif /* defined(__cocos2d_libs__Network__) */
//
//  Network.cpp
//  cocos2d_libs
//
//  Created by TinyUlt on 11/16/15.
//
//

#include "Network.h"
#include "cocos2d.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "cocos2d_specifics.hpp"
Network::Network(){
    
}
Network::~Network(){
    
}
bool Network::OnMessage(){
    js_proxy_t * p = jsb_get_native_proxy(this);
    jsval retval;
    JSContext* jc = ScriptingCore::getInstance()->getGlobalContext();
    //  定义参数,由两个参数
    jsval v[] = {
        v[0] = int32_to_jsval(jc, 32),
        v[1] =UINT_TO_JSVAL(88)
    };
    // 通过 ScriptingCore 封装好的方法实现回调,可以帮助我们节省很多细节上的研究
    ScriptingCore::getInstance()->executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), "callback", 2, v);
    return true;
}
 network.callback = function (i, j) {
            log("network.callback " + i + j);
        }
        network.OnMessage();



js函数传给c++  用于函数回调
//
//  jsb_JS_APP_CB.hpp
//  cocos2d_js_bindings
//
//  Created by TinyUlt on 1/9/16.
//
//

#ifndef jsb_JS_APP_CB_hpp
#define jsb_JS_APP_CB_hpp

#include "jsapi.h"
#include "jsfriendapi.h"

void register_all_js_cpp_cb(JSContext* cx, JS::HandleObject global);

#endif /* jsb_JS_APP_CB_hpp */

//
//  jsb_JS_APP_CB.cpp
//  cocos2d_js_bindings
//
//  Created by TinyUlt on 1/9/16.
//
//

#include "jsb_JS_APP_CB.hpp"
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)

#include "ScriptingCore.h"
#include "cocos2d_specifics.hpp"

#include "jsb_TinyJSWork.hpp"
#include "TinyJSWork.h"
using namespace cocos2d;


static bool jsb_applyStorePay(JSContext *cx, uint32_t argc, jsval *vp)
{
    JSObject *obj = JS_THIS_OBJECT(cx, vp);
    js_proxy_t *proxy = jsb_get_js_proxy(obj);
    TinyJSWork* cobj = (TinyJSWork *)(proxy ? proxy->ptr : NULL);
    JSB_PRECONDITION2( cobj, cx, false, "Invalid Native Object");
    
    if(true){
        JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
        
        int _type;
        jsval_to_int32(cx, args.get(0),  &_type);

        int _proID;
        jsval_to_int32(cx, args.get(1),  &_proID);

        int _gameID;
        jsval_to_int32(cx, args.get(2),  &_gameID);
        
        std::shared_ptr<JSFunctionWrapper> func(new JSFunctionWrapper(cx, obj, args.get(3)));
        
        cobj->applyStorePay((PurchasePlatform)_type, (ProductID)_proID, _gameID,[=](int purType, int proID, int gameID,bool paySuc)->bool{
            Director::getInstance()->getScheduler()->performFunctionInCocosThread([=] {
                JSB_AUTOCOMPARTMENT_WITH_GLOBAL_OBJCET
                jsval arg[4];
                
                arg[0] = int32_to_jsval(cx, purType);
                arg[1] = int32_to_jsval(cx, proID);
                arg[2] = int32_to_jsval(cx, gameID);
                arg[3] = bool_to_jsval(cx, paySuc);
                JS::RootedValue rval(cx);
                
                CCLOG("执行js中的 applyStorePay 设置的回调函数");
                bool ok = func->invoke(4, arg, &rval);
                if (!ok && JS_IsExceptionPending(cx)) {
                    JS_ReportPendingException(cx);
                }
            });
            return true;
        });
        return true;
    }
}

extern JSObject* jsb_TinyJSWork_prototype;

void register_all_js_cpp_cb(JSContext* cx, JS::HandleObject global)
{
    JS_DefineFunction(cx, JS::RootedObject(cx, jsb_TinyJSWork_prototype), "applyStorePay", jsb_applyStorePay, 4, JSPROP_ENUMERATE | JSPROP_PERMANENT);
}

#endif

typedef void TinyJSStoreCallback(int purType, int proID, int gameID,bool paySuc);
    //支付接口
    void applyStorePay(PurchasePlatform type, ProductID proID,int gameID, std::function<TinyJSStoreCallback> jsCallback);



测试1

void SwimAlg::test1(){
    
}
bool js_SwimAlg_SwimAlg_test1(JSContext *cx, uint32_t argc/*形参个数*/, jsval *vp)
{
    //得到参数数组
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
    //js对象转化为c++对象
    JS::RootedObject obj(cx, args.thisv().toObjectOrNull());
    js_proxy_t *proxy = jsb_get_js_proxy(obj);
    SwimAlg* cobj = (SwimAlg *)(proxy ? proxy->ptr : NULL);
    //如果不为对象,则报错
    JSB_PRECONDITION2( cobj, cx, false, "js_SwimAlg_SwimAlg_test1 : Invalid Native Object");
    ///
    //如果参数个数为0
    if (argc == 0) {
    //执行c++函数
        cobj->test1();
        //设置返回类型为未定义的
        args.rval().setUndefined();
        return true;
    }
    //如果传入的行参不等于0 则报错
    JS_ReportError(cx, "js_SwimAlg_SwimAlg_test1 : wrong number of arguments: %d, was expecting %d", argc, 0);
    return false;
}

测试2

void SwimAlg::test2(int i){
    
}
bool js_SwimAlg_SwimAlg_test2(JSContext *cx, uint32_t argc, jsval *vp)
{
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
    bool ok = true;
    JS::RootedObject obj(cx, args.thisv().toObjectOrNull());
    js_proxy_t *proxy = jsb_get_js_proxy(obj);
    SwimAlg* cobj = (SwimAlg *)(proxy ? proxy->ptr : NULL);
    JSB_PRECONDITION2( cobj, cx, false, "js_SwimAlg_SwimAlg_test2 : Invalid Native Object");
    //如果参数有1个
    if (argc == 1) {
        int arg0;
        //js的整型转c++整型
        ok &= jsval_to_int32(cx, args.get(0), (int32_t *)&arg0);
        JSB_PRECONDITION2(ok, cx, false, "js_SwimAlg_SwimAlg_test2 : Error processing arguments");
        //传入参数
        cobj->test2(arg0);
        args.rval().setUndefined();
        return true;
    }

    JS_ReportError(cx, "js_SwimAlg_SwimAlg_test2 : wrong number of arguments: %d, was expecting %d", argc, 1);
    return false;
}


测试3

void SwimAlg::test3(int i, float f){
    
}
bool js_SwimAlg_SwimAlg_test3(JSContext *cx, uint32_t argc, jsval *vp)
{
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
    bool ok = true;
    JS::RootedObject obj(cx, args.thisv().toObjectOrNull());
    js_proxy_t *proxy = jsb_get_js_proxy(obj);
    SwimAlg* cobj = (SwimAlg *)(proxy ? proxy->ptr : NULL);
    JSB_PRECONDITION2( cobj, cx, false, "js_SwimAlg_SwimAlg_test3 : Invalid Native Object");
    //如果参数有2个
    if (argc == 2) {
        int arg0;
        double arg1;
        ok &= jsval_to_int32(cx, args.get(0), (int32_t *)&arg0);
        //js数字类型转c++浮点型
        ok &= JS::ToNumber( cx, args.get(1), &arg1) && !isnan(arg1);
        JSB_PRECONDITION2(ok, cx, false, "js_SwimAlg_SwimAlg_test3 : Error processing arguments");
        cobj->test3(arg0, arg1);
        args.rval().setUndefined();
        return true;
    }

    JS_ReportError(cx, "js_SwimAlg_SwimAlg_test3 : wrong number of arguments: %d, was expecting %d", argc, 2);
    return false;
}

测试4

void SwimAlg::test4(double d, Vec2 v){
    
}
bool js_SwimAlg_SwimAlg_test4(JSContext *cx, uint32_t argc, jsval *vp)
{
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
    bool ok = true;
    JS::RootedObject obj(cx, args.thisv().toObjectOrNull());
    js_proxy_t *proxy = jsb_get_js_proxy(obj);
    SwimAlg* cobj = (SwimAlg *)(proxy ? proxy->ptr : NULL);
    JSB_PRECONDITION2( cobj, cx, false, "js_SwimAlg_SwimAlg_test4 : Invalid Native Object");
    if (argc == 2) {
        double arg0;
        cocos2d::Vec2 arg1;
        ok &= JS::ToNumber( cx, args.get(0), &arg0) && !isnan(arg0);
        //js cc.p()类型转 c++ Vec2() 类型
        ok &= jsval_to_vector2(cx, args.get(1), &arg1);
        JSB_PRECONDITION2(ok, cx, false, "js_SwimAlg_SwimAlg_test4 : Error processing arguments");
        cobj->test4(arg0, arg1);
        args.rval().setUndefined();
        return true;
    }

    JS_ReportError(cx, "js_SwimAlg_SwimAlg_test4 : wrong number of arguments: %d, was expecting %d", argc, 2);
    return false;
}
bool jsval_to_vector2(JSContext *cx, JS::HandleValue vp/*传入js类型的值, 该值可能是一个js对象或者是一个js变量*/, cocos2d::Vec2* ret/*传出*/)
{
    //创建js对象
    JS::RootedObject tmp(cx);
    //创建js变量
    JS::RootedValue jsx(cx);
    JS::RootedValue jsy(cx);
    double x, y;
    bool ok = vp.isObject() &&//vp是否可转为对象
    JS_ValueToObject(cx, vp, &tmp) &&//vp转为对象
    JS_GetProperty(cx, tmp, "x", &jsx) &&//获取json属性 中的js变量值
    JS_GetProperty(cx, tmp, "y", &jsy) &&
    JS::ToNumber(cx, jsx, &x) &&//js变量转C++浮点
    JS::ToNumber(cx, jsy, &y);
    
    JSB_PRECONDITION3(ok, cx, false, "Error processing arguments");
    
    ret->x = (float)x;//赋值
    ret->y = (float)y;
    return true;
}


测试5

int SwimAlg::test5(){
    return 0;
}
bool js_SwimAlg_SwimAlg_test5(JSContext *cx, uint32_t argc, jsval *vp)
{
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
    JS::RootedObject obj(cx, args.thisv().toObjectOrNull());
    js_proxy_t *proxy = jsb_get_js_proxy(obj);
    SwimAlg* cobj = (SwimAlg *)(proxy ? proxy->ptr : NULL);
    JSB_PRECONDITION2( cobj, cx, false, "js_SwimAlg_SwimAlg_test5 : Invalid Native Object");
    if (argc == 0) {
        //得到返回值
        int ret = cobj->test5();
        //c++整型转js数值
        jsval jsret = JSVAL_NULL;
        jsret = int32_to_jsval(cx, ret);
        //设置返回值
        args.rval().set(jsret);
        return true;
    }

    JS_ReportError(cx, "js_SwimAlg_SwimAlg_test5 : wrong number of arguments: %d, was expecting %d", argc, 0);
    return false;
}

测试6

Vec2 SwimAlg::test6(){
    return Vec2::ZERO;
}
bool js_SwimAlg_SwimAlg_test6(JSContext *cx, uint32_t argc, jsval *vp)
{
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
    JS::RootedObject obj(cx, args.thisv().toObjectOrNull());
    js_proxy_t *proxy = jsb_get_js_proxy(obj);
    SwimAlg* cobj = (SwimAlg *)(proxy ? proxy->ptr : NULL);
    JSB_PRECONDITION2( cobj, cx, false, "js_SwimAlg_SwimAlg_test6 : Invalid Native Object");
    if (argc == 0) {
        cocos2d::Vec2 ret = cobj->test6();
        jsval jsret = JSVAL_NULL;
        //Vec2转cc.p();
        jsret = vector2_to_jsval(cx, ret);
        //设置返回值
        args.rval().set(jsret);
        return true;
    }

    JS_ReportError(cx, "js_SwimAlg_SwimAlg_test6 : wrong number of arguments: %d, was expecting %d", argc, 0);
    return false;
}
jsval vector2_to_jsval(JSContext *cx, const cocos2d::Vec2& v)
{
    //创建一个js对象
    JS::RootedObject proto(cx);
    JS::RootedObject parent(cx);
    JS::RootedObject tmp(cx, JS_NewObject(cx, NULL, proto, parent));
    if (!tmp) return JSVAL_NULL;
    //定义对象的属性x和y
    bool ok = JS_DefineProperty(cx, tmp, "x", v.x, JSPROP_ENUMERATE | JSPROP_PERMANENT) &&
    JS_DefineProperty(cx, tmp, "y", v.y, JSPROP_ENUMERATE | JSPROP_PERMANENT);
    if (ok) {
        //返回一个jsval值
        return OBJECT_TO_JSVAL(tmp);
    }
    return JSVAL_NULL;
}


测试7

void SwimAlg::test7(std::vector<int> v){
    
}
bool jsval_to_std_vector_int( JSContext *cx, JS::HandleValue vp, std::vector<int>* ret)
{
    //创建js 对象
    JS::RootedObject jsobj(cx);
    //如果能转为对象 则转为对象   计算结果得到的都是RootedValue,  RootedObject, JSString, Number 或其他类型的值都保存在RootedValue中
    bool ok = vp.isObject() && JS_ValueToObject( cx, vp, &jsobj );
    JSB_PRECONDITION3( ok, cx, false, "Error converting value to object");
    JSB_PRECONDITION3( jsobj && JS_IsArrayObject( cx, jsobj),  cx, false, "Object must be an array");
    
    uint32_t len = 0;
    //获取对象的数组长度
    JS_GetArrayLength(cx, jsobj, &len);
    
    for (uint32_t i=0; i < len; i++)
    {
        //创建js类型变量
        JS::RootedValue value(cx);
        if (JS_GetElement(cx, jsobj, i, &value))
        {
            if (value.isNumber())
            {
                double number = 0.0;
                //转类型
                ok = JS::ToNumber(cx, value, &number);
                if (ok)
                {
                    ret->push_back(static_cast<int>(number));
                }
            }
            else
            {
                JS_ReportError(cx, "not supported type in array");
                return false;
            }
        }
    }
    
    return true;
}


测试8

void SwimAlg::test8(std::vector<Vec2> v){
    
}

bool js_SwimAlg_SwimAlg_test8(JSContext *cx, uint32_t argc, jsval *vp)
{
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
    bool ok = true;
    JS::RootedObject obj(cx, args.thisv().toObjectOrNull());
    js_proxy_t *proxy = jsb_get_js_proxy(obj);
    SwimAlg* cobj = (SwimAlg *)(proxy ? proxy->ptr : NULL);
    JSB_PRECONDITION2( cobj, cx, false, "js_SwimAlg_SwimAlg_test8 : Invalid Native Object");
    if (argc == 1) {
        std::vector<cocos2d::Vec2> arg0;
        ok &= jsval_to_std_vector_ccvec2(cx, args.get(0), &arg0);
        JSB_PRECONDITION2(ok, cx, false, "js_SwimAlg_SwimAlg_test8 : Error processing arguments");
        cobj->test8(arg0);
        args.rval().setUndefined();
        return true;
    }

    JS_ReportError(cx, "js_SwimAlg_SwimAlg_test8 : wrong number of arguments: %d, was expecting %d", argc, 1);
    return false;
}








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值