diff --git a/packages/hooks/src/use-count-down.ts b/packages/hooks/src/use-count-down.ts index bfad064b..4f95b731 100644 --- a/packages/hooks/src/use-count-down.ts +++ b/packages/hooks/src/use-count-down.ts @@ -2,40 +2,59 @@ import { computed, onScopeDispose, ref } from 'vue'; import { useRafFn } from '@vueuse/core'; /** - * count down + * A hook for implementing a countdown timer. It uses `requestAnimationFrame` for smooth and accurate timing, + * independent of the screen refresh rate. * - * @param seconds - count down seconds + * @param initialSeconds - The total number of seconds for the countdown. */ -export default function useCountDown(seconds: number) { - const FPS_PER_SECOND = 60; +export default function useCountDown(initialSeconds: number) { + const remainingSeconds = ref(0); - const fps = ref(0); + const count = computed(() => Math.ceil(remainingSeconds.value)); - const count = computed(() => Math.ceil(fps.value / FPS_PER_SECOND)); - - const isCounting = computed(() => fps.value > 0); + const isCounting = computed(() => remainingSeconds.value > 0); const { pause, resume } = useRafFn( - () => { - if (fps.value > 0) { - fps.value -= 1; - } else { + ({ delta }) => { + // delta: milliseconds elapsed since the last frame. + + // If countdown already reached zero or below, ensure it's 0 and stop. + if (remainingSeconds.value <= 0) { + remainingSeconds.value = 0; + pause(); + return; + } + + // Calculate seconds passed since the last frame. + const secondsPassed = delta / 1000; + remainingSeconds.value -= secondsPassed; + + // If countdown has finished after decrementing. + if (remainingSeconds.value <= 0) { + remainingSeconds.value = 0; pause(); } }, - { immediate: false } + { immediate: false } // The timer does not start automatically. ); - function start(updateSeconds: number = seconds) { - fps.value = FPS_PER_SECOND * updateSeconds; + /** + * Starts the countdown. + * + * @param [updatedSeconds=initialSeconds] - Optionally, start with a new duration. Default is `initialSeconds` + */ + function start(updatedSeconds: number = initialSeconds) { + remainingSeconds.value = updatedSeconds; resume(); } + /** Stops the countdown and resets the remaining time to 0. */ function stop() { - fps.value = 0; + remainingSeconds.value = 0; pause(); } + // Ensure the rAF loop is cleaned up when the component is unmounted. onScopeDispose(() => { pause(); }); diff --git a/src/layouts/modules/global-tab/index.vue b/src/layouts/modules/global-tab/index.vue index 56034271..ef5bb910 100644 --- a/src/layouts/modules/global-tab/index.vue +++ b/src/layouts/modules/global-tab/index.vue @@ -5,7 +5,6 @@ import { useElementBounding } from '@vueuse/core'; import { PageTab } from '@sa/materials'; import { useAppStore } from '@/store/modules/app'; import { useThemeStore } from '@/store/modules/theme'; -import { useRouteStore } from '@/store/modules/route'; import { useTabStore } from '@/store/modules/tab'; import { isPC } from '@/utils/agent'; import BetterScroll from '@/components/custom/better-scroll.vue'; @@ -18,7 +17,6 @@ defineOptions({ const route = useRoute(); const appStore = useAppStore(); const themeStore = useThemeStore(); -const routeStore = useRouteStore(); const tabStore = useTabStore(); const bsWrapper = ref(); @@ -82,12 +80,8 @@ function getContextMenuDisabledKeys(tabId: string) { return disabledKeys; } -async function handleCloseTab(tab: App.Global.Tab) { - await tabStore.removeTab(tab.id); - - if (themeStore.resetCacheStrategy === 'close') { - routeStore.resetRouteCache(tab.routeKey); - } +function handleCloseTab(tab: App.Global.Tab) { + tabStore.removeTab(tab.id); } async function refresh() { diff --git a/src/store/modules/tab/index.ts b/src/store/modules/tab/index.ts index ace7d7dc..78aaac69 100644 --- a/src/store/modules/tab/index.ts +++ b/src/store/modules/tab/index.ts @@ -98,13 +98,22 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => { const removeTabIndex = tabs.value.findIndex(tab => tab.id === tabId); if (removeTabIndex === -1) return; + const removedTabRouteKey = tabs.value[removeTabIndex].routeKey; const isRemoveActiveTab = activeTabId.value === tabId; const nextTab = tabs.value[removeTabIndex + 1] || homeTab.value; + // remove tab tabs.value.splice(removeTabIndex, 1); + + // if current tab is removed, then switch to next tab if (isRemoveActiveTab && nextTab) { await switchRouteByTab(nextTab); } + + // reset route cache if cache strategy is close + if (themeStore.resetCacheStrategy === 'close') { + routeStore.resetRouteCache(removedTabRouteKey); + } } /** remove active tab */ @@ -131,9 +140,26 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => { */ async function clearTabs(excludes: string[] = [], clearCache: boolean = false) { const remainTabIds = [...getFixedTabIds(tabs.value), ...excludes]; - const removedTabsIds = tabs.value.map(tab => tab.id).filter(id => !remainTabIds.includes(id)); + + // Identify tabs to be removed and collect their routeKeys if strategy is 'close' + const tabsToRemove = tabs.value.filter(tab => !remainTabIds.includes(tab.id)); + const routeKeysToReset: RouteKey[] = []; + + if (themeStore.resetCacheStrategy === 'close') { + for (const tab of tabsToRemove) { + routeKeysToReset.push(tab.routeKey); + } + } + + const removedTabsIds = tabsToRemove.map(tab => tab.id); + + // If no tabs are actually being removed based on excludes and fixed tabs, exit + if (removedTabsIds.length === 0) { + return; + } const isRemoveActiveTab = removedTabsIds.includes(activeTabId.value); + // filterTabsByIds returns tabs NOT in removedTabsIds, so these are the tabs that will remain const updatedTabs = filterTabsByIds(removedTabsIds, tabs.value); if (clearCache) { @@ -152,13 +178,21 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => { if (!isRemoveActiveTab) { update(); - return; + } else { + const activeTabCandidate = updatedTabs[updatedTabs.length - 1] || homeTab.value; + + if (activeTabCandidate) { + // Ensure there's a tab to switch to + await switchRouteByTab(activeTabCandidate); + } + // Update the tabs array regardless of switch success or if a candidate was found + update(); } - const activeTab = updatedTabs[updatedTabs.length - 1] || homeTab.value; - - await switchRouteByTab(activeTab); - update(); + // After tabs are updated and route potentially switched, reset cache for removed tabs + for (const routeKey of routeKeysToReset) { + routeStore.resetRouteCache(routeKey); + } } const { routerPushByKey } = useRouterPush();