vue-cli-webpack模板配置全解析-2

vue-cli webpack 模板配置解析 - 2 重要配置文件解析

重要模块版本

  • vue-cli : 2.9.1
  • vue : 2.4.2
  • vue-router : 2.7.0
  • webpack : 3.6.0
  • 其他依赖都由 vue-cli 生成

重要配置文件解析

1. /build/dev-server.js

执行 npm run dev 或者 npm start 后运行的脚本

主要功能
  1. 检查 node、npm 版本,警告提示
  2. 启动 webpack 服务
  3. 配置挂载开发、热重载中间件
  4. 挂载代理中间件
  5. 开启 express 服务器,打开浏览器
代码详解
    'use strict'

    // 检查 node vue 版本
    require('./check-versions')()

    // 引入默认配置
    const config = require('../config')

    // 设置默认环境
    if (!process.env.NODE_ENV) {
        process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
    }

    /*
       opn 插件可以强制打开浏览器跳转到指定页面
       介绍:https://github.com/sindresorhus/opn
    */
    const opn = require('opn')
    const path = require('path')
    const express = require('express')
    const webpack = require('webpack')

    /*
       http-proxy-middleware 是一个 node 代理中间件,可以将http请求代理到其他服务器
       详情文档:https://github.com/chimurai/http-proxy-middleware
    */
    const proxyMiddleware = require('http-proxy-middleware')

    // 使用 dev 开发环境的配置
    const webpackConfig = require('./webpack.dev.conf')

    // 指定运行端口
    const port = process.env.PORT || config.dev.port

    // 自动打开浏览器,默认 false
    const autoOpenBrowser = !!config.dev.autoOpenBrowser

    // 设置代理配置,格式参考 http-proxy-middleware 的文档
    const proxyTable = config.dev.proxyTable

    // 使用 express 开启服务
    const app = express()

    /*
       webpack 函数传入一个配置对象 就会执行相应的编译
       可以传入第二个参数(回调函数)可以在这里处理错误信息
    */
    const compiler = webpack(webpackConfig)

    /*
       webpack-dev-middleware
       用来在内存中生成打包好的文件,而不用写到磁盘上,它提供从 webpack 到服务器的文件传输,用来配合进行热重载

       第一个参数:webpack 实例
       第二个参数:配置 只有 publicPath 必填,
       其他参考文档 https://www.npmjs.com/package/webpack-dev-middleware
    */
    const devMiddleware = require('webpack-dev-middleware')(compiler, {
        /*
          在这里('/config/index.js' dev.assetsPublicPath)设置 publicPath
          与 webpack-dev-server 异同,可以在这里查看 https://doc.webpack-china.org/configuration/dev-server/
       */
        publicPath: webpackConfig.output.publicPath,
        quiet: true
    })
    /*
       webpack-hot-middleware
       用来接受 webpack 传递来的更新,并将其反应到浏览器客户端,需要与 webpack-dev-middleware 一起使用

       使用文档:https://www.npmjs.com/package/webpack-hot-middleware
    */
    const hotMiddleware = require('webpack-hot-middleware')(compiler, {
        log: false,
        heartbeat: 2000
    })

    /*
       当 html-webpack-plugin 模板更新的时候强制刷新页面
       目前有BUG 所以禁用 BUG原因 https://github.com/jantimon/html-webpack-plugin/issues/680
    */
    // compiler.plugin('compilation', function (compilation) {
    //   compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
    //     hotMiddleware.publish({ action: 'reload' })
    //     cb()
    //   })
    // })

    // enable hot-reload and state-preserving
    // compilation error display
    app.use(hotMiddleware)

    /*
       将代理的请求配置挂载到掐动的 express 服务上
       对应 webpack-dev-server 的 'proxy' 配置
    */
    Object.keys(proxyTable).forEach(function(context) {
        const options = proxyTable[context]
        if (typeof options === 'string') {
            options = {
                target: options
            }
        }
        app.use(proxyMiddleware(options.filter || context, options))
    })

    /*
       使用 connect-history-api-fallback 匹配资源,如果不匹配就可以重定向到指定地址
       对应 webpack-dev-server 'historyApiFallback: true' 配置
    */
    app.use(require('connect-history-api-fallback')())

    // serve webpack bundle output
    app.use(devMiddleware)

    // 拼接 static 文件夹的静态资源路径
    const staticPath = path
        .posix
        .join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
    // 为静态资源提供响应服务
    app.use(staticPath, express.static('./static'))

    // 应用的地址信息
    const uri = 'http://localhost:' + port

    var _resolve
    var _reject
    var readyPromise = new Promise((resolve, reject) => {
        _resolve = resolve
        _reject = reject
    })

    var server
    var portfinder = require('portfinder')
    portfinder.basePort = port

    console.log('> Starting dev server...')

    // 直道打好的包有效后执行回调
    devMiddleware.waitUntilValid(() => {
        /*
          portfinder 用于获取 port
       */
        portfinder.getPort((err, port) => {
            if (err) {
                _reject(err)
            }
            process.env.PORT = port
            var uri = 'http://localhost:' + port
            console.log('> Listening at ' + uri + '\n')
            // 如果不是测试环境,自动打开浏览器并跳到我们的开发地址
            if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
                opn(uri)
            }
            server = app.listen(port)
            //  server = app.listen(port, hostName) 可以在此加入主机名
            _resolve()
        })
    })

    module.exports = {
        ready: readyPromise,
        close: () => {
            server.close()
        }
    }
