史上最详细的typescript入门教程

什么是Typescript?

TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript。TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。

配置Typescript环境

  • 安装Typescript Compiler
npm install -g typescript

  可使用tsc -V进行版本查看.

  • tslint的配置(可选配置,编辑器都有默认配置)
  1. 安装tslint
npm install -g tslint
  1. C:\Users\16609\AppData\Roaming\npm\node_modules\tslint路径下新建tslint.json
{
  "rules": {
    // TS特性
    "member-access": true, // 设置成员对象的访问权限(public,private,protect)
    "member-ordering": [// 设置修饰符顺序
      true,
      {
        "order": [
          "public-static-field",
          "public-static-method",
          "protected-static-field",
          "protected-static-method",
          "private-static-field",
          "private-static-method",
          "public-instance-field",
          "protected-instance-field",
          "private-instance-field",
          "public-constructor",
          "protected-constructor",
          "private-constructor",
          "public-instance-method",
          "protected-instance-method",
          "private-instance-method"
        ]
      }
    ],
    "no-empty-interface":true,// 不允许空接口
    "no-parameter-reassignment":true,// 不允许修改方法输入参数
    "prefer-for-of":true,// 如果for循环中没有使用索引,建议是使用for-of

    // 功能特性
    "await-promise":true,// 不允许没有Promise的情况下使用await
    "curly":true,// if/for/do/while强制使用大括号
    "forin":true,// 使用for in语句时,强制进行hasOwnProperty检查
    "no-arg":true,// 不允许使用arguments.callee
    // "no-bitwise":true, // 不允许使用特殊运算符 &, &=, |, |=, ^, ^=, <<, <<=, >>, >>=, >>>, >>>=, ~
    "no-conditional-assignment":true,// do while/for/if/while 语句中将会对例如if(a=b)进行检查
    // "no-console":true,// 不允许使用console对象
    "no-debugger":true,// 不允许使用debugger
    "no-duplicate-super":true,// 不允许super() 两次使用在构造函数中
    "interface-name" : [true, "never-prefix"],
    "no-empty":true,// 函数体不允许空
    "no-eval":false,// 不允许使用eval
    "no-for-in-array":true,// 不允许对Array使用for-in
    "no-invalid-template-strings":true,// 只允许在模板字符串中使用${
    "no-invalid-this":false,// 不允许在class之外使用this
    "no-null-keyword":true,// 不允许使用null,使用undefined代替null,指代空指针对象
    "no-sparse-arrays":true,// 不允许array中有空元素
    "no-string-throw":true,// 不允许throw一个字符串
    "no-switch-case-fall-through":true,// 不允许case段落中在没有使用breack的情况下,在新启一段case逻辑
    "no-unsafe-finally":true,// 不允许在finally语句中使用return/continue/break/throw
    "no-unused-expression":true,// 不允许使用未使用的表达式
    "no-use-before-declare":true,// 在使用前必须声明
    "no-var-keyword":true,// 不允许使用var
    "radix":true,// parseInt时,必须输入radix精度参数
    "restrict-plus-operands":true,// 不允许自动类型转换,如果已设置不允许使用关键字var该设置无效
    "triple-equals":true,// 必须使用恒等号,进行等于比较
    "use-isnan":true,// 只允许使用isNaN方法检查数字是否有效

    // 维护性功能
    "indent":[true, "spaces", 4],// 每行开始以4个空格符开始
    "linebreak-style":["off","windows"],// 换行符格式 CR/LF可以通用使用在windows和osx
    // "max-classes-per-file":[true,1],// 每个文件中可定义类的个数
    "max-file-line-count":[true,1000],// 定义每个文件代码行数
    "max-line-length":[false,120],// 定义每行代码数
    "no-default-export":true,// 禁止使用export default关键字,因为当export对象名称发生变化时,需要修改import中的对象名。https://github.com/palantir/tslint/issues/1182#issue-151780453
    "no-duplicate-imports":true,// 禁止在一个文件内,多次引用同一module

    // 格式
    "align":[true,"parameters","arguments","statements","members","elements"],// 定义对齐风格
    "array-type":[true,"array"],// 建议使用T[]方式声明一个数组对象
    "class-name":true,// 类名以大驼峰格式命名
    "comment-format":[true, "check-space"],// 定义注释格式
    "encoding":true,// 定义编码格式默认utf-8
    "import-spacing":true,// import关键字后加空格
    "jsdoc-format":true,// 注释基于jsdoc风格
    "new-parens":true,// 调用构造函数时需要用括号
    "no-consecutive-blank-lines":[true,2],// 不允许有空行
    "no-trailing-whitespace": [// 不允许空格结尾
      true,
      "ignore-comments",
      "ignore-jsdoc"
    ],
    "no-unnecessary-initializer":true,// 不允许没有必要的初始化
    "variable-name":[false,"check-format",// 定义变量命名规则
      "allow-leading-underscore",
      "allow-trailing-underscore",
      "ban-keywords"]
  }
}
  1. 随便找一个目录新建一个文件夹,切换到cmdgit bush,输入命令
