feat(fulfillment): 同城运费与路由修复,配送单展示商品用户地址
同城 MANUAL/ZZXFX 按小飞侠价规计费,路由查询回退仓配凭证;HQ 配送单补商品、用户和收货地址。门店核销回跳与小程序核销码一并带上。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,10 +11,23 @@ import PackageImagesUpload from './PackageImagesUpload';
|
||||
type PackageRow = StorePackageItemDto;
|
||||
|
||||
export type AdminStorePackagesHandle = {
|
||||
/** 套餐已加载时写入;加载中则跳过,避免空数据覆盖线上套餐 */
|
||||
/** 仅在用户改过套餐时写入;加载中或未改动则跳过,避免空表单覆盖刚审核通过的线上套餐 */
|
||||
saveIfLoaded: (opts?: { quiet?: boolean }) => Promise<{ skipped: boolean }>;
|
||||
};
|
||||
|
||||
function mapLiveRows(live: StorePackagesResponse['live']): PackageRow[] {
|
||||
return (live ?? []).map((p, i) => {
|
||||
const imageUrls = normalizeStorePackageImageUrls(p);
|
||||
return {
|
||||
...p,
|
||||
price: String(p.price),
|
||||
imageUrl: imageUrls[0] ?? '',
|
||||
imageUrls,
|
||||
sortOrder: i,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function emptyRow(index = 0): PackageRow {
|
||||
return {
|
||||
name: '',
|
||||
@@ -37,6 +50,7 @@ const AdminStorePackagesSection = forwardRef<AdminStorePackagesHandle, { storeId
|
||||
const [pendingRequest, setPendingRequest] = useState<StorePackagesResponse['pendingRequest']>(null);
|
||||
const itemsRef = useRef(items);
|
||||
const loadingRef = useRef(loading);
|
||||
const dirtyRef = useRef(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -47,36 +61,33 @@ const AdminStorePackagesSection = forwardRef<AdminStorePackagesHandle, { storeId
|
||||
loadingRef.current = loading;
|
||||
}, [loading]);
|
||||
|
||||
function applyServerPackages(data: StorePackagesResponse) {
|
||||
dirtyRef.current = false;
|
||||
setPendingRequest(data.pendingRequest ?? null);
|
||||
setItems(mapLiveRows(data.live));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
setPendingRequest(null);
|
||||
dirtyRef.current = false;
|
||||
request<StorePackagesResponse>(`/admin/stores/${storeId}/packages`)
|
||||
.then((data) => {
|
||||
setPendingRequest(data.pendingRequest ?? null);
|
||||
setItems(
|
||||
data.live?.length
|
||||
? data.live.map((p, i) => {
|
||||
const imageUrls = normalizeStorePackageImageUrls(p);
|
||||
return {
|
||||
...p,
|
||||
price: String(p.price),
|
||||
imageUrl: imageUrls[0] ?? '',
|
||||
imageUrls,
|
||||
sortOrder: i,
|
||||
};
|
||||
})
|
||||
: [],
|
||||
);
|
||||
})
|
||||
.then((data) => applyServerPackages(data))
|
||||
.catch((e) => message.error(e instanceof Error ? e.message : '加载套餐失败'))
|
||||
.finally(() => setLoading(false));
|
||||
}, [storeId]);
|
||||
|
||||
// 在审核页完成审核后,自动刷新本页「有待审核套餐」提醒
|
||||
// 审核通过/驳回后刷新提醒;用户未改套餐时同步线上结果,避免抽屉里仍显示空套餐
|
||||
useEffect(() => {
|
||||
const onChanged = () => {
|
||||
request<StorePackagesResponse>(`/admin/stores/${storeId}/packages`)
|
||||
.then((data) => setPendingRequest(data.pendingRequest ?? null))
|
||||
.then((data) => {
|
||||
setPendingRequest(data.pendingRequest ?? null);
|
||||
if (!dirtyRef.current) {
|
||||
dirtyRef.current = false;
|
||||
setItems(mapLiveRows(data.live));
|
||||
}
|
||||
})
|
||||
.catch(() => undefined);
|
||||
};
|
||||
window.addEventListener(PACKAGE_AUDIT_CHANGED_EVENT, onChanged);
|
||||
@@ -108,16 +119,19 @@ const AdminStorePackagesSection = forwardRef<AdminStorePackagesHandle, { storeId
|
||||
) : null;
|
||||
|
||||
function updateAt(index: number, patch: Partial<PackageRow>) {
|
||||
dirtyRef.current = true;
|
||||
setItems((prev) => prev.map((item, i) => (i === index ? { ...item, ...patch } : item)));
|
||||
}
|
||||
|
||||
function addRow() {
|
||||
if (items.length >= STORE_PACKAGE_MAX_COUNT) return;
|
||||
dirtyRef.current = true;
|
||||
setItems((prev) => [...prev, emptyRow(prev.length)]);
|
||||
}
|
||||
|
||||
function removeAt(index: number) {
|
||||
const run = () => {
|
||||
dirtyRef.current = true;
|
||||
setItems((prev) => {
|
||||
const next = prev.filter((_, i) => i !== index).map((item, i) => ({ ...item, sortOrder: i }));
|
||||
return next.length ? next : [];
|
||||
@@ -203,20 +217,8 @@ const AdminStorePackagesSection = forwardRef<AdminStorePackagesHandle, { storeId
|
||||
}),
|
||||
});
|
||||
if (!opts?.quiet) message.success('套餐已保存并生效');
|
||||
setItems(
|
||||
data.live?.length
|
||||
? data.live.map((p, i) => {
|
||||
const imageUrls = normalizeStorePackageImageUrls(p);
|
||||
return {
|
||||
...p,
|
||||
price: String(p.price),
|
||||
imageUrl: imageUrls[0] ?? '',
|
||||
imageUrls,
|
||||
sortOrder: i,
|
||||
};
|
||||
})
|
||||
: [],
|
||||
);
|
||||
dirtyRef.current = false;
|
||||
setItems(mapLiveRows(data.live));
|
||||
} catch (e) {
|
||||
if (!opts?.quiet) message.error(e instanceof Error ? e.message : '保存失败');
|
||||
throw e;
|
||||
@@ -227,7 +229,7 @@ const AdminStorePackagesSection = forwardRef<AdminStorePackagesHandle, { storeId
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
saveIfLoaded: async (opts) => {
|
||||
if (loadingRef.current) return { skipped: true };
|
||||
if (loadingRef.current || !dirtyRef.current) return { skipped: true };
|
||||
await save(opts);
|
||||
return { skipped: false };
|
||||
},
|
||||
|
||||
@@ -173,7 +173,9 @@ export default function OrderTrackDrawer({
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description={
|
||||
track?.manualQueryUrl
|
||||
track?.queryError
|
||||
? track.queryError
|
||||
: track?.manualQueryUrl
|
||||
? '暂无实时路由节点,可使用上方物流查询链接'
|
||||
: '暂无路由信息,请稍后刷新'
|
||||
}
|
||||
|
||||
@@ -20,7 +20,11 @@ import {
|
||||
AccountBookOutlined,
|
||||
ProjectOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { hasAnySystemSettingsPermission, hasAnyStoreMenuPermission } from '@dukang/shared-types';
|
||||
import {
|
||||
hasAnySystemSettingsPermission,
|
||||
hasAnyStoreMenuPermission,
|
||||
HQ_ADMIN_ROLES,
|
||||
} from '@dukang/shared-types';
|
||||
import { clearAuth, request, type HqProfile } from '../lib/api';
|
||||
import { bindAdminEllipsisTitle } from '../lib/ellipsis-title';
|
||||
import { AUDIT_NOTICE_CHANGED_EVENT, PACKAGE_AUDIT_CHANGED_EVENT } from '../lib/admin-events';
|
||||
@@ -28,6 +32,10 @@ import { ListColumnPrefsProvider } from '../lib/ListColumnPrefsContext';
|
||||
|
||||
const { Header, Sider, Content } = Layout;
|
||||
|
||||
const HQ_ROLE_LABELS: Record<string, string> = Object.fromEntries(
|
||||
HQ_ADMIN_ROLES.map((r) => [r.value, r.label]),
|
||||
);
|
||||
|
||||
type MenuItem = NonNullable<MenuProps['items']>[number];
|
||||
|
||||
const MENU_ITEMS: MenuProps['items'] = [
|
||||
@@ -405,7 +413,9 @@ export default function AdminLayout() {
|
||||
</Typography.Title>
|
||||
<Space>
|
||||
<span>{profile?.name || '—'}</span>
|
||||
<span style={{ color: '#999' }}>{profile?.adminRole}</span>
|
||||
<span style={{ color: '#999' }}>
|
||||
{HQ_ROLE_LABELS[profile?.adminRole ?? ''] || profile?.adminRole || ''}
|
||||
</span>
|
||||
<Button type="text" icon={<LogoutOutlined />} onClick={logout}>
|
||||
退出
|
||||
</Button>
|
||||
|
||||
@@ -236,5 +236,7 @@ export type AdminOrderRow = {
|
||||
providerOrderNo: string | null;
|
||||
logisticsCompany?: string | null;
|
||||
manualQueryUrl?: string | null;
|
||||
/** 当次应付物流费(按瓶当量 × 承运商计价) */
|
||||
logisticsFee?: number | null;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,16 +1,43 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Button, Descriptions, Drawer, Form, Input, Space, Table, Typography, message,
|
||||
} from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import OrderTrackDrawer from '../components/OrderTrackDrawer';
|
||||
import { request } from '../lib/api';
|
||||
import { ORDER_STATUS_LABELS, fmtTime } from '../lib/constants';
|
||||
import {
|
||||
DELIVERY_PROVIDER_LABELS,
|
||||
DELIVERY_TYPE_LABELS,
|
||||
ORDER_STATUS_LABELS,
|
||||
fmtTime,
|
||||
} from '../lib/constants';
|
||||
import { useAdminList } from '../lib/useAdminList';
|
||||
import { useAdminListColumns } from '../lib/useAdminListColumns';
|
||||
import { AdminListHeader } from '../components/AdminListHeader';
|
||||
import { AdminPrimaryLink } from '../components/AdminPrimaryLink';
|
||||
|
||||
type DeliveryOrder = {
|
||||
id: string;
|
||||
orderNo: string;
|
||||
status: string;
|
||||
deliveryType: string;
|
||||
productName: string;
|
||||
productSpec?: string | null;
|
||||
barcode69?: string | null;
|
||||
quantity: number;
|
||||
saleUnit?: string;
|
||||
bottlesPerUnit?: number;
|
||||
payAmount?: number;
|
||||
receiverName: string;
|
||||
receiverPhone: string;
|
||||
receiverAddress?: string;
|
||||
receiverProvince?: string;
|
||||
receiverCity?: string;
|
||||
receiverDistrict?: string;
|
||||
user?: { id: string; userNo: string; phone: string | null; nickname: string | null };
|
||||
imageResource?: { url: string } | null;
|
||||
};
|
||||
|
||||
type Row = {
|
||||
id: string;
|
||||
@@ -19,19 +46,34 @@ type Row = {
|
||||
trackingNo: string | null;
|
||||
providerOrderNo: string | null;
|
||||
updatedAt: string;
|
||||
/** 当次应付物流费 */
|
||||
logisticsFee?: number | null;
|
||||
order?: {
|
||||
id: string;
|
||||
orderNo: string;
|
||||
status: string;
|
||||
receiverName: string;
|
||||
receiverPhone: string;
|
||||
deliveryType: string;
|
||||
};
|
||||
order?: DeliveryOrder;
|
||||
};
|
||||
|
||||
function formatQty(order?: DeliveryOrder | null) {
|
||||
if (!order || order.quantity == null) return '—';
|
||||
const unit = order.saleUnit === 'BOX' ? '箱' : '瓶';
|
||||
const bottles =
|
||||
order.saleUnit === 'BOX' && order.bottlesPerUnit && order.bottlesPerUnit > 1
|
||||
? `(${order.quantity * order.bottlesPerUnit}瓶)`
|
||||
: '';
|
||||
return `${order.quantity}${unit}${bottles}`;
|
||||
}
|
||||
|
||||
function formatAddress(order?: DeliveryOrder | null) {
|
||||
if (!order) return '—';
|
||||
if (order.deliveryType === 'ON_SITE_PICKUP') return '现场取货';
|
||||
const region = [order.receiverProvince, order.receiverCity, order.receiverDistrict]
|
||||
.filter(Boolean)
|
||||
.join('');
|
||||
const detail = (order.receiverAddress || '').trim();
|
||||
if (!region) return detail || '—';
|
||||
if (!detail || detail.startsWith(region)) return detail || region;
|
||||
return `${region}${detail}`;
|
||||
}
|
||||
|
||||
export default function DeliveriesPage() {
|
||||
const navigate = useNavigate();
|
||||
const [form] = Form.useForm();
|
||||
const [editForm] = Form.useForm();
|
||||
const [filters, setFilters] = useState<Record<string, string>>({});
|
||||
@@ -65,25 +107,91 @@ export default function DeliveriesPage() {
|
||||
setTrackOpen(true);
|
||||
}
|
||||
|
||||
async function openDetail(row: Row) {
|
||||
const d = await request<Row>(`/admin/deliveries/${row.id}`);
|
||||
setDetail(d);
|
||||
editForm.setFieldsValue({
|
||||
provider: d.provider,
|
||||
trackingNo: d.trackingNo,
|
||||
providerOrderNo: d.providerOrderNo,
|
||||
});
|
||||
setDrawerOpen(true);
|
||||
}
|
||||
|
||||
const baseColumns: ColumnsType<Row> = [
|
||||
{
|
||||
title: '订单号',
|
||||
dataIndex: ['order', 'orderNo'],
|
||||
width: 170,
|
||||
render: (v, row) => (
|
||||
<AdminPrimaryLink
|
||||
onClick={async () => {
|
||||
const d = await request<Row>(`/admin/deliveries/${row.id}`);
|
||||
setDetail(d);
|
||||
editForm.setFieldsValue({ provider: d.provider, trackingNo: d.trackingNo, providerOrderNo: d.providerOrderNo });
|
||||
setDrawerOpen(true);
|
||||
}}
|
||||
>
|
||||
{v}
|
||||
</AdminPrimaryLink>
|
||||
<AdminPrimaryLink onClick={() => void openDetail(row)}>{v}</AdminPrimaryLink>
|
||||
),
|
||||
},
|
||||
{ title: 'provider', dataIndex: 'provider', width: 90 },
|
||||
{
|
||||
title: '用户',
|
||||
key: 'user',
|
||||
width: 140,
|
||||
render: (_, row) => {
|
||||
const user = row.order?.user;
|
||||
if (!user) return '—';
|
||||
const label = user.userNo || user.phone || '—';
|
||||
return (
|
||||
<div>
|
||||
{user.id ? (
|
||||
<AdminPrimaryLink
|
||||
onClick={() => navigate('/users', { state: { openUserId: String(user.id) } })}
|
||||
>
|
||||
{label}
|
||||
</AdminPrimaryLink>
|
||||
) : (
|
||||
label
|
||||
)}
|
||||
{user.phone && user.userNo ? (
|
||||
<Typography.Text type="secondary" style={{ display: 'block', fontSize: 12 }}>
|
||||
{user.phone}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '商品',
|
||||
key: 'product',
|
||||
width: 200,
|
||||
render: (_, row) => {
|
||||
const order = row.order;
|
||||
if (!order?.productName) return '—';
|
||||
return (
|
||||
<div>
|
||||
<span>{order.productName}</span>
|
||||
{order.productSpec ? (
|
||||
<Typography.Text type="secondary" style={{ display: 'block', fontSize: 12 }}>
|
||||
{order.productSpec}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '数量',
|
||||
key: 'quantity',
|
||||
width: 90,
|
||||
render: (_, row) => formatQty(row.order),
|
||||
},
|
||||
{
|
||||
title: '配送方式',
|
||||
dataIndex: ['order', 'deliveryType'],
|
||||
width: 90,
|
||||
render: (v: string | undefined) => DELIVERY_TYPE_LABELS[v ?? ''] || v || '—',
|
||||
},
|
||||
{
|
||||
title: '承运商',
|
||||
dataIndex: 'provider',
|
||||
width: 90,
|
||||
render: (v: string) => DELIVERY_PROVIDER_LABELS[v] || v || '—',
|
||||
},
|
||||
{ title: '运单号', dataIndex: 'trackingNo', width: 140, render: (v) => v || '—' },
|
||||
{
|
||||
title: '运费',
|
||||
@@ -92,20 +200,33 @@ export default function DeliveriesPage() {
|
||||
render: (v: number | null | undefined) => (v == null ? '—' : `¥${Number(v).toFixed(2)}`),
|
||||
},
|
||||
{ title: '第三方单号', dataIndex: 'providerOrderNo', width: 140, render: (v) => v || '—' },
|
||||
{ title: '订单状态', dataIndex: ['order', 'status'], width: 100, render: (s) => ORDER_STATUS_LABELS[s] || s },
|
||||
{
|
||||
title: '订单状态',
|
||||
dataIndex: ['order', 'status'],
|
||||
width: 100,
|
||||
render: (s) => ORDER_STATUS_LABELS[s] || s,
|
||||
},
|
||||
{ title: '收货人', dataIndex: ['order', 'receiverName'], width: 90 },
|
||||
{
|
||||
title: '收货电话',
|
||||
dataIndex: ['order', 'receiverPhone'],
|
||||
width: 120,
|
||||
render: (v) => v || '—',
|
||||
},
|
||||
{
|
||||
title: '配送地址',
|
||||
key: 'address',
|
||||
width: 280,
|
||||
ellipsis: true,
|
||||
render: (_, row) => formatAddress(row.order),
|
||||
},
|
||||
{ title: '更新', dataIndex: 'updatedAt', width: 160, render: fmtTime },
|
||||
{
|
||||
title: '操作', width: 140,
|
||||
render: (_, row) => (
|
||||
<Space size={0}>
|
||||
<Button type="link" size="small" onClick={() => openTrack(row)}>路由</Button>
|
||||
<Button type="link" size="small" onClick={async () => {
|
||||
const d = await request<Row>(`/admin/deliveries/${row.id}`);
|
||||
setDetail(d);
|
||||
editForm.setFieldsValue({ provider: d.provider, trackingNo: d.trackingNo, providerOrderNo: d.providerOrderNo });
|
||||
setDrawerOpen(true);
|
||||
}}>编辑</Button>
|
||||
<Button type="link" size="small" onClick={() => void openDetail(row)}>编辑</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
@@ -113,19 +234,21 @@ export default function DeliveriesPage() {
|
||||
|
||||
const { columns, settingsButton, settingsModal } = useAdminListColumns('deliveries', baseColumns, { page, pageSize });
|
||||
|
||||
const order = detail?.order;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{settingsModal}
|
||||
<AdminListHeader title="快递/配送单" settings={settingsButton} />
|
||||
<Form form={form} layout="inline" style={{ marginBottom: 16 }} onFinish={(v) => { setFilters(v); setPage(1); }}>
|
||||
<Form.Item name="orderNo" label="订单号"><Input allowClear /></Form.Item>
|
||||
<Form.Item name="provider" label="provider"><Input allowClear placeholder="MOCK" /></Form.Item>
|
||||
<Form.Item name="provider" label="承运商"><Input allowClear placeholder="XFX" /></Form.Item>
|
||||
<Form.Item name="trackingNo" label="运单号"><Input allowClear /></Form.Item>
|
||||
<Form.Item><Button type="primary" htmlType="submit">查询</Button></Form.Item>
|
||||
</Form>
|
||||
<Table rowKey="id" loading={loading} columns={columns} dataSource={data?.items ?? []} scroll={{ x: 'max-content' }}
|
||||
pagination={{ current: page, pageSize, total: data?.total ?? 0, showSizeChanger: true, onChange: (p, ps) => { setPage(p); setPageSize(ps); } }} />
|
||||
<Drawer title="配送单编辑" width={480} open={drawerOpen} onClose={() => setDrawerOpen(false)}
|
||||
<Drawer title="配送单编辑" width={560} open={drawerOpen} onClose={() => setDrawerOpen(false)}
|
||||
extra={
|
||||
<Space>
|
||||
<Button onClick={() => setDrawerOpen(false)}>取消</Button>
|
||||
@@ -142,14 +265,63 @@ export default function DeliveriesPage() {
|
||||
{detail && (
|
||||
<>
|
||||
<Descriptions column={1} bordered size="small" style={{ marginBottom: 16 }}>
|
||||
<Descriptions.Item label="订单">{detail.order?.orderNo}</Descriptions.Item>
|
||||
<Descriptions.Item label="收货">{detail.order?.receiverName} {detail.order?.receiverPhone}</Descriptions.Item>
|
||||
<Descriptions.Item label="订单">{order?.orderNo || '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label="用户">
|
||||
{order?.user ? (
|
||||
<Space size={8} wrap>
|
||||
{order.user.id ? (
|
||||
<AdminPrimaryLink
|
||||
onClick={() => navigate('/users', { state: { openUserId: String(order.user!.id) } })}
|
||||
>
|
||||
{order.user.userNo}
|
||||
</AdminPrimaryLink>
|
||||
) : (
|
||||
order.user.userNo || '—'
|
||||
)}
|
||||
<Typography.Text type="secondary">
|
||||
{[order.user.nickname, order.user.phone].filter(Boolean).join(' / ') || ''}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
) : '—'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="商品">
|
||||
<Space align="start">
|
||||
{order?.imageResource?.url ? (
|
||||
<img
|
||||
src={order.imageResource.url}
|
||||
alt=""
|
||||
style={{ width: 48, height: 48, objectFit: 'cover', borderRadius: 4 }}
|
||||
/>
|
||||
) : null}
|
||||
<span>
|
||||
{order?.productName || '—'}
|
||||
{order?.productSpec ? (
|
||||
<Typography.Text type="secondary" style={{ display: 'block', fontSize: 12 }}>
|
||||
{order.productSpec}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
{order?.barcode69 ? (
|
||||
<Typography.Text type="secondary" style={{ display: 'block', fontSize: 12 }}>
|
||||
{order.barcode69}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</span>
|
||||
</Space>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="数量">{formatQty(order)}</Descriptions.Item>
|
||||
<Descriptions.Item label="配送方式">
|
||||
{DELIVERY_TYPE_LABELS[order?.deliveryType ?? ''] || order?.deliveryType || '—'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="收货人">
|
||||
{[order?.receiverName, order?.receiverPhone].filter(Boolean).join(' ') || '—'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="配送地址">{formatAddress(order)}</Descriptions.Item>
|
||||
<Descriptions.Item label="运费">
|
||||
{detail.logisticsFee == null ? '—' : `¥${Number(detail.logisticsFee).toFixed(2)}`}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
<Form form={editForm} layout="vertical">
|
||||
<Form.Item name="provider" label="provider" rules={[{ required: true }]}><Input /></Form.Item>
|
||||
<Form.Item name="provider" label="承运商" rules={[{ required: true }]}><Input /></Form.Item>
|
||||
<Form.Item name="providerOrderNo" label="第三方单号"><Input /></Form.Item>
|
||||
<Form.Item name="trackingNo" label="运单号"><Input /></Form.Item>
|
||||
</Form>
|
||||
|
||||
@@ -674,6 +674,13 @@ export default function OrdersPage() {
|
||||
width: 90,
|
||||
render: (v: number) => `¥${v}`,
|
||||
},
|
||||
{
|
||||
title: '运费',
|
||||
key: 'logisticsFee',
|
||||
width: 90,
|
||||
render: (_, row) =>
|
||||
row.delivery?.logisticsFee == null ? '—' : `¥${Number(row.delivery.logisticsFee).toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
title: '好客权益',
|
||||
width: 200,
|
||||
@@ -1158,6 +1165,11 @@ export default function OrdersPage() {
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="运单号">{detail.delivery.trackingNo || '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label="三方单号">{detail.delivery.providerOrderNo || '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label="运费">
|
||||
{detail.delivery.logisticsFee == null
|
||||
? '—'
|
||||
: `¥${Number(detail.delivery.logisticsFee).toFixed(2)}`}
|
||||
</Descriptions.Item>
|
||||
{detail.delivery.manualQueryUrl && (
|
||||
<Descriptions.Item label="查询链接">
|
||||
<a href={detail.delivery.manualQueryUrl} target="_blank" rel="noreferrer">
|
||||
|
||||
Reference in New Issue
Block a user