vue3 - vue项目自动检测更新

vue3 GitHub Demo 地址

vue3在线预览

vue2 GitHub Demo 地址
vue2 在线预览

web项目当页面检测到需要更新,然后弹框提示是否更新(刷新页面)这种可以通过纯前端实现也可以通过接口实现

  • 接口实现:通过调用接口轮询和本地的版本号比较,检查是否需要弹框提示更新
  • 纯前端实现:项目打包时配置一个版本号然后和本地的版本号轮询比较,检查是否需要弹框提示更新

实现方案

当用户在项目所在的页面时轮询请求,然后在离开页面时停止轮询,再次切回来再次启动轮询,
如果超过轮询的最大次数后,停止轮询。
当请求的版本号大于本地的,或者本地版本号为空,则弹框提示需要更新

效果图

请添加图片描述

一、纯前端实现

打包配置版本号

const versionNumber = '1.1.0'const { dependencies, devDependencies, name, version } = pkg
export const __APP_INFO__ = {pkg: { dependencies, devDependencies, name, version },lastBuildTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),checkUpdateVersion: versionNumber
}

vite.config.ts 启用配置

defineConfig {...define: {__APP_INFO__: JSON.stringify(__APP_INFO__)}...
}

版本号本地存储和比较

import { setStorage, getStorage } from '@/utils/storage'
import { getCheckUpgrade } from '@/api/auth'
const kCheckUpdateVersion = 'checkUpdateVersion'
const { checkUpdateVersion } = __APP_INFO__// 方式一: 通过build\info.ts 配置的版本号和本地进行比较
export const checkUpgrade = () => {const version = getVersion()let needUpgrade = falseif ((version && version !== checkUpdateVersion) || !version) {needUpgrade = true}return needUpgrade
}export const getVersion = () => {return getStorage(kCheckUpdateVersion)
}export const setVersion = (newVersion = checkUpdateVersion) => {setStorage(kCheckUpdateVersion, newVersion)
}

轮询工具类

