diff --git a/src/App.tsx b/src/App.tsx index 92acf37..7bf9fc9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,2805 +1,13 @@ -import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; -import { - Truck, - Warehouse, - Activity, - PlusCircle, - MinusCircle, - History, - ChevronDown, - ChevronRight, - Info, - Loader2, - Search, - Filter, - ArrowRightLeft, - Users, - MapPin, - Building2, -} from 'lucide-react'; -import { motion, AnimatePresence } from 'motion/react'; -import { - BarChart, - Bar, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, - PieChart, - Pie, - Cell, - LabelList, -} from 'recharts'; -import type { SummaryData, TypeSummary, VehicleListItem, DeptGroup, RegionGroup, CustomerStats, RegionalInventoryStats } from './modules/assets/types'; -import { fetchSummary, fetchByType, fetchVehicleList, fetchWeeklyDetail, fetchDeptStats, fetchRegionStats, fetchCustomerStats, fetchInventoryStats, fetchRegionChart } from './modules/assets/api'; -import type { WeeklyDetailItem } from './modules/assets/api'; +import { Truck, Route } from 'lucide-react'; +import { Shell, type ModuleConfig } from './components/Shell'; +import AssetsModule from './modules/assets/AssetsModule'; +import MileageModule from './modules/mileage/MileageModule'; -// --- SearchSelect Component --- -function SearchSelect({ value, onChange, options, placeholder, className }: { - value: string; - onChange: (v: string) => void; - options: string[]; - placeholder: string; - className?: string; -}) { - const [open, setOpen] = useState(false); - const [query, setQuery] = useState(''); - const ref = useRef(null); - - useEffect(() => { - const handler = (e: MouseEvent) => { - if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); - }; - document.addEventListener('mousedown', handler); - return () => document.removeEventListener('mousedown', handler); - }, []); - - const filtered = useMemo(() => { - if (!query) return options; - const q = query.toLowerCase(); - return options.filter((o) => o.toLowerCase().includes(q)); - }, [options, query]); - - const displayValue = value || ''; - - return ( -
-
setOpen(!open)} - > - { setQuery(e.target.value); if (!open) setOpen(true); }} - onFocus={() => { setOpen(true); setQuery(''); }} - /> - -
- {open && ( -
-
{ onChange(''); setQuery(''); setOpen(false); }} - > - {placeholder} -
- {filtered.map((o) => ( -
{ onChange(o); setQuery(''); setOpen(false); }} - > - {o} -
- ))} - {filtered.length === 0 && ( -
无匹配项
- )} -
- )} -
- ); -} - -// --- Constants --- -const TABS = [ - { id: 'overview', label: '总览' }, - { id: 'department', label: '按部门' }, - { id: 'region', label: '按区域' }, - { id: 'customer', label: '按客户' }, +const MODULES: ModuleConfig[] = [ + { id: 'assets', label: '资产管理', icon: Truck, component: AssetsModule }, + { id: 'mileage', label: '里程管理', icon: Route, component: MileageModule }, ]; export default function App() { - const [activeTab, setActiveTab] = useState<'overview' | 'department' | 'region' | 'customer'>('overview'); - const [tabReady, setTabReady] = useState(true); - const prevTabRef = useRef(activeTab); - useEffect(() => { - if (prevTabRef.current !== activeTab) { - setTabReady(false); - prevTabRef.current = activeTab; - const id = requestAnimationFrame(() => { setTabReady(true); }); - return () => cancelAnimationFrame(id); - } - }, [activeTab]); - const [theme, setTheme] = useState<'soft' | 'minimal' | 'vibrant'>('soft'); - const [expandedModels, setExpandedModels] = useState>(new Set()); - const [expandedAssetTypes, setExpandedAssetTypes] = useState>(new Set()); - const [showPlateNumbers, setShowPlateNumbers] = useState<{ - batch: string; - model: string; - location: string; - category?: 'Inventory' | 'Pending' | 'Delivered' | 'Returned' | 'Replaced' | 'Operating'; - vehicleType?: string; - manager?: string; - customer?: string; - department?: string; - attendance?: 'active' | 'idle'; - isColdChain?: boolean; - isTrailer?: boolean; - type?: string; - source?: string; - title?: string; - } | null>(null); - - // Data state - const [summary, setSummary] = useState(null); - const [processedData, setProcessedData] = useState([]); - const [modalVehicles, setModalVehicles] = useState([]); - const [modalWeeklyDetail, setModalWeeklyDetail] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [lastUpdate, setLastUpdate] = useState(''); - const [modalLoading, setModalLoading] = useState(false); - - // Dept/Region/Customer data - const [deptData, setDeptData] = useState([]); - const [regionData, setRegionData] = useState([]); - const [customerData, setCustomerData] = useState([]); - - // Dept section state - const [deptViewMode, setDeptViewMode] = useState<'department' | 'manager'>('department'); - const [expandedDepts, setExpandedDepts] = useState>(new Set()); - const [expandedManagerDetails, setExpandedManagerDetails] = useState>(new Set()); - const [selectedManager, setSelectedManager] = useState('All'); - - // Region section state - const [expandedRegions, setExpandedRegions] = useState>(new Set()); - const [expandedRegionCities, setExpandedRegionCities] = useState>(new Set()); - const [regionFilters, setRegionFilters] = useState({ region: '', city: '', customer: '' }); - const [isRegionFilterOpen, setIsRegionFilterOpen] = useState(false); - const [draftRegionFilters, setDraftRegionFilters] = useState({ region: '', city: '', customer: '' }); - - // Customer section state - const [expandedCustomers, setExpandedCustomers] = useState>(new Set()); - const [customerFilters, setCustomerFilters] = useState({ customer: '', brand: '', department: '', manager: '', region: '' }); - const [isCustomerFilterOpen, setIsCustomerFilterOpen] = useState(false); - const [draftCustomerFilters, setDraftCustomerFilters] = useState({ customer: '', brand: '', department: '', manager: '', region: '' }); - - // Inventory statistics section state - const [inventoryData, setInventoryData] = useState([]); - const [inventoryTab, setInventoryTab] = useState<'region' | 'model'>('region'); - const [expandedInventoryRegions, setExpandedInventoryRegions] = useState>(new Set()); - const [expandedInventoryTypes, setExpandedInventoryTypes] = useState>(new Set(['4.5T普货'])); - const [inventoryFilters, setInventoryFilters] = useState({ region: '', city: '', brand: '', type: '', model: '' }); - const [isInventoryFilterOpen, setIsInventoryFilterOpen] = useState(false); - const [draftInventoryFilters, setDraftInventoryFilters] = useState({ region: '', city: '', brand: '', type: '', model: '' }); - - // Chart view states - const [customerChartView, setCustomerChartView] = useState<'region' | 'province'>('region'); - const [regionChartView, setRegionChartView] = useState<'region' | 'province'>('region'); - const [regionChartData, setRegionChartData] = useState<{ name: string; value: number }[]>([]); - - // Modal filter state - const [modalFilters, setModalFilters] = useState({ plateNumber: '', model: '', brand: '', location: '' }); - const [isModalFilterExpanded, setIsModalFilterExpanded] = useState(false); - - // Reset modal filters when modal opens - useEffect(() => { - if (showPlateNumbers) { - setModalFilters({ plateNumber: '', model: '', brand: '', location: '' }); - } - }, [showPlateNumbers]); - - const loadData = useCallback(async () => { - try { - setLoading(true); - setError(null); - const [s, byType, dept, region, cust, inv] = await Promise.all([ - fetchSummary(), - fetchByType(), - fetchDeptStats(), - fetchRegionStats(), - fetchCustomerStats(), - fetchInventoryStats(), - ]); - setSummary(s); - setProcessedData(byType); - setDeptData(dept); - setRegionData(region); - setCustomerData(cust); - setInventoryData(inv); - setLastUpdate(new Date().toLocaleString('zh-CN')); - } catch (e) { - setError(e instanceof Error ? e.message : '数据加载失败'); - } finally { - setLoading(false); - } - }, []); - - useEffect(() => { - loadData(); - const interval = setInterval(loadData, 60 * 1000); - 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 === 'province' ? 5 : 8).then(setRegionChartData).catch(() => setRegionChartData([])); - }, [regionChartView]); - - // Load modal vehicles - useEffect(() => { - if (!showPlateNumbers) { - setModalVehicles([]); - setModalWeeklyDetail([]); - return; - } - setModalLoading(true); - const cat = showPlateNumbers.category; - - // Weekly categories use the dedicated weekly-detail endpoint - const weeklyTypes: Record = { Delivered: 'delivered', Returned: 'returned', Replaced: 'replaced', Pending: 'pending' }; - if (cat && weeklyTypes[cat]) { - setModalVehicles([]); - fetchWeeklyDetail(weeklyTypes[cat]) - .then(setModalWeeklyDetail) - .catch(() => setModalWeeklyDetail([])) - .finally(() => setModalLoading(false)); - return; - } - - // Normal vehicle list - setModalWeeklyDetail([]); - const params: Record = {}; - if (showPlateNumbers.vehicleType) params.vehicleType = showPlateNumbers.vehicleType; - if (showPlateNumbers.batch !== 'All') params.batch = showPlateNumbers.batch; - if (showPlateNumbers.model !== 'All') params.model = showPlateNumbers.model; - if (showPlateNumbers.location !== 'All') params.location = showPlateNumbers.location; - if (cat === 'Inventory') params.category = 'Inventory'; - if (cat === 'Operating') params.category = 'Operating'; - if (showPlateNumbers.manager) params.manager = showPlateNumbers.manager; - if (showPlateNumbers.customer) params.customer = showPlateNumbers.customer; - if (showPlateNumbers.department) params.department = showPlateNumbers.department; - if (showPlateNumbers.attendance) params.attendance = showPlateNumbers.attendance; - if (!showPlateNumbers.type) { - if (showPlateNumbers.isColdChain !== undefined) params.isColdChain = String(showPlateNumbers.isColdChain); - if (showPlateNumbers.isTrailer !== undefined) params.isTrailer = String(showPlateNumbers.isTrailer); - } - // Map type field to backend vehicleType - if (showPlateNumbers.type) { - const t = showPlateNumbers.type; - if (t === '4.5T') { - if (showPlateNumbers.isColdChain === true) params.vehicleType = '4.5T冷链'; - else if (showPlateNumbers.isColdChain === false) params.vehicleType = '4.5T普货'; - } else if (t === '4.5T普货' || t === '4.5T冷链' || t === '18T' || t === '49T' || t === '挂车' || t === '其他') { - params.vehicleType = t; - } else if (t === '其他车型') { - if (showPlateNumbers.isTrailer === true) params.isTrailer = 'true'; - else if (showPlateNumbers.isTrailer === false) params.vehicleType = '其他'; - } - } - fetchVehicleList(params) - .then(setModalVehicles) - .catch(() => setModalVehicles([])) - .finally(() => setModalLoading(false)); - }, [showPlateNumbers]); - - const allTypesExpanded = processedData.length > 0 && processedData.every((t) => expandedAssetTypes.has(t.type)); - - const toggleAllAssetTypes = () => { - if (allTypesExpanded) { - setExpandedAssetTypes(new Set()); - } else { - setExpandedAssetTypes(new Set(processedData.map((t) => t.type))); - } - }; - - const toggleAssetType = (type: string) => { - const newSet = new Set(expandedAssetTypes); - if (newSet.has(type)) newSet.delete(type); - else newSet.add(type); - setExpandedAssetTypes(newSet); - }; - - const toggleModel = (model: string) => { - const newSet = new Set(expandedModels); - if (newSet.has(model)) newSet.delete(model); - else newSet.add(model); - setExpandedModels(newSet); - }; - - const toggleDept = (dept: string) => { - const newSet = new Set(expandedDepts); - if (newSet.has(dept)) newSet.delete(dept); - else newSet.add(dept); - setExpandedDepts(newSet); - }; - - const toggleManagerDetails = (manager: string) => { - const newSet = new Set(expandedManagerDetails); - if (newSet.has(manager)) newSet.delete(manager); - else newSet.add(manager); - setExpandedManagerDetails(newSet); - }; - - const toggleRegion = (region: string) => { - const newSet = new Set(expandedRegions); - if (newSet.has(region)) newSet.delete(region); - else newSet.add(region); - setExpandedRegions(newSet); - }; - - const toggleRegionCity = (key: string) => { - const newSet = new Set(expandedRegionCities); - if (newSet.has(key)) newSet.delete(key); - else newSet.add(key); - setExpandedRegionCities(newSet); - }; - - const toggleCustomer = (customer: string) => { - const newSet = new Set(expandedCustomers); - if (newSet.has(customer)) newSet.delete(customer); - else newSet.add(customer); - setExpandedCustomers(newSet); - }; - - const toggleInventoryRegion = (region: string) => { - const newSet = new Set(expandedInventoryRegions); - if (newSet.has(region)) newSet.delete(region); - else newSet.add(region); - setExpandedInventoryRegions(newSet); - }; - - const toggleInventoryType = (type: string) => { - const newSet = new Set(expandedInventoryTypes); - if (newSet.has(type)) newSet.delete(type); - else newSet.add(type); - setExpandedInventoryTypes(newSet); - }; - - // Derived data for inventory section - const filteredInventoryStats = useMemo(() => inventoryData.filter((s) => { - const mr = !inventoryFilters.region || s.region === inventoryFilters.region; - const mc = !inventoryFilters.city || s.city === inventoryFilters.city; - const mb = !inventoryFilters.brand || s.brand === inventoryFilters.brand; - const mt = !inventoryFilters.type || s.type === inventoryFilters.type; - const mm = !inventoryFilters.model || s.model === inventoryFilters.model; - return mr && mc && mb && mt && mm; - }), [inventoryData, inventoryFilters]); - - const uniqueInventoryBrands = useMemo(() => Array.from(new Set(inventoryData.map((s) => s.brand).filter(Boolean))), [inventoryData]); - const uniqueInventoryRegions = useMemo(() => Array.from(new Set(inventoryData.map((s) => s.region))), [inventoryData]); - const uniqueInventoryCities = useMemo(() => Array.from(new Set(inventoryData.map((s) => s.city).filter(Boolean))), [inventoryData]); - const INVENTORY_TYPE_ORDER_LIST = ['4.5T普货', '4.5T冷链', '18T', '49T', '挂车', '其他']; - const uniqueInventoryTypes = useMemo(() => { - const types = Array.from(new Set(inventoryData.map((s) => s.type).filter(Boolean))); - return types.sort((a, b) => INVENTORY_TYPE_ORDER_LIST.indexOf(a) - INVENTORY_TYPE_ORDER_LIST.indexOf(b)); - }, [inventoryData]); - const uniqueInventoryModelsForType = useMemo(() => { - const typeFilter = isInventoryFilterOpen ? draftInventoryFilters.type : inventoryFilters.type; - const source = typeFilter ? inventoryData.filter((s) => s.type === typeFilter) : inventoryData; - return Array.from(new Set(source.map((s) => s.model).filter(Boolean))); - }, [inventoryData, inventoryFilters.type, draftInventoryFilters.type, isInventoryFilterOpen]); - - const inventoryByRegion = useMemo(() => { - const result: Record> = {}; - for (const s of filteredInventoryStats) { - if (!result[s.region]) result[s.region] = {}; - if (!result[s.region][s.city]) result[s.region][s.city] = []; - result[s.region][s.city].push(s); - } - return result; - }, [filteredInventoryStats]); - - const inventoryByModel = useMemo(() => { - const INVENTORY_TYPE_ORDER = ['4.5T普货', '4.5T冷链', '18T', '49T', '挂车', '其他']; - const raw: Record> = {}; - for (const s of filteredInventoryStats) { - if (!raw[s.type]) raw[s.type] = {}; - if (!raw[s.type][s.model]) raw[s.type][s.model] = []; - raw[s.type][s.model].push(s); - } - const result: Record> = {}; - for (const t of INVENTORY_TYPE_ORDER) { if (raw[t]) result[t] = raw[t]; } - for (const t of Object.keys(raw)) { if (!result[t]) result[t] = raw[t]; } - return result; - }, [filteredInventoryStats]); - - // Derived data for dept section - const allManagersList = useMemo(() => deptData.flatMap((d) => d.managers.map((m) => m.manager)).filter((v, i, a) => a.indexOf(v) === i).sort(), [deptData]); - const managersGroupedByDept = useMemo(() => deptData.map((d) => ({ department: d.department, managers: d.managers.map((m) => m.manager) })), [deptData]); - const managerStats = useMemo(() => deptData - .flatMap((d) => d.managers) - .filter((m) => selectedManager === 'All' || m.manager === selectedManager) - .sort((a, b) => b.total - a.total), [deptData, selectedManager]); - - // Derived data for customer section - const filteredCustomerStats = useMemo(() => customerData.filter((s) => { - const mc = !customerFilters.customer || s.customer === customerFilters.customer; - const mb = !customerFilters.brand || s.brand === customerFilters.brand; - const md = !customerFilters.department || s.department === customerFilters.department; - const mm = !customerFilters.manager || s.manager === customerFilters.manager; - const mr = !customerFilters.region || s.region === customerFilters.region; - return mc && mb && md && mm && mr; - }), [customerData, customerFilters]); - const uniqueBrands = useMemo(() => Array.from(new Set(customerData.map((s) => s.brand).filter(Boolean))), [customerData]); - const uniqueDepts = useMemo(() => { - const numMap: Record = { '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10 }; - const getOrder = (name: string) => { const m = name.match(/[一二三四五六七八九十]/); return m ? (numMap[m[0]] || 99) : 99; }; - return Array.from(new Set(customerData.map((s) => s.department).filter(Boolean))).sort((a, b) => getOrder(a) - getOrder(b)); - }, [customerData]); - const uniqueRegions = useMemo(() => Array.from(new Set(customerData.map((s) => s.region))), [customerData]); - const uniqueCities = useMemo(() => Array.from(new Set(customerData.map((s) => s.city).filter(Boolean))), [customerData]); - const uniqueCustomerNames = useMemo(() => Array.from(new Set(customerData.map((s) => s.customer).filter(Boolean))), [customerData]); - const uniqueCustomerManagers = useMemo(() => Array.from(new Set(customerData.map((s) => s.manager).filter(Boolean))), [customerData]); - const customerManagersGroupedByDept = useMemo(() => { - const numMap: Record = { '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10 }; - const getDeptOrder = (name: string) => { - if (name === '公务车') return 100; - const m = name.match(/[一二三四五六七八九十]/); - return m ? (numMap[m[0]] || 99) : 99; - }; - // Use deptData order as primary source (already sorted), fallback to customerData - const deptMap = new Map>(); - // First add from deptData to get correct dept order and all managers - for (const d of deptData) { - if (!deptMap.has(d.department)) deptMap.set(d.department, new Set()); - for (const m of d.managers) deptMap.get(d.department)!.add(m.manager); - } - // Then add any extra from customerData - for (const s of customerData) { - if (!s.manager || !s.department) continue; - if (!deptMap.has(s.department)) deptMap.set(s.department, new Set()); - deptMap.get(s.department)!.add(s.manager); - } - return Array.from(deptMap.entries()) - .sort((a, b) => getDeptOrder(a[0]) - getDeptOrder(b[0])) - .map(([dept, mgrs]) => ({ department: dept, managers: Array.from(mgrs) })); - }, [customerData, deptData]); - const uniqueInventoryModels = useMemo(() => Array.from(new Set(inventoryData.map((s) => s.model).filter(Boolean))), [inventoryData]); - const uniqueModalPlates = useMemo(() => Array.from(new Set(modalVehicles.map(v => v.plateNumber || v.vin).filter(Boolean))), [modalVehicles]); - const uniqueModalModels = useMemo(() => Array.from(new Set(modalVehicles.map(v => v.model).filter(Boolean))), [modalVehicles]); - const uniqueModalBrands = useMemo(() => Array.from(new Set(modalVehicles.map(v => v.brandLabel).filter((x): x is string => Boolean(x)))), [modalVehicles]); - const uniqueModalLocations = useMemo(() => Array.from(new Set(modalVehicles.map(v => v.location).filter(Boolean))), [modalVehicles]); - - // Derived data for region section - // regionData is already filtered by backend based on regionFilters - - // Filtered modal vehicles based on modal filters - const filteredModalVehicles = useMemo(() => modalVehicles.filter((v) => { - const mp = !modalFilters.plateNumber || (v.plateNumber || v.vin) === modalFilters.plateNumber; - const mm = !modalFilters.model || v.model === modalFilters.model; - const mb = !modalFilters.brand || v.brandLabel === modalFilters.brand; - const ml = !modalFilters.location || v.location === modalFilters.location; - return mp && mm && mb && ml; - }), [modalVehicles, modalFilters]); - - const filteredModalWeeklyDetail = useMemo(() => modalWeeklyDetail.filter((v) => { - const mp = !modalFilters.plateNumber || v.plate_number === modalFilters.plateNumber; - return mp; - }), [modalWeeklyDetail, modalFilters.plateNumber]); - - const [customerProvinceData, setCustomerProvinceData] = useState<{ name: string; value: number }[]>([]); - useEffect(() => { - if (customerChartView === 'province') { - fetchRegionChart('province', 5, 'vehicle').then(setCustomerProvinceData).catch(() => setCustomerProvinceData([])); - } - }, [customerChartView]); - - const customerPieData = useMemo(() => { - if (customerChartView === 'region') { - const map: Record = {}; - customerData.forEach(item => { map[item.region] = (map[item.region] || 0) + item.total; }); - return Object.entries(map).map(([name, value]) => ({ name, value })).sort((a, b) => b.value - a.value); - } else { - return customerProvinceData; - } - }, [customerData, customerChartView, customerProvinceData]); - - const watermarkText = useMemo(() => `羚牛氢能-${new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/\//g, '-')}`, [lastUpdate]); - - if (loading && !summary) { - return ( -
-
- - 正在加载数据... -
-
- ); - } - - if (error && !summary) { - return ( -
-
-
加载失败
-
{error}
- -
-
- ); - } - - const SUMMARY = summary!; - - return ( -
- {/* Watermark */} -
-
${watermarkText}`)}")`, - backgroundRepeat: 'repeat', - }} /> -
- {/* Compact Header Bar */} -
- {/* Title row */} -
-

