diff --git a/src/pages/Order/Items/index.tsx b/src/pages/Order/Items/index.tsx new file mode 100644 index 0000000..ee452fe --- /dev/null +++ b/src/pages/Order/Items/index.tsx @@ -0,0 +1,149 @@ +import React, { useRef } from 'react'; +import { PageContainer } from '@ant-design/pro-layout'; +import type { ProColumns, ActionType, ProTableProps } from '@ant-design/pro-components'; +import { ProTable } from '@ant-design/pro-components'; +import { App } from 'antd'; +import dayjs from 'dayjs'; +import { ordercontrollerGetordersales } from '@/servers/api/order'; +import { sitecontrollerAll } from '@/servers/api/site'; + +// 列表行数据结构(订单商品聚合) +interface OrderItemAggRow { + externalProductId: number; // 商品ID(来自 WooCommerce 产品ID) + externalVariationId: number; // 变体ID(来自 WooCommerce 变体ID) + name: string; // 商品名称 + totalQuantity: number; // 总售出数量(时间范围内) + totalOrders: number; // 涉及订单数(去重) + firstOrderCount: number; // 客户首单次数(该商品) + secondOrderCount: number; // 客户第二次购买次数(该商品) + thirdOrderCount: number; // 客户第三次购买次数(该商品) + moreThirdOrderCount: number; // 客户超过三次购买次数(该商品) +} + +const OrderItemsPage: React.FC = () => { + const actionRef = useRef(); + const { message } = App.useApp(); + + // 列配置(中文标题,符合当前项目风格;显示英文默认语言可后续走国际化) + const columns: ProColumns[] = [ + { + title: '商品名称', + dataIndex: 'name', + width: 220, + }, + { + title: '商品ID', + dataIndex: 'externalProductId', + width: 120, + hideInSearch: true, + }, + { + title: '变体ID', + dataIndex: 'externalVariationId', + width: 120, + hideInSearch: true, + }, + { + title: '总售出数量', + dataIndex: 'totalQuantity', + width: 130, + hideInSearch: true, + }, + { + title: '订单数', + dataIndex: 'totalOrders', + width: 110, + hideInSearch: true, + }, + { + title: '首单次数', + dataIndex: 'firstOrderCount', + width: 120, + hideInSearch: true, + }, + { + title: '第二次购买', + dataIndex: 'secondOrderCount', + width: 120, + hideInSearch: true, + }, + { + title: '第三次购买', + dataIndex: 'thirdOrderCount', + width: 120, + hideInSearch: true, + }, + { + title: '超过三次购买', + dataIndex: 'moreThirdOrderCount', + width: 140, + hideInSearch: true, + }, + // 搜索区域字段 + { + title: '站点', + dataIndex: 'siteId', + valueType: 'select', + request: async () => { + // 拉取站点列表(后台 /site/all) + const { data = [] } = await sitecontrollerAll(); + return (data || []).map((item: any) => ({ label: item.siteName, value: item.id })); + }, + }, + { + title: '时间范围', + dataIndex: 'dateRange', + valueType: 'dateRange', + hideInTable: true, + }, + { + title: '商品关键字', + dataIndex: 'name', + hideInTable: true, + }, + ]; + + // 表格请求方法:调用 /order/getOrderSales 接口并设置 isSource=true 获取订单项聚合 + const request: ProTableProps['request'] = async (params:any) => { + try { + const { current = 1, pageSize = 10, siteId, name } = params as any; + const [startDate, endDate] = (params as any).dateRange || []; + // 调用后端接口(isSource=true 表示按订单项聚合) + const resp = await ordercontrollerGetordersales({ + current, + pageSize, + siteId, + name, + isSource: true as any, + startDate: startDate ? (dayjs(startDate).toISOString() as any) : undefined, + endDate: endDate ? (dayjs(endDate).toISOString() as any) : undefined, + } as any); + const { success, data, message: errMsg } = resp as any; + if (!success) throw new Error(errMsg || '获取失败'); + return { + data: (data?.items ?? []) as OrderItemAggRow[], + total: data?.total ?? 0, + success: true, + }; + } catch (e: any) { + message.error(e?.message || '获取失败'); + return { data: [], total: 0, success: false }; + } + }; + + return ( + + + actionRef={actionRef} + rowKey={(r) => `${r.externalProductId}-${r.externalVariationId}-${r.name}`} + columns={columns} + request={request} + pagination={{ showSizeChanger: true }} + search={{ labelWidth: 90, span: 6 }} + toolBarRender={false} + /> + + ); +}; + +export default OrderItemsPage; \ No newline at end of file diff --git a/src/pages/Order/List/index.tsx b/src/pages/Order/List/index.tsx index 35e2d6e..4561f65 100644 --- a/src/pages/Order/List/index.tsx +++ b/src/pages/Order/List/index.tsx @@ -893,7 +893,23 @@ const Detail: React.FC<{
    {record?.items?.map((item: any) => (
  • - {item.name}:{item.quantity} + {item.name}:{item.quantity} +
  • + ))} +
