基于Vue.js的大型报告页项目实现过程及问题总结(一)

今年5月份的时候做了一个测评报告项目,需要在网页正常显示的同时且可打印为pdf,当时的技术方案采用jquery+template的方式,因为是固定模板所以并没有考虑报告的模块化区分,九月底产品提出新的需求,由于报告页数动辄上千页,所以希望用户自行选择内容生成报告,这个时候原项目就不够灵活了,与小伙伴商量决定将这个项目使用vue进行重构,对报告模块进行细分封装组件复用,大概一个月的工期,中途遇到n多坑,趁着今天有时间将实现思路整理出来并将出现的问题总结一下

整体的实现思维导图如下:

需要考虑的:

1.可生成PDF版且可打印

2.根据后台获取的json生成包含相应模块的报告

3.组件内基于echarts封装图表的引用

4.目录模块的页码定位

5.如何进行模块内的细分(如1.2.1.3);

6.webpack对多页面编译的配置

Ps:转PDF插件使用的是OpenHtmlToPdf具体配置方法可自行百度,在这里不过多赘述。

关于pdf的一点小坑(知识点朋友们!):

网页打印A4纸的尺寸是(1123*793),在使用OpenHtmlToPdf时无法使用css3百分之八十的属性,像translate等,还有就是margin-top不会生效,使用padding-top代替吧,打印生无法请求ajax,如需打印请将数据先存储到本地再行打印,可根据不同浏览方式判断两种方案。

以下实现全部是基于Vue-cli快速构建的项目中实现的,vue-cli的安装网上有很多详细的教程不过多说了

1.新建项目,命令行执行代码:

vue init webpack vuetest

命令输入后,会进入安装阶段,需要用户输入一些信息

Project name (vuetest)                    项目名称,可以自己指定,也可直接回车,按照括号中默认名字(注意这里的名字不能有大写字母,如果有会报错Sorry, name can no longer contain capital letters),阮一峰老师博客为什么文件名要小写 ,可以参考一下。

Project description (A Vue.js project)  项目描述,也可直接点击回车,使用默认名字

Author (........)       作者,不用说了,你想输什么就输什么吧

接下来会让用户选择

Runtime + Compiler: recommended for most users    运行加编译,既然已经说了推荐,就选它了

Runtime-only: about 6KB lighter min+gzip, but templates (or any Vue-specificHTML) are ONLY allowed in .vue files - render functions are required elsewhere   仅运行时,已经有推荐了就选择第一个了

Install vue-router? (Y/n)    是否安装vue-router,这是官方的路由,大多数情况下都使用,vue-router官网 。这里就输入“y”后回车即可。

Use ESLint to lint your code? (Y/n)      是否使用ESLint管理代码,ESLint是个代码风格管理工具,是用来统一代码风格的,并不会影响整体的运行,这也是为了多人协作,新手就不用了,一般项目中都会使用。ESLint官网

接下来也是选择题Pick an ESLint preset (Use arrow keys)            选择一个ESLint预设,编写vue项目时的代码风格,因为我选择了使用ESLint

