check-updates.vue 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. <script setup lang="ts">
  2. import { h, onMounted, onUnmounted, ref } from 'vue';
  3. import { $t } from '@vben/locales';
  4. import { ToastAction, useToast } from '@vben-core/shadcn-ui';
  5. interface Props {
  6. // 轮训时间,分钟
  7. checkUpdatesInterval?: number;
  8. }
  9. defineOptions({ name: 'CheckUpdates' });
  10. const props = withDefaults(defineProps<Props>(), {
  11. checkUpdatesInterval: 1,
  12. });
  13. const lastVersionTag = ref('');
  14. let isCheckingUpdates = false;
  15. const timer = ref<ReturnType<typeof setInterval>>();
  16. const { toast } = useToast();
  17. async function getVersionTag() {
  18. try {
  19. if (
  20. location.hostname === 'localhost' ||
  21. location.hostname === '127.0.0.1'
  22. ) {
  23. return null;
  24. }
  25. const response = await fetch('/', {
  26. cache: 'no-cache',
  27. method: 'HEAD',
  28. });
  29. return (
  30. response.headers.get('etag') || response.headers.get('last-modified')
  31. );
  32. } catch {
  33. console.error('Failed to fetch version tag');
  34. return null;
  35. }
  36. }
  37. async function checkForUpdates() {
  38. const versionTag = await getVersionTag();
  39. if (!versionTag) {
  40. return;
  41. }
  42. // 首次运行时不提示更新
  43. if (!lastVersionTag.value) {
  44. lastVersionTag.value = versionTag;
  45. return;
  46. }
  47. if (lastVersionTag.value !== versionTag && versionTag) {
  48. clearInterval(timer.value);
  49. handleNotice(versionTag);
  50. }
  51. }
  52. function handleNotice(versionTag: string) {
  53. const { dismiss } = toast({
  54. action: h('div', [
  55. h(
  56. ToastAction,
  57. {
  58. altText: $t('common.cancel'),
  59. onClick: () => dismiss(),
  60. },
  61. {
  62. default: () => $t('common.cancel'),
  63. },
  64. ),
  65. h(
  66. ToastAction,
  67. {
  68. altText: $t('common.refresh'),
  69. class: 'bg-primary hover:bg-primary-hover mx-1',
  70. onClick: () => {
  71. lastVersionTag.value = versionTag;
  72. window.location.reload();
  73. },
  74. },
  75. {
  76. default: () => $t('common.refresh'),
  77. },
  78. ),
  79. ]),
  80. description: $t('widgets.checkUpdatesDescription'),
  81. duration: 0,
  82. title: $t('widgets.checkUpdatesTitle'),
  83. });
  84. }
  85. function start() {
  86. // 每5分钟检查一次
  87. timer.value = setInterval(
  88. checkForUpdates,
  89. props.checkUpdatesInterval * 60 * 1000,
  90. );
  91. }
  92. function handleVisibilitychange() {
  93. if (document.hidden) {
  94. stop();
  95. } else {
  96. if (!isCheckingUpdates) {
  97. isCheckingUpdates = true;
  98. checkForUpdates().finally(() => {
  99. isCheckingUpdates = false;
  100. start();
  101. });
  102. }
  103. }
  104. }
  105. function stop() {
  106. clearInterval(timer.value);
  107. }
  108. onMounted(() => {
  109. start();
  110. document.addEventListener('visibilitychange', handleVisibilitychange);
  111. });
  112. onUnmounted(() => {
  113. stop();
  114. document.removeEventListener('visibilitychange', handleVisibilitychange);
  115. });
  116. </script>
  117. <template>
  118. <slot></slot>
  119. </template>