Files
ln-bi/src/api.ts
kkfluous 2ba25427de
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
feat: tab导航、recharts图表、库存统计、出勤率里程、区域城市下钻、数据一致性修复
- 新增tab导航(总览/按部门/按区域/按客户)+ 移动端底部导航
- 新增recharts柱状图(区域分布)和饼图(客户分布)
- 新增库存统计模块(按区域/按车型,筛选面板)
- 对接ln_vehicle_day_mileage表实现出勤率和日均里程
- 区域运营支持区域→城市→车型三级下钻
- 修复ownership取字段错误(改用truck_rent_status)
- 修复部门统计闲置定义(当日无行驶里程)
- 修复区域图表"其他"重复问题(后端Top N合并)
- 修复城市名空值降级(resolveCity取province)
- 修复下钻数据不一致(统一category/vehicleType参数)
- 扩展/list端点支持大区过滤和未分配匹配
- 所有筛选改为searchable datalist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 18:09:18 +08:00

84 lines
2.7 KiB
TypeScript

import type {
SummaryData,
TypeSummary,
VehicleListItem,
DeptGroup,
RegionGroup,
CustomerStats,
RegionalInventoryStats,
} from './types';
const BASE = '/api/vehicles';
async function fetchJson<T>(url: string): Promise<T> {
const res = await fetch(url);
if (!res.ok) throw new Error(`API error: ${res.status} ${res.statusText}`);
return res.json();
}
export async function fetchSummary(): Promise<SummaryData> {
return fetchJson<SummaryData>(`${BASE}/summary`);
}
export async function fetchByType(): Promise<TypeSummary[]> {
return fetchJson<TypeSummary[]>(`${BASE}/by-type`);
}
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;
}): 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);
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(): Promise<DeptGroup[]> {
return fetchJson<DeptGroup[]>(`${BASE}/dept-stats`);
}
export async function fetchRegionStats(): Promise<RegionGroup[]> {
return fetchJson<RegionGroup[]>(`${BASE}/region-stats`);
}
export async function fetchCustomerStats(): Promise<CustomerStats[]> {
return fetchJson<CustomerStats[]>(`${BASE}/customer-stats`);
}
export async function fetchInventoryStats(): Promise<RegionalInventoryStats[]> {
return fetchJson<RegionalInventoryStats[]>(`${BASE}/inventory-stats`);
}
export async function fetchRegionChart(groupBy: string, top = 8): Promise<{ name: string; value: number }[]> {
return fetchJson<{ name: string; value: number }[]>(`${BASE}/region-chart?groupBy=${groupBy}&top=${top}`);
}
export async function fetchWeeklyDetail(type: string): Promise<WeeklyDetailItem[]> {
return fetchJson<WeeklyDetailItem[]>(`${BASE}/weekly-detail?type=${type}`);
}