2. /build/build.js

执行 npm run build 后运行的脚本

主要功能
  1. 创建目标文件夹
  2. 目标文件夹如果存在,则清除后重新创建
  3. 使用 webpack 编译输出
代码详解
    'use strict'

    // 检查 node npm 版本
    require('./check-versions')()

    process.env.NODE_ENV = 'production'

    const ora = require('ora') // node 终端的 loading 图
    const rm = require('rimraf') // 用于深度删除模块
    const path = require('path')
    const chalk = require('chalk') // 命令行彩色输出
    const webpack = require('webpack')
    const config = require('../config')
    const webpackConfig = require('./webpack.prod.conf')

    // loading
    const spinner = ora('building for production...')
    spinner.start()

    /*
       使用 rimraf 将旧的编译输出的文件夹删除,然后重新编译生成
       rimraf(f: 路径, [opts], callback: 回调)
    */
    rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
        if (err) throw err
        webpack(webpackConfig, function(err, stats) {
            spinner.stop()
            if (err)
                throw err
                //  输出提示信息
            process
                .stdout
                .write(stats.toString({colors: true, modules: false, children: false, chunks: false, chunkModules: false}) + '\n\n')

            if (stats.hasErrors()) {
                console.log(chalk.red('  Build failed with errors.\n'))
                process.exit(1)
            }

            console.log(chalk.cyan('  Build complete.\n'))
            console.log(chalk.yellow('  Tip: built files are meant to be served over an HTTP server.\n' +
                '  Opening index.html over file:// won\'t work.\n'))
        })
    })
3. /build/check-version.js

/build/build.js/build/dev-server.js 都有引用

主要功能
  1. 检测 node、npm 版本,警告提示