+ ); + }} + /> + {/* TODO 显示 related order */} + { + return ( +
    + {record?.related?.map((item: any) => ( +
  • + {JSON.stringify(item)}
  • ))}
diff --git a/src/pages/Subscription/List/index.tsx b/src/pages/Subscription/List/index.tsx index 41f801d..7182cdf 100644 --- a/src/pages/Subscription/List/index.tsx +++ b/src/pages/Subscription/List/index.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useRef, useState } from 'react'; import { ActionType, DrawerForm, @@ -7,7 +7,9 @@ import { ProFormSelect, ProTable, } from '@ant-design/pro-components'; -import { App, Button, Tag } from 'antd'; +import { App, Button, Tag, Drawer, List } from 'antd'; +import dayjs from 'dayjs'; +import { request } from 'umi'; import { subscriptioncontrollerList, subscriptioncontrollerSync, @@ -32,6 +34,12 @@ const SUBSCRIPTION_STATUS_ENUM: Record = { const ListPage: React.FC = () => { // 表格操作引用:用于在同步后触发表格刷新 const actionRef = useRef(); + const { message } = App.useApp(); + + // 关联订单抽屉状态 + const [drawerOpen, setDrawerOpen] = useState(false); + const [drawerTitle, setDrawerTitle] = useState('详情'); + const [relatedOrders, setRelatedOrders] = useState([]); // 表格列定义(尽量与项目风格保持一致) const columns: ProColumns[] = [ @@ -55,6 +63,13 @@ const ListPage: React.FC = () => { hideInSearch: true, width: 120, }, + { + title: '父订单号', + dataIndex: 'parent_id', + hideInSearch: true, + width: 120, + render: (_, row) => (row?.parent_id ? {row.parent_id} : '-'), + }, { title: '状态', dataIndex: 'status', @@ -114,6 +129,69 @@ const ListPage: React.FC = () => { hideInSearch: true, width: 160, }, + { + title: '操作', + dataIndex: 'actions', + hideInSearch: true, + width: 120, + render: (_, row) => ( + + ), + }, ]; return ( @@ -138,6 +216,34 @@ const ListPage: React.FC = () => { // 工具栏:订阅同步入口 toolBarRender={() => []} /> + {/* 关联订单抽屉:展示订单号、关系、时间、状态与金额 */} + setDrawerOpen(false)} + > + 关联订单} + dataSource={relatedOrders} + renderItem={(item: any) => ( + + +
+ {item?.date_created ? dayjs(item.date_created).format('YYYY-MM-DD HH:mm') : '-'} + {item?.status || '-'} + + {item?.currency_symbol || ''} + {typeof item?.total === 'number' ? item.total.toFixed(2) : item?.total ?? '-'} + +
+
+ )} + /> +
); }; diff --git a/src/pages/Subscription/Orders/index.tsx b/src/pages/Subscription/Orders/index.tsx new file mode 100644 index 0000000..e940239 --- /dev/null +++ b/src/pages/Subscription/Orders/index.tsx @@ -0,0 +1,230 @@ +import React, { useRef, useState } from 'react'; +import { PageContainer } from '@ant-design/pro-layout'; +import type { ProColumns, ActionType, ProTableProps } from '@ant-design/pro-components'; +import { ProTable } from '@ant-design/pro-components'; +import { App, Tag, Button, Drawer, List } from 'antd'; +import dayjs from 'dayjs'; +import { ordercontrollerGetorders } from '@/servers/api/order'; +import { sitecontrollerAll } from '@/servers/api/site'; +import { request } from 'umi'; + +interface OrderItemRow { + id: number; + externalOrderId: string; + siteId: string; + date_created: string; + customer_email: string; + payment_method: string; + total: number; + orderStatus: string; +} + +const OrdersPage: React.FC = () => { + const actionRef = useRef(); + const { message } = App.useApp(); + // 抽屉状态:用于展示与订阅相关的订单详情(含行项目meta) + const [drawerOpen, setDrawerOpen] = useState(false); + const [drawerTitle, setDrawerTitle] = useState('订阅关联'); + const [drawerItems, setDrawerItems] = useState([]); + const [drawerMeta, setDrawerMeta] = useState([]); + const [isSubscription, setIsSubscription] = useState(false); + + const columns: ProColumns[] = [ + { + title: '订单ID', + dataIndex: 'externalOrderId', + width: 120, + ellipsis: true, + hideInSearch: true, + }, + { + title: '站点', + dataIndex: 'siteId', + width: 120, + valueType: 'select', + request: async () => { + const { data = [] } = await sitecontrollerAll(); + return (data || []).map((item: any) => ({ label: item.siteName, value: item.id })); + }, + }, + { + title: '下单时间', + dataIndex: 'date_created', + width: 180, + hideInSearch: true, + render: (_, row) => (row?.date_created ? dayjs(row.date_created).format('YYYY-MM-DD HH:mm') : '-'), + }, + { + title: '邮箱', + dataIndex: 'customer_email', + width: 200, + }, + { + title: '支付方式', + dataIndex: 'payment_method', + width: 140, + }, + { + title: '金额', + dataIndex: 'total', + width: 100, + hideInSearch: true, + }, + { + title: 'ERP状态', + dataIndex: 'orderStatus', + width: 120, + hideInSearch: true, + render: (_, row) => {row.orderStatus}, + }, + { + title: '订阅关联', + dataIndex: 'subscription_related', + width: 120, + hideInSearch: true, + render: (_, row) => ( + + ), + }, + { + title: '时间范围', + dataIndex: 'dateRange', + valueType: 'dateRange', + hideInTable: true, + }, + { + title: '商品关键字', + dataIndex: 'keyword', + hideInTable: true, + }, + ]; + + const request: ProTableProps['request'] = async (params) => { + try { + const { current = 1, pageSize = 10, siteId, keyword, customer_email, payment_method } = params as any; + const [startDate, endDate] = (params as any).dateRange || []; + const resp = await ordercontrollerGetorders({ + current, + pageSize, + siteId, + keyword, + customer_email, + payment_method, + isSubscriptionOnly: true as any, + startDate: startDate ? (dayjs(startDate).toISOString() as any) : undefined, + endDate: endDate ? (dayjs(endDate).toISOString() as any) : undefined, + } as any); + const { success, data, message: errMsg } = resp as any; + if (!success) throw new Error(errMsg || '获取失败'); + return { + data: (data?.items ?? []) as OrderItemRow[], + total: data?.total ?? 0, + success: true, + }; + } catch (e: any) { + message.error(e?.message || '获取失败'); + return { data: [], total: 0, success: false }; + } + }; + + return ( + + + actionRef={actionRef} + rowKey='id' + columns={columns} + request={request} + pagination={{ showSizeChanger: true }} + search={{ + labelWidth: 90, + span: 6, + }} + toolBarRender={false} + /> + {/* 订阅关联抽屉:展示行项目与订单元数据,标注是否订阅 */} + setDrawerOpen(false)} + > +
+ + {isSubscription ? '订阅订单' : '非订阅订单'} + +
+ {/* 行项目列表,展示 meta_data 关键键值 */} + 订单行项目} + dataSource={drawerItems} + renderItem={(item: any) => ( + + +
+ {(Array.isArray(item?.meta_data) ? item.meta_data : []).map((m: any) => ( + {`${m?.key}: ${m?.value}`} + ))} +
+
+ )} + /> + {/* 订单级元数据 */} + 订单元数据} + dataSource={drawerMeta} + renderItem={(m: any) => ( + + {`${m?.key}: ${m?.value}`} + + )} + /> +
+
+ ); +}; + +export default OrdersPage;