世外天堂

世外天堂

代码、番剧与灵感共振的赛博据点

首页typescript封装请求axios

基于TypeScript的Axios封装实践方案

📅 2026年3月21日✍️ 4116 字⏱ 13 分钟阅读 👁 -- 次阅读

在项目中,合理封装请求可以提升代码复用性、统一错误处理并增强类型安全,本文将介绍一个完整的TypeScript封装方案,包含uniapp端和pc端请求拦截、响应拦截、错误处理和模块化API管理。

pc端请求封装(ts版)

import axios, {
    type AxiosRequestConfig,
    type AxiosResponse,
    type AxiosInstance,
    type InternalAxiosRequestConfig
} from 'axios'
import { ElLoading, ElMessage, type LoadingInstance } from 'element-plus'
import router from '@/router'

/**
 * Axios 实例
 * baseURL 优先使用环境变量,没有则退回 /api
 */
const service: AxiosInstance = axios.create({
    baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
    timeout: 15000,
    headers: {
        'Content-Type': 'application/json;charset=UTF-8'
    }
})

/**
 * 后端统一返回结构
 */
export interface ApiResponse<T = unknown> {
    data: T
    msg: string
    code: number
}

/**
 * 扩展后的请求配置
 * 仍兼容 AxiosRequestConfig
 */
export interface RequestConfig extends AxiosRequestConfig {
    showSuccessMessage?: boolean
    showErrorMessage?: boolean
    showLoading?: boolean
}

/**
 * 自定义element-plus行为(集中管理)
 */
const requestConfig = {
    showSuccessMessage: false,
    showErrorMessage: true,
    showLoading: false
}

// -------- Loading 计数,避免多个请求同时闪烁 --------
let loadingInstance: LoadingInstance | null = null
let loadingCount = 0

function showLoading(title = '加载中...'): void {
    if (loadingCount === 0) {
        loadingInstance = ElLoading.service({
            lock: true,
            text: title,
            background: 'rgba(0, 0, 0, 0.7)'
        })
    }
    loadingCount += 1
}

function hideLoading(): void {
    if (loadingCount > 0) {
        loadingCount -= 1
    }
    if (loadingCount === 0) {
        loadingInstance?.close()
        loadingInstance = null
    }
}

// -------- 请求拦截器 --------
service.interceptors.request.use(
    (config: InternalAxiosRequestConfig) => {
        const reqConfig = config as RequestConfig

        // 根据 requestConfig 控制 loading
        if (reqConfig.showLoading ?? requestConfig.showLoading) {
            showLoading()
        }

        // 统一拼装请求头
        const token = localStorage.getItem('token')
        if (token) {
            config.headers.set('Authorization', `Bearer ${token}`)
        }

        return config
    },
    (error) => Promise.reject(error)
)

// -------- 响应拦截器 --------
service.interceptors.response.use(
    (response: AxiosResponse<ApiResponse>) => {
        const config = response.config as RequestConfig
        if (config.showLoading ?? requestConfig.showLoading) {
            hideLoading()
        }

        const { code, msg } = response.data
        const showError = config.showErrorMessage ?? requestConfig.showErrorMessage

        if (code === 200) {
            if (config.showSuccessMessage ?? requestConfig.showSuccessMessage) {
                ElMessage.success(msg || '操作成功')
            }
            return response
        }

        if (showError) {
            ElMessage.error(msg || '请求失败')
        }
        if (code === 401) {
            handleLogout()
        }
        return Promise.reject(msg || '请求失败')
    },
    (error) => {
        const config = (error?.config || {}) as RequestConfig
        if (config.showLoading ?? requestConfig.showLoading) {
            hideLoading()
        }

        const showError = config.showErrorMessage ?? requestConfig.showErrorMessage
        if (error?.response?.status) {
            handleHttpError(error.response.status, showError)
        } else if (showError) {
            ElMessage.error('网络异常,请稍后再试')
        }

        return Promise.reject(error)
    }
)

/**
 * 核心请求函数
 * 约定:后端 code === 200 才视为成功
 */
export async function request<T = unknown>(config: RequestConfig): Promise<T> {
    const response = await service.request<ApiResponse<T>, AxiosResponse<ApiResponse<T>>>(config)
    //成功时只返回 data 字段,简化业务层调用
    return response.data.data as T
}

// -------- HTTP 状态码统一处理 --------
function handleHttpError(status: number, showError: boolean): void {
    const map: Record<number, string> = {
        400: '请求错误',
        401: '未授权,请重新登录',
        403: '拒绝访问',
        404: '接口不存在',
        500: '服务器错误'
    }

    if (showError) {
        ElMessage.error(map[status] || '网络错误')
    }

    if (status === 401) {
        handleLogout()
    }
}

// -------- 退出登录 --------
function handleLogout(): void {
    localStorage.removeItem('token')
    router.replace({ name: 'login' })
}

使用方法