tsc --init

输入上述命令之后会在目录上生成一个tsconfig.json文件,这个文件是用来将ts编译成js代码的文件

基本数据类型

总所周知,javascript的基本类型有7种,分别是

  1. Number
  2. String
  3. Boolean
  4. Null
  5. Undefined
  6. Object
  7. Symbol(ES6新增)

  而这其中在typescript种均可以使用,除此之外typescript存在很多其他的数据类型,诸如联合类型(一个值可能有两种类型的类型)接口类型等等,所以在typescript种我们既可以使用js的类型又可以自己定义类型。

Number类型

Number、Boolean、String、Symbol四个规则一样,不能被赋予其他值
小技巧:vscode使用快捷键ctrl + shift + b,可实时进行编译
请看如下代码

let num: number = 1
console.log(num)
// 这里我们的num就被指定成了number类型,所以你只能给num赋值或改变成number类型的值
// 否则编辑器就会报错,但是我们能编译成js通过

  当我们将num修改为其他类型的变量时结果如下,这个时候就会报错,但是我们能在编译出来的js文件种查看到能编译通过
在这里插入图片描述

null和undefined类型

  null和undefined类型在js里就是一对相爱相杀的兄弟,在ts里面也如,他们可以被互相赋值,但是其它类型的值(除了any类型)都不可以赋值给他们;

let a3: undefined;
let a4: null;
let a1: null = a3
let a2: undefined = a4

数组类型

  在typescript中,数组类型的变量有两种定义方式

let num: number[] = [1, 2]
let str: Array<string> = ['1', '2'];
// 推荐使用第一种字面量的形式

元组类型

  其实元组类型是数组类型的子集。在数组类型中,我们的数组元素只能用定义好的一种类型的变量,但是在元组中,我们可以定义多个类型的变量的数组,但是这也限定了元组的数据量,不能多也不能少

let arr3: [number, string, boolean] = [1, 'sad', true];
枚举类型

  枚举类型是一种特殊的类型,主要用于状态码的取值

enum Color {
    blue = 3,
    red = 1
}

  默认情况下,每一个元组key的value都是number,而且是从0开始计,而且都可以被指定新的value。
  但是我们可以认为改变这个value,value的改变规则如下。

  1. 将第一个改变为number类型时,第二个以及以后的key他的value紧接着第一个key的value+1
  2. 将第一个改变为string类型时,第二个必须指定为number类型或一个string类型,若指定为string类型,第三个和第二个的情况一样,依此类推。
  3. 类似其他情况与第二种情况一样,若某一个改变为string类型其下一个的value必须指定为number或string,依此类推。
enum Color {
    blue = '5',
    red = 1,
    yellow = 'str'
}

  获取元组类型的变量某一个值就只需要像对象一样取得就可以比如,但是比对象更灵活,你不仅可以取得他的key,而且可以取得他的value,不必像对象那样进行Object.keys操作

