Webpack 常用知识储备及优化

本文详细介绍Webpack的高级配置,包括防止缓存、配置本地服务、合并配置文件、多入口打包、插件使用、优化构建速度等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 防止打包后出现缓存文件?

两种方案

  • 使用 HASH:filename:"main.[hash:数字].js"「默认名字」
    • 可以控制 hash 值的长度:hash:你想要的长度main.[hash:5].js
    • 防止出现缓存,每次都会生成一个新的随机字符串,不会出现缓存问题
  • 使用插件:clean-webpack-plugin清除缓存文件
    • 如果没有修改文件,那么执行使用缓存文件,不会打包成新的文件
    • 形成新的文件,删除旧的文件

2. 怎么配置本地服务和proxy 代理?

配置本地服务,有两种方案

  • 通过 webpack-dev-server 可以打开一个本地服务

    • 配置 package.json 文件

      "scripts": {
        "start": "webpack-dev-server"
      }
      
      • 执行 npm run start
      • 通过 localhost:8080 在浏览器中打开
      • 本地服务启动成功
    • 配置 webpack.config.js

    devServer:{
      port: 9000,
      open: true,
      compress: true,
      contentBase: resolve(__dirname, '../public')
    }
    
    • port9090「修改服务的端口号」
    • opentrue「服务启动之后自动打开浏览器」
      • 或者是在 package.json 中添加--open ,也是可以实现的
    • compresstrue「是否开启静态压缩(gzip)」
    • contentBaseresolve(__dirname, '../public')
      • public 文件下的资源都可以通过服务方式请求
      • public 下的文件当做可访问的服务器中的文件
    • hottrue「热更新」
      • 不需要其他配置,webpack 自动处理
    • before:function(app){}
      • 接口造假数据,在请求数据之前先走这里
  • 安装 @webpack-cli/serve

    • 配置 package.json 文件
    "scripts": {
      "start": "webpack serve"
    }
    
    • 执行 npm run start
    • 通过 localhost:8080 在浏览器中打开
    • 本地服务启动成功

配置proxy 代理

两种方式:

  • 第一种方式
proxy: {
    'api': {
        target: "代理路径"
        pathRewrite: {// 路径重写:凡是以 api 开头的,都被替换成:''
            '^/api': ''
        }
    }
}
  • 第二种方式
proxy: {
	// 只要路径中包含:"/api" 的请求,都会被代理到你配置的路径中
    "/api": "代理路径"
}  

3. 合并配置文件

借助插件 webpack-merge:两个对象合并成一个对象

使用方法

  • 引入必须使用解构赋值的方式
let {merge} = require('webpack-merge');
merge(base, {
  // 这里是需要合并的另一个配置文件
})

4. 多入口打包

  • 入口文件

    • entry 是一个对象,配置多个入口
    entry:{
      index: './src/index.js',
      other: './src/other.js'
    }
    
  • output:出口

    • 对应多个出口:name「入口定义的名字」: index/other
output: {
	filename: '[name].[hash:5].js'
}
  • 几个页面 new 几个 HtmlWebpackPlugin

5. webpack 插件

clean-webpack-plugin :清除打包文件缓存

  • 描述
    • 在放入新打包的资源文件之前,先把旧的资源文件删除
  • 引入
let {CleanWebpackPlugin} = require('clean-webpack-plugin')
  • 使用方法:
plugins:[
   new CleanWebpackPlugin()
]
  • 配置项:cleanOnceBeforeBuildPatterns
    • 默认会全部删除旧的资源
    • 指定某个文件资源不删除