import { request } from '@/utils/request'
export function searchFriend(params: { username: string }){
    return request<FriendItem[]>({
        url: '/api/friend/search',
        method: 'get',
        params
    })
}
export function updateProfile(data: { name: string }){
  return request<UserInfo>({
    url: '/api/user/profile',
    method: 'post',
    data,
    showLoading: true,
    showSuccessMessage: true
  })
}

pc端请求封装(js版)

import axios from 'axios'
import { ElLoading, ElMessage } from 'element-plus'
import router from '@/router'
/**
 * Axios 实例
 */
const service = axios.create({
    baseURL: (import.meta.env && import.meta.env.VITE_API_BASE_URL) || '/api',
    timeout: 15000,
    headers: {
        'Content-Type': 'application/json;charset=UTF-8'
    }
})

/**
 * 全局默认配置
 */
const defaultConfig = {
    showSuccessMessage: false,
    showErrorMessage: true,
    showLoading: false
}

// -------- Loading 控制 --------
let loadingInstance = null
let loadingCount = 0

function showLoading(title = '加载中...') {
    if (loadingCount === 0) {
        loadingInstance = ElLoading.service({
            lock: true,
            text: title,
            background: 'rgba(0, 0, 0, 0.7)'
        })
    }
    loadingCount++
}

function hideLoading() {
    if (loadingCount > 0) {
        loadingCount--
    }
    if (loadingCount === 0 && loadingInstance) {
        loadingInstance.close()
        loadingInstance = null
    }
}

// -------- 请求拦截器 --------
service.interceptors.request.use(
    (config) => {
        const custom = config

        // ⚠️ 修复逻辑:优先用单次配置,否则用全局配置
        if ((custom.showLoading ?? defaultConfig.showLoading)) {
            showLoading()
        }

        // token
        const token = localStorage.getItem('token')
        if (token) {
            config.headers['Authorization'] = `Bearer ${token}`
        }

        return config
    },
    (error) => Promise.reject(error)
)

// -------- 响应拦截器 --------
service.interceptors.response.use(
    (response) => {
        const config = response.config || {}

        if ((config.showLoading ?? defaultConfig.showLoading)) {
            hideLoading()
        }

        const { code, msg } = response.data || {}
        const showError = config.showErrorMessage ?? defaultConfig.showErrorMessage

        if (code === 200) {
            if (config.showSuccessMessage ?? defaultConfig.showSuccessMessage) {
                ElMessage.success(msg || '操作成功')
            }
            return response
        }

        if (showError) {
            ElMessage.error(msg || '请求失败')
        }

        if (code === 401) {
            handleLogout()
        }

        return Promise.reject(msg || '请求失败')
    },
    (error) => {
        const config = error.config || {}

        if ((config.showLoading ?? defaultConfig.showLoading)) {
            hideLoading()
        }

        const showError = config.showErrorMessage ?? defaultConfig.showErrorMessage

        if (error.response && error.response.status) {
            handleHttpError(error.response.status, showError)
        } else if (showError) {
            ElMessage.error('网络异常,请稍后再试')
        }

        return Promise.reject(error)
    }
)

/**
 * 核心请求函数
 */
export function request(config) {
    return service.request(config).then((res) => {
        //成功时只返回 data 字段,简化业务层调用
        return res.data.data
    })
}

// -------- HTTP 错误处理 --------
function handleHttpError(status, showError) {
    const map = {
        400: '请求错误',
        401: '未授权,请重新登录',
        403: '拒绝访问',
        404: '接口不存在',
        500: '服务器错误'
    }

    if (showError) {
        ElMessage.error(map[status] || '网络错误')
    }

    if (status === 401) {
        handleLogout()
    }
}

// -------- 退出登录 --------
function handleLogout() {
    localStorage.removeItem('token')
    router.replace({ name: 'login' })
}

uniapp端请求封装(ts版)

// request.ts
const base_url = 'https://xx.com'

// ===== 类型定义 =====
interface ApiResponse<T = any> {
    code: number
    msg: string
    data: T
}

interface RequestOptions<T = any> {
    url?: string
    method?: 'GET' | 'POST'
    data?: any
    header?: Record<string, any>
    loading?: boolean
    showError?: boolean
    showSuccess?: boolean   // ✅ 新增
    responseType?: 'text' | 'arraybuffer'
    timeout?: number
}

// ===== loading 控制 =====
let loadingCount = 0

function showLoading(title = '加载中...') {
    if (loadingCount === 0) {
        uni.showLoading({ title, mask: true })
    }
    loadingCount++
}

function hideLoading() {
    if (loadingCount > 0) {
        loadingCount--
    }
    if (loadingCount === 0) {
        uni.hideLoading()
    }
}

