This commit is contained in:
beabigegg
2025-09-12 08:56:44 +08:00
commit 0bc8c4c81c
86 changed files with 23146 additions and 0 deletions

View 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