index.vue 11 KB
<!--
 * @Date: 2026-01-31
 * @Description: 资料列表页
-->
<template>
  <div class="min-h-screen bg-[#F9FAFB] pb-[calc(160rpx+env(safe-area-inset-bottom))]">
    <!-- Navigation Header -->
    <NavHeader title="资料列表" />

    <!-- Search Bar -->
    <div class="px-[32rpx] mt-[32rpx]">
      <div class="bg-white rounded-[20rpx] flex items-center px-[32rpx] py-[24rpx] shadow-sm border border-gray-50">
        <IconFont name="search" size="20" color="#9CA3AF" class="mr-[16rpx]" />
        <input v-model="searchValue" type="text" placeholder="搜索资料..."
          class="flex-1 text-[28rpx] text-[#1F2937] placeholder-gray-400 bg-transparent outline-none"
          @confirm="onSearch" />
      </div>
    </div>

    <!-- Category Tabs -->
    <!-- 根据是否有分类数据决定是否显示 tab -->
    <div v-if="categories && categories.length > 0" class="px-[32rpx] mt-[32rpx]">
      <div class="flex overflow-x-auto no-scrollbar mb-[40rpx] space-x-[24rpx]">
        <div v-for="(category, index) in categories" :key="index"
          class="px-[32rpx] py-[16rpx] rounded-full text-[28rpx] whitespace-nowrap transition-colors"
          :class="activeCategoryIndex === index ? 'bg-[#2563EB] text-white' : 'bg-[#F3F4F6] text-[#6B7280]'"
          @tap="activeCategoryIndex = index">
          {{ category.name }}
        </div>
      </div>
    </div>

    <!-- Material List -->
    <div class="px-[32rpx] mt-[32rpx]">
      <div class="flex flex-col gap-[24rpx]">
        <div v-for="(item, index) in list" :key="index"
          class="material-item bg-white rounded-[24rpx] p-[24rpx] shadow-sm transition-all duration-200 border border-gray-50 flex flex-row"
          :style="{ animationDelay: `${index * 50}ms` }">

          <!-- 左侧图标 -->
          <div
            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.fileName)"
              class="w-[48rpx] h-[48rpx]"
              mode="aspectFit"
            />
          </div>

          <!-- 内容区域 -->
          <div class="flex-1 min-w-0">
            <h3 class="text-[#1F2937] text-[30rpx] font-bold leading-[1.4] line-clamp-2 mb-[8rpx]">
              {{ item.title }}
            </h3>
            <p class="text-[#6B7280] text-[24rpx] leading-[1.4] line-clamp-1 mb-[16rpx]">
              {{ item.desc }}
            </p>

            <!-- 文件信息 -->
            <div class="flex items-center gap-[12rpx] mb-[20rpx]">
              <span
                class="inline-flex items-center justify-center px-[12rpx] py-[4rpx] bg-gray-100 text-gray-500 text-[20rpx] font-medium rounded-[8rpx]">
                {{ getDocumentLabel(item.fileName) }}
              </span>
              <span class="text-[#9CA3AF] text-[22rpx]">
                {{ item.size }}
              </span>
            </div>

            <!-- 分割线 -->
            <view class="h-[1rpx] bg-gray-100 my-[20rpx]"></view>

            <!-- 操作按钮 -->
            <ListItemActions
              :viewable="true"
              :collectable="true"
              :collected="item.collected"
              @view="onView(item)"
              @collect="toggleCollect(item)"
              @delete="onDelete(item)"
            />
          </div>
        </div>
      </div>
    </div>

    <!-- Tab Bar -->
    <!-- <TabBar /> -->
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useLoad } from '@tarojs/taro'
import NavHeader from '@/components/NavHeader.vue'
import TabBar from '@/components/TabBar.vue'
import IconFont from '@/components/IconFont.vue'
import ListItemActions from '@/components/ListItemActions/index.vue'
import { useListItemClick, ListType } from '@/composables/useListItemClick'
import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons'
import Taro from '@tarojs/taro'

const searchValue = ref('')
const categoryId = ref('')

/**
 * 资料列表数据
 *
 * @description 包含文件信息、图标、收藏状态等完整资料信息
 */