Standard (https://github.com/feross/standard)    标准,有些看不明白,什么标准呢,去给提示的standardgithub地址看一下, 原来时js的标准风格

AirBNB (https://github.com/airbnb/javascript)    JavaScript最合理的方法,这个github地址说的是JavaScript最合理的方法

none (configure it yourself)    这个不用说,自己定义风格

具体选择哪个因人而异吧  ,我选择标准风格

Setup unit tests with Karma + Mocha? (Y/n)  是否安装单元测试,我选择安装

Setup e2e tests with Nightwatch(Y/n)?     是否安装e2e测试 ,我选择安装

完成

 初始的目录结构大概是这样的

由于是多页面应用所以需要在src下建一个modle文件夹里面是两个不同的项目

注意:

 这里的index.html是入口文件,一定不能少,这这里做中转默认进入demo1的页面

<body>
    <script>
        location.href = "module/demo1.html";
    </script>
</body>

下面对多页面进行配置,主要操作config和build这两个文件夹

修改默认的webpack配置webpack.base.conf.js

生成需要的入口文件

var path = require('path')
var config = require('../config')
var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../')
var glob = require('glob');
var entries = getEntry(['./src/demo1/index/*.js', './src/module/demo2/*.js']); // 获得入口js文件
var env = process.env.NODE_ENV
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
// various preprocessor loaders added to vue-loader at the end of this file
var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)
var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)
var useCssSourceMap = cssSourceMapDev || cssSourceMapProd
module.exports = {
  entry: entries,
  output: {
    path: config.build.assetsRoot,
    publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
    filename: '[name].js'
  },
  resolve: {
    extensions: ['', '.js', '.vue','.json'],
    
    fallback: [path.join(__dirname, '../node_modules')],
    alias: {
      'vue$': 'vue/dist/vue',
      'src': path.resolve(__dirname, '../src'),
      'common': path.resolve(__dirname, '../src/common'),
      'components': path.resolve(__dirname, '../src/components')
    }
  },
  resolveLoader: {
    fallback: [path.join(__dirname, '../node_modules')]
  },
  module: {
    loaders: [{
        test: /\.vue$/,
        loader: 'vue'
      },
      {
        test: /\.js$/,
        loader: 'babel',
        include: projectRoot,
        exclude: /node_modules/
      },
      {
        test: /\.json$/,
        loader: 'json'
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url',
        query: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url',
        query: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  },
  vue: {
    loaders: utils.cssLoaders({
      sourceMap: useCssSourceMap
    }),
    postcss: [
      require('autoprefixer')({
        browsers: ['last 2 versions']
      })
    ]
  }
}
function getEntry(globPath) {
  var entries = {},
    basename, tmp, pathname;
  if (typeof (globPath) != "object") {
    globPath = [globPath]
  }
  globPath.forEach((itemPath) => {
    glob.sync(itemPath).forEach(function (entry) {
      basename = path.basename(entry, path.extname(entry));
      if (entry.split('/').length > 4) {
        tmp = entry.split('/').splice(-3);
        pathname = tmp.splice(0, 1) + '/' + basename; // 正确输出js和html的路径
        entries[pathname] = entry;
      } else {
        entries[basename] = entry;
      }
    });
  });
  return entries;
}

修改本地开发的webpack配置webpack.dev.conf.js

这里是和本地服务器有关的配置

这里是根据目录生成对应的页面

var path = require('path');
var config = require('../config')
var webpack = require('webpack')
var merge = require('webpack-merge')
var utils = require('./utils')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var glob = require('glob')
// 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])
})
module.exports = merge(baseWebpackConfig, {
  module: {
    loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
  },
  // eval-source-map is faster for development
  devtool: '#eval-source-map',
  plugins: [
    new webpack.DefinePlugin({
      'process.env': config.dev.env
    }),
    // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  ]
})
function getEntry(globPath) {
  var entries = {},
    basename, tmp, pathname;
  if (typeof (globPath) != "object") {
    globPath = [globPath]
  }
  globPath.forEach((itemPath) => {
    glob.sync(itemPath).forEach(function (entry) {
      basename = path.basename(entry, path.extname(entry));
      if (entry.split('/').length > 4) {
        tmp = entry.split('/').splice(-3);
        pathname = tmp.splice(0, 1) + '/' + basename; // 正确输出js和html的路径
        entries[pathname] = entry;
      } else {
        entries[basename] = entry;
      }
    });
  });
  return entries;
}
var pages = getEntry(['./src/module/*.html','./src/module/**/*.html']);
for (var pathname in pages) {
  // 配置生成的html文件,定义路径等
  var conf = {
    filename: pathname + '.html',
    template: pages[pathname],   // 模板路径
    inject: true,              // js插入位置
    // necessary to consistently work with multiple chunks via CommonsChunkPlugin
    chunksSortMode: 'dependency'
  };
  if (pathname in module.exports.entry) {
    conf.chunks = ['manifest', 'vendor', pathname];
    conf.hash = true;
  }
  module.exports.plugins.push(new HtmlWebpackPlugin(conf));
}

修改构建生产的webpack配置webpack.prod.conf.js

var path = require('path')
var config = require('../config')
var utils = require('./utils')
var webpack = require('webpack')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var CleanPlugin = require('clean-webpack-plugin')//webpack插件,用于清除目录文件
var glob = require('glob');
var env = config.build.env

var webpackConfig = merge(baseWebpackConfig, {
  module: {
    loaders: 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')
  },
  vue: {
    loaders: utils.cssLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true
    })
  },
  plugins: [
    // http://vuejs.github.io/vue-loader/workflow/production.html
    new webpack.DefinePlugin({
      'process.env': env
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    }),
    new CleanPlugin(['../dist']), //清空生成目录
    new webpack.optimize.OccurenceOrderPlugin(),
    // extract css into its own file
    new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
    // 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
    // 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')
          ) === 0
        )
      }
    }),
    // 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']
    })
  ]
})

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: 10240,
      minRatio: 0.8
    })
  )
}

