zksu
/
WEB
forked from yoone/WEB
1
0
Fork 0
WEB/src/pages/Product/BrandSpace/index.tsx

487 lines
16 KiB
TypeScript

import { PageContainer } from '@ant-design/pro-components';
import { request } from '@umijs/max';
import {
Card,
Col,
Image,
Layout,
Row,
Select,
Space,
Typography,
message,
} from 'antd';
import React, { useEffect, useState } from 'react';
const { Sider, Content } = Layout;
const { Title, Text } = Typography;
const { Option } = Select;
// Define interfaces
interface Brand {
id: number;
name: string;
shortName?: string;
image?: string;
}
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]: any };
}
const BrandSpace: React.FC = () => {
// State management
const [brands, setBrands] = useState<Brand[]>([]);
const [selectedBrand, setSelectedBrand] = useState<number | null>(null);
const [attributes, setAttributes] = useState<Attribute[]>([]);
const [selectedAttribute, setSelectedAttribute] = useState<string | null>(
null,
);
const [attributeValues, setAttributeValues] = useState<AttributeValue[]>([]);
const [selectedAttributeValue, setSelectedAttributeValue] = useState<
number | null
>(null);
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(false);
const [attributeValuesLoading, setAttributeValuesLoading] = useState(false);
// Fetch brands list
const fetchBrands = async () => {
try {
const response = await request('/dict/items', {
params: { dictId: 'brand' }, // Assuming brand is a dict
});
const brandList = Array.isArray(response)
? response
: response?.data || [];
setBrands(brandList);
// Set default brand to "yoone" if exists
const defaultBrand = brandList.find((brand) => brand.name === 'yoone');
if (defaultBrand) {
setSelectedBrand(defaultBrand.id);
}
} catch (error) {
console.error('Failed to fetch brands:', error);
message.error('获取品牌列表失败');
}
};
// Fetch attributes list
const fetchAttributes = async () => {
try {
// Get all dicts that are attributes (excluding non-attribute dicts)
const response = await request('/dict/list');
const dictList = Array.isArray(response)
? response
: response?.data || [];
// Filter out non-attribute dicts (assuming attributes are specific dicts)
const attributeDicts = dictList.filter((dict: any) =>
['strength', 'flavor', 'humidity', 'size', 'version'].includes(
dict.name,
),
);
setAttributes(attributeDicts);
// Set default attribute to strength if exists
const defaultAttribute = attributeDicts.find(
(attr) => attr.name === 'strength',
);
if (defaultAttribute) {
setSelectedAttribute(defaultAttribute.name);
}
} catch (error) {
console.error('Failed to fetch attributes:', error);
message.error('获取属性列表失败');
}
};
// Fetch attribute values based on selected attribute
const fetchAttributeValues = async (attributeName: string) => {
setAttributeValuesLoading(true);
try {
const response = await request('/dict/items', {
params: { dictId: attributeName },
});
const values = Array.isArray(response) ? response : response?.data || [];
setAttributeValues(values);
} catch (error) {
console.error('Failed to fetch attribute values:', error);
message.error('获取属性值列表失败');
// Clear attribute values on error
setAttributeValues([]);
} finally {
setAttributeValuesLoading(false);
}
};
// Fetch products based on filters
const fetchProducts = async () => {
if (!selectedBrand) return;
setLoading(true);
try {
const params: any = {
brandId: selectedBrand,
};
// Add attribute filter if selected
if (selectedAttribute) {
// If attribute value is selected, filter by both attribute and value
if (selectedAttributeValue) {
params[selectedAttribute] = selectedAttributeValue;
} else {
// If only attribute is selected, filter by attribute presence
params[selectedAttribute] = 'hasValue';
}
}
const response = await request('/product/list', {
params,
});
const productList = response?.data?.items || [];
setProducts(productList);
} catch (error) {
console.error('Failed to fetch products:', error);
message.error('获取产品列表失败');
// Clear products on error
setProducts([]);
} finally {
setLoading(false);
}
};
// Initial data fetch
useEffect(() => {
fetchBrands();
fetchAttributes();
}, []);
// Fetch attribute values when attribute changes
useEffect(() => {
if (selectedAttribute) {
fetchAttributeValues(selectedAttribute);
setSelectedAttributeValue(null); // Reset selected value when attribute changes
}
}, [selectedAttribute]);
// Fetch products when filters change
useEffect(() => {
fetchProducts();
}, [selectedBrand, selectedAttribute, selectedAttributeValue]);
// Handle brand selection change
const handleBrandChange = (value: number) => {
setSelectedBrand(value);
};
// Handle attribute selection change
const handleAttributeChange = (value: string) => {
setSelectedAttribute(value);
};
// Handle attribute value selection change
const handleAttributeValueChange = (value: number) => {
setSelectedAttributeValue(value);
};
return (
<PageContainer title="品牌空间">
<Layout style={{ minHeight: 'calc(100vh - 64px)', background: '#fff' }}>
{/* Top Brand Selection */}
<div style={{ padding: '16px', borderBottom: '1px solid #f0f0f0' }}>
<Space direction="vertical" style={{ width: '100%' }}>
<Title level={4} style={{ margin: 0 }}>
</Title>
<Select
placeholder="请选择品牌"
style={{ width: 300 }}
value={selectedBrand}
onChange={handleBrandChange}
allowClear
>
{brands.map((brand) => (
<Option key={brand.id} value={brand.id}>
{brand.name}
</Option>
))}
</Select>
</Space>
</div>
<Layout>
{/* Left Attribute Selection */}
<Sider
width={240}
style={{ background: '#fafafa', borderRight: '1px solid #f0f0f0' }}
>
<div style={{ padding: '16px' }}>
<Space direction="vertical" style={{ width: '100%' }}>
<Title level={5} style={{ margin: 0 }}>
</Title>
<Select
placeholder="请选择属性类型"
style={{ width: '100%' }}
value={selectedAttribute}
onChange={handleAttributeChange}
allowClear
>
{attributes.map((attr) => (
<Option key={attr.id} value={attr.name}>
{attr.title}
</Option>
))}
</Select>
{selectedAttribute && (
<>
<Title level={5} style={{ margin: '16px 0 8px 0' }}>
</Title>
<Select
placeholder={`请选择${
attributes.find((a) => a.name === selectedAttribute)
?.title
}`}
style={{ width: '100%' }}
value={selectedAttributeValue}
onChange={handleAttributeValueChange}
allowClear
loading={attributeValuesLoading}
>
{attributeValues.map((value) => (
<Option key={value.id} value={value.id}>
<Space>
{value.image && (
<Image
src={value.image}
style={{
width: 24,
height: 24,
objectFit: 'cover',
borderRadius: 4,
}}
/>
)}
<span>
{value.titleCN || value.title || value.name}
</span>
</Space>
</Option>
))}
</Select>
</>
)}
{/* Filter Summary */}
{selectedBrand && (
<div
style={{
marginTop: 24,
padding: 12,
background: '#fff',
borderRadius: 8,
}}
>
<Text strong>:</Text>
<div style={{ marginTop: 8 }}>
<Text type="secondary">: </Text>
<Text>
{brands.find((b) => b.id === selectedBrand)?.name}
</Text>
</div>
{selectedAttribute && (
<div style={{ marginTop: 4 }}>
<Text type="secondary">
{
attributes.find((a) => a.name === selectedAttribute)
?.title
}
:
</Text>
<Text>
{selectedAttributeValue
? attributeValues.find(
(v) => v.id === selectedAttributeValue,
)?.titleCN ||
attributeValues.find(
(v) => v.id === selectedAttributeValue,
)?.title
: '所有值'}
</Text>
</div>
)}
</div>
)}
</Space>
</div>
</Sider>
{/* Main Content - Product List */}
<Content style={{ padding: '16px', overflowX: 'auto' }}>
<div style={{ marginBottom: 16 }}>
<Title level={4} style={{ margin: 0 }}>
<Text type="secondary" style={{ fontSize: 16, marginLeft: 8 }}>
({products.length} )
</Text>
</Title>
</div>
{loading ? (
<div style={{ textAlign: 'center', padding: '64px' }}>
<Text>...</Text>
</div>
) : attributeValues.length > 0 ? (
<div style={{ minWidth: 'max-content' }}>
<Row gutter={[16, 16]}>
{attributeValues.map((attributeValue) => {
// Filter products for this attribute value
if(!Array.isArray(products)){
console.log('products',products)
return null
}
const attributeValueProducts = selectedAttribute
? (products || []).filter((product) =>
product.attributes &&
product.attributes[selectedAttribute] === attributeValue.id
)
: [];
return (
<Col key={attributeValue.id} style={{ minWidth: 300 }}>
<Card
title={
<Space>
{attributeValue.image && (
<Image
src={attributeValue.image}
style={{
width: 24,
height: 24,
objectFit: 'cover',
borderRadius: 4,
}}
/>
)}
<span>
{attributeValue.titleCN || attributeValue.title || attributeValue.name}
</span>
</Space>
}
style={{ width: 300 }}
>
{attributeValueProducts.length > 0 ? (
<div style={{ maxHeight: 600, overflowY: 'auto' }}>
{attributeValueProducts.map((product) => (
<Card
key={product.id}
size="small"
hoverable
style={{ marginBottom: 8 }}
>
<div
style={{
height: 100,
overflow: 'hidden',
marginBottom: 8,
}}
>
<Image
src={
product.image ||
'https://via.placeholder.com/100x100?text=No+Image'
}
alt={product.name}
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
}}
/>
</div>
<div>
<Text
type="secondary"
style={{ fontSize: 12, marginBottom: 4 }}
>
{product.sku}
</Text>
<div style={{ marginTop: 4 }}>
<Text ellipsis style={{ width: '100%', display: 'block' }}>
{product.name}
</Text>
</div>
<div style={{ marginTop: 8 }}>
<Text
strong
style={{ fontSize: 14, color: '#ff4d4f' }}
>
¥{product.price || '--'}
</Text>
</div>
</div>
</Card>
))}
</div>
) : (
<div style={{ textAlign: 'center', padding: '32px' }}>
<Text type="secondary"></Text>
</div>
)}
</Card>
</Col>
);
})}
</Row>
</div>
) : (
<div
style={{
textAlign: 'center',
padding: '64px',
background: '#fafafa',
borderRadius: 8,
}}
>
<Text type="secondary"></Text>
</div>
)}
</Content>
</Layout>
</Layout>
</PageContainer>
);
};
export default BrandSpace;