49 Fluttet getx状态管理(一)

Fluttet getx状态管理

1.Get介绍

Bloc问题:使用Bloc的时候,有一个让我至今为止十分在意的问题,无法真正的跨页面交互!在反复的查阅官方文档后,使用一个全局Bloc的方式,实现了“伪”跨页面交互。fishRedux,在中小型项目中使用时,会降低一定开发效率,但是在大型项目中,是非常好的

Getx优势:

- 路由管理
- build刷新方式
- 跨页面交互,在StreamController篇介绍过,当时是通过它实现的
- 实现了全局BuildContext
- 国际化,主题实现

GetMaterialApp 对于路由、snackbar、国际化、bottomSheet、对话框以及与路由相关的高级apis和没有上下文(context)的情况下是起重要作用的,它只是一个Widget,它的子组件是默认的MaterialApp。

路由管理

1.依赖
  get: ^4.1.4

导包

import 'package:get/get.dart';

2.使用

在MaterialApp之前添加“获取”,将其转换为GetMaterialApp

  • 注意:这不会修改Flutter的MaterialApp,GetMaterialApp并不是经过修改的MaterialApp,它只是一个预配置的小部件,其默认值为MaterialApp作为子级。您可以手动配置,但是绝对没有必要。GetMaterialApp将创建路线,注入路线,注入翻译,注入路线导航所需的一切。如果将“获取”仅用于状态管理或依赖项管理,则不必使用GetMaterialApp。GetMaterialApp对于路线,小吃店,国际化,bottomSheets,对话框以及与路线和缺少上下文相关的高级api是必需的。
  • 注意²:仅当您要使用路由管理(Get.to()Get.back()依此类推)时,才需要执行此步骤。如果您不使用它,则无需执行步骤1

1.动态路由Get.to

 Get.to(TestAPage());

2.替换路由replace

Get.off(NextScreen());

3.返回 Navigator.pop(context

Get.back();

4.返回根,取消前面所有路线

Get.offAll(NextScreen());

5.静态路由

Get.toNamed('/details');

6.发送前一个路由的数据

Get.back(result: 'success');

7.Get提供高级动态URL

Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");

print(Get.parameters['id']);
// out: 354
print(Get.parameters['name']);
// out: Enzo

8.在系统Dialog上跳转页面,可以这样写

Get.to(XxxxPage(), preventDuplicates: false);
// 或者
Get.toNamed('xxx',  preventDuplicates: false);

9.传参

Get.toNamed("/home/list/detail", arguments: {"id": 999})');
场景

A->B,B传参回A,A获取值,类似于Android中startActivityForResult

//动态 路由的方式
    Navigator.of(context).push(
      new MaterialPageRoute(
        builder: (BuildContext context) {
          return new TestA();
        },
      ),
    ).then((value) {
      //获取上一个页面的数据
    });
 //Gex 动态的方式  获取数据
  void fun5() async {
    //动态 态的方式 并获取A页面的返回值
    var value = await Get.to(new TestA());
    print("A页面的返回值 $value");
  }

退出登录时,关闭所有的页面,然后打开登录页面

Navigator.of(context).pushAndRemoveUntil(
      new MaterialPageRoute(
        builder: (BuildContext context) {
          return new TestAPage();
        },
      ),
          (Route route) => false,
    );
Get.offAll(new TestAPage());

代码

import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'MyHomePage.dart';
import 'TestAPage.dart';

//程序入口
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //使用 GetX第一步
    return GetMaterialApp(
      theme: ThemeData(
          primarySwatch: Colors.yellow,
          highlightColor: Color.fromRGBO(255, 255, 255, 0.5),
          splashColor: Colors.blue),
      //静态路径
      routes: {
        "/testa": (context) => TestAPage(),
      },
      //默认显示的首页页面
      home: MyHomePage(),
    );
  }
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class TestAPage extends StatefulWidget {
  TestAPage({Key key}) : super(key: key);

  @override
  _TestAPageState createState() {
    return _TestAPageState();
  }
}

class _TestAPageState extends State<TestAPage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Center(
      child: Container(
        color: Colors.red,
        child: OutlinedButton(
          onPressed: (){
            Get.back();
          },
        ),
      ),
    );
  }
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';


class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() {
    return _MyHomePageState();
  }
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("首页"),
      ),
      body: Center(
        child: Container(
          width: 200,
          height: 200,
          color: Colors.red,
          child: OutlinedButton(
            onPressed: (){
              Get.toNamed("/testa");
            },
          ),
        ),
      ),
    );
  }
}

