vue-cli的webpack模板项目配置文件说明

如果没有vue-cli,那么进行vue项目的开发环境配置将是很费力的一件事情,现在脚手架可以快速构建一个开发环境,是很不错的。不过对于脚手架构建的配置,还是要大概了解一下,方便自己调试配置。

初始化一个vue项目操作命令如下:

# 全局安装 vue-cli
$ npm install --global vue-cli

# 创建一个基于 webpack 模板的新项目
$ vue init webpack my-project

# 安装依赖
$ cd my-project
$ npm install
$ npm run dev

 

一般来说,vue-cli构建项目时候回生成类似下面的项目文件结构:

 

├─build
│   ├─build.js
│   ├─check-versions.js
│   ├─dev-client.js
│   ├─dev-server.js
│   ├─utils.js
│   ├─vue-loader.conf.js
│   ├─webpack.base.conf.js
│   ├─webpack.dev.conf.js
│   ├─webpack.prod.conf.js
├─config
│   ├─dev.env.js
│   ├─index.js
│   ├─prod.env.js
├─...
└─package.json

 

一、项目指令

 

我们可以在package.json里面的看到scripts字段是这样:

"scripts": {
  "dev": "node build/dev-server.js",
  "build": "node build/build.js",
  "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
  "e2e": "node test/e2e/runner.js",
  "test": "npm run unit && npm run e2e",
  "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
 }

这里包含开发运行,项目打包,单元测试等命令,测试的东西先放一边,看dev和build命令。运行”npm run dev”的时候执行的是build/dev-server.js文件,运行”npm run build”的时候执行的是build/build.js文件,我们可以从这两个文件开始进行代码阅读分析。

 

二、build文件夹配置

 

1、build/dev-server.js

这个文件主要是完成下面操作:

