index.vue 7.43 KB
<!--
 * @Date: 2026-02-06
 * @Description: 本周热门资料页 - 使用 MaterialCard 组件
-->
<template>
  <view class="h-screen bg-[#F9FAFB] flex flex-col py-[32rpx]">
    <NavHeader title="本周热门资料" />

    <!-- 列表容器 -->
    <view
      v-if="listVisible"
      :key="listRenderKey"
      class="flex-1 min-h-0 overflow-y-auto px-[32rpx] pb-[calc(160rpx+env(safe-area-inset-bottom))] box-border"
    >
      <!-- 加载状态 -->
      <view v-if="loading && currentList.length === 0" class="flex items-center justify-center py-[60rpx]">
        <view class="loading-spinner"></view>
        <text class="ml-[16rpx] text-[#9CA3AF] text-[28rpx]">加载中...</text>
      </view>

      <view v-else class="flex flex-col gap-[24rpx]">
        <MaterialCard
          v-for="(item, index) in currentList"
          :key="item.meta_id"
          :id="item.meta_id"
          :title="item.name"
          :file-name="item.name"
          :file-size="item.size"
          :learners="item.read_people_count ? `${item.read_people_count}人学习` : ''"
          :read-people-percent="item.read_people_percent"
          :collected="item.collected"
          :extension="item.extension"
          :download-url="item.downloadUrl"
          :style="{ animationDelay: `${index * 50}ms` }"
          @collect-changed="handleCollectChanged(item, $event)"
        />

        <!-- 空状态 -->
        <view v-if="currentList.length === 0 && !loading && !loadingMore">
          <nut-empty description="暂无热门资料" image="empty" />
        </view>

        <!-- 加载更多提示 -->
        <view v-if="currentList.length > 0" class="flex items-center justify-center py-[40rpx]">
          <view v-if="loadingMore" class="flex items-center">
            <view class="loading-spinner-small"></view>
            <text class="ml-[12rpx] text-[#9CA3AF] text-[24rpx]">加载中...</text>
          </view>
          <view v-else-if="!hasMore" class="text-[#9CA3AF] text-[24rpx]">
            没有更多了
          </view>
        </view>
      </view>
    </view>
  </view>
</template>

<script setup>
import { ref } from 'vue'
import Taro, { useLoad, useReachBottom } from '@tarojs/taro'
import NavHeader from '@/components/NavHeader.vue'
import MaterialCard from '@/components/MaterialCard.vue'
import { weekHotAPI } from '@/api/file'

const listVisible = ref(true)
const listRenderKey = ref(0)
const loading = ref(false)
const loadingMore = ref(false)  // 加载更多状态
const hasMore = ref(true)  // 是否还有更多数据
const currentList = ref([])
const currentPage = ref(0)  // 当前页码(从0开始)
const pageSize = 20  // 每页数量

/**
 * 处理收藏状态改变
 *
 * @description 当用户点击收藏按钮时,更新本地状态
 * @param {Object} item - 资料对象
 * @param {Object} newStatus - 新的状态
 */
const handleCollectChanged = (item, newStatus) => {
  console.log('[Week Hot] 收藏状态改变:', item.name, newStatus.collected)
  // 找到对应的项并更新状态
  const material = currentList.value.find(m => m.meta_id === item.meta_id)
  if (material) {
    material.collected = newStatus.collected
  }
}

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

    console.log('[Week Hot] 请求参数:', params)

    // 调用接口
    const res = await weekHotAPI(params)

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

      // 处理列表数据
      if (res.data.list?.length) {
        // 直接映射为 MaterialCard 需要的格式
        const listData = res.data.list.map(item => {
          const fileName = item.name || '未命名文件'
          const extension = item.extension || fileName.split('.').pop()?.toLowerCase() || ''

          return {
            meta_id: item.meta_id,
            name: fileName,
            size: item.size || '',
            downloadUrl: item.src,
            extension: extension,
            collected: item.is_favorite === '1' || item.is_favorite === 1 || item.is_favorite === true,
            read_people_count: item.read_people_count,
            read_people_percent: item.read_people_percent
          }
        })

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

        // 判断是否还有更多数据
        // 如果返回的数据量少于请求的量,说明没有更多了
        hasMore.value = listData.length >= params.limit
      } else {
        // 没有数据了
        if (isLoadMore) {
          hasMore.value = false
        } else {
          currentList.value = []
        }
      }
    } else {
      Taro.showToast({
        title: res.msg || '获取热门资料失败',
        icon: 'none',
        duration: 2000
      })
    }
  } catch (error) {
    console.error('[Week Hot] 获取热门资料失败:', error)
    Taro.showToast({
      title: '加载失败',
      icon: 'error',
      duration: 2000
    })
  } finally {
    if (isLoadMore) {
      loadingMore.value = false
    } else {
      loading.value = false
    }
  }
}

/**
 * 页面加载时获取数据
 */
useLoad(async (options) => {
  console.log('[Week Hot] 页面参数:', options)

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

  // 获取本周热门资料列表
  await fetchWeekHotList({ page: 0, limit: pageSize })
})

/**
 * 触底加载更多
 * @description 使用防抖避免频繁触发
 */
let loadMoreTimer = null
useReachBottom(() => {
  // 如果正在加载或没有更多数据,不执行
  if (loadingMore.value || !hasMore.value) {
    return
  }

  // 防抖:300ms 内只触发一次
  if (loadMoreTimer) {
    clearTimeout(loadMoreTimer)
  }

  loadMoreTimer = setTimeout(async () => {
    console.log('[Week Hot] 触底加载更多')

    // 页码 +1
    currentPage.value += 1

    // 加载下一页数据
    await fetchWeekHotList(
      { page: currentPage.value, limit: pageSize },
      true  // 标记为加载更多
    )
  }, 300)
})
</script>

<style lang="less">
/* 列表项进入动画 */
@keyframes slideIn {
  from {
    opacity: 0;
    transform: translateY(20rpx);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.material-item {
  animation: slideIn 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
}

/* 加载动画 */
@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

.loading-spinner {
  width: 40rpx;
  height: 40rpx;
  border: 4rpx solid #E5E7EB;
  border-top-color: #4CAF50;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

.loading-spinner-small {
  width: 32rpx;
  height: 32rpx;
  border: 3rpx solid #E5E7EB;
  border-top-color: #4CAF50;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

/* 多行文本省略 */
.line-clamp-2 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  word-break: break-all;
}
</style>