From e8372dab243099e740e3d60a07818fb5c11fa992 Mon Sep 17 00:00:00 2001 From: "ethan.chen" Date: Mon, 23 Jun 2025 16:57:23 +0800 Subject: [PATCH] feat: enhance authentication and UI components in App; add Toast for notifications, improve login handling, and update pagination logic --- src/App.svelte | 110 ++++++++++++++++++++++++++++++------------- src/lib/Toast.svelte | 9 ++++ src/lib/request.ts | 31 +++++++++--- src/lib/toast.ts | 10 ++++ vite.config.ts | 2 +- 5 files changed, 121 insertions(+), 41 deletions(-) create mode 100644 src/lib/Toast.svelte create mode 100644 src/lib/toast.ts diff --git a/src/App.svelte b/src/App.svelte index 99e4976..4465650 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -5,6 +5,7 @@ import MediaItem from './lib/MediaItem.svelte'; import MediaFormModal from './lib/MediaFormModal.svelte'; import DeleteConfirmModal from './lib/DeleteConfirmModal.svelte'; + import Toast from './lib/Toast.svelte'; // 类型定义 interface ApiResponse { @@ -56,9 +57,16 @@ // 初始化函数:检查认证状态 async function initializeAuth() { - const auth = localStorage.getItem('auth'); - if (auth) { + const authString = localStorage.getItem('auth'); + if (authString) { try { + const authData = JSON.parse(authString); + if (new Date().getTime() > authData.expiry) { + localStorage.removeItem('auth'); + isInitializing = false; + return; + } + // 使用新的验证接口检查登录状态 const response = await request.get>('/auth/verify'); @@ -105,20 +113,6 @@ } } - // 监听类别变化 - $effect(() => { - if (currentCategory) { - currentPage = 1; - } - }); - - // 监听分页变化 - $effect(() => { - if (currentPage) { - fetchMediaList(); - } - }); - // 登录处理 async function handleLogin(e: Event) { e.preventDefault(); @@ -133,7 +127,9 @@ }); if (response.data.code === 200) { - localStorage.setItem('auth', response.data.data.token); + const expiry = new Date().getTime() + 2 * 60 * 60 * 1000; // 2 hours + const authData = { token: response.data.data.token, expiry }; + localStorage.setItem('auth', JSON.stringify(authData)); isAuthenticated = true; error = ''; // 获取初始数据 @@ -211,12 +207,30 @@ error = e.message || 'Failed to delete media'; } } + + // 计算总页数 + let totalPages = 1; + let pageNumbers: (number | string)[] = []; + $effect(() => { + totalPages = Math.ceil(totalItems / pageSize); + if (totalPages <= 5) { + pageNumbers = Array.from({length: totalPages}, (_, i) => i + 1); + } else if (currentPage <= 3) { + pageNumbers = [1,2,3,4,'...',totalPages]; + } else if (currentPage >= totalPages - 2) { + pageNumbers = [1,'...',totalPages-3,totalPages-2,totalPages-1,totalPages]; + } else { + pageNumbers = [1,'...',currentPage-1,currentPage,currentPage+1,'...',totalPages]; + } + }); My Score + + {#if isInitializing}
@@ -229,35 +243,35 @@

- My Score + 我的评分

- Sign in to your account + 登录你的账号

- +
- +
@@ -274,7 +288,7 @@ type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" > - Sign in + 登录
@@ -291,7 +305,11 @@ {#each categories as category} @@ -322,24 +340,48 @@
-
+
- Showing {(currentPage - 1) * pageSize + 1} to {Math.min(currentPage * pageSize, totalItems)} of {totalItems} items + 显示第 {(currentPage - 1) * pageSize + 1} - {Math.min(currentPage * pageSize, totalItems)} 条,共 {totalItems} 条
-
+
+ {#each pageNumbers as num, i} + {#if num === '...'} + ... + {:else} + + {/if} + {/each}
diff --git a/src/lib/Toast.svelte b/src/lib/Toast.svelte new file mode 100644 index 0000000..d55e43a --- /dev/null +++ b/src/lib/Toast.svelte @@ -0,0 +1,9 @@ + + +{#if $toast.visible} +
+ { $toast.message } +
+{/if} \ No newline at end of file diff --git a/src/lib/request.ts b/src/lib/request.ts index 361cf2a..f15329a 100644 --- a/src/lib/request.ts +++ b/src/lib/request.ts @@ -1,10 +1,11 @@ /* * @Date: 2025-05-19 18:10:10 * @LastEditors: 陈子健 - * @LastEditTime: 2025-05-20 18:14:14 + * @LastEditTime: 2025-06-23 14:54:40 * @FilePath: /my-score/frontend/src/lib/request.ts */ import axios from 'axios'; +import { showToast } from './toast'; const request = axios.create({ baseURL: '/api', @@ -18,9 +19,17 @@ const request = axios.create({ request.interceptors.request.use( (config) => { // 从 localStorage 获取认证信息 - const auth = localStorage.getItem('auth'); - if (auth) { - config.headers.Authorization = `Basic ${auth}`; + const authString = localStorage.getItem('auth'); + if (authString) { + try { + const auth = JSON.parse(authString); + if (auth && auth.token) { + config.headers.Authorization = `Basic ${auth.token}`; + } + } catch (e) { + console.error('Failed to parse auth from localStorage', e); + localStorage.removeItem('auth'); + } } return config; }, @@ -36,9 +45,19 @@ request.interceptors.response.use( }, (error) => { if (error.response?.status === 401) { - // 清除认证信息 + // For login failures, we don't redirect. The component will handle the error. + if (error.config.url === '/user/login') { + return Promise.reject(error.response?.data || error); + } + + // For other 401 errors, it means the token is invalid/expired. + // Clear auth data and show toast, then redirect. localStorage.removeItem('auth'); - return + showToast('登录已过期,请重新登录'); + setTimeout(() => { + window.location.href = '/'; + }, 1500); + return new Promise(() => {}); // Prevent further promise chain execution } return Promise.reject(error.response?.data || error); } diff --git a/src/lib/toast.ts b/src/lib/toast.ts new file mode 100644 index 0000000..cdb812b --- /dev/null +++ b/src/lib/toast.ts @@ -0,0 +1,10 @@ +import { writable } from 'svelte/store'; + +export const toast = writable<{ message: string; visible: boolean }>({ message: '', visible: false }); + +export function showToast(message: string, duration = 2000) { + toast.set({ message, visible: true }); + setTimeout(() => { + toast.set({ message: '', visible: false }); + }, duration); +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 522e4bc..8ddf749 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,7 @@ /* * @Date: 2025-05-20 17:59:45 * @LastEditors: 陈子健 - * @LastEditTime: 2025-06-13 14:07:41 + * @LastEditTime: 2025-06-23 14:40:15 * @FilePath: /my-score/frontend/vite.config.ts */ import { defineConfig } from 'vite'