322 lines
9.6 KiB
TypeScript
322 lines
9.6 KiB
TypeScript
import {
|
|
productcontrollerCreateproduct,
|
|
productcontrollerGetcategoriesall,
|
|
productcontrollerGetcategoryattributes,
|
|
productcontrollerGetproductlist,
|
|
} from '@/servers/api/product';
|
|
import { request } from '@umijs/max';
|
|
import {
|
|
ActionType,
|
|
PageContainer,
|
|
ProCard,
|
|
ProColumns,
|
|
ProForm,
|
|
ProFormSelect,
|
|
ProTable,
|
|
} from '@ant-design/pro-components';
|
|
import { Button, Table, Tag, message } from 'antd';
|
|
import React, { useEffect, useState, useRef } from 'react';
|
|
import CreateModal from './components/CreateModal';
|
|
import EditForm from '../List/EditForm';
|
|
|
|
const PermutationPage: React.FC = () => {
|
|
const [categoryId, setCategoryId] = useState<number>();
|
|
const [attributes, setAttributes] = useState<any[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [attributeValues, setAttributeValues] = useState<Record<string, any[]>>({});
|
|
const [permutations, setPermutations] = useState<any[]>([]);
|
|
const [existingProducts, setExistingProducts] = useState<Map<string, API.Product>>(new Map());
|
|
const [productsLoading, setProductsLoading] = useState(false);
|
|
|
|
const [createModalVisible, setCreateModalVisible] = useState(false);
|
|
const [selectedPermutation, setSelectedPermutation] = useState<any>(null);
|
|
const [categories, setCategories] = useState<any[]>([]);
|
|
const [form] = ProForm.useForm();
|
|
|
|
// Create a ref to mock ActionType for EditForm
|
|
const actionRef = useRef<ActionType>();
|
|
|
|
useEffect(() => {
|
|
productcontrollerGetcategoriesall().then((res) => {
|
|
const list = Array.isArray(res) ? res : (res?.data || []);
|
|
setCategories(list);
|
|
if (list.length > 0) {
|
|
setCategoryId(list[0].id);
|
|
form.setFieldValue('categoryId', list[0].id);
|
|
}
|
|
});
|
|
}, []);
|
|
|
|
const fetchProducts = async (catId: number) => {
|
|
setProductsLoading(true);
|
|
try {
|
|
const productRes = await productcontrollerGetproductlist({
|
|
categoryId: catId,
|
|
pageSize: 2000,
|
|
current: 1
|
|
});
|
|
const products = productRes.data?.items || [];
|
|
|
|
const productMap = new Map<string, API.Product>();
|
|
products.forEach((p: any) => {
|
|
if (p.attributes && Array.isArray(p.attributes)) {
|
|
const key = generateAttributeKey(p.attributes);
|
|
if (key) productMap.set(key, p);
|
|
}
|
|
});
|
|
setExistingProducts(productMap);
|
|
} catch (error) {
|
|
console.error(error);
|
|
message.error('获取现有产品失败');
|
|
} finally {
|
|
setProductsLoading(false);
|
|
}
|
|
};
|
|
|
|
// Assign reload method to actionRef
|
|
useEffect(() => {
|
|
actionRef.current = {
|
|
reload: async () => {
|
|
if (categoryId) await fetchProducts(categoryId);
|
|
},
|
|
reloadAndRest: async () => {
|
|
if (categoryId) await fetchProducts(categoryId);
|
|
},
|
|
reset: () => {},
|
|
clearSelected: () => {},
|
|
} as any;
|
|
}, [categoryId]);
|
|
|
|
// Fetch attributes and products when category changes
|
|
useEffect(() => {
|
|
if (!categoryId) {
|
|
setAttributes([]);
|
|
setAttributeValues({});
|
|
setPermutations([]);
|
|
setExistingProducts(new Map());
|
|
return;
|
|
}
|
|
|
|
const fetchData = async () => {
|
|
setLoading(true);
|
|
try {
|
|
// 1. Fetch Attributes
|
|
const attrRes = await productcontrollerGetcategoryattributes({ id: categoryId });
|
|
const attrs = Array.isArray(attrRes) ? attrRes : (attrRes?.data || []);
|
|
setAttributes(attrs);
|
|
|
|
// 2. Fetch Attribute Values (Dict Items)
|
|
const valuesMap: Record<string, any[]> = {};
|
|
for (const attr of attrs) {
|
|
const dictId = attr.dict?.id || attr.dictId;
|
|
if (dictId) {
|
|
const itemsRes = await request('/dict/items', { params: { dictId } });
|
|
valuesMap[attr.name] = itemsRes || [];
|
|
}
|
|
}
|
|
setAttributeValues(valuesMap);
|
|
|
|
// 3. Fetch Existing Products
|
|
await fetchProducts(categoryId);
|
|
|
|
} catch (error) {
|
|
console.error(error);
|
|
message.error('获取数据失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchData();
|
|
}, [categoryId]);
|
|
|
|
// Generate Permutations when attributes or values change
|
|
useEffect(() => {
|
|
if (attributes.length === 0 || Object.keys(attributeValues).length === 0) {
|
|
setPermutations([]);
|
|
return;
|
|
}
|
|
|
|
const validAttributes = attributes.filter(attr => attributeValues[attr.name]?.length > 0);
|
|
|
|
if (validAttributes.length === 0) {
|
|
setPermutations([]);
|
|
return;
|
|
}
|
|
|
|
const generateCombinations = (index: number, current: any): any[] => {
|
|
if (index === validAttributes.length) {
|
|
return [current];
|
|
}
|
|
|
|
const attr = validAttributes[index];
|
|
const values = attributeValues[attr.name];
|
|
let res: any[] = [];
|
|
|
|
for (const val of values) {
|
|
res = res.concat(generateCombinations(index + 1, { ...current, [attr.name]: val }));
|
|
}
|
|
return res;
|
|
};
|
|
|
|
const combos = generateCombinations(0, {});
|
|
setPermutations(combos);
|
|
|
|
}, [attributes, attributeValues]);
|
|
|
|
const generateAttributeKey = (attrs: any[]) => {
|
|
const parts = attrs.map(a => {
|
|
const key = a.dict?.name || a.dictName;
|
|
const val = a.name || a.value;
|
|
return `${key}:${val}`;
|
|
});
|
|
return parts.sort().join('|');
|
|
};
|
|
|
|
const generateKeyFromPermutation = (perm: any) => {
|
|
const parts = Object.keys(perm).map(attrName => {
|
|
const valItem = perm[attrName];
|
|
const val = valItem.name;
|
|
return `${attrName}:${val}`;
|
|
});
|
|
return parts.sort().join('|');
|
|
};
|
|
|
|
const handleAdd = (record: any) => {
|
|
setSelectedPermutation(record);
|
|
setCreateModalVisible(true);
|
|
};
|
|
|
|
const columns: any[] = [
|
|
...attributes.map(attr => ({
|
|
title: attr.title || attr.name,
|
|
dataIndex: attr.name,
|
|
width: 100, // Make columns narrower
|
|
render: (item: any) => item?.name || '-',
|
|
sorter: (a: any, b: any) => {
|
|
const valA = a[attr.name]?.name || '';
|
|
const valB = b[attr.name]?.name || '';
|
|
return valA.localeCompare(valB);
|
|
},
|
|
filters: attributeValues[attr.name]?.map((v: any) => ({ text: v.name, value: v.name })),
|
|
onFilter: (value: any, record: any) => record[attr.name]?.name === value,
|
|
})),
|
|
{
|
|
title: '现有 SKU',
|
|
key: 'sku',
|
|
width: 150,
|
|
sorter: (a: any, b: any) => {
|
|
const keyA = generateKeyFromPermutation(a);
|
|
const productA = existingProducts.get(keyA);
|
|
const skuA = productA?.sku || '';
|
|
|
|
const keyB = generateKeyFromPermutation(b);
|
|
const productB = existingProducts.get(keyB);
|
|
const skuB = productB?.sku || '';
|
|
|
|
return skuA.localeCompare(skuB);
|
|
},
|
|
filters: [
|
|
{ text: '已存在', value: 'exists' },
|
|
{ text: '未创建', value: 'missing' },
|
|
],
|
|
onFilter: (value: any, record: any) => {
|
|
const key = generateKeyFromPermutation(record);
|
|
const exists = existingProducts.has(key);
|
|
if (value === 'exists') return exists;
|
|
if (value === 'missing') return !exists;
|
|
return true;
|
|
},
|
|
render: (_: any, record: any) => {
|
|
const key = generateKeyFromPermutation(record);
|
|
const product = existingProducts.get(key);
|
|
return product ? <Tag color="green">{product.sku}</Tag> : '-';
|
|
},
|
|
},
|
|
{
|
|
title: '操作',
|
|
key: 'action',
|
|
width: 100,
|
|
render: (_: any, record: any) => {
|
|
const key = generateKeyFromPermutation(record);
|
|
const product = existingProducts.get(key);
|
|
if (product) {
|
|
return (
|
|
<EditForm
|
|
record={product}
|
|
tableRef={actionRef}
|
|
trigger={<Button type="link" size="small">编辑</Button>}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Button type="primary" size="small" onClick={() => handleAdd(record)}>
|
|
添加
|
|
</Button>
|
|
);
|
|
},
|
|
},
|
|
];
|
|
|
|
return (
|
|
<PageContainer>
|
|
<ProCard>
|
|
<ProForm
|
|
form={form}
|
|
layout="inline"
|
|
submitter={false}
|
|
style={{ marginBottom: 24 }}
|
|
>
|
|
<ProFormSelect
|
|
name="categoryId"
|
|
label="选择分类"
|
|
width="md"
|
|
options={categories.map((item: any) => ({
|
|
label: item.name,
|
|
value: item.id,
|
|
}))}
|
|
fieldProps={{
|
|
onChange: (val) => setCategoryId(val as number),
|
|
}}
|
|
/>
|
|
</ProForm>
|
|
|
|
{categoryId && (
|
|
<ProTable
|
|
size="small"
|
|
dataSource={permutations}
|
|
columns={columns}
|
|
loading={loading || productsLoading}
|
|
rowKey={(record) => generateKeyFromPermutation(record)}
|
|
pagination={{
|
|
defaultPageSize: 50,
|
|
showSizeChanger: true,
|
|
pageSizeOptions: ['50', '100', '200', '500', '1000', '2000']
|
|
}}
|
|
scroll={{ x: 'max-content' }}
|
|
search={false}
|
|
toolBarRender={false}
|
|
/>
|
|
)}
|
|
</ProCard>
|
|
|
|
{selectedPermutation && (
|
|
<CreateModal
|
|
visible={createModalVisible}
|
|
onClose={() => setCreateModalVisible(false)}
|
|
onSuccess={() => {
|
|
setCreateModalVisible(false);
|
|
if (categoryId) fetchProducts(categoryId);
|
|
}}
|
|
category={categories.find(c => c.id === categoryId) || null}
|
|
permutation={selectedPermutation}
|
|
attributes={attributes}
|
|
/>
|
|
)}
|
|
</PageContainer>
|
|
);
|
|
};
|
|
|
|
export default PermutationPage;
|