feat: 车牌区域筛选、型号批次筛选、回到顶部修复、删除涨跌幅
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

- 新增车牌区域筛选(粤/沪/浙+数量),替代旧地区代码
- 新增型号批次筛选(从考核目标名称筛选车辆)
- 客户/部门增加"无值"选项筛选空值
- 修复回到顶部按钮在iOS上失效
- 删除KPI卡片涨跌幅百分比显示
- 全屏刷新按钮实际触发数据重新加载+加载动画
- 统计报表全屏刷新按钮修复

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-04-02 11:21:38 +08:00
parent adc9c3a9db
commit 8822ddf8ae
4 changed files with 76 additions and 23 deletions

View File

@@ -106,7 +106,8 @@ export default function MonitoringView() {
const [filterProject, setFilterProject] = useState('All');
const [filterEntity, setFilterEntity] = useState('All');
const [filterRentStatus, setFilterRentStatus] = useState('All');
const [filterRegionCode, setFilterRegionCode] = useState('All');
const [filterPlatePrefix, setFilterPlatePrefix] = useState('All');
const [filterTargetName, setFilterTargetName] = useState('All');
const [filterMileageRange, setFilterMileageRange] = useState({ min: '', max: '' });
const [appliedMileageRange, setAppliedMileageRange] = useState({ min: '', max: '' });
const [filterDate, setFilterDate] = useState(() => {
@@ -117,7 +118,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: [], rentStatuses: [] });
const [filterOptions, setFilterOptions] = useState<MonitoringFilters>({ departments: [], customers: [], plates: [], projects: [], entities: [], rentStatuses: [], platePrefixes: [], targetNames: [] });
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
@@ -141,6 +142,8 @@ export default function MonitoringView() {
project: filterProject !== 'All' ? filterProject : undefined,
entity: filterEntity !== 'All' ? filterEntity : undefined,
rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined,
platePrefix: filterPlatePrefix !== 'All' ? filterPlatePrefix : undefined,
targetName: filterTargetName !== 'All' ? filterTargetName : undefined,
plate: filterPlate !== 'All' ? filterPlate : undefined,
mileageMin: appliedMileageRange.min || undefined,
mileageMax: appliedMileageRange.max || undefined,
@@ -153,7 +156,7 @@ export default function MonitoringView() {
setPage(1);
setHasMore(d.page < d.totalPages);
}).catch(() => {});
}, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterRentStatus, filterPlate, appliedMileageRange, filterDate]);
}, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterRentStatus, filterPlatePrefix, filterTargetName, filterPlate, appliedMileageRange, filterDate]);
// 加载更多
const loadMore = useCallback(() => {
@@ -171,6 +174,8 @@ export default function MonitoringView() {
project: filterProject !== 'All' ? filterProject : undefined,
entity: filterEntity !== 'All' ? filterEntity : undefined,
rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined,
platePrefix: filterPlatePrefix !== 'All' ? filterPlatePrefix : undefined,
targetName: filterTargetName !== 'All' ? filterTargetName : undefined,
plate: filterPlate !== 'All' ? filterPlate : undefined,
mileageMin: appliedMileageRange.min || undefined,
mileageMax: appliedMileageRange.max || undefined,
@@ -226,8 +231,9 @@ export default function MonitoringView() {
}, []);
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
window.scrollTo(0, 0);
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
};
const filteredVehicles = vehicles;
@@ -249,6 +255,8 @@ export default function MonitoringView() {
dept: filterDept !== 'All' ? filterDept : undefined,
customer: filterCustomer !== 'All' ? filterCustomer : undefined,
rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined,
platePrefix: filterPlatePrefix !== 'All' ? filterPlatePrefix : undefined,
targetName: filterTargetName !== 'All' ? filterTargetName : undefined,
plate: filterPlate !== 'All' ? filterPlate : undefined,
date: filterDate || undefined,
}).then(d => {
@@ -626,18 +634,29 @@ export default function MonitoringView() {
</div>
</div>
{/* Region Code */}
{/* Target Name */}
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-slate-400 uppercase tracking-wider"></label>
<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={filterRegionCode}
onChange={(e) => setFilterRegionCode(e.target.value)}
value={filterTargetName}
onChange={(e) => setFilterTargetName(e.target.value)}
>
<option value="All"></option>
<option value="330400">330400 ()</option>
<option value="440100">440100 (广)</option>
<option value="110100">110100 ()</option>
{filterOptions.targetNames.map(n => <option key={n} value={n}>{n}</option>)}
</select>
</div>
{/* Plate Prefix */}
<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={filterPlatePrefix}
onChange={(e) => setFilterPlatePrefix(e.target.value)}
>
<option value="All"></option>
{filterOptions.platePrefixes.map(p => <option key={p.prefix} value={p.prefix}>{p.prefix}{p.count}</option>)}
</select>
</div>
@@ -674,7 +693,7 @@ export default function MonitoringView() {
setFilterCustomer('All');
setFilterProject('All');
setFilterEntity('All');
setFilterRegionCode('All');
setFilterPlatePrefix('All');
setFilterMileageRange({ min: '', max: '' });
setAppliedMileageRange({ min: '', max: '' });
}}
@@ -709,12 +728,13 @@ export default function MonitoringView() {
if (searchTerm) tags.push({ label: `搜索: ${searchTerm}`, onClear: () => setSearchTerm('') });
if (appliedMileageRange.min) tags.push({ label: `里程≥${appliedMileageRange.min}`, onClear: () => { setFilterMileageRange(prev => ({ ...prev, min: '' })); setAppliedMileageRange(prev => ({ ...prev, min: '' })); } });
if (appliedMileageRange.max) tags.push({ label: `里程≤${appliedMileageRange.max}`, onClear: () => { setFilterMileageRange(prev => ({ ...prev, max: '' })); setAppliedMileageRange(prev => ({ ...prev, max: '' })); } });
if (filterRegionCode !== 'All') tags.push({ label: `地区: ${filterRegionCode}`, onClear: () => setFilterRegionCode('All') });
if (filterTargetName !== 'All') tags.push({ label: `批次: ${filterTargetName}`, onClear: () => setFilterTargetName('All') });
if (filterPlatePrefix !== 'All') tags.push({ label: `区域: ${filterPlatePrefix}`, onClear: () => setFilterPlatePrefix('All') });
if (filterDate) tags.push({ label: `日期: ${filterDate}`, onClear: () => setFilterDate('') });
if (tags.length === 0) return null;
const clearAll = () => {
setFilterDept('All'); setFilterCustomer('All'); setFilterRentStatus('All'); setFilterProject('All'); setFilterEntity('All');
setFilterPlate('All'); setSearchTerm(''); setFilterRegionCode('All');
setFilterPlate('All'); setSearchTerm(''); setFilterPlatePrefix('All'); setFilterTargetName('All');
setFilterMileageRange({ min: '', max: '' }); setAppliedMileageRange({ min: '', max: '' });
setFilterDate('');
};
@@ -741,11 +761,6 @@ export default function MonitoringView() {
<div className="text-lg font-black tracking-tighter leading-tight flex items-baseline gap-1">
{Math.round(sortBy === 'today' ? stats.totalToday : stats.totalAll).toLocaleString()}
<span className="text-[8px] text-slate-400">km</span>
{sortBy === 'today' && stats.yesterdayTotal > 0 && (() => {
const change = ((stats.totalToday - stats.yesterdayTotal) / stats.yesterdayTotal) * 100;
const isUp = change >= 0;
return <span className={`text-[9px] font-bold ${isUp ? 'text-blue-400' : 'text-rose-400'}`}>{isUp ? '\u2191' : '\u2193'}{Math.abs(change).toFixed(1)}%</span>;
})()}
</div>
</div>
<div className="bg-white p-2.5 rounded-xl border border-gray-100 shadow-sm">

View File

@@ -19,6 +19,8 @@ export async function fetchMonitoring(params?: {
project?: string;
entity?: string;
rentStatus?: string;
platePrefix?: string;
targetName?: string;
plate?: string;
mileageMin?: string;
mileageMax?: string;
@@ -35,6 +37,8 @@ export async function fetchMonitoring(params?: {
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?.platePrefix) query.set('platePrefix', params.platePrefix);
if (params?.targetName) query.set('targetName', params.targetName);
if (params?.plate) query.set('plate', params.plate);
if (params?.mileageMin) query.set('mileageMin', params.mileageMin);
if (params?.mileageMax) query.set('mileageMax', params.mileageMax);

View File

@@ -28,6 +28,8 @@ export interface MonitoringFilters {
projects: string[];
entities: string[];
rentStatuses: string[];
platePrefixes: { prefix: string; count: number }[];
targetNames: string[];
}
export interface MonitoringData {