feat: 租赁状态与部门分列筛选,未同步车辆显示-,卡片增加今/总标签,全屏监控压缩优化
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -101,6 +101,7 @@ export default function MonitoringView() {
|
||||
const [filterCustomer, setFilterCustomer] = useState('All');
|
||||
const [filterProject, setFilterProject] = useState('All');
|
||||
const [filterEntity, setFilterEntity] = useState('All');
|
||||
const [filterRentStatus, setFilterRentStatus] = useState('All');
|
||||
const [filterRegionCode, setFilterRegionCode] = useState('All');
|
||||
const [filterMileageRange, setFilterMileageRange] = useState({ min: '', max: '' });
|
||||
const [appliedMileageRange, setAppliedMileageRange] = useState({ min: '', max: '' });
|
||||
@@ -112,7 +113,7 @@ export default function MonitoringView() {
|
||||
|
||||
const [vehicles, setVehicles] = useState<MonitoringVehicle[]>([]);
|
||||
const [stats, setStats] = useState<MonitoringStats>({ totalToday: 0, totalAll: 0, vehicleCount: 0, yesterdayTotal: 0 });
|
||||
const [filterOptions, setFilterOptions] = useState<MonitoringFilters>({ departments: [], customers: [], plates: [], projects: [], entities: [] });
|
||||
const [filterOptions, setFilterOptions] = useState<MonitoringFilters>({ departments: [], customers: [], plates: [], projects: [], entities: [], rentStatuses: [] });
|
||||
const [total, setTotal] = useState(0);
|
||||
const [page, setPage] = useState(1);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
@@ -135,6 +136,7 @@ export default function MonitoringView() {
|
||||
customer: filterCustomer !== 'All' ? filterCustomer : undefined,
|
||||
project: filterProject !== 'All' ? filterProject : undefined,
|
||||
entity: filterEntity !== 'All' ? filterEntity : undefined,
|
||||
rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined,
|
||||
plate: filterPlate !== 'All' ? filterPlate : undefined,
|
||||
mileageMin: appliedMileageRange.min || undefined,
|
||||
mileageMax: appliedMileageRange.max || undefined,
|
||||
@@ -147,7 +149,7 @@ export default function MonitoringView() {
|
||||
setPage(1);
|
||||
setHasMore(d.page < d.totalPages);
|
||||
}).catch(() => {});
|
||||
}, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterPlate, appliedMileageRange, filterDate]);
|
||||
}, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterRentStatus, filterPlate, appliedMileageRange, filterDate]);
|
||||
|
||||
// 加载更多
|
||||
const loadMore = useCallback(() => {
|
||||
@@ -164,6 +166,7 @@ export default function MonitoringView() {
|
||||
customer: filterCustomer !== 'All' ? filterCustomer : undefined,
|
||||
project: filterProject !== 'All' ? filterProject : undefined,
|
||||
entity: filterEntity !== 'All' ? filterEntity : undefined,
|
||||
rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined,
|
||||
plate: filterPlate !== 'All' ? filterPlate : undefined,
|
||||
mileageMin: appliedMileageRange.min || undefined,
|
||||
mileageMax: appliedMileageRange.max || undefined,
|
||||
@@ -173,7 +176,7 @@ export default function MonitoringView() {
|
||||
setPage(nextPage);
|
||||
setHasMore(nextPage < d.totalPages);
|
||||
}).catch(() => {}).finally(() => setLoadingMore(false));
|
||||
}, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterPlate, appliedMileageRange, filterDate, page, loadingMore, hasMore]);
|
||||
}, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterRentStatus, filterPlate, appliedMileageRange, filterDate, page, loadingMore, hasMore]);
|
||||
|
||||
// 筛选/排序变化时重新加载
|
||||
useEffect(() => {
|
||||
@@ -247,76 +250,66 @@ export default function MonitoringView() {
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-[100] bg-slate-950 flex flex-col overflow-hidden"
|
||||
>
|
||||
{/* Top bar: title + KPI row + close */}
|
||||
<div className="flex-shrink-0 p-3 border-b border-slate-800 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-1 h-5 bg-blue-500 rounded-full"></div>
|
||||
<h2 className="text-white font-bold text-sm">全屏监控</h2>
|
||||
</div>
|
||||
{/* Top bar: compact inline KPI */}
|
||||
<div className="flex-shrink-0 px-3 py-2 border-b border-slate-800/60 flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => { setFilterDept('All'); setFilterCustomer('All'); setFilterPlate('All'); setSearchTerm(''); }}
|
||||
className="p-1.5 bg-slate-800 text-slate-400 rounded-full hover:text-blue-400 transition-colors"
|
||||
>
|
||||
<RotateCcw size={14} />
|
||||
</button>
|
||||
<button onClick={toggleFullscreen} className="p-1.5 bg-slate-800 text-slate-400 rounded-full hover:text-white transition-colors">
|
||||
<Minimize2 size={16} />
|
||||
</button>
|
||||
<div className="w-0.5 h-4 bg-blue-500 rounded-full"></div>
|
||||
<h2 className="text-white font-bold text-xs">全屏监控</h2>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-[10px]">
|
||||
<span className="text-slate-500">今日 <span className="text-white font-black">{Math.round(stats.totalToday).toLocaleString()}</span> <span className="text-blue-400">km</span></span>
|
||||
<span className="text-slate-700">|</span>
|
||||
<span className="text-slate-500">累计 <span className="text-white font-black">{Math.round(stats.totalAll).toLocaleString()}</span> <span className="text-blue-400">km</span></span>
|
||||
<span className="text-slate-700">|</span>
|
||||
<span className="text-slate-500">车辆 <span className="text-white font-black">{stats.vehicleCount}</span> 台</span>
|
||||
<span className="text-slate-700">|</span>
|
||||
<span className="text-slate-500">均 <span className="text-white font-black">{(stats.vehicleCount > 0 ? (sortBy === 'today' ? stats.totalToday : stats.totalAll) / stats.vehicleCount : 0).toFixed(0)}</span> <span className="text-blue-400">km</span></span>
|
||||
</div>
|
||||
</div>
|
||||
{/* KPI — single row */}
|
||||
<div className="flex gap-2">
|
||||
<div className={`flex-1 bg-slate-900/50 border px-3 py-2 rounded-xl ${sortBy === 'today' ? 'border-blue-500/50' : 'border-slate-800'}`}>
|
||||
<div className="text-[8px] font-bold text-slate-500 uppercase">今日总里程</div>
|
||||
<div className="text-base font-black text-white">{Math.round(stats.totalToday).toLocaleString()} <span className="text-blue-400 text-[9px]">km</span></div>
|
||||
</div>
|
||||
<div className={`flex-1 bg-slate-900/50 border px-3 py-2 rounded-xl ${sortBy === 'total' ? 'border-blue-500/50' : 'border-slate-800'}`}>
|
||||
<div className="text-[8px] font-bold text-slate-500 uppercase">累计总里程</div>
|
||||
<div className="text-base font-black text-white">{Math.round(stats.totalAll).toLocaleString()} <span className="text-blue-400 text-[9px]">km</span></div>
|
||||
</div>
|
||||
<div className="flex-1 bg-slate-900/50 border border-slate-800 px-3 py-2 rounded-xl">
|
||||
<div className="text-[8px] font-bold text-slate-500 uppercase">监控台数</div>
|
||||
<div className="text-base font-black text-white">{stats.vehicleCount} <span className="text-blue-400 text-[9px]">台</span></div>
|
||||
</div>
|
||||
<div className="flex-1 bg-slate-900/50 border border-slate-800 px-3 py-2 rounded-xl">
|
||||
<div className="text-[8px] font-bold text-slate-500 uppercase">平均单车</div>
|
||||
<div className="text-base font-black text-white">{(stats.vehicleCount > 0 ? (sortBy === 'today' ? stats.totalToday : stats.totalAll) / stats.vehicleCount : 0).toFixed(0)} <span className="text-blue-400 text-[9px]">km</span></div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => { setFilterDept('All'); setFilterCustomer('All'); setFilterRentStatus('All'); setFilterPlate('All'); setSearchTerm(''); }}
|
||||
className="p-1.5 text-slate-500 hover:text-blue-400 transition-colors"
|
||||
>
|
||||
<RotateCcw size={13} />
|
||||
</button>
|
||||
<button onClick={toggleFullscreen} className="p-1.5 text-slate-500 hover:text-white transition-colors">
|
||||
<Minimize2 size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table Area */}
|
||||
<div className="flex-1 overflow-hidden flex flex-col">
|
||||
<div className="px-3 py-2 border-b border-slate-800 flex justify-between items-center flex-shrink-0">
|
||||
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">车辆实时明细数据</span>
|
||||
<div className="px-3 py-1.5 border-b border-slate-800/60 flex justify-end items-center flex-shrink-0">
|
||||
<div className="flex items-center gap-3 text-[9px] text-slate-500">
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-2 h-2 rounded-full bg-green-500"></div>
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-green-500"></div>
|
||||
<span>在线</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-2 h-2 rounded-full bg-slate-500"></div>
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-slate-500"></div>
|
||||
<span>离线</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-2 h-2 rounded-full bg-amber-400"></div>
|
||||
<span>未对接车机</span>
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-amber-400"></div>
|
||||
<span>未对接</span>
|
||||
</div>
|
||||
<span>最后更新: {new Date().toLocaleTimeString()}</span>
|
||||
<span className="text-slate-600">{new Date().toLocaleTimeString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-auto">
|
||||
<table className="w-full text-left border-collapse">
|
||||
<thead className="sticky top-0 bg-slate-900 z-10">
|
||||
<tr className="border-b border-slate-800">
|
||||
<th className="p-4 text-[10px] font-bold text-slate-500 uppercase">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<tr className="border-b border-slate-800/60">
|
||||
<th className="px-3 py-2 text-[10px] font-bold text-slate-500 uppercase w-12 text-center">状态</th>
|
||||
<th className="px-3 py-2 text-[10px] font-bold text-slate-500 uppercase">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span>车牌号</span>
|
||||
<select
|
||||
className="bg-slate-800 border-none rounded-lg px-2 py-1 text-[9px] text-slate-300 outline-none focus:ring-1 focus:ring-blue-500/30"
|
||||
className="bg-slate-800 border-none rounded px-2 py-0.5 text-[9px] text-slate-300 outline-none focus:ring-1 focus:ring-blue-500/30"
|
||||
value={filterPlate}
|
||||
onChange={(e) => setFilterPlate(e.target.value)}
|
||||
>
|
||||
@@ -325,12 +318,11 @@ export default function MonitoringView() {
|
||||
</select>
|
||||
</div>
|
||||
</th>
|
||||
<th className="p-4 text-[10px] font-bold text-slate-500 uppercase">在线状态</th>
|
||||
<th className="p-4 text-[10px] font-bold text-slate-500 uppercase">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<th className="px-3 py-2 text-[10px] font-bold text-slate-500 uppercase">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span>客户</span>
|
||||
<select
|
||||
className="bg-slate-800 border-none rounded-lg px-2 py-1 text-[9px] text-slate-300 outline-none focus:ring-1 focus:ring-blue-500/30"
|
||||
className="bg-slate-800 border-none rounded px-2 py-0.5 text-[9px] text-slate-300 outline-none focus:ring-1 focus:ring-blue-500/30"
|
||||
value={filterCustomer}
|
||||
onChange={(e) => setFilterCustomer(e.target.value)}
|
||||
>
|
||||
@@ -339,11 +331,24 @@ export default function MonitoringView() {
|
||||
</select>
|
||||
</div>
|
||||
</th>
|
||||
<th className="p-4 text-[10px] font-bold text-slate-500 uppercase">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<span>业务部门</span>
|
||||
<th className="px-3 py-2 text-[10px] font-bold text-slate-500 uppercase">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span>租赁状态</span>
|
||||
<select
|
||||
className="bg-slate-800 border-none rounded-lg px-2 py-1 text-[9px] text-slate-300 outline-none focus:ring-1 focus:ring-blue-500/30"
|
||||
className="bg-slate-800 border-none rounded px-2 py-0.5 text-[9px] text-slate-300 outline-none focus:ring-1 focus:ring-blue-500/30"
|
||||
value={filterRentStatus}
|
||||
onChange={(e) => setFilterRentStatus(e.target.value)}
|
||||
>
|
||||
<option value="All">全部状态</option>
|
||||
{filterOptions.rentStatuses.map(s => <option key={s} value={s}>{s}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</th>
|
||||
<th className="px-3 py-2 text-[10px] font-bold text-slate-500 uppercase">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span>部门</span>
|
||||
<select
|
||||
className="bg-slate-800 border-none rounded px-2 py-0.5 text-[9px] text-slate-300 outline-none focus:ring-1 focus:ring-blue-500/30"
|
||||
value={filterDept}
|
||||
onChange={(e) => setFilterDept(e.target.value)}
|
||||
>
|
||||
@@ -353,7 +358,7 @@ export default function MonitoringView() {
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
className="p-4 text-[10px] font-bold text-slate-500 uppercase text-right cursor-pointer hover:text-blue-400 transition-colors"
|
||||
className="px-3 py-2 text-[10px] font-bold text-slate-500 uppercase text-right cursor-pointer hover:text-blue-400 transition-colors"
|
||||
onClick={() => {
|
||||
if (sortBy === 'today') {
|
||||
setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc');
|
||||
@@ -371,7 +376,7 @@ export default function MonitoringView() {
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
className="p-4 text-[10px] font-bold text-slate-500 uppercase text-right cursor-pointer hover:text-blue-400 transition-colors"
|
||||
className="px-3 py-2 text-[10px] font-bold text-slate-500 uppercase text-right cursor-pointer hover:text-blue-400 transition-colors"
|
||||
onClick={() => {
|
||||
if (sortBy === 'total') {
|
||||
setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc');
|
||||
@@ -388,46 +393,26 @@ export default function MonitoringView() {
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
<th className="p-4 text-[10px] font-bold text-slate-500 uppercase">状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-800/50">
|
||||
<tbody className="divide-y divide-slate-800/30">
|
||||
{filteredVehicles.map((v) => (
|
||||
<tr key={v.plate} className="hover:bg-slate-800/30 transition-colors group">
|
||||
<td className="p-4 text-sm font-bold text-white">{v.plate}</td>
|
||||
<td className="p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-2 h-2 rounded-full ${v.isOnline ? 'bg-green-500 shadow-[0_0_8px_rgba(34,197,94,0.4)]' : 'bg-slate-600'}`}></div>
|
||||
<span className={`text-[10px] font-bold ${v.isOnline ? 'text-green-500' : 'text-slate-500'}`}>
|
||||
{v.isOnline ? '在线' : '离线'}
|
||||
</span>
|
||||
</div>
|
||||
<tr key={v.plate} className="hover:bg-slate-800/20 transition-colors">
|
||||
<td className="px-3 py-2 text-center">
|
||||
<div className={`w-2 h-2 rounded-full mx-auto ${v.isOnline ? 'bg-green-500 shadow-[0_0_6px_rgba(34,197,94,0.4)]' : v.isDataSynced ? 'bg-slate-600' : 'bg-amber-400 animate-pulse'}`}></div>
|
||||
</td>
|
||||
<td className="p-4 text-xs text-slate-400">{v.customer}</td>
|
||||
<td className="p-4 text-xs text-slate-400">{v.department || v.rentStatus || ''}</td>
|
||||
<td className="p-4 text-right">
|
||||
<div className="flex flex-col items-end">
|
||||
<div className="flex items-center gap-1.5">
|
||||
{!v.isDataSynced && (
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-amber-400 animate-pulse"></div>
|
||||
)}
|
||||
<span className={`text-sm font-mono font-bold ${v.isDataSynced ? 'text-blue-400' : 'text-amber-400'}`}>
|
||||
{v.dailyKm?.toLocaleString()} <span className="text-[8px] text-slate-500 font-bold">km</span>
|
||||
</span>
|
||||
</div>
|
||||
{!v.isDataSynced && <span className="text-[8px] text-amber-500/50 font-bold">未对接</span>}
|
||||
</div>
|
||||
<td className="px-3 py-2 text-xs font-bold text-white">{v.plate}</td>
|
||||
<td className="px-3 py-2 text-[11px] text-slate-400">{v.customer || '-'}</td>
|
||||
<td className="px-3 py-2 text-[11px] text-slate-400">{v.rentStatus || '-'}</td>
|
||||
<td className="px-3 py-2 text-[11px] text-slate-400">{v.department || '-'}</td>
|
||||
<td className="px-3 py-2 text-right">
|
||||
<span className={`text-xs font-mono font-bold ${v.isDataSynced ? 'text-blue-400' : 'text-amber-400'}`}>
|
||||
{v.isDataSynced ? <>{v.dailyKm?.toLocaleString()} <span className="text-[8px] text-slate-500">km</span></> : '-'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="p-4 text-right">
|
||||
<div className="flex flex-col items-end">
|
||||
<span className={`text-sm font-mono font-bold ${v.isDataSynced ? 'text-slate-300' : 'text-amber-400/70'}`}>
|
||||
{v.totalKm?.toLocaleString()} <span className="text-[8px] text-slate-500 font-bold">km</span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4">
|
||||
<span className={`px-2 py-0.5 rounded-full ${v.isOnline ? 'bg-green-500/10 text-green-500' : 'bg-slate-500/10 text-slate-500'} text-[9px] font-bold uppercase`}>
|
||||
{v.isOnline ? '运行中' : '静止/离线'}
|
||||
<td className="px-3 py-2 text-right">
|
||||
<span className={`text-xs font-mono font-bold ${v.isDataSynced ? 'text-slate-300' : 'text-slate-600'}`}>
|
||||
{v.isDataSynced && v.totalKm != null ? <>{v.totalKm.toLocaleString()} <span className="text-[8px] text-slate-500">km</span></> : '-'}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -514,7 +499,7 @@ export default function MonitoringView() {
|
||||
|
||||
<button
|
||||
onClick={() => setIsFilterOpen(!isFilterOpen)}
|
||||
className={`p-1.5 rounded-lg transition-all flex-shrink-0 ${isFilterOpen || searchTerm || filterDept !== 'All' || filterCustomer !== 'All' || filterPlate !== 'All' || filterProject !== 'All' ? 'bg-blue-50 text-blue-600 border border-blue-100' : 'bg-slate-50 text-slate-400 border border-transparent'}`}
|
||||
className={`p-1.5 rounded-lg transition-all flex-shrink-0 ${isFilterOpen || searchTerm || filterDept !== 'All' || filterCustomer !== 'All' || filterRentStatus !== 'All' || filterPlate !== 'All' || filterProject !== 'All' ? 'bg-blue-50 text-blue-600 border border-blue-100' : 'bg-slate-50 text-slate-400 border border-transparent'}`}
|
||||
>
|
||||
<Filter size={16} />
|
||||
</button>
|
||||
@@ -569,6 +554,21 @@ export default function MonitoringView() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Rent Status */}
|
||||
<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={filterRentStatus}
|
||||
onChange={(e) => setFilterRentStatus(e.target.value)}
|
||||
>
|
||||
<option value="All">无限制</option>
|
||||
{filterOptions.rentStatuses.map(s => <option key={s} value={s}>{s}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{/* Entity */}
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] font-bold text-slate-400 uppercase tracking-wider">主体查询</label>
|
||||
@@ -657,6 +657,7 @@ export default function MonitoringView() {
|
||||
{/* Active Filter Tags */}
|
||||
{(() => {
|
||||
const tags: { label: string; onClear: () => void }[] = [];
|
||||
if (filterRentStatus !== 'All') tags.push({ label: `状态: ${filterRentStatus}`, onClear: () => setFilterRentStatus('All') });
|
||||
if (filterDept !== 'All') tags.push({ label: `部门: ${filterDept}`, onClear: () => setFilterDept('All') });
|
||||
if (filterCustomer !== 'All') tags.push({ label: `客户: ${filterCustomer}`, onClear: () => setFilterCustomer('All') });
|
||||
if (filterProject !== 'All') tags.push({ label: `项目: ${filterProject}`, onClear: () => setFilterProject('All') });
|
||||
@@ -750,7 +751,7 @@ export default function MonitoringView() {
|
||||
</span>
|
||||
</div>
|
||||
<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.rentStatus || ''}{v.department ? ` · ${v.department.replace('业务', '')}` : ''}</span>
|
||||
<span className="text-[9px] font-bold text-slate-600 truncate">{v.customer || '-'}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -760,13 +761,15 @@ export default function MonitoringView() {
|
||||
{!v.isDataSynced && (
|
||||
<div className="w-2 h-2 rounded-full bg-amber-400 animate-pulse" title="未对接车机数据"></div>
|
||||
)}
|
||||
<span className="text-[7px] font-black text-blue-600/40 bg-blue-50 w-3 h-3 rounded flex items-center justify-center leading-none">今</span>
|
||||
<div className={`text-sm font-black leading-none ${v.isDataSynced ? 'text-blue-600' : 'text-amber-600'}`}>
|
||||
{v.dailyKm?.toLocaleString()} <span className="text-[8px] text-slate-400">km</span>
|
||||
{v.isDataSynced ? <>{v.dailyKm?.toLocaleString()} <span className="text-[8px] text-slate-400">km</span></> : '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-[7px] font-black text-slate-400/60 bg-slate-100 w-3 h-3 rounded flex items-center justify-center leading-none">总</span>
|
||||
<span className="text-[8px] font-bold text-slate-300">
|
||||
{v.totalKm?.toLocaleString()} km
|
||||
{v.isDataSynced && v.totalKm != null ? `${v.totalKm.toLocaleString()} km` : '-'}
|
||||
</span>
|
||||
{!v.isDataSynced && (
|
||||
<span className="text-[7px] font-bold text-amber-500/70 bg-amber-50 px-1 rounded">未同步</span>
|
||||
|
||||
@@ -18,6 +18,7 @@ export async function fetchMonitoring(params?: {
|
||||
customer?: string;
|
||||
project?: string;
|
||||
entity?: string;
|
||||
rentStatus?: string;
|
||||
plate?: string;
|
||||
mileageMin?: string;
|
||||
mileageMax?: string;
|
||||
@@ -33,6 +34,7 @@ export async function fetchMonitoring(params?: {
|
||||
if (params?.customer) query.set('customer', params.customer);
|
||||
if (params?.project) query.set('project', params.project);
|
||||
if (params?.entity) query.set('entity', params.entity);
|
||||
if (params?.rentStatus) query.set('rentStatus', params.rentStatus);
|
||||
if (params?.plate) query.set('plate', params.plate);
|
||||
if (params?.mileageMin) query.set('mileageMin', params.mileageMin);
|
||||
if (params?.mileageMax) query.set('mileageMax', params.mileageMax);
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface MonitoringFilters {
|
||||
plates: string[];
|
||||
projects: string[];
|
||||
entities: string[];
|
||||
rentStatuses: string[];
|
||||
}
|
||||
|
||||
export interface MonitoringData {
|
||||
|
||||
Reference in New Issue
Block a user