3.实现Provider类似的功能

注意:使用Getx之后,基本不需要使用StatelessWidget,提出疑问?那是widget是怎么改变状态的了,怎么点击事情,onPress

答:StatelessWidget仅仅只是代表自身无状态,无setState,不能更改自身的widget,但是不代表子widget没有自身的状态,他们如果自身是statefulWidget,

​ 则说明,子widget可以改变自身的状态,这也是一种实现局部刷新的方式


class StateTest extends StatelessWidget {
  StateTest({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Center(
      child: Container(
        width: 200,
        height: 200,
        color: Colors.red,
        child: OutlinedButton(
          onPressed: (){
            print("StateTest");
          },
        ),
      ),
    );
  }
}

点击时:onPressed会被执行,但是StateTest自身widget不能被改变,无法执行自身的setStatus

1.被观察的属性成员,先申请为被观察者
import 'package:flutter_first/day54Getx/day2/PersonMy.dart';
import 'package:get/get.dart';
class CountController extends GetxController{

  //声明为被观察者
  var _count = 0.obs;

  var _myPerson = PersonMy('zhangsan', 20).obs;
  RxInt get getCount => _count;
  void addCount() {
    _count++;
  }
}
2.GetX创建GetxController对象,并被动监听
  GetX<CountController>(
                //初始化控制器
                init: CountController(),
                //监听回调
                builder: (CountController controller) {
                  return Text("当前 count 的值为 ${controller.getCount}");
                },
      ),
3.观察者自动更新,并被动监听
  Obx(() {
                return Text(
                    "Obx 当前 count 的值为 ${Get.find<CountController>().getCount}");
    }),

——————这种更新都是局部更新,实现了Provider中Selector的功能

注意:使用依赖管理时,应该使用Get.put

Controller controller = Get.put(Controller()); // Rather Controller controller = Controller();
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'CountObsMainPage.dart';

//程序入口
void main() {
  runApp(MyApp());
}


class MyApp extends StatelessWidget {
  MyApp({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return GetMaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      //默认显示的首页页面
      home: CountObsMainPage(),
    );
  }
}
import 'package:flutter_first/day54Getx/day2/PersonMy.dart';
import 'package:get/get.dart';

///第一步定义  Controller
class CountController extends GetxController {
  //声明为被观察者
  var _count = 0.obs;
  var _age = 0.obs;

  get age => _age;
  var _myPerson = PersonMy('zhangsan', 20).obs;

  RxInt get getCount => _count;

  //操作方法
  void addCount() {
    _count++;
  }

  void addAge() {
    _age++;
  }
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'CountController.dart';

class CountObsMainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Gex 响应编程"),
      ),
      backgroundColor: Colors.white,

      ///填充布局
      body: Container(
          padding: EdgeInsets.all(30),
          width: double.infinity,
          height: double.infinity,
          child: Column(
            children: [
              GetX<CountController>(
                //初始化控制器
                init: Get.put(CountController()),
                //监听回调
                builder: (CountController controller) {
                  return Text("当前 count 的值为 ${controller.getCount}");
                },
              ),

              //观察者自动更新
              Obx(() {
                return Text(
                    "Obx 当前 count 的值为 ${Get.find<CountController>().getCount}");
              }),
              Obx(() {
                return Text("Obx2 ${Get.find<CountController>().age}");
              })
            ],
          )),
      //点击按钮修改值
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          Get.find<CountController>().addCount();
        },
      ),
    );
  }
}
4.GetX小操作

当前页面

 final Controller c = Get.put(Controller());

则,可以通过Get找到一个正在被其他页面使用的Controller,并将它返回给你。

 final Controller c = Get.find();
