axios网络模块封装笔记

本文介绍了在前端开发中,为什么选择axios作为网络模块,而非传统的Ajax或jQuery-Ajax。详细阐述了JSONP的工作原理,并对axios的功能特点、使用方法、创建实例以及拦截器的应用进行了详细的讲解,包括其支持的多种请求方式、全局配置和拦截器中的请求与响应处理。此外,还讨论了如何在项目中封装axios实例以提高代码复用性和灵活性。
部署运行你感兴趣的模型镜像

1.选择什么网络模块?

选择一: 传统的Ajax是基于XMLHttpRequest(XHR)

为什么不用它呢?

  • 非常好解释, 配置和调用方式等非常混乱.
  • 编码起来看起来就非常繁琐.
  • 所以真实开发中很少直接使用, 而是使用jQuery-Ajax

选择二: 在前面的学习中, 我们经常会使用jQuery-Ajax

相对于传统的Ajax非常好用.
为什么不选择它呢?

  • 首先, 我们先明确一点: 在Vue的整个开发中都是不需要使用jQuery了.
  • 那么, 就意味着为了方便我们进行一个网络请求, 特意引用一个jQuery, 不合理

选择三:axios

其实官方在Vue1.x的时候, 推出了Vue-resource.但是后来作者在说明不再继续更新和维护vue-resource了,所以现在推荐axios
axios有非常多的优点, 并且用起来也非常方便.

2.JSONP

在前端开发中, 我们一种常见的网络请求方式就是JSONP

  • 使用JSONP最主要的原因往往是为了解决跨域访问的问题.

JSONP的原理是什么呢?

  • JSONP的核心在于通过<script>标签的src来帮助我们请求数据.
  • 原因是我们的项目部署在domain1.com服务器上时, 是不能直接访问domain2.com服务器上的资料的.
  • 这个时候, 我们利用<script>标签的src帮助我们去服务器请求到数据, 将数据当做一个javascript的函数来执行,
    并且执行的过程中传入我们需要的json.
  • 所以, 封装jsonp的核心就在于我们监听window上的jsonp进行回调时的名称.

3.axios

功能特点:

  • 在浏览器中发送 XMLHttpRequests 请求
  • 在 node.js 中发送 http请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求和响应数据

支持多种请求方式:

  • axios(config)
  • axios.request(config)
  • axios.get(url[, config])
  • axios.delete(url[, config])
  • axios.head(url[, config])
  • axios.post(url[, data[, config]])
  • axios.put(url[, data[, config]])
  • axios.patch(url[, data[, config]])

axios的使用

npm install axios --save

(1)最基本使用
import axios from 'axios'

//axios本身可以返回一个promise,调用内部resolve
axios({
  // url:'httpbin.org/'
  url:'http://123.207.32.32:8000/home/multidata',
  method:'get'//默认情况下get请求
}).then(res => {
  console.log(res)
})
(2)专门针对get请求的参数拼接
axios({
  url:'http://123.207.32.32:8000/home/data',
  params:{
    type:'pop',
    page:1
  },
  method:'get'
}).then(res => {
  console.log(res)
})
(3)axios.get()
// 为给定 page 的 data 创建请求
axios.get('http://123.207.32.32:8000/home/data?type=sell&page=3')
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });
// 可选地,上面的请求可以这样做
axios.get('http://123.207.32.32:8000/home/data', {
    params: {
        type:'pop',
    	page:1
    }
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

执行 POST 请求

axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });
(4)axios.all执行多个并发请求

axios.all([axios({}),axios({})]).then(=>res{})

