您的位置:首页 > 产品设计 > UI/UE

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

2017-11-02 14:19 651 查看
今年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.新建项目,命令行执行代码:

vueinitwebpackvuetest


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

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

Projectdescription(AVue.jsproject)项目描述,也可直接点击回车,使用默认名字

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

接下来会让用户选择

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

Runtime-only:about6KBlightermin+gzip,buttemplates(oranyVue-specificHTML)areONLYallowedin.vuefiles-renderfunctionsarerequiredelsewhere仅运行时,已经有推荐了就选择第一个了

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

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

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

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

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

none(configureityourself)这个不用说,自己定义风格

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

SetupunittestswithKarma+Mocha?(Y/n)是否安装单元测试,我选择安装

Setupe2etestswithNightwatch(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

生成需要的入口文件


varpath=require('path')
varconfig=require('../config')
varutils=require('./utils')
varprojectRoot=path.resolve(__dirname,'../')
varglob=require('glob');
varentries=getEntry(['./src/demo1/index/*.js','./src/module/demo2/*.js']);//获得入口js文件

varenv=process.env.NODE_ENV
//checkenv&config/index.jstodecideweithertoenableCSSSourcemapsforthe
//variouspreprocessorloadersaddedtovue-loaderattheendofthisfile
varcssSourceMapDev=(env==='development'&&config.dev.cssSourceMap)
varcssSourceMapProd=(env==='production'&&config.build.productionSourceMap)
varuseCssSourceMap=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:['last2versions']
})
]
}
}

functiongetEntry(globPath){
varentries={},
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;
}
});
});
returnentries;
}

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

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


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

varpath=require('path');
varconfig=require('../config')
varwebpack=require('webpack')
varmerge=require('webpack-merge')
varutils=require('./utils')
varbaseWebpackConfig=require('./webpack.base.conf')
varHtmlWebpackPlugin=require('html-webpack-plugin')
varglob=require('glob')
//addhot-reloadrelatedcodetoentrychunks
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-mapisfasterfordevelopment
devtool:'#eval-source-map',
plugins:[
newwebpack.DefinePlugin({
'process.env':config.dev.env
}),
//https://github.com/glenjamin/webpack-hot-middleware#installation--usagenewwebpack.optimize.OccurenceOrderPlugin(),
newwebpack.HotModuleReplacementPlugin(),
newwebpack.NoErrorsPlugin()
]
})

functiongetEntry(globPath){
varentries={},
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;
}
});
});
returnentries;
}

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

for(varpathnameinpages){
//配置生成的html文件,定义路径等
varconf={
filename:pathname+'.html',
template:pages[pathname],//模板路径
inject:true,//js插入位置
//necessarytoconsistentlyworkwithmultiplechunksviaCommonsChunkPlugin
chunksSortMode:'dependency'

};

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

module.exports.plugins.push(newHtmlWebpackPlugin(conf));
}

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

varpath=require('path')
varconfig=require('../config')
varutils=require('./utils')
varwebpack=require('webpack')
varmerge=require('webpack-merge')
varbaseWebpackConfig=require('./webpack.base.conf')
varExtractTextPlugin=require('extract-text-webpack-plugin')
varHtmlWebpackPlugin=require('html-webpack-plugin')
varCleanPlugin=require('clean-webpack-plugin')//webpack插件,用于清除目录文件
varglob=require('glob');
varenv=config.build.env

varwebpackConfig=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.htmlnewwebpack.DefinePlugin({
'process.env':env
}),
newwebpack.optimize.UglifyJsPlugin({
compress:{
warnings:false
}
}),
newCleanPlugin(['../dist']),//清空生成目录
newwebpack.optimize.OccurenceOrderPlugin(),
//extractcssintoitsownfile
newExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
//generatedistindex.htmlwithcorrectassethashforcaching.
//youcancustomizeoutputbyediting/index.html
//seehttps://github.com/ampedandwired/html-webpack-plugin//splitvendorjsintoitsownfile
newwebpack.optimize.CommonsChunkPlugin({
name:'vendor',
minChunks:function(module,count){
//anyrequiredmodulesinsidenode_modulesareextractedtovendor
return(
module.resource&&
/\.js$/.test(module.resource)&&
module.resource.indexOf(
path.join(__dirname,'../node_modules')
)===0
)
}
}),
//extractwebpackruntimeandmodulemanifesttoitsownfileinorderto
//preventvendorhashfrombeingupdatedwheneverappbundleisupdated
newwebpack.optimize.CommonsChunkPlugin({
name:'manifest',
chunks:['vendor']
})
]
})

