feat: 羚牛 BI 报表服务初始版本
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful

- Hono + TypeScript 后端,连接 MySQL 数据库
- React + Vite + Tailwind 前端
- 车辆资产实时汇总(按车型/品牌型号分组)
- 本周交车/还车/替换统计(关联业务单据)
- 车牌号详情弹窗
- Dockerfile + Woodpecker CI 流水线

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-03-26 14:02:49 +08:00
commit 0cc5024132
23 changed files with 5783 additions and 0 deletions

49
src/api.ts Normal file
View File

@@ -0,0 +1,49 @@
import type {
SummaryData,
TypeSummary,
VehicleListItem,
} 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;
}): 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);
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 fetchWeeklyDetail(type: string): Promise<WeeklyDetailItem[]> {
return fetchJson<WeeklyDetailItem[]>(`${BASE}/weekly-detail?type=${type}`);
}