refactor(stage1): 删除已证实无引用的死代码
按 import 图可达性分析(从 main.tsx / server index+local / vite.config 出发) 删除 0 引用的文件,删除后可达性分析 UNREACHABLE 归零。 - 删旧氢能实现:PrototypeBoard / HydrogenBiV2App / prototype-adapter / prototype-source 全部 / HydrogenView+Overview+Daily+Settlement+BoardChrome+ StationBoard / hydrogen-overview / hydrogen-daily - 删死 CSS 4 个(约 12k 行)、删死 vendor 残留(自挂载 index.tsx、AccessGate、SdDatePicker) - 删 server/mileage-db.ts(含硬编码凭据的死模块) - DailyRangeControls:移除从未被传入的 vehicleScope/onVehicleScopeChange 死分支 - tsconfig:移除指向已删除/不存在路径的 exclude 测试 146 -> 131(删掉的 3 个测试文件覆盖的是被删的死代码)。 lint / test / build 全绿。
This commit is contained in:
@@ -1,228 +0,0 @@
|
||||
import type { HydrogenCustomerRow, HydrogenStationFull } from '../../types';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { ChevronRight, Search } from 'lucide-react';
|
||||
import { SortableColumnHeader, sortBy, toggleSort, type SortDirection } from '../../components/SortableColumnHeader';
|
||||
import {
|
||||
buildCustomerDrillPayload,
|
||||
buildStationDrillPayload,
|
||||
formatKg as fmtKg,
|
||||
formatYuan as fmtYuan,
|
||||
type OverviewDrillPayload,
|
||||
type OverviewDrillRequest,
|
||||
type OverviewScope,
|
||||
} from '../model';
|
||||
import { OverviewDrillDialog } from './OverviewDrillDialog';
|
||||
|
||||
export function StationSummaryTable({
|
||||
stations,
|
||||
onSelectStation,
|
||||
scope = 'global',
|
||||
scopeLabel,
|
||||
onDrillRequest,
|
||||
}: {
|
||||
stations: HydrogenStationFull[];
|
||||
onSelectStation?: (stationId: number) => void;
|
||||
scope?: OverviewScope;
|
||||
scopeLabel?: string | null;
|
||||
onDrillRequest?: (request: OverviewDrillRequest) => void;
|
||||
}) {
|
||||
const [province, setProvince] = useState('all');
|
||||
const [sortKey, setSortKey] = useState<'name' | 'province' | 'kg' | 'share' | 'revenue' | 'revenueShare'>('kg');
|
||||
const [sortDirection, setSortDirection] = useState<SortDirection>('desc');
|
||||
const [drill, setDrill] = useState<OverviewDrillPayload | null>(null);
|
||||
const [selectedStationId, setSelectedStationId] = useState<number | null>(null);
|
||||
const provinces = useMemo(() => [...new Set(stations.map(station => station.province?.trim()).filter(Boolean) as string[])], [stations]);
|
||||
const filteredStations = province === 'all'
|
||||
? stations
|
||||
: stations.filter(station => station.province === province);
|
||||
const sortedStations = useMemo(() => sortBy(filteredStations, sortKey, sortDirection, (station, key) => station[key]), [filteredStations, sortDirection, sortKey]);
|
||||
const changeSort = (nextKey: typeof sortKey) => {
|
||||
const next = toggleSort(sortKey, sortDirection, nextKey);
|
||||
setSortKey(next.key);
|
||||
setSortDirection(next.direction);
|
||||
};
|
||||
const openStation = (station: HydrogenStationFull) => {
|
||||
setSelectedStationId(station.id);
|
||||
if (onDrillRequest) onDrillRequest({ kind: 'station', key: String(station.id), label: station.name, entityId: station.id });
|
||||
else setDrill(buildStationDrillPayload(station));
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{stations.length > 0 && (
|
||||
<div className="ehb-sum-table-card">
|
||||
<div className="ehb-sum-table-card__head" style={{ flexWrap: 'wrap', gap: 12 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
|
||||
<div className="ehb-sum-table-card__title"><Search size={14} className="text-sky-600" />加氢站加氢汇总{scope === 'station' && scopeLabel ? ` · ${scopeLabel}` : ''}</div>
|
||||
<div className="ehb-mini-tabs">
|
||||
<button type="button" onClick={() => setProvince('all')} className={`ehb-mini-tab ${province === 'all' ? 'is-active' : ''}`}>全国</button>
|
||||
{provinces.map(item => <button key={item} type="button" onClick={() => setProvince(item)} className={`ehb-mini-tab ${province === item ? 'is-active' : ''}`}>{item}</button>)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ehb-sum-table-card__meta">
|
||||
统计范围内共 {stations.length} 站
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ehb-sum-table-wrap">
|
||||
<table className="ehb-sum-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="col-idx">#</th>
|
||||
<th style={{ textAlign: 'left' }}><SortableColumnHeader label="加氢站" sortKey="name" activeSortKey={sortKey} sortDirection={sortDirection} onSort={changeSort} /></th>
|
||||
<th style={{ textAlign: 'left' }}><SortableColumnHeader label="所属省份" sortKey="province" activeSortKey={sortKey} sortDirection={sortDirection} onSort={changeSort} /></th>
|
||||
<th style={{ textAlign: 'right' }}><SortableColumnHeader label="加氢量" sortKey="kg" activeSortKey={sortKey} sortDirection={sortDirection} onSort={changeSort} align="right" /></th>
|
||||
<th style={{ textAlign: 'right' }}><SortableColumnHeader label="占比" sortKey="share" activeSortKey={sortKey} sortDirection={sortDirection} onSort={changeSort} align="right" /></th>
|
||||
<th style={{ textAlign: 'right' }}><SortableColumnHeader label="氢费收入" sortKey="revenue" activeSortKey={sortKey} sortDirection={sortDirection} onSort={changeSort} align="right" /></th>
|
||||
<th style={{ textAlign: 'right' }}><SortableColumnHeader label="收入占比" sortKey="revenueShare" activeSortKey={sortKey} sortDirection={sortDirection} onSort={changeSort} align="right" /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sortedStations.map((s, i) => {
|
||||
const kgFmt = fmtKg(s.kg);
|
||||
const revFmt = fmtYuan(s.revenue);
|
||||
return (
|
||||
<tr key={s.name + i} onClick={() => openStation(s)} style={{ cursor: 'pointer' }} title="查看加氢站汇总明细">
|
||||
<td className="col-idx">{i + 1}</td>
|
||||
<td style={{ fontWeight: 600, color: '#0284c7' }}>
|
||||
{s.name} <span style={{ fontSize: 11, fontWeight: 400, opacity: 0.8 }}>钻取 ›</span>
|
||||
</td>
|
||||
<td>
|
||||
<span style={{ fontSize: 11, color: '#0284c7', background: '#eff6ff', padding: '1px 6px', borderRadius: 4, fontWeight: 500 }}>
|
||||
{s.province ?? '未归属'}
|
||||
</span>
|
||||
</td>
|
||||
<td style={{ textAlign: 'right' }} className="col-bold-kg">
|
||||
{kgFmt.value} <span style={{ fontSize: 11, fontWeight: 400, color: '#64748b' }}>{kgFmt.unit}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div className="ehb-ratio-flex">
|
||||
<div className="ehb-mini-bar-track">
|
||||
<div className="ehb-mini-bar-fill is-blue" style={{ width: `${Math.min(100, s.share * 100 * 2.5)}%` }} />
|
||||
</div>
|
||||
<span className="ehb-ratio-text">{(s.share * 100).toFixed(1)}%</span>
|
||||
</div>
|
||||
</td>
|
||||
<td style={{ textAlign: 'right' }} className="col-green-fee">
|
||||
¥{revFmt.value} <span style={{ fontSize: 11, fontWeight: 400 }}>{revFmt.unit}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div className="ehb-ratio-flex">
|
||||
<div className="ehb-mini-bar-track">
|
||||
<div className="ehb-mini-bar-fill is-green" style={{ width: `${Math.min(100, s.revenueShare * 100 * 5)}%` }} />
|
||||
</div>
|
||||
<span className="ehb-ratio-text">{(s.revenueShare * 100).toFixed(1)}%</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<OverviewDrillDialog
|
||||
payload={drill}
|
||||
onClose={() => { setDrill(null); setSelectedStationId(null); }}
|
||||
onPrimaryAction={selectedStationId && onSelectStation ? () => { const stationId = selectedStationId; setDrill(null); setSelectedStationId(null); onSelectStation(stationId); } : undefined}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function CustomerSummaryTable({
|
||||
customers,
|
||||
scope = 'global',
|
||||
scopeLabel,
|
||||
onDrillRequest,
|
||||
}: {
|
||||
customers: HydrogenCustomerRow[];
|
||||
scope?: OverviewScope;
|
||||
scopeLabel?: string | null;
|
||||
onDrillRequest?: (request: OverviewDrillRequest) => void;
|
||||
}) {
|
||||
const [sortKey, setSortKey] = useState<'name' | 'payer' | 'kg' | 'cost' | 'revenue'>('kg');
|
||||
const [sortDirection, setSortDirection] = useState<SortDirection>('desc');
|
||||
const [drill, setDrill] = useState<OverviewDrillPayload | null>(null);
|
||||
const sortedCustomers = useMemo(() => sortBy(customers, sortKey, sortDirection, (customer, key) => customer[key]), [customers, sortDirection, sortKey]);
|
||||
const changeSort = (nextKey: typeof sortKey) => {
|
||||
const next = toggleSort(sortKey, sortDirection, nextKey);
|
||||
setSortKey(next.key);
|
||||
setSortDirection(next.direction);
|
||||
};
|
||||
const openCustomer = (customer: HydrogenCustomerRow, index: number) => {
|
||||
if (onDrillRequest) onDrillRequest({ kind: 'customer', key: `${customer.name}-${index}`, label: customer.name });
|
||||
else setDrill(buildCustomerDrillPayload(customer));
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{customers.length > 0 && (
|
||||
<div className="ehb-sum-table-card">
|
||||
<div className="ehb-sum-table-card__head">
|
||||
<div className="ehb-sum-table-card__title">
|
||||
<Search size={14} className="text-sky-600" style={{ display: 'inline-block', verticalAlign: 'middle', marginRight: 6 }} />
|
||||
客户账单汇总{scope === 'station' && scopeLabel ? ` · ${scopeLabel}` : ''}
|
||||
<span className="ehb-title-sub ehb-hide-h5">
|
||||
(已收 / 未收:等待客户能源账户和对账单打通后获取)
|
||||
</span>
|
||||
<span className="ehb-title-sub ehb-show-h5">
|
||||
(已收未收打通中)
|
||||
</span>
|
||||
</div>
|
||||
<div className="ehb-sum-table-card__meta">
|
||||
Top {customers.length}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ehb-sum-table-wrap">
|
||||
<table className="ehb-sum-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="col-idx">#</th>
|
||||
<th style={{ textAlign: 'left' }}><SortableColumnHeader label="客户" sortKey="name" activeSortKey={sortKey} sortDirection={sortDirection} onSort={changeSort} /></th>
|
||||
<th style={{ textAlign: 'center' }}><SortableColumnHeader label="承担方" sortKey="payer" activeSortKey={sortKey} sortDirection={sortDirection} onSort={changeSort} align="center" /></th>
|
||||
<th style={{ textAlign: 'right' }}><SortableColumnHeader label="加氢量" sortKey="kg" activeSortKey={sortKey} sortDirection={sortDirection} onSort={changeSort} align="right" /></th>
|
||||
<th style={{ textAlign: 'right' }}><SortableColumnHeader label="成本支出" sortKey="cost" activeSortKey={sortKey} sortDirection={sortDirection} onSort={changeSort} align="right" /></th>
|
||||
<th style={{ textAlign: 'right' }}><SortableColumnHeader label="应收" sortKey="revenue" activeSortKey={sortKey} sortDirection={sortDirection} onSort={changeSort} align="right" /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sortedCustomers.map((c2, i) => {
|
||||
const kgFmt = fmtKg(c2.kg);
|
||||
const costFmt = fmtYuan(c2.cost);
|
||||
const revFmt = fmtYuan(c2.revenue);
|
||||
return (
|
||||
<tr key={c2.name + i} onClick={() => openCustomer(c2, i)} style={{ cursor: 'pointer' }} title="点击查看客户账单明细">
|
||||
<td className="col-idx">{i + 1}</td>
|
||||
<td style={{ fontWeight: 600, color: '#0284c7' }}>
|
||||
{c2.name} <span style={{ fontSize: 11, fontWeight: 400, opacity: 0.8 }}>钻取 ›</span>
|
||||
</td>
|
||||
<td style={{ textAlign: 'center' }}>
|
||||
{c2.payer === 'lingniu' ? (
|
||||
<span className="ehb-payer-tag is-own">羚牛</span>
|
||||
) : c2.payer === 'mixed' ? (
|
||||
<span className="ehb-payer-tag is-mix">混合</span>
|
||||
) : (
|
||||
<span className="ehb-payer-tag is-ext">客户</span>
|
||||
)}
|
||||
</td>
|
||||
<td style={{ textAlign: 'right' }} className="col-bold-kg">
|
||||
{kgFmt.value} <span style={{ fontSize: 11, fontWeight: 400, color: '#64748b' }}>{kgFmt.unit}</span>
|
||||
</td>
|
||||
<td style={{ textAlign: 'right' }} className="col-amber-fee">
|
||||
¥{costFmt.value} <span style={{ fontSize: 11, fontWeight: 400 }}>{costFmt.unit}</span>
|
||||
</td>
|
||||
<td style={{ textAlign: 'right' }} className="col-green-fee">
|
||||
¥{revFmt.value} <span style={{ fontSize: 11, fontWeight: 400 }}>{revFmt.unit}</span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<OverviewDrillDialog payload={drill} onClose={() => setDrill(null)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user