5.使用过程注意
  1. GetxController 无需自行手动释放,原因 :

    在Getx中,Getx继承StatefulWidget,执行了GetInstance().delete(tag: widget.tag);

      @override
      void dispose() {
        if (widget.dispose != null) widget.dispose!(this);
        if (_isCreator! || widget.assignId) {
          if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) {
            GetInstance().delete<T>(tag: widget.tag);
          }
        }
        _subs.cancel();
        _observer!.close();
        controller = null;
        _isCreator = null;
        super.dispose();
      }
    
  2. 如果使用到Getx的obs,则全部整体需要使用GetX.to ,toNamed那一套,否则可能会出错,原因

    通过上面会在GetPage注册可知,说明在我们跳转页面的时候,GetX会拿你到页面信息存储起来,加以管理,下面俩种场景会导致GetxController无法释放

    • 未使用GetX提供的路由跳转:直接使用原生路由api的跳转操作
    • 这样会直接导致GetX无法感知对应页面GetxController的生命周期,会导致其无法释放
    • 在GetPage注册页面,不使用Get.toName,这样无法释放;GetPage+Get.toName配套使用可释放
    • 直接使用Get.to,可释放

4.跨页面通信,实现StreamController

​ A页面和B页面能找到相同的GetxController,然后B页面在GetxController更新数据,GetxController则通过发布订阅模式,将数据发送到A页面,起始本质与StreamController相同

import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'CountObsMainPage01.dart';

//程序入口
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  MyApp({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return GetMaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      //默认显示的首页页面
      home: CountObsMainPage01(),
    );
  }
}

import 'package:flutter_first/day54Getx/day2/PersonMy.dart';
import 'package:get/get.dart';

///第一步定义  Controller
class CountController01 extends GetxController {
  //声明为被观察者
  var _count = 0.obs;
  var _age = 0.obs;

  get age => _age;
  var _myPerson = PersonMy('zhangsan', 20).obs;

  RxInt get getCount => _count;

  //操作方法
  void addCount() {
    _count++;
  }

  void addAge() {
    _age++;
  }
}

import 'package:flutter/material.dart';
import 'package:flutter_first/day54Getx/day3/CountObsMainPage02.dart';
import 'package:get/get.dart';

import 'CountController01.dart';

class CountObsMainPage01 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Gex 响应编程"),
      ),
      backgroundColor: Colors.white,

      ///填充布局
      body: Container(
          padding: EdgeInsets.all(30),
          width: double.infinity,
          height: double.infinity,
          child: Column(
            children: [
              GetX<CountController01>(
                //初始化控制器
                init: Get.put(CountController01()),
                //监听回调
                builder: (CountController01 controller) {
                  return Text("当前 count 的值为 ${controller.getCount}");
                },
              ),
              //观察者自动更新
              Obx(() {
                return Text(
                    "Obx 当前 count 的值为 ${Get.find<CountController01>().getCount}");
              }),
              Obx(() {
                return Text("Obx2 ${Get.find<CountController01>().age}");
              }),
              OutlinedButton(
                  onPressed: () {
                    Get.to(CountObsMainPage02());
                  },
                  child: Text('进入2页面'))
            ],
          )),
      //点击按钮修改值
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          Get.find<CountController01>().addCount();
        },
      ),
    );
  }
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'CountController01.dart';

class CountObsMainPage02 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Gex 响应编程"),
      ),
      backgroundColor: Colors.white,

      ///填充布局
      body: Container(
          padding: EdgeInsets.all(30),
          width: double.infinity,
          height: double.infinity,
          child: Column(
            children: [
              //观察者自动更新
              Obx(() {
                return Text(
                    "Obx 当前 count 的值为 ${Get.find<CountController01>().getCount()}");
              }),
              OutlinedButton(
                  onPressed: () {
                    Get.find<CountController01>().addCount();
                  },
                  child: Text("点击"))
            ],
          )),
      //点击按钮修改值
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          Get.find<CountController01>().addCount();
        },
      ),
    );
  }
}

——————B页面点击时,A页面也会发生改变

5.GetBuilder

可以提升性能,但不太常用,主动观察,类似于Provider中notifyListeners,主动通知

通过GetBuilder实现响应式控件,控制器必须继承自GetxController,所定义的目标状态变量之后无需后缀".obs ",但需要定义方法update;并且加载指定控制器,不仅需要使用Get.put进行注入,而且GetBuilder还需要通过指定泛型绑定目标注入的控制器。

  GetBuilder<CountController>(
                builder: (CountController controller) {
                  return Text("当前Height值${controller.height}");
                },
              ),
import 'package:flutter_first/day54Getx/day2/PersonMy.dart';
import 'package:get/get.dart';

///第一步定义  Controller
class CountController extends GetxController {
  //声明为被观察者
  var _count = 0.obs;
  var _age = 0.obs;

  var _height = 0;

  get height => _height;

  get age => _age;
  var _myPerson = PersonMy('zhangsan', 20).obs;

