feat: 产品完善 #44

Merged
zksu merged 2 commits from zksu/WEB:main into main 2026-01-16 08:00:17 +00:00
5 changed files with 128 additions and 94 deletions

View File

@ -149,11 +149,6 @@ export default defineConfig({
path: '/product/list',
component: './Product/List',
},
{
name: '产品属性排列',
path: '/product/permutation',
component: './Product/Permutation',
},
{
name: '产品分类',
path: '/product/category',
@ -164,6 +159,11 @@ export default defineConfig({
path: '/product/attribute',
component: './Product/Attribute',
},
{
name: '产品属性排列',
path: '/product/permutation',
component: './Product/Permutation',
},
{
name: '产品品牌空间',
path: '/product/brandspace',

View File

@ -52,12 +52,6 @@ const DictItemImportButton: React.FC<DictItemImportButtonProps> = ({
});
}
// 检查返回结果是否包含 success 字段
if (result && result.success !== undefined && !result.success) {
throw new Error(result.message || '导入失败');
}
onSuccess?.(result);
// 显示导入结果详情
showImportResult(result);

View File

@ -67,6 +67,7 @@ const BrandSpace: React.FC = () => {
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(false);
const [attributeValuesLoading, setAttributeValuesLoading] = useState(false);
// Fetch brands list
const fetchBrands = async () => {
@ -123,6 +124,7 @@ const BrandSpace: React.FC = () => {
// Fetch attribute values based on selected attribute
const fetchAttributeValues = async (attributeName: string) => {
setAttributeValuesLoading(true);
try {
const response = await request('/dict/items', {
params: { dictId: attributeName },
@ -132,6 +134,10 @@ const BrandSpace: React.FC = () => {
} catch (error) {
console.error('Failed to fetch attribute values:', error);
message.error('获取属性值列表失败');
// Clear attribute values on error
setAttributeValues([]);
} finally {
setAttributeValuesLoading(false);
}
};
@ -160,13 +166,13 @@ const BrandSpace: React.FC = () => {
params,
});
const productList = Array.isArray(response)
? response
: response?.data || [];
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);
}
@ -181,7 +187,7 @@ const BrandSpace: React.FC = () => {
// Fetch attribute values when attribute changes
useEffect(() => {
if (selectedAttribute) {
fetchAttributeValues(selectedAttribute);
fetchAttributeValues(selectedAttribute);
setSelectedAttributeValue(null); // Reset selected value when attribute changes
}
}, [selectedAttribute]);
@ -270,6 +276,7 @@ const BrandSpace: React.FC = () => {
value={selectedAttributeValue}
onChange={handleAttributeValueChange}
allowClear
loading={attributeValuesLoading}
>
{attributeValues.map((value) => (
<Option key={value.id} value={value.id}>
@ -340,7 +347,7 @@ const BrandSpace: React.FC = () => {
</Sider>
{/* Main Content - Product List */}
<Content style={{ padding: '16px' }}>
<Content style={{ padding: '16px', overflowX: 'auto' }}>
<div style={{ marginBottom: 16 }}>
<Title level={4} style={{ margin: 0 }}>
@ -354,82 +361,109 @@ const BrandSpace: React.FC = () => {
<div style={{ textAlign: 'center', padding: '64px' }}>
<Text>...</Text>
</div>
) : products.length > 0 ? (
<Row gutter={[16, 16]}>
{products.map((product) => (
<Col xs={24} sm={12} md={8} lg={6} key={product.id}>
<Card
hoverable
style={{
height: '100%',
display: 'flex',
flexDirection: 'column',
}}
>
<div
style={{
height: 200,
overflow: 'hidden',
marginBottom: 12,
}}
>
<Image
src={
product.image ||
'https://via.placeholder.com/200x200?text=No+Image'
) : 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>
}
alt={product.name}
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
}}
/>
</div>
<div
style={{
flex: 1,
display: 'flex',
flexDirection: 'column',
}}
>
<Text
type="secondary"
style={{ fontSize: 12, marginBottom: 4 }}
style={{ width: 300 }}
>
{product.sku}
</Text>
<Title
level={5}
style={{
margin: '4px 0',
fontSize: 16,
height: 48,
overflow: 'hidden',
}}
>
{product.name}
</Title>
<div
style={{
marginTop: 'auto',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<Text
strong
style={{ fontSize: 18, color: '#ff4d4f' }}
>
¥{product.price || '--'}
</Text>
</div>
</div>
</Card>
</Col>
))}
</Row>
{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={{
@ -439,7 +473,7 @@ const BrandSpace: React.FC = () => {
borderRadius: 8,
}}
>
<Text type="secondary"></Text>
<Text type="secondary"></Text>
</div>
)}
</Content>

View File

@ -418,7 +418,6 @@ const CsvTool: React.FC = () => {
if (
quantity
) {
console.log(quantity, attributeMappings.quantities[quantity])
// 使用quantity的shortName如果没有则使用quantity但匹配 4 个零
const quantityShortName = attributeMappings.quantities[quantity] || Number(quantity).toString().padStart(4, '0');
skuComponents.push(quantityShortName);
@ -570,7 +569,7 @@ const CsvTool: React.FC = () => {
// Determine which data to use for processing and download
let finalData = dataWithSku;
console.log('generateBundleSkuForSingle',generateBundleSkuForSingle)
// If generateBundleSkuForSingle is enabled, generate bundle products for single products
if (generateBundleSkuForSingle) {
// Filter out single records
@ -773,7 +772,7 @@ const CsvTool: React.FC = () => {
valuePropName="checked"
initialValue={true}
>
<Checkbox onChange={setGenerateBundleSkuForSingle}>
<Checkbox onChange={(e)=> setGenerateBundleSkuForSingle(e.target.checked)}>
single类型生成bundle SKU
</Checkbox>
</ProForm.Item>

View File

@ -234,6 +234,12 @@ const List: React.FC = () => {
</>
),
},
{
title: "图片",
dataIndex: 'image',
width: 100,
valueType:'image'
},
{
title: '名称',
dataIndex: 'name',
@ -249,6 +255,7 @@ const List: React.FC = () => {
},
},
{
title: '价格',
dataIndex: 'price',