hookehuyr

feat(composables): 新增图片加载、用户信息和错误处理的组合式函数

添加三个组合式函数:
- useImageLoader 提供图片加载错误处理和重试逻辑
- useUserInfo 封装用户信息获取、缓存和刷新功能
- useErrorHandler 实现统一错误处理和表单验证

这些函数将用于统一处理应用中常见的业务逻辑
/**
* 错误处理 composable
* 提供统一的错误处理机制
*
* @Date: 2026-01-18
* @Description: 统一处理各种错误,提供用户友好的错误提示
*/
import { showToast, showFailToast } from 'vant'
/**
* @function useErrorHandler
* @description 提供错误处理功能
* @returns {Object} 包含错误处理方法的对象
*/
export function useErrorHandler() {
/**
* 默认错误消息
*/
const DEFAULT_ERROR_MESSAGE = '操作失败,请稍后重试'
/**
* 错误消息映射
* 将常见的错误码映射为用户友好的提示
*/
const ERROR_MESSAGES = {
400: '请求参数错误',
401: '登录已过期,请重新登录',
403: '没有权限执行此操作',
404: '请求的资源不存在',
500: '服务器错误,请稍后重试',
502: '网关错误,请稍后重试',
503: '服务暂不可用,请稍后重试',
504: '请求超时,请稍后重试'
}
/**
* 处理通用错误
* @param {Error|Object|string} error - 错误对象或错误消息
* @param {string} [customMessage] - 自定义错误消息,优先使用
*/
const handleError = (error, customMessage = null) => {
console.error('Error:', error)
// 使用自定义消息
if (customMessage) {
showFailToast(customMessage)
return
}
// 从错误对象中提取消息
let message = DEFAULT_ERROR_MESSAGE
if (typeof error === 'string') {
message = error
} else if (error && error.message) {
message = error.message
} else if (error && error.msg) {
message = error.msg
} else if (error && error.response) {
// Axios 错误
const status = error.response.status
message = ERROR_MESSAGES[status] || error.response.data?.msg || DEFAULT_ERROR_MESSAGE
}
showFailToast(message)
}
/**
* 处理 API 错误(检查 code === 1)
* @param {Object} response - API 响应对象
* @param {Function} onSuccess - 成功回调
* @param {Function} [onFailure] - 失败回调
* @returns {boolean} 是否成功
*/
const handleApiResponse = (response, onSuccess, onFailure = null) => {
const { code, data, msg } = response
if (code === 1) {
if (onSuccess) {
onSuccess(data)
}
return true
} else {
const errorMessage = msg || DEFAULT_ERROR_MESSAGE
console.error('API Error:', errorMessage)
if (onFailure) {
onFailure(errorMessage)
} else {
showFailToast(errorMessage)
}
return false
}
}
/**
* 处理异步操作错误
* @param {Function} asyncFn - 异步函数
* @param {Object} options - 配置选项
* @param {Function} [options.onSuccess] - 成功回调
* @param {Function} [options.onError] - 错误回调
* @param {string} [options.successMessage] - 成功提示消息
* @param {string} [options.errorMessage] - 错误提示消息
* @returns {Promise<boolean>} 是否成功
*/
const handleAsyncOperation = async (asyncFn, options = {}) => {
const {
onSuccess,
onError,
successMessage,
errorMessage
} = options
try {
const result = await asyncFn()
// 检查 API 响应
const { code, data, msg } = result
if (code === 1) {
if (successMessage) {
showToast(successMessage)
}
if (onSuccess) {
onSuccess(data)
}
return true
} else {
const message = errorMessage || msg || DEFAULT_ERROR_MESSAGE
showFailToast(message)
if (onError) {
onError(message)
}
return false
}
} catch (err) {
handleError(err, errorMessage)
if (onError) {
onError(err.message || DEFAULT_ERROR_MESSAGE)
}
return false
}
}
/**
* 验证并处理表单错误
* @param {Object} formData - 表单数据
* @param {Object} rules - 验证规则
* @returns {Object} 包含 isValid 和 errors 的对象
*/
const validateForm = (formData, rules) => {
const errors = {}
for (const [field, rule] of Object.entries(rules)) {
const value = formData[field]
// 必填检查
if (rule.required && (value === undefined || value === null || value === '')) {
errors[field] = rule.message || `${field} 为必填项`
continue
}
// 自定义验证
if (rule.validator && typeof rule.validator === 'function') {
const result = rule.validator(value, formData)
if (result !== true) {
errors[field] = result || rule.message || `${field} 格式不正确`
}
}
}
return {
isValid: Object.keys(errors).length === 0,
errors
}
}
return {
handleError,
handleApiResponse,
handleAsyncOperation,
validateForm,
DEFAULT_ERROR_MESSAGE,
ERROR_MESSAGES
}
}
/**
* 图片加载错误处理 composable
* 提供统一的图片加载错误处理逻辑
*
* @Date: 2026-01-18
* @Description: 统一处理图片加载失败,替换为默认头像
*/
/**
* @function useImageLoader
* @description 提供图片加载错误处理功能
* @returns {Object} 包含图片错误处理方法的对象
*/
export function useImageLoader() {
/**
* 默认头像 URL
*/
const DEFAULT_AVATAR = 'https://cdn.ipadbiz.cn/mlaj/images/default-avatar.jpeg'
/**
* 处理图片加载错误
* @param {Event} e - 图片加载错误事件对象
* @param {string} [fallbackUrl] - 自定义的备用图片 URL
*/
const handleImageError = (e, fallbackUrl = DEFAULT_AVATAR) => {
if (e.target && e.target.src) {
e.target.src = fallbackUrl
}
}
/**
* 处理图片加载错误(带重试逻辑)
* @param {Event} e - 图片加载错误事件对象
* @param {string[]} fallbackUrls - 备用图片 URL 列表,按顺序尝试
*/
const handleImageErrorWithRetry = (e, fallbackUrls) => {
if (e.target && e.target.src) {
const currentSrc = e.target.src
const currentIndex = fallbackUrls.indexOf(currentSrc)
if (currentIndex >= 0 && currentIndex < fallbackUrls.length - 1) {
// 尝试下一个备用 URL
e.target.src = fallbackUrls[currentIndex + 1]
} else if (fallbackUrls.length > 0) {
// 使用第一个或最后的备用 URL
e.target.src = fallbackUrls[0]
}
}
}
return {
handleImageError,
handleImageErrorWithRetry,
DEFAULT_AVATAR
}
}
/**
* 用户信息获取 composable
* 提供统一的用户信息获取逻辑
*
* @Date: 2026-01-18
* @Description: 统一获取用户信息,处理缓存和错误
*/
import { ref } from 'vue'
import { getUserInfoAPI } from '@/api/users'
/**
* @function useUserInfo
* @description 提供用户信息获取功能
* @returns {Object} 包含用户信息状态和方法的对象
*/
export function useUserInfo() {
// 用户信息状态
const userInfo = ref(null)
const loading = ref(false)
const error = ref(null)
/**
* 刷新用户信息
* @returns {Promise<Object>} 用户信息对象
* @throws {Error} 获取失败时抛出错误
*/
const refreshUserInfo = async () => {
loading.value = true
error.value = null
try {
const { code, data, msg } = await getUserInfoAPI()
if (code === 1) {
userInfo.value = data
// 合并用户和打卡数据
const mergedUser = {
...data.user,
...data.checkin
}
// 更新本地存储
localStorage.setItem('currentUser', JSON.stringify(mergedUser))
loading.value = false
return mergedUser
} else {
throw new Error(msg || '获取用户信息失败')
}
} catch (err) {
error.value = err.message || '获取用户信息失败'
loading.value = false
throw err
}
}
/**
* 从本地存储获取用户信息(不发起网络请求)
* @returns {Object|null} 用户信息对象,如果不存在则返回 null
*/
const getUserInfoFromLocal = () => {
try {
const savedUser = localStorage.getItem('currentUser')
return savedUser ? JSON.parse(savedUser) : null
} catch (err) {
console.error('解析本地用户信息失败:', err)
return null
}
}
/**
* 清除本地用户信息
*/
const clearUserInfo = () => {
userInfo.value = null
localStorage.removeItem('currentUser')
}
/**
* 初始化用户信息(优先从本地存储,必要时刷新)
* @param {boolean} [forceRefresh=false] - 是否强制刷新,忽略本地缓存
* @returns {Promise<Object|null>} 用户信息对象
*/
const initUserInfo = async (forceRefresh = false) => {
if (!forceRefresh) {
const localUser = getUserInfoFromLocal()
if (localUser) {
userInfo.value = localUser
return localUser
}
}
return await refreshUserInfo()
}
return {
userInfo,
loading,
error,
refreshUserInfo,
getUserInfoFromLocal,
clearUserInfo,
initUserInfo
}
}