index.vue 12.8 KB
<template>
  <view class="relative w-full min-h-screen overflow-y-auto overflow-x-hidden bg-[#F9FAFB] pb-[200rpx]">
    <!-- Header Section -->
    <view class="absolute left-0 top-0 w-full h-[544rpx] z-0">
      <image class="w-full h-full" src="https://picsum.photos/seed/header/750/544" mode="aspectFill" />
      <view class="absolute inset-0 bg-gradient-to-b from-blue-600/80 to-transparent"></view>
    </view>

    <view class="relative z-10 px-[32rpx] pt-[180rpx]">
      <text class="block text-white text-[44rpx] font-bold mb-[40rpx] text-center">臻奇荟</text>

      <!-- Search Bar -->
      <view
        class="flex items-center w-full h-[88rpx] bg-white/20 backdrop-blur-md rounded-full px-[32rpx] border border-white/30"
        @tap="go('/pages/search/index')"
      >
        <IconFont name="search" class="text-white/80 mr-[16rpx]" size="18" />
        <text class="text-white/80 text-[28rpx]">搜索培训资料、案例...</text>
      </view>
    </view>

    <!-- Main Content -->
    <view class="relative z-10 mt-[40rpx] px-[24rpx]">
      <!-- Grid Icons -->
      <view class="bg-white rounded-[32rpx] shadow-sm p-[40rpx] mb-[24rpx]">
        <view class="flex flex-wrap">
          <view
            class="flex flex-col items-center w-1/3 mb-[40rpx]"
            v-for="(item, index) in loopNav"
            :key="index"
            @tap="handleGridNav(item)"
          >
            <view class="w-[88rpx] h-[88rpx] rounded-[24rpx] bg-blue-50 flex items-center justify-center mb-[16rpx]">
              <IconFont :name="item.icon" class="text-blue-600" size="24" />
            </view>
            <text class="text-gray-800 text-[26rpx]">{{ item.name }}</text>
          </view>
        </view>
      </view>

      <!-- Hot Products -->
      <view class="bg-white rounded-[32rpx] shadow-sm p-[32rpx] mb-[24rpx]">
        <view class="flex justify-between items-center mb-[24rpx]">
          <text class="text-gray-900 text-[32rpx] font-bold">热卖产品</text>
          <view class="flex items-center text-blue-600" @tap="go('/pages/knowledge-base/index')">
            <text class="text-[26rpx] mr-[4rpx]">查看更多</text>
            <IconFont name="rectRight" size="12" />
          </view>
        </view>

        <!-- 动态产品列表 -->
        <view
          v-for="(product, index) in hotProducts"
          :key="product.id"
          class="bg-gray-50 rounded-[24rpx] p-[28rpx]"
          :class="{ 'mb-[24rpx]': index < hotProducts.length - 1 }"
        >
          <text class="block text-gray-800 text-[28rpx] font-medium mb-[20rpx]">{{ product.product_name }}</text>

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

          <view class="flex justify-between gap-[24rpx]">
            <nut-button
              plain
              color="#2563EB"
              class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0 !border-blue-600"
              @tap="goToProductDetail(product.id)"
            >
              产品详情
            </nut-button>
            <nut-button
              color="#2563EB"
              class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0"
              @tap="openPlanPopup(product.id)"
            >
              计划书
            </nut-button>
          </view>
        </view>
      </view>

      <!-- Hot Materials -->
      <view v-if="hotMaterials.length > 0" class="bg-white rounded-[32rpx] shadow-sm p-[32rpx] mb-[48rpx]">
        <view class="flex justify-between items-center mb-[24rpx]">
          <text class="text-gray-900 text-[32rpx] font-bold">本周热门资料</text>
          <view class="flex items-center text-blue-600" @tap="go('/pages/material-list/index')">
            <text class="text-[26rpx] mr-[4rpx]">查看更多</text>
            <IconFont name="rectRight" size="12" />
          </view>
        </view>

        <!-- Material List -->
        <view class="flex flex-col gap-[24rpx]">
          <!-- Material Items -->
          <view v-for="(item, index) in hotMaterials" :key="item.id || index"
            class="flex flex-row bg-white rounded-[24rpx] p-[24rpx] border border-gray-50">

            <!-- 左侧图标 -->
            <view 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" />
            </view>

            <!-- 内容区域 -->
            <view class="flex-1 min-w-0">
              <text class="text-[#1F2937] text-[30rpx] font-bold leading-[1.4] line-clamp-2 mb-[8rpx]">
                {{ item.title }}
              </text>
              <view class="flex items-center gap-[12rpx] mb-[16rpx]">
                <view class="inline-flex items-center justify-center px-[12rpx] py-[4rpx] bg-gray-100 text-gray-500 text-[20rpx] font-medium rounded-[8rpx]">
                  <text>{{ getDocumentLabel(item.fileName) }}</text>
                </view>
                <text class="text-[#9CA3AF] text-[22rpx]">{{ item.learners }}</text>
                <!-- 学习人数比例 -->
                <view v-if="item.readPeoplePercent !== undefined && item.readPeoplePercent !== null" class="inline-flex items-center px-[8rpx] py-[2rpx] bg-green-50 text-green-600 text-[20rpx] font-medium rounded-[6rpx]">
                  <text>{{ item.readPeoplePercent }}%热度</text>
                </view>
              </view>

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

              <!-- 操作按钮 -->
              <ListItemActions
                :viewable="true"
                :collectable="true"
                :deletable="false"
                :collected="item.collected"
                @view="onViewMaterial(item)"
                @collect="toggleMaterialCollect(item)"
              />
            </view>
          </view>
        </view>
      </view>
    </view>

    <!-- Bottom Tab Bar -->
    <TabBar current="home" />

    <!-- Plan Popup -->
    <PlanPopup v-model:visible="showPlanPopup">
      <SchemeA
        v-if="currentScheme === 'A'"
        @close="showPlanPopup = false"
        @submit="handlePlanSubmit"
      />
      <SchemeB
        v-if="currentScheme === 'B'"
        @close="showPlanPopup = false"
        @submit="handlePlanSubmit"
      />
    </PlanPopup>
  </view>
