Files
ln-bi/src/modules/assets/api.ts
kkfluous cfe79cace2
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
fix(assets): correct modal filtering for 待交车/库存-其他/本周X
三个弹窗筛选问题一起修:

1. 待交车 drill-in:Pending 原本错归入 weekly-detail(该接口不支持
   model/batch/location 过滤),改走 /list 并给 /list 的 category 分支
   补上 'Pending' 状态匹配。

2. 库存-其他:'其他' 同时存在于两个体系——资产表的"库存-其他"
   (mapRegion 结果) vs 区域统计的"其他"(mapMacroRegion 结果),
   过滤语义完全不同。引入 source 参数由前端传递,source==='asset'
   时按 v.location 匹配(库存语义),否则按 mapMacroRegion(宏观区域)。
   抽取 filterByLocation 辅助函数供 /list 与 /weekly-detail 共用。

3. 本周交车/还车/替换:/weekly-detail 接口新增 model/batch/location/source
   过滤;前端 fetchWeeklyDetail 签名扩容。实现方式:SQL 结果与缓存
   车辆集(按过滤条件筛)按 truck_id 取交集。

4. BIGINT 精度丢失:DELIVERED_SQL / RETURNED_SQL / REPLACED_SQL 及
   pending/new 子查询原本使用裸 truck.id,mysql2 驱动把 BIGINT 当
   JS Number 返回,大 id (>2^53) 尾部被截,导致 truck_id 交集永远
   为空。全部改为 CAST(truck.id AS CHAR),与 MAIN_SQL 保持一致。

5. fetchVehicleList 类型补上 source,避免前端传的 source 被 URLSearchParams
   构造时静默丢弃。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 11:00:45 +08:00

128 lines
4.5 KiB
TypeScript

import type {
SummaryData,
TypeSummary,
VehicleListItem,
DeptGroup,
RegionGroup,
CustomerStats,
RegionalInventoryStats,
} from './types';
import { fetchJson } from '../../auth/api-client';
const BASE = '/api/vehicles';
export interface SubjectOption {
name: string;
total: number;
inventory: number;
operating: number;
}
function withSubject(path: string, subject?: string | null): string {
if (!subject) return path;
const sep = path.includes('?') ? '&' : '?';
return `${path}${sep}subject=${encodeURIComponent(subject)}`;
}
export async function fetchSubjects(): Promise<SubjectOption[]> {
return fetchJson<SubjectOption[]>(`${BASE}/subjects`);
}
export async function fetchSummary(subject?: string | null): Promise<SummaryData> {
return fetchJson<SummaryData>(withSubject(`${BASE}/summary`, subject));
}
export async function fetchByType(subject?: string | null): Promise<TypeSummary[]> {
return fetchJson<TypeSummary[]>(withSubject(`${BASE}/by-type`, subject));
}
export async function fetchVehicleList(params: {
batch?: string;
model?: string;
location?: string;
status?: string;
category?: string;
vehicleType?: string;
manager?: string;
customer?: string;
isColdChain?: string;
isTrailer?: string;
department?: string;
attendance?: string;
subject?: string | null;
source?: string;
}): Promise<VehicleListItem[]> {
const query = new URLSearchParams();
if (params.batch) query.set('batch', params.batch);
if (params.model) query.set('model', params.model);
if (params.location) query.set('location', params.location);
if (params.status) query.set('status', params.status);
if (params.category) query.set('category', params.category);
if (params.vehicleType) query.set('vehicleType', params.vehicleType);
if (params.manager) query.set('manager', params.manager);
if (params.customer) query.set('customer', params.customer);
if (params.isColdChain) query.set('isColdChain', params.isColdChain);
if (params.isTrailer) query.set('isTrailer', params.isTrailer);
if (params.department) query.set('department', params.department);
if (params.attendance) query.set('attendance', params.attendance);
if (params.subject) query.set('subject', params.subject);
if (params.source) query.set('source', params.source);
return fetchJson<VehicleListItem[]>(`${BASE}/list?${query.toString()}`);
}
export interface WeeklyDetailItem {
truck_id: number;
plate_number: string;
handover_date: string | null;
contract_type: string | null;
customer_name: string | null;
}
export async function fetchDeptStats(subject?: string | null): Promise<DeptGroup[]> {
return fetchJson<DeptGroup[]>(withSubject(`${BASE}/dept-stats`, subject));
}
export async function fetchRegionStats(
params?: { customer?: string; city?: string; region?: string },
subject?: string | null,
): Promise<RegionGroup[]> {
const query = new URLSearchParams();
if (params?.customer) query.set('customer', params.customer);
if (params?.city) query.set('city', params.city);
if (params?.region) query.set('region', params.region);
if (subject) query.set('subject', subject);
const qs = query.toString();
return fetchJson<RegionGroup[]>(`${BASE}/region-stats${qs ? `?${qs}` : ''}`);
}
export async function fetchCustomerStats(subject?: string | null): Promise<CustomerStats[]> {
return fetchJson<CustomerStats[]>(withSubject(`${BASE}/customer-stats`, subject));
}
export async function fetchInventoryStats(subject?: string | null): Promise<RegionalInventoryStats[]> {
return fetchJson<RegionalInventoryStats[]>(withSubject(`${BASE}/inventory-stats`, subject));
}
export async function fetchRegionChart(
groupBy: string,
top = 8,
source = 'realtime',
subject?: string | null,
): Promise<{ name: string; value: number }[]> {
return fetchJson<{ name: string; value: number }[]>(
withSubject(`${BASE}/region-chart?groupBy=${groupBy}&top=${top}&source=${source}`, subject),
);
}
export async function fetchWeeklyDetail(
type: string,
filters?: { model?: string; batch?: string; location?: string; source?: string },
): Promise<WeeklyDetailItem[]> {
const params = new URLSearchParams({ type });
if (filters?.model && filters.model !== 'All') params.set('model', filters.model);
if (filters?.batch && filters.batch !== 'All') params.set('batch', filters.batch);
if (filters?.location && filters.location !== 'All') params.set('location', filters.location);
if (filters?.source) params.set('source', filters.source);
return fetchJson<WeeklyDetailItem[]>(`${BASE}/weekly-detail?${params.toString()}`);
}