WEB/src/pages/Product/Permutation/index.tsx

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;