diff --git a/.env b/.env index 61ffae9..82421a5 100644 --- a/.env +++ b/.env @@ -32,7 +32,7 @@ SMTP_PORT=25 SMTP_USE_TLS=false SMTP_USE_SSL=false SMTP_AUTH_REQUIRED=false -SMTP_SENDER_EMAIL=todo-system@panjit.com.tw +SMTP_SENDER_EMAIL=document_translator@panjit.com.tw SMTP_SENDER_PASSWORD= # 檔案儲存 diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 241ab5f..496625d 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -56,15 +56,15 @@ body { background: var(--el-border-color); } -// Element Plus 主題色彩自定義 +// Element Plus 主題色彩自定義 - 深色系 :root { - --el-color-primary: #409eff; - --el-color-primary-light-3: #79bbff; - --el-color-primary-light-5: #a0cfff; - --el-color-primary-light-7: #c6e2ff; - --el-color-primary-light-8: #d9ecff; - --el-color-primary-light-9: #ecf5ff; - --el-color-primary-dark-2: #337ecc; + --el-color-primary: #2c3e50; + --el-color-primary-light-3: #5d6d7e; + --el-color-primary-light-5: #85929e; + --el-color-primary-light-7: #aeb6bf; + --el-color-primary-light-8: #d5d8dc; + --el-color-primary-light-9: #eaeded; + --el-color-primary-dark-2: #1a252f; } // 過渡動畫 diff --git a/frontend/src/stores/auth.js b/frontend/src/stores/auth.js index 9cec718..8e8f513 100644 --- a/frontend/src/stores/auth.js +++ b/frontend/src/stores/auth.js @@ -69,18 +69,29 @@ export const useAuthStore = defineStore('auth', { /** * 使用者登出 + * @param {boolean} showMessage - 是否顯示登出訊息(預設為 true) + * @param {boolean} isAutoLogout - 是否為自動登出(預設為 false) */ - async logout() { + async logout(showMessage = true, isAutoLogout = false) { try { - console.log('🚪 [Auth] 開始登出流程') - await authAPI.logout() - console.log('🚪 [Auth] 登出 API 完成') + console.log('🚪 [Auth] 開始登出流程', { showMessage, isAutoLogout }) + + // 只有手動登出時才呼叫 API + if (!isAutoLogout) { + await authAPI.logout() + console.log('🚪 [Auth] 登出 API 完成') + } } catch (error) { - console.error('❌ [Auth] 登出錯誤:', error) + // 登出 API 失敗不影響本地清除動作,且不顯示錯誤 + console.error('❌ [Auth] 登出 API 錯誤(已忽略):', error) } finally { console.log('🚪 [Auth] 清除認證資料') this.clearAuth() - ElMessage.success('已安全登出') + + // 只在需要時顯示訊息 + if (showMessage && !isAutoLogout) { + ElMessage.success('已安全登出') + } } }, diff --git a/frontend/src/style/layouts.scss b/frontend/src/style/layouts.scss index 3ded601..f0b63f0 100644 --- a/frontend/src/style/layouts.scss +++ b/frontend/src/style/layouts.scss @@ -335,7 +335,7 @@ // 登入頁面布局 .login-layout { min-height: 100vh; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%); @include flex-center; padding: $spacing-lg; @@ -348,20 +348,23 @@ overflow: hidden; .login-header { - background: linear-gradient(45deg, $primary-color, lighten($primary-color, 10%)); + background: linear-gradient(45deg, #1a1a2e, #16213e); padding: $spacing-xxl; text-align: center; color: white; .login-logo { - width: 64px; - height: 64px; + width: 200px; + height: 80px; margin: 0 auto $spacing-lg; - background: rgba(255, 255, 255, 0.2); - border-radius: 50%; @include flex-center; - font-size: $font-size-extra-large; - font-weight: bold; + + img { + width: 100%; + height: 100%; + object-fit: contain; + // 移除濾鏡,讓白色 LOGO 在深色背景上自然顯示 + } } .login-title { diff --git a/frontend/src/style/variables.scss b/frontend/src/style/variables.scss index 4c67c9e..90c47bc 100644 --- a/frontend/src/style/variables.scss +++ b/frontend/src/style/variables.scss @@ -1,11 +1,11 @@ // SCSS 變數定義 -// 顏色系統 -$primary-color: #409eff; -$success-color: #67c23a; -$warning-color: #e6a23c; -$danger-color: #f56c6c; -$info-color: #909399; +// 顏色系統 - 調整為深色系 +$primary-color: #2c3e50; +$success-color: #27ae60; +$warning-color: #f39c12; +$danger-color: #e74c3c; +$info-color: #34495e; // 文字顏色 $text-color-primary: #303133; @@ -81,17 +81,17 @@ $ease-in-out-back: cubic-bezier(0.71, -0.46, 0.29, 1.46); // 組件特定顏色 $header-bg: #fff; -$sidebar-bg: #304156; +$sidebar-bg: #1a1a2e; $sidebar-text-color: #bfcbd9; -$sidebar-active-color: #409eff; +$sidebar-active-color: #3498db; // 狀態顏色映射 $status-colors: ( - 'PENDING': #909399, - 'PROCESSING': #409eff, - 'COMPLETED': #67c23a, - 'FAILED': #f56c6c, - 'RETRY': #e6a23c + 'PENDING': #7f8c8d, + 'PROCESSING': #3498db, + 'COMPLETED': #27ae60, + 'FAILED': #e74c3c, + 'RETRY': #f39c12 ); // 檔案類型圖示顏色 diff --git a/frontend/src/utils/request.js b/frontend/src/utils/request.js index 848511d..2ddada1 100644 --- a/frontend/src/utils/request.js +++ b/frontend/src/utils/request.js @@ -43,6 +43,9 @@ service.interceptors.request.use( } ) +// 用於防止重複處理 401 錯誤 +let isHandling401 = false + // 回應攔截器 service.interceptors.response.use( response => { @@ -95,19 +98,32 @@ service.interceptors.response.use( currentPath, isLoginPage: currentPath === '/login', isLoginRequest: requestUrl.includes('/auth/login'), - willTriggerLogout: currentPath !== '/login' && !requestUrl.includes('/auth/login'), - timestamp: new Date().toISOString(), - errorData: data, - requestHeaders: error.config?.headers + isHandling401, + willTriggerLogout: currentPath !== '/login' && !requestUrl.includes('/auth/login') && !isHandling401, + timestamp: new Date().toISOString() }) - if (currentPath !== '/login' && !requestUrl.includes('/auth/login')) { + // 防止重複處理 + if (!isHandling401 && currentPath !== '/login' && !requestUrl.includes('/auth/login')) { + isHandling401 = true console.error('🚪 [Auto Logout] 認證失效,觸發自動登出') - ElMessage.error('認證失效,請重新登入') - authStore.logout() - router.push('/login') + + // 只顯示一次訊息 + ElMessage.error('認證已過期,請重新登入') + + // 使用自動登出模式,不顯示額外訊息 + authStore.logout(false, true).finally(() => { + router.push('/login').then(() => { + // 導航完成後重置標記 + setTimeout(() => { + isHandling401 = false + }, 1000) + }) + }) + } else if (isHandling401) { + console.log('🔐 [401 Skipped] 已在處理其他 401 錯誤') } else { - console.log('🔐 [401 Ignored] 在登入頁面或登入請求,不觸發自動登出') + console.log('🔐 [401 Ignored] 在登入頁面或登入請求') } break diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue index 04922c4..d6cd7bd 100644 --- a/frontend/src/views/HomeView.vue +++ b/frontend/src/views/HomeView.vue @@ -316,22 +316,37 @@ onMounted(async () => { console.log('🏠 [HomeView] 當前認證狀態', { isAuthenticated: authStore.isAuthenticated, user: authStore.user, - token: authStore.token + hasToken: !!authStore.token }) + // 如果沒有認證,不要嘗試載入資料 + if (!authStore.isAuthenticated || !authStore.token) { + console.warn('🏠 [HomeView] 未認證,跳過資料載入') + loading.value = false + return + } + try { // 延遲載入任務列表,避免登入後立即請求造成認證問題 console.log('🏠 [HomeView] 等待 500ms 後載入任務列表') await new Promise(resolve => setTimeout(resolve, 500)) + // 再次檢查認證狀態(可能在延遲期間已登出) + if (!authStore.isAuthenticated) { + console.log('🏠 [HomeView] 認證狀態已改變,取消載入') + return + } + console.log('🏠 [HomeView] 開始載入任務列表') // 載入最近的任務 await jobsStore.fetchJobs({ per_page: 10 }) console.log('🏠 [HomeView] 任務列表載入成功') } catch (error) { console.error('❌ [HomeView] 載入任務失敗:', error) - // 如果是認證錯誤,不要顯示錯誤訊息,因為 request.js 會處理 - if (!error.message?.includes('認證') && !error.response?.status === 401) { + // 如果是認證錯誤,不顯示額外的錯誤訊息 + if (error.response?.status !== 401 && + !error.message?.includes('認證') && + !error.message?.includes('401')) { ElMessage.error('載入任務失敗,請稍後重試') } } finally { diff --git a/frontend/src/views/LoginView.vue b/frontend/src/views/LoginView.vue index 3c67b6a..1e0ec7a 100644 --- a/frontend/src/views/LoginView.vue +++ b/frontend/src/views/LoginView.vue @@ -3,7 +3,7 @@
企業級文件批量翻譯管理系統
@@ -48,12 +48,13 @@