需求
项目迭代快,频繁部署。当前端项目部署后,需要频繁通知用户(使用人员)去刷新界面,否则用户在不刷新的情况下一直使用旧版本内容。 为了免去通知这一过程以及因为未更新引起的不可预知的错误,故在部署后在界面上弹消息提示告知用户去刷新界面。
项目框架
vue-cli搭建的 vue2 + webpack 项目
原理
在每次部署时,将当前时间戳作为最新版本号插入到public/version.js,用户在使用界面时,使用轮询的方式来检测当前界面的版本号(从index.html中的meta取,详情看下方解决方案),从而判断本地版本号和远程最新版本号是否相同,如若不相同,则消息通知用户去刷新界面。
效果图
解决方案
1.根目录创建version.js
// 用于生成版本号
const fs = require('fs')
// 以当前时间戳作为最新版本号
const timeStamp = new Date().getTime()
fs.writeFile('public/version.json',
`{ "version" : ${timeStamp}} `,
function(err) {
if (err) {
return console.log(err)
}
}
)
2.修改配置文件
// vue.config.js
const version = process.env.NODE_ENV === 'production'
? require('./public/version.json') : { version: 'dev' }
module.exports = {
pages: {
index: {
entry: 'src/main.js',
template: 'public/index.html',
filename: 'index.html',
// 用于index.html中的meta version
version: version.version,
// 在这个页面中包含的块,默认情况下会包含
// 提取出来的通用 chunk 和 vendor chunk。
chunks: ['chunk-vendors', 'chunk-common', 'index']
}
},
...
}
3.更新public/index.html
<!DOCTYPE html>
<html lang="">
<head>
...
<!-- 存储本地版本号 -->
<meta name="version" content="<%= htmlWebpackPlugin.options.version%>">
...
</head>
</html>
4.更新package.json命令
打包项目前生成最新版本号
{
...
"scripts": {
"build": "node version.js && vue-cli-service build",
...
},
...
}
5.更新.gitignore文件
防止每次打包部署时都要提交version.json,如有此需要,可忽略此步骤。
...
version.json
...
5.添加校验版本号方法,将其挂载在Vue原型链
此处方法用来校验版本号是否相同(根据自身项目调整),如若版本有更新,此处使用了Element的Notification来通知用户,该方式可自行调整。
import axios from 'axios'
import { Notification } from 'element-ui'
/** 轮询版本是否更新 */
export function checkVersion(module) {
const timeout = 3 * 60 * 1000
const checkFn = async() => {
try {
if (process.env.NODE_ENV === 'development') { return }
// 获取本地版本号
const metaList = [...document.getElementsByTagName('meta')]
const clientVersion = metaList.find(d => d.getAttribute('name') === 'version').content
// 获取服务器端最新版本号(此处url应当根据自身项目调整)
const res = await axios.get(`http://${window.location.hostname}/app${module}/version.json`)
const latestVersion = res.data.version + ''
// 版本信息打印
console.info('%c Environment ' + '%c ' + process.env.NODE_ENV + ' ',
'padding: 1px; border-radius: 3px 0 0 3px;color: #fff; background:#606060',
'padding: 1px; border-radius: 0 3px 3px 0;color: #fff; background:#42c02e')
console.info('%c Build Date ' + '%c ' +
dateFormat(new Date(clientVersion), 'yyyy-MM-dd hh:mm:ss') + ' ',
'padding: 1px; border-radius: 3px 0 0 3px;color: #fff; background:#606060',
'padding: 1px; border-radius: 0 3px 3px 0;color: #fff; background:#1475b2')
console.info('%c Lasest Build Date ' + '%c ' +
dateFormat(new Date(latestVersion), 'yyyy-MM-dd hh:mm:ss') + ' ',
'padding: 1px; border-radius: 3px 0 0 3px;color: #fff; background:#606060',
'padding: 1px; border-radius: 0 3px 3px 0;color: #fff; background:#1475b2')
if (latestVersion !== clientVersion) {
Notification.closeAll()
Notification.warning({
title: '发现新版本',
duration: 0,
showClose: false,
dangerouslyUseHTMLString: true,
message: `<div style="display: flex;flex-direction: column;">
<strong style="margin-bottom: 10px">检测到最新版本,刷新后立即使用</strong>
<button onclick="window.location.reload()">刷新</button>
</div>`
})
}
} catch (error) {
console.log('检查版本更新异常:', error)
}
}
checkFn()
setInterval(async() => { checkFn() }, timeout)
}
/** 时间格式化 */
export function dateFormat(date, fmt) {
if (date) {
const o = {
'M+': date.getMonth() + 1, // 月份
'd+': date.getDate(), // 日
'h+': date.getHours(), // 小时
'm+': date.getMinutes(), // 分
's+': date.getSeconds(), // 秒
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
'S': date.getMilliseconds() // 毫秒
}
if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) }
for (const k in o) { if (new RegExp('(' + k + ')').test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))) }
return fmt
}
return ''
}
6.App.vue中执行校验方法
<script>
export default {
name: 'App',
created() {
// 版本校验(此处的参数应根据校验方法自行判断)
this.$checkVersion(process.env.VUE_APP_BASIC)
},
...
}
</script>