今年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這兩個資料夾
/build
build.js #構建生產程式碼
dev-client.js
dev-server.js #執行本地伺服器
utils.js #額外的通用方法
webpack.base.conf.js #預設的webpack配置
webpack.dev.conf.js #本地開發的webpack配置
webpack.prod.conf.js #構建生產的webpack配置
/config 配置檔案
dev.env.js
index.js
pord.env.js
test.env.js
/src
assets #放資源
components #元件
/module #頁面模組
/home #子頁面
index.html #模版頁面
index.js #js入口
// 注意,這裡的html和js的檔名要一致,如上面就是index
/dist #最後打包生成的資源
/js
/css
/home
修改預設的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,配置結束,一個基本的多頁面應用已經成功建成
接下來就進入正題了,放在下一篇來寫。。。。。。。