All files / src/services http.js

93.75% Statements 45/48
91.66% Branches 33/36
75% Functions 6/8
97.61% Lines 41/42

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100      12x 12x   12x                 12x     3x 3x   2x 2x 2x 2x 2x       8x       3x 3x 3x       12x 2x 2x 1x   2x       12x   4x     4x 1x               3x   3x   2x   2x 4x 4x 4x 4x     6x 6x       6x 6x   6x       6x 6x 6x 6x                  
import axios from 'axios'
import { useAuthStore } from '@/stores/auth'
 
const AUTH_ERROR_CODE = 401
const SESSION_REPLACED_CODE = 40101
 
const http = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
  timeout: 15000,
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
})
 
let redirectingToLogin = false
 
function redirectToLogin(reason) {
  const currentPath = window.location.pathname.replace(/\/+$/, '') || '/'
  if (currentPath === '/login' || redirectingToLogin) return
 
  redirectingToLogin = true
  const redirect = `${window.location.pathname}${window.location.search}${window.location.hash}`
  const query = new URLSearchParams({ redirect })
  Eif (reason) query.set('reason', reason)
  window.location.assign(`/login?${query.toString()}`)
}
 
function isAuthenticationError(code) {
  return code === AUTH_ERROR_CODE || code === SESSION_REPLACED_CODE
}
 
function handleUnauthorized(code) {
  const authStore = useAuthStore()
  authStore.clearSession()
  redirectToLogin(code === SESSION_REPLACED_CODE ? 'session-replaced' : 'expired')
}
 
// 请求拦截器:注入 Bearer token(调用时读取 store,保证 pinia 已激活)
http.interceptors.request.use((config) => {
  const authStore = useAuthStore()
  if (authStore.loginInfo.token) {
    config.headers.Authorization = `Bearer ${authStore.loginInfo.token}`
  }
  return config
})
 
// 响应拦截器:统一按 ApiResult 结构处理
http.interceptors.response.use(
  (response) => {
    const payload = response.data
 
    // 无 code 字段时按成功透传
    if (payload?.code === undefined) {
      return {
        success: true,
        code: 0,
        message: '',
        data: payload,
      }
    }
 
    payload.success = payload.code === 0
 
    if (payload.success) return response
 
    Iif (isAuthenticationError(payload.code)) handleUnauthorized(payload.code)
 
    const err = new Error(payload.message || '请求失败')
    err.code = payload.code
    err.data = payload.data
    err.success = payload.success
    throw err
  },
  (error) => {
    const status = error.response?.status
    const payload = error.response?.data
 
    // 仅鉴权类错误(401 / 40101)才清会话并跳登录页;
    // 网络异常(无 status)、服务器 5xx 等普通错误不应把用户登出
    const errorCode = payload?.code ?? status ?? 0
    if (isAuthenticationError(errorCode)) handleUnauthorized(errorCode)
 
    const requestError = new Error(
      payload?.message ||
        (error.code === 'ECONNABORTED' ? '请求超时,请稍后重试' : '网络异常,请检查服务是否正常'),
    )
    requestError.code = payload?.code ?? status ?? -1
    requestError.data = payload?.data
    requestError.success = false
    throw requestError
  },
)
 
export function post(path, data, config) {
  return http.post(path, data, config).then((r) => r.data)
}
 
export default http