function getUserAccount() {
  return axios.get('/user/12345');
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
  .then(axios.spread(function (acct, perms) {
    // 两个请求现在都执行完成
  }));

axios.all([]) 返回的结果是一个数组res[0]、res[1],使用 axios.spread 可将数组 [res1,res2] 展开为 res1, res2

axios.all([axios.get('http://123.207.32.32:8000/home/multidata'),
  axios.get('http://123.207.32.32:8000/home/data',{params:{type:'pop',page:1}})
])
.then(axios.spread((res1,res2) => {
  console.log(res1);
  console.log(res2);
}))

补充一个小小的知识点
数组的解构

const names = ['11','22','33']
//const name1 = names[0]
//const name2 = names[1]
//const name3 = names[2]
const [name1,name2,name3] = names
(5)全局配置defaults

在这里插入图片描述

axios.defaults.baseURL='http://123.207.32.32:8000'
axios.defaults.timeout=5000

axios.all([axios.get('/home/multidata'),
  axios.get('/home/data',{params:{type:'pop',page:1}})
])
.then(axios.spread((res1,res2) => {
  console.log(res1);
  console.log(res2);
}))
(6)常见的配置选项

请求地址

  • url: ‘/user’,

请求类型

  • method: ‘get’,

请根路径

  • baseURL: ‘http://www.mt.com/api’,

请求前的数据处理

  • transformRequest:[function(data){}],

请求后的数据处理

  • transformResponse: [function(data){}],

自定义的请求头

  • headers:{‘x-Requested-With’:‘XMLHttpRequest’},

URL查询对象
(method: ‘get’,)

  • params:{ id: 12 },

查询对象序列化函数

  • paramsSerializer: function(params){ }

request body
(methos:‘post’)

  • data: { key: ‘aa’},

超时设置s

  • timeout: 1000,

跨域是否带Token

  • withCredentials: false,

自定义请求处理

  • adapter: function(resolve, reject, config){},

身份验证信息

  • auth: { uname: ‘’, pwd: ‘12’},

响应的数据格式 json / blob /document /arraybuffer / text / stream

  • responseType: ‘json’,

axios创建实例

为什么要创建axios的实例呢?

  • 当我们从axios模块中导入对象时, 使用的实例是默认的实例.
  • 当给该实例设置一些默认配置时, 这些配置就被固定下来了.
  • 但是后续开发中, 某些配置可能会不太一样.
  • 比如某些请求需要使用特定的baseURL或者timeout或者content-Type等.
  • 这个时候, 我们就可以创建新的实例, 并且传入属于该实例的配置信息.

服务器以后不在同一个ip地址里,就不方便使用全局配置,我们要创建对应的axios实例

const instancel = axios.create({
  baseURL:'http://123.207.32.32:8000',
  timeout:5000
})

instancel({
  url:'/home/multidata'
}).then(res => {
  console.log(res);
})

instancel({
  url:'/home/data',
  params:{type:'pop',page:1}
}).then(res => {
  console.log(res);
})

如果遇到不同的ip地址,再定义一个const instancel2 写上对应ip地址就好了
在每一个vue文件使用时在单独引入
HelloWorld.vue(这算一个子组件,注意要在父组件中引入)

<template>
  <h2>{{categories}}</h2>
</template>

<script>
  import axios from 'axios'
  export default {
    name: "HelloWorld",
    data() {
      return {
        categories: ''
      }
    },
    created() {//实例创建完成生命周期
      axios({
        url: 'http://123.207.32.32:8000/category'
      }).then(res => {
        this.categories = res;
      })
    }
  }
</script>

父组件中

<template>
  <div id="app">
    <div>{{result}}</div>
    <hello-world/>
  </div>
</template>

<script>
  import HelloWorld from './components/HelloWorld'

  import axios from 'axios'

  export default {
    name: 'App',
    components: {
      HelloWorld
    },
    data() {
      return {
        result: ''
      }
    },
    created() {
      axios({
        url: 'http://123.207.32.32:8000/home/multidata'
      }).then(res => {
        this.result = res;
      })
    }
  }
</script>

但是如果不封装一个实例,每次我们用到axios都要单独引用一下,很麻烦,下面我们进行封装
在这里插入图片描述

import axios from 'axios'

//考虑到扩展就不用defaule了
export function request(config) {
  //1.创建axios的实例
  const instance = axios.create({
    baseURL:'http://123.207.32.32:8000',
    timeout:5000
  })
  //发送真正的网络请求
  instance(config).then(res =>{
     console.log(res);
  }).catch(err => {
     console.log(err);
  })
}

console.log()但是我们不能在这个文件里进行传值呀,怎么传出去呢?
给request多添加两个参数 success, failure
方法一:

import axios from 'axios'

export function request(config, success, failure) {
  //1.创建axios的实例
  const instance = axios.create({
    baseURL:'http://123.207.32.32:8000',
    timeout:5000
  })
  //发送真正的网络请求
  instance(config).then(res =>{
    // console.log(res);
    success(res)//回调数据
  }).catch(err => {
    // console.log(err);
    failure(err)
  })
}

再在main.js中封装一个request模块

import {request} from "./network/request";

request({
  url:'/home/multidata'
},res =>{
  console.log(res);
},err => {
  console.log(err);
})

在这里插入图片描述
方法二

import axios from 'axios'

export function request(config) {
  //1.创建axios的实例
  const instance = axios.create({
    baseURL:'http://123.207.32.32:8000',
    timeout:5000
  })
  //发送真正的网络请求
  instance(config.baseConfig).then(res =>{//config中取出baseConfig
    config.success(res)//config中取出success,在这里进行回调1
  }).catch(err => {
    config.failure(err)//config中取出failure
  })
}

main.js中

import {request} from "./network/request";

request({
  baseConfig:{
    
  },
  success: function (res) {
    
  },
  failure: function (err) {
    
  }
})

除了以上两种回调函数方式,我们一般在项目中用第三种方法,promise方式

方法三

request.js

import axios from 'axios'

export function request(config) {
  return new Promise(((resolve, reject) => {
    const instance = axios.create({
    baseURL:'http://123.207.32.32:8000',
    timeout:5000
  })
  //发送真正的网络请求
  instance(config)
    .then(res =>{
    resolve(res)
  })
    .catch(err => {
    reject(err)
  })
  }
  ))
}

main.js封装一个request模块

import {request} from "./network/request";

request({
  url:'/home/multidata'
}).then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})

