• 直接引入 <script src="https://unpkg.com/vue"></script>
  • 用npm安装   $ npm install vue
  • Vue.js 提供一个官方命令行工具,vue-cli可创建并启动一个带热重载、保存时静态检查以及可用于生产环境的构建配置的项目
    # 全局安装 vue-cli
    $ npm install --global vue-cli
    # 创建一个基于 webpack 模板的新项目
    $ vue init webpack my-project
    # 安装依赖
    $ cd my-project
    $ npm install $ cnpm install
    $ npm run dev
  • http://localhost:8080/#/ 查看页面

配置

package.json里面的scripts字段

"scripts": {
"dev": "node build/dev-server.js",
"start": "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"
},

config/index.js

// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path') module.exports = {
// 构建产品时使用的配置
build: {
// webpack的编译环境
env: require('./prod.env'),
// 编译输入的index.html文件
index: path.resolve(__dirname, '../dist/index.html'),
// webpack输出的目标文件夹路径
assetsRoot: path.resolve(__dirname, '../dist'),
// webpack编译输出的二级文件夹
assetsSubDirectory: 'static',
// webpack编译输出的发布路径
assetsPublicPath: '/',
// 使用SourceMap
productionSourceMap: true,
// 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
// 默认不打开开启gzip模式
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
},
// gzip模式下需要压缩的文件的扩展名
dev: {
// webpack的编译环境
env: require('./dev.env'),
// dev-server监听的端口
port: ,
// 启动dev-server之后自动打开浏览器
autoOpenBrowser: true,
// webpack编译输出的二级文件夹
assetsSubDirectory: 'static',
// webpack编译输出的发布路径
assetsPublicPath: '/',
//请求代理表,在这里可以配置特定的请求代理到对应的API接口
// 例如将'/api/xxx'代理到'www.example.com/api/xxx'
proxyTable: {},
// 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
}
}

config/index.js

