index.vue 8.3 KB
<!--
 * @Date: 2026-02-08
 * @Description: 我的收藏 - 使用 LoadMoreList 组件重构版本
-->
<template>
  <view class="h-screen bg-gray-50 flex flex-col overflow-hidden">
    <view class="bg-gray-50 z-10">
      <NavHeader title="我的收藏" />
    </view>

    <!-- LoadMoreList 组件 -->
    <LoadMoreList
      :list="currentList"
      :page="currentPage"
      :page-size="pageSize"
      :has-more="hasMore"
      :loading="loading"
      :loading-more="loadingMore"
      key-field="meta_id"
      :show-header="false"
      :has-footer="false"
      @load-more="handleLoadMore"
    >
      <!-- 列表项 -->
      <template #item="{ item }">
        <view class="bg-white rounded-[24rpx] p-[24rpx] mb-[24rpx] shadow-sm favorite-item">
          <!-- Header with Icon -->
          <view class="flex gap-[24rpx] mb-[12rpx]">
            <!-- Document Icon -->
            <view class="w-[88rpx] h-[88rpx] mr-[24rpx] flex-shrink-0 flex items-center justify-center bg-gradient-to-br from-blue-50 to-blue-100 rounded-[20rpx] shadow-inner self-start">
              <image :src="getDocumentIcon(item.name)" class="w-[48rpx] h-[48rpx]" mode="aspectFit" />
            </view>

            <!-- Title -->
            <view class="flex-1 min-w-0">
              <view class="text-[30rpx] font-bold text-gray-900 leading-normal mb-1">{{ item.name }}</view>
              <view class="text-gray-400 text-[22rpx]">{{ item.size }}</view>
            </view>
          </view>

          <!-- Date -->
          <view class="text-gray-500 text-[24rpx] mb-[20rpx] text-right">
            <text>{{ item.created_time }}</text>
          </view>

          <!-- Divider -->
          <view class="h-[1rpx] bg-gray-100 mb-[20rpx]"></view>

          <!-- Actions -->
          <ListItemActions
            :viewable="true"
            :deletable="true"
            :item-id="String(item.meta_id)"
            @view="viewFile({...item, fileName: item.name, downloadUrl: item.src})"
            @delete="onDelete(item)"
          />
        </view>
      </template>
    </LoadMoreList>
  </view>
</template>

<script setup>
import { ref, Ref, onMounted, onUnmounted } from 'vue'
import Taro, { useLoad } from '@tarojs/taro'
import LoadMoreList from '@/components/list/LoadMoreList'
import { useFileOperation } from '@/composables/useFileOperation'
import { getDocumentIcon } from '@/utils/documentIcons'
import NavHeader from '@/components/navigation/NavHeader.vue'
import ListItemActions from '@/components/list/ListItemActions/index.vue'
import { listAPI, delAPI } from '@/api/favorite'
import { mockFavoriteListAPI } from '@/utils/mockData'
import eventBus, { Events } from '@/utils/eventBus'

// ⚠️ MOCK 数据开关 - 开发环境使用 mock 数据,生产环境使用真实 API
const USE_MOCK_DATA = process.env.NODE_ENV === 'development'

const { viewFile } = useFileOperation()

/**
 * 当前列表数据
 * @type {Ref<Array<any>>}
 */
const currentList = ref([])

/**
 * 当前页码(从0开始)
 * @type {Ref<number>}
 */
const currentPage = ref(0)

/**
 * 每页数量
 * @type {number}
 */
const pageSize = 20

/**
 * 是否还有更多数据
 * @type {Ref<boolean>}
 */
const hasMore = ref(true)

/**
 * 首次加载状态
 * @type {Ref<boolean>}
 */
const loading = ref(false)

/**
 * 加载更多状态
 * @type {Ref<boolean>}
 */
const loadingMore = ref(false)

/**
 * 获取收藏列表
 *
 * @param {Object} params - 请求参数
 * @param {number} params.page - 页码(从0开始)
 * @param {number} params.limit - 每页数量
 * @param {boolean} isLoadMore - 是否为加载更多
 * @returns {Promise<void>}
 */
