mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-03-14 02:20:53 +08:00
【修复】条件节点前fromNodeId传值问题
【修复】部署参数默认错误问题 【测设】部分项目代码结构 【同步】前端项目代码
This commit is contained in:
290
frontend/packages/vue/hooks/src/axios/index.ts
Normal file
290
frontend/packages/vue/hooks/src/axios/index.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
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 }
|
||||
45
frontend/packages/vue/hooks/src/axios/model/axios-cancel.ts
Normal file
45
frontend/packages/vue/hooks/src/axios/model/axios-cancel.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { AxiosRequestConfig } from 'axios'
|
||||
import { requestMiddleware } from './other'
|
||||
|
||||
// * 声明一个 Map 用于存储每个请求的标识 和 取消函数
|
||||
export const pendingMap = new Map<string, AbortController>()
|
||||
|
||||
// 获取请求的唯一标识
|
||||
// const getAbortUrl = (config: AxiosRequestConfig) => config.url
|
||||
|
||||
/**
|
||||
* 添加取消请求中间件
|
||||
* @param {AxiosRequestConfig} config 请求配置
|
||||
* @param {AbortController} controller 取消请求控制器
|
||||
* @returns {AbortController} 返回取消请求控制器
|
||||
*/
|
||||
export const addAbortMiddles = requestMiddleware((config: AxiosRequestConfig) => {
|
||||
const controller = new AbortController() // 创建取消请求控制器
|
||||
pendingMap.set(config.url as string, controller) // 设置取消请求控制器
|
||||
config.signal = controller.signal // 设置请求的信号,当调用 abort 时,会触发信号
|
||||
return config // 返回配置
|
||||
})
|
||||
|
||||
// /**
|
||||
// * 删除取消请求中间件
|
||||
// * @param {AxiosRequestConfig} config 请求配置
|
||||
// */
|
||||
// export const removeAbortMiddles = responseMiddleware((response: AxiosResponse) => {
|
||||
// pendingMap.delete(response.config.url as string)
|
||||
// return response
|
||||
// })
|
||||
|
||||
/**
|
||||
* 取消请求
|
||||
* @param {AxiosRequestConfig} config 请求配置
|
||||
*/
|
||||
export const cancelRequest = (url: string) => {
|
||||
pendingMap.get(url)?.abort()
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除所有取消请求控制器
|
||||
*/
|
||||
export const removeAllAbortController = () => {
|
||||
pendingMap.clear() // 清空取消请求控制器列表
|
||||
}
|
||||
35
frontend/packages/vue/hooks/src/axios/model/axios-options.ts
Normal file
35
frontend/packages/vue/hooks/src/axios/model/axios-options.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { AxiosRequestConfig } from 'axios'
|
||||
import { getCookie, isDev } from '@baota/utils/browser'
|
||||
import { objectToQueryString } from '@baota/utils/data'
|
||||
import { requestMiddleware } from './other'
|
||||
|
||||
/**
|
||||
* @description 请求头处理-基础
|
||||
* @param {AxiosRequestConfig} options 请求头参数
|
||||
* @param {boolean} isDev 是否为开发环境
|
||||
*/
|
||||
export const requestDefalutOptionsMiddles = requestMiddleware((options: AxiosRequestConfig, dev: boolean = isDev()) => {
|
||||
const defaultOpt: AxiosRequestConfig = {
|
||||
baseURL: dev ? '/api' : '', // 请求基础路径,相对路径用于追加到 baseURL
|
||||
timeout: 250000, // 请求超时时间: 250s
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
transformRequest: [objectToQueryString],
|
||||
}
|
||||
return { ...options, ...defaultOpt }
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 默认配置-面板配置
|
||||
* @param options
|
||||
*/
|
||||
export const requestPanelOptionsMiddle = requestMiddleware((options: AxiosRequestConfig, dev: boolean = isDev()) => {
|
||||
if (!dev) {
|
||||
const cookies = getCookie('request_token') // 获取请求头token
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
...{ 'x-http-token': window.request_token },
|
||||
...(cookies ? { 'x-cookie-token': cookies } : {}),
|
||||
}
|
||||
}
|
||||
return options
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
import { AxiosError } from 'axios'
|
||||
|
||||
export const handleResponseError = (error: AxiosError): never => {
|
||||
// 自定义错误处理逻辑通知等
|
||||
console.error('Handled Error:', error)
|
||||
throw error
|
||||
}
|
||||
188
frontend/packages/vue/hooks/src/axios/model/index.ts
Normal file
188
frontend/packages/vue/hooks/src/axios/model/index.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
|
||||
import { requestMiddleware, responseMiddleware, errorMiddleware } from './other'
|
||||
|
||||
/**
|
||||
* 中间件类型定义
|
||||
* @property request - 请求拦截器,用于处理请求配置
|
||||
* @property response - 响应拦截器,用于处理响应数据
|
||||
* @property error - 错误处理器,用于处理请求过程中的错误
|
||||
*/
|
||||
export type Middleware = {
|
||||
request?: (config: AxiosRequestConfig) => AxiosRequestConfig | Promise<AxiosRequestConfig>
|
||||
response?: (response: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>
|
||||
error?: (error: unknown) => unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP客户端配置接口
|
||||
*/
|
||||
export interface HttpClientConfig extends AxiosRequestConfig {
|
||||
/** 全局中间件 */
|
||||
middlewares?: Middleware[]
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP客户端类
|
||||
* 封装axios实例,提供中间件机制和常用的HTTP方法
|
||||
*/
|
||||
class HttpClient {
|
||||
// axios实例
|
||||
private instance: AxiosInstance
|
||||
// 全局中间件数组
|
||||
private middlewares: Middleware[] = []
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param config - HTTP客户端配置
|
||||
*/
|
||||
constructor(config: HttpClientConfig = {}) {
|
||||
const { middlewares = [], ...axiosConfig } = config
|
||||
|
||||
// 创建axios实例
|
||||
this.instance = axios.create(axiosConfig)
|
||||
|
||||
// 初始化全局中间件
|
||||
this.middlewares = [...middlewares]
|
||||
|
||||
// 设置拦截器
|
||||
this.setupInterceptors()
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行中间件链
|
||||
* @param handler - 处理函数名称
|
||||
* @param context - 上下文数据
|
||||
* @returns 处理后的上下文数据
|
||||
*/
|
||||
private async executeMiddlewareChain<T>(handler: keyof Middleware, context: T): Promise<T> {
|
||||
const currentContext = { ...context }
|
||||
let Context = currentContext as T
|
||||
// 执行中间件链
|
||||
for (const middleware of this.middlewares) {
|
||||
const handlerFn = middleware[handler]
|
||||
if (handlerFn) Context = (await handlerFn(Context as any)) as T
|
||||
}
|
||||
return Context
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求和响应拦截器
|
||||
* 用于执行中间件链
|
||||
*/
|
||||
private setupInterceptors() {
|
||||
// 请求拦截器
|
||||
this.instance.interceptors.request.use(
|
||||
async (config) => {
|
||||
// 复制配置对象,避免直接修改原始配置
|
||||
let currentConfig = { ...config } as AxiosRequestConfig
|
||||
// 执行请求中间件链
|
||||
currentConfig = await this.executeMiddlewareChain('request', currentConfig)
|
||||
return currentConfig as InternalAxiosRequestConfig
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error)
|
||||
},
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
this.instance.interceptors.response.use(async (response) => {
|
||||
// 复制响应对象,避免直接修改原始响应
|
||||
let currentResponse = { ...response }
|
||||
// 执行响应中间件链
|
||||
currentResponse = await this.executeMiddlewareChain('response', currentResponse)
|
||||
return currentResponse
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加全局中间件
|
||||
* @param middleware - 中间件对象
|
||||
* @returns this - 返回实例本身,支持链式调用
|
||||
*/
|
||||
public use(middleware: Middleware) {
|
||||
this.middlewares.push(middleware)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取axios实例
|
||||
* @returns AxiosInstance - 返回当前的axios实例
|
||||
*/
|
||||
public getAxiosInstance() {
|
||||
return this.instance
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送请求
|
||||
* @param config - 请求配置
|
||||
* @returns Promise<AxiosResponse<T>> - 返回请求响应
|
||||
*/
|
||||
public async request<T = unknown>(config: HttpClientConfig): Promise<AxiosResponse<T> | void> {
|
||||
try {
|
||||
const processedConfig = await this.executeMiddlewareChain('request', config) // 执行请求中间件链
|
||||
const response = await this.instance.request(processedConfig) // 发送请求
|
||||
return this.executeMiddlewareChain('response', response) // 执行响应中间件链
|
||||
} catch (error) {
|
||||
// 执行错误处理中间件链
|
||||
const middleError = await this.executeMiddlewareChain('error', error) // 执行错误处理中间件链,返回错误信息
|
||||
return Promise.reject(middleError)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送GET请求
|
||||
* @param url - 请求地址
|
||||
* @param config - 请求配置
|
||||
* @returns Promise<AxiosResponse<T>> - 返回请求响应
|
||||
*/
|
||||
public async get<T = unknown>(url: string, config: AxiosRequestConfig = {}): Promise<AxiosResponse<T>> {
|
||||
return this.request<T>({ ...config, url, method: 'get' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送POST请求
|
||||
* @param url - 请求地址
|
||||
* @param data - 请求数据
|
||||
* @param config - 请求配置
|
||||
* @returns Promise<AxiosResponse<T>> - 返回请求响应
|
||||
*/
|
||||
public async post<T = unknown>(
|
||||
url: string,
|
||||
data?: Record<string, unknown>,
|
||||
config: AxiosRequestConfig = {},
|
||||
): Promise<AxiosResponse<T>> {
|
||||
return this.request<T>({ ...config, url, data, method: 'post' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送PUT请求
|
||||
* @param url - 请求地址
|
||||
* @param data - 请求数据
|
||||
* @param config - 请求配置
|
||||
* @returns Promise<AxiosResponse<T>> - 返回请求响应
|
||||
*/
|
||||
public async put<T = unknown>(
|
||||
url: string,
|
||||
data?: Record<string, unknown>,
|
||||
config: AxiosRequestConfig = {},
|
||||
): Promise<AxiosResponse<T>> {
|
||||
return this.request<T>({ ...config, url, data, method: 'put' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送DELETE请求
|
||||
* @param url - 请求地址
|
||||
* @param config - 请求配置
|
||||
* @returns Promise<AxiosResponse<T>> - 返回请求响应
|
||||
*/
|
||||
public async delete<T = unknown>(url: string, config: AxiosRequestConfig = {}): Promise<AxiosResponse<T>> {
|
||||
return this.request<T>({ ...config, url, method: 'delete' })
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
HttpClient,
|
||||
requestMiddleware, // 请求中间件
|
||||
responseMiddleware, // 响应中间件
|
||||
errorMiddleware, // 错误中间件
|
||||
}
|
||||
36
frontend/packages/vue/hooks/src/axios/model/other.ts
Normal file
36
frontend/packages/vue/hooks/src/axios/model/other.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
|
||||
|
||||
type RequestMiddlewareProps = (fn: (config: AxiosRequestConfig) => AxiosRequestConfig) => {
|
||||
request: (config: AxiosRequestConfig) => AxiosRequestConfig
|
||||
}
|
||||
|
||||
type ResponseMiddlewareProps = (fn: (response: AxiosResponse) => AxiosResponse) => {
|
||||
response: (response: AxiosResponse) => AxiosResponse
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建Request 请求中间件
|
||||
* @param fn 请求中间件函数
|
||||
* @returns 请求中间件
|
||||
*/
|
||||
export const requestMiddleware: RequestMiddlewareProps = (fn: (config: AxiosRequestConfig) => AxiosRequestConfig) => ({
|
||||
request: fn,
|
||||
})
|
||||
|
||||
/**
|
||||
* 构建Response 响应中间件
|
||||
* @param fn 响应中间件函数
|
||||
* @returns 响应中间件
|
||||
*/
|
||||
export const responseMiddleware: ResponseMiddlewareProps = (fn: (response: AxiosResponse) => AxiosResponse) => ({
|
||||
response: fn,
|
||||
})
|
||||
|
||||
/**
|
||||
* 构建Error 错误中间件
|
||||
* @param fn 错误中间件函数
|
||||
* @returns 错误中间件
|
||||
*/
|
||||
export const errorMiddleware = (fn: (error: AxiosError) => AxiosError) => ({
|
||||
error: fn,
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
import { AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
|
||||
import { getProxyConfig, type ProxyConfig } from '@baota/utils/business'
|
||||
import { isDev } from '@baota/utils/browser'
|
||||
import { requestMiddleware } from './other'
|
||||
|
||||
/**
|
||||
* 代理请求中间件
|
||||
* @param {InternalAxiosRequestConfig<any>} config 请求配置
|
||||
* @param {boolean} dev 是否为开发环境
|
||||
* @returns {InternalAxiosRequestConfig<any>} 返回请求配置
|
||||
*/
|
||||
export const proxyRequestMiddle = requestMiddleware((config: AxiosRequestConfig, dev: boolean = isDev()) => {
|
||||
if (dev) {
|
||||
const { requestTime, requestToken } = getProxyConfig('request_token') as unknown as ProxyConfig
|
||||
config.params = {
|
||||
...config.params,
|
||||
request_time: requestTime,
|
||||
request_token: requestToken,
|
||||
}
|
||||
}
|
||||
return config
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
import { AxiosResponse } from 'axios'
|
||||
import { responseMiddleware } from './other'
|
||||
import { isObject, isString } from '@baota/utils/type'
|
||||
import { hasRequiredKeys } from '@baota/utils/data'
|
||||
|
||||
/*
|
||||
* 预处理响应数据中间件,该组件运行在
|
||||
*/
|
||||
export const processPanelDataMiddle = responseMiddleware((response: AxiosResponse) => {
|
||||
const defaultOption = {
|
||||
data: {}, // 请求数据
|
||||
code: 0, // 状态码,200为成功,其他为失败
|
||||
msg: 'success', // 提示信息
|
||||
status: true, // 接口状态
|
||||
default: true, // 默认状态,用于判断当前数据是否为默认数据,没有经过处理
|
||||
cache: false, // 是否缓存,基于前端缓存
|
||||
oldData: null, // 旧数据,用于保存原始shuj
|
||||
timestamp: 0, // 时间戳
|
||||
}
|
||||
const { data } = response
|
||||
const { custom } = response.config
|
||||
const result = { ...defaultOption } // 拷贝一份数据
|
||||
// 监测字段是否存在
|
||||
if (isObject(data)) {
|
||||
const hasRequiredKeysCurry = hasRequiredKeys(data)
|
||||
const hasStatus = hasRequiredKeysCurry(['status']) // 是否存在status字段
|
||||
const hasMsg = hasRequiredKeysCurry(['msg']) // 是否存在msg字段
|
||||
const hasData = hasRequiredKeysCurry(['data']) // 是否存在data字段
|
||||
if (hasStatus) result.status = (data as { status: boolean }).status
|
||||
if (hasMsg) result.msg = (data as { msg: string }).msg
|
||||
if (hasData) result.data = (data as { data: any }).data
|
||||
result.default = false
|
||||
} else {
|
||||
result.data = data
|
||||
result.default = true // 原数据,仅移动至data
|
||||
}
|
||||
result.oldData = data
|
||||
if (isString(data)) return response
|
||||
return response
|
||||
})
|
||||
310
frontend/packages/vue/hooks/src/error/index.ts
Normal file
310
frontend/packages/vue/hooks/src/error/index.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
/**
|
||||
* 错误处理 Hook
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 基础使用
|
||||
* const { handleError } = useError()
|
||||
*
|
||||
* // 处理运行时错误
|
||||
* try {
|
||||
* throw new Error('运行时错误')
|
||||
* } catch (error) {
|
||||
* handleError(error)
|
||||
* }
|
||||
*
|
||||
* // 处理网络错误(自动显示弹窗)
|
||||
* try {
|
||||
* await fetch('invalid-url')
|
||||
* } catch (error) {
|
||||
* handleError(error, {
|
||||
* title: '网络错误',
|
||||
* content: '请检查网络连接',
|
||||
* showCancel: true
|
||||
* })
|
||||
* }
|
||||
*
|
||||
* // 处理业务错误
|
||||
* const businessError = {
|
||||
* code: 'E001',
|
||||
* message: '余额不足'
|
||||
* }
|
||||
* handleError(businessError)
|
||||
*
|
||||
* // 高级配置
|
||||
* const { handleError, collector } = useError({
|
||||
* showMessage: true, // 显示错误消息
|
||||
* showDialog: true, // 显示错误弹窗
|
||||
* reportError: true, // 启用错误上报
|
||||
* autoAnalyze: true, // 自动分析错误
|
||||
* reportHandler: (errors) => {
|
||||
* // 自定义错误上报逻辑
|
||||
* console.log('上报错误:', errors)
|
||||
* },
|
||||
* customHandler: (error) => {
|
||||
* // 自定义错误处理逻辑
|
||||
* console.error('自定义处理:', error)
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* // 错误收集和上报
|
||||
* collector.collect({
|
||||
* message: '收集错误',
|
||||
* type: 'business'
|
||||
* })
|
||||
*
|
||||
* // 上报所有错误
|
||||
* collector.report()
|
||||
*
|
||||
* // 清空错误队列
|
||||
* collector.clear()
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { useMessage } from '@baota/naive-ui/hooks'
|
||||
import { ref } from 'vue'
|
||||
import type { ErrorInfo, ErrorHandlerOptions, ErrorCollector, ErrorAnalysis, ErrorDialogConfig } from './type'
|
||||
import { AxiosError } from 'axios'
|
||||
import { isArray } from '@baota/utils/type'
|
||||
|
||||
/** 错误队列 */
|
||||
const errorQueue = ref<ErrorInfo[]>([])
|
||||
|
||||
/** 默认错误处理选项 */
|
||||
const DEFAULT_OPTIONS: ErrorHandlerOptions = {
|
||||
showMessage: true, // 显示错误消息
|
||||
reportError: true, // 启用错误上报
|
||||
autoAnalyze: true, // 自动分析错误
|
||||
showDialog: false, // 显示错误弹窗
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认错误弹窗配置
|
||||
*/
|
||||
const DEFAULT_DIALOG_CONFIG: ErrorDialogConfig = {
|
||||
title: '错误提示',
|
||||
confirmText: '确定',
|
||||
cancelText: '取消',
|
||||
showCancel: false,
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析错误类型和级别
|
||||
* @param error 错误对象
|
||||
* @returns 错误分析结果
|
||||
*/
|
||||
const analyzeErrorType = (error: Error | unknown): ErrorAnalysis => {
|
||||
// 如果是 AxiosError,则直接返回错误消息
|
||||
if ((error as AxiosError).name === 'AxiosError') {
|
||||
return {
|
||||
type: 'network',
|
||||
level: 'error',
|
||||
summary: (error as AxiosError).message,
|
||||
details: { message: (error as AxiosError).message },
|
||||
}
|
||||
}
|
||||
|
||||
// 网络错误
|
||||
if (error instanceof TypeError && error.message.includes('network')) {
|
||||
return {
|
||||
type: 'network',
|
||||
level: 'error',
|
||||
summary: '网络请求错误',
|
||||
details: { message: error.message },
|
||||
}
|
||||
}
|
||||
|
||||
// 运行时错误
|
||||
if (error instanceof Error) {
|
||||
return {
|
||||
type: 'runtime',
|
||||
level: 'error',
|
||||
summary: error.message,
|
||||
details: {
|
||||
stack: error.stack,
|
||||
name: error.name,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 业务错误
|
||||
if (typeof error === 'object' && error !== null && 'code' in error) {
|
||||
return {
|
||||
type: 'business',
|
||||
level: 'warning',
|
||||
summary: '业务处理错误,请联系管理员',
|
||||
details: error,
|
||||
}
|
||||
}
|
||||
|
||||
// 验证错误
|
||||
if (typeof error === 'object' && error !== null && Array.isArray(error)) {
|
||||
return {
|
||||
type: 'validation',
|
||||
level: 'warning',
|
||||
summary: '数据验证错误',
|
||||
details: { message: '数据验证错误,请检查输入内容' },
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof error === 'string') {
|
||||
return {
|
||||
type: 'runtime',
|
||||
level: 'error',
|
||||
summary: error,
|
||||
details: { message: error },
|
||||
}
|
||||
}
|
||||
// 未知错误
|
||||
return {
|
||||
type: 'runtime',
|
||||
level: 'error',
|
||||
summary: '未知错误',
|
||||
details: { message: error?.message || '未知错误' },
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示错误弹窗
|
||||
* @param error 错误信息
|
||||
* @param config 弹窗配置
|
||||
*/
|
||||
const showErrorDialog = (error: ErrorInfo, config: ErrorDialogConfig = {}) => {
|
||||
const dialogConfig = { ...DEFAULT_DIALOG_CONFIG, ...config }
|
||||
// TODO: 实现错误弹窗显示逻辑
|
||||
console.log('Show error dialog:', error, dialogConfig)
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误处理 Hook
|
||||
* @param options 错误处理选项
|
||||
* @returns 错误处理函数和收集器
|
||||
*/
|
||||
export const useError = (options: ErrorHandlerOptions = {}) => {
|
||||
const mergedOptions = { ...DEFAULT_OPTIONS, ...options }
|
||||
|
||||
// 判断是否为 ErrorInfo
|
||||
const isErrorInfo = (error: unknown): error is ErrorInfo =>
|
||||
typeof error === 'object' && error !== null && 'message' in error
|
||||
|
||||
// 默认错误处理函数
|
||||
const defaultFn = (error: ErrorInfo | Error | AxiosError | unknown, msg: string) =>
|
||||
typeof error !== 'boolean' && isErrorInfo(error) ? error.message : msg
|
||||
|
||||
/**
|
||||
* 处理错误
|
||||
* @param error 错误信息或原始错误对象
|
||||
* @param dialogConfig 弹窗配置(可选)
|
||||
* @returns 处理后的错误信息
|
||||
*/
|
||||
const handleError = (error: ErrorInfo | Error | AxiosError | unknown | string, dialogConfig?: ErrorDialogConfig) => {
|
||||
const message = useMessage()
|
||||
|
||||
let errorInfo: ErrorInfo
|
||||
|
||||
// 如果传入的错误为布尔值,则直接返回
|
||||
if (typeof error === 'boolean') return { default: (msg: string) => defaultFn(error, msg) }
|
||||
|
||||
// 如果启用了自动分析,且传入的是原始错误对象
|
||||
if (mergedOptions.autoAnalyze && typeof error === 'object' && error !== null && 'message' in error) {
|
||||
errorInfo = collector.analyze(error)
|
||||
} else {
|
||||
errorInfo = error as ErrorInfo
|
||||
}
|
||||
// 添加时间戳
|
||||
errorInfo.timestamp = Date.now()
|
||||
|
||||
// 收集错误
|
||||
errorQueue.value.push(errorInfo)
|
||||
|
||||
// 根据错误级别显示不同类型的消息
|
||||
if (mergedOptions.showMessage) {
|
||||
// 如果是 ErrorInfo,则根据错误级别显示不同类型的消息
|
||||
const analysis = analyzeErrorType(error)
|
||||
console.log('handleError', typeof error, analysis)
|
||||
|
||||
switch (analysis.level) {
|
||||
case 'error':
|
||||
message.error(analysis.details.message || analysis.summary)
|
||||
break
|
||||
case 'warning':
|
||||
message.warning(analysis.details.message || analysis.summary)
|
||||
break
|
||||
case 'info':
|
||||
message.info(errorInfo.message || analysis.summary)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 显示错误弹窗
|
||||
if (mergedOptions.showDialog) {
|
||||
showErrorDialog(errorInfo, dialogConfig)
|
||||
}
|
||||
|
||||
// 自定义处理
|
||||
if (mergedOptions.customHandler) {
|
||||
mergedOptions.customHandler(errorInfo)
|
||||
}
|
||||
|
||||
return { errorInfo, ...message, default: (msg: string) => defaultFn(error, msg) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误收集器
|
||||
*/
|
||||
const collector: ErrorCollector = {
|
||||
/**
|
||||
* 收集错误
|
||||
* @param error 错误信息
|
||||
*/
|
||||
collect: (error: ErrorInfo) => {
|
||||
errorQueue.value.push({
|
||||
...error,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 上报错误
|
||||
* @param errors 错误列表
|
||||
*/
|
||||
report: (errors: ErrorInfo[] = errorQueue.value) => {
|
||||
if (mergedOptions.reportError) {
|
||||
if (mergedOptions.reportHandler) {
|
||||
mergedOptions.reportHandler(errors)
|
||||
} else {
|
||||
// 默认上报逻辑
|
||||
console.log('Reporting errors:', errors)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空错误队列
|
||||
*/
|
||||
clear: () => {
|
||||
errorQueue.value = []
|
||||
},
|
||||
|
||||
/**
|
||||
* 分析错误
|
||||
* @param error 错误对象
|
||||
* @returns 标准化的错误信息
|
||||
*/
|
||||
analyze: (error: Error | unknown): ErrorInfo => {
|
||||
const analysis = analyzeErrorType(error)
|
||||
return {
|
||||
message: analysis.summary,
|
||||
type: analysis.type,
|
||||
metadata: analysis.details,
|
||||
timestamp: Date.now(),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
handleError,
|
||||
collector,
|
||||
errorQueue,
|
||||
}
|
||||
}
|
||||
89
frontend/packages/vue/hooks/src/error/type.d.ts
vendored
Normal file
89
frontend/packages/vue/hooks/src/error/type.d.ts
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 错误信息接口
|
||||
*/
|
||||
export interface ErrorInfo {
|
||||
/** 错误消息 */
|
||||
message: string
|
||||
/** 错误代码 */
|
||||
code?: string | number
|
||||
/** 错误堆栈 */
|
||||
stack?: string
|
||||
/** 错误类型 */
|
||||
type?: ErrorType
|
||||
/** 时间戳 */
|
||||
timestamp?: number
|
||||
/** 元数据 */
|
||||
metadata?: Record<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误类型
|
||||
*/
|
||||
export type ErrorType = 'runtime' | 'network' | 'business' | 'validation'
|
||||
|
||||
/**
|
||||
* 错误级别
|
||||
*/
|
||||
export type ErrorLevel = 'error' | 'warning' | 'info'
|
||||
|
||||
/**
|
||||
* 错误分析结果
|
||||
*/
|
||||
export interface ErrorAnalysis {
|
||||
/** 错误类型 */
|
||||
type: ErrorType
|
||||
/** 错误级别 */
|
||||
level: ErrorLevel
|
||||
/** 错误摘要 */
|
||||
summary: string
|
||||
/** 详细信息 */
|
||||
details: Record<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误处理选项
|
||||
*/
|
||||
export interface ErrorHandlerOptions {
|
||||
/** 是否显示错误消息 */
|
||||
showMessage?: boolean
|
||||
/** 是否上报错误 */
|
||||
reportError?: boolean
|
||||
/** 是否自动分析错误 */
|
||||
autoAnalyze?: boolean
|
||||
/** 自定义错误处理函数 */
|
||||
customHandler?: (error: ErrorInfo) => void
|
||||
/** 错误上报函数 */
|
||||
reportHandler?: (errors: ErrorInfo[]) => void
|
||||
/** 是否显示错误弹窗 */
|
||||
showDialog?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误收集器接口
|
||||
*/
|
||||
export interface ErrorCollector {
|
||||
/** 收集错误 */
|
||||
collect: (error: ErrorInfo) => void
|
||||
/** 上报错误 */
|
||||
report: (errors: ErrorInfo[]) => void
|
||||
/** 清空错误队列 */
|
||||
clear: () => void
|
||||
/** 分析错误 */
|
||||
analyze: (error: Error | unknown) => ErrorInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误弹窗配置
|
||||
*/
|
||||
export interface ErrorDialogConfig {
|
||||
/** 标题 */
|
||||
title?: string
|
||||
/** 内容 */
|
||||
content?: string
|
||||
/** 确认按钮文本 */
|
||||
confirmText?: string
|
||||
/** 取消按钮文本 */
|
||||
cancelText?: string
|
||||
/** 是否显示取消按钮 */
|
||||
showCancel?: boolean
|
||||
}
|
||||
0
frontend/packages/vue/hooks/src/negotiate/index.ts
Normal file
0
frontend/packages/vue/hooks/src/negotiate/index.ts
Normal file
52
frontend/packages/vue/hooks/src/retry/index.ts
Normal file
52
frontend/packages/vue/hooks/src/retry/index.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export interface RetryOptions {
|
||||
retries?: number // 重试次数
|
||||
delay?: number // 重试延迟,单位毫秒,默认为1000ms
|
||||
}
|
||||
|
||||
// 等待函数,用于暂停指定的毫秒数
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 重试hook,用于尝试执行异步操作并提供重试逻辑
|
||||
* @param {() => Promise<T>} fn 待执行的异步函数
|
||||
* @param {RetryOptions} options 配置重试策略
|
||||
* @returns { run, loading, error } 包含执行函数、加载状态和错误信息
|
||||
*/
|
||||
export default function useRetry<T>(fn: () => Promise<T>, options?: RetryOptions) {
|
||||
const { retries = 3, delay = 1000 } = options || {}
|
||||
const loading = ref(false)
|
||||
const error = ref<Error | null>(null)
|
||||
|
||||
// run方法封装了重试逻辑
|
||||
const run = async () => {
|
||||
loading.value = true // 标记开始执行
|
||||
error.value = null // 清除之前的错误状态
|
||||
let attempt = 0
|
||||
let lastError: Error | null = null
|
||||
// 循环尝试执行异步函数
|
||||
while (attempt < retries) {
|
||||
try {
|
||||
// 尝试调用传入的异步函数
|
||||
const result = await fn()
|
||||
loading.value = false // 成功后取消加载状态
|
||||
return result
|
||||
} catch (err: any) {
|
||||
lastError = err
|
||||
error.value = err // 记录错误
|
||||
attempt++
|
||||
// 若未达到最大重试次数,则等待后重试
|
||||
if (attempt < retries) {
|
||||
await sleep(delay)
|
||||
}
|
||||
}
|
||||
}
|
||||
loading.value = false // 重试完毕后取消加载状态
|
||||
throw lastError || new Error('重试失败')
|
||||
}
|
||||
|
||||
return { run, loading, error }
|
||||
}
|
||||
143
frontend/packages/vue/hooks/src/socket/index.ts
Normal file
143
frontend/packages/vue/hooks/src/socket/index.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export interface SocketOptions {
|
||||
autoReconnect?: boolean // 是否自动重连, 默认为 true
|
||||
middleware?: (data: any) => any // 数据中间件函数,支持原始数据处理,默认为直接返回,支持多个中间件
|
||||
maxReconnectAttempts?: number // 最大重连次数, 默认无限制
|
||||
reconnectDelay?: number // 重连延迟, 默认为3000ms
|
||||
heartbeatInterval?: number // 心跳间隔, 单位毫秒
|
||||
heartbeatMessage?: any // 心跳包消息
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将对象参数拼接到 URL 上,返回新的 URL 字符串
|
||||
* @param url - 基础 URL(如 'https://example.com')
|
||||
* @param obj - 要拼接到 URL 上的对象参数(键值对)
|
||||
* @returns 拼接参数后的完整 URL 字符串
|
||||
*/
|
||||
export function setObjToUrlParams(url: string, obj: Record<string, any>): string {
|
||||
// 将对象的每一个键值对转换成 'key=value' 形式,并且对 value 做 URL 编码
|
||||
const paramsArray: string[] = Object.entries(obj).map(([key, value]) => {
|
||||
return `${String(key)}=${encodeURIComponent(String(value))}`
|
||||
})
|
||||
|
||||
// 将所有 'key=value' 形式的字符串用 '&' 符号连接起来,形成 URL 查询字符串
|
||||
const parameters: string = paramsArray.join('&')
|
||||
|
||||
// 判断原 URL 是否以 '?' 结尾
|
||||
const hasQuestionMarkAtEnd: boolean = /\?$/.test(url)
|
||||
|
||||
// 根据 URL 是否已经有 '?' 来决定如何拼接参数
|
||||
if (hasQuestionMarkAtEnd) {
|
||||
// 如果 URL 已经以 '?' 结尾,直接加参数
|
||||
return url + parameters
|
||||
} else {
|
||||
// 如果 URL 没有以 '?' 结尾,需要先去除可能的结尾 '/',然后加上 '?'
|
||||
const cleanUrl = url.replace(/\/?$/, '')
|
||||
return cleanUrl + '?' + parameters
|
||||
}
|
||||
}
|
||||
|
||||
export default function useSocket(url: string, options?: SocketOptions) {
|
||||
const {
|
||||
autoReconnect = true,
|
||||
reconnectDelay = 3000,
|
||||
middleware = (data: any) => data,
|
||||
maxReconnectAttempts,
|
||||
heartbeatInterval = 5000,
|
||||
heartbeatMessage = 'ping',
|
||||
} = options || {}
|
||||
|
||||
const socket = ref<WebSocket | null>(null)
|
||||
const connected = ref(false)
|
||||
const message = ref<any>(null)
|
||||
let manuallyDisconnected = false // 标记是否主动断开
|
||||
|
||||
// 新增重连计数与心跳定时器变量
|
||||
let reconnectAttempts = 0
|
||||
let heartbeatTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
// 建立WebSocket连接
|
||||
const connect = () => {
|
||||
// 兼容检测:若当前环境不支持 WebSocket,则打印错误并退出
|
||||
if (typeof WebSocket === 'undefined') {
|
||||
console.error('WebSocket is not supported in this environment.')
|
||||
return
|
||||
}
|
||||
manuallyDisconnected = false // 重置主动断开标记
|
||||
// 清除之前的心跳定时器
|
||||
if (heartbeatTimer) {
|
||||
clearInterval(heartbeatTimer)
|
||||
heartbeatTimer = null
|
||||
}
|
||||
socket.value = new WebSocket(url)
|
||||
|
||||
// 连接成功的回调
|
||||
socket.value.onopen = () => {
|
||||
connected.value = true
|
||||
reconnectAttempts = 0 // 重置重连计数
|
||||
// 如果配置了心跳包,启动心跳定时器
|
||||
if (heartbeatInterval && heartbeatMessage !== undefined) {
|
||||
heartbeatTimer = setInterval(() => {
|
||||
if (socket.value && connected.value) {
|
||||
socket.value.send(heartbeatMessage)
|
||||
}
|
||||
}, heartbeatInterval)
|
||||
}
|
||||
}
|
||||
|
||||
// 收到消息后,使用中间件处理数据
|
||||
socket.value.onmessage = (event: MessageEvent) => {
|
||||
message.value = middleware(event.data)
|
||||
}
|
||||
|
||||
// 出现错误时打印日志
|
||||
socket.value.onerror = (error) => {
|
||||
console.error('WebSocket error:', error)
|
||||
}
|
||||
|
||||
// 关闭连接的回调,判断是否需要自动重连
|
||||
socket.value.onclose = () => {
|
||||
connected.value = false // 更新连接状态
|
||||
socket.value = null
|
||||
// 清除心跳定时器
|
||||
if (heartbeatTimer) {
|
||||
clearInterval(heartbeatTimer)
|
||||
heartbeatTimer = null
|
||||
}
|
||||
// 如果自动重连且未主动断开,则在延迟后重连
|
||||
if (autoReconnect && !manuallyDisconnected) {
|
||||
if (maxReconnectAttempts !== undefined) {
|
||||
if (reconnectAttempts < maxReconnectAttempts) {
|
||||
reconnectAttempts++
|
||||
setTimeout(() => connect(), reconnectDelay)
|
||||
}
|
||||
} else {
|
||||
setTimeout(() => connect(), reconnectDelay)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主动断开连接,禁止自动重连
|
||||
const disconnect = () => {
|
||||
manuallyDisconnected = true // 标记为主动断开
|
||||
if (socket.value) {
|
||||
socket.value.close()
|
||||
}
|
||||
// 清除心跳定时器
|
||||
if (heartbeatTimer) {
|
||||
clearInterval(heartbeatTimer)
|
||||
heartbeatTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
// 发送数据方法,仅在连接状态时执行
|
||||
const send = (data: any) => {
|
||||
if (socket.value && connected.value) {
|
||||
socket.value.send(data)
|
||||
}
|
||||
}
|
||||
|
||||
return { socket, connect, disconnect, send, message, connected }
|
||||
}
|
||||
157
frontend/packages/vue/hooks/src/task-queue/index.tsx
Normal file
157
frontend/packages/vue/hooks/src/task-queue/index.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
/* eslint-disable guard-for-in */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { ref, reactive, watch, type Ref, defineComponent } from 'vue'
|
||||
|
||||
// 定义任务状态类型
|
||||
interface TaskStatus<T = any> {
|
||||
result: T | null
|
||||
status: boolean
|
||||
error?: Error | null
|
||||
}
|
||||
|
||||
// 定义任务接口
|
||||
interface Task<T = any> {
|
||||
fn: () => Promise<T>
|
||||
name: string
|
||||
status: boolean
|
||||
}
|
||||
|
||||
// 定义任务队列接口
|
||||
export interface TaskQueuePromise<T> {
|
||||
addTask: <T>(taskName: string, taskFn: () => Promise<T>) => Promise<T>
|
||||
getTaskStatus: <T>(taskName: string) => Ref<TaskStatus<T>>
|
||||
getTaskResult: <T>(taskName: string) => Promise<T>
|
||||
processQueue: () => Promise<void>
|
||||
clearAllTasks: () => void
|
||||
isProcessing: Ref<boolean>
|
||||
TaskQueueLoader: ReturnType<typeof defineComponent>
|
||||
}
|
||||
|
||||
// 使用 hooks 改写任务队列
|
||||
export default function useTaskQueue<T>(): TaskQueuePromise<T> {
|
||||
const taskList = ref<Task[]>([])
|
||||
const taskResults = reactive<Record<string, TaskStatus>>({})
|
||||
const isProcessing = ref(false)
|
||||
|
||||
/**
|
||||
* 添加任务到队列
|
||||
*/
|
||||
const addTask = <T,>(taskName: string, taskFn: () => Promise<T>): Promise<T> => {
|
||||
if (!taskName || !taskFn) {
|
||||
return Promise.reject(new Error('任务名称和函数不能为空'))
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
// 初始化任务状态
|
||||
taskResults[taskName] = { result: null, status: false, error: null }
|
||||
taskList.value.push({
|
||||
name: taskName,
|
||||
fn: async () => {
|
||||
try {
|
||||
const result = await taskFn()
|
||||
taskResults[taskName] = { result, status: true, error: null }
|
||||
resolve(result)
|
||||
return result
|
||||
} catch (error) {
|
||||
const taskError = error instanceof Error ? error : new Error(String(error))
|
||||
taskResults[taskName] = { result: null, status: true, error: taskError }
|
||||
reject(taskError)
|
||||
throw taskError
|
||||
}
|
||||
},
|
||||
status: false,
|
||||
})
|
||||
startProcessing()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务状态
|
||||
*/
|
||||
const getTaskStatus = <T,>(taskName: string): Ref<TaskStatus<T>> => {
|
||||
if (!taskResults[taskName]) {
|
||||
throw new Error(`任务 "${taskName}" 不存在`)
|
||||
}
|
||||
return ref(taskResults[taskName]) as Ref<TaskStatus<T>>
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务结果
|
||||
*/
|
||||
const getTaskResult = <T,>(taskName: string): Promise<T> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const status = getTaskStatus<T>(taskName)
|
||||
if (status.value.status) {
|
||||
status.value.error ? reject(status.value.error) : resolve(status.value.result as T)
|
||||
return
|
||||
}
|
||||
watch(
|
||||
() => status.value,
|
||||
(newStatus) => {
|
||||
if (newStatus.status) {
|
||||
newStatus.error ? reject(newStatus.error) : resolve(newStatus.result as T)
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理任务队列中的任务
|
||||
*/
|
||||
const processQueue = async (): Promise<void> => {
|
||||
if (taskList.value.length === 0) {
|
||||
isProcessing.value = false
|
||||
return
|
||||
}
|
||||
const taskIndex = taskList.value.findIndex((task) => !task.status)
|
||||
if (taskIndex === -1) return
|
||||
const task = taskList.value[taskIndex] || { status: false, name: '', fn: () => {} } // 获取未处理的任务
|
||||
try {
|
||||
task.status = true
|
||||
await task.fn()
|
||||
taskList.value.splice(taskIndex, 1)
|
||||
} catch (error) {
|
||||
console.error(`任务 "${task.name}" 执行失败:`, error)
|
||||
task.status = false
|
||||
}
|
||||
await processQueue()
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有任务和任务状态
|
||||
*/
|
||||
const clearAllTasks = (): void => {
|
||||
taskList.value = []
|
||||
for (const key in taskResults) delete taskResults[key]
|
||||
isProcessing.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部方法:启动任务处理
|
||||
*/
|
||||
const startProcessing = (): void => {
|
||||
if (!isProcessing.value) {
|
||||
isProcessing.value = true
|
||||
processQueue()
|
||||
}
|
||||
}
|
||||
|
||||
// 合并加载组件:根据任务队列处理状态展示加载提示
|
||||
const TaskQueueLoader = defineComponent({
|
||||
name: 'TaskQueueLoader',
|
||||
setup(_, { slots }) {
|
||||
const { isProcessing } = useTaskQueue()
|
||||
return () => (
|
||||
<div>
|
||||
{isProcessing.value && <div class="loading-indicator">加载中...</div>}
|
||||
<div>{slots.default ? slots.default() : null}</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
return { addTask, getTaskStatus, getTaskResult, processQueue, clearAllTasks, isProcessing, TaskQueueLoader }
|
||||
}
|
||||
Reference in New Issue
Block a user