if(config.build.productionGzip){
varCompressionWebpackPlugin=require('compression-webpack-plugin')

webpackConfig.plugins.push(
newCompressionWebpackPlugin({
asset:'[path].gz[query]',
algorithm:'gzip',
test:newRegExp(
'\\.('+
config.build.productionGzipExtensions.join('|')+
')$'
),
threshold:10240,
minRatio:0.8
})
)
}

module.exports=webpackConfig

functiongetEntry(globPath){
varentries={},
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;
}
});
});
returnentries;
}

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

for(varpathnameinpages){
//配置生成的html文件,定义路径等
varconf={
filename:pathname+'.html',
template:pages[pathname],//模板路径
inject:true,//js插入位置
//necessarytoconsistentlyworkwithmultiplechunksviaCommonsChunkPlugin
chunksSortMode:'dependency'
};

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

module.exports.plugins.push(newHtmlWebpackPlugin(conf));
}


varpath=require('path')
varconfig=require('../config')
varutils=require('./utils')
varwebpack=require('webpack')
varmerge=require('webpack-merge')
varbaseWebpackConfig=require('./webpack.base.conf')
varExtractTextPlugin=require('extract-text-webpack-plugin')
varHtmlWebpackPlugin=require('html-webpack-plugin')
varCleanPlugin=require('clean-webpack-plugin')//webpack插件,用于清除目录文件
varglob=require('glob');
varenv=config.build.env

varwebpackConfig=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.htmlnewwebpack.DefinePlugin({
'process.env':env
}),
newwebpack.optimize.UglifyJsPlugin({
compress:{
warnings:false
}
}),
newCleanPlugin(['../dist']),//清空生成目录
newwebpack.optimize.OccurenceOrderPlugin(),
//extractcssintoitsownfile
newExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
//generatedistindex.htmlwithcorrectassethashforcaching.
//youcancustomizeoutputbyediting/index.html
//seehttps://github.com/ampedandwired/html-webpack-plugin//splitvendorjsintoitsownfile
newwebpack.optimize.CommonsChunkPlugin({
name:'vendor',
minChunks:function(module,count){
//anyrequiredmodulesinsidenode_modulesareextractedtovendor
return(
module.resource&&
/\.js$/.test(module.resource)&&
module.resource.indexOf(
path.join(__dirname,'../node_modules')
)===0
)
}
}),
//extractwebpackruntimeandmodulemanifesttoitsownfileinorderto
//preventvendorhashfrombeingupdatedwheneverappbundleisupdated
newwebpack.optimize.CommonsChunkPlugin({
name:'manifest',
chunks:['vendor']
})
]
})

if(config.build.productionGzip){
varCompressionWebpackPlugin=require('compression-webpack-plugin')

webpackConfig.plugins.push(
newCompressionWebpackPlugin({
asset:'[path].gz[query]',
algorithm:'gzip',
test:newRegExp(
'\\.('+
config.build.productionGzipExtensions.join('|')+
')$'
),
threshold:10240,
minRatio:0.8
})
)
}

module.exports=webpackConfig

functiongetEntry(globPath){
varentries={},
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;
}
});
});
returnentries;
}

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

for(varpathnameinpages){
//配置生成的html文件,定义路径等
varconf={
filename:pathname+'.html',
template:pages[pathname],//模板路径
inject:true,//js插入位置
//necessarytoconsistentlyworkwithmultiplechunksviaCommonsChunkPlugin
chunksSortMode:'dependency'
};

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

module.exports.plugins.push(newHtmlWebpackPlugin(conf));
}


修改配置文件config

修改index.js


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


//seehttp://vuejs-templates.github.io/webpackfordocumentation.
varpath=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,
//Gzipoffbydefaultasmanypopularstatichostssuchas
//SurgeorNetlifyalreadygzipallstaticassetsforyou.
//Beforesettingto`true`,makesureto:
//npminstall--save-devcompression-webpack-plugin
productionGzip:false,
productionGzipExtensions:['js','css']
},
dev:{
env:require('./dev.env'),
port:8080,
assetsSubDirectory:'static',
assetsPublicPath:'/',
proxyTable:{},
//CSSSourcemapsoffbydefaultbecauserelativepathsare"buggy"
//withthisoption,accordingtotheCSS-LoaderREADME
//(https://github.com/webpack/css-loader#sourcemaps)
//Inourexperience,theygenerallyworkasexpected,
//justbeawareofthisissuewhenenablingthisoption.
cssSourceMap:false
}
}


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

接下来就进入正题了,放在下一篇来写。。。。。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