代码详解
    'use strict'
    /*
       检查 node vue 版本
    */

    const chalk = require('chalk') // 命令行字体颜色美化
    const semver = require('semver') // 版本解析插件
    const packageConfig = require('../package.json')
    const shell = require('shelljs') // node 中使用 shell 命令

    function exec(cmd) {
        // child_process 开启子进程,并执行 cmd 命令 然后返回结果
        return require('child_process')
            .execSync(cmd)
            .toString()
            .trim()
    }

    const versionRequirements = [
        {
            name: 'node',
            /*
             semver.clean(version) 能格式化版本号
             例如:'  =v1.2.3   ' -> '1.2.3'
          */
            currentVersion: semver.clean(process.version),
            versionRequirement: packageConfig.engines.node
        }
    ]

    // shell.which('命令')在系统的路径搜索命令, 如果用的是 npm 就检查 npm 版本
    if (shell.which('npm')) {
        versionRequirements.push({
          name: 'npm',
          currentVersion: exec('npm --version'),
          versionRequirement: packageConfig.engines.npm
       })
    }

    module.exports = function() {
        const warnings = []
        for (let i = 0; i < versionRequirements.length; i++) {
            const mod = versionRequirements[i]
            /*
          semver.satisfies('当前版本','版本范围')
          用于判断版本是否符合需求,不符合要求则加入警告
       */
            if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
                warnings.push(mod.name + ': ' + chalk.red(mod.currentVersion) + ' should be ' + chalk.green(mod.versionRequirement))
            }
        }

        // 警告输出
        if (warnings.length) {
            console.log('')
            console.log(chalk.yellow('To use this template, you must update following to modules:'))
            console.log()
            for (let i = 0; i < warnings.length; i++) {
                const warning = warnings[i]
                console.log('  ' + warning)
            }
            console.log()
            process.exit(1)
        }
    }
4. /build/utils.js

多处引用

主要功能
  1. 配置静态资源路径
  2. 样式文件的 loaders 配置
代码详解
    'use strict'
    const path = require('path')
    const config = require('../config')
    const ExtractTextPlugin = require('extract-text-webpack-plugin')

    // 图片、音频、视频、字体等资源的路径配置
    exports.assetsPath = function(_path) {
        const assetsSubDirectory = process.env.NODE_ENV === 'production'
            ? config.build.assetsSubDirectory
            : config.dev.assetsSubDirectory

        return path.posix.join(assetsSubDirectory, _path)
    }

    exports.cssLoaders = function(options) {
        options = options || {}

        // 一个基础的 cssLoader
        const cssLoader = {
            loader: 'css-loader',
            options: {
                minimize: process.env.NODE_ENV === 'production',
                sourceMap: options.sourceMap
            }
        }

        // 一个生成 loaders 的函数,只需传入 loader:类型,loaderOptions:loader 参数
        function generateLoaders(loader, loaderOptions) {
            /*
             loader:要加载的loader名
             loaderOptions:配置
          */
            const loaders = [cssLoader]
            if (loader) {
                loaders.push({
                    loader: loader + '-loader',
                    options: Object.assign({}, loaderOptions, {sourceMap: options.sourceMap})
                })
            }

            // 生产环境是,会指定提取 样式为单独文件
            if (options.extract) {
                return ExtractTextPlugin.extract({
                use: loaders,
                fallback: 'vue-style-loader'
             })
            } else {
                return ['vue-style-loader'].concat(loaders)
            }
        }

        // https://vue-loader.vuejs.org/en/configurations/extract-css.html
        return {
            css: generateLoaders(),
            postcss: generateLoaders(),
            less: generateLoaders('less'),
            sass: generateLoaders('sass', {indentedSyntax: true}),
            scss: generateLoaders('sass'),
            stylus: generateLoaders('stylus'),
            styl: generateLoaders('stylus')
        }
    }

    // Generate loaders for standalone style files (outside of .vue)
    exports.styleLoaders = function(options) {

        const output = []
        // 引用上边的 cssLoaders,得到一个样式处理方案的对象,全部执行添加 loader
        const loaders = exports.cssLoaders(options)
        for (const extension in loaders) {
            const loader = loaders[extension]
            output.push({
                test: new RegExp('\\.' + extension + '$'),
                use: loader
            })
        }
        return output
    }
5. /build/vue-loader.conf.js

webpack.basic-conf.js引用

主要功能
  1. 配置 css 加载
  2. 配置 将 src 等引用的资源转成模块