const fetchFavoritesList = async (params = {}, isLoadMore = false) => {
  try {
    // 如果是加载更多,使用 loadingMore 状态,否则使用 loading 状态
    if (isLoadMore) {
      loadingMore.value = true
    } else {
      loading.value = true
    }

    console.log('[Favorites] 请求参数:', params)
    console.log('[Favorites] 使用 Mock 数据:', USE_MOCK_DATA)

    // 根据开关选择使用真实 API 或 Mock 数据
    const res = USE_MOCK_DATA
      ? await mockFavoriteListAPI(params)
      : await listAPI({
          page: String(params.page),
          limit: String(params.limit)
        })

    if (res.code === 1 && res.data && res.data.list) {
      console.log('[Favorites] 数据:', res.data.list)

      if (isLoadMore) {
        // 加载更多:追加数据
        currentList.value = [...currentList.value, ...res.data.list]
      } else {
        // 首次加载或刷新:替换数据
        currentList.value = res.data.list
      }

      // 判断是否还有更多数据
      hasMore.value = res.data.list.length >= params.limit
    } else {
      if (!isLoadMore) {
        currentList.value = []
      }
      Taro.showToast({
        title: res.msg || '获取收藏列表失败',
        icon: 'none'
      })
    }
  } catch (err) {
    console.error('[Favorites] 获取收藏列表失败:', err)
    if (!isLoadMore) {
      currentList.value = []
    }
    Taro.showToast({
      title: '网络错误,请稍后重试',
      icon: 'none'
    })
  } finally {
    if (isLoadMore) {
      loadingMore.value = false
    } else {
      loading.value = false
    }
  }
}

/**
 * 删除收藏
 *
 * @param {Object} item - 收藏项
 */
const onDelete = async (item) => {
  if (USE_MOCK_DATA) {
    // Mock 模式:直接从列表中移除
    Taro.showModal({
      title: '提示',
      content: '确定要删除该收藏吗?(Mock模式)',
      success: (res) => {
        if (res.confirm) {
          const index = currentList.value.findIndex(i => i.meta_id === item.meta_id)
          if (index !== -1) {
            currentList.value.splice(index, 1)
          }
          Taro.showToast({ title: '已删除', icon: 'success' })
        }
      }
    })
    return
  }

  Taro.showModal({
    title: '提示',
    content: '确定要删除该收藏吗?',
    success: async (res) => {
      if (res.confirm) {
        try {
          const delRes = await delAPI({ meta_id: item.meta_id })

          if (delRes.code === 1) {
            // 从列表中移除
            const index = currentList.value.findIndex(i => i.meta_id === item.meta_id)
            if (index !== -1) {
              currentList.value.splice(index, 1)
            }

            Taro.showToast({ title: '已删除', icon: 'success' })
          } else {
            Taro.showToast({
              title: delRes.msg || '删除失败',
              icon: 'none'
            })
          }
        } catch (err) {
          console.error('[Favorites] 删除收藏失败:', err)
          Taro.showToast({
            title: '网络错误,请稍后重试',
            icon: 'none'
          })
        }
      }
    }
  })
}

/**
 * 处理加载更多事件
 *
 * @param {number} page - 下一页页码
 * @returns {Promise<void>}
 */
const handleLoadMore = async (page) => {
  console.log('[Favorites] 加载更多,页码:', page)

  // 更新页码
  currentPage.value = page

  // 加载下一页数据
  await fetchFavoritesList(
    { page: page, limit: pageSize },
    true  // 标记为加载更多
  )
}

/**
 * @description 刷新收藏列表
 * @returns {Promise<void>}
 */
const refreshList = async () => {
  console.log('[Favorites] 刷新列表')

  // 重置分页状态
  currentPage.value = 0
  hasMore.value = true

  // 重新获取列表
  await fetchFavoritesList({ page: 0, limit: pageSize })
}

/**
 * 页面加载时获取列表(只执行一次)
 */
useLoad(() => {
  console.log('[Favorites] 页面加载,获取列表')

  // 重置分页状态
  currentPage.value = 0
  hasMore.value = true

  // 获取收藏列表
  fetchFavoritesList({ page: 0, limit: pageSize })
})

/**
 * @description 监听收藏更新事件
 */
onMounted(() => {
  console.log('[Favorites] 注册事件监听')

  // 监听收藏更新事件(收藏/取消收藏)
  const unsubscribe = eventBus.on(Events.FAVORITES_UPDATE, async (data) => {
    console.log('[Favorites] 收到收藏更新事件:', data)

    // 刷新列表
    await refreshList()
  })

  // 组件卸载时取消监听
  onUnmounted(() => {
    unsubscribe()
    console.log('[Favorites] 取消事件监听')
  })
})
</script>

<style lang="less">
.favorite-item {
  transition: all 0.3s ease;

  &:active {
    transform: scale(0.98);
  }
}

/* LoadMoreList 组件已内置样式,包括 Loading 和 Empty State */
</style>