let yellowValue: Color = Color.yellow
let yellowKey: string = Color['str']
console.log(yellow)
any类型

  any类型就是任意类型,在这个时候编辑器不会对any类型的变量做出静态类型检查,它可以被赋值和改变成其他所有类型的变量,而且均不报错。
  切记:不要滥用any类型,一般用于:你不知道这个参数应该被赋予什么类型的时候这个参数的类型可以暂定为any类型

let num: any = 'sad'
num = {obj: 'xxx'}

nerver类型

  never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never。

let num1: never
let a: never = num1;
联合类型和类型断言

  前面我们说过typescript中存在联合类型,也就是一个变量存在一个或两个以上的数据类型,导致我们不清楚他是什么类型,这个类型就叫做联合类型,具体如下

function getArg(param: string | number) {
    return param.length
}

  这个时候param就被称为一个联合类型变量,但是这里我们的编辑器会报错,因为number类型的变量不存在length属性,所以我们需要对param这一联合类型的变量做一个类型断言(判定他是这个类型),代码如下

// 类型断言有两种
// 第一种:(<type>param),这一种不可用于JSX或TSX语法
function getArg(param: string | number) {
    if ((<string>param).length) {
        return (<string>param).length
    } else {
        return param
    }
}
// 第二种:(param as type),可用于TSX或JSX语法,推荐使用这一种
function getArg(param: string | number) {
    if ((param as string).length) {
        return (param as string).length
    } else {
        return param
    }
}

函数

  在typescript中函数也是一种类型,也分为函数声明和函数表达式。同样函数也支持ES6的默认参数和rest参数,以及自己的可选参数,支持函数重载(相同函数名,不同的参数)

  • 可选参数
// 传参数, ?代表可选参数,但是可选参数后面不能有任何其他的必选参数
function foo(arg1: string, arg2?: number): string {
    return arg1 + arg2
}
  • 默认参数
// 默认参数,当传过来的值严格等于undefined时,arg默认为20
let foo2 = function (arg: number = 20): number {
    return arg
};
  • rest参数
function sum(...rest: number []): number {
    return rest.reduce((cur, next) => cur + next)
}
  • 函数重载
    函数重载和java的函数重载概念类似
function css(el: string, value: Object): any;

function css(el: string, value: string): any;

function css(el: string, value: any): any {
    if (typeof value === 'string') {
        console.log('传入的时string')
    } else if ((value as object)){
        console.log('传入的是对象')
    }
}

  我们都知道ES6是有类的,并且现在主流浏览器都支持class类的声明,在typescript里也有类的概念。并且typescript的类支持接口的实现继承父类

class Person {
    protected age: number; // 子类可访问到,实例也访问得到,外部访问不了
    private name: string; // 子类访问不到, 实例也访问不到,外部访问不了
    public sex: string; // 子类、实例、外部均可访问
    constructor (name: string, age: number) {
        this.name = name
        this.age = age
    }
    public getName (): string {
        return this.name
    }
    public getAge (): number {
        return this.age
    }
}
  • 类的继承
    在上述例子中,我们定义了一个Person的类
class Man extends Person {

    constructor (name: string, age: number) {
        super(name, age) // 必须调用,否则Man类的this不明,
        //需要传递父类构造方法需要的参数,并且只能定义在这个方法后的第一行
    }
	// 重写父类的getAge方法
    public getAge (): number {
        return this.age
    }
}

接口

在typescript中我们使用interface关键字来定义接口,接口主要用来限定对象、函数的返回值、参数、变量等的类型以及类的属性,在接口里面定义的必需参数就必须实现

  • 普通接口
    普通接口主要实现对对象的属性进行限制
interface Config {
    method: string;
    url: string;
    data?: string; // 可选接口参数
    dataType: string;
}
// 必须实现必需参数,否则会报错
let congig: Config = {
    method: '',
    url: '',
    data: '', 
    dataType: ''
}
  • 函数接口
    函数接口主要实现对函数参数和返回值的限制
