backup
This commit is contained in:
203
frontend/src/utils/request.js
Normal file
203
frontend/src/utils/request.js
Normal file
@@ -0,0 +1,203 @@
|
||||
import axios from 'axios'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import router from '@/router'
|
||||
import NProgress from 'nprogress'
|
||||
|
||||
// 創建 axios 實例
|
||||
const service = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://127.0.0.1:12010/api/v1',
|
||||
timeout: 30000, // 30秒超時
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// 請求攔截器
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
NProgress.start()
|
||||
|
||||
|
||||
// JWT 認證:添加 Authorization header
|
||||
const authStore = useAuthStore()
|
||||
if (authStore.token) {
|
||||
config.headers.Authorization = `Bearer ${authStore.token}`
|
||||
}
|
||||
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
NProgress.done()
|
||||
console.error('❌ [Request Error]:', error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 用於防止重複處理 401 錯誤
|
||||
let isHandling401 = false
|
||||
|
||||
// 回應攔截器
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
NProgress.done()
|
||||
|
||||
console.log('✅ [API Response]', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
url: response.config.url,
|
||||
method: response.config.method.toUpperCase(),
|
||||
data: response.data,
|
||||
headers: response.headers,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
|
||||
const { data } = response
|
||||
|
||||
// 後端統一回應格式處理
|
||||
if (data && typeof data === 'object') {
|
||||
if (data.success === false) {
|
||||
// 業務錯誤處理
|
||||
const message = data.message || '操作失敗'
|
||||
console.warn('⚠️ [Business Error]:', message)
|
||||
ElMessage.error(message)
|
||||
return Promise.reject(new Error(message))
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
return response
|
||||
},
|
||||
error => {
|
||||
NProgress.done()
|
||||
|
||||
const { response } = error
|
||||
const authStore = useAuthStore()
|
||||
|
||||
if (response) {
|
||||
const { status, data } = response
|
||||
|
||||
switch (status) {
|
||||
case 401:
|
||||
// 避免在登入頁面或登入過程中觸發自動登出
|
||||
const requestUrl = error.config?.url || ''
|
||||
const currentPath = router.currentRoute.value.path
|
||||
|
||||
console.error('🔐 [401 Unauthorized]', {
|
||||
requestUrl,
|
||||
currentPath,
|
||||
isLoginPage: currentPath === '/login',
|
||||
isLoginRequest: requestUrl.includes('/auth/login'),
|
||||
isHandling401,
|
||||
willTriggerLogout: currentPath !== '/login' && !requestUrl.includes('/auth/login') && !isHandling401,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
|
||||
// 防止重複處理
|
||||
if (!isHandling401 && currentPath !== '/login' && !requestUrl.includes('/auth/login')) {
|
||||
isHandling401 = true
|
||||
console.error('🚪 [Auto Logout] 認證失效,觸發自動登出')
|
||||
|
||||
// 只顯示一次訊息
|
||||
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] 在登入頁面或登入請求')
|
||||
}
|
||||
break
|
||||
|
||||
case 403:
|
||||
ElMessage.error('無權限存取此資源')
|
||||
break
|
||||
|
||||
case 404:
|
||||
ElMessage.error('請求的資源不存在')
|
||||
break
|
||||
|
||||
case 422:
|
||||
// 表單驗證錯誤
|
||||
const message = data.message || '輸入資料格式錯誤'
|
||||
ElMessage.error(message)
|
||||
break
|
||||
|
||||
case 429:
|
||||
ElMessage.error('請求過於頻繁,請稍後再試')
|
||||
break
|
||||
|
||||
case 500:
|
||||
ElMessage.error('伺服器內部錯誤')
|
||||
break
|
||||
|
||||
case 502:
|
||||
case 503:
|
||||
case 504:
|
||||
ElMessage.error('伺服器暫時無法存取,請稍後再試')
|
||||
break
|
||||
|
||||
default:
|
||||
const errorMessage = data?.message || error.message || '網路錯誤'
|
||||
ElMessage.error(errorMessage)
|
||||
}
|
||||
} else if (error.code === 'ECONNABORTED') {
|
||||
ElMessage.error('請求超時,請檢查網路連線')
|
||||
} else {
|
||||
ElMessage.error('網路連線失敗,請檢查網路設定')
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 檔案上傳專用請求實例
|
||||
export const uploadRequest = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://127.0.0.1:12010/api/v1',
|
||||
timeout: 120000, // 2分鐘超時
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
|
||||
// 為上傳請求添加攔截器
|
||||
uploadRequest.interceptors.request.use(
|
||||
config => {
|
||||
// JWT 認證:添加 Authorization header
|
||||
const authStore = useAuthStore()
|
||||
if (authStore.token) {
|
||||
config.headers.Authorization = `Bearer ${authStore.token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => Promise.reject(error)
|
||||
)
|
||||
|
||||
uploadRequest.interceptors.response.use(
|
||||
response => response.data,
|
||||
error => {
|
||||
const message = error.response?.data?.message || '檔案上傳失敗'
|
||||
ElMessage.error(message)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 常用請求方法封裝
|
||||
export const request = {
|
||||
get: (url, config = {}) => service.get(url, config),
|
||||
post: (url, data = {}, config = {}) => service.post(url, data, config),
|
||||
put: (url, data = {}, config = {}) => service.put(url, data, config),
|
||||
delete: (url, config = {}) => service.delete(url, config),
|
||||
patch: (url, data = {}, config = {}) => service.patch(url, data, config)
|
||||
}
|
||||
|
||||
export default service
|
Reference in New Issue
Block a user