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:
2026-08-05 23:03:41 +08:00
parent fa6fb80d19
commit 8ca893efab
7 changed files with 270 additions and 55 deletions
@@ -8,6 +8,13 @@ import WechatShareReady from '../../components/WechatShareReady';
import ContactCsButton from '../../components/ContactCsButton';
import { request, toast } from '../../lib/api';
import { buildPayUrl } from '../../lib/checkout-nav';
import {
fetchOrderTrack,
latestTrackNode,
ORDER_LOGISTICS_PREVIEW_STATUSES,
shouldLoadOrderTrack,
type OrderTrackNode,
} from '../../lib/order-logistics';
import { maskPhone } from '../../lib/phone';
import { confirmOrderReceive, type WechatConfirmPayload } from '../../lib/wechat-order-confirm';
import {
@@ -85,11 +92,32 @@ export default function OrderDetailPage() {
usePageView('order_detail_view', orderId ? { orderId } : undefined);
const [order, setOrder] = useState<OrderDetail | null>(null);
const [confirming, setConfirming] = useState(false);
const [latestTrack, setLatestTrack] = useState<OrderTrackNode | null>(null);
const [trackLoading, setTrackLoading] = useState(false);
async function loadOrderTrack(delivery: OrderDetail['delivery']) {
if (!orderId || !shouldLoadOrderTrack(delivery)) {
setLatestTrack(null);
return;
}
setTrackLoading(true);
try {
const track = await fetchOrderTrack(orderId);
setLatestTrack(latestTrackNode(track.nodes ?? []));
} catch {
setLatestTrack(null);
} finally {
setTrackLoading(false);
}
}
useEffect(() => {
if (!orderId) return;
request<OrderDetail>(`/trade/orders/${orderId}`)
.then(setOrder)
.then((data) => {
setOrder(data);
void loadOrderTrack(data.delivery);
})
.catch((e) => toast(e instanceof Error ? e.message : '加载失败'));
}, [orderId]);
@@ -97,7 +125,10 @@ export default function OrderDetailPage() {
if (!orderId) return;
// 从微信确认收货组件返回后刷新
request<OrderDetail>(`/trade/orders/${orderId}`)
.then(setOrder)
.then((data) => {
setOrder(data);
void loadOrderTrack(data.delivery);
})
.catch(() => {});
});
@@ -113,6 +144,11 @@ export default function OrderDetailPage() {
['PENDING_SHIP', 'OUT_WAREHOUSE', 'SHIPPING', 'SHIPPED', 'PENDING_RECEIVE', 'DELIVERED', 'COMPLETED'].includes(
order.status || '',
);
const showLogisticsPreview =
canViewLogistics &&
ORDER_LOGISTICS_PREVIEW_STATUSES.includes(
(order?.status || '') as (typeof ORDER_LOGISTICS_PREVIEW_STATUSES)[number],
);
const item = order?.items?.[0];
const productName = item?.productName || order?.productName || '杜康商品';
@@ -230,7 +266,22 @@ export default function OrderDetailPage() {
{isProxy && order.proxyPartnerName ? (
<Text className="order-proxy-hint"> {order.proxyPartnerName} </Text>
) : null}
{canViewLogistics ? (
{showLogisticsPreview ? (
<View className="order-logistics-preview" onClick={goLogistics}>
<View className="order-logistics-preview-main">
{trackLoading ? (
<Text className="order-logistics-preview-info u-muted"></Text>
) : latestTrack?.trackInfo ? (
<Text className="order-logistics-preview-info">{latestTrack.trackInfo}</Text>
) : order.delivery?.manualQueryUrl ? (
<Text className="order-logistics-preview-info u-muted"></Text>
) : (
<Text className="order-logistics-preview-info u-muted"></Text>
)}
</View>
<Text className="order-logistics-preview-link"></Text>
</View>
) : canViewLogistics ? (
<Text className="order-logistics-link" onClick={goLogistics}>
</Text>