module.exports = webpackConfig

function getEntry(globPath) {
  var entries = {},
    basename, tmp, pathname;
  if (typeof (globPath) != "object") {
    globPath = [globPath]
  }
  globPath.forEach((itemPath) => {
    glob.sync(itemPath).forEach(function (entry) {
      basename = path.basename(entry, path.extname(entry));
      if (entry.split('/').length > 4) {
        tmp = entry.split('/').splice(-3);
        pathname = tmp.splice(0, 1) + '/' + basename; // 正确输出js和html的路径
        entries[pathname] = entry;
      } else {
        entries[basename] = entry;
      }
    });
  });
  return entries;
}

var pages = getEntry(['./src/module/*.html','./src/module/**/*.html']);

for (var pathname in pages) {
  // 配置生成的html文件,定义路径等
  var conf = {
    filename: pathname + '.html',
    template: pages[pathname],   // 模板路径
    inject: true,              // js插入位置
    // necessary to consistently work with multiple chunks via CommonsChunkPlugin
    chunksSortMode: 'dependency'
  };

  if (pathname in module.exports.entry) {
    conf.chunks = ['manifest', 'vendor', pathname];
    conf.hash = true;
  }

  module.exports.plugins.push(new HtmlWebpackPlugin(conf));
}
var path = require('path')
var config = require('../config')
var utils = require('./utils')
var webpack = require('webpack')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var CleanPlugin = require('clean-webpack-plugin')//webpack插件,用于清除目录文件
var glob = require('glob');
var env = config.build.env

var webpackConfig = merge(baseWebpackConfig, {
  module: {
    loaders: 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')
  },
  vue: {
    loaders: utils.cssLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true
    })
  },
  plugins: [
    // http://vuejs.github.io/vue-loader/workflow/production.html
    new webpack.DefinePlugin({
      'process.env': env
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    }),
    new CleanPlugin(['../dist']), //清空生成目录
    new webpack.optimize.OccurenceOrderPlugin(),
    // extract css into its own file
    new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
    // 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
    // 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')
          ) === 0
        )
      }
    }),
    // 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']
    })
  ]
})

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: 10240,
      minRatio: 0.8
    })
  )
}

module.exports = webpackConfig

function getEntry(globPath) {
  var entries = {},
    basename, tmp, pathname;
  if (typeof (globPath) != "object") {
    globPath = [globPath]
  }
  globPath.forEach((itemPath) => {
    glob.sync(itemPath).forEach(function (entry) {
      basename = path.basename(entry, path.extname(entry));
      if (entry.split('/').length > 4) {
        tmp = entry.split('/').splice(-3);
        pathname = tmp.splice(0, 1) + '/' + basename; // 正确输出js和html的路径
        entries[pathname] = entry;
      } else {
        entries[basename] = entry;
      }
    });
  });
  return entries;
}

var pages = getEntry(['./src/module/*.html','./src/module/**/*.html']);