1 检查node和npm的版本
2 引入相关插件和配置
3 创建express服务器和webpack编译器
4 配置开发中间件(webpack-dev-middleware)和热重载中间件(webpack-hot-middleware)
5 挂载代理服务和中间件
6 配置静态资源
7 启动服务器监听特定端口(80808 自动打开浏览器并打开特定网址(localhost:8080)

Ps:express服务器提供静态文件服务,不过它还使用了http-proxy-middleware,一个http请求代理的中间件。前端开发过程中需要使用到后台的API的话,可以通过配置proxyTable来将相应的后台请求代理到专用的API服务器。

代码配置注释:

  1 // 检查NodeJS和npm的版本
  2 require('./check-versions')()
  3 
  4 // 获取配置
  5 var config = require('../config')
  6 // 如果Node的环境变量中没有设置当前的环境(NODE_ENV),则使用config中的配置作为当前的环境
  7 if (!process.env.NODE_ENV) {
  8   process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
  9 }
 10 
 11 // 一个可以调用默认软件打开网址、图片、文件等内容的插件
 12 // 这里用它来调用默认浏览器打开dev-server监听的端口,例如:localhost:8080
 13 var opn = require('opn')
 14 var path = require('path')
 15 var express = require('express')
 16 var webpack = require('webpack')
 17 
 18 // 一个express中间件,用于将http请求代理到其他服务器
 19 // 例:localhost:8080/api/xxx  -->  localhost:3000/api/xxx
 20 // 这里使用该插件可以将前端开发中涉及到的请求代理到API服务器上,方便与服务器对接
 21 var proxyMiddleware = require('http-proxy-middleware')
 22 
 23 // 根据 Node 环境来引入相应的 webpack 配置
 24 var webpackConfig = process.env.NODE_ENV === 'testing'
 25   ? require('./webpack.prod.conf')
 26   : require('./webpack.dev.conf')
 27 
 28 // dev-server 监听的端口,默认为config.dev.port设置的端口,即8080
 29 var port = process.env.PORT || config.dev.port
 30 
 31 // 用于判断是否要自动打开浏览器的布尔变量,当配置文件中没有设置自动打开浏览器的时候其值为 false
 32 var autoOpenBrowser = !!config.dev.autoOpenBrowser
 33 
 34 // 定义 HTTP 代理表,代理到 API 服务器
 35 var proxyTable = config.dev.proxyTable
 36 
 37 // 创建1个 express 实例
 38 var app = express()
 39 
 40 // 根据webpack配置文件创建Compiler对象
 41 var compiler = webpack(webpackConfig)
 42 
 43 // webpack-dev-middleware使用compiler对象来对相应的文件进行编译和绑定
 44 // 编译绑定后将得到的产物存放在内存中而没有写进磁盘
 45 // 将这个中间件交给express使用之后即可访问这些编译后的产品文件
 46 var devMiddleware = require('webpack-dev-middleware')(compiler, {
 47   publicPath: webpackConfig.output.publicPath,
 48   quiet: true
 49 })
 50 
 51 // webpack-hot-middleware,用于实现热重载功能的中间件
 52 var hotMiddleware = require('webpack-hot-middleware')(compiler, {
 53   log: () => {}
 54 })
 55 
 56 // 当html-webpack-plugin提交之后通过热重载中间件发布重载动作使得页面重载
 57 compiler.plugin('compilation', function (compilation) {
 58   compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
 59     hotMiddleware.publish({ action: 'reload' })
 60     cb()
 61   })
 62 })
 63 
 64 // 将 proxyTable 中的代理请求配置挂在到express服务器上
 65 Object.keys(proxyTable).forEach(function (context) {
 66   var options = proxyTable[context]
 67   // 格式化options,例如将'www.example.com'变成{ target: 'www.example.com' }
 68   if (typeof options === 'string') {
 69     options = { target: options }
 70   }
 71   app.use(proxyMiddleware(options.filter || context, options))
 72 })
 73 
 74 // handle fallback for HTML5 history API
 75 // 重定向不存在的URL,常用于SPA
 76 app.use(require('connect-history-api-fallback')())
 77 
 78 // serve webpack bundle output
 79 // 使用webpack开发中间件
 80 // 即将webpack编译后输出到内存中的文件资源挂到express服务器上
 81 app.use(devMiddleware)
 82 
 83 // enable hot-reload and state-preserving
 84 // compilation error display
 85 // 将热重载中间件挂在到express服务器上
 86 app.use(hotMiddleware)
 87 
 88 // serve pure static assets
 89 // 静态资源的路径
 90 var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
 91 
 92 // 将静态资源挂到express服务器上
 93 app.use(staticPath, express.static('./static'))
 94 
 95 // 应用的地址信息,例如:http://localhost:8080
 96 var uri = 'http://localhost:' + port
 97 
 98 // webpack开发中间件合法(valid)之后输出提示语到控制台,表明服务器已启动
 99 devMiddleware.waitUntilValid(function () {
100   console.log('> Listening at ' + uri + '\n')
101 })
102 
103 // 启动express服务器并监听相应的端口(8080)
104 module.exports = app.listen(port, function (err) {
105   if (err) {
106     console.log(err)
107     return
108   }
109 
110   // when env is testing, don't need open it
111   // 如果符合自动打开浏览器的条件,则通过opn插件调用系统默认浏览器打开对应的地址uri
112   if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
113     opn(uri)
114   }
115 })

2、build/webpack.base.conf.js

从代码中看到,dev-server使用的webpack配置来自build/webpack.dev.conf.js文件(测试环境下使用的是build/webpack.prod.conf.js,这里暂时不考虑测试环境)。而build/webpack.dev.conf.js中又引用了webpack.base.conf.js,所以这里先看webpack.base.conf.js。

webpack.base.conf.js主要完成下面的操作:

1 配置webpack编译入口
2 配置webpack输出路径和命名规则
3 配置模块resolve规则
4 配置不同类型模块的处理规则

代码配置注释:

 1 var path = require('path')
 2 var utils = require('./utils')
 3 var config = require('../config')
 4 var vueLoaderConfig = require('./vue-loader.conf')
 5 
 6 // 给出正确的绝对路径
 7 function resolve (dir) {
 8   return path.join(__dirname, '..', dir)
 9 }