interface Encrypto {
	// key,value必须是string,函数返回值也必须是string
    (key: string, value: string): string
}
let md5: Encrypto = function (key: string, value: string): string {
	return key + value
}
  • 可索引接口
    可索引接口主要是对数组或对象的key和value做出约束
// 可索引接口(对数组的约束)
interface UseArr {
    [index: number]: string 
    // 索引是number类型,索引值是string类型
}
// 对对象的约束
interface UseObj {
    [index: string]: number 
    // 索引是string类型(重要),索引值number类型(或其他类型)
}

let obj: UseObj = {
    name: 1
};

let arr: UseArr = ['234'];
  • 类接口
      类接口用于类实现类接口所定义的必需的方法和属性,可选方法和属性可以实现也可以不实现。
// 类类型接口,对类的约束,和抽象类相似
interface Animal {
    public name: string;

    eat?(str: string): void; // 可选方法接口类型
}
 // Dog必须要实现Animal的方法和属性
class Dog implements Animal {
    public name: string;

    constructor(name: string) {
        this.name = name
    }

    public eat(str: string): void {
        console.log(this.name + '吃' + str)
    }
}
  • 继承接口
      继承接口主要用于类,这样类就可以实现多个方法,主要用于一个类实现多个接口的方法和参数
interface Person {
    readonly name: string;
    work(job: string): void
}
// Man接口继承Person接口
interface Man extends Person{
    eat(food: string):void
}
// 定义一个程序员类
class Programmer {
    public readonly name: string;
    constructor (name: string) {
        this.name = name;
    }
    public code () {
        console.log(this.name + '写代码')
    }
}
// 前端不仅继承程序员,还要实现Man接口的方法和参数
class Web extends Programmer implements Man{
    public name: string;
    constructor (name: string) {
        super(name)
    }
    public work(job: string): void {
        console.log(this.name + '从事' + job)
    }
    public eat(food: string): void {
        console.log(this.name + '吃' + food)
    }
}

泛型

  泛型是typescript一种特殊的类型,是为了弥补强制类型和any类型之间的不足。

  • 泛型方法
// 第一个需求:我们需要方法传入和返回一个string类型的值
function getData(value: string):string {
    return value
}
// 第二个需求:我们需要方法传入和返回一个number类型的值
function getData(value: number):number{
    return value
}
// ....

  如果这样的需求很多的话,我们的代码就会非常冗余,所以为了简便,typescript定义了泛型这一新的类型。
  所以上述的需求我们只用一个函数实现

function getData<T>(value: T): T {
	return value
}
// 第一个需求
getData<string>('123')
// 第二个需求
getData<number>(123)
// ...

  这样我们的代码就会非常简洁,而且易于阅读

  • 泛型类
      如果类里面的方法或属性是一个泛型方法或泛型属性时,我们就需要这个类是一个泛型类。
// 泛型类
class MinClass<T> {
    public list: T[] = []; // 泛型参数

    public add(num: T): void { // 泛型方法
        this.list.push(num)
    }

    public min(): T {
        let minNum = this.list[0];
        for (let i = 0; i < this.list.length; i++) {
            if (minNum > this.list[i]) {
                minNum = this.list[i]
            }
        }
        return minNum
    }
}
// 指定泛型类的类型时number类型,类里面凡是属于泛型的方法或参数,
// 其类型就绑定为了number类型
let m = new MinClass<number>(); // 实例化类,并指定类的类型是number
m.add(1);
m.add(8);
m.add(6);
m.add(9);
console.log(m.min());
  • 泛型接口

泛型接口有两种:

  1. 第一种:接口不是泛型,但是接口属性是泛型;(不完全泛型接口)
  2. 第二种:接口就是泛型
  • 不完全泛型接口

不完全泛型接口,主要指的是接口属性是泛型。

interface CompA {
   // 函数类型接口
   <T>(key: T): T
}
// 实现接口的泛型方法,
let setData: CompA = function <T>(key: T): T {
   return key
};
// 指定泛型方法为number类型
setData<number>(123)
  • 完全泛型接口

完全泛型接口指的是接口本身就是泛型的