开发环境配置:

  1. build/dev-server.js

    // 检查NodeJS和npm的版本
    require('./check-versions')() // 获取配置 config/index.js
    var config = require('../config')
    // 如果Node的环境变量中没有设置当前的环境(NODE_ENV),则使用config中的配置作为当前的环境
    if (!process.env.NODE_ENV) {
    process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
    } // 一个可以调用默认软件打开网址、图片、文件等内容的插件
    // 这里用它来调用默认浏览器打开dev-server监听的端口,例如:localhost:8080
    var opn = require('opn')
    var path = require('path')
    var express = require('express')
    var webpack = require('webpack') // 一个express中间件,用于将http请求代理到其他服务器
    // 例:localhost:8080/api/xxx --> localhost:3000/api/xxx
    // 这里使用该插件可以将前端开发中涉及到的请求代理到API服务器上,方便与服务器对接
    var proxyMiddleware = require('http-proxy-middleware') // 根据 Node 环境来引入相应的 webpack 配置
    var webpackConfig = process.env.NODE_ENV === 'testing' ? require('./webpack.prod.conf') : require('./webpack.dev.conf') // default port where dev server listens for incoming traffic
    // dev-server 监听的端口,默认为config.dev.port设置的端口,即8080
    var port = process.env.PORT || config.dev.port // automatically open browser, if not set will be false
    // 用于判断是否要自动打开浏览器的布尔变量,当配置文件中没有设置自动打开浏览器的时候其值为 false
    var autoOpenBrowser = !!config.dev.autoOpenBrowser // Define HTTP proxies to your custom API backend
    // https://github.com/chimurai/http-proxy-middleware
    // 定义 HTTP 代理表,代理到 API 服务器
    var proxyTable = config.dev.proxyTable // 创建1个 express 实例
    var app = express() // 根据webpack配置文件创建Compiler对象
    var compiler = webpack(webpackConfig) // webpack-dev-middleware使用compiler对象来对相应的文件进行编译和绑定
    // 编译绑定后将得到的产物存放在内存中而没有写进磁盘
    // 将这个中间件交给express使用之后即可访问这些编译后的产品文件
    var devMiddleware = require('webpack-dev-middleware')(compiler, {
    publicPath: webpackConfig.output.publicPath,
    quiet: true
    }) // webpack-hot-middleware,用于实现热重载功能的中间件
    var hotMiddleware = require('webpack-hot-middleware')(compiler, {
    log: false,
    heartbeat:
    }) // force page reload when html-webpack-plugin template changes
    // 当html-webpack-plugin提交之后通过热重载中间件发布重载动作使得页面重载
    compiler.plugin('compilation', function(compilation) {
    compilation.plugin('html-webpack-plugin-after-emit', function(data, cb) {
    hotMiddleware.publish({
    action: 'reload'
    })
    cb()
    })
    }) // proxy api requests
    // 将 proxyTable 中的代理请求配置挂在到express服务器上
    Object.keys(proxyTable).forEach(function(context) {
    var options = proxyTable[context]
    // 格式化options,例如将'www.example.com'变成{ target: 'www.example.com' }
    if (typeof options === 'string') {
    options = {
    target: options
    }
    }
    app.use(proxyMiddleware(options.filter || context, options))
    }) // handle fallback for HTML5 history API
    // 重定向不存在的URL,常用于SPA
    app.use(require('connect-history-api-fallback')()) // serve webpack bundle output
    // 使用webpack开发中间件
    // 即将webpack编译后输出到内存中的文件资源挂到express服务器上
    app.use(devMiddleware) // enable hot-reload and state-preserving
    // compilation error display
    // 将热重载中间件挂在到express服务器上
    app.use(hotMiddleware) // serve pure static assets
    // 静态资源的路径
    var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) // 将静态资源挂到express服务器上
    app.use(staticPath, express.static('./static')) // 应用的地址信息,例如:http://localhost:8080
    var uri = 'http://localhost:' + port
    var _resolve
    var readyPromise = new Promise(resolve => {
    _resolve = resolve
    }) console.log('> Starting dev server...') // webpack开发中间件合法(valid)之后输出提示语到控制台,表明服务器已启动
    devMiddleware.waitUntilValid(() => {
    console.log('> Listening at ' + uri + '\n')
    // when env is testing, don't need open it
    // 如果符合自动打开浏览器的条件,则通过opn插件调用系统默认浏览器打开对应的地址uri
    if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
    opn(uri)
    }
    _resolve()
    }) var server = app.listen(port)
    // 启动express服务器并监听相应的端口(8080)
    module.exports = {
    ready: readyPromise,
    close: () => {
    server.close()
    }
    }

    dev-server

  2. dev-server使用的webpack配置来自build/webpack.dev.conf.js文件
    var utils = require('./utils')
    var webpack = require('webpack')
    var config = require('../config') // 一个可以合并数组和对象的插件
    var merge = require('webpack-merge')
    var baseWebpackConfig = require('./webpack.base.conf') // 一个用于生成HTML文件并自动注入依赖文件(link/script)的webpack插件
    var HtmlWebpackPlugin = require('html-webpack-plugin') // 用于更友好地输出webpack的警告、错误等信息
    var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') // add hot-reload related code to entry chunks
    Object.keys(baseWebpackConfig.entry).forEach(function(name) {
    baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
    }) // 合并基础的webpack配置
    module.exports = merge(baseWebpackConfig, { // 配置样式文件的处理规则,使用styleLoaders
    module: {
    rules: utils.styleLoaders({
    sourceMap: config.dev.cssSourceMap
    })
    },
    // cheap-module-eval-source-map is faster for development
    // 配置Source Maps。在开发中使用cheap-module-eval-source-map更快
    devtool: '#cheap-module-eval-source-map',
    // 配置webpack插件
    plugins: [
    new webpack.DefinePlugin({
    'process.env': config.dev.env
    }),
    // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
    new webpack.HotModuleReplacementPlugin(),
    // 后页面中的报错不会阻塞,但是会在编译结束后报错
    new webpack.NoEmitOnErrorsPlugin(),
    // https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
    filename: 'index.html',
    template: 'index.html',
    inject: true
    }),
    new FriendlyErrorsPlugin()
    ]
    })

    webpack.dev.conf.js

  3. build/webpack.dev.conf.js中又引用了build/webpack.base.conf.js
    var path = require('path')
    var utils = require('./utils')
    var config = require('../config')
    var vueLoaderConfig = require('./vue-loader.conf') // 给出正确的绝对路径
    function resolve(dir) {
    return path.join(__dirname, '..', dir)
    } module.exports = {
    // 配置webpack编译入口
    entry: {
    app: './src/main.js'
    },
    // 配置webpack输出路径和命名规则
    output: {
    // webpack输出的目标文件夹路径(例如:/dist)
    path: config.build.assetsRoot,
    // webpack输出bundle文件命名格式
    filename: '[name].js',
    // webpack编译输出的发布路径
    publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath
    },
    // 配置模块的规则
    resolve: {
    // 自动resolve的扩展名
    extensions: ['.js', '.vue', '.json'],
    // 创建路径别名,有了别名之后引用模块更方便,例如
    // import Vue from 'vue/dist/vue.common.js'可以写成 import Vue from 'vue'
    alias: {
    'vue$': 'vue/dist/vue.esm.js',
    '@': resolve('src')
    }
    },
    // 配置不同类型模块的处理规则
    module: {
    rules: [{
    // 对src和test文件夹下的.js和.vue文件使用eslint-loader
    testresolve: /\.(js|vue)$/,
    loader: 'eslint-loader',
    enforce: 'pre',
    include: [resolve('src'), resolve('test')],
    options: {
    formatter: require('eslint-friendly-formatter')
    }
    }, {
    // 对所有.vue文件使用vue-loader
    test: /\.vue$/,
    loader: 'vue-loader',
    options: vueLoaderConfig
    }, {
    // 对src和test文件夹下的.js文件使用babel-loader
    test: /\.js$/,
    loader: 'babel-loader',
    include: [resolve('src'), resolve('test')]
    }, {
    // 对图片资源文件使用url-loader,query.name指明了输出的命名规则
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    loader: 'url-loader',
    options: {
    limit: ,
    name: utils.assetsPath('img/[name].[hash:7].[ext]')
    }
    }, {
    // 对音频视频资源文件使用url-loader,query.name指明了输出的命名规则
    test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
    loader: 'url-loader',
    options: {
    limit: ,
    name: utils.assetsPath('media/[name].[hash:7].[ext]')
    }
    }, {
    // 对字体资源文件使用url-loader,query.name指明了输出的命名规则
    test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
    loader: 'url-loader',
    options: {
    limit: ,
    name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
    }
    }]
    }
    }

    webpack.base.conf

  4. webpack配置文件中使用到了build/utils.js
    引用check-version.js完成对node和npm的版本检测
    配置静态资源路径
    生成cssLoaders用于加载.vue文件中的样式
    生成styleLoaders用于加载不在.vue文件中的单独存在的样式文件
  5. webpack配置文件中使用到了build/vue-loader.conf.js
    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'
    }
    }

    vue-loader.conf.js