10 
11 module.exports = {
12   // 配置webpack编译入口
13   entry: {
14     app: './src/main.js'
15   },
16 
17   // 配置webpack输出路径和命名规则
18   output: {
19     // webpack输出的目标文件夹路径(例如:/dist)
20     path: config.build.assetsRoot,
21     // webpack输出bundle文件命名格式
22     filename: '[name].js',
23     // webpack编译输出的发布路径
24     publicPath: process.env.NODE_ENV === 'production'
25       ? config.build.assetsPublicPath
26       : config.dev.assetsPublicPath
27   },
28 
29   // 配置模块resolve的规则
30   resolve: {
31     // 自动resolve的扩展名
32     extensions: ['.js', '.vue', '.json'],
33     // resolve模块的时候要搜索的文件夹
34     modules: [
35       resolve('src'),
36       resolve('node_modules')
37     ],
38     // 创建路径别名,有了别名之后引用模块更方便,例如
39     // import Vue from 'vue/dist/vue.common.js'可以写成 import Vue from 'vue'
40     alias: {
41       'vue$': 'vue/dist/vue.common.js',
42       'src': resolve('src'),
43       'assets': resolve('src/assets'),
44       'components': resolve('src/components')
45     }
46   },
47 
48   // 配置不同类型模块的处理规则
49   module: {
50     rules: [
51       {// 对src和test文件夹下的.js和.vue文件使用eslint-loader
52         test: /\.(js|vue)$/,
53         loader: 'eslint-loader',
54         enforce: "pre",
55         include: [resolve('src'), resolve('test')],
56         options: {
57           formatter: require('eslint-friendly-formatter')
58         }
59       },
60       {// 对所有.vue文件使用vue-loader
61         test: /\.vue$/,
62         loader: 'vue-loader',
63         options: vueLoaderConfig
64       },
65       {// 对src和test文件夹下的.js文件使用babel-loader
66         test: /\.js$/,
67         loader: 'babel-loader',
68         include: [resolve('src'), resolve('test')]
69       },
70       {// 对图片资源文件使用url-loader,query.name指明了输出的命名规则
71         test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
72         loader: 'url-loader',
73         query: {
74           limit: 10000,
75           name: utils.assetsPath('img/[name].[hash:7].[ext]')
76         }
77       },
78       {// 对字体资源文件使用url-loader,query.name指明了输出的命名规则
79         test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
80         loader: 'url-loader',
81         query: {
82           limit: 10000,
83           name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
84         }
85       }
86     ]
87   }
88 }

3、build/webpack.dev.conf.js

接下来看webpack.dev.conf.js,这里面在webpack.base.conf的基础上增加完善了开发环境下面的配置,主要完成下面操作:

1 将hot-reload相关的代码添加到entry chunks
2 合并基础的webpack配置
3 使用styleLoaders
4 配置Source Maps
5 配置webpack插件

代码配置注释:

 1 var utils = require('./utils')
 2 var webpack = require('webpack')
 3 var config = require('../config')
 4 
 5 // 一个可以合并数组和对象的插件
 6 var merge = require('webpack-merge')
 7 var baseWebpackConfig = require('./webpack.base.conf')
 8 
 9 // 一个用于生成HTML文件并自动注入依赖文件(link/script)的webpack插件
10 var HtmlWebpackPlugin = require('html-webpack-plugin')
11 
12 // 用于更友好地输出webpack的警告、错误等信息
13 var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
14 
15 // add hot-reload related code to entry chunks
16 Object.keys(baseWebpackConfig.entry).forEach(function (name) {
17   baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
18 })
19 
20 // 合并基础的webpack配置
21 module.exports = merge(baseWebpackConfig, {
22   // 配置样式文件的处理规则,使用styleLoaders
23   module: {
24     rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
25   },
26 
27   // 配置Source Maps。在开发中使用cheap-module-eval-source-map更快
28   devtool: '#cheap-module-eval-source-map',
29 
30   // 配置webpack插件
31   plugins: [
32     new webpack.DefinePlugin({
33       'process.env': config.dev.env
34     }),
35     // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
36     new webpack.HotModuleReplacementPlugin(),
37     // 后页面中的报错不会阻塞,但是会在编译结束后报错
38     new webpack.NoEmitOnErrorsPlugin(),
39     // https://github.com/ampedandwired/html-webpack-plugin
40     new HtmlWebpackPlugin({
41       filename: 'index.html',
42       template: 'index.html',
43       inject: true
44     }),
45     new FriendlyErrorsPlugin()
46   ]
47 })

