Flutter 插件turn_gen的使用

发布于 1周前 作者 vueper 最后一次编辑是 5天前 来自 Flutter

Flutter 插件turn_gen的使用

欢迎来到 TurnGen!这个项目是一组脚本组合而成的命令行工具。所有脚本都是用Dart语言编写的,并且可以即时运行而无需使用 build_runner。这些脚本旨在简化各种任务,例如:

  • 操作枚举类
  • 在数据类中创建不同的方法
  • 生成资源文件夹中所有文件的链接
  • 从标准构造函数创建联合类型

Alt 文本

安装

要使用 TurnGen,只需将其添加到 pubspec.yaml 文件作为 dev_dependencies

对于Flutter项目:

flutter pub add --dev turn_gen

对于Dart项目:

dart pub add --dev turn_gen

如果您打算使用资源文件夹中的链接生成器,可以在 pubspec.yaml 中添加输出文件路径,默认情况下,文件将在 lib/gen/ 中生成:

turn_gen:
  assets_output: "lib/app_gen/"

如果在 pubspec.yaml 中将 show_comments 设置为 true,这意味着方法和变量上的注释将在您的代码中显示,这极大地提高了可读性并帮助您理解发生了什么。默认情况下,此设置被禁用。

turn_gen:
  show_comments: true

然后运行 flutter pub getdart pub get 来安装包。

使用

自动检测正在运行的脚本

TurnGen 可以通过单个命令来运行,该命令会搜索具有特定注释的文件:

// turngen

我们使用以下命令启动它:

dart run turn_gen

例如,当使用 union 脚本时:

import 'package:meta/meta.dart';
// turngen
/* no: tojson fromJson */
@immutable
class _ConnectivityState {
  const _ConnectivityState.isDisonnected();
  const _ConnectivityState.isConnected();
  const _ConnectivityState.notDetermined();
}
// end

例如,当使用 data 脚本时:

// import 'package:meta/meta.dart';
// turngen
@immutable
class DataFio {
  /* init:'' */
  final String surname;
  /* init:'' */
  final String name;
  /* init:'' */
  final String patronymic;
  // end
}

例如,当使用 enum 脚本时:

// turngen
enum EnumLang  {
  ru,
  en;
// end
}

或者:

// turngen
enum EnumActivity  {
  normal(10),
  light(5),
  none(0);

  const EnumActivity(this.value);
  final int value;
// end
}

但是,运行此命令比直接调用所需的脚本稍慢一些,这些脚本将在下面描述。

枚举脚本

枚举类型

上述图显示了 enum 类的一些使用方式。Turngen 为方便操作 enum 添加了一些额外的方法。最重要的是,在构造函数中变量的类型并不重要。关键是要在关闭大括号之前添加注释 // end 以了解生成开始的位置。

以下是运行脚本的命令:

dart run turn_gen -t enum -f <path to your file>

如果您使用的是 VSCode,可以在 tasks.json 中添加任务:

{
  "label": "turn_gen enum",
  "type": "dart",
  "command": "dart",
  "args": ["run", "turn_gen", "-t", "enum", "-f", "${file}"],
}

运行脚本后,您将获得额外的方法并覆盖标准方法:

  • from...
  • from...OrNull
  • map
  • maybeMap
  • maybeMapOrNull
  • mapValue
  • maybeMapValue
  • maybeMapOrNullValue
  • getListValues
  • compareTo
  • toString

示例

enum Speed implements Comparable<Speed> {
  stop(0),
  slow(5),
  normal(10),
  fast(20);

  const Speed(this.value);

  final int value;
  // end

//          --TURN_GEN--
//             (enum)
//  *************************************
//         GENERATED CODE
//  *************************************

  static Speed fromValue(
    int? value, {
    Speed? fallback,
  }) {
    switch (value) {
      case 0:
        return stop;
      case 5:
        return slow;
      case 10:
        return normal;
      case 20:
        return fast;
      default:
        return fallback ??
            (throw ArgumentError.value(
              value,
              'value',
              'Value not found in Speed',
            ));
    }
  }

