PWAUpdatePrompt.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. <template>
  2. <Transition name="slide-down">
  3. <div
  4. v-if="showUpdatePrompt"
  5. class="fixed top-4 left-4 right-4 md:left-auto md:right-4 md:w-96 z-50"
  6. >
  7. <div
  8. class="bg-white dark:bg-slate-800 rounded-2xl shadow-2xl border border-slate-200 dark:border-slate-700 p-4"
  9. >
  10. <div class="flex items-start gap-3">
  11. <!-- 图标 -->
  12. <div class="flex-shrink-0">
  13. <div
  14. class="w-10 h-10 rounded-full bg-blue-500/10 flex items-center justify-center"
  15. >
  16. <svg
  17. class="w-5 h-5 text-blue-500"
  18. fill="none"
  19. stroke="currentColor"
  20. viewBox="0 0 24 24"
  21. >
  22. <path
  23. stroke-linecap="round"
  24. stroke-linejoin="round"
  25. stroke-width="2"
  26. d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
  27. />
  28. </svg>
  29. </div>
  30. </div>
  31. <!-- 内容 -->
  32. <div class="flex-1 min-w-0">
  33. <h3 class="text-sm font-semibold text-slate-900 dark:text-white mb-1">
  34. 发现新版本
  35. </h3>
  36. <p class="text-xs text-slate-600 dark:text-slate-400">
  37. 应用有更新,点击刷新以获取最新功能
  38. </p>
  39. <!-- 按钮 -->
  40. <div class="mt-3 flex gap-2">
  41. <button
  42. @click="updateApp"
  43. :disabled="updating"
  44. class="flex-1 bg-blue-500 hover:bg-blue-600 disabled:bg-blue-400 text-white text-xs font-medium py-2 px-3 rounded-lg transition"
  45. >
  46. {{ updating ? '更新中...' : '立即更新' }}
  47. </button>
  48. <button
  49. @click="dismissUpdate"
  50. class="px-3 py-2 text-xs text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700 rounded-lg transition"
  51. >
  52. 稍后
  53. </button>
  54. </div>
  55. </div>
  56. </div>
  57. </div>
  58. </div>
  59. </Transition>
  60. </template>
  61. <script setup lang="ts">
  62. import { ref, onMounted } from 'vue';
  63. import { useRegisterSW } from 'virtual:pwa-register/vue';
  64. const showUpdatePrompt = ref(false);
  65. const updating = ref(false);
  66. const { needRefresh, updateServiceWorker } = useRegisterSW({
  67. onRegistered(r) {
  68. console.log('Service Worker 已注册');
  69. // 每小时检查一次更新
  70. r && setInterval(() => {
  71. r.update();
  72. }, 60 * 60 * 1000);
  73. },
  74. onRegisterError(error) {
  75. console.error('Service Worker 注册失败:', error);
  76. },
  77. });
  78. // 更新应用
  79. const updateApp = async () => {
  80. updating.value = true;
  81. try {
  82. await updateServiceWorker(true);
  83. // 更新后刷新页面
  84. window.location.reload();
  85. } catch (error) {
  86. console.error('更新失败:', error);
  87. updating.value = false;
  88. }
  89. };
  90. // 关闭更新提示
  91. const dismissUpdate = () => {
  92. showUpdatePrompt.value = false;
  93. };
  94. // 监听更新
  95. onMounted(() => {
  96. if (needRefresh.value) {
  97. showUpdatePrompt.value = true;
  98. }
  99. });
  100. </script>
  101. <style scoped>
  102. .slide-down-enter-active,
  103. .slide-down-leave-active {
  104. transition: all 0.3s ease-out;
  105. }
  106. .slide-down-enter-from {
  107. opacity: 0;
  108. transform: translateY(-20px);
  109. }
  110. .slide-down-leave-to {
  111. opacity: 0;
  112. transform: translateY(-20px);
  113. }
  114. </style>