TypeScript全网最全学习资料--完整

TypeScript

一、简介📘

1.安装🆕

npm install -g typescript

2.创建文件📁

文件以xxx.ts结尾。

###3.编译文件📤

在命令行上使用TypeScript编译器:

tsc xxx.ts

二、TypeScript理论📝

1.基础数据类型🏡

TypeScript支持与JavaScript几乎相同的数据类型。

1.1. 布尔值-Boolean🍂
let isTag:boolean = false;
1.2. 数字-Number👊

和JavaScript一样,TypeScript里的所有数字都是浮点数,支持二进制、八进制、十进制、十六进制字面量。

let binaryLiteral: number = 0b1010; // 二进制
let octalLiteral: number = 0o744; // 八进制
let decLiteral: number = 6; // 十进制
let hexLiteral: number = 0xf00d; // 十六进制
1.3. 字符串-String🌶

字符串类型与JavaScript使用方式一致🔋,可以使用单引号和双引号表示,支持使用模板字符串。

let name:string = '梧桐也来';
let sentence:string = `${name}今年18岁`
1.4. 数组-Array🍡

TypeScript有两种方式定义数组😂。

第一种方式:在元素类型后面直接用[]定义,元素类型放在[]前。

let list:number[] = [1,2,3,4];

第二种方式:使用数组泛型,Array<元素类型>,关于泛型详见后面文档。

let list:Array<number> = [1,2,3,4];
1.5. 元组-Tuple🍐

元组类型是一个数组,是已知该数组的长度和元素类型,且各元素类型可不必相同。可以理解为混合类型数组💨。

let MixArr:[string,number,boolean]
MixArr = ['梧桐野老',18,true]
1.6. 枚举-Enum🐝

枚举类型可以理解为JavaScript中的对象,以键值对的形式存在,通过enum关键字来声明。

1.6.1 数字枚举🏃

定义枚举对象,默认不赋值,那么当前的枚举对象里面第一个的值从0开始一次递增。

enum device {
    phone,
    index,
    desktop,
    notebook
}

console.log(device.phone) // 0

也可以直接给赋值🍇。

enum device {
    phone = 2,
    index,
    desktop,
    notebook
}

默认给phone赋值一个2,则后面的值会默认递增,会变成index = 3desktop = 4notebook = 5

注意事项:❗️❗️❗️

  • 枚举对象成员递增值为1;

  • 枚举对象成员递增是看前一个枚举成员是否有值,有值就依次递增。跟第一个枚举成员值无关。

  • 当值重复时,枚举对象成员谁在后,就是谁。

enum device {
    phone = 2,
    notebook = 1,
    desktop
}

console.log(device.phone) // 2
console.log(device[2]) // desktop
1.6.2 字符串枚举🏃

字符串枚举没有递增,如果当前枚举成员的前一个枚举成员是一个字符串,那么当前枚举成员不赋值就会报错。

enum device {
  phone = '1',
  notebook, // 这里不赋值会报错
}
1.6.3 异构枚举🏃‍♀

异构枚举就是同时存在数字枚举成员和字符串枚举成员。

enum Preson {
  name = '梧桐野老',
  age = 30
}
1.6.4 计算的和常量成员🎽

枚举对象中的枚举成员都带有一个值,那么这个值不是计算的就是常量。那么如何区别是计算的还是常量?官方给出以下规则,当满足其中一个规则,那么当前的枚举成员就是一个常量。

  • 一个枚举表达式字面量(主要是字符串字面量或数字字面量);
  • 一个对之前定义的常量枚举成员引用(可以是再不同的枚举类型中定义的);
  • 带括号的常量枚举表达式;
  • 一元运算符 + ,- ,~其中之一应用在常量枚举表达式;
  • 常量枚举表达式作为二元运算符 + ,- , * ,/ ,% , << ,>> ,>>> , &,| ,^ 的操作对象。若常数枚举表达式求值后为NaN或Infinity,则会在编译阶段报错。
enum FileAccess {
    // constant members
    None,
    Read    = 1 << 1,
    Write   = 1 << 2,
    ReadWrite  = Read | Write,
    // computed member
    G = "123".length
}
1.6.5 联合枚举🏃

一种非计算的常量枚举成员子集:字面量枚举成员;字面量枚举成员是不带初始值的常量枚举成员,或者是值被初始话为:任何字符串字面量、任何数字字面量、应用一元(-)符号的数字字面量。

enum ShapeKind {
    Circle,
    Square,
}