4、build/utils.js

utils主要完成下面3个操作:

1 配置静态资源路径
2 生成cssLoaders用于加载.vue文件中的样式
3 生成styleLoaders用于加载不在.vue文件中的单独存在的样式文件

5、build/vue-loader.conf.js

vue-loader.conf则只配置了css加载器以及编译css之后自动添加前缀。

代码配置注释:

var utils = require('./utils')
var config = require('../config')
var isProduction = process.env.NODE_ENV === 'production'

module.exports = {
  // css加载器
  loaders: utils.cssLoaders({
    sourceMap: isProduction
      ? config.build.productionSourceMap
      : config.dev.cssSourceMap,
    extract: isProduction
  }),

  // 让 vue-loader 知道需要对 audio 的 src 属性的内容转换为模块
  transformToRequire: {
    video: ‘src‘,
    source: ‘src‘,
    img: ‘src‘,
    image: ‘xlink:href‘
  }
}

6、build/build.js

build.js主要完成下面操作:

1 loading动画
2 删除创建目标文件夹
3 webpack编译
4 输出信息

Ps: webpack编译之后会输出到配置里面指定的目标文件夹;删除目标文件夹之后再创建是为了去除旧的内容,以免产生不可预测的影响。

代码配置注释:

 1 // 检查NodeJS和npm的版本
 2 require('./check-versions')()
 3 
 4 process.env.NODE_ENV = 'production'
 5 
 6 //ora插件,实现node.js 命令行环境的 loading效果, 和显示各种状态的图标等
 7 var ora = require('ora')
 8 var rm = require('rimraf')
 9 var path = require('path')
10 
11 // 用于在控制台输出带颜色字体的插件
12 var chalk = require('chalk')
13 
14 var webpack = require('webpack')
15 var config = require('../config')
16 var webpackConfig = require('./webpack.prod.conf')
17 
18 var spinner = ora('building for production...')
19 spinner.start()
20 
21 //rimraf插件,每次启动编译或者打包之前,先把整个dist文件夹删除,然后再重新生成dist
22 rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
23   if (err) throw err
24   webpack(webpackConfig, function (err, stats) {
25     spinner.stop()
26     if (err) throw err
27     process.stdout.write(stats.toString({
28       colors: true,
29       modules: false,
30       children: false,
31       chunks: false,
32       chunkModules: false
33     }) + '\n\n')
34 
35     console.log(chalk.cyan('  Build complete.\n'))
36     console.log(chalk.yellow(
37       '  Tip: built files are meant to be served over an HTTP server.\n' +
38       '  Opening index.html over file:// won\'t work.\n'
39     ))
40   })
41 })

7、build/webpack.prod.conf.js

构建的时候用到的webpack配置来自webpack.prod.conf.js,该配置同样是在webpack.base.conf基础上的进一步完善。主要完成下面操作:

1 合并基础的webpack配置
2 使用styleLoaders
3 配置webpack的输出
4 配置webpack插件
5 gzip模式下的webpack插件配置
6 webpack-bundle分析

