index.vue 9.83 KB
<template>
  <view class="min-h-screen bg-[#F9FAFB] pb-[200rpx] flex flex-col items-center">
    <!-- Header -->
    <NavHeader title="我的收藏" />

    <!-- Tabs Section -->
    <!-- Background image from design -->
    <view
      class="w-[706rpx] h-[144rpx] mt-[40rpx] bg-white flex items-center px-[32rpx] rounded-[24rpx]">
      <div class="flex overflow-x-auto no-scrollbar space-x-[24rpx] w-full items-center">
        <div v-for="(tab, index) in tabs" :key="index"
          class="px-[32rpx] py-[16rpx] rounded-full text-[28rpx] whitespace-nowrap transition-colors"
          :class="activeTab === tab.key ? 'bg-[#007AFF] text-white' : 'bg-transparent text-[#6B7280]'"
          @click="activeTab = tab.key">
          {{ tab.title }}
        </div>
      </div>
    </view>

    <!-- List Section -->
    <view class="w-[706rpx] mt-[32rpx] flex flex-col gap-[24rpx]">
      <view v-for="(item, index) in filteredList" :key="index"
        class="bg-white rounded-[24rpx] p-[32rpx] shadow-sm flex items-center justify-between">
        <view class="flex items-center flex-1 mr-[24rpx]" @tap="onView(item)">
          <!-- Icon (Text Type Icons as requested) -->
          <view class="w-[80rpx] h-[80rpx] rounded-[16rpx] flex items-center justify-center mr-[24rpx]"
            :class="item.iconBgClass">
            <IconFont :name="item.icon" size="24" :color="item.iconColor" />
          </view>

          <!-- Text Content -->
          <view class="flex flex-col flex-1">
            <view class="text-[32rpx] text-[#1F2937] font-bold mb-[8rpx] line-clamp-1">{{ item.title }}</view>
            <view class="flex flex-col items-start gap-[8rpx]">
              <view class="bg-gray-100 px-[12rpx] py-[4rpx] rounded-[8rpx] text-[24rpx] text-[#9CA3AF] mt-1">{{ item.category
                }}</view>
              <view class="text-[24rpx] text-gray-400 mt-2">{{ item.date }}</view>
            </view>
          </view>
        </view>

        <!-- Action Buttons -->
        <view class="flex items-center gap-[16rpx]">
          <!-- Delete Button -->
          <view class="p-[10rpx]" @tap.stop="onDelete(item)">
            <IconFont name="del" size="18" color="#999" />
          </view>
        </view>
      </view>

      <!-- Empty State -->
      <view v-if="filteredList.length === 0"
        class="flex flex-col items-center justify-center py-[100rpx] text-gray-400">
        <IconFont name="star" size="48" class="mb-[24rpx] opacity-50" />
        <text class="text-[28rpx]">暂无收藏内容</text>
      </view>
    </view>

    <!-- TabBar -->
    <TabBar current="me" />
  </view>
</template>

<script setup>
import { ref, computed } from 'vue'
import { useGo } from '@/hooks/useGo'
import IconFont from '@/components/IconFont.vue'
import TabBar from '@/components/TabBar.vue'
import NavHeader from '@/components/NavHeader.vue'
import Taro from '@tarojs/taro'

const go = useGo()
const activeTab = ref('all')

const tabs = [
  { title: '全部', key: 'all' },
  { title: '入职培训', key: 'onboarding' },
  { title: '签单相关', key: 'signing' },
  { title: '产品知识', key: 'product' }
]

const list = ref([
  {
    id: 1,
    title: '新员工入职培训手册.pdf',
    category: '入职培训',
    date: '2024-01-15',
    type: 'onboarding',
    icon: 'Order', // Represents a document
    iconColor: '#EF4444', // Red for PDF
    iconBgClass: 'bg-red-50',
    // 使用真实的可下载 PDF 文件(W3C 测试文件)
    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'
  },
  {
    id: 2,
    title: '保险产品销售话术.docx',
    category: '签单相关',
    date: '2024-01-14',
    type: 'signing',
    icon: 'Order', // Represents a document
    iconColor: '#2563EB', // Blue for Word
    iconBgClass: 'bg-blue-50',
    // 使用真实的可下载图片(作为文档示例)
    downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/%E8%80%81%E6%9D%A5%E8%B5%9B%E9%9A%90%E7%A7%81%E6%94%BF%E7%AD%96.docx'
  },
  {
    id: 3,
    title: '重疾险产品知识详解.pptx',
    category: '产品知识',
    date: '2024-01-13',
    type: 'product',
    icon: 'Order', // Represents a document
    iconColor: '#F59E0B', // Orange for PPT
    iconBgClass: 'bg-orange-50',
    // 使用另一个图片作为示例
    downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/%E8%82%A1%E5%88%A4%E5%90%88%E5%8F%8B%E7%94%A8%E7%9F%A5%E8%AF%86%E8%AF%B4%E6%98%8E20240112110417414.pptx'
  },
  {
    id: 4,
    title: '2024年最新保险政策解读.txt',
    category: '政策解读',
    date: '2024-01-12',
    type: 'other',
    icon: 'Edit', // Represents text
    iconColor: '#10B981', // Green for TXT
    iconBgClass: 'bg-green-50',
    downloadUrl: '' // 空下载地址,用于测试无地址情况
  }
])

const filteredList = computed(() => {
  if (activeTab.value === 'all') return list.value
  return list.value.filter(item => item.type === activeTab.value)
})

const onView = (item) => {
  // 检查是否有下载地址
  if (!item.downloadUrl) {
    Taro.showToast({
      title: '该文件暂无查看地址',
      icon: 'none',
      duration: 2000
    })
    return
  }

  // 显示加载提示
  Taro.showLoading({
    title: '打开中...',
    mask: true
  })

  // 下载并打开文件
  downloadAndOpenFile(item)
}

// 打开文件的通用函数
const openFile = async (filePath, item) => {
  try {
    await Taro.openDocument({
      filePath: filePath,
      showMenu: true, // 显示右上角菜单,用户可以转发、保存等
      success: () => {
        console.log('文件打开成功')
        // 文件打开后,延迟提示用户如果看不到内容该如何操作
        const fileExt = item.title.split('.').pop()?.toLowerCase() || ''
        const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls']

        if (unsupportedFormats.includes(fileExt)) {
          setTimeout(() => {
            Taro.showToast({
              title: '如无法预览,请使用右上角菜单分享',
              icon: 'none',
              duration: 3000
            })
          }, 1500)
        }
      },
      fail: (err) => {
        console.error('打开文件失败:', err)

        // 获取文件扩展名
        const fileExt = item.title.split('.').pop()?.toLowerCase() || ''

        // 根据文件类型给出提示
        let message = '文件打开失败'
        let suggestion = ''

        if (['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls'].includes(fileExt)) {
          message = '暂不支持预览 Office 文档'
          suggestion = '\n\n建议:\n1. 点击右上角"..."菜单\n2. 选择"发送给朋友"\n3. 在电脑或支持的应用中打开'
        } else if (['pdf'].includes(fileExt)) {
          message = 'PDF 文件打开失败'
          suggestion = '\n\n文件可能已损坏,请联系管理员'
        } else {
          message = `暂不支持预览 ${fileExt.toUpperCase()} 格式文件`
          suggestion = '\n\n请在电脑或其他应用中打开'
        }

        Taro.showModal({
          title: '提示',
          content: message + suggestion,
          showCancel: false,
          confirmText: '我知道了'
        })
      }
    })
  } catch (error) {
    console.error('打开文件异常:', error)
    Taro.showToast({
      title: '打开文件失败',
      icon: 'none',
      duration: 2000
    })
  }
}

const onDelete = (item) => {
  Taro.showModal({
    title: '提示',
    content: '确定要删除该收藏吗?',
    success: (res) => {
      if (res.confirm) {
        list.value = list.value.filter(i => i.id !== item.id)
        Taro.showToast({ title: '已删除', icon: 'success' })
      }
    }
  })
}

// 下载并打开文件的内部函数
const downloadAndOpenFile = async (item) => {
  try {
    // 下载文件
    const downloadResult = await Taro.downloadFile({
      url: item.downloadUrl
    })

    // 检查下载结果
    if (downloadResult.statusCode !== 200) {
      throw new Error(`打开失败: HTTP ${downloadResult.statusCode}`)
    }

    if (!downloadResult.tempFilePath) {
      throw new Error('打开失败: 未获取到文件')
    }

    // 隐藏加载提示
    Taro.hideLoading()

    // 获取文件扩展名
    const fileExt = item.title.split('.').pop()?.toLowerCase() || ''

    // 微信小程序对 Office 文档支持有限,提前提示用户
    const unsupportedFormats = ['docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls']

    if (unsupportedFormats.includes(fileExt)) {
      // 对于 Office 文档,先提示用户,但仍尝试打开
      Taro.showModal({
        title: '预览提示',
        content: `小程序对 ${fileExt.toUpperCase()} 文档的预览支持有限,如果显示为空白,请点击右上角"..."菜单,选择"发送给朋友"后在电脑或其他应用中打开。\n\n是否继续尝试预览?`,
        confirmText: '继续',
        cancelText: '取消',
        success: (modalRes) => {
          if (modalRes.confirm) {
            openFile(downloadResult.tempFilePath, item)
          }
        }
      })
    } else {
      // 其他格式直接打开
      await openFile(downloadResult.tempFilePath, item)
    }
  } catch (error) {
    // 确保隐藏加载提示
    Taro.hideLoading()

    console.error('打开文件出错:', error)

    // 根据错误类型显示不同的提示
    let errorMessage = '打开失败,请重试'
    if (error.errMsg && error.errMsg.includes('network')) {
      errorMessage = '网络连接失败,请检查网络'
    } else if (error.errMsg && error.errMsg.includes('TLS')) {
      errorMessage = '安全连接失败,请检查网络'
    }

    Taro.showToast({
      title: errorMessage,
      icon: 'none',
      duration: 2000
    })
  }
}
</script>

<style lang="less">
.no-scrollbar::-webkit-scrollbar {
  display: none;
}

.no-scrollbar {
  -ms-overflow-style: none;
  scrollbar-width: none;
}
</style>