interface Circle {
    kind: ShapeKind.Circle;
    radius: number;
}

interface Square {
    kind: ShapeKind.Square;
    sideLength: number;
}

let c: Circle = {
    kind: ShapeKind.Square,
    //    ~~~~~~~~~~~~~~~~ Error!
    radius: 100,
}
1.6.6 const枚举-常量枚举⚪️

一般情况下,普通枚举对象就可以满足我的需求,但是有些情况比如为了节省额外的开销和性能,我们可以选择使用常量枚举,常量枚举使用const关键字定义,它与普通枚举不同的时,它会在编译阶段删除该对象,且不能访问该枚举对象,只能访问该枚举对象成员。常量枚举的成员只能是常量枚举表达式,不可以使用计算值

const enum obj {
    A = 1,
    B = 3 * 6,
    C = 1 & 2
}

console.log(obj) // 报错


const enum obj {
    A = 1,
    B = 3 * 6,
    C = 1 & 2
}

console.log(obj.A) // 1
console.log(obj.B) // 18
console.log(obj.C) // 0
1.6.7 外部枚举🏉

外部枚举使用declare关键字定义,文档描述:外部枚举用来描述已经存在的枚举类型的形状,意思就是说外部枚举用来描述当前环境中存在的枚举对象。外部枚举和普通枚举的一个区别就是,在外部枚举里面没有初始化的枚举成员会当成一个计算值,而在普通枚举里面则是一个常量

declare enum Enum {
   A = 1,
   B,
   C = 2
}
1.6.8 反向映射🐤

除了创建一个以属性名做为对象成员的对象之外,数字枚举成员还具有了反向映射,从枚举值到枚举名字 。

enum Enum {
    A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
1.7 Any-任何类型✴️

不知道数据类型的时候,可以使用any来定义数据。

1.8 Void-没有类型❔

常用于一个函数没有返回值时,返回值类型就是void。声明一哥void类型的变量没有用,因为只能为它赋值undefined和null。

1.9 Null和Undefined💱

TypeScript里,undefinednull两者各自有自己的类型分别叫做undefinednull。 和 void相似,它们的本身的类型用处不是很大。默认情况下nullundefined是所有类型的子类型 。你指定了--strictNullChecks标记,nullundefined只能赋值给void和它们各自。

1.10 Never-永不存在的值的类型🔌

never类型是任何类型的子类型,也可以赋值给任何类型。

1.11 Object-非原始类型😡

使用object类型,就可以更好的表示像Object.create这样的API

declare function create(o: object | null): void;

create({ prop: 0 }); // OK
create(null); // OK

create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error
1.12 类型断言😻

类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用.断言类型有两种形式:

第一种是’尖括号’语法:

let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

第二种就是as语法:

let someValue: any = "this is a string";

let strLength: number = (someValue as string).length;

2.变量声明🏤

TypeScript变量声明方式和JavaScript的方式一致,其特点也与JavaScript一样。这里就不一一说明,常推荐使用letconst

TypeScript也支持和JavaScript的解构和展开运算方式,其使用方式也和JavaScript相似,需要注意的是目前TypeScript针对对象展开会丢失他的方法,因此使用时需要注意。

3.接口🌟🌟🌟

接口是TypeScript的核心原则之一是对值所具有的结构进行类型检查。被称做“鸭式辨型法”或“结构性子类型化” 。接口的作用就是为这些类型命名和为你的代码或第三方代码定义规则。用interface关键字进行定义。

3.1 接口属性👽
interface Person {
  name:string, // 必传属性
  age:number,
  gender?:string, // 可选属性
  [propName:string]:any, // 额外的属性检查
  readonly color:string // 只读属性
}

上述表示定义接口中的属性:当直接定义的属性为必传属性,缺少其中一个,则会报错;属性后面连接?则表示这个属性为可选属性,可传可不传;[propName:string]是额外的属性检查,当我们传入的属性如果不在接口中,如果不定义会报错,通过该方式可以进行解决;readonly属性则表示该属性只读,一旦被初始化定义后,就不能被改动。

var student:Preson = {
    name:'梧桐野老',
    age:18,
    weight:60,
    hight:180,
    color:'yellow'
}
// student.color = 'red' // Error 
student.gender = '男' // 这里需要注意,为什么使用var,使用let来定义会成为块作用域,外部不能访问
3.2 函数类型🐉

函数类型接口是把参数类型和返回值进行定义。

interface isMax {
    (x:number,y:number):boolean
}

let xIsMax:isMax
xIsMax = function(x:number,y:number){

    return x > y
}

let res = xIsMax(30,45)

console.log(res); // false

函数类型里面的形参可以和接口中定义的参数不一样。

let aMax:isMax
aMax = function(a:number,b:number):boolean {
    return a > b
}
let res1 = aMax(50,33)
console.log(res1); // true

函数类型中的形参类型也可以不定义,当不定义的时候,会根据接口中定义的类型来进行推断,当类型与接口中定义类型不一致时,会报错。

let cMax:isMax
cMax = function(c,d){   
    return c > d
    // return c // Error
}
let res2 = cMax(4,6)
console.log(res2);
// let res3 = cMax(4,'y') // Error
3.2 类类型🐍

接口在类中主要是实现类中的公共方法和属性,对于类中的私有属性,不会进行检测。

interface PersonInterface {
    name:string,
    age:number
}
class teacher implements PersonInterface {
    name: string;
    age: number;
    // age:string; // Error
    constructor(gender:string,hight:number){} // 不检测私有属性类型
}

需要注意,当我们使用constructor(构造器)去签名一个实例类型的接口时,会报错。因为constructor是类的静态部分,因此在类型检测时,只会检测实例部分的类型,而不会检测静态部分。

如果需要对实例类型进行接口签名,那么我们需要将实例接口改造为一个静态方法,通过创建一个实例类型的构成函数接口以及实例类型使用方法的接口;再定义一个构造函数将创建实例给传入。

interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
    tick();
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}
3.2.1 接口继承🐳

