186 lines
6.2 KiB
TypeScript
186 lines
6.2 KiB
TypeScript
import type { TargetSummary, TargetVehicle, TargetYearlyAssessment, TrendPoint } from '../types';
|
|
|
|
export type ChartType = 'bar' | 'line' | 'area';
|
|
export type VehicleSort = 'asc' | 'desc';
|
|
export type PressureLevel = '健康' | '关注' | '高压';
|
|
|
|
export interface SelectedTargetMetrics {
|
|
assessment: TargetYearlyAssessment | null;
|
|
completion: number;
|
|
remaining: number;
|
|
daysLeft: number;
|
|
dailyTarget: number;
|
|
qualifiedRate: number;
|
|
trendDelta: number;
|
|
pressureLevel: PressureLevel;
|
|
}
|
|
|
|
export interface TargetTableRow {
|
|
assessment: TargetYearlyAssessment | null;
|
|
completion: number;
|
|
qualified: number;
|
|
halfQualified: number;
|
|
goal: number;
|
|
completed: number;
|
|
remainingMileage: number;
|
|
days: number;
|
|
daily: number;
|
|
taskPerVehicle: number;
|
|
}
|
|
|
|
export interface TargetTotals {
|
|
today: number;
|
|
cumulative: number;
|
|
vehicleCount: number;
|
|
averageCompletion: number;
|
|
}
|
|
|
|
export function getDefaultDate(now = new Date()): string {
|
|
const date = new Date(now);
|
|
if (date.getHours() < 5) date.setDate(date.getDate() - 1);
|
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
}
|
|
|
|
export function getCurrentDateLabel(now = new Date()): string {
|
|
return `${now.getFullYear()}.${now.getMonth() + 1}.${now.getDate()}`;
|
|
}
|
|
|
|
export function fmtKm(value: number): string {
|
|
if (value >= 10000) return (value / 10000).toFixed(2) + '万';
|
|
return value.toLocaleString();
|
|
}
|
|
|
|
export function fmtPercent(value: number): string {
|
|
return `${value.toFixed(1)}%`;
|
|
}
|
|
|
|
export function getTargetAssessment(
|
|
target: TargetSummary,
|
|
selectedYear?: number,
|
|
): TargetYearlyAssessment | null {
|
|
if (target.yearlyAssessments.length === 0) return null;
|
|
return target.yearlyAssessments.find(item => item.yearNumber === selectedYear) || target.yearlyAssessments[0];
|
|
}
|
|
|
|
export function fmtDateLabel(date: string | null): string {
|
|
if (!date) return '';
|
|
const [year, month, day] = date.split('-');
|
|
return `${year}.${Number(month)}.${Number(day)}`;
|
|
}
|
|
|
|
export function shortTargetName(name: string): string {
|
|
const match = name.match(/(\d+)[辆台](.+)/);
|
|
if (!match) return name;
|
|
const count = match[1];
|
|
let desc = match[2];
|
|
desc = desc.replace('4.5T普货', '普货');
|
|
desc = desc.replace('4.5T冷链车', '冷藏车');
|
|
desc = desc.replace('4.5T冷链', '冷藏车');
|
|
desc = desc.replace('18T', '18T');
|
|
return `${count}台${desc}`;
|
|
}
|
|
|
|
export function trendDateLabel(value: string): string {
|
|
const [month, day] = value.split('-');
|
|
return `${parseInt(month)}.${parseInt(day)}`;
|
|
}
|
|
|
|
export function orderTargets(targets: TargetSummary[]): TargetSummary[] {
|
|
const focused = targets.find(item => item.targetName.includes('羚牛136')) || targets[0];
|
|
return focused
|
|
? [focused, ...targets.filter(item => item.id !== focused.id)]
|
|
: targets;
|
|
}
|
|
|
|
export function initialAssessmentYears(targets: TargetSummary[]): Record<number, number> {
|
|
return Object.fromEntries(
|
|
targets.map(item => [item.id, item.yearlyAssessments[0]?.yearNumber || 1]),
|
|
);
|
|
}
|
|
|
|
export function deriveSelectedTargetMetrics(
|
|
target: TargetSummary | undefined,
|
|
selectedYear: number | undefined,
|
|
trendData: TrendPoint[],
|
|
): SelectedTargetMetrics {
|
|
const assessment = target ? getTargetAssessment(target, selectedYear) : null;
|
|
const completion = assessment?.completionRate ?? target?.avgCompletion ?? 0;
|
|
const remaining = assessment?.remaining ?? 0;
|
|
const daysLeft = assessment?.daysLeft ?? target?.daysLeft ?? 0;
|
|
const dailyTarget = assessment?.dailyTarget ?? (daysLeft > 0 ? remaining / daysLeft : 0);
|
|
const qualifiedRate = assessment?.qualifiedRate
|
|
?? (target?.vehicleCount ? target.yearQualifiedCount / target.vehicleCount * 100 : 0);
|
|
const latestTrend = trendData[trendData.length - 1];
|
|
const previousTrend = trendData[trendData.length - 2];
|
|
const trendDelta = latestTrend && previousTrend ? latestTrend.mileage - previousTrend.mileage : 0;
|
|
const pressureLevel = completion >= 90 ? '健康' : completion >= 70 ? '关注' : '高压';
|
|
|
|
return {
|
|
assessment,
|
|
completion,
|
|
remaining,
|
|
daysLeft,
|
|
dailyTarget,
|
|
qualifiedRate,
|
|
trendDelta,
|
|
pressureLevel,
|
|
};
|
|
}
|
|
|
|
export function deriveTargetTableRow(
|
|
target: TargetSummary,
|
|
selectedYear?: number,
|
|
): TargetTableRow {
|
|
const assessment = getTargetAssessment(target, selectedYear);
|
|
return {
|
|
assessment,
|
|
completion: assessment?.completionRate ?? target.avgCompletion,
|
|
qualified: assessment?.qualifiedCount ?? target.yearQualifiedCount,
|
|
halfQualified: assessment?.halfQualifiedCount ?? target.halfQualifiedCount,
|
|
goal: assessment?.target ?? target.currentYearTarget,
|
|
completed: assessment?.completed ?? target.currentYearCompleted,
|
|
remainingMileage: assessment?.remaining ?? target.remaining,
|
|
days: assessment?.daysLeft ?? target.daysLeft,
|
|
daily: assessment?.dailyTarget ?? target.dailyTarget,
|
|
taskPerVehicle: target.annualMileagePerVehicle * (assessment?.yearNumber || 1),
|
|
};
|
|
}
|
|
|
|
export function summarizeTargets(
|
|
targets: TargetSummary[],
|
|
assessmentYearMap: Record<number, number>,
|
|
): TargetTotals {
|
|
const totals = targets.reduce((result, target) => ({
|
|
today: result.today + target.todayTotal,
|
|
cumulative: result.cumulative + target.cumulativeTotal,
|
|
vehicleCount: result.vehicleCount + target.vehicleCount,
|
|
completion: result.completion
|
|
+ (getTargetAssessment(target, assessmentYearMap[target.id])?.completionRate ?? target.avgCompletion),
|
|
}), { today: 0, cumulative: 0, vehicleCount: 0, completion: 0 });
|
|
|
|
return {
|
|
today: totals.today,
|
|
cumulative: totals.cumulative,
|
|
vehicleCount: totals.vehicleCount,
|
|
averageCompletion: targets.length > 0 ? totals.completion / targets.length : 0,
|
|
};
|
|
}
|
|
|
|
export function filterAndSortTargetVehicles(
|
|
vehicles: TargetVehicle[],
|
|
search: string,
|
|
sort: VehicleSort,
|
|
): TargetVehicle[] {
|
|
return vehicles
|
|
.filter(vehicle => vehicle.plateNumber.toLowerCase().includes(search.toLowerCase()))
|
|
.sort((a, b) => {
|
|
const valueA = a.todayMileage || 0;
|
|
const valueB = b.todayMileage || 0;
|
|
return sort === 'desc' ? valueB - valueA : valueA - valueB;
|
|
});
|
|
}
|
|
|
|
export function totalTodayMileage(vehicles: TargetVehicle[]): number {
|
|
return vehicles.reduce((sum, vehicle) => sum + (vehicle.todayMileage || 0), 0);
|
|
}
|