import { productcontrollerCreateproduct, productcontrollerDeleteproduct, productcontrollerGetproductlist, productcontrollerUpdateproductnamecn, productcontrollerUpdateproduct, productcontrollerGetproductcomponents, productcontrollerSetproductcomponents, productcontrollerAutobindcomponents, productcontrollerGetattributelist, } from '@/servers/api/product'; import { stockcontrollerGetstocks } from '@/servers/api/stock'; import { request } from '@umijs/max'; import { templatecontrollerRendertemplate } from '@/servers/api/template'; import { PlusOutlined } from '@ant-design/icons'; import { ActionType, DrawerForm, PageContainer, ProColumns, ProForm, ProFormInstance, ProFormList, ProFormSelect, ProFormText, ProFormTextArea, ProTable, } from '@ant-design/pro-components'; import { App, Button, Popconfirm, Tag, Upload } from 'antd'; import AttributeFormItem from '@/pages/Product/Attribute/components/AttributeFormItem'; import React, { useMemo, useRef, useState } from 'react'; const capitalize = (s: string) => s.charAt(0).toLocaleUpperCase() + s.slice(1); // TODO interface DictItem { id: number; name: string; title: string; } const NameCn: React.FC<{ id: number; value: string | undefined; tableRef: React.MutableRefObject; }> = ({ value, tableRef, id }) => { const { message } = App.useApp(); const [editable, setEditable] = React.useState(false); if (!editable) return
setEditable(true)}>{value || '-'}
; return ( ) => { if (!e.target.value) return setEditable(false); const { success, message: errMsg } = await productcontrollerUpdateproductnamecn({ id, nameCn: e.target.value, }); setEditable(false); if (!success) { return message.error(errMsg); } tableRef?.current?.reloadAndRest?.(); }, }} /> ); }; const AttributesCell: React.FC<{ record: any }> = ({ record }) => { return (
{(record.attributes || []).map((data: any, idx: number) => ( {data?.dict?.name}: {data.name} ))}
); }; const ComponentsCell: React.FC<{ productId: number }> = ({ productId }) => { const [components, setComponents] = React.useState([]); React.useEffect(() => { (async () => { const { data = [] } = await productcontrollerGetproductcomponents({ id: productId }); setComponents(data || []); })(); }, [productId]); return (
{components && components.length ? ( components.map((component: any) => ( {(component.productSku) || `#${component.id}`} × {component.quantity}(库存: {component.stock?.map((s: any) => `${s.name}:${s.quantity}`).join(', ') || '-'} ) )) ) : ( - )}
); }; const List: React.FC = () => { const actionRef = useRef(); // 状态:存储当前选中的行 const [selectedRows, setSelectedRows] = React.useState([]); const { message } = App.useApp(); // 中文注释:导出产品 CSV(带认证请求) const handleDownloadProductsCSV = async () => { try { // 中文注释:发起认证请求获取 CSV Blob const blob = await request('/product/export', { responseType: 'blob' }); // 中文注释:构建下载文件名 const d = new Date(); const pad = (n: number) => String(n).padStart(2, '0'); const filename = `products-${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}.csv`; // 中文注释:创建临时链接并触发下载 const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); window.URL.revokeObjectURL(url); } catch (error) { message.error('导出失败'); } }; const columns: ProColumns[] = [ { title: 'sku', dataIndex: 'sku', }, { title: '名称', dataIndex: 'name', }, { title: '中文名', dataIndex: 'nameCn', render: (_, record) => { return ( ); }, }, { title: '价格', dataIndex: 'price', hideInSearch: true, }, { title: '促销价', dataIndex: 'promotionPrice', hideInSearch: true, }, { title: '属性', dataIndex: 'attributes', hideInSearch: true, render: (_, record) => , }, { title: '构成', dataIndex: 'components', hideInSearch: true, render: (_, record) => , }, { title: '描述', dataIndex: 'description', hideInSearch: true, }, { title: '更新时间', dataIndex: 'updatedAt', valueType: 'dateTime', hideInSearch: true, }, { title: '创建时间', dataIndex: 'createdAt', valueType: 'dateTime', hideInSearch: true, }, { title: '操作', dataIndex: 'option', valueType: 'option', render: (_, record) => ( <> { try { const { success, message: errMsg } = await productcontrollerDeleteproduct({ id: record.id }); if (!success) { throw new Error(errMsg); } actionRef.current?.reload(); } catch (error: any) { message.error(error.message); } }} > ), }, ]; return ( headerTitle="查询表格" actionRef={actionRef} rowKey="id" toolBarRender={() => [ // 中文注释:新建按钮 , // 中文注释:导出 CSV(后端返回 text/csv,直接新窗口下载) , // 中文注释:导入 CSV(上传文件成功后刷新表格) { if (file.status === 'done') { message.success('导入完成'); actionRef.current?.reload(); } else if (file.status === 'error') { message.error('导入失败'); } }} > , ]} request={async (params) => { const { data, success } = await productcontrollerGetproductlist( params, ); return { total: data?.total || 0, data: data?.items || [], success, }; }} columns={columns} editable={{ type: 'single', onSave: async (key, record, originRow) => { console.log('保存数据:', record); }, }} rowSelection={{ onChange: (_, selectedRows) => setSelectedRows(selectedRows), }} /> ); }; const CreateForm: React.FC<{ tableRef: React.MutableRefObject; }> = ({ tableRef }) => { // antd 的消息提醒 const { message } = App.useApp(); // 表单引用 const formRef = useRef(); const [productType, setProductType] = useState<'single' | 'bundle' | null>(null); /** * @description 生成 SKU */ const handleGenerateSku = async () => { try { // 从表单引用中获取当前表单的值 const formValues = formRef.current?.getFieldsValue(); const { humidityValues, brandValues, strengthValues, flavorValues } = formValues; // 检查是否所有必需的字段都已选择 if (!brandValues?.length || !strengthValues?.length || !flavorValues?.length || !humidityValues?.length) { message.warning('请先选择品牌、强度、口味和干湿'); return; } // 所选值(用于 SKU 模板传入 name) const brandName: string = String(brandValues[0]); const strengthName: string = String(strengthValues[0]); const flavorName: string = String(flavorValues[0]); const humidityName: string = String(humidityValues[0]); // 调用模板渲染API来生成SKU const { data: rendered, message: msg, success } = await templatecontrollerRendertemplate( { name: 'product.sku' }, { brand: brandName || "", strength: strengthName || '', flavor: flavorName || '', humidity: humidityName ? capitalize(humidityName) : '', }, ); if (!success) { throw new Error(msg); } // 将生成的SKU设置到表单字段中 formRef.current?.setFieldsValue({ sku: rendered }); } catch (error: any) { message.error(`生成失败: ${error.message}`); } }; /** * @description 生成产品名称 */ const handleGenerateName = async () => { try { // 从表单引用中获取当前表单的值 const formValues = formRef.current?.getFieldsValue(); const { humidityValues, brandValues, strengthValues, flavorValues } = formValues; // 检查是否所有必需的字段都已选择 if (!brandValues?.length || !strengthValues?.length || !flavorValues?.length || !humidityValues?.length) { message.warning('请先选择品牌、强度、口味和干湿'); return; } const brandName: string = String(brandValues[0]); const strengthName: string = String(strengthValues[0]); const flavorName: string = String(flavorValues[0]); const humidityName: string = String(humidityValues[0]); const brandTitle = brandName; const strengthTitle = strengthName; const flavorTitle = flavorName; // 调用模板渲染API来生成产品名称 const { message: msg, data: rendered, success } = await templatecontrollerRendertemplate( { name: 'product.title' }, { brand: brandTitle, strength: strengthTitle, flavor: flavorTitle, model: '', humidity: humidityName === 'dry' ? 'Dry' : humidityName === 'moisture' ? 'Moisture' : capitalize(humidityName), }, ); if (!success) { throw new Error(msg); } // 将生成的名称设置到表单字段中 formRef.current?.setFieldsValue({ name: rendered }); } catch (error: any) { message.error(`生成失败: ${error.message}`); } }; // TODO 可以输入brand等 return ( title="新建" formRef={formRef} // Pass formRef trigger={ } autoFocusFirstInput drawerProps={{ destroyOnHidden: true, }} onValuesChange={async (changedValues) => { if ('sku' in changedValues) { const sku = changedValues.sku; if (sku) { const { data } = await stockcontrollerGetstocks({ productSku: sku } as any); if (data && data.items && data.items.length > 0) { setProductType('single'); } else { setProductType('bundle'); } } else { setProductType(null); } } }} onFinish={async (values) => { // 中文注释:组装 attributes(支持输入新值,按标题传入创建/绑定) const attributes = [ ...(values.brandValues || []).map((v: string) => ({ dictName: 'brand', name: v })), ...(values.strengthValues || []).map((v: string) => ({ dictName: 'strength', name: v })), ...(values.flavorValues || []).map((v: string) => ({ dictName: 'flavor', name: v })), ...(values.humidityValues || []).map((v: string) => ({ dictName: 'humidity', name: v })), ...(values.sizeValues || []).map((v: string) => ({ dictName: 'size', name: v })), ...(values.category ? [{ dictName: 'category', name: values.category }] : []), ]; const payload: any = { name: (values as any).name, description: (values as any).description, sku: (values as any).sku, price: (values as any).price, promotionPrice: (values as any).promotionPrice, attributes, }; const { success, message: errMsg } = await productcontrollerCreateproduct(payload); if (success) { message.success('提交成功'); tableRef.current?.reloadAndRest?.(); return true; } message.error(errMsg); return false; }} > {productType && ( {productType === 'single' ? '单品' : '套装'} )} ); }; export default List; const EditForm: React.FC<{ tableRef: React.MutableRefObject; record: API.Product; }> = ({ tableRef, record }) => { const { message } = App.useApp(); const formRef = useRef(); const [components, setComponents] = useState<{ productSku: string; quantity: number }[]>([]); const [productType, setProductType] = useState<'single' | 'bundle' | null>(null); React.useEffect(() => { // 中文注释:加载当前产品的组成 (async () => { const { data = [] } = await productcontrollerGetproductcomponents({ id: (record as any).id }); const items = (data || []).map((c: any) => ({ productSku: c.productSku, quantity: c.quantity })); setComponents(items as any); formRef.current?.setFieldsValue({ components: items }); // 检查产品类型 if (record.sku) { const { data: stockData } = await stockcontrollerGetstocks({ productSku: record.sku } as any); if (stockData && stockData.items && stockData.items.length > 0) { setProductType('single'); } else { setProductType('bundle'); } } })(); }, [record]); const initialValues = useMemo(() => { if (!record) return {}; const attributes = record.attributes || []; const attributesGroupedByName = (attributes as any[]).reduce((group, attr) => { const dictName = attr.dict.name; if (!group[dictName]) { group[dictName] = []; } group[dictName].push(attr.name); return group; }, {} as Record); const values: any = { ...record, brandValues: attributesGroupedByName.brand || [], strengthValues: attributesGroupedByName.strength || [], flavorValues: attributesGroupedByName.flavor || [], humidityValues: attributesGroupedByName.humidity || [], sizeValues: attributesGroupedByName.size || [], category: attributesGroupedByName.category?.[0] || '', }; return values; }, [record]); return ( formRef={formRef} title="编辑" trigger={} initialValues={initialValues} onValuesChange={async (changedValues) => { if ('sku' in changedValues) { const sku = changedValues.sku; if (sku) { const { data } = await stockcontrollerGetstocks({ productSku: sku } as any); if (data && data.items && data.items.length > 0) { setProductType('single'); } else { setProductType('bundle'); } } else { setProductType(null); } } }} onFinish={async (values) => { const attributes = [ ...(values.brandValues || []).map((v: string) => ({ dictName: 'brand', name: v })), ...(values.strengthValues || []).map((v: string) => ({ dictName: 'strength', name: v })), ...(values.flavorValues || []).map((v: string) => ({ dictName: 'flavor', name: v })), ...(values.humidityValues || []).map((v: string) => ({ dictName: 'humidity', name: v })), ...(values.sizeValues || []).map((v: string) => ({ dictName: 'size', name: v })), ...(values.category ? [{ dictName: 'category', name: values.category }] : []), ]; const updatePayload: any = { name: values.name, sku: values.sku, description: values.description, price: values.price, promotionPrice: values.promotionPrice, attributes, }; const { success, message: errMsg } = await productcontrollerUpdateproduct( { id: (record as any).id }, updatePayload, ); if (success) { // 中文注释:同步更新组成(覆盖式) const items = (values as any)?.components || []; if (Array.isArray(items)) { const payloadItems = items .filter((i: any) => i && i.productSku && i.quantity && i.quantity > 0) .map((i: any) => ({ productSku: i.productSku, quantity: Number(i.quantity) })); await productcontrollerSetproductcomponents({ id: (record as any).id }, { items: payloadItems as any }); } message.success('更新成功'); tableRef.current?.reloadAndRest?.(); return true; } message.error(errMsg); return false; }} > {/* 在这里列举attribute字段 */} {productType && ( {productType === 'single' ? '单品' : '套装'} )} {/* 中文注释:编辑产品组成(库存ID + 数量) */} ); };