From 6ae214c891385448d1ad8068b55ea92c77acd18b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=80=E5=AF=B8=E7=81=B0?= Date: Thu, 21 Aug 2025 19:50:02 +0800 Subject: [PATCH] =?UTF-8?q?refactor(hooks):=20=E2=99=BB=EF=B8=8F=20sync=20?= =?UTF-8?q?refactor=20useCountDown=20hook.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(projects): :bug: tab closure did not remove cache correctly. * fix(projects): :bug: fix the lint warning of no-console. * refactor(hooks): :recycle: refactor useCountDown hook for improved countdown logic and clarity. --- packages/hooks/src/use-count-down.ts | 51 ++++++++++++++++-------- src/layouts/modules/global-tab/index.vue | 10 +---- src/plugins/app.ts | 2 + src/store/modules/tab/index.ts | 48 ++++++++++++++++++---- 4 files changed, 79 insertions(+), 32 deletions(-) diff --git a/packages/hooks/src/use-count-down.ts b/packages/hooks/src/use-count-down.ts index bfad064..4f95b73 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 61d23f6..b4ebb0c 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'; @@ -16,7 +15,6 @@ defineOptions({ name: 'GlobalTab' }); const route = useRoute(); const appStore = useAppStore(); const themeStore = useThemeStore(); -const routeStore = useRouteStore(); const tabStore = useTabStore(); const bsWrapper = ref(); @@ -80,12 +78,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/plugins/app.ts b/src/plugins/app.ts index ed02dbc..2097a45 100644 --- a/src/plugins/app.ts +++ b/src/plugins/app.ts @@ -90,6 +90,7 @@ async function getHtmlBuildTime(): Promise { const res = await fetch(`${baseUrl}index.html?time=${Date.now()}`); if (!res.ok) { + // eslint-disable-next-line no-console console.error('getHtmlBuildTime error:', res.status, res.statusText); return null; } @@ -98,6 +99,7 @@ async function getHtmlBuildTime(): Promise { const match = html.match(//); return match?.[1] || null; } catch (error) { + // eslint-disable-next-line no-console console.error('getHtmlBuildTime error:', error); return null; } diff --git a/src/store/modules/tab/index.ts b/src/store/modules/tab/index.ts index 8ad89a1..cd9b45c 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,24 +140,47 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => { */ async function clearTabs(excludes: string[] = []) { 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); function update() { tabs.value = updatedTabs; } - if (!isRemoveActiveTab) { - update(); - return; + if (isRemoveActiveTab) { + const activeTabCandidate = updatedTabs[updatedTabs.length - 1] || homeTab.value; + + if (activeTabCandidate) { + // Ensure there's a tab to switch to + await switchRouteByTab(activeTabCandidate); + } } - - const activeTab = updatedTabs[updatedTabs.length - 1] || homeTab.value; - - await switchRouteByTab(activeTab); + // Update the tabs array regardless of switch success or if a candidate was found update(); + + // After tabs are updated and route potentially switched, reset cache for removed tabs + for (const routeKey of routeKeysToReset) { + routeStore.resetRouteCache(routeKey); + } } /**