feat(promo): HQ 推广码订单只计已完成并补扫码订单快链
列表与详情的订单数/转化率按 COMPLETED 实时统计;扫码、订单、事件 ID 可跳到对应页。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { Link, useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
@@ -153,6 +153,7 @@ type OrderExportFilters = {
|
||||
fulfillmentHold?: boolean;
|
||||
excludeTest?: boolean;
|
||||
deliveryType?: string;
|
||||
promoCodeId?: string;
|
||||
dateRange?: [Dayjs, Dayjs];
|
||||
};
|
||||
|
||||
@@ -212,6 +213,15 @@ const DATE_PRESETS: Array<{ key: 'day' | 'week' | 'month' | 'quarter' | 'year';
|
||||
{ key: 'year', label: '当年' },
|
||||
];
|
||||
|
||||
function formatUserWithRemark(user?: {
|
||||
userNo?: string;
|
||||
hqRemark?: string | null;
|
||||
} | null) {
|
||||
const userNo = user?.userNo || '—';
|
||||
const remark = user?.hqRemark?.trim();
|
||||
return remark ? `${userNo}(${remark})` : userNo;
|
||||
}
|
||||
|
||||
function formatBenefitBrief(row: AdminOrderRow) {
|
||||
const coupon = row.benefitCoupon;
|
||||
if (coupon) {
|
||||
@@ -247,6 +257,7 @@ function buildExportPayload(
|
||||
if (filters.deliveryType) payload.deliveryType = filters.deliveryType;
|
||||
if (filters.fulfillmentHold) payload.fulfillmentHold = true;
|
||||
if (filters.excludeTest) payload.excludeTest = true;
|
||||
if (filters.promoCodeId) payload.promoCodeId = filters.promoCodeId;
|
||||
if (filters.dateRange?.[0]) payload.createdFrom = filters.dateRange[0].format('YYYY-MM-DD');
|
||||
if (filters.dateRange?.[1]) payload.createdTo = filters.dateRange[1].format('YYYY-MM-DD');
|
||||
return payload;
|
||||
@@ -285,8 +296,15 @@ function warehouseToDefaults(wh: WarehouseOption, base?: ShipDefaults | null): S
|
||||
|
||||
export default function OrdersPage() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const initialOrderNo = searchParams.get('orderNo')?.trim() || '';
|
||||
const initialPromoCodeId = searchParams.get('promoCodeId')?.trim() || '';
|
||||
const initialStatusParam = searchParams.getAll('status').join(',');
|
||||
const initialStatuses = useMemo(
|
||||
() => initialStatusParam.split(',').map((s) => s.trim()).filter(Boolean),
|
||||
[initialStatusParam],
|
||||
);
|
||||
const [form] = Form.useForm();
|
||||
const [shipForm] = Form.useForm();
|
||||
const [logisticsForm] = Form.useForm();
|
||||
@@ -336,6 +354,20 @@ export default function OrdersPage() {
|
||||
}
|
||||
}, [form, initialOrderNo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialStatuses.length) {
|
||||
form.setFieldsValue({ status: initialStatuses });
|
||||
}
|
||||
}, [form, initialStatuses]);
|
||||
|
||||
useEffect(() => {
|
||||
const openOrderId = (location.state as { openOrderId?: string } | null)?.openOrderId;
|
||||
if (openOrderId) {
|
||||
void openDetail(openOrderId);
|
||||
navigate(`${location.pathname}${location.search}`, { replace: true, state: null });
|
||||
}
|
||||
}, [location.state, location.pathname, location.search, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialOrderNo || deepLinkOpenedRef.current || loading) return;
|
||||
const first = data?.items?.[0];
|
||||
@@ -366,13 +398,16 @@ export default function OrdersPage() {
|
||||
const qs = new URLSearchParams({ page: String(page), pageSize: String(pageSize) });
|
||||
if (initialOrderNo) qs.set('orderNo', initialOrderNo);
|
||||
if (values.orderNo) qs.set('orderNo', values.orderNo);
|
||||
for (const status of selectedStatuses(values.status)) qs.append('status', status);
|
||||
const statuses = selectedStatuses(values.status);
|
||||
const statusList = statuses.length ? statuses : initialStatuses;
|
||||
for (const status of statusList) qs.append('status', status);
|
||||
if (values.orderType) qs.set('orderType', values.orderType);
|
||||
if (values.cityId) qs.set('cityId', values.cityId);
|
||||
if (values.receiverPhone) qs.set('receiverPhone', values.receiverPhone);
|
||||
if (values.fulfillmentHold) qs.set('fulfillmentHold', 'true');
|
||||
if (values.excludeTest) qs.set('excludeTest', 'true');
|
||||
if (values.deliveryType) qs.set('deliveryType', values.deliveryType);
|
||||
if (initialPromoCodeId) qs.set('promoCodeId', initialPromoCodeId);
|
||||
if (values.dateRange?.[0]) qs.set('createdFrom', values.dateRange[0].format('YYYY-MM-DD'));
|
||||
if (values.dateRange?.[1]) qs.set('createdTo', values.dateRange[1].format('YYYY-MM-DD'));
|
||||
const res = await request<Paginated<AdminOrderRow>>(`/admin/orders?${qs}`);
|
||||
@@ -380,7 +415,7 @@ export default function OrdersPage() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [form, page, pageSize, initialOrderNo]);
|
||||
}, [form, page, pageSize, initialOrderNo, initialPromoCodeId, initialStatuses]);
|
||||
|
||||
useEffect(() => {
|
||||
void load();
|
||||
@@ -589,7 +624,16 @@ export default function OrdersPage() {
|
||||
}
|
||||
setExporting(true);
|
||||
try {
|
||||
const payload = buildExportPayload(scope, exportFormat, values, selectedRowKeys);
|
||||
const payload = buildExportPayload(
|
||||
scope,
|
||||
exportFormat,
|
||||
{
|
||||
...values,
|
||||
promoCodeId: initialPromoCodeId || values.promoCodeId,
|
||||
status: selectedStatuses(values.status).length ? values.status : initialStatuses,
|
||||
},
|
||||
selectedRowKeys,
|
||||
);
|
||||
const result = await request<OrderExportResult>('/admin/orders/export', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
@@ -618,14 +662,14 @@ export default function OrdersPage() {
|
||||
{
|
||||
title: '用户',
|
||||
key: 'user',
|
||||
width: 120,
|
||||
width: 200,
|
||||
render: (_, row) =>
|
||||
row.user?.id ? (
|
||||
<AdminPrimaryLink onClick={() => navigate('/users', { state: { openUserId: String(row.user!.id) } })}>
|
||||
{row.user.userNo}
|
||||
<AdminPrimaryLink onClick={() => navigate(`/users?userId=${encodeURIComponent(String(row.user!.id))}`)}>
|
||||
{formatUserWithRemark(row.user)}
|
||||
</AdminPrimaryLink>
|
||||
) : (
|
||||
(row.user?.userNo || '—')
|
||||
formatUserWithRemark(row.user)
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -778,6 +822,26 @@ export default function OrdersPage() {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{initialPromoCodeId ? (
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
closable
|
||||
style={{ marginBottom: 16 }}
|
||||
message={
|
||||
initialStatuses.includes('COMPLETED') && initialStatuses.length === 1
|
||||
? '已按推广码筛选已完成订单'
|
||||
: '已按推广码筛选订单'
|
||||
}
|
||||
action={
|
||||
<Button size="small" onClick={() => navigate('/orders')}>
|
||||
清除筛选
|
||||
</Button>
|
||||
}
|
||||
onClose={() => navigate('/orders')}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Form form={form} layout="vertical" onFinish={() => { setPage(1); void load(); }}>
|
||||
<Row gutter={[16, 8]}>
|
||||
<Col xs={24} md={14} lg={12}>
|
||||
@@ -877,7 +941,16 @@ export default function OrdersPage() {
|
||||
) : <span />}
|
||||
<Space size={12} wrap>
|
||||
<Button type="primary" htmlType="submit">查询</Button>
|
||||
<Button onClick={() => { form.resetFields(); setPage(1); void load(); }}>重置</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
form.resetFields();
|
||||
setPage(1);
|
||||
if (initialPromoCodeId || initialOrderNo || initialStatuses.length) navigate('/orders');
|
||||
else void load();
|
||||
}}
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
{canDeleteOrders ? (
|
||||
<Button
|
||||
danger
|
||||
@@ -987,7 +1060,18 @@ export default function OrdersPage() {
|
||||
{[detail.proxyPartnerName, detail.proxyPartnerPhone].filter(Boolean).join(' / ') || '—'}
|
||||
</Descriptions.Item>
|
||||
) : null}
|
||||
<Descriptions.Item label="用户">{detail.user?.userNo} / {detail.user?.nickname}</Descriptions.Item>
|
||||
<Descriptions.Item label="用户">
|
||||
{detail.user?.id ? (
|
||||
<AdminPrimaryLink
|
||||
onClick={() => navigate(`/users?userId=${encodeURIComponent(String(detail.user!.id))}`)}
|
||||
>
|
||||
{formatUserWithRemark(detail.user)}
|
||||
</AdminPrimaryLink>
|
||||
) : (
|
||||
formatUserWithRemark(detail.user)
|
||||
)}
|
||||
{detail.user?.nickname ? ` / ${detail.user.nickname}` : ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="商品额">¥{detail.productAmount}</Descriptions.Item>
|
||||
<Descriptions.Item label="实付">¥{detail.payAmount}</Descriptions.Item>
|
||||
<Descriptions.Item label="好客权益">¥{detail.benefitAmount}</Descriptions.Item>
|
||||
|
||||
Reference in New Issue
Block a user