fix: 筛选器和显示优化
- 删除年份筛选 - 项目筛选改用真实项目数据(ln_vehicle_contract.project_name) - 主体查询改用 tab_truck → tab_org 的 org_name - 里程区间改为两个独立条件(里程≥ / 里程≤) - 未分配客户显示为 - - 统计报表日期格式改为 M.D(如 3.25) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -98,7 +98,6 @@ export default function MonitoringView() {
|
|||||||
|
|
||||||
// New filters from image
|
// New filters from image
|
||||||
const [filterPlate, setFilterPlate] = useState('All');
|
const [filterPlate, setFilterPlate] = useState('All');
|
||||||
const [filterYear, setFilterYear] = useState('All');
|
|
||||||
const [filterDateRange, setFilterDateRange] = useState({ start: '', end: '' });
|
const [filterDateRange, setFilterDateRange] = useState({ start: '', end: '' });
|
||||||
const [filterDate, setFilterDate] = useState('2026-04-01');
|
const [filterDate, setFilterDate] = useState('2026-04-01');
|
||||||
const [filterProject, setFilterProject] = useState('All');
|
const [filterProject, setFilterProject] = useState('All');
|
||||||
@@ -108,7 +107,7 @@ export default function MonitoringView() {
|
|||||||
|
|
||||||
const [vehicles, setVehicles] = useState<MonitoringVehicle[]>([]);
|
const [vehicles, setVehicles] = useState<MonitoringVehicle[]>([]);
|
||||||
const [stats, setStats] = useState<MonitoringStats>({ totalToday: 0, totalAll: 0, onlineCount: 0, vehicleCount: 0 });
|
const [stats, setStats] = useState<MonitoringStats>({ totalToday: 0, totalAll: 0, onlineCount: 0, vehicleCount: 0 });
|
||||||
const [filterOptions, setFilterOptions] = useState<MonitoringFilters>({ departments: [], customers: [], plates: [] });
|
const [filterOptions, setFilterOptions] = useState<MonitoringFilters>({ departments: [], customers: [], plates: [], projects: [], entities: [] });
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [hasMore, setHasMore] = useState(true);
|
const [hasMore, setHasMore] = useState(true);
|
||||||
@@ -131,7 +130,8 @@ export default function MonitoringView() {
|
|||||||
page: 1,
|
page: 1,
|
||||||
search: searchTerm || undefined,
|
search: searchTerm || undefined,
|
||||||
dept: filterDept !== 'All' ? filterDept : undefined,
|
dept: filterDept !== 'All' ? filterDept : undefined,
|
||||||
customer: filterProject !== 'All' ? filterProject : undefined,
|
project: filterProject !== 'All' ? filterProject : undefined,
|
||||||
|
entity: filterEntity !== 'All' ? filterEntity : undefined,
|
||||||
}).then(d => {
|
}).then(d => {
|
||||||
setVehicles(d.vehicles);
|
setVehicles(d.vehicles);
|
||||||
setStats(d.stats);
|
setStats(d.stats);
|
||||||
@@ -140,7 +140,7 @@ export default function MonitoringView() {
|
|||||||
setPage(1);
|
setPage(1);
|
||||||
setHasMore(d.page < d.totalPages);
|
setHasMore(d.page < d.totalPages);
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}, [sortBy, sortOrder, searchTerm, filterDept, filterProject]);
|
}, [sortBy, sortOrder, searchTerm, filterDept, filterProject, filterEntity]);
|
||||||
|
|
||||||
// 加载更多
|
// 加载更多
|
||||||
const loadMore = useCallback(() => {
|
const loadMore = useCallback(() => {
|
||||||
@@ -154,13 +154,14 @@ export default function MonitoringView() {
|
|||||||
page: nextPage,
|
page: nextPage,
|
||||||
search: searchTerm || undefined,
|
search: searchTerm || undefined,
|
||||||
dept: filterDept !== 'All' ? filterDept : undefined,
|
dept: filterDept !== 'All' ? filterDept : undefined,
|
||||||
customer: filterProject !== 'All' ? filterProject : undefined,
|
project: filterProject !== 'All' ? filterProject : undefined,
|
||||||
|
entity: filterEntity !== 'All' ? filterEntity : undefined,
|
||||||
}).then(d => {
|
}).then(d => {
|
||||||
setVehicles(prev => [...prev, ...d.vehicles]);
|
setVehicles(prev => [...prev, ...d.vehicles]);
|
||||||
setPage(nextPage);
|
setPage(nextPage);
|
||||||
setHasMore(nextPage < d.totalPages);
|
setHasMore(nextPage < d.totalPages);
|
||||||
}).catch(() => {}).finally(() => setLoadingMore(false));
|
}).catch(() => {}).finally(() => setLoadingMore(false));
|
||||||
}, [sortBy, sortOrder, searchTerm, filterDept, filterProject, page, loadingMore, hasMore]);
|
}, [sortBy, sortOrder, searchTerm, filterDept, filterProject, filterEntity, page, loadingMore, hasMore]);
|
||||||
|
|
||||||
// 筛选/排序变化时重新加载
|
// 筛选/排序变化时重新加载
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -550,20 +551,6 @@ export default function MonitoringView() {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Year */}
|
|
||||||
<div className="space-y-1.5">
|
|
||||||
<label className="text-[10px] font-bold text-slate-400 uppercase tracking-wider">年份</label>
|
|
||||||
<select
|
|
||||||
className="w-full bg-slate-50 border-none rounded-xl py-2 px-3 text-xs focus:ring-2 focus:ring-blue-500/20 outline-none"
|
|
||||||
value={filterYear}
|
|
||||||
onChange={(e) => setFilterYear(e.target.value)}
|
|
||||||
>
|
|
||||||
<option value="All">无限制</option>
|
|
||||||
<option value="2026">2026年</option>
|
|
||||||
<option value="2025">2025年</option>
|
|
||||||
<option value="2024">2024年</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Date Range */}
|
{/* Date Range */}
|
||||||
@@ -614,7 +601,7 @@ export default function MonitoringView() {
|
|||||||
onChange={(e) => setFilterProject(e.target.value)}
|
onChange={(e) => setFilterProject(e.target.value)}
|
||||||
>
|
>
|
||||||
<option value="All">无限制</option>
|
<option value="All">无限制</option>
|
||||||
{projects.map(p => <option key={p} value={p}>{p}</option>)}
|
{filterOptions.projects.map(p => <option key={p} value={p}>{p}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -641,8 +628,7 @@ export default function MonitoringView() {
|
|||||||
onChange={(e) => setFilterEntity(e.target.value)}
|
onChange={(e) => setFilterEntity(e.target.value)}
|
||||||
>
|
>
|
||||||
<option value="All">无限制</option>
|
<option value="All">无限制</option>
|
||||||
<option value="羚牛氢能">羚牛氢能</option>
|
{filterOptions.entities.map(e => <option key={e} value={e}>{e}</option>)}
|
||||||
<option value="其他主体">其他主体</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -663,21 +649,23 @@ export default function MonitoringView() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mileage Range */}
|
{/* Mileage Range */}
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] font-bold text-slate-400 uppercase tracking-wider">当日里程区间 (KM)</label>
|
<label className="text-[10px] font-bold text-slate-400 uppercase tracking-wider">里程 ≥ (KM)</label>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="最小值"
|
placeholder="不限"
|
||||||
className="flex-1 bg-slate-50 border-none rounded-xl py-2 px-3 text-xs focus:ring-2 focus:ring-blue-500/20 outline-none"
|
className="w-full bg-slate-50 border-none rounded-xl py-2 px-3 text-xs focus:ring-2 focus:ring-blue-500/20 outline-none"
|
||||||
value={filterMileageRange.min}
|
value={filterMileageRange.min}
|
||||||
onChange={(e) => setFilterMileageRange(prev => ({ ...prev, min: e.target.value }))}
|
onChange={(e) => setFilterMileageRange(prev => ({ ...prev, min: e.target.value }))}
|
||||||
/>
|
/>
|
||||||
<span className="text-slate-300">{'\u2264'} 值 {'\u2264'}</span>
|
</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="text-[10px] font-bold text-slate-400 uppercase tracking-wider">里程 ≤ (KM)</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="最大值"
|
placeholder="不限"
|
||||||
className="flex-1 bg-slate-50 border-none rounded-xl py-2 px-3 text-xs focus:ring-2 focus:ring-blue-500/20 outline-none"
|
className="w-full bg-slate-50 border-none rounded-xl py-2 px-3 text-xs focus:ring-2 focus:ring-blue-500/20 outline-none"
|
||||||
value={filterMileageRange.max}
|
value={filterMileageRange.max}
|
||||||
onChange={(e) => setFilterMileageRange(prev => ({ ...prev, max: e.target.value }))}
|
onChange={(e) => setFilterMileageRange(prev => ({ ...prev, max: e.target.value }))}
|
||||||
/>
|
/>
|
||||||
@@ -690,7 +678,6 @@ export default function MonitoringView() {
|
|||||||
setSearchTerm('');
|
setSearchTerm('');
|
||||||
setFilterDept('All');
|
setFilterDept('All');
|
||||||
setFilterPlate('All');
|
setFilterPlate('All');
|
||||||
setFilterYear('All');
|
|
||||||
setFilterDateRange({ start: '', end: '' });
|
setFilterDateRange({ start: '', end: '' });
|
||||||
setFilterDate('2026-04-01');
|
setFilterDate('2026-04-01');
|
||||||
setFilterProject('All');
|
setFilterProject('All');
|
||||||
@@ -772,7 +759,7 @@ export default function MonitoringView() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span className="text-[8px] text-slate-300 font-bold">{v.department ? v.department.replace('业务', '') : v.rentStatus || ''}</span>
|
<span className="text-[8px] text-slate-300 font-bold">{v.department ? v.department.replace('业务', '') : v.rentStatus || ''}</span>
|
||||||
<span className="text-[9px] font-bold text-slate-600 truncate">{v.customer || '未分配'}</span>
|
<span className="text-[9px] font-bold text-slate-600 truncate">{v.customer || '-'}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export default function StatisticsView() {
|
|||||||
{chartType === 'bar' ? (
|
{chartType === 'bar' ? (
|
||||||
<BarChart data={trendData} margin={{ top: 20, right: 10, left: 0, bottom: 0 }}>
|
<BarChart data={trendData} margin={{ top: 20, right: 10, left: 0, bottom: 0 }}>
|
||||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f1f5f9" strokeOpacity={0.1} />
|
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f1f5f9" strokeOpacity={0.1} />
|
||||||
<XAxis dataKey="date" axisLine={false} tickLine={false} tick={{ fontSize: 10, fill: '#94a3b8' }} dy={10} tickFormatter={(val) => val.split('-')[1]} />
|
<XAxis dataKey="date" axisLine={false} tickLine={false} tick={{ fontSize: 10, fill: '#94a3b8' }} dy={10} tickFormatter={(val: string) => { const [m, d] = val.split('-'); return `${parseInt(m)}.${parseInt(d)}`; }} />
|
||||||
<YAxis axisLine={false} tickLine={false} tick={{ fontSize: 10, fill: '#94a3b8' }} />
|
<YAxis axisLine={false} tickLine={false} tick={{ fontSize: 10, fill: '#94a3b8' }} />
|
||||||
<Tooltip cursor={{ fill: '#f8fafc', fillOpacity: 0.1 }} contentStyle={{ borderRadius: '12px', border: 'none', backgroundColor: '#1e293b', color: '#fff', boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)', fontSize: '10px' }} />
|
<Tooltip cursor={{ fill: '#f8fafc', fillOpacity: 0.1 }} contentStyle={{ borderRadius: '12px', border: 'none', backgroundColor: '#1e293b', color: '#fff', boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)', fontSize: '10px' }} />
|
||||||
<Bar dataKey="mileage" fill="#3b82f6" radius={[4, 4, 0, 0]} barSize={20}>
|
<Bar dataKey="mileage" fill="#3b82f6" radius={[4, 4, 0, 0]} barSize={20}>
|
||||||
@@ -154,7 +154,7 @@ export default function StatisticsView() {
|
|||||||
) : chartType === 'line' ? (
|
) : chartType === 'line' ? (
|
||||||
<LineChart data={trendData} margin={{ top: 20, right: 10, left: 0, bottom: 0 }}>
|
<LineChart data={trendData} margin={{ top: 20, right: 10, left: 0, bottom: 0 }}>
|
||||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f1f5f9" strokeOpacity={0.1} />
|
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f1f5f9" strokeOpacity={0.1} />
|
||||||
<XAxis dataKey="date" axisLine={false} tickLine={false} tick={{ fontSize: 10, fill: '#94a3b8' }} dy={10} tickFormatter={(val) => val.split('-')[1]} />
|
<XAxis dataKey="date" axisLine={false} tickLine={false} tick={{ fontSize: 10, fill: '#94a3b8' }} dy={10} tickFormatter={(val: string) => { const [m, d] = val.split('-'); return `${parseInt(m)}.${parseInt(d)}`; }} />
|
||||||
<YAxis axisLine={false} tickLine={false} tick={{ fontSize: 10, fill: '#94a3b8' }} />
|
<YAxis axisLine={false} tickLine={false} tick={{ fontSize: 10, fill: '#94a3b8' }} />
|
||||||
<Tooltip contentStyle={{ borderRadius: '12px', border: 'none', backgroundColor: '#1e293b', color: '#fff', boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)', fontSize: '10px' }} />
|
<Tooltip contentStyle={{ borderRadius: '12px', border: 'none', backgroundColor: '#1e293b', color: '#fff', boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)', fontSize: '10px' }} />
|
||||||
<Line type="monotone" dataKey="mileage" stroke="#3b82f6" strokeWidth={3} dot={{ r: 4, fill: '#3b82f6' }}>
|
<Line type="monotone" dataKey="mileage" stroke="#3b82f6" strokeWidth={3} dot={{ r: 4, fill: '#3b82f6' }}>
|
||||||
@@ -170,7 +170,7 @@ export default function StatisticsView() {
|
|||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f1f5f9" strokeOpacity={0.1} />
|
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f1f5f9" strokeOpacity={0.1} />
|
||||||
<XAxis dataKey="date" axisLine={false} tickLine={false} tick={{ fontSize: 10, fill: '#94a3b8' }} dy={10} tickFormatter={(val) => val.split('-')[1]} />
|
<XAxis dataKey="date" axisLine={false} tickLine={false} tick={{ fontSize: 10, fill: '#94a3b8' }} dy={10} tickFormatter={(val: string) => { const [m, d] = val.split('-'); return `${parseInt(m)}.${parseInt(d)}`; }} />
|
||||||
<YAxis axisLine={false} tickLine={false} tick={{ fontSize: 10, fill: '#94a3b8' }} />
|
<YAxis axisLine={false} tickLine={false} tick={{ fontSize: 10, fill: '#94a3b8' }} />
|
||||||
<Tooltip contentStyle={{ borderRadius: '12px', border: 'none', backgroundColor: '#1e293b', color: '#fff', boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)', fontSize: '10px' }} />
|
<Tooltip contentStyle={{ borderRadius: '12px', border: 'none', backgroundColor: '#1e293b', color: '#fff', boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)', fontSize: '10px' }} />
|
||||||
<Area type="monotone" dataKey="mileage" stroke="#3b82f6" fillOpacity={1} fill="url(#colorMileage)">
|
<Area type="monotone" dataKey="mileage" stroke="#3b82f6" fillOpacity={1} fill="url(#colorMileage)">
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ export async function fetchMonitoring(params?: {
|
|||||||
search?: string;
|
search?: string;
|
||||||
dept?: string;
|
dept?: string;
|
||||||
customer?: string;
|
customer?: string;
|
||||||
|
project?: string;
|
||||||
|
entity?: string;
|
||||||
}): Promise<MonitoringData> {
|
}): Promise<MonitoringData> {
|
||||||
const query = new URLSearchParams();
|
const query = new URLSearchParams();
|
||||||
if (params?.sortBy) query.set('sortBy', params.sortBy);
|
if (params?.sortBy) query.set('sortBy', params.sortBy);
|
||||||
@@ -25,6 +27,8 @@ export async function fetchMonitoring(params?: {
|
|||||||
if (params?.search) query.set('search', params.search);
|
if (params?.search) query.set('search', params.search);
|
||||||
if (params?.dept) query.set('dept', params.dept);
|
if (params?.dept) query.set('dept', params.dept);
|
||||||
if (params?.customer) query.set('customer', params.customer);
|
if (params?.customer) query.set('customer', params.customer);
|
||||||
|
if (params?.project) query.set('project', params.project);
|
||||||
|
if (params?.entity) query.set('entity', params.entity);
|
||||||
const qs = query.toString();
|
const qs = query.toString();
|
||||||
return fetchJson<MonitoringData>(`${BASE}/monitoring${qs ? `?${qs}` : ''}`);
|
return fetchJson<MonitoringData>(`${BASE}/monitoring${qs ? `?${qs}` : ''}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ export interface MonitoringVehicle {
|
|||||||
department: string | null;
|
department: string | null;
|
||||||
manager: string | null;
|
manager: string | null;
|
||||||
rentStatus: string | null;
|
rentStatus: string | null;
|
||||||
|
entity: string | null;
|
||||||
|
project: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MonitoringStats {
|
export interface MonitoringStats {
|
||||||
@@ -23,6 +25,8 @@ export interface MonitoringFilters {
|
|||||||
departments: string[];
|
departments: string[];
|
||||||
customers: string[];
|
customers: string[];
|
||||||
plates: string[];
|
plates: string[];
|
||||||
|
projects: string[];
|
||||||
|
entities: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MonitoringData {
|
export interface MonitoringData {
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ const VEHICLE_INFO_SQL = `SELECT
|
|||||||
cus.customer_name AS customer,
|
cus.customer_name AS customer,
|
||||||
dep.dep_name AS department,
|
dep.dep_name AS department,
|
||||||
u.user_name AS manager,
|
u.user_name AS manager,
|
||||||
dic_status.dic_name AS rent_status
|
dic_status.dic_name AS rent_status,
|
||||||
|
org_truck.org_name AS entity,
|
||||||
|
c.project_name AS project
|
||||||
FROM tab_truck truck
|
FROM tab_truck truck
|
||||||
LEFT JOIN tab_truck_status_info si ON si.truck_id = truck.id AND si.is_deleted = 0
|
LEFT JOIN tab_truck_status_info si ON si.truck_id = truck.id AND si.is_deleted = 0
|
||||||
LEFT JOIN tab_contract c ON c.id = si.contract_id AND c.is_deleted = 0
|
LEFT JOIN tab_contract c ON c.id = si.contract_id AND c.is_deleted = 0
|
||||||
@@ -19,6 +21,7 @@ LEFT JOIN tab_user u ON u.id = c.bd AND u.is_deleted = 0
|
|||||||
LEFT JOIN tab_department dep ON dep.id = u.dep_id AND dep.is_deleted = 0
|
LEFT JOIN tab_department dep ON dep.id = u.dep_id AND dep.is_deleted = 0
|
||||||
LEFT JOIN tab_dic dic_status ON dic_status.parent_code = 'dic_truck_rent_status'
|
LEFT JOIN tab_dic dic_status ON dic_status.parent_code = 'dic_truck_rent_status'
|
||||||
AND dic_status.dic_code = truck.truck_rent_status AND dic_status.is_deleted = 0
|
AND dic_status.dic_code = truck.truck_rent_status AND dic_status.is_deleted = 0
|
||||||
|
LEFT JOIN tab_org org_truck ON org_truck.id = truck.org_id AND org_truck.is_deleted = 0
|
||||||
WHERE truck.is_deleted = 0 AND truck.is_operation = 1`;
|
WHERE truck.is_deleted = 0 AND truck.is_operation = 1`;
|
||||||
|
|
||||||
// ========== 实时监控缓存(每2分钟刷新) ==========
|
// ========== 实时监控缓存(每2分钟刷新) ==========
|
||||||
@@ -34,12 +37,14 @@ interface CachedVehicle {
|
|||||||
department: string | null;
|
department: string | null;
|
||||||
manager: string | null;
|
manager: string | null;
|
||||||
rentStatus: string | null;
|
rentStatus: string | null;
|
||||||
|
entity: string | null;
|
||||||
|
project: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MonitoringCache {
|
interface MonitoringCache {
|
||||||
vehicles: CachedVehicle[];
|
vehicles: CachedVehicle[];
|
||||||
stats: { totalToday: number; totalAll: number; onlineCount: number; vehicleCount: number };
|
stats: { totalToday: number; totalAll: number; onlineCount: number; vehicleCount: number };
|
||||||
filters: { departments: string[]; customers: string[]; plates: string[] };
|
filters: { departments: string[]; customers: string[]; plates: string[]; projects: string[]; entities: string[] };
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +105,8 @@ async function refreshMonitoringCache() {
|
|||||||
department: info?.department || null,
|
department: info?.department || null,
|
||||||
manager: info?.manager || null,
|
manager: info?.manager || null,
|
||||||
rentStatus: info?.rent_status || null,
|
rentStatus: info?.rent_status || null,
|
||||||
|
entity: info?.entity || null,
|
||||||
|
project: info?.project || null,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -112,11 +119,13 @@ async function refreshMonitoringCache() {
|
|||||||
const departments = Array.from(new Set(vehicles.map(v => v.department).filter(Boolean))) as string[];
|
const departments = Array.from(new Set(vehicles.map(v => v.department).filter(Boolean))) as string[];
|
||||||
const customers = Array.from(new Set(vehicles.map(v => v.customer).filter(Boolean))) as string[];
|
const customers = Array.from(new Set(vehicles.map(v => v.customer).filter(Boolean))) as string[];
|
||||||
const plates = vehicles.map(v => v.plate);
|
const plates = vehicles.map(v => v.plate);
|
||||||
|
const projects = Array.from(new Set(vehicles.map(v => v.project).filter(Boolean))) as string[];
|
||||||
|
const entities = Array.from(new Set(vehicles.map(v => v.entity).filter(Boolean))) as string[];
|
||||||
|
|
||||||
monitoringCache = {
|
monitoringCache = {
|
||||||
vehicles,
|
vehicles,
|
||||||
stats: { totalToday, totalAll, onlineCount, vehicleCount: vehicles.length },
|
stats: { totalToday, totalAll, onlineCount, vehicleCount: vehicles.length },
|
||||||
filters: { departments, customers, plates },
|
filters: { departments, customers, plates, projects, entities },
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -133,7 +142,7 @@ setInterval(refreshMonitoringCache, 2 * 60 * 1000);
|
|||||||
// GET /monitoring — 从缓存取数据,支持筛选/排序/分页
|
// GET /monitoring — 从缓存取数据,支持筛选/排序/分页
|
||||||
app.get('/monitoring', (c) => {
|
app.get('/monitoring', (c) => {
|
||||||
if (!monitoringCache) {
|
if (!monitoringCache) {
|
||||||
return c.json({ vehicles: [], stats: { totalToday: 0, totalAll: 0, onlineCount: 0, vehicleCount: 0 }, filters: { departments: [], customers: [], plates: [] }, total: 0, updatedAt: new Date().toISOString() });
|
return c.json({ vehicles: [], stats: { totalToday: 0, totalAll: 0, onlineCount: 0, vehicleCount: 0 }, filters: { departments: [], customers: [], plates: [], projects: [], entities: [] }, total: 0, page: 1, totalPages: 1, updatedAt: new Date().toISOString() });
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortBy = c.req.query('sortBy') || 'today';
|
const sortBy = c.req.query('sortBy') || 'today';
|
||||||
@@ -143,6 +152,8 @@ app.get('/monitoring', (c) => {
|
|||||||
const search = c.req.query('search') || '';
|
const search = c.req.query('search') || '';
|
||||||
const dept = c.req.query('dept') || '';
|
const dept = c.req.query('dept') || '';
|
||||||
const customer = c.req.query('customer') || '';
|
const customer = c.req.query('customer') || '';
|
||||||
|
const project = c.req.query('project') || '';
|
||||||
|
const entity = c.req.query('entity') || '';
|
||||||
|
|
||||||
let vehicles = monitoringCache.vehicles;
|
let vehicles = monitoringCache.vehicles;
|
||||||
|
|
||||||
@@ -151,11 +162,14 @@ app.get('/monitoring', (c) => {
|
|||||||
const q = search.toLowerCase();
|
const q = search.toLowerCase();
|
||||||
vehicles = vehicles.filter(v =>
|
vehicles = vehicles.filter(v =>
|
||||||
v.plate.toLowerCase().includes(q) ||
|
v.plate.toLowerCase().includes(q) ||
|
||||||
(v.customer || '').toLowerCase().includes(q)
|
(v.customer || '').toLowerCase().includes(q) ||
|
||||||
|
(v.project || '').toLowerCase().includes(q)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (dept) vehicles = vehicles.filter(v => v.department === dept);
|
if (dept) vehicles = vehicles.filter(v => v.department === dept);
|
||||||
if (customer) vehicles = vehicles.filter(v => v.customer === customer);
|
if (customer) vehicles = vehicles.filter(v => v.customer === customer);
|
||||||
|
if (project) vehicles = vehicles.filter(v => v.project === project);
|
||||||
|
if (entity) vehicles = vehicles.filter(v => v.entity === entity);
|
||||||
|
|
||||||
const total = vehicles.length;
|
const total = vehicles.length;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user