// ===== 核心请求函数 =====
export function request<T = any>(options: RequestOptions<T> = {}): Promise<ApiResponse<T>> {
    const {
        url = '',
        method = 'GET',
        data = {},
        header = {},
        loading = false,
        showError = true,
        showSuccess = false, 
        responseType = 'text',
        timeout = 15000
    } = options

    // token 注入
    const requestHeader: Record<string, any> = { ...header }
    const token = uni.getStorageSync('token')
    if (token) {
        requestHeader.Authorization = `Bearer ${token}`
    }

    if (loading) {
        showLoading()
    }

    return new Promise((resolve, reject) => {
        uni.request({
            url: base_url + url,
            method,
            data,
            header: requestHeader,
            timeout,
            responseType,

            success: (res) => {
                const { statusCode } = res
                const resData = res.data as ApiResponse<T>

                // HTTP 错误
                if (statusCode !== 200) {
                    handleHttpError(statusCode || 0, showError)
                    reject(res)
                    return
                }

                // 业务错误
                if (resData.code !== 200) {
                    if (showError) {
                        uni.showToast({
                            title: resData.msg || '请求失败',
                            icon: 'none'
                        })
                    }

                    if (resData.code === 401) {
                        handleLogout()
                    }

                    reject(resData)
                    return
                }

                //  成功提示
                if (showSuccess) {
                    uni.showToast({
                        title: resData.msg || '操作成功',
                        icon: 'none'
                    })
                }
                // 只返回 data 字段
                resolve(resData.data)
            },

            fail: (err) => {
                if (showError) {
                    uni.showToast({
                        title: '网络异常,请稍后再试',
                        icon: 'none'
                    })
                }
                reject(err)
            },

            complete: () => {
                if (loading) {
                    hideLoading()
                }
            }
        })
    })
}

// ===== HTTP 错误处理 =====
function handleHttpError(statusCode: number, showError: boolean) {
    const map: Record<number, string> = {
        400: '请求错误',
        401: '未授权,请重新登录',
        403: '拒绝访问',
        404: '接口不存在',
        500: '服务器错误'
    }

    if (showError) {
        uni.showToast({
            title: map[statusCode] || '网络错误',
            icon: 'none'
        })
    }

    if (statusCode === 401) {
        handleLogout()
    }
}

// ===== 退出登录 =====
function handleLogout() {
    uni.removeStorageSync('token')
    uni.reLaunch({
        url: '/pages/login/login'
    })
}

使用方法:

import { request } from '@/utils/request'

export function article_list(query){
    return request<ArticleItem[]>({
        url: '/api/article/list',
        method: 'GET',
        params: query,
        loading: true
    })
}

uniapp端请求封装(js版)

const base_url = 'https://xx.com'

// ===== loading 控制 =====
let loadingCount = 0

function showLoading(title = '加载中...') {
    if (loadingCount === 0) {
        uni.showLoading({ title, mask: true })
    }
    loadingCount++
}

function hideLoading() {
    if (loadingCount > 0) {
        loadingCount--
    }
    if (loadingCount === 0) {
        uni.hideLoading()
    }
}

// ===== 核心请求函数 =====
export function request(options = {}) {
    const {
        url = '',
        method = 'GET',
        data = {},
        header = {},
        loading = false,
        showError = true,
        showSuccess = false,
        responseType = 'text',
        timeout = 15000
    } = options

    // token 注入
    const requestHeader = { ...header }
    const token = uni.getStorageSync('token')
    if (token) {
        requestHeader.Authorization = `Bearer ${token}`
    }

    if (loading) {
        showLoading()
    }

    return new Promise((resolve, reject) => {
        uni.request({
            url: base_url + url,
            method,
            data,
            header: requestHeader,
            timeout,
            responseType,

            success: (res) => {
                const statusCode = res.statusCode
                const resData = res.data || {}

                // HTTP 错误
                if (statusCode !== 200) {
                    handleHttpError(statusCode || 0, showError)
                    reject(res)
                    return
                }

                // 业务错误
                if (resData.code !== 200) {
                    if (showError) {
                        uni.showToast({
                            title: resData.msg || '请求失败',
                            icon: 'none'
                        })
                    }

                    if (resData.code === 401) {
                        handleLogout()
                    }

                    reject(resData)
                    return
                }

                // 成功提示
                if (showSuccess) {
                    uni.showToast({
                        title: resData.msg || '操作成功',
                        icon: 'none'
                    })
                }

                // 只返回 data 字段
                resolve(resData.data)
            },

            fail: (err) => {
                if (showError) {
                    uni.showToast({
                        title: '网络异常,请稍后再试',
                        icon: 'none'
                    })
                }
                reject(err)
            },

            complete: () => {
                if (loading) {
                    hideLoading()
                }
            }
        })
    })
}

// ===== HTTP 错误处理 =====
function handleHttpError(statusCode, showError) {
    const map = {
        400: '请求错误',
        401: '未授权,请重新登录',
        403: '拒绝访问',
        404: '接口不存在',
        500: '服务器错误'
    }

    if (showError) {
        uni.showToast({
            title: map[statusCode] || '网络错误',
            icon: 'none'
        })
    }

    if (statusCode === 401) {
        handleLogout()
    }
}

// ===== 退出登录 =====
function handleLogout() {
    uni.removeStorageSync('token')
    uni.reLaunch({
        url: '/pages/login/login'
    })
}

世外天堂

代码、番剧与灵感共振的赛博据点

© 2026 小翼·访客 --·浏览 --