代码配置注释:

  1 var path = require('path')
  2 var utils = require('./utils')
  3 var webpack = require('webpack')
  4 var config = require('../config')
  5 var merge = require('webpack-merge')
  6 var baseWebpackConfig = require('./webpack.base.conf')
  7 var HtmlWebpackPlugin = require('html-webpack-plugin')
  8 
  9 // 用于从webpack生成的bundle中提取文本到特定文件中的插件
 10 // 可以抽取出css,js文件将其与webpack输出的bundle分离
 11 var ExtractTextPlugin = require('extract-text-webpack-plugin')
 12 
 13 var env = process.env.NODE_ENV === 'testing'
 14   ? require('../config/test.env')
 15   : config.build.env
 16 
 17 // 合并基础的webpack配置
 18 var webpackConfig = merge(baseWebpackConfig, {
 19   module: {
 20     rules: utils.styleLoaders({
 21       sourceMap: config.build.productionSourceMap,
 22       extract: true
 23     })
 24   },
 25   devtool: config.build.productionSourceMap ? '#source-map' : false,
 26   // 配置webpack的输出
 27   output: {
 28     // 编译输出目录
 29     path: config.build.assetsRoot,
 30     // 编译输出文件名格式
 31     filename: utils.assetsPath('js/[name].[chunkhash].js'),
 32     // 没有指定输出名的文件输出的文件名格式
 33     chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
 34   },
 35 
 36   // 配置webpack插件
 37   plugins: [
 38     // http://vuejs.github.io/vue-loader/en/workflow/production.html
 39     new webpack.DefinePlugin({
 40       'process.env': env
 41     }),
 42 
 43     // 丑化压缩代码
 44     new webpack.optimize.UglifyJsPlugin({
 45       compress: {
 46         warnings: false
 47       },
 48       sourceMap: true
 49     }),
 50 
 51     // 抽离css文件
 52     new ExtractTextPlugin({
 53       filename: utils.assetsPath('css/[name].[contenthash].css')
 54     }),
 55 
 56     // generate dist index.html with correct asset hash for caching.
 57     // you can customize output by editing /index.html
 58     // see https://github.com/ampedandwired/html-webpack-plugin
 59     new HtmlWebpackPlugin({
 60       filename: process.env.NODE_ENV === 'testing'
 61         ? 'index.html'
 62         : config.build.index,
 63       template: 'index.html',
 64       inject: true,
 65       minify: {
 66         removeComments: true,
 67         collapseWhitespace: true,
 68         removeAttributeQuotes: true
 69         // more options:
 70         // https://github.com/kangax/html-minifier#options-quick-reference
 71       },
 72       // necessary to consistently work with multiple chunks via CommonsChunkPlugin
 73       chunksSortMode: 'dependency'
 74     }),
 75 
 76     // split vendor js into its own file
 77     new webpack.optimize.CommonsChunkPlugin({
 78       name: 'vendor',
 79       minChunks: function (module, count) {
 80         // any required modules inside node_modules are extracted to vendor
 81         return (
 82           module.resource &&
 83           /\.js$/.test(module.resource) &&
 84           module.resource.indexOf(
 85             path.join(__dirname, '../node_modules')
 86           ) === 0
 87         )
 88       }
 89     }),
 90     // extract webpack runtime and module manifest to its own file in order to
 91     // prevent vendor hash from being updated whenever app bundle is updated
 92     new webpack.optimize.CommonsChunkPlugin({
 93       name: 'manifest',
 94       chunks: ['vendor']
 95     })
 96   ]
 97 })
 98 
 99 // gzip模式下需要引入compression插件进行压缩
100 if (config.build.productionGzip) {
101   var CompressionWebpackPlugin = require('compression-webpack-plugin')
102   webpackConfig.plugins.push(
103     new CompressionWebpackPlugin({
104       asset: '[path].gz[query]',
105       algorithm: 'gzip',
106       test: new RegExp(
107         '\\.(' +
108         config.build.productionGzipExtensions.join('|') +
109         ')$'
110       ),
111       threshold: 10240,
112       minRatio: 0.8
113     })
114   )
115 }
116 
117 if (config.build.bundleAnalyzerReport) {
118   var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
119   webpackConfig.plugins.push(new BundleAnalyzerPlugin())
120 }
121 
122 module.exports = webpackConfig

8、build/check-versions.js

check-version.js完成对node和npm的版本检测。

代码配置注释:

 1 // 用于在控制台输出带颜色字体的插件
 2 var chalk = require('chalk')
 3 
 4 // 语义化版本检查插件(The semantic version parser used by npm)
 5 var semver = require('semver')
 6 
 7 // 引入package.json
 8 var packageConfig = require('../package.json')
 9 