构建环境配置:

  1. build/build.js

    // 检查NodeJS和npm的版本
    require('./check-versions')() process.env.NODE_ENV = 'production' var ora = require('ora')
    var rm = require('rimraf')
    var path = require('path')
    // 用于在控制台输出带颜色字体的插件
    var chalk = require('chalk')
    var webpack = require('webpack')
    var config = require('../config')
    var webpackConfig = require('./webpack.prod.conf') var spinner = ora('building for production...')
    // 开启loading动画
    spinner.start() rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
    if (err) throw err
    webpack(webpackConfig, function(err, stats) {
    // 停止loading动画
    spinner.stop()
    if (err) throw err
    // 没有出错则输出相关信息
    process.stdout.write(stats.toString({
    colors: true,
    modules: false,
    children: false,
    chunks: false,
    chunkModules: false
    }) + '\n\n') 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'
    ))
    })
    })

    build/build.js

  2. build/webpack.prod.conf.js

    var path = require('path')
    var utils = require('./utils')
    var webpack = require('webpack')
    var config = require('../config')
    var merge = require('webpack-merge')
    var baseWebpackConfig = require('./webpack.base.conf')
    var CopyWebpackPlugin = require('copy-webpack-plugin')
    var HtmlWebpackPlugin = require('html-webpack-plugin')
    // 用于从webpack生成的bundle中提取文本到特定文件中的插件
    // 可以抽取出css,js文件将其与webpack输出的bundle分离
    var ExtractTextPlugin = require('extract-text-webpack-plugin')
    var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') var env = process.env.NODE_ENV === 'testing' ? require('../config/test.env') : config.build.env // 合并基础的webpack配置
    var webpackConfig = merge(baseWebpackConfig, {
    module: {
    rules: utils.styleLoaders({
    sourceMap: config.build.productionSourceMap,
    extract: true
    })
    },
    devtool: config.build.productionSourceMap ? '#source-map' : false,
    // 配置webpack的输出
    output: {
    // 编译输出目录
    path: config.build.assetsRoot,
    // 编译输出文件名格式
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    // 没有指定输出名的文件输出的文件名格式
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
    },
    // 配置webpack插件
    plugins: [
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
    new webpack.DefinePlugin({
    'process.env': env
    }),
    // 丑化压缩代码
    new webpack.optimize.UglifyJsPlugin({
    compress: {
    warnings: false
    },
    sourceMap: true
    }),
    // extract css into its own file
    // 抽离css文件
    new ExtractTextPlugin({
    filename: utils.assetsPath('css/[name].[contenthash].css')
    }),
    // Compress extracted CSS. We are using this plugin so that possible
    // duplicated CSS from different components can be deduped.
    new OptimizeCSSPlugin({
    cssProcessorOptions: {
    safe: true
    }
    }),
    // generate dist index.html with correct asset hash for caching.
    // you can customize output by editing /index.html
    // see https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
    filename: process.env.NODE_ENV === 'testing' ? 'index.html' : 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
    },
    // necessary to consistently work with multiple chunks via CommonsChunkPlugin
    chunksSortMode: 'dependency'
    }),
    // split vendor js into its own file
    new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor',
    minChunks: function(module, count) {
    // any required modules inside node_modules are extracted to vendor
    return (
    module.resource &&
    /\.js$/.test(module.resource) &&
    module.resource.indexOf(
    path.join(__dirname, '../node_modules')
    ) ===
    )
    }
    }),
    // extract webpack runtime and module manifest to its own file in order to
    // prevent vendor hash from being updated whenever app bundle is updated
    new webpack.optimize.CommonsChunkPlugin({
    name: 'manifest',
    chunks: ['vendor']
    }),
    // copy custom static assets
    new CopyWebpackPlugin([{
    from: path.resolve(__dirname, '../static'),
    to: config.build.assetsSubDirectory,
    ignore: ['.*']
    }])
    ]
    }) // gzip模式下需要引入compression插件进行压缩
    if (config.build.productionGzip) {
    var CompressionWebpackPlugin = require('compression-webpack-plugin') webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
    asset: '[path].gz[query]',
    algorithm: 'gzip',
    test: new RegExp(
    '\\.(' +
    config.build.productionGzipExtensions.join('|') +
    ')$'
    ),
    threshold: ,
    minRatio: 0.8
    })
    )
    } if (config.build.bundleAnalyzerReport) {
    var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
    webpackConfig.plugins.push(new BundleAnalyzerPlugin())
    } module.exports = webpackConfig

    webpack.prod.conf

  3. build/check-versions.js

    // 用于在控制台输出带颜色字体的插件
    var chalk = require('chalk')
    // 语义化版本检查插件(The semantic version parser used by npm)
    var semver = require('semver')
    // 引入package.json
    var packageConfig = require('../package.json')
    var shell = require('shelljs') // 开辟子进程执行指令cmd并返回结果
    function exec(cmd) {
    return require('child_process').execSync(cmd).toString().trim()
    } // node和npm版本需求
    var versionRequirements = [{
    name: 'node',
    currentVersion: semver.clean(process.version),
    versionRequirement: packageConfig.engines.node
    }, ] if (shell.which('npm')) {
    versionRequirements.push({
    name: 'npm',
    currentVersion: exec('npm --version'),
    versionRequirement: packageConfig.engines.npm
    })
    } module.exports = function() {
    var warnings = []
    // 依次判断版本是否符合要求
    for (var i = ; i < versionRequirements.length; i++) {
    var mod = versionRequirements[i]
    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 (var i = ; i < warnings.length; i++) {
    var warning = warnings[i]
    console.log(' ' + warning)
    }
    console.log()
    process.exit()
    }
    }

    check-versions