const list = ref([
  {
    title: '2024年保险代理人考试大纲.pdf',
    desc: '最新考试范围与重点解析',
    size: '2.1MB',
    iconName: 'order',
    iconColor: '#EF4444',
    collected: true,
    fileName: '2024年保险代理人考试大纲.pdf',
    downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
  },
  {
    title: '历年真题汇总及解析.pdf',
    desc: '2019-2023年真题完整版',
    size: '5.3MB',
    iconName: 'order',
    iconColor: '#EF4444',
    collected: false,
    fileName: '历年真题汇总及解析.pdf',
    downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
  },
  {
    title: '考试技巧与经验分享.pdf',
    desc: '高分学员备考心得',
    size: '1.8MB',
    iconName: 'order',
    iconColor: '#EF4444',
    collected: false,
    fileName: '考试技巧与经验分享.pdf',
    downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
  },
  {
    title: '保险基础知识速记手册.pdf',
    desc: '核心知识点快速记忆',
    size: '3.2MB',
    iconName: 'order',
    iconColor: '#EF4444',
    collected: false,
    fileName: '保险基础知识速记手册.pdf',
    downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
  },
  {
    title: '模拟试卷10套及答案.pdf',
    desc: '考前冲刺模拟练习',
    size: '4.5MB',
    iconName: 'order',
    iconColor: '#EF4444',
    collected: true,
    fileName: '模拟试卷10套及答案.pdf',
    downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
  },
  {
    title: '法律法规重点条款解读.pdf',
    desc: '保险相关法规详解',
    size: '2.8MB',
    iconName: 'order',
    iconColor: '#EF4444',
    collected: false,
    fileName: '法律法规重点条款解读.pdf',
    downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
  },
  {
    title: '考试常见易错题分析.pdf',
    desc: '高频错题归纳总结',
    size: '1.5MB',
    iconName: 'order',
    iconColor: '#EF4444',
    collected: false,
    fileName: '考试常见易错题分析.pdf',
    downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
  },
  {
    title: '案例分析题库及解答.pdf',
    desc: '实务案例精选练习',
    size: '3.9MB',
    iconName: 'order',
    iconColor: '#EF4444',
    collected: false,
    fileName: '案例分析题库及解答.pdf',
    downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
  },
  {
    title: '考前冲刺复习资料.pdf',
    desc: '最后一周复习要点',
    size: '2.3MB',
    iconName: 'order',
    iconColor: '#EF4444',
    collected: false,
    fileName: '考前冲刺复习资料.pdf',
    downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
  },
  {
    title: '考场注意事项及答题技巧.pdf',
    desc: '应试策略与时间分配',
    size: '1.2MB',
    iconName: 'order',
    iconColor: '#EF4444',
    collected: false,
    fileName: '考场注意事项及答题技巧.pdf',
    downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
  }
])

/**
 * 页面加载时接收参数
 *
 * @description 使用 Taro 的 useLoad hook 接收路由参数
 */
useLoad((options) => {
  console.log('[Material List] 页面参数:', options)

  // 接收 categoryId 参数
  if (options.categoryId) {
    categoryId.value = options.categoryId
    console.log('[Material List] 分类 ID:', categoryId.value)

    // 根据 categoryId 加载对应的资料列表
    loadMaterialsByCategory(categoryId.value)
  } else {
    console.log('[Material List] 无分类 ID,显示所有资料')
  }
})

/**
 * 根据分类 ID 加载资料列表
 *
 * @description 根据 categoryId 从 API 获取对应的资料列表
 * @param {string} categoryId - 分类 ID
 */
const loadMaterialsByCategory = async (id) => {
  try {
    Taro.showLoading({ title: '加载中...', mask: true })

    // TODO: 调用真实的 API 接口
    // const res = await getMaterialsByCategoryAPI({ categoryId: id })
    // if (res.code === 1) {
    //   list.value = res.data
    // }

    // 模拟 API 调用延迟
    await new Promise(resolve => setTimeout(resolve, 500))

    console.log(`[Material List] 已加载分类 "${id}" 的资料列表`)

    Taro.hideLoading()
  } catch (error) {
    console.error('[Material List] 加载资料列表失败:', error)
    Taro.showToast({
      title: '加载失败,请重试',
      icon: 'none'
    })
  } finally {
    Taro.hideLoading()
  }
}

/**
 * 搜索处理函数
 *
 * @description 处理用户搜索操作
 */
const onSearch = () => {
  console.log('Searching for:', searchValue.value)
  console.log('当前分类:', categoryId.value)
  // TODO: 根据 categoryId 和 searchValue 进行搜索
}

/**
 * 使用文件列表点击处理器
 *
 * @description 配置为文件类型列表,点击时打开文件预览
 */
const { handleClick: onView } = useListItemClick({
  listType: ListType.FILE,
  onAfterClick: (item) => {
    console.log('用户打开了资料:', item.title)
  }
})

/**
 * 切换收藏状态
 *
 * @description 切换资料的收藏状态
 * @param {Object} item - 资料项
 */
const toggleCollect = (item) => {
  item.collected = !item.collected
  Taro.showToast({
    title: item.collected ? '已收藏' : '已取消收藏',
    icon: 'success',
    duration: 1000
  })
}

/**
 * 删除资料
 */
const onDelete = (item) => {
  Taro.showModal({
    title: '提示',
    content: '确定要删除该资料吗?',
    success: (res) => {
      if (res.confirm) {
        const index = list.value.findIndex(i => i.title === item.title)
        if (index !== -1) {
          list.value.splice(index, 1)
          Taro.showToast({ title: '已删除', icon: 'success' })
        }
      }
    }
  })
}
</script>

<style lang="less" scoped>
@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;
}
</style>