  static Speed? fromValueOrNull(
    int? value,
  ) {
    switch (value) {
      case 0:
        return stop;
      case 5:
        return slow;
      case 10:
        return normal;
      case 20:
        return fast;
      default:
        return null;
    }
  }

  T map<T>({
    required T Function() stop,
    required T Function() slow,
    required T Function() normal,
    required T Function() fast,
  }) {
    switch (this) {
      case Speed.stop:
        return stop();
      case Speed.slow:
        return slow();
      case Speed.normal:
        return normal();
      case Speed.fast:
        return fast();
    }
  }

  T mapValue<T>({
    required T stop,
    required T slow,
    required T normal,
    required T fast,
  }) {
    switch (this) {
      case Speed.stop:
        return stop;
      case Speed.slow:
        return slow;
      case Speed.normal:
        return normal;
      case Speed.fast:
        return fast;
    }
  }

  T maybeMap<T>({
    required T Function() orElse,
    T Function()? stop,
    T Function()? slow,
    T Function()? normal,
    T Function()? fast,
  }) =>
      map<T>(
        stop: stop ?? orElse,
        slow: slow ?? orElse,
        normal: normal ?? orElse,
        fast: fast ?? orElse,
      );

  T maybeMapValue<T>({
    required T orElse,
    T? stop,
    T? slow,
    T? normal,
    T? fast,
  }) =>
      mapValue<T>(
        stop: stop ?? orElse,
        slow: slow ?? orElse,
        normal: normal ?? orElse,
        fast: fast ?? orElse,
      );

  T? maybeMapOrNull<T>({
    T Function()? stop,
    T Function()? slow,
    T Function()? normal,
    T Function()? fast,
  }) =>
      maybeMap<T?>(
        orElse: () => null,
        stop: stop,
        slow: slow,
        normal: normal,
        fast: fast,
      );

  T? maybeMapOrNullValue<T>({
    T? stop,
    T? slow,
    T? normal,
    T? fast,
  }) =>
      maybeMapValue<T?>(
        orElse: null,
        stop: stop,
        slow: slow,
        normal: normal,
        fast: fast,
      );

  static List<int> getListValue() => Speed.values.map((e) => e.value).toList();

  @override
  int compareTo(Speed other) => index.compareTo(other.index);
}

extension $Speed on Speed {
  bool get isStop => this == Speed.stop;
  bool get isSlow => this == Speed.slow;
  bool get isNormal => this == Speed.normal;
  bool get isFast => this == Speed.fast;
}

资源脚本

TurnGen 还允许生成资源文件夹中所有文件的字符串常量,并且可以根据文件名使用不同的字符和字母。如果发现相同的文件名,则会在常量名称中添加数字。

如果您需要不同的路径来生成文件,请在 pubspec.yaml 中使用以下设置:

turn_gen:
  assets_output: "lib/gen/"

要启动,请使用以下命令:

dart run turn_gen assets

如果您使用的是 VSCode,可以在 tasks.json 中添加任务:

{
  "label": "turn_gen assets",
  "type": "shell",
  "command": "dart run turn_gen assets",
  "problemMatcher": []
}

运行脚本后,您将获得一个类中的所有文件路径:

示例资源生成器

class AssetPaths {
  const AssetPaths._();

  /// * 大小:8.8 KB
  /// * 文件路径:_assets/icons/app_icons.ttf
  static const String appIconsIcons = 'assets/icons/app_icons.ttf';

  /// * 大小:24.6 KB
  /// * 文件路径:_assets/image/onboarding_remind_you.svg
  static const String onboardingRemindYouImage = 'assets/image/onboarding_remind_you.svg';

  /// * 大小:60.8 KB
  /// * 文件路径:_assets/image/splash.png
  static const String splashImage = 'assets/image/splash.png';

  /// * 大小:4.8 KB
  /// * 文件路径:_assets/lottie/load_btn.json
  static const String loadBtnLottie = 'assets/lottie/load_btn.json';

  ...

  /// TTF 资产列表
  static const List<String> valuesTTF = [appIconsIcons];

  /// SVG 资产列表
  static const List<String> valuesSVG = [onboardingRemindYouImage, icErrorSvg, icErrorCloseSvg, icInfoSvg, icInfoCloseSvg, icSuccessSvg, icSuccessCloseSvg, icWarningSvg, icWarningCloseSvg, logoSvg, onb1Svg, onb2Svg, onb3Svg, onb4Svg, sortAscSvg, sortDescSvg];