for (var pathname in pages) {
  // 配置生成的html文件,定义路径等
  var conf = {
    filename: pathname + '.html',
    template: pages[pathname],   // 模板路径
    inject: true,              // js插入位置
    // necessary to consistently work with multiple chunks via CommonsChunkPlugin
    chunksSortMode: 'dependency'
  };

  if (pathname in module.exports.entry) {
    conf.chunks = ['manifest', 'vendor', pathname];
    conf.hash = true;
  }

  module.exports.plugins.push(new HtmlWebpackPlugin(conf));
}

修改配置文件config

修改index.js

在build.js中会引用assetsRoot,这里就是对应的根目录,改成你想要输出的地址就好了。ps:这里是相对地址 assetsPublicPath会被引用插入到页面的模版中,这个是你资源的根目录

// see http://vuejs-templates.github.io/webpack for documentation.
var 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: '../',
    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
    productionGzip: false,
    productionGzipExtensions: ['js', 'css']
  },
  dev: {
    env: require('./dev.env'),
    port: 8080,
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    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
  }
}

ok,配置结束,一个基本的多页面应用已经成功建成

接下来就进入正题了,放在下一篇来写。。。。。。。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

加快Android编译速度的技巧总结

对于Android开发者而言,随着工程不断的壮大,Android项目的编译时间也逐渐变长,即便是有时候添加一行代码也需要等待好久才能看见期待的效果。之前加快An...

451
来自专栏Laoqi's Linux运维专列

限定某个目录禁止解析php+限制 user_agent

限定某个目录禁止解析php : 实例解析: 有这样一种情况,有些站点和论坛是允许上传图片到服务器,但是这就给黑客留下了可进入服务器的大门,他们上传一些php或者...

2887
来自专栏Seebug漏洞平台

傲游浏览器漏洞系列(上)- 任意文件写入,UXSS

原作者:Neil Bergman 译:Holic (知道创宇404安全实验室) 译者测试环境:Maxthon 4.5.6,Android 5.1.1 / And...

2674
来自专栏FreeBuf

网站管理软件 – AspxSpy2014 Final

受bin牛委托修改并发布,版权归bin牛所有。 Bug/建议提交:zcgonvh@rootkit.net.cn 祝各位马年大吉,财源滚滚。 免责声明: 本程序只...

2019
来自专栏破晓之歌

webpack通用功能开发 原

在util/mm.js下,包含ajax的数据请求,服务器地址的获取,url正则提取后的路径,html模板渲染的,成功、错误提示,字段的正则验证,对登录的统一处理...

752
来自专栏安恒网络空间安全讲武堂

赛前福利①最新2018HITB国际赛writeup

FIRST 距离“西湖论剑杯”全国大学生网络空间安全技能大赛只有10天啦! 要拿大奖、赢offer,那必须得来点赛前练习定定心啊~这不,讲武堂就拿到了2018H...

4185
来自专栏Android先生

Gradle多渠道打包(动态设定App名称,应用图标,替换常量,更改包名,变更渠道)

最近有个需求一次要打包9个类型的App,而且常量和String.xml都有变量。虽然之前也是一直存在变量,但是每次也仅仅只打包一个。这让我每次改变量,打包9个。...

1056
来自专栏java一日一条

加快Android编译速度的技巧总结

对于Android开发者而言,随着工程不断的壮大,Android项目的编译时间也逐渐变长,即便是有时候添加一行代码也需要等待好久才能看见期待的效果。之前加快An...

563
来自专栏安恒网络空间安全讲武堂

Sniper-OJ 练习平台多题WriteUp

题目 ### 图书管理系统(200) ### as fast as you can(50) ### md5-vs-injection(50) ### 2048...

4747
来自专栏李成熙heyli

webpack使用优化(基本篇)

前言 本文不是webpack入门文章,如果对webpack还不了解,请前往题叶的Webpack入门,或者阮老师的Webpack-Demos。 为什么要使用Web...

22910

扫码关注云+社区