mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 14:06:28 +00:00
style: use united style
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 通用异步操作 Composable
|
||||
* 统一处理 loading、error 状态和消息提示
|
||||
*
|
||||
* @example
|
||||
* const { loading, error, execute } = useAsyncAction()
|
||||
*
|
||||
* const handleSubmit = async () => {
|
||||
* await execute(
|
||||
* () => api.createTask(formData),
|
||||
* { successMsg: '创建成功', errorMsg: '创建失败' }
|
||||
* )
|
||||
* }
|
||||
*/
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
export function useAsyncAction(options = {}) {
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
|
||||
/**
|
||||
* 执行异步操作
|
||||
* @param {Function} asyncFn - 异步函数
|
||||
* @param {Object} config - 配置选项
|
||||
* @param {string} config.successMsg - 成功提示消息
|
||||
* @param {string} config.errorMsg - 错误提示消息
|
||||
* @param {boolean} config.throwOnError - 是否抛出错误
|
||||
* @param {boolean} config.silent - 是否静默模式(不显示消息)
|
||||
* @returns {Promise} 异步函数的返回值
|
||||
*/
|
||||
const execute = async (asyncFn, config = {}) => {
|
||||
const {
|
||||
successMsg = options.successMsg,
|
||||
errorMsg = options.errorMsg,
|
||||
throwOnError = false,
|
||||
silent = false
|
||||
} = config
|
||||
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const result = await asyncFn()
|
||||
|
||||
if (!silent && successMsg) {
|
||||
message.success(successMsg)
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (err) {
|
||||
error.value = err
|
||||
|
||||
if (!silent) {
|
||||
const msg = err.message || err.detail || errorMsg || '操作失败'
|
||||
message.error(msg)
|
||||
}
|
||||
|
||||
if (throwOnError) {
|
||||
throw err
|
||||
}
|
||||
|
||||
return null
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置状态
|
||||
*/
|
||||
const reset = () => {
|
||||
loading.value = false
|
||||
error.value = null
|
||||
}
|
||||
|
||||
return {
|
||||
loading,
|
||||
error,
|
||||
execute,
|
||||
reset
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 状态轮询 Composable
|
||||
* 支持指数退避、最大重试次数、自动清理
|
||||
*
|
||||
* @example
|
||||
* const { polling, startPolling, stopPolling } = usePollStatus({
|
||||
* interval: 2000,
|
||||
* maxRetries: 15,
|
||||
* backoff: true
|
||||
* })
|
||||
*
|
||||
* startPolling(
|
||||
* async () => {
|
||||
* const status = await api.getStatus(id)
|
||||
* return {
|
||||
* completed: status.status !== 'pending',
|
||||
* success: status.status === 'success',
|
||||
* data: status
|
||||
* }
|
||||
* },
|
||||
* {
|
||||
* onSuccess: (result) => console.log('完成', result),
|
||||
* onFailure: (error) => console.error('失败', error),
|
||||
* onTimeout: () => console.warn('超时')
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
|
||||
export function usePollStatus(options = {}) {
|
||||
const {
|
||||
interval = 2000, // 初始轮询间隔(毫秒)
|
||||
maxRetries = 15, // 最大重试次数
|
||||
backoff = false, // 是否使用指数退避
|
||||
maxBackoffInterval = 10000 // 最大退避间隔(毫秒)
|
||||
} = options
|
||||
|
||||
const polling = ref(false)
|
||||
let pollTimer = null
|
||||
let retryCount = 0
|
||||
|
||||
/**
|
||||
* 开始轮询
|
||||
* @param {Function} checkFn - 检查函数,应返回 { completed, success, data }
|
||||
* @param {Object} callbacks - 回调函数
|
||||
* @param {Function} callbacks.onSuccess - 成功回调
|
||||
* @param {Function} callbacks.onFailure - 失败回调
|
||||
* @param {Function} callbacks.onTimeout - 超时回调
|
||||
*/
|
||||
const startPolling = async (checkFn, callbacks = {}) => {
|
||||
const { onSuccess, onFailure, onTimeout } = callbacks
|
||||
|
||||
// 重置状态
|
||||
stopPolling()
|
||||
polling.value = true
|
||||
retryCount = 0
|
||||
|
||||
const poll = async () => {
|
||||
try {
|
||||
const result = await checkFn()
|
||||
|
||||
// 检查是否完成
|
||||
if (result.completed) {
|
||||
stopPolling()
|
||||
|
||||
if (result.success) {
|
||||
onSuccess?.(result.data || result)
|
||||
} else {
|
||||
onFailure?.(result.data || result)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否超时
|
||||
retryCount++
|
||||
if (retryCount >= maxRetries) {
|
||||
stopPolling()
|
||||
onTimeout?.()
|
||||
return
|
||||
}
|
||||
|
||||
// 计算下次轮询间隔(支持指数退避)
|
||||
let nextInterval = interval
|
||||
if (backoff) {
|
||||
// 指数退避:2s -> 4s -> 8s -> 最大10s
|
||||
nextInterval = Math.min(
|
||||
interval * Math.pow(2, retryCount - 1),
|
||||
maxBackoffInterval
|
||||
)
|
||||
}
|
||||
|
||||
// 继续轮询
|
||||
pollTimer = setTimeout(poll, nextInterval)
|
||||
|
||||
} catch (error) {
|
||||
stopPolling()
|
||||
onFailure?.(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 立即执行第一次检查
|
||||
poll()
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止轮询
|
||||
*/
|
||||
const stopPolling = () => {
|
||||
if (pollTimer) {
|
||||
clearTimeout(pollTimer)
|
||||
pollTimer = null
|
||||
}
|
||||
polling.value = false
|
||||
retryCount = 0
|
||||
}
|
||||
|
||||
// 组件卸载时自动清理
|
||||
onUnmounted(() => {
|
||||
stopPolling()
|
||||
})
|
||||
|
||||
return {
|
||||
polling,
|
||||
startPolling,
|
||||
stopPolling
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user