10 // 开辟子进程执行指令cmd并返回结果
11 function exec (cmd) {
12   return require('child_process').execSync(cmd).toString().trim()
13 }
14 
15 // node和npm版本需求
16 var versionRequirements = [
17   {
18     name: 'node',
19     currentVersion: semver.clean(process.version),
20     versionRequirement: packageConfig.engines.node
21   },
22   {
23     name: 'npm',
24     currentVersion: exec('npm --version'),
25     versionRequirement: packageConfig.engines.npm
26   }
27 ]
28 
29 module.exports = function () {
30   var warnings = []
31   // 依次判断版本是否符合要求
32   for (var i = 0; i < versionRequirements.length; i++) {
33     var mod = versionRequirements[i]
34     if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
35       warnings.push(mod.name + ': ' +
36         chalk.red(mod.currentVersion) + ' should be ' +
37         chalk.green(mod.versionRequirement)
38       )
39     }
40   }
41 
42   // 如果有警告则将其输出到控制台
43   if (warnings.length) {
44     console.log('')
45     console.log(chalk.yellow('To use this template, you must update following to modules:'))
46     console.log()
47     for (var i = 0; i < warnings.length; i++) {
48       var warning = warnings[i]
49       console.log('  ' + warning)
50     }
51     console.log()
52     process.exit(1)
53   }
54 }

 

三、config文件夹

 

1、config/index.js

config文件夹下最主要的文件就是index.js了,在这里面描述了开发和构建两种环境下的配置,前面的build文件夹下也有不少文件引用了index.js里面的配置。

代码配置注释:

 1 // see http://vuejs-templates.github.io/webpack for documentation.
 2 var path = require('path')
 3 
 4 module.exports = {
 5   // 构建产品时使用的配置
 6   build: {
 7     // webpack的编译环境
 8     env: require('./prod.env'),
 9     // 编译输入的index.html文件
10     index: path.resolve(__dirname, '../dist/index.html'),
11     // webpack输出的目标文件夹路径
12     assetsRoot: path.resolve(__dirname, '../dist'),
13     // webpack编译输出的二级文件夹
14     assetsSubDirectory: 'static',
15     // webpack编译输出的发布路径
16     assetsPublicPath: '/',
17     // 使用SourceMap
18     productionSourceMap: true,
19     // Gzip off by default as many popular static hosts such as
20     // Surge or Netlify already gzip all static assets for you.
21     // Before setting to `true`, make sure to:
22     // npm install --save-dev compression-webpack-plugin
23     // 默认不打开开启gzip模式
24     productionGzip: false,
25     // gzip模式下需要压缩的文件的扩展名
26     productionGzipExtensions: ['js', 'css'],
27     // Run the build command with an extra argument to
28     // View the bundle analyzer report after build finishes:
29     // `npm run build --report`
30     // Set to `true` or `false` to always turn it on or off
31     bundleAnalyzerReport: process.env.npm_config_report
32   },
33   // 开发过程中使用的配置
34   dev: {
35     // webpack的编译环境
36     env: require('./dev.env'),
37     // dev-server监听的端口
38     port: 8080,
39     // 启动dev-server之后自动打开浏览器
40     autoOpenBrowser: true,
41     // webpack编译输出的二级文件夹
42     assetsSubDirectory: 'static',
43     // webpack编译输出的发布路径
44     assetsPublicPath: '/',
45     // 请求代理表,在这里可以配置特定的请求代理到对应的API接口
46     // 例如将'/api/xxx'代理到'www.example.com/api/xxx'
47     proxyTable: {},
48     // CSS Sourcemaps off by default because relative paths are "buggy"
49     // with this option, according to the CSS-Loader README
50     // (https://github.com/webpack/css-loader#sourcemaps)
51     // In our experience, they generally work as expected,
52     // just be aware of this issue when enabling this option.
53     // 是否开启 cssSourceMap
54     cssSourceMap: false
55   }
56 }

Ps:config/dev.env.js、config/prod.env.js,这两个个文件就简单设置了环境变量而已,没什么特别。

 

posted @ 2017-08-17 11:23  chendechang  阅读(6829)  评论(0编辑  收藏  举报