接口也可以类似类一样可以实现继承功能,运用extends。

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

// 多个继承
interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: number;
}
3.2.2 混合类型🐋

混合多个类型的接口。一个对象可以同时做为函数和对象使用,并带有额外的属性 。

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
3.2.3 接口继承类🐬

当一个接口继承类,那么只有这个类和其子类可以使用这个接口。

class Parent {
    name:string;
    age:number
}
// 定义一个接口继承类
interface ParentInterface extends Parent {
    say():void
}
// 子类
class Son extends Parent implements ParentInterface{
    say(){

    }
    name:string;
    age:number
}
class children extends Parent {
    say(){

    }
    name: string;
    age:number
}
// 非子类
class student implements ParentInterface{
    //类“student”错误实现接口“ParentInterface”。
  //类型“student”缺少类型“ParentInterface”中的以下属性: say, name, age
}

4.类🏠

4.1 创建类🏚

TypeScript中创建类和JavaScript中相同。

class Parent {
    name:string;
    age:number;
    la:string;
    constructor(lang:string){
        this.la = lang
    };
    say():string{
        return this.la // 注:方法中当类型不为void或any时,必须有返回值。
    }
}

let p = new Parent('chinese')
console.log(p.say()); // chinese
4.2 继承🏡

TypeScript创建类的继承和JavaScript中相同,如果要使用子类中使用父类的属性,那么需要使用super关键字。

class Parent {
    name:string;
    age:number;
    la:string;
    constructor(lang:string){
        this.la = lang
    };
    say():string{
        return this.la 
    }
}

class Son extends Parent {
    height:number
    constructor(name:string,age:number,la:string,height:number){
        super(name)
        this.name = name
        this.age = age
        this.la = la
        this.height = height
    };
    say(): string {
        return super.say() // 使用父类方法
    }
}

let s = new Son('梧桐野老',18,'chinese',170)
console.log(s);
console.log(s.say())
4.3 修饰符🏘
4.3.1 公共修饰符-public🏫

在TypeScript中的属性默认是public,也可以明确标记。

class Parent {
   public name:string;
    age:number;
    la:string;
    public constructor(public lang:string){
        this.la = lang
    };
    public say():string{
        return this.la 
    }
}
4.3.2 私有修饰符-private🏢

通过private修饰过的成员,将不被外部访问,只能内部使用,且子类也不能继承使用该属性。

class Parent {
   public name:string;
    private age:number;
    la:string;
    public constructor(public lang:string){
        this.la = lang
    };
    public say():string{
        return this.la 
    }
}
new Parent('中国话').age // Error
4.3.3 受保护的修饰符-protected🎒

通过protected修饰的成员,将不能被外部访问,但和private不同的是,被它修饰的成员可以在子类中使用。

class Parent {
   public name:string;
    private age:number;
    protected la:string;
    public constructor(public lang:string){
        this.la = lang
    };
    public say():string{
        return this.la 
    }
}
new Parent('中国话').la // Error
class Son extends Parent {
    height:number
    constructor(name:string,age:number,la:string,height:number){
        super(name)
        this.name = name
        this.age = age // Error 被Private修饰,不能在子类中使用。
        this.la = la
        this.height = height
    };
    say(): string {
        return super.say()
    }
}
4.3.4 只读修饰符-readonly🏣

只读修饰,当定义的成员为只读属性时,只能在声明时和构造函数中完成初始化。

class Teacher {
    readonly name:string;
    readonly num:number = 9527;
    constructor(thename:string){
        this.name = thename
    }
}

let t = new Teacher('梧桐野老')
console.log(t.name);
t.name = 'qwe'  // Error

当在构造函数中使用readonly时,相当于在构造函数中完成定义和初始化。

class Teacher {
    readonly num:number = 9527;
    constructor(readonly name:string){
    
    }
}

let t = new Teacher('梧桐野老')
console.log(t.name); // 梧桐野老
4.3.5 存取器🏥

TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。

let pwCode = 'hi123'

class UpdatePwCode{
    private _name:string = admin;

    get name():string{
        return this._name
    }

    set name(newName:string){
        if(pwCode && pwCode === 'hi123'){
            this._name = newName
        }else{
            console.log('update error');
        }
    }
}

let updatePwCode = new UpdatePwCode()
updatePwCode.name = '梧桐野老'

使用存取器需要注意以下几点:

​ 存取器要求你将编译器设置为输出ECMAScript 5或更高,不支持降级到ECMAScript 3;

​ 只带有get不带有set的存取器自动被推断为readonly。

4.3.6 静态属性-static🏦

用静态属性修饰的属性,将不是实例上,而是类本身的属性,如果需要访问该属性,将通过类名.属性来访问。

class Person {
    static pro = {name:'wutongyelao',age:18}
    static getPro(){
        console.log(Person.pro.name);      
    }
}

console.log(Person.getPro);
4.3.7 抽象类-abstract🏪

抽象类是作为其他类的基类来使用,不会直接被实例化,abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。当抽象类中使用了抽象方法,那么其子类内部必须要实现该方法。

abstract class Person {
    name:string;
    constructor(theName:string){
        this.name = theName
    }
    abstract say():void
}

class zhangSan extends Person{
    _age:number
    constructor(name:string,age:number){
        super(name)
        this._age = age
    };
    say(): void {
        
    }
}

5.函数🐭

5.1 函数定义🐱

简单的定义一个函数。

let ab = function(a:number,b:number):number{
  return a + b
}

一个完整的函数定义分为两部分:参数类型和返回值类型,通常以=>方式分割。

let ab:(val1:number,val2:number) => number = function(a:number,b:number):number{
  return a + b
}
5.2 推断类型🐶

一边指定了类型但是另一边没有类型,编译的时候会自动推断出类型。

let myAdd: (baseValue: number, increment: number) => number = function(x, y) { return x + y; };
5.3 默认参数和可选参数🐹

默认参数和JavaScript的使用方式一样,可选参数使用?标记。

function fn(first?:string,last='world'):string{}
5.4 剩余参数🐯

当不清楚传递实参有多少个时,可以创建一个变量将这些参数收集起来,采用数组的形式来收集。

function(color:string,...other:string[]):string{}
5.5 this问题🐰

TypeScript中的this问题和JavaScript中的一样,如果说明白this问题,那么我们需要搞懂this指向问题,这里就不需要过多累述。

6. 泛型🎍

6.1 使用泛型的原因💝

定义的API需要重复使用,那么就需要做到既要支持当前的数据类型,也要做好支持未来的数据类。

6.2 定义泛型 🎎

通过<>符号来定义泛型,通过传递一个变量进去,根据后续传入变量数据类型来确认数据类型。

6.2.1 泛型函数🎓
// 创建一个函数泛型
function fn<T>(arg:T):T{
  return arg
}

当在这里我们传入数据类型,就会得到相应的数据类型。

// 定义类型
let num = fn<number>(1) // typeOf(num)为number
// 推导类型
let num = fn(1) // typeOf(num)为number
6.2.2 泛型参数🎏

在使用函数泛型时,当我们需要对参数类型进行泛型定义。

// 在T变量后面直接添加类型
function fn<T>(arg:T[]):T[]{
  return arg
}
// 使用类型定义
function fn<T>(arg:Array<T>):Array<T>{
  return arg
}
以上两种方式是等价的。
6.2.3 泛型接口 🎆

