import InternationalPhoneInput from '@/components/InternationalPhoneInput'; import { HistoryOrder } from '@/pages/Statistics/Order'; import { ORDER_STATUS_ENUM } from '@/constants'; import { logisticscontrollerCreateshipment, logisticscontrollerGetShipmentFee, logisticscontrollerDelshipment, logisticscontrollerGetpaymentmethods, logisticscontrollerGetratelist, logisticscontrollerGetshippingaddresslist, logisticscontrollerGetShipmentLabel, } from '@/servers/api/logistics'; import { ordercontrollerCancelorder, ordercontrollerChangestatus, ordercontrollerCompletedorder, ordercontrollerCreatenote, ordercontrollerCreateorder, ordercontrollerGetorderbynumber, ordercontrollerGetorderdetail, ordercontrollerGetorders, ordercontrollerRefundorder, ordercontrollerSyncorder, ordercontrollerSyncorderbyid, ordercontrollerUpdateOrderItems, } from '@/servers/api/order'; import { productcontrollerSearchproducts } from '@/servers/api/product'; import { sitecontrollerAll } from '@/servers/api/site'; import { stockcontrollerGetallstockpoints } from '@/servers/api/stock'; import { formatShipmentState, formatSource } from '@/utils/format'; import { CodeSandboxOutlined, CopyOutlined, DeleteFilled, DownOutlined, FileDoneOutlined, SyncOutlined, TagsOutlined, } from '@ant-design/icons'; import { ActionType, DrawerForm, ModalForm, PageContainer, ProColumns, ProDescriptions, ProForm, ProFormDatePicker, ProFormDependency, ProFormDigit, ProFormInstance, ProFormItem, ProFormList, ProFormRadio, ProFormSelect, ProFormText, ProFormTextArea, ProTable, } from '@ant-design/pro-components'; import { App, Button, Card, Col, Descriptions, Divider, Drawer, Dropdown, Empty, message, Popconfirm, Radio, Row, Space, Tabs, TabsProps, } from 'antd'; import Item from 'antd/es/list/Item'; import dayjs from 'dayjs'; import React, { useMemo, useRef, useState } from 'react'; import { printPDF } from '@/utils/util'; const ListPage: React.FC = () => { const actionRef = useRef(); const [activeKey, setActiveKey] = useState('all'); const [count, setCount] = useState([]); const tabs: TabsProps['items'] = useMemo(() => { const total = count.reduce((acc, cur) => acc + Number(cur.count), 0); const tabs = [ { key: 'pending', label: '待确认', }, { key: 'processing', label: '待发货', }, { key: 'completed', label: '已完成', }, { key: 'cancelled', label: '已取消', }, { key: 'refunded', label: '已退款', }, { key: 'failed', label: '失败', }, { key: 'after_sale_pending', label: '售后处理中', }, { key: 'pending_reshipment', label: '待补发', }, // { // key: 'pending_refund', // label: '待退款', // }, ].map((v) => { const number = count.find((el) => el.status === v.key)?.count || '0'; return { label: `${v.label}(${number})`, key: v.key, }; }); return [ { key: 'all', label: `全部(${total})`, }, ...tabs, ]; }, [count]); const { message } = App.useApp(); const columns: ProColumns[] = [ { title: 'ID', dataIndex: 'id', hideInSearch: true, }, { title: '日期', dataIndex: 'date', hideInTable: true, valueType: 'dateRange', }, { title: '订单号', dataIndex: 'externalOrderId', }, { title: '站点', dataIndex: 'siteId', valueType: 'select', request: async () => { const { data = [] } = await sitecontrollerAll(); return data.map((item) => ({ label: item.siteName, value: item.id, })); }, }, { title: '订单包含', dataIndex: 'keyword', hideInTable: true, }, { title: '订单日期', dataIndex: 'date_created', hideInSearch: true, valueType: 'dateTime', }, { title: '金额', dataIndex: 'total', hideInSearch: true, }, { title: '总订单数', dataIndex: 'order_count', hideInSearch: true, }, { title: '总订单金额', dataIndex: 'total_spent', hideInSearch: true, }, { title: '客户邮箱', dataIndex: 'customer_email', }, { title: '州', hideInSearch: true, render: (_, record) => record.shipping?.state || record.billing?.state, }, { title: '状态', dataIndex: 'orderStatus', hideInSearch: true, valueType: 'select', valueEnum: ORDER_STATUS_ENUM, }, { title: '物流', dataIndex: 'shipmentList', hideInSearch: true, render: (_, record) => { return (
{record?.shipmentList?.map((item) => { if (!item) return; return (
{item.tracking_provider}:{item.primary_tracking_number} ( {formatShipmentState(item.state)})
); })}
); }, }, { title: 'IP', dataIndex: 'customer_ip_address', hideInSearch: true, }, { title: '设备', dataIndex: 'device_type', hideInSearch: true, }, { title: '来源', hideInSearch: true, render: (_, record) => formatSource(record.source_type, record.utm_source), }, { title: '客户备注', dataIndex: 'customer_note', hideInSearch: true, }, { title: '操作', dataIndex: 'option', valueType: 'option', fixed: 'right', width: '200', render: (_, record) => { return ( <> {['processing', 'pending_reshipment'].includes( record.orderStatus, ) ? ( <> ) : ( <> )} { try { const { success, message: errMsg } = await ordercontrollerSyncorderbyid({ siteId: record.siteId as string, orderId: record.externalOrderId as string, }); if (!success) { throw new Error(errMsg); } message.success('同步成功'); actionRef.current?.reload(); } catch (error) { message.error(error?.message || '同步失败'); } }} > 同步订单 ), style: { display: [ 'after_sale_pending', 'pending_reshipment', ].includes(record.orderStatus) ? 'none' : 'block', }, }, { key: 'history', label: , }, { key: 'note', label: , }, { key: 'cancel', label: ( { try { const { success, message: errMsg } = await ordercontrollerChangestatus( { id: record.id, }, { status: 'after_sale_pending', }, ); if (!success) { throw new Error(errMsg); } actionRef.current?.reload(); } catch (error: any) { message.error(error.message); } }} > ), style: { display: [ 'processing', 'pending_reshipment', 'completed', 'pending_refund', ].includes(record.orderStatus) ? 'block' : 'none', }, }, ], }} > e.preventDefault()}> 更多 ); }, }, ]; return ( [ , , ]} request={async ({ date, ...param }) => { if (param.status === 'all') { delete param.status; } if (date) { const [startDate, endDate] = date; param.startDate = `${startDate} 00:00:00`; param.endDate = `${endDate} 23:59:59`; } const { data, success } = await ordercontrollerGetorders(param); if (success) { setCount(data?.count || []); return { total: data?.total || 0, data: data?.items || [], }; } return { data: [], }; }} columns={columns} /> ); }; const SyncForm: React.FC<{ tableRef: React.MutableRefObject; }> = ({ tableRef }) => { const { message } = App.useApp(); return ( title="同步订单" trigger={ } autoFocusFirstInput drawerProps={{ destroyOnHidden: true, }} onFinish={async (values) => { try { const { success, message: errMsg } = await ordercontrollerSyncorder( values, ); if (!success) { throw new Error(errMsg); } message.success('同步成功'); tableRef.current?.reload(); return true; } catch (error: any) { message.error(error.message); } }} > { const { data = [] } = await sitecontrollerAll(); return data.map((item) => ({ label: item.siteName, value: item.id, })); }} /> ); }; const Detail: React.FC<{ tableRef: React.MutableRefObject; orderId: number; record: API.Order; }> = ({ tableRef, orderId, record }) => { const [visiable, setVisiable] = useState(false); const { message } = App.useApp(); const ref = useRef(); const initRequest = async () => { const { data, success }: API.OrderDetailRes = await ordercontrollerGetorderdetail({ orderId, }); if (!success || !data) return { data: {} }; data.sales = data.sales?.reduce( (acc: API.OrderSale[], cur: API.OrderSale) => { let idx = acc.findIndex((v: any) => v.productId === cur.productId); if (idx === -1) { acc.push(cur); } else { acc[idx].quantity += cur.quantity; } return acc; }, [], ); return { data, }; }; return ( <> setVisiable(false)} footer={[ , ...(['after_sale_pending', 'pending_reshipment'].includes( record.orderStatus, ) ? [] : [ , , ]), // ...(['processing', 'pending_reshipment'].includes(record.orderStatus) // ? [ // , // , // ] // : []), ...([ 'processing', 'pending_reshipment', 'completed', 'pending_refund', ].includes(record.orderStatus) ? [ , { try { const { success, message: errMsg } = await ordercontrollerChangestatus( { id: record.id, }, { status: 'after_sale_pending', }, ); if (!success) { throw new Error(errMsg); } tableRef.current?.reload(); } catch (error: any) { message.error(error.message); } }} > , ] : []), ...(record.orderStatus === 'after_sale_pending' ? [ , { try { const { success, message: errMsg } = await ordercontrollerCancelorder({ id: record.id, }); if (!success) { throw new Error(errMsg); } tableRef.current?.reload(); } catch (error: any) { message.error(error.message); } }} > , , { try { const { success, message: errMsg } = await ordercontrollerRefundorder({ id: record.id, }); if (!success) { throw new Error(errMsg); } tableRef.current?.reload(); } catch (error: any) { message.error(error.message); } }} > , , { try { const { success, message: errMsg } = await ordercontrollerCompletedorder({ id: record.id, }); if (!success) { throw new Error(errMsg); } tableRef.current?.reload(); } catch (error: any) { message.error(error.message); } }} > , , { try { const { success, message: errMsg } = await ordercontrollerChangestatus( { id: record.id, }, { status: 'pending_reshipment', }, ); if (!success) { throw new Error(errMsg); } tableRef.current?.reload(); } catch (error: any) { message.error(error.message); } }} > , ] : []), ]} > { const { data = [] } = await sitecontrollerAll(); return data.map((item) => ({ label: item.siteName, value: item.id, })); }} /> formatSource(record.source_type, record.utm_source) } /> { return (
company: {record?.shipping?.company || record?.billing?.company || '-'}
first_name: {record?.shipping?.first_name || record?.billing?.first_name || '-'}
last_name: {record?.shipping?.last_name || record?.billing?.last_name || '-'}
country: {record?.shipping?.country || record?.billing?.country || '-'}
state: {record?.shipping?.state || record?.billing?.state || '-'}
city: {record?.shipping?.city || record?.billing?.city || '-'}
postcode: {record?.shipping?.postcode || record?.billing?.postcode || '-'}
phone: {record?.shipping?.phone || record?.billing?.phone || '-'}
address_1: {record?.shipping?.address_1 || record?.billing?.address_1 || '-'}
); }} /> { return (
    {record?.items?.map((item: any) => (
  • {item.name}:{item.quantity}
  • ))}
); }} /> { return (
    {record?.sales?.map((item: any) => (
  • {item.name}:{item.quantity}
  • ))}
); }} /> { return ( ) }} /> { if (!record.notes || record.notes.length === 0) return ; return (
{record.notes.map((note: any) => (
{note.username} {note.createdAt}
{note.content}
))}
); }} /> { console.log('record', record); if (!record.shipment || record.shipment.length === 0) { return ; } return (
{record.shipment.map((v) => ( {v.tracking_provider} {v.primary_tracking_number} { try { await navigator.clipboard.writeText( v.tracking_url, ); message.success('复制成功!'); } catch (err) { message.error('复制失败!'); } }} /> } actions={ v.state === 'waiting-for-scheduling' || v.state === 'waiting-for-transit' ? [ { try { const { success, message: errMsg } = await logisticscontrollerDelshipment({ id: v.id, }); if (!success) { throw new Error(errMsg); } tableRef.current?.reload(); initRequest(); } catch (error: any) { message.error(error.message); } }} > 取消运单 , ] : [] } >
订单号: {v?.orderIds?.join(',')}
{v?.items?.map((item) => (
{item.name}: {item.quantity}
))}
))}
); }} />
); }; const OrderNote: React.FC<{ id: number; descRef?: React.MutableRefObject; }> = ({ id, descRef }) => { const { message } = App.useApp(); return ( 备注 } onFinish={async (values: any) => { try { const { success, message: errMsg } = await ordercontrollerCreatenote({ ...values, orderId: id, }); if (!success) { throw new Error(errMsg); } descRef?.current?.reload(); message.success('提交成功'); return true; } catch (error: any) { message.error(error.message); } }} > ); }; const region = { AB: 'Alberta', BC: 'British', MB: 'Manitoba', NB: 'New', NL: 'Newfoundland', NS: 'Nova', ON: 'Ontario', PE: 'Prince', QC: 'Quebec', SK: 'Saskatchewan', NT: 'Northwest', NU: 'Nunavut', YT: 'Yukon', }; const Shipping: React.FC<{ id: number; tableRef?: React.MutableRefObject; descRef?: React.MutableRefObject; reShipping?: boolean; }> = ({ id, tableRef, descRef, reShipping = false }) => { const [options, setOptions] = useState([]); const formRef = useRef(); const [shipmentFee, setShipmentFee] = useState(0); const [rates, setRates] = useState([]); const [ratesLoading, setRatesLoading] = useState(false); const { message } = App.useApp(); return ( 创建运单 } request={async () => { const { data, success }: API.OrderDetailRes = await ordercontrollerGetorderdetail({ orderId: id, }); if (!success || !data) return {}; data.sales = data.sales?.reduce( (acc: API.OrderSale[], cur: API.OrderSale) => { let idx = acc.findIndex((v: any) => v.productId === cur.productId); if (idx === -1) { acc.push(cur); } else { acc[idx].quantity += cur.quantity; } return acc; }, [], ); setOptions( data.sales?.map((item) => ({ label: item.name, value: item.sku, })) || [], ); if (reShipping) data.sales = [{}]; let shipmentInfo = localStorage.getItem('shipmentInfo'); if (shipmentInfo) shipmentInfo = JSON.parse(shipmentInfo); return { ...data, // payment_method_id: shipmentInfo?.payment_method_id, stockPointId: shipmentInfo?.stockPointId, details: { destination: { name: data?.shipping?.company || data?.billing?.company || ' ', address: { address_line_1: data?.shipping?.address_1 || data?.billing?.address_1, city: data?.shipping?.city || data?.billing?.city, region: data?.shipping?.state || data?.billing?.state, postal_code: data?.shipping?.postcode || data?.billing?.postcode, }, contact_name: data?.shipping?.first_name || data?.shipping?.last_name ? `${data?.shipping?.first_name} ${data?.shipping?.last_name}` : `${data?.billing?.first_name} ${data?.billing?.last_name}`, phone_number: { phone: data?.shipping?.phone || data?.billing?.phone, }, email_addresses: data?.shipping?.email || data?.billing?.email, signature_requirement: 'not-required', }, origin: { name: data?.siteName, email_addresses: data?.email, contact_name: data?.siteName, phone_number: shipmentInfo?.phone_number, address: { region: shipmentInfo?.region, city: shipmentInfo?.city, postal_code: shipmentInfo?.postal_code, address_line_1: shipmentInfo?.address_line_1, }, }, packaging_type: 'package', expected_ship_date: dayjs(), packaging_properties: { packages: [ { measurements: { weight: { unit: 'LBS', value: 1, }, cuboid: { unit: 'IN', l: 6, w: 4, h: 4, }, }, description: 'food', }, ], }, }, }; }} onFinish={async ({ customer_note, notes, items, details, externalOrderId, ...data }) => { details.origin.email_addresses = details.origin.email_addresses.split(','); details.destination.email_addresses = details.destination.email_addresses.split(','); details.destination.phone_number.number = details.destination.phone_number.phone; details.origin.phone_number.number = details.origin.phone_number.phone; try { const { success, message: errMsg, ...resShipment } = await logisticscontrollerCreateshipment( { orderId: id }, { details, ...data, }, ); if (!success) throw new Error(errMsg); message.success('创建成功'); tableRef?.current?.reload(); descRef?.current?.reload(); localStorage.setItem( 'shipmentInfo', JSON.stringify({ // payment_method_id: data.payment_method_id, stockPointId: data.stockPointId, region: details.origin.address.region, city: details.origin.address.city, postal_code: details.origin.address.postal_code, address_line_1: details.origin.address.address_line_1, phone_number: details.origin.phone_number, }), ); // todo, 直接打印label // const { resLabel } = await logisticscontrollerGetShipmentLabel(resShipment.data.shipmentId); // console.log('res', resShipment.data.shipmentId, resLabel); // const labelContent = resLabel.content; // printPDF([labelContent]); return true; } catch (error) { message.error(error?.message || '创建失败'); } }} onFinishFailed={() => { const element = document.querySelector('.ant-form-item-explain-error'); if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }} > []} readonly > { if (!number) return []; const { data, success } = await ordercontrollerGetorderbynumber({ number, }); if (success) { return data.map((v) => ({ label: `${v.siteName} ${v.externalOrderId}`, value: v.id, })); } return []; }} /> {/* { const { data, success } = await logisticscontrollerGetpaymentmethods(); if (success) { return data.map((v) => ({ label: v.label, value: v.id, })); } return []; }} /> */} []} > // value && value.length > 0 // ? Promise.resolve() // : Promise.reject('至少需要一个商品'), // }, // ]} > { if (!keyWords || keyWords.length < 2) return options; try { const { data } = await productcontrollerSearchproducts({ name: keyWords, }); return ( data?.map((item) => { return { label: `${item.name} - ${item.nameCn}`, value: item?.sku, }; }) || options ); } catch (error) { return options; } }} name="sku" label="产品" placeholder="请选择产品" tooltip="至少输入3个字符" fieldProps={{ showSearch: true, filterOption: false, }} debounceTime={300} // 防抖,减少请求频率 rules={[{ required: true, message: '请选择产品' }]} /> { formRef?.current?.setFieldsValue({ stockPointId, details: { origin: { address, phone_number: { phone: phone_number, extension: phone_number_extension, }, }, }, }); }} /> } > { const { data = [] } = await stockcontrollerGetallstockpoints(); return data.map((item) => ({ label: item.name, value: item.id, })); }} /> { return current && current < dayjs().startOf('day'); }, }} transform={(value) => { return { details: { expected_ship_date: { year: dayjs(value).year(), month: dayjs(value).month() + 1, day: dayjs(value).date(), }, }, }; }} /> {({ details: { packaging_type } }) => { if (packaging_type === 'package') { return ( value && value.length > 0 ? Promise.resolve() : Promise.reject('至少选择一个包裹'), }, ]} > {(f, idx, action) => { return ( { action.setCurrentRowData({ measurements: { weight: { unit: 'lb', value: 1, }, cuboid: { unit: 'in', l: 6, w: 4, h: 4, }, }, }); }} > 尺寸1 { action.setCurrentRowData({ measurements: { weight: { unit: 'lb', value: 5, }, cuboid: { unit: 'in', l: 8, w: 6, h: 6, }, }, }); }} > 尺寸2 { action.setCurrentRowData({ measurements: { weight: { unit: 'lb', value: 5, }, cuboid: { unit: 'in', l: 12, w: 8, h: 6, }, }, }); }} > 尺寸3 ); }} ); } if (packaging_type === 'courier-pak') { return ( value && value.length > 0 ? Promise.resolve() : Promise.reject('至少选择一个'), }, ]} > ); } }} ); }; const SalesChange: React.FC<{ id: number; detailRef?: React.MutableRefObject; reShipping?: boolean; }> = ({ id, detailRef }) => { const [options, setOptions] = useState([]); const formRef = useRef(); return ( 换货 } request={async () => { const { data, success }: API.OrderDetailRes = await ordercontrollerGetorderdetail({ orderId: id, }); if (!success || !data) return {}; data.sales = data.sales?.reduce((acc: API.OrderSale[], cur: API.OrderSale) => { let idx = acc.findIndex((v: any) => v.productId === cur.productId); if (idx === -1) { acc.push(cur); } else { acc[idx].quantity += cur.quantity; } return acc; }, [], ); // setOptions( // data.sales?.map((item) => ({ // label: item.name, // value: item.sku, // })) || [], // ); return { ...data }; }} onFinish={async (formData: any) => { const { sales } = formData; const res = await ordercontrollerUpdateOrderItems(id, sales); if (!res.success) { message.error(`更新货物信息失败: ${res.message}`); return false; } message.success('更新成功') detailRef?.current?.reload(); return true; }} > { if (!keyWords || keyWords.length < 2) return options; try { const { data } = await productcontrollerSearchproducts({ name: keyWords, }); return ( data?.map((item) => { return { label: `${item.name} - ${item.nameCn}`, value: item?.sku, }; }) || options ); } catch (error) { return options; } }} name="sku" label="产品" placeholder="请选择产品" tooltip="至少输入3个字符" fieldProps={{ showSearch: true, filterOption: false, }} debounceTime={300} // 防抖,减少请求频率 rules={[{ required: true, message: '请选择产品' }]} /> ); } const CreateOrder: React.FC<{ tableRef?: React.MutableRefObject; }> = ({ tableRef }) => { const formRef = useRef(); const { message } = App.useApp(); return ( 创建订单 } params={{ source_type: 'admin', }} onFinish={async ({ items, details, ...data }) => { try { const { success, message: errMsg } = await ordercontrollerCreateorder( { ...data, customer_email: data?.billing?.email, }, ); if (!success) throw new Error(errMsg); message.success('创建成功'); tableRef?.current?.reload(); return true; } catch (error) { message.error(error?.message || '创建失败'); } }} onFinishFailed={() => { const element = document.querySelector('.ant-form-item-explain-error'); if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }} > { if (!keyWords || keyWords.length < 2) return options; try { const { data } = await productcontrollerSearchproducts({ name: keyWords, }); return ( data?.map((item) => { return { label: `${item.name} - ${item.nameCn}`, value: item?.sku, }; }) || options ); } catch (error) { return options; } }} name="sku" label="产品" placeholder="请选择产品" tooltip="至少输入3个字符" fieldProps={{ showSearch: true, filterOption: false, }} debounceTime={300} // 防抖,减少请求频率 rules={[{ required: true, message: '请选择产品' }]} /> {/* */} ); }; const AddressPicker: React.FC<{ value?: any; onChange?: (value: any) => void; }> = ({ onChange, value }) => { const [selectedRow, setSelectedRow] = useState(null); const { message } = App.useApp(); const columns: ProColumns[] = [ { title: '仓库点', dataIndex: 'stockPointId', hideInSearch: true, valueType: 'select', request: async () => { const { data = [] } = await stockcontrollerGetallstockpoints(); return data.map((item) => ({ label: item.name, value: item.id, })); }, }, { title: '地区', dataIndex: ['address', 'region'], hideInSearch: true, }, { title: '城市', dataIndex: ['address', 'city'], hideInSearch: true, }, { title: '邮编', dataIndex: ['address', 'postal_code'], hideInSearch: true, }, { title: '详细地址', dataIndex: ['address', 'address_line_1'], hideInSearch: true, }, { title: '联系电话', render: (_, record) => `+${record.phone_number_extension} ${record.phone_number}`, hideInSearch: true, }, ]; return ( 选择地址} modalProps={{ destroyOnHidden: true }} onFinish={async () => { if (!selectedRow) { message.error('请选择地址'); return false; } if (onChange) onChange(selectedRow); return true; }} > { const { data, success } = await logisticscontrollerGetshippingaddresslist(); if (success) { return { data: data, }; } return { data: [], }; }} columns={columns} search={false} rowSelection={{ type: 'radio', onChange: (_, selectedRows) => { setSelectedRow(selectedRows[0]); }, }} /> ); }; export default ListPage;