import { productcontrollerBatchdeleteproduct, productcontrollerBatchupdateproduct, productcontrollerBindproductsiteskus, productcontrollerDeleteproduct, productcontrollerGetcategoriesall, productcontrollerGetproductcomponents, productcontrollerGetproductlist, productcontrollerUpdatenamecn, } from '@/servers/api/product'; import { sitecontrollerAll } from '@/servers/api/site'; import { siteapicontrollerGetproducts } from '@/servers/api/siteApi'; import { wpproductcontrollerBatchsynctosite, wpproductcontrollerSynctoproduct, } from '@/servers/api/wpProduct'; import { ActionType, ModalForm, PageContainer, ProColumns, ProFormSelect, ProFormText, ProTable, } from '@ant-design/pro-components'; import { request } from '@umijs/max'; import { App, Button, Modal, Popconfirm, Tag, Upload } from 'antd'; import React, { useEffect, useRef, useState } from 'react'; import CreateForm from './CreateForm'; import EditForm from './EditForm'; 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 productcontrollerUpdatenamecn({ 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.sku || `#${component.id}`} × {component.quantity} (库存: {component.stock ?.map((s: any) => `${s.name}:${s.quantity}`) .join(', ') || '-'} ) )) ) : ( - )}
); }; const BatchEditModal: React.FC<{ visible: boolean; onClose: () => void; selectedRows: API.Product[]; tableRef: React.MutableRefObject; onSuccess: () => void; }> = ({ visible, onClose, selectedRows, tableRef, onSuccess }) => { const { message } = App.useApp(); const [categories, setCategories] = useState([]); useEffect(() => { if (visible) { productcontrollerGetcategoriesall().then((res: any) => { setCategories(res?.data || []); }); } }, [visible]); return ( !open && onClose()} modalProps={{ destroyOnClose: true }} onFinish={async (values) => { const ids = selectedRows.map((row) => row.id); const updateData: any = { ids }; // 只有当用户输入了值才进行更新 if (values.price) updateData.price = Number(values.price); if (values.promotionPrice) updateData.promotionPrice = Number(values.promotionPrice); if (values.categoryId) updateData.categoryId = values.categoryId; if (Object.keys(updateData).length <= 1) { message.warning('未修改任何属性'); return false; } const { success, message: errMsg } = await productcontrollerBatchupdateproduct(updateData); if (success) { message.success('批量修改成功'); onSuccess(); tableRef.current?.reload(); return true; } else { message.error(errMsg); return false; } }} > ({ label: c.title, value: c.id }))} placeholder="不修改请留空" /> ); }; const SyncToSiteModal: React.FC<{ visible: boolean; onClose: () => void; productIds: number[]; productRows: API.Product[]; onSuccess: () => void; }> = ({ visible, onClose, productIds, productRows, onSuccess }) => { const { message } = App.useApp(); const [sites, setSites] = useState([]); const formRef = useRef(); useEffect(() => { if (visible) { sitecontrollerAll().then((res: any) => { setSites(res?.data || []); }); } }, [visible]); return ( !open && onClose()} modalProps={{ destroyOnClose: true }} formRef={formRef} onValuesChange={(changedValues) => { if ('siteId' in changedValues && changedValues.siteId) { const siteId = changedValues.siteId; const site = sites.find((s: any) => s.id === siteId) || {}; const prefix = site.skuPrefix || ''; const map: Record = {}; productRows.forEach((p) => { map[p.id] = { code: `${prefix}${p.sku || ''}`, quantity: undefined, }; }); formRef.current?.setFieldsValue({ productSiteSkus: map }); } }} onFinish={async (values) => { if (!values.siteId) return false; try { await wpproductcontrollerBatchsynctosite( { siteId: values.siteId }, { productIds }, ); const map = values.productSiteSkus || {}; for (const currentProductId of productIds) { const entry = map?.[currentProductId]; if (entry && entry.code) { await productcontrollerBindproductsiteskus( { id: currentProductId }, { siteSkus: [ { siteId: values.siteId, code: entry.code, quantity: entry.quantity, }, ], }, ); } } message.success('同步任务已提交'); onSuccess(); return true; } catch (error: any) { message.error(error.message || '同步失败'); return false; } }} > ({ label: site.name, value: site.id }))} rules={[{ required: true, message: '请选择站点' }]} /> {productRows.map((row) => (
原始SKU: {row.sku || '-'}
))}
); }; const WpProductInfo: React.FC<{ skus: string[]; record: API.Product; parentTableRef: React.MutableRefObject; }> = ({ skus, record, parentTableRef }) => { const actionRef = useRef(); const { message } = App.useApp(); return ( [ , ]} request={async () => { // 判断是否存在站点SKU列表 if (!skus || skus.length === 0) return { data: [] }; try { // 获取所有站点列表用于遍历查询 const { data: siteResponse } = await sitecontrollerAll(); const siteList = siteResponse || []; // 聚合所有站点的产品数据 const aggregatedProducts: any[] = []; // 遍历每一个站点 for (const siteItem of siteList) { // 遍历每一个SKU在当前站点进行搜索 for (const skuCode of skus) { // 直接调用站点API根据搜索关键字获取产品列表 const response = await siteapicontrollerGetproducts({ siteId: Number(siteItem.id), per_page: 100, search: skuCode, }); const productPage = response as any; const siteProducts = productPage?.data?.items || []; // 将站点信息附加到产品数据中便于展示 siteProducts.forEach((p: any) => { aggregatedProducts.push({ ...p, siteId: siteItem.id, siteName: siteItem.name, }); }); } } return { data: aggregatedProducts, success: true }; } catch (error: any) { // 请求失败进行错误提示 message.error(error?.message || '获取站点产品失败'); return { data: [], success: false }; } }} columns={[ { title: '站点', dataIndex: 'siteName', }, { title: 'SKU', dataIndex: 'sku', }, { title: '价格', dataIndex: 'regular_price', render: (_, row) => (
常规: {row.regular_price}
促销: {row.sale_price}
), }, { title: '状态', dataIndex: 'status', }, { title: '操作', valueType: 'option', render: (_, wpRow) => [ { try { await wpproductcontrollerBatchsynctosite( { siteId: wpRow.siteId }, { productIds: [record.id] }, ); message.success('同步到站点成功'); actionRef.current?.reload(); } catch (e: any) { message.error(e.message || '同步失败'); } }} > 同步到站点 , { try { await wpproductcontrollerSynctoproduct({ id: wpRow.id }); message.success('同步进商品成功'); parentTableRef.current?.reload(); } catch (e: any) { message.error(e.message || '同步失败'); } }} > 同步进商品 , { try { await request(`/wp_product/${wpRow.id}`, { method: 'DELETE', }); message.success('删除成功'); actionRef.current?.reload(); } catch (e: any) { message.error(e.message || '删除失败'); } }} > 删除 , ], }, ]} /> ); }; const List: React.FC = () => { const actionRef = useRef(); // 状态:存储当前选中的行 const [selectedRows, setSelectedRows] = React.useState([]); const [batchEditModalVisible, setBatchEditModalVisible] = useState(false); const [syncModalVisible, setSyncModalVisible] = useState(false); const [syncProductIds, setSyncProductIds] = 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', sorter: true, }, { title: '商品SKU', dataIndex: 'siteSkus', render: (_, record) => ( <> {record.siteSkus?.map((code, index) => ( {code} ))} ), }, { title: '名称', dataIndex: 'name', sorter: true, }, { title: '中文名', dataIndex: 'nameCn', render: (_, record) => { return ( ); }, }, { title: '商品类型', dataIndex: 'category', render: (_, record: any) => { return record.category?.title || record.category?.name || '-'; }, }, { title: '价格', dataIndex: 'price', hideInSearch: true, sorter: true, }, { title: '促销价', dataIndex: 'promotionPrice', hideInSearch: true, sorter: true, }, { title: '属性', dataIndex: 'attributes', hideInSearch: true, render: (_, record) => , }, { title: '产品类型', dataIndex: 'type', valueType: 'select', valueEnum: { single: { text: '单品' }, bundle: { text: '套装' }, }, render: (_, record) => { // 如果类型不存在,则返回- if (!record.type) return '-'; // 判断是否为单品 const isSingle = record.type === 'single'; // 根据类型显示不同颜色的标签 return ( {isSingle ? '单品' : '套装'} ); }, }, { title: '构成', dataIndex: 'components', hideInSearch: true, render: (_, record) => , }, { title: '描述', dataIndex: 'description', hideInSearch: true, }, { title: '更新时间', dataIndex: 'updatedAt', valueType: 'dateTime', hideInSearch: true, sorter: true, }, { title: '创建时间', dataIndex: 'createdAt', valueType: 'dateTime', hideInSearch: true, sorter: true, }, { title: '操作', dataIndex: 'option', valueType: 'option', fixed: 'right', 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 ( scroll={{ x: 'max-content' }} headerTitle="查询表格" actionRef={actionRef} rowKey="id" toolBarRender={() => [ // 新建按钮 , // 批量编辑按钮 , // 批量同步按钮 , // 批量删除按钮 , // 导出 CSV(后端返回 text/csv,直接新窗口下载) , // 导入 CSV(使用 customRequest 以支持 request 拦截器和鉴权) { const { file, onSuccess, onError } = options; const formData = new FormData(); formData.append('file', file); try { const res = await request('/product/import', { method: 'POST', data: formData, requestType: 'form', }); const { created = 0, updated = 0, errors = [], } = res.data || {}; if (errors && errors.length > 0) { Modal.warning({ title: '导入结果 (存在错误)', width: 600, content: (

创建成功: {created}

更新成功: {updated}

失败数量: {errors.length}

{errors.map((err: string, idx: number) => (
{idx + 1}. {err}
))}
), }); } else { message.success(`导入成功: 创建 ${created}, 更新 ${updated}`); } onSuccess?.('ok'); actionRef.current?.reload(); } catch (error: any) { message.error('导入失败: ' + (error.message || '未知错误')); onError?.(error); } }} >
, ]} request={async (params, sort) => { let sortField = undefined; let sortOrder = undefined; if (sort && Object.keys(sort).length > 0) { const field = Object.keys(sort)[0]; sortField = field; sortOrder = sort[field]; } const { data, success } = await productcontrollerGetproductlist({ ...params, sortField, sortOrder, } as any); return { total: data?.total || 0, data: data?.items || [], success, }; }} columns={columns} expandable={{ expandedRowRender: (record) => ( ), rowExpandable: (record) => !!(record.siteSkus && record.siteSkus.length > 0), }} editable={{ type: 'single', onSave: async (key, record, originRow) => { console.log('保存数据:', record); }, }} rowSelection={{ onChange: (_, selectedRows) => setSelectedRows(selectedRows), }} /> setBatchEditModalVisible(false)} selectedRows={selectedRows} tableRef={actionRef} onSuccess={() => { setBatchEditModalVisible(false); setSelectedRows([]); }} /> setSyncModalVisible(false)} productIds={syncProductIds} productRows={selectedRows} onSuccess={() => { setSyncModalVisible(false); setSelectedRows([]); actionRef.current?.reload(); }} />
); }; export default List;