  /// PNG 资产列表
  static const List<String> valuesPNG = [splashImage];

  /// JSON 资产列表
  static const List<String> valuesJSON = [loadBtnLottie, loadPageLottie, waterDownLottie, waterUpLottie];

  /// 所有资产列表
  static const List<String> valuesAll = [appIconsIcons, onboardingRemindYouImage, splashImage, loadBtnLottie, loadPageLottie, waterDownLottie, waterUpLottie, icErrorSvg, icErrorCloseSvg, icInfoSvg, icInfoCloseSvg, icSuccessSvg, icSuccessCloseSvg, icWarningSvg, icWarningCloseSvg, logoSvg, onb1Svg, onb2Svg, onb3Svg, onb4Svg, sortAscSvg, sortDescSvg];
}

数据脚本

TurnGen 脚本可以为 Dart 类生成和覆盖额外的方法,例如:

  • toMap/fromJsonfromMap/fromJson/fromDynamicMap 用于 Map/Json 序列化和反序列化
  • copyWith - 用于克隆具有不同属性的对象
  • operator == 和重写 hashCode(因为 TurnGet 仅适用于不可变类)
  • toString - 显示对象的所有属性列表

最重要的是,我们只需通过在类体中添加注释来进行定制,以下是必需的注释:

@immutable
class RegistrationState {
  final bool isLoad;
  final String? name;
  final List<int> activitySelected;

// end
}

现在让我们描述使用 TurnGen 的基本条件:

  • 类的所有字段都必须是 final
  • 声明完所有字段后,在末尾添加注释 // end

就这样!

额外类设置

您可以在类的开头使用注释添加其他设置,例如:

  • 只有 copyWith 方法。
/* only: copyWith  */
class RegistrationState {
...
  • 移除某些方法或几个方法。
/* no: fromMap toMap  */
class RegistrationState {
...
  • 使用 equatable 库。
/* use: equatable  */
class RegistrationState {
...

在上面的示例中,您可以组合来自其他方法的不同选项。

额外变量设置

变量也有设置,我们只需在类上方写入我们的关键字,例如:

  • 您可以初始化变量为任何文本。
/* init: true */
final bool isLoad;
/* init: 'Jon' */
final String name;
  • 如果未定义变量的类型,TurnGen 将尝试确定它并向您提示,但您可以明确指定它,关键字为 type: 和可能的选项 enum data List<data>
/*
type: enum
init: FormzSubmissionStatus.initial
*/
final FormzSubmissionStatus status;
/*
type: data
init: const DateRegModel()
*/
final DateRegModel dateRegModel;
/* type: List<data> */
final List<Name> nameList;
  • 如果 TurnGen 无法确定变量类型,请覆盖 toMapfromMap 方法。
/*
init: const DateRegModel()
fromMap: DateRegModel.fromMap(map['dateRegModel'] as Map<String, dynamic>)
toMap: dateRegModel.toMap()
*/
final DateRegModel dateRegModel;

使用

要启动,请使用以下命令:

dart run turn_gen
# 或者
dart run turn_gen -t data -f <path to your file>

如果您使用的是 VSCode,可以在 tasks.json 中添加任务:

{
  "label": "turn_gen data",
  "type": "dart",
  "command": "dart",
  "args": ["run", "turn_gen", "-t", "data", "-f", "${file}"]
}

执行脚本后,您将获得一个带有新方法和覆盖方法的标准 Dart 类:

示例

import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:meta/meta.dart';

// turngen
@immutable
class RegistrationState {
  final bool isLoad;
  final String? name;
  final List<int> activitySelected;

// end

//          --TURN_GEN--
//             (data)
//  *************************************
//         GENERATED CODE
//  *************************************
  const RegistrationState({
    required this.isLoad,
    required this.activitySelected,
    this.name,
  });

  Map<String, dynamic> toMap() {
    return <String, dynamic>{
      'isLoad': isLoad,
      'name': name,
      'activitySelected': activitySelected,
    };
  }