// 接口就是泛型
interface CompB<T> {
    (key: T): T
}
// 指定泛型接口的类型为string类型(这个接口内所有的泛型属性和方法均为string类型)
// 然后实现泛型方法
let myGetData: CompB<string> = function getData <T>(key: T): T {
    return key
};
setData<string>('123')
  • 类为泛型参数

  类当作参数的情况主要用于数据的增删改查,将泛型类里面的泛型参数限定为类。

// 定义一个User类
class User {
    private username: string;
    private password: string;
    constructor (username: string, password: string) {
        this.username = username;
        this.password = password
    }
}
// 定义一个文章类
interface ArticleListPram {
	title: string, 
	time: string
}
// 定义一个文章类
class ArticleList {
    private title: string;
    private time: string;
    constructor (params: ArticleListPram) {
        this.title = params.title;
        this.time = params.time
    }
}
// 定义一个MySqlDB的泛型类
class MySqlDB<T> {
    public add(user: T): boolean {
        console.log(user);
        return true
    }
}
// 这里我们就能够通过切换泛型类的指定类型来实现不用的数据的增删改查
let user = new User('张三', '123456');
let mySqlDBForUser = new MySqlDB<User>(); // 指定User为泛型类的类型
mySqlDBForUser.add(user);

let article = new ArticleList({title: '掘金', time: '2018-1-2'});
// 指定ArticleList为泛型类的类型
let mySqlDBForArticle = new MySqlDB<ArticleList>(); 
mySqlDBForArticle.add(article);

模块系统

  在ES6我们知道有模块系统,在typescript里面同样也有,且导入导出规则和ES6规则一致。

  1. export default有且只能有一个,在导入该方式导出的模块不用加{}
  2. export导出的模块可以有多个,在导入该方式导出的模块要加{},并且名字必须一样
// App.ts
export default "http://localhost:3000";

export function getData(): any[] {
    return [
        { title: 123 },
        { id: 123 }
    ]
}
// index.ts
import url, {getData} from './App'

let data: any[] = getData();
console.log(data);
console.log(url)

命名空间

  假设这么一个情况,我们有多人在开发一个ts项目,那么就必然会存在函数或者方法重名的情况。这个时候我们就需要命名空间来解决这一问题。
  命名空间关键字是namespace,并且支持命名空间的导入和导出,并且在命名空间内部的属性必须通过export才能够实现命名空间的调用

// index.ts
// 导出一个命名空间A
export namespace A {
    interface Animal {
        name: string;
        eat(food: string): void
    }
	// 在命名空间里面导出一个Lion的类,并且Lion类实现Animal接口
    export class Lion implements Animal {
        public name: string;        
        constructor (name?: string) {
            this.name = name
        }
        public eat(food: string): void {
            console.log(this.name + 'is eating' + food)
        }
    }
}
// 命名空间未导出
namespace B {
    interface Animal {
        name: string;
        eat(food: string): void
    }

    export class Lion implements Animal {
        public name: string;        
        public eat(food: string): void {
            console.log(this.name + 'is eating' + food)
        }
    }
}

let lion = new B.Lion(); // 因为B导出了Lione类,所以我们可以使用 ”.“调用
lion.name = '小黑'
lion.eat('屎')
// App.ts
import {A} from './index'

let lion = new A.Lion(); // 导入了A命名空间,用法和B一样
lion.name = '老鼠'
lion.eat('苍蝇')

装饰器

  装饰器就是用来对装饰的类、属性、方法进行一次或多次地加工,和spring地注解有点相似,都是使用@符号进行操作,但是两者之间的用途却相差甚远。
  所谓的装饰器其实就是一个方法,不过参数被限制了而已。
  装饰器是ES7的stage-1的一个规范,现在的浏览器和node环境还不支持装饰器。所以若要使用请先开启tsconfig.json的一个参数。
  装饰器的运行主要在于:在实例化类的之前或实例化类之后调用方法之前,运行装饰器
可以将@理解为方法的一个自动执行的符号,条件是类被实例化