let activeCount: number = 0
const MAX_INACTIVE_COUNT: number = 5
const MAX_INACTIVE_COUNT_HIDDEN: number = 1
let pollingInterval: NodeJS.Timeout | null = null
const normalInterval = 1000 * 60 * 30
const isLimitTimer = trueexport function startPolling(callback: () => void, interval: number = normalInterval): void {// 首次执行callback()pollingInterval = setInterval(() => {if (document.visibilityState === 'visible') {if (isLimitTimer) {activeCount++if (activeCount >= MAX_INACTIVE_COUNT) {stopPolling()}} else {activeCount = 0}callback() // 执行轮询任务的回调函数} else {activeCount++if (activeCount >= MAX_INACTIVE_COUNT_HIDDEN) {stopPolling()}}}, interval)
}export function stopPolling(): void {if (pollingInterval) {clearInterval(pollingInterval)pollingInterval = null}
}export function visibilityChange(callback: () => void, interval: number = normalInterval): void {document.addEventListener('visibilitychange', () => {if (document.visibilityState === 'visible') {activeCount = 0stopPolling()// 页面可见时重新开始轮询startPolling(callback, interval)} else {activeCount++if (activeCount >= MAX_INACTIVE_COUNT_HIDDEN) {stopPolling()}}})
}

app.vue 轮询判断是否需要更新

onMounted(() => {startPolling(() => {state.needUpgrade = checkIsUpgrade()console.log('needUpgrade111', JSON.stringify(state.needUpgrade))})visibilityChange(() => {state.needUpgrade = checkIsUpgrade()console.log('needUpgrade222', JSON.stringify(state.needUpgrade))})
})
const checkIsUpgrade = () => {let needUpgrade = falseif (route.path !== '/login') {needUpgrade = checkUpgrade()}return needUpgrade
}

二、接口实现

接口格式

{code: 20000,msg: 'success',data: {isForceUpgrade: false,version: '2.1.0',descList: ['1. 新增xxx功能', '2. 优化xxx功能', '3. 修复xxx功能']}
}

请求版本信息和本地比较


/*** @description: 比较版本号* @param {string} version1* @param {string} version2* @return {*} version1>version2 返回 1;如果小于返回 -1;如果相等 返回 0*/
export function compareVersions(version1: string, version2: string): number {const parts1: number[] = version1.split('.').map((part) => parseInt(part))const parts2: number[] = version2.split('.').map((part) => parseInt(part))const maxLength: number = Math.max(parts1.length, parts2.length)for (let i = 0; i < maxLength; i++) {const num1: number = parts1[i] || 0const num2: number = parts2[i] || 0if (num1 > num2) {return 1} else if (num1 < num2) {return -1}// 如果相等,则继续比较下一个部分}return 0 // 如果版本号完全相等
}export function requestCheckUpgrade() {return new Promise((resolve, reject) => {getCheckUpgrade().then((res) => {if (res.code === 20000) {var data = JSON.parse(JSON.stringify(res.data))let needUpgrade = falseconst version = getVersion()// console.log('version', JSON.stringify(version))// console.log('data.version', JSON.stringify(data.version))// console.log('checkUpdateVersion', JSON.stringify(checkUpdateVersion))// console.log('needUpgrade', compareVersions(data.version, version ? version : checkUpdateVersion))if (compareVersions(data.version, version ? version : checkUpdateVersion) === 1) {needUpgrade = true}var dict = { needUpgrade: needUpgrade, upgradeData: data }resolve(dict)} else {reject(res.msg)ElMessage.warning(res.msg)}}).catch((err) => {reject(err)})})
}

app.vue 轮询调用获取版本信息的请求和弹框显示

<UpgradeDialog v-model:is-show="needUpgrade" :upgrade-data="upgradeData" />import { useRoute } from 'vue-router'
const route = useRoute()
import UpgradeDialog from '@/components/upgrade-dialog/index.vue'
import { checkUpgrade, startPolling, visibilityChange, requestCheckUpgrade } from '@/components/upgrade-dialog/upgradeUtils'const state = reactive({needUpgrade: false,upgradeData: {}
})
const { needUpgrade, upgradeData } = toRefs(state)onMounted(() => {startPolling(() => {getCheckUpgrade('startPolling')})visibilityChange(() => {getCheckUpgrade('visibilityChange')})
})
const getCheckUpgrade = (type: 'startPolling' | 'visibilityChange') => {// if (route.path === '/login') {//   return// }requestCheckUpgrade().then((data: any) => {state.needUpgrade = data.needUpgradestate.upgradeData = data.upgradeDataif (type === 'startPolling') {console.log('needUpgrade111', JSON.stringify(data))} else {console.log('needUpgrade222', JSON.stringify(data))}}).catch((err) => {console.log('err', JSON.stringify(err))})
}

注意:点击弹框确认按钮刷新页面并把最新的版本号保存到本地

这是弹框组件内的点击按钮事件

  if (props.upgradeData && props.upgradeData.version) {setVersion(props.upgradeData.version)} else {setVersion()}window.location.reload()

弹框组件代码

<template><div class="upgrade-dialog-bg"><el-dialogv-model="isShowDialog":width="dialogConfig.width":append-to-body="dialogConfig.appendToBody"class="upgrade-dialog"destroy-on-close:show-close="false":close-on-click-modal="false":close-on-press-escape="false"><template #header><div class="upgrade-title"><div class="upgrade-title-warp"><span class="upgrade-title-warp-txt">{{ dialogConfig.title }}</span><span class="upgrade-title-warp-version">v{{ upgradeInfo.version }}</span></div></div></template><div class="upgrade-content"><div v-for="(item, index) in upgradeInfo.descList" :key="index"><div style="margin-bottom: 10px">{{ item }}</div><!-- <div :style="{ marginBottom: index === upgradeInfo.descList.length - 1 ? '0' : '10px' }">{{ item }}</div> --></div><div class="b-tag-warning">{{ dialogConfig.note }}</div></div><div class="upgrade-btn"><el-button v-if="!upgradeInfo.isForceUpgrade" round size="default" type="info" text @click="onCancel">{{ dialogConfig.cancelText }}</el-button><el-button type="primary" round size="default" @click="onUpgrade" :loading="state.dialogSubmitBtnLoading">{{ dialogConfig.confirmText }}</el-button></div></el-dialog></div>
</template><!-- 当用户的视角离开页面的时候停止发送请求,用户回到页面视角继续请求 活跃状态把轮询次数清0,非活跃状态轮询次数+1,超过x次停止轮询
-->
<script setup lang="ts">
import { getVersion, setVersion } from './upgradeUtils'const emit = defineEmits(['update:isShow'])const props = defineProps({isShow: { type: Boolean, default: false },upgradeData: { type: Object, default: () => ({}) }
})const { checkUpdateVersion } = __APP_INFO__const state = reactive({isShowDialog: false,// @ts-ignoreversion: checkUpdateVersion,dialogSubmitBtnLoading: false,dialogConfig: {appendToBody: true,width: '50%',// title: 'New version',// cancelText: 'Cancel',// confirmText: 'Update',// note: 'Note: The system has been updated. Click the confirm button to refresh the page (please make sure the current page data has been saved before refreshing).'title: '发现新版本',cancelText: '残忍拒绝',confirmText: '马上更新',note: '注意:系统已更新,点击确定按钮刷新页面(请在刷新前确认当前页面数据已保存)。'},upgradeInfo: {isForceUpgrade: false,version: '1.2.0',descList: ['1. 新增xxx功能', '2. 优化xxx功能', '3. 修复xxx功能']// descList: ['1. add xxx func', '2. optimize xxx func', '3. fix xxx bug']// descList: [//   '1. 新增xxx功能新增xxx功能新增xxx功能新增xxx功能新增xxx功能新增xxx功能新增xxx功能新增xxx功能新增xxx功能新增xxx功能新增xxx功能新增xxx功能新增xxx功能',//   '2. 优化xxx功能优化xxx功能优化xxx功能优化xxx功能优化xxx功能优化xxx功能优化xxx功能优化xxx功能',//   '3. 修复xxx功能',//   '1. 新增xxx功能新增xxx功能新增xxx功能新增xxx功能新增xxx功能新增xxx功能新增xxx功能新增xxx功能新增xxx功能新增xxx功能',//   '2. 优化xxx功能',//   '3. 修复xxx功能',//   '1. 新增xxx功能',//   '2. 优化xxx功能',//   '3. 修复xxx功能',//   '3. 修复xxx功能',//   '1. 新增xxx功能'// ]}
})const { isShowDialog, dialogConfig, upgradeInfo } = toRefs(state)watch(() => props.isShow,(val) => {state.isShowDialog = val // isShow改变是同步子组件isShowDialog的值}
)
watch(() => state.isShowDialog,(val) => {emit('update:isShow', val) // isShowDialog改变时同步父组件isShow的值}
)
watch(() => props.upgradeData,(val) => {state.upgradeInfo = JSON.parse(JSON.stringify(val))}
)onMounted(() => {// delayShow()
})// 延迟显示,防止刷新时界面显示太快
const delayShow = () => {setTimeout(() => {state.isShowDialog = true}, 2000)
}const onCancel = () => {state.isShowDialog = false
}
const onUpgrade = () => {state.dialogSubmitBtnLoading = truesetTimeout(() => {if (props.upgradeData && props.upgradeData.version) {setVersion(props.upgradeData.version)} else {setVersion()}window.location.reload()// window.location.replace(window.location.href); //没有刷新缓存// location.reload(true); // 刷新了缓存}, 2000)
}
</script><style lang="scss">
.upgrade-dialog {.el-dialog__header {padding: 0px;margin-right: 0px;overflow: hidden;}.el-dialog__body {padding: 0 !important;max-height: calc(90vh - 111px) !important;overflow-y: auto;overflow-x: hidden;}@media screen and (min-width: 750px), (min-device-width: 750px) {width: max(30%, 300px);}@media screen and (max-width: 750px), (max-device-width: 750px) {width: max(60%, 300px);}
}
</style><style scoped lang="scss">
.upgrade-dialog {.upgrade-title {text-align: center;height: 130px;display: flex;align-items: center;justify-content: center;position: relative;&::after {content: '';position: absolute;// background-color: #409eff;background-color: var(--el-color-primary);width: 130%;height: 130px;border-bottom-left-radius: 100%;border-bottom-right-radius: 100%;}.upgrade-title-warp {z-index: 1;position: relative;.upgrade-title-warp-txt {color: white;font-size: 22px;letter-spacing: 3px;}.upgrade-title-warp-version {color: white;// background-color: #79bbff;background-color: var(--el-color-primary-light-3);font-size: 12px;position: absolute;display: flex;top: -2px;right: -50px;padding: 2px 4px;border-radius: 2px;}}}.upgrade-content {padding: 20px;line-height: 22px;.upgrade-content-desc {color: #c8c9cc;color: var(--el-color-info-light-5);font-size: 12px;}}.upgrade-btn {// border-top: 1px solid #ebeef5;border-top: 1px solid var(--el-border-color-lighter, #ebeef5);display: flex;justify-content: space-around;padding: 15px 20px;.el-button {width: 100%;}}
}.b-tag-warning {display: inline-block;margin: 0.2rem;padding: 5px 10px;font-size: 12px;color: #e6a23c;background: #fdf6ec;border: 1px solid #faecd8;border-radius: 4px;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/377276.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

护网HW面试——redis利用方式即复现

参考&#xff1a;https://xz.aliyun.com/t/13071 面试中经常会问到ssrf的打法&#xff0c;讲到ssrf那么就会讲到配合打内网的redis&#xff0c;本篇就介绍redis的打法。 未授权 原理&#xff1a; Redis默认情况下&#xff0c;会绑定在0.0.0.0:6379&#xff0c;如果没有采用相关…

FPGA设计之跨时钟域(CDC)设计篇(1)----亚稳态到底是什么?

1、什么是亚稳态? 在数字电路中,如果数据传输时不满足触发器FF的建立时间要求Tsu和保持时间要求Th,就可能产生亚稳态(Metastability),此时触发器的输出端(Q端)在有效时钟沿之后比较长的一段时间都会处于不确定的状态(在0和1之间振荡),而不是等于数据输入端(D端)的…

强制升级最新系统,微软全面淘汰Win10和部分11用户

说出来可能不信&#xff0c;距离 Windows 11 正式发布已过去整整三年时间&#xff0c;按理说现在怎么也得人均 Win 11 水平了吧&#xff1f; 然而事实却是&#xff0c;三年时间过去 Win 11 占有率仅仅突破到 29%&#xff0c;也就跳起来摸 Win 10 屁股的程度。 2024 年 6 月 Wi…

功率继电器【HF46F】

目的&#xff1a;通过单片机控制继电器动作。 原理图如下&#xff0c;原理图中使用的继电器为HF46F5H&#xff0c; 上述原理图的电路原理&#xff1a; 在这个电路图中&#xff0c;电源开关相关的部分包括一个电源开关、一个三极管Q1、一个二极管D2和一个继电器K1。当电源开关…

阿里云ECS服务器安装jdk并运行jar包,访问成功详解

安装 OpenJDK 8 使用 yum 包管理器安装 OpenJDK 8 sudo yum install -y java-1.8.0-openjdk-devel 验证安装 安装完成后&#xff0c;验证 JDK 是否安装成功&#xff1a; java -version设置 JAVA_HOME 环境变量&#xff1a; 为了确保系统中的其他应用程序可以找到 JDK&…

开源必看!50 多个本地运行 LLM 的开源选项

在我之前的文章中&#xff0c;我讨论了使用本地托管的开源权重 LLM 的好处&#xff0c;例如数据隐私和成本节约。通过主要使用免费模型并偶尔切换到 GPT-4&#xff0c;我的月度开支从 20 美元降至 0.50 美元。设置端口转发到本地 LLM 服务器是移动访问的免费解决方案。 有许多…

WSL2 的安装与运行 Linux 系统

前言 适用于 Linux 的 Windows 子系统 (WSL) 是 Windows 的一项功能&#xff0c;允许开发人员在 Windows 系统上直接安装并使用 Linux 发行版。不用进行任何修改&#xff0c;也无需承担传统虚拟机或双启动设置的开销。 可以将 WSL 看作也是一个虚拟机&#xff0c;但是它更为便…

let/const/var的区别及理解

在JavaScript中&#xff0c;let、const 和 var 是用来声明变量的关键字&#xff0c;但它们之间在作用域、变量提升、重复声明等方面存在区别&#xff0c;详细情况如下: 1. let、const、var 的区别 (1) 块级作用域 let 和 const&#xff1a;具有块级作用域&#xff0c;由 {} 包…

记录些Redis题集(2)

Redis 的多路IO复用 多路I/O复用是一种同时监听多个文件描述符&#xff08;如Socket&#xff09;的状态变化&#xff0c;并能在某个文件描述符就绪时执行相应操作的技术。在Redis中&#xff0c;多路I/O复用技术主要用于处理客户端的连接请求和读写操作&#xff0c;以实现高并发…

Redis 中String类型操作命令(命令演示,时间复杂度,返回值,注意事项)

String 类型 文章目录 String 类型set 命令get 命令mset 命令mget 命令get 和 mget 的区别incr 命令incrby 命令decr 命令decrby 命令incrbyfloat 命令append 命令getrange 命令setrange 命令 字符串类型是 Redis 中最基础的数据类型&#xff0c;在讲解命令之前&#xff0c;我们…

STM32基础篇:EXTI × 事件 × EXTI标准库

EXTI EXTI简介 EXTI&#xff1a;译作外部中断/事件控制器&#xff0c;STM32的众多片上外设之一&#xff0c;能够检测外部输入信号的边沿变化并由此产生中断。 例如&#xff0c;在检测按键时&#xff0c;按键按下时会使电平产生翻转&#xff0c;因此可以使用EXTI来读取按下时…

用AirScript脚本给女/男朋友发送每日早安邮件(极简版本)

先看效果 工具 金山文档/WPS提供了每日定时的AirScript脚本服务&#xff0c;非常方便&#xff5e; 话不多说&#xff0c;我们以金山文档为例&#xff0c;只有简单的五个步骤&#xff0c;非常容易&#xff5e; 教程开始 步骤1 我们打开金山文档新建一个智能表格 步骤2 按下图…

基于Python thinker GUI界面的股票评论数据及投资者情绪分析设计与实现

1.绪论 1.1背景介绍 Python 的 Tkinter 库提供了创建用户界面的工具&#xff0c;可以用来构建股票评论数据及投资者情绪分析的图形用户界面&#xff08;GUI&#xff09;。通过该界面&#xff0c;用户可以输入股票评论数据&#xff0c;然后通过情感分析等技术对评论进行情绪分析…

【Linux网络】IP协议{初识/报头/分片/网段划分/子网掩码/私网公网IP/认识网络世界/路由表}

文章目录 1.入门了解2.认识报头3.认识网段4.路由跳转相关指令路由 该文诸多理解参考文章&#xff1a;好文&#xff01; 1.入门了解 用户需求&#xff1a;将我的数据可靠的跨网络从A主机送到B主机 传输层TCP&#xff1a;由各种方法&#xff08;流量控制/超时重传/滑动窗口/拥塞…

【JavaEE】网络编程——TCP

&#x1f921;&#x1f921;&#x1f921;个人主页&#x1f921;&#x1f921;&#x1f921; &#x1f921;&#x1f921;&#x1f921;JavaEE专栏&#x1f921;&#x1f921;&#x1f921; 文章目录 前言1.网络编程套接字1.1流式套接字(TCP)1.1.1特点1.1.2编码1.1.2.1ServerSo…

微信小游戏 彩色试管 倒水游戏 逻辑 (二)

最近开始研究微信小游戏&#xff0c;有兴趣的 可以关注一下 公众号&#xff0c; 记录一些心路历程和源代码。 定义一个 Water class 1. **定义接口和枚举**&#xff1a; - WaterInfo 接口定义了水的颜色、高度等信息。 - PourAction 枚举定义了水的倒动状态&#xff0c;…

【Nuxt3】vue3+tailwindcss+vuetify引入自定义字体样式

一、目的 在项目中引入自定义的字体样式&#xff08;全局页面都可使用&#xff09; 二、步骤 1、下载好字体 字体的后缀可以是ttf、otf、woff、eot或者svg&#xff08;推荐前三种&#xff09; 以抖音字体为例下载好放在静态文件夹&#xff08;font&#xff09;下 案例字…

数学建模入门

目录 文章目录 前言 一、数学建模是什么&#xff1f; 1、官方概念&#xff1a; 2、具体过程 3、适合哪一类人参加&#xff1f; 4、需要有哪些学科基础呢&#xff1f; 二、怎样准备数学建模&#xff08;必备‘硬件’&#xff09; 1.组队 2.资料搜索 3.常用算法总结 4.论文撰写的…

【密码学】数字签名

一、数字签名的基本概念 数字签名是一种用于验证电子文档完整性和身份认证的密码学技术。它通过使用公钥加密体系中的私钥对文档的一部分&#xff08;通常是文档的摘要&#xff09;进行加密&#xff0c;从而创建一个“签名”。这个签名可以附在文档上&#xff0c;或作为一个单独…

【数据结构】--- 堆的应用

​ 个人主页&#xff1a;星纭-CSDN博客 系列文章专栏 :数据结构 踏上取经路&#xff0c;比抵达灵山更重要&#xff01;一起努力一起进步&#xff01; 一.堆排序 在前一个文章的学习中&#xff0c;我们使用数组的物理结构构造出了逻辑结构上的堆。那么堆到底有什么用呢&…