后端仓库配送逻辑完善
CI / verify (pull_request) Has been cancelled

This commit is contained in:
2026-07-17 08:57:19 +08:00
parent c204fb0a92
commit e9537ce052
2 changed files with 81 additions and 35 deletions
+79 -33
View File
@@ -45,6 +45,8 @@ type CityOption = { id: string; name: string; code: string };
type WarehouseOption = {
id: string;
name: string;
cityId?: string;
cityName?: string;
status?: string;
contactName: string;
contactPhone: string;
@@ -192,16 +194,13 @@ export default function OrdersPage() {
});
}
async function loadWarehousesForOrder(order: OrderDetail) {
const cityId = order.city?.id || order.cityId;
if (!cityId) {
setWarehouses([]);
return [] as WarehouseOption[];
}
const rows = await request<WarehouseOption[]>(`/admin/cities/${cityId}/warehouses`).catch(() => []);
const active = rows.filter((w) => !w.status || w.status === 'ACTIVE');
setWarehouses(active);
return active;
async function loadWarehouses() {
const res = await request<Paginated<WarehouseOption>>(
`/admin/city-warehouses?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}&status=ACTIVE`,
).catch(() => null);
const rows = (res?.items ?? []).filter((w) => !w.status || w.status === 'ACTIVE');
setWarehouses(rows);
return rows;
}
async function openDetail(id: string) {
@@ -214,16 +213,25 @@ export default function OrdersPage() {
const res = await request<OrderDetail>(`/admin/orders/${id}`);
setShipTarget(res);
setDetail(res);
logisticsForm.resetFields();
const rows = await loadWarehousesForOrder(res);
const preferredWarehouseId = res.fulfillmentWarehouseId || res.fulfillmentWarehouse?.id || rows[0]?.id;
const preferred = rows.find((w) => w.id === preferredWarehouseId) || rows[0];
logisticsForm.setFieldsValue({
logisticsCompany: '',
trackingNo: '',
manualQueryUrl: '',
});
const rows = await loadWarehouses();
const preferredWarehouseId = res.fulfillmentWarehouseId || res.fulfillmentWarehouse?.id;
const preferred =
rows.find((w) => w.id === preferredWarehouseId) ||
rows.find((w) => w.cityId === (res.city?.id || res.cityId)) ||
rows[0];
const defaults = preferred
? warehouseToDefaults(preferred, shipDefaults)
: (shipDefaults ?? await request<ShipDefaults>('/admin/orders/ship-defaults').catch(() => null));
if (defaults) {
setShipDefaults(defaults);
applyShipDefaults(defaults, preferred?.id);
} else {
shipForm.setFieldsValue({ warehouseId: preferred?.id, provider: 'XFX' });
}
setShipMode(preferred ? 'WAREHOUSE' : 'EXPRESS');
setShipModalOpen(true);
@@ -242,9 +250,21 @@ export default function OrdersPage() {
try {
const res = await request<OrderDetail>(`/admin/orders/${shipTarget.id}/ship`, {
method: 'POST',
body: JSON.stringify(values),
body: JSON.stringify({
provider: values.provider || 'XFX',
warehouseId: values.warehouseId,
weight: values.weight,
payMode: values.payMode,
remark: values.remark,
fromName: values.fromName,
fromMobile: values.fromMobile,
fromAddress: values.fromAddress,
fromAddressDetail: values.fromAddressDetail,
fromLng: values.fromLng,
fromLat: values.fromLat,
}),
});
message.success('仓发货成功');
message.success('仓发货成功');
setShipTarget(res);
setDetail(res);
setShipModalOpen(false);
@@ -258,12 +278,22 @@ export default function OrdersPage() {
async function submitLogisticsShip() {
if (!shipTarget) return;
const values = await logisticsForm.validateFields();
const values = await logisticsForm.validateFields(['logisticsCompany', 'trackingNo', 'manualQueryUrl']);
const logisticsCompany = String(values.logisticsCompany ?? '').trim();
const trackingNo = String(values.trackingNo ?? '').trim();
if (!logisticsCompany || !trackingNo) {
message.error('请填写快递公司与运单号');
return;
}
setLogisticsShipping(true);
try {
const res = await request<OrderDetail>(`/admin/orders/${shipTarget.id}/logistics-ship`, {
method: 'POST',
body: JSON.stringify(values),
body: JSON.stringify({
logisticsCompany,
trackingNo,
manualQueryUrl: String(values.manualQueryUrl ?? '').trim() || undefined,
}),
});
message.success('快递单已录入');
setShipTarget(res);
@@ -593,13 +623,14 @@ export default function OrdersPage() {
open={shipModalOpen}
onCancel={() => setShipModalOpen(false)}
width={560}
destroyOnClose
destroyOnClose={false}
forceRender
footer={
<Space>
<Button onClick={() => setShipModalOpen(false)}></Button>
{shipMode === 'WAREHOUSE' ? (
<Button type="primary" loading={shipping} onClick={() => void submitShip()}>
</Button>
) : (
<Button type="primary" loading={logisticsShipping} onClick={() => void submitLogisticsShip()}>
@@ -629,21 +660,26 @@ export default function OrdersPage() {
buttonStyle="solid"
style={{ marginBottom: 16 }}
options={[
{ value: 'WAREHOUSE', label: '仓库配送(小飞侠)' },
{ value: 'WAREHOUSE', label: '选仓配送' },
{ value: 'EXPRESS', label: '填写快递单号' },
]}
/>
{shipMode === 'WAREHOUSE' ? (
<Form form={shipForm} layout="vertical" size="small">
<div style={{ display: shipMode === 'WAREHOUSE' ? 'block' : 'none' }}>
<Form form={shipForm} layout="vertical" size="small" preserve>
<Form.Item
name="warehouseId"
label="履约仓库"
label="选择仓库"
rules={[{ required: true, message: '请选择仓库' }]}
>
<Select
placeholder={warehouses.length ? '选择仓库' : '该城市暂无仓库'}
options={warehouses.map((w) => ({ value: w.id, label: w.name }))}
showSearch
optionFilterProp="label"
placeholder={warehouses.length ? '选择仓库' : '暂无可用仓库'}
options={warehouses.map((w) => ({
value: w.id,
label: w.cityName ? `${w.cityName} · ${w.name}` : w.name,
}))}
onChange={(id) => void onWarehouseChange(id)}
/>
</Form.Item>
@@ -696,22 +732,32 @@ export default function OrdersPage() {
}]}
/>
</Form>
) : (
<Form form={logisticsForm} layout="vertical" size="small">
</div>
<div style={{ display: shipMode === 'EXPRESS' ? 'block' : 'none' }}>
<Form form={logisticsForm} layout="vertical" size="small" preserve>
<Typography.Paragraph type="secondary" style={{ fontSize: 12, marginTop: 0 }}>
</Typography.Paragraph>
<Form.Item name="logisticsCompany" label="快递公司" rules={[{ required: true, message: '请填写快递公司' }]}>
<Form.Item
name="logisticsCompany"
label="快递公司"
rules={[{ required: true, message: '请填写快递公司' }]}
>
<Input placeholder="如 顺丰速运、京东物流" />
</Form.Item>
<Form.Item name="trackingNo" label="运单号" rules={[{ required: true, message: '请填写运单号' }]}>
<Input />
<Form.Item
name="trackingNo"
label="运单号"
rules={[{ required: true, message: '请填写运单号' }]}
>
<Input placeholder="请输入运单号" />
</Form.Item>
<Form.Item name="manualQueryUrl" label="物流查询链接(可选)">
<Input placeholder="https://..." />
</Form.Item>
</Form>
)}
</div>
</>
)}
</Modal>