vue安装与配置的更多相关文章

  1. vue安装以及配置

    今天又重新做了一遍vue的安装步骤: 1.条件,vue需要安装在node环境里面,确保安装了node. 2.安装脚手架. 找一个文件夹,放你的项目.待会儿安装的时候,项目会在你找的这个文件下新增一个你 ...

  2. 前端笔记之Vue(一)初识SPA和Vue&webpack配置和vue安装&指令

    一.单页面应用(SPA) 1.1 C/S到B/S页面架构的转变 C/S:客户端/服务器(Client/Server)架构的软件. C/S 软件的特点: ① 从window桌面双击打开 ② 更新的时候会 ...

  3. day 85 Vue学习之vue-cli脚手架下载安装及配置

      1. 先下载node.js,下载地址:https://nodejs.org/en/download/ 找个目录保存,解压下载的文件,然后配置环境变量,将下面的路径配置到环境变量中. 由于 Node ...

  4. idea npm vue java开发工具安装 环境配置

    感谢此链接内容作者,从前往后流程较完整详细,助我成功配置好(不知道在这之前做的一些尝试有没有影响) https://blog.csdn.net/qq_42564846/article/details/ ...

  5. Vue学习之vue-cli脚手架下载安装及配置

    Vue学习之vue-cli脚手架下载安装及配置:https://www.cnblogs.com/clschao/articles/10650862.html 1. 先下载node.js,下载地址:ht ...

  6. day 84 Vue学习之vue-cli脚手架下载安装及配置

    Vue学习之vue-cli脚手架下载安装及配置   1. 先下载node.js,下载地址:https://nodejs.org/en/download/ 找个目录保存,解压下载的文件,然后配置环境变量 ...

  7. Vue学习笔记-VSCode安装与配置

    一  使用环境: windows 7 64位操作系统 二  VSCode安装与配置  1.下载: https://code.visualstudio.com 直接点击即可. 2. 点击按装程序,默认安 ...

  8. Vue之webpack的安装与配置及其简单应用

    一.文件结构 二.index.html <!DOCTYPE html> <html lang="en"> <head> <meta cha ...

  9. 【前端】vue.js环境配置以及实例运行简明教程

    vue.js环境配置以及实例运行简明教程 声明:本文档编写参考如下两篇博客,是对它们的修改与补充,欢迎点击链接查看原文: 原文1:vue.js在windows本地下搭建环境和创建项目 原文2:Vue. ...

