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:
kkfluous
2026-04-01 23:06:56 +08:00
parent 2f6269e071
commit 863ab17b58
5 changed files with 50 additions and 41 deletions

View File

@@ -98,7 +98,6 @@ export default function MonitoringView() {
// New filters from image
const [filterPlate, setFilterPlate] = useState('All');
const [filterYear, setFilterYear] = useState('All');
const [filterDateRange, setFilterDateRange] = useState({ start: '', end: '' });
const [filterDate, setFilterDate] = useState('2026-04-01');
const [filterProject, setFilterProject] = useState('All');
@@ -108,7 +107,7 @@ export default function MonitoringView() {
const [vehicles, setVehicles] = useState<MonitoringVehicle[]>([]);
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 [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
@@ -131,7 +130,8 @@ export default function MonitoringView() {
page: 1,
search: searchTerm || undefined,
dept: filterDept !== 'All' ? filterDept : undefined,
customer: filterProject !== 'All' ? filterProject : undefined,
project: filterProject !== 'All' ? filterProject : undefined,
entity: filterEntity !== 'All' ? filterEntity : undefined,
}).then(d => {
setVehicles(d.vehicles);
setStats(d.stats);
@@ -140,7 +140,7 @@ export default function MonitoringView() {
setPage(1);
setHasMore(d.page < d.totalPages);
}).catch(() => {});
}, [sortBy, sortOrder, searchTerm, filterDept, filterProject]);
}, [sortBy, sortOrder, searchTerm, filterDept, filterProject, filterEntity]);
// 加载更多
const loadMore = useCallback(() => {
@@ -154,13 +154,14 @@ export default function MonitoringView() {
page: nextPage,
search: searchTerm || undefined,
dept: filterDept !== 'All' ? filterDept : undefined,
customer: filterProject !== 'All' ? filterProject : undefined,
project: filterProject !== 'All' ? filterProject : undefined,
entity: filterEntity !== 'All' ? filterEntity : undefined,
}).then(d => {
setVehicles(prev => [...prev, ...d.vehicles]);
setPage(nextPage);
setHasMore(nextPage < d.totalPages);
}).catch(() => {}).finally(() => setLoadingMore(false));
}, [sortBy, sortOrder, searchTerm, filterDept, filterProject, page, loadingMore, hasMore]);
}, [sortBy, sortOrder, searchTerm, filterDept, filterProject, filterEntity, page, loadingMore, hasMore]);
// 筛选/排序变化时重新加载
useEffect(() => {
@@ -550,20 +551,6 @@ export default function MonitoringView() {
</select>
</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>
{/* Date Range */}
@@ -614,7 +601,7 @@ export default function MonitoringView() {
onChange={(e) => setFilterProject(e.target.value)}
>
<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>
</div>
@@ -641,8 +628,7 @@ export default function MonitoringView() {
onChange={(e) => setFilterEntity(e.target.value)}
>
<option value="All"></option>
<option value="羚牛氢能"></option>
<option value="其他主体"></option>
{filterOptions.entities.map(e => <option key={e} value={e}>{e}</option>)}
</select>
</div>
</div>
@@ -663,21 +649,23 @@ export default function MonitoringView() {
</div>
{/* Mileage Range */}
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-slate-400 uppercase tracking-wider"> (KM)</label>
<div className="flex items-center gap-2">
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<label className="text-[10px] font-bold text-slate-400 uppercase tracking-wider"> (KM)</label>
<input
type="number"
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"
placeholder="不限"
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}
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
type="number"
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"
placeholder="不限"
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}
onChange={(e) => setFilterMileageRange(prev => ({ ...prev, max: e.target.value }))}
/>
@@ -690,7 +678,6 @@ export default function MonitoringView() {
setSearchTerm('');
setFilterDept('All');
setFilterPlate('All');
setFilterYear('All');
setFilterDateRange({ start: '', end: '' });
setFilterDate('2026-04-01');
setFilterProject('All');
@@ -772,7 +759,7 @@ export default function MonitoringView() {
</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-[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>

View File

@@ -141,7 +141,7 @@ export default function StatisticsView() {
{chartType === 'bar' ? (
<BarChart data={trendData} margin={{ top: 20, right: 10, left: 0, bottom: 0 }}>
<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' }} />
<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}>
@@ -154,7 +154,7 @@ export default function StatisticsView() {
) : chartType === 'line' ? (
<LineChart data={trendData} margin={{ top: 20, right: 10, left: 0, bottom: 0 }}>
<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' }} />
<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' }}>
@@ -170,7 +170,7 @@ export default function StatisticsView() {
</linearGradient>
</defs>
<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' }} />
<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)">

View File

@@ -16,6 +16,8 @@ export async function fetchMonitoring(params?: {
search?: string;
dept?: string;
customer?: string;
project?: string;
entity?: string;
}): Promise<MonitoringData> {
const query = new URLSearchParams();
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?.dept) query.set('dept', params.dept);
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();
return fetchJson<MonitoringData>(`${BASE}/monitoring${qs ? `?${qs}` : ''}`);
}

View File

@@ -10,6 +10,8 @@ export interface MonitoringVehicle {
department: string | null;
manager: string | null;
rentStatus: string | null;
entity: string | null;
project: string | null;
}
export interface MonitoringStats {
@@ -23,6 +25,8 @@ export interface MonitoringFilters {
departments: string[];
customers: string[];
plates: string[];
projects: string[];
entities: string[];
}
export interface MonitoringData {