feat: 主要优化产品相关工作 #39
|
|
@ -170,6 +170,11 @@ export default defineConfig({
|
|||
path: '/product/sync',
|
||||
component: './Product/Sync',
|
||||
},
|
||||
{
|
||||
name: '产品CSV 工具',
|
||||
path: '/product/csvtool',
|
||||
component: './Product/CsvTool',
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ WORKDIR /app
|
|||
# 复制 package.json 和 package-lock.json
|
||||
COPY package*.json ./
|
||||
|
||||
# 安装依赖(使用 --legacy-peer-deps 解决依赖冲突)
|
||||
# 安装依赖(使用 --legacy-peer-deps 解决依赖冲突)
|
||||
RUN npm install --legacy-peer-deps
|
||||
|
||||
# 复制源代码
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export const showSyncResult = (
|
|||
} = result;
|
||||
|
||||
// 构建结果消息
|
||||
let resultMessage = `同步完成!共处理 ${processed} 个${entityType}(总数 ${total} 个):`;
|
||||
let resultMessage = `同步完成!共处理 ${processed} 个${entityType}(总数 ${total} 个):`;
|
||||
if (created > 0) resultMessage += ` 新建 ${created} 个`;
|
||||
if (updated > 0) resultMessage += ` 更新 ${updated} 个`;
|
||||
if (synced > 0) resultMessage += ` 同步成功 ${synced} 个`;
|
||||
|
|
@ -50,7 +50,7 @@ export const showSyncResult = (
|
|||
<div>
|
||||
<div>{resultMessage}</div>
|
||||
<div style={{ marginTop: 8, fontSize: 12, color: '#faad14' }}>
|
||||
失败详情:
|
||||
失败详情:
|
||||
{errors
|
||||
.slice(0, 3)
|
||||
.map((err: any) => `${err.identifier}: ${err.error}`)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ interface Site {
|
|||
[key: string]: any;
|
||||
}
|
||||
|
||||
// 自定义 Hook:管理站点数据
|
||||
// 自定义 Hook:管理站点数据
|
||||
const useSites = () => {
|
||||
// 添加站点数据状态
|
||||
const [sites, setSites] = useState<Site[]>([]);
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ const HistoryOrders: React.FC<HistoryOrdersProps> = ({ customer, siteId }) => {
|
|||
const items = order.line_items || order.items || [];
|
||||
if (Array.isArray(items)) {
|
||||
items.forEach((item: any) => {
|
||||
// 检查商品名称或SKU是否包含yoone(不区分大小写)
|
||||
// 检查商品名称或SKU是否包含yoone(不区分大小写)
|
||||
const itemName = (item.name || '').toLowerCase();
|
||||
const sku = (item.sku || '').toLowerCase();
|
||||
|
||||
|
|
|
|||
|
|
@ -502,7 +502,7 @@ const SyncCustomersModal: React.FC<{
|
|||
};
|
||||
}
|
||||
|
||||
// 添加日期范围过滤(使用 after 和 before 参数)
|
||||
// 添加日期范围过滤(使用 after 和 before 参数)
|
||||
if (values.dateRange && values.dateRange[0] && values.dateRange[1]) {
|
||||
params.where = {
|
||||
...params.where,
|
||||
|
|
@ -536,7 +536,7 @@ const SyncCustomersModal: React.FC<{
|
|||
errors = [],
|
||||
} = result;
|
||||
|
||||
let resultMessage = `同步完成!共处理 ${total} 个客户:`;
|
||||
let resultMessage = `同步完成!共处理 ${total} 个客户:`;
|
||||
if (created > 0) resultMessage += ` 新建 ${created} 个`;
|
||||
if (updated > 0) resultMessage += ` 更新 ${updated} 个`;
|
||||
if (synced > 0) resultMessage += ` 同步成功 ${synced} 个`;
|
||||
|
|
@ -549,7 +549,7 @@ const SyncCustomersModal: React.FC<{
|
|||
<div>
|
||||
<div>{resultMessage}</div>
|
||||
<div style={{ marginTop: 8, fontSize: 12, color: '#faad14' }}>
|
||||
失败详情:
|
||||
失败详情:
|
||||
{errors
|
||||
.slice(0, 3)
|
||||
.map((err: any) => err.email || err.error)
|
||||
|
|
@ -653,12 +653,12 @@ const SyncCustomersModal: React.FC<{
|
|||
placeholder="选择排序方式"
|
||||
options={[
|
||||
{ label: '默认排序', value: '' },
|
||||
{ label: '注册时间(升序)', value: 'date_created:asc' },
|
||||
{ label: '注册时间(降序)', value: 'date_created:desc' },
|
||||
{ label: '邮箱(升序)', value: 'email:asc' },
|
||||
{ label: '邮箱(降序)', value: 'email:desc' },
|
||||
{ label: '姓名(升序)', value: 'first_name:asc' },
|
||||
{ label: '姓名(降序)', value: 'first_name:desc' },
|
||||
{ label: '注册时间(升序)', value: 'date_created:asc' },
|
||||
{ label: '注册时间(降序)', value: 'date_created:desc' },
|
||||
{ label: '邮箱(升序)', value: 'email:asc' },
|
||||
{ label: '邮箱(降序)', value: 'email:desc' },
|
||||
{ label: '姓名(升序)', value: 'first_name:asc' },
|
||||
{ label: '姓名(降序)', value: 'first_name:desc' },
|
||||
]}
|
||||
fieldProps={{
|
||||
allowClear: true,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ const DictPage: React.FC = () => {
|
|||
const [isEditDictModalVisible, setIsEditDictModalVisible] = useState(false);
|
||||
const [editDictData, setEditDictData] = useState<any>(null);
|
||||
|
||||
// 字典项模态框状态(由 DictItemModal 组件管理)
|
||||
// 字典项模态框状态(由 DictItemModal 组件管理)
|
||||
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
||||
const [isEditDictItem, setIsEditDictItem] = useState(false);
|
||||
const [editingDictItemData, setEditingDictItemData] = useState<any>(null);
|
||||
|
|
@ -137,7 +137,7 @@ const DictPage: React.FC = () => {
|
|||
link.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error: any) {
|
||||
message.error('下载字典模板失败:' + (error.message || '未知错误'));
|
||||
message.error('下载字典模板失败:' + (error.message || '未知错误'));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -173,7 +173,7 @@ const DictPage: React.FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
// 处理字典项模态框提交(添加或编辑)
|
||||
// 处理字典项模态框提交(添加或编辑)
|
||||
const handleDictItemModalOk = async (values: any) => {
|
||||
try {
|
||||
if (isEditDictItem && editingDictItemData) {
|
||||
|
|
@ -205,7 +205,7 @@ const DictPage: React.FC = () => {
|
|||
}, 100);
|
||||
} catch (error: any) {
|
||||
message.error(
|
||||
`${isEditDictItem ? '更新' : '添加'}失败:${
|
||||
`${isEditDictItem ? '更新' : '添加'}失败:${
|
||||
error.message || '未知错误'
|
||||
}`,
|
||||
);
|
||||
|
|
@ -275,7 +275,7 @@ const DictPage: React.FC = () => {
|
|||
|
||||
message.success(`成功导出 ${response.length} 条数据`);
|
||||
} catch (error: any) {
|
||||
message.error('导出字典项失败:' + (error.message || '未知错误'));
|
||||
message.error('导出字典项失败:' + (error.message || '未知错误'));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -477,7 +477,7 @@ const DictPage: React.FC = () => {
|
|||
};
|
||||
}
|
||||
|
||||
// 兼容旧的响应格式(直接返回数组)
|
||||
// 兼容旧的响应格式(直接返回数组)
|
||||
return {
|
||||
data: res || [],
|
||||
success: true,
|
||||
|
|
@ -524,7 +524,7 @@ const DictPage: React.FC = () => {
|
|||
</Content>
|
||||
</Layout>
|
||||
|
||||
{/* 字典项 Modal(添加或编辑) */}
|
||||
{/* 字典项 Modal(添加或编辑) */}
|
||||
<DictItemModal
|
||||
visible={isDictItemModalVisible}
|
||||
isEdit={isEditDictItem}
|
||||
|
|
|
|||
|
|
@ -11,19 +11,19 @@ interface DictItemActionsProps {
|
|||
selectedDict?: any;
|
||||
// ProTable 的 actionRef,用于刷新列表
|
||||
actionRef?: React.MutableRefObject<ActionType | undefined>;
|
||||
// 是否显示导出按钮(某些页面可能不需要导出功能)
|
||||
// 是否显示导出按钮(某些页面可能不需要导出功能)
|
||||
showExport?: boolean;
|
||||
// 导入字典项的回调函数(如果不提供,则使用默认的导入逻辑)
|
||||
// 导入字典项的回调函数(如果不提供,则使用默认的导入逻辑)
|
||||
onImport?: (file: File, dictId: number) => Promise<any>;
|
||||
// 导出字典项的回调函数
|
||||
onExport?: () => Promise<void>;
|
||||
// 添加字典项的回调函数
|
||||
onAdd?: () => void;
|
||||
// 刷新字典列表的回调函数(导入成功后可能需要刷新左侧字典列表)
|
||||
// 刷新字典列表的回调函数(导入成功后可能需要刷新左侧字典列表)
|
||||
onRefreshDicts?: () => void;
|
||||
}
|
||||
|
||||
// 字典项操作组合组件(包含添加、导入、导出按钮)
|
||||
// 字典项操作组合组件(包含添加、导入、导出按钮)
|
||||
const DictItemActions: React.FC<DictItemActionsProps> = ({
|
||||
selectedDict,
|
||||
actionRef,
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ const DictItemExportButton: React.FC<DictItemExportButtonProps> = ({
|
|||
message.warning('未提供导出函数');
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error('导出字典项失败:' + (error.message || '未知错误'));
|
||||
message.error('导出字典项失败:' + (error.message || '未知错误'));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ interface DictItemImportButtonProps {
|
|||
actionRef?: React.MutableRefObject<ActionType | undefined>;
|
||||
// 是否禁用按钮
|
||||
disabled?: boolean;
|
||||
// 自定义导入函数,返回 Promise(如果不提供,则使用默认的导入逻辑)
|
||||
// 自定义导入函数,返回 Promise(如果不提供,则使用默认的导入逻辑)
|
||||
onImport?: (file: File, dictId: number) => Promise<any>;
|
||||
// 导入成功后刷新字典列表的回调函数
|
||||
onRefreshDicts?: () => void;
|
||||
|
|
@ -73,7 +73,7 @@ const DictItemImportButton: React.FC<DictItemImportButtonProps> = ({
|
|||
|
||||
// 显示导入结果详情
|
||||
const showImportResult = (result: any) => {
|
||||
// 从 result.data 中获取实际数据(因为后端返回格式为 { success: true, data: {...} })
|
||||
// 从 result.data 中获取实际数据(因为后端返回格式为 { success: true, data: {...} })
|
||||
const data = result.data || result;
|
||||
const { total, processed, updated, created, errors } = data;
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const AttributePage: React.FC = () => {
|
|||
// 右侧字典项 ProTable 的引用
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
||||
// 字典项模态框状态(由 DictItemModal 组件管理)
|
||||
// 字典项模态框状态(由 DictItemModal 组件管理)
|
||||
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
||||
const [isEditDictItem, setIsEditDictItem] = useState(false);
|
||||
const [editingDictItemData, setEditingDictItemData] = useState<any>(null);
|
||||
|
|
@ -99,7 +99,7 @@ const AttributePage: React.FC = () => {
|
|||
|
||||
message.success(`成功导出 ${data.length} 条数据`);
|
||||
} catch (error: any) {
|
||||
message.error('导出字典项失败:' + (error.message || '未知错误'));
|
||||
message.error('导出字典项失败:' + (error.message || '未知错误'));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -389,7 +389,7 @@ const AttributePage: React.FC = () => {
|
|||
</Content>
|
||||
</Layout>
|
||||
|
||||
{/* 字典项 Modal(添加或编辑) */}
|
||||
{/* 字典项 Modal(添加或编辑) */}
|
||||
<DictItemModal
|
||||
visible={isDictItemModalVisible}
|
||||
isEdit={isEditDictItem}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
export default function CsvTool() {
|
||||
return (
|
||||
<div>
|
||||
<h1>产品CSV 工具</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -77,7 +77,7 @@ const CreateForm: React.FC<{
|
|||
const strengthName: string = String(strengthValues?.[0] || '');
|
||||
const flavorName: string = String(flavorValues?.[0] || '');
|
||||
const humidityName: string = String(humidityValues?.[0] || '');
|
||||
|
||||
console.log(formValues)
|
||||
// 调用模板渲染API来生成SKU
|
||||
const {
|
||||
data: rendered,
|
||||
|
|
@ -86,10 +86,25 @@ const CreateForm: React.FC<{
|
|||
} = await templatecontrollerRendertemplate(
|
||||
{ name: 'product.sku' },
|
||||
{
|
||||
brand: brandName || '',
|
||||
strength: strengthName || '',
|
||||
flavor: flavorName || '',
|
||||
humidity: humidityName ? capitalize(humidityName) : '',
|
||||
category: formValues.category,
|
||||
attributes: [
|
||||
{
|
||||
dict: {name: "brand"},
|
||||
shortName: brandName || '',
|
||||
},
|
||||
{
|
||||
dict: {name: "flavor"},
|
||||
shortName: flavorName || '',
|
||||
},
|
||||
{
|
||||
dict: {name: "strength"},
|
||||
shortName: strengthName || '',
|
||||
},
|
||||
{
|
||||
dict: {name: "humidity"},
|
||||
shortName: humidityName ? capitalize(humidityName) : '',
|
||||
},
|
||||
]
|
||||
},
|
||||
);
|
||||
if (!success) {
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ const List: React.FC = () => {
|
|||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '商品SKU',
|
||||
title: '关联商品',
|
||||
dataIndex: 'siteSkus',
|
||||
render: (_, record) => (
|
||||
<>
|
||||
|
|
@ -244,13 +244,7 @@ const List: React.FC = () => {
|
|||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: '商品类型',
|
||||
dataIndex: 'category',
|
||||
render: (_, record: any) => {
|
||||
return record.category?.title || record.category?.name || '-';
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: '价格',
|
||||
dataIndex: 'price',
|
||||
|
|
@ -262,6 +256,13 @@ const List: React.FC = () => {
|
|||
dataIndex: 'promotionPrice',
|
||||
hideInSearch: true,
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '商品类型',
|
||||
dataIndex: 'category',
|
||||
render: (_, record: any) => {
|
||||
return record.category?.title || record.category?.name || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '属性',
|
||||
|
|
@ -442,6 +443,7 @@ const List: React.FC = () => {
|
|||
onError?.(error);
|
||||
}
|
||||
}}
|
||||
|
||||
>
|
||||
<Button>批量导入</Button>
|
||||
</Upload>,
|
||||
|
|
@ -541,6 +543,11 @@ const List: React.FC = () => {
|
|||
rowSelection={{
|
||||
onChange: (_, selectedRows) => setSelectedRows(selectedRows),
|
||||
}}
|
||||
pagination={{
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
pageSizeOptions: ['10', '20', '50', '100', '1000', '2000'],
|
||||
}}
|
||||
/>
|
||||
<BatchEditModal
|
||||
visible={batchEditModalVisible}
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ const PermutationPage: React.FC = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
// 生成CSV表头(包含所有属性列和SKU列)
|
||||
// 生成CSV表头(包含所有属性列和SKU列)
|
||||
const headers = [
|
||||
...attributes.map((attr) => attr.title || attr.name),
|
||||
'SKU',
|
||||
|
|
@ -239,7 +239,7 @@ const PermutationPage: React.FC = () => {
|
|||
// 将表头和数据行合并
|
||||
const csvContent = [headers, ...rows]
|
||||
.map((row) =>
|
||||
// 处理CSV中的特殊字符(逗号、双引号、换行符)
|
||||
// 处理CSV中的特殊字符(逗号、双引号、换行符)
|
||||
row
|
||||
.map((cell) => {
|
||||
const cellStr = String(cell || '');
|
||||
|
|
@ -268,7 +268,7 @@ const PermutationPage: React.FC = () => {
|
|||
const url = URL.createObjectURL(blob);
|
||||
link.setAttribute('href', url);
|
||||
|
||||
// 生成文件名(包含当前分类名称和日期)
|
||||
// 生成文件名(包含当前分类名称和日期)
|
||||
const category = categories.find((c) => c.id === categoryId);
|
||||
const categoryName = category?.name || '产品';
|
||||
const date = new Date().toISOString().slice(0, 10);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ interface Site {
|
|||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
// 定义本地产品接口(与后端 Product 实体匹配)
|
||||
// 定义本地产品接口(与后端 Product 实体匹配)
|
||||
interface SiteProduct {
|
||||
id: number;
|
||||
sku: string;
|
||||
|
|
@ -115,7 +115,7 @@ const SiteProductCell: React.FC<SiteProductCellProps> = ({
|
|||
setLoading(true);
|
||||
try {
|
||||
// 首先查找该产品在该站点的实际SKU
|
||||
// 注意:siteSkus 现在是字符串数组,无法直接匹配站点
|
||||
// 注意:siteSkus 现在是字符串数组,无法直接匹配站点
|
||||
// 这里使用模板生成的 SKU 作为默认值
|
||||
let siteProductSku = '';
|
||||
// 如果需要更精确的站点 SKU 匹配,需要后端提供额外的接口
|
||||
|
|
@ -363,7 +363,7 @@ const SiteProductCell: React.FC<SiteProductCellProps> = ({
|
|||
// 如果没有找到站点产品,显示同步按钮
|
||||
if (!siteProduct) {
|
||||
// 首先查找该产品在该站点的实际SKU
|
||||
// 注意:siteSkus 现在是字符串数组,无法直接匹配站点
|
||||
// 注意:siteSkus 现在是字符串数组,无法直接匹配站点
|
||||
// 这里使用模板生成的 SKU 作为默认值
|
||||
let siteProductSku = '';
|
||||
// 如果需要更精确的站点 SKU 匹配,需要后端提供额外的接口
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ interface Site {
|
|||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
// 定义本地产品接口(与后端 Product 实体匹配)
|
||||
// 定义本地产品接口(与后端 Product 实体匹配)
|
||||
interface SiteProduct {
|
||||
id: number;
|
||||
sku: string;
|
||||
|
|
@ -438,7 +438,7 @@ const ProductSyncPage: React.FC = () => {
|
|||
>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<p>
|
||||
目标站点:
|
||||
目标站点:
|
||||
<strong>{sites.find((s) => s.id === selectedSiteId)?.name}</strong>
|
||||
</p>
|
||||
{selectedRows.length > 0 ? (
|
||||
|
|
@ -452,17 +452,17 @@ const ProductSyncPage: React.FC = () => {
|
|||
|
||||
{syncing && (
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{ marginBottom: 8 }}>同步进度:</div>
|
||||
<div style={{ marginBottom: 8 }}>同步进度:</div>
|
||||
<Progress percent={syncProgress} status="active" />
|
||||
<div style={{ marginTop: 8, fontSize: 12, color: '#666' }}>
|
||||
成功:{syncResults.success} | 失败:{syncResults.failed}
|
||||
成功:{syncResults.success} | 失败:{syncResults.failed}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{syncResults.errors.length > 0 && (
|
||||
<div style={{ marginBottom: 16, maxHeight: 200, overflow: 'auto' }}>
|
||||
<div style={{ marginBottom: 8, color: '#ff4d4f' }}>错误详情:</div>
|
||||
<div style={{ marginBottom: 8, color: '#ff4d4f' }}>错误详情:</div>
|
||||
{syncResults.errors.slice(0, 10).map((error, index) => (
|
||||
<div
|
||||
key={index}
|
||||
|
|
|
|||
|
|
@ -411,7 +411,7 @@ const SiteList: React.FC = () => {
|
|||
name="areas"
|
||||
label="区域"
|
||||
mode="multiple"
|
||||
placeholder="请选择区域(留空表示不修改)"
|
||||
placeholder="请选择区域(留空表示不修改)"
|
||||
showSearch
|
||||
filterOption={(input, option) =>
|
||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||
|
|
@ -422,7 +422,7 @@ const SiteList: React.FC = () => {
|
|||
name="stockPointIds"
|
||||
label="关联仓库"
|
||||
mode="multiple"
|
||||
placeholder="请选择关联仓库(留空表示不修改)"
|
||||
placeholder="请选择关联仓库(留空表示不修改)"
|
||||
request={async () => {
|
||||
// 从后端接口获取仓库数据
|
||||
const res = await stockcontrollerGetallstockpoints();
|
||||
|
|
|
|||
|
|
@ -116,7 +116,6 @@ const ProductsPage: React.FC = () => {
|
|||
// ID
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
hideInSearch: true,
|
||||
width: 120,
|
||||
copyable: true,
|
||||
render: (_, record) => {
|
||||
|
|
@ -156,7 +155,7 @@ const ProductsPage: React.FC = () => {
|
|||
},
|
||||
{
|
||||
// 库存
|
||||
title: '库存',
|
||||
title: '库存数量',
|
||||
dataIndex: 'stock_quantity',
|
||||
hideInSearch: true,
|
||||
},
|
||||
|
|
@ -423,7 +422,7 @@ const ProductsPage: React.FC = () => {
|
|||
params: {
|
||||
page,
|
||||
per_page: pageSize,
|
||||
...where,
|
||||
where,
|
||||
...(orderObj
|
||||
? {
|
||||
sortField: Object.keys(orderObj)[0],
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ const WebhooksPage: React.FC = () => {
|
|||
message.error(isEditMode ? '更新失败' : '创建失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error('表单验证失败:' + error.message);
|
||||
message.error('表单验证失败:' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -305,12 +305,12 @@ const WebhooksPage: React.FC = () => {
|
|||
{ type: 'url', message: '请输入有效的URL' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入回调URL,如:https://example.com/webhook" />
|
||||
<Input placeholder="请输入回调URL,如:https://example.com/webhook" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="secret"
|
||||
label="密钥(可选)"
|
||||
label="密钥(可选)"
|
||||
rules={[{ max: 255, message: '密钥不能超过255个字符' }]}
|
||||
>
|
||||
<Input placeholder="请输入密钥,用于验证webhook请求" />
|
||||
|
|
|
|||
|
|
@ -69,12 +69,12 @@ export const ErpProductBindModal: React.FC<ErpProductBindModalProps> = ({
|
|||
onFinish={handleBind}
|
||||
>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<strong>站点产品信息:</strong>
|
||||
<strong>站点产品信息:</strong>
|
||||
<div>SKU: {siteProduct.sku}</div>
|
||||
<div>名称: {siteProduct.name}</div>
|
||||
{siteProduct.erpProduct && (
|
||||
<div style={{ color: '#ff4d4f' }}>
|
||||
⚠️ 当前已绑定ERP产品:{siteProduct.erpProduct.sku} -{' '}
|
||||
⚠️ 当前已绑定ERP产品:{siteProduct.erpProduct.sku} -{' '}
|
||||
{siteProduct.erpProduct.name}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -164,7 +164,7 @@ export const ErpProductBindModal: React.FC<ErpProductBindModalProps> = ({
|
|||
border: '1px solid #b7eb8f',
|
||||
}}
|
||||
>
|
||||
<strong>已选择:</strong>
|
||||
<strong>已选择:</strong>
|
||||
<div>SKU: {selectedProduct.sku}</div>
|
||||
<div>名称: {selectedProduct.name}</div>
|
||||
{selectedProduct.nameCn && (
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
// API 更新时间:
|
||||
// API 唯一标识:
|
||||
// API 更新时间:
|
||||
// API 唯一标识:
|
||||
import * as area from './area';
|
||||
import * as category from './category';
|
||||
import * as customer from './customer';
|
||||
import * as dict from './dict';
|
||||
import * as locales from './locales';
|
||||
import * as logistics from './logistics';
|
||||
import * as media from './media';
|
||||
import * as order from './order';
|
||||
import * as product from './product';
|
||||
import * as site from './site';
|
||||
|
|
@ -26,7 +25,6 @@ export default {
|
|||
dict,
|
||||
locales,
|
||||
logistics,
|
||||
media,
|
||||
order,
|
||||
product,
|
||||
siteApi,
|
||||
|
|
|
|||
|
|
@ -1,92 +0,0 @@
|
|||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
import { request } from 'umi';
|
||||
|
||||
/** 此处后端没有提供注释 DELETE /media/${param0} */
|
||||
export async function mediacontrollerDelete(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.mediacontrollerDeleteParams,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { id: param0, ...queryParams } = params;
|
||||
return request<any>(`/media/${param0}`, {
|
||||
method: 'DELETE',
|
||||
params: {
|
||||
...queryParams,
|
||||
},
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /media/list */
|
||||
export async function mediacontrollerList(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.mediacontrollerListParams,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<any>('/media/list', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
...params,
|
||||
},
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 POST /media/update/${param0} */
|
||||
export async function mediacontrollerUpdate(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.mediacontrollerUpdateParams,
|
||||
body: Record<string, any>,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { id: param0, ...queryParams } = params;
|
||||
return request<any>(`/media/update/${param0}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
params: { ...queryParams },
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 POST /media/upload */
|
||||
export async function mediacontrollerUpload(
|
||||
body: {},
|
||||
files?: File[],
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const formData = new FormData();
|
||||
|
||||
if (files) {
|
||||
files.forEach((f) => formData.append('files', f || ''));
|
||||
}
|
||||
|
||||
Object.keys(body).forEach((ele) => {
|
||||
const item = (body as any)[ele];
|
||||
|
||||
if (item !== undefined && item !== null) {
|
||||
if (typeof item === 'object' && !(item instanceof File)) {
|
||||
if (item instanceof Array) {
|
||||
item.forEach((f) => formData.append(ele, f || ''));
|
||||
} else {
|
||||
formData.append(
|
||||
ele,
|
||||
new Blob([JSON.stringify(item)], { type: 'application/json' }),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
formData.append(ele, item);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return request<any>('/media/upload', {
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
requestType: 'form',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
|
@ -282,6 +282,20 @@ export async function siteapicontrollerBatchfulfillorders(
|
|||
);
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /site-api/${param0}/orders/count */
|
||||
export async function siteapicontrollerCountorders(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.siteapicontrollerCountordersParams,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { siteId: param0, ...queryParams } = params;
|
||||
return request<Record<string, any>>(`/site-api/${param0}/orders/count`, {
|
||||
method: 'GET',
|
||||
params: { ...queryParams },
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /site-api/${param0}/orders/export */
|
||||
export async function siteapicontrollerExportorders(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
|
|
@ -776,23 +790,18 @@ export async function siteapicontrollerCancelfulfillment(
|
|||
);
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 POST /site-api/${param1}/orders/${param0}/fulfill */
|
||||
export async function siteapicontrollerFulfillorder(
|
||||
/** 此处后端没有提供注释 GET /site-api/${param1}/orders/${param0}/fulfillments */
|
||||
export async function siteapicontrollerGetorderfulfillments(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.siteapicontrollerFulfillorderParams,
|
||||
body: API.FulfillmentDTO,
|
||||
params: API.siteapicontrollerGetorderfulfillmentsParams,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { id: param0, siteId: param1, ...queryParams } = params;
|
||||
const { orderId: param0, siteId: param1, ...queryParams } = params;
|
||||
return request<Record<string, any>>(
|
||||
`/site-api/${param1}/orders/${param0}/fulfill`,
|
||||
`/site-api/${param1}/orders/${param0}/fulfillments`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'GET',
|
||||
params: { ...queryParams },
|
||||
data: body,
|
||||
...(options || {}),
|
||||
},
|
||||
);
|
||||
|
|
@ -859,23 +868,6 @@ export async function siteapicontrollerCreateordernote(
|
|||
);
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /site-api/${param1}/orders/${param0}/trackings */
|
||||
export async function siteapicontrollerGetordertrackings(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.siteapicontrollerGetordertrackingsParams,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const { orderId: param0, siteId: param1, ...queryParams } = params;
|
||||
return request<Record<string, any>>(
|
||||
`/site-api/${param1}/orders/${param0}/trackings`,
|
||||
{
|
||||
method: 'GET',
|
||||
params: { ...queryParams },
|
||||
...(options || {}),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /site-api/${param1}/products/${param0} */
|
||||
export async function siteapicontrollerGetproduct(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ declare namespace API {
|
|||
};
|
||||
|
||||
type BatchErrorItemDTO = {
|
||||
/** 错误项标识(如ID、邮箱等) */
|
||||
/** 错误项标识(如ID、邮箱等) */
|
||||
identifier?: string;
|
||||
/** 错误信息 */
|
||||
error?: string;
|
||||
|
|
@ -591,22 +591,6 @@ declare namespace API {
|
|||
weight?: Cubid;
|
||||
};
|
||||
|
||||
type mediacontrollerDeleteParams = {
|
||||
force?: boolean;
|
||||
siteId?: number;
|
||||
id: number;
|
||||
};
|
||||
|
||||
type mediacontrollerListParams = {
|
||||
pageSize?: number;
|
||||
page?: number;
|
||||
siteId?: number;
|
||||
};
|
||||
|
||||
type mediacontrollerUpdateParams = {
|
||||
id: number;
|
||||
};
|
||||
|
||||
type Money = {
|
||||
currency?: string;
|
||||
value?: string;
|
||||
|
|
@ -1433,7 +1417,7 @@ declare namespace API {
|
|||
keyword?: string;
|
||||
/** 是否禁用 */
|
||||
isDisabled?: boolean;
|
||||
/** 站点ID列表(逗号分隔) */
|
||||
/** 站点ID列表(逗号分隔) */
|
||||
ids?: string;
|
||||
};
|
||||
|
||||
|
|
@ -1635,6 +1619,10 @@ declare namespace API {
|
|||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerCountordersParams = {
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerCreatecustomerParams = {
|
||||
siteId: number;
|
||||
};
|
||||
|
|
@ -1813,11 +1801,6 @@ declare namespace API {
|
|||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerFulfillorderParams = {
|
||||
id: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerGetcustomerordersParams = {
|
||||
/** 页码 */
|
||||
page?: number;
|
||||
|
|
@ -1882,6 +1865,11 @@ declare namespace API {
|
|||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerGetorderfulfillmentsParams = {
|
||||
orderId: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerGetordernotesParams = {
|
||||
id: string;
|
||||
siteId: number;
|
||||
|
|
@ -1910,11 +1898,6 @@ declare namespace API {
|
|||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerGetordertrackingsParams = {
|
||||
orderId: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type siteapicontrollerGetproductParams = {
|
||||
id: string;
|
||||
siteId: number;
|
||||
|
|
@ -2095,7 +2078,7 @@ declare namespace API {
|
|||
keyword?: string;
|
||||
/** 是否禁用 */
|
||||
isDisabled?: boolean;
|
||||
/** 站点ID列表(逗号分隔) */
|
||||
/** 站点ID列表(逗号分隔) */
|
||||
ids?: string;
|
||||
};
|
||||
|
||||
|
|
@ -2406,7 +2389,7 @@ declare namespace API {
|
|||
type SyncCustomersDTO = {
|
||||
/** 站点ID */
|
||||
siteId?: number;
|
||||
/** 查询参数(支持where和orderBy) */
|
||||
/** 查询参数(支持where和orderBy) */
|
||||
params?: UnifiedSearchParamsDTO;
|
||||
};
|
||||
|
||||
|
|
@ -2787,7 +2770,7 @@ declare namespace API {
|
|||
variation?: boolean;
|
||||
/** 属性选项 */
|
||||
options?: string[];
|
||||
/** 变体属性值(单个值) */
|
||||
/** 变体属性值(单个值) */
|
||||
option?: string;
|
||||
};
|
||||
|
||||
|
|
@ -3222,6 +3205,11 @@ declare namespace API {
|
|||
id?: number;
|
||||
};
|
||||
|
||||
type webhookcontrollerHandleshoppywebhookParams = {
|
||||
signature?: string;
|
||||
siteId?: string;
|
||||
};
|
||||
|
||||
type webhookcontrollerHandlewoowebhookParams = {
|
||||
siteId?: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,26 @@ export async function webhookcontrollerTest(options?: { [key: string]: any }) {
|
|||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 POST /webhook/shoppy */
|
||||
export async function webhookcontrollerHandleshoppywebhook(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
params: API.webhookcontrollerHandleshoppywebhookParams,
|
||||
body: Record<string, any>,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
return request<any>('/webhook/shoppy', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
params: {
|
||||
...params,
|
||||
},
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 POST /webhook/woocommerce */
|
||||
export async function webhookcontrollerHandlewoowebhook(
|
||||
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export interface BatchOperationResult {
|
|||
}
|
||||
|
||||
/**
|
||||
* 显示批量操作结果(导入、删除等)
|
||||
* 显示批量操作结果(导入、删除等)
|
||||
* @param result 批量操作结果对象
|
||||
* @param operationType 操作类型,用于显示在消息中
|
||||
*/
|
||||
|
|
@ -30,12 +30,12 @@ export function showBatchOperationResult(
|
|||
result: BatchOperationResult,
|
||||
operationType: string = '操作',
|
||||
): string {
|
||||
// 从 result.data 中获取实际数据(因为后端返回格式为 { success: true, data: {...} })
|
||||
// 从 result.data 中获取实际数据(因为后端返回格式为 { success: true, data: {...} })
|
||||
const data = (result as any).data || result;
|
||||
const { total, processed, created, updated, deleted, errors } = data;
|
||||
|
||||
// 构建结果消息
|
||||
let messageContent = `${operationType}结果:共 ${total} 条,成功 ${processed} 条`;
|
||||
let messageContent = `${operationType}结果:共 ${total} 条,成功 ${processed} 条`;
|
||||
|
||||
if (created) {
|
||||
messageContent += `,创建 ${created} 条`;
|
||||
|
|
@ -54,7 +54,7 @@ export function showBatchOperationResult(
|
|||
const errorDetails = errors
|
||||
.map((err: BatchErrorItem) => `${err.identifier}: ${err.error}`)
|
||||
.join('\n');
|
||||
message.warning(messageContent + '\n\n错误详情:\n' + errorDetails);
|
||||
message.warning(messageContent + '\n\n错误详情:\n' + errorDetails);
|
||||
} else {
|
||||
message.success(messageContent);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue