233 lines
8.8 KiB
TypeScript
233 lines
8.8 KiB
TypeScript
import { useEffect, useState } from 'react';
|
||
import { useNavigate, useParams } from 'react-router-dom';
|
||
import PageHeader from '@dukang/shared-ui/PageHeader';
|
||
import type { PartnerProxyOrderListItem } from '@dukang/shared-types';
|
||
import { request } from '../lib/api';
|
||
import {
|
||
proxyOrderPayLabel,
|
||
proxyOrderStatusColor,
|
||
proxyOrderStatusLabel,
|
||
} from '../lib/proxyOrderStatus';
|
||
import { toastError, toastSuccess } from '../lib/toast';
|
||
|
||
function fmtMoney(n: number) {
|
||
return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||
}
|
||
|
||
function fmtTime(value: string | undefined) {
|
||
if (!value) return '—';
|
||
const d = new Date(value);
|
||
if (Number.isNaN(d.getTime())) return value;
|
||
const pad = (n: number) => String(n).padStart(2, '0');
|
||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||
}
|
||
|
||
function deliveryLabel(order: PartnerProxyOrderListItem) {
|
||
const t = String(order.deliveryType || '').toUpperCase();
|
||
if (t === 'ON_SITE_PICKUP') return '现场提货';
|
||
if (t === 'CROSS_CITY') return '跨城配送';
|
||
if (t === 'LOCAL') return '同城配送';
|
||
return '代下单';
|
||
}
|
||
|
||
type TrackNode = {
|
||
time?: string;
|
||
status?: string;
|
||
description?: string;
|
||
};
|
||
|
||
type TrackResult = {
|
||
nodes?: TrackNode[];
|
||
trackingNo?: string | null;
|
||
provider?: string | null;
|
||
manualQueryUrl?: string | null;
|
||
};
|
||
|
||
export default function ProxyOrderDetailPage() {
|
||
const { id } = useParams();
|
||
const navigate = useNavigate();
|
||
const [order, setOrder] = useState<PartnerProxyOrderListItem | null>(null);
|
||
const [error, setError] = useState('');
|
||
const [track, setTrack] = useState<TrackResult | null>(null);
|
||
const [trackError, setTrackError] = useState('');
|
||
const [paying, setPaying] = useState(false);
|
||
|
||
useEffect(() => {
|
||
document.title = '代下单详情';
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
if (!id) return;
|
||
void request<PartnerProxyOrderListItem>('PARTNER_H5', `/partner/proxy-orders/${id}`)
|
||
.then(setOrder)
|
||
.catch((e) => setError(e instanceof Error ? e.message : '加载失败'));
|
||
}, [id]);
|
||
|
||
useEffect(() => {
|
||
if (!id || !order) return;
|
||
const dt = String(order.deliveryType || '').toUpperCase();
|
||
if (dt === 'ON_SITE_PICKUP' || order.payStatus !== 'PAID') {
|
||
setTrack(null);
|
||
return;
|
||
}
|
||
void request<TrackResult>('PARTNER_H5', `/partner/proxy-orders/${id}/track`, { silent: true })
|
||
.then(setTrack)
|
||
.catch((e) => setTrackError(e instanceof Error ? e.message : '物流暂不可用'));
|
||
}, [id, order]);
|
||
|
||
async function continuePay() {
|
||
if (!id) return;
|
||
setPaying(true);
|
||
try {
|
||
await request('PARTNER_H5', `/partner/proxy-orders/${id}/pay/mock-confirm`, {
|
||
method: 'POST',
|
||
body: '{}',
|
||
silent: true,
|
||
});
|
||
toastSuccess('支付成功');
|
||
const refreshed = await request<PartnerProxyOrderListItem>(
|
||
'PARTNER_H5',
|
||
`/partner/proxy-orders/${id}`,
|
||
);
|
||
setOrder(refreshed);
|
||
} catch (e) {
|
||
toastError(e instanceof Error ? e.message : '支付失败');
|
||
} finally {
|
||
setPaying(false);
|
||
}
|
||
}
|
||
|
||
const img = order?.imageResource?.url || '';
|
||
const isOnSite = String(order?.deliveryType || '').toUpperCase() === 'ON_SITE_PICKUP';
|
||
|
||
return (
|
||
<div className="page-no-tab partner-orders-page">
|
||
<PageHeader title="代下单详情" onBack={() => navigate('/center/proxy-orders')} />
|
||
|
||
{error && (
|
||
<p className="partner-form-error" role="alert" style={{ margin: '16px 20px' }}>
|
||
{error}
|
||
</p>
|
||
)}
|
||
|
||
{!order && !error && <p className="label-md text-muted" style={{ margin: 20 }}>加载中…</p>}
|
||
|
||
{order && (
|
||
<div style={{ padding: '0 16px 32px' }}>
|
||
<div className="partner-order-card" style={{ pointerEvents: 'none' }}>
|
||
<div className="partner-order-card-top">
|
||
<span className="label-md text-muted">NO. {order.orderNo}</span>
|
||
<span
|
||
className="label-md"
|
||
style={{ color: proxyOrderStatusColor(order.status), fontWeight: 600 }}
|
||
>
|
||
{proxyOrderStatusLabel(order.status)}
|
||
</span>
|
||
</div>
|
||
<div className="partner-order-product">
|
||
<div
|
||
className="partner-order-product-img"
|
||
style={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
overflow: 'hidden',
|
||
background: img ? `center/cover no-repeat url(${img})` : undefined,
|
||
}}
|
||
>
|
||
{!img && <span className="material-symbols-outlined text-muted">liquor</span>}
|
||
</div>
|
||
<div style={{ flex: 1, minWidth: 0 }}>
|
||
<h3 className="headline-md">{order.productName || '杜康好酒'}</h3>
|
||
{order.productSpec && (
|
||
<p className="label-md text-muted" style={{ marginTop: 4 }}>
|
||
{order.productSpec}
|
||
</p>
|
||
)}
|
||
<p className="body-md" style={{ marginTop: 8 }}>
|
||
×{order.quantity} · ¥{fmtMoney(Number(order.payAmount || 0))}
|
||
</p>
|
||
<p className="label-md text-muted" style={{ marginTop: 4 }}>
|
||
权益 ¥{fmtMoney(Number(order.benefitAmount || 0))}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<section className="partner-form-card" style={{ marginTop: 12 }}>
|
||
<h3 className="headline-md" style={{ marginBottom: 12 }}>客户信息</h3>
|
||
<p className="body-md">姓名:{order.receiverName || '—'}</p>
|
||
<p className="body-md" style={{ marginTop: 8 }}>手机:{order.receiverPhone || '—'}</p>
|
||
<p className="body-md" style={{ marginTop: 8 }}>
|
||
地址:{order.receiverAddress || (isOnSite ? '现场提货' : '—')}
|
||
</p>
|
||
</section>
|
||
|
||
<section className="partner-form-card" style={{ marginTop: 12 }}>
|
||
<h3 className="headline-md" style={{ marginBottom: 12 }}>订单信息</h3>
|
||
<p className="body-md">配送方式:{deliveryLabel(order)}</p>
|
||
<p className="body-md" style={{ marginTop: 8 }}>下单时间:{fmtTime(order.createdAt)}</p>
|
||
<p className="body-md" style={{ marginTop: 8 }}>
|
||
支付状态:{proxyOrderPayLabel(order.payStatus)}
|
||
</p>
|
||
<p className="body-md" style={{ marginTop: 8 }}>
|
||
订单状态:{proxyOrderStatusLabel(order.status)}
|
||
</p>
|
||
</section>
|
||
|
||
{order.payStatus === 'UNPAID' || order.status === 'PENDING_PAY' ? (
|
||
<button
|
||
type="button"
|
||
className="btn btn-primary btn-block"
|
||
style={{ marginTop: 16 }}
|
||
disabled={paying}
|
||
onClick={() => void continuePay()}
|
||
>
|
||
{paying ? '处理中…' : '继续支付(模拟)'}
|
||
</button>
|
||
) : null}
|
||
|
||
{!isOnSite && order.payStatus === 'PAID' ? (
|
||
<section className="partner-form-card" style={{ marginTop: 12 }}>
|
||
<h3 className="headline-md" style={{ marginBottom: 12 }}>物流信息</h3>
|
||
{trackError ? (
|
||
<p className="label-md text-muted">{trackError}</p>
|
||
) : !track ? (
|
||
<p className="label-md text-muted">加载物流…</p>
|
||
) : (
|
||
<>
|
||
{track.trackingNo ? (
|
||
<p className="body-md" style={{ marginBottom: 8 }}>
|
||
运单号:{track.trackingNo}
|
||
{track.provider ? `(${track.provider})` : ''}
|
||
</p>
|
||
) : null}
|
||
{track.manualQueryUrl ? (
|
||
<p className="body-md" style={{ marginBottom: 8 }}>
|
||
<a href={track.manualQueryUrl} target="_blank" rel="noreferrer">
|
||
查看物流官网
|
||
</a>
|
||
</p>
|
||
) : null}
|
||
{(track.nodes ?? []).length === 0 ? (
|
||
<p className="label-md text-muted">暂无物流轨迹(待总部发货后更新)</p>
|
||
) : (
|
||
<ul style={{ paddingLeft: 18, margin: 0 }}>
|
||
{(track.nodes ?? []).map((n, i) => (
|
||
<li key={`${n.time}-${i}`} className="body-md" style={{ marginBottom: 8 }}>
|
||
<div className="label-md text-muted">{fmtTime(n.time)}</div>
|
||
<div>{n.description || n.status || '—'}</div>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
</>
|
||
)}
|
||
</section>
|
||
) : null}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|