299 lines
13 KiB
TypeScript
299 lines
13 KiB
TypeScript
import { useEffect, useState } from 'react';
|
||
import { Link } from 'react-router-dom';
|
||
import {
|
||
Alert, Button, Card, Col, Descriptions, Form, Input, InputNumber, Row, Select, Space,
|
||
Tabs, Tag, Typography, message,
|
||
} from 'antd';
|
||
import { request } from '../lib/api';
|
||
|
||
type XfxConfig = {
|
||
provider: string;
|
||
activeProvider: string;
|
||
apiUrl: string;
|
||
appId: string | null;
|
||
mchId: string | null;
|
||
mchIdMasked: string | null;
|
||
hasApiKey: boolean;
|
||
signType: string;
|
||
ready: boolean;
|
||
source?: string;
|
||
hint?: string;
|
||
};
|
||
|
||
type ApiResult = {
|
||
ok: boolean;
|
||
elapsedMs: number;
|
||
data?: unknown;
|
||
error?: string;
|
||
code?: string;
|
||
provider?: string;
|
||
raw?: unknown;
|
||
};
|
||
|
||
const CREATE_DEFAULTS = {
|
||
outNumber: `TEST-${Date.now()}`,
|
||
fromName: '杜康仓库',
|
||
fromMobile: '13800000000',
|
||
fromAddress: '河南省郑州市金水区',
|
||
fromAddressDetail: '杜康酒业仓',
|
||
fromLng: 113.665,
|
||
fromLat: 34.757,
|
||
toName: '测试收货人',
|
||
toMobile: '13800000001',
|
||
toAddress: '河南省郑州市二七区',
|
||
toAddressDetail: '测试路 1 号',
|
||
toLng: 113.640,
|
||
toLat: 34.720,
|
||
goodsName: '杜康酒',
|
||
goodsNum: 2,
|
||
weight: 2,
|
||
payMode: '1',
|
||
remark: 'Admin 联调测试',
|
||
};
|
||
|
||
function ResultPanel({ result }: { result: ApiResult | null }) {
|
||
if (!result) return <Typography.Text type="secondary">点击「调用接口」后在此显示响应</Typography.Text>;
|
||
return (
|
||
<div>
|
||
<Space style={{ marginBottom: 8 }}>
|
||
<Tag color={result.ok ? 'green' : 'red'}>{result.ok ? '成功' : '失败'}</Tag>
|
||
<span>{result.elapsedMs} ms</span>
|
||
{result.code && <span>code: {result.code}</span>}
|
||
</Space>
|
||
<pre style={{
|
||
margin: 0, padding: 12, background: '#f5f5f5', borderRadius: 4,
|
||
maxHeight: 360, overflow: 'auto', fontSize: 12,
|
||
}}>
|
||
{JSON.stringify(result, null, 2)}
|
||
</pre>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default function XiaofeixiaTestPage() {
|
||
const [config, setConfig] = useState<XfxConfig | null>(null);
|
||
const [loading, setLoading] = useState(false);
|
||
const [result, setResult] = useState<ApiResult | null>(null);
|
||
|
||
const [freightForm] = Form.useForm();
|
||
const [coverageForm] = Form.useForm();
|
||
const [createForm] = Form.useForm();
|
||
const [queryForm] = Form.useForm();
|
||
const [batchForm] = Form.useForm();
|
||
const [trackForm] = Form.useForm();
|
||
const [cancelForm] = Form.useForm();
|
||
|
||
useEffect(() => {
|
||
void request<XfxConfig>('/admin/courier/xiaofeixia/config').then(setConfig);
|
||
createForm.setFieldsValue(CREATE_DEFAULTS);
|
||
freightForm.setFieldsValue({ weight: 2 });
|
||
coverageForm.setFieldsValue({ toAddress: '河南省郑州市二七区测试路1号' });
|
||
}, [createForm, freightForm, coverageForm]);
|
||
|
||
async function invoke(path: string, body: unknown) {
|
||
setLoading(true);
|
||
setResult(null);
|
||
try {
|
||
const res = await request<ApiResult>(path, {
|
||
method: 'POST',
|
||
body: JSON.stringify(body),
|
||
});
|
||
setResult(res);
|
||
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 });
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
|
||
function parseList(text?: string) {
|
||
return (text ?? '').split(/[,,\s]+/).map((s) => s.trim()).filter(Boolean);
|
||
}
|
||
|
||
return (
|
||
<div>
|
||
<Typography.Title level={4}>小飞侠接口联调</Typography.Title>
|
||
<Typography.Paragraph type="secondary">
|
||
通过 HQ 后台直接调用后端封装的小飞侠 API(cmd 100101~100301)。凭证请在
|
||
<Link to="/fulfillment-providers">仓配管理</Link>
|
||
中配置;联调会优先使用仓配管理里启用的小飞侠承运商。
|
||
</Typography.Paragraph>
|
||
|
||
{config && (
|
||
<Card size="small" style={{ marginBottom: 16 }}>
|
||
<Descriptions column={3} size="small">
|
||
<Descriptions.Item label="Provider">{config.activeProvider}</Descriptions.Item>
|
||
<Descriptions.Item label="API">{config.apiUrl}</Descriptions.Item>
|
||
<Descriptions.Item label="商户号">{config.mchIdMasked || '—'}</Descriptions.Item>
|
||
<Descriptions.Item label="签名">{config.signType}</Descriptions.Item>
|
||
<Descriptions.Item label="密钥">
|
||
{config.hasApiKey ? <Tag color="green">已配置</Tag> : <Tag color="red">未配置</Tag>}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="状态">
|
||
{config.ready ? <Tag color="green">可调用</Tag> : <Tag color="orange">配置不完整</Tag>}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="来源" span={3}>
|
||
{config.source === 'fulfillment_provider' ? '仓配管理' : '环境变量(兼容)'}
|
||
{config.hint ? ` · ${config.hint}` : ''}
|
||
</Descriptions.Item>
|
||
</Descriptions>
|
||
{!config.ready && (
|
||
<Alert
|
||
type="warning"
|
||
showIcon
|
||
style={{ marginTop: 12 }}
|
||
message="请先在仓配管理中注册小飞侠并填写 API 地址、商户号与 API Key"
|
||
/>
|
||
)}
|
||
</Card>
|
||
)}
|
||
|
||
<Row gutter={16}>
|
||
<Col xs={24} lg={14}>
|
||
<Tabs
|
||
items={[
|
||
{
|
||
key: 'freight',
|
||
label: '运费预估 (100105)',
|
||
children: (
|
||
<Form form={freightForm} layout="vertical" onFinish={(v) => void invoke('/admin/courier/xiaofeixia/estimate-freight', v)}>
|
||
<Form.Item name="weight" label="重量 (kg)" rules={[{ required: true }]}>
|
||
<InputNumber min={0.01} step={0.5} style={{ width: '100%' }} />
|
||
</Form.Item>
|
||
<Button type="primary" htmlType="submit" loading={loading}>调用接口</Button>
|
||
</Form>
|
||
),
|
||
},
|
||
{
|
||
key: 'coverage',
|
||
label: '配送范围 (100301)',
|
||
children: (
|
||
<Form form={coverageForm} layout="vertical" onFinish={(v) => void invoke('/admin/courier/xiaofeixia/check-coverage', v)}>
|
||
<Form.Item name="toAddress" label="收件地址" rules={[{ required: true }]}>
|
||
<Input.TextArea rows={2} placeholder="完整收件地址" />
|
||
</Form.Item>
|
||
<Button type="primary" htmlType="submit" loading={loading}>调用接口</Button>
|
||
</Form>
|
||
),
|
||
},
|
||
{
|
||
key: 'create',
|
||
label: '创建订单 (100101)',
|
||
children: (
|
||
<Form form={createForm} layout="vertical" onFinish={(v) => void invoke('/admin/courier/xiaofeixia/create-shipment', v)}>
|
||
<Form.Item name="outNumber" label="商家单号 outNumber" rules={[{ required: true }]}>
|
||
<Input />
|
||
</Form.Item>
|
||
<Row gutter={12}>
|
||
<Col span={12}>
|
||
<Typography.Text strong>寄件人</Typography.Text>
|
||
<Form.Item name="fromName" label="姓名" rules={[{ required: true }]}><Input /></Form.Item>
|
||
<Form.Item name="fromMobile" label="手机" rules={[{ required: true }]}><Input /></Form.Item>
|
||
<Form.Item name="fromAddress" label="地址" rules={[{ required: true }]}><Input /></Form.Item>
|
||
<Form.Item name="fromAddressDetail" label="详细地址" rules={[{ required: true }]}><Input /></Form.Item>
|
||
<Space>
|
||
<Form.Item name="fromLng" label="经度"><InputNumber style={{ width: 120 }} /></Form.Item>
|
||
<Form.Item name="fromLat" label="纬度"><InputNumber style={{ width: 120 }} /></Form.Item>
|
||
</Space>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Typography.Text strong>收件人</Typography.Text>
|
||
<Form.Item name="toName" label="姓名" rules={[{ required: true }]}><Input /></Form.Item>
|
||
<Form.Item name="toMobile" label="手机" rules={[{ required: true }]}><Input /></Form.Item>
|
||
<Form.Item name="toAddress" label="地址" rules={[{ required: true }]}><Input /></Form.Item>
|
||
<Form.Item name="toAddressDetail" label="详细地址" rules={[{ required: true }]}><Input /></Form.Item>
|
||
<Space>
|
||
<Form.Item name="toLng" label="经度"><InputNumber style={{ width: 120 }} /></Form.Item>
|
||
<Form.Item name="toLat" label="纬度"><InputNumber style={{ width: 120 }} /></Form.Item>
|
||
</Space>
|
||
</Col>
|
||
</Row>
|
||
<Row gutter={12}>
|
||
<Col span={8}><Form.Item name="goodsName" label="货品"><Input /></Form.Item></Col>
|
||
<Col span={8}><Form.Item name="goodsNum" label="件数"><InputNumber min={1} style={{ width: '100%' }} /></Form.Item></Col>
|
||
<Col span={8}><Form.Item name="weight" label="重量 kg"><InputNumber min={0.01} style={{ width: '100%' }} /></Form.Item></Col>
|
||
</Row>
|
||
<Form.Item name="payMode" label="付费方式" rules={[{ required: true }]}>
|
||
<Select options={[
|
||
{ value: '1', label: '寄付' },
|
||
{ value: '2', label: '到付' },
|
||
]} />
|
||
</Form.Item>
|
||
<Form.Item name="remark" label="备注"><Input /></Form.Item>
|
||
<Space>
|
||
<Button type="primary" htmlType="submit" loading={loading}>调用接口</Button>
|
||
<Button onClick={() => createForm.setFieldsValue({ ...CREATE_DEFAULTS, outNumber: `TEST-${Date.now()}` })}>
|
||
重置示例
|
||
</Button>
|
||
</Space>
|
||
</Form>
|
||
),
|
||
},
|
||
{
|
||
key: 'query',
|
||
label: '查询订单 (100104)',
|
||
children: (
|
||
<Form form={queryForm} layout="vertical" onFinish={(v) => void invoke('/admin/courier/xiaofeixia/get-shipment', v)}>
|
||
<Form.Item name="trackingNumber" label="运单号 number"><Input placeholder="小飞侠运单号" /></Form.Item>
|
||
<Form.Item name="outNumber" label="商家单号 outNumber"><Input /></Form.Item>
|
||
<Button type="primary" htmlType="submit" loading={loading}>调用接口</Button>
|
||
</Form>
|
||
),
|
||
},
|
||
{
|
||
key: 'batch',
|
||
label: '批量查询 (100106)',
|
||
children: (
|
||
<Form form={batchForm} layout="vertical" onFinish={(v) => void invoke('/admin/courier/xiaofeixia/batch-get-shipments', {
|
||
trackingNumbers: parseList(v.trackingNumbersText),
|
||
outNumbers: parseList(v.outNumbersText),
|
||
})}>
|
||
<Form.Item name="trackingNumbersText" label="运单号(逗号分隔)">
|
||
<Input.TextArea rows={2} placeholder="NO001,NO002" />
|
||
</Form.Item>
|
||
<Form.Item name="outNumbersText" label="商家单号(逗号分隔)">
|
||
<Input.TextArea rows={2} />
|
||
</Form.Item>
|
||
<Button type="primary" htmlType="submit" loading={loading}>调用接口</Button>
|
||
</Form>
|
||
),
|
||
},
|
||
{
|
||
key: 'track',
|
||
label: '路由查询 (100102)',
|
||
children: (
|
||
<Form form={trackForm} layout="vertical" onFinish={(v) => void invoke('/admin/courier/xiaofeixia/get-track', v)}>
|
||
<Form.Item name="trackingNumber" label="运单号"><Input /></Form.Item>
|
||
<Form.Item name="outNumber" label="商家单号"><Input /></Form.Item>
|
||
<Button type="primary" htmlType="submit" loading={loading}>调用接口</Button>
|
||
</Form>
|
||
),
|
||
},
|
||
{
|
||
key: 'cancel',
|
||
label: '取消订单 (100103)',
|
||
children: (
|
||
<Form form={cancelForm} layout="vertical" onFinish={(v) => void invoke('/admin/courier/xiaofeixia/cancel-shipment', v)}>
|
||
<Form.Item name="trackingNumber" label="运单号"><Input /></Form.Item>
|
||
<Form.Item name="outNumber" label="商家单号"><Input /></Form.Item>
|
||
<Button type="primary" danger htmlType="submit" loading={loading}>调用接口</Button>
|
||
</Form>
|
||
),
|
||
},
|
||
]}
|
||
/>
|
||
</Col>
|
||
<Col xs={24} lg={10}>
|
||
<Card title="响应结果" size="small">
|
||
<ResultPanel result={result} />
|
||
</Card>
|
||
</Col>
|
||
</Row>
|
||
</div>
|
||
);
|
||
}
|