随机推荐

  1. gem "ransack"(4000✨) 简单介绍

    Object-based searching:演示. git:  https://github.com/activerecord-hackery/ransack Gorails视频和我的博客记录:ht ...

  2. Confluence 6 自定义空间布局

    你可以通过编辑布局文件来对 Confluence 的外观和表现进行编辑.这个页面将会告诉你如何来为空间自定义布局文件.你需要系统管理员的 全局权限(global permission) 和你希望进行修 ...

  3. 删除 github 相应仓库下的文件(不删除仓库)

    1.git  clone url(仓库的ssh) 将仓库克隆 到本地 2.进入到本地仓库文件夹 将想要删除的文件删除 3.右键 git bash here 4.git add . 5.git comm ...

  4. 【Oracle】【6】去掉字符串最后一个特殊字符

    --去除字符串末尾的省字,若无省字则无变化 SELECT DISTINCT TRIM('省' FROM PROVINCE) PROVINCE FROM ADDRESS 参考博客: 1,使用oracle ...

  5. 向java高级工程师和项目经理的道路进发【转】

    转自https://www.cnblogs.com/ahudyan-forever/p/5263296.html 宏观 一. JAVA.要想成为JAVA(高级)工程师肯定要学习JAVA.一般的程序员或 ...

  6. h5手机端禁止缩放问题

    最近测试html5页面,发现默认都允许用户缩放页面,或者在屏幕双击放大或缩小.即相当于这样设置 <meta name="viewport" content="wid ...

  7. 成功解决You are using pip version 9.0.1, however version 9.0.3 is available. You should consider upgra

    解决问题 You are using pip version 9.0.3, however version 10.0.1 is available.You should consider upgrad ...

  8. set unused的用法(ORACLE删除字段)

    set unused的用法(ORACLE删除字段) 一.问题 现场有一张大数据量的分区表,数据量在10G以上.因某种原因需要删除其中的某些字段.如果直接用alter table1 drop (colu ...

  9. os模块,序列化模块,json模块,pickle模块

    一.os模块os.system("bash command") 运行shell命令,直接显示 os.popen("bash command).read() 运行shell ...

  10. Xshell中文乱码怎么处理?

    改成如下图: