index.vue 6.15 KB
<!--
 * @Date: 2026-01-30
 * @Description: 产品详情页
-->
<template>
  <div class="min-h-screen bg-[#F9FAFB] pb-[calc(160rpx+env(safe-area-inset-bottom))]">
    <NavHeader title="产品详情" />

    <!-- Loading State -->
    <div v-if="loading" class="flex items-center justify-center h-screen">
      <div class="flex flex-col items-center">
        <div class="w-[64rpx] h-[64rpx] border-4 border-gray-200 border-t-blue-600 rounded-full animate-spin mb-[24rpx]"></div>
        <text class="text-gray-600 text-[28rpx]">加载中...</text>
      </div>
    </div>

    <!-- Content -->
    <div v-else>
      <!-- Banner Image -->
      <div class="w-full h-[420rpx] relative">
        <img
          class="w-full h-full object-cover"
          :src="productDetail.cover_image || bannerImage"
          mode="aspectFill"
        />
        <div class="absolute top-[32rpx] right-[32rpx] flex items-center gap-[16rpx]">
          <!-- Hot Tag -->
          <div
            v-if="productDetail.recommend === 'hot'"
            class="bg-red-500 text-white text-[24rpx] px-[20rpx] py-[10rpx] rounded-full shadow-sm backdrop-blur-sm bg-opacity-90"
          >
            热卖
          </div>
        </div>
      </div>

      <!-- Product Header -->
      <div class="relative mt-[-40rpx] bg-white rounded-t-[40rpx] px-[40rpx] pt-[48rpx] pb-[40rpx] z-10">
        <h1 class="text-[#1F2937] text-[44rpx] font-bold leading-[1.2] mb-[24rpx]">
          {{ productDetail.product_name || '产品详情' }}
        </h1>

        <!-- 动态标签 -->
        <div v-if="productDetail.tags && productDetail.tags.length" class="flex flex-wrap gap-[16rpx]">
          <div
            v-for="tag in productDetail.tags"
            :key="tag.id"
            class="rounded-[8rpx] px-[16rpx] py-[6rpx]"
            :style="{
              backgroundColor: tag.bg_color,
              color: tag.text_color
            }"
          >
            <span class="text-[24rpx]">{{ tag.name }}</span>
          </div>
        </div>
      </div>

      <!-- Product Description (富文本) -->
      <div v-if="productDetail.product_description" class="px-[32rpx] mt-[32rpx]">
        <div class="bg-white rounded-[32rpx] p-[40rpx]">
          <h2 class="text-[#1F2937] text-[32rpx] font-bold mb-[32rpx]">产品描述</h2>
          <!-- 使用 rich-text 渲染富文本 -->
          <rich-text :nodes="productDetail.product_description" class="text-[#4B5563] text-[28rpx] leading-[1.6]"></rich-text>
        </div>
      </div>

      <!-- Attachments -->
      <div v-if="productDetail.documents && productDetail.documents.length" class="px-[32rpx] mt-[32rpx]">
        <div class="bg-white rounded-[32rpx] p-[40rpx]">
          <h2 class="text-[#1F2937] text-[32rpx] font-bold mb-[16rpx]">相关附件</h2>
          <text class="text-[#9CA3AF] text-[24rpx] mb-[24rpx] block">如无法下载,可在预览页点击右上角「...」转发至其他设备</text>
          <div class="flex flex-col gap-[24rpx]">
            <div
              v-for="(doc, index) in productDetail.documents"
              :key="index"
              class="flex flex-col p-[24rpx] bg-gray-50 rounded-[16rpx]"
            >
              <div class="flex items-center justify-between">
                <div class="flex items-center flex-1 mr-[24rpx]">
                  <image
                    :src="getDocumentIcon(doc.file_name)"
                    class="w-[48rpx] h-[48rpx] mr-[24rpx]"
                    mode="aspectFit"
                  />
                  <div class="flex flex-col">
                    <span class="text-[#1F2937] text-[28rpx] font-medium mb-[4rpx] line-clamp-1">{{ doc.file_name }}</span>
                    <span class="text-[#9CA3AF] text-[24rpx]">{{ doc.file_size_formatted }}</span>
                  </div>
                </div>
                <IconFont name="eye" size="20" color="#2563EB" @tap="viewDocument(doc)" />
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

    <!-- TabBar -->
    <!-- <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 { useFileOperation } from '@/composables/useFileOperation'
import Taro, { useLoad } from '@tarojs/taro'
import { getDocumentIcon } from '@/utils/documentIcons'
import { detailAPI } from '@/api/get_product'

const { viewFile } = useFileOperation()

// 接收页面参数
const productId = ref(null)

// 加载状态
const loading = ref(true)

// 产品详情数据
const productDetail = ref({
  id: null,
  product_name: '',
  recommend: '',
  product_description: '',
  cover_image: '',
  tags: [],
  documents: []
})

/**
 * 获取产品详情
 *
 * @description 调用 detailAPI 获取产品详情数据
 * @param {string} id - 产品ID
 */
const fetchProductDetail = async (id) => {
  try {
    loading.value = true

    const res = await detailAPI({
      i: id
    })

    if (res.code === 1 && res.data) {
      productDetail.value = res.data
    } else {
      Taro.showToast({
        title: res.msg || '获取产品详情失败',
        icon: 'none',
        duration: 2000
      })
    }
  } catch (err) {
    console.error('获取产品详情失败:', err)
    Taro.showToast({
      title: '网络错误,请重试',
      icon: 'none',
      duration: 2000
    })
  } finally {
    loading.value = false
  }
}

/**
 * 查看文档
 *
 * @description 打开文档预览
 * @param {Object} doc - 文档对象
 */
const viewDocument = (doc) => {
  viewFile({
    fileName: doc.file_name,
    downloadUrl: doc.file_url
  })
}

useLoad((options) => {
  console.log('产品详情页参数:', options)

  if (options.id) {
    productId.value = options.id
    console.log('产品ID:', productId.value)

    // 获取产品详情数据
    fetchProductDetail(options.id)
  } else {
    console.warn('未接收到产品ID')
    loading.value = false
    Taro.showToast({
      title: '产品ID不存在',
      icon: 'none',
      duration: 2000
    })
  }
})

// Random banner image (fallback)
const bannerImage = `https://picsum.photos/seed/${Math.floor(Math.random() * 1000)}/750/420`
</script>