  RxInt get getCount => _count;

  get controller => Get.find<CountController>();

  //操作方法
  void addCount() {
    _count++;
  }

  void addAge() {
    _age++;
  }

  void addHeight() {
    _height++;
    update();
  }
}
唯一的ID
如果你想用GetBuilder完善一个widget的更新控件,你可以给它们分配唯一的ID。不太常用

GetBuilder<Controller>(
  id: 'text' 、、这里
  init: Controller(), // 每个控制器只用一次
  builder: (_) => Text(
    '${Get.find<Controller>().counter}', //here
  ),
),

对比Getx vs obs vs GetBuilder

GetX比其他响应式状态管理器还是比较高效的,但它比GetBuilder多消耗一点内存。思前想后,以最大限度地消耗资源为目标,Obx应运而生。与GetX和GetBuilder不同的是,你将无法在Obx内部初始化一个控制器,它只是一个带有StreamSubscription的Widget,接收来自你的子代的变化事件,仅此而已。它比GetX更高效,但输给了GetBuilder,这是意料之中的,因为它是响应式的,而且GetBuilder有最简单的方法,即存储widget的hashcode和它的StateSetter。使用Obx,你不需要写你的控制器类型,你可以从多个不同的控制器中监听到变化,但它需要在之前进行初始化,或者使用本readme开头的示例方法,或者使用Bindings类。

6.workder的使用

///每次`count1`变化时调用。    
ever(_count, (callback) {
      print("$callback has been changed");
    });

    ///只有在变量$_第一次被改变时才会被调用。
    once(_age, (callback) => print("$callback was changed once"));

    ///防DDos - 每当用户停止输入1秒时调用,debounce将只发送最后一个事件。
    debounce(_count, (callback) => print("debouce$callback"), time: Duration(seconds: 3));

    ///忽略1秒内的所有变化。
    interval(_age, (callback) => print("interval $callback"), time: Duration(seconds: 1));

——————个人觉得debounce最有用,用于搜索时,当连续输入时,只用最后一次的进行网络请求搜索

7.依赖注入方式

这样,不影响其他的状态管理器

class Controller extends GetxController {
  StreamController<PersonMy> user = StreamController<PersonMy>();
  StreamController<String> name = StreamController<String>();

  @override
  void onInit() {
    super.onInit();
  }

  ///关闭流用onClose方法,而不是dispose
  @override
  void onClose() {
    user.close();
    name.close();
    super.onClose();
  }
}

8.GetView

这个挺有用的,封装了StatelessWidget,方便简洁使用,获取controller

abstract class GetView<T> extends StatelessWidget {
  const GetView({Key? key}) : super(key: key);

  final String? tag = null;

  T get controller => GetInstance().find<T>(tag: tag)!;

  @override
  Widget build(BuildContext context);
}

9.依赖管理

1. Get.put()
Get.put<SomeClass>(SomeClass());
Get.put<LoginController>(LoginController(), permanent: true);
Get.put<ListItemController>(ListItemController, tag: "some unique string");
Get.put<S>(
  // 必备:你想得到保存的类,比如控制器或其他东西。
  // 注:"S "意味着它可以是任何类型的类。
  S dependency

  // 可选:当你想要多个相同类型的类时,可以用这个方法。
  // 因为你通常使用Get.find<Controller>()来获取一个类。
  // 你需要使用标签来告诉你需要哪个实例。
  // 必须是唯一的字符串
  String tag,

  // 可选:默认情况下,get会在实例不再使用后进行销毁
  // (例如:一个已经销毁的视图的Controller)
  // 但你可能需要这个实例在整个应用生命周期中保留在那里,就像一个sharedPreferences的实例或其他东西。
  //所以你设置这个选项
  // 默认值为false
  bool permanent = false,

  // 可选:允许你在测试中使用一个抽象类后,用另一个抽象类代替它,然后再进行测试。
  // 默认为false
  bool overrideAbstract = false,

  // 可选:允许你使用函数而不是依赖(dependency)本身来创建依赖。
  // 这个不常用
  InstanceBuilderCallback<S> builder,
)

——————————tag的使用场景,同一个Controller时,可以使用,区别开来

——————————permanent : 生命周期,在整个应用中

2.Get.lazyPut

可以懒加载一个依赖,这样它只有在使用时才会被实例化。 在Get.find时才会被调用

3.Get.create

