fix(mini-user): split order-logistics utils for weapp bundle stability
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { Text } from '@tarojs/components';
|
import { Text } from '@tarojs/components';
|
||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import { splitLogisticsText } from '../lib/order-logistics';
|
import { splitLogisticsText } from '../lib/order-logistics-utils';
|
||||||
import { toast } from '../lib/api';
|
import { toast } from '../lib/api';
|
||||||
|
|
||||||
type LogisticsRichTextProps = {
|
type LogisticsRichTextProps = {
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import type { OrderTrackDto } from '@dukang/shared-types';
|
||||||
|
import { isXfxProviderCode } from '@dukang/shared-types';
|
||||||
|
import { formatShanghaiDateTime } from './datetime';
|
||||||
|
|
||||||
|
export type OrderDelivery = {
|
||||||
|
provider?: string;
|
||||||
|
trackingNo?: string;
|
||||||
|
logisticsCompany?: string;
|
||||||
|
manualQueryUrl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderTrackNode = {
|
||||||
|
trackInfo?: string;
|
||||||
|
createTime?: string;
|
||||||
|
createdAt?: string;
|
||||||
|
statusName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderTrackEstimatedArrival = {
|
||||||
|
arriveTime: string;
|
||||||
|
siteName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderTrackResponse = OrderTrackDto;
|
||||||
|
|
||||||
|
/** 订单详情页展示物流摘要的状态(配送中) */
|
||||||
|
export const ORDER_LOGISTICS_PREVIEW_STATUSES = ['SHIPPING', 'SHIPPED'] as const;
|
||||||
|
|
||||||
|
const PHONE_PATTERN = /(1[3-9]\d{9}|\d{3,4}-\d{7,8})/g;
|
||||||
|
|
||||||
|
function compareTrackNodeTime(a: OrderTrackNode, b: OrderTrackNode) {
|
||||||
|
const ta = new Date(a.createTime || a.createdAt || 0).getTime();
|
||||||
|
const tb = new Date(b.createTime || b.createdAt || 0).getTime();
|
||||||
|
if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;
|
||||||
|
if (Number.isNaN(ta)) return 1;
|
||||||
|
if (Number.isNaN(tb)) return -1;
|
||||||
|
return ta - tb;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deliveryProviderLabel(provider?: string, company?: string) {
|
||||||
|
if (company) return company;
|
||||||
|
if (!provider || provider === 'MOCK' || isXfxProviderCode(provider)) {
|
||||||
|
return '小飞侠配送';
|
||||||
|
}
|
||||||
|
if (provider === 'LOGISTICS') return '快递配送';
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldLoadOrderTrack(delivery?: OrderDelivery | null) {
|
||||||
|
if (!delivery) return false;
|
||||||
|
if (delivery.trackingNo) return true;
|
||||||
|
return isXfxProviderCode(delivery.provider || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatTrackNodeTime(node: OrderTrackNode) {
|
||||||
|
return formatShanghaiDateTime(node.createTime || node.createdAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sortTrackNodesNewestFirst(nodes: OrderTrackNode[]): OrderTrackNode[] {
|
||||||
|
return [...nodes].sort((a, b) => -compareTrackNodeTime(a, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sortTrackNodesOldestFirst(nodes: OrderTrackNode[]): OrderTrackNode[] {
|
||||||
|
return [...nodes].sort(compareTrackNodeTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function latestTrackNode(nodes: OrderTrackNode[]) {
|
||||||
|
if (!nodes.length) return null;
|
||||||
|
let latest = nodes[0];
|
||||||
|
for (let i = 1; i < nodes.length; i += 1) {
|
||||||
|
if (compareTrackNodeTime(nodes[i], latest) > 0) {
|
||||||
|
latest = nodes[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return latest;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLogisticsNotArrived(nodes: OrderTrackNode[], orderStatus?: string) {
|
||||||
|
if (['PENDING_RECEIVE', 'DELIVERED', 'COMPLETED'].includes(orderStatus || '')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (nodes.length === 0) return true;
|
||||||
|
return !nodes.some(
|
||||||
|
(node) => node.statusName?.includes('签收') || node.trackInfo?.includes('签收'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatEstimatedArrival(eta?: OrderTrackEstimatedArrival | null) {
|
||||||
|
if (!eta?.arriveTime) return '';
|
||||||
|
const time = formatShanghaiDateTime(eta.arriveTime);
|
||||||
|
if (eta.siteName) return `预计送达:${time}(${eta.siteName})`;
|
||||||
|
return `预计送达:${time}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LogisticsTextSegment = { type: 'text' | 'phone'; value: string };
|
||||||
|
|
||||||
|
export function splitLogisticsText(text: string): LogisticsTextSegment[] {
|
||||||
|
if (!text) return [];
|
||||||
|
const segments: LogisticsTextSegment[] = [];
|
||||||
|
let lastIndex = 0;
|
||||||
|
const regex = new RegExp(PHONE_PATTERN.source, 'g');
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
while ((match = regex.exec(text)) !== null) {
|
||||||
|
if (match.index > lastIndex) {
|
||||||
|
segments.push({ type: 'text', value: text.slice(lastIndex, match.index) });
|
||||||
|
}
|
||||||
|
segments.push({ type: 'phone', value: match[0] });
|
||||||
|
lastIndex = match.index + match[0].length;
|
||||||
|
}
|
||||||
|
if (lastIndex < text.length) {
|
||||||
|
segments.push({ type: 'text', value: text.slice(lastIndex) });
|
||||||
|
}
|
||||||
|
return segments.length ? segments : [{ type: 'text', value: text }];
|
||||||
|
}
|
||||||
@@ -1,118 +1,7 @@
|
|||||||
import type { OrderTrackDto } from '@dukang/shared-types';
|
export * from './order-logistics-utils';
|
||||||
import { isXfxProviderCode } from '@dukang/shared-types';
|
import type { OrderTrackResponse } from './order-logistics-utils';
|
||||||
import { formatShanghaiDateTime } from './datetime';
|
|
||||||
import { request } from './api';
|
import { request } from './api';
|
||||||
|
|
||||||
export type OrderDelivery = {
|
|
||||||
provider?: string;
|
|
||||||
trackingNo?: string;
|
|
||||||
logisticsCompany?: string;
|
|
||||||
manualQueryUrl?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type OrderTrackNode = {
|
|
||||||
trackInfo?: string;
|
|
||||||
createTime?: string;
|
|
||||||
createdAt?: string;
|
|
||||||
statusName?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type OrderTrackEstimatedArrival = {
|
|
||||||
arriveTime: string;
|
|
||||||
siteName?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type OrderTrackResponse = OrderTrackDto;
|
|
||||||
|
|
||||||
/** 订单详情页展示物流摘要的状态(配送中) */
|
|
||||||
export const ORDER_LOGISTICS_PREVIEW_STATUSES = ['SHIPPING', 'SHIPPED'] as const;
|
|
||||||
|
|
||||||
const PHONE_PATTERN = /(1[3-9]\d{9}|\d{3,4}-\d{7,8})/g;
|
|
||||||
|
|
||||||
export function deliveryProviderLabel(provider?: string, company?: string) {
|
|
||||||
if (company) return company;
|
|
||||||
if (!provider || provider === 'MOCK' || isXfxProviderCode(provider)) {
|
|
||||||
return '小飞侠配送';
|
|
||||||
}
|
|
||||||
if (provider === 'LOGISTICS') return '快递配送';
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shouldLoadOrderTrack(delivery?: OrderDelivery | null) {
|
|
||||||
if (!delivery) return false;
|
|
||||||
if (delivery.trackingNo) return true;
|
|
||||||
return isXfxProviderCode(delivery.provider || '');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatTrackNodeTime(node: OrderTrackNode) {
|
|
||||||
return formatShanghaiDateTime(node.createTime || node.createdAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sortTrackNodesNewestFirst(nodes: OrderTrackNode[]) {
|
|
||||||
return [...nodes].sort((a, b) => {
|
|
||||||
const ta = new Date(a.createTime || a.createdAt || 0).getTime();
|
|
||||||
const tb = new Date(b.createTime || b.createdAt || 0).getTime();
|
|
||||||
if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;
|
|
||||||
if (Number.isNaN(ta)) return 1;
|
|
||||||
if (Number.isNaN(tb)) return -1;
|
|
||||||
return tb - ta;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sortTrackNodesOldestFirst(nodes: OrderTrackNode[]) {
|
|
||||||
return [...nodes].sort((a, b) => {
|
|
||||||
const ta = new Date(a.createTime || a.createdAt || 0).getTime();
|
|
||||||
const tb = new Date(b.createTime || b.createdAt || 0).getTime();
|
|
||||||
if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;
|
|
||||||
if (Number.isNaN(ta)) return 1;
|
|
||||||
if (Number.isNaN(tb)) return -1;
|
|
||||||
return ta - tb;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function latestTrackNode(nodes: OrderTrackNode[]) {
|
|
||||||
const sorted = sortTrackNodesNewestFirst(nodes);
|
|
||||||
return sorted[0] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isLogisticsNotArrived(nodes: OrderTrackNode[], orderStatus?: string) {
|
|
||||||
if (['PENDING_RECEIVE', 'DELIVERED', 'COMPLETED'].includes(orderStatus || '')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (nodes.length === 0) return true;
|
|
||||||
return !nodes.some(
|
|
||||||
(node) => node.statusName?.includes('签收') || node.trackInfo?.includes('签收'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatEstimatedArrival(eta?: OrderTrackEstimatedArrival | null) {
|
|
||||||
if (!eta?.arriveTime) return '';
|
|
||||||
const time = formatShanghaiDateTime(eta.arriveTime);
|
|
||||||
if (eta.siteName) return `预计送达:${time}(${eta.siteName})`;
|
|
||||||
return `预计送达:${time}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LogisticsTextSegment = { type: 'text' | 'phone'; value: string };
|
|
||||||
|
|
||||||
export function splitLogisticsText(text: string): LogisticsTextSegment[] {
|
|
||||||
if (!text) return [];
|
|
||||||
const segments: LogisticsTextSegment[] = [];
|
|
||||||
let lastIndex = 0;
|
|
||||||
const regex = new RegExp(PHONE_PATTERN.source, 'g');
|
|
||||||
let match: RegExpExecArray | null;
|
|
||||||
while ((match = regex.exec(text)) !== null) {
|
|
||||||
if (match.index > lastIndex) {
|
|
||||||
segments.push({ type: 'text', value: text.slice(lastIndex, match.index) });
|
|
||||||
}
|
|
||||||
segments.push({ type: 'phone', value: match[0] });
|
|
||||||
lastIndex = match.index + match[0].length;
|
|
||||||
}
|
|
||||||
if (lastIndex < text.length) {
|
|
||||||
segments.push({ type: 'text', value: text.slice(lastIndex) });
|
|
||||||
}
|
|
||||||
return segments.length ? segments : [{ type: 'text', value: text }];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchOrderTrack(orderId: string) {
|
export async function fetchOrderTrack(orderId: string) {
|
||||||
return request<OrderTrackResponse>(`/trade/orders/${orderId}/track`);
|
return request<OrderTrackResponse>(`/trade/orders/${orderId}/track`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import LogisticsRichText from '../../components/LogisticsRichText';
|
|||||||
import { request, toast } from '../../lib/api';
|
import { request, toast } from '../../lib/api';
|
||||||
import { buildPayUrl } from '../../lib/checkout-nav';
|
import { buildPayUrl } from '../../lib/checkout-nav';
|
||||||
import {
|
import {
|
||||||
fetchOrderTrack,
|
|
||||||
formatEstimatedArrival,
|
formatEstimatedArrival,
|
||||||
isLogisticsNotArrived,
|
isLogisticsNotArrived,
|
||||||
latestTrackNode,
|
latestTrackNode,
|
||||||
@@ -18,7 +17,8 @@ import {
|
|||||||
shouldLoadOrderTrack,
|
shouldLoadOrderTrack,
|
||||||
type OrderTrackEstimatedArrival,
|
type OrderTrackEstimatedArrival,
|
||||||
type OrderTrackNode,
|
type OrderTrackNode,
|
||||||
} from '../../lib/order-logistics';
|
} from '../../lib/order-logistics-utils';
|
||||||
|
import { fetchOrderTrack } from '../../lib/order-logistics';
|
||||||
import { maskPhone } from '../../lib/phone';
|
import { maskPhone } from '../../lib/phone';
|
||||||
import { confirmOrderReceive, type WechatConfirmPayload } from '../../lib/wechat-order-confirm';
|
import { confirmOrderReceive, type WechatConfirmPayload } from '../../lib/wechat-order-confirm';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import LogisticsRichText from '../../components/LogisticsRichText';
|
|||||||
import { request, toast } from '../../lib/api';
|
import { request, toast } from '../../lib/api';
|
||||||
import {
|
import {
|
||||||
deliveryProviderLabel,
|
deliveryProviderLabel,
|
||||||
fetchOrderTrack,
|
|
||||||
formatTrackNodeTime,
|
formatTrackNodeTime,
|
||||||
shouldLoadOrderTrack,
|
shouldLoadOrderTrack,
|
||||||
sortTrackNodesOldestFirst,
|
sortTrackNodesOldestFirst,
|
||||||
type OrderDelivery,
|
type OrderDelivery,
|
||||||
type OrderTrackNode,
|
type OrderTrackNode,
|
||||||
} from '../../lib/order-logistics';
|
} from '../../lib/order-logistics-utils';
|
||||||
|
import { fetchOrderTrack } from '../../lib/order-logistics';
|
||||||
import { usePageView } from '../../lib/usePageView';
|
import { usePageView } from '../../lib/usePageView';
|
||||||
|
|
||||||
type OrderDetail = {
|
type OrderDetail = {
|
||||||
@@ -32,7 +32,10 @@ export default function OrderLogisticsPage() {
|
|||||||
const [signPhotoUrls, setSignPhotoUrls] = useState<string[]>([]);
|
const [signPhotoUrls, setSignPhotoUrls] = useState<string[]>([]);
|
||||||
const [trackLoading, setTrackLoading] = useState(false);
|
const [trackLoading, setTrackLoading] = useState(false);
|
||||||
|
|
||||||
const sortedTrackNodes = useMemo(() => sortTrackNodesOldestFirst(trackNodes), [trackNodes]);
|
const sortedTrackNodes = useMemo(
|
||||||
|
() => (Array.isArray(trackNodes) ? sortTrackNodesOldestFirst(trackNodes) : []),
|
||||||
|
[trackNodes],
|
||||||
|
);
|
||||||
const timelineComplete = ['PENDING_RECEIVE', 'DELIVERED', 'COMPLETED'].includes(
|
const timelineComplete = ['PENDING_RECEIVE', 'DELIVERED', 'COMPLETED'].includes(
|
||||||
order?.status || '',
|
order?.status || '',
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user