再或者request.js直接写成
面向request发送请求

export function request(config) {
      const instance = axios.create({
        baseURL:'http://123.207.32.32:8000',
        timeout:5000
      })
      //发送真正的网络请求
      return instance(config)
}

在这里插入图片描述
进入后再长按Ctrl进入AxiosInstance
在这里插入图片描述
也就是说我们定义的这个instance本身的返回值就是Promise
在这里插入图片描述
所以我们直接return instance,就可以了
封装好之后如果有一天axios不可用了,我们直接修改request.js中的引入,再修改export function request(config) {}中的内容就可以了

axios拦截器的使用

axios提供了拦截器,用于我们在发送每次请求或者得到相应后,进行对应的处理。
在这里插入图片描述

import axios from 'axios'

export function request(config) {
  //1.创建axios的实例
      const instance = axios.create({
        baseURL:'http://123.207.32.32:8000',
      })
      
  //2.axios拦截器
  instance.interceptors.request.use(config =>{
    console.log(config);
    //请求拦截
    return config//拦截掉之后还要原封不动的返回出去
  },err =>{} );
  
  instance.interceptors.response.use(res => {
  	//响应拦截
    // console.log(res);
    return res.data
  },err =>{
    console.log(err);
  });
  //3.发送真正的网络请求
      return instance(config)
}
//封装一个request模块
import {request} from "./network/request";

request({
  url:'/home/multidata'
}).then(res => {
  console.log(res)
}).catch(err => {
  // console.log(err)
})
拦截器中都做什么呢?
请求拦截
  • 1.比如config中的一些信息不符合服务器的要求
  • 2.比如每次发送网络请求时,都希望在界面中显示一个请求的图标(比如一个加载的图片)
  • 3.某些网络请求(比如说登录),必须携带一些特殊的信息
  • 请求拦截中错误拦截较少,通常都是配置相关的拦截
  • 可能的错误比如请求超时,可以将页面跳转到一个错误页面中。
响应拦截:
  • 响应的成功拦截中,主要是对数据进行过滤。上面代码中就对data进行了过滤return res.data
  • 响应的失败拦截中,可以根据status判断报错的错误码,跳转到不同的错误提示页面。
