fix: 部门出勤/闲置下钻改为基于当日里程区分
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- 后端/list新增attendance参数(active/idle),查询当日里程表区分 出勤(mileage>0)和闲置(mileage=0),仅对Operating车辆生效 - 前端部门Tab出勤/闲置点击改用attendance:'active'/'idle'替代 原来错误的category:'Operating'/'Inventory' - 修复department='公务车'过滤:匹配departmentName为null的车辆 - 前端API层新增attendance参数传递 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
14
src/App.tsx
14
src/App.tsx
@@ -57,6 +57,7 @@ export default function App() {
|
|||||||
manager?: string;
|
manager?: string;
|
||||||
customer?: string;
|
customer?: string;
|
||||||
department?: string;
|
department?: string;
|
||||||
|
attendance?: 'active' | 'idle';
|
||||||
isColdChain?: boolean;
|
isColdChain?: boolean;
|
||||||
isTrailer?: boolean;
|
isTrailer?: boolean;
|
||||||
type?: string;
|
type?: string;
|
||||||
@@ -190,6 +191,7 @@ export default function App() {
|
|||||||
if (showPlateNumbers.manager) params.manager = showPlateNumbers.manager;
|
if (showPlateNumbers.manager) params.manager = showPlateNumbers.manager;
|
||||||
if (showPlateNumbers.customer) params.customer = showPlateNumbers.customer;
|
if (showPlateNumbers.customer) params.customer = showPlateNumbers.customer;
|
||||||
if (showPlateNumbers.department) params.department = showPlateNumbers.department;
|
if (showPlateNumbers.department) params.department = showPlateNumbers.department;
|
||||||
|
if (showPlateNumbers.attendance) params.attendance = showPlateNumbers.attendance;
|
||||||
if (!showPlateNumbers.type) {
|
if (!showPlateNumbers.type) {
|
||||||
if (showPlateNumbers.isColdChain !== undefined) params.isColdChain = String(showPlateNumbers.isColdChain);
|
if (showPlateNumbers.isColdChain !== undefined) params.isColdChain = String(showPlateNumbers.isColdChain);
|
||||||
if (showPlateNumbers.isTrailer !== undefined) params.isTrailer = String(showPlateNumbers.isTrailer);
|
if (showPlateNumbers.isTrailer !== undefined) params.isTrailer = String(showPlateNumbers.isTrailer);
|
||||||
@@ -1274,14 +1276,14 @@ export default function App() {
|
|||||||
<span className="text-xl font-black">{deptData.reduce((s, d) => s + d.totalAssets, 0)}</span>
|
<span className="text-xl font-black">{deptData.reduce((s, d) => s + d.totalAssets, 0)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col cursor-pointer hover:opacity-80 transition-opacity"
|
<div className="flex flex-col cursor-pointer hover:opacity-80 transition-opacity"
|
||||||
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Operating', source: 'department', title: '部门运营统计 - 出勤车辆' })}>
|
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', attendance: 'active', source: 'department', title: '部门运营统计 - 出勤车辆' })}>
|
||||||
<span className="text-[9px] opacity-50 uppercase font-bold tracking-widest mb-0.5 text-green-400">出勤车辆</span>
|
<span className="text-[9px] opacity-50 uppercase font-bold tracking-widest mb-0.5 text-green-400">出勤车辆</span>
|
||||||
<span className="text-xl font-black text-green-400">
|
<span className="text-xl font-black text-green-400">
|
||||||
{deptData.reduce((acc, d) => acc + d.operatingCount, 0)}
|
{deptData.reduce((acc, d) => acc + d.operatingCount, 0)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col cursor-pointer hover:opacity-80 transition-opacity"
|
<div className="flex flex-col cursor-pointer hover:opacity-80 transition-opacity"
|
||||||
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Inventory', source: 'department', title: '部门运营统计 - 闲置车辆' })}>
|
onClick={() => setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', attendance: 'idle', source: 'department', title: '部门运营统计 - 闲置车辆' })}>
|
||||||
<span className="text-[9px] opacity-50 uppercase font-bold tracking-widest mb-0.5 text-slate-400">闲置车辆</span>
|
<span className="text-[9px] opacity-50 uppercase font-bold tracking-widest mb-0.5 text-slate-400">闲置车辆</span>
|
||||||
<span className="text-xl font-black text-slate-400">
|
<span className="text-xl font-black text-slate-400">
|
||||||
{deptData.reduce((acc, d) => acc + d.idleCount, 0)}
|
{deptData.reduce((acc, d) => acc + d.idleCount, 0)}
|
||||||
@@ -1396,7 +1398,7 @@ export default function App() {
|
|||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', department: dept.department, category: 'Operating', source: 'department', title: `部门运营统计 - ${dept.department} - 出勤车辆` });
|
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', department: dept.department, attendance: 'active', source: 'department', title: `部门运营统计 - ${dept.department} - 出勤车辆` });
|
||||||
}}
|
}}
|
||||||
className="text-green-500 hover:underline font-black"
|
className="text-green-500 hover:underline font-black"
|
||||||
>
|
>
|
||||||
@@ -1407,7 +1409,7 @@ export default function App() {
|
|||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', department: dept.department, category: 'Inventory', source: 'department', title: `部门运营统计 - ${dept.department} - 闲置车辆` });
|
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', department: dept.department, attendance: 'idle', source: 'department', title: `部门运营统计 - ${dept.department} - 闲置车辆` });
|
||||||
}}
|
}}
|
||||||
className="text-gray-400 hover:underline font-black"
|
className="text-gray-400 hover:underline font-black"
|
||||||
>
|
>
|
||||||
@@ -1608,12 +1610,12 @@ export default function App() {
|
|||||||
<div className="text-xs font-black text-gray-800">{dept.totalAssets}</div>
|
<div className="text-xs font-black text-gray-800">{dept.totalAssets}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center cursor-pointer hover:bg-green-50 rounded p-1"
|
<div className="text-center cursor-pointer hover:bg-green-50 rounded p-1"
|
||||||
onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', department: dept.department, category: 'Operating', source: 'department', title: `部门运营统计 - ${dept.department} - 出勤车辆` }); }}>
|
onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', department: dept.department, attendance: 'active', source: 'department', title: `部门运营统计 - ${dept.department} - 出勤车辆` }); }}>
|
||||||
<div className="text-[8px] text-green-500 uppercase font-bold mb-0.5">出勤</div>
|
<div className="text-[8px] text-green-500 uppercase font-bold mb-0.5">出勤</div>
|
||||||
<div className="text-xs font-black text-green-500">{dept.operatingCount}</div>
|
<div className="text-xs font-black text-green-500">{dept.operatingCount}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center cursor-pointer hover:bg-gray-100 rounded p-1"
|
<div className="text-center cursor-pointer hover:bg-gray-100 rounded p-1"
|
||||||
onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', department: dept.department, category: 'Inventory', source: 'department', title: `部门运营统计 - ${dept.department} - 闲置车辆` }); }}>
|
onClick={(e) => { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', department: dept.department, attendance: 'idle', source: 'department', title: `部门运营统计 - ${dept.department} - 闲置车辆` }); }}>
|
||||||
<div className="text-[8px] text-gray-400 uppercase font-bold mb-0.5">闲置</div>
|
<div className="text-[8px] text-gray-400 uppercase font-bold mb-0.5">闲置</div>
|
||||||
<div className="text-xs font-black text-gray-400">{dept.idleCount}</div>
|
<div className="text-xs font-black text-gray-400">{dept.idleCount}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export async function fetchVehicleList(params: {
|
|||||||
isColdChain?: string;
|
isColdChain?: string;
|
||||||
isTrailer?: string;
|
isTrailer?: string;
|
||||||
department?: string;
|
department?: string;
|
||||||
|
attendance?: string;
|
||||||
}): Promise<VehicleListItem[]> {
|
}): Promise<VehicleListItem[]> {
|
||||||
const query = new URLSearchParams();
|
const query = new URLSearchParams();
|
||||||
if (params.batch) query.set('batch', params.batch);
|
if (params.batch) query.set('batch', params.batch);
|
||||||
@@ -49,6 +50,7 @@ export async function fetchVehicleList(params: {
|
|||||||
if (params.isColdChain) query.set('isColdChain', params.isColdChain);
|
if (params.isColdChain) query.set('isColdChain', params.isColdChain);
|
||||||
if (params.isTrailer) query.set('isTrailer', params.isTrailer);
|
if (params.isTrailer) query.set('isTrailer', params.isTrailer);
|
||||||
if (params.department) query.set('department', params.department);
|
if (params.department) query.set('department', params.department);
|
||||||
|
if (params.attendance) query.set('attendance', params.attendance);
|
||||||
return fetchJson<VehicleListItem[]>(`${BASE}/list?${query.toString()}`);
|
return fetchJson<VehicleListItem[]>(`${BASE}/list?${query.toString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -858,9 +858,22 @@ const VEHICLE_TYPE_FILTERS: Record<string, (v: Vehicle) => boolean> = {
|
|||||||
// GET /api/vehicles/list — flat list with optional filters
|
// GET /api/vehicles/list — flat list with optional filters
|
||||||
app.get('/list', async (c) => {
|
app.get('/list', async (c) => {
|
||||||
const vehicles = await getVehicles();
|
const vehicles = await getVehicles();
|
||||||
const { batch, model, location, status, category, vehicleType, manager, customer, isColdChain, isTrailer, department } = c.req.query();
|
const { batch, model, location, status, category, vehicleType, manager, customer, isColdChain, isTrailer, department, attendance } = c.req.query();
|
||||||
|
|
||||||
let filtered = vehicles;
|
let filtered = vehicles;
|
||||||
|
|
||||||
|
// attendance filter: active = today mileage > 0, idle = today mileage = 0 (only for Operating vehicles)
|
||||||
|
if (attendance === 'active' || attendance === 'idle') {
|
||||||
|
const [todayRows] = await pool.query<any[]>(`SELECT plateNumber, dayMileage FROM ln_vehicle_day_mileage WHERE dates = CURDATE()`);
|
||||||
|
const todayMap = new Map<string, number>();
|
||||||
|
for (const row of todayRows as any[]) todayMap.set(row.plateNumber, Number(row.dayMileage));
|
||||||
|
filtered = filtered.filter((v) => v.status === 'Operating');
|
||||||
|
if (attendance === 'active') {
|
||||||
|
filtered = filtered.filter((v) => (todayMap.get(v.plateNumber) || 0) > 0);
|
||||||
|
} else {
|
||||||
|
filtered = filtered.filter((v) => (todayMap.get(v.plateNumber) || 0) === 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (vehicleType) {
|
if (vehicleType) {
|
||||||
if (VEHICLE_TYPE_FILTERS[vehicleType]) {
|
if (VEHICLE_TYPE_FILTERS[vehicleType]) {
|
||||||
filtered = filtered.filter(VEHICLE_TYPE_FILTERS[vehicleType]);
|
filtered = filtered.filter(VEHICLE_TYPE_FILTERS[vehicleType]);
|
||||||
@@ -904,7 +917,7 @@ app.get('/list', async (c) => {
|
|||||||
filtered = filtered.filter((v) => customer === '未分配客户' ? !v.customerName : v.customerName === customer);
|
filtered = filtered.filter((v) => customer === '未分配客户' ? !v.customerName : v.customerName === customer);
|
||||||
}
|
}
|
||||||
if (department) {
|
if (department) {
|
||||||
filtered = filtered.filter((v) => v.departmentName === department);
|
filtered = filtered.filter((v) => department === '公务车' ? !v.departmentName : v.departmentName === department);
|
||||||
}
|
}
|
||||||
if (isColdChain !== undefined) {
|
if (isColdChain !== undefined) {
|
||||||
const wantCold = isColdChain === 'true';
|
const wantCold = isColdChain === 'true';
|
||||||
|
|||||||
Reference in New Issue
Block a user