index.vue 8.81 KB
<!--
 * @Date: 2026-01-30
 * @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>

    <!-- 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 flex items-start active:scale-[0.98] transition-all duration-200 border border-gray-50"
          :style="{ animationDelay: `${index * 50}ms` }" @click="onView(item)">

          <!-- 左侧图标 -->
          <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">
            <IconFont :name="item.iconName || 'order'" size="32" :color="item.iconColor || '#2563EB'" />
          </div>

          <!-- 内容区域 -->
          <div class="flex-1 min-w-0">
            <div class="flex justify-between items-start gap-[16rpx]">
              <div class="flex-1 pr-[8rpx]">
                <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>

              <!-- 收藏图标 -->
              <div
                class="heart-btn w-[64rpx] h-[64rpx] -mt-[12rpx] -mr-[12rpx] flex items-center justify-center rounded-full active:bg-gray-50 transition-colors"
                @click.stop="toggleCollect(item)">
                <div :class="{ 'is-collected': item.collected }" class="transform transition-transform duration-300">
                  <IconFont :name="item.collected ? 'heart-fill' : 'heart'" size="24"
                    :color="item.collected ? '#EF4444' : '#D1D5DB'" />
                </div>
              </div>
            </div>

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

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

<script setup>
import { ref } from 'vue'
import NavHeader from '@/components/NavHeader.vue'
import TabBar from '@/components/TabBar.vue'
import IconFont from '@/components/IconFont.vue'
import { useListItemClick, ListType } from '@/composables/useListItemClick'

const searchValue = 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%8E%E4%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%8E%E4%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%8E%E4%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%8E%E4%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%8E%E4%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%8E%E4%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%8E%E4%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.9MB',
    iconName: 'order',
    iconColor: '#EF4444',
    collected: false,
    fileName: '案例分析题库及解答.pdf',
    downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%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%8E%E4%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%8E%E4%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'
  }
])

/**
 * 搜索处理函数
 *
 * @description 处理用户搜索操作
 */
const onSearch = () => {
  console.log('Searching for:', searchValue.value)
}

/**
 * 切换收藏状态
 *
 * @description 切换资料的收藏状态
 * @param {Object} item - 资料项
 */
const toggleCollect = (item) => {
  item.collected = !item.collected
}

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

/* 收藏成功的动画 */
.is-collected {
  animation: heartBeat 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

@keyframes heartBeat {
  0% {
    transform: scale(1);
  }

  14% {
    transform: scale(1.3);
  }

  28% {
    transform: scale(1);
  }

  42% {
    transform: scale(1.3);
  }

  70% {
    transform: scale(1);
  }
}
</style>