  factory RegistrationState.fromMap(Map<dynamic, dynamic> map) {
    return RegistrationState(
      isLoad: map['isLoad'] != null
          ? map['isLoad'] as bool
          : throw Exception(
              "map['isLoad']_type_'Null'",
            ),
      name: map['name'] as String?,
      activitySelected: map['activitySelected'] != null
          ? (map['activitySelected'] as List<dynamic>)
              .map((e) => e as int)
              .toList()
          : throw Exception(
              "map['activitySelected']_type_'Null'",
            ),
    );
  }

  RegistrationState copyWith({
    bool? isLoad,
    String? name,
    List<int>? activitySelected,
  }) {
    return RegistrationState(
      isLoad: isLoad ?? this.isLoad,
      name: name ?? this.name,
      activitySelected: activitySelected ?? this.activitySelected,
    );
  }

  String toJson() => json.encode(toMap());
  factory RegistrationState.fromJson(String source) =>
      RegistrationState.fromMap(
        json.decode(source) as Map<String, dynamic>,
      );

  @override
  bool operator ==(dynamic other) {
    return identical(this, other) ||
        (other.runtimeType == runtimeType &&
            other is RegistrationState &&
            (identical(
                  other.isLoad,
                  isLoad,
                ) ||
                other.isLoad == isLoad) &&
            (identical(
                  other.name,
                  name,
                ) ||
                other.name == name) &&
            const DeepCollectionEquality().equals(
              other.activitySelected,
              activitySelected,
            ));
  }

  @override
  int get hashCode => Object.hashAll([
        runtimeType,
        isLoad,
        name,
        const DeepCollectionEquality().hash(
          activitySelected,
        ),
      ]);

  @override
  String toString() {
    return 'RegistrationState(isLoad: $isLoad, name: $name, activitySelected: $activitySelected, )';
  }
}

联合脚本

TurnGen 脚本可以生成“联合类型”通过创建具有命名构造函数的类,但这需要创建一个假的私有类。这个类在任何地方都没有使用,但对于修改生成的代码很有用。并且在类末尾添加 // end 注释,如下所示的示例:

import 'package:collection/collection.dart';
import 'package:meta/meta.dart';

// turngen
class _Union {
  _Union.success({required List<User> listUser});
  _Union.load();
  _Union.init([String hello = 'Hello world']);
  _Union.error({String msg = ''});
}

// end

使用

要启动,请使用以下命令:

dart run turn_gen
# 或者
dart run turn_gen -t union -f <path to your file>

如果您使用的是 VSCode,可以在 tasks.json 中添加任务:

{
  "label": "turn_gen union",
  "type": "dart",
  "command": "dart",
  "args": ["run", "turn_gen", "-t", "union", "-f", "${file}"]
}

运行脚本后,您将获得额外的方法并覆盖标准方法:

  • map
  • maybeMap
  • mapOrNull
  • when
  • compareTo
  • toString, operator ==, hashCode

toJson/fromJson

添加了将 Union 类转换为 JSON 字符串和反向的功能。在进行初始转换时,您需要提供 tag。然而,对于后续转换,tag 参数是可选的。

// union class example

// turngen
@immutable
class _ApiLoanSchedule {

  /// 创建成功的 API 贷款计划。
  const _ApiLoanSchedule.success({
    List<ApiLoanScheduleItem> list = const [],
  });

  /// 创建错误的 API 贷款计划。
  const _ApiLoanSchedule.error({
    String message = '',
    String error = '',
    String code = '',
  });
}

// ... 其他代码

// 反序列化类
return response.statusCode == 200
          ? ApiLoanSchedule.fromJson(
              response.data,
              ApiLoanScheduleTag.success,
            )
          : ApiLoanSchedule.fromJson(
              response.data,
              ApiLoanScheduleTag.error,
            );

// 如何使用

loanScheduleModel.map(
      success: (v) {
// Something to do
      },
      error: (v) {
// Something to do
      },
    );

// 数据序列化
final loanScheduleJson = loanScheduleModel.toJson();

生成文件示例

class _Union {
  _Union.success({required List<User> listUser});
  _Union.load();
  _Union.init([String hello = 'Hello world']);
  _Union.error({String msg = ''});
}
// end

//          --TURN_GEN--
//  *************************************
//           GENERATED CODE 
//  *************************************

@immutable
class Union {
  const Union.success({required List<User> listUser}):
        _tag = _UnionTag.success,
        _listUser_success = listUser,
        _hello_init = null,
        _msg_error = null;
  const Union.load():
        _tag = _UnionTag.load,
        _listUser_success = null,
        _hello_init = null,
        _msg_error = null;
  const Union.init([String hello = 'Hello world']):
        _tag = _UnionTag.init,
        _listUser_success = null,
        _hello_init = hello,
        _msg_error = null;
  const Union.error({String msg = ''}):
        _tag = _UnionTag.error,
        _listUser_success = null,
        _hello_init = null,
        _msg_error = msg;

  T? mapOrNull<T>({
    T? Function(_UnionSuccess v)? success,
    T? Function(_UnionLoad v)? load,
    T? Function(_UnionInit v)? init,
    T? Function(_UnionError v)? error,
  }) {
    switch (_tag) {
      case _UnionTag.success:
        return success?.call(_UnionSuccess(_listUser_success!));
      case _UnionTag.load:
        return load?.call(const _UnionLoad());
      case _UnionTag.init:
        return init?.call(_UnionInit(_hello_init!));
      case _UnionTag.error:
        return error?.call(_UnionError(_msg_error!));
    }
  }

  T map<T>({
    required T Function(_UnionSuccess v) success,
    required T Function(_UnionLoad v) load,
    required T Function(_UnionInit v) init,
    required T Function(_UnionError v) error,
  }) {
    switch (_tag) {
      case _UnionTag.success:
        return success(_UnionSuccess(_listUser_success!));
      case _UnionTag.load:
        return load(const _UnionLoad());
      case _UnionTag.init:
        return init(_UnionInit(_hello_init!));
      case _UnionTag.error:
        return error(_UnionError(_msg_error!));
    }
  }

  T maybeMap<T>({
    T Function(_UnionSuccess v)? success,
    T Function(_UnionLoad v)? load,
    T Function(_UnionInit v)? init,
    T Function(_UnionError v)? error,
      required T Function() orElse,
  }) {
    switch (_tag) {
      case _UnionTag.success:
        if(success != null) return success(_UnionSuccess(_listUser_success!));
        return orElse();
      case _UnionTag.load:
        if(load != null) return load(const _UnionLoad());
        return orElse();
      case _UnionTag.init:
        if(init != null) return init(_UnionInit(_hello_init!));
        return orElse();
      case _UnionTag.error:
        if(error != null) return error(_UnionError(_msg_error!));
        return orElse();
    }
  }

  T when<T>({
    required T Function (List<User> listUser) success,
    required T Function () load,
    required T Function (String hello) init,
    required T Function (String msg) error,
}) {
    switch (_tag) {
      case _UnionTag.success:
        return success(_listUser_success!);
      case _UnionTag.load:
        return load();
      case _UnionTag.init:
        return init(_hello_init!);
      case _UnionTag.error:
        return error(_msg_error!);
    }
  }

