fix: 区域统计客户筛选生效,后端region-stats支持过滤参数
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

- 后端/region-stats新增customer/city/region查询参数
- 前端regionFilters变化时重新请求后端数据
- 移除前端冗余过滤逻辑,由后端统一处理

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-03-29 00:42:59 +08:00
parent 1d19bb07a7
commit 16f5ef8741
3 changed files with 27 additions and 6 deletions

View File

@@ -236,6 +236,18 @@ export default function App() {
return () => clearInterval(interval);
}, [loadData]);
// Re-fetch region data when filters change
useEffect(() => {
const hasFilter = regionFilters.customer || regionFilters.city || regionFilters.region;
if (hasFilter) {
fetchRegionStats({ customer: regionFilters.customer || undefined, city: regionFilters.city || undefined, region: regionFilters.region || undefined })
.then(setRegionData).catch(() => {});
} else {
// No filters: use data from the main loadData cycle
fetchRegionStats().then(setRegionData).catch(() => {});
}
}, [regionFilters]);
// Fetch region chart data when view changes
useEffect(() => {
fetchRegionChart(regionChartView, regionChartView === 'city' ? 5 : 8).then(setRegionChartData).catch(() => setRegionChartData([]));
@@ -477,7 +489,7 @@ export default function App() {
const uniqueModalLocations = useMemo(() => Array.from(new Set(modalVehicles.map(v => v.location).filter(Boolean))), [modalVehicles]);
// Derived data for region section
const filteredRegionData = useMemo(() => regionData.filter((r) => !regionFilters.region || r.region === regionFilters.region), [regionData, regionFilters.region]);
// regionData is already filtered by backend based on regionFilters
// Filtered modal vehicles based on modal filters
const filteredModalVehicles = useMemo(() => modalVehicles.filter((v) => {
@@ -2063,7 +2075,7 @@ export default function App() {
</tr>
</thead>
<tbody className="text-xs">
{regionData.filter(r => !regionFilters.region || r.region === regionFilters.region).map((r) => {
{regionData.map((r) => {
const isExpanded = expandedRegions.has(r.region);
return (
<React.Fragment key={r.region}>
@@ -2124,7 +2136,7 @@ export default function App() {
{/* Mobile View (Region) */}
<div className="lg:hidden p-2 space-y-3">
{regionData.filter(r => !regionFilters.region || r.region === regionFilters.region).map((r) => {
{regionData.map((r) => {
const isExpanded = expandedRegions.has(r.region);
return (
<div key={r.region} className="bg-slate-50/50 rounded-xl border border-slate-100 overflow-hidden">

View File

@@ -66,8 +66,13 @@ export async function fetchDeptStats(): Promise<DeptGroup[]> {
return fetchJson<DeptGroup[]>(`${BASE}/dept-stats`);
}
export async function fetchRegionStats(): Promise<RegionGroup[]> {
return fetchJson<RegionGroup[]>(`${BASE}/region-stats`);
export async function fetchRegionStats(params?: { customer?: string; city?: string; region?: string }): Promise<RegionGroup[]> {
const query = new URLSearchParams();
if (params?.customer) query.set('customer', params.customer);
if (params?.city) query.set('city', params.city);
if (params?.region) query.set('region', params.region);
const qs = query.toString();
return fetchJson<RegionGroup[]>(`${BASE}/region-stats${qs ? `?${qs}` : ''}`);
}
export async function fetchCustomerStats(): Promise<CustomerStats[]> {

View File

@@ -724,7 +724,11 @@ app.get('/dept-stats', async (c) => {
// GET /api/vehicles/region-stats — macro-region with city drill-down
app.get('/region-stats', async (c) => {
const vehicles = await getVehicles();
const operating = vehicles.filter((v) => v.status === 'Operating' || v.status === 'Pending');
const { customer, city: filterCity, region: filterRegion } = c.req.query();
let operating = vehicles.filter((v) => v.status === 'Operating' || v.status === 'Pending');
if (customer) operating = operating.filter((v) => v.customerName === customer);
if (filterCity) operating = operating.filter((v) => resolveCity(v.city, v.province) === filterCity);
if (filterRegion) operating = operating.filter((v) => mapMacroRegion(v.province, v.city) === filterRegion);
const regionCityMap = new Map<string, Map<string, Vehicle[]>>();
for (const v of operating) {