forked from yoone/WEB
306 lines
7.9 KiB
TypeScript
306 lines
7.9 KiB
TypeScript
import { ordercontrollerSyncorders } from '@/servers/api/order';
|
|
import {
|
|
sitecontrollerCreate,
|
|
sitecontrollerDisable,
|
|
sitecontrollerList,
|
|
sitecontrollerUpdate,
|
|
} from '@/servers/api/site';
|
|
import { subscriptioncontrollerSync } from '@/servers/api/subscription';
|
|
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
|
import { Button, message, notification, Popconfirm, Space, Tag } from 'antd';
|
|
import React, { useRef, useState } from 'react';
|
|
import EditSiteForm from '../Shop/EditSiteForm'; // 引入重构后的表单组件
|
|
|
|
// 区域数据项类型
|
|
interface AreaItem {
|
|
code: string;
|
|
name: string;
|
|
}
|
|
|
|
// 仓库数据项类型
|
|
interface StockPointItem {
|
|
id: number;
|
|
name: string;
|
|
}
|
|
|
|
// 站点数据项类型(前端不包含密钥字段,后端列表不返回密钥)
|
|
export interface SiteItem {
|
|
id: number;
|
|
name: string;
|
|
description?: string;
|
|
apiUrl?: string;
|
|
websiteUrl?: string; // 网站地址
|
|
type?: 'woocommerce' | 'shopyy';
|
|
skuPrefix?: string;
|
|
isDisabled: number;
|
|
areas?: AreaItem[];
|
|
stockPoints?: StockPointItem[];
|
|
}
|
|
|
|
const SiteList: React.FC = () => {
|
|
const actionRef = useRef<ActionType>();
|
|
const [open, setOpen] = useState(false);
|
|
const [editing, setEditing] = useState<SiteItem | null>(null);
|
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
|
|
|
const handleSync = async (ids: number[]) => {
|
|
if (!ids.length) return;
|
|
const hide = message.loading('正在同步...', 0);
|
|
|
|
const stats = {
|
|
orders: { success: 0, fail: 0 },
|
|
subscriptions: { success: 0, fail: 0 },
|
|
};
|
|
|
|
try {
|
|
for (const id of ids) {
|
|
// 同步订单
|
|
const orderRes = await ordercontrollerSyncorders({ siteId: id });
|
|
if (orderRes.success) {
|
|
stats.orders.success += 1;
|
|
} else {
|
|
stats.orders.fail += 1;
|
|
}
|
|
|
|
// 同步订阅
|
|
const subRes = await subscriptioncontrollerSync({ siteId: id });
|
|
if (subRes.success) {
|
|
stats.subscriptions.success += 1;
|
|
} else {
|
|
stats.subscriptions.fail += 1;
|
|
}
|
|
}
|
|
hide();
|
|
|
|
notification.success({
|
|
message: '同步完成',
|
|
description: (
|
|
<div>
|
|
<p>
|
|
订单: 成功 {stats.orders.success}, 失败 {stats.orders.fail}
|
|
</p>
|
|
<p>
|
|
订阅: 成功 {stats.subscriptions.success}, 失败{' '}
|
|
{stats.subscriptions.fail}
|
|
</p>
|
|
</div>
|
|
),
|
|
duration: null, // 不自动关闭
|
|
});
|
|
|
|
setSelectedRowKeys([]);
|
|
actionRef.current?.reload();
|
|
} catch (error: any) {
|
|
hide();
|
|
message.error(error.message || '同步失败');
|
|
}
|
|
};
|
|
|
|
// 表格列定义
|
|
const columns: ProColumns<SiteItem>[] = [
|
|
{
|
|
title: 'ID',
|
|
dataIndex: 'id',
|
|
width: 80,
|
|
sorter: true,
|
|
hideInSearch: true,
|
|
},
|
|
{ title: '名称', dataIndex: 'name', width: 220 },
|
|
{ title: '描述', dataIndex: 'description', width: 220, hideInSearch: true },
|
|
{ title: 'API 地址', dataIndex: 'apiUrl', width: 280, hideInSearch: true },
|
|
{
|
|
title: '网站地址',
|
|
dataIndex: 'websiteUrl',
|
|
width: 280,
|
|
hideInSearch: true,
|
|
render: (text) => (
|
|
<a href={text as string} target="_blank" rel="noopener noreferrer">
|
|
{text}
|
|
</a>
|
|
),
|
|
},
|
|
{
|
|
title: 'SKU 前缀',
|
|
dataIndex: 'skuPrefix',
|
|
width: 160,
|
|
hideInSearch: true,
|
|
},
|
|
{
|
|
title: '平台',
|
|
dataIndex: 'type',
|
|
width: 140,
|
|
valueType: 'select',
|
|
request: async () => [
|
|
{ label: 'WooCommerce', value: 'woocommerce' },
|
|
{ label: 'Shopyy', value: 'shopyy' },
|
|
],
|
|
},
|
|
{
|
|
title: '关联仓库',
|
|
dataIndex: 'stockPoints',
|
|
width: 200,
|
|
hideInSearch: true,
|
|
render: (_, row) => {
|
|
if (!row.stockPoints || row.stockPoints.length === 0) {
|
|
return <Tag>无</Tag>;
|
|
}
|
|
return (
|
|
<Space wrap>
|
|
{row.stockPoints.map((sp) => (
|
|
<Tag color="blue" key={sp.id}>
|
|
{sp.name}
|
|
</Tag>
|
|
))}
|
|
</Space>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'isDisabled',
|
|
width: 120,
|
|
hideInSearch: true,
|
|
render: (_, row) => (
|
|
<Tag color={row.isDisabled ? 'red' : 'green'}>
|
|
{row.isDisabled ? '已禁用' : '启用中'}
|
|
</Tag>
|
|
),
|
|
},
|
|
{
|
|
title: '操作',
|
|
dataIndex: 'actions',
|
|
width: 240,
|
|
fixed: 'right',
|
|
hideInSearch: true,
|
|
render: (_, row) => (
|
|
<Space>
|
|
<Button
|
|
size="small"
|
|
type="primary"
|
|
onClick={() => handleSync([row.id])}
|
|
>
|
|
同步
|
|
</Button>
|
|
<Button
|
|
size="small"
|
|
onClick={() => {
|
|
setEditing(row);
|
|
setOpen(true);
|
|
}}
|
|
>
|
|
编辑
|
|
</Button>
|
|
<Popconfirm
|
|
title={row.isDisabled ? '启用站点' : '禁用站点'}
|
|
description={row.isDisabled ? '确认启用该站点?' : '确认禁用该站点?'}
|
|
onConfirm={async () => {
|
|
try {
|
|
await sitecontrollerDisable(
|
|
{ id: String(row.id) },
|
|
{ disabled: !row.isDisabled },
|
|
);
|
|
message.success('更新成功');
|
|
actionRef.current?.reload();
|
|
} catch (e: any) {
|
|
message.error(e?.message || '更新失败');
|
|
}
|
|
}}
|
|
>
|
|
<Button size="small" type="primary" danger={!row.isDisabled}>
|
|
{row.isDisabled ? '启用' : '禁用'}
|
|
</Button>
|
|
</Popconfirm>
|
|
</Space>
|
|
),
|
|
},
|
|
];
|
|
|
|
// 表格数据请求
|
|
const tableRequest = async (params: Record<string, any>) => {
|
|
try {
|
|
const { current, pageSize, name, type } = params;
|
|
const resp = await sitecontrollerList({
|
|
current,
|
|
pageSize,
|
|
keyword: name || undefined,
|
|
type: type || undefined,
|
|
});
|
|
// 假设 resp 直接就是后端返回的结构,包含 items 和 total
|
|
return {
|
|
data: (resp?.data?.items ?? []) as SiteItem[],
|
|
total: resp?.data?.total ?? 0,
|
|
success: true,
|
|
};
|
|
} catch (e: any) {
|
|
message.error(e?.message || '获取失败');
|
|
return { data: [], total: 0, success: false };
|
|
}
|
|
};
|
|
|
|
const handleFinish = async (values: any) => {
|
|
try {
|
|
if (editing) {
|
|
await sitecontrollerUpdate({ id: String(editing.id) }, values);
|
|
message.success('更新成功');
|
|
} else {
|
|
await sitecontrollerCreate(values);
|
|
message.success('创建成功');
|
|
}
|
|
setOpen(false);
|
|
actionRef.current?.reload();
|
|
return true;
|
|
} catch (error: any) {
|
|
message.error(error.message || '操作失败');
|
|
return false;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<ProTable<SiteItem>
|
|
scroll={{ x: 'max-content' }}
|
|
actionRef={actionRef}
|
|
rowKey="id"
|
|
columns={columns}
|
|
request={tableRequest}
|
|
rowSelection={{
|
|
selectedRowKeys,
|
|
onChange: setSelectedRowKeys,
|
|
}}
|
|
toolBarRender={() => [
|
|
<Button
|
|
type="primary"
|
|
onClick={() => {
|
|
setEditing(null);
|
|
setOpen(true);
|
|
}}
|
|
>
|
|
新建站点
|
|
</Button>,
|
|
<Button
|
|
disabled={!selectedRowKeys.length}
|
|
onClick={() => handleSync(selectedRowKeys as number[])}
|
|
>
|
|
批量同步
|
|
</Button>,
|
|
]}
|
|
/>
|
|
|
|
<EditSiteForm
|
|
open={open}
|
|
onOpenChange={(visible) => {
|
|
setOpen(visible);
|
|
if (!visible) {
|
|
setEditing(null);
|
|
}
|
|
}}
|
|
initialValues={editing}
|
|
isEdit={!!editing}
|
|
onFinish={handleFinish}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default SiteList;
|