Improve pairing planning and statistics visibility
This commit is contained in:
@@ -295,7 +295,11 @@ function buildPipelineOption(rows) {
|
||||
axisPointer: { type: 'shadow' },
|
||||
formatter: (params) => {
|
||||
const item = params?.[0]?.data || {};
|
||||
return `${item.label}<br/>数量:${formatNumber(item.value)}<br/>比例:${formatPercent(item.rate)}`;
|
||||
const denominator = Number(item.denominator);
|
||||
const denominatorText = Number.isFinite(denominator) && denominator > 0
|
||||
? `<br/>适用总数:${formatNumber(denominator)}`
|
||||
: '';
|
||||
return `${item.label}<br/>数量:${formatNumber(item.value)}${denominatorText}<br/>比例:${formatPercent(item.rate)}`;
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
@@ -327,7 +331,13 @@ function buildPipelineOption(rows) {
|
||||
show: true,
|
||||
position: 'right',
|
||||
color: '#475569',
|
||||
formatter: (params) => `${formatNumber(params.data.value)} / ${formatPercent(params.data.rate)}`,
|
||||
formatter: (params) => {
|
||||
const denominator = Number(params.data.denominator);
|
||||
const totalText = Number.isFinite(denominator) && denominator > 0
|
||||
? `/${formatNumber(denominator)}`
|
||||
: '';
|
||||
return `${formatNumber(params.data.value)}${totalText} / ${formatPercent(params.data.rate)}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -743,6 +753,87 @@ function FamilyLegend({ rows }) {
|
||||
);
|
||||
}
|
||||
|
||||
function formatYmd(value) {
|
||||
const text = String(value || '').trim();
|
||||
if (/^\d{8}$/.test(text)) {
|
||||
return `${text.slice(0, 4)}-${text.slice(4, 6)}-${text.slice(6, 8)}`;
|
||||
}
|
||||
return text || '-';
|
||||
}
|
||||
|
||||
function MissingOrbitSummary({ summary }) {
|
||||
const affectedSceneCount = Number(summary?.affected_scene_count || 0);
|
||||
const missingDateCount = Number(summary?.missing_orbit_date_count || 0);
|
||||
const bySatellite = Array.isArray(summary?.by_satellite) ? summary.by_satellite : [];
|
||||
|
||||
if (!affectedSceneCount) {
|
||||
return (
|
||||
<div className="statistics-orbit-gap statistics-orbit-gap--ok">
|
||||
<strong>精轨缺口</strong>
|
||||
<span>当前没有开放的缺精轨场景。</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="statistics-orbit-gap">
|
||||
<div className="statistics-orbit-gap-head">
|
||||
<div>
|
||||
<strong>精轨缺口说明</strong>
|
||||
<span>缺轨按受影响影像景数统计;补齐时按卫星日期准备 LT-1 TXT 精轨。</span>
|
||||
</div>
|
||||
<div className="statistics-orbit-gap-metrics">
|
||||
<div>
|
||||
<span>受影响景数</span>
|
||||
<b>{formatNumber(affectedSceneCount)}</b>
|
||||
</div>
|
||||
<div>
|
||||
<span>缺失卫星日期</span>
|
||||
<b>{formatNumber(missingDateCount)}</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="statistics-orbit-gap-list">
|
||||
{bySatellite.map((item) => {
|
||||
const months = Array.isArray(item.months) ? item.months : [];
|
||||
const dates = Array.isArray(item.dates) ? item.dates : [];
|
||||
return (
|
||||
<div className="statistics-orbit-gap-satellite" key={item.satellite}>
|
||||
<div className="statistics-orbit-gap-satellite-head">
|
||||
<strong>{item.satellite || 'UNKNOWN'}</strong>
|
||||
<span>
|
||||
{formatNumber(item.affected_scene_count)} 景 / {formatNumber(item.missing_orbit_date_count)} 个日期
|
||||
{item.first_missing_date && item.last_missing_date
|
||||
? ` / ${formatYmd(item.first_missing_date)} 至 ${formatYmd(item.last_missing_date)}`
|
||||
: ''}
|
||||
</span>
|
||||
</div>
|
||||
<div className="statistics-orbit-month-grid">
|
||||
{months.map((month) => (
|
||||
<span key={`${item.satellite}-${month.month}`}>
|
||||
{month.month}
|
||||
<b>{formatNumber(month.affected_scene_count)}景</b>
|
||||
<em>{formatNumber(month.missing_orbit_date_count)}日</em>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="statistics-orbit-date-row" aria-label={`${item.satellite} 缺失精轨日期`}>
|
||||
{dates.map((date) => (
|
||||
<span key={`${item.satellite}-${date.date}`} title={`${formatNumber(date.affected_scene_count)} 景受影响`}>
|
||||
{formatYmd(date.date)}
|
||||
<b>{formatNumber(date.affected_scene_count)}</b>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProductionRunList({ rows }) {
|
||||
const data = Array.isArray(rows) ? rows : [];
|
||||
if (!data.length) {
|
||||
@@ -792,11 +883,17 @@ export default function StatisticsDashboard() {
|
||||
const [lastLoadedAt, setLastLoadedAt] = useState(null);
|
||||
const [coverageMode, setCoverageMode] = useState('source');
|
||||
|
||||
const loadDashboard = async () => {
|
||||
const loadDashboard = async ({ fresh = false } = {}) => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const payload = await getStatisticsDashboard();
|
||||
let payload;
|
||||
try {
|
||||
payload = await getStatisticsDashboard(fresh);
|
||||
} catch (err) {
|
||||
if (!fresh) throw err;
|
||||
payload = await getStatisticsDashboard(false);
|
||||
}
|
||||
setData(payload);
|
||||
setLastLoadedAt(new Date());
|
||||
} catch (err) {
|
||||
@@ -875,7 +972,7 @@ export default function StatisticsDashboard() {
|
||||
{data.cache_meta.ttl_seconds ? ` · ${data.cache_meta.ttl_seconds}秒缓存` : ''}
|
||||
</span>
|
||||
)}
|
||||
<button type="button" onClick={loadDashboard} disabled={loading}>
|
||||
<button type="button" onClick={() => loadDashboard({ fresh: true })} disabled={loading}>
|
||||
{loading ? '刷新中' : '刷新'}
|
||||
</button>
|
||||
</div>
|
||||
@@ -948,29 +1045,38 @@ export default function StatisticsDashboard() {
|
||||
<Chart option={sourceTrendOption} className="statistics-chart-md" />
|
||||
</Section>
|
||||
|
||||
<Section title="精轨保障" subtitle={`${formatNumber(data?.orbit?.selected_bindings)} 景已选中`}>
|
||||
<div className="statistics-orbit-summary">
|
||||
<div>
|
||||
<span>精轨资产</span>
|
||||
<strong>{formatNumber(data?.orbit?.orbit_total)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>绑定覆盖率</span>
|
||||
<strong>{formatPercent(data?.orbit?.selected_rate)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>匹配记录</span>
|
||||
<strong>{formatNumber(data?.orbit?.matched_bindings)}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="statistics-mini-bars">
|
||||
{(data?.orbit?.orbit_by_family || []).map((item) => (
|
||||
<div key={item.family}>
|
||||
<span>{item.family}</span>
|
||||
<b>{formatNumber(item.count)}</b>
|
||||
<i style={{ width: `${Math.min(100, Math.max(2, item.ok_rate * 100))}%` }} />
|
||||
<Section
|
||||
title="精轨保障"
|
||||
subtitle={`${formatNumber(data?.orbit?.selected_bindings)} 景已选中,${formatNumber(data?.orbit?.missing_summary?.affected_scene_count || data?.orbit?.missing_scene_count)} 景待补`}
|
||||
className="statistics-section--orbit"
|
||||
>
|
||||
<div className="statistics-orbit-layout">
|
||||
<div className="statistics-orbit-overview">
|
||||
<div className="statistics-orbit-summary">
|
||||
<div>
|
||||
<span>精轨资产</span>
|
||||
<strong>{formatNumber(data?.orbit?.orbit_total)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>绑定覆盖率</span>
|
||||
<strong>{formatPercent(data?.orbit?.selected_rate)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>匹配记录</span>
|
||||
<strong>{formatNumber(data?.orbit?.matched_bindings)}</strong>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="statistics-mini-bars">
|
||||
{(data?.orbit?.orbit_by_family || []).map((item) => (
|
||||
<div key={item.family}>
|
||||
<span>{item.family}</span>
|
||||
<b>{formatNumber(item.count)}</b>
|
||||
<i style={{ width: `${Math.min(100, Math.max(2, item.ok_rate * 100))}%` }} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<MissingOrbitSummary summary={data?.orbit?.missing_summary} />
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user