feat(assoc): v4.0.1 合伙人关联码、分佣账单与 H5 用户管理

订单佣金只认关联用户;合伙人备注写入独立表;H5 增加用户管理与首页统计。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-30 14:35:39 +08:00
parent 3b669f7e38
commit 9c8d5f2cad
125 changed files with 6355 additions and 1436 deletions
+66
View File
@@ -57,6 +57,7 @@ type ShipDefaults = {
};
type CityOption = { id: string; name: string; code: string };
type PartnerOption = { id: string; companyName?: string | null; name?: string; phone?: string };
type WarehouseOption = {
id: string;
@@ -93,6 +94,20 @@ type OrderRedeemSummary = {
redeemRecordSum: number;
};
type AssocPartnerBrief = {
id: string;
name: string;
companyName?: string | null;
phone?: string | null;
orderCommissionRate?: number | null;
};
function formatAssocPartner(p?: AssocPartnerBrief | null) {
if (!p) return '—';
const title = p.companyName || p.name;
return [title, p.phone].filter(Boolean).join(' / ') || '—';
}
type OrderDetail = AdminOrderRow & {
receiverAddress?: string;
receiverProvince?: string;
@@ -136,6 +151,11 @@ type OrderDetail = AdminOrderRow & {
lng?: number | null;
lat?: number | null;
} | null;
assocPartnerAtPay?: AssocPartnerBrief | null;
orderCommissionRateAtPay?: number | null;
user?: AdminOrderRow['user'] & {
assocPartner?: AssocPartnerBrief | null;
};
};
type ShipMode = 'WAREHOUSE' | 'EXPRESS';
@@ -154,6 +174,7 @@ type OrderExportFilters = {
fulfillmentHold?: boolean;
deliveryType?: string;
promoCodeId?: string;
assocPartnerAccountId?: string;
dateRange?: [Dayjs, Dayjs];
};
@@ -261,6 +282,7 @@ function buildExportPayload(
if (filters.deliveryType) payload.deliveryType = filters.deliveryType;
if (filters.fulfillmentHold) payload.fulfillmentHold = true;
if (filters.promoCodeId) payload.promoCodeId = filters.promoCodeId;
if (filters.assocPartnerAccountId) payload.assocPartnerAccountId = filters.assocPartnerAccountId;
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;
@@ -330,6 +352,7 @@ export default function OrdersPage() {
const [shipping, setShipping] = useState(false);
const [logisticsShipping, setLogisticsShipping] = useState(false);
const [cities, setCities] = useState<CityOption[]>([]);
const [partners, setPartners] = useState<PartnerOption[]>([]);
const [shipModalOpen, setShipModalOpen] = useState(false);
const [shipTarget, setShipTarget] = useState<OrderDetail | null>(null);
const [shipMode, setShipMode] = useState<ShipMode>('EXPRESS');
@@ -418,6 +441,7 @@ export default function OrdersPage() {
if (values.productKeyword) qs.set('productKeyword', values.productKeyword);
if (values.fulfillmentHold) qs.set('fulfillmentHold', 'true');
if (values.deliveryType) qs.set('deliveryType', values.deliveryType);
if (values.assocPartnerAccountId) qs.set('assocPartnerAccountId', values.assocPartnerAccountId);
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'));
@@ -437,6 +461,9 @@ export default function OrdersPage() {
void request<Paginated<CityOption>>(`/admin/cities?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}`)
.then((res) => setCities(res.items ?? []))
.catch(() => {});
void request<Paginated<PartnerOption>>(`/admin/partners?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}`)
.then((res) => setPartners(res.items ?? []))
.catch(() => setPartners([]));
}, []);
function applyShipDefaults(defaults: ShipDefaults, warehouseId?: string | null) {
@@ -888,6 +915,30 @@ export default function OrdersPage() {
<Form.Item name="deliveryType" label="配送" className="admin-orders-filter-item admin-orders-filter-item--sm">
<Select allowClear placeholder="全部" options={DELIVERY_TYPE_OPTIONS} />
</Form.Item>
<Form.Item
name="assocPartnerAccountId"
label="关联合伙人"
className="admin-orders-filter-item admin-orders-filter-item--status"
>
<Select
allowClear
showSearch
optionFilterProp="label"
placeholder="全部"
options={[
{ value: 'none', label: '未关联' },
...partners.map((p) => ({
value: p.id,
label: formatAssocPartner({
id: p.id,
name: p.name || p.id,
companyName: p.companyName,
phone: p.phone,
}),
})),
]}
/>
</Form.Item>
<Form.Item label=" " colon={false} className="admin-orders-filter-item admin-orders-filter-actions">
<Space size={8} wrap>
<Button type="primary" htmlType="submit"></Button>
@@ -1082,6 +1133,21 @@ export default function OrdersPage() {
{[detail.proxyPartnerName, detail.proxyPartnerPhone].filter(Boolean).join(' / ') || '—'}
</Descriptions.Item>
) : null}
<Descriptions.Item label="关联合伙人(本单佣金)">
{detail.assocPartnerAtPay
? `${formatAssocPartner(detail.assocPartnerAtPay)}${
detail.assocPartnerAtPay.orderCommissionRate != null
? ` · 费率 ${(Number(detail.assocPartnerAtPay.orderCommissionRate) * 100).toFixed(2)}%`
: ''
}`
: '—'}
</Descriptions.Item>
{detail.user?.assocPartner &&
detail.user.assocPartner.id !== detail.assocPartnerAtPay?.id ? (
<Descriptions.Item label="用户当前关联">
{formatAssocPartner(detail.user.assocPartner)}
</Descriptions.Item>
) : null}
<Descriptions.Item label="用户">
{detail.user?.id ? (
<AdminPrimaryLink