diff --git a/.umirc.ts b/.umirc.ts index 0063d2b..5d47237 100644 --- a/.umirc.ts +++ b/.umirc.ts @@ -166,8 +166,8 @@ export default defineConfig({ }, { name: '产品品牌空间', - path: '/product/brandspace', - component: './Product/BrandSpace', + path: '/product/groupBy', + component: './Product/GroupBy', }, // sync { diff --git a/src/pages/Product/GroupBy/index.tsx b/src/pages/Product/GroupBy/index.tsx new file mode 100644 index 0000000..44a34c4 --- /dev/null +++ b/src/pages/Product/GroupBy/index.tsx @@ -0,0 +1,368 @@ +import React, { useEffect, useState, useMemo } from 'react'; +import { PageContainer, ProFromSelect } 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.name} +
*/} +
+ + {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 */} +
+ 选择分类: + +
+ + {/* 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 && ( +
+ 分组依据: + +
+ )} +
+
+ + + + {/* Products Section */} +
+ 产品列表 ({products.length} 个产品) + + {loading ? ( +
+ 加载中... +
+ ) : groupByAttribute && Object.keys(groupedProducts).length > 0 ? ( +
+ {Object.entries(groupedProducts).map(([attrValueId, groupProducts]) => { + return ( + + ); + })} +
+ ) : ( +
+ 暂无产品 +
+ )} +
+
+
+ ); +}; + +export default ProductGroupBy;