import React, { useEffect, useState, useMemo } from 'react'; import { PageContainer, ProFormSelect } from '@ant-design/pro-components'; import { Card, Collapse, Divider, Image, Select, Space, Typography, message } from 'antd'; import { categorycontrollerGetall } from '@/servers/api/category'; import { productcontrollerGetproductlistgrouped } from '@/servers/api/product'; import { dictcontrollerGetdictitems } from '@/servers/api/dict'; // Define interfaces interface Category { id: number; name: string; title: string; attributes: string[]; // List of attribute names for this category } interface Attribute { id: number; name: string; title: string; } interface AttributeValue { id: number; name: string; title: string; titleCN?: string; value?: string; image?: string; } interface Product { id: number; sku: string; name: string; image?: string; brandId: number; brandName: string; attributes: { [key: string]: number }; // attribute name to attribute value id mapping price?: number; } // Grouped products by attribute value interface GroupedProducts { [attributeValueId: string]: Product[]; } // ProductCard component for displaying single product const ProductCard: React.FC<{ product: Product }> = ({ product }) => { return ( {/* */} {product.sku} {product.name} ¥{product.price || '--'} ); }; // ProductGroup component for displaying grouped products const ProductGroup: React.FC<{ attributeValueId: string; groupProducts: Product[]; attributeValue: AttributeValue | undefined; attributeName: string; }> = ({ attributeValueId, groupProducts, attributeValue }) => { // State for collapse control const [isCollapsed, setIsCollapsed] = useState(false); // Create collapse panel header const panelHeader = ( {attributeValue?.image && ( )} {attributeValue?.titleCN || attributeValue?.title || attributeValue?.name || attributeValueId||'未知'} (共 {groupProducts.length} 个产品) ); return ( setIsCollapsed(Array.isArray(key) && key.length === 0)} ghost bordered={false} items={[ { key: attributeValueId, label: panelHeader, children: ( {groupProducts.map((product) => ( ))} ), }, ]} /> ); }; // Main ProductGroupBy component const ProductGroupBy: React.FC = () => { // State management const [categories, setCategories] = useState([]); const [selectedCategory, setSelectedCategory] = useState(null); // Store selected values for each attribute const [attributeFilters, setAttributeFilters] = useState<{ [key: string]: number | null }>({}); // Group by attribute const [groupByAttribute, setGroupByAttribute] = useState(null); // Products const [products, setProducts] = useState([]); const [groupedProducts, setGroupedProducts] = useState({}); const [loading, setLoading] = useState(false); // Extract all unique attributes from categories const categoryAttributes = useMemo(() => { if (!selectedCategory) return []; const categoryItem = categories.find((category: any) => category.name === selectedCategory); if (!categoryItem) return []; const attributesList: Attribute[] = categoryItem.attributes.map((attribute: any, index) => ({ ...attribute.attributeDict, id: index + 1, })); return attributesList; }, [selectedCategory]); // Fetch categories list const fetchCategories = async () => { try { const response = await categorycontrollerGetall(); const rawCategories = Array.isArray(response) ? response : response?.data || []; setCategories(rawCategories); // Set default category if (rawCategories.length > 0) { const defaultCategory = rawCategories.find((category: any) => category.name === 'nicotine-pouches'); setSelectedCategory(defaultCategory?.name || rawCategories[0].name); } } catch (error) { console.error('Failed to fetch categories:', error); message.error('获取分类列表失败'); } }; // Update category attributes when selected category changes useEffect(() => { if (!selectedCategory) return; const category = categories.find(cat => cat.name === selectedCategory); if (!category) return; // Get attributes for this category const attributesForCategory = categoryAttributes.filter(attr => attr.name === 'brand' || category.attributes.includes(attr.name) ); // Reset attribute filters when category changes const newFilters: { [key: string]: number | null } = {}; attributesForCategory.forEach(attr => { newFilters[attr.name] = null; }); setAttributeFilters(newFilters); // Set default group by attribute if (attributesForCategory.length > 0) { setGroupByAttribute(attributesForCategory[0].name); } }, [selectedCategory, categories, categoryAttributes]); // Handle attribute filter change const handleAttributeFilterChange = (attributeName: string, value: number | null) => { setAttributeFilters(prev => ({ ...prev, [attributeName]: value })); }; // Fetch products based on filters const fetchProducts = async () => { if (!selectedCategory || !groupByAttribute) return; setLoading(true); try { const params: any = { category: selectedCategory, groupBy: groupByAttribute }; const response = await productcontrollerGetproductlistgrouped(params); const grouped = response?.data || {}; setGroupedProducts(grouped); // Flatten grouped products to get all products const allProducts = Object.values(grouped).flat() as Product[]; setProducts(allProducts); } catch (error) { console.error('Failed to fetch grouped products:', error); message.error('获取分组产品列表失败'); setProducts([]); setGroupedProducts({}); } finally { setLoading(false); } }; // Initial data fetch useEffect(() => { fetchCategories(); }, []); // Fetch products when filters change useEffect(() => { fetchProducts(); }, [selectedCategory, attributeFilters, groupByAttribute]); // Destructure antd components const { Title, Text } = Typography; return ( {/* Filter Section */} 筛选条件 {/* Category Filter */} 选择分类: {categories.map(category => ( {category.title} ))} {/* Attribute Filters */} {categoryAttributes.length > 0 && ( 属性筛选: {categoryAttributes.map(attr => ( {attr.title}: handleAttributeFilterChange(attr.name, value)} allowClear showSearch optionFilterProp="children" request={async (params) => { try { console.log('params', params,attr); const response = await dictcontrollerGetdictitems({ dictId: attr.name }); const rawValues = Array.isArray(response) ? response : response?.data?.items || []; const filteredValues = rawValues.filter((value: any) => value.dictId === attr.name || value.dict?.id === attr.name || value.dict?.name === attr.name ); return { options: filteredValues.map((value: any) => ({ label: `${value.name}${value.titleCN || value.title}`, value: value.id })) }; } catch (error) { console.error(`Failed to fetch ${attr.title} values:`, error); message.error(`获取${attr.title}属性值失败`); return { options: [] }; } }} /> ))} )} {/* Group By Attribute */} {categoryAttributes.length > 0 && ( 分组依据: {categoryAttributes.map(attr => ( {attr.title} ))} )} {/* Products Section */} 产品列表 ({products.length} 个产品) {loading ? ( 加载中... ) : groupByAttribute && Object.keys(groupedProducts).length > 0 ? ( {Object.entries(groupedProducts).map(([attrValueId, groupProducts]) => { return ( ); })} ) : ( 暂无产品 )} ); }; export default ProductGroupBy;