fix(mini-user): split order-logistics utils for weapp bundle stability

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-05 23:41:31 +08:00
parent 345d5db9c9
commit 672a995c59
5 changed files with 125 additions and 119 deletions
@@ -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 }];
}
+2 -113
View File
@@ -1,118 +1,7 @@
import type { OrderTrackDto } from '@dukang/shared-types';
import { isXfxProviderCode } from '@dukang/shared-types';
import { formatShanghaiDateTime } from './datetime';
export * from './order-logistics-utils';
import type { OrderTrackResponse } from './order-logistics-utils';
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) {
return request<OrderTrackResponse>(`/trade/orders/${orderId}/track`);
}