plugins:[
  new CleanWebpackPlugin({
      cleanOnceBeforeBuildPatterns: [**/*, '!文件名']
  })
]

html-webpack-plugin:打包文件自动引入 HTML 中

描述:自动生成带有入口文件引用的 index.html
使用方法

let HtmlwebpackPlugin = require("html-webpack-plugin");
// 配置
plugins: [
  new HTMLWebpackPlugin({
      template: 'HTML 文件地址',
      filename: '打包之后的 HTML 文件名',
      title: '设置 HTML 文件名'
  })
]

配置项

  • templateHTML 文件地址
    • 如果不设置 template,那么 webpack 会自动生成一个 HTML 文件
  • filename:打包之后的 HTML 文件名
  • title:设置 HTML 文件名
    • html 模板文件需要设置:
<%= htmlWebpackPlugin.options.title %>

<title>
     <%= htmlWebpackPlugin.options.title %>
</title>
  • hashtrue「给CSSJS 文件设置 hash 值」
    • 打包之后的 HTML 文件中引入的 JSCSS 会加 hash 值,比如:
      <script src="dist_index.js?fdfde3b822e33cb629a9"</script>
  • chunks: [index, other]:指定当前模板引入哪个JS
    • 多入口打包:chunks:['index123','common']

mini-css-extract-plugin

描述:将 CSS 提取为独立的文件的插件,对每个包含 CSS 的 JS 文件都会创建一个 CSS 文件,致辞按需加载
使用方法:们需要使用的是这个插件的 loader ,在 css-loader之前添加

let MiniCssExtractPlugin = require("mini-css-extract-plugin");
plugins: [
 new MiniCssExtractPlugin()
]
rules: [
  {test: '/.\css$/', use: [
    MiniCssExtractPlugin.loader, 'css-loader'
  ]}
]
  • 配置项
    • filename:'css/[css文件名].css'

optimize-css-assets-webpack-plugin:CSS 文件压缩「CSS 默认不压缩」

  • 使用方法
    • 引入:let optimizationCssAssets = require('optimize-css-assets-webpack-plugin')
    • 配置
    optimization:{
      minimizer: [
        new optimizationCssAssets()
      ]
    }
    
    

terser-webpack-plugin:处理 CSS 压缩之后,JS 不自动压缩的请情况

描述:使用 terser 来压缩 JS
使用方法

  • 引入:let terserWebpackPlugin = require('terser-webpack-plugin')
  • 配置
optimization:{
  minimizer: [
  	new terserWebpackPlugin()
  ]
}

webpack-manifest-plugin

描述:生产资产的显示清单文件

optimize-css-assests-webpack-plugin

描述: 用于优化或者压缩 CSS 资源

6. 常用的 loader

CSS 中的常用 loader

css-loader

描述:加载 CSS,支持模块化、压缩、文件导入等
弊端:把所有的 CSS 代码全都插入到 JS 代码文件中
优化:使用插件:mini-css-extract-plugin,把 CSS 单拎成一个文件

style-loader

描述:把 CSS 代码注入到 JS 中,通过 DOM 来操作去加载 CSS

less-loader:处理 less 文件

描述:把 LESS 编译成 CSS

file-loader

描述:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件

url-loader

描述: 在文件很小的情况下以 base64 的方式把文件内容注入到代码中去

  • url-loader会默认调用 file-loader,需要引入 这两个 loader
  • 把文件变成一个路径,是调用了 file-loader

配置项options

  • name:img/[name].[ext]
    • 原始名字叫什么,使用 loader 之后还叫什么「这是 file-laoder 的配置」
  • limit:限制文件大小「数字代表的是 bit 字节」
    • 如果图片小于设置的这个限制,会转成 base64,大于则默认会调用 file-loader,变成路径
    • 图片转 base64 的优点
      • 变成 base64 的好处就是可以减少 HTTP 请求
      • 减少占用服务器资源
postcss-loader:自动添加 CSS 前缀

描述:可以处理 CSS 的兼容写法

  • 在处理对应的 CSS 文件之前,先加上 postcss-loader
rules: [
  {test: '/.\css$/', use: [
    MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'
  ]}
]
  • 配置 postcss.config.js
    • 需要安装:postcss-preset-env
    • package.json 同级下建立一个文件:postcss.config.js『这个文件是 postcss 的配置文件』
module.exports = {
  plugins: [
  	'post-preset-env'
  ]
}
  • 设置浏览器的兼容版本「.browserslisttrc 文件」
    • package.json 同级下建立一个文件:.browserslisttrc『这个文件是告诉 postcss 需要兼容浏览器』
    • 文件内容:cover 99.9%

JS 中常用的 loader

eslint-loader

描述:通过 ESlint 检查 JS 代码

处理 JS 高级语言转换为低级语言
  • 需要借助 babel
    • babel-loader
    • @babel/core
    • @babel/preset-env
  • ES6 新语法需要安装:@babel/plugin-syntax-class-properties 插件来编译,babel-loader目前不支持
class A {
  constructor() {
     this.a=677; // 支持
  }
  xxx=123;// 不支持
}

  • @logger:装饰器,需要安装 @babel/plugin-proposal-decorators 插件
  • async await ,需要安装的插件:
    • @babel/plugin-transform-runtime

    • @babel/runtime/

    • @babel/runtime-corejs3

    • 配置项:corjs,是 @babel/runtime-corejs3 核心库,把不兼容的语法全都转义成兼容语法,值为 3 即可

  • babelpluginspreset 的区别
    • presets:预设「插件的集合」
    • presets 可以作为 babel 插件的组合,提前准备好所需要的插件,如果局部使用插件,则不会放在预设中
    • plugins:插件,我们需要使用的,但是,预设中没有包含的插件
{
    test: /\.js$/,
    use: {
        loader: 'babel-loader',
        options: {
            presets: ['@babel/preset-env'],
            plugins: [
                 ['@babel/plugin- proposal-decorators'{legacy: true}]['@babel/plugin- proposal-class-properties', {loose: true}]['@babel/plugin-transform-runtime'{ corejs: 3 }]
             ]
       }
    },
    exclude: /node_modules/
}
// @babel/plugin-proposal-decorators 和 @babel/plugin- proposal-class-properties ,同时存在,其 decorators 插件先引入,class-properties 随后

7. Loader 和 Plugin 的不同

Loader

  • Loader 是转换器。webpack 只能解析 JS 文件,如果想把其他文件也打包的话,就会用到 Loader

  • Loader 的作用:让 webpack 具有加载和解析非 JS 文件的能力
    Plugin

  • Plugin 是插件。可以扩展 webpack 的功能,让 webpack 具有更多的灵活性。

8. webpack 的构建流程

  • 初始化参数:拿到配置文件中的参数和 Shell 语句中「输入命令行时传递的参数:webpack --mode=development」读取并合并参数,拿到最终的参数

  • 开始编译:用上一步拿到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;根据配置中的 entry 找到所有的入口文件

  • 编译模块:从入口文件开始,调用所有配置的 Loader 对模块进行编译,通过递归查询该模块依赖的模块,然后在对模块进行编译,直至所有入口依赖的文件都经过了处理

  • 完成模块编译:经过使用 Loader 编译完所有模块后,得到每一个模块被编译后的最终内容以及它们之间的依赖关系,根据入口和模块之间的依赖关系,组成一个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件假如到输出列表中;到这里是修改输出内容的最后机会

  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统中

9. source map 是什么?生产环境怎么用?

  • source map 是为了解决开发代码与实际运行代码不一致时,帮助我们 debug 到原始开发代码的技术
  • webpack 通过配置可以自动给我们 source maps 文件,map 文件是一种对应编译文件和源文件的方法
source map 的类型
  • source-map:原始代码,会生成 map 格式的文件,有映射关系的代码「性能慢」
  • eval-source-map:原始代码,会生成 source-map「最高的执行和最低的性能」
  • cheap-module-eval-source-map:原始代码(只有行信息)「更高的质量和更低的性能」
  • cheap-eval-source-map:转换代码(行内)「无法看到真正的源码」
  • eval:生成代码
  • cheap-source-map:转换代码(行信息)没有映射
  • cheap-module-source-map:原始代码(只有行信息)

记不住?来看下面:
关键字介绍

  • eval:使用 eval 包裹模块代码
  • source-map:产生 .map 文件
  • cheap:不包含列信息
  • module:包含 loader 和 source-map
  • inline:将 map 作为 DataURI 嵌入,不单独生成 map 文件
如何选 source-map 的类型
  • 在源代码的列信息是没有意义的,只要有行信息就能完整的建立打包前后代码之间的依赖关系。所以,不管是开发还是生产环境都可忽略列信息。所以,cheap 属性可以忽略
  • 需要定位 debug 到最原始的资源,而不是编译后的 JS 代码。所以,不能忽略掉 module
  • 需要生成 .map 文件,所以得有 source-map 属性

选择类型
开发环境:cheap-module-eval-source-map
生产环境:cheap-module-source-map

module.exports = {
  devtool: 'none', // SourceMap
  entry: './src/index.js',  // 入口文件
  output: {
    filename: 'bundle.js',  // 文件名
    path: path.resolve(__dirname, 'dist')  // 文件夹
  }
}

10. 如何利用 webpack 来优化前端性能

压缩 JS

new TerserPlugin()

压缩 CSS

new OptimizeCssAssetsPlugin()

压缩图片

image-webpack-loader

清除无用的 CSS

单独提取 CSS:mini-css-extract-plugin

loader: MiniCssExtractPlugin.loader

并清除用不到的 CSS:purgecss-webpack-plugin

new PurgecssPlugin({
// 查找 src 目录下的所有文件,如果没有用到的文件清除
 paths: glob.sync(`${PATHS.src}/**/*`, {nodir: true});
})

Tree Shaking

一个模块中会有很多方法,Tree Shaking 就是只把用到的方法打包,没用到的方法不会被打包进去

module: {
	rules: [{
    	test: /\.js/,
        use: [{
        	loader: 'bable-loader',
            options: {
            	presets: [['@babel/preset-env', {'modules': false}]]
            }
        }]
    }]
}

代码分割

描述:将代码分割成 chunks 语块,当代码运行到需要他们的时候在进行加载

分割的方式

  • 入口点分割:在 entry 中配置多个入口文件「适用于多页应用」

    • 弊端:
      • 如果入口文件中包含重复的模块,那么这些模块都会被打包到各个 bundle 中
      • 不灵活,不能动态分割代码
  • 动态导入和懒加载

    描述:需要什么功能模块就是加载对应的代码,也就是所谓的按需加载

    使用方法:通过 import 关键字来实现模块的懒加载,遇到 import 会进行代码分割

    // 只有在点击事件触发之后才会去加载 login 模块
    root.addEventListener('click', ()=>{
        import('./login').then(result=>{
            console.log(result.data);
        })
    })
    

    React 中如何实现按需加载

    Loading 组件渲染完成后再去渲染 Title 组件

    const App = React.lazy(()=>{
        // 懒加载 Title 模块
        import(/* webpackChunkName: 'title' */'./components/Title')
    })
    
    // render 函数
    render() {
        return (
            <>
                <Suspense fallback={<Loading />}>
                    <Title />
                <Suspense/>
            </>
        )
    }
    
  • preload 预先加载

    描述:preload 通常用于本页面要用到的关键资源,包括关键 JS、字体、CSS 等,preload 将会把资源的下载顺序放在前面「异步/延迟插入的脚本在网络优先级中是低的」,使关键资源提前下载,可以优化页面打开速度

    使用方法:在资源上添加预先加载的注释,指明该模块需要立即被使用

    <link rel='preload' as='script' href='utils.js'>
    
    
    import(
    `./utils.js`
    /*webpackPreload: true*/
    /*webpackChunkName: 'utils'*/
    )
    
    
  • prefetch 预先拉取

    描述:告诉浏览器未来可能会使用到某个资源,让浏览器闲下来的时候去加载相应的资源

    使用方法

    import(
    `./utils.js`
    /*webpackPrefetch: true*/
    /*webpackChunkName: 'utils'*/
    )
    
    
  • preload 和 preFetch 的区别

    • preload:告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源「当页面一定需要的资源,使用 preload」
    • prefetch:告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源「当页面可能需要的资源,使用 prefetch」
  • 提取公共代码

    • 为什么提取?
      • 一个页面中包含很多的公共代码,如果都包含进来可能会发生代码冲突
      • 相同的代码被重复加载,会浪费用户的流量和服务器的成本
      • 每个页面需要加载的资源太大,导致网页首屏加载慢,影响用户体验
      • 如果抽离出公共代码,单独创建一个文件进行加载能进行优化,可以减少网络传输流量,降低服务器成本
    • 如何提取?
      • 页面之间的公用代码,单独创建成一个文件
      • 每个页面单独生成一个文件
      • 在需要的页面中引入公用文件
  • 代码分割的方式:splitChunks

    • 这是一个分包的配置「从 node_modules 中单独拎出来,打包生成一个单独的文件」
    • module:通过 import 语句引入的代码
    • chunk:webpack 根据功能拆分出来的,包含三种情况
      • 你的项目入口「entry」
      • 通过 import() 动态引入的代码
      • 通过 splitChunks 拆分出来的代码
    • bundle:webpack 打包之后的各个文件,一般就是和 chunk 一对一的关系,bundle 就是对 chunk 进行编译压缩打包等处理后的文件
    • 默认配置
      • cacheGroups:缓存组:符合这些条件生成的包,在后续过程中可以直接走这个缓存「只生成一次,下次及以后都使用缓存」
      • 遇到 import 就会进行分割,默认只分割异步的
    splitChunks: {
      chunks: 'all', // 默认只分割异步的,all: 全部,initial: 同步,async: 异步
      name: true,// 打包后的名字 page1~page2.chunk.js
      minSize: 0,// 代码块的最小尺寸,默认:30kb
      minChunks: 1,// 在分割前模块的被引用次数
      maxAsyncRequests: 2,// 每个 import() 中的最大并行请求数量
      maxInitialRequests: 4 // 每个入口的最大拆分数量
      automaticNameDelimiter: '~',// 拆分后的 JS 命名规则page1~page2.chunk.js
      cacheGroups: {
      	// 设置缓存组用来抽取不同规则的 chunk
        vendors: {
        	chunks: 'all',
            test: /node_modules/,// 条件
            priority: -10,// 优先级 ,如果一个 chunk 满足多个缓存组,会被拆分到优先级最高的缓存组中
        },
        commons: {
        	chunks: 'all',
            minSize: 0, //最小提取的大小
            minChunks: 1//最小被几个 chunk 引用
            priority: -20,
            reuseExistingChunk: true,// 如果引用了被抽取的 chunk,直接引用这个 chunk,不会重复打包代码
        }
      	
      }
    }
    
  • HASH

    • 每次 webpack 构建的时候都会生成一个唯一的 hash 值
    • 整个编译的过程,所有设置的 HASH,都会共享一个 HASH 值
    • 只要有一个值发生变化,所有的文件都会重新生成新的 HASH 值
    • 文件资源的缓存方式
      • 一般 HTML 不缓存
      • 第三方库:强缓存
      • 其他资源文件:协商缓存
      • 合理使用缓存的话,需要配置 hash,文件更新重新打包,没有更新则用原来的文件资源
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[hash].js',
        chunkFilename: '[name].[hash].chunk.js',
        publicPath: 'http://www.guyal.cn'
    }
    // MiniCssExtractPlugin:自行下载引入
    plugins: [{
        new MiniCssExtractPlugin({
            filename: '[name].[hash].css'
        })
    
    }]
    
  • ChunkHash:代码块 HASH

    • 根据 chunk 生成 hash 值,来自同一个 chunk,则 hash 值就一样
    • 代码块的模块发生改变了,HASH 值才会重新生成,否则 HASH 值不会发生变化
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[chunkhash].js',
        chunkFilename: '[name].[chunkhash].chunk.js'
    }
    // MiniCssExtractPlugin:自行下载引入
    plugins: [{
        new MiniCssExtractPlugin({
            filename: '[name].[chunkhash].css'
        })
    
    }]
    
  • ContentHash:代码块 HASH

    • 根据内容生成的 hash 值文件
    • 文件内容相同,HASH 值就相同
    • 内容发生变化的时候,HASH 值才会发生变化
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[contenthash].js',
        chunkFilename: '[name].[contenthash].chunk.js'
    }
    // MiniCssExtractPlugin:自行下载引入
    plugins: [{
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash].css'
        })
    
    }]
    

11. 如何提高 webpack 的构建速度

  • 打包速度分析插件:speed-measure-webpack-plugin
  • 优化方案
    • extensions:在引入文件的时候,可以省略文件的后缀名;用的多的放前边,少的放后面
    resolve: {
    	extensions: ['.js', '.jsx', '.json', '.css']
    }
    
    • alias:配置绝对路径的别名,可以加快查找模块的速度;
    resolve: {
      alias: {
         '@': path.resolve(__dirname, '../src')
      }
    }
    
    • externals:外部扩展

      • 把项目中使用的包,从打包文件中提取出来,我们需要通过配置 externals 来实现,在 HTML 中使用 cdn 引入 jQuery

      • 打包体积减小,打包速度加快

      • 配置

      externals: {
          "jQuery", "jQuery"
      }
      
      • 引入:import $ from 'jQuery'
    • ProvidePlugin:webpack 自带插件

      • 这样的配置可以让我们在项目中不用再引入:import xxx from "xxx" ,直接使用对应的变量即可
       webpack.ProvidePlugin({
          "要在文件中使用的变量名""安装的包的名字"
      })
      

      配置插件

      plugins: [
        new webpack.ProvidePlugin({
           '$': 'jQuery',
           '$$': 'jQuery'
          })
      ],
      externals: {
          "jQuery", “jQuery”
      }
      
    • resolve.alias

      • 配置绝对路径的别名,可以加快模块的查找速度
      resolve.alias = {
          'xxx': 'xxx 的绝对路径'
      }
      
      • 配置
      resolve: {
        alias: {
           '@': path.resolve(__dirname, '../src')
        }
      }
      
      • 引入
        import xxx from '@/index'
        优点
      • 可以加快查找文件速度
      • 提升构建速度
    • resolve.modules

      • 当遇到没有写相对路径文件时,优先去 src 中查找,没有的话在去 node_modules 中查找
      • 配置:
      resolve.modules = [path.resolve(__dirname, 'src'), 'node_modules']
      
    • module.noParse

      • 明确告诉 webpack 这俩包不依赖其他任何包
      • 配置
      module: {
        noParse: /jQuery|lodash/
      }
      
    • definePlugin「定义全局变量」

      • 定义在编译阶段加上的全局变量
      • 配置
      webpack.DefinePlugin({
          xxx: JSON.stringify("xxx")
      })
      
      new webpack.DefinePlugin({
      	BASEURL: "https://baidu.com"
      })
      // BASEURL 此时是一个变量,需要把值转换为字符串:
      // JSON.stringify("https://baidu.com"),
      // 输出:"https://baidu.com"
      
    • thread-loader

      • 多进程打包,提升构建速度「以前使用的 happypack 包,已经不维护了」
      • 配置
      module: {
       rules: [
           {
              test: /\.js$/,
              include: path.resolve('src'),
              use: [
                 "babel-loader", "thread-loader"
              ]
           }
        ]
      }
      

      优点:可以提升构建速度;「@vue/cli create-react-app 都已经内置了,平时我们不需要配置」

    • ignorePlugin

      • 在打包时,忽略指定的文件「自己知道什么包用不上」
      • 例如语言切换的包:moment
        • 安装:yarn add moment
          • 语言切换的包,但是:所有语言的包都会安装上
        • 引用:new webpack.IgnorePlugin(/local/, /moment/)
          • 在打包时,忽略 moment 下的 local 这个文件
        • 使用什么包引入什么包:import 'moment/locale/zh-cn'
          优点:减少打包后的体积
    • 利用缓存

      • babel-loader:开启缓存
        • 把 babel-loader 执行的结果缓存起来,重新构建的时候读取缓存,提高打包速度
        {
        	test: /\.js$/,
            use: [{
            	loader: 'babel-loader'
            }]
        }
        
      • cache-loader
        • 把消耗性能的 loader 之前加上 cache-loader,然后将结果缓存到磁盘中
        {
        	test: /\.js$/,
            use: ['cache-laoder', ...loaders]
        }
        
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值