feat: 产品完善 #44
10
.umirc.ts
10
.umirc.ts
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in New Issue