难点: 对于同一个位置百度地图定位的经纬度和腾讯地图定位的经纬度不一样?
解决:由于两者所用的算法不同,计算出来的经纬度也是不一样的,将百度地图的经纬度转换成腾讯地图的经纬度/腾讯的经纬度转化百度的经纬度
export function bMapTransQQMap(lng,lat){
let x_pi = 3.14159265358979324 * 3000.0 / 180.0;
let x = lng - 0.0065;
let y = lat - 0.006;
let z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi);
let theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi);
let lngs = z * Math.cos(theta);
let lats = z * Math.sin(theta);
return {
longitude: lngs,
latitude: lats
}
}
// 腾讯转百度
export function qqMapTransBMap(lng,lat){
let x_pi = 3.14159265358979324 * 3000.0 / 180.0;
let x = lng;
let y = lat;
let z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * x_pi);
let theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * x_pi);
let lngs = z * Math.cos(theta) + 0.0065;
let lats = z * Math.sin(theta) + 0.006;
return {
longitude: lngs,
latitude: lats
}
}
难点 : 移动端使用echarts绘制图表使用tooltip无效果
解决:
echarts 引入的时候,会优先判断当前的环境。
uniapp的又一个全局变量就叫做wx。
导致这里的判断直接走第一个。
在main.js中
window.wx = {} 直接将wx重新赋值。
难点:echarts中的面积图,由于点击区域、折线、折线中的小圆点都会出现提示框,只有点击折线上的点才出现tooltip(提示框),
解决:通过监听echart的鼠标的mouseover事件,函数中设置某个全局变量为当前折线的seriesName(名字),然后再监听鼠标的mouseout事件,函数中设置某个全局变量为空字符串,这两个事件只有直线上的小圆点才执行的,所以可以区分区域点击事件和折线点击事件,之后在提示框的自定义内容中(formatter),如果存在某个全局变量就显示提示框,否则不显示。
难点:在线上,使用window.open方法无法实现下载功能
解决:
webpack构建优化
-
初始打包速度
97s
-
初始编译速度
165s
-
第二次编译速度
76s
开始优化 ✈︎
1、externals 提取项目依赖
从上面的打包分析页面中可以看到,
chunk-vendors.js
体积为2.21M
,其中最大的几个文件都是一些公共依赖包,那么只要把这些依赖提取出来,就可以解决 chunk-vendors.js 过大的问题可以使用
externals
来提取这些依赖包,告诉 webpack 这些依赖是外部环境提供的,在打包时可以忽略它们,就不会再打到 chunk-vendors.js 中1)vue.config.js 中配置:
module.exports = { configureWebpack: { externals: { vue: 'Vue', 'vue-router': 'VueRouter', axios: 'axios', echarts: 'echarts' } } 复制代码
configureWebpack: config => { if (process.env.NODE_ENV === 'production') { //生产环境取消 console.log config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true // 不打包 tinymce config.externals = { tinymce: 'tinymce', 'tinymce-vue': 'tinymceVue', 'vue-baidu-map': 'vueBaiduMap', vue: 'Vue', 'vue-router': 'VueRouter', axios: 'axios', echarts: 'echarts', "vxe-table": "vxeTable", "vxe-table-plugin-antd": "vxeTablePluginAntd", "xe-utils": "xeUtils", "moment-timezone": "momentTimezone", "antv":"antv", "@antv":"@antv", "ant-design": "antDesign", "@ant-design": "@antDesign" } } }
cdn可能会断开链接,同时存在不安全的隐患
2)在 index.html 中使用 CDN 引入依赖
<body> <script src="http://lib.baomitu.com/vue/2.6.14/vue.min.js"></script> <script src="http://lib.baomitu.com/vue-router/3.5.1/vue-router.min.js"></script> <script src="http://lib.baomitu.com/axios/1.2.1/axios.min.js"></script> <script src="http://lib.baomitu.com/echarts/5.3.2/echarts.min.js"></script> </body> 复制代码
验证 externals 的有效性:
重新打包,最新数据如下:92s
使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
缩小查找后缀文件的范围
noParse
configureWebpack: config => { //生产环境取消 console.log if (process.env.NODE_ENV === 'production') { config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true config.externals = { tinymce: 'tinymce', 'tinymce-vue': 'tinymceVue', 'vue-baidu-map': 'vueBaiduMap', vue: 'Vue', 'vue-router': 'VueRouter', axios: 'axios', echarts: 'echarts' } } // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤 // __diename 表示当前工作目录,也就是项目根目录 config.resolve.modules.unshift(resolve('node_modules')) // 缩小查找后缀文件的范围 config.resolve.extensions = ['.vue', '.js', '.json'] // 防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略的文件中 // 不应该含有 import, require, define 的调用,或任何其他导入机制。忽略大型的 library 可以提高构建性能。 config.module.noParse = /^(vue|vue-router|vuex|vuex-router-sync|jquery|lodash|chartjs|echarts|axios)$/ config.plugins.push(new ProgressBarPlugin({ format: ` :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)` })) },
减少第三方库的体积
按需加载第三方库,减少代码体积
echarts按需加载文件
@/utils/echarts.js
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。 import * as echarts from 'echarts/core'; import 'echarts/lib/component/legend' // 引入柱状图图表,图表后缀都为 Chart // 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component import { TitleComponent, TooltipComponent, GridComponent, DatasetComponent, TransformComponent } from 'echarts/components'; // 标签自动布局、全局过渡动画等特性 import {LabelLayout, UniversalTransition} from 'echarts/features'; // 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步 import {CanvasRenderer} from 'echarts/renderers'; // **引入组件 都是以Chart结尾 关键 我这里只用到了折线图, 如果要引入饼状图 PieChart import {LineChart, BarChart, PieChart, TreemapChart, FunnelChart} from 'echarts/charts'; // 注册必须的组件 echarts.use([ TitleComponent, TooltipComponent, GridComponent, DatasetComponent, TransformComponent, LineChart, TreemapChart, FunnelChart, BarChart, PieChart, LabelLayout, UniversalTransition, CanvasRenderer ]); export default echarts
使用:import echarts from ‘@/utils/Echarts’
moment
使用
moment-locales-webpack-plugin
插件,剔除掉无用的语言包1)安装
npm install moment-locales-webpack-plugin -D 复制代码
2)vue.config.js 中引入
const MomentLocalesPlugin = require('moment-locales-webpack-plugin'); configureWebpack: config => { config.plugins.push(new ProgressBarPlugin({ format: ` :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)` }), new MomentLocalesPlugin({ localesToKeep: ['zh-cn'] })) }, 复制代码
验证插件的有效性:
-
初始打包速度
82s
-
初始编译速度
144s
-
第二次编译速度
63s
-
webpack构建优化
构建前: 编译速度:2.69m
构建速度 :第一次构建速度: 59.93,
cache-loader: 默认脚手架使用了cache-loader进行缓存,第二次的构建速度: 39.84 ,构建速度提升了33%
为 loader 指定 include,减少 loader 应用范围,仅应用于最少数量的必要模块,。rule.exclude 可以排除模块范围,也可用于减少 loader 应用范围.
thread-loader多线程:
第一次编译:177 第二次编译: 75 热更新:15
在优化开始之前,需要做一些准备工作。
安装以下 webpack 插件,帮助我们分析优化效率:
1. 编译进度条
一般来说,中型项目的首次编译时间为 5-20s,没个进度条等得多着急,通过 progress-bar-webpack-plugin 插件查看编译进度,方便我们掌握编译情况。
安装:
npm i -D progress-bar-webpack-plugin
vue.config.js 配置如下
const chalk = require('chalk')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
configureWebpack: config => {
config.plugins.push(new ProgressBarPlugin({
format: ` :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`
}))
}
2. 编译速度分析
优化 webpack 构建速度,首先需要知道是哪些插件、哪些 loader 耗时长,方便我们针对性的优化。
通过 speed-measure-webpack-plugin 插件进行构建速度分析,可以看到各个 loader、plugin 的构建时长,后续可针对耗时 loader、plugin 进行优化。
安装:
npm i -D speed-measure-webpack-plugin
vue.config.js 配置如下
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin")
configureWebpack: config => {
config.plugins.push(new ProgressBarPlugin({
format: ` :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`
}), new SpeedMeasurePlugin())
}
3. 打包体积分析
build 构建打包命令加入 --report inspect命令生成一份vue-cli 内置的配置内容
"scripts": {
"build": "vue-cli-service build --report",
"inspect": "vue-cli-service inspect --> output.js"
},
4. 查看vue.config.js 的输出后的所有配置
"scripts": {
"inspect": "vue-cli-service inspect --> output.js"
},
第一次编译:219s ,第二次编译:72s
加入配置config.resolve.modules 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤 extensions缩小查找后缀的范围
configureWebpack: config => {
// 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
// __diename 表示当前工作目录,也就是项目根目录
config.resolve.modules.unshift(resolve('node_modules'))
config.resolve.extensions = ['.js', '.vue', '.json']
},
构建完成:第一次编译:173s 153s 第二次编译:65s
加入配置 **config.module.noParse = /^(vue|vue-router|vuex|vuex-router-sync|jquery|lodash|chartjs|echarts)$/ ** 防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略的文件中 不应该含有 import, require, define 的调用,或任何其他导入机制。忽略大型的 library 可以提高构建性能。
configureWebpack: config => {
// 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
// __diename 表示当前工作目录,也就是项目根目录
config.resolve.modules.unshift(resolve('node_modules'))
config.resolve.extensions = ['.js', '.vue', '.json']
// 防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略的文件中
// 不应该含有 import, require, define 的调用,或任何其他导入机制。忽略大型的 library 可以提高构建性能。
config.module.noParse = /^(vue|vue-router|vuex|vuex-router-sync|lodash)$/
},
构建完成:第一次编译:155s 第二次编译:64
加入thread-loader —— 开启多线程优化配置,对于编译时间过长的loader加入thread-loader,
注意:仅在耗时的操作中使用 thread-loader,否则使用 thread-loader 会后可能会导致项目构建时间变得更长,因为每个 worker 都是一个独立的 node.js 进程,其开销大约为 600ms 左右,同时还会限制跨进程的数据交换等。
configureWebpack: config => {
// 对babel-loader 使用thread-loader
config.module.rules[12].use.unshift({
loader: 'thread-loader',
options: {
workers: 3 // 进程3个
}
})
config.module.rules[15].use.unshift({
loader: 'thread-loader',
options: {
workers: 3 // 进程3个
}
})
}
chainWebpack: config => {
config.module
.rule('markdown')
.test(/\.md$/)
.use('loader1')
.loader('loader1') // 第一个 Loader
.end()
.use('loader2')
.loader('loader2') // 第二个 Loader
.options({ /* Loader 的选项 */ })
.end()
.use('loader3')
.loader('loader3') // 第三个 Loader
.end();
}
构建完成:第一次编译:151s 第二次编译:62s
IgnorePlugin
webpack
的内置插件,作用是忽略第三方包指定目录。
例如: moment
(2.24.0版本) 会将所有本地化内容和核心功能一起打包,我们就可以使用 IgnorePlugin
在打包时忽略本地化内容。
//webpack.config.js
module.exports = {
//...
plugins: [
//忽略 moment 下的 ./locale 目录
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
]
}
在使用的时候,如果我们需要指定语言,那么需要我们手动的去引入语言包,例如,引入中文语言包:
import moment from 'moment';
import 'moment/locale/zh-cn';// 手动引入
index.js
中只引入 moment
,打包出来的 bundle.js
大小为 263KB
,如果配置了 IgnorePlugin
,单独引入 moment/locale/zh-cn
,构建出来的包大小为 55KB
。
externals
我们可以将一些JS文件存储在 CDN
上(减少 Webpack
打包出来的 js
体积),在 index.html
中通过 <script>
标签引入,如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root">root</div>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
</body>
</html>
我们希望在使用时,仍然可以通过 import
的方式去引用(如 import $ from 'jquery'
),并且希望 webpack
不会对其进行打包,此时就可以配置 externals
。
//webpack.config.js
module.exports = {
//...
externals: {
//jquery通过script引入之后,全局中即有了 jQuery 变量
'jquery': 'jQuery'
}
}
构建完成: 226s 186s 代码体积减少50%,文件数量减少40%
性能优化
性能分析工具:chrome-Lighthouse
分数:25
Eliminate render-blocking resources: 消除渲染阻塞资源
优化前:1.7s
优化后:0s
优化css样式:异步加载css:1、js动态加载css 2、rel=“preload”
<link type="text/css" rel="preload" href="<%= BASE_URL %>tinymce/skins/lightgray/skin.min.css"/>
优化js:babel-polyfill插件是开发环境才需要,不需要在生产环境上使用,去除掉babel-polyfill的引入,如需引入可异步加载js
splitChunk: 对代码进行分割,合并公用代码块,对第三方库分别独立打包,减少下载次数,合并零散的文件,减少http请求
压缩图片的体积
开启http2请求
开启gzip压缩
分数:57
const path = require('path')
const CompressionPlugin = require('compression-webpack-plugin')
function resolve(dir) {
return path.join(__dirname, dir)
}
// vue.config.js
module.exports = {
/*
Vue-cli3:
Crashed when using Webpack `import()` #2463
https://github.com/vuejs/vue-cli/issues/2463
*/
// 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
productionSourceMap: false,
//qiankuan打包时放开
//outputDir: "../dist/main",
// 多入口配置
// pages: {
// index: {
// entry: 'src/main.js',
// template: 'public/index.html',
// filename: 'index.html',
// }
// },
//打包app时放开该配置
publicPath: '/health-archives-ui',
configureWebpack: config => {
//生产环境取消 console.log
// if (process.env.NODE_ENV === 'production') {
// config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
// }
config.optimization = {
splitChunks: {
chunks: 'all',
minSize: 0,
minChunks: 1,
cacheGroups: {
vendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
chunks: 'all'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
chunks: 'all'
},
elementUI: {
name: 'chunk-elementUI',
priority: 20, // initial the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
chunks: 'all'
},
echarts: {
name: 'chunk-echarts',
priority: 20,
test: /[\\/]node_modules[\\/]_?echarts(.*)/,
chunks: 'all'
}
// moment: {
// priority: 20,
// test: /[\\/]node_modules[\\/]_?moment(.*)/,
// reuseExistingChunk: true
// }
}
}
}
},
chainWebpack: config => {
config.resolve.alias
.set('@$', resolve('src'))
.set('@api', resolve('src/api'))
.set('@assets', resolve('src/assets'))
.set('@comp', resolve('src/components'))
.set('@views', resolve('src/views'))
//生产环境,开启js\css压缩
if (process.env.NODE_ENV === 'production') {
config.plugin('compressionPlugin').use(
new CompressionPlugin({
test: /\.(js|css|less)$/, // 匹配文件名
threshold: 10240, // 对超过10k的数据压缩
deleteOriginalAssets: false // 不删除源文件
})
)
}
// 配置 webpack 识别 markdown 为普通的文件
config.module
.rule('markdown')
.test(/\.md$/)
.use()
.loader('file-loader')
.end()
// 编译vxe-table包里的es6代码,解决IE11兼容问题
config.module
.rule('vxe')
.test(/\.js$/)
.include.add(resolve('node_modules/vxe-table'))
.add(resolve('node_modules/vxe-table-plugin-antd'))
.end()
.use()
.loader('babel-loader')
.end()
},
css: {
loaderOptions: {
less: {
modifyVars: {
/* less 变量覆盖,用于自定义 ant design 主题 */
'primary-color': '#1890FF',
'link-color': '#1890FF',
'border-radius-base': '4px'
},
javascriptEnabled: true
}
}
},
devServer: {
port: 3000,
// hot: true,
// disableHostCheck: true,
// overlay: {
// warnings: false,
// errors: true,
// },
// headers: {
// 'Access-Control-Allow-Origin': '*',
// },
proxy: {
/* '/api': {
target: 'https://mock.ihx.me/mock/5baf3052f7da7e07e04a5116/antd-pro', //mock API接口系统
ws: false,
changeOrigin: true,
pathRewrite: {
'/jeecg-boot': '' //默认所有请求都加了jeecg-boot前缀,需要去掉
}
},*/
'/jeecg-boot': {
target: 'http://localhost:8080', //请求本地 需要jeecg-boot后台项目
ws: false,
changeOrigin: true
}
}
},
lintOnSave: undefined
}
图片水印
图片如何添加水印步骤:1、选择图片,2、创建canvas对象,ctx.drawImage把图片放入到canvas中,3、设置水印的位置、大小、颜色,4、执行ctx.draw()方 法在回调函数中将执行canvas.canvasToTempFilePath()方法,canvas转换成图片,5、将生成的图片上传到服务器并回显图片
问题:选择多个图片后只上传了一张图片? 原因:遍历多张图片的时间快于canvas.draw回调方法执行时间,canvas.draw回调函数只执行了一次,导致只能上传一张图片
解决:通过使用promise方式解决,在循环中使用promise,promise回调函数执行canvas.draw方法,只有执行完第一次的promise回调函数改变了状态,才会执行第二次的promise,因此canvas.draw方法也会执行多次,就可以上传多个图片了。
for (let item of that.data.imgList) {
// 循环遍历promise
const data = await this.getCanvasImg(item)
this.uploadFile(data);
}
getCanvasImg(item) {
const that = this
return new Promise((resolve, reject) => {
console.log(245);
let ctx = that.data.ctx
console.log("获取图片详情", ctx)
//将图片src放到cancas内,宽高为图片大小
ctx.drawImage(item, 0, 0, 375, 375)
//将声明的时间放入canvas
ctx.setFontSize(18) //注意:设置文字大小必须放在填充文字之前,否则不生效
// 添加水印
// 日期时间
const watermar = that.data.watermarks
watermar[0].text = getTime()
for (let index = 0; index < watermar.length; index++) {
const element = watermar[index];
ctx.setFillStyle(element.color)
ctx.fillText(element.text, element.x, element.y)
ctx.strokeText(element.text, element.x, element.y)
}
ctx.draw(true, function () {
wx.canvasToTempFilePath({
canvasId: 'firstCanvas',
success: (res) => {
resolve([res.tempFilePath])
},
fail: (e) => {
console.log(e)
}
}, that)
})
})
},
uploadFile(arrImg) {
console.log('arrImg', arrImg);
wx.showLoading({
title: '正在上传',
})
var that = this;
var t = 0;
for (var i = 0; i < arrImg.length; i++) {
wx.uploadFile({
url: api.upload.img,
filePath: arrImg[i],
name: 'file',
dataType: "json",
success: function (res) {
console.log(272, res);
var redata = JSON.parse(res.data);
if (t < arrImg.length) {
if (redata.code == 200) {
setTimeout(function () {
that.setTopicResource(redata.data, 1);
}, 500)
} else {
// app.showError('请上传小于5M的图片')
}
wx.hideLoading();
}
t++;
},
})
}
},