2026-03-19 01:39:34 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* [MOCK] 物流管理数据源
|
|
|
|
|
|
* AI注意: 这是Mock实现,不是真实业务逻辑
|
|
|
|
|
|
* 仅在USE_MOCK=true时启用
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
export interface LogisticsProvider {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
code: string;
|
|
|
|
|
|
status: 'ACTIVE' | 'INACTIVE';
|
|
|
|
|
|
apiEndpoint?: string;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface Shipment {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
orderId: string;
|
|
|
|
|
|
trackingNumber: string;
|
|
|
|
|
|
provider: string;
|
|
|
|
|
|
status: 'PENDING' | 'PICKED_UP' | 'IN_TRANSIT' | 'OUT_FOR_DELIVERY' | 'DELIVERED' | 'FAILED';
|
|
|
|
|
|
origin: string;
|
|
|
|
|
|
destination: string;
|
|
|
|
|
|
estimatedDelivery: string;
|
|
|
|
|
|
actualDelivery?: string;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface TrackingEvent {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
shipmentId: string;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
location: string;
|
|
|
|
|
|
description: string;
|
|
|
|
|
|
timestamp: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-22 11:25:28 +08:00
|
|
|
|
export interface FreightQuote {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
channel: string;
|
|
|
|
|
|
carrier: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
basePrice: number;
|
|
|
|
|
|
pricePerKg: number;
|
|
|
|
|
|
pricePerVol: number;
|
|
|
|
|
|
estimatedDays: number;
|
|
|
|
|
|
totalPrice: number;
|
|
|
|
|
|
fuelSurcharge: number;
|
|
|
|
|
|
remoteAreaSurcharge: number;
|
|
|
|
|
|
customsDuty: number;
|
|
|
|
|
|
insurance: number;
|
|
|
|
|
|
totalWithDuty: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
export interface ILogisticsDataSource {
|
|
|
|
|
|
fetchProviders(): Promise<LogisticsProvider[]>;
|
|
|
|
|
|
createProvider(data: Partial<LogisticsProvider>): Promise<LogisticsProvider>;
|
|
|
|
|
|
updateProvider(id: string, data: Partial<LogisticsProvider>): Promise<LogisticsProvider>;
|
|
|
|
|
|
deleteProvider(id: string): Promise<void>;
|
|
|
|
|
|
|
|
|
|
|
|
fetchShipments(params?: { status?: string; provider?: string }): Promise<Shipment[]>;
|
|
|
|
|
|
createShipment(data: Partial<Shipment>): Promise<Shipment>;
|
|
|
|
|
|
updateShipment(id: string, data: Partial<Shipment>): Promise<Shipment>;
|
|
|
|
|
|
|
|
|
|
|
|
fetchTrackingEvents(shipmentId: string): Promise<TrackingEvent[]>;
|
|
|
|
|
|
calculateFreight(params: { origin: string; destination: string; weight: number; dimensions: { length: number; width: number; height: number } }): Promise<{ provider: string; cost: number; estimatedDays: number }[]>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class MockLogisticsDataSource implements ILogisticsDataSource {
|
|
|
|
|
|
private providers: LogisticsProvider[] = [
|
|
|
|
|
|
{ id: '1', name: 'FedEx', code: 'FEDEX', status: 'ACTIVE', createdAt: '2026-03-01', updatedAt: '2026-03-01' },
|
|
|
|
|
|
{ id: '2', name: 'UPS', code: 'UPS', status: 'ACTIVE', createdAt: '2026-03-01', updatedAt: '2026-03-01' },
|
|
|
|
|
|
{ id: '3', name: 'DHL', code: 'DHL', status: 'ACTIVE', createdAt: '2026-03-01', updatedAt: '2026-03-01' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
private shipments: Shipment[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '1',
|
|
|
|
|
|
orderId: 'ORD-2026-001',
|
|
|
|
|
|
trackingNumber: 'FX123456789',
|
|
|
|
|
|
provider: 'FEDEX',
|
|
|
|
|
|
status: 'IN_TRANSIT',
|
|
|
|
|
|
origin: 'Los Angeles, CA',
|
|
|
|
|
|
destination: 'New York, NY',
|
|
|
|
|
|
estimatedDelivery: '2026-03-20',
|
|
|
|
|
|
createdAt: '2026-03-15',
|
|
|
|
|
|
updatedAt: '2026-03-16',
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
private trackingEvents: TrackingEvent[] = [
|
|
|
|
|
|
{ id: '1', shipmentId: '1', status: 'PICKED_UP', location: 'Los Angeles, CA', description: 'Package picked up', timestamp: '2026-03-15T10:00:00Z' },
|
|
|
|
|
|
{ id: '2', shipmentId: '1', status: 'IN_TRANSIT', location: 'Phoenix, AZ', description: 'In transit', timestamp: '2026-03-16T08:00:00Z' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
async fetchProviders(): Promise<LogisticsProvider[]> {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
|
return [...this.providers];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async createProvider(data: Partial<LogisticsProvider>): Promise<LogisticsProvider> {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
|
const newProvider: LogisticsProvider = {
|
|
|
|
|
|
id: `${Date.now()}`,
|
|
|
|
|
|
name: data.name || '',
|
|
|
|
|
|
code: data.code || '',
|
|
|
|
|
|
status: 'ACTIVE',
|
|
|
|
|
|
createdAt: new Date().toISOString().split('T')[0],
|
|
|
|
|
|
updatedAt: new Date().toISOString().split('T')[0],
|
|
|
|
|
|
...data,
|
|
|
|
|
|
};
|
|
|
|
|
|
this.providers.push(newProvider);
|
|
|
|
|
|
return newProvider;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async updateProvider(id: string, data: Partial<LogisticsProvider>): Promise<LogisticsProvider> {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
|
const index = this.providers.findIndex(p => p.id === id);
|
|
|
|
|
|
if (index === -1) throw new Error('Provider not found');
|
|
|
|
|
|
this.providers[index] = { ...this.providers[index], ...data, updatedAt: new Date().toISOString().split('T')[0] };
|
|
|
|
|
|
return this.providers[index];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async deleteProvider(id: string): Promise<void> {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
|
const index = this.providers.findIndex(p => p.id === id);
|
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
|
this.providers.splice(index, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fetchShipments(params?: { status?: string; provider?: string }): Promise<Shipment[]> {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
|
let result = [...this.shipments];
|
|
|
|
|
|
if (params?.status) {
|
|
|
|
|
|
result = result.filter(s => s.status === params.status);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (params?.provider) {
|
|
|
|
|
|
result = result.filter(s => s.provider === params.provider);
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async createShipment(data: Partial<Shipment>): Promise<Shipment> {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
|
const newShipment: Shipment = {
|
|
|
|
|
|
id: `${Date.now()}`,
|
|
|
|
|
|
orderId: data.orderId || '',
|
|
|
|
|
|
trackingNumber: `FX${Date.now()}`,
|
|
|
|
|
|
provider: data.provider || 'FEDEX',
|
|
|
|
|
|
status: 'PENDING',
|
|
|
|
|
|
origin: data.origin || '',
|
|
|
|
|
|
destination: data.destination || '',
|
|
|
|
|
|
estimatedDelivery: data.estimatedDelivery || '',
|
|
|
|
|
|
createdAt: new Date().toISOString().split('T')[0],
|
|
|
|
|
|
updatedAt: new Date().toISOString().split('T')[0],
|
|
|
|
|
|
...data,
|
|
|
|
|
|
};
|
|
|
|
|
|
this.shipments.push(newShipment);
|
|
|
|
|
|
return newShipment;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async updateShipment(id: string, data: Partial<Shipment>): Promise<Shipment> {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
|
const index = this.shipments.findIndex(s => s.id === id);
|
|
|
|
|
|
if (index === -1) throw new Error('Shipment not found');
|
|
|
|
|
|
this.shipments[index] = { ...this.shipments[index], ...data, updatedAt: new Date().toISOString().split('T')[0] };
|
|
|
|
|
|
return this.shipments[index];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fetchTrackingEvents(shipmentId: string): Promise<TrackingEvent[]> {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
|
return this.trackingEvents.filter(e => e.shipmentId === shipmentId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async calculateFreight(params: { origin: string; destination: string; weight: number; dimensions: { length: number; width: number; height: number } }): Promise<{ provider: string; cost: number; estimatedDays: number }[]> {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
|
|
return [
|
|
|
|
|
|
{ provider: 'FEDEX', cost: 15.99, estimatedDays: 3 },
|
|
|
|
|
|
{ provider: 'UPS', cost: 18.50, estimatedDays: 2 },
|
|
|
|
|
|
{ provider: 'DHL', cost: 22.00, estimatedDays: 1 },
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class ApiLogisticsDataSource implements ILogisticsDataSource {
|
|
|
|
|
|
private baseUrl = '/api/logistics';
|
|
|
|
|
|
|
|
|
|
|
|
async fetchProviders(): Promise<LogisticsProvider[]> {
|
|
|
|
|
|
const response = await fetch(`${this.baseUrl}/providers`);
|
|
|
|
|
|
if (!response.ok) throw new Error('Failed to fetch providers');
|
|
|
|
|
|
return response.json();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async createProvider(data: Partial<LogisticsProvider>): Promise<LogisticsProvider> {
|
|
|
|
|
|
const response = await fetch(`${this.baseUrl}/providers`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
|
body: JSON.stringify(data),
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!response.ok) throw new Error('Failed to create provider');
|
|
|
|
|
|
return response.json();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async updateProvider(id: string, data: Partial<LogisticsProvider>): Promise<LogisticsProvider> {
|
|
|
|
|
|
const response = await fetch(`${this.baseUrl}/providers/${id}`, {
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
|
body: JSON.stringify(data),
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!response.ok) throw new Error('Failed to update provider');
|
|
|
|
|
|
return response.json();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async deleteProvider(id: string): Promise<void> {
|
|
|
|
|
|
const response = await fetch(`${this.baseUrl}/providers/${id}`, { method: 'DELETE' });
|
|
|
|
|
|
if (!response.ok) throw new Error('Failed to delete provider');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fetchShipments(params?: { status?: string; provider?: string }): Promise<Shipment[]> {
|
|
|
|
|
|
const response = await fetch(`${this.baseUrl}/shipments?${new URLSearchParams(params as any)}`);
|
|
|
|
|
|
if (!response.ok) throw new Error('Failed to fetch shipments');
|
|
|
|
|
|
return response.json();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async createShipment(data: Partial<Shipment>): Promise<Shipment> {
|
|
|
|
|
|
const response = await fetch(`${this.baseUrl}/shipments`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
|
body: JSON.stringify(data),
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!response.ok) throw new Error('Failed to create shipment');
|
|
|
|
|
|
return response.json();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async updateShipment(id: string, data: Partial<Shipment>): Promise<Shipment> {
|
|
|
|
|
|
const response = await fetch(`${this.baseUrl}/shipments/${id}`, {
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
|
body: JSON.stringify(data),
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!response.ok) throw new Error('Failed to update shipment');
|
|
|
|
|
|
return response.json();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fetchTrackingEvents(shipmentId: string): Promise<TrackingEvent[]> {
|
|
|
|
|
|
const response = await fetch(`${this.baseUrl}/shipments/${shipmentId}/tracking`);
|
|
|
|
|
|
if (!response.ok) throw new Error('Failed to fetch tracking events');
|
|
|
|
|
|
return response.json();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async calculateFreight(params: { origin: string; destination: string; weight: number; dimensions: { length: number; width: number; height: number } }): Promise<{ provider: string; cost: number; estimatedDays: number }[]> {
|
|
|
|
|
|
const response = await fetch(`${this.baseUrl}/freight/calculate`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
|
body: JSON.stringify(params),
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!response.ok) throw new Error('Failed to calculate freight');
|
|
|
|
|
|
return response.json();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const useMock = process.env.REACT_APP_USE_MOCK === 'true';
|
|
|
|
|
|
export const logisticsDataSource: ILogisticsDataSource = useMock
|
|
|
|
|
|
? new MockLogisticsDataSource()
|
|
|
|
|
|
: new ApiLogisticsDataSource();
|