|
|
@@ -0,0 +1,198 @@
|
|
|
+import React, { useState } from 'react';
|
|
|
+import { View } from '@tarojs/components';
|
|
|
+import { useQuery } from '@tanstack/react-query';
|
|
|
+import { goodsCategoryClient, advertisementClient } from '@/api';
|
|
|
+import CategorySidebar from '@/components/category/CategorySidebar';
|
|
|
+import CategorySidebarItem from '@/components/category/CategorySidebarItem';
|
|
|
+import CategoryTabbar from '@/components/category/CategoryTabbar';
|
|
|
+import { navigateTo } from '@tarojs/taro';
|
|
|
+import { InferResponseType } from 'hono';
|
|
|
+import './index.css';
|
|
|
+
|
|
|
+type GoodsCategoryResponse = InferResponseType<typeof goodsCategoryClient.$get, 200>
|
|
|
+type Category = GoodsCategoryResponse['data'][0]
|
|
|
+
|
|
|
+type AdvertisementResponse = InferResponseType<typeof advertisementClient.$get, 200>
|
|
|
+type Advertisement = AdvertisementResponse['data'][0]
|
|
|
+
|
|
|
+const CategoryPage: React.FC = () => {
|
|
|
+ const [activeCategoryIndex, setActiveCategoryIndex] = useState<number>(0);
|
|
|
+ const [activeSubCategoryId, setActiveSubCategoryId] = useState<string>('');
|
|
|
+
|
|
|
+ // 获取分类数据
|
|
|
+ const { data: categoryData, isLoading, error } = useQuery({
|
|
|
+ queryKey: ['goods-categories'],
|
|
|
+ queryFn: async () => {
|
|
|
+ const response = await goodsCategoryClient.$get({
|
|
|
+ query: {
|
|
|
+ filters: JSON.stringify({ status: 1 }), // 只显示启用的分类
|
|
|
+ sortBy: 'sort',
|
|
|
+ sortOrder: 'ASC'
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (response.status !== 200) {
|
|
|
+ throw new Error('获取分类数据失败');
|
|
|
+ }
|
|
|
+ return response.json();
|
|
|
+ },
|
|
|
+ staleTime: 5 * 60 * 1000, // 5分钟缓存
|
|
|
+ });
|
|
|
+
|
|
|
+ // 获取广告数据
|
|
|
+ const { data: advertisementData } = useQuery({
|
|
|
+ queryKey: ['category-advertisements'],
|
|
|
+ queryFn: async () => {
|
|
|
+ const response = await advertisementClient.$get({
|
|
|
+ query: {
|
|
|
+ filters: JSON.stringify({ status: 1, typeId: 2 }), // 过滤启用的分类页广告
|
|
|
+ sortBy: 'sort',
|
|
|
+ sortOrder: 'ASC'
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (response.status !== 200) {
|
|
|
+ throw new Error('获取广告数据失败');
|
|
|
+ }
|
|
|
+ return response.json();
|
|
|
+ },
|
|
|
+ staleTime: 5 * 60 * 1000,
|
|
|
+ });
|
|
|
+
|
|
|
+ const categories = categoryData?.data || [];
|
|
|
+ const advertisements = advertisementData?.data || [];
|
|
|
+
|
|
|
+ // 当前选中的一级分类
|
|
|
+ const currentCategory = categories[activeCategoryIndex];
|
|
|
+
|
|
|
+ // 当前一级分类的子分类
|
|
|
+ const subCategories = currentCategory?.child_cate || [];
|
|
|
+
|
|
|
+ // 广告图片
|
|
|
+ const advertisementImage = advertisements[0]?.imageFile?.fullUrl || '';
|
|
|
+
|
|
|
+ // 处理一级分类切换
|
|
|
+ const handleCategoryChange = (index: number) => {
|
|
|
+ setActiveCategoryIndex(index);
|
|
|
+ // 重置二级分类选中状态
|
|
|
+ setActiveSubCategoryId('');
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理二级分类切换
|
|
|
+ const handleSubCategoryChange = (id: string) => {
|
|
|
+ setActiveSubCategoryId(id);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理分类跳转
|
|
|
+ const handleCategoryClick = (categoryId: string) => {
|
|
|
+ navigateTo({
|
|
|
+ url: `/pages/goods-list/index?cateId=${encodeURIComponent(categoryId)}`,
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理二级分类点击
|
|
|
+ const handleSubCategoryClick = (category: Category) => {
|
|
|
+ handleCategoryClick(String(category.id));
|
|
|
+ };
|
|
|
+
|
|
|
+ if (isLoading) {
|
|
|
+ return (
|
|
|
+ <View className="category-page">
|
|
|
+ <View className="loading">加载中...</View>
|
|
|
+ </View>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (error) {
|
|
|
+ return (
|
|
|
+ <View className="category-page">
|
|
|
+ <View className="error">加载失败,请重试</View>
|
|
|
+ </View>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <View className="category-page">
|
|
|
+ <View className="category-container">
|
|
|
+ {/* 左侧边栏 - 一级分类 */}
|
|
|
+ <View className="category-sidebar-container">
|
|
|
+ <CategorySidebar
|
|
|
+ activeKey={activeCategoryIndex}
|
|
|
+ onChange={handleCategoryChange}
|
|
|
+ >
|
|
|
+ {categories.map((category: Category, index: number) => (
|
|
|
+ <CategorySidebarItem
|
|
|
+ key={category.id}
|
|
|
+ title={category.name}
|
|
|
+ onClick={() => handleCategoryChange(index)}
|
|
|
+ />
|
|
|
+ ))}
|
|
|
+ </CategorySidebar>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 右侧内容区 */}
|
|
|
+ <View className="category-content">
|
|
|
+ {/* 二级分类标签栏 */}
|
|
|
+ {subCategories.length > 0 && (
|
|
|
+ <View className="sub-category-tabbar">
|
|
|
+ <CategoryTabbar
|
|
|
+ tabList={subCategories.map((cat: Category) => ({
|
|
|
+ id: cat.id,
|
|
|
+ name: cat.name,
|
|
|
+ }))}
|
|
|
+ currentActive={activeSubCategoryId}
|
|
|
+ onChange={handleSubCategoryChange}
|
|
|
+ showMore={true}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 二级分类网格布局 */}
|
|
|
+ <View className="sub-category-grid">
|
|
|
+ {subCategories.map((category: Category) => (
|
|
|
+ <View
|
|
|
+ key={category.id}
|
|
|
+ className="sub-category-item"
|
|
|
+ onClick={() => handleSubCategoryClick(category)}
|
|
|
+ >
|
|
|
+ <View className="sub-category-image-container">
|
|
|
+ <View className="sub-category-image">
|
|
|
+ {category.image ? (
|
|
|
+ <View className="image-placeholder">
|
|
|
+ {/* 这里需要集成 Shadcn Image 组件 */}
|
|
|
+ <View className="image-fallback">
|
|
|
+ {category.name.charAt(0)}
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ ) : (
|
|
|
+ <View className="image-placeholder">
|
|
|
+ <View className="image-fallback">
|
|
|
+ {category.name.charAt(0)}
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ <View className="sub-category-name">
|
|
|
+ {category.name}
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ ))}
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 广告图 */}
|
|
|
+ {advertisementImage && (
|
|
|
+ <View className="advertisement-container">
|
|
|
+ <View className="advertisement-image">
|
|
|
+ {/* 这里需要集成 Shadcn Image 组件 */}
|
|
|
+ <View className="ad-image-placeholder">
|
|
|
+ 广告图
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default CategoryPage;
|