forked from yoone/WEB
1
0
Fork 0
WEB/src/pages/Site/List/index.tsx

450 lines
12 KiB
TypeScript

import { ordercontrollerSyncorders } from '@/servers/api/order';
import {
sitecontrollerCreate,
sitecontrollerDisable,
sitecontrollerList,
sitecontrollerUpdate,
} from '@/servers/api/site';
import { subscriptioncontrollerSync } from '@/servers/api/subscription';
import { stockcontrollerGetallstockpoints } from '@/servers/api/stock';
import { ActionType, ProColumns, ProTable, DrawerForm, ProFormSelect, ProFormSwitch } from '@ant-design/pro-components';
import { Button, message, notification, Popconfirm, Space, Tag, Form } from 'antd';
import React, { useRef, useState, useEffect } from 'react';
import EditSiteForm from '../Shop/EditSiteForm'; // 引入重构后的表单组件
import * as countries from 'i18n-iso-countries';
import zhCN from 'i18n-iso-countries/langs/zh';
// 区域数据项类型
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 [batchEditOpen, setBatchEditOpen] = useState(false);
const [batchEditForm] = Form.useForm();
countries.registerLocale(zhCN);
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 getCountryOptions = () => {
// 获取所有国家的 ISO 代码
const countryCodes = countries.getAlpha2Codes();
// 将国家代码转换为选项数组
return Object.keys(countryCodes).map((code) => ({
label: countries.getName(code, 'zh') || code, // 使用中文名称, 如果没有则使用代码
value: code,
}));
};
// 处理批量编辑提交
const handleBatchEditFinish = async (values: any) => {
if (!selectedRowKeys.length) return;
const hide = message.loading('正在批量更新...', 0);
try {
// 遍历所有选中的站点 ID
for (const id of selectedRowKeys) {
// 构建更新数据对象,只包含用户填写了值的字段
const updateData: any = {};
// 如果用户选择了区域,则更新区域
if (values.areas && values.areas.length > 0) {
updateData.areas = values.areas;
}
// 如果用户选择了仓库,则更新仓库
if (values.stockPointIds && values.stockPointIds.length > 0) {
updateData.stockPointIds = values.stockPointIds;
}
// 如果用户设置了禁用状态,则更新状态
if (values.isDisabled !== undefined) {
updateData.isDisabled = values.isDisabled;
}
// 如果有需要更新的字段,则调用更新接口
if (Object.keys(updateData).length > 0) {
await sitecontrollerUpdate({ id: String(id) }, updateData);
}
}
hide();
message.success('批量更新成功');
setBatchEditOpen(false);
setSelectedRowKeys([]);
batchEditForm.resetFields();
actionRef.current?.reload();
} catch (error: any) {
hide();
message.error(error.message || '批量更新失败');
}
};
// 当批量编辑弹窗打开时,重置表单
useEffect(() => {
if (batchEditOpen) {
batchEditForm.resetFields();
}
}, [batchEditOpen, batchEditForm]);
// 表格列定义
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',
hideInSearch: true,
},
{
title: '平台',
dataIndex: 'type',
valueType: 'select',
request: async () => [
{ label: 'WooCommerce', value: 'woocommerce' },
{ label: 'Shopyy', value: 'shopyy' },
],
},
{
// 地区列配置
title: "地区",
dataIndex: "areas",
hideInSearch: true,
render: (_, row) => {
// 如果没有关联地区,显示"全局"标签
if (!row.areas || row.areas.length === 0) {
return <Tag color="default"></Tag>;
}
// 遍历显示所有关联的地区名称
return (
<Space wrap>
{row.areas.map((area) => (
<Tag color="geekblue" key={area.code}>
{area.name}
</Tag>
))}
</Space>
);
},
},
{
title: '关联仓库',
dataIndex: 'stockPoints',
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,
}}
pagination={{
defaultPageSize: 20,
showSizeChanger: true,
showQuickJumper: true,
}}
toolBarRender={() => [
<Button
type="primary"
onClick={() => {
setEditing(null);
setOpen(true);
}}
>
</Button>,
<Button
disabled={!selectedRowKeys.length}
onClick={() => setBatchEditOpen(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}
/>
{/* 批量编辑弹窗 */}
<DrawerForm
title={`批量编辑站点 (${selectedRowKeys.length} 个)`}
form={batchEditForm}
open={batchEditOpen}
onOpenChange={setBatchEditOpen}
onFinish={handleBatchEditFinish}
layout="vertical"
>
<ProFormSelect
name="areas"
label="区域"
mode="multiple"
placeholder="请选择区域(留空表示不修改)"
showSearch
filterOption={(input, option) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
}
options={getCountryOptions()}
/>
<ProFormSelect
name="stockPointIds"
label="关联仓库"
mode="multiple"
placeholder="请选择关联仓库(留空表示不修改)"
request={async () => {
// 从后端接口获取仓库数据
const res = await stockcontrollerGetallstockpoints();
// 使用可选链和空值合并运算符来安全地处理可能未定义的数据
return (
res?.data?.map((sp: any) => ({ label: sp.name, value: sp.id })) ??
[]
);
}}
/>
<ProFormSwitch
name="isDisabled"
label="是否禁用"
fieldProps={{
checkedChildren: '是',
unCheckedChildren: '否',
}}
/>
</DrawerForm>
</>
);
};
export default SiteList;