  @override
  bool operator ==(dynamic other) {
    switch (_tag) {
      case _UnionTag.success:
        return identical(this, other) ||
        (other.runtimeType == runtimeType &&
            other is Union  &&  
 const DeepCollectionEquality().equals(other._listUser_success, _listUser_success,)); 
      case _UnionTag.load:
        return identical(this, other) ||
        (other.runtimeType == runtimeType &&
            other is Union ); 
      case _UnionTag.init:
        return identical(this, other) ||
        (other.runtimeType == runtimeType &&
            other is Union  &&  
 (identical(other._hello_init, _hello_init) || other._hello_init == _hello_init)); 
      case _UnionTag.error:
        return identical(this, other) ||
        (other.runtimeType == runtimeType &&
            other is Union  &&  
 (identical(other._msg_error, _msg_error) || other._msg_error == _msg_error));   
  }
}
  @override
  int get hashCode {
    switch (_tag) {
      case _UnionTag.success:
        return Object.hashAll([runtimeType, const DeepCollectionEquality().hash(_listUser_success)]);
      case _UnionTag.load:
        return Object.hashAll([runtimeType]);
      case _UnionTag.init:
        return Object.hashAll([runtimeType, _hello_init]);
      case _UnionTag.error:
        return Object.hashAll([runtimeType, _msg_error]);  
  }
}
  @override
  String toString() {
    switch (_tag) {
      case _UnionTag.success:
        return 'Union.success(listUser: $_listUser_success)';
      case _UnionTag.load:
        return 'Union.load()';
      case _UnionTag.init:
        return 'Union.init(hello: $_hello_init)';
      case _UnionTag.error:
        return 'Union.error(msg: $_msg_error)';  
  }
}
  final _UnionTag _tag;
  final List<User>? _listUser_success;
  final String? _hello_init;
  final String? _msg_error;

}

enum _UnionTag {
  success,
  load,
  init,
  error,
}
@immutable
class _UnionSuccess extends Union {
  const _UnionSuccess(this.listUser) : super.success(listUser: listUser);
  final List<User> listUser;
}
@immutable
class _UnionLoad extends Union {
  const _UnionLoad() : super.load();
}
@immutable
class _UnionInit extends Union {
  const _UnionInit(this.hello) : super.init( hello);
  final String hello;
}
@immutable
class _UnionError extends Union {
  const _UnionError(this.msg) : super.error(msg: msg);
  final String msg;
}

更新所有文件

如果需要更新所有使用 TurnGen 的文件,这些文件包含以下文本:

//          --TURN_GEN--
//             (data)
//  *************************************
//         GENERATED CODE 
//  *************************************

只需运行以下命令:

dart run turn_gen build

更多关于Flutter 插件turn_gen的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter 插件turn_gen的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中,如果你遇到一个名为 turn_gen 的插件,并且它的功能描述为“undefined”(未定义),这意味着我们没有官方文档或明确的功能说明来指导使用。然而,作为一个IT专家,我可以提供一些通用的方法来探索和使用这个插件,包括如何安装、初始化以及尝试调用其可能的方法。

请注意,由于我们不知道 turn_gen 插件的具体功能,以下代码案例将基于Flutter插件的一般使用模式进行假设。

1. 安装插件

首先,你需要在 pubspec.yaml 文件中添加这个插件的依赖。由于我们不知道具体的依赖名称和版本,这里假设依赖名称为 turn_gen(在实际使用中,你需要根据插件的实际名称进行修改):

dependencies:
  flutter:
    sdk: flutter
  turn_gen: ^x.y.z  # 替换为实际的版本号

然后运行 flutter pub get 来安装插件。

2. 导入插件

在你的 Dart 文件中导入插件:

import 'package:turn_gen/turn_gen.dart';

3. 初始化插件(如果需要)

有些插件可能需要在应用启动时进行初始化。由于我们不知道 turn_gen 插件是否需要初始化,这里提供一个假设性的初始化代码示例:

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  // 假设turn_gen有一个初始化方法initTurnGen
  // TurnGen.instance.initTurnGen();
  runApp(MyApp());
}

请注意,上面的 initTurnGen 方法是假设存在的,你需要根据插件的实际API进行调整。

4. 使用插件功能

由于我们不知道 turn_gen 插件的具体功能,这里只能提供一些通用的调用方法示例。假设插件有一个名为 generateSomething 的方法,你可以这样调用它:

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String result = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TurnGen Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Result: $result',
            ),
            ElevatedButton(
              onPressed: () async {
                // 假设turn_gen有一个名为generateSomething的异步方法
                // String generated = await TurnGen.instance.generateSomething();
                // setState(() {
                //   result = generated;
                // });
              },
              child: Text('Generate Something'),
            ),
          ],
        ),
      ),
    );
  }
}

同样,上面的 generateSomething 方法和 TurnGen.instance 是假设存在的。你需要根据插件的实际API文档进行调整。

结论

由于 turn_gen 插件的功能是未知的,上述代码案例都是基于假设的。在实际使用中,你需要查阅插件的官方文档或源代码来了解其具体的API和使用方法。如果插件没有提供足够的文档,你可以尝试在GitHub的issue页面或相关社区寻求帮助。

回到顶部