feat: 添加版本检测功能,包括构建时自动更新版本信息和运行时定期检查更新
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "tsx src/utils/update-version.ts && vite build --mode release",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
5
public/version.json
Normal file
5
public/version.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"version": "2025-10-15 09:50:32",
|
||||||
|
"buildTime": "2025-10-15T01:50:32.937Z",
|
||||||
|
"environment": "production"
|
||||||
|
}
|
||||||
38
src/App.vue
38
src/App.vue
@@ -1,10 +1,46 @@
|
|||||||
<!-- 基于rspack打包github&gitee -->
|
<!-- 基于rspack打包github&gitee -->
|
||||||
<template>
|
<template>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
|
<footer class="app-version">
|
||||||
|
<span>版本:{{ versionText }}</span>
|
||||||
|
</footer>
|
||||||
</template>
|
</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>
|
<style>
|
||||||
@import './assets/scss/index.scss';
|
@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>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||||
import { DialogPlugin } from 'tdesign-vue-next'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
// 获取浏览器中的url地址和端口号并拼接
|
// 获取浏览器中的url地址和端口号并拼接
|
||||||
/* import { config } from 'public/config.js'
|
/* import { config } from 'public/config.js'
|
||||||
const getBaseUrl = () => {
|
const getBaseUrl = () => {
|
||||||
@@ -14,7 +14,7 @@ const getBaseUrl = () => {
|
|||||||
// 创建一个新的axios实例
|
// 创建一个新的axios实例
|
||||||
const instance: AxiosInstance = axios.create({
|
const instance: AxiosInstance = axios.create({
|
||||||
baseURL: import.meta.env.VITE_BASE_API,
|
baseURL: import.meta.env.VITE_BASE_API,
|
||||||
timeout: 60000,
|
timeout: 30000,
|
||||||
withCredentials: false,
|
withCredentials: false,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -45,18 +45,14 @@ instance.interceptors.response.use(
|
|||||||
console.log(process.env.NODE_ENV, '变量')
|
console.log(process.env.NODE_ENV, '变量')
|
||||||
|
|
||||||
const data = response.data
|
const data = response.data
|
||||||
|
console.log('接口返回', data)
|
||||||
// 判断接口返回的 Message 字段是否为 Success
|
// 判断接口返回的 Message 字段是否为 Success
|
||||||
if (data && data.Message !== 'Success') {
|
if (!data?.Message.includes('Success')) {
|
||||||
// 如果不是 Success,则将请求视为失败
|
// 如果不是 Success,则将请求视为失败
|
||||||
const confirmDia = DialogPlugin({
|
ElMessageBox.alert(JSON.stringify(data), '错误', {
|
||||||
header: '错误',
|
confirmButtonText: '确定',
|
||||||
body: `${JSON.stringify(data)}`,
|
type: 'error',
|
||||||
confirmBtn: '确定',
|
dangerouslyUseHTMLString: false
|
||||||
cancelBtn: null,
|
|
||||||
onConfirm: ({ e }) => {
|
|
||||||
confirmDia.hide()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
return Promise.reject({
|
return Promise.reject({
|
||||||
response: {
|
response: {
|
||||||
@@ -66,7 +62,6 @@ instance.interceptors.response.use(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是 Success,则返回数据
|
// 如果是 Success,则返回数据
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
@@ -84,15 +79,21 @@ instance.interceptors.response.use(
|
|||||||
// 在设置请求时发生了一些事情,触发了一个错误
|
// 在设置请求时发生了一些事情,触发了一个错误
|
||||||
console.error('错误:', error.message)
|
console.error('错误:', error.message)
|
||||||
}
|
}
|
||||||
const confirmDia = DialogPlugin({
|
|
||||||
header: '错误',
|
console.error('报错', error)
|
||||||
body: `${JSON.stringify(error)}`,
|
|
||||||
confirmBtn: '确定',
|
ElMessageBox.alert(
|
||||||
cancelBtn: null,
|
`接口:【${error.config.url}】报错
|
||||||
onConfirm: ({ e }) => {
|
${error.message}
|
||||||
confirmDia.hide()
|
${JSON.stringify(error?.response?.data)}
|
||||||
|
`,
|
||||||
|
'报错',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
type: 'error',
|
||||||
|
dangerouslyUseHTMLString: false
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ import { createApp } from 'vue'
|
|||||||
import router from './router/index' //引入vue-router
|
import router from './router/index' //引入vue-router
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
|
import { startVersionCheck } from './utils/versionCheck'
|
||||||
import TDesign from 'tdesign-vue-next'
|
import TDesign from 'tdesign-vue-next'
|
||||||
import 'tdesign-vue-next/es/style/index.css'
|
import 'tdesign-vue-next/es/style/index.css'
|
||||||
import 'normalize.css/normalize.css'
|
import 'normalize.css/normalize.css'
|
||||||
|
// 启动版本检测服务 (20秒检查一次)
|
||||||
|
startVersionCheck(1000 * 20)
|
||||||
// 挂载到app上
|
// 挂载到app上
|
||||||
createApp(App)
|
createApp(App)
|
||||||
.use(router)
|
.use(router)
|
||||||
|
|||||||
57
src/utils/update-version.ts
Normal file
57
src/utils/update-version.ts
Normal 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
154
src/utils/versionCheck.ts
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user