"compilerOptions": {
    "experimentalDecorators": true,
}

注意:装饰器只能用于类或类的属性和方法,不可用于普通函数或其他情况

  • 装饰类的装饰器

  • 不带参数的直接装饰
      不带参数的直接装饰器接收一个参数target,代表装饰的目标类,当目标类被实例化时运行装饰器方法
function logo(target: any) {
	target.flag = true; // 为目标类的实例绑定一个flag属性
    console.log('this is router')
}
// 每当类被实例化,就会执行logo方法
@log
class Person() {
 	
}
  • 带参数的类装饰器
      带参数的类装饰器其实就是一个方法返回一个方法,这样当类实例化的时候先执行方法再执行返回的方法
function log(url: string): Function {
   return function (target: any) {
       target.prototype.apiUrl = url
   }
}
// 每当类被实例化,先执行log方法,然后再执行@这个自执行操作
@log('http//localhost:3000')
class Person() {

}
  • 方法装饰器

  属性装饰器同类装饰器使用方法一致,分为带参数的属性装饰器和不带参数的属性装饰器,两种装饰器都接受两个参数

  1. target: 装饰的目标类
  2. name: 装饰的方法名字
  3. descriptor: 目标参数的属性描述符(属性有value(函数体)、enumrable、writable、configurable)

  使用方法和原理同类装饰器一致。可以为被装饰的方法设置相关的操作。

// 方法装饰器
function logMethods(params: any) {
    return function (target: any, methodsName: string, descriptor: any): void {
        console.log(target, methodsName, descriptor);
        target.run = function () { // 为目标实例新增run方法
            console.log('I\'m runnigng')
        }
        let oldValue = descriptor.value; // 保存原始的函数体
        descriptor.value = function (...args: any[]) { // 重新赋值函数体
            args = args.map(arg => '' + arg);
            oldValue.apply(this, args);
        }
    }
}
class HttpServer {
    public url: string;
    constructor() {
        this.url = "this is url"
    }
    @logMethods('get')
    getDate(...args: any[]): void {
        console.log(args);
        console.log('我是getdata')
    }
}
  • 方法参数装饰器

  方法参数装饰器用来装饰方法的参数,在实例化对象并调用方法之前执行方法参数装饰器,用来对参数进行包装或返回一个新的参数。
接收三个参数:

  1. target: 目标对象
  2. paramName: 参数名
  3. index: 参数的位置
// 方法参数装饰器(target, paramsName, paramsIndex)
function logParams (params: any) {
    return function (target: any, paramsName: any, index: number) {
        target.params = params // 为方法添加一个新属性param
        console.log(target, paramsName, index, params)
    }
}
class HttpServer {
    public url: string;
    constructor() {
        this.url = "this is url"
    }

    // 在调用方法的时候会执行方法,然后再执行参数装饰器
    postData(@logParams('uuid') uuid: any): void {
        console.log(uuid)
    }
}

  目前位置,掌握以上typescript知识可以开始编写相关的一些项目。
  麻烦为我点个赞,写了不久的时间,多谢各位美女和帅哥

  • 12
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
如果你想学习 TypeScript,我推荐你查看引用中提到的一篇入门教程。这篇教程将带你一步步学习 TypeScript 的十四个入门知识点,并且提供了详细的内容大纲。教程会教你如何在系统中全局安装 TypeScript,可以使用 npm 或者 yarn 进行安装。TypeScript 是 JavaScript 的超集,它在 JavaScript 的基础上添加了可选的静态类型和基于类的面向对象编程特性。微软在 2012 年正式发布了 TypeScript,并且持续更新和维护了 8 年。因此学习 TypeScript 是非常有价值的,尤其对于前端开发人员来说,它已经成为一门必备的技能。所以如果你想学习 TypeScript,这个入门教程会是一个不错的起点。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [最全TypeScript 入门基础教程,看完就会](https://blog.csdn.net/qq_43623970/article/details/107067306)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [TypeScript入门教程](https://blog.csdn.net/SoSalty/article/details/129372440)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值