Vue项目发版通知

654 阅读1分钟

需求

项目迭代快,频繁部署。当前端项目部署后,需要频繁通知用户(使用人员)去刷新界面,否则用户在不刷新的情况下一直使用旧版本内容。 为了免去通知这一过程以及因为未更新引起的不可预知的错误,故在部署后在界面上弹消息提示告知用户去刷新界面。

项目框架

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>