Compare commits

...

2 Commits

Author SHA1 Message Date
zhuotianyuan 80ba76aacc fix(订单同步): 修复订单同步功能并添加日期范围选择
修复订单同步接口调用参数错误
在同步表单中添加日期范围选择器
调整订单来源统计接口名称拼写错误
2026-01-13 19:26:51 +08:00
zhuotianyuan d372f72935 feat: 添加webhook地址字段和区域选择功能
在站点列表和编辑表单中添加webhook地址字段
在订单列表中添加付款日期字段
在统计页面添加国家/区域选择功能,支持多选和搜索
引入i18n-iso-countries库实现国家名称本地化
2026-01-09 14:59:19 +08:00
6 changed files with 135 additions and 26 deletions

View File

@ -5,8 +5,10 @@ import {
DrawerForm,
ProForm,
ProFormSelect,
ProFormDateRangePicker,
} from '@ant-design/pro-components';
import { Button } from 'antd';
import dayjs from 'dayjs';
import React from 'react';
// 定义SyncForm组件的props类型
@ -14,6 +16,7 @@ interface SyncFormProps {
tableRef: React.MutableRefObject<ActionType | undefined>;
onFinish: (values: any) => Promise<void>;
siteId?: string;
dateRange?: [dayjs.Dayjs, dayjs.Dayjs];
}
/**
@ -21,7 +24,7 @@ interface SyncFormProps {
* @param {SyncFormProps} props
* @returns {React.ReactElement}
*/
const SyncForm: React.FC<SyncFormProps> = ({ tableRef, onFinish, siteId }) => {
const SyncForm: React.FC<SyncFormProps> = ({ tableRef, onFinish, siteId, dateRange }) => {
// 使用 antd 的 App 组件提供的 message API
const [loading, setLoading] = React.useState(false);
@ -49,7 +52,11 @@ const SyncForm: React.FC<SyncFormProps> = ({ tableRef, onFinish, siteId }) => {
// 返回一个抽屉表单
return (
<DrawerForm<API.ordercontrollerSyncorderParams>
title="同步订单"
initialValues={{
dateRange: [dayjs().subtract(1, 'week'), dayjs()],
}}
title="同步订单"
// 表单的触发器,一个带图标的按钮
trigger={
<Button key="syncSite" type="primary">
@ -67,6 +74,7 @@ const SyncForm: React.FC<SyncFormProps> = ({ tableRef, onFinish, siteId }) => {
onFinish={onFinish}
>
<ProForm.Group>
{/* 站点选择框 */}
<ProFormSelect
name="siteId"
@ -83,6 +91,22 @@ const SyncForm: React.FC<SyncFormProps> = ({ tableRef, onFinish, siteId }) => {
}));
}}
/>
<ProFormDateRangePicker
name="dateRange"
label="同步日期范围"
placeholder={['开始日期', '结束日期']}
transform={(value) => {
return {
dateRange: value,
};
}}
fieldProps={{
showTime: false,
style: { width: '100%' },
}}
/>
</ProForm.Group>
</DrawerForm>
);

View File

@ -215,11 +215,17 @@ const ListPage: React.FC = () => {
dataIndex: 'externalOrderId',
},
{
title: '订单日期',
title: '订单创建日期',
dataIndex: 'date_created',
hideInSearch: true,
valueType: 'dateTime',
},
{
title: '付款日期',
dataIndex: 'date_paid',
hideInSearch: true,
valueType: 'dateTime',
},
{
title: '金额',
dataIndex: 'total',
@ -490,7 +496,7 @@ const ListPage: React.FC = () => {
success,
message: errMsg,
data,
} = await ordercontrollerSyncorders(values);
} = await ordercontrollerSyncorders(values,{after:values.dateRange?.[0]+'T00:00:00Z',before:values.dateRange?.[1]+'T23:59:59Z'});
if (!success) {
throw new Error(errMsg);
}
@ -986,6 +992,7 @@ const Detail: React.FC<{
);
}}
/>
{/* 显示 related order */}
<ProDescriptions.Item
label="关联"
@ -1495,15 +1502,15 @@ const Shipping: React.FC<{
<ProFormList
label="发货产品"
name="sales"
// rules={[
// {
// rules={[
// {
// required: true,
// message: '至少需要一个商品',
// validator: (_, value) =>
// value && value.length > 0
// ? Promise.resolve()
// : Promise.reject('至少需要一个商品'),
// },
//</Col> ? Promise.resolve()
// : Promise.reject('至少需要一个商品'),
// },
// ]}
>
<ProForm.Group>

View File

@ -188,6 +188,8 @@ const SiteList: React.FC = () => {
</a>
),
},
{ title: 'webhook地址', dataIndex: 'webhookUrl', width: 280, hideInSearch: true },
{
title: 'SKU 前缀',
dataIndex: 'skuPrefix',

View File

@ -100,6 +100,11 @@ const EditSiteForm: React.FC<EditSiteFormProps> = ({
label="网站地址"
placeholder="请输入网站地址"
/>
<ProFormText
name="webhookUrl"
label="Webhook 地址"
placeholder="请输入 Webhook 地址"
/>
<ProFormSelect
name="type"
label="平台"
@ -164,6 +169,7 @@ const EditSiteForm: React.FC<EditSiteFormProps> = ({
label="区域"
mode="multiple"
placeholder="请选择区域"
showSearch
filterOption={(input, option) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())

View File

@ -2,6 +2,7 @@ import { ORDER_STATUS_ENUM } from '@/constants';
import { AddTag } from '@/pages/Customer/List';
import { customercontrollerDeltag } from '@/servers/api/customer';
import { sitecontrollerAll } from '@/servers/api/site';
import * as countries from 'i18n-iso-countries';
import {
statisticscontrollerGetorderbydate,
statisticscontrollerGetorderbyemail,
@ -22,7 +23,8 @@ import dayjs from 'dayjs';
import ReactECharts from 'echarts-for-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import weekOfYear from 'dayjs/plugin/weekOfYear';
import zhCN from 'i18n-iso-countries/langs/zh';
countries.registerLocale(zhCN);
dayjs.extend(weekOfYear);
const highlightText = (text: string, keyword: string) => {
if (!keyword) return text;
@ -38,6 +40,17 @@ const highlightText = (text: string, keyword: string) => {
);
};
// 获取所有国家/地区的选项
const getCountryOptions = () => {
// 获取所有国家的 ISO 代码
const countryCodes = countries.getAlpha2Codes();
// 将国家代码转换为选项数组
return Object.keys(countryCodes).map((code) => ({
label: countries.getName(code, 'zh') || code, // 使用中文名称, 如果没有则使用代码
value: code,
}));
};
const ListPage: React.FC = () => {
const [xAxis, setXAxis] = useState([]);
const [series, setSeries] = useState<any[]>([]);
@ -620,6 +633,19 @@ const ListPage: React.FC = () => {
}));
}}
/>
<ProFormSelect
name="country"
label="区域"
mode="multiple"
placeholder="请选择区域"
showSearch
filterOption={(input, option) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
}
options={getCountryOptions()}
/>
{/* <ProFormSelect
label="类型"
name="purchaseType"

View File

@ -2,25 +2,35 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
statisticscontrollerGetinativeusersbymonth,
statisticscontrollerGetordersorce,
statisticscontrollerGetordersource,
} from '@/servers/api/statistics';
import {
ActionType,
PageContainer,
ProColumns,
ProTable,
ProForm,
ProFormSelect,
} from '@ant-design/pro-components';
import { Space, Tag } from 'antd';
import dayjs from 'dayjs';
import ReactECharts from 'echarts-for-react';
import { HistoryOrder } from '../Order';
import * as countries from 'i18n-iso-countries';
import zhCN from 'i18n-iso-countries/langs/zh';
countries.registerLocale(zhCN);
const ListPage: React.FC = () => {
const [data, setData] = useState({});
useEffect(() => {
statisticscontrollerGetordersorce().then(({ data, success }) => {
const initialValues = {
country: ['CA'],
};
function handleSubmit(values: typeof initialValues) {
statisticscontrollerGetordersource({params: values}).then(({ data, success }) => {
if (success) setData(data);
});
}
useEffect(() => {
handleSubmit(initialValues)
}, []);
const option = useMemo(() => {
@ -39,11 +49,11 @@ const ListPage: React.FC = () => {
data: data?.inactiveRes?.map((v) => v.new_user_count)?.sort((_) => -1),
label: {
show: true,
formatter: function (params) {
formatter: function (params) {
if (!params.value) return '';
return Math.abs(params.value)
+'\n'
+Math.abs(data?.inactiveRes?.find((item) => item.order_month === params.name)?.new_user_total || 0);
+ '\n'
+ Math.abs(data?.inactiveRes?.find((item) => item.order_month === params.name)?.new_user_total || 0);
},
color: '#000000',
},
@ -59,11 +69,11 @@ const ListPage: React.FC = () => {
data: data?.inactiveRes?.map((v) => v.old_user_count)?.sort((_) => -1),
label: {
show: true,
formatter: function (params) {
formatter: function (params) {
if (!params.value) return '';
return Math.abs(params.value)
+'\n'
+Math.abs(data?.inactiveRes?.find((item) => item.order_month === params.name)?.old_user_total || 0);
+ '\n'
+ Math.abs(data?.inactiveRes?.find((item) => item.order_month === params.name)?.old_user_total || 0);
},
color: '#000000',
},
@ -83,11 +93,11 @@ const ListPage: React.FC = () => {
show: true,
formatter: function (params) {
if (!params.value) return '';
return Math.abs(params.value)
+'\n'+
+Math.abs(data?.res?.find((item) => item.order_month === params.name &&
return Math.abs(params.value)
+ '\n' +
+Math.abs(data?.res?.find((item) => item.order_month === params.name &&
item.first_order_month_group === v)?.total || 0);
},
},
color: '#000000',
},
data: xAxisData.map((month) => {
@ -258,9 +268,29 @@ const ListPage: React.FC = () => {
);
},
},
];
return (
<PageContainer ghost>
<ProForm
initialValues={initialValues}
layout="inline"
onFinish={handleSubmit}
>
<ProFormSelect
name="country"
label="区域"
mode="multiple"
placeholder="请选择区域"
showSearch
filterOption={(input, option) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
}
options={getCountryOptions()}
/>
</ProForm>
<ReactECharts
option={option}
style={{ height: 1050 }}
@ -277,6 +307,7 @@ const ListPage: React.FC = () => {
},
}}
/>
{tableData?.length ? (
<ProTable
search={false}
@ -293,4 +324,17 @@ const ListPage: React.FC = () => {
);
};
// 获取所有国家/地区的选项
const getCountryOptions = () => {
// 获取所有国家的 ISO 代码
const countryCodes = countries.getAlpha2Codes();
// 将国家代码转换为选项数组
return Object.keys(countryCodes).map((code) => ({
label: countries.getName(code, 'zh') || code, // 使用中文名称, 如果没有则使用代码
value: code,
}));
};
export default ListPage;