instance.interceptors.response.use(res => {
    return res.data
  },err =>{
    console.log(err);
    if (err && err.response) {
      switch (err.response.status) {
        case 400:
          err.message = '请求错误'
          break
        case 401:
          err.message = '未授权的防问'
          break
      }
    }
    return err
  });

(以上内容根据微博“coderwhy”的vue视频课程整理,感谢王红元老师ღ( ´・ᴗ・` )比心)

您可能感兴趣的与本文相关的镜像

Seed-Coder-8B-Base

Seed-Coder-8B-Base

文本生成
Seed-Coder

Seed-Coder是一个功能强大、透明、参数高效的 8B 级开源代码模型系列,包括基础变体、指导变体和推理变体,由字节团队开源

<think>我们使用TypeScript对axios进行二次封装,主要目的是为了统一处理请求和响应,增加类型安全,以及便于后续维护。封装将包括以下功能: 1.创建axios实例并配置基础URL、超时时间等。2.请求拦截器:例如,在请求头中添加token。3.响应拦截器:统一处理响应数据,处理错误(如网络错误、状态码错误等)。 4.封装GET、POST等常用方法,并支持类型泛型,以便在调用时指定返回数据类型。5.可扩展的配置,例如可以自定义处理请求和响应的逻辑。根据引用[3]和[4],我们将创建一个Axios实例,并添加拦截器,同时封装请求方法。下面是一个详细的封装步骤: ###一、创建axios实例首先,我们创建一个axios实例,设置一些默认配置(如baseURL、超时时间等)。###二、定义拦截器1.**请求拦截器**:在发送请求之前做些什么,例如加入token。2.**响应拦截器**:对响应数据做处理,例如统一处理错误状态码。 ###三、封装请求方法封装GET、POST、PUT、DELETE等方法,使用泛型来定义返回的数据类型。###四、使用示例###代码实现####1.创建实例和拦截器在`src/utils/request.ts`中: ```typescriptimportaxios,{ AxiosInstance, AxiosRequestConfig,AxiosResponse,InternalAxiosRequestConfig }from 'axios';//定义接口返回数据的结构(根据后端返回的数据结构定义) interfaceResponseData<T =any>{code:number;data: T;message:string;} //创建axios实例constinstance:AxiosInstance =axios.create({baseURL: import.meta.env.VITE_APP_API_BASE, //从环境变量获取timeout:10000, //超时时间headers:{'Content-Type':'application/json;charset=UTF-8'}}); //请求拦截器instance.interceptors.request.use((config: InternalAxiosRequestConfig) =>{//在发送请求之前做些什么,例如添加tokenconst token= localStorage.getItem('token');if(token) {config.headers.Authorization= `Bearer ${token}`;}return config;},(error)=> {//对请求错误做些什么return Promise.reject(error);}); //响应拦截器instance.interceptors.response.use((response: AxiosResponse<ResponseData>)=> {//对响应数据做点什么const res= response.data;//根据自定义错误码判断请求是否成功if(res.code ===0) {returnres.data; //返回后端接口返回的有效数据}else {//处理业务错误,例如抛出错误信息return Promise.reject(new Error(res.message|| 'Error'));}},(error) =>{//对响应错误做点什么if(error.response){//根据不同的状态码做不同的处理switch(error.response.status) {case401://未授权,跳转到登录页break;case403://禁止访问break;case404://资源未找到break;case500://服务器内部错误break;default:break;}}return Promise.reject(error);});``` ####2.封装请求方法在同一个文件中,我们封装常用的请求方法: ```typescript//封装GET请求functionget<T>(url:string,config?:AxiosRequestConfig): Promise<T>{return instance.get<T, T>(url, config); }//封装POST请求function post<T>(url:string,data?:any,config?:AxiosRequestConfig): Promise<T>{return instance.post<T, T>(url, data, config); }//封装PUT请求function put<T>(url:string,data?:any,config?:AxiosRequestConfig): Promise<T>{return instance.put<T, T>(url, data, config); }//封装DELETE请求function del<T>(url:string,config?:AxiosRequestConfig): Promise<T>{return instance.delete<T, T>(url, config); }//导出封装的方法export{ get, post, put, del}; ```####3.使用示例在组件中,我们可以这样使用:```typescriptimport {get,post }from '@/utils/request';//定义返回数据的类型interface UserInfo {name: string;age:number;} //获取用户信息constgetUserInfo= async() =>{try {constuserInfo =await get<UserInfo>('/user/info');console.log(userInfo);}catch (error){console.error(error);}}; //提交用户信息constsubmitUserInfo =async (userInfo: UserInfo)=> {try{const result= awaitpost<{success:boolean }>('/user/submit',userInfo);console.log(result);}catch (error){console.error(error);}}; ```###五、高级封装(可选)根据引用[4],我们可以进一步封装,将API接口集中管理,例如: 在`src/api/user.ts`中:```typescriptimport {get,post }from '@/utils/request';exportfunctiongetUserInfo() {returnget<UserInfo>('/user/info'); }export functionsubmitUserInfo(userInfo:UserInfo) {returnpost<{ success: boolean}>('/user/submit', userInfo);} ```然后在组件中引入并使用这些函数。###六、类型安全TypeScript的泛型在封装中起到了关键作用,它允许我们在调用时指定返回数据的类型,从而在开发过程中获得类型提示和编译时检查。 ###七、配置环境变量在`.env.development`等环境文件中配置`VITE_APP_API_BASE`(Vite项目)或`VUE_APP_API_BASE`(VueCLI项目)作为基础URL。###八、注意事项1.错误处理:在拦截器中统一处理错误,并在业务代码中捕获异常。2.类型定义:为后端返回的数据结构定义类型,并在请求方法中传入泛型。这样,我们就完成了一个基于TypeScript的axios二次封装,并配置了实例。这种封装方式提高了代码的可维护性和类型安全性。 ---§§相关问题§§1.如何在axios拦截器中实现token刷新机制? 2.如何取消重复的axios请求?3.如何为不同的API模块配置不同的baseURL?4.如何实现请求重试机制? 5.如何上传文件并跟踪上传进度?</think>### TypeScript 二次封装 Axios 完整指南 我将为您提供一个完整的 TypeScript 二次封装 Axios 解决方案,包含类型安全、统一配置和模块化管理功能。 #### 一、基础封装结构 ```typescript // src/utils/axios.ts import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; // 定义基础响应结构 export interface IResponse<T = any> { code: number; message: string; data: T; } // 创建 Axios 实例 const createAxiosInstance = (): AxiosInstance => { const instance = axios.create({ baseURL: import.meta.env.VITE_APP_API_BASE, // 使用环境变量 timeout: 10000, headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' } }); // 请求拦截器 instance.interceptors.request.use( (config) => { // 添加认证 token const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error) ); // 响应拦截器 instance.interceptors.response.use( (response: AxiosResponse<IResponse>) => { // 统一处理业务错误 if (response.data.code !== 0) { return Promise.reject(new Error(response.data.message)); } return response.data.data; // 直接返回有效数据 }, (error) => { // 统一处理 HTTP 错误 const status = error.response?.status; let message = '请求失败'; switch (status) { case 401: message = '身份验证失败'; break; case 403: message = '拒绝访问'; break; case 404: message = '资源不存在'; break; case 500: message = '服务器错误'; break; } return Promise.reject(new Error(message)); } ); return instance; }; export const http = createAxiosInstance(); ``` #### 二、高级封装(模块化管理) ```typescript // src/utils/httpService.ts import { http } from './axios'; // 定义 API 配置类型 export interface IApiConfig { url: string; method: 'get' | 'post' | 'put' | 'delete' | 'patch'; } // 定义模块 API 集合 export interface IApiCollection { [key: string]: IApiConfig; } // 定义请求选项 export interface IRequestOptions<T = any> { params?: Record<string, any>; // URL 参数 data?: T; // 请求体数据 handler?: (response: any) => void; // 自定义响应处理 } class HttpService { private apiCollections: Record<string, IApiCollection> = {}; // 注册 API 模块 registerModule(moduleName: string, apis: IApiCollection): void { this.apiCollections[moduleName] = apis; } // 发起请求 async request<T = any, R = any>( moduleName: string, apiName: string, options: IRequestOptions<T> = {} ): Promise<R> { const module = this.apiCollections[moduleName]; if (!module) throw new Error(`模块 ${moduleName} 未注册`); const apiConfig = module[apiName]; if (!apiConfig) throw new Error(`API ${apiName} 不存在`); const { url, method } = apiConfig; const { params, data, handler } = options; try { const response = await http.request({ url, method, params, data }); // 自定义处理 if (handler) handler(response); return response as R; } catch (error) { console.error(`请求失败: ${moduleName}.${apiName}`, error); throw error; } } } export const httpService = new HttpService(); ``` #### 三、API 模块配置 ```typescript // src/api/public.ts import { IApiCollection } from '@/utils/httpService'; const publicApis: IApiCollection = { login: { url: '/auth/login', method: 'post' }, logout: { url: '/auth/logout', method: 'post' }, getCurrentUser: { url: '/user/current', method: 'get' } }; export default publicApis; ``` ```typescript // src/api/business.ts import { IApiCollection } from '@/utils/httpService'; const businessApis: IApiCollection = { getNotes: { url: '/notes', method: 'get' }, createNote: { url: '/notes', method: 'post' }, deleteNote: { url: '/notes/{id}', method: 'delete' } }; export default businessApis; ``` #### 四、初始化 HTTP 服务 ```typescript // src/services/http.ts import httpService from '@/utils/httpService'; import publicApis from '@/api/public'; import businessApis from '@/api/business'; // 注册 API 模块 httpService.registerModule('public', publicApis); httpService.registerModule('business', businessApis); export default httpService; ``` #### 五、使用示例 ```typescript // 在组件中使用 import httpService from '@/services/http'; // 定义数据类型 interface User { id: number; name: string; email: string; } interface Note { id: number; title: string; content: string; } // 登录请求 const handleLogin = async () => { try { const user = await httpService.request<User>( 'public', 'login', { data: { username: 'admin', password: '123456' } } ); console.log('登录成功', user); } catch (error) { console.error('登录失败', error); } }; // 获取笔记列表 const fetchNotes = async () => { try { const notes = await httpService.request<Note[]>( 'business', 'getNotes' ); return notes; } catch (error) { console.error('获取笔记失败', error); return []; } }; // 自定义处理响应 const customHandler = () => { httpService.request( 'business', 'getNotes', { handler: (response: Note[]) => { console.log('自定义处理:', response); // 执行额外逻辑... } } ); }; ``` #### 六、类型增强(可选) ```typescript // src/types/http.d.ts declare module '@/services/http' { interface ApiModules { public: { login: { data: { username: string; password: string }; response: User }; logout: { response: void }; getCurrentUser: { response: User }; }; business: { getNotes: { response: Note[] }; createNote: { data: Omit<Note, 'id'>; response: Note }; deleteNote: { params: { id: number }; response: void }; }; } // 增强 request 方法类型 export interface EnhancedHttpService { request< Module extends keyof ApiModules, Api extends keyof ApiModules[Module] >( module: Module, api: Api, options?: IRequestOptions<ApiModules[Module][Api]['data']> ): Promise<ApiModules[Module][Api]['response']>; } } ``` ### 关键优势: 1. **类型安全**:全程 TypeScript 类型支持 2. **模块化管理**:按业务模块组织 API 3. **统一错误处理**:全局拦截器处理异常 4. **可扩展性**:支持自定义响应处理器 5. **环境集成**:无缝对接环境变量配置 6. **认证集成**:自动添加身份验证令牌 此封装方案参考了 Axios 最佳实践[^1][^3][^4],结合 TypeScript 类型系统[^1],提供了企业级的 API 管理解决方案。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值