feat(client): show digital sync failure details

This commit is contained in:
baiyanyun
2026-06-07 01:05:49 +08:00
parent ac1a6fd0e6
commit 0672e82fc7
10 changed files with 280 additions and 126 deletions

View File

@@ -61,6 +61,19 @@ interface ManagementSyncStatus {
failed: number;
lastSyncAt: string | null;
lastSyncError: string | null;
recentFailures?: ManagementSyncFailure[];
}
interface ManagementSyncFailure {
id: string;
entityType: string;
entityId: string;
operation: string;
attempts: number;
lastAttemptAt: string | null;
error: string | null;
createdAt: string;
updatedAt: string;
}
// 技术侧 PlanTemplate 是任务模板来源;用户侧只展示为“任务”,运行时 Task/PlanStep 展示为“步骤”。
@@ -448,6 +461,31 @@ function syncStatusSummary(status: ManagementSyncStatus | null): string {
: '等待本地 outbox 数据。';
}
function entityTypeLabel(entityType: string): string {
const labels: Record<string, string> = {
plan: 'Plan',
task: 'Task',
run: 'Run',
event: 'Event',
artifact: 'Artifact',
approval: 'Approval',
};
return labels[entityType] || entityType || 'Entity';
}
function syncFailureSummary(failure: ManagementSyncFailure): string {
const error = summarizeSyncError(failure.error);
return [
`${entityTypeLabel(failure.entityType)} / ${failure.operation}`,
`重试 ${failure.attempts}`,
error,
].filter(Boolean).join(' · ');
}
function syncFailureTime(failure: ManagementSyncFailure): string {
return formatBeijingDateTime(failure.lastAttemptAt || failure.updatedAt || failure.createdAt) || '未尝试';
}
async function readManagementSyncStatus(): Promise<ManagementSyncStatus | null> {
const bridge = window.QimingClawBridge?.digital;
if (!bridge?.getSyncStatus) return null;
@@ -824,6 +862,7 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
const managementSyncLabel = syncStatusLabel(managementSyncStatus);
const managementSyncSummary = syncStatusSummary(managementSyncStatus);
const managementSyncError = summarizeSyncError(managementSyncStatus?.lastSyncError);
const managementSyncFailures = managementSyncStatus?.recentFailures ?? [];
const resolvedVisualState = resolveEmployeeVisualState({
confirmed: Boolean(workday?.confirmed),
currentEvent,
@@ -1332,6 +1371,19 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
<span>{managementSyncSummary}</span>
</div>
{managementSyncFailures.length > 0 && (
<div className="de-management-sync-failures" aria-label="同步失败明细">
{managementSyncFailures.map((failure) => (
<div key={failure.id} className="de-management-sync-failure" title={failure.error || failure.id}>
<span>{syncFailureTime(failure)}</span>
<strong>{entityTypeLabel(failure.entityType)}</strong>
<em>{failure.id}</em>
<small>{syncFailureSummary(failure)}</small>
</div>
))}
</div>
)}
{(actionNotice || actionError || planDataError || workdayProjection?.demo_reason) && (
<div className={`de-schedule-result ${actionError ? 'de-schedule-result--error' : 'de-schedule-result--success'}`} style={{ marginBottom: 10 }}>
{actionError || actionNotice || planDataError || workdayProjection?.demo_reason}