feat(mini-user): show shipping logistics preview and detail timeline (v3.4.13)
Order detail displays latest XFX route under 配送中 with link to full timeline page; docs updated. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,20 +4,16 @@ import Taro, { useRouter } from '@tarojs/taro';
|
||||
import PageShell from '../../components/PageShell';
|
||||
import SubPageHeader from '../../components/SubPageHeader';
|
||||
import { request, toast } from '../../lib/api';
|
||||
import {
|
||||
deliveryProviderLabel,
|
||||
fetchOrderTrack,
|
||||
formatTrackNodeTime,
|
||||
shouldLoadOrderTrack,
|
||||
type OrderDelivery,
|
||||
type OrderTrackNode,
|
||||
} from '../../lib/order-logistics';
|
||||
import { usePageView } from '../../lib/usePageView';
|
||||
|
||||
type OrderDelivery = {
|
||||
provider?: string;
|
||||
trackingNo?: string;
|
||||
logisticsCompany?: string;
|
||||
manualQueryUrl?: string;
|
||||
};
|
||||
|
||||
type TrackNode = {
|
||||
trackInfo?: string;
|
||||
createdAt?: string;
|
||||
};
|
||||
|
||||
type OrderDetail = {
|
||||
id: string;
|
||||
orderNo?: string;
|
||||
@@ -35,25 +31,13 @@ const STATUS_LABELS: Record<string, string> = {
|
||||
COMPLETED: '已完成',
|
||||
};
|
||||
|
||||
function deliveryProviderLabel(provider?: string, company?: string) {
|
||||
if (company) return company;
|
||||
if (!provider || provider === 'MOCK' || provider === 'XFX' || provider === 'XIAOFEIXIA') {
|
||||
return '小飞侠配送';
|
||||
}
|
||||
if (provider === 'LOGISTICS') return '快递配送';
|
||||
return provider;
|
||||
}
|
||||
|
||||
function formatDateTime(value: string) {
|
||||
return String(value).slice(0, 19).replace('T', ' ');
|
||||
}
|
||||
|
||||
export default function OrderLogisticsPage() {
|
||||
const router = useRouter();
|
||||
const orderId = router.params.id ?? '';
|
||||
usePageView('order_logistics_view', orderId ? { orderId } : undefined);
|
||||
const [order, setOrder] = useState<OrderDetail | null>(null);
|
||||
const [trackNodes, setTrackNodes] = useState<TrackNode[]>([]);
|
||||
const [trackNodes, setTrackNodes] = useState<OrderTrackNode[]>([]);
|
||||
const [trackLoading, setTrackLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!orderId) return;
|
||||
@@ -63,13 +47,18 @@ export default function OrderLogisticsPage() {
|
||||
const data = await request<OrderDetail>(`/trade/orders/${orderId}`);
|
||||
if (cancelled) return;
|
||||
setOrder(data);
|
||||
if (data.delivery?.trackingNo || data.delivery?.provider === 'XFX') {
|
||||
try {
|
||||
const track = await request<{ nodes?: TrackNode[] }>(`/trade/orders/${orderId}/track`);
|
||||
if (!cancelled) setTrackNodes(track.nodes ?? []);
|
||||
} catch {
|
||||
if (!cancelled) setTrackNodes([]);
|
||||
}
|
||||
if (!shouldLoadOrderTrack(data.delivery)) {
|
||||
setTrackNodes([]);
|
||||
return;
|
||||
}
|
||||
setTrackLoading(true);
|
||||
try {
|
||||
const track = await fetchOrderTrack(orderId);
|
||||
if (!cancelled) setTrackNodes(track.nodes ?? []);
|
||||
} catch {
|
||||
if (!cancelled) setTrackNodes([]);
|
||||
} finally {
|
||||
if (!cancelled) setTrackLoading(false);
|
||||
}
|
||||
} catch (e) {
|
||||
if (!cancelled) toast(e instanceof Error ? e.message : '加载失败');
|
||||
@@ -99,7 +88,7 @@ export default function OrderLogisticsPage() {
|
||||
|
||||
return (
|
||||
<PageShell variant="sub" className="order-logistics-page">
|
||||
<SubPageHeader title="物流追踪" />
|
||||
<SubPageHeader title="物流详情" />
|
||||
<View className="sub-page-body">
|
||||
{!order ? (
|
||||
<View className="u-empty">加载中…</View>
|
||||
@@ -139,17 +128,36 @@ export default function OrderLogisticsPage() {
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{trackNodes.length > 0 ? (
|
||||
{trackLoading ? (
|
||||
<View className="order-card">
|
||||
<Text className="order-card-title">物流动态</Text>
|
||||
{trackNodes.map((node, index) => (
|
||||
<View key={`${node.trackInfo}-${index}`} className="order-logistics-node">
|
||||
<Text className="order-logistics-node-time">
|
||||
{node.createdAt ? formatDateTime(node.createdAt) : '—'}
|
||||
</Text>
|
||||
<Text className="order-logistics-node-info">{node.trackInfo || '—'}</Text>
|
||||
</View>
|
||||
))}
|
||||
<Text className="u-muted">加载中…</Text>
|
||||
</View>
|
||||
) : trackNodes.length > 0 ? (
|
||||
<View className="order-card">
|
||||
<Text className="order-card-title">物流动态</Text>
|
||||
<View className="order-logistics-timeline">
|
||||
{trackNodes.map((node, index) => (
|
||||
<View
|
||||
key={`${node.trackInfo}-${node.createTime}-${index}`}
|
||||
className={`order-logistics-timeline-item${index === 0 ? ' order-logistics-timeline-item--latest' : ''}`}
|
||||
>
|
||||
<View className="order-logistics-timeline-dot" />
|
||||
<View className="order-logistics-timeline-body">
|
||||
{node.statusName ? (
|
||||
<Text className="order-logistics-timeline-status">{node.statusName}</Text>
|
||||
) : null}
|
||||
<Text className="order-logistics-node-info">{node.trackInfo || '—'}</Text>
|
||||
<Text className="order-logistics-node-time">{formatTrackNodeTime(node)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
) : shouldLoadOrderTrack(delivery) ? (
|
||||
<View className="order-card">
|
||||
<Text className="order-card-title">物流动态</Text>
|
||||
<Text className="u-muted">暂无路由信息,请稍后刷新</Text>
|
||||
</View>
|
||||
) : null}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user