代码详解
    'use strict'
    const utils = require('./utils')
    const config = require('../config')
    const isProduction = process.env.NODE_ENV === 'production'

    module.exports = {
        loaders: utils.cssLoaders({
            sourceMap: isProduction
                ? config.build.productionSourceMap
                : config.dev.cssSourceMap,
            extract: isProduction // 表示是否提取样式为单独文件
        }),
        /*
          vue 中引入图片等媒体资源时,需要先 require('./images/xxx.png')
          这个配置可以将 对应的属性中的内容,装换为模块,这样就可以向正常的HTML 一样使用了
        */
        transformToRequire: {
            video: 'src',
            source: 'src',
            img: 'src',
            image: 'xlink:href'
        }
    }
6. /build/webpack.base.conf.js

webpack.dev.conf.jswebpack.prod.conf.js引用

主要功能
  1. webpack 基础配置
代码详解
    'use strict'
    /*
       webpack 基础配置文件
    */
    const path = require('path')
    const utils = require('./utils')
    const config = require('../config')
    const vueLoaderConfig = require('./vue-loader.conf')

    // 封装生成绝对路径函数
    function resolve(dir) {
        return path.join(__dirname, '..', dir)
    }

    module.exports = {
        // 入口
        entry: {
            app: './src/main.js'
        },
        // 出口配置
        output: {
            // 编译后的输出路径等
            path: config.build.assetsRoot,
            filename: '[name].js',
            // 发布路径
            publicPath: process.env.NODE_ENV === 'production'
                ? config.build.assetsPublicPath
                : config.dev.assetsPublicPath
        },
        resolve: {
            // 自动补全扩展名
            extensions: [
                '.js', '.vue', '.json'
            ],
            // 模块别名,简化模块引入
            alias: {
                'vue$': 'vue/dist/vue.esm.js',
                '@': resolve('src')
            }
        },
        // 模块处理规则配置
        module: {
            rules: [
                {
                    test: /\.(js|vue)$/,
                    loader: 'eslint-loader',
                    enforce: 'pre', // 先执行
                    include: [
                        resolve('src'), resolve('test')
                    ],
                    options: {
                        formatter: require('eslint-friendly-formatter')
                    }
                }, {
                    test: /\.vue$/,
                    loader: 'vue-loader',
                    options: vueLoaderConfig // 规则在 vue-loader.conf.js 中
                }, {
                    test: /\.js$/,
                    loader: 'babel-loader',
                    include: [resolve('src'), resolve('test')]
                }, {
                    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                    loader: 'url-loader',
                    options: {
                        limit: 10000,   
                        name: utils.assetsPath('img/[name].[hash:7].[ext]')
                    }
                }, {
                    test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
                    loader: 'url-loader',
                    options: {
                        limit: 10000,
                        name: utils.assetsPath('media/[name].[hash:7].[ext]')
                    }
                }, {
                    test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
                    loader: 'url-loader',
                    options: {
                        limit: 10000,
                        name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
                    }
                }
            ]
        }
    }
7. /build/webpack.dev.conf.js

/build/dev-server.js 引用

主要功能
  1. 开发环境热更新配置添加
  2. 在基础配置之上增加 样式的 loaders
  3. 增加开发环境的 插件配置
代码详解
    'use strict'
    /*
       开发环境配置文件
    */
    const utils = require('./utils') // 工具文件
    const webpack = require('webpack')
    const config = require('../config') // 引入配置
    const merge = require('webpack-merge') // webpack 配置合并
    const baseWebpackConfig = require('./webpack.base.conf') // 引入基础配置
    const HtmlWebpackPlugin = require('html-webpack-plugin') // 根据模板生成 HTML
    const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')

    /*
       开发环境中,为了配合使用 热重载,需要在 入口前加入 './build/dev-client'(Hot-reload 的相对路径)
    */
    Object.keys(baseWebpackConfig.entry).forEach(function(name) {
        baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
    })

    // 合并开发环境配置到 基础配置上
    module.exports = merge(baseWebpackConfig, {
        module: {
            /*
             这部分就是定义 style 样式需要用什么 loader 解析
          */
            rules: utils.styleLoaders({sourceMap: config.dev.cssSourceMap})
        },
        // cheap-module-eval-source-map is faster for development
        devtool: '#cheap-module-eval-source-map',
        plugins: [
            // 定义环境变量
            new webpack.DefinePlugin({'process.env': config.dev.env}),
            /*
             热重载需要插件
             地址:https://github.com/glenjamin/webpack-hot-middleware#installation--usage
          */
            new webpack.HotModuleReplacementPlugin(),
            new webpack.NoEmitOnErrorsPlugin(),
            /*
             根据模板生成 HTML 的插件
             配置看这里:https://github.com/ampedandwired/html-webpack-plugin
          */
            new HtmlWebpackPlugin({filename: 'index.html', template: 'index.html', inject: true}),
            new FriendlyErrorsPlugin()
        ]
    })
