feat(courier): 小飞侠签收照联调与 OSS 失败回退
CI / verify (push) Waiting to run

补齐 admin 100108 联调入口;track 优先用缓存签收照,上传失败回退 dataURI,日志截断大图。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-02 17:56:56 +08:00
parent c6b01def4c
commit e5fe8b903a
8 changed files with 183 additions and 40 deletions
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import {
Alert,
Button,
Descriptions,
Drawer,
@@ -140,6 +141,14 @@ export default function OrderTrackDrawer({
</Space>
</Image.PreviewGroup>
</div>
) : nodes.some((n) => n.statusName?.includes('签收') || n.trackInfo?.includes('签收')) ? (
<Alert
type="info"
showIcon
style={{ marginBottom: 16 }}
message="暂无签收照片"
description="小飞侠 100108 未返回图片(可能是本人签收未拍照,或照片已过期)。可在「小飞侠联调」用运单号复测。"
/>
) : null}
<Typography.Title level={5} style={{ marginTop: 0 }}>
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import {
Alert, Button, Card, Col, Descriptions, Form, Input, InputNumber, Row, Select, Space,
Alert, Button, Card, Col, Descriptions, Form, Image, Input, InputNumber, Row, Select, Space,
Tabs, Tag, Typography, message,
} from 'antd';
import { request } from '../lib/api';
@@ -53,6 +53,22 @@ const CREATE_DEFAULTS = {
function ResultPanel({ result }: { result: ApiResult | null }) {
if (!result) return <Typography.Text type="secondary"></Typography.Text>;
const display = (() => {
if (!result.data || typeof result.data !== 'object') return result;
const data = result.data as { count?: number; data?: string[] };
if (!Array.isArray(data.data)) return result;
return {
...result,
data: {
...data,
data: data.data.map((item) =>
typeof item === 'string' && item.length > 120
? `${item.slice(0, 80)}…(len=${item.length})`
: item,
),
},
};
})();
return (
<div>
<Space style={{ marginBottom: 8 }}>
@@ -64,7 +80,7 @@ function ResultPanel({ result }: { result: ApiResult | null }) {
margin: 0, padding: 12, background: '#f5f5f5', borderRadius: 4,
maxHeight: 360, overflow: 'auto', fontSize: 12,
}}>
{JSON.stringify(result, null, 2)}
{JSON.stringify(display, null, 2)}
</pre>
</div>
);
@@ -81,7 +97,9 @@ export default function XiaofeixiaTestPage() {
const [queryForm] = Form.useForm();
const [batchForm] = Form.useForm();
const [trackForm] = Form.useForm();
const [signPhotoForm] = Form.useForm();
const [cancelForm] = Form.useForm();
const [signPhotoPreview, setSignPhotoPreview] = useState<string[]>([]);
useEffect(() => {
void request<XfxConfig>('/admin/courier/xiaofeixia/config').then(setConfig);
@@ -99,12 +117,19 @@ export default function XiaofeixiaTestPage() {
body: JSON.stringify(body),
});
setResult(res);
if (path.endsWith('/get-sign-photos')) {
const data = (res.data as { data?: string[] } | undefined)?.data;
setSignPhotoPreview(Array.isArray(data) ? data.filter((u) => typeof u === 'string') : []);
} else {
setSignPhotoPreview([]);
}
if (res.ok) message.success('调用成功');
else message.warning(res.error || '调用失败');
} catch (e) {
const err = e instanceof Error ? e.message : String(e);
message.error(err);
setResult({ ok: false, elapsedMs: 0, error: err });
setSignPhotoPreview([]);
} finally {
setLoading(false);
}
@@ -118,7 +143,7 @@ export default function XiaofeixiaTestPage() {
<div>
<Typography.Title level={4}></Typography.Title>
<Typography.Paragraph type="secondary">
HQ APIcmd 100101~100301
HQ APIcmd 100101~100301 100108
<Link to="/fulfillment-providers"></Link>
使
</Typography.Paragraph>
@@ -273,6 +298,25 @@ export default function XiaofeixiaTestPage() {
</Form>
),
},
{
key: 'signPhotos',
label: '签收图片 (100108)',
children: (
<Form
form={signPhotoForm}
layout="vertical"
onFinish={(v) => void invoke('/admin/courier/xiaofeixia/get-sign-photos', v)}
>
<Form.Item name="trackingNumber" label="运单号 number">
<Input placeholder="小飞侠运单号,与商家单号二选一" />
</Form.Item>
<Form.Item name="outNumber" label="商家单号 outNumber">
<Input placeholder="外部单号,与运单号二选一" />
</Form.Item>
<Button type="primary" htmlType="submit" loading={loading}></Button>
</Form>
),
},
{
key: 'cancel',
label: '取消订单 (100103)',
@@ -290,6 +334,24 @@ export default function XiaofeixiaTestPage() {
<Col xs={24} lg={10}>
<Card title="响应结果" size="small">
<ResultPanel result={result} />
{signPhotoPreview.length > 0 ? (
<div style={{ marginTop: 16 }}>
<Typography.Text strong>{signPhotoPreview.length}</Typography.Text>
<Image.PreviewGroup>
<Space wrap style={{ marginTop: 8 }}>
{signPhotoPreview.map((url, index) => (
<Image
key={`${index}-${url.slice(0, 32)}`}
src={url}
width={96}
height={96}
style={{ objectFit: 'cover', borderRadius: 6 }}
/>
))}
</Space>
</Image.PreviewGroup>
</div>
) : null}
</Card>
</Col>
</Row>