Skip to content

背景

INFO

上篇总结了如何实现网站更新的功能,现在还有另外的一种做法pwa技术实现;

创建项目

vite+ts+vue创建一个空项目

bash
pnpm create vite vite-pwa-demo --template vue-ts

安装插件

安装完成之后,需要再次安装pwa插件

bash
pnpm add -D vite-plugin-pwa

配置插件

完成安装之后,需要改一下vite.config.ts文件,添加pwa插件

ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { VitePWA } from "vite-plugin-pwa";

export default defineConfig({
	plugins: [
		vue(),
		VitePWA({
			// prompt 弹框模式。 autoupdate 自动更新模式
			registerType: "prompt",
			devOptions: {
				enabled: true,
			},
			workbox: {
				cleanupOutdatedCaches: true,
			},
			manifest: {},
		}),
	],
	server: {
		port: 5177,
	},
});

接着需要在vite-env.d.ts中添加对应的服务:

ts
declare module "virtual:pwa-register/vue" {
	import type { Ref } from "vue";
	import type { RegisterSWOptions } from "vite-plugin-pwa/types";

	export type { RegisterSWOptions };

	export function useRegisterSW(options?: RegisterSWOptions): {
		needRefresh: Ref<boolean>;
		offlineReady: Ref<boolean>;
		updateServiceWorker: (reloadPage?: boolean) => Promise<void>;
	};
}

接着在tsconfig.json中配置

json
{
	"compilerOptions": {
		// 添加这一行
		"types": ["vite-plugin-pwa/client", "vite-plugin-pwa/vue", "vite/client"]
	}
}

更新检测

ts
// 核心代码
import { useRegisterSW } from "virtual:pwa-register/vue";
const { offlineReady, needRefresh, updateServiceWorker } = useRegisterSW({
	// 每隔3s检查一次是否有新版本
	onRegistered(r) {
		if (r) {
			setInterval(() => {
				r.update();
			}, 1000 * 3);
		}
	},
});

编写更新弹框

vue
<script setup lang="ts">
import { useRegisterSW } from "virtual:pwa-register/vue";
import { ref, watch } from "vue";
import { ElMessage } from "element-plus";

const { offlineReady, needRefresh, updateServiceWorker } = useRegisterSW({
	onRegistered(r) {
		if (r && !check.value) {
			setInterval(() => {
				r.update();
			}, 1000 * 3);
		}
	},
});

// 如果是强制更新,只需要needRefresh.value 即可;

// 用另外的变量控制弹框的显示
const check = ref(needRefresh.value);

const loading = ref(false);
const btnText = ref("立即更新");
// 更新
const updataPage = async () => {
	loading.value = true;
	btnText.value = "正在更新中...";
	await updateServiceWorker();
};

// 关闭弹框
const close = () => {
	check.value = false;
	loading.value = false;
	btnText.value = "立即更新";
};
// 更新的时候禁止关闭
const beforeClose = (done: any) => {
	if (!loading.value) {
		return done();
	}
	ElMessage({
		message: "正在进行更新, 请稍等...",
		type: "warning",
	});
};
// 没3s会更新needRefresh.value的状态,所以这里监听一下即可;
watch(
	() => needRefresh.value,
	val => {
		check.value = val;
	}
);
</script>

<template>
	<div>======11111213+++---</div>
	<el-dialog
		v-model="check"
		append-to-body
		class="w-update"
		draggable
		width="410px"
		:before-close="beforeClose"
	>
		<template #header></template>
		<div class="w-update-icon"></div>
		<h3>新版本来袭:</h3>
		<p>更新时间:最近更新</p>

		<p>更新内容:更新的内容呢还请详看上线邮件~</p>
		<p style="color: #ff0000; font-size: 12px">
			如果您暂时不想更新(如:您正在操作内容),点击关闭按钮可关闭,后续想更新只需要刷新页面进行更新即可~如果想使用新功能,请点击更新按钮即可
		</p>
		<template #footer>
			<el-button type="primary" @click="updataPage" :loading="loading">
				{{ btnText }}
			</el-button>
			<el-button type="default" :loading="loading" @click="close">
				暂时不更新
			</el-button>
		</template>
	</el-dialog>
</template>

<style scoped>
.w-update {
	position: relative;
}
.w-update-icon {
	position: absolute;
	top: -50px;
	left: 50%;
	width: 100px;
	height: 100px;
	line-height: 100px;
	text-align: center;
	background: linear-gradient(
		50deg,
		var(--el-color-primary),
		var(--el-color-primary-light-7)
	);
	border-radius: 50%;
	transform: translateX(-50%);
}
.w-update-icon i {
	font-size: 50px;
	color: var(--el-color-white);
}

.w-update-cup {
	position: absolute;
	right: 20px;
	bottom: 70px;
	font-size: 80px;
	-webkit-text-fill-color: transparent;
	background-image: linear-gradient(
		var(--el-color-primary-light-7),
		var(--el-color-primary-light-9)
	);
	background-clip: text;
}
</style>

<style>
.w-update.el-dialog {
	margin-top: 30vh !important;
	border-radius: 15px;
}

.w-update .el-dialog__body {
	margin: 0 40px 0 40px;
}

.w-update .el-dialog__footer {
	text-align: center !important;
}
.w-update .el-button {
	margin-bottom: 20px;
	border-radius: 20px;
}
</style>

测试

测试的话,可以上传服务器或者本地nginx测试;

项目执行pnpm run build将生成dist文件夹,将dist文件夹上传到服务器或者nginx的html文件夹中即可;

然后等待页面弹框即可;

wangxiaoze | MIT License.