通过接口,将所有数据类型进行定义。

interface GenericIdentityFn<T> {
    (arg: T): T;
}
function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
6.2.4 泛型类🎇
class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}
6.2.5 泛型约束🎐

一个数据类型限制另外一个数据类型。

// 方式一:通过继承下来的类型
interface Lengthwise {
    length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length); // 这里的的length为数字类型,故不会报错。
}
// 当传入数字类型,那么就会报错。因为这里就约束到传递的参数需要存在length的属性。
loggingIdentity(5) // Error
loggingIdentity("red") // true

// 方式二:通过声明的方式
function getProperty(obj: T, key: K) {
    return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // true
getProperty(x, "m"); // Error:这里通过obj对象限制了key的输出,只能是abcd中的一个。

这里主要的理论告一段落,关于其他知识点,和JavaScript中使用方式基本一致。这里简单描述下:

  • 定义多个数据类型可以使用|;
  • 备注使用三斜线///;
  • 模块、命名空间的使用方式和JavaScript一致。

三、项目配置🎑

这里主要讲解如何配置vue3项目。

1.安装依赖🎃

npm install --save-dev typescript webpack webpack-cli ts-loader css-loader vue vue-loader @vue/compiler-sfc

**注意:**在vue3中@vue/compiler-sfc替换了vue-template-compiler。对于版本问题,暂时未去探索。

如果不使用webpack进行打包的,可以将webpackwebpack-cli 去掉,直接搭建项目框架的vuevue-loader也可以去掉。这些依赖主要关注package.json文件,缺什么,补什么。

2.Typescript文件配置 👻

在tsconfig.json文件中创建。

// 初始化
tsc --init

tsconfig.json文件中的配置
{
    "compilerOptions": {
    		"baseUrl": ".", // 基础地址
    		"paths": {"@vue": ["packages/vue/src"]} // 路径查找
        "outDir": "dist", // 文件输出目录
        "sourceMap": false, // 是否生成目标文件的sourceMap文件
        "strict": true, // 严格模式总开关
        "noImplicitReturns": true,
        "module": "es2015", // 模块语法
        "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
        "target": "es5" // 编译后的目标版本,兼容低版本浏览器es5,中配es6,最新的esnext      
    		"noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
    		"experimentalDecorators": true, // 是否启用实验性的装饰器特性,不开启会出错
    		"jsx": "preserve", // 输出文件会带有.jsx扩展名
    },
    // 指定要编译的路径列表
    "include": [
        "./src/**/*"
    ]
}

3.脚手架搭建项目🎅

在vue中直接使用脚手架可以搭建支持TypeScript的项目。

# 1. Install Vue CLI, 如果尚未安装
npm install --global @vue/cli@next

# 2. 创建一个新项目, 选择 "Manually select features" 选项
vue create my-project-name

# 3. 如果已经有一个不存在TypeScript的 Vue CLI项目,请添加适当的 Vue CLI插件:
vue add typescrip

4.webpack配置 🎄

当不使用vue官方提供的脚手架搭建,使用webpack来搭建的情况下,需要添加webpack.config.js

var path = require('path')
var webpack = require('webpack')
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  entry: './src/index.ts',
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/dist/',
    filename: 'build.js'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            'scss': 'vue-style-loader!css-loader!sass-loader',
            'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
          }
          // other vue-loader options go here
        }
      },
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        exclude: /node_modules/,
        options: {
          appendTsSuffixTo: [/\.vue$/],
        }
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        loader: 'file-loader',
        options: {
          name: '[name].[ext]?[hash]'
        }
      },
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          'css-loader'
        ]
      }
    ]
  },
  resolve: {
    extensions: ['.ts', '.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  },
  devServer: {
    historyApiFallback: true,
    noInfo: true
  },
  performance: {
    hints: false
  },
  devtool: '#eval-source-map',
  plugins: [
    // make sure to include the plugin for the magic
    new VueLoaderPlugin()
  ]
}

if (process.env.NODE_ENV === 'production') {
  module.exports.devtool = '#source-map'
  // http://vue-loader.vuejs.org/en/workflow/production.html
  module.exports.plugins = (module.exports.plugins || []).concat([
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    }),
    new webpack.optimize.UglifyJsPlugin({
      sourceMap: true,
      compress: {
        warnings: false
      }
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true
    })
  ])
}

以上就是本次TypeScript相关的学习总结!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一心就想回农村

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值