Files
AllinSSL/frontend/packages/vue/hooks/src/axios/index.ts
chudong f1a75afaba 【同步】前端项目源码
【修复】工作流兼容问题
2025-05-10 11:53:11 +08:00

291 lines
7.3 KiB
TypeScript

import { AxiosError, AxiosResponse } from 'axios'
import { ref, shallowRef, computed, watch, effectScope, onScopeDispose } from 'vue'
import type { Ref, ShallowRef, ComputedRef } from 'vue'
import { useLoadingMask, useDialog, useMessage } from '@baota/naive-ui/hooks'
import { HttpClient, type Middleware } from './model'
import { useError } from '../error'
import { cancelRequest, removeAllAbortController } from './model/axios-cancel'
import type { CustomDialogOptions } from '@baota/naive-ui/types/dialog'
import type { LoadingMaskOptions } from '@baota/naive-ui/types/loadingMask'
export type HttpStatusCode = 200 | 201 | 204 | 400 | 401 | 403 | 404 | 500 | 502 | 504
/**
* @description API响应类型
*/
export interface ApiResponse<T = unknown> {
status: boolean
message: string
code: HttpStatusCode
data: T
}
export interface useAxiosReturn<T, Z> {
/** 加载遮罩 */
loadingMask: Ref<{ status: boolean } & LoadingMaskOptions>
/** 消息提示 */
message: Ref<boolean>
/** 确认框 */
dialog: Ref<{ status: boolean } & CustomDialogOptions>
/** 响应式状态 */
loading: Ref<boolean>
/** 错误 */
error: ShallowRef<Error | null | string>
/** 响应 */
response: ShallowRef<AxiosResponse<T> | null>
/** 响应数据 */
data: Ref<T>
/** 默认数据 */
defaultData: Ref<T>
/** HTTP状态码 */
statusCode: ComputedRef<HttpStatusCode | null>
/** 是否被中断 */
aborted: Ref<boolean>
/** URL和参数 */
urlRef: Ref<string>
/** 请求参数 */
paramsRef: Ref<Z>
/** 执行请求 */
execute: (url: string, params?: Z) => Promise<T>
/** 设置参数 */
setParams: (params: Z) => Promise<T>
/** 设置URL */
setUrl: (url: string, params?: Z) => Promise<T>
/** 取消请求 */
cancel: (url: string) => void
/** 取消所有请求 */
cancelAll: () => void
/** 发起请求 */
fetch: (params?: Z) => Promise<T>
}
/**
* @description axios hooks
* @param instance HTTP客户端实例
* @param config 配置项
* @returns 响应式对象和方法
*/
const useAxios = <T = unknown, Z = Record<string, unknown>>(instance: HttpClient): useAxiosReturn<T, Z> => {
const { open, close, update } = useLoadingMask() // 加载遮罩
// 请求状态
const loadingMaskRefs = ref<{ status: boolean } & LoadingMaskOptions>({
status: false, // 是否启用遮罩过渡
text: '正在处理,请稍后...', // 加载文本
})
// 响应数据
const dialogRefs = ref<{ status: boolean } & CustomDialogOptions>({
status: false, // 是否启动确认框
}) // 消息提示
const loadingRef = ref(false) // 是否正在加载
const messageRef = ref(false) // 消息提示
const loadingInstance = shallowRef<unknown>(null) // 加载实例
// 响应数据
const errorRef = shallowRef<Error | null | string>(null) // 错误
const response = shallowRef<AxiosResponse<T> | null>(null) // 原始响应
const statusCode = computed<HttpStatusCode | null>(() => (response.value?.status as HttpStatusCode) || null) // HTTP状态码
const dataRef = ref<T>({} as T) // 处理后的数据
const defaultData = ref<T>({} as T) // 默认数据
// 请求参数
const urlRef = ref('') // url
const paramsRef = ref<Z>({} as Z) // 参数
// const replayRef = ref({ url: '', params: {} as Z }) // 重放请求
const aborted = ref(false) // 是否被中断
// 控制加载遮罩
const showLoadingMask = () => {
if (loadingMaskRefs.value.status && !loadingInstance.value) {
update({ ...loadingMaskRefs.value }) // 更新加载文本
open() // 打开加载遮罩
}
}
// 关闭加载遮罩
const closeLoadingMask = () => {
if (loadingInstance.value) {
close() // 关闭加载遮罩
loadingInstance.value = null
}
}
// 显示响应消息
const showResponseMessage = () => {
if (!messageRef.value || !dataRef.value) return
if (dataRef.value && typeof dataRef.value === 'object') {
if ('status' in dataRef.value && 'message' in dataRef.value) {
const { request } = useMessage() // 消息提示
console.log(dataRef.value, '+++++++')
const { status, message } = dataRef.value
if (message) request({ status, message })
}
}
}
// 处理请求错误
const handleApiError = (err: AxiosError) => {
const { handleError } = useError()
if (typeof err === 'boolean') return
aborted.value = (err as Error)?.name === 'AbortError' || false // 是否被中断
// 检查是否为服务器错误
if (err.status != 200 && err.status != 404 && err?.response) {
const { message } = err.response?.data as { status: number; message: string }
return handleError(new Error(message))
} else {
handleError(err)
}
return err
}
/**
* 执行请求
* @param {string} url 请求地址
* @param params 请求参数
* @returns 响应数据
*/
const execute = async (url: string, params?: Z) => {
// 避免空URL请求
if (!url.trim()) return
try {
// 重置状态
errorRef.value = null
aborted.value = false
loadingRef.value = true
// 保留请求信息
urlRef.value = url
paramsRef.value = params || {}
// 是否显示提示框
if (dialogRefs.value.status) {
const { create } = useDialog()
await create({
type: 'info',
...dialogRefs.value,
})
}
// 显示加载遮罩
if (loadingMaskRefs.value.status) showLoadingMask()
// 执行请求
const res = await instance.post<T>(url, params as Record<string, unknown>)
// 保存响应
response.value = res
// 处理响应数据
if (res.data) dataRef.value = { ...defaultData.value, ...res.data }
// 显示响应消息
if (messageRef.value) showResponseMessage()
return res.data
} catch (err: unknown) {
handleApiError(err as AxiosError)
} finally {
// 关闭加载状态
loadingRef.value = false
// 关闭加载遮罩
if (loadingMaskRefs.value.text) closeLoadingMask()
}
}
/**
* 设置请求参数,并执行请求
* @param params 请求参数
*/
const setParams = (params: Z) => {
paramsRef.value = params
return execute(urlRef.value, params)
}
/**
* 设置请求地址,并执行请求
* @param url 请求地址
* @param params 请求参数
*/
const setUrl = (url: string, params: Z) => {
urlRef.value = url
paramsRef.value = params || {}
return execute(url, paramsRef.value)
}
/**
* 取消特定请求
* @param url 请求地址
*/
const cancel = (url: string) => {
aborted.value = true
return cancelRequest(url)
}
/**
* 取消所有请求
*/
const cancelAll = () => {
aborted.value = true
return removeAllAbortController()
}
/**
* 重放上一次请求
*/
const fetch = (params?: Z) => {
if (!urlRef.value) return
return execute(urlRef.value, params || paramsRef.value)
}
const scope = effectScope()
scope.run(() => {
// 监听 loadingMask 变化
watch(loadingMaskRefs, (newVal) => {
if (newVal && loadingRef.value) {
showLoadingMask()
} else if (!newVal) {
closeLoadingMask()
}
})
onScopeDispose(() => {
scope.stop()
})
})
// 封装响应式状态
const state = {
// 集成组件状态
loadingMask: loadingMaskRefs,
dialog: dialogRefs,
message: messageRef,
// 响应式状态
loading: loadingRef,
error: errorRef,
response,
data: dataRef,
defaultData,
statusCode,
aborted,
urlRef,
paramsRef,
}
// 封装方法
const methods = {
execute,
setParams,
setUrl,
cancel,
cancelAll,
fetch,
}
return <useAxiosReturn<T, Z>>{
...state,
...methods,
}
}
export { HttpClient, useAxios, type Middleware }