</template>

<script setup>
import { ref, shallowRef } from 'vue';
import Taro, { useShareAppMessage, useLoad, useDidShow } from '@tarojs/taro';
import { useGo } from '@/hooks/useGo';
import { useListItemClick, ListType } from '@/composables/useListItemClick';
import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons';
import { useUserStore } from '@/stores/user';
import TabBar from '@/components/TabBar.vue';
import IconFont from '@/components/IconFont.vue';
import PlanPopup from '@/components/PlanPopup/index.vue';
import SchemeA from '@/components/PlanSchemes/SchemeA.vue';
import SchemeB from '@/components/PlanSchemes/SchemeB.vue';
import ListItemActions from '@/components/ListItemActions/index.vue';
import { listAPI } from '@/api/get_product';
import { weekHotAPI } from '@/api/file';
import { useCollectOperation } from '@/composables/useCollectOperation';

// User Store
const userStore = useUserStore();

// Plan Popup State
const showPlanPopup = ref(false);
const currentScheme = ref('A');

const openPlanPopup = (scheme) => {
  currentScheme.value = scheme;
  showPlanPopup.value = true;
};

/**
 * 处理计划书提交
 * @description 模拟提交计划书,跳转到结果页面
 * @param {Object} formData - 表单数据
 */
const handlePlanSubmit = (formData) => {
  console.log(`方案${currentScheme.value}提交:`, formData);

  // 关闭弹窗
  showPlanPopup.value = false;

  // 模拟提交成功,跳转到结果页面
  // TODO: 后续接入真实API
  go('/pages/plan-submit-result/index', {
    success: 'true'
  });
};

/**
 * 分类 ID 配置
 * @description 各业务模块对应的分类 ID,需要根据后端实际返回的 ID 配置
 * TODO: 将这些 CID 替换为实际的分类 ID
 */
const CATEGORY_IDS = {
  onboarding: '3129684',      // 入职相关分类 ID
  signing: '',         // 签单相关分类 ID
  familyOffice: '',    // 家办相关分类 ID
  customerService: ''  // 客户服务分类 ID
}