8. /build/webpack.prod.conf.js

/build/build.js 引用

主要功能
  1. 开发环境热更新配置添加
  2. 在基础配置之上增加 样式的 loaders
  3. 增加开发环境的 插件配置
代码详解
    'use strict'
    const path = require('path')
    const utils = require('./utils')
    const webpack = require('webpack')
    const config = require('../config')
    const merge = require('webpack-merge')
    const baseWebpackConfig = require('./webpack.base.conf')
    // copy-webpack-plugin 用于文件或者目录复制
    const CopyWebpackPlugin = require('copy-webpack-plugin')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const ExtractTextPlugin = require('extract-text-webpack-plugin')
    // optimize-css-assets-webpack-plugin 一个 css 优化插件
    const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')

    const env = config.build.env

    const webpackConfig = merge(baseWebpackConfig, {
        module: {
            // 配置样式使用的 loader
            rules: utils.styleLoaders({
             sourceMap: config.build.productionSourceMap,
             extract: true
          })
        },
        devtool: config.build.productionSourceMap
            ? '#source-map'
            : false,
        output: {
            path: config.build.assetsRoot,
            filename: utils.assetsPath('js/[name].[chunkhash].js'),
            chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
        },
        plugins: [
            // 定义环境变量
            new webpack.DefinePlugin({'process.env': env}),
            /*
             js 压缩
             UglifyJs do not support ES6+, you can also use babel-minify for better treeshaking: https://github.com/babel/minify
          */
            new webpack.optimize.UglifyJsPlugin({
                compress: {
                    warnings: false
                },
                sourceMap: true // 是否生成 sourceMap
            }),
            // css 文件分离
            new ExtractTextPlugin({
                filename: utils.assetsPath('css/[name].[contenthash].css')
            }),
            /*
             css 优化,css 复用
          */
            new OptimizeCSSPlugin({
                cssProcessorOptions: {
                    safe: true
                }
            }),
            /*
             根据模板生成 HTML
             更多配置:https://www.npmjs.com/package/html-webpack-plugin
          */
            new HtmlWebpackPlugin({
                filename: config.build.index,
                template: 'index.html',
                inject: true,
                minify: {
                    removeComments: true,
                    collapseWhitespace: true,
                    removeAttributeQuotes: true
                    // more options:
                    // https://github.com/kangax/html-minifier#options-quick-reference
                },
                chunksSortMode: 'dependency'
            }),
            /*
             HashedModuleIdsPlugin插件,
             它根据模块的相对路径生成一个长度只有四位的字符串作为模块的 id,
             所以只要我们不重命名一个模块文件,那么它的id就不会变,
             这样解决了某一个文件修改,其他文件的 hash随之改变的问题
          */
            new webpack.HashedModuleIdsPlugin(),
            // 抽离第三方代码
            new webpack.optimize.CommonsChunkPlugin({
                name: 'vendor',
                minChunks: function(module) {
                    // 依赖的 node_modules 文件会被提取
                    return (
                   module.resource &&
                   /\.js$/.test(module.resource) &&
                   module.resource.indexOf(
                      path.join(__dirname, '../node_modules')
                   ) === 0
                )
                }
            }),
            // 提取运行时代码,避免每次打包后文件更改
            new webpack.optimize.CommonsChunkPlugin({
             name: 'manifest',
             chunks: ['vendor']
          }),
            // 复制静态资源
            new CopyWebpackPlugin([
                {
                    from: path.resolve(__dirname, '../static'),
                    to: config.build.assetsSubDirectory,
                    ignore: ['.*'] // 忽略文件
                }
            ])
        ]
    })

    // 开启 gzip 的配置
    if (config.build.productionGzip) {
        const CompressionWebpackPlugin = require('compression-webpack-plugin')

        webpackConfig.plugins.push(
            /*
             压缩
             更多配置:https://github.com/webpack-contrib/compression-webpack-plugin
          */
            new CompressionWebpackPlugin({
                asset: '[path].gz[query]',
                algorithm: 'gzip',
                test: new RegExp(
                '\\.(' +
                config.build.productionGzipExtensions.join('|') +
                ')$'
             ),
                threshold: 10240,
                minRatio: 0.8
            }))
    }

    if (config.build.bundleAnalyzerReport) {
        /*
          webpack-bundle-analyzer 插件
          解析出模块构成、以及各自的大小体积,最后显示为一个页面
          地址: https://www.npmjs.com/package/webpack-bundle-analyzer
       */
        const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
        webpackConfig.plugins.push(new BundleAnalyzerPlugin())
    }

    module.exports = webpackConfig
