From e82e6ab128a956b5a1077acc4ca237707c09ed08 Mon Sep 17 00:00:00 2001 From: Estrella Pan Date: Mon, 23 Feb 2026 11:46:56 +0100 Subject: [PATCH] fix(ui): fix auth routing, i18n init, and component lifecycle issues - useAuth: replace watcher with explicit router.replace on login/logout - useMyI18n: create single i18n instance at module level (avoid dupes) - usePasskey: detect WebAuthn support synchronously (no onMounted) - main.ts: import i18n from hook module instead of calling composable - ab-add-rss: hoist useApi composables outside functions to avoid recreating them on each call - calendar: prevent duplicate refreshes when already refreshing - downloader: guard interval polling against stale activation state - router: only mark setupChecked on successful status check - log store: stop SSE log updates on logout - i18n: add missing "edit" translation key (en + zh-CN) Co-Authored-By: Claude Opus 4.6 --- backend/uv.lock | 2 +- webui/src/components/ab-add-rss.vue | 112 ++++++++++++++------------- webui/src/hooks/useAuth.ts | 10 +-- webui/src/hooks/useMyI18n.ts | 14 ++-- webui/src/hooks/usePasskey.ts | 7 +- webui/src/i18n/en.json | 1 + webui/src/i18n/zh-CN.json | 1 + webui/src/main.ts | 2 +- webui/src/pages/index/calendar.vue | 1 + webui/src/pages/index/downloader.vue | 5 +- webui/src/router/index.ts | 4 +- webui/src/store/log.ts | 6 ++ 12 files changed, 87 insertions(+), 78 deletions(-) diff --git a/backend/uv.lock b/backend/uv.lock index 4e95f3ae..d456bec0 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -61,7 +61,7 @@ wheels = [ [[package]] name = "auto-bangumi" -version = "3.2.3b4" +version = "3.2.3b5" source = { virtual = "." } dependencies = [ { name = "aiosqlite" }, diff --git a/webui/src/components/ab-add-rss.vue b/webui/src/components/ab-add-rss.vue index 86d99970..8a7a2e54 100644 --- a/webui/src/components/ab-add-rss.vue +++ b/webui/src/components/ab-add-rss.vue @@ -30,6 +30,61 @@ const loading = reactive({ subscribe: false, }); +const { execute: addRssAggregate } = useApi(apiRSS.add, { + showMessage: true, + onBeforeExecute() { + loading.analyze = true; + }, + onSuccess() { + show.value = false; + }, + onFinally() { + loading.analyze = false; + }, +}); + +const { execute: analyzeRss } = useApi(apiDownload.analysis, { + showMessage: true, + onBeforeExecute() { + loading.analyze = true; + }, + onSuccess(res) { + rule.value = res; + step.value = 'confirm'; + }, + onFinally() { + loading.analyze = false; + }, +}); + +const { execute: executeCollect } = useApi(apiDownload.collection, { + showMessage: true, + onBeforeExecute() { + loading.collect = true; + }, + onSuccess() { + getAll(); + show.value = false; + }, + onFinally() { + loading.collect = false; + }, +}); + +const { execute: executeSubscribe } = useApi(apiDownload.subscribe, { + showMessage: true, + onBeforeExecute() { + loading.subscribe = true; + }, + onSuccess() { + getAll(); + show.value = false; + }, + onFinally() { + loading.subscribe = false; + }, +}); + // Computed const posterSrc = computed(() => resolvePosterUrl(rule.value.poster_link)); @@ -82,34 +137,9 @@ function addRss() { } if (rss.value.aggregate) { - // Aggregate mode: directly add RSS - useApi(apiRSS.add, { - showMessage: true, - onBeforeExecute() { - loading.analyze = true; - }, - onSuccess() { - show.value = false; - }, - onFinally() { - loading.analyze = false; - }, - }).execute(rss.value); + addRssAggregate(rss.value); } else { - // Single mode: analyze and show confirm - useApi(apiDownload.analysis, { - showMessage: true, - onBeforeExecute() { - loading.analyze = true; - }, - onSuccess(res) { - rule.value = res; - step.value = 'confirm'; - }, - onFinally() { - loading.analyze = false; - }, - }).execute(rss.value); + analyzeRss(rss.value); } } @@ -146,36 +176,12 @@ async function autoDetectOffset() { function collect() { if (!rule.value) return; - useApi(apiDownload.collection, { - showMessage: true, - onBeforeExecute() { - loading.collect = true; - }, - onSuccess() { - getAll(); - show.value = false; - }, - onFinally() { - loading.collect = false; - }, - }).execute(rule.value); + executeCollect(rule.value); } function subscribe() { if (!rule.value) return; - useApi(apiDownload.subscribe, { - showMessage: true, - onBeforeExecute() { - loading.subscribe = true; - }, - onSuccess() { - getAll(); - show.value = false; - }, - onFinally() { - loading.subscribe = false; - }, - }).execute(rule.value, rss.value); + executeSubscribe(rule.value, rss.value); } diff --git a/webui/src/hooks/useAuth.ts b/webui/src/hooks/useAuth.ts index e69ed4ea..1ce244a0 100644 --- a/webui/src/hooks/useAuth.ts +++ b/webui/src/hooks/useAuth.ts @@ -9,14 +9,6 @@ export const useAuth = createSharedComposable(() => { const isLoggedIn = useLocalStorage('isLoggedIn', false); - watch(isLoggedIn, (v) => { - if (v) { - router.replace({ name: 'Index' }); - } else { - router.replace({ name: 'Login' }); - } - }); - const user = reactive({ username: '', password: '', @@ -51,6 +43,7 @@ export const useAuth = createSharedComposable(() => { isLoggedIn.value = true; clearUser(); message.success(t('notify.login_success')); + router.replace({ name: 'Index' }); }) .catch((err: ApiError) => { if (err.status === 404) { @@ -64,6 +57,7 @@ export const useAuth = createSharedComposable(() => { onSuccess() { clearUser(); isLoggedIn.value = false; + router.replace({ name: 'Login' }); }, }); diff --git a/webui/src/hooks/useMyI18n.ts b/webui/src/hooks/useMyI18n.ts index 46a24612..00c7ea7a 100644 --- a/webui/src/hooks/useMyI18n.ts +++ b/webui/src/hooks/useMyI18n.ts @@ -16,18 +16,20 @@ function normalizeLocale(locale: string): Languages { return 'en'; } +export const i18n = createI18n({ + legacy: false, + locale: normalizeLocale(navigator.language), + fallbackLocale: 'en', + messages, +}); + export const useMyI18n = createSharedComposable(() => { const lang = useLocalStorage( 'lang', normalizeLocale(navigator.language) ); - const i18n = createI18n({ - legacy: false, - locale: lang.value, - fallbackLocale: 'en', - messages, - }); + i18n.global.locale.value = lang.value as unknown as Languages; watch(lang, (val) => { i18n.global.locale.value = val as unknown as Languages; diff --git a/webui/src/hooks/usePasskey.ts b/webui/src/hooks/usePasskey.ts index e9933888..b15c7920 100644 --- a/webui/src/hooks/usePasskey.ts +++ b/webui/src/hooks/usePasskey.ts @@ -15,12 +15,7 @@ export const usePasskey = createSharedComposable(() => { // 状态 const passkeys = ref([]); const loading = ref(false); - const isSupported = ref(false); - - // 检测浏览器支持 - onMounted(() => { - isSupported.value = isWebAuthnSupported(); - }); + const isSupported = ref(isWebAuthnSupported()); // 加载 Passkey 列表 async function loadPasskeys() { diff --git a/webui/src/i18n/en.json b/webui/src/i18n/en.json index 04ca5513..378af74c 100644 --- a/webui/src/i18n/en.json +++ b/webui/src/i18n/en.json @@ -151,6 +151,7 @@ "delete": "Delete", "delete_hit": "Delete Local File?", "disable": "Disable", + "edit": "Edit", "edit_rule": "Edit Rule", "enable": "Enable", "enable_hit": "Do you want to enable this rule?", diff --git a/webui/src/i18n/zh-CN.json b/webui/src/i18n/zh-CN.json index f7b090c9..a5688541 100644 --- a/webui/src/i18n/zh-CN.json +++ b/webui/src/i18n/zh-CN.json @@ -151,6 +151,7 @@ "delete": "删除", "delete_hit": "是否删除本地文件?", "disable": "禁用", + "edit": "编辑", "edit_rule": "编辑规则", "enable": "启用", "enable_hit": "确定启用该规则?", diff --git a/webui/src/main.ts b/webui/src/main.ts index ab78afa3..97132439 100644 --- a/webui/src/main.ts +++ b/webui/src/main.ts @@ -1,13 +1,13 @@ import { createApp } from 'vue'; import { createPinia } from 'pinia'; import { router } from './router'; +import { i18n } from './hooks/useMyI18n'; import App from './App.vue'; import '@unocss/reset/tailwind-compat.css'; import 'virtual:uno.css'; const pinia = createPinia(); -const { i18n } = useMyI18n(); const app = createApp(App); app.use(router); diff --git a/webui/src/pages/index/calendar.vue b/webui/src/pages/index/calendar.vue index 89aa3ca9..8de4d719 100644 --- a/webui/src/pages/index/calendar.vue +++ b/webui/src/pages/index/calendar.vue @@ -25,6 +25,7 @@ async function refreshCalendar() { } onActivated(() => { + if (refreshing.value) return; refreshCalendar(); }); diff --git a/webui/src/pages/index/downloader.vue b/webui/src/pages/index/downloader.vue index 4b2e6978..410341f2 100644 --- a/webui/src/pages/index/downloader.vue +++ b/webui/src/pages/index/downloader.vue @@ -24,17 +24,20 @@ const isNull = computed(() => { return config.value.downloader.host === ''; }); +const isActive = ref(false); const { pause, resume } = useIntervalFn(getAll, 5000, { immediate: false }); onActivated(async () => { + isActive.value = true; await getConfig(); - if (!isNull.value) { + if (isActive.value && !isNull.value) { getAll(); resume(); } }); onDeactivated(() => { + isActive.value = false; pause(); clearSelection(); }); diff --git a/webui/src/router/index.ts b/webui/src/router/index.ts index 4e20def5..277be782 100644 --- a/webui/src/router/index.ts +++ b/webui/src/router/index.ts @@ -16,10 +16,10 @@ router.beforeEach(async (to) => { try { const status = await apiSetup.getStatus(); needSetup = status.need_setup; + setupChecked = true; } catch { - // If check fails, proceed normally + // If check fails, retry on next navigation } - setupChecked = true; } // Redirect to setup if needed diff --git a/webui/src/store/log.ts b/webui/src/store/log.ts index 9a339182..5f238434 100644 --- a/webui/src/store/log.ts +++ b/webui/src/store/log.ts @@ -27,6 +27,12 @@ export const useLogStore = defineStore('log', () => { immediateCallback: true, }); + watch(isLoggedIn, (loggedIn) => { + if (!loggedIn) { + offUpdate(); + } + }); + const { copy: clipboardCopy, isSupported: clipboardSupported } = useClipboard({ legacy: true, });