看到检测到新版本后自动更新版本, 第一反应想到就是ServiceWorker, 是的, 使用ServiceWorker好处多多, 包括提示更新版本, 但是使用ServiceWorker有一个硬性条件, 那就是必须是 https 或者 localhost, 一些时候需要部署在内网时, 就没法实现了, 那么就必须换个思路来
大概思路就是: 请求一个文件, 读取该文件内容里的时间戳, 和本地缓存里的时间戳做对比, 如果不一致就给出提示, 让用户点击刷新
第一步: 那就是编译时, 附带生成一个文件, 并记录编译时的时间戳
这个我们可以写一个简单的vite插件, 编译后自动生成一个json文件, 当然不嫌麻烦, 也可以手动来改动一个文件
export default defineConfig(({ mode }: ConfigEnv) => {
// 编译后保存文件的文件, 一般是dist文件夹
const outDir = 'dist'
return {
// 其他配置
plugins: [
// 其他配置
{
name: 'generate-timestamp',
closeBundle() {
// 我们记录这几个数据
const buildInfo = {
buildTime: new Date().toISOString(),
timestamp: Date.now(),
buildMode: process.env.VITE_APP_ENV || 'production',
outDir,
}
const content = JSON.stringify(buildInfo, null, 2)
const outputPath = path.resolve(__dirname, outDir, 'timestamp.json')
// 确保 outputPath 目录存在
fs.mkdirSync(path.dirname(outputPath), { recursive: true })
// 写入文件
fs.writeFileSync(outputPath, content)
console.log(`时间戳文件已生成: ${outputPath}`)
},
},
],
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
第二步: 编写检测更新的逻辑
interface TimestampData {
timestamp: string | number
}
class TimestampChecker {
private readonly LOCAL_STORAGE_KEY = 'last_timestamp'
private readonly CHECK_INTERVAL = 2000 // 2秒
private readonly API_URL = '/timestamp.json'
private intervalId: number | null = null
/**
* 开始定时检查时间戳
*/
start(): void {
if (this.intervalId !== null) {
console.warn('时间戳检查器已经在运行中')
return
}
// 立即执行一次检查
this.checkTimestamp()
// 设置定时器
this.intervalId = window.setInterval(() => {
this.checkTimestamp()
}, this.CHECK_INTERVAL)
console.log('时间戳检查器已启动')
}
/**
* 停止定时检查
*/
stop(): void {
if (this.intervalId !== null) {
window.clearInterval(this.intervalId)
this.intervalId = null
console.log('时间戳检查器已停止')
}
}
/**
* 检查时间戳
*/
private async checkTimestamp(): Promise<void> {
try {
const remoteTimestamp = await this.fetchTimestamp()
this.compareTimestamp(remoteTimestamp)
}
catch (error) {
console.error('获取时间戳失败:', error)
}
}
/**
* 获取远程时间戳
*/
private async fetchTimestamp(): Promise<string> {
const response = await fetch(this.API_URL)
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`)
}
const data: TimestampData = await response.json()
// 确保时间戳是字符串类型
return String(data.timestamp)
}
/**
* 比较时间戳
*/
private compareTimestamp(remoteTimestamp: string): void {
const localTimestamp = localStorage.getItem(this.LOCAL_STORAGE_KEY)
if (localTimestamp === null) {
// 本地存储为空,写入时间戳
localStorage.setItem(this.LOCAL_STORAGE_KEY, remoteTimestamp)
console.log('初始化本地时间戳:', remoteTimestamp)
}
else if (localTimestamp !== remoteTimestamp) {
// 时间戳发生变化,给出提示
this.notifyTimestampChanged(localTimestamp, remoteTimestamp)
// 更新本地存储的时间戳
localStorage.setItem(this.LOCAL_STORAGE_KEY, remoteTimestamp)
}
else {
// 时间戳相同,无变化
console.debug('时间戳未变化:', remoteTimestamp)
}
}
/**
* 时间戳变化通知
*/
private notifyTimestampChanged(oldTimestamp: string, newTimestamp: string): void {
const message = `时间戳已更新:\n旧时间戳: ${oldTimestamp}\n新时间戳: ${newTimestamp}`
console.log('时间戳变化通知:', message)
// 触发自定义事件
const event = new CustomEvent('timestamp-changed', {
detail: { oldTimestamp, newTimestamp },
})
window.dispatchEvent(event)
}
/**
* 获取当前本地存储的时间戳
*/
getCurrentTimestamp(): string | null {
return localStorage.getItem(this.LOCAL_STORAGE_KEY)
}
/**
* 清空本地存储的时间戳
*/
clearTimestamp(): void {
localStorage.removeItem(this.LOCAL_STORAGE_KEY)
console.log('已清空本地时间戳')
}
}
export default TimestampChecker
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
第三步: 在入口文件里调用
// main.ts
// 其他代码
app.mount('#app')
// 这里判断下, 不在开发环境和SSR环境执行
if (!import.meta.env.DEV && import.meta.env.SSR !== true) {
const timestampChecker = new TimestampChecker()
// 开始检查
timestampChecker.start()
// 如果需要停止检查
// timestampChecker.stop();
// 获取当前存储的时间戳
// const current = timestampChecker.getCurrentTimestamp();
// 清空时间戳
// timestampChecker.clearTimestamp();
// 监听自定义事件
window.addEventListener('timestamp-changed', (event: Event) => {
const customEvent = event as CustomEvent
console.log('时间戳变化事件:', customEvent.detail)
ElNotification({
type: 'success',
title: '通知',
dangerouslyUseHTMLString: true,
message: '<div>新内容可用,单击<b style="color: red; cursor: pointer;">这里</b>更新 (刷新前请确认所有内容都已保存)</div>',
onClick() {
window.location.reload()
},
duration: 20000,
})
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37