————————创建新的Controller, 使用不多

4.GetxService

但里面没有 “逻辑”。它只是通知GetX的依赖注入系统,这个子类不能从内存中删除。

所以这对保持你的 "服务 "总是可以被Get.find()获取到并保持运行是超级有用的。比如
ApiServiceStorageServiceCacheService

4.Bindings 重点,依赖注入

Bindings,常与Get.lazyPut/GetView一起使用,达到简洁的目的,这样可以不必要在Widget中写入Controller

1.定义Controller

import 'package:get/get.dart';

class HomeController extends GetxController {
  var _count = 0.obs;
  var _age = 0.obs;

  get count => _count;

  set count(value) {
    _count = value;
  } //操作方法
  void addCount() {
    _count++;
  }

  void addAge() {
    _age++;
  }

  get age => _age;

  set age(value) {
    _age = value;
  }
}

2.定义HomeBinding

import 'package:get/get.dart';

import 'HomeController.dart';

class HomeBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => HomeController());
  }
}

3.定义GetView

import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'HomeController.dart';

class HomeView extends GetView<HomeController> {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      decoration: BoxDecoration(
        color: Colors.white,
        image: DecorationImage(
          fit: BoxFit.cover,
          colorFilter: ColorFilter.linearToSrgbGamma(),
          image: NetworkImage(
              "https://images.pexels.com/photos/3902882/pexels-photo-3902882.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"),
        ),
      ),
      child: Scaffold(
        appBar: AppBar(
          title: Text("Gex 响应编程"),
        ),
        backgroundColor: Colors.white,
        body: buildWidget(),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: () {
            controller.addCount();
            controller.addAge();
          },
        ),
      ),
    );
  }

  Widget buildWidget() {
    return Container(
        padding: EdgeInsets.all(30),
        width: double.infinity,
        height: double.infinity,
        child: Column(
          children: [
            GetBuilder<HomeController>(
              builder: (HomeController controller) {
                return Text("当前Height值${controller.count}");
              },
            ),
            GetX<HomeController>(
              //监听回调
              builder: (HomeController controller) {
                return Text("当前 count 的值为 ${controller.count}");
              },
            ),
            //观察者自动更新
            Obx(() {
              return Text("Obx 当前 count 的值为 ${controller.age}");
            }),
            Obx(() {
              return Text("Obx2 ${controller.age}");
            })
          ],
        ));
  }
}

4.将GetMaterialApp设置为widget视图顶端

import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'package:flutter/cupertino.dart';

import 'AppPages.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  MyApp({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return GetMaterialApp(
      initialRoute: AppPages.INITIAL,
      getPages: AppPages.routes,
    );
  }
}

5.路由管理

import 'package:flutter_first/day54Getx/day4/routes/app_pages.dart';
import 'package:get/get.dart';

import 'HomeBinding.dart';
import 'HomeView.dart';

class AppPages {
  static const INITIAL = Routes.HOME;
  static final routes = [
    GetPage(
      name: Routes.HOME,
      page: () => HomeView(),
      binding: HomeBinding(),
      // children: [
      //   GetPage(
      //     name: Routes.COUNTRY,
      //     page: () => CountryView(),
      //     children: [
      //       GetPage(
      //         name: Routes.DETAILS,
      //         page: () => DetailsView(),
      //       ),
      //     ],
      //   ),
      // ]
    ),
  ];
}

6.route

part of 'app_pages.dart';
abstract class Routes {
  static const HOME = '/home';
  static const COUNTRY = '/country';
  static const DETAILS = '/details';
}

6.argument传值

import 'package:flutter_first/day54Getx/day4/routes/app_pages.dart';
import 'package:get/get.dart';

import 'CountryView.dart';
import 'HomeBinding.dart';
import 'HomeView.dart';

class AppPages {
  static const INITIAL = Routes.HOME;
  static final routes = [
    GetPage(
      name: Routes.HOME,
      page: () => HomeView(),
      binding: HomeBinding(),
      children: [
        GetPage(
          name: Routes.COUNTRY,
          page: () => CountryView(),
        ),
      ]
    ),
  ];
}

HomeView:

 OutlinedButton(
                onPressed: () {
                  Get.toNamed('/home/country',
                      arguments: {'name': 'zhangsan', 'age': 40});
                },
                child: Text('点击'))

countryView

 final country = Get.arguments as Map; //获取传输值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值