羚牛氢能车辆资产

- {/* Right: status + theme */} -
-
- - {lastUpdate} -
- {loading && ( -
- -
- )} -
- {(['soft','minimal','vibrant'] as const).map((t) => ( - - ))} -
-
-
- {/* Tab row */} -
- {TABS.map(tab => ( - - ))} -
- {/* Status row */} -
-
- - 最后更新: {lastUpdate} -
-
- - 每分钟更新一次 -
-
-
- - {/* Main Content Area */} -
- - {!tabReady && ( -
- -
- )} - - {tabReady && activeTab === 'overview' && ( - <> - {/* Header Summary - Ultra Compact */} -
- {/* Total Assets */} -
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', source: 'asset', title: '资产概览' })}> -
- -
-
-
资产总数
-
{SUMMARY.totalAssets.toLocaleString()}
-
-
- - {/* Operating */} -
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Operating', source: 'asset', title: '正在运营' })}> -
- -
-
-
总运营
-
- {SUMMARY.operating.total} - 自{SUMMARY.operating.self} 租{SUMMARY.operating.leased}{SUMMARY.operating.hanging > 0 && ` 挂${SUMMARY.operating.hanging}`} -
-
-
- - {/* Inventory */} -
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Inventory', source: 'asset', title: '库存总数' })}> -
- -
-
-
总库存
-
- {SUMMARY.inventory.total} - 库{SUMMARY.inventory.inStock} 异{SUMMARY.inventory.abnormal} -
-
-
- - {/* Pending */} -
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Pending', source: 'asset', title: '待交车' })}> -
- -
-
-
待交车
-
{SUMMARY.pendingDelivery}
-
-
- - {/* Dynamics */} -
-
-
本周动态
-
上周六-本周五
-
-
-
- {SUMMARY.weeklyNew} - 新增 -
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Delivered', source: 'asset', title: '本周交车' })}> - {SUMMARY.weeklyDelivered} - 交车 -
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Returned', source: 'asset', title: '本周还车' })}> - {SUMMARY.weeklyReturned} - 还车 -
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Replaced', source: 'asset', title: '本周替换' })}> - {SUMMARY.weeklyReplaced} - 替换 -
-
-
-
- - {/* Asset Summary Table */} -
-
-
-

资产数据实时汇总

-
- - 点击型号展开详情 -
-
-
- - {/* Desktop View Table */} -
- - - - - - - - - - - - - - - - - - - - - {processedData.map((typeGroup) => ( - - {/* Category Header Row */} - toggleAssetType(typeGroup.type)}> - - - - - {expandedAssetTypes.has(typeGroup.type) && typeGroup.models.map((model) => ( - - toggleModel(model.model)} - > - - - - - {['嘉兴', '广东', '北京', '新疆', '其他'].map(reg => ( - - ))} - - - - - - - - ))} - - - ))} - -
车型品牌型号车辆总资产库存总数库存-江浙沪库存-广东库存-北京库存-新疆库存-其他待交车当前在运营本周交车本周还车本周替换
-
-
- {expandedAssetTypes.has(typeGroup.type) ? - : - - } - {typeGroup.type} -
-
- 资产 {typeGroup.totalAssets} - 库存 {typeGroup.totalInventory} - 运营 {typeGroup.totalOperating} -
-
-
{typeGroup.type} -
- {expandedModels.has(model.model) ? : } - {model.model} -
-
- 资产 {model.total} - 库存 {model.inventory} - 运营 {model.operating} -
-
- - - - - {model.inventoryRegions[reg] > 0 ? ( - - ) : ''} - - {model.pending > 0 ? ( - - ) : model.pending} - - - - {model.weeklyDelivered > 0 ? ( - - ) : model.weeklyDelivered} - - {model.weeklyReturned > 0 ? ( - - ) : model.weeklyReturned} - - {model.weeklyReplaced > 0 ? ( - - ) : model.weeklyReplaced} -
-
- - {/* Mobile View Cards for Asset Summary */} -
- {processedData.map((typeGroup) => ( -
-
toggleAssetType(typeGroup.type)} - > -
- {expandedAssetTypes.has(typeGroup.type) ? - : - - } - {typeGroup.type} -
-
- 资产 {typeGroup.totalAssets} - 库存 {typeGroup.totalInventory} - 运营 {typeGroup.totalOperating} -
-
- - - {expandedAssetTypes.has(typeGroup.type) && ( - - {typeGroup.models.map((model) => ( -
-
toggleModel(model.model)} - > -
- {expandedModels.has(model.model) ? : } - {model.model} -
-
- { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', source: 'asset', title: model.model }); }}>资产 {model.total} - { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Inventory', source: 'asset', title: `${model.model} - 库存` }); }}>库存 {model.inventory} - { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Operating', source: 'asset', title: `${model.model} - 在运营` }); }}>运营 {model.operating} -
-
- - {expandedModels.has(model.model) && ( -
-
-
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Inventory', source: 'asset', title: `${model.model} - 库存` })}> - 总库存 - {model.inventory} -
-
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Pending', source: 'asset', title: `${model.model} - 待交车` })}> - 待交车 - {model.pending} -
-
- {['嘉兴', '广东', '北京', '新疆', '其他'].map(reg => ( -
-
{reg === '嘉兴' ? '浙' : reg === '广东' ? '粤' : reg === '北京' ? '京' : reg === '新疆' ? '新' : '其'}
- {model.inventoryRegions[reg] > 0 ? ( - - ) : ( -
-
- )} -
- ))} -
-
-
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Delivered', source: 'asset', title: `${model.model} - 本周交车` })}> - 本周已交车 - {model.weeklyDelivered} -
-
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Returned', source: 'asset', title: `${model.model} - 本周还车` })}> - 已还车 - {model.weeklyReturned} -
-
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Replaced', source: 'asset', title: `${model.model} - 本周替换` })}> - 已替换 - {model.weeklyReplaced} -
-
-
-
- )} -
- ))} -
- )} -
-
- ))} -
-
- - {/* Inventory Statistics */} -
-
-
-
-
-

