useCollectOperation.js 3 KB
/**
 * 收藏操作 Composable
 *
 * @description 统一的收藏/取消收藏逻辑,支持乐观更新和错误回滚
 * @author Claude Code
 * @created 2026-02-05
 */

import { addAPI, delAPI } from '../api/favorite.js'
import Taro from '@tarojs/taro'
import eventBus, { Events } from '@/utils/eventBus'

/**
 * 使用收藏操作
 *
 * @description 提供统一的收藏/取消收藏功能,包含乐观更新和错误处理
 * @param {Object} options - 配置选项
 * @param {Function} [options.onSuccess] - 成功回调
 * @param {Function} [options.onError] - 错误回调
 * @returns {Object} 收藏操作方法
 *
 * @example
 * const { toggleCollect } = useCollectOperation()
 *
 * // 在组件中使用
 * const item = ref({ id: 123, collected: false })
 *
 * await toggleCollect(item, '收藏成功', '已取消收藏')
 */
export function useCollectOperation(options = {}) {
  const { onSuccess, onError } = options

  /**
   * 切换收藏状态
   * @description 统一的收藏/取消收藏操作
   * @param {Object} item - 资料项(必须包含 collected 和 id/meta_id 字段)
   * @param {string} successMsg - 成功提示文案
   * @param {string} [errorMsg='操作失败'] - 失败提示文案
   * @returns {Promise<boolean>} 操作是否成功
   */
  const toggleCollect = async (item, successMsg, errorMsg = '操作失败') => {
    try {
      // 乐观更新 UI
      const newCollectStatus = !item.collected
      item.collected = newCollectStatus

      // 获取 meta_id(优先使用 meta_id,其次使用 id)
      const metaId = item.meta_id || item.id

      // 调用 API
      const res = newCollectStatus
        ? await addAPI({ meta_id: metaId })  // 添加收藏
        : await delAPI({ meta_id: metaId })  // 取消收藏

      if (res.code === 1) {
        // API 调用成功,显示提示
        Taro.showToast({
          title: successMsg || (newCollectStatus ? '已收藏' : '已取消收藏'),
          icon: 'success',
          duration: 1000
        })

        // 发送收藏更新事件(通知收藏列表页刷新)
        eventBus.emit(Events.FAVORITES_UPDATE, {
          metaId,
          collected: newCollectStatus,
          timestamp: Date.now()
        })

        // 调用成功回调
        onSuccess?.(item, newCollectStatus)

        return true
      } else {
        // API 调用失败,回滚 UI 状态
        item.collected = !newCollectStatus
        Taro.showToast({
          title: res.msg || errorMsg,
          icon: 'none',
          duration: 2000
        })

        // 调用错误回调
        onError?.(item, res.msg)

        return false
      }
    } catch (err) {
      // 发生错误,回滚 UI 状态
      item.collected = !item.collected
      console.error('[useCollectOperation] 收藏操作失败:', err)
      Taro.showToast({
        title: '网络错误,请重试',
        icon: 'none',
        duration: 2000
      })

      // 调用错误回调
      onError?.(item, err.message)

      return false
    }
  }

  return {
    toggleCollect
  }
}