feat: 添加版本检测功能,包括构建时自动更新版本信息和运行时定期检查更新

This commit is contained in:
刘引
2025-10-15 09:54:16 +08:00
parent fae1999a93
commit 20428e2ac8
8 changed files with 279 additions and 24 deletions

View File

@@ -4,7 +4,7 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"build": "tsx src/utils/update-version.ts && vite build --mode release",
"preview": "vite preview"
},
"dependencies": {

View File

5
public/version.json Normal file
View File

@@ -0,0 +1,5 @@
{
"version": "2025-10-15 09:50:32",
"buildTime": "2025-10-15T01:50:32.937Z",
"environment": "production"
}

View File

@@ -1,10 +1,46 @@
<!-- 基于rspack打包github&gitee -->
<template>
<router-view></router-view>
<footer class="app-version">
<span>版本{{ versionText }}</span>
</footer>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
const versionText = ref('未知')
onMounted(async () => {
try {
const res = await fetch('/version.json', { cache: 'no-store' })
if (res.ok) {
const data = await res.json()
const v = typeof data?.version === 'string' && data.version.trim() ? data.version.trim() : ''
versionText.value = v || '未知'
} else {
versionText.value = '未知'
}
} catch (e) {
versionText.value = '未知'
}
})
</script>
<style>
@import './assets/scss/index.scss';
.app-version {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
justify-content: center;
padding: 6px 8px;
font-size: 12px;
color: #333;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: saturate(180%) blur(6px);
border-top: 1px solid rgba(0, 0, 0, 0.08);
}
</style>

View File

@@ -1,5 +1,5 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import { DialogPlugin } from 'tdesign-vue-next'
import { ElMessage, ElMessageBox } from 'element-plus'
// 获取浏览器中的url地址和端口号并拼接
/* import { config } from 'public/config.js'
const getBaseUrl = () => {
@@ -14,7 +14,7 @@ const getBaseUrl = () => {
// 创建一个新的axios实例
const instance: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_BASE_API,
timeout: 60000,
timeout: 30000,
withCredentials: false,
headers: {
'Content-Type': 'application/json'
@@ -45,18 +45,14 @@ instance.interceptors.response.use(
console.log(process.env.NODE_ENV, '变量')
const data = response.data
console.log('接口返回', data)
// 判断接口返回的 Message 字段是否为 Success
if (data && data.Message !== 'Success') {
if (!data?.Message.includes('Success')) {
// 如果不是 Success则将请求视为失败
const confirmDia = DialogPlugin({
header: '错误',
body: `${JSON.stringify(data)}`,
confirmBtn: '确定',
cancelBtn: null,
onConfirm: ({ e }) => {
confirmDia.hide()
}
ElMessageBox.alert(JSON.stringify(data), '错误', {
confirmButtonText: '确定',
type: 'error',
dangerouslyUseHTMLString: false
})
return Promise.reject({
response: {
@@ -66,7 +62,6 @@ instance.interceptors.response.use(
}
})
}
// 如果是 Success则返回数据
return data
},
@@ -84,15 +79,21 @@ instance.interceptors.response.use(
// 在设置请求时发生了一些事情,触发了一个错误
console.error('错误:', error.message)
}
const confirmDia = DialogPlugin({
header: '错误',
body: `${JSON.stringify(error)}`,
confirmBtn: '确定',
cancelBtn: null,
onConfirm: ({ e }) => {
confirmDia.hide()
console.error('报错', error)
ElMessageBox.alert(
`接口:【${error.config.url}】报错
${error.message}
${JSON.stringify(error?.response?.data)}
`,
'报错',
{
confirmButtonText: '确定',
type: 'error',
dangerouslyUseHTMLString: false
}
})
)
return Promise.reject(error)
}
)

View File

@@ -10,10 +10,12 @@ import { createApp } from 'vue'
import router from './router/index' //引入vue-router
import App from './App.vue'
import { createPinia } from 'pinia'
import { startVersionCheck } from './utils/versionCheck'
import TDesign from 'tdesign-vue-next'
import 'tdesign-vue-next/es/style/index.css'
import 'normalize.css/normalize.css'
// 启动版本检测服务 (20秒检查一次)
startVersionCheck(1000 * 20)
// 挂载到app上
createApp(App)
.use(router)

View File

@@ -0,0 +1,57 @@
/**
* 更新版本信息脚本
* 在构建过程中自动更新 version.json 文件
*/
import fs from 'node:fs'
import path, { dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
// 获取当前文件的目录路径
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
// 版本信息
const now = new Date()
const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(
2,
'0'
)}-${String(now.getDate()).padStart(2, '0')}`
const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(
2,
'0'
)}:${String(now.getSeconds()).padStart(2, '0')}`
interface VersionInfo {
version: string
buildTime: string
environment: string
}
// 不再需要检查构建序号,直接使用时间戳作为版本号
try {
if (fs.existsSync(path.resolve(__dirname, '../../public/version.json'))) {
const currentVersionInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../public/version.json'), 'utf8'))
console.log(`当前版本: ${currentVersionInfo.version}, 更新为时间格式版本`)
}
} catch (error) {
console.error('读取当前版本信息失败', error)
}
// 设置版本格式为 YYYY-MM-DD HH:MM:SS
const versionFormat = `${dateStr} ${timeStr}`
const versionInfo: VersionInfo = {
version: versionFormat,
buildTime: now.toISOString(),
environment: process.env.NODE_ENV || 'production'
}
// 版本文件路径
// 确保路径正确解析到 pc-front/public/version.json
const versionFilePath = path.resolve(__dirname, '../../public/version.json')
// 写入版本信息
fs.writeFileSync(versionFilePath, JSON.stringify(versionInfo, null, 2), 'utf8')
console.log(`版本信息已更新: ${JSON.stringify(versionInfo)}`)
console.log(`当前版本时间戳: ${versionFormat}`)

154
src/utils/versionCheck.ts Normal file
View File

@@ -0,0 +1,154 @@
/**
* 版本检测服务
* 定期检查应用版本并提示用户更新
*/
import { NotifyPlugin } from 'tdesign-vue-next'
import { h } from 'vue'
interface VersionInfo {
version: string
buildTime: string
environment: string
}
let currentVersion: string | null = null
let checkInterval: number | null = null
let isUpdateNotificationShown = false
/**
* 获取当前版本信息
*/
export const fetchVersionInfo = async (): Promise<VersionInfo> => {
try {
// 确保使用正确的路径访问version.json
const response = await fetch(`/version.json?t=${new Date().getTime()}`)
if (!response.ok) {
throw new Error('无法获取版本信息')
}
return await response.json()
} catch (error) {
console.error('获取版本信息失败:', error)
return {
version: '0.0.0',
buildTime: new Date().toISOString(),
environment: 'unknown'
}
}
}
/**
* 检查版本更新
*/
export const checkVersion = async (): Promise<boolean> => {
try {
const versionInfo = await fetchVersionInfo()
// 首次加载,保存当前版本
if (!currentVersion) {
currentVersion = versionInfo.version
return false
}
// 版本不一致,需要更新
if (currentVersion !== versionInfo.version) {
return true
}
return false
} catch (error) {
console.error('检查版本更新失败:', error)
return false
}
}
/**
* 显示更新提示
*/
export const showUpdateNotification = () => {
// 如果已经显示了更新提示,则不再重复显示
if (isUpdateNotificationShown) {
return
}
// 标记已显示更新提示
isUpdateNotificationShown = true
const notify = NotifyPlugin.warning({
title: '有新的版本需要更新',
content: '点击后将刷新页面获取最新版本!',
footer: h('div', { class: 't-notification__detail' }, [
h(
't-button',
{
class: 't-notification__detail-item',
theme: 'default',
variant: 'text',
onClick: () => window.location.reload()
},
'立即刷新'
),
h(
't-button',
{
class: 't-notification__detail-item',
theme: 'primary',
variant: 'text',
onClick: () => {
isUpdateNotificationShown = false
NotifyPlugin.close(notify)
}
},
'稍后提醒我'
)
]),
duration: 0
})
}
/**
* 启动版本检测服务
* @param intervalTime 检测间隔时间(毫秒)默认5分钟
*/
export const startVersionCheck = (intervalTime = 1000) => {
// 重置通知标志
isUpdateNotificationShown = false
// 初始化获取当前版本
fetchVersionInfo().then(info => {
currentVersion = info.version
console.log('当前应用版本:', currentVersion)
})
// 清除可能存在的旧定时器
if (checkInterval) {
window.clearInterval(checkInterval)
}
// 设置定期检查
checkInterval = window.setInterval(async () => {
const needUpdate = await checkVersion()
if (needUpdate) {
showUpdateNotification()
}
}, intervalTime)
return () => {
if (checkInterval) {
window.clearInterval(checkInterval)
checkInterval = null
}
}
}
/**
* 停止版本检测服务
*/
export const stopVersionCheck = () => {
if (checkInterval) {
window.clearInterval(checkInterval)
checkInterval = null
}
// 重置通知标志
isUpdateNotificationShown = false
}