库存统计

- 实时更新库存数据 -
-
- -
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Inventory', source: 'asset', title: '库存总数' })}> - 总库存数据 - - {filteredInventoryStats.reduce((acc, s) => acc + s.quantity, 0)} - - -
- -
- - - - {isInventoryFilterOpen && ( - <> - -
-

库存统计 - 数据筛选

- -
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
- - )} -
-
-
-
- -
-
- - -
-
- - {/* Active Filters Bar */} - {(inventoryFilters.region || inventoryFilters.city || inventoryFilters.brand || inventoryFilters.type || inventoryFilters.model) && ( -
- {inventoryFilters.region && ( - - 区域: {inventoryFilters.region} - - - )} - {inventoryFilters.city && ( - - 城市: {inventoryFilters.city} - - - )} - {inventoryFilters.brand && ( - - 品牌: {inventoryFilters.brand} - - - )} - {inventoryFilters.type && ( - - 车型: {inventoryFilters.type} - - - )} - {inventoryFilters.model && ( - - 批次: {inventoryFilters.model} - - - )} - -
- )} - - {/* Desktop View Table */} -
- - - - - - - - - - - {inventoryTab === 'region' ? ( - Object.entries(inventoryByRegion).map(([region, cities]) => ( - - toggleInventoryRegion(region)} - > - - - - {expandedInventoryRegions.has(region) && Object.entries(cities).map(([city, stats]) => ( - - {stats.map((stat, idx) => ( - - - - - - - ))} - - ))} - - - )) - ) : ( - Object.entries(inventoryByModel).map(([type, models]) => ( - - toggleInventoryType(type)} - > - - - - {expandedInventoryTypes.has(type) && Object.entries(models).map(([model, stats]) => ( - - {stats.map((stat, idx) => ( - - - - - - - ))} - - ))} - - - )) - )} - -
{inventoryTab === 'region' ? '区域 / 城市' : '车型分类 / 型号'}{inventoryTab === 'region' ? '品牌' : '品牌'}{inventoryTab === 'region' ? '车型' : '所在区域/城市'}库存数量
-
- {expandedInventoryRegions.has(region) ? : } - {region} - { - e.stopPropagation(); - setShowPlateNumbers({ batch: 'All', model: 'All', location: region, category: 'Inventory', source: 'asset', title: `库存统计 - ${region}` }); - }}> - (共 {Object.values(cities).flat().reduce((acc, s) => acc + s.quantity, 0)} 台) - -
-
- {idx === 0 ? {city} : ''} - {stat.brand}{stat.model} - -
-
- {expandedInventoryTypes.has(type) ? : } - {type} - { - e.stopPropagation(); - setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', type: type, category: 'Inventory', source: 'asset', title: `库存统计 - ${type}` }); - }}> - (共 {Object.values(models).flat().reduce((acc, s) => acc + s.quantity, 0)} 台) - -
-
- {idx === 0 ? {model} : ''} - {stat.brand}{stat.region} / {stat.city} - -
-
- - {/* Mobile View */} -
- {inventoryTab === 'region' ? ( - Object.entries(inventoryByRegion).map(([region, cities]) => ( -
-
toggleInventoryRegion(region)} - > -
- {expandedInventoryRegions.has(region) ? : } - {region} -
- - {Object.values(cities).flat().reduce((acc, s) => acc + s.quantity, 0)} 台 - -
- - {expandedInventoryRegions.has(region) && ( -
- {Object.entries(cities).map(([city, stats]) => ( -
-
{city}
-
- {stats.map((stat, idx) => ( -
-
- {stat.brand} - {stat.model} -
- -
- ))} -
-
- ))} -
- )} -
- )) - ) : ( - Object.entries(inventoryByModel).map(([type, models]) => ( -
-
toggleInventoryType(type)} - > -
- {expandedInventoryTypes.has(type) ? : } - {type} -
- - {Object.values(models).flat().reduce((acc, s) => acc + s.quantity, 0)} 台 - -
- - {expandedInventoryTypes.has(type) && ( -
- {Object.entries(models).map(([model, stats]) => ( -
-
{model}
-
- {stats.map((stat, idx) => ( -
-
- {stat.brand} - {stat.region} / {stat.city} -
- -
- ))} -
-
- ))} -
- )} -
- )) - )} -
-
- - )} - - {tabReady && activeTab === 'department' && ( -
-
- {/* Overall Total Summary (Compact) - Moved to Top */} -
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', source: 'department', title: '部门运营统计' })}> - 总运营车辆 - {deptData.reduce((s, d) => s + d.totalAssets, 0)} -
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', attendance: 'active', source: 'department', title: '部门运营统计 - 出勤车辆' })}> - 出勤车辆 - - {deptData.reduce((acc, d) => acc + d.operatingCount, 0)} - -
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', attendance: 'idle', source: 'department', title: '部门运营统计 - 闲置车辆' })}> - 闲置车辆 - - {deptData.reduce((acc, d) => acc + d.idleCount, 0)} - -
-
- 平均出勤 - - {deptData.length > 0 ? (deptData.reduce((acc, d) => acc + d.attendanceRate, 0) / deptData.length).toFixed(1) : 0}% - -
-
-
- -
- *说明:当天里程>0即为出勤。 -
- - {/* Controls Row: Toggles Left, Filter Right */} -
-
- - -
- -
- {deptViewMode === 'manager' && ( -
- - - -
- )} -
-
- - {/* Desktop Table View */} -
- - - - - {deptViewMode === 'manager' && } - - {deptViewMode === 'department' && ( - <> - - - - - )} - {deptViewMode === 'manager' && ( - <> - - - - - - - - )} - - - - - {deptViewMode === 'department' ? ( - deptData.map((dept) => { - const isExpanded = expandedDepts.has(dept.department); - return ( - - toggleDept(dept.department)} - > - - - - - - - - {isExpanded && ( - - - - )} - - ); - }) - ) : ( - managerStats.map((m) => { - const isManagerExpanded = expandedManagerDetails.has(m.manager); - return ( - - toggleManagerDetails(m.manager)} - > - - - - - - - - - - - - {isManagerExpanded && ( - - - - )} - - ); - }) - )} - -
{deptViewMode === 'department' ? '部门名称' : '业务负责人'}所属部门{deptViewMode === 'department' ? '出勤率' : '合计资产'}总运营车辆出勤车辆闲置车辆4.5T冷链18T49T挂车其他详情
- {dept.department} - - - {dept.attendanceRate}% - - - - - - - - - {isExpanded ? : } -
-
- {dept.managers.map(m => { - const isManagerExpanded = expandedManagerDetails.has(m.manager); - return ( -
-
toggleManagerDetails(m.manager)} - > -
- {isManagerExpanded ? : } - {m.manager} -
- -
- - {isManagerExpanded && ( -
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T` })} - > -
4.5T
-
{m.t4_5}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T冷链` })} - > -
冷链
-
{m.t4_5c}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '18T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 18T` })} - > -
18T
-
{m.t18}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '49T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 49T` })} - > -
49T
-
{m.t49}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 挂车` })} - > -
挂车
-
{m.trailer}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 其他` })} - > -
其他
-
{m.other}
-
-
-
- )} -
- ); - })} -
-
- {isManagerExpanded ? : } - {m.manager} - {m.department} { - e.stopPropagation(); - setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 正在运营` }); - }} - > - {m.total} - ------ - -
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T` })}> - 4.5T - {m.t4_5} -
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T冷链` })}> - 冷链 - {m.t4_5c} -
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '18T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 18T` })}> - 18T - {m.t18} -
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '49T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 49T` })}> - 49T - {m.t49} -
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 挂车` })}> - 挂车 - {m.trailer} -
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 其他` })}> - 其他 - {m.other} -
-
-
-
- - {/* Mobile Card View */} -
- {deptViewMode === 'department' ? ( - deptData.map((dept) => { - const isExpanded = expandedDepts.has(dept.department); - return ( -
-
toggleDept(dept.department)} - > -
-

{dept.department}

- - 出勤率: {dept.attendanceRate}% - -
-
-
{ e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', department: dept.department, source: 'department', title: `部门运营统计 - ${dept.department}` }); }}> -
总运营
-
{dept.totalAssets}
-
-
{ e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', department: dept.department, attendance: 'active', source: 'department', title: `部门运营统计 - ${dept.department} - 出勤车辆` }); }}> -
出勤
-
{dept.operatingCount}
-
-
{ e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', department: dept.department, attendance: 'idle', source: 'department', title: `部门运营统计 - ${dept.department} - 闲置车辆` }); }}> -
闲置
-
{dept.idleCount}
-
-
-
- {isExpanded ? : } -
-
- {isExpanded && ( -
- {dept.managers.map(m => { - const isManagerExpanded = expandedManagerDetails.has(m.manager); - return ( -
-
toggleManagerDetails(m.manager)} - > -
- {isManagerExpanded ? : } - {m.manager} -
- -
- - {isManagerExpanded && ( -
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T` })} - > -
4.5T
-
{m.t4_5}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T冷链` })} - > -
冷链
-
{m.t4_5c}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '18T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 18T` })} - > -
18T
-
{m.t18}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '49T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 49T` })} - > -
49T
-
{m.t49}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 挂车` })} - > -
挂车
-
{m.trailer}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 其他` })} - > -
其他
-
{m.other}
-
-
- )} -
- ); - })} -
- )} -
- ); - }) - ) : ( - managerStats.map((m) => { - const isManagerExpanded = expandedManagerDetails.has(m.manager); - return ( -
-
toggleManagerDetails(m.manager)} - > -
- {isManagerExpanded ? : } -
-

{m.manager}

- {m.department} -
{ - e.stopPropagation(); - setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 正在运营` }); - }} - > - 资产: {m.total} -
-
-
- - -
- - {isManagerExpanded && ( -
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T` })} - > -
4.5T
-
{m.t4_5}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T冷链` })} - > -
冷链
-
{m.t4_5c}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '18T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 18T` })} - > -
18T
-
{m.t18}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '49T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 49T` })} - > -
49T
-
{m.t49}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 挂车` })} - > -
挂车
-
{m.trailer}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 其他` })} - > -
其他
-
{m.other}
-
-
- )} -
- ); - }) - )} -
-
-
- )} - - {tabReady && activeTab === 'region' && ( -
- {/* Region Distribution Chart */} -
-
-
-
-

区域资产分布概览

-
-
- - -
-
-
- - - - - - - - - - - -
-
- - {/* Region - Vehicle - Customer Section */} -
-
-
-
-
-

区域运营统计

-

*按区域—车型—客户维度统计

-
-
- -
- - - - {isRegionFilterOpen && ( - <> -
- -
-

区域筛选

- -
- -
-
- - setDraftRegionFilters(prev => ({ ...prev, customer: v }))} options={uniqueCustomerNames} placeholder="所有客户" className="text-xs py-2 px-2" /> -
- -
-
- - -
-
- - -
-
-
- -
- - )} - -
-
- - {Object.values(regionFilters).some(v => v !== '') && ( -
- {regionFilters.customer && ( - - 客户: {regionFilters.customer} - - - )} - {regionFilters.region && ( - - 区域: {regionFilters.region} - - - )} - {regionFilters.city && ( - - 城市: {regionFilters.city} - - - )} - -
- )} - -
-
- - - - - - - - - - - - {regionData.map((r) => { - const isExpanded = expandedRegions.has(r.region); - return ( - - toggleRegion(r.region)} - > - - - - - - - {isExpanded && r.cities.map((city) => { - const cityKey = `${r.region}-${city.city}`; - const isCityExpanded = expandedRegionCities.has(cityKey); - return ( - - toggleRegionCity(cityKey)} - > - - - - - - - {isCityExpanded && city.typeBreakdown.map(tb => ( - - - - - - - - ))} - - ); - })} - - ); - })} - -
区域 / 车型 / 客户资产总数运营中待交车主要客户
- {isExpanded ? : } - - {r.region}区域 - { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: r.region, source: 'region', title: `区域运营统计 - ${r.region}` }); }}>{r.totalAssets} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: r.region, category: 'Operating', source: 'region', title: `区域运营统计 - ${r.region} - 正在运营` }); }}>{r.operatingCount} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: r.region, category: 'Pending', source: 'region', title: `区域运营统计 - ${r.region} - 待交车` }); }}>{r.pendingCount}{r.customers.slice(0, 2).join(', ')}
- {isCityExpanded ? : } - - {city.city} - { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: city.city, source: 'region', title: `区域运营统计 - ${city.city}` }); }}>{city.totalAssets} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: city.city, category: 'Operating', source: 'region', title: `区域运营统计 - ${city.city} - 正在运营` }); }}>{city.operatingCount} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: city.city, category: 'Pending', source: 'region', title: `区域运营统计 - ${city.city} - 待交车` }); }}>{city.pendingCount}{city.customers.slice(0, 2).join(', ')}
-
- {tb.type} 车型 -
setShowPlateNumbers({ batch: 'All', model: 'All', location: city.city, vehicleType: tb.type, source: 'region', title: `区域运营统计 - ${city.city} - ${tb.type}` })}>{tb.total} setShowPlateNumbers({ batch: 'All', model: 'All', location: city.city, vehicleType: tb.type, category: 'Operating', source: 'region', title: `区域运营统计 - ${city.city} - ${tb.type} - 正在运营` })}>{tb.operating} { setShowPlateNumbers({ batch: 'All', model: 'All', location: city.city, vehicleType: tb.type, category: 'Inventory', source: 'region', title: `区域运营统计 - ${city.city} - ${tb.type} - 库存` }); }}>{tb.inventory}{tb.customers.slice(0, 2).join(', ')}
-
- - {/* Mobile View (Region) */} -
- {regionData.map((r) => { - const isExpanded = expandedRegions.has(r.region); - return ( -
-
toggleRegion(r.region)} - > -
- {isExpanded ? : } - - {r.region}区域 -
-
资产: {r.totalAssets}
-
- {isExpanded && ( - <> -
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: r.region, category: 'Operating', source: 'region', title: `区域运营统计 - ${r.region} - 正在运营` })} - > -
运营中
-
{r.operatingCount}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: r.region, category: 'Pending', source: 'region', title: `区域运营统计 - ${r.region} - 待交车` })} - > -
待交车
-
{r.pendingCount}
-
-
-
- {r.typeBreakdown.map(tb => ( -
- {tb.type} 车型 -
- setShowPlateNumbers({ batch: 'All', model: 'All', location: r.region, vehicleType: tb.type, category: 'Operating', source: 'region', title: `区域运营统计 - ${r.region} - ${tb.type} - 正在运营` })} - > - 运:{tb.operating} - - setShowPlateNumbers({ batch: 'All', model: 'All', location: r.region, vehicleType: tb.type, category: 'Inventory', source: 'region', title: `区域运营统计 - ${r.region} - ${tb.type} - 库存` })} - > - 待:{tb.inventory} - -
-
- ))} -
- - )} -
- ); - })} -
-
-
-
- )} - - {tabReady && activeTab === 'customer' && ( -
- - {/* Customer Operations Statistics Section */} -
-
-
-
-
-

客户运营统计

-

*按客户维度统计资产分布

-
-
- -
- - - - {isCustomerFilterOpen && ( - <> - {/* Backdrop */} -
- - {/* Popover Content */} - -
-

数据筛选

-
- -
-
- -
-
- - setDraftCustomerFilters(prev => ({ ...prev, customer: v }))} options={uniqueCustomerNames} placeholder="所有客户" className="text-xs py-2 px-2" /> -
- -
- - -
- -
-
- - -
-
- - -
-
- - -
-
-
- -
- - )} - -
-
- - {Object.values(customerFilters).some(v => v !== '') && ( -
- {customerFilters.customer && ( - - 客户: {customerFilters.customer} - - - )} - {customerFilters.manager && ( - - 负责人: {customerFilters.manager} - - - )} - {customerFilters.brand && ( - - 品牌: {customerFilters.brand} - - - )} - {customerFilters.department && ( - - 部门: {customerFilters.department} - - - )} - {customerFilters.region && ( - - 区域: {customerFilters.region} - - - )} - -
- )} - -
- {/* Desktop Table View (Customer) */} -
- - - - - - - - - - - - - - - - - {filteredCustomerStats.map((cust) => { - const isExpanded = expandedCustomers.has(cust.customer); - return ( - - toggleCustomer(cust.customer)} - > - - - - - - - - - - - - {isExpanded && ( - - - - )} - - ); - })} - -
客户名称所在区域关联业务负责人4.5T4.5T冷链18T49T挂车其他合计
- {isExpanded ? : } - {cust.customer} - - {cust.region} - {cust.manager} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '4.5T', isColdChain: false, source: 'customer', title: `客户运营统计 - ${cust.customer} - 4.5T` }); }}>{cust.t4_5} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '4.5T', isColdChain: true, source: 'customer', title: `客户运营统计 - ${cust.customer} - 4.5T冷链` }); }}>{cust.t4_5c} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '18T', source: 'customer', title: `客户运营统计 - ${cust.customer} - 18T` }); }}>{cust.t18} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '49T', source: 'customer', title: `客户运营统计 - ${cust.customer} - 49T` }); }}>{cust.t49} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '其他车型', source: 'customer', title: `客户运营统计 - ${cust.customer} - 挂车` }); }}>{cust.trailer} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '其他车型', source: 'customer', title: `客户运营统计 - ${cust.customer} - 其他` }); }}>{cust.other} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, source: 'customer', title: `客户运营统计 - ${cust.customer}` }); }}>{cust.total}
-
-
-
客户详情
-
{cust.customer}
-
-
-
主要车型
-
- {cust.t49 > cust.t18 ? '49T 重卡' : (cust.t18 > cust.t4_5c ? '18T 货车' : '4.5T 轻卡')} -
-
-
-
业务经理
-
{cust.manager}
-
-
-
资产占比
-
- {((cust.total / deptData.reduce((s, d) => s + d.totalAssets, 0)) * 100).toFixed(1)}% -
-
-
-
-
- - {/* Mobile Card View (Customer) */} -
- {filteredCustomerStats.map((cust) => { - const isExpanded = expandedCustomers.has(cust.customer); - return ( -
-
toggleCustomer(cust.customer)} - > -
- {isExpanded ? : } -
- {cust.customer} - {cust.region}区域 -
-
-
{ e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, source: 'customer', title: `客户运营统计 - ${cust.customer}` }); }} - > - 合计: {cust.total} -
-
- - {isExpanded && ( -
- {/* Details Cards for Mobile */} -
-
-
客户详情
-
{cust.customer}
-
-
-
主要车型
-
- {cust.t49 > cust.t18 ? '49T 重卡' : (cust.t18 > cust.t4_5c ? '18T 货车' : '4.5T 轻卡')} -
-
-
-
业务经理
-
{cust.manager}
-
-
-
资产占比
-
- {((cust.total / deptData.reduce((s, d) => s + d.totalAssets, 0)) * 100).toFixed(1)}% -
-
-
- -
-
车型分布
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '4.5T', isColdChain: false, source: 'customer', title: `客户运营统计 - ${cust.customer} - 4.5T` })} - > -
4.5T
-
{cust.t4_5}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '4.5T', isColdChain: true, source: 'customer', title: `客户运营统计 - ${cust.customer} - 4.5T冷链` })} - > -
冷链
-
{cust.t4_5c}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '18T', source: 'customer', title: `客户运营统计 - ${cust.customer} - 18T` })} - > -
18T
-
{cust.t18}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '49T', source: 'customer', title: `客户运营统计 - ${cust.customer} - 49T` })} - > -
49T
-
{cust.t49}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '其他车型', source: 'customer', title: `客户运营统计 - ${cust.customer} - 挂车` })} - > -
挂车
-
{cust.trailer}
-
-
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '其他车型', source: 'customer', title: `客户运营统计 - ${cust.customer} - 其他` })} - > -
其他
-
{cust.other}
-
-
-
-
- )} -
- ); - })} -
-
-
-
- )} - - - {/* Vehicle Detail Modal */} - - {showPlateNumbers && ( -
- -
-
-

- - {showPlateNumbers.title || ( - (showPlateNumbers.manager ? `${showPlateNumbers.manager} 的${showPlateNumbers.type || ''}车辆` : - showPlateNumbers.customer ? `${showPlateNumbers.customer} 的${showPlateNumbers.type || ''}车辆` : - showPlateNumbers.batch === 'All' ? '全量批次' : `${showPlateNumbers.batch} 批次`) + ' - 运营明细' - )} -

-

- {showPlateNumbers.model === 'All' ? '全量型号' : showPlateNumbers.model} | - {showPlateNumbers.category === 'Pending' ? '待交车' : - showPlateNumbers.category === 'Delivered' ? '本周已交车' : - showPlateNumbers.category === 'Returned' ? '已还车' : - showPlateNumbers.category === 'Replaced' ? '已替换' : - showPlateNumbers.category === 'Inventory' ? `${showPlateNumbers.location}库存` : - showPlateNumbers.category === 'Operating' ? '正在运营' : '全部状态'} -

-
- -
- - {/* Modal Filters */} -
-
setIsModalFilterExpanded(!isModalFilterExpanded)} - > -
- - 筛选条件 -
-
- {/* Quick Search always visible when collapsed */} - {!isModalFilterExpanded && ( -
e.stopPropagation()}> - setModalFilters({...modalFilters, plateNumber: v})} options={uniqueModalPlates} placeholder="快速搜索车牌..." className="text-[11px] py-1 px-2" /> -
- )} - - - -
-
- - - {isModalFilterExpanded && ( - -
-
- - setModalFilters({...modalFilters, plateNumber: v})} options={uniqueModalPlates} placeholder="全部车牌" className="text-[11px] py-1.5 px-2" /> -
-
- - -
-
- - -
-
- - -
-
-
- -
-
- )} -
-
- -
- {modalLoading ? ( -
- -
- ) : modalWeeklyDetail.length > 0 ? ( -
- - - - - - - - - - {filteredModalWeeklyDetail.map((v, i) => ( - - - - - - ))} - -
车牌客户名称日期
{v.plate_number}{v.customer_name || '—'}{v.handover_date ? v.handover_date.slice(0, 10) : '—'}
-
- ) : ( -
- - - - {showPlateNumbers.source === 'customer' ? ( - <> - - - - - - - - - - - - - - - ) : ( - <> - - {showPlateNumbers.source !== 'asset' && showPlateNumbers.category !== 'Inventory' && ( - - )} - - - - - )} - - - - {filteredModalVehicles.map((v, idx) => ( - - {showPlateNumbers.source === 'customer' ? ( - <> - - - - - - - - - - - - - - - ) : ( - <> - - {showPlateNumbers.source !== 'asset' && showPlateNumbers.category !== 'Inventory' && ( - - )} - - - - - )} - - ))} - {filteredModalVehicles.length === 0 && ( - - - - )} - -
业务部门业务负责人品牌车型资产归属客户名称车牌状态提车时间合同到期时间运营区域离到期签约公司车牌客户名称品牌车型所在地
{v.departmentName || '—'}{v.customerManager || '—'}{v.brandLabel || '—'}{v.type}{v.subjectOrg || '—'}{v.customerName || '—'}{v.plateNumber || v.vin || '—'} - - {v.status === 'Operating' ? '在租' : v.status === 'Inventory' ? '库存' : '异常'} - - {'—'}{'—'}{v.location === '其他' ? '对接中' : v.location}{'—'}{v.orgName || '—'}{v.plateNumber || v.vin || '—'}{v.customerName || '—'}{v.brandLabel || '—'}{v.type}{v.location === '其他' ? '对接中' : v.location}
- 暂无符合条件的车辆数据 -
-
- )} -
- -
-
- 共计 {filteredModalWeeklyDetail.length > 0 ? filteredModalWeeklyDetail.length : filteredModalVehicles.length} 台车辆 -
- -
-
-
- )} -
- -
- - {/* Footer / Navigation */} -
- - - - -
-
- ); + return ; }