const loopNav = shallowRef([
  { id: 'plan', icon: 'order', name: '计划书', route: '/pages/plan/index' },
  { id: 'onboarding', icon: 'my', name: '入职相关', route: '/pages/category-list/index', cid: CATEGORY_IDS.onboarding },
  { id: 'signing', icon: 'cart', name: '签单相关', route: '/pages/category-list/index', cid: CATEGORY_IDS.signing },
  { id: 'family-office', icon: 'home', name: '家办相关', route: '/pages/category-list/index', cid: CATEGORY_IDS.familyOffice },
  { id: 'knowledge-base', icon: 'category', name: '产品知识库', route: '/pages/knowledge-base/index' },
  { id: 'customer-service', icon: 'star', name: '客户服务', route: '/pages/category-list/index', cid: CATEGORY_IDS.customerService },
]);

/**
 * 热卖产品数据
 *
 * @description 从服务器获取的热卖产品列表
 */
const hotProducts = ref([]);

/**
 * 获取热卖产品列表
 *
 * @description 调用产品列表API,recommend参数为hot
 */
const fetchHotProducts = async () => {
  try {
    const res = await listAPI({
      recommend: 'hot'
    });

    if (res.code === 1 && res.data && res.data.list) {
      hotProducts.value = res.data.list;
    }
  } catch (err) {
    console.error('获取热卖产品失败:', err);
  }
};

/**
 * 热门资料数据
 *
 * @description 本周热门资料列表数据,从 API 获取
 */
const hotMaterials = ref([]);

/**
 * 热门资料加载状态
 *
 * @description 用于控制加载状态和空状态显示
 */
const hotMaterialsLoading = ref(false);

/**
 * 获取本周热门资料
 *
 * @description 调用 weekHotAPI 获取热门资料列表
 */
const fetchHotMaterials = async () => {
  try {
    hotMaterialsLoading.value = true;
    const res = await weekHotAPI({
      page: 0,
      limit: 10
    });

    if (res.code === 1 && res.data && res.data.list) {
      // 转换 API 数据格式为组件所需格式
      hotMaterials.value = res.data.list.map(item => ({
        id: item.meta_id,
        title: item.name,
        fileName: item.name,
        downloadUrl: item.src,
        fileSize: item.size,
        learners: `${item.read_people_count}人学习`,
        readPeoplePercent: item.read_people_percent, // 学习人数比例
        collected: item.is_favorite
      }));
    } else {
      hotMaterials.value = [];
    }
  } catch (err) {
    console.error('获取本周热门资料失败:', err);
    hotMaterials.value = [];
  } finally {
    hotMaterialsLoading.value = false;
  }
};

// Navigation
const go = useGo();

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

// 使用收藏操作 composable
const { toggleCollect: toggleMaterialCollect } = useCollectOperation();

// Handle grid navigation click
const handleGridNav = (item) => {
  if (!item.route) {
    Taro.showToast({
      title: '功能开发中',
      icon: 'none',
      duration: 2000
    });
    return;
  }

  // 如果是分类列表页面,需要带参数跳转
  if (item.route === '/pages/category-list/index') {
    go(item.route, {
      cid: item.cid,      // 分类 ID
      title: item.name    // 页面标题
    });
  } else {
    // 其他页面直接跳转
    go(item.route);
  }
};

// 跳转到产品详情页
const goToProductDetail = (productId) => {
  go('/pages/product-detail/index', {
    id: productId
  });
};

// Open webview with URL
const openWebView = (url) => {
  go('/pages/webview/index', {
    url: encodeURIComponent(url)
  });
};

// 页面加载时获取热卖产品和热门资料
useLoad(() => {
  fetchHotProducts();
  fetchHotMaterials();
});

// 页面显示时刷新用户信息(更新 TabBar 红点状态)
useDidShow(() => {
  // 只在已登录状态下刷新
  if (userStore.isLoggedIn) {
    userStore.fetchUserInfo().catch(err => {
      console.error('刷新用户信息失败:', err);
    });
  }
});

useShareAppMessage(() => {
  return {
    title: '臻奇智荟圈',
    path: '/pages/index/index'
  };
});
</script>