9. /config/index.js

多处引用

主要功能
  1. 提供开发、生产环境的用户配置
代码详解
    'use strict'
    // Template version: 1.1.1
    // see http://vuejs-templates.github.io/webpack for documentation.

    const path = require('path')

    module.exports = {
        // 生产环境
        build: {
            env: require('./prod.env'),
            index: path.resolve(__dirname, '../dist/index.html'), // 页面入口
            assetsRoot: path.resolve(__dirname, '../dist'), // 静态资源路径,文件编译后输出路径
            assetsSubDirectory: 'static', // 二级路径、图片、音视频等媒体资源路径
            assetsPublicPath: '/', // 编译发布的根目录,最后会被写到 HTML 中,可以是服务器域名
            productionSourceMap: true, // 是否开启 SourceMap (css)
            // Gzip off by default as many popular static hosts such as
            // Surge or Netlify already gzip all static assets for you.
            // Before setting to `true`, make sure to:
            // npm install --save-dev compression-webpack-plugin
            productionGzip: false, // gzip 配置
            productionGzipExtensions: [
                'js', 'css'
            ],
            // Run the build command with an extra argument to
            // View the bundle analyzer report after build finishes:
            // `npm run build --report`
            // Set to `true` or `false` to always turn it on or off
            bundleAnalyzerReport: process.env.npm_config_report //是否显示 report
        },
        // 开发环境
        dev: {
            env: require('./dev.env'),
            port: process.env.PORT || 8080, // 设置端口号
            autoOpenBrowser: true, // 是否默认打开浏览器
            assetsSubDirectory: 'static', // 二级路径、图片、音视频等媒体资源路径
            assetsPublicPath: '/', // 编译发布的根目录
            proxyTable: { // 代理的配置
                // 更多配置 https://github.com/chimurai/http-proxy-middleware
                '/text/*': {
                    target: 'http://debug.xxx.com', // 要代理到的地址
                    secure: false, // 是否验证 ssL 证书
                    changeOrigin: true // 更改host header的origin到目标URL
                },
                '/uaa/*': {
                    target: 'http://debug.xxx.com',
                    secure: false,
                    changeOrigin: true
                }
            },
            // CSS Sourcemaps off by default because relative paths are "buggy"
            // with this option, according to the CSS-Loader README
            // (https://github.com/webpack/css-loader#sourcemaps)
            // In our experience, they generally work as expected,
            // just be aware of this issue when enabling this option.
            cssSourceMap: false
        }
    }

其他

1. .babelrc 的配置,如下参考
2. .eslintrc.js

参考

vue-cli的webpack模